aboutsummaryrefslogtreecommitdiff
path: root/mp/src/game/server/episodic
diff options
context:
space:
mode:
authorNarendra Umate <[email protected]>2013-12-02 23:36:05 -0800
committerNarendra Umate <[email protected]>2013-12-02 23:36:05 -0800
commit8737f191f3b59f001a77bf6c08091109211c1c9f (patch)
treedbbf05c004d9b026f2c1f23f06600fe0add82c36 /mp/src/game/server/episodic
parentUpdate .gitignore. (diff)
parentMake .xcconfigs text files too. (diff)
downloadsource-sdk-2013-8737f191f3b59f001a77bf6c08091109211c1c9f.tar.xz
source-sdk-2013-8737f191f3b59f001a77bf6c08091109211c1c9f.zip
Merge remote-tracking branch 'upstream/master'
Diffstat (limited to 'mp/src/game/server/episodic')
-rw-r--r--mp/src/game/server/episodic/ai_behavior_alyx_injured.cpp1242
-rw-r--r--mp/src/game/server/episodic/ai_behavior_alyx_injured.h190
-rw-r--r--mp/src/game/server/episodic/ai_behavior_passenger_companion.cpp4106
-rw-r--r--mp/src/game/server/episodic/ai_behavior_passenger_companion.h336
-rw-r--r--mp/src/game/server/episodic/ai_behavior_passenger_zombie.cpp1756
-rw-r--r--mp/src/game/server/episodic/ai_behavior_passenger_zombie.h194
-rw-r--r--mp/src/game/server/episodic/ep1_gamestats.cpp148
-rw-r--r--mp/src/game/server/episodic/ep1_gamestats.h62
-rw-r--r--mp/src/game/server/episodic/ep2_gamestats.cpp1168
-rw-r--r--mp/src/game/server/episodic/ep2_gamestats.h1064
-rw-r--r--mp/src/game/server/episodic/grenade_hopwire.cpp1162
-rw-r--r--mp/src/game/server/episodic/grenade_hopwire.h92
-rw-r--r--mp/src/game/server/episodic/npc_advisor.cpp4102
-rw-r--r--mp/src/game/server/episodic/npc_combine_cannon.cpp2670
-rw-r--r--mp/src/game/server/episodic/npc_hunter.cpp15528
-rw-r--r--mp/src/game/server/episodic/npc_hunter.h50
-rw-r--r--mp/src/game/server/episodic/npc_magnusson.cpp254
-rw-r--r--mp/src/game/server/episodic/npc_puppet.cpp242
-rw-r--r--mp/src/game/server/episodic/prop_scalable.cpp298
-rw-r--r--mp/src/game/server/episodic/vehicle_jeep_episodic.cpp3528
-rw-r--r--mp/src/game/server/episodic/vehicle_jeep_episodic.h300
-rw-r--r--mp/src/game/server/episodic/weapon_hopwire.cpp1016
-rw-r--r--mp/src/game/server/episodic/weapon_oldmanharpoon.cpp72
-rw-r--r--mp/src/game/server/episodic/weapon_striderbuster.cpp2350
-rw-r--r--mp/src/game/server/episodic/weapon_striderbuster.h40
25 files changed, 20985 insertions, 20985 deletions
diff --git a/mp/src/game/server/episodic/ai_behavior_alyx_injured.cpp b/mp/src/game/server/episodic/ai_behavior_alyx_injured.cpp
index a63e7616..a762587d 100644
--- a/mp/src/game/server/episodic/ai_behavior_alyx_injured.cpp
+++ b/mp/src/game/server/episodic/ai_behavior_alyx_injured.cpp
@@ -1,621 +1,621 @@
-//========= Copyright Valve Corporation, All rights reserved. ============//
-//
-// Purpose: FIXME: This will ultimately become a more generic implementation
-//
-//=============================================================================
-
-#include "cbase.h"
-#include "ai_memory.h"
-#include "ai_speech.h"
-#include "ai_behavior.h"
-#include "ai_navigator.h"
-#include "ai_playerally.h"
-#include "ai_behavior_follow.h"
-#include "ai_moveprobe.h"
-
-#include "ai_behavior_alyx_injured.h"
-
-ConVar g_debug_injured_follow( "g_debug_injured_follow", "0" );
-ConVar injured_help_plee_range( "injured_help_plee_range", "256" );
-
-#define TLK_INJURED_FOLLOW_TOO_FAR "TLK_INJURED_FOLLOW_TOO_FAR"
-
-BEGIN_DATADESC( CAI_BehaviorAlyxInjured )
- DEFINE_FIELD( m_flNextWarnTime, FIELD_TIME ),
- // m_ActivityMap
-
-END_DATADESC();
-
-Activity ACT_INJURED_COWER;
-Activity ACT_GESTURE_INJURED_COWER_FLINCH;
-
-#define COVER_DISTANCE 128.0f // Distance behind target to find cover
-#define MIN_ENEMY_MOB 3 // Number of enemies considerd overwhelming
-#define MAX_DIST_FROM_FOLLOW_TARGET 256 // If the follow target is farther than this, the NPC will run to it
-
-//=============================================================================
-
-CAI_BehaviorAlyxInjured::CAI_BehaviorAlyxInjured( void ) : m_flNextWarnTime( 0.0f )
-{
- SetDefLessFunc( m_ActivityMap );
-}
-
-struct ActivityMap_t
-{
- Activity activity;
- Activity translation;
-};
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CAI_BehaviorAlyxInjured::PopulateActivityMap( void )
-{
- // Maps one activity to a translated one
- ActivityMap_t map[] =
- {
- // Runs
- { ACT_RUN, ACT_RUN_HURT },
- { ACT_RUN_AIM, ACT_RUN_AIM }, // FIMXE: No appropriate temp anim right now!
- { ACT_RUN_CROUCH, ACT_RUN_HURT },
- { ACT_RUN_CROUCH_AIM, ACT_RUN_HURT },
- { ACT_RUN_PROTECTED, ACT_RUN_HURT },
- { ACT_RUN_RELAXED, ACT_RUN_HURT },
- { ACT_RUN_STIMULATED, ACT_RUN_HURT },
- { ACT_RUN_AGITATED, ACT_RUN_HURT },
- { ACT_RUN_AIM_RELAXED, ACT_RUN_AIM_RELAXED }, // FIMXE: No appropriate temp anim right now!
- { ACT_RUN_AIM_STIMULATED, ACT_RUN_AIM_STIMULATED }, // FIMXE: No appropriate temp anim right now!
- { ACT_RUN_AIM_AGITATED, ACT_RUN_AIM_AGITATED }, // FIMXE: No appropriate temp anim right now!
- { ACT_RUN_HURT, ACT_RUN_HURT },
-
- // Walks
- { ACT_WALK, ACT_WALK_HURT },
- { ACT_WALK_AIM, ACT_WALK_HURT },
- { ACT_WALK_CROUCH, ACT_WALK_HURT },
- { ACT_WALK_CROUCH_AIM, ACT_WALK_HURT },
- { ACT_WALK_RELAXED, ACT_WALK_HURT },
- { ACT_WALK_STIMULATED, ACT_WALK_HURT },
- { ACT_WALK_AGITATED, ACT_WALK_HURT },
- { ACT_WALK_AIM_RELAXED, ACT_WALK_HURT },
- { ACT_WALK_AIM_STIMULATED, ACT_WALK_HURT },
- { ACT_WALK_AIM_AGITATED, ACT_WALK_HURT },
- { ACT_WALK_HURT, ACT_WALK_HURT },
-
- { ACT_IDLE, ACT_IDLE_HURT },
- { ACT_COVER_LOW, ACT_INJURED_COWER },
- { ACT_COWER, ACT_INJURED_COWER },
- };
-
- // Clear the map
- m_ActivityMap.RemoveAll();
-
- // Add all translations
- for ( int i = 0; i < ARRAYSIZE( map ); i++ )
- {
- Assert( m_ActivityMap.Find( map[i].activity ) == m_ActivityMap.InvalidIndex() );
- m_ActivityMap.Insert( map[i].activity, map[i].translation );
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Populate the list after save/load
-//-----------------------------------------------------------------------------
-void CAI_BehaviorAlyxInjured::OnRestore( void )
-{
- PopulateActivityMap();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Populate the list on spawn
-//-----------------------------------------------------------------------------
-void CAI_BehaviorAlyxInjured::Spawn( void )
-{
- PopulateActivityMap();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Get the flinch activity for us to play
-// Input : bHeavyDamage -
-// bGesture -
-// Output : Activity
-//-----------------------------------------------------------------------------
-Activity CAI_BehaviorAlyxInjured::GetFlinchActivity( bool bHeavyDamage, bool bGesture )
-{
- //
- if ( ( bGesture == false ) || ( GetOuter()->GetActivity() != ACT_COWER ) )
- return BaseClass::GetFlinchActivity( bHeavyDamage, bGesture );
-
- // Translate the flinch if we're cowering
- return ACT_GESTURE_INJURED_COWER_FLINCH;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : nActivity -
-//-----------------------------------------------------------------------------
-Activity CAI_BehaviorAlyxInjured::NPC_TranslateActivity( Activity nActivity )
-{
- // Find out what the base class wants to do with the activity
- Activity nNewActivity = BaseClass::NPC_TranslateActivity( nActivity );
-
- // Look it up in the translation map
- int nIndex = m_ActivityMap.Find( nNewActivity );
-
- if ( m_ActivityMap.IsValidIndex( nIndex ) )
- return m_ActivityMap[nIndex];
-
- return nNewActivity;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Determines if Alyx should run away from enemies or stay put
-// Output : Returns true on success, false on failure.
-//-----------------------------------------------------------------------------
-bool CAI_BehaviorAlyxInjured::ShouldRunToCover( void )
-{
- Vector vecRetreatPos;
- float flRetreatRadius = 128.0f;
-
- // See how far off from our cover position we are
- if ( FindCoverFromEnemyBehindTarget( GetFollowTarget(), flRetreatRadius, &vecRetreatPos ) )
- {
- float flDestDistSqr = ( GetOuter()->WorldSpaceCenter() - vecRetreatPos ).LengthSqr();
- if ( flDestDistSqr > Square( flRetreatRadius ) )
- return true;
- }
-
- return false;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: See if we need to follow our goal
-// Output : Returns true on success, false on failure.
-//-----------------------------------------------------------------------------
-bool CAI_BehaviorAlyxInjured::ShouldRunToFollowGoal( void )
-{
- // If we're too far from our follow target, we need to chase after them
- float flDistToFollowGoalSqr = ( GetOuter()->GetAbsOrigin() - GetFollowTarget()->GetAbsOrigin() ).LengthSqr();
- if ( flDistToFollowGoalSqr > Square(MAX_DIST_FROM_FOLLOW_TARGET) )
- return true;
-
- return false;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Translate base schedules into overridden forms
-//-----------------------------------------------------------------------------
-int CAI_BehaviorAlyxInjured::TranslateSchedule( int scheduleType )
-{
- switch( scheduleType )
- {
- case SCHED_RUN_FROM_ENEMY:
- case SCHED_RUN_FROM_ENEMY_MOB:
- {
- // Get under cover if we're able to
- if ( ShouldRunToCover() )
- return SCHED_INJURED_RUN_FROM_ENEMY;
-
- // Run to our follow goal if we're too far away from it
- if ( ShouldRunToFollowGoal() )
- return SCHED_FOLLOW;
-
- // Cower if surrounded
- if ( HasCondition( COND_INJURED_OVERWHELMED ) )
- return SCHED_INJURED_COWER;
-
- // Face our enemies
- return SCHED_INJURED_FEAR_FACE;
- }
- break;
-
- case SCHED_RUN_FROM_ENEMY_FALLBACK:
- return SCHED_INJURED_COWER;
- break;
- }
-
- return BaseClass::TranslateSchedule( scheduleType );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Pick up failure cases and handle them
-//-----------------------------------------------------------------------------
-int CAI_BehaviorAlyxInjured::SelectFailSchedule( int failedSchedule, int failedTask, AI_TaskFailureCode_t taskFailCode )
-{
- // Failed schedules
- switch( failedSchedule )
- {
- case SCHED_RUN_FROM_ENEMY:
- case SCHED_RUN_FROM_ENEMY_MOB:
- case SCHED_FOLLOW:
- return SCHED_INJURED_COWER;
- }
-
- // Failed tasks
- switch( failedTask )
- {
- case TASK_FIND_COVER_FROM_ENEMY:
- case TASK_FIND_INJURED_COVER_FROM_ENEMY:
-
- // Only cower if we're already near enough to our follow target
- float flDistToFollowTargetSqr = ( GetOuter()->GetAbsOrigin() - GetFollowTarget()->GetAbsOrigin() ).LengthSqr();
- if ( flDistToFollowTargetSqr > Square( 256 ) )
- return SCHED_FOLLOW;
-
- return SCHED_INJURED_COWER;
- break;
- }
-
- return BaseClass::SelectFailSchedule( failedSchedule, failedTask, taskFailCode );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Find the general direction enemies are coming towards us at
-//-----------------------------------------------------------------------------
-bool CAI_BehaviorAlyxInjured::FindThreatDirection2D( const Vector &vecSource, Vector *vecOut )
-{
- // Find the general direction our threat is coming from
- bool bValid = false;
- Vector vecScratch;
- AIEnemiesIter_t iter;
-
- // Iterate through all known enemies
- for( AI_EnemyInfo_t *pMemory = GetOuter()->GetEnemies()->GetFirst(&iter); pMemory != NULL; pMemory = GetOuter()->GetEnemies()->GetNext(&iter) )
- {
- if ( pMemory == NULL || pMemory->hEnemy == NULL )
- continue;
-
- vecScratch = ( vecSource - pMemory->hEnemy->WorldSpaceCenter() );
- VectorNormalize( vecScratch );
-
- (*vecOut) += vecScratch;
- bValid = true;
- }
-
- // Find the general direction
- (*vecOut).z = 0.0f;
- VectorNormalize( (*vecOut) );
- return bValid;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Find a position that hides us from our threats while interposing the
-// target entity between us and the threat
-// Input : pTarget - entity to hide behind
-// flRadius - Radius around the target to search
-// *vecOut - position
-//-----------------------------------------------------------------------------
-bool CAI_BehaviorAlyxInjured::FindCoverFromEnemyBehindTarget( CBaseEntity *pTarget, float flRadius, Vector *vecOut )
-{
- if ( pTarget == NULL )
- return false;
-
- Vector vecTargetPos = pTarget->GetAbsOrigin();
- Vector vecThreatDir = vec3_origin;
-
- // Find our threat direction and base our cover on that
- if ( FindThreatDirection2D( vecTargetPos, &vecThreatDir ) )
- {
- // Get a general location for taking cover
- Vector vecTestPos = vecTargetPos + ( vecThreatDir * flRadius );
-
- if ( g_debug_injured_follow.GetBool() )
- {
- NDebugOverlay::HorzArrow( GetOuter()->GetAbsOrigin(), vecTestPos, 8.0f, 255, 255, 0, 32, true, 2.0f );
- }
-
- // Make sure we never move towards our threat to get to cover!
- Vector vecMoveDir = GetOuter()->GetAbsOrigin() - vecTestPos;
- VectorNormalize( vecMoveDir );
- float flDotToCover = DotProduct( vecMoveDir, vecThreatDir );
- if ( flDotToCover > 0.0f )
- {
- if ( g_debug_injured_follow.GetBool() )
- {
- NDebugOverlay::HorzArrow( GetOuter()->GetAbsOrigin(), vecTestPos, 8.0f, 255, 0, 0, 32, true, 2.0f );
- }
-
- return false;
- }
-
- AIMoveTrace_t moveTrace;
- GetOuter()->GetMoveProbe()->MoveLimit( NAV_GROUND,
- GetOuter()->GetAbsOrigin(),
- vecTestPos,
- MASK_SOLID_BRUSHONLY,
- NULL,
- 0,
- &moveTrace );
-
- bool bWithinRangeToGoal = ( moveTrace.vEndPosition - vecTestPos ).Length2DSqr() < Square( GetOuter()->GetHullWidth() * 3.0f );
- bool bCanStandAtGoal = GetOuter()->GetMoveProbe()->CheckStandPosition( moveTrace.vEndPosition, MASK_SOLID_BRUSHONLY );
-
- if ( bWithinRangeToGoal == false || bCanStandAtGoal == false )
- {
- if ( g_debug_injured_follow.GetBool() )
- {
- NDebugOverlay::SweptBox( GetOuter()->GetAbsOrigin(), vecTestPos, GetOuter()->GetHullMins(), GetOuter()->GetHullMaxs(), vec3_angle, 255, 0, 0, 0, 2.0f );
- }
-
- return false;
- }
-
- // Accept it
- *vecOut = moveTrace.vEndPosition;
-
- if ( g_debug_injured_follow.GetBool() )
- {
- NDebugOverlay::SweptBox( GetOuter()->GetAbsOrigin(), (*vecOut), GetOuter()->GetHullMins(), GetOuter()->GetHullMaxs(), vec3_angle, 0, 255, 0, 0, 2.0f );
- }
-
- return true;
- }
-
- return false;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : *pTask -
-//-----------------------------------------------------------------------------
-void CAI_BehaviorAlyxInjured::StartTask( const Task_t *pTask )
-{
- switch( pTask->iTask )
- {
- case TASK_FIND_COVER_FROM_ENEMY:
- {
- CBaseEntity *pLeader = GetFollowTarget();
- if ( !pLeader )
- {
- BaseClass::StartTask( pTask );
- break;
- }
-
- // Find a position behind our follow target
- Vector coverPos = vec3_invalid;
- if ( FindCoverFromEnemyBehindTarget( pLeader, COVER_DISTANCE, &coverPos ) )
- {
- AI_NavGoal_t goal( GOALTYPE_LOCATION, coverPos, ACT_RUN, AIN_HULL_TOLERANCE, AIN_DEF_FLAGS );
- GetOuter()->GetNavigator()->SetGoal( goal );
- GetOuter()->m_flMoveWaitFinished = gpGlobals->curtime + pTask->flTaskData;
- TaskComplete();
- return;
- }
-
- // Couldn't find anything
- TaskFail( FAIL_NO_COVER );
- break;
- }
-
- default:
- BaseClass::StartTask( pTask );
- break;
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Whether or not Alyx is injured
-//-----------------------------------------------------------------------------
-bool CAI_BehaviorAlyxInjured::IsInjured( void ) const
-{
- return IsAlyxInInjuredMode();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CAI_BehaviorAlyxInjured::GatherConditions( void )
-{
- BaseClass::GatherConditions();
-
- // Always stomp over this
- ClearCondition( COND_INJURED_TOO_FAR_FROM_PLAYER );
- ClearCondition( COND_INJURED_OVERWHELMED );
-
- // See if we're overwhelmed by foes
- if ( NumKnownEnemiesInRadius( GetOuter()->GetAbsOrigin(), COVER_DISTANCE ) >= MIN_ENEMY_MOB )
- {
- SetCondition( COND_INJURED_OVERWHELMED );
- }
-
- // Determines whether we consider ourselves in danger
- bool bInDanger = ( HasCondition( COND_LIGHT_DAMAGE ) ||
- HasCondition( COND_HEAVY_DAMAGE ) ||
- HasCondition( COND_INJURED_OVERWHELMED ) );
-
- // See if we're too far away from the player and in danger
- if ( AI_IsSinglePlayer() && bInDanger )
- {
- bool bWarnPlayer = false;
-
- // This only works in single-player
- CBasePlayer *pPlayer = UTIL_PlayerByIndex( 1 );
- if ( pPlayer != NULL )
- {
- // FIXME: This distance may need to be the length of the shortest walked path between the follower and the target
-
- // Get our approximate distance to the player
- float flDistToPlayer = UTIL_DistApprox2D( GetOuter()->GetAbsOrigin(), pPlayer->GetAbsOrigin() );
- if ( flDistToPlayer > injured_help_plee_range.GetFloat() )
- {
- bWarnPlayer = true;
- }
- else if ( flDistToPlayer > (injured_help_plee_range.GetFloat()*0.5f) && HasCondition( COND_SEE_PLAYER ) == false )
- {
- // Cut our distance in half if we can't see the player
- bWarnPlayer = true;
- }
- }
-
- // Yell for help!
- if ( bWarnPlayer )
- {
- // FIXME: This should be routed through the normal speaking code with a system to emit from the player's suit.
-
- CBasePlayer *pPlayer = UTIL_PlayerByIndex( 1 );
- //float flPlayerDistSqr = ( GetOuter()->GetAbsOrigin() - pPlayer->GetAbsOrigin() ).LengthSqr();
-
- // If the player is too far away or we can't see him
- //if ( HasCondition( COND_SEE_PLAYER ) == false || flPlayerDistSqr > Square( 128 ) )
- {
- if ( m_flNextWarnTime < gpGlobals->curtime )
- {
- pPlayer->EmitSound( "npc_alyx.injured_too_far" );
- m_flNextWarnTime = gpGlobals->curtime + random->RandomFloat( 3.0f, 5.0f );
- }
- }
- /*
- else
- {
- SpeakIfAllowed( TLK_INJURED_FOLLOW_TOO_FAR );
- m_flNextWarnTime = gpGlobals->curtime + random->RandomFloat( 3.0f, 5.0f );
- }
- */
-
- SetCondition( COND_INJURED_TOO_FAR_FROM_PLAYER );
- }
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Speak a concept if we're able to
-//-----------------------------------------------------------------------------
-void CAI_BehaviorAlyxInjured::SpeakIfAllowed( AIConcept_t concept )
-{
- CAI_Expresser *pExpresser = GetOuter()->GetExpresser();
- if ( pExpresser == NULL )
- return;
-
- // Must be able to speak the concept
- if ( pExpresser->CanSpeakConcept( concept ) )
- {
- pExpresser->Speak( concept );
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Get the number of known enemies within a radius to a point
-//-----------------------------------------------------------------------------
-int CAI_BehaviorAlyxInjured::NumKnownEnemiesInRadius( const Vector &vecSource, float flRadius )
-{
- int nNumEnemies = 0;
- float flRadiusSqr = Square( flRadius );
-
- AIEnemiesIter_t iter;
-
- // Iterate through all known enemies
- for( AI_EnemyInfo_t *pMemory = GetEnemies()->GetFirst(&iter); pMemory != NULL; pMemory = GetEnemies()->GetNext(&iter) )
- {
- if ( pMemory == NULL || pMemory->hEnemy == NULL )
- continue;
-
- // Must hate or fear them
- if ( GetOuter()->IRelationType( pMemory->hEnemy ) != D_HT && GetOuter()->IRelationType( pMemory->hEnemy ) != D_FR )
- continue;
-
- // Count only the enemies I've seen recently
- if ( gpGlobals->curtime - pMemory->timeLastSeen > 0.5f )
- continue;
-
- // Must be within the radius we've specified
- float flEnemyDistSqr = ( vecSource - pMemory->hEnemy->GetAbsOrigin() ).Length2DSqr();
- if ( flEnemyDistSqr < flRadiusSqr )
- {
- nNumEnemies++;
- }
- }
-
- return nNumEnemies;
-}
-
-// ----------------------------------------------
-// Custom AI declarations
-// ----------------------------------------------
-
-AI_BEGIN_CUSTOM_SCHEDULE_PROVIDER( CAI_BehaviorAlyxInjured )
-{
- DECLARE_ACTIVITY( ACT_GESTURE_INJURED_COWER_FLINCH )
- DECLARE_ACTIVITY( ACT_INJURED_COWER )
-
- DECLARE_CONDITION( COND_INJURED_TOO_FAR_FROM_PLAYER )
- DECLARE_CONDITION( COND_INJURED_OVERWHELMED )
-
- DECLARE_TASK( TASK_FIND_INJURED_COVER_FROM_ENEMY )
-
- DEFINE_SCHEDULE
- (
- SCHED_INJURED_COWER,
-
- " Tasks"
- // TOOD: Announce cower
- " TASK_PLAY_SEQUENCE ACTIVITY:ACT_COWER"
- " TASK_WAIT 2"
- ""
- " Interrupts"
- " COND_GIVE_WAY"
- " COND_PLAYER_PUSHING"
- )
-
- DEFINE_SCHEDULE
- (
- SCHED_INJURED_FEAR_FACE,
-
- " Tasks"
- " TASK_STOP_MOVING 0"
- " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE" // FIXME: Scared idle?
- " TASK_FACE_ENEMY 0"
- ""
- " Interrupts"
- " COND_GIVE_WAY"
- " COND_PLAYER_PUSHING"
- );
-
- DEFINE_SCHEDULE
- (
- SCHED_INJURED_RUN_FROM_ENEMY,
-
- " Tasks"
- " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_INJURED_COWER"
- " TASK_STOP_MOVING 0"
- " TASK_FIND_COVER_FROM_ENEMY 0"
- " TASK_RUN_PATH 0"
- " TASK_WAIT_FOR_MOVEMENT 0"
- ""
- " Interrupts"
- );
-
- AI_END_CUSTOM_SCHEDULE_PROVIDER()
-}
-
-//-----------------------------------------------------------------------------
-// CAI_InjuredFollowGoal
-//-----------------------------------------------------------------------------
-
-BEGIN_DATADESC( CAI_InjuredFollowGoal )
-END_DATADESC()
-
-LINK_ENTITY_TO_CLASS( ai_goal_injured_follow, CAI_InjuredFollowGoal );
-
-//-------------------------------------
-
-void CAI_InjuredFollowGoal::EnableGoal( CAI_BaseNPC *pAI )
-{
- CAI_BehaviorAlyxInjured *pBehavior;
- if ( !pAI->GetBehavior( &pBehavior ) )
- return;
-
- if ( GetGoalEntity() == NULL )
- return;
-
- pBehavior->SetFollowGoal( this );
-}
-
-//-------------------------------------
-
-void CAI_InjuredFollowGoal::DisableGoal( CAI_BaseNPC *pAI )
-{
- CAI_BehaviorAlyxInjured *pBehavior;
- if ( !pAI->GetBehavior( &pBehavior ) )
- return;
-
- pBehavior->ClearFollowGoal( this );
-}
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: FIXME: This will ultimately become a more generic implementation
+//
+//=============================================================================
+
+#include "cbase.h"
+#include "ai_memory.h"
+#include "ai_speech.h"
+#include "ai_behavior.h"
+#include "ai_navigator.h"
+#include "ai_playerally.h"
+#include "ai_behavior_follow.h"
+#include "ai_moveprobe.h"
+
+#include "ai_behavior_alyx_injured.h"
+
+ConVar g_debug_injured_follow( "g_debug_injured_follow", "0" );
+ConVar injured_help_plee_range( "injured_help_plee_range", "256" );
+
+#define TLK_INJURED_FOLLOW_TOO_FAR "TLK_INJURED_FOLLOW_TOO_FAR"
+
+BEGIN_DATADESC( CAI_BehaviorAlyxInjured )
+ DEFINE_FIELD( m_flNextWarnTime, FIELD_TIME ),
+ // m_ActivityMap
+
+END_DATADESC();
+
+Activity ACT_INJURED_COWER;
+Activity ACT_GESTURE_INJURED_COWER_FLINCH;
+
+#define COVER_DISTANCE 128.0f // Distance behind target to find cover
+#define MIN_ENEMY_MOB 3 // Number of enemies considerd overwhelming
+#define MAX_DIST_FROM_FOLLOW_TARGET 256 // If the follow target is farther than this, the NPC will run to it
+
+//=============================================================================
+
+CAI_BehaviorAlyxInjured::CAI_BehaviorAlyxInjured( void ) : m_flNextWarnTime( 0.0f )
+{
+ SetDefLessFunc( m_ActivityMap );
+}
+
+struct ActivityMap_t
+{
+ Activity activity;
+ Activity translation;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CAI_BehaviorAlyxInjured::PopulateActivityMap( void )
+{
+ // Maps one activity to a translated one
+ ActivityMap_t map[] =
+ {
+ // Runs
+ { ACT_RUN, ACT_RUN_HURT },
+ { ACT_RUN_AIM, ACT_RUN_AIM }, // FIMXE: No appropriate temp anim right now!
+ { ACT_RUN_CROUCH, ACT_RUN_HURT },
+ { ACT_RUN_CROUCH_AIM, ACT_RUN_HURT },
+ { ACT_RUN_PROTECTED, ACT_RUN_HURT },
+ { ACT_RUN_RELAXED, ACT_RUN_HURT },
+ { ACT_RUN_STIMULATED, ACT_RUN_HURT },
+ { ACT_RUN_AGITATED, ACT_RUN_HURT },
+ { ACT_RUN_AIM_RELAXED, ACT_RUN_AIM_RELAXED }, // FIMXE: No appropriate temp anim right now!
+ { ACT_RUN_AIM_STIMULATED, ACT_RUN_AIM_STIMULATED }, // FIMXE: No appropriate temp anim right now!
+ { ACT_RUN_AIM_AGITATED, ACT_RUN_AIM_AGITATED }, // FIMXE: No appropriate temp anim right now!
+ { ACT_RUN_HURT, ACT_RUN_HURT },
+
+ // Walks
+ { ACT_WALK, ACT_WALK_HURT },
+ { ACT_WALK_AIM, ACT_WALK_HURT },
+ { ACT_WALK_CROUCH, ACT_WALK_HURT },
+ { ACT_WALK_CROUCH_AIM, ACT_WALK_HURT },
+ { ACT_WALK_RELAXED, ACT_WALK_HURT },
+ { ACT_WALK_STIMULATED, ACT_WALK_HURT },
+ { ACT_WALK_AGITATED, ACT_WALK_HURT },
+ { ACT_WALK_AIM_RELAXED, ACT_WALK_HURT },
+ { ACT_WALK_AIM_STIMULATED, ACT_WALK_HURT },
+ { ACT_WALK_AIM_AGITATED, ACT_WALK_HURT },
+ { ACT_WALK_HURT, ACT_WALK_HURT },
+
+ { ACT_IDLE, ACT_IDLE_HURT },
+ { ACT_COVER_LOW, ACT_INJURED_COWER },
+ { ACT_COWER, ACT_INJURED_COWER },
+ };
+
+ // Clear the map
+ m_ActivityMap.RemoveAll();
+
+ // Add all translations
+ for ( int i = 0; i < ARRAYSIZE( map ); i++ )
+ {
+ Assert( m_ActivityMap.Find( map[i].activity ) == m_ActivityMap.InvalidIndex() );
+ m_ActivityMap.Insert( map[i].activity, map[i].translation );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Populate the list after save/load
+//-----------------------------------------------------------------------------
+void CAI_BehaviorAlyxInjured::OnRestore( void )
+{
+ PopulateActivityMap();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Populate the list on spawn
+//-----------------------------------------------------------------------------
+void CAI_BehaviorAlyxInjured::Spawn( void )
+{
+ PopulateActivityMap();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Get the flinch activity for us to play
+// Input : bHeavyDamage -
+// bGesture -
+// Output : Activity
+//-----------------------------------------------------------------------------
+Activity CAI_BehaviorAlyxInjured::GetFlinchActivity( bool bHeavyDamage, bool bGesture )
+{
+ //
+ if ( ( bGesture == false ) || ( GetOuter()->GetActivity() != ACT_COWER ) )
+ return BaseClass::GetFlinchActivity( bHeavyDamage, bGesture );
+
+ // Translate the flinch if we're cowering
+ return ACT_GESTURE_INJURED_COWER_FLINCH;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : nActivity -
+//-----------------------------------------------------------------------------
+Activity CAI_BehaviorAlyxInjured::NPC_TranslateActivity( Activity nActivity )
+{
+ // Find out what the base class wants to do with the activity
+ Activity nNewActivity = BaseClass::NPC_TranslateActivity( nActivity );
+
+ // Look it up in the translation map
+ int nIndex = m_ActivityMap.Find( nNewActivity );
+
+ if ( m_ActivityMap.IsValidIndex( nIndex ) )
+ return m_ActivityMap[nIndex];
+
+ return nNewActivity;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Determines if Alyx should run away from enemies or stay put
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CAI_BehaviorAlyxInjured::ShouldRunToCover( void )
+{
+ Vector vecRetreatPos;
+ float flRetreatRadius = 128.0f;
+
+ // See how far off from our cover position we are
+ if ( FindCoverFromEnemyBehindTarget( GetFollowTarget(), flRetreatRadius, &vecRetreatPos ) )
+ {
+ float flDestDistSqr = ( GetOuter()->WorldSpaceCenter() - vecRetreatPos ).LengthSqr();
+ if ( flDestDistSqr > Square( flRetreatRadius ) )
+ return true;
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: See if we need to follow our goal
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CAI_BehaviorAlyxInjured::ShouldRunToFollowGoal( void )
+{
+ // If we're too far from our follow target, we need to chase after them
+ float flDistToFollowGoalSqr = ( GetOuter()->GetAbsOrigin() - GetFollowTarget()->GetAbsOrigin() ).LengthSqr();
+ if ( flDistToFollowGoalSqr > Square(MAX_DIST_FROM_FOLLOW_TARGET) )
+ return true;
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Translate base schedules into overridden forms
+//-----------------------------------------------------------------------------
+int CAI_BehaviorAlyxInjured::TranslateSchedule( int scheduleType )
+{
+ switch( scheduleType )
+ {
+ case SCHED_RUN_FROM_ENEMY:
+ case SCHED_RUN_FROM_ENEMY_MOB:
+ {
+ // Get under cover if we're able to
+ if ( ShouldRunToCover() )
+ return SCHED_INJURED_RUN_FROM_ENEMY;
+
+ // Run to our follow goal if we're too far away from it
+ if ( ShouldRunToFollowGoal() )
+ return SCHED_FOLLOW;
+
+ // Cower if surrounded
+ if ( HasCondition( COND_INJURED_OVERWHELMED ) )
+ return SCHED_INJURED_COWER;
+
+ // Face our enemies
+ return SCHED_INJURED_FEAR_FACE;
+ }
+ break;
+
+ case SCHED_RUN_FROM_ENEMY_FALLBACK:
+ return SCHED_INJURED_COWER;
+ break;
+ }
+
+ return BaseClass::TranslateSchedule( scheduleType );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Pick up failure cases and handle them
+//-----------------------------------------------------------------------------
+int CAI_BehaviorAlyxInjured::SelectFailSchedule( int failedSchedule, int failedTask, AI_TaskFailureCode_t taskFailCode )
+{
+ // Failed schedules
+ switch( failedSchedule )
+ {
+ case SCHED_RUN_FROM_ENEMY:
+ case SCHED_RUN_FROM_ENEMY_MOB:
+ case SCHED_FOLLOW:
+ return SCHED_INJURED_COWER;
+ }
+
+ // Failed tasks
+ switch( failedTask )
+ {
+ case TASK_FIND_COVER_FROM_ENEMY:
+ case TASK_FIND_INJURED_COVER_FROM_ENEMY:
+
+ // Only cower if we're already near enough to our follow target
+ float flDistToFollowTargetSqr = ( GetOuter()->GetAbsOrigin() - GetFollowTarget()->GetAbsOrigin() ).LengthSqr();
+ if ( flDistToFollowTargetSqr > Square( 256 ) )
+ return SCHED_FOLLOW;
+
+ return SCHED_INJURED_COWER;
+ break;
+ }
+
+ return BaseClass::SelectFailSchedule( failedSchedule, failedTask, taskFailCode );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Find the general direction enemies are coming towards us at
+//-----------------------------------------------------------------------------
+bool CAI_BehaviorAlyxInjured::FindThreatDirection2D( const Vector &vecSource, Vector *vecOut )
+{
+ // Find the general direction our threat is coming from
+ bool bValid = false;
+ Vector vecScratch;
+ AIEnemiesIter_t iter;
+
+ // Iterate through all known enemies
+ for( AI_EnemyInfo_t *pMemory = GetOuter()->GetEnemies()->GetFirst(&iter); pMemory != NULL; pMemory = GetOuter()->GetEnemies()->GetNext(&iter) )
+ {
+ if ( pMemory == NULL || pMemory->hEnemy == NULL )
+ continue;
+
+ vecScratch = ( vecSource - pMemory->hEnemy->WorldSpaceCenter() );
+ VectorNormalize( vecScratch );
+
+ (*vecOut) += vecScratch;
+ bValid = true;
+ }
+
+ // Find the general direction
+ (*vecOut).z = 0.0f;
+ VectorNormalize( (*vecOut) );
+ return bValid;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Find a position that hides us from our threats while interposing the
+// target entity between us and the threat
+// Input : pTarget - entity to hide behind
+// flRadius - Radius around the target to search
+// *vecOut - position
+//-----------------------------------------------------------------------------
+bool CAI_BehaviorAlyxInjured::FindCoverFromEnemyBehindTarget( CBaseEntity *pTarget, float flRadius, Vector *vecOut )
+{
+ if ( pTarget == NULL )
+ return false;
+
+ Vector vecTargetPos = pTarget->GetAbsOrigin();
+ Vector vecThreatDir = vec3_origin;
+
+ // Find our threat direction and base our cover on that
+ if ( FindThreatDirection2D( vecTargetPos, &vecThreatDir ) )
+ {
+ // Get a general location for taking cover
+ Vector vecTestPos = vecTargetPos + ( vecThreatDir * flRadius );
+
+ if ( g_debug_injured_follow.GetBool() )
+ {
+ NDebugOverlay::HorzArrow( GetOuter()->GetAbsOrigin(), vecTestPos, 8.0f, 255, 255, 0, 32, true, 2.0f );
+ }
+
+ // Make sure we never move towards our threat to get to cover!
+ Vector vecMoveDir = GetOuter()->GetAbsOrigin() - vecTestPos;
+ VectorNormalize( vecMoveDir );
+ float flDotToCover = DotProduct( vecMoveDir, vecThreatDir );
+ if ( flDotToCover > 0.0f )
+ {
+ if ( g_debug_injured_follow.GetBool() )
+ {
+ NDebugOverlay::HorzArrow( GetOuter()->GetAbsOrigin(), vecTestPos, 8.0f, 255, 0, 0, 32, true, 2.0f );
+ }
+
+ return false;
+ }
+
+ AIMoveTrace_t moveTrace;
+ GetOuter()->GetMoveProbe()->MoveLimit( NAV_GROUND,
+ GetOuter()->GetAbsOrigin(),
+ vecTestPos,
+ MASK_SOLID_BRUSHONLY,
+ NULL,
+ 0,
+ &moveTrace );
+
+ bool bWithinRangeToGoal = ( moveTrace.vEndPosition - vecTestPos ).Length2DSqr() < Square( GetOuter()->GetHullWidth() * 3.0f );
+ bool bCanStandAtGoal = GetOuter()->GetMoveProbe()->CheckStandPosition( moveTrace.vEndPosition, MASK_SOLID_BRUSHONLY );
+
+ if ( bWithinRangeToGoal == false || bCanStandAtGoal == false )
+ {
+ if ( g_debug_injured_follow.GetBool() )
+ {
+ NDebugOverlay::SweptBox( GetOuter()->GetAbsOrigin(), vecTestPos, GetOuter()->GetHullMins(), GetOuter()->GetHullMaxs(), vec3_angle, 255, 0, 0, 0, 2.0f );
+ }
+
+ return false;
+ }
+
+ // Accept it
+ *vecOut = moveTrace.vEndPosition;
+
+ if ( g_debug_injured_follow.GetBool() )
+ {
+ NDebugOverlay::SweptBox( GetOuter()->GetAbsOrigin(), (*vecOut), GetOuter()->GetHullMins(), GetOuter()->GetHullMaxs(), vec3_angle, 0, 255, 0, 0, 2.0f );
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pTask -
+//-----------------------------------------------------------------------------
+void CAI_BehaviorAlyxInjured::StartTask( const Task_t *pTask )
+{
+ switch( pTask->iTask )
+ {
+ case TASK_FIND_COVER_FROM_ENEMY:
+ {
+ CBaseEntity *pLeader = GetFollowTarget();
+ if ( !pLeader )
+ {
+ BaseClass::StartTask( pTask );
+ break;
+ }
+
+ // Find a position behind our follow target
+ Vector coverPos = vec3_invalid;
+ if ( FindCoverFromEnemyBehindTarget( pLeader, COVER_DISTANCE, &coverPos ) )
+ {
+ AI_NavGoal_t goal( GOALTYPE_LOCATION, coverPos, ACT_RUN, AIN_HULL_TOLERANCE, AIN_DEF_FLAGS );
+ GetOuter()->GetNavigator()->SetGoal( goal );
+ GetOuter()->m_flMoveWaitFinished = gpGlobals->curtime + pTask->flTaskData;
+ TaskComplete();
+ return;
+ }
+
+ // Couldn't find anything
+ TaskFail( FAIL_NO_COVER );
+ break;
+ }
+
+ default:
+ BaseClass::StartTask( pTask );
+ break;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Whether or not Alyx is injured
+//-----------------------------------------------------------------------------
+bool CAI_BehaviorAlyxInjured::IsInjured( void ) const
+{
+ return IsAlyxInInjuredMode();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CAI_BehaviorAlyxInjured::GatherConditions( void )
+{
+ BaseClass::GatherConditions();
+
+ // Always stomp over this
+ ClearCondition( COND_INJURED_TOO_FAR_FROM_PLAYER );
+ ClearCondition( COND_INJURED_OVERWHELMED );
+
+ // See if we're overwhelmed by foes
+ if ( NumKnownEnemiesInRadius( GetOuter()->GetAbsOrigin(), COVER_DISTANCE ) >= MIN_ENEMY_MOB )
+ {
+ SetCondition( COND_INJURED_OVERWHELMED );
+ }
+
+ // Determines whether we consider ourselves in danger
+ bool bInDanger = ( HasCondition( COND_LIGHT_DAMAGE ) ||
+ HasCondition( COND_HEAVY_DAMAGE ) ||
+ HasCondition( COND_INJURED_OVERWHELMED ) );
+
+ // See if we're too far away from the player and in danger
+ if ( AI_IsSinglePlayer() && bInDanger )
+ {
+ bool bWarnPlayer = false;
+
+ // This only works in single-player
+ CBasePlayer *pPlayer = UTIL_PlayerByIndex( 1 );
+ if ( pPlayer != NULL )
+ {
+ // FIXME: This distance may need to be the length of the shortest walked path between the follower and the target
+
+ // Get our approximate distance to the player
+ float flDistToPlayer = UTIL_DistApprox2D( GetOuter()->GetAbsOrigin(), pPlayer->GetAbsOrigin() );
+ if ( flDistToPlayer > injured_help_plee_range.GetFloat() )
+ {
+ bWarnPlayer = true;
+ }
+ else if ( flDistToPlayer > (injured_help_plee_range.GetFloat()*0.5f) && HasCondition( COND_SEE_PLAYER ) == false )
+ {
+ // Cut our distance in half if we can't see the player
+ bWarnPlayer = true;
+ }
+ }
+
+ // Yell for help!
+ if ( bWarnPlayer )
+ {
+ // FIXME: This should be routed through the normal speaking code with a system to emit from the player's suit.
+
+ CBasePlayer *pPlayer = UTIL_PlayerByIndex( 1 );
+ //float flPlayerDistSqr = ( GetOuter()->GetAbsOrigin() - pPlayer->GetAbsOrigin() ).LengthSqr();
+
+ // If the player is too far away or we can't see him
+ //if ( HasCondition( COND_SEE_PLAYER ) == false || flPlayerDistSqr > Square( 128 ) )
+ {
+ if ( m_flNextWarnTime < gpGlobals->curtime )
+ {
+ pPlayer->EmitSound( "npc_alyx.injured_too_far" );
+ m_flNextWarnTime = gpGlobals->curtime + random->RandomFloat( 3.0f, 5.0f );
+ }
+ }
+ /*
+ else
+ {
+ SpeakIfAllowed( TLK_INJURED_FOLLOW_TOO_FAR );
+ m_flNextWarnTime = gpGlobals->curtime + random->RandomFloat( 3.0f, 5.0f );
+ }
+ */
+
+ SetCondition( COND_INJURED_TOO_FAR_FROM_PLAYER );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Speak a concept if we're able to
+//-----------------------------------------------------------------------------
+void CAI_BehaviorAlyxInjured::SpeakIfAllowed( AIConcept_t concept )
+{
+ CAI_Expresser *pExpresser = GetOuter()->GetExpresser();
+ if ( pExpresser == NULL )
+ return;
+
+ // Must be able to speak the concept
+ if ( pExpresser->CanSpeakConcept( concept ) )
+ {
+ pExpresser->Speak( concept );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Get the number of known enemies within a radius to a point
+//-----------------------------------------------------------------------------
+int CAI_BehaviorAlyxInjured::NumKnownEnemiesInRadius( const Vector &vecSource, float flRadius )
+{
+ int nNumEnemies = 0;
+ float flRadiusSqr = Square( flRadius );
+
+ AIEnemiesIter_t iter;
+
+ // Iterate through all known enemies
+ for( AI_EnemyInfo_t *pMemory = GetEnemies()->GetFirst(&iter); pMemory != NULL; pMemory = GetEnemies()->GetNext(&iter) )
+ {
+ if ( pMemory == NULL || pMemory->hEnemy == NULL )
+ continue;
+
+ // Must hate or fear them
+ if ( GetOuter()->IRelationType( pMemory->hEnemy ) != D_HT && GetOuter()->IRelationType( pMemory->hEnemy ) != D_FR )
+ continue;
+
+ // Count only the enemies I've seen recently
+ if ( gpGlobals->curtime - pMemory->timeLastSeen > 0.5f )
+ continue;
+
+ // Must be within the radius we've specified
+ float flEnemyDistSqr = ( vecSource - pMemory->hEnemy->GetAbsOrigin() ).Length2DSqr();
+ if ( flEnemyDistSqr < flRadiusSqr )
+ {
+ nNumEnemies++;
+ }
+ }
+
+ return nNumEnemies;
+}
+
+// ----------------------------------------------
+// Custom AI declarations
+// ----------------------------------------------
+
+AI_BEGIN_CUSTOM_SCHEDULE_PROVIDER( CAI_BehaviorAlyxInjured )
+{
+ DECLARE_ACTIVITY( ACT_GESTURE_INJURED_COWER_FLINCH )
+ DECLARE_ACTIVITY( ACT_INJURED_COWER )
+
+ DECLARE_CONDITION( COND_INJURED_TOO_FAR_FROM_PLAYER )
+ DECLARE_CONDITION( COND_INJURED_OVERWHELMED )
+
+ DECLARE_TASK( TASK_FIND_INJURED_COVER_FROM_ENEMY )
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_INJURED_COWER,
+
+ " Tasks"
+ // TOOD: Announce cower
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_COWER"
+ " TASK_WAIT 2"
+ ""
+ " Interrupts"
+ " COND_GIVE_WAY"
+ " COND_PLAYER_PUSHING"
+ )
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_INJURED_FEAR_FACE,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE" // FIXME: Scared idle?
+ " TASK_FACE_ENEMY 0"
+ ""
+ " Interrupts"
+ " COND_GIVE_WAY"
+ " COND_PLAYER_PUSHING"
+ );
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_INJURED_RUN_FROM_ENEMY,
+
+ " Tasks"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_INJURED_COWER"
+ " TASK_STOP_MOVING 0"
+ " TASK_FIND_COVER_FROM_ENEMY 0"
+ " TASK_RUN_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ ""
+ " Interrupts"
+ );
+
+ AI_END_CUSTOM_SCHEDULE_PROVIDER()
+}
+
+//-----------------------------------------------------------------------------
+// CAI_InjuredFollowGoal
+//-----------------------------------------------------------------------------
+
+BEGIN_DATADESC( CAI_InjuredFollowGoal )
+END_DATADESC()
+
+LINK_ENTITY_TO_CLASS( ai_goal_injured_follow, CAI_InjuredFollowGoal );
+
+//-------------------------------------
+
+void CAI_InjuredFollowGoal::EnableGoal( CAI_BaseNPC *pAI )
+{
+ CAI_BehaviorAlyxInjured *pBehavior;
+ if ( !pAI->GetBehavior( &pBehavior ) )
+ return;
+
+ if ( GetGoalEntity() == NULL )
+ return;
+
+ pBehavior->SetFollowGoal( this );
+}
+
+//-------------------------------------
+
+void CAI_InjuredFollowGoal::DisableGoal( CAI_BaseNPC *pAI )
+{
+ CAI_BehaviorAlyxInjured *pBehavior;
+ if ( !pAI->GetBehavior( &pBehavior ) )
+ return;
+
+ pBehavior->ClearFollowGoal( this );
+}
diff --git a/mp/src/game/server/episodic/ai_behavior_alyx_injured.h b/mp/src/game/server/episodic/ai_behavior_alyx_injured.h
index 8dfe7576..2eb1ff19 100644
--- a/mp/src/game/server/episodic/ai_behavior_alyx_injured.h
+++ b/mp/src/game/server/episodic/ai_behavior_alyx_injured.h
@@ -1,95 +1,95 @@
-//========= Copyright Valve Corporation, All rights reserved. ============//
-//
-// Purpose: FIXME: This will ultimately become a more generic implementation
-//
-//=============================================================================
-
-#ifndef AI_BEHAVIOR_ALYX_INJURED_H
-#define AI_BEHAVIOR_ALYX_INJURED_H
-#ifdef _WIN32
-#pragma once
-#endif
-
-#include "utlmap.h"
-
-extern bool IsAlyxInInjuredMode( void );
-
-//
-//
-//
-
-class CAI_InjuredFollowGoal : public CAI_FollowGoal
-{
- DECLARE_CLASS( CAI_InjuredFollowGoal, CAI_FollowGoal );
-
-public:
-
- virtual void EnableGoal( CAI_BaseNPC *pAI );
- virtual void DisableGoal( CAI_BaseNPC *pAI );
-
- DECLARE_DATADESC();
-};
-
-//
-//
-//
-
-class CAI_BehaviorAlyxInjured : public CAI_FollowBehavior
-{
- DECLARE_CLASS( CAI_BehaviorAlyxInjured, CAI_FollowBehavior );
- DECLARE_DATADESC();
-
-public:
- CAI_BehaviorAlyxInjured( void );
-
- virtual const char *GetName( void ) { return "AlyxInjuredFollow"; }
- virtual Activity NPC_TranslateActivity( Activity nActivity );
- virtual int TranslateSchedule( int scheduleType );
- virtual void Spawn( void );
- virtual void OnRestore( void );
- virtual void StartTask( const Task_t *pTask );
- virtual int SelectFailSchedule( int failedSchedule, int failedTask, AI_TaskFailureCode_t taskFailCode );
- virtual void GatherConditions( void );
- virtual Activity GetFlinchActivity( bool bHeavyDamage, bool bGesture );
-
- enum
- {
- // Schedules
- SCHED_INJURED_COWER = BaseClass::NEXT_SCHEDULE,
- SCHED_INJURED_FEAR_FACE,
- SCHED_INJURED_RUN_FROM_ENEMY,
- NEXT_SCHEDULE,
-
- // Tasks
- TASK_FIND_INJURED_COVER_FROM_ENEMY = BaseClass::NEXT_TASK,
- NEXT_TASK,
-
- // Conditions
- COND_INJURED_TOO_FAR_FROM_PLAYER = BaseClass::NEXT_CONDITION,
- COND_INJURED_OVERWHELMED,
- NEXT_CONDITION
- };
-
- bool IsReadinessCapable( void ) { return ( IsInjured() == false ); } // Never use the readiness system when injured
- bool IsInjured( void ) const;
-
-private:
-
- void SpeakIfAllowed( AIConcept_t concept );
- bool ShouldRunToCover( void );
- bool ShouldRunToFollowGoal( void );
- bool FindThreatDirection2D( const Vector &vecSource, Vector *vecOut );
- bool FindCoverFromEnemyBehindTarget( CBaseEntity *pTarget, float flRadius, Vector *vecOut );
- void PopulateActivityMap( void );
- int NumKnownEnemiesInRadius( const Vector &vecSource, float flRadius );
-
- CUtlMap<Activity,Activity> m_ActivityMap;
-
- float m_flNextWarnTime;
-
-protected:
- DEFINE_CUSTOM_SCHEDULE_PROVIDER;
-};
-
-
-#endif // AI_BEHAVIOR_ALYX_INJURED_H
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: FIXME: This will ultimately become a more generic implementation
+//
+//=============================================================================
+
+#ifndef AI_BEHAVIOR_ALYX_INJURED_H
+#define AI_BEHAVIOR_ALYX_INJURED_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "utlmap.h"
+
+extern bool IsAlyxInInjuredMode( void );
+
+//
+//
+//
+
+class CAI_InjuredFollowGoal : public CAI_FollowGoal
+{
+ DECLARE_CLASS( CAI_InjuredFollowGoal, CAI_FollowGoal );
+
+public:
+
+ virtual void EnableGoal( CAI_BaseNPC *pAI );
+ virtual void DisableGoal( CAI_BaseNPC *pAI );
+
+ DECLARE_DATADESC();
+};
+
+//
+//
+//
+
+class CAI_BehaviorAlyxInjured : public CAI_FollowBehavior
+{
+ DECLARE_CLASS( CAI_BehaviorAlyxInjured, CAI_FollowBehavior );
+ DECLARE_DATADESC();
+
+public:
+ CAI_BehaviorAlyxInjured( void );
+
+ virtual const char *GetName( void ) { return "AlyxInjuredFollow"; }
+ virtual Activity NPC_TranslateActivity( Activity nActivity );
+ virtual int TranslateSchedule( int scheduleType );
+ virtual void Spawn( void );
+ virtual void OnRestore( void );
+ virtual void StartTask( const Task_t *pTask );
+ virtual int SelectFailSchedule( int failedSchedule, int failedTask, AI_TaskFailureCode_t taskFailCode );
+ virtual void GatherConditions( void );
+ virtual Activity GetFlinchActivity( bool bHeavyDamage, bool bGesture );
+
+ enum
+ {
+ // Schedules
+ SCHED_INJURED_COWER = BaseClass::NEXT_SCHEDULE,
+ SCHED_INJURED_FEAR_FACE,
+ SCHED_INJURED_RUN_FROM_ENEMY,
+ NEXT_SCHEDULE,
+
+ // Tasks
+ TASK_FIND_INJURED_COVER_FROM_ENEMY = BaseClass::NEXT_TASK,
+ NEXT_TASK,
+
+ // Conditions
+ COND_INJURED_TOO_FAR_FROM_PLAYER = BaseClass::NEXT_CONDITION,
+ COND_INJURED_OVERWHELMED,
+ NEXT_CONDITION
+ };
+
+ bool IsReadinessCapable( void ) { return ( IsInjured() == false ); } // Never use the readiness system when injured
+ bool IsInjured( void ) const;
+
+private:
+
+ void SpeakIfAllowed( AIConcept_t concept );
+ bool ShouldRunToCover( void );
+ bool ShouldRunToFollowGoal( void );
+ bool FindThreatDirection2D( const Vector &vecSource, Vector *vecOut );
+ bool FindCoverFromEnemyBehindTarget( CBaseEntity *pTarget, float flRadius, Vector *vecOut );
+ void PopulateActivityMap( void );
+ int NumKnownEnemiesInRadius( const Vector &vecSource, float flRadius );
+
+ CUtlMap<Activity,Activity> m_ActivityMap;
+
+ float m_flNextWarnTime;
+
+protected:
+ DEFINE_CUSTOM_SCHEDULE_PROVIDER;
+};
+
+
+#endif // AI_BEHAVIOR_ALYX_INJURED_H
diff --git a/mp/src/game/server/episodic/ai_behavior_passenger_companion.cpp b/mp/src/game/server/episodic/ai_behavior_passenger_companion.cpp
index f662f04a..f96c2a55 100644
--- a/mp/src/game/server/episodic/ai_behavior_passenger_companion.cpp
+++ b/mp/src/game/server/episodic/ai_behavior_passenger_companion.cpp
@@ -1,2053 +1,2053 @@
-//========= Copyright Valve Corporation, All rights reserved. ============//
-//
-// Purpose: Companion NPCs riding in cars
-//
-//=============================================================================
-
-#include "cbase.h"
-#include "ai_speech.h"
-#include "ai_pathfinder.h"
-#include "ai_waypoint.h"
-#include "ai_navigator.h"
-#include "ai_navgoaltype.h"
-#include "ai_memory.h"
-#include "ai_behavior_passenger_companion.h"
-#include "ai_squadslot.h"
-#include "npc_playercompanion.h"
-#include "ai_route.h"
-#include "saverestore_utlvector.h"
-#include "cplane.h"
-#include "util_shared.h"
-#include "sceneentity.h"
-
-bool SphereWithinPlayerFOV( CBasePlayer *pPlayer, const Vector &vecCenter, float flRadius );
-
-#define PASSENGER_NEAR_VEHICLE_THRESHOLD 64.0f
-
-#define MIN_OVERTURNED_DURATION 1.0f // seconds
-#define MIN_FAILED_EXIT_ATTEMPTS 4
-#define MIN_OVERTURNED_WARN_DURATION 4.0f // seconds
-
-ConVar passenger_collision_response_threshold( "passenger_collision_response_threshold", "250.0" );
-ConVar passenger_debug_entry( "passenger_debug_entry", "0" );
-ConVar passenger_use_leaning("passenger_use_leaning", "1" );
-extern ConVar passenger_debug_transition;
-
-// Custom activities
-Activity ACT_PASSENGER_IDLE_AIM;
-Activity ACT_PASSENGER_RELOAD;
-Activity ACT_PASSENGER_OVERTURNED;
-Activity ACT_PASSENGER_IMPACT;
-Activity ACT_PASSENGER_IMPACT_WEAPON;
-Activity ACT_PASSENGER_POINT;
-Activity ACT_PASSENGER_POINT_BEHIND;
-Activity ACT_PASSENGER_IDLE_READY;
-Activity ACT_PASSENGER_GESTURE_JOSTLE_LARGE;
-Activity ACT_PASSENGER_GESTURE_JOSTLE_SMALL;
-Activity ACT_PASSENGER_GESTURE_JOSTLE_LARGE_STIMULATED;
-Activity ACT_PASSENGER_GESTURE_JOSTLE_SMALL_STIMULATED;
-Activity ACT_PASSENGER_COWER_IN;
-Activity ACT_PASSENGER_COWER_LOOP;
-Activity ACT_PASSENGER_COWER_OUT;
-Activity ACT_PASSENGER_IDLE_FIDGET;
-
-BEGIN_DATADESC( CAI_PassengerBehaviorCompanion )
-
- DEFINE_EMBEDDED( m_VehicleMonitor ),
-
- DEFINE_UTLVECTOR( m_FailedEntryPositions, FIELD_EMBEDDED ),
-
- DEFINE_FIELD( m_flOverturnedDuration, FIELD_FLOAT ),
- DEFINE_FIELD( m_flUnseenDuration, FIELD_FLOAT ),
- DEFINE_FIELD( m_nExitAttempts, FIELD_INTEGER ),
- DEFINE_FIELD( m_flNextOverturnWarning, FIELD_TIME ),
- DEFINE_FIELD( m_flEnterBeginTime, FIELD_TIME ),
- DEFINE_FIELD( m_hCompanion, FIELD_EHANDLE ),
- DEFINE_FIELD( m_flNextJostleTime, FIELD_TIME ),
- DEFINE_FIELD( m_nVisibleEnemies, FIELD_INTEGER ),
- DEFINE_FIELD( m_flLastLateralLean, FIELD_FLOAT ),
- DEFINE_FIELD( m_flEntraceUpdateTime, FIELD_TIME ),
- DEFINE_FIELD( m_flNextEnterAttempt, FIELD_TIME ),
- DEFINE_FIELD( m_flNextFidgetTime, FIELD_TIME ),
-
-END_DATADESC();
-
-BEGIN_SIMPLE_DATADESC( FailPosition_t )
-
- DEFINE_FIELD( vecPosition, FIELD_VECTOR ),
- DEFINE_FIELD( flTime, FIELD_TIME ),
-
-END_DATADESC();
-
-CAI_PassengerBehaviorCompanion::CAI_PassengerBehaviorCompanion( void ) :
-m_flUnseenDuration( 0.0f ),
-m_flNextOverturnWarning( 0.0f ),
-m_flOverturnedDuration( 0.0f ),
-m_nExitAttempts( 0 ),
-m_flNextEnterAttempt( 0.0f ),
-m_flLastLateralLean( 0.0f ),
-m_flNextJostleTime( 0.0f )
-{
- memset( &m_vehicleState, 0, sizeof( m_vehicleState ) );
- m_VehicleMonitor.ClearMark();
-}
-
-void CAI_PassengerBehaviorCompanion::Enable( CPropJeepEpisodic *pVehicle, bool bImmediateEnter /*= false*/ )
-{
- BaseClass::Enable( pVehicle );
-
- // Store this up for quick reference later on
- m_hCompanion = dynamic_cast<CNPC_PlayerCompanion *>(GetOuter());
-
- // See if we want to sit in the vehicle immediately
- if ( bImmediateEnter )
- {
- // Find the seat and sit in it
- if ( ReserveEntryPoint( VEHICLE_SEAT_ANY ) )
- {
- // Attach
- AttachToVehicle();
-
- // This will slam us into the right position and clean up
- FinishEnterVehicle();
- GetOuter()->IncrementInterpolationFrame();
-
- // Start our schedule immediately
- ClearSchedule( "Immediate entry to vehicle" );
- }
- }
-}
-
-//-----------------------------------------------------------------------------
-// Set up the shot regulator based on the equipped weapon
-//-----------------------------------------------------------------------------
-void CAI_PassengerBehaviorCompanion::OnUpdateShotRegulator( void )
-{
- if ( GetVehicleSpeed() > 250 )
- {
- // Default values
- GetOuter()->GetShotRegulator()->SetBurstInterval( 0.1f, 0.5f );
- GetOuter()->GetShotRegulator()->SetBurstShotCountRange( 1, 4 );
- GetOuter()->GetShotRegulator()->SetRestInterval( 0.25f, 1.0f );
- }
- else
- {
- BaseClass::OnUpdateShotRegulator();
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Output : Returns true on success, false on failure.
-//-----------------------------------------------------------------------------
-bool CAI_PassengerBehaviorCompanion::IsValidEnemy( CBaseEntity *pEntity )
-{
- // The target must be much closer in the vehicle
- float flDistSqr = ( pEntity->GetAbsOrigin() - GetAbsOrigin() ).LengthSqr();
- if ( flDistSqr > Square( (40*12) ) && pEntity->Classify() != CLASS_BULLSEYE )
- return false;
-
- // Determine if the target is going to move past us?
- return BaseClass::IsValidEnemy( pEntity );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Returns the speed the vehicle is moving at
-// Output : units per second
-//-----------------------------------------------------------------------------
-float CAI_PassengerBehaviorCompanion::GetVehicleSpeed( void )
-{
- if ( m_hVehicle == NULL )
- {
- Assert(0);
- return -1.0f;
- }
-
- Vector vecVelocity;
- m_hVehicle->GetVelocity( &vecVelocity, NULL );
-
- // Get our speed
- return vecVelocity.Length();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Detect oncoming collisions
-//-----------------------------------------------------------------------------
-void CAI_PassengerBehaviorCompanion::GatherVehicleCollisionConditions( const Vector &localVelocity )
-{
- // Look for walls in front of us
- if ( localVelocity.y > passenger_collision_response_threshold.GetFloat() )
- {
- // Detect an upcoming collision
- Vector vForward;
- m_hVehicle->GetVectors( &vForward, NULL, NULL );
-
- // Use a smaller bounding box to make it detect mostly head-on impacts
- Vector mins, maxs;
- mins.Init( -24, -24, 32 );
- maxs.Init( 24, 24, 64 );
-
- float dt = 0.6f; // Seconds
- float distance = localVelocity.y * dt;
-
- // Find our angular velocity as a vector
- Vector vecAngularVelocity;
- vecAngularVelocity.z = 0.0f;
- SinCos( DEG2RAD( m_vehicleState.m_vecLastAngles.z * dt ), &vecAngularVelocity.y, &vecAngularVelocity.x );
-
- Vector vecOffset;
- VectorRotate( vecAngularVelocity, m_hVehicle->GetAbsAngles() + QAngle( 0, 90, 0 ), vecOffset );
-
- vForward += vecOffset;
- VectorNormalize( vForward );
-
- // Trace ahead of us to see what's there
- CTraceFilterNoNPCsOrPlayer filter( m_hVehicle, COLLISION_GROUP_NONE ); // We don't care about NPCs or the player (certainly if they're in the vehicle!)
-
- trace_t tr;
- UTIL_TraceHull( m_hVehicle->GetAbsOrigin(), m_hVehicle->GetAbsOrigin() + ( vForward * distance ), mins, maxs, MASK_SOLID, &filter, &tr );
-
- bool bWarnCollision = true;
- if ( tr.DidHit() )
- {
- // We need to see how "head-on" to the surface we are
- float impactDot = DotProduct( tr.plane.normal, vForward );
-
- // Don't warn over grazing blows or slopes
- if ( impactDot < -0.9f && tr.plane.normal.z < 0.75f )
- {
- // Make sure this is a worthwhile thing to warn about
- if ( tr.m_pEnt )
- {
- // If it's physical and moveable, then ignore it because we'll probably smash or move it
- IPhysicsObject *pObject = tr.m_pEnt->VPhysicsGetObject();
- if ( pObject && pObject->IsMoveable() )
- {
- bWarnCollision = false;
- }
- }
-
- // Note that we should say something to the player about it
- if ( bWarnCollision )
- {
- SetCondition( COND_PASSENGER_WARN_COLLISION );
- }
- }
- }
- }
-
- if ( passenger_use_leaning.GetBool() )
- {
- // Calculate how our body is leaning
- CalculateBodyLean();
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Speak various lines about the state of the vehicle
-//-----------------------------------------------------------------------------
-void CAI_PassengerBehaviorCompanion::SpeakVehicleConditions( void )
-{
- Assert( m_hVehicle != NULL );
- if ( m_hVehicle == NULL )
- return;
-
- // Speak if we just hit something
- if ( HasCondition( COND_PASSENGER_HARD_IMPACT ) )
- {
- SpeakIfAllowed( TLK_PASSENGER_IMPACT );
- }
-
- // Speak if we're overturned
- if ( HasCondition( COND_PASSENGER_OVERTURNED ) )
- {
- SpeakIfAllowed( TLK_PASSENGER_OVERTURNED );
- }
-
- // Speak if we're about to hit something
- if ( HasCondition( COND_PASSENGER_WARN_COLLISION ) )
- {
- // Make Alyx look at the impending impact
- Vector vecForward;
- m_hVehicle->GetVectors( &vecForward, NULL, NULL );
- Vector vecLookPos = m_hVehicle->WorldSpaceCenter() + ( vecForward * 64.0f );
- GetOuter()->AddLookTarget( vecLookPos, 1.0f, 1.0f );
-
- SpeakIfAllowed( TLK_PASSENGER_WARN_COLLISION );
- ClearCondition( COND_PASSENGER_WARN_COLLISION );
- }
-
- // Speak if the player is driving like a madman
- if ( HasCondition( COND_PASSENGER_ERRATIC_DRIVING ) )
- {
- SpeakIfAllowed( TLK_PASSENGER_ERRATIC_DRIVING );
- }
-
- // The vehicle has come to a halt
- if ( HasCondition( COND_PASSENGER_VEHICLE_STOPPED ) )
- {
- float flDist = ( WorldSpaceCenter() - m_hVehicle->WorldSpaceCenter() ).Length();
- CFmtStrN<128> modifiers( "vehicle_distance:%f", flDist );
- SpeakIfAllowed( TLK_PASSENGER_VEHICLE_STOPPED, modifiers );
- }
-
- // The vehicle has started to move
- if ( HasCondition( COND_PASSENGER_VEHICLE_STARTED ) )
- {
- float flDist = ( WorldSpaceCenter() - m_hVehicle->WorldSpaceCenter() ).Length();
- CFmtStrN<128> modifiers( "vehicle_distance:%f", flDist );
- SpeakIfAllowed( TLK_PASSENGER_VEHICLE_STARTED, modifiers );
- }
-
- // Player got in
- if ( HasCondition( COND_PASSENGER_PLAYER_EXITED_VEHICLE ) )
- {
- CPropJeepEpisodic *pJalopy = dynamic_cast<CPropJeepEpisodic*>(m_hVehicle.Get());
- if( pJalopy != NULL && pJalopy->NumRadarContacts() > 0 )
- {
- SpeakIfAllowed( TLK_PASSENGER_PLAYER_EXITED, "radar_has_targets" );
- }
- else
- {
- SpeakIfAllowed( TLK_PASSENGER_PLAYER_EXITED );
- }
- }
-
- // Player got out
- if ( HasCondition( COND_PASSENGER_PLAYER_ENTERED_VEHICLE ) )
- {
- SpeakIfAllowed( TLK_PASSENGER_PLAYER_ENTERED );
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Whether or not we should jostle at this moment
-// Output : Returns true on success, false on failure.
-//-----------------------------------------------------------------------------
-bool CAI_PassengerBehaviorCompanion::CanPlayJostle( bool bLargeJostle )
-{
- // We've been told to suppress the jostle
- if ( m_flNextJostleTime > gpGlobals->curtime )
- return false;
-
- // Can't do this if we're at a high readiness level
- if ( m_hCompanion && m_hCompanion->ShouldBeAiming() )
- return false;
-
- // Can't do this when we're upside-down
- if ( HasCondition( COND_PASSENGER_OVERTURNED ) )
- return false;
-
- // Allow our normal impact code to handle this one instead
- if ( HasCondition( COND_PASSENGER_HARD_IMPACT ) || IsCurSchedule( SCHED_PASSENGER_IMPACT ) )
- return false;
-
- if ( bLargeJostle )
- {
- // Don't bother under certain circumstances
- if ( IsCurSchedule( SCHED_PASSENGER_COWER ) ||
- IsCurSchedule( SCHED_PASSENGER_FIDGET ) )
- return false;
- }
- else
- {
- // Don't interrupt a larger gesture
- if ( GetOuter()->IsPlayingGesture( ACT_PASSENGER_GESTURE_JOSTLE_LARGE ) || GetOuter()->IsPlayingGesture( ACT_PASSENGER_GESTURE_JOSTLE_LARGE_STIMULATED ) )
- return false;
- }
-
- return true;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Gather conditions we can comment on or react to while riding in the vehicle
-//-----------------------------------------------------------------------------
-void CAI_PassengerBehaviorCompanion::GatherVehicleStateConditions( void )
-{
- // Gather the base class
- BaseClass::GatherVehicleStateConditions();
-
- // See if we're going to collide with anything soon
- GatherVehicleCollisionConditions( m_vehicleState.m_vecLastLocalVelocity );
-
- // Say anything we're meant to through the response rules
- SpeakVehicleConditions();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Handles exit failure notifications
-//-----------------------------------------------------------------------------
-void CAI_PassengerBehaviorCompanion::OnExitVehicleFailed( void )
-{
- m_nExitAttempts++;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Track how long we've been overturned
-//-----------------------------------------------------------------------------
-void CAI_PassengerBehaviorCompanion::UpdateStuckStatus( void )
-{
- if ( m_hVehicle == NULL )
- return;
-
- // Always clear this to start out with
- ClearCondition( COND_PASSENGER_CAN_LEAVE_STUCK_VEHICLE );
-
- // If we can't exit the vehicle, then don't bother with these checks
- if ( m_hVehicle->NPC_CanExitVehicle( GetOuter(), true ) == false )
- return;
-
- bool bVisibleToPlayer = false;
- bool bPlayerInVehicle = false;
-
- CBasePlayer *pPlayer = UTIL_PlayerByIndex( 1 );
- if ( pPlayer )
- {
- bVisibleToPlayer = pPlayer->FInViewCone( GetOuter()->GetAbsOrigin() );
- bPlayerInVehicle = pPlayer->IsInAVehicle();
- }
-
- // If we're not overturned, just reset our counter
- if ( m_vehicleState.m_bWasOverturned == false )
- {
- m_flOverturnedDuration = 0.0f;
- m_flUnseenDuration = 0.0f;
- }
- else
- {
- // Add up the time since we last checked
- m_flOverturnedDuration += ( gpGlobals->curtime - GetLastThink() );
- }
-
- // Warn about being stuck upside-down if it's been long enough
- if ( m_flOverturnedDuration > MIN_OVERTURNED_WARN_DURATION && m_flNextOverturnWarning < gpGlobals->curtime )
- {
- SetCondition( COND_PASSENGER_WARN_OVERTURNED );
- }
-
- // If the player can see us or is still in the vehicle, we never exit
- if ( bVisibleToPlayer || bPlayerInVehicle )
- {
- // Reset our timer
- m_flUnseenDuration = 0.0f;
- return;
- }
-
- // Add up the time since we last checked
- m_flUnseenDuration += ( gpGlobals->curtime - GetLastThink() );
-
- // If we've been overturned for long enough or tried to exit one too many times
- if ( m_vehicleState.m_bWasOverturned )
- {
- if ( m_flUnseenDuration > MIN_OVERTURNED_DURATION )
- {
- SetCondition( COND_PASSENGER_CAN_LEAVE_STUCK_VEHICLE );
- }
- }
- else if ( m_nExitAttempts >= MIN_FAILED_EXIT_ATTEMPTS )
- {
- // The player can't be looking at us
- SetCondition( COND_PASSENGER_CAN_LEAVE_STUCK_VEHICLE );
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Gather conditions for our use in making decisions
-//-----------------------------------------------------------------------------
-void CAI_PassengerBehaviorCompanion::GatherConditions( void )
-{
- // Code below relies on these conditions being set first!
- BaseClass::GatherConditions();
-
- // We're not enabled
- if ( IsEnabled() == false )
- return;
-
- // In-car conditions
- if ( GetPassengerState() == PASSENGER_STATE_INSIDE )
- {
- // If we're jostling, then note that
- if ( HasCondition( COND_PASSENGER_ERRATIC_DRIVING ) )
- {
- if ( CanPlayJostle( true ) )
- {
- // Add the gesture to be played. If it's already playing, the underlying function will simply opt-out
- int nSequence = GetOuter()->AddGesture( GetOuter()->NPC_TranslateActivity( ACT_PASSENGER_GESTURE_JOSTLE_LARGE ), true );
-
- GetOuter()->SetNextAttack( gpGlobals->curtime + ( GetOuter()->SequenceDuration( nSequence ) * 2.0f ) );
- GetOuter()->GetShotRegulator()->FireNoEarlierThan( GetOuter()->GetNextAttack() );
-
- // Push out our fidget into the future so that we don't act unnaturally over bumpy terrain
- ExtendFidgetDelay( random->RandomFloat( 1.5f, 3.0f ) );
- }
- }
- else if ( HasCondition( COND_PASSENGER_JOSTLE_SMALL ) )
- {
- if ( CanPlayJostle( false ) )
- {
- // Add the gesture to be played. If it's already playing, the underlying function will simply opt-out
- GetOuter()->AddGesture( GetOuter()->NPC_TranslateActivity( ACT_PASSENGER_GESTURE_JOSTLE_SMALL ), true );
-
- // Push out our fidget into the future so that we don't act unnaturally over bumpy terrain
- ExtendFidgetDelay( random->RandomFloat( 1.5f, 3.0f ) );
- }
- }
-
- // See if we're upside-down
- UpdateStuckStatus();
-
- // See if we're able to fidget
- if ( CanFidget() )
- {
- SetCondition( COND_PASSENGER_CAN_FIDGET );
- }
- }
-
- // Clear this out
- ClearCondition( COND_PASSENGER_CAN_ENTER_IMMEDIATELY );
-
- // Make sure a vehicle doesn't stray from its mark
- if ( IsCurSchedule( SCHED_PASSENGER_RUN_TO_ENTER_VEHICLE ) )
- {
- if ( m_VehicleMonitor.TargetMoved( m_hVehicle ) )
- {
- SetCondition( COND_PASSENGER_VEHICLE_MOVED_FROM_MARK );
- }
-
- // If we can get in the car right away, set us up to do so
- int nNearestSequence;
- if ( CanEnterVehicleImmediately( &nNearestSequence, &m_vecTargetPosition, &m_vecTargetAngles ) )
- {
- SetTransitionSequence( nNearestSequence );
- SetCondition( COND_PASSENGER_ENTERING );
- SetCondition( COND_PASSENGER_CAN_ENTER_IMMEDIATELY );
- }
- }
-
- // Clear the number for now
- m_nVisibleEnemies = 0;
-
- AIEnemiesIter_t iter;
- for( AI_EnemyInfo_t *pEMemory = GetEnemies()->GetFirst(&iter); pEMemory != NULL; pEMemory = GetEnemies()->GetNext(&iter) )
- {
- if( GetOuter()->IRelationType( pEMemory->hEnemy ) == D_HT )
- {
- if( pEMemory->timeLastSeen == gpGlobals->curtime )
- {
- m_nVisibleEnemies++;
- }
- }
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CAI_PassengerBehaviorCompanion::AimGun( void )
-{
- // If there is no aiming target, return to center
- if ( GetEnemy() == NULL )
- {
- GetOuter()->RelaxAim();
- return;
- }
-
- // Otherwise try and shoot down the barrel
- Vector vecForward, vecRight, vecUp;
- GetOuter()->GetVectors( &vecForward, &vecRight, &vecUp );
- Vector vecTorso = GetAbsOrigin() + ( vecUp * 48.0f );
-
- Vector vecShootDir = GetOuter()->GetShootEnemyDir( vecTorso, false );
-
- Vector vecDirToEnemy = GetEnemy()->GetAbsOrigin() - vecTorso;
- VectorNormalize( vecDirToEnemy );
-
- bool bRightSide = ( DotProduct( vecDirToEnemy, vecRight ) > 0.0f );
- float flTargetDot = ( bRightSide ) ? -0.7f : 0.0f;
-
- if ( DotProduct( vecForward, vecDirToEnemy ) <= flTargetDot )
- {
- // Don't aim at something that's outside our reach
- GetOuter()->RelaxAim();
- }
- else
- {
- // Aim at it
- GetOuter()->SetAim( vecShootDir );
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Allow us to deny selecting a schedule if we're not in a state to do so
-//-----------------------------------------------------------------------------
-bool CAI_PassengerBehaviorCompanion::CanSelectSchedule( void )
-{
- if ( BaseClass::CanSelectSchedule() == false )
- return false;
-
- // We're in a period where we're allowing our base class to override us
- if ( m_flNextEnterAttempt > gpGlobals->curtime )
- return false;
-
- return true;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Deal with enter/exit of the vehicle
-//-----------------------------------------------------------------------------
-int CAI_PassengerBehaviorCompanion::SelectTransitionSchedule( void )
-{
- // Attempt to instantly enter the vehicle
- if ( HasCondition( COND_PASSENGER_CAN_ENTER_IMMEDIATELY ) )
- {
- // Snap to position and begin to animate into the seat
- EnterVehicleImmediately();
- return SCHED_PASSENGER_ENTER_VEHICLE_IMMEDIATELY;
- }
-
- // Entering schedule
- if ( HasCondition( COND_PASSENGER_ENTERING ) || m_PassengerIntent == PASSENGER_INTENT_ENTER )
- {
- if ( GetPassengerState() == PASSENGER_STATE_INSIDE )
- {
- ClearCondition( COND_PASSENGER_ENTERING );
- m_PassengerIntent = PASSENGER_INTENT_NONE;
- return SCHED_NONE;
- }
-
- // Don't attempt to enter for a period of time
- if ( m_flNextEnterAttempt > gpGlobals->curtime )
- return SCHED_NONE;
-
- ClearCondition( COND_PASSENGER_ENTERING );
-
- // Failing that, run to the right place
- return SCHED_PASSENGER_RUN_TO_ENTER_VEHICLE;
- }
-
- return BaseClass::SelectTransitionSchedule();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Select schedules when we're riding in the car
-//-----------------------------------------------------------------------------
-int CAI_PassengerBehaviorCompanion::SelectScheduleInsideVehicle( void )
-{
- // Overturned
- if ( HasCondition( COND_PASSENGER_OVERTURNED ) )
- return SCHED_PASSENGER_OVERTURNED;
-
- if ( HasCondition( COND_PASSENGER_HARD_IMPACT ) )
- {
- // Push out our fidget into the future so that we don't act unnaturally over bumpy terrain
- ExtendFidgetDelay( random->RandomFloat( 1.5f, 3.0f ) );
- m_flNextJostleTime = gpGlobals->curtime + random->RandomFloat( 2.5f, 4.0f );
- return SCHED_PASSENGER_IMPACT;
- }
-
- // Look for exiting the vehicle
- if ( HasCondition( COND_PASSENGER_CAN_LEAVE_STUCK_VEHICLE ) )
- return SCHED_PASSENGER_EXIT_STUCK_VEHICLE;
-
- // Cower if we're about to get nailed
- if ( HasCondition( COND_HEAR_DANGER ) && IsCurSchedule( SCHED_PASSENGER_COWER ) == false )
- {
- SpeakIfAllowed( TLK_DANGER );
- return SCHED_PASSENGER_COWER;
- }
-
- // Fire on targets
- if ( GetEnemy() )
- {
- // Limit how long we'll keep an enemy if there are many on screen
- if ( HasCondition( COND_NEW_ENEMY ) && m_nVisibleEnemies > 1 )
- {
- GetEnemies()->SetTimeValidEnemy( GetEnemy(), random->RandomFloat( 0.5f, 1.0f ) );
- }
-
- // Always face
- GetOuter()->AddLookTarget( GetEnemy(), 1.0f, 2.0f );
-
- if ( HasCondition( COND_CAN_RANGE_ATTACK1 ) && ( GetOuter()->GetShotRegulator()->IsInRestInterval() == false ) )
- return SCHED_PASSENGER_RANGE_ATTACK1;
- }
-
- // Reload when we have the chance
- if ( HasCondition( COND_LOW_PRIMARY_AMMO ) && HasCondition( COND_SEE_ENEMY ) == false )
- return SCHED_PASSENGER_RELOAD;
-
- // Say an overturned line
- if ( HasCondition( COND_PASSENGER_WARN_OVERTURNED ) )
- {
- SpeakIfAllowed( TLK_PASSENGER_REQUEST_UPRIGHT );
- m_flNextOverturnWarning = gpGlobals->curtime + random->RandomFloat( 5.0f, 10.0f );
- ClearCondition( COND_PASSENGER_WARN_OVERTURNED );
- }
-
- // Should we fidget?
- if ( HasCondition( COND_PASSENGER_CAN_FIDGET ) )
- {
- ExtendFidgetDelay( random->RandomFloat( 6.0f, 12.0f ) );
- return SCHED_PASSENGER_FIDGET;
- }
-
- return SCHED_NONE;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Select schedules while we're outside the car
-//-----------------------------------------------------------------------------
-int CAI_PassengerBehaviorCompanion::SelectScheduleOutsideVehicle( void )
-{
- // FIXME: How can we get in here?
- Assert( m_hVehicle );
- if ( m_hVehicle == NULL )
- return SCHED_NONE;
-
- // Handle our mark moving
- if ( HasCondition( COND_PASSENGER_VEHICLE_MOVED_FROM_MARK ) )
- {
- // Reset our mark
- m_VehicleMonitor.SetMark( m_hVehicle, 36.0f );
- ClearCondition( COND_PASSENGER_VEHICLE_MOVED_FROM_MARK );
- }
-
- // If we want to get in, the try to do so
- if ( m_PassengerIntent == PASSENGER_INTENT_ENTER )
- {
- // If we're not attempting to enter the vehicle again, just fall to the base class
- if ( m_flNextEnterAttempt > gpGlobals->curtime )
- return BaseClass::SelectSchedule();
-
- // Otherwise try and enter thec car
- return SCHED_PASSENGER_RUN_TO_ENTER_VEHICLE;
- }
-
- // This means that we're outside the vehicle with no intent to enter, which should have disabled us!
- Disable();
- Assert( 0 );
-
- return SCHED_NONE;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : *pPlayer -
-// &vecCenter -
-// flRadius -
-// Output : Returns true on success, false on failure.
-//-----------------------------------------------------------------------------
-bool SphereWithinPlayerFOV( CBasePlayer *pPlayer, const Vector &vecCenter, float flRadius )
-{
- // TODO: For safety sake, we might want to do a more fully qualified test against the frustum using the bbox
-
- // If the player can see us, then we can't enter immediately anyway
- if ( pPlayer == NULL )
- return false;
-
- // Find the length to the point
- Vector los = ( vecCenter - pPlayer->EyePosition() );
- float flLength = VectorNormalize( los );
-
- // Get the player's forward direction
- Vector vecPlayerForward;
- pPlayer->EyeVectors( &vecPlayerForward, NULL, NULL );
-
- // This is the additional number of degrees to add to account for our distance
- float flArcAddition = atan2( flRadius, flLength );
-
- // Find if the sphere is within our FOV
- float flDot = DotProduct( los, vecPlayerForward );
- float flPlayerFOV = cos( DEG2RAD( pPlayer->GetFOV() / 2.0f ) );
-
- return ( flDot > (flPlayerFOV-flArcAddition) );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Output : Returns true on success, false on failure.
-//-----------------------------------------------------------------------------
-bool CAI_PassengerBehaviorCompanion::CanEnterVehicleImmediately( int *pResultSequence, Vector *pResultPos, QAngle *pResultAngles )
-{
- // Must wait a short time before trying to do this (otherwise we stack up on the player!)
- if ( ( gpGlobals->curtime - m_flEnterBeginTime ) < 0.5f )
- return false;
-
- // Vehicle can't be moving too quickly
- if ( GetVehicleSpeed() > 150 )
- return false;
-
- // If the player can see us, then we can't enter immediately anyway
- CBasePlayer *pPlayer = AI_GetSinglePlayer();
- if ( pPlayer == NULL )
- return false;
-
- Vector vecPosition = GetOuter()->WorldSpaceCenter();
- float flRadius = GetOuter()->CollisionProp()->BoundingRadius2D();
- if ( SphereWithinPlayerFOV( pPlayer, vecPosition, flRadius ) )
- return false;
-
- // Reserve an entry point
- if ( ReserveEntryPoint( VEHICLE_SEAT_ANY ) == false )
- return false;
-
- // Get a list of all our animations
- const PassengerSeatAnims_t *pEntryAnims = m_hVehicle->GetServerVehicle()->NPC_GetPassengerSeatAnims( GetOuter(), PASSENGER_SEAT_ENTRY );
- if ( pEntryAnims == NULL )
- return -1;
-
- // Get the ultimate position we'll end up at
- Vector vecStartPos, vecEndPos;
- QAngle vecStartAngles;
- if ( m_hVehicle->GetServerVehicle()->NPC_GetPassengerSeatPosition( GetOuter(), &vecEndPos, NULL ) == false )
- return -1;
-
- // Categorize the passenger in terms of being on the left or right side of the vehicle
- Vector vecRight;
- m_hVehicle->GetVectors( NULL, &vecRight, NULL );
-
- CPlane lateralPlane;
- lateralPlane.InitializePlane( vecRight, m_hVehicle->WorldSpaceCenter() );
-
- bool bPlaneSide = lateralPlane.PointInFront( GetOuter()->GetAbsOrigin() );
-
- Vector vecPassengerOffset = ( GetOuter()->WorldSpaceCenter() - GetOuter()->GetAbsOrigin() );
-
- const CPassengerSeatTransition *pTransition;
- float flNearestDistSqr = FLT_MAX;
- float flSeatDistSqr;
- int nNearestSequence = -1;
- int nSequence;
- Vector vecNearestPos;
- QAngle vecNearestAngles;
-
- // Test each animation (sorted by priority) for the best match
- for ( int i = 0; i < pEntryAnims->Count(); i++ )
- {
- // Find the activity for this animation name
- pTransition = &pEntryAnims->Element(i);
- nSequence = GetOuter()->LookupSequence( STRING( pTransition->GetAnimationName() ) );
- if ( nSequence == -1 )
- continue;
-
- // Test this entry for validity
- if ( GetEntryPoint( nSequence, &vecStartPos, &vecStartAngles ) == false )
- continue;
-
- // See if the passenger would be visible if standing at this position
- if ( SphereWithinPlayerFOV( pPlayer, (vecStartPos+vecPassengerOffset), flRadius ) )
- continue;
-
- // Otherwise distance is the deciding factor
- flSeatDistSqr = ( vecStartPos - GetOuter()->GetAbsOrigin() ).LengthSqr();
-
- // We must be within a certain distance to the vehicle
- if ( flSeatDistSqr > Square( 25*12 ) )
- continue;
-
- // We cannot cross between the plane which splits the vehicle laterally in half down the middle
- // This avoids cases where the character magically ends up on one side of the vehicle after they were
- // clearly just on the other side.
- if ( lateralPlane.PointInFront( vecStartPos ) != bPlaneSide )
- continue;
-
- // Closer, take it
- if ( flSeatDistSqr < flNearestDistSqr )
- {
- flNearestDistSqr = flSeatDistSqr;
- nNearestSequence = nSequence;
- vecNearestPos = vecStartPos;
- vecNearestAngles = vecStartAngles;
- }
- }
-
- // Fail if we didn't find anything
- if ( nNearestSequence == -1 )
- return false;
-
- // Return the results
- if ( pResultSequence )
- {
- *pResultSequence = nNearestSequence;
- }
-
- if ( pResultPos )
- {
- *pResultPos = vecNearestPos;
- }
-
- if ( pResultAngles )
- {
- *pResultAngles = vecNearestAngles;
- }
-
- return true;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Put us into the vehicle immediately
-// Output : Returns true on success, false on failure.
-//-----------------------------------------------------------------------------
-void CAI_PassengerBehaviorCompanion::EnterVehicleImmediately( void )
-{
- // Now play the animation
- GetOuter()->SetIdealActivity( ACT_SCRIPT_CUSTOM_MOVE );
- GetOuter()->GetNavigator()->ClearGoal();
-
- // Put us there and get going (no interpolation!)
- GetOuter()->Teleport( &m_vecTargetPosition, &m_vecTargetAngles, &vec3_origin );
- GetOuter()->IncrementInterpolationFrame();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Overrides the schedule selection
-// Output : int - Schedule to play
-//-----------------------------------------------------------------------------
-int CAI_PassengerBehaviorCompanion::SelectSchedule( void )
-{
- // First, keep track of our transition state (enter/exit)
- int nSched = SelectTransitionSchedule();
- if ( nSched != SCHED_NONE )
- return nSched;
-
- // Handle schedules based on our passenger state
- if ( GetPassengerState() == PASSENGER_STATE_OUTSIDE )
- {
- nSched = SelectScheduleOutsideVehicle();
- if ( nSched != SCHED_NONE )
- return nSched;
- }
- else if ( GetPassengerState() == PASSENGER_STATE_INSIDE )
- {
- nSched = SelectScheduleInsideVehicle();
- if ( nSched != SCHED_NONE )
- return nSched;
- }
-
- return BaseClass::SelectSchedule();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-int CAI_PassengerBehaviorCompanion::SelectFailSchedule( int failedSchedule, int failedTask, AI_TaskFailureCode_t taskFailCode )
-{
- switch( failedTask )
- {
- case TASK_GET_PATH_TO_VEHICLE_ENTRY_POINT:
- {
- // This is not allowed!
- if ( GetPassengerState() != PASSENGER_STATE_OUTSIDE )
- {
- Assert( 0 );
- return SCHED_FAIL;
- }
-
- // If we're not close enough, then get nearer the target
- if ( UTIL_DistApprox( m_hVehicle->GetAbsOrigin(), GetOuter()->GetAbsOrigin() ) > PASSENGER_NEAR_VEHICLE_THRESHOLD )
- return SCHED_PASSENGER_RUN_TO_ENTER_VEHICLE_FAILED;
- }
-
- // Fall through
-
- case TASK_GET_PATH_TO_NEAR_VEHICLE:
- m_flNextEnterAttempt = gpGlobals->curtime + 3.0f;
- break;
- }
-
- return BaseClass::SelectFailSchedule( failedSchedule, failedTask, taskFailCode );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Start to enter the vehicle
-//-----------------------------------------------------------------------------
-void CAI_PassengerBehaviorCompanion::EnterVehicle( void )
-{
- BaseClass::EnterVehicle();
-
- m_nExitAttempts = 0;
- m_VehicleMonitor.SetMark( m_hVehicle, 8.0f );
- m_flEnterBeginTime = gpGlobals->curtime;
-
- // Remove this flag because we're sitting so close we always think we're going to hit the player
- // FIXME: We need to store this state so we don't incorrectly restore it later
- GetOuter()->CapabilitiesRemove( bits_CAP_NO_HIT_PLAYER );
-
- // Discard enemies quickly
- GetOuter()->GetEnemies()->SetEnemyDiscardTime( 2.0f );
-
- SpeakIfAllowed( TLK_PASSENGER_BEGIN_ENTRANCE );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CAI_PassengerBehaviorCompanion::FinishEnterVehicle( void )
-{
- BaseClass::FinishEnterVehicle();
-
- // We succeeded
- ResetVehicleEntryFailedState();
-
- // Push this out into the future so we don't always fidget immediately in the vehicle
- ExtendFidgetDelay( random->RandomFloat( 4.0, 15.0f ) );
-
- SpeakIfAllowed( TLK_PASSENGER_FINISH_ENTRANCE );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CAI_PassengerBehaviorCompanion::ExitVehicle( void )
-{
- BaseClass::ExitVehicle();
-
- SpeakIfAllowed( TLK_PASSENGER_BEGIN_EXIT );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Vehicle has been completely exited
-//-----------------------------------------------------------------------------
-void CAI_PassengerBehaviorCompanion::FinishExitVehicle( void )
-{
- BaseClass::FinishExitVehicle();
-
- m_nExitAttempts = 0;
- m_VehicleMonitor.ClearMark();
-
- // FIXME: We need to store this state so we don't incorrectly restore it later
- GetOuter()->CapabilitiesAdd( bits_CAP_NO_HIT_PLAYER );
-
- // FIXME: Restore this properly
- GetOuter()->GetEnemies()->SetEnemyDiscardTime( AI_DEF_ENEMY_DISCARD_TIME );
-
- SpeakIfAllowed( TLK_PASSENGER_FINISH_EXIT );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Tries to build a route to the entry point of the target vehicle.
-// Output : Returns true on success, false on failure.
-//-----------------------------------------------------------------------------
-bool CAI_PassengerBehaviorCompanion::FindPathToVehicleEntryPoint( void )
-{
- // Set our custom move name
- // bool bFindNearest = ( GetOuter()->m_NPCState == NPC_STATE_COMBAT || GetOuter()->m_NPCState == NPC_STATE_ALERT );
- bool bFindNearest = true; // For the sake of quick gameplay, just make Alyx move directly!
- int nSequence = FindEntrySequence( bFindNearest );
- if ( nSequence == -1 )
- return false;
-
- // We have to do this specially because the activities are not named
- SetTransitionSequence( nSequence );
-
- // Get the entry position
- Vector vecEntryPoint;
- QAngle vecEntryAngles;
- if ( GetEntryPoint( m_nTransitionSequence, &vecEntryPoint, &vecEntryAngles ) == false )
- {
- MarkVehicleEntryFailed( vecEntryPoint );
- return false;
- }
-
- // If we're already close enough, just succeed
- float flDistToGoalSqr = ( GetOuter()->GetAbsOrigin() - vecEntryPoint ).LengthSqr();
- if ( flDistToGoalSqr < Square(3*12) )
- return true;
-
- // Setup our goal
- AI_NavGoal_t goal( GOALTYPE_LOCATION );
- // goal.arrivalActivity = ACT_SCRIPT_CUSTOM_MOVE;
- goal.dest = vecEntryPoint;
-
- // See if we need a radial route around the car, to our goal
- if ( UseRadialRouteToEntryPoint( vecEntryPoint ) )
- {
- // Find the bounding radius of the vehicle
- Vector vecCenterPoint = m_hVehicle->WorldSpaceCenter();
- vecCenterPoint.z = vecEntryPoint.z;
- bool bClockwise;
- float flArc = GetArcToEntryPoint( vecCenterPoint, vecEntryPoint, bClockwise );
- float flRadius = m_hVehicle->CollisionProp()->BoundingRadius2D();
-
- // Try and set a radial route
- if ( GetOuter()->GetNavigator()->SetRadialGoal( vecEntryPoint, vecCenterPoint, flRadius, flArc, 64.0f, bClockwise ) == false )
- {
- // Try the opposite way
- flArc = 360.0f - flArc;
-
- // Try the opposite way around
- if ( GetOuter()->GetNavigator()->SetRadialGoal( vecEntryPoint, vecCenterPoint, flRadius, flArc, 64.0f, !bClockwise ) == false )
- {
- // Try and set a direct route as a last resort
- if ( GetOuter()->GetNavigator()->SetGoal( goal ) == false )
- return false;
- }
- }
-
- // We found a goal
- GetOuter()->GetNavigator()->SetArrivalDirection( vecEntryAngles );
- GetOuter()->GetNavigator()->SetArrivalSpeed( 64.0f );
- return true;
- }
- else
- {
- // Try and set a direct route
- if ( GetOuter()->GetNavigator()->SetGoal( goal ) )
- {
- GetOuter()->GetNavigator()->SetArrivalDirection( vecEntryAngles );
- GetOuter()->GetNavigator()->SetArrivalSpeed( 64.0f );
- return true;
- }
- }
-
- // We failed, so remember it
- MarkVehicleEntryFailed( vecEntryPoint );
- return false;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Tests the route and position to see if it's valid
-// Input : &vecTestPos - position to test
-// Output : Returns true on success, false on failure.
-//-----------------------------------------------------------------------------
-bool CAI_PassengerBehaviorCompanion::CanExitAtPosition( const Vector &vecTestPos )
-{
- CBasePlayer *pPlayer = AI_GetSinglePlayer();
- if ( pPlayer == NULL )
- return false;
-
- // Can't be in our potential view
- if ( pPlayer->FInViewCone( vecTestPos ) )
- return false;
-
- // NOTE: There's no reason to do this since this is only called from a node's reported position
- // Find the exact ground at this position
- //Vector vecGroundPos;
- //if ( FindGroundAtPosition( vecTestPos, 16.0f, 64.0f, &vecGroundPos ) == false )
- // return false;
-
- // Get the ultimate position we'll end up at
- Vector vecStartPos;
- if ( m_hVehicle->GetServerVehicle()->NPC_GetPassengerSeatPosition( GetOuter(), &vecStartPos, NULL ) == false )
- return false;
-
- // See if we can move from where we are to that position in space
- if ( IsValidTransitionPoint( vecStartPos, vecTestPos ) == false )
- return false;
-
- // Trace down to the ground
- // FIXME: This piece of code is redundant and happening in IsValidTransitionPoint() as well
- /*
- Vector vecGroundPos;
- if ( FindGroundAtPosition( vecTestPos, GetOuter()->StepHeight(), 64.0f, &vecGroundPos ) == false )
- return false;
- */
-
- // Try and sweep a box through space and make sure it's clear of obstructions
- /*
- trace_t tr;
- CTraceFilterVehicleTransition skipFilter( GetOuter(), m_hVehicle, COLLISION_GROUP_NONE );
-
- // These are very approximated (and magical) numbers to allow passengers greater head room and leg room when transitioning
- Vector vecMins = GetOuter()->GetHullMins() + Vector( 0, 0, GetOuter()->StepHeight()*2.0f ); // FIXME:
- Vector vecMaxs = GetOuter()->GetHullMaxs() - Vector( 0, 0, GetOuter()->StepHeight() );
-
- UTIL_TraceHull( GetOuter()->GetAbsOrigin(), vecGroundPos, vecMins, vecMaxs, MASK_NPCSOLID, &skipFilter, &tr );
-
- // If we're blocked, we can't get out there
- if ( tr.fraction < 1.0f || tr.allsolid || tr.startsolid )
- {
- if ( passenger_debug_transition.GetBool() )
- {
- NDebugOverlay::SweptBox( GetOuter()->GetAbsOrigin(), vecGroundPos, vecMins, GetOuter()->GetHullMaxs(), vec3_angle, 255, 0, 0, 64, 2.0f );
- }
- return false;
- }
- */
-
- return true;
-}
-
-#define NUM_EXIT_ITERATIONS 8
-
-//-----------------------------------------------------------------------------
-// Purpose: Find a position we can use to exit the vehicle via teleportation
-// Input : *vecResult - safe place to exit to
-// Output : Returns true on success, false on failure.
-//-----------------------------------------------------------------------------
-bool CAI_PassengerBehaviorCompanion::GetStuckExitPos( Vector *vecResult )
-{
- // Get our right direction
- Vector vecVehicleRight;
- m_hVehicle->GetVectors( NULL, &vecVehicleRight, NULL );
-
- // Get the vehicle's rough horizontal bounds
- float flVehicleRadius = m_hVehicle->CollisionProp()->BoundingRadius2D();
-
- // Use the vehicle's center as our hub
- Vector vecCenter = m_hVehicle->WorldSpaceCenter();
-
- // Angle whose tan is: y/x
- float flCurAngle = atan2f( vecVehicleRight.y, vecVehicleRight.x );
- float flAngleIncr = (M_PI*2.0f)/(float)NUM_EXIT_ITERATIONS;
- Vector vecTestPos;
-
- // Test a number of discrete exit routes
- for ( int i = 0; i <= NUM_EXIT_ITERATIONS-1; i++ )
- {
- // Get our position
- SinCos( flCurAngle, &vecTestPos.y, &vecTestPos.x );
- vecTestPos.z = 0.0f;
- vecTestPos *= flVehicleRadius;
- vecTestPos += vecCenter;
-
- // Now find the nearest node and use that
- int nNearNode = GetOuter()->GetPathfinder()->NearestNodeToPoint( vecTestPos );
- if ( nNearNode != NO_NODE )
- {
- Vector vecNodePos = g_pBigAINet->GetNodePosition( GetOuter()->GetHullType(), nNearNode );
-
- // Test the position
- if ( CanExitAtPosition( vecNodePos ) )
- {
- // Take the result
- *vecResult = vecNodePos;
- return true;
- }
-
- // Move to the next iteration
- flCurAngle += flAngleIncr;
- }
- }
-
- // None found
- return false;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Attempt to get out of an overturned vehicle when the player isn't looking
-// Output : Returns true on success, false on failure.
-//-----------------------------------------------------------------------------
-bool CAI_PassengerBehaviorCompanion::ExitStuckVehicle( void )
-{
- // Try and find an exit position
- Vector vecExitPos;
- if ( GetStuckExitPos( &vecExitPos ) == false )
- return false;
-
- // Detach from the parent
- GetOuter()->SetParent( NULL );
-
- // Do all necessary clean-up
- FinishExitVehicle();
-
- // Teleport to the destination
- // TODO: Make sure that the player can't see this!
- GetOuter()->Teleport( &vecExitPos, &vec3_angle, &vec3_origin );
- GetOuter()->IncrementInterpolationFrame();
-
- return true;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CAI_PassengerBehaviorCompanion::StartTask( const Task_t *pTask )
-{
- // We need to override these so we never face
- if ( GetPassengerState() == PASSENGER_STATE_INSIDE )
- {
- if ( pTask->iTask == TASK_FACE_TARGET ||
- pTask->iTask == TASK_FACE_ENEMY ||
- pTask->iTask == TASK_FACE_IDEAL ||
- pTask->iTask == TASK_FACE_HINTNODE ||
- pTask->iTask == TASK_FACE_LASTPOSITION ||
- pTask->iTask == TASK_FACE_PATH ||
- pTask->iTask == TASK_FACE_PLAYER ||
- pTask->iTask == TASK_FACE_REASONABLE ||
- pTask->iTask == TASK_FACE_SAVEPOSITION ||
- pTask->iTask == TASK_FACE_SCRIPT )
- {
- return TaskComplete();
- }
- }
-
- switch ( pTask->iTask )
- {
- case TASK_RUN_TO_VEHICLE_ENTRANCE:
- {
- // Get a move on!
- GetOuter()->GetNavigator()->SetMovementActivity( ACT_RUN );
- }
- break;
-
- case TASK_GET_PATH_TO_VEHICLE_ENTRY_POINT:
- {
- if ( GetPassengerState() != PASSENGER_STATE_OUTSIDE )
- {
- Assert( 0 );
- TaskFail( "Trying to run while inside a vehicle!\n");
- return;
- }
-
- // Reserve an entry point
- if ( ReserveEntryPoint( VEHICLE_SEAT_ANY ) == false )
- {
- TaskFail( "No valid entry point!\n" );
- return;
- }
-
- // Find where we're going
- if ( FindPathToVehicleEntryPoint() )
- {
- TaskComplete();
- return;
- }
-
- // We didn't find a path
- TaskFail( "TASK_GET_PATH_TO_VEHICLE_ENTRY_POINT: Unable to run to entry point" );
- }
- break;
-
- case TASK_GET_PATH_TO_TARGET:
- {
- GetOuter()->SetTarget( m_hVehicle );
- BaseClass::StartTask( pTask );
- }
- break;
-
- case TASK_GET_PATH_TO_NEAR_VEHICLE:
- {
- if ( m_hVehicle == NULL )
- {
- TaskFail("Lost vehicle pointer\n");
- return;
- }
-
- // Find the passenger offset we're going for
- Vector vecRight;
- m_hVehicle->GetVectors( NULL, &vecRight, NULL );
- Vector vecTargetOffset = vecRight * 64.0f;
-
- // Try and find a path near there
- AI_NavGoal_t goal( GOALTYPE_TARGETENT, vecTargetOffset, AIN_DEF_ACTIVITY, 64.0f, AIN_UPDATE_TARGET_POS, m_hVehicle );
- GetOuter()->SetTarget( m_hVehicle );
- if ( GetOuter()->GetNavigator()->SetGoal( goal ) )
- {
- TaskComplete();
- return;
- }
-
- TaskFail( "Unable to find path to get closer to vehicle!\n" );
- return;
- }
-
- break;
-
- case TASK_PASSENGER_RELOAD:
- {
- GetOuter()->SetIdealActivity( ACT_PASSENGER_RELOAD );
- return;
- }
- break;
-
- case TASK_PASSENGER_EXIT_STUCK_VEHICLE:
- {
- if ( ExitStuckVehicle() )
- {
- TaskComplete();
- return;
- }
-
- TaskFail("Unable to exit overturned vehicle!\n");
- }
- break;
-
- case TASK_PASSENGER_OVERTURNED:
- {
- // Go into our overturned animation
- if ( GetOuter()->GetActivity() != ACT_PASSENGER_OVERTURNED )
- {
- GetOuter()->SetActivity( ACT_RESET );
- GetOuter()->SetActivity( ACT_PASSENGER_OVERTURNED );
- }
-
- TaskComplete();
- }
- break;
-
- case TASK_PASSENGER_IMPACT:
- {
- // Stomp anything currently playing on top of us, this has to take priority
- GetOuter()->RemoveAllGestures();
-
- // Go into our impact animation
- GetOuter()->ResetIdealActivity( ACT_PASSENGER_IMPACT );
-
- // Delay for twice the duration of our impact animation
- int nSequence = GetOuter()->SelectWeightedSequence( ACT_PASSENGER_IMPACT );
- float flSeqDuration = GetOuter()->SequenceDuration( nSequence );
- float flStunTime = flSeqDuration + random->RandomFloat( 1.0f, 2.0f );
- GetOuter()->SetNextAttack( gpGlobals->curtime + flStunTime );
- ExtendFidgetDelay( flStunTime );
- }
- break;
-
- default:
- BaseClass::StartTask( pTask );
- break;
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-bool CAI_PassengerBehaviorCompanion::IsCurTaskContinuousMove( void )
-{
- const Task_t *pCurTask = GetCurTask();
- if ( pCurTask && pCurTask->iTask == TASK_RUN_TO_VEHICLE_ENTRANCE )
- return true;
-
- return BaseClass::IsCurTaskContinuousMove();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Update our path if we're running towards the vehicle (since it can move)
-//-----------------------------------------------------------------------------
-bool CAI_PassengerBehaviorCompanion::UpdateVehicleEntrancePath( void )
-{
- // If it's too soon to check again, don't bother
- if ( m_flEntraceUpdateTime > gpGlobals->curtime )
- return true;
-
- // Find out if we need to update
- if ( m_VehicleMonitor.TargetMoved2D( m_hVehicle ) == false )
- {
- m_flEntraceUpdateTime = gpGlobals->curtime + 0.5f;
- return true;
- }
-
- // Don't attempt again for some amount of time
- m_flEntraceUpdateTime = gpGlobals->curtime + 1.0f;
-
- int nSequence = FindEntrySequence( true );
- if ( nSequence == -1 )
- return false;
-
- SetTransitionSequence( nSequence );
-
- // Get the entry position
- Vector vecEntryPoint;
- QAngle vecEntryAngles;
- if ( GetEntryPoint( m_nTransitionSequence, &vecEntryPoint, &vecEntryAngles ) == false )
- return false;
-
- // Move the entry point forward in time a bit to predict where it'll be
- Vector vecVehicleSpeed = m_hVehicle->GetSmoothedVelocity();
-
- // Tack on the smoothed velocity
- vecEntryPoint += vecVehicleSpeed; // one second
-
- // Update our entry point
- if ( GetOuter()->GetNavigator()->UpdateGoalPos( vecEntryPoint ) == false )
- return false;
-
- // Reset the goal angles
- GetNavigator()->SetArrivalDirection( vecEntryAngles );
-
- return true;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CAI_PassengerBehaviorCompanion::RunTask( const Task_t *pTask )
-{
- switch ( pTask->iTask )
- {
- case TASK_PASSENGER_RELOAD:
- {
- if ( GetOuter()->IsSequenceFinished() )
- {
- TaskComplete();
- }
- }
- break;
-
- case TASK_PASSENGER_IMPACT:
- {
- if ( GetOuter()->IsSequenceFinished() )
- {
- TaskComplete();
- return;
- }
- }
- break;
-
- case TASK_RUN_TO_VEHICLE_ENTRANCE:
- {
- // Update our entrance point if we can
- if ( UpdateVehicleEntrancePath() == false )
- {
- TaskFail("Unable to find entrance to vehicle");
- break;
- }
-
- // See if we're close enough to our goal
- if ( GetOuter ()->GetNavigator()->IsGoalActive() == false )
- {
- // See if we're close enough now to enter the vehicle
- Vector vecEntryPoint;
- GetEntryPoint( m_nTransitionSequence, &vecEntryPoint );
- if ( ( vecEntryPoint - GetAbsOrigin() ).Length2DSqr() < Square( 36.0f ) )
- {
- if ( GetNavigator()->GetArrivalActivity() != ACT_INVALID )
- {
- SetActivity( GetNavigator()->GetArrivalActivity() );
- }
-
- TaskComplete();
- }
- else
- {
- TaskFail( "Unable to navigate to vehicle" );
- }
- }
-
- // Keep merrily going!
- }
- break;
-
- default:
- BaseClass::RunTask( pTask );
- break;
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Add custom interrupt conditions
-//-----------------------------------------------------------------------------
-void CAI_PassengerBehaviorCompanion::BuildScheduleTestBits( void )
-{
- // Always break on being able to exit
- if ( GetPassengerState() == PASSENGER_STATE_INSIDE )
- {
- GetOuter()->SetCustomInterruptCondition( GetClassScheduleIdSpace()->ConditionLocalToGlobal( COND_PASSENGER_CAN_LEAVE_STUCK_VEHICLE ) );
- GetOuter()->SetCustomInterruptCondition( GetClassScheduleIdSpace()->ConditionLocalToGlobal( COND_PASSENGER_HARD_IMPACT) );
-
- if ( IsCurSchedule( SCHED_PASSENGER_OVERTURNED ) == false )
- {
- GetOuter()->SetCustomInterruptCondition( GetClassScheduleIdSpace()->ConditionLocalToGlobal( COND_PASSENGER_OVERTURNED ) );
- }
-
- // Append the ability to break on fidgeting
- if ( IsCurSchedule( SCHED_PASSENGER_IDLE ) )
- {
- GetOuter()->SetCustomInterruptCondition( GetClassScheduleIdSpace()->ConditionLocalToGlobal( COND_PASSENGER_CAN_FIDGET ) );
- }
-
- // Add this so we're prompt about exiting the vehicle when able to
- if ( m_PassengerIntent == PASSENGER_INTENT_EXIT )
- {
- GetOuter()->SetCustomInterruptCondition( GetClassScheduleIdSpace()->ConditionLocalToGlobal( COND_PASSENGER_VEHICLE_STOPPED ) );
- }
- }
-
- BaseClass::BuildScheduleTestBits();
-}
-//-----------------------------------------------------------------------------
-// Purpose: Determines if the passenger should take a radial route to the goal
-// Input : &vecEntryPoint - Point of entry
-// Output : Returns true on success, false on failure.
-//-----------------------------------------------------------------------------
-bool CAI_PassengerBehaviorCompanion::UseRadialRouteToEntryPoint( const Vector &vecEntryPoint )
-{
- // Get the center position of the vehicle we'll radiate around
- Vector vecCenterPos = m_hVehicle->WorldSpaceCenter();
- vecCenterPos.z = vecEntryPoint.z;
-
- // Find out if we need to go around the vehicle
- float flDistToVehicleCenter = ( vecCenterPos - GetOuter()->GetAbsOrigin() ).Length();
- float flDistToGoal = ( vecEntryPoint - GetOuter()->GetAbsOrigin() ).Length();
- if ( flDistToGoal > flDistToVehicleCenter )
- return true;
-
- return false;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Find the arc in degrees to reach our goal position
-// Input : &vecCenterPoint - Point around which the arc rotates
-// &vecEntryPoint - Point we're trying to reach
-// &bClockwise - If we should move clockwise or not to get there
-// Output : float - degrees around arc to follow
-//-----------------------------------------------------------------------------
-float CAI_PassengerBehaviorCompanion::GetArcToEntryPoint( const Vector &vecCenterPoint, const Vector &vecEntryPoint, bool &bClockwise )
-{
- // We want the entry point to be at the same level as the center to make this a two dimensional problem
- Vector vecEntryPointAdjusted = vecEntryPoint;
- vecEntryPointAdjusted.z = vecCenterPoint.z;
-
- // Direction from vehicle center to passenger
- Vector vecVehicleToPassenger = ( GetOuter()->GetAbsOrigin() - vecCenterPoint );
- VectorNormalize( vecVehicleToPassenger );
-
- // Direction from vehicle center to entry point
- Vector vecVehicleToEntry = ( vecEntryPointAdjusted - vecCenterPoint );
- VectorNormalize( vecVehicleToEntry );
-
- float flVehicleToPassengerYaw = UTIL_VecToYaw( vecVehicleToPassenger );
- float flVehicleToEntryYaw = UTIL_VecToYaw( vecVehicleToEntry );
- float flArcDist = UTIL_AngleDistance( flVehicleToEntryYaw, flVehicleToPassengerYaw );
-
- bClockwise = ( flArcDist < 0.0f );
- return fabs( flArcDist );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Removes all failed points
-//-----------------------------------------------------------------------------
-void CAI_PassengerBehaviorCompanion::ResetVehicleEntryFailedState( void )
-{
- m_FailedEntryPositions.RemoveAll();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Adds a failed position to the list and marks when it occurred
-// Input : &vecPosition - Position that failed
-//-----------------------------------------------------------------------------
-void CAI_PassengerBehaviorCompanion::MarkVehicleEntryFailed( const Vector &vecPosition )
-{
- FailPosition_t failPos;
- failPos.flTime = gpGlobals->curtime;
- failPos.vecPosition = vecPosition;
- m_FailedEntryPositions.AddToTail( failPos );
-
- // Show this as failed
- if ( passenger_debug_entry.GetBool() )
- {
- NDebugOverlay::Box( vecPosition, -Vector(8,8,8), Vector(8,8,8), 255, 0, 0, 0, 2.0f );
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: See if a vector is near enough to a previously failed position
-// Input : &vecPosition - position to test
-// Output : Returns true if the point is near enough another to be considered equivalent
-//-----------------------------------------------------------------------------
-bool CAI_PassengerBehaviorCompanion::PointIsWithinEntryFailureRadius( const Vector &vecPosition )
-{
- // Test this point against our known failed points and reject it if it's too near
- for ( int i = 0; i < m_FailedEntryPositions.Count(); i++ )
- {
- // If our time has expired, kill the position
- if ( ( gpGlobals->curtime - m_FailedEntryPositions[i].flTime ) > 3.0f )
- {
- // Show that we've cleared it
- if ( passenger_debug_entry.GetBool() )
- {
- NDebugOverlay::Box( m_FailedEntryPositions[i].vecPosition, -Vector(12,12,12), Vector(12,12,12), 255, 255, 0, 0, 2.0f );
- }
-
- m_FailedEntryPositions.Remove( i );
- continue;
- }
-
- // See if this position is too near our last failed attempt
- if ( ( vecPosition - m_FailedEntryPositions[i].vecPosition ).LengthSqr() < Square(3*12) )
- {
- // Show that this was denied
- if ( passenger_debug_entry.GetBool() )
- {
- NDebugOverlay::Box( m_FailedEntryPositions[i].vecPosition, -Vector(12,12,12), Vector(12,12,12), 255, 0, 0, 128, 2.0f );
- }
-
- return true;
- }
- }
-
- return false;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Find the proper sequence to use (weighted by priority or distance from current position)
-// to enter the vehicle.
-// Input : bNearest - Use distance as the criteria for a "best" sequence. Otherwise the order of the
-// seats is their priority.
-// Output : int - sequence index
-//-----------------------------------------------------------------------------
-int CAI_PassengerBehaviorCompanion::FindEntrySequence( bool bNearest /*= false*/ )
-{
- // Get a list of all our animations
- const PassengerSeatAnims_t *pEntryAnims = m_hVehicle->GetServerVehicle()->NPC_GetPassengerSeatAnims( GetOuter(), PASSENGER_SEAT_ENTRY );
- if ( pEntryAnims == NULL )
- return -1;
-
- // Get the ultimate position we'll end up at
- Vector vecStartPos, vecEndPos;
- if ( m_hVehicle->GetServerVehicle()->NPC_GetPassengerSeatPosition( GetOuter(), &vecEndPos, NULL ) == false )
- return -1;
-
- const CPassengerSeatTransition *pTransition;
- float flNearestDistSqr = FLT_MAX;
- float flSeatDistSqr;
- int nNearestSequence = -1;
- int nSequence;
-
- // Test each animation (sorted by priority) for the best match
- for ( int i = 0; i < pEntryAnims->Count(); i++ )
- {
- // Find the activity for this animation name
- pTransition = &pEntryAnims->Element(i);
- nSequence = GetOuter()->LookupSequence( STRING( pTransition->GetAnimationName() ) );
- if ( nSequence == -1 )
- continue;
-
- // Test this entry for validity
- if ( GetEntryPoint( nSequence, &vecStartPos ) == false )
- continue;
-
- // See if this entry position is in our list of known unreachable places
- if ( PointIsWithinEntryFailureRadius( vecStartPos ) )
- continue;
-
- // Check to see if we can use this
- if ( IsValidTransitionPoint( vecStartPos, vecEndPos ) )
- {
- // If we're just looking for the first, we're done
- if ( bNearest == false )
- return nSequence;
-
- // Otherwise distance is the deciding factor
- flSeatDistSqr = ( vecStartPos - GetOuter()->GetAbsOrigin() ).LengthSqr();
-
- // Closer, take it
- if ( flSeatDistSqr < flNearestDistSqr )
- {
- flNearestDistSqr = flSeatDistSqr;
- nNearestSequence = nSequence;
- }
- }
-
- }
-
- return nNearestSequence;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Override certain animations
-//-----------------------------------------------------------------------------
-Activity CAI_PassengerBehaviorCompanion::NPC_TranslateActivity( Activity activity )
-{
- Activity newActivity = BaseClass::NPC_TranslateActivity( activity );
-
- // Handle animations from inside the vehicle
- if ( GetPassengerState() == PASSENGER_STATE_INSIDE )
- {
- // Alter idle depending on the vehicle's state
- if ( newActivity == ACT_IDLE )
- {
- // Always play the overturned animation
- if ( m_vehicleState.m_bWasOverturned )
- return ACT_PASSENGER_OVERTURNED;
- }
- }
-
- return newActivity;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Output : Returns true on success, false on failure.
-//-----------------------------------------------------------------------------
-bool CAI_PassengerBehaviorCompanion::CanExitVehicle( void )
-{
- if ( BaseClass::CanExitVehicle() == false )
- return false;
-
- // If we're tipped too much, we can't exit
- Vector vecUp;
- GetOuter()->GetVectors( NULL, NULL, &vecUp );
- if ( DotProduct( vecUp, Vector(0,0,1) ) < DOT_45DEGREE )
- return false;
-
- return true;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: NPC needs to get to their marks, so do so with urgent navigation
-//-----------------------------------------------------------------------------
-bool CAI_PassengerBehaviorCompanion::IsNavigationUrgent( void )
-{
- // If we're running to the vehicle, do so urgently
- if ( GetPassengerState() == PASSENGER_STATE_OUTSIDE && m_PassengerIntent == PASSENGER_INTENT_ENTER )
- return true;
-
- return BaseClass::IsNavigationUrgent();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Calculate our body lean based on our delta velocity
-//-----------------------------------------------------------------------------
-void CAI_PassengerBehaviorCompanion::CalculateBodyLean( void )
-{
- // Calculate our lateral displacement from a perfectly centered start
- float flLateralDisp = SimpleSplineRemapVal( m_vehicleState.m_vecLastAngles.z, 100.0f, -100.0f, -1.0f, 1.0f );
- flLateralDisp = clamp( flLateralDisp, -1.0f, 1.0f );
-
- // FIXME: Framerate dependent!
- m_flLastLateralLean = ( m_flLastLateralLean * 0.2f ) + ( flLateralDisp * 0.8f );
-
- // Here we can make Alyx do something different on an "extreme" lean condition
- if ( fabs( m_flLastLateralLean ) > 0.75f )
- {
- // Large lean, make us react?
- }
-
- // Set these parameters
- GetOuter()->SetPoseParameter( "vehicle_lean", m_flLastLateralLean );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Whether or not we're allowed to fidget
-//-----------------------------------------------------------------------------
-bool CAI_PassengerBehaviorCompanion::CanFidget( void )
-{
- // Can't fidget again too quickly
- if ( m_flNextFidgetTime > gpGlobals->curtime )
- return false;
-
- // FIXME: Really we want to check our readiness level at this point
- if ( GetOuter()->GetEnemy() != NULL )
- return false;
-
- // Don't fidget unless we're at low readiness
- if ( m_hCompanion && ( m_hCompanion->GetReadinessLevel() > AIRL_RELAXED ) )
- return false;
-
- // Don't fidget while we're in a script
- if ( GetOuter()->IsInAScript() || GetOuter()->GetIdealState() == NPC_STATE_SCRIPT || IsRunningScriptedScene( GetOuter() ) )
- return false;
-
- // If we're upside down, don't bother
- if ( HasCondition( COND_PASSENGER_OVERTURNED ) )
- return false;
-
- // Must be visible to the player
- CBasePlayer *pPlayer = AI_GetSinglePlayer();
- if ( pPlayer && pPlayer->FInViewCone( GetOuter()->EyePosition() ) == false )
- return false;
-
- return true;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Extends the fidget delay by the time specified
-// Input : flDuration - in seconds
-//-----------------------------------------------------------------------------
-void CAI_PassengerBehaviorCompanion::ExtendFidgetDelay( float flDuration )
-{
- // If we're already expired, just set this as the next time
- if ( m_flNextFidgetTime < gpGlobals->curtime )
- {
- m_flNextFidgetTime = gpGlobals->curtime + flDuration;
- }
- else
- {
- // Otherwise bump the delay farther into the future
- m_flNextFidgetTime += flDuration;
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: We never want to be marked as crouching when inside a vehicle
-//-----------------------------------------------------------------------------
-bool CAI_PassengerBehaviorCompanion::IsCrouching( void )
-{
- return false;
-}
-
-AI_BEGIN_CUSTOM_SCHEDULE_PROVIDER( CAI_PassengerBehaviorCompanion )
-{
- DECLARE_ACTIVITY( ACT_PASSENGER_IDLE_AIM )
- DECLARE_ACTIVITY( ACT_PASSENGER_RELOAD )
- DECLARE_ACTIVITY( ACT_PASSENGER_OVERTURNED )
- DECLARE_ACTIVITY( ACT_PASSENGER_IMPACT )
- DECLARE_ACTIVITY( ACT_PASSENGER_IMPACT_WEAPON )
- DECLARE_ACTIVITY( ACT_PASSENGER_POINT )
- DECLARE_ACTIVITY( ACT_PASSENGER_POINT_BEHIND )
- DECLARE_ACTIVITY( ACT_PASSENGER_IDLE_READY )
- DECLARE_ACTIVITY( ACT_PASSENGER_GESTURE_JOSTLE_LARGE )
- DECLARE_ACTIVITY( ACT_PASSENGER_GESTURE_JOSTLE_SMALL )
- DECLARE_ACTIVITY( ACT_PASSENGER_GESTURE_JOSTLE_LARGE_STIMULATED )
- DECLARE_ACTIVITY( ACT_PASSENGER_GESTURE_JOSTLE_SMALL_STIMULATED )
- DECLARE_ACTIVITY( ACT_PASSENGER_COWER_IN )
- DECLARE_ACTIVITY( ACT_PASSENGER_COWER_LOOP )
- DECLARE_ACTIVITY( ACT_PASSENGER_COWER_OUT )
- DECLARE_ACTIVITY( ACT_PASSENGER_IDLE_FIDGET )
-
- DECLARE_TASK( TASK_GET_PATH_TO_VEHICLE_ENTRY_POINT )
- DECLARE_TASK( TASK_GET_PATH_TO_NEAR_VEHICLE )
- DECLARE_TASK( TASK_PASSENGER_RELOAD )
- DECLARE_TASK( TASK_PASSENGER_EXIT_STUCK_VEHICLE )
- DECLARE_TASK( TASK_PASSENGER_OVERTURNED )
- DECLARE_TASK( TASK_PASSENGER_IMPACT )
- DECLARE_TASK( TASK_RUN_TO_VEHICLE_ENTRANCE )
-
- DECLARE_CONDITION( COND_PASSENGER_VEHICLE_MOVED_FROM_MARK )
- DECLARE_CONDITION( COND_PASSENGER_CAN_LEAVE_STUCK_VEHICLE )
- DECLARE_CONDITION( COND_PASSENGER_WARN_OVERTURNED )
- DECLARE_CONDITION( COND_PASSENGER_WARN_COLLISION )
- DECLARE_CONDITION( COND_PASSENGER_CAN_FIDGET )
- DECLARE_CONDITION( COND_PASSENGER_CAN_ENTER_IMMEDIATELY )
-
- DEFINE_SCHEDULE
- (
- SCHED_PASSENGER_RUN_TO_ENTER_VEHICLE,
-
- " Tasks"
- " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_PASSENGER_RUN_TO_ENTER_VEHICLE_FAILED"
- " TASK_STOP_MOVING 0"
- " TASK_SET_TOLERANCE_DISTANCE 36" // 3 ft
- " TASK_SET_ROUTE_SEARCH_TIME 5"
- " TASK_GET_PATH_TO_VEHICLE_ENTRY_POINT 0"
- " TASK_RUN_TO_VEHICLE_ENTRANCE 0"
- " TASK_SET_SCHEDULE SCHEDULE:SCHED_PASSENGER_ENTER_VEHICLE"
- ""
- " Interrupts"
- " COND_PASSENGER_CAN_ENTER_IMMEDIATELY"
- " COND_PASSENGER_CANCEL_ENTER"
- )
-
- DEFINE_SCHEDULE
- (
- SCHED_PASSENGER_RUN_TO_ENTER_VEHICLE_FAILED,
-
- " Tasks"
- " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_PASSENGER_ENTER_VEHICLE_PAUSE"
- " TASK_STOP_MOVING 0"
- " TASK_SET_TOLERANCE_DISTANCE 36"
- " TASK_SET_ROUTE_SEARCH_TIME 3"
- " TASK_GET_PATH_TO_NEAR_VEHICLE 0"
- " TASK_RUN_PATH 0"
- " TASK_WAIT_FOR_MOVEMENT 0"
- ""
- " Interrupts"
- " COND_PASSENGER_CANCEL_ENTER"
- )
-
- DEFINE_SCHEDULE
- (
- SCHED_PASSENGER_ENTER_VEHICLE_PAUSE,
-
- " Tasks"
- " TASK_STOP_MOVING 1"
- " TASK_FACE_TARGET 0"
- " TASK_WAIT 2"
- ""
- " Interrupts"
- " COND_LIGHT_DAMAGE"
- " COND_NEW_ENEMY"
- " COND_PASSENGER_CANCEL_ENTER"
- )
-
- DEFINE_SCHEDULE
- (
- SCHED_PASSENGER_RANGE_ATTACK1,
-
- " Tasks"
- " TASK_ANNOUNCE_ATTACK 1" // 1 = primary attack
- " TASK_RANGE_ATTACK1 0"
- ""
- " Interrupts"
- " COND_NEW_ENEMY"
- " COND_ENEMY_DEAD"
- " COND_LIGHT_DAMAGE"
- " COND_HEAVY_DAMAGE"
- " COND_ENEMY_OCCLUDED"
- " COND_NO_PRIMARY_AMMO"
- " COND_HEAR_DANGER"
- " COND_WEAPON_BLOCKED_BY_FRIEND"
- " COND_WEAPON_SIGHT_OCCLUDED"
- )
-
- DEFINE_SCHEDULE
- (
- SCHED_PASSENGER_EXIT_STUCK_VEHICLE,
-
- " Tasks"
- " TASK_PASSENGER_EXIT_STUCK_VEHICLE 0"
- ""
- " Interrupts"
- )
-
- DEFINE_SCHEDULE
- (
- SCHED_PASSENGER_RELOAD,
-
- " Tasks"
- " TASK_PASSENGER_RELOAD 0"
- ""
- " Interrupts"
- )
-
- DEFINE_SCHEDULE
- (
- SCHED_PASSENGER_OVERTURNED,
-
- " Tasks"
- " TASK_PASSENGER_OVERTURNED 0"
- ""
- " Interrupts"
- )
-
- DEFINE_SCHEDULE
- (
- SCHED_PASSENGER_IMPACT,
-
- " Tasks"
- " TASK_PASSENGER_IMPACT 0"
- ""
- " Interrupts"
- )
-
- DEFINE_SCHEDULE
- (
- SCHED_PASSENGER_ENTER_VEHICLE_IMMEDIATELY,
-
- " Tasks"
- " TASK_PASSENGER_ATTACH_TO_VEHICLE 0"
- " TASK_PASSENGER_ENTER_VEHICLE 0"
- ""
- " Interrupts"
- " COND_NO_CUSTOM_INTERRUPTS"
- )
-
- DEFINE_SCHEDULE
- (
- SCHED_PASSENGER_COWER,
-
- " Tasks"
- " TASK_PLAY_SEQUENCE ACTIVITY:ACT_PASSENGER_COWER_IN"
- " TASK_PLAY_SEQUENCE ACTIVITY:ACT_PASSENGER_COWER_LOOP"
- " TASK_WAIT_UNTIL_NO_DANGER_SOUND 0"
- " TASK_WAIT 2"
- " TASK_PLAY_SEQUENCE ACTIVITY:ACT_PASSENGER_COWER_OUT"
- ""
- " Interrupts"
- " COND_NO_CUSTOM_INTERRUPTS"
- )
-
- DEFINE_SCHEDULE
- (
- SCHED_PASSENGER_FIDGET,
-
- " Tasks"
- " TASK_PLAY_SEQUENCE ACTIVITY:ACT_PASSENGER_IDLE_FIDGET"
- ""
- " Interrupts"
- " COND_NO_CUSTOM_INTERRUPTS"
- )
-
- AI_END_CUSTOM_SCHEDULE_PROVIDER()
-}
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Companion NPCs riding in cars
+//
+//=============================================================================
+
+#include "cbase.h"
+#include "ai_speech.h"
+#include "ai_pathfinder.h"
+#include "ai_waypoint.h"
+#include "ai_navigator.h"
+#include "ai_navgoaltype.h"
+#include "ai_memory.h"
+#include "ai_behavior_passenger_companion.h"
+#include "ai_squadslot.h"
+#include "npc_playercompanion.h"
+#include "ai_route.h"
+#include "saverestore_utlvector.h"
+#include "cplane.h"
+#include "util_shared.h"
+#include "sceneentity.h"
+
+bool SphereWithinPlayerFOV( CBasePlayer *pPlayer, const Vector &vecCenter, float flRadius );
+
+#define PASSENGER_NEAR_VEHICLE_THRESHOLD 64.0f
+
+#define MIN_OVERTURNED_DURATION 1.0f // seconds
+#define MIN_FAILED_EXIT_ATTEMPTS 4
+#define MIN_OVERTURNED_WARN_DURATION 4.0f // seconds
+
+ConVar passenger_collision_response_threshold( "passenger_collision_response_threshold", "250.0" );
+ConVar passenger_debug_entry( "passenger_debug_entry", "0" );
+ConVar passenger_use_leaning("passenger_use_leaning", "1" );
+extern ConVar passenger_debug_transition;
+
+// Custom activities
+Activity ACT_PASSENGER_IDLE_AIM;
+Activity ACT_PASSENGER_RELOAD;
+Activity ACT_PASSENGER_OVERTURNED;
+Activity ACT_PASSENGER_IMPACT;
+Activity ACT_PASSENGER_IMPACT_WEAPON;
+Activity ACT_PASSENGER_POINT;
+Activity ACT_PASSENGER_POINT_BEHIND;
+Activity ACT_PASSENGER_IDLE_READY;
+Activity ACT_PASSENGER_GESTURE_JOSTLE_LARGE;
+Activity ACT_PASSENGER_GESTURE_JOSTLE_SMALL;
+Activity ACT_PASSENGER_GESTURE_JOSTLE_LARGE_STIMULATED;
+Activity ACT_PASSENGER_GESTURE_JOSTLE_SMALL_STIMULATED;
+Activity ACT_PASSENGER_COWER_IN;
+Activity ACT_PASSENGER_COWER_LOOP;
+Activity ACT_PASSENGER_COWER_OUT;
+Activity ACT_PASSENGER_IDLE_FIDGET;
+
+BEGIN_DATADESC( CAI_PassengerBehaviorCompanion )
+
+ DEFINE_EMBEDDED( m_VehicleMonitor ),
+
+ DEFINE_UTLVECTOR( m_FailedEntryPositions, FIELD_EMBEDDED ),
+
+ DEFINE_FIELD( m_flOverturnedDuration, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flUnseenDuration, FIELD_FLOAT ),
+ DEFINE_FIELD( m_nExitAttempts, FIELD_INTEGER ),
+ DEFINE_FIELD( m_flNextOverturnWarning, FIELD_TIME ),
+ DEFINE_FIELD( m_flEnterBeginTime, FIELD_TIME ),
+ DEFINE_FIELD( m_hCompanion, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_flNextJostleTime, FIELD_TIME ),
+ DEFINE_FIELD( m_nVisibleEnemies, FIELD_INTEGER ),
+ DEFINE_FIELD( m_flLastLateralLean, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flEntraceUpdateTime, FIELD_TIME ),
+ DEFINE_FIELD( m_flNextEnterAttempt, FIELD_TIME ),
+ DEFINE_FIELD( m_flNextFidgetTime, FIELD_TIME ),
+
+END_DATADESC();
+
+BEGIN_SIMPLE_DATADESC( FailPosition_t )
+
+ DEFINE_FIELD( vecPosition, FIELD_VECTOR ),
+ DEFINE_FIELD( flTime, FIELD_TIME ),
+
+END_DATADESC();
+
+CAI_PassengerBehaviorCompanion::CAI_PassengerBehaviorCompanion( void ) :
+m_flUnseenDuration( 0.0f ),
+m_flNextOverturnWarning( 0.0f ),
+m_flOverturnedDuration( 0.0f ),
+m_nExitAttempts( 0 ),
+m_flNextEnterAttempt( 0.0f ),
+m_flLastLateralLean( 0.0f ),
+m_flNextJostleTime( 0.0f )
+{
+ memset( &m_vehicleState, 0, sizeof( m_vehicleState ) );
+ m_VehicleMonitor.ClearMark();
+}
+
+void CAI_PassengerBehaviorCompanion::Enable( CPropJeepEpisodic *pVehicle, bool bImmediateEnter /*= false*/ )
+{
+ BaseClass::Enable( pVehicle );
+
+ // Store this up for quick reference later on
+ m_hCompanion = dynamic_cast<CNPC_PlayerCompanion *>(GetOuter());
+
+ // See if we want to sit in the vehicle immediately
+ if ( bImmediateEnter )
+ {
+ // Find the seat and sit in it
+ if ( ReserveEntryPoint( VEHICLE_SEAT_ANY ) )
+ {
+ // Attach
+ AttachToVehicle();
+
+ // This will slam us into the right position and clean up
+ FinishEnterVehicle();
+ GetOuter()->IncrementInterpolationFrame();
+
+ // Start our schedule immediately
+ ClearSchedule( "Immediate entry to vehicle" );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Set up the shot regulator based on the equipped weapon
+//-----------------------------------------------------------------------------
+void CAI_PassengerBehaviorCompanion::OnUpdateShotRegulator( void )
+{
+ if ( GetVehicleSpeed() > 250 )
+ {
+ // Default values
+ GetOuter()->GetShotRegulator()->SetBurstInterval( 0.1f, 0.5f );
+ GetOuter()->GetShotRegulator()->SetBurstShotCountRange( 1, 4 );
+ GetOuter()->GetShotRegulator()->SetRestInterval( 0.25f, 1.0f );
+ }
+ else
+ {
+ BaseClass::OnUpdateShotRegulator();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CAI_PassengerBehaviorCompanion::IsValidEnemy( CBaseEntity *pEntity )
+{
+ // The target must be much closer in the vehicle
+ float flDistSqr = ( pEntity->GetAbsOrigin() - GetAbsOrigin() ).LengthSqr();
+ if ( flDistSqr > Square( (40*12) ) && pEntity->Classify() != CLASS_BULLSEYE )
+ return false;
+
+ // Determine if the target is going to move past us?
+ return BaseClass::IsValidEnemy( pEntity );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns the speed the vehicle is moving at
+// Output : units per second
+//-----------------------------------------------------------------------------
+float CAI_PassengerBehaviorCompanion::GetVehicleSpeed( void )
+{
+ if ( m_hVehicle == NULL )
+ {
+ Assert(0);
+ return -1.0f;
+ }
+
+ Vector vecVelocity;
+ m_hVehicle->GetVelocity( &vecVelocity, NULL );
+
+ // Get our speed
+ return vecVelocity.Length();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Detect oncoming collisions
+//-----------------------------------------------------------------------------
+void CAI_PassengerBehaviorCompanion::GatherVehicleCollisionConditions( const Vector &localVelocity )
+{
+ // Look for walls in front of us
+ if ( localVelocity.y > passenger_collision_response_threshold.GetFloat() )
+ {
+ // Detect an upcoming collision
+ Vector vForward;
+ m_hVehicle->GetVectors( &vForward, NULL, NULL );
+
+ // Use a smaller bounding box to make it detect mostly head-on impacts
+ Vector mins, maxs;
+ mins.Init( -24, -24, 32 );
+ maxs.Init( 24, 24, 64 );
+
+ float dt = 0.6f; // Seconds
+ float distance = localVelocity.y * dt;
+
+ // Find our angular velocity as a vector
+ Vector vecAngularVelocity;
+ vecAngularVelocity.z = 0.0f;
+ SinCos( DEG2RAD( m_vehicleState.m_vecLastAngles.z * dt ), &vecAngularVelocity.y, &vecAngularVelocity.x );
+
+ Vector vecOffset;
+ VectorRotate( vecAngularVelocity, m_hVehicle->GetAbsAngles() + QAngle( 0, 90, 0 ), vecOffset );
+
+ vForward += vecOffset;
+ VectorNormalize( vForward );
+
+ // Trace ahead of us to see what's there
+ CTraceFilterNoNPCsOrPlayer filter( m_hVehicle, COLLISION_GROUP_NONE ); // We don't care about NPCs or the player (certainly if they're in the vehicle!)
+
+ trace_t tr;
+ UTIL_TraceHull( m_hVehicle->GetAbsOrigin(), m_hVehicle->GetAbsOrigin() + ( vForward * distance ), mins, maxs, MASK_SOLID, &filter, &tr );
+
+ bool bWarnCollision = true;
+ if ( tr.DidHit() )
+ {
+ // We need to see how "head-on" to the surface we are
+ float impactDot = DotProduct( tr.plane.normal, vForward );
+
+ // Don't warn over grazing blows or slopes
+ if ( impactDot < -0.9f && tr.plane.normal.z < 0.75f )
+ {
+ // Make sure this is a worthwhile thing to warn about
+ if ( tr.m_pEnt )
+ {
+ // If it's physical and moveable, then ignore it because we'll probably smash or move it
+ IPhysicsObject *pObject = tr.m_pEnt->VPhysicsGetObject();
+ if ( pObject && pObject->IsMoveable() )
+ {
+ bWarnCollision = false;
+ }
+ }
+
+ // Note that we should say something to the player about it
+ if ( bWarnCollision )
+ {
+ SetCondition( COND_PASSENGER_WARN_COLLISION );
+ }
+ }
+ }
+ }
+
+ if ( passenger_use_leaning.GetBool() )
+ {
+ // Calculate how our body is leaning
+ CalculateBodyLean();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Speak various lines about the state of the vehicle
+//-----------------------------------------------------------------------------
+void CAI_PassengerBehaviorCompanion::SpeakVehicleConditions( void )
+{
+ Assert( m_hVehicle != NULL );
+ if ( m_hVehicle == NULL )
+ return;
+
+ // Speak if we just hit something
+ if ( HasCondition( COND_PASSENGER_HARD_IMPACT ) )
+ {
+ SpeakIfAllowed( TLK_PASSENGER_IMPACT );
+ }
+
+ // Speak if we're overturned
+ if ( HasCondition( COND_PASSENGER_OVERTURNED ) )
+ {
+ SpeakIfAllowed( TLK_PASSENGER_OVERTURNED );
+ }
+
+ // Speak if we're about to hit something
+ if ( HasCondition( COND_PASSENGER_WARN_COLLISION ) )
+ {
+ // Make Alyx look at the impending impact
+ Vector vecForward;
+ m_hVehicle->GetVectors( &vecForward, NULL, NULL );
+ Vector vecLookPos = m_hVehicle->WorldSpaceCenter() + ( vecForward * 64.0f );
+ GetOuter()->AddLookTarget( vecLookPos, 1.0f, 1.0f );
+
+ SpeakIfAllowed( TLK_PASSENGER_WARN_COLLISION );
+ ClearCondition( COND_PASSENGER_WARN_COLLISION );
+ }
+
+ // Speak if the player is driving like a madman
+ if ( HasCondition( COND_PASSENGER_ERRATIC_DRIVING ) )
+ {
+ SpeakIfAllowed( TLK_PASSENGER_ERRATIC_DRIVING );
+ }
+
+ // The vehicle has come to a halt
+ if ( HasCondition( COND_PASSENGER_VEHICLE_STOPPED ) )
+ {
+ float flDist = ( WorldSpaceCenter() - m_hVehicle->WorldSpaceCenter() ).Length();
+ CFmtStrN<128> modifiers( "vehicle_distance:%f", flDist );
+ SpeakIfAllowed( TLK_PASSENGER_VEHICLE_STOPPED, modifiers );
+ }
+
+ // The vehicle has started to move
+ if ( HasCondition( COND_PASSENGER_VEHICLE_STARTED ) )
+ {
+ float flDist = ( WorldSpaceCenter() - m_hVehicle->WorldSpaceCenter() ).Length();
+ CFmtStrN<128> modifiers( "vehicle_distance:%f", flDist );
+ SpeakIfAllowed( TLK_PASSENGER_VEHICLE_STARTED, modifiers );
+ }
+
+ // Player got in
+ if ( HasCondition( COND_PASSENGER_PLAYER_EXITED_VEHICLE ) )
+ {
+ CPropJeepEpisodic *pJalopy = dynamic_cast<CPropJeepEpisodic*>(m_hVehicle.Get());
+ if( pJalopy != NULL && pJalopy->NumRadarContacts() > 0 )
+ {
+ SpeakIfAllowed( TLK_PASSENGER_PLAYER_EXITED, "radar_has_targets" );
+ }
+ else
+ {
+ SpeakIfAllowed( TLK_PASSENGER_PLAYER_EXITED );
+ }
+ }
+
+ // Player got out
+ if ( HasCondition( COND_PASSENGER_PLAYER_ENTERED_VEHICLE ) )
+ {
+ SpeakIfAllowed( TLK_PASSENGER_PLAYER_ENTERED );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Whether or not we should jostle at this moment
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CAI_PassengerBehaviorCompanion::CanPlayJostle( bool bLargeJostle )
+{
+ // We've been told to suppress the jostle
+ if ( m_flNextJostleTime > gpGlobals->curtime )
+ return false;
+
+ // Can't do this if we're at a high readiness level
+ if ( m_hCompanion && m_hCompanion->ShouldBeAiming() )
+ return false;
+
+ // Can't do this when we're upside-down
+ if ( HasCondition( COND_PASSENGER_OVERTURNED ) )
+ return false;
+
+ // Allow our normal impact code to handle this one instead
+ if ( HasCondition( COND_PASSENGER_HARD_IMPACT ) || IsCurSchedule( SCHED_PASSENGER_IMPACT ) )
+ return false;
+
+ if ( bLargeJostle )
+ {
+ // Don't bother under certain circumstances
+ if ( IsCurSchedule( SCHED_PASSENGER_COWER ) ||
+ IsCurSchedule( SCHED_PASSENGER_FIDGET ) )
+ return false;
+ }
+ else
+ {
+ // Don't interrupt a larger gesture
+ if ( GetOuter()->IsPlayingGesture( ACT_PASSENGER_GESTURE_JOSTLE_LARGE ) || GetOuter()->IsPlayingGesture( ACT_PASSENGER_GESTURE_JOSTLE_LARGE_STIMULATED ) )
+ return false;
+ }
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Gather conditions we can comment on or react to while riding in the vehicle
+//-----------------------------------------------------------------------------
+void CAI_PassengerBehaviorCompanion::GatherVehicleStateConditions( void )
+{
+ // Gather the base class
+ BaseClass::GatherVehicleStateConditions();
+
+ // See if we're going to collide with anything soon
+ GatherVehicleCollisionConditions( m_vehicleState.m_vecLastLocalVelocity );
+
+ // Say anything we're meant to through the response rules
+ SpeakVehicleConditions();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Handles exit failure notifications
+//-----------------------------------------------------------------------------
+void CAI_PassengerBehaviorCompanion::OnExitVehicleFailed( void )
+{
+ m_nExitAttempts++;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Track how long we've been overturned
+//-----------------------------------------------------------------------------
+void CAI_PassengerBehaviorCompanion::UpdateStuckStatus( void )
+{
+ if ( m_hVehicle == NULL )
+ return;
+
+ // Always clear this to start out with
+ ClearCondition( COND_PASSENGER_CAN_LEAVE_STUCK_VEHICLE );
+
+ // If we can't exit the vehicle, then don't bother with these checks
+ if ( m_hVehicle->NPC_CanExitVehicle( GetOuter(), true ) == false )
+ return;
+
+ bool bVisibleToPlayer = false;
+ bool bPlayerInVehicle = false;
+
+ CBasePlayer *pPlayer = UTIL_PlayerByIndex( 1 );
+ if ( pPlayer )
+ {
+ bVisibleToPlayer = pPlayer->FInViewCone( GetOuter()->GetAbsOrigin() );
+ bPlayerInVehicle = pPlayer->IsInAVehicle();
+ }
+
+ // If we're not overturned, just reset our counter
+ if ( m_vehicleState.m_bWasOverturned == false )
+ {
+ m_flOverturnedDuration = 0.0f;
+ m_flUnseenDuration = 0.0f;
+ }
+ else
+ {
+ // Add up the time since we last checked
+ m_flOverturnedDuration += ( gpGlobals->curtime - GetLastThink() );
+ }
+
+ // Warn about being stuck upside-down if it's been long enough
+ if ( m_flOverturnedDuration > MIN_OVERTURNED_WARN_DURATION && m_flNextOverturnWarning < gpGlobals->curtime )
+ {
+ SetCondition( COND_PASSENGER_WARN_OVERTURNED );
+ }
+
+ // If the player can see us or is still in the vehicle, we never exit
+ if ( bVisibleToPlayer || bPlayerInVehicle )
+ {
+ // Reset our timer
+ m_flUnseenDuration = 0.0f;
+ return;
+ }
+
+ // Add up the time since we last checked
+ m_flUnseenDuration += ( gpGlobals->curtime - GetLastThink() );
+
+ // If we've been overturned for long enough or tried to exit one too many times
+ if ( m_vehicleState.m_bWasOverturned )
+ {
+ if ( m_flUnseenDuration > MIN_OVERTURNED_DURATION )
+ {
+ SetCondition( COND_PASSENGER_CAN_LEAVE_STUCK_VEHICLE );
+ }
+ }
+ else if ( m_nExitAttempts >= MIN_FAILED_EXIT_ATTEMPTS )
+ {
+ // The player can't be looking at us
+ SetCondition( COND_PASSENGER_CAN_LEAVE_STUCK_VEHICLE );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Gather conditions for our use in making decisions
+//-----------------------------------------------------------------------------
+void CAI_PassengerBehaviorCompanion::GatherConditions( void )
+{
+ // Code below relies on these conditions being set first!
+ BaseClass::GatherConditions();
+
+ // We're not enabled
+ if ( IsEnabled() == false )
+ return;
+
+ // In-car conditions
+ if ( GetPassengerState() == PASSENGER_STATE_INSIDE )
+ {
+ // If we're jostling, then note that
+ if ( HasCondition( COND_PASSENGER_ERRATIC_DRIVING ) )
+ {
+ if ( CanPlayJostle( true ) )
+ {
+ // Add the gesture to be played. If it's already playing, the underlying function will simply opt-out
+ int nSequence = GetOuter()->AddGesture( GetOuter()->NPC_TranslateActivity( ACT_PASSENGER_GESTURE_JOSTLE_LARGE ), true );
+
+ GetOuter()->SetNextAttack( gpGlobals->curtime + ( GetOuter()->SequenceDuration( nSequence ) * 2.0f ) );
+ GetOuter()->GetShotRegulator()->FireNoEarlierThan( GetOuter()->GetNextAttack() );
+
+ // Push out our fidget into the future so that we don't act unnaturally over bumpy terrain
+ ExtendFidgetDelay( random->RandomFloat( 1.5f, 3.0f ) );
+ }
+ }
+ else if ( HasCondition( COND_PASSENGER_JOSTLE_SMALL ) )
+ {
+ if ( CanPlayJostle( false ) )
+ {
+ // Add the gesture to be played. If it's already playing, the underlying function will simply opt-out
+ GetOuter()->AddGesture( GetOuter()->NPC_TranslateActivity( ACT_PASSENGER_GESTURE_JOSTLE_SMALL ), true );
+
+ // Push out our fidget into the future so that we don't act unnaturally over bumpy terrain
+ ExtendFidgetDelay( random->RandomFloat( 1.5f, 3.0f ) );
+ }
+ }
+
+ // See if we're upside-down
+ UpdateStuckStatus();
+
+ // See if we're able to fidget
+ if ( CanFidget() )
+ {
+ SetCondition( COND_PASSENGER_CAN_FIDGET );
+ }
+ }
+
+ // Clear this out
+ ClearCondition( COND_PASSENGER_CAN_ENTER_IMMEDIATELY );
+
+ // Make sure a vehicle doesn't stray from its mark
+ if ( IsCurSchedule( SCHED_PASSENGER_RUN_TO_ENTER_VEHICLE ) )
+ {
+ if ( m_VehicleMonitor.TargetMoved( m_hVehicle ) )
+ {
+ SetCondition( COND_PASSENGER_VEHICLE_MOVED_FROM_MARK );
+ }
+
+ // If we can get in the car right away, set us up to do so
+ int nNearestSequence;
+ if ( CanEnterVehicleImmediately( &nNearestSequence, &m_vecTargetPosition, &m_vecTargetAngles ) )
+ {
+ SetTransitionSequence( nNearestSequence );
+ SetCondition( COND_PASSENGER_ENTERING );
+ SetCondition( COND_PASSENGER_CAN_ENTER_IMMEDIATELY );
+ }
+ }
+
+ // Clear the number for now
+ m_nVisibleEnemies = 0;
+
+ AIEnemiesIter_t iter;
+ for( AI_EnemyInfo_t *pEMemory = GetEnemies()->GetFirst(&iter); pEMemory != NULL; pEMemory = GetEnemies()->GetNext(&iter) )
+ {
+ if( GetOuter()->IRelationType( pEMemory->hEnemy ) == D_HT )
+ {
+ if( pEMemory->timeLastSeen == gpGlobals->curtime )
+ {
+ m_nVisibleEnemies++;
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CAI_PassengerBehaviorCompanion::AimGun( void )
+{
+ // If there is no aiming target, return to center
+ if ( GetEnemy() == NULL )
+ {
+ GetOuter()->RelaxAim();
+ return;
+ }
+
+ // Otherwise try and shoot down the barrel
+ Vector vecForward, vecRight, vecUp;
+ GetOuter()->GetVectors( &vecForward, &vecRight, &vecUp );
+ Vector vecTorso = GetAbsOrigin() + ( vecUp * 48.0f );
+
+ Vector vecShootDir = GetOuter()->GetShootEnemyDir( vecTorso, false );
+
+ Vector vecDirToEnemy = GetEnemy()->GetAbsOrigin() - vecTorso;
+ VectorNormalize( vecDirToEnemy );
+
+ bool bRightSide = ( DotProduct( vecDirToEnemy, vecRight ) > 0.0f );
+ float flTargetDot = ( bRightSide ) ? -0.7f : 0.0f;
+
+ if ( DotProduct( vecForward, vecDirToEnemy ) <= flTargetDot )
+ {
+ // Don't aim at something that's outside our reach
+ GetOuter()->RelaxAim();
+ }
+ else
+ {
+ // Aim at it
+ GetOuter()->SetAim( vecShootDir );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Allow us to deny selecting a schedule if we're not in a state to do so
+//-----------------------------------------------------------------------------
+bool CAI_PassengerBehaviorCompanion::CanSelectSchedule( void )
+{
+ if ( BaseClass::CanSelectSchedule() == false )
+ return false;
+
+ // We're in a period where we're allowing our base class to override us
+ if ( m_flNextEnterAttempt > gpGlobals->curtime )
+ return false;
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Deal with enter/exit of the vehicle
+//-----------------------------------------------------------------------------
+int CAI_PassengerBehaviorCompanion::SelectTransitionSchedule( void )
+{
+ // Attempt to instantly enter the vehicle
+ if ( HasCondition( COND_PASSENGER_CAN_ENTER_IMMEDIATELY ) )
+ {
+ // Snap to position and begin to animate into the seat
+ EnterVehicleImmediately();
+ return SCHED_PASSENGER_ENTER_VEHICLE_IMMEDIATELY;
+ }
+
+ // Entering schedule
+ if ( HasCondition( COND_PASSENGER_ENTERING ) || m_PassengerIntent == PASSENGER_INTENT_ENTER )
+ {
+ if ( GetPassengerState() == PASSENGER_STATE_INSIDE )
+ {
+ ClearCondition( COND_PASSENGER_ENTERING );
+ m_PassengerIntent = PASSENGER_INTENT_NONE;
+ return SCHED_NONE;
+ }
+
+ // Don't attempt to enter for a period of time
+ if ( m_flNextEnterAttempt > gpGlobals->curtime )
+ return SCHED_NONE;
+
+ ClearCondition( COND_PASSENGER_ENTERING );
+
+ // Failing that, run to the right place
+ return SCHED_PASSENGER_RUN_TO_ENTER_VEHICLE;
+ }
+
+ return BaseClass::SelectTransitionSchedule();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Select schedules when we're riding in the car
+//-----------------------------------------------------------------------------
+int CAI_PassengerBehaviorCompanion::SelectScheduleInsideVehicle( void )
+{
+ // Overturned
+ if ( HasCondition( COND_PASSENGER_OVERTURNED ) )
+ return SCHED_PASSENGER_OVERTURNED;
+
+ if ( HasCondition( COND_PASSENGER_HARD_IMPACT ) )
+ {
+ // Push out our fidget into the future so that we don't act unnaturally over bumpy terrain
+ ExtendFidgetDelay( random->RandomFloat( 1.5f, 3.0f ) );
+ m_flNextJostleTime = gpGlobals->curtime + random->RandomFloat( 2.5f, 4.0f );
+ return SCHED_PASSENGER_IMPACT;
+ }
+
+ // Look for exiting the vehicle
+ if ( HasCondition( COND_PASSENGER_CAN_LEAVE_STUCK_VEHICLE ) )
+ return SCHED_PASSENGER_EXIT_STUCK_VEHICLE;
+
+ // Cower if we're about to get nailed
+ if ( HasCondition( COND_HEAR_DANGER ) && IsCurSchedule( SCHED_PASSENGER_COWER ) == false )
+ {
+ SpeakIfAllowed( TLK_DANGER );
+ return SCHED_PASSENGER_COWER;
+ }
+
+ // Fire on targets
+ if ( GetEnemy() )
+ {
+ // Limit how long we'll keep an enemy if there are many on screen
+ if ( HasCondition( COND_NEW_ENEMY ) && m_nVisibleEnemies > 1 )
+ {
+ GetEnemies()->SetTimeValidEnemy( GetEnemy(), random->RandomFloat( 0.5f, 1.0f ) );
+ }
+
+ // Always face
+ GetOuter()->AddLookTarget( GetEnemy(), 1.0f, 2.0f );
+
+ if ( HasCondition( COND_CAN_RANGE_ATTACK1 ) && ( GetOuter()->GetShotRegulator()->IsInRestInterval() == false ) )
+ return SCHED_PASSENGER_RANGE_ATTACK1;
+ }
+
+ // Reload when we have the chance
+ if ( HasCondition( COND_LOW_PRIMARY_AMMO ) && HasCondition( COND_SEE_ENEMY ) == false )
+ return SCHED_PASSENGER_RELOAD;
+
+ // Say an overturned line
+ if ( HasCondition( COND_PASSENGER_WARN_OVERTURNED ) )
+ {
+ SpeakIfAllowed( TLK_PASSENGER_REQUEST_UPRIGHT );
+ m_flNextOverturnWarning = gpGlobals->curtime + random->RandomFloat( 5.0f, 10.0f );
+ ClearCondition( COND_PASSENGER_WARN_OVERTURNED );
+ }
+
+ // Should we fidget?
+ if ( HasCondition( COND_PASSENGER_CAN_FIDGET ) )
+ {
+ ExtendFidgetDelay( random->RandomFloat( 6.0f, 12.0f ) );
+ return SCHED_PASSENGER_FIDGET;
+ }
+
+ return SCHED_NONE;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Select schedules while we're outside the car
+//-----------------------------------------------------------------------------
+int CAI_PassengerBehaviorCompanion::SelectScheduleOutsideVehicle( void )
+{
+ // FIXME: How can we get in here?
+ Assert( m_hVehicle );
+ if ( m_hVehicle == NULL )
+ return SCHED_NONE;
+
+ // Handle our mark moving
+ if ( HasCondition( COND_PASSENGER_VEHICLE_MOVED_FROM_MARK ) )
+ {
+ // Reset our mark
+ m_VehicleMonitor.SetMark( m_hVehicle, 36.0f );
+ ClearCondition( COND_PASSENGER_VEHICLE_MOVED_FROM_MARK );
+ }
+
+ // If we want to get in, the try to do so
+ if ( m_PassengerIntent == PASSENGER_INTENT_ENTER )
+ {
+ // If we're not attempting to enter the vehicle again, just fall to the base class
+ if ( m_flNextEnterAttempt > gpGlobals->curtime )
+ return BaseClass::SelectSchedule();
+
+ // Otherwise try and enter thec car
+ return SCHED_PASSENGER_RUN_TO_ENTER_VEHICLE;
+ }
+
+ // This means that we're outside the vehicle with no intent to enter, which should have disabled us!
+ Disable();
+ Assert( 0 );
+
+ return SCHED_NONE;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pPlayer -
+// &vecCenter -
+// flRadius -
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool SphereWithinPlayerFOV( CBasePlayer *pPlayer, const Vector &vecCenter, float flRadius )
+{
+ // TODO: For safety sake, we might want to do a more fully qualified test against the frustum using the bbox
+
+ // If the player can see us, then we can't enter immediately anyway
+ if ( pPlayer == NULL )
+ return false;
+
+ // Find the length to the point
+ Vector los = ( vecCenter - pPlayer->EyePosition() );
+ float flLength = VectorNormalize( los );
+
+ // Get the player's forward direction
+ Vector vecPlayerForward;
+ pPlayer->EyeVectors( &vecPlayerForward, NULL, NULL );
+
+ // This is the additional number of degrees to add to account for our distance
+ float flArcAddition = atan2( flRadius, flLength );
+
+ // Find if the sphere is within our FOV
+ float flDot = DotProduct( los, vecPlayerForward );
+ float flPlayerFOV = cos( DEG2RAD( pPlayer->GetFOV() / 2.0f ) );
+
+ return ( flDot > (flPlayerFOV-flArcAddition) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CAI_PassengerBehaviorCompanion::CanEnterVehicleImmediately( int *pResultSequence, Vector *pResultPos, QAngle *pResultAngles )
+{
+ // Must wait a short time before trying to do this (otherwise we stack up on the player!)
+ if ( ( gpGlobals->curtime - m_flEnterBeginTime ) < 0.5f )
+ return false;
+
+ // Vehicle can't be moving too quickly
+ if ( GetVehicleSpeed() > 150 )
+ return false;
+
+ // If the player can see us, then we can't enter immediately anyway
+ CBasePlayer *pPlayer = AI_GetSinglePlayer();
+ if ( pPlayer == NULL )
+ return false;
+
+ Vector vecPosition = GetOuter()->WorldSpaceCenter();
+ float flRadius = GetOuter()->CollisionProp()->BoundingRadius2D();
+ if ( SphereWithinPlayerFOV( pPlayer, vecPosition, flRadius ) )
+ return false;
+
+ // Reserve an entry point
+ if ( ReserveEntryPoint( VEHICLE_SEAT_ANY ) == false )
+ return false;
+
+ // Get a list of all our animations
+ const PassengerSeatAnims_t *pEntryAnims = m_hVehicle->GetServerVehicle()->NPC_GetPassengerSeatAnims( GetOuter(), PASSENGER_SEAT_ENTRY );
+ if ( pEntryAnims == NULL )
+ return -1;
+
+ // Get the ultimate position we'll end up at
+ Vector vecStartPos, vecEndPos;
+ QAngle vecStartAngles;
+ if ( m_hVehicle->GetServerVehicle()->NPC_GetPassengerSeatPosition( GetOuter(), &vecEndPos, NULL ) == false )
+ return -1;
+
+ // Categorize the passenger in terms of being on the left or right side of the vehicle
+ Vector vecRight;
+ m_hVehicle->GetVectors( NULL, &vecRight, NULL );
+
+ CPlane lateralPlane;
+ lateralPlane.InitializePlane( vecRight, m_hVehicle->WorldSpaceCenter() );
+
+ bool bPlaneSide = lateralPlane.PointInFront( GetOuter()->GetAbsOrigin() );
+
+ Vector vecPassengerOffset = ( GetOuter()->WorldSpaceCenter() - GetOuter()->GetAbsOrigin() );
+
+ const CPassengerSeatTransition *pTransition;
+ float flNearestDistSqr = FLT_MAX;
+ float flSeatDistSqr;
+ int nNearestSequence = -1;
+ int nSequence;
+ Vector vecNearestPos;
+ QAngle vecNearestAngles;
+
+ // Test each animation (sorted by priority) for the best match
+ for ( int i = 0; i < pEntryAnims->Count(); i++ )
+ {
+ // Find the activity for this animation name
+ pTransition = &pEntryAnims->Element(i);
+ nSequence = GetOuter()->LookupSequence( STRING( pTransition->GetAnimationName() ) );
+ if ( nSequence == -1 )
+ continue;
+
+ // Test this entry for validity
+ if ( GetEntryPoint( nSequence, &vecStartPos, &vecStartAngles ) == false )
+ continue;
+
+ // See if the passenger would be visible if standing at this position
+ if ( SphereWithinPlayerFOV( pPlayer, (vecStartPos+vecPassengerOffset), flRadius ) )
+ continue;
+
+ // Otherwise distance is the deciding factor
+ flSeatDistSqr = ( vecStartPos - GetOuter()->GetAbsOrigin() ).LengthSqr();
+
+ // We must be within a certain distance to the vehicle
+ if ( flSeatDistSqr > Square( 25*12 ) )
+ continue;
+
+ // We cannot cross between the plane which splits the vehicle laterally in half down the middle
+ // This avoids cases where the character magically ends up on one side of the vehicle after they were
+ // clearly just on the other side.
+ if ( lateralPlane.PointInFront( vecStartPos ) != bPlaneSide )
+ continue;
+
+ // Closer, take it
+ if ( flSeatDistSqr < flNearestDistSqr )
+ {
+ flNearestDistSqr = flSeatDistSqr;
+ nNearestSequence = nSequence;
+ vecNearestPos = vecStartPos;
+ vecNearestAngles = vecStartAngles;
+ }
+ }
+
+ // Fail if we didn't find anything
+ if ( nNearestSequence == -1 )
+ return false;
+
+ // Return the results
+ if ( pResultSequence )
+ {
+ *pResultSequence = nNearestSequence;
+ }
+
+ if ( pResultPos )
+ {
+ *pResultPos = vecNearestPos;
+ }
+
+ if ( pResultAngles )
+ {
+ *pResultAngles = vecNearestAngles;
+ }
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Put us into the vehicle immediately
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+void CAI_PassengerBehaviorCompanion::EnterVehicleImmediately( void )
+{
+ // Now play the animation
+ GetOuter()->SetIdealActivity( ACT_SCRIPT_CUSTOM_MOVE );
+ GetOuter()->GetNavigator()->ClearGoal();
+
+ // Put us there and get going (no interpolation!)
+ GetOuter()->Teleport( &m_vecTargetPosition, &m_vecTargetAngles, &vec3_origin );
+ GetOuter()->IncrementInterpolationFrame();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Overrides the schedule selection
+// Output : int - Schedule to play
+//-----------------------------------------------------------------------------
+int CAI_PassengerBehaviorCompanion::SelectSchedule( void )
+{
+ // First, keep track of our transition state (enter/exit)
+ int nSched = SelectTransitionSchedule();
+ if ( nSched != SCHED_NONE )
+ return nSched;
+
+ // Handle schedules based on our passenger state
+ if ( GetPassengerState() == PASSENGER_STATE_OUTSIDE )
+ {
+ nSched = SelectScheduleOutsideVehicle();
+ if ( nSched != SCHED_NONE )
+ return nSched;
+ }
+ else if ( GetPassengerState() == PASSENGER_STATE_INSIDE )
+ {
+ nSched = SelectScheduleInsideVehicle();
+ if ( nSched != SCHED_NONE )
+ return nSched;
+ }
+
+ return BaseClass::SelectSchedule();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CAI_PassengerBehaviorCompanion::SelectFailSchedule( int failedSchedule, int failedTask, AI_TaskFailureCode_t taskFailCode )
+{
+ switch( failedTask )
+ {
+ case TASK_GET_PATH_TO_VEHICLE_ENTRY_POINT:
+ {
+ // This is not allowed!
+ if ( GetPassengerState() != PASSENGER_STATE_OUTSIDE )
+ {
+ Assert( 0 );
+ return SCHED_FAIL;
+ }
+
+ // If we're not close enough, then get nearer the target
+ if ( UTIL_DistApprox( m_hVehicle->GetAbsOrigin(), GetOuter()->GetAbsOrigin() ) > PASSENGER_NEAR_VEHICLE_THRESHOLD )
+ return SCHED_PASSENGER_RUN_TO_ENTER_VEHICLE_FAILED;
+ }
+
+ // Fall through
+
+ case TASK_GET_PATH_TO_NEAR_VEHICLE:
+ m_flNextEnterAttempt = gpGlobals->curtime + 3.0f;
+ break;
+ }
+
+ return BaseClass::SelectFailSchedule( failedSchedule, failedTask, taskFailCode );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Start to enter the vehicle
+//-----------------------------------------------------------------------------
+void CAI_PassengerBehaviorCompanion::EnterVehicle( void )
+{
+ BaseClass::EnterVehicle();
+
+ m_nExitAttempts = 0;
+ m_VehicleMonitor.SetMark( m_hVehicle, 8.0f );
+ m_flEnterBeginTime = gpGlobals->curtime;
+
+ // Remove this flag because we're sitting so close we always think we're going to hit the player
+ // FIXME: We need to store this state so we don't incorrectly restore it later
+ GetOuter()->CapabilitiesRemove( bits_CAP_NO_HIT_PLAYER );
+
+ // Discard enemies quickly
+ GetOuter()->GetEnemies()->SetEnemyDiscardTime( 2.0f );
+
+ SpeakIfAllowed( TLK_PASSENGER_BEGIN_ENTRANCE );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CAI_PassengerBehaviorCompanion::FinishEnterVehicle( void )
+{
+ BaseClass::FinishEnterVehicle();
+
+ // We succeeded
+ ResetVehicleEntryFailedState();
+
+ // Push this out into the future so we don't always fidget immediately in the vehicle
+ ExtendFidgetDelay( random->RandomFloat( 4.0, 15.0f ) );
+
+ SpeakIfAllowed( TLK_PASSENGER_FINISH_ENTRANCE );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CAI_PassengerBehaviorCompanion::ExitVehicle( void )
+{
+ BaseClass::ExitVehicle();
+
+ SpeakIfAllowed( TLK_PASSENGER_BEGIN_EXIT );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Vehicle has been completely exited
+//-----------------------------------------------------------------------------
+void CAI_PassengerBehaviorCompanion::FinishExitVehicle( void )
+{
+ BaseClass::FinishExitVehicle();
+
+ m_nExitAttempts = 0;
+ m_VehicleMonitor.ClearMark();
+
+ // FIXME: We need to store this state so we don't incorrectly restore it later
+ GetOuter()->CapabilitiesAdd( bits_CAP_NO_HIT_PLAYER );
+
+ // FIXME: Restore this properly
+ GetOuter()->GetEnemies()->SetEnemyDiscardTime( AI_DEF_ENEMY_DISCARD_TIME );
+
+ SpeakIfAllowed( TLK_PASSENGER_FINISH_EXIT );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Tries to build a route to the entry point of the target vehicle.
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CAI_PassengerBehaviorCompanion::FindPathToVehicleEntryPoint( void )
+{
+ // Set our custom move name
+ // bool bFindNearest = ( GetOuter()->m_NPCState == NPC_STATE_COMBAT || GetOuter()->m_NPCState == NPC_STATE_ALERT );
+ bool bFindNearest = true; // For the sake of quick gameplay, just make Alyx move directly!
+ int nSequence = FindEntrySequence( bFindNearest );
+ if ( nSequence == -1 )
+ return false;
+
+ // We have to do this specially because the activities are not named
+ SetTransitionSequence( nSequence );
+
+ // Get the entry position
+ Vector vecEntryPoint;
+ QAngle vecEntryAngles;
+ if ( GetEntryPoint( m_nTransitionSequence, &vecEntryPoint, &vecEntryAngles ) == false )
+ {
+ MarkVehicleEntryFailed( vecEntryPoint );
+ return false;
+ }
+
+ // If we're already close enough, just succeed
+ float flDistToGoalSqr = ( GetOuter()->GetAbsOrigin() - vecEntryPoint ).LengthSqr();
+ if ( flDistToGoalSqr < Square(3*12) )
+ return true;
+
+ // Setup our goal
+ AI_NavGoal_t goal( GOALTYPE_LOCATION );
+ // goal.arrivalActivity = ACT_SCRIPT_CUSTOM_MOVE;
+ goal.dest = vecEntryPoint;
+
+ // See if we need a radial route around the car, to our goal
+ if ( UseRadialRouteToEntryPoint( vecEntryPoint ) )
+ {
+ // Find the bounding radius of the vehicle
+ Vector vecCenterPoint = m_hVehicle->WorldSpaceCenter();
+ vecCenterPoint.z = vecEntryPoint.z;
+ bool bClockwise;
+ float flArc = GetArcToEntryPoint( vecCenterPoint, vecEntryPoint, bClockwise );
+ float flRadius = m_hVehicle->CollisionProp()->BoundingRadius2D();
+
+ // Try and set a radial route
+ if ( GetOuter()->GetNavigator()->SetRadialGoal( vecEntryPoint, vecCenterPoint, flRadius, flArc, 64.0f, bClockwise ) == false )
+ {
+ // Try the opposite way
+ flArc = 360.0f - flArc;
+
+ // Try the opposite way around
+ if ( GetOuter()->GetNavigator()->SetRadialGoal( vecEntryPoint, vecCenterPoint, flRadius, flArc, 64.0f, !bClockwise ) == false )
+ {
+ // Try and set a direct route as a last resort
+ if ( GetOuter()->GetNavigator()->SetGoal( goal ) == false )
+ return false;
+ }
+ }
+
+ // We found a goal
+ GetOuter()->GetNavigator()->SetArrivalDirection( vecEntryAngles );
+ GetOuter()->GetNavigator()->SetArrivalSpeed( 64.0f );
+ return true;
+ }
+ else
+ {
+ // Try and set a direct route
+ if ( GetOuter()->GetNavigator()->SetGoal( goal ) )
+ {
+ GetOuter()->GetNavigator()->SetArrivalDirection( vecEntryAngles );
+ GetOuter()->GetNavigator()->SetArrivalSpeed( 64.0f );
+ return true;
+ }
+ }
+
+ // We failed, so remember it
+ MarkVehicleEntryFailed( vecEntryPoint );
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Tests the route and position to see if it's valid
+// Input : &vecTestPos - position to test
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CAI_PassengerBehaviorCompanion::CanExitAtPosition( const Vector &vecTestPos )
+{
+ CBasePlayer *pPlayer = AI_GetSinglePlayer();
+ if ( pPlayer == NULL )
+ return false;
+
+ // Can't be in our potential view
+ if ( pPlayer->FInViewCone( vecTestPos ) )
+ return false;
+
+ // NOTE: There's no reason to do this since this is only called from a node's reported position
+ // Find the exact ground at this position
+ //Vector vecGroundPos;
+ //if ( FindGroundAtPosition( vecTestPos, 16.0f, 64.0f, &vecGroundPos ) == false )
+ // return false;
+
+ // Get the ultimate position we'll end up at
+ Vector vecStartPos;
+ if ( m_hVehicle->GetServerVehicle()->NPC_GetPassengerSeatPosition( GetOuter(), &vecStartPos, NULL ) == false )
+ return false;
+
+ // See if we can move from where we are to that position in space
+ if ( IsValidTransitionPoint( vecStartPos, vecTestPos ) == false )
+ return false;
+
+ // Trace down to the ground
+ // FIXME: This piece of code is redundant and happening in IsValidTransitionPoint() as well
+ /*
+ Vector vecGroundPos;
+ if ( FindGroundAtPosition( vecTestPos, GetOuter()->StepHeight(), 64.0f, &vecGroundPos ) == false )
+ return false;
+ */
+
+ // Try and sweep a box through space and make sure it's clear of obstructions
+ /*
+ trace_t tr;
+ CTraceFilterVehicleTransition skipFilter( GetOuter(), m_hVehicle, COLLISION_GROUP_NONE );
+
+ // These are very approximated (and magical) numbers to allow passengers greater head room and leg room when transitioning
+ Vector vecMins = GetOuter()->GetHullMins() + Vector( 0, 0, GetOuter()->StepHeight()*2.0f ); // FIXME:
+ Vector vecMaxs = GetOuter()->GetHullMaxs() - Vector( 0, 0, GetOuter()->StepHeight() );
+
+ UTIL_TraceHull( GetOuter()->GetAbsOrigin(), vecGroundPos, vecMins, vecMaxs, MASK_NPCSOLID, &skipFilter, &tr );
+
+ // If we're blocked, we can't get out there
+ if ( tr.fraction < 1.0f || tr.allsolid || tr.startsolid )
+ {
+ if ( passenger_debug_transition.GetBool() )
+ {
+ NDebugOverlay::SweptBox( GetOuter()->GetAbsOrigin(), vecGroundPos, vecMins, GetOuter()->GetHullMaxs(), vec3_angle, 255, 0, 0, 64, 2.0f );
+ }
+ return false;
+ }
+ */
+
+ return true;
+}
+
+#define NUM_EXIT_ITERATIONS 8
+
+//-----------------------------------------------------------------------------
+// Purpose: Find a position we can use to exit the vehicle via teleportation
+// Input : *vecResult - safe place to exit to
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CAI_PassengerBehaviorCompanion::GetStuckExitPos( Vector *vecResult )
+{
+ // Get our right direction
+ Vector vecVehicleRight;
+ m_hVehicle->GetVectors( NULL, &vecVehicleRight, NULL );
+
+ // Get the vehicle's rough horizontal bounds
+ float flVehicleRadius = m_hVehicle->CollisionProp()->BoundingRadius2D();
+
+ // Use the vehicle's center as our hub
+ Vector vecCenter = m_hVehicle->WorldSpaceCenter();
+
+ // Angle whose tan is: y/x
+ float flCurAngle = atan2f( vecVehicleRight.y, vecVehicleRight.x );
+ float flAngleIncr = (M_PI*2.0f)/(float)NUM_EXIT_ITERATIONS;
+ Vector vecTestPos;
+
+ // Test a number of discrete exit routes
+ for ( int i = 0; i <= NUM_EXIT_ITERATIONS-1; i++ )
+ {
+ // Get our position
+ SinCos( flCurAngle, &vecTestPos.y, &vecTestPos.x );
+ vecTestPos.z = 0.0f;
+ vecTestPos *= flVehicleRadius;
+ vecTestPos += vecCenter;
+
+ // Now find the nearest node and use that
+ int nNearNode = GetOuter()->GetPathfinder()->NearestNodeToPoint( vecTestPos );
+ if ( nNearNode != NO_NODE )
+ {
+ Vector vecNodePos = g_pBigAINet->GetNodePosition( GetOuter()->GetHullType(), nNearNode );
+
+ // Test the position
+ if ( CanExitAtPosition( vecNodePos ) )
+ {
+ // Take the result
+ *vecResult = vecNodePos;
+ return true;
+ }
+
+ // Move to the next iteration
+ flCurAngle += flAngleIncr;
+ }
+ }
+
+ // None found
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Attempt to get out of an overturned vehicle when the player isn't looking
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CAI_PassengerBehaviorCompanion::ExitStuckVehicle( void )
+{
+ // Try and find an exit position
+ Vector vecExitPos;
+ if ( GetStuckExitPos( &vecExitPos ) == false )
+ return false;
+
+ // Detach from the parent
+ GetOuter()->SetParent( NULL );
+
+ // Do all necessary clean-up
+ FinishExitVehicle();
+
+ // Teleport to the destination
+ // TODO: Make sure that the player can't see this!
+ GetOuter()->Teleport( &vecExitPos, &vec3_angle, &vec3_origin );
+ GetOuter()->IncrementInterpolationFrame();
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CAI_PassengerBehaviorCompanion::StartTask( const Task_t *pTask )
+{
+ // We need to override these so we never face
+ if ( GetPassengerState() == PASSENGER_STATE_INSIDE )
+ {
+ if ( pTask->iTask == TASK_FACE_TARGET ||
+ pTask->iTask == TASK_FACE_ENEMY ||
+ pTask->iTask == TASK_FACE_IDEAL ||
+ pTask->iTask == TASK_FACE_HINTNODE ||
+ pTask->iTask == TASK_FACE_LASTPOSITION ||
+ pTask->iTask == TASK_FACE_PATH ||
+ pTask->iTask == TASK_FACE_PLAYER ||
+ pTask->iTask == TASK_FACE_REASONABLE ||
+ pTask->iTask == TASK_FACE_SAVEPOSITION ||
+ pTask->iTask == TASK_FACE_SCRIPT )
+ {
+ return TaskComplete();
+ }
+ }
+
+ switch ( pTask->iTask )
+ {
+ case TASK_RUN_TO_VEHICLE_ENTRANCE:
+ {
+ // Get a move on!
+ GetOuter()->GetNavigator()->SetMovementActivity( ACT_RUN );
+ }
+ break;
+
+ case TASK_GET_PATH_TO_VEHICLE_ENTRY_POINT:
+ {
+ if ( GetPassengerState() != PASSENGER_STATE_OUTSIDE )
+ {
+ Assert( 0 );
+ TaskFail( "Trying to run while inside a vehicle!\n");
+ return;
+ }
+
+ // Reserve an entry point
+ if ( ReserveEntryPoint( VEHICLE_SEAT_ANY ) == false )
+ {
+ TaskFail( "No valid entry point!\n" );
+ return;
+ }
+
+ // Find where we're going
+ if ( FindPathToVehicleEntryPoint() )
+ {
+ TaskComplete();
+ return;
+ }
+
+ // We didn't find a path
+ TaskFail( "TASK_GET_PATH_TO_VEHICLE_ENTRY_POINT: Unable to run to entry point" );
+ }
+ break;
+
+ case TASK_GET_PATH_TO_TARGET:
+ {
+ GetOuter()->SetTarget( m_hVehicle );
+ BaseClass::StartTask( pTask );
+ }
+ break;
+
+ case TASK_GET_PATH_TO_NEAR_VEHICLE:
+ {
+ if ( m_hVehicle == NULL )
+ {
+ TaskFail("Lost vehicle pointer\n");
+ return;
+ }
+
+ // Find the passenger offset we're going for
+ Vector vecRight;
+ m_hVehicle->GetVectors( NULL, &vecRight, NULL );
+ Vector vecTargetOffset = vecRight * 64.0f;
+
+ // Try and find a path near there
+ AI_NavGoal_t goal( GOALTYPE_TARGETENT, vecTargetOffset, AIN_DEF_ACTIVITY, 64.0f, AIN_UPDATE_TARGET_POS, m_hVehicle );
+ GetOuter()->SetTarget( m_hVehicle );
+ if ( GetOuter()->GetNavigator()->SetGoal( goal ) )
+ {
+ TaskComplete();
+ return;
+ }
+
+ TaskFail( "Unable to find path to get closer to vehicle!\n" );
+ return;
+ }
+
+ break;
+
+ case TASK_PASSENGER_RELOAD:
+ {
+ GetOuter()->SetIdealActivity( ACT_PASSENGER_RELOAD );
+ return;
+ }
+ break;
+
+ case TASK_PASSENGER_EXIT_STUCK_VEHICLE:
+ {
+ if ( ExitStuckVehicle() )
+ {
+ TaskComplete();
+ return;
+ }
+
+ TaskFail("Unable to exit overturned vehicle!\n");
+ }
+ break;
+
+ case TASK_PASSENGER_OVERTURNED:
+ {
+ // Go into our overturned animation
+ if ( GetOuter()->GetActivity() != ACT_PASSENGER_OVERTURNED )
+ {
+ GetOuter()->SetActivity( ACT_RESET );
+ GetOuter()->SetActivity( ACT_PASSENGER_OVERTURNED );
+ }
+
+ TaskComplete();
+ }
+ break;
+
+ case TASK_PASSENGER_IMPACT:
+ {
+ // Stomp anything currently playing on top of us, this has to take priority
+ GetOuter()->RemoveAllGestures();
+
+ // Go into our impact animation
+ GetOuter()->ResetIdealActivity( ACT_PASSENGER_IMPACT );
+
+ // Delay for twice the duration of our impact animation
+ int nSequence = GetOuter()->SelectWeightedSequence( ACT_PASSENGER_IMPACT );
+ float flSeqDuration = GetOuter()->SequenceDuration( nSequence );
+ float flStunTime = flSeqDuration + random->RandomFloat( 1.0f, 2.0f );
+ GetOuter()->SetNextAttack( gpGlobals->curtime + flStunTime );
+ ExtendFidgetDelay( flStunTime );
+ }
+ break;
+
+ default:
+ BaseClass::StartTask( pTask );
+ break;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CAI_PassengerBehaviorCompanion::IsCurTaskContinuousMove( void )
+{
+ const Task_t *pCurTask = GetCurTask();
+ if ( pCurTask && pCurTask->iTask == TASK_RUN_TO_VEHICLE_ENTRANCE )
+ return true;
+
+ return BaseClass::IsCurTaskContinuousMove();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Update our path if we're running towards the vehicle (since it can move)
+//-----------------------------------------------------------------------------
+bool CAI_PassengerBehaviorCompanion::UpdateVehicleEntrancePath( void )
+{
+ // If it's too soon to check again, don't bother
+ if ( m_flEntraceUpdateTime > gpGlobals->curtime )
+ return true;
+
+ // Find out if we need to update
+ if ( m_VehicleMonitor.TargetMoved2D( m_hVehicle ) == false )
+ {
+ m_flEntraceUpdateTime = gpGlobals->curtime + 0.5f;
+ return true;
+ }
+
+ // Don't attempt again for some amount of time
+ m_flEntraceUpdateTime = gpGlobals->curtime + 1.0f;
+
+ int nSequence = FindEntrySequence( true );
+ if ( nSequence == -1 )
+ return false;
+
+ SetTransitionSequence( nSequence );
+
+ // Get the entry position
+ Vector vecEntryPoint;
+ QAngle vecEntryAngles;
+ if ( GetEntryPoint( m_nTransitionSequence, &vecEntryPoint, &vecEntryAngles ) == false )
+ return false;
+
+ // Move the entry point forward in time a bit to predict where it'll be
+ Vector vecVehicleSpeed = m_hVehicle->GetSmoothedVelocity();
+
+ // Tack on the smoothed velocity
+ vecEntryPoint += vecVehicleSpeed; // one second
+
+ // Update our entry point
+ if ( GetOuter()->GetNavigator()->UpdateGoalPos( vecEntryPoint ) == false )
+ return false;
+
+ // Reset the goal angles
+ GetNavigator()->SetArrivalDirection( vecEntryAngles );
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CAI_PassengerBehaviorCompanion::RunTask( const Task_t *pTask )
+{
+ switch ( pTask->iTask )
+ {
+ case TASK_PASSENGER_RELOAD:
+ {
+ if ( GetOuter()->IsSequenceFinished() )
+ {
+ TaskComplete();
+ }
+ }
+ break;
+
+ case TASK_PASSENGER_IMPACT:
+ {
+ if ( GetOuter()->IsSequenceFinished() )
+ {
+ TaskComplete();
+ return;
+ }
+ }
+ break;
+
+ case TASK_RUN_TO_VEHICLE_ENTRANCE:
+ {
+ // Update our entrance point if we can
+ if ( UpdateVehicleEntrancePath() == false )
+ {
+ TaskFail("Unable to find entrance to vehicle");
+ break;
+ }
+
+ // See if we're close enough to our goal
+ if ( GetOuter ()->GetNavigator()->IsGoalActive() == false )
+ {
+ // See if we're close enough now to enter the vehicle
+ Vector vecEntryPoint;
+ GetEntryPoint( m_nTransitionSequence, &vecEntryPoint );
+ if ( ( vecEntryPoint - GetAbsOrigin() ).Length2DSqr() < Square( 36.0f ) )
+ {
+ if ( GetNavigator()->GetArrivalActivity() != ACT_INVALID )
+ {
+ SetActivity( GetNavigator()->GetArrivalActivity() );
+ }
+
+ TaskComplete();
+ }
+ else
+ {
+ TaskFail( "Unable to navigate to vehicle" );
+ }
+ }
+
+ // Keep merrily going!
+ }
+ break;
+
+ default:
+ BaseClass::RunTask( pTask );
+ break;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Add custom interrupt conditions
+//-----------------------------------------------------------------------------
+void CAI_PassengerBehaviorCompanion::BuildScheduleTestBits( void )
+{
+ // Always break on being able to exit
+ if ( GetPassengerState() == PASSENGER_STATE_INSIDE )
+ {
+ GetOuter()->SetCustomInterruptCondition( GetClassScheduleIdSpace()->ConditionLocalToGlobal( COND_PASSENGER_CAN_LEAVE_STUCK_VEHICLE ) );
+ GetOuter()->SetCustomInterruptCondition( GetClassScheduleIdSpace()->ConditionLocalToGlobal( COND_PASSENGER_HARD_IMPACT) );
+
+ if ( IsCurSchedule( SCHED_PASSENGER_OVERTURNED ) == false )
+ {
+ GetOuter()->SetCustomInterruptCondition( GetClassScheduleIdSpace()->ConditionLocalToGlobal( COND_PASSENGER_OVERTURNED ) );
+ }
+
+ // Append the ability to break on fidgeting
+ if ( IsCurSchedule( SCHED_PASSENGER_IDLE ) )
+ {
+ GetOuter()->SetCustomInterruptCondition( GetClassScheduleIdSpace()->ConditionLocalToGlobal( COND_PASSENGER_CAN_FIDGET ) );
+ }
+
+ // Add this so we're prompt about exiting the vehicle when able to
+ if ( m_PassengerIntent == PASSENGER_INTENT_EXIT )
+ {
+ GetOuter()->SetCustomInterruptCondition( GetClassScheduleIdSpace()->ConditionLocalToGlobal( COND_PASSENGER_VEHICLE_STOPPED ) );
+ }
+ }
+
+ BaseClass::BuildScheduleTestBits();
+}
+//-----------------------------------------------------------------------------
+// Purpose: Determines if the passenger should take a radial route to the goal
+// Input : &vecEntryPoint - Point of entry
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CAI_PassengerBehaviorCompanion::UseRadialRouteToEntryPoint( const Vector &vecEntryPoint )
+{
+ // Get the center position of the vehicle we'll radiate around
+ Vector vecCenterPos = m_hVehicle->WorldSpaceCenter();
+ vecCenterPos.z = vecEntryPoint.z;
+
+ // Find out if we need to go around the vehicle
+ float flDistToVehicleCenter = ( vecCenterPos - GetOuter()->GetAbsOrigin() ).Length();
+ float flDistToGoal = ( vecEntryPoint - GetOuter()->GetAbsOrigin() ).Length();
+ if ( flDistToGoal > flDistToVehicleCenter )
+ return true;
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Find the arc in degrees to reach our goal position
+// Input : &vecCenterPoint - Point around which the arc rotates
+// &vecEntryPoint - Point we're trying to reach
+// &bClockwise - If we should move clockwise or not to get there
+// Output : float - degrees around arc to follow
+//-----------------------------------------------------------------------------
+float CAI_PassengerBehaviorCompanion::GetArcToEntryPoint( const Vector &vecCenterPoint, const Vector &vecEntryPoint, bool &bClockwise )
+{
+ // We want the entry point to be at the same level as the center to make this a two dimensional problem
+ Vector vecEntryPointAdjusted = vecEntryPoint;
+ vecEntryPointAdjusted.z = vecCenterPoint.z;
+
+ // Direction from vehicle center to passenger
+ Vector vecVehicleToPassenger = ( GetOuter()->GetAbsOrigin() - vecCenterPoint );
+ VectorNormalize( vecVehicleToPassenger );
+
+ // Direction from vehicle center to entry point
+ Vector vecVehicleToEntry = ( vecEntryPointAdjusted - vecCenterPoint );
+ VectorNormalize( vecVehicleToEntry );
+
+ float flVehicleToPassengerYaw = UTIL_VecToYaw( vecVehicleToPassenger );
+ float flVehicleToEntryYaw = UTIL_VecToYaw( vecVehicleToEntry );
+ float flArcDist = UTIL_AngleDistance( flVehicleToEntryYaw, flVehicleToPassengerYaw );
+
+ bClockwise = ( flArcDist < 0.0f );
+ return fabs( flArcDist );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Removes all failed points
+//-----------------------------------------------------------------------------
+void CAI_PassengerBehaviorCompanion::ResetVehicleEntryFailedState( void )
+{
+ m_FailedEntryPositions.RemoveAll();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Adds a failed position to the list and marks when it occurred
+// Input : &vecPosition - Position that failed
+//-----------------------------------------------------------------------------
+void CAI_PassengerBehaviorCompanion::MarkVehicleEntryFailed( const Vector &vecPosition )
+{
+ FailPosition_t failPos;
+ failPos.flTime = gpGlobals->curtime;
+ failPos.vecPosition = vecPosition;
+ m_FailedEntryPositions.AddToTail( failPos );
+
+ // Show this as failed
+ if ( passenger_debug_entry.GetBool() )
+ {
+ NDebugOverlay::Box( vecPosition, -Vector(8,8,8), Vector(8,8,8), 255, 0, 0, 0, 2.0f );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: See if a vector is near enough to a previously failed position
+// Input : &vecPosition - position to test
+// Output : Returns true if the point is near enough another to be considered equivalent
+//-----------------------------------------------------------------------------
+bool CAI_PassengerBehaviorCompanion::PointIsWithinEntryFailureRadius( const Vector &vecPosition )
+{
+ // Test this point against our known failed points and reject it if it's too near
+ for ( int i = 0; i < m_FailedEntryPositions.Count(); i++ )
+ {
+ // If our time has expired, kill the position
+ if ( ( gpGlobals->curtime - m_FailedEntryPositions[i].flTime ) > 3.0f )
+ {
+ // Show that we've cleared it
+ if ( passenger_debug_entry.GetBool() )
+ {
+ NDebugOverlay::Box( m_FailedEntryPositions[i].vecPosition, -Vector(12,12,12), Vector(12,12,12), 255, 255, 0, 0, 2.0f );
+ }
+
+ m_FailedEntryPositions.Remove( i );
+ continue;
+ }
+
+ // See if this position is too near our last failed attempt
+ if ( ( vecPosition - m_FailedEntryPositions[i].vecPosition ).LengthSqr() < Square(3*12) )
+ {
+ // Show that this was denied
+ if ( passenger_debug_entry.GetBool() )
+ {
+ NDebugOverlay::Box( m_FailedEntryPositions[i].vecPosition, -Vector(12,12,12), Vector(12,12,12), 255, 0, 0, 128, 2.0f );
+ }
+
+ return true;
+ }
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Find the proper sequence to use (weighted by priority or distance from current position)
+// to enter the vehicle.
+// Input : bNearest - Use distance as the criteria for a "best" sequence. Otherwise the order of the
+// seats is their priority.
+// Output : int - sequence index
+//-----------------------------------------------------------------------------
+int CAI_PassengerBehaviorCompanion::FindEntrySequence( bool bNearest /*= false*/ )
+{
+ // Get a list of all our animations
+ const PassengerSeatAnims_t *pEntryAnims = m_hVehicle->GetServerVehicle()->NPC_GetPassengerSeatAnims( GetOuter(), PASSENGER_SEAT_ENTRY );
+ if ( pEntryAnims == NULL )
+ return -1;
+
+ // Get the ultimate position we'll end up at
+ Vector vecStartPos, vecEndPos;
+ if ( m_hVehicle->GetServerVehicle()->NPC_GetPassengerSeatPosition( GetOuter(), &vecEndPos, NULL ) == false )
+ return -1;
+
+ const CPassengerSeatTransition *pTransition;
+ float flNearestDistSqr = FLT_MAX;
+ float flSeatDistSqr;
+ int nNearestSequence = -1;
+ int nSequence;
+
+ // Test each animation (sorted by priority) for the best match
+ for ( int i = 0; i < pEntryAnims->Count(); i++ )
+ {
+ // Find the activity for this animation name
+ pTransition = &pEntryAnims->Element(i);
+ nSequence = GetOuter()->LookupSequence( STRING( pTransition->GetAnimationName() ) );
+ if ( nSequence == -1 )
+ continue;
+
+ // Test this entry for validity
+ if ( GetEntryPoint( nSequence, &vecStartPos ) == false )
+ continue;
+
+ // See if this entry position is in our list of known unreachable places
+ if ( PointIsWithinEntryFailureRadius( vecStartPos ) )
+ continue;
+
+ // Check to see if we can use this
+ if ( IsValidTransitionPoint( vecStartPos, vecEndPos ) )
+ {
+ // If we're just looking for the first, we're done
+ if ( bNearest == false )
+ return nSequence;
+
+ // Otherwise distance is the deciding factor
+ flSeatDistSqr = ( vecStartPos - GetOuter()->GetAbsOrigin() ).LengthSqr();
+
+ // Closer, take it
+ if ( flSeatDistSqr < flNearestDistSqr )
+ {
+ flNearestDistSqr = flSeatDistSqr;
+ nNearestSequence = nSequence;
+ }
+ }
+
+ }
+
+ return nNearestSequence;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Override certain animations
+//-----------------------------------------------------------------------------
+Activity CAI_PassengerBehaviorCompanion::NPC_TranslateActivity( Activity activity )
+{
+ Activity newActivity = BaseClass::NPC_TranslateActivity( activity );
+
+ // Handle animations from inside the vehicle
+ if ( GetPassengerState() == PASSENGER_STATE_INSIDE )
+ {
+ // Alter idle depending on the vehicle's state
+ if ( newActivity == ACT_IDLE )
+ {
+ // Always play the overturned animation
+ if ( m_vehicleState.m_bWasOverturned )
+ return ACT_PASSENGER_OVERTURNED;
+ }
+ }
+
+ return newActivity;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CAI_PassengerBehaviorCompanion::CanExitVehicle( void )
+{
+ if ( BaseClass::CanExitVehicle() == false )
+ return false;
+
+ // If we're tipped too much, we can't exit
+ Vector vecUp;
+ GetOuter()->GetVectors( NULL, NULL, &vecUp );
+ if ( DotProduct( vecUp, Vector(0,0,1) ) < DOT_45DEGREE )
+ return false;
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: NPC needs to get to their marks, so do so with urgent navigation
+//-----------------------------------------------------------------------------
+bool CAI_PassengerBehaviorCompanion::IsNavigationUrgent( void )
+{
+ // If we're running to the vehicle, do so urgently
+ if ( GetPassengerState() == PASSENGER_STATE_OUTSIDE && m_PassengerIntent == PASSENGER_INTENT_ENTER )
+ return true;
+
+ return BaseClass::IsNavigationUrgent();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Calculate our body lean based on our delta velocity
+//-----------------------------------------------------------------------------
+void CAI_PassengerBehaviorCompanion::CalculateBodyLean( void )
+{
+ // Calculate our lateral displacement from a perfectly centered start
+ float flLateralDisp = SimpleSplineRemapVal( m_vehicleState.m_vecLastAngles.z, 100.0f, -100.0f, -1.0f, 1.0f );
+ flLateralDisp = clamp( flLateralDisp, -1.0f, 1.0f );
+
+ // FIXME: Framerate dependent!
+ m_flLastLateralLean = ( m_flLastLateralLean * 0.2f ) + ( flLateralDisp * 0.8f );
+
+ // Here we can make Alyx do something different on an "extreme" lean condition
+ if ( fabs( m_flLastLateralLean ) > 0.75f )
+ {
+ // Large lean, make us react?
+ }
+
+ // Set these parameters
+ GetOuter()->SetPoseParameter( "vehicle_lean", m_flLastLateralLean );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Whether or not we're allowed to fidget
+//-----------------------------------------------------------------------------
+bool CAI_PassengerBehaviorCompanion::CanFidget( void )
+{
+ // Can't fidget again too quickly
+ if ( m_flNextFidgetTime > gpGlobals->curtime )
+ return false;
+
+ // FIXME: Really we want to check our readiness level at this point
+ if ( GetOuter()->GetEnemy() != NULL )
+ return false;
+
+ // Don't fidget unless we're at low readiness
+ if ( m_hCompanion && ( m_hCompanion->GetReadinessLevel() > AIRL_RELAXED ) )
+ return false;
+
+ // Don't fidget while we're in a script
+ if ( GetOuter()->IsInAScript() || GetOuter()->GetIdealState() == NPC_STATE_SCRIPT || IsRunningScriptedScene( GetOuter() ) )
+ return false;
+
+ // If we're upside down, don't bother
+ if ( HasCondition( COND_PASSENGER_OVERTURNED ) )
+ return false;
+
+ // Must be visible to the player
+ CBasePlayer *pPlayer = AI_GetSinglePlayer();
+ if ( pPlayer && pPlayer->FInViewCone( GetOuter()->EyePosition() ) == false )
+ return false;
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Extends the fidget delay by the time specified
+// Input : flDuration - in seconds
+//-----------------------------------------------------------------------------
+void CAI_PassengerBehaviorCompanion::ExtendFidgetDelay( float flDuration )
+{
+ // If we're already expired, just set this as the next time
+ if ( m_flNextFidgetTime < gpGlobals->curtime )
+ {
+ m_flNextFidgetTime = gpGlobals->curtime + flDuration;
+ }
+ else
+ {
+ // Otherwise bump the delay farther into the future
+ m_flNextFidgetTime += flDuration;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: We never want to be marked as crouching when inside a vehicle
+//-----------------------------------------------------------------------------
+bool CAI_PassengerBehaviorCompanion::IsCrouching( void )
+{
+ return false;
+}
+
+AI_BEGIN_CUSTOM_SCHEDULE_PROVIDER( CAI_PassengerBehaviorCompanion )
+{
+ DECLARE_ACTIVITY( ACT_PASSENGER_IDLE_AIM )
+ DECLARE_ACTIVITY( ACT_PASSENGER_RELOAD )
+ DECLARE_ACTIVITY( ACT_PASSENGER_OVERTURNED )
+ DECLARE_ACTIVITY( ACT_PASSENGER_IMPACT )
+ DECLARE_ACTIVITY( ACT_PASSENGER_IMPACT_WEAPON )
+ DECLARE_ACTIVITY( ACT_PASSENGER_POINT )
+ DECLARE_ACTIVITY( ACT_PASSENGER_POINT_BEHIND )
+ DECLARE_ACTIVITY( ACT_PASSENGER_IDLE_READY )
+ DECLARE_ACTIVITY( ACT_PASSENGER_GESTURE_JOSTLE_LARGE )
+ DECLARE_ACTIVITY( ACT_PASSENGER_GESTURE_JOSTLE_SMALL )
+ DECLARE_ACTIVITY( ACT_PASSENGER_GESTURE_JOSTLE_LARGE_STIMULATED )
+ DECLARE_ACTIVITY( ACT_PASSENGER_GESTURE_JOSTLE_SMALL_STIMULATED )
+ DECLARE_ACTIVITY( ACT_PASSENGER_COWER_IN )
+ DECLARE_ACTIVITY( ACT_PASSENGER_COWER_LOOP )
+ DECLARE_ACTIVITY( ACT_PASSENGER_COWER_OUT )
+ DECLARE_ACTIVITY( ACT_PASSENGER_IDLE_FIDGET )
+
+ DECLARE_TASK( TASK_GET_PATH_TO_VEHICLE_ENTRY_POINT )
+ DECLARE_TASK( TASK_GET_PATH_TO_NEAR_VEHICLE )
+ DECLARE_TASK( TASK_PASSENGER_RELOAD )
+ DECLARE_TASK( TASK_PASSENGER_EXIT_STUCK_VEHICLE )
+ DECLARE_TASK( TASK_PASSENGER_OVERTURNED )
+ DECLARE_TASK( TASK_PASSENGER_IMPACT )
+ DECLARE_TASK( TASK_RUN_TO_VEHICLE_ENTRANCE )
+
+ DECLARE_CONDITION( COND_PASSENGER_VEHICLE_MOVED_FROM_MARK )
+ DECLARE_CONDITION( COND_PASSENGER_CAN_LEAVE_STUCK_VEHICLE )
+ DECLARE_CONDITION( COND_PASSENGER_WARN_OVERTURNED )
+ DECLARE_CONDITION( COND_PASSENGER_WARN_COLLISION )
+ DECLARE_CONDITION( COND_PASSENGER_CAN_FIDGET )
+ DECLARE_CONDITION( COND_PASSENGER_CAN_ENTER_IMMEDIATELY )
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_PASSENGER_RUN_TO_ENTER_VEHICLE,
+
+ " Tasks"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_PASSENGER_RUN_TO_ENTER_VEHICLE_FAILED"
+ " TASK_STOP_MOVING 0"
+ " TASK_SET_TOLERANCE_DISTANCE 36" // 3 ft
+ " TASK_SET_ROUTE_SEARCH_TIME 5"
+ " TASK_GET_PATH_TO_VEHICLE_ENTRY_POINT 0"
+ " TASK_RUN_TO_VEHICLE_ENTRANCE 0"
+ " TASK_SET_SCHEDULE SCHEDULE:SCHED_PASSENGER_ENTER_VEHICLE"
+ ""
+ " Interrupts"
+ " COND_PASSENGER_CAN_ENTER_IMMEDIATELY"
+ " COND_PASSENGER_CANCEL_ENTER"
+ )
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_PASSENGER_RUN_TO_ENTER_VEHICLE_FAILED,
+
+ " Tasks"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_PASSENGER_ENTER_VEHICLE_PAUSE"
+ " TASK_STOP_MOVING 0"
+ " TASK_SET_TOLERANCE_DISTANCE 36"
+ " TASK_SET_ROUTE_SEARCH_TIME 3"
+ " TASK_GET_PATH_TO_NEAR_VEHICLE 0"
+ " TASK_RUN_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ ""
+ " Interrupts"
+ " COND_PASSENGER_CANCEL_ENTER"
+ )
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_PASSENGER_ENTER_VEHICLE_PAUSE,
+
+ " Tasks"
+ " TASK_STOP_MOVING 1"
+ " TASK_FACE_TARGET 0"
+ " TASK_WAIT 2"
+ ""
+ " Interrupts"
+ " COND_LIGHT_DAMAGE"
+ " COND_NEW_ENEMY"
+ " COND_PASSENGER_CANCEL_ENTER"
+ )
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_PASSENGER_RANGE_ATTACK1,
+
+ " Tasks"
+ " TASK_ANNOUNCE_ATTACK 1" // 1 = primary attack
+ " TASK_RANGE_ATTACK1 0"
+ ""
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_ENEMY_DEAD"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ " COND_ENEMY_OCCLUDED"
+ " COND_NO_PRIMARY_AMMO"
+ " COND_HEAR_DANGER"
+ " COND_WEAPON_BLOCKED_BY_FRIEND"
+ " COND_WEAPON_SIGHT_OCCLUDED"
+ )
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_PASSENGER_EXIT_STUCK_VEHICLE,
+
+ " Tasks"
+ " TASK_PASSENGER_EXIT_STUCK_VEHICLE 0"
+ ""
+ " Interrupts"
+ )
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_PASSENGER_RELOAD,
+
+ " Tasks"
+ " TASK_PASSENGER_RELOAD 0"
+ ""
+ " Interrupts"
+ )
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_PASSENGER_OVERTURNED,
+
+ " Tasks"
+ " TASK_PASSENGER_OVERTURNED 0"
+ ""
+ " Interrupts"
+ )
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_PASSENGER_IMPACT,
+
+ " Tasks"
+ " TASK_PASSENGER_IMPACT 0"
+ ""
+ " Interrupts"
+ )
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_PASSENGER_ENTER_VEHICLE_IMMEDIATELY,
+
+ " Tasks"
+ " TASK_PASSENGER_ATTACH_TO_VEHICLE 0"
+ " TASK_PASSENGER_ENTER_VEHICLE 0"
+ ""
+ " Interrupts"
+ " COND_NO_CUSTOM_INTERRUPTS"
+ )
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_PASSENGER_COWER,
+
+ " Tasks"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_PASSENGER_COWER_IN"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_PASSENGER_COWER_LOOP"
+ " TASK_WAIT_UNTIL_NO_DANGER_SOUND 0"
+ " TASK_WAIT 2"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_PASSENGER_COWER_OUT"
+ ""
+ " Interrupts"
+ " COND_NO_CUSTOM_INTERRUPTS"
+ )
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_PASSENGER_FIDGET,
+
+ " Tasks"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_PASSENGER_IDLE_FIDGET"
+ ""
+ " Interrupts"
+ " COND_NO_CUSTOM_INTERRUPTS"
+ )
+
+ AI_END_CUSTOM_SCHEDULE_PROVIDER()
+}
diff --git a/mp/src/game/server/episodic/ai_behavior_passenger_companion.h b/mp/src/game/server/episodic/ai_behavior_passenger_companion.h
index 53034168..857254e0 100644
--- a/mp/src/game/server/episodic/ai_behavior_passenger_companion.h
+++ b/mp/src/game/server/episodic/ai_behavior_passenger_companion.h
@@ -1,168 +1,168 @@
-//========= Copyright Valve Corporation, All rights reserved. ============//
-//
-// Purpose:
-//
-//=============================================================================
-
-#ifndef AI_BEHAVIOR_PASSENGER_COMPANION_H
-#define AI_BEHAVIOR_PASSENGER_COMPANION_H
-#ifdef _WIN32
-#pragma once
-#endif
-
-#include "ai_behavior_passenger.h"
-
-class CNPC_PlayerCompanion;
-
-struct VehicleAvoidParams_t
-{
- Vector vecStartPos;
- Vector vecGoalPos;
- Vector *pNodePositions;
- int nNumNodes;
- int nDirection;
- int nStartNode;
- int nEndNode;
-};
-
-struct FailPosition_t
-{
- Vector vecPosition;
- float flTime;
-
- DECLARE_SIMPLE_DATADESC();
-};
-
-class CAI_PassengerBehaviorCompanion : public CAI_PassengerBehavior
-{
- DECLARE_CLASS( CAI_PassengerBehaviorCompanion, CAI_PassengerBehavior );
- DECLARE_DATADESC()
-
-public:
-
- CAI_PassengerBehaviorCompanion( void );
-
- enum
- {
- // Schedules
- SCHED_PASSENGER_RUN_TO_ENTER_VEHICLE = BaseClass::NEXT_SCHEDULE,
- SCHED_PASSENGER_RUN_TO_ENTER_VEHICLE_FAILED,
- SCHED_PASSENGER_ENTER_VEHICLE_PAUSE,
- SCHED_PASSENGER_RANGE_ATTACK1,
- SCHED_PASSENGER_RELOAD,
- SCHED_PASSENGER_EXIT_STUCK_VEHICLE,
- SCHED_PASSENGER_OVERTURNED,
- SCHED_PASSENGER_IMPACT,
- SCHED_PASSENGER_ENTER_VEHICLE_IMMEDIATELY,
- SCHED_PASSENGER_COWER,
- SCHED_PASSENGER_FIDGET,
- NEXT_SCHEDULE,
-
- // Tasks
- TASK_GET_PATH_TO_VEHICLE_ENTRY_POINT = BaseClass::NEXT_TASK,
- TASK_GET_PATH_TO_NEAR_VEHICLE,
- TASK_PASSENGER_RELOAD,
- TASK_PASSENGER_EXIT_STUCK_VEHICLE,
- TASK_PASSENGER_OVERTURNED,
- TASK_PASSENGER_IMPACT,
- TASK_RUN_TO_VEHICLE_ENTRANCE,
- NEXT_TASK,
-
- // Conditions
-
- COND_PASSENGER_CAN_LEAVE_STUCK_VEHICLE = BaseClass::NEXT_CONDITION,
- COND_PASSENGER_WARN_OVERTURNED,
- COND_PASSENGER_WARN_COLLISION,
- COND_PASSENGER_VEHICLE_MOVED_FROM_MARK,
- COND_PASSENGER_CAN_FIDGET,
- COND_PASSENGER_CAN_ENTER_IMMEDIATELY,
- NEXT_CONDITION,
- };
-
- virtual bool CanSelectSchedule( void );
- virtual void Enable( CPropJeepEpisodic *pVehicle, bool bImmediateEnter = false);
- virtual void GatherConditions( void );
- virtual int SelectSchedule( void );
- virtual int SelectFailSchedule( int failedSchedule, int failedTask, AI_TaskFailureCode_t taskFailCode );
- virtual void StartTask( const Task_t *pTask );
- virtual void RunTask( const Task_t *pTask );
- virtual void AimGun( void );
- virtual void EnterVehicle( void );
- virtual void ExitVehicle( void );
- virtual void FinishEnterVehicle( void );
- virtual void FinishExitVehicle( void );
- virtual void BuildScheduleTestBits( void );
- virtual Activity NPC_TranslateActivity( Activity activity );
- virtual bool CanExitVehicle( void );
- virtual bool IsValidEnemy( CBaseEntity *pEntity );
- virtual void OnUpdateShotRegulator( void );
- virtual bool IsNavigationUrgent( void );
- virtual bool IsCurTaskContinuousMove( void );
- virtual bool IsCrouching( void );
-
-private:
-
- void SpeakVehicleConditions( void );
- virtual void OnExitVehicleFailed( void );
-
- bool CanFidget( void );
- bool UseRadialRouteToEntryPoint( const Vector &vecEntryPoint );
- float GetArcToEntryPoint( const Vector &vecCenterPoint, const Vector &vecEntryPoint, bool &bClockwise );
- int SelectScheduleInsideVehicle( void );
- int SelectScheduleOutsideVehicle( void );
- bool FindPathToVehicleEntryPoint( void );
- bool CanEnterVehicleImmediately( int *pResultSequence, Vector *pResultPos, QAngle *pResultAngles );
- void EnterVehicleImmediately( void );
-
- // ------------------------------------------
- // Passenger sensing
- // ------------------------------------------
-
- virtual void GatherVehicleStateConditions( void );
-
- float GetVehicleSpeed( void );
- void GatherVehicleCollisionConditions( const Vector &localVelocity );
-
- // ------------------------------------------
- // Overturned tracking
- // ------------------------------------------
- void UpdateStuckStatus( void );
- bool CanExitAtPosition( const Vector &vecTestPos );
- bool GetStuckExitPos( Vector *vecResult );
- bool ExitStuckVehicle( void );
-
- bool UpdateVehicleEntrancePath( void );
- bool PointIsWithinEntryFailureRadius( const Vector &vecPosition );
- void ResetVehicleEntryFailedState( void );
- void MarkVehicleEntryFailed( const Vector &vecPosition );
- virtual int FindEntrySequence( bool bNearest = false );
- void CalculateBodyLean( void );
-
- float m_flNextJostleTime;
- float m_flNextOverturnWarning; // The next time the NPC may complained about being upside-down
- float m_flOverturnedDuration; // Amount of time we've been stuck in the vehicle (unable to exit)
- float m_flUnseenDuration; // Amount of time we've been hidden from the player's view
-
- float m_flEnterBeginTime; // Time the NPC started to try and enter the vehicle
- int m_nExitAttempts; // Number of times we've attempted to exit the vehicle but failed
- int m_nVisibleEnemies; // Keeps a record of how many enemies I know about
- float m_flLastLateralLean; // Our last lean value
-
- CAI_MoveMonitor m_VehicleMonitor; // Used to keep track of the vehicle's movement relative to a mark
- CUtlVector<FailPosition_t> m_FailedEntryPositions; // Used to keep track of the vehicle's movement relative to a mark
-
-protected:
- virtual int SelectTransitionSchedule( void );
-
- void ExtendFidgetDelay( float flDuration );
- bool CanPlayJostle( bool bLargeJostle );
-
- float m_flEntraceUpdateTime;
- float m_flNextEnterAttempt;
- float m_flNextFidgetTime;
- CHandle< CNPC_PlayerCompanion > m_hCompanion;
-
- DEFINE_CUSTOM_SCHEDULE_PROVIDER;
-};
-
-#endif // AI_BEHAVIOR_PASSENGER_COMPANION_H
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================
+
+#ifndef AI_BEHAVIOR_PASSENGER_COMPANION_H
+#define AI_BEHAVIOR_PASSENGER_COMPANION_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "ai_behavior_passenger.h"
+
+class CNPC_PlayerCompanion;
+
+struct VehicleAvoidParams_t
+{
+ Vector vecStartPos;
+ Vector vecGoalPos;
+ Vector *pNodePositions;
+ int nNumNodes;
+ int nDirection;
+ int nStartNode;
+ int nEndNode;
+};
+
+struct FailPosition_t
+{
+ Vector vecPosition;
+ float flTime;
+
+ DECLARE_SIMPLE_DATADESC();
+};
+
+class CAI_PassengerBehaviorCompanion : public CAI_PassengerBehavior
+{
+ DECLARE_CLASS( CAI_PassengerBehaviorCompanion, CAI_PassengerBehavior );
+ DECLARE_DATADESC()
+
+public:
+
+ CAI_PassengerBehaviorCompanion( void );
+
+ enum
+ {
+ // Schedules
+ SCHED_PASSENGER_RUN_TO_ENTER_VEHICLE = BaseClass::NEXT_SCHEDULE,
+ SCHED_PASSENGER_RUN_TO_ENTER_VEHICLE_FAILED,
+ SCHED_PASSENGER_ENTER_VEHICLE_PAUSE,
+ SCHED_PASSENGER_RANGE_ATTACK1,
+ SCHED_PASSENGER_RELOAD,
+ SCHED_PASSENGER_EXIT_STUCK_VEHICLE,
+ SCHED_PASSENGER_OVERTURNED,
+ SCHED_PASSENGER_IMPACT,
+ SCHED_PASSENGER_ENTER_VEHICLE_IMMEDIATELY,
+ SCHED_PASSENGER_COWER,
+ SCHED_PASSENGER_FIDGET,
+ NEXT_SCHEDULE,
+
+ // Tasks
+ TASK_GET_PATH_TO_VEHICLE_ENTRY_POINT = BaseClass::NEXT_TASK,
+ TASK_GET_PATH_TO_NEAR_VEHICLE,
+ TASK_PASSENGER_RELOAD,
+ TASK_PASSENGER_EXIT_STUCK_VEHICLE,
+ TASK_PASSENGER_OVERTURNED,
+ TASK_PASSENGER_IMPACT,
+ TASK_RUN_TO_VEHICLE_ENTRANCE,
+ NEXT_TASK,
+
+ // Conditions
+
+ COND_PASSENGER_CAN_LEAVE_STUCK_VEHICLE = BaseClass::NEXT_CONDITION,
+ COND_PASSENGER_WARN_OVERTURNED,
+ COND_PASSENGER_WARN_COLLISION,
+ COND_PASSENGER_VEHICLE_MOVED_FROM_MARK,
+ COND_PASSENGER_CAN_FIDGET,
+ COND_PASSENGER_CAN_ENTER_IMMEDIATELY,
+ NEXT_CONDITION,
+ };
+
+ virtual bool CanSelectSchedule( void );
+ virtual void Enable( CPropJeepEpisodic *pVehicle, bool bImmediateEnter = false);
+ virtual void GatherConditions( void );
+ virtual int SelectSchedule( void );
+ virtual int SelectFailSchedule( int failedSchedule, int failedTask, AI_TaskFailureCode_t taskFailCode );
+ virtual void StartTask( const Task_t *pTask );
+ virtual void RunTask( const Task_t *pTask );
+ virtual void AimGun( void );
+ virtual void EnterVehicle( void );
+ virtual void ExitVehicle( void );
+ virtual void FinishEnterVehicle( void );
+ virtual void FinishExitVehicle( void );
+ virtual void BuildScheduleTestBits( void );
+ virtual Activity NPC_TranslateActivity( Activity activity );
+ virtual bool CanExitVehicle( void );
+ virtual bool IsValidEnemy( CBaseEntity *pEntity );
+ virtual void OnUpdateShotRegulator( void );
+ virtual bool IsNavigationUrgent( void );
+ virtual bool IsCurTaskContinuousMove( void );
+ virtual bool IsCrouching( void );
+
+private:
+
+ void SpeakVehicleConditions( void );
+ virtual void OnExitVehicleFailed( void );
+
+ bool CanFidget( void );
+ bool UseRadialRouteToEntryPoint( const Vector &vecEntryPoint );
+ float GetArcToEntryPoint( const Vector &vecCenterPoint, const Vector &vecEntryPoint, bool &bClockwise );
+ int SelectScheduleInsideVehicle( void );
+ int SelectScheduleOutsideVehicle( void );
+ bool FindPathToVehicleEntryPoint( void );
+ bool CanEnterVehicleImmediately( int *pResultSequence, Vector *pResultPos, QAngle *pResultAngles );
+ void EnterVehicleImmediately( void );
+
+ // ------------------------------------------
+ // Passenger sensing
+ // ------------------------------------------
+
+ virtual void GatherVehicleStateConditions( void );
+
+ float GetVehicleSpeed( void );
+ void GatherVehicleCollisionConditions( const Vector &localVelocity );
+
+ // ------------------------------------------
+ // Overturned tracking
+ // ------------------------------------------
+ void UpdateStuckStatus( void );
+ bool CanExitAtPosition( const Vector &vecTestPos );
+ bool GetStuckExitPos( Vector *vecResult );
+ bool ExitStuckVehicle( void );
+
+ bool UpdateVehicleEntrancePath( void );
+ bool PointIsWithinEntryFailureRadius( const Vector &vecPosition );
+ void ResetVehicleEntryFailedState( void );
+ void MarkVehicleEntryFailed( const Vector &vecPosition );
+ virtual int FindEntrySequence( bool bNearest = false );
+ void CalculateBodyLean( void );
+
+ float m_flNextJostleTime;
+ float m_flNextOverturnWarning; // The next time the NPC may complained about being upside-down
+ float m_flOverturnedDuration; // Amount of time we've been stuck in the vehicle (unable to exit)
+ float m_flUnseenDuration; // Amount of time we've been hidden from the player's view
+
+ float m_flEnterBeginTime; // Time the NPC started to try and enter the vehicle
+ int m_nExitAttempts; // Number of times we've attempted to exit the vehicle but failed
+ int m_nVisibleEnemies; // Keeps a record of how many enemies I know about
+ float m_flLastLateralLean; // Our last lean value
+
+ CAI_MoveMonitor m_VehicleMonitor; // Used to keep track of the vehicle's movement relative to a mark
+ CUtlVector<FailPosition_t> m_FailedEntryPositions; // Used to keep track of the vehicle's movement relative to a mark
+
+protected:
+ virtual int SelectTransitionSchedule( void );
+
+ void ExtendFidgetDelay( float flDuration );
+ bool CanPlayJostle( bool bLargeJostle );
+
+ float m_flEntraceUpdateTime;
+ float m_flNextEnterAttempt;
+ float m_flNextFidgetTime;
+ CHandle< CNPC_PlayerCompanion > m_hCompanion;
+
+ DEFINE_CUSTOM_SCHEDULE_PROVIDER;
+};
+
+#endif // AI_BEHAVIOR_PASSENGER_COMPANION_H
diff --git a/mp/src/game/server/episodic/ai_behavior_passenger_zombie.cpp b/mp/src/game/server/episodic/ai_behavior_passenger_zombie.cpp
index 41434287..0667530b 100644
--- a/mp/src/game/server/episodic/ai_behavior_passenger_zombie.cpp
+++ b/mp/src/game/server/episodic/ai_behavior_passenger_zombie.cpp
@@ -1,878 +1,878 @@
-//========= Copyright Valve Corporation, All rights reserved. ============//
-//
-// Purpose: Zombies on cars!
-//
-//=============================================================================
-
-#include "cbase.h"
-#include "npcevent.h"
-#include "ai_motor.h"
-#include "ai_senses.h"
-#include "vehicle_jeep_episodic.h"
-#include "npc_alyx_episodic.h"
-#include "ai_behavior_passenger_zombie.h"
-
-#define JUMP_ATTACH_DIST_THRESHOLD 1000
-#define JUMP_ATTACH_FACING_THRESHOLD DOT_45DEGREE
-
-#define ATTACH_PREDICTION_INTERVAL 0.2f
-#define ATTACH_PREDICTION_FACING_THRESHOLD 0.75f
-#define ATTACH_PREDICTION_DIST_THRESHOLD 128
-
-int ACT_PASSENGER_MELEE_ATTACK1;
-int ACT_PASSENGER_THREATEN;
-int ACT_PASSENGER_FLINCH;
-int ACT_PASSENGER_ZOMBIE_LEAP_LOOP;
-
-BEGIN_DATADESC( CAI_PassengerBehaviorZombie )
-
- DEFINE_FIELD( m_flLastVerticalLean, FIELD_FLOAT ),
- DEFINE_FIELD( m_flLastLateralLean, FIELD_FLOAT ),
- DEFINE_FIELD( m_flNextLeapTime, FIELD_TIME ),
-
-END_DATADESC();
-
-extern int AE_PASSENGER_PHYSICS_PUSH;
-
-//==============================================================================================
-// Passenger damage table
-//==============================================================================================
-static impactentry_t zombieLinearTable[] =
-{
- { 200*200, 100 },
-};
-
-static impactentry_t zombieAngularTable[] =
-{
- { 100*100, 100 },
-};
-
-impactdamagetable_t gZombiePassengerImpactDamageTable =
-{
- zombieLinearTable,
- zombieAngularTable,
-
- ARRAYSIZE(zombieLinearTable),
- ARRAYSIZE(zombieAngularTable),
-
- 24*24, // minimum linear speed squared
- 360*360, // minimum angular speed squared (360 deg/s to cause spin/slice damage)
- 2, // can't take damage from anything under 2kg
-
- 5, // anything less than 5kg is "small"
- 5, // never take more than 5 pts of damage from anything under 5kg
- 36*36, // <5kg objects must go faster than 36 in/s to do damage
-
- VPHYSICS_LARGE_OBJECT_MASS, // large mass in kg
- 4, // large mass scale (anything over 500kg does 4X as much energy to read from damage table)
- 5, // large mass falling scale (emphasize falling/crushing damage over sideways impacts since the stress will kill you anyway)
- 0.0f, // min vel
-};
-
-//-----------------------------------------------------------------------------
-// Constructor
-//-----------------------------------------------------------------------------
-CAI_PassengerBehaviorZombie::CAI_PassengerBehaviorZombie( void ) :
-m_flLastVerticalLean( 0.0f ),
-m_flLastLateralLean( 0.0f ),
-m_flNextLeapTime( 0.0f )
-{
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Output : Returns true on success, false on failure.
-//-----------------------------------------------------------------------------
-bool CAI_PassengerBehaviorZombie::CanEnterVehicle( void )
-{
- return false;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Translate into vehicle passengers
-//-----------------------------------------------------------------------------
-int CAI_PassengerBehaviorZombie::TranslateSchedule( int scheduleType )
-{
- // We do different animations when inside the vehicle
- if ( GetPassengerState() == PASSENGER_STATE_INSIDE )
- {
- if ( scheduleType == SCHED_MELEE_ATTACK1 )
- return SCHED_PASSENGER_ZOMBIE_MELEE_ATTACK1;
-
- if ( scheduleType == SCHED_RANGE_ATTACK1 )
- return SCHED_PASSENGER_ZOMBIE_RANGE_ATTACK1;
- }
-
- return BaseClass::TranslateSchedule( scheduleType );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : activity -
-// Output : Activity
-//-----------------------------------------------------------------------------
-Activity CAI_PassengerBehaviorZombie::NPC_TranslateActivity( Activity activity )
-{
- Activity nNewActivity = BaseClass::NPC_TranslateActivity( activity );
- if ( activity == ACT_IDLE )
- return (Activity) ACT_PASSENGER_IDLE;
-
- return nNewActivity;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Suppress melee attacks against enemies for the given duration
-// Input : flDuration - Amount of time to suppress the attacks
-//-----------------------------------------------------------------------------
-void CAI_PassengerBehaviorZombie::SuppressAttack( float flDuration )
-{
- GetOuter()->SetNextAttack( gpGlobals->curtime + flDuration );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Determines if an enemy is inside a vehicle or not
-// Output : Returns true if the enemy is outside the vehicle.
-//-----------------------------------------------------------------------------
-bool CAI_PassengerBehaviorZombie::EnemyInVehicle( void )
-{
- // Obviously they're not...
- if ( GetOuter()->GetEnemy() == NULL )
- return false;
-
- // See if they're in a vehicle, currently
- CBaseCombatCharacter *pCCEnemy = GetOuter()->GetEnemy()->MyCombatCharacterPointer();
- if ( pCCEnemy && pCCEnemy->IsInAVehicle() )
- return true;
-
- return false;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Select a schedule when we're outside of the vehicle
-//-----------------------------------------------------------------------------
-int CAI_PassengerBehaviorZombie::SelectOutsideSchedule( void )
-{
- // Attaching to target
- if ( HasCondition( COND_CAN_RANGE_ATTACK1 ) )
- return SCHED_PASSENGER_ZOMBIE_RANGE_ATTACK1;
-
- // Attack the player if we're able
- if ( HasCondition( COND_CAN_MELEE_ATTACK1 ) )
- return SCHED_MELEE_ATTACK1;
-
- // Attach to the vehicle
- if ( HasCondition( COND_PASSENGER_ZOMBIE_CAN_ATTACH_TO_VEHICLE ) )
- return SCHED_PASSENGER_ZOMBIE_ATTACH;
-
- // Otherwise chase after him
- return SCHED_PASSENGER_ZOMBIE_RUN_TO_VEHICLE;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Pick a schedule for being "inside" the vehicle
-//-----------------------------------------------------------------------------
-int CAI_PassengerBehaviorZombie::SelectInsideSchedule( void )
-{
- // Attacking target
- if ( HasCondition( COND_CAN_MELEE_ATTACK1 ) )
- return SCHED_PASSENGER_ZOMBIE_MELEE_ATTACK1;
-
- return SCHED_IDLE_STAND;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Move the zombie to the vehicle
-//-----------------------------------------------------------------------------
-int CAI_PassengerBehaviorZombie::SelectSchedule( void )
-{
- // See if our enemy got out
- if ( GetOuter()->GetEnemy() != NULL && EnemyInVehicle() == false )
- {
- if ( GetPassengerState() == PASSENGER_STATE_INSIDE )
- {
- // Exit the vehicle
- SetCondition( COND_PASSENGER_EXITING );
- }
- else if ( GetPassengerState() == PASSENGER_STATE_OUTSIDE )
- {
- // Our target has left the vehicle and we're outside as well, so give up
- Disable();
- return BaseClass::SelectSchedule();
- }
- }
-
- // Entering schedule
- if ( HasCondition( COND_PASSENGER_ENTERING ) )
- {
- ClearCondition( COND_PASSENGER_ENTERING );
- return SCHED_PASSENGER_ZOMBIE_ENTER_VEHICLE;
- }
-
- // Exiting schedule
- if ( HasCondition( COND_PASSENGER_EXITING ) )
- {
- ClearCondition( COND_PASSENGER_EXITING );
- return SCHED_PASSENGER_ZOMBIE_EXIT_VEHICLE;
- }
-
- // Select different schedules based on our state
- PassengerState_e nState = GetPassengerState();
- int nNewSchedule = SCHED_NONE;
-
- if ( nState == PASSENGER_STATE_INSIDE )
- {
- nNewSchedule = SelectInsideSchedule();
- if ( nNewSchedule != SCHED_NONE )
- return nNewSchedule;
- }
- else if ( nState == PASSENGER_STATE_OUTSIDE )
- {
- nNewSchedule = SelectOutsideSchedule();
- if ( nNewSchedule != SCHED_NONE )
- return nNewSchedule;
- }
-
- // Worst case he just stands here
- Assert(0);
- return SCHED_IDLE_STAND;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Output : Returns true on success, false on failure.
-//-----------------------------------------------------------------------------
-bool CAI_PassengerBehaviorZombie::CanJumpToAttachToVehicle( void )
-{
- // FIXME: Probably move this up one level and out of this function
- if ( m_flNextLeapTime > gpGlobals->curtime )
- return false;
-
- // Predict an attachment jump
- CBaseEntity *pEnemy = GetOuter()->GetEnemy();
-
- Vector vecPredictedPosition;
- UTIL_PredictedPosition( pEnemy, 1.0f, &vecPredictedPosition );
-
- float flDist = UTIL_DistApprox( vecPredictedPosition, GetOuter()->GetAbsOrigin() );
-
- // If we're facing them enough, allow the jump
- if ( ( flDist < JUMP_ATTACH_DIST_THRESHOLD ) && UTIL_IsFacingWithinTolerance( GetOuter(), pEnemy, JUMP_ATTACH_FACING_THRESHOLD ) )
- return true;
-
- return false;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Determine if we can jump to be on the enemy's vehicle
-// Output : Returns true on success, false on failure.
-//-----------------------------------------------------------------------------
-inline bool CAI_PassengerBehaviorZombie::CanBeOnEnemyVehicle( void )
-{
- CBaseCombatCharacter *pEnemy = ToBaseCombatCharacter( GetOuter()->GetEnemy() );
- if ( pEnemy != NULL )
- {
- IServerVehicle *pVehicle = pEnemy->GetVehicle();
- if ( pVehicle && pVehicle->NPC_HasAvailableSeat( GetRoleName() ) )
- return true;
- }
-
- return false;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CAI_PassengerBehaviorZombie::GatherConditions( void )
-{
- BaseClass::GatherConditions();
-
- // Always clear the base conditions
- ClearCondition( COND_CAN_MELEE_ATTACK1 );
-
- // Behavior when outside the vehicle
- if ( GetPassengerState() == PASSENGER_STATE_OUTSIDE )
- {
- if ( CanBeOnEnemyVehicle() && CanJumpToAttachToVehicle() )
- {
- SetCondition( COND_CAN_RANGE_ATTACK1 );
- }
-
- // Determine if we can latch on to the vehicle (out of sight)
- ClearCondition( COND_PASSENGER_ZOMBIE_CAN_ATTACH_TO_VEHICLE );
- CBasePlayer *pPlayer = AI_GetSinglePlayer();
-
- if ( pPlayer != NULL &&
- GetOuter()->GetEnemy() == pPlayer &&
- pPlayer->GetVehicleEntity() == m_hVehicle )
- {
- // Can't be visible to the player and must be close enough
- bool bNotVisibleToPlayer = ( pPlayer->FInViewCone( GetOuter() ) == false );
- float flDistSqr = ( pPlayer->GetAbsOrigin() - GetOuter()->GetAbsOrigin() ).LengthSqr();
- bool bInRange = ( flDistSqr < Square(250.0f) );
- if ( bNotVisibleToPlayer && bInRange )
- {
- // We can latch on and "enter" the vehicle
- SetCondition( COND_PASSENGER_ZOMBIE_CAN_ATTACH_TO_VEHICLE );
- }
- else if ( bNotVisibleToPlayer == false && flDistSqr < Square(128.0f) )
- {
- // Otherwise just hit the vehicle in anger
- SetCondition( COND_CAN_MELEE_ATTACK1 );
- }
- }
- }
-
- // Behavior when on the car
- if ( GetPassengerState() == PASSENGER_STATE_INSIDE )
- {
- // Check for melee attack
- if ( GetOuter()->GetNextAttack() < gpGlobals->curtime )
- {
- SetCondition( COND_CAN_MELEE_ATTACK1 );
- }
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Handle death case
-//-----------------------------------------------------------------------------
-void CAI_PassengerBehaviorZombie::Event_Killed( const CTakeDamageInfo &info )
-{
- if ( m_hVehicle )
- {
- // Stop taking messages from the vehicle
- m_hVehicle->RemovePhysicsChild( GetOuter() );
- m_hVehicle->NPC_RemovePassenger( GetOuter() );
- m_hVehicle->NPC_FinishedExitVehicle( GetOuter(), false );
- }
-
- BaseClass::Event_Killed( info );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Build our custom interrupt cases for the behavior
-//-----------------------------------------------------------------------------
-void CAI_PassengerBehaviorZombie::BuildScheduleTestBits( void )
-{
- // Always interrupt when we need to get in or out
- if ( GetPassengerState() == PASSENGER_STATE_OUTSIDE )
- {
- GetOuter()->SetCustomInterruptCondition( GetClassScheduleIdSpace()->ConditionLocalToGlobal( COND_CAN_RANGE_ATTACK1 ) );
- GetOuter()->SetCustomInterruptCondition( GetClassScheduleIdSpace()->ConditionLocalToGlobal( COND_PASSENGER_ENTERING ) );
- }
-
- BaseClass::BuildScheduleTestBits();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Get the absolute position of the desired attachment point
-//-----------------------------------------------------------------------------
-void CAI_PassengerBehaviorZombie::GetAttachmentPoint( Vector *vecPoint )
-{
- Vector vecEntryOffset, vecFinalOffset;
- GetEntryTarget( &vecEntryOffset, NULL );
- VectorRotate( vecEntryOffset, m_hVehicle->GetAbsAngles(), vecFinalOffset );
- *vecPoint = ( m_hVehicle->GetAbsOrigin() + vecFinalOffset );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Output : Returns true on success, false on failure.
-//-----------------------------------------------------------------------------
-int CAI_PassengerBehaviorZombie::FindExitSequence( void )
-{
- // Get a list of all our animations
- const PassengerSeatAnims_t *pExitAnims = m_hVehicle->GetServerVehicle()->NPC_GetPassengerSeatAnims( GetOuter(), PASSENGER_SEAT_EXIT );
- if ( pExitAnims == NULL )
- return -1;
-
- // Test each animation (sorted by priority) for the best match
- for ( int i = 0; i < pExitAnims->Count(); i++ )
- {
- // Find the activity for this animation name
- int nSequence = GetOuter()->LookupSequence( STRING( pExitAnims->Element(i).GetAnimationName() ) );
- Assert( nSequence != -1 );
- if ( nSequence == -1 )
- continue;
-
- return nSequence;
- }
-
- return -1;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CAI_PassengerBehaviorZombie::StartDismount( void )
-{
- // Leap off the vehicle
- int nSequence = FindExitSequence();
- Assert( nSequence != -1 );
-
- SetTransitionSequence( nSequence );
- GetOuter()->SetIdealActivity( ACT_SCRIPT_CUSTOM_MOVE );
-
- // This removes the NPC from the vehicle's handling and fires all necessary outputs
- m_hVehicle->RemovePhysicsChild( GetOuter() );
- m_hVehicle->NPC_RemovePassenger( GetOuter() );
- m_hVehicle->NPC_FinishedExitVehicle( GetOuter(), (IsPassengerHostile()==false) );
-
- // Detach from the parent
- GetOuter()->SetParent( NULL );
- GetOuter()->SetMoveType( MOVETYPE_STEP );
- GetMotor()->SetYawLocked( false );
-
- QAngle vecAngles = GetAbsAngles();
- vecAngles.z = 0.0f;
- GetOuter()->SetAbsAngles( vecAngles );
-
- // HACK: Will this work?
- IPhysicsObject *pPhysObj = GetOuter()->VPhysicsGetObject();
- if ( pPhysObj != NULL )
- {
- pPhysObj->EnableCollisions( true );
- }
-
- // Clear this
- m_PassengerIntent = PASSENGER_INTENT_NONE;
- SetPassengerState( PASSENGER_STATE_EXITING );
-
- // Get the velocity
- Vector vecUp, vecJumpDir;
- GetOuter()->GetVectors( &vecJumpDir, NULL, &vecUp );
-
- // Move back and up
- vecJumpDir *= random->RandomFloat( -400.0f, -500.0f );
- vecJumpDir += vecUp * 150.0f;
- GetOuter()->SetAbsVelocity( vecJumpDir );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CAI_PassengerBehaviorZombie::FinishDismount( void )
-{
- SetPassengerState( PASSENGER_STATE_OUTSIDE );
- Disable();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CAI_PassengerBehaviorZombie::StartTask( const Task_t *pTask )
-{
- switch ( pTask->iTask )
- {
- case TASK_FACE_HINTNODE:
- case TASK_FACE_LASTPOSITION:
- case TASK_FACE_SAVEPOSITION:
- case TASK_FACE_TARGET:
- case TASK_FACE_IDEAL:
- case TASK_FACE_SCRIPT:
- case TASK_FACE_PATH:
- TaskComplete();
- break;
-
- case TASK_PASSENGER_ZOMBIE_RANGE_ATTACK1:
- break;
-
- case TASK_MELEE_ATTACK1:
- {
- // Only override this if we're "in" the vehicle
- if ( GetPassengerState() != PASSENGER_STATE_INSIDE )
- {
- BaseClass::StartTask( pTask );
- break;
- }
-
- // Swipe
- GetOuter()->SetIdealActivity( (Activity) ACT_PASSENGER_MELEE_ATTACK1 );
-
- // Randomly attack again in the future
- float flWait = random->RandomFloat( 0.0f, 1.0f );
- SuppressAttack( flWait );
- }
- break;
-
- case TASK_PASSENGER_ZOMBIE_DISMOUNT:
- {
- // Start the process of dismounting from the vehicle
- StartDismount();
- }
- break;
-
- case TASK_PASSENGER_ZOMBIE_ATTACH:
- {
- if ( AttachToVehicle() )
- {
- TaskComplete();
- return;
- }
-
- TaskFail( "Unable to attach to vehicle!" );
- }
- break;
-
- default:
- BaseClass::StartTask( pTask );
- break;
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Handle task running
-//-----------------------------------------------------------------------------
-void CAI_PassengerBehaviorZombie::RunTask( const Task_t *pTask )
-{
- switch ( pTask->iTask )
- {
- case TASK_PASSENGER_ZOMBIE_RANGE_ATTACK1:
- {
- // Face the entry point
- Vector vecAttachPoint;
- GetAttachmentPoint( &vecAttachPoint );
- GetOuter()->GetMotor()->SetIdealYawToTarget( vecAttachPoint );
-
- // All done when you touch the ground
- if ( GetOuter()->GetFlags() & FL_ONGROUND )
- {
- m_flNextLeapTime = gpGlobals->curtime + 2.0f;
- TaskComplete();
- return;
- }
- }
- break;
-
- case TASK_MELEE_ATTACK1:
-
- if ( GetOuter()->IsSequenceFinished() )
- {
- TaskComplete();
- }
-
- break;
-
- case TASK_PASSENGER_ZOMBIE_DISMOUNT:
- {
- if ( GetOuter()->IsSequenceFinished() )
- {
- // Completely separate from the vehicle
- FinishDismount();
- TaskComplete();
- }
-
- break;
- }
-
- default:
- BaseClass::RunTask( pTask );
- break;
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Find the relative cost of an entry point based on facing
-// Input : &vecEntryPos - Position we're evaluating
-// Output : Returns the cost as a modified distance value
-//-----------------------------------------------------------------------------
-float CAI_PassengerBehaviorZombie::GetEntryPointCost( const Vector &vecEntryPos )
-{
- // FIXME: We don't care about cost any longer!
- return 1.0f;
-
- // Find the direction from us to the entry point
- Vector vecEntryDir = ( vecEntryPos - GetAbsOrigin() );
- float flCost = VectorNormalize( vecEntryDir );
-
- // Get our current facing
- Vector vecDir;
- GetOuter()->GetVectors( &vecDir, NULL, NULL );
-
- // Scale our cost by how closely it matches our facing
- float flDot = DotProduct( vecEntryDir, vecDir );
- if ( flDot < 0.0f )
- return FLT_MAX;
-
- flCost *= RemapValClamped( flDot, 1.0f, 0.0f, 1.0f, 2.0f );
-
- return flCost;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : bNearest -
-// Output : int
-//-----------------------------------------------------------------------------
-int CAI_PassengerBehaviorZombie::FindEntrySequence( bool bNearest /*= false*/ )
-{
- // Get a list of all our animations
- const PassengerSeatAnims_t *pEntryAnims = m_hVehicle->GetServerVehicle()->NPC_GetPassengerSeatAnims( GetOuter(), PASSENGER_SEAT_ENTRY );
- if ( pEntryAnims == NULL )
- return -1;
-
- Vector vecStartPos;
- const CPassengerSeatTransition *pTransition;
- float flBestCost = FLT_MAX;
- float flCost;
- int nBestSequence = -1;
- int nSequence = -1;
-
- // Test each animation (sorted by priority) for the best match
- for ( int i = 0; i < pEntryAnims->Count(); i++ )
- {
- // Find the activity for this animation name
- pTransition = &pEntryAnims->Element(i);
- nSequence = GetOuter()->LookupSequence( STRING( pTransition->GetAnimationName() ) );
-
- Assert( nSequence != -1 );
- if ( nSequence == -1 )
- continue;
-
- // Test this entry for validity
- GetEntryPoint( nSequence, &vecStartPos );
-
- // Evaluate the cost
- flCost = GetEntryPointCost( vecStartPos );
- if ( flCost < flBestCost )
- {
- nBestSequence = nSequence;
- flBestCost = flCost;
- continue;
- }
- }
-
- return nBestSequence;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CAI_PassengerBehaviorZombie::ExitVehicle( void )
-{
- BaseClass::ExitVehicle();
-
- // Remove us as a passenger
- m_hVehicle->NPC_RemovePassenger( GetOuter() );
- m_hVehicle->NPC_FinishedExitVehicle( GetOuter(), false );
-
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Calculate our body lean based on our delta velocity
-//-----------------------------------------------------------------------------
-void CAI_PassengerBehaviorZombie::CalculateBodyLean( void )
-{
- // Calculate our lateral displacement from a perfectly centered start
- float flLateralDisp = SimpleSplineRemapVal( m_vehicleState.m_vecLastAngles.z, 100.0f, -100.0f, -1.0f, 1.0f );
- flLateralDisp = clamp( flLateralDisp, -1.0f, 1.0f );
-
- // FIXME: Framerate dependant!
- m_flLastLateralLean = ( m_flLastLateralLean * 0.2f ) + ( flLateralDisp * 0.8f );
-
- // Factor in a "stun" if the zombie was moved too far off course
- if ( fabs( m_flLastLateralLean ) > 0.75f )
- {
- SuppressAttack( 0.5f );
- }
-
- // Calc our vertical displacement
- float flVerticalDisp = SimpleSplineRemapVal( m_vehicleState.m_vecDeltaVelocity.z, -50.0f, 50.0f, -1.0f, 1.0f );
- flVerticalDisp = clamp( flVerticalDisp, -1.0f, 1.0f );
-
- // FIXME: Framerate dependant!
- m_flLastVerticalLean = ( m_flLastVerticalLean * 0.75f ) + ( flVerticalDisp * 0.25f );
-
- // Set these parameters
- GetOuter()->SetPoseParameter( "lean_lateral", m_flLastLateralLean );
- GetOuter()->SetPoseParameter( "lean_vertical", m_flLastVerticalLean );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CAI_PassengerBehaviorZombie::GatherVehicleStateConditions( void )
-{
- // Call to the base
- BaseClass::GatherVehicleStateConditions();
-
- // Only do this if we're on the vehicle
- if ( GetPassengerState() != PASSENGER_STATE_INSIDE )
- return;
-
- // Calculate how our body is leaning
- CalculateBodyLean();
-
- // The forward delta of the vehicle
- float flLateralDelta = ( m_vehicleState.m_vecDeltaVelocity.x + m_vehicleState.m_vecDeltaVelocity.y );
-
- // Detect a sudden stop
- if ( flLateralDelta < -350.0f )
- {
- if ( m_hVehicle )
- {
- Vector vecDamageForce;
- m_hVehicle->GetVelocity( &vecDamageForce, NULL );
- VectorNormalize( vecDamageForce );
- vecDamageForce *= random->RandomFloat( 50000.0f, 60000.0f );
-
- //NDebugOverlay::HorzArrow( GetAbsOrigin(), GetAbsOrigin() + ( vecDamageForce * 256.0f ), 16.0f, 255, 0, 0, 16, true, 2.0f );
-
- // Fake it!
- CTakeDamageInfo info( m_hVehicle, m_hVehicle, vecDamageForce, GetOuter()->WorldSpaceCenter(), 200, (DMG_CRUSH|DMG_VEHICLE) );
- GetOuter()->TakeDamage( info );
- }
- }
- else if ( flLateralDelta < -150.0f )
- {
- // FIXME: Realistically this should interrupt and play a schedule to do it
- GetOuter()->SetIdealActivity( (Activity) ACT_PASSENGER_FLINCH );
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : *pEvent -
-//-----------------------------------------------------------------------------
-void CAI_PassengerBehaviorZombie::HandleAnimEvent( animevent_t *pEvent )
-{
- if ( pEvent->event == AE_PASSENGER_PHYSICS_PUSH )
- {
- // Add a push into the vehicle
- float flForce = (float) atof( pEvent->options );
- AddPhysicsPush( flForce * 0.75f );
- return;
- }
-
- BaseClass::HandleAnimEvent( pEvent );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Attach to the vehicle if we're able
-//-----------------------------------------------------------------------------
-bool CAI_PassengerBehaviorZombie::AttachToVehicle( void )
-{
- // Must be able to enter the vehicle
- if ( m_hVehicle->NPC_CanEnterVehicle( GetOuter(), false ) == false )
- return false;
-
- // Reserve the seat
- if ( ReserveEntryPoint( VEHICLE_SEAT_ANY ) == false )
- return false;
-
- // Use the best one we've found
- int nSequence = FindEntrySequence();
- if ( nSequence == -1 )
- return false;
-
- // Take the transition sequence
- SetTransitionSequence( nSequence );
-
- // Get in the vehicle
- EnterVehicle();
-
- // Start our scripted sequence with any other passengers
- // Find Alyx
- // TODO: Iterate through the list of passengers in the vehicle and find one we can interact with
- CNPC_Alyx *pAlyx = CNPC_Alyx::GetAlyx();
- if ( pAlyx )
- {
- // Tell Alyx to play along!
- pAlyx->ForceVehicleInteraction( GetOuter()->GetSequenceName( nSequence ), GetOuter() );
- }
-
- return true;
-}
-
-AI_BEGIN_CUSTOM_SCHEDULE_PROVIDER( CAI_PassengerBehaviorZombie )
-{
- DECLARE_ACTIVITY( ACT_PASSENGER_MELEE_ATTACK1 )
- DECLARE_ACTIVITY( ACT_PASSENGER_THREATEN )
- DECLARE_ACTIVITY( ACT_PASSENGER_FLINCH )
- DECLARE_ACTIVITY( ACT_PASSENGER_ZOMBIE_LEAP_LOOP )
-
- DECLARE_TASK( TASK_PASSENGER_ZOMBIE_RANGE_ATTACK1 )
- DECLARE_TASK( TASK_PASSENGER_ZOMBIE_DISMOUNT )
- DECLARE_TASK( TASK_PASSENGER_ZOMBIE_ATTACH )
-
- DECLARE_CONDITION( COND_PASSENGER_ZOMBIE_CAN_ATTACH_TO_VEHICLE )
-
- DEFINE_SCHEDULE
- (
- SCHED_PASSENGER_ZOMBIE_ENTER_VEHICLE,
-
- " Tasks"
- " TASK_PASSENGER_ATTACH_TO_VEHICLE 0"
- " TASK_PASSENGER_ENTER_VEHICLE 0"
- ""
- " Interrupts"
- )
-
- DEFINE_SCHEDULE
- (
- SCHED_PASSENGER_ZOMBIE_EXIT_VEHICLE,
-
- " Tasks"
- " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_PASSENGER_IDLE"
- " TASK_STOP_MOVING 0"
- " TASK_PASSENGER_ZOMBIE_DISMOUNT 0"
- ""
- " Interrupts"
- " COND_TASK_FAILED"
- )
-
- DEFINE_SCHEDULE
- (
- SCHED_PASSENGER_ZOMBIE_MELEE_ATTACK1,
-
- " Tasks"
- " TASK_ANNOUNCE_ATTACK 1"
- " TASK_MELEE_ATTACK1 0"
- ""
- " Interrupts"
- )
-
- DEFINE_SCHEDULE
- (
- SCHED_PASSENGER_ZOMBIE_RANGE_ATTACK1,
-
- " Tasks"
- " TASK_PLAY_SEQUENCE ACTIVITY:ACT_PASSENGER_RANGE_ATTACK1"
- " TASK_SET_ACTIVITY ACTIVITY:ACT_PASSENGER_ZOMBIE_LEAP_LOOP"
- " TASK_PASSENGER_ZOMBIE_RANGE_ATTACK1 0"
- " "
- " Interrupts"
- )
-
- DEFINE_SCHEDULE
- (
- SCHED_PASSENGER_ZOMBIE_RUN_TO_VEHICLE,
-
- " Tasks"
- " TASK_STOP_MOVING 0"
- " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_CHASE_ENEMY_FAILED"
- " TASK_GET_CHASE_PATH_TO_ENEMY 2400"
- " TASK_RUN_PATH 0"
- " TASK_WAIT_FOR_MOVEMENT 0"
- ""
- " Interrupts"
- " COND_NEW_ENEMY"
- " COND_ENEMY_DEAD"
- " COND_ENEMY_UNREACHABLE"
- " COND_TASK_FAILED"
- " COND_LOST_ENEMY"
- " COND_PASSENGER_ZOMBIE_CAN_ATTACH_TO_VEHICLE"
- )
-
- DEFINE_SCHEDULE
- (
- SCHED_PASSENGER_ZOMBIE_ATTACH,
-
- " Tasks"
- " TASK_PASSENGER_ZOMBIE_ATTACH 0"
- ""
- " Interrupts"
- )
-
- AI_END_CUSTOM_SCHEDULE_PROVIDER()
-}
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Zombies on cars!
+//
+//=============================================================================
+
+#include "cbase.h"
+#include "npcevent.h"
+#include "ai_motor.h"
+#include "ai_senses.h"
+#include "vehicle_jeep_episodic.h"
+#include "npc_alyx_episodic.h"
+#include "ai_behavior_passenger_zombie.h"
+
+#define JUMP_ATTACH_DIST_THRESHOLD 1000
+#define JUMP_ATTACH_FACING_THRESHOLD DOT_45DEGREE
+
+#define ATTACH_PREDICTION_INTERVAL 0.2f
+#define ATTACH_PREDICTION_FACING_THRESHOLD 0.75f
+#define ATTACH_PREDICTION_DIST_THRESHOLD 128
+
+int ACT_PASSENGER_MELEE_ATTACK1;
+int ACT_PASSENGER_THREATEN;
+int ACT_PASSENGER_FLINCH;
+int ACT_PASSENGER_ZOMBIE_LEAP_LOOP;
+
+BEGIN_DATADESC( CAI_PassengerBehaviorZombie )
+
+ DEFINE_FIELD( m_flLastVerticalLean, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flLastLateralLean, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flNextLeapTime, FIELD_TIME ),
+
+END_DATADESC();
+
+extern int AE_PASSENGER_PHYSICS_PUSH;
+
+//==============================================================================================
+// Passenger damage table
+//==============================================================================================
+static impactentry_t zombieLinearTable[] =
+{
+ { 200*200, 100 },
+};
+
+static impactentry_t zombieAngularTable[] =
+{
+ { 100*100, 100 },
+};
+
+impactdamagetable_t gZombiePassengerImpactDamageTable =
+{
+ zombieLinearTable,
+ zombieAngularTable,
+
+ ARRAYSIZE(zombieLinearTable),
+ ARRAYSIZE(zombieAngularTable),
+
+ 24*24, // minimum linear speed squared
+ 360*360, // minimum angular speed squared (360 deg/s to cause spin/slice damage)
+ 2, // can't take damage from anything under 2kg
+
+ 5, // anything less than 5kg is "small"
+ 5, // never take more than 5 pts of damage from anything under 5kg
+ 36*36, // <5kg objects must go faster than 36 in/s to do damage
+
+ VPHYSICS_LARGE_OBJECT_MASS, // large mass in kg
+ 4, // large mass scale (anything over 500kg does 4X as much energy to read from damage table)
+ 5, // large mass falling scale (emphasize falling/crushing damage over sideways impacts since the stress will kill you anyway)
+ 0.0f, // min vel
+};
+
+//-----------------------------------------------------------------------------
+// Constructor
+//-----------------------------------------------------------------------------
+CAI_PassengerBehaviorZombie::CAI_PassengerBehaviorZombie( void ) :
+m_flLastVerticalLean( 0.0f ),
+m_flLastLateralLean( 0.0f ),
+m_flNextLeapTime( 0.0f )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CAI_PassengerBehaviorZombie::CanEnterVehicle( void )
+{
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Translate into vehicle passengers
+//-----------------------------------------------------------------------------
+int CAI_PassengerBehaviorZombie::TranslateSchedule( int scheduleType )
+{
+ // We do different animations when inside the vehicle
+ if ( GetPassengerState() == PASSENGER_STATE_INSIDE )
+ {
+ if ( scheduleType == SCHED_MELEE_ATTACK1 )
+ return SCHED_PASSENGER_ZOMBIE_MELEE_ATTACK1;
+
+ if ( scheduleType == SCHED_RANGE_ATTACK1 )
+ return SCHED_PASSENGER_ZOMBIE_RANGE_ATTACK1;
+ }
+
+ return BaseClass::TranslateSchedule( scheduleType );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : activity -
+// Output : Activity
+//-----------------------------------------------------------------------------
+Activity CAI_PassengerBehaviorZombie::NPC_TranslateActivity( Activity activity )
+{
+ Activity nNewActivity = BaseClass::NPC_TranslateActivity( activity );
+ if ( activity == ACT_IDLE )
+ return (Activity) ACT_PASSENGER_IDLE;
+
+ return nNewActivity;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Suppress melee attacks against enemies for the given duration
+// Input : flDuration - Amount of time to suppress the attacks
+//-----------------------------------------------------------------------------
+void CAI_PassengerBehaviorZombie::SuppressAttack( float flDuration )
+{
+ GetOuter()->SetNextAttack( gpGlobals->curtime + flDuration );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Determines if an enemy is inside a vehicle or not
+// Output : Returns true if the enemy is outside the vehicle.
+//-----------------------------------------------------------------------------
+bool CAI_PassengerBehaviorZombie::EnemyInVehicle( void )
+{
+ // Obviously they're not...
+ if ( GetOuter()->GetEnemy() == NULL )
+ return false;
+
+ // See if they're in a vehicle, currently
+ CBaseCombatCharacter *pCCEnemy = GetOuter()->GetEnemy()->MyCombatCharacterPointer();
+ if ( pCCEnemy && pCCEnemy->IsInAVehicle() )
+ return true;
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Select a schedule when we're outside of the vehicle
+//-----------------------------------------------------------------------------
+int CAI_PassengerBehaviorZombie::SelectOutsideSchedule( void )
+{
+ // Attaching to target
+ if ( HasCondition( COND_CAN_RANGE_ATTACK1 ) )
+ return SCHED_PASSENGER_ZOMBIE_RANGE_ATTACK1;
+
+ // Attack the player if we're able
+ if ( HasCondition( COND_CAN_MELEE_ATTACK1 ) )
+ return SCHED_MELEE_ATTACK1;
+
+ // Attach to the vehicle
+ if ( HasCondition( COND_PASSENGER_ZOMBIE_CAN_ATTACH_TO_VEHICLE ) )
+ return SCHED_PASSENGER_ZOMBIE_ATTACH;
+
+ // Otherwise chase after him
+ return SCHED_PASSENGER_ZOMBIE_RUN_TO_VEHICLE;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Pick a schedule for being "inside" the vehicle
+//-----------------------------------------------------------------------------
+int CAI_PassengerBehaviorZombie::SelectInsideSchedule( void )
+{
+ // Attacking target
+ if ( HasCondition( COND_CAN_MELEE_ATTACK1 ) )
+ return SCHED_PASSENGER_ZOMBIE_MELEE_ATTACK1;
+
+ return SCHED_IDLE_STAND;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Move the zombie to the vehicle
+//-----------------------------------------------------------------------------
+int CAI_PassengerBehaviorZombie::SelectSchedule( void )
+{
+ // See if our enemy got out
+ if ( GetOuter()->GetEnemy() != NULL && EnemyInVehicle() == false )
+ {
+ if ( GetPassengerState() == PASSENGER_STATE_INSIDE )
+ {
+ // Exit the vehicle
+ SetCondition( COND_PASSENGER_EXITING );
+ }
+ else if ( GetPassengerState() == PASSENGER_STATE_OUTSIDE )
+ {
+ // Our target has left the vehicle and we're outside as well, so give up
+ Disable();
+ return BaseClass::SelectSchedule();
+ }
+ }
+
+ // Entering schedule
+ if ( HasCondition( COND_PASSENGER_ENTERING ) )
+ {
+ ClearCondition( COND_PASSENGER_ENTERING );
+ return SCHED_PASSENGER_ZOMBIE_ENTER_VEHICLE;
+ }
+
+ // Exiting schedule
+ if ( HasCondition( COND_PASSENGER_EXITING ) )
+ {
+ ClearCondition( COND_PASSENGER_EXITING );
+ return SCHED_PASSENGER_ZOMBIE_EXIT_VEHICLE;
+ }
+
+ // Select different schedules based on our state
+ PassengerState_e nState = GetPassengerState();
+ int nNewSchedule = SCHED_NONE;
+
+ if ( nState == PASSENGER_STATE_INSIDE )
+ {
+ nNewSchedule = SelectInsideSchedule();
+ if ( nNewSchedule != SCHED_NONE )
+ return nNewSchedule;
+ }
+ else if ( nState == PASSENGER_STATE_OUTSIDE )
+ {
+ nNewSchedule = SelectOutsideSchedule();
+ if ( nNewSchedule != SCHED_NONE )
+ return nNewSchedule;
+ }
+
+ // Worst case he just stands here
+ Assert(0);
+ return SCHED_IDLE_STAND;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CAI_PassengerBehaviorZombie::CanJumpToAttachToVehicle( void )
+{
+ // FIXME: Probably move this up one level and out of this function
+ if ( m_flNextLeapTime > gpGlobals->curtime )
+ return false;
+
+ // Predict an attachment jump
+ CBaseEntity *pEnemy = GetOuter()->GetEnemy();
+
+ Vector vecPredictedPosition;
+ UTIL_PredictedPosition( pEnemy, 1.0f, &vecPredictedPosition );
+
+ float flDist = UTIL_DistApprox( vecPredictedPosition, GetOuter()->GetAbsOrigin() );
+
+ // If we're facing them enough, allow the jump
+ if ( ( flDist < JUMP_ATTACH_DIST_THRESHOLD ) && UTIL_IsFacingWithinTolerance( GetOuter(), pEnemy, JUMP_ATTACH_FACING_THRESHOLD ) )
+ return true;
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Determine if we can jump to be on the enemy's vehicle
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+inline bool CAI_PassengerBehaviorZombie::CanBeOnEnemyVehicle( void )
+{
+ CBaseCombatCharacter *pEnemy = ToBaseCombatCharacter( GetOuter()->GetEnemy() );
+ if ( pEnemy != NULL )
+ {
+ IServerVehicle *pVehicle = pEnemy->GetVehicle();
+ if ( pVehicle && pVehicle->NPC_HasAvailableSeat( GetRoleName() ) )
+ return true;
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CAI_PassengerBehaviorZombie::GatherConditions( void )
+{
+ BaseClass::GatherConditions();
+
+ // Always clear the base conditions
+ ClearCondition( COND_CAN_MELEE_ATTACK1 );
+
+ // Behavior when outside the vehicle
+ if ( GetPassengerState() == PASSENGER_STATE_OUTSIDE )
+ {
+ if ( CanBeOnEnemyVehicle() && CanJumpToAttachToVehicle() )
+ {
+ SetCondition( COND_CAN_RANGE_ATTACK1 );
+ }
+
+ // Determine if we can latch on to the vehicle (out of sight)
+ ClearCondition( COND_PASSENGER_ZOMBIE_CAN_ATTACH_TO_VEHICLE );
+ CBasePlayer *pPlayer = AI_GetSinglePlayer();
+
+ if ( pPlayer != NULL &&
+ GetOuter()->GetEnemy() == pPlayer &&
+ pPlayer->GetVehicleEntity() == m_hVehicle )
+ {
+ // Can't be visible to the player and must be close enough
+ bool bNotVisibleToPlayer = ( pPlayer->FInViewCone( GetOuter() ) == false );
+ float flDistSqr = ( pPlayer->GetAbsOrigin() - GetOuter()->GetAbsOrigin() ).LengthSqr();
+ bool bInRange = ( flDistSqr < Square(250.0f) );
+ if ( bNotVisibleToPlayer && bInRange )
+ {
+ // We can latch on and "enter" the vehicle
+ SetCondition( COND_PASSENGER_ZOMBIE_CAN_ATTACH_TO_VEHICLE );
+ }
+ else if ( bNotVisibleToPlayer == false && flDistSqr < Square(128.0f) )
+ {
+ // Otherwise just hit the vehicle in anger
+ SetCondition( COND_CAN_MELEE_ATTACK1 );
+ }
+ }
+ }
+
+ // Behavior when on the car
+ if ( GetPassengerState() == PASSENGER_STATE_INSIDE )
+ {
+ // Check for melee attack
+ if ( GetOuter()->GetNextAttack() < gpGlobals->curtime )
+ {
+ SetCondition( COND_CAN_MELEE_ATTACK1 );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Handle death case
+//-----------------------------------------------------------------------------
+void CAI_PassengerBehaviorZombie::Event_Killed( const CTakeDamageInfo &info )
+{
+ if ( m_hVehicle )
+ {
+ // Stop taking messages from the vehicle
+ m_hVehicle->RemovePhysicsChild( GetOuter() );
+ m_hVehicle->NPC_RemovePassenger( GetOuter() );
+ m_hVehicle->NPC_FinishedExitVehicle( GetOuter(), false );
+ }
+
+ BaseClass::Event_Killed( info );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Build our custom interrupt cases for the behavior
+//-----------------------------------------------------------------------------
+void CAI_PassengerBehaviorZombie::BuildScheduleTestBits( void )
+{
+ // Always interrupt when we need to get in or out
+ if ( GetPassengerState() == PASSENGER_STATE_OUTSIDE )
+ {
+ GetOuter()->SetCustomInterruptCondition( GetClassScheduleIdSpace()->ConditionLocalToGlobal( COND_CAN_RANGE_ATTACK1 ) );
+ GetOuter()->SetCustomInterruptCondition( GetClassScheduleIdSpace()->ConditionLocalToGlobal( COND_PASSENGER_ENTERING ) );
+ }
+
+ BaseClass::BuildScheduleTestBits();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Get the absolute position of the desired attachment point
+//-----------------------------------------------------------------------------
+void CAI_PassengerBehaviorZombie::GetAttachmentPoint( Vector *vecPoint )
+{
+ Vector vecEntryOffset, vecFinalOffset;
+ GetEntryTarget( &vecEntryOffset, NULL );
+ VectorRotate( vecEntryOffset, m_hVehicle->GetAbsAngles(), vecFinalOffset );
+ *vecPoint = ( m_hVehicle->GetAbsOrigin() + vecFinalOffset );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+int CAI_PassengerBehaviorZombie::FindExitSequence( void )
+{
+ // Get a list of all our animations
+ const PassengerSeatAnims_t *pExitAnims = m_hVehicle->GetServerVehicle()->NPC_GetPassengerSeatAnims( GetOuter(), PASSENGER_SEAT_EXIT );
+ if ( pExitAnims == NULL )
+ return -1;
+
+ // Test each animation (sorted by priority) for the best match
+ for ( int i = 0; i < pExitAnims->Count(); i++ )
+ {
+ // Find the activity for this animation name
+ int nSequence = GetOuter()->LookupSequence( STRING( pExitAnims->Element(i).GetAnimationName() ) );
+ Assert( nSequence != -1 );
+ if ( nSequence == -1 )
+ continue;
+
+ return nSequence;
+ }
+
+ return -1;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CAI_PassengerBehaviorZombie::StartDismount( void )
+{
+ // Leap off the vehicle
+ int nSequence = FindExitSequence();
+ Assert( nSequence != -1 );
+
+ SetTransitionSequence( nSequence );
+ GetOuter()->SetIdealActivity( ACT_SCRIPT_CUSTOM_MOVE );
+
+ // This removes the NPC from the vehicle's handling and fires all necessary outputs
+ m_hVehicle->RemovePhysicsChild( GetOuter() );
+ m_hVehicle->NPC_RemovePassenger( GetOuter() );
+ m_hVehicle->NPC_FinishedExitVehicle( GetOuter(), (IsPassengerHostile()==false) );
+
+ // Detach from the parent
+ GetOuter()->SetParent( NULL );
+ GetOuter()->SetMoveType( MOVETYPE_STEP );
+ GetMotor()->SetYawLocked( false );
+
+ QAngle vecAngles = GetAbsAngles();
+ vecAngles.z = 0.0f;
+ GetOuter()->SetAbsAngles( vecAngles );
+
+ // HACK: Will this work?
+ IPhysicsObject *pPhysObj = GetOuter()->VPhysicsGetObject();
+ if ( pPhysObj != NULL )
+ {
+ pPhysObj->EnableCollisions( true );
+ }
+
+ // Clear this
+ m_PassengerIntent = PASSENGER_INTENT_NONE;
+ SetPassengerState( PASSENGER_STATE_EXITING );
+
+ // Get the velocity
+ Vector vecUp, vecJumpDir;
+ GetOuter()->GetVectors( &vecJumpDir, NULL, &vecUp );
+
+ // Move back and up
+ vecJumpDir *= random->RandomFloat( -400.0f, -500.0f );
+ vecJumpDir += vecUp * 150.0f;
+ GetOuter()->SetAbsVelocity( vecJumpDir );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CAI_PassengerBehaviorZombie::FinishDismount( void )
+{
+ SetPassengerState( PASSENGER_STATE_OUTSIDE );
+ Disable();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CAI_PassengerBehaviorZombie::StartTask( const Task_t *pTask )
+{
+ switch ( pTask->iTask )
+ {
+ case TASK_FACE_HINTNODE:
+ case TASK_FACE_LASTPOSITION:
+ case TASK_FACE_SAVEPOSITION:
+ case TASK_FACE_TARGET:
+ case TASK_FACE_IDEAL:
+ case TASK_FACE_SCRIPT:
+ case TASK_FACE_PATH:
+ TaskComplete();
+ break;
+
+ case TASK_PASSENGER_ZOMBIE_RANGE_ATTACK1:
+ break;
+
+ case TASK_MELEE_ATTACK1:
+ {
+ // Only override this if we're "in" the vehicle
+ if ( GetPassengerState() != PASSENGER_STATE_INSIDE )
+ {
+ BaseClass::StartTask( pTask );
+ break;
+ }
+
+ // Swipe
+ GetOuter()->SetIdealActivity( (Activity) ACT_PASSENGER_MELEE_ATTACK1 );
+
+ // Randomly attack again in the future
+ float flWait = random->RandomFloat( 0.0f, 1.0f );
+ SuppressAttack( flWait );
+ }
+ break;
+
+ case TASK_PASSENGER_ZOMBIE_DISMOUNT:
+ {
+ // Start the process of dismounting from the vehicle
+ StartDismount();
+ }
+ break;
+
+ case TASK_PASSENGER_ZOMBIE_ATTACH:
+ {
+ if ( AttachToVehicle() )
+ {
+ TaskComplete();
+ return;
+ }
+
+ TaskFail( "Unable to attach to vehicle!" );
+ }
+ break;
+
+ default:
+ BaseClass::StartTask( pTask );
+ break;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Handle task running
+//-----------------------------------------------------------------------------
+void CAI_PassengerBehaviorZombie::RunTask( const Task_t *pTask )
+{
+ switch ( pTask->iTask )
+ {
+ case TASK_PASSENGER_ZOMBIE_RANGE_ATTACK1:
+ {
+ // Face the entry point
+ Vector vecAttachPoint;
+ GetAttachmentPoint( &vecAttachPoint );
+ GetOuter()->GetMotor()->SetIdealYawToTarget( vecAttachPoint );
+
+ // All done when you touch the ground
+ if ( GetOuter()->GetFlags() & FL_ONGROUND )
+ {
+ m_flNextLeapTime = gpGlobals->curtime + 2.0f;
+ TaskComplete();
+ return;
+ }
+ }
+ break;
+
+ case TASK_MELEE_ATTACK1:
+
+ if ( GetOuter()->IsSequenceFinished() )
+ {
+ TaskComplete();
+ }
+
+ break;
+
+ case TASK_PASSENGER_ZOMBIE_DISMOUNT:
+ {
+ if ( GetOuter()->IsSequenceFinished() )
+ {
+ // Completely separate from the vehicle
+ FinishDismount();
+ TaskComplete();
+ }
+
+ break;
+ }
+
+ default:
+ BaseClass::RunTask( pTask );
+ break;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Find the relative cost of an entry point based on facing
+// Input : &vecEntryPos - Position we're evaluating
+// Output : Returns the cost as a modified distance value
+//-----------------------------------------------------------------------------
+float CAI_PassengerBehaviorZombie::GetEntryPointCost( const Vector &vecEntryPos )
+{
+ // FIXME: We don't care about cost any longer!
+ return 1.0f;
+
+ // Find the direction from us to the entry point
+ Vector vecEntryDir = ( vecEntryPos - GetAbsOrigin() );
+ float flCost = VectorNormalize( vecEntryDir );
+
+ // Get our current facing
+ Vector vecDir;
+ GetOuter()->GetVectors( &vecDir, NULL, NULL );
+
+ // Scale our cost by how closely it matches our facing
+ float flDot = DotProduct( vecEntryDir, vecDir );
+ if ( flDot < 0.0f )
+ return FLT_MAX;
+
+ flCost *= RemapValClamped( flDot, 1.0f, 0.0f, 1.0f, 2.0f );
+
+ return flCost;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : bNearest -
+// Output : int
+//-----------------------------------------------------------------------------
+int CAI_PassengerBehaviorZombie::FindEntrySequence( bool bNearest /*= false*/ )
+{
+ // Get a list of all our animations
+ const PassengerSeatAnims_t *pEntryAnims = m_hVehicle->GetServerVehicle()->NPC_GetPassengerSeatAnims( GetOuter(), PASSENGER_SEAT_ENTRY );
+ if ( pEntryAnims == NULL )
+ return -1;
+
+ Vector vecStartPos;
+ const CPassengerSeatTransition *pTransition;
+ float flBestCost = FLT_MAX;
+ float flCost;
+ int nBestSequence = -1;
+ int nSequence = -1;
+
+ // Test each animation (sorted by priority) for the best match
+ for ( int i = 0; i < pEntryAnims->Count(); i++ )
+ {
+ // Find the activity for this animation name
+ pTransition = &pEntryAnims->Element(i);
+ nSequence = GetOuter()->LookupSequence( STRING( pTransition->GetAnimationName() ) );
+
+ Assert( nSequence != -1 );
+ if ( nSequence == -1 )
+ continue;
+
+ // Test this entry for validity
+ GetEntryPoint( nSequence, &vecStartPos );
+
+ // Evaluate the cost
+ flCost = GetEntryPointCost( vecStartPos );
+ if ( flCost < flBestCost )
+ {
+ nBestSequence = nSequence;
+ flBestCost = flCost;
+ continue;
+ }
+ }
+
+ return nBestSequence;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CAI_PassengerBehaviorZombie::ExitVehicle( void )
+{
+ BaseClass::ExitVehicle();
+
+ // Remove us as a passenger
+ m_hVehicle->NPC_RemovePassenger( GetOuter() );
+ m_hVehicle->NPC_FinishedExitVehicle( GetOuter(), false );
+
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Calculate our body lean based on our delta velocity
+//-----------------------------------------------------------------------------
+void CAI_PassengerBehaviorZombie::CalculateBodyLean( void )
+{
+ // Calculate our lateral displacement from a perfectly centered start
+ float flLateralDisp = SimpleSplineRemapVal( m_vehicleState.m_vecLastAngles.z, 100.0f, -100.0f, -1.0f, 1.0f );
+ flLateralDisp = clamp( flLateralDisp, -1.0f, 1.0f );
+
+ // FIXME: Framerate dependant!
+ m_flLastLateralLean = ( m_flLastLateralLean * 0.2f ) + ( flLateralDisp * 0.8f );
+
+ // Factor in a "stun" if the zombie was moved too far off course
+ if ( fabs( m_flLastLateralLean ) > 0.75f )
+ {
+ SuppressAttack( 0.5f );
+ }
+
+ // Calc our vertical displacement
+ float flVerticalDisp = SimpleSplineRemapVal( m_vehicleState.m_vecDeltaVelocity.z, -50.0f, 50.0f, -1.0f, 1.0f );
+ flVerticalDisp = clamp( flVerticalDisp, -1.0f, 1.0f );
+
+ // FIXME: Framerate dependant!
+ m_flLastVerticalLean = ( m_flLastVerticalLean * 0.75f ) + ( flVerticalDisp * 0.25f );
+
+ // Set these parameters
+ GetOuter()->SetPoseParameter( "lean_lateral", m_flLastLateralLean );
+ GetOuter()->SetPoseParameter( "lean_vertical", m_flLastVerticalLean );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CAI_PassengerBehaviorZombie::GatherVehicleStateConditions( void )
+{
+ // Call to the base
+ BaseClass::GatherVehicleStateConditions();
+
+ // Only do this if we're on the vehicle
+ if ( GetPassengerState() != PASSENGER_STATE_INSIDE )
+ return;
+
+ // Calculate how our body is leaning
+ CalculateBodyLean();
+
+ // The forward delta of the vehicle
+ float flLateralDelta = ( m_vehicleState.m_vecDeltaVelocity.x + m_vehicleState.m_vecDeltaVelocity.y );
+
+ // Detect a sudden stop
+ if ( flLateralDelta < -350.0f )
+ {
+ if ( m_hVehicle )
+ {
+ Vector vecDamageForce;
+ m_hVehicle->GetVelocity( &vecDamageForce, NULL );
+ VectorNormalize( vecDamageForce );
+ vecDamageForce *= random->RandomFloat( 50000.0f, 60000.0f );
+
+ //NDebugOverlay::HorzArrow( GetAbsOrigin(), GetAbsOrigin() + ( vecDamageForce * 256.0f ), 16.0f, 255, 0, 0, 16, true, 2.0f );
+
+ // Fake it!
+ CTakeDamageInfo info( m_hVehicle, m_hVehicle, vecDamageForce, GetOuter()->WorldSpaceCenter(), 200, (DMG_CRUSH|DMG_VEHICLE) );
+ GetOuter()->TakeDamage( info );
+ }
+ }
+ else if ( flLateralDelta < -150.0f )
+ {
+ // FIXME: Realistically this should interrupt and play a schedule to do it
+ GetOuter()->SetIdealActivity( (Activity) ACT_PASSENGER_FLINCH );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pEvent -
+//-----------------------------------------------------------------------------
+void CAI_PassengerBehaviorZombie::HandleAnimEvent( animevent_t *pEvent )
+{
+ if ( pEvent->event == AE_PASSENGER_PHYSICS_PUSH )
+ {
+ // Add a push into the vehicle
+ float flForce = (float) atof( pEvent->options );
+ AddPhysicsPush( flForce * 0.75f );
+ return;
+ }
+
+ BaseClass::HandleAnimEvent( pEvent );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Attach to the vehicle if we're able
+//-----------------------------------------------------------------------------
+bool CAI_PassengerBehaviorZombie::AttachToVehicle( void )
+{
+ // Must be able to enter the vehicle
+ if ( m_hVehicle->NPC_CanEnterVehicle( GetOuter(), false ) == false )
+ return false;
+
+ // Reserve the seat
+ if ( ReserveEntryPoint( VEHICLE_SEAT_ANY ) == false )
+ return false;
+
+ // Use the best one we've found
+ int nSequence = FindEntrySequence();
+ if ( nSequence == -1 )
+ return false;
+
+ // Take the transition sequence
+ SetTransitionSequence( nSequence );
+
+ // Get in the vehicle
+ EnterVehicle();
+
+ // Start our scripted sequence with any other passengers
+ // Find Alyx
+ // TODO: Iterate through the list of passengers in the vehicle and find one we can interact with
+ CNPC_Alyx *pAlyx = CNPC_Alyx::GetAlyx();
+ if ( pAlyx )
+ {
+ // Tell Alyx to play along!
+ pAlyx->ForceVehicleInteraction( GetOuter()->GetSequenceName( nSequence ), GetOuter() );
+ }
+
+ return true;
+}
+
+AI_BEGIN_CUSTOM_SCHEDULE_PROVIDER( CAI_PassengerBehaviorZombie )
+{
+ DECLARE_ACTIVITY( ACT_PASSENGER_MELEE_ATTACK1 )
+ DECLARE_ACTIVITY( ACT_PASSENGER_THREATEN )
+ DECLARE_ACTIVITY( ACT_PASSENGER_FLINCH )
+ DECLARE_ACTIVITY( ACT_PASSENGER_ZOMBIE_LEAP_LOOP )
+
+ DECLARE_TASK( TASK_PASSENGER_ZOMBIE_RANGE_ATTACK1 )
+ DECLARE_TASK( TASK_PASSENGER_ZOMBIE_DISMOUNT )
+ DECLARE_TASK( TASK_PASSENGER_ZOMBIE_ATTACH )
+
+ DECLARE_CONDITION( COND_PASSENGER_ZOMBIE_CAN_ATTACH_TO_VEHICLE )
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_PASSENGER_ZOMBIE_ENTER_VEHICLE,
+
+ " Tasks"
+ " TASK_PASSENGER_ATTACH_TO_VEHICLE 0"
+ " TASK_PASSENGER_ENTER_VEHICLE 0"
+ ""
+ " Interrupts"
+ )
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_PASSENGER_ZOMBIE_EXIT_VEHICLE,
+
+ " Tasks"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_PASSENGER_IDLE"
+ " TASK_STOP_MOVING 0"
+ " TASK_PASSENGER_ZOMBIE_DISMOUNT 0"
+ ""
+ " Interrupts"
+ " COND_TASK_FAILED"
+ )
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_PASSENGER_ZOMBIE_MELEE_ATTACK1,
+
+ " Tasks"
+ " TASK_ANNOUNCE_ATTACK 1"
+ " TASK_MELEE_ATTACK1 0"
+ ""
+ " Interrupts"
+ )
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_PASSENGER_ZOMBIE_RANGE_ATTACK1,
+
+ " Tasks"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_PASSENGER_RANGE_ATTACK1"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_PASSENGER_ZOMBIE_LEAP_LOOP"
+ " TASK_PASSENGER_ZOMBIE_RANGE_ATTACK1 0"
+ " "
+ " Interrupts"
+ )
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_PASSENGER_ZOMBIE_RUN_TO_VEHICLE,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_CHASE_ENEMY_FAILED"
+ " TASK_GET_CHASE_PATH_TO_ENEMY 2400"
+ " TASK_RUN_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ ""
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_ENEMY_DEAD"
+ " COND_ENEMY_UNREACHABLE"
+ " COND_TASK_FAILED"
+ " COND_LOST_ENEMY"
+ " COND_PASSENGER_ZOMBIE_CAN_ATTACH_TO_VEHICLE"
+ )
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_PASSENGER_ZOMBIE_ATTACH,
+
+ " Tasks"
+ " TASK_PASSENGER_ZOMBIE_ATTACH 0"
+ ""
+ " Interrupts"
+ )
+
+ AI_END_CUSTOM_SCHEDULE_PROVIDER()
+}
diff --git a/mp/src/game/server/episodic/ai_behavior_passenger_zombie.h b/mp/src/game/server/episodic/ai_behavior_passenger_zombie.h
index eaeb58b5..98b2ea0a 100644
--- a/mp/src/game/server/episodic/ai_behavior_passenger_zombie.h
+++ b/mp/src/game/server/episodic/ai_behavior_passenger_zombie.h
@@ -1,97 +1,97 @@
-//========= Copyright Valve Corporation, All rights reserved. ============//
-//
-// Purpose: Zombies on cars!
-//
-//=============================================================================
-
-#ifndef AI_BEHAVIOR_PASSENGER_ZOMBIE_H
-#define AI_BEHAVIOR_PASSENGER_ZOMBIE_H
-#ifdef _WIN32
-#pragma once
-#endif
-
-#include "ai_behavior_passenger.h"
-#include "ai_utils.h"
-#include "vehicle_base.h"
-
-extern impactdamagetable_t gZombiePassengerImpactDamageTable;
-
-class CAI_PassengerBehaviorZombie : public CAI_PassengerBehavior
-{
- DECLARE_CLASS( CAI_PassengerBehaviorZombie, CAI_PassengerBehavior );
- DECLARE_DATADESC()
-
-public:
-
- CAI_PassengerBehaviorZombie( void );
-
- enum
- {
- // Schedules
- SCHED_PASSENGER_ZOMBIE_ENTER_VEHICLE = BaseClass::NEXT_SCHEDULE,
- SCHED_PASSENGER_ZOMBIE_EXIT_VEHICLE,
- SCHED_PASSENGER_ZOMBIE_MELEE_ATTACK1,
- SCHED_PASSENGER_ZOMBIE_RANGE_ATTACK1,
- SCHED_PASSENGER_ZOMBIE_ATTACH,
- SCHED_PASSENGER_ZOMBIE_RUN_TO_VEHICLE,
- NEXT_SCHEDULE,
-
- // Tasks
- TASK_PASSENGER_ZOMBIE_RANGE_ATTACK1 = BaseClass::NEXT_TASK,
- TASK_PASSENGER_ZOMBIE_DISMOUNT,
- TASK_PASSENGER_ZOMBIE_ATTACH,
- NEXT_TASK,
-
- // Conditions
- COND_PASSENGER_ZOMBIE_CAN_ATTACH_TO_VEHICLE = BaseClass::NEXT_CONDITION,
- NEXT_CONDITION
- };
-
- virtual const char *GetName( void ) { return "ZombiePassenger"; }
- virtual string_t GetRoleName( void ) { return MAKE_STRING( "passenger_zombie" ); }
- virtual int SelectSchedule( void );
- virtual int TranslateSchedule( int scheduleType );
- virtual void GatherConditions( void );
- virtual void Event_Killed( const CTakeDamageInfo &info );
- virtual void BuildScheduleTestBits( void );
- virtual void RunTask( const Task_t *pTask );
- virtual void StartTask( const Task_t *pTask );
- virtual bool CanEnterVehicle( void );
- virtual void ExitVehicle( void );
- virtual void HandleAnimEvent( animevent_t *pEvent );
- virtual Activity NPC_TranslateActivity( Activity activity );
-
- virtual bool AttachToVehicle( void );
-
- void SuppressAttack( float flDuration );
-
- DEFINE_CUSTOM_SCHEDULE_PROVIDER;
-
-protected:
-
- int SelectOutsideSchedule( void );
- int SelectInsideSchedule( void );
- virtual int FindExitSequence( void );
- void StartDismount( void );
- void FinishDismount( void );
- virtual void CalculateBodyLean( void );
- virtual void GatherVehicleStateConditions( void );
- virtual int FindEntrySequence( bool bNearest = false );
-
-private:
-
- void VehicleLeapAttackTouch( CBaseEntity *pOther );
- void VehicleLeapAttack( void );
- bool CanBeOnEnemyVehicle( void );
- float GetEntryPointCost( const Vector &vecEntryPos );
- bool EnemyInVehicle( void );
- void GetAttachmentPoint( Vector *vecPoint );
- bool CanJumpToAttachToVehicle( void );
- //bool WithinAttachRange( void );
-
- float m_flLastLateralLean;
- float m_flLastVerticalLean;
- float m_flNextLeapTime;
-};
-
-#endif // AI_BEHAVIOR_PASSENGER_ZOMBIE_H
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Zombies on cars!
+//
+//=============================================================================
+
+#ifndef AI_BEHAVIOR_PASSENGER_ZOMBIE_H
+#define AI_BEHAVIOR_PASSENGER_ZOMBIE_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "ai_behavior_passenger.h"
+#include "ai_utils.h"
+#include "vehicle_base.h"
+
+extern impactdamagetable_t gZombiePassengerImpactDamageTable;
+
+class CAI_PassengerBehaviorZombie : public CAI_PassengerBehavior
+{
+ DECLARE_CLASS( CAI_PassengerBehaviorZombie, CAI_PassengerBehavior );
+ DECLARE_DATADESC()
+
+public:
+
+ CAI_PassengerBehaviorZombie( void );
+
+ enum
+ {
+ // Schedules
+ SCHED_PASSENGER_ZOMBIE_ENTER_VEHICLE = BaseClass::NEXT_SCHEDULE,
+ SCHED_PASSENGER_ZOMBIE_EXIT_VEHICLE,
+ SCHED_PASSENGER_ZOMBIE_MELEE_ATTACK1,
+ SCHED_PASSENGER_ZOMBIE_RANGE_ATTACK1,
+ SCHED_PASSENGER_ZOMBIE_ATTACH,
+ SCHED_PASSENGER_ZOMBIE_RUN_TO_VEHICLE,
+ NEXT_SCHEDULE,
+
+ // Tasks
+ TASK_PASSENGER_ZOMBIE_RANGE_ATTACK1 = BaseClass::NEXT_TASK,
+ TASK_PASSENGER_ZOMBIE_DISMOUNT,
+ TASK_PASSENGER_ZOMBIE_ATTACH,
+ NEXT_TASK,
+
+ // Conditions
+ COND_PASSENGER_ZOMBIE_CAN_ATTACH_TO_VEHICLE = BaseClass::NEXT_CONDITION,
+ NEXT_CONDITION
+ };
+
+ virtual const char *GetName( void ) { return "ZombiePassenger"; }
+ virtual string_t GetRoleName( void ) { return MAKE_STRING( "passenger_zombie" ); }
+ virtual int SelectSchedule( void );
+ virtual int TranslateSchedule( int scheduleType );
+ virtual void GatherConditions( void );
+ virtual void Event_Killed( const CTakeDamageInfo &info );
+ virtual void BuildScheduleTestBits( void );
+ virtual void RunTask( const Task_t *pTask );
+ virtual void StartTask( const Task_t *pTask );
+ virtual bool CanEnterVehicle( void );
+ virtual void ExitVehicle( void );
+ virtual void HandleAnimEvent( animevent_t *pEvent );
+ virtual Activity NPC_TranslateActivity( Activity activity );
+
+ virtual bool AttachToVehicle( void );
+
+ void SuppressAttack( float flDuration );
+
+ DEFINE_CUSTOM_SCHEDULE_PROVIDER;
+
+protected:
+
+ int SelectOutsideSchedule( void );
+ int SelectInsideSchedule( void );
+ virtual int FindExitSequence( void );
+ void StartDismount( void );
+ void FinishDismount( void );
+ virtual void CalculateBodyLean( void );
+ virtual void GatherVehicleStateConditions( void );
+ virtual int FindEntrySequence( bool bNearest = false );
+
+private:
+
+ void VehicleLeapAttackTouch( CBaseEntity *pOther );
+ void VehicleLeapAttack( void );
+ bool CanBeOnEnemyVehicle( void );
+ float GetEntryPointCost( const Vector &vecEntryPos );
+ bool EnemyInVehicle( void );
+ void GetAttachmentPoint( Vector *vecPoint );
+ bool CanJumpToAttachToVehicle( void );
+ //bool WithinAttachRange( void );
+
+ float m_flLastLateralLean;
+ float m_flLastVerticalLean;
+ float m_flNextLeapTime;
+};
+
+#endif // AI_BEHAVIOR_PASSENGER_ZOMBIE_H
diff --git a/mp/src/game/server/episodic/ep1_gamestats.cpp b/mp/src/game/server/episodic/ep1_gamestats.cpp
index 58d3fe52..a82c27db 100644
--- a/mp/src/game/server/episodic/ep1_gamestats.cpp
+++ b/mp/src/game/server/episodic/ep1_gamestats.cpp
@@ -1,74 +1,74 @@
-//========= Copyright Valve Corporation, All rights reserved. ============//
-//
-// Purpose:
-//
-//=============================================================================
-
-//Gamestats was built for ep1, so this file is going to be amazingly short seeing as how ep1 set the standard
-
-#include "cbase.h"
-#include "ep1_gamestats.h"
-#include "tier1/utlbuffer.h"
-static CEP1GameStats s_CEP1GS_ThisJustSitsInMemory;
-
-// A bit of a hack to redirect the gamestats API for ep2 (ep3, etc.)
-extern CBaseGameStats *g_pEP2GameStats;
-
-CEP1GameStats::CEP1GameStats( void )
-{
- gamestats = &s_CEP1GS_ThisJustSitsInMemory;
-}
-
-CBaseGameStats *CEP1GameStats::OnInit( CBaseGameStats *pCurrentGameStats, char const *gamedir )
-{
- if ( !Q_stricmp( gamedir, "ep2" ) )
- {
- return g_pEP2GameStats;
- }
-
- return pCurrentGameStats;
-}
-
-const char *CEP1GameStats::GetStatSaveFileName( void )
-{
- return "ep1_gamestats.dat"; //overriding the default for backwards compatibility with release stat tracking code
-}
-
-const char *CEP1GameStats::GetStatUploadRegistryKeyName( void )
-{
- return "GameStatsUpload_Ep1"; //overriding the default for backwards compatibility with release stat tracking code
-}
-
-
-static char const *ep1Maps[] =
-{
- "ep1_citadel_00",
- "ep1_citadel_01",
- "ep1_citadel_02",
- "ep1_citadel_02b",
- "ep1_citadel_03",
- "ep1_citadel_04",
- "ep1_c17_00",
- "ep1_c17_00a",
- "ep1_c17_01",
- "ep1_c17_02",
- "ep1_c17_02b",
- "ep1_c17_02a",
- "ep1_c17_05",
- "ep1_c17_06",
-};
-
-
-bool CEP1GameStats::UserPlayedAllTheMaps( void )
-{
- int c = ARRAYSIZE( ep1Maps );
- for ( int i = 0; i < c; ++i )
- {
- int idx = m_BasicStats.m_MapTotals.Find( ep1Maps[ i ] );
- if( idx == m_BasicStats.m_MapTotals.InvalidIndex() )
- return false;
- }
-
- return true;
-}
-
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================
+
+//Gamestats was built for ep1, so this file is going to be amazingly short seeing as how ep1 set the standard
+
+#include "cbase.h"
+#include "ep1_gamestats.h"
+#include "tier1/utlbuffer.h"
+static CEP1GameStats s_CEP1GS_ThisJustSitsInMemory;
+
+// A bit of a hack to redirect the gamestats API for ep2 (ep3, etc.)
+extern CBaseGameStats *g_pEP2GameStats;
+
+CEP1GameStats::CEP1GameStats( void )
+{
+ gamestats = &s_CEP1GS_ThisJustSitsInMemory;
+}
+
+CBaseGameStats *CEP1GameStats::OnInit( CBaseGameStats *pCurrentGameStats, char const *gamedir )
+{
+ if ( !Q_stricmp( gamedir, "ep2" ) )
+ {
+ return g_pEP2GameStats;
+ }
+
+ return pCurrentGameStats;
+}
+
+const char *CEP1GameStats::GetStatSaveFileName( void )
+{
+ return "ep1_gamestats.dat"; //overriding the default for backwards compatibility with release stat tracking code
+}
+
+const char *CEP1GameStats::GetStatUploadRegistryKeyName( void )
+{
+ return "GameStatsUpload_Ep1"; //overriding the default for backwards compatibility with release stat tracking code
+}
+
+
+static char const *ep1Maps[] =
+{
+ "ep1_citadel_00",
+ "ep1_citadel_01",
+ "ep1_citadel_02",
+ "ep1_citadel_02b",
+ "ep1_citadel_03",
+ "ep1_citadel_04",
+ "ep1_c17_00",
+ "ep1_c17_00a",
+ "ep1_c17_01",
+ "ep1_c17_02",
+ "ep1_c17_02b",
+ "ep1_c17_02a",
+ "ep1_c17_05",
+ "ep1_c17_06",
+};
+
+
+bool CEP1GameStats::UserPlayedAllTheMaps( void )
+{
+ int c = ARRAYSIZE( ep1Maps );
+ for ( int i = 0; i < c; ++i )
+ {
+ int idx = m_BasicStats.m_MapTotals.Find( ep1Maps[ i ] );
+ if( idx == m_BasicStats.m_MapTotals.InvalidIndex() )
+ return false;
+ }
+
+ return true;
+}
+
diff --git a/mp/src/game/server/episodic/ep1_gamestats.h b/mp/src/game/server/episodic/ep1_gamestats.h
index b77b2eb8..5cd13562 100644
--- a/mp/src/game/server/episodic/ep1_gamestats.h
+++ b/mp/src/game/server/episodic/ep1_gamestats.h
@@ -1,31 +1,31 @@
-//========= Copyright Valve Corporation, All rights reserved. ============//
-//
-// Purpose:
-//
-//=============================================================================
-
-#ifndef EP1_GAMESTATS_H
-#define EP1_GAMESTATS_H
-#ifdef _WIN32
-#pragma once
-#endif
-
-#include "gamestats.h"
-
-class CEP1GameStats : public CBaseGameStats
-{
- typedef CBaseGameStats BaseClass;
-
-public:
- CEP1GameStats( void );
-
- virtual CBaseGameStats *OnInit( CBaseGameStats *pCurrentGameStats, char const *gamedir );
-
- virtual bool StatTrackingEnabledForMod( void ) { return true; }
- virtual bool UserPlayedAllTheMaps( void );
-
- virtual const char *GetStatSaveFileName( void );
- virtual const char *GetStatUploadRegistryKeyName( void );
-};
-
-#endif // EP1_GAMESTATS_H
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================
+
+#ifndef EP1_GAMESTATS_H
+#define EP1_GAMESTATS_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "gamestats.h"
+
+class CEP1GameStats : public CBaseGameStats
+{
+ typedef CBaseGameStats BaseClass;
+
+public:
+ CEP1GameStats( void );
+
+ virtual CBaseGameStats *OnInit( CBaseGameStats *pCurrentGameStats, char const *gamedir );
+
+ virtual bool StatTrackingEnabledForMod( void ) { return true; }
+ virtual bool UserPlayedAllTheMaps( void );
+
+ virtual const char *GetStatSaveFileName( void );
+ virtual const char *GetStatUploadRegistryKeyName( void );
+};
+
+#endif // EP1_GAMESTATS_H
diff --git a/mp/src/game/server/episodic/ep2_gamestats.cpp b/mp/src/game/server/episodic/ep2_gamestats.cpp
index 42e3c0f4..af6d5e94 100644
--- a/mp/src/game/server/episodic/ep2_gamestats.cpp
+++ b/mp/src/game/server/episodic/ep2_gamestats.cpp
@@ -1,585 +1,585 @@
-//========= Copyright Valve Corporation, All rights reserved. ============//
-//
-// Purpose:
-//
-//=============================================================================
-#if defined( GAME_DLL )
-#include "cbase.h"
-#endif
-#include "ep2_gamestats.h"
-#include "tier1/utlbuffer.h"
-#include "vehicle_base.h"
-#include "tier1/utlstring.h"
-#include "filesystem.h"
-#include "icommandline.h"
-
-static CEP2GameStats s_CEP2GameStats_Singleton;
-CBaseGameStats *g_pEP2GameStats = &s_CEP2GameStats_Singleton;
-
-
-CEP2GameStats::CEP2GameStats( void )
-{
- Q_memset( m_flInchesRemainder, 0, sizeof( m_flInchesRemainder ) );
- m_pCurrentMap = NULL;
- m_dictMapStats.Purge();
-}
-
-const char *CEP2GameStats::GetStatSaveFileName( void )
-{
- //overriding the default for backwards compatibility with release stat tracking code
- return "ep2_gamestats.dat";
-}
-
-const char *CEP2GameStats::GetStatUploadRegistryKeyName( void )
-{
- //overriding the default for backwards compatibility with release stat tracking code
- return "GameStatsUpload_Ep2";
-}
-
-
-static char const *ep2Maps[] =
-{
- "ep2_outland_01",
- "ep2_outland_02",
- "ep2_outland_03",
- "ep2_outland_04",
- "ep2_outland_05",
- "ep2_outland_06",
- "ep2_outland_06a",
- "ep2_outland_07",
- "ep2_outland_08",
- "ep2_outland_09",
- "ep2_outland_10",
- "ep2_outland_10a",
- "ep2_outland_11",
- "ep2_outland_11a",
- "ep2_outland_12",
- "ep2_outland_12a"
-};
-
-
-bool CEP2GameStats::UserPlayedAllTheMaps( void )
-{
- int c = ARRAYSIZE( ep2Maps );
- for ( int i = 0; i < c; ++i )
- {
- int idx = m_BasicStats.m_MapTotals.Find( ep2Maps[ i ] );
- if( idx == m_BasicStats.m_MapTotals.InvalidIndex() )
- return false;
- }
-
- return true;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Destructor
-// Input : -
-//-----------------------------------------------------------------------------
-CEP2GameStats::~CEP2GameStats()
-{
- m_pCurrentMap = NULL;
- m_dictMapStats.Purge();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : &SaveBuffer -
-//-----------------------------------------------------------------------------
-void CEP2GameStats::AppendCustomDataToSaveBuffer( CUtlBuffer &SaveBuffer )
-{
- // Save data per map.
- for ( int iMap = m_dictMapStats.First(); iMap != m_dictMapStats.InvalidIndex(); iMap = m_dictMapStats.Next( iMap ) )
- {
- // Get the current map.
- Ep2LevelStats_t *pCurrentMap = &m_dictMapStats[iMap];
- Assert( pCurrentMap );
- pCurrentMap->AppendToBuffer( SaveBuffer );
- }
-}
-
-void CEP2GameStats::LoadCustomDataFromBuffer( CUtlBuffer &LoadBuffer )
-{
- Ep2LevelStats_t::LoadData( m_dictMapStats, LoadBuffer );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CEP2GameStats::Event_LevelInit( void )
-{
- BaseClass::Event_LevelInit();
-
- char const *pchTag = NULL;
- CommandLine()->CheckParm( "-gamestatstag", &pchTag );
- if ( !pchTag )
- {
- pchTag = "";
- }
-
- m_pCurrentMap = FindOrAddMapStats( STRING( gpGlobals->mapname ) );
- m_pCurrentMap->Init( STRING( gpGlobals->mapname ), gpGlobals->curtime, pchTag, gpGlobals->mapversion );
-}
-
-Ep2LevelStats_t::EntityDeathsLump_t *CEP2GameStats::FindDeathsLump( char const *npcName )
-{
- if ( !m_pCurrentMap )
- return NULL;
-
- char const *name = npcName;
- // Hack to fixup name
- if ( !Q_stricmp( name, "npc_ministrider" ) )
- {
- name = "npc_hunter";
- }
-
- if ( Q_strnicmp( name, "npc_", 4 ) )
- return NULL;
-
- int idx = m_pCurrentMap->m_dictEntityDeaths.Find( name );
- if ( idx == m_pCurrentMap->m_dictEntityDeaths.InvalidIndex() )
- {
- idx = m_pCurrentMap->m_dictEntityDeaths.Insert( name );
- }
-
- return &m_pCurrentMap->m_dictEntityDeaths[ idx ];
-}
-
-Ep2LevelStats_t::WeaponLump_t *CEP2GameStats::FindWeaponsLump( char const *pchWeaponName, bool bPrimary )
-{
- if ( !m_pCurrentMap )
- return NULL;
-
- if ( !pchWeaponName )
- {
- AssertOnce( !"FindWeaponsLump pchWeaponName == NULL" );
- return NULL;
- }
-
- char lookup[ 512 ];
- Q_snprintf( lookup, sizeof( lookup ), "%s_%s", pchWeaponName, bPrimary ? "primary" : "secondary" );
- int idx = m_pCurrentMap->m_dictWeapons.Find( lookup );
- if ( idx == m_pCurrentMap->m_dictWeapons.InvalidIndex() )
- {
- idx = m_pCurrentMap->m_dictWeapons.Insert( lookup );
- }
-
- return &m_pCurrentMap->m_dictWeapons[ idx ];
-}
-
-// Finds the generic stats lump
-Ep2LevelStats_t::GenericStatsLump_t *CEP2GameStats::FindGenericLump( char const *pchStatName )
-{
- if ( !m_pCurrentMap )
- return NULL;
- if ( !pchStatName || !*pchStatName )
- return NULL;
-
- int idx = m_pCurrentMap->m_dictGeneric.Find( pchStatName );
- if ( idx == m_pCurrentMap->m_dictGeneric.InvalidIndex() )
- {
- idx = m_pCurrentMap->m_dictGeneric.Insert( pchStatName );
- }
-
- return &m_pCurrentMap->m_dictGeneric[ idx ];
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : *szMapName -
-// Output : Ep2LevelStats_t
-//-----------------------------------------------------------------------------
-Ep2LevelStats_t *CEP2GameStats::FindOrAddMapStats( const char *szMapName )
-{
- int iMap = m_dictMapStats.Find( szMapName );
- if( iMap == m_dictMapStats.InvalidIndex() )
- {
- iMap = m_dictMapStats.Insert( szMapName );
- }
-
- return &m_dictMapStats[iMap];
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CEP2GameStats::Event_PlayerDamage( CBasePlayer *pBasePlayer, const CTakeDamageInfo &info )
-{
- BaseClass::Event_PlayerDamage( pBasePlayer, info );
-
- m_pCurrentMap->m_FloatCounters[ Ep2LevelStats_t::COUNTER_DAMAGETAKEN ] += info.GetDamage();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CEP2GameStats::Event_PlayerKilledOther( CBasePlayer *pAttacker, CBaseEntity *pVictim, const CTakeDamageInfo &info )
-{
- BaseClass::Event_PlayerKilledOther( pAttacker, pVictim, info );
-
- if ( pAttacker )
- {
- StatsLog( "Attacker: %s\n", pAttacker->GetClassname() );
- }
-
- if ( !pVictim )
- {
- return;
- }
-
- char const *pchVictim = pVictim->GetClassname();
- Ep2LevelStats_t::EntityDeathsLump_t *lump = FindDeathsLump( pchVictim );
- if ( lump )
- {
- ++lump->m_nBodyCount;
- StatsLog( "Player has killed %d %s's\n", lump->m_nBodyCount, pchVictim );
-
- CPropVehicleDriveable *veh = dynamic_cast< CPropVehicleDriveable * >( pAttacker );
- if ( !veh )
- veh = dynamic_cast< CPropVehicleDriveable * >( info.GetInflictor() );
- if ( veh )
- {
- CBaseEntity *driver = veh->GetDriver();
- if ( driver && driver->IsPlayer() )
- {
- ++m_pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_VEHICULARHOMICIDES ];
- StatsLog( " Vehicular homicide [%I64d] of %s's\n", m_pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_VEHICULARHOMICIDES ], pchVictim );
- }
- }
- }
- else
- {
- StatsLog( "Player killed %s (not tracked)\n", pchVictim );
- }
-}
-
-void CEP2GameStats::Event_Punted( CBaseEntity *pObject )
-{
- BaseClass::Event_Punted( pObject );
- ++m_pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_OBJECTSPUNTED ];
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CEP2GameStats::Event_PlayerKilled( CBasePlayer *pPlayer, const CTakeDamageInfo &info )
-{
- BaseClass::Event_PlayerKilled( pPlayer, info );
-
- if ( info.GetDamageType() & DMG_FALL )
- {
- ++m_pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_FALLINGDEATHS ];
- }
-
- Ep2LevelStats_t::PlayerDeathsLump_t death;
-
- // set the location where the target died
- const Vector &org = pPlayer->GetAbsOrigin();
- death.nPosition[ 0 ] = static_cast<short>( org.x );
- death.nPosition[ 1 ] = static_cast<short>( org.y );
- death.nPosition[ 2 ] = static_cast<short>( org.z );
-
- StatsLog( "CEP2GameStats::Event_PlayerKilled at location [%d %d %d]\n", (int)death.nPosition[ 0 ], (int)death.nPosition[ 1 ], (int)death.nPosition[ 2 ] );
-
- // set the class of the attacker
- CBaseEntity *pInflictor = info.GetInflictor();
- CBaseEntity *pKiller = info.GetAttacker();
-
- if ( pInflictor )
- {
- StatsLog( "Inflictor: %s\n", pInflictor->GetClassname() );
- }
-
- if ( pKiller )
- {
- char const *pchKiller = pKiller->GetClassname();
- Ep2LevelStats_t::EntityDeathsLump_t *lump = FindDeathsLump( pchKiller );
- if ( lump )
- {
- ++lump->m_nKilledPlayer;
- StatsLog( "Player has been killed %d times by %s's\n", lump->m_nKilledPlayer, pchKiller );
- }
- else
- {
- StatsLog( "Player killed by %s (not tracked)\n", pchKiller );
- }
- }
-
- // add it to the list of deaths
- Ep2LevelStats_t *map = FindOrAddMapStats( STRING( gpGlobals->mapname ) );
- int slot = map->m_aPlayerDeaths.AddToTail( death );
-
- Ep2LevelStats_t::SaveGameInfoRecord2_t *rec = map->m_SaveGameInfo.m_pCurrentRecord;
- if ( rec )
- {
- if ( rec->m_nFirstDeathIndex == -1 )
- {
- rec->m_nFirstDeathIndex = slot;
- }
- ++rec->m_nNumDeaths;
-
- StatsLog( "Player has died %d times since last save/load\n", rec->m_nNumDeaths );
- }
-}
-
-void CEP2GameStats::Event_CrateSmashed()
-{
- BaseClass::Event_CrateSmashed();
-
- ++m_pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_CRATESSMASHED ];
-}
-
-void CEP2GameStats::Event_PlayerTraveled( CBasePlayer *pBasePlayer, float distanceInInches, bool bInVehicle, bool bSprinting )
-{
- BaseClass::Event_PlayerTraveled( pBasePlayer, distanceInInches, bInVehicle, bSprinting );
-
- int iIndex = INVEHICLE;
- if ( !bInVehicle )
- {
- iIndex = bSprinting ? ONFOOTSPRINTING : ONFOOT;
- }
-
- m_flInchesRemainder[ iIndex ] += distanceInInches;
- uint64 intPart = (uint64)m_flInchesRemainder[ iIndex ];
- m_flInchesRemainder[ iIndex ] -= intPart;
- if ( intPart > 0 )
- {
- if ( bInVehicle )
- {
- m_pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_DISTANCE_INVEHICLE ] += intPart;
- }
- else
- {
- if ( bSprinting )
- {
- m_pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_DISTANCE_ONFOOTSPRINTING ] += intPart;
- }
- else
- {
- m_pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_DISTANCE_ONFOOT ] += intPart;
- }
- }
- }
-
- Ep2LevelStats_t *map = m_pCurrentMap;
- if ( !map )
- return;
-
- Ep2LevelStats_t::SaveGameInfoRecord2_t *rec = map->m_SaveGameInfo.m_pCurrentRecord;
-
- if ( rec &&
- rec->m_nSaveHealth == -1 )
- {
- Vector pos = pBasePlayer->GetAbsOrigin();
- rec->m_nSavePos[ 0 ] = (short)pos.x;
- rec->m_nSavePos[ 1 ] = (short)pos.y;
- rec->m_nSavePos[ 2 ] = (short)pos.z;
- rec->m_nSaveHealth = clamp( pBasePlayer->GetHealth(), 0, 100 );
- }
-}
-
-void CEP2GameStats::Event_WeaponFired( CBasePlayer *pShooter, bool bPrimary, char const *pchWeaponName )
-{
- BaseClass::Event_WeaponFired( pShooter, bPrimary, pchWeaponName );
-
- Ep2LevelStats_t::WeaponLump_t *lump = FindWeaponsLump( pchWeaponName, bPrimary );
- if ( lump )
- {
- ++lump->m_nShots;
- }
-}
-
-void CEP2GameStats::Event_WeaponHit( CBasePlayer *pShooter, bool bPrimary, char const *pchWeaponName, const CTakeDamageInfo &info )
-{
- BaseClass::Event_WeaponHit( pShooter, bPrimary, pchWeaponName, info );
- Ep2LevelStats_t::WeaponLump_t *lump = FindWeaponsLump( pchWeaponName, bPrimary );
- if ( lump )
- {
- ++lump->m_nHits;
- lump->m_flDamageInflicted += info.GetDamage();
- }
-}
-
-void CEP2GameStats::Event_SaveGame( void )
-{
- BaseClass::Event_SaveGame();
-
- Ep2LevelStats_t *map = m_pCurrentMap;
- if ( !map )
- return;
-
- ++map->m_IntCounters[ Ep2LevelStats_t::COUNTER_SAVES ];
- StatsLog( " %I64uth save on this map\n", map->m_IntCounters[ Ep2LevelStats_t::COUNTER_SAVES ] );
-
- char const *pchSaveFile = engine->GetSaveFileName();
- if ( !pchSaveFile || !pchSaveFile[ 0 ] )
- return;
-
- char name[ 512 ];
- Q_strncpy( name, pchSaveFile, sizeof( name ) );
- Q_strlower( name );
- Q_FixSlashes( name );
-
- unsigned int uFileTime = filesystem->GetFileTime( name, "GAME" );
- // Latch off previous
- map->m_SaveGameInfo.Latch( name, uFileTime );
-
- Ep2LevelStats_t::SaveGameInfoRecord2_t *rec = map->m_SaveGameInfo.m_pCurrentRecord;
-
- CBasePlayer *pPlayer = UTIL_GetLocalPlayer();
- if ( pPlayer )
- {
- Vector pos = pPlayer->GetAbsOrigin();
- rec->m_nSavePos[ 0 ] = (short)pos.x;
- rec->m_nSavePos[ 1 ] = (short)pos.y;
- rec->m_nSavePos[ 2 ] = (short)pos.z;
- rec->m_nSaveHealth = clamp( pPlayer->GetHealth(), 0, 100 );
- rec->m_SaveType = Q_stristr( pchSaveFile, "autosave" ) ?
- Ep2LevelStats_t::SaveGameInfoRecord2_t::TYPE_AUTOSAVE : Ep2LevelStats_t::SaveGameInfoRecord2_t::TYPE_USERSAVE;
-
- StatsLog( "save pos %i %i %i w/ health %d\n",
- rec->m_nSavePos[ 0 ],
- rec->m_nSavePos[ 1 ],
- rec->m_nSavePos[ 2 ],
- rec->m_nSaveHealth );
-
- }
-}
-
-void CEP2GameStats::Event_LoadGame( void )
-{
- BaseClass::Event_LoadGame();
-
- Ep2LevelStats_t *map = m_pCurrentMap;
- if ( !map )
- return;
-
- ++map->m_IntCounters[ Ep2LevelStats_t::COUNTER_LOADS ];
- StatsLog( " %I64uth load on this map\n", map->m_IntCounters[ Ep2LevelStats_t::COUNTER_LOADS ] );
-
- char const *pchSaveFile = engine->GetMostRecentlyLoadedFileName();
- if ( !pchSaveFile || !pchSaveFile[ 0 ] )
- return;
-
- char name[ 512 ];
- Q_snprintf( name, sizeof( name ), "save/%s", pchSaveFile );
- Q_DefaultExtension( name, IsX360() ? ".360.sav" : ".sav", sizeof( name ) );
- Q_FixSlashes( name );
- Q_strlower( name );
-
- Ep2LevelStats_t::SaveGameInfo_t *pSaveGameInfo = &map->m_SaveGameInfo;
-
- if ( pSaveGameInfo->m_nCurrentSaveFileTime == 0 ||
- pSaveGameInfo->m_sCurrentSaveFile != name )
- {
- unsigned int uFileTime = filesystem->GetFileTime( name, "GAME" );
-
- // Latch off previous
- StatsLog( "Relatching save game file due to time or filename change (%s : %u)\n", name, uFileTime );
- pSaveGameInfo->Latch( name, uFileTime );
- }
-}
-
-void CEP2GameStats::Event_FlippedVehicle( CBasePlayer *pDriver, CPropVehicleDriveable *pVehicle )
-{
- BaseClass::Event_FlippedVehicle( pDriver, pVehicle );
- ++m_pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_VEHICLE_OVERTURNED ];
- StatsLog( "%I64u time vehicle overturned\n", m_pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_VEHICLE_OVERTURNED ] );
-}
-
-void CEP2GameStats::Event_PreSaveGameLoaded( char const *pSaveName, bool bInGame )
-{
- BaseClass::Event_PreSaveGameLoaded( pSaveName, bInGame );
-
- // Not currently in a level
- if ( !bInGame )
- return;
-
- CBasePlayer *pPlayer = UTIL_GetLocalPlayer();
- if ( !pPlayer )
- return;
-
- // We're loading a saved game while the player is still alive (are they stuck?)
- if ( pPlayer->IsAlive() )
- {
- ++m_pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_LOADGAME_STILLALIVE ];
- StatsLog( "%I64u game loaded with living player\n", m_pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_LOADGAME_STILLALIVE ] );
- }
-}
-
-void CEP2GameStats::Event_PlayerEnteredGodMode( CBasePlayer *pBasePlayer )
-{
- BaseClass::Event_PlayerEnteredGodMode( pBasePlayer );
- ++m_pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_GODMODES ];
- StatsLog( "%I64u time entering godmode\n", m_pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_GODMODES ] );
-}
-
-void CEP2GameStats::Event_PlayerEnteredNoClip( CBasePlayer *pBasePlayer )
-{
- BaseClass::Event_PlayerEnteredNoClip( pBasePlayer );
- ++m_pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_NOCLIPS ];
- StatsLog( "%I64u time entering NOCLIP\n", m_pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_NOCLIPS ] );
-}
-
-void CEP2GameStats::Event_DecrementPlayerEnteredNoClip( CBasePlayer *pBasePlayer )
-{
- BaseClass::Event_DecrementPlayerEnteredNoClip( pBasePlayer );
- if ( m_pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_NOCLIPS ] > 0 )
- {
- --m_pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_NOCLIPS ];
- }
- StatsLog( "%I64u decrement entering NOCLIP (entering vehicle doesn't count)\n", m_pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_NOCLIPS ] );
-}
-
-// Generic statistics lump
-void CEP2GameStats::Event_IncrementCountedStatistic( const Vector& vecAbsOrigin, char const *pchStatisticName, float flIncrementAmount )
-{
- BaseClass::Event_IncrementCountedStatistic( vecAbsOrigin, pchStatisticName, flIncrementAmount );
-
- // Find the generic lump
- Ep2LevelStats_t::GenericStatsLump_t *lump = FindGenericLump( pchStatisticName );
- if ( lump )
- {
- lump->m_Pos[ 0 ] = (short)vecAbsOrigin.x;
- lump->m_Pos[ 1 ] = (short)vecAbsOrigin.y;
- lump->m_Pos[ 2 ] = (short)vecAbsOrigin.z;
- lump->m_flCurrentValue += (double)flIncrementAmount;
- ++lump->m_unCount;
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-static void CC_ListDeaths( const CCommand &args )
-{
- Ep2LevelStats_t *map = s_CEP2GameStats_Singleton.FindOrAddMapStats( STRING( gpGlobals->mapname ) );
- if ( !map )
- return;
-
- int nRendered = 0;
- for ( int i = map->m_aPlayerDeaths.Count() - 1; i >= 0 ; --i, ++nRendered )
- {
- Vector org( map->m_aPlayerDeaths[ i ].nPosition[ 0 ],
- map->m_aPlayerDeaths[ i ].nPosition[ 1 ],
- map->m_aPlayerDeaths[ i ].nPosition[ 2 ] + 36.0f );
-
- // FIXME: This might overflow
- NDebugOverlay::Box( org, Vector( -8, -8, -8 ), Vector( 8, 8, 8 ), 0, 255, 0, 128, 10.0f );
-
- /*
- Msg( "%s killed %s with %s at (%d,%d,%d)\n",
- g_aClassNames[ map->m_aPlayerDeaths[ i ].iAttackClass ],
- g_aClassNames[ map->m_aPlayerDeaths[ i ].iTargetClass ],
- WeaponIdToAlias( map->m_aPlayerDeaths[ i ].iWeapon ),
- map->m_aPlayerDeaths[ i ].nPosition[ 0 ],
- map->m_aPlayerDeaths[ i ].nPosition[ 1 ],
- map->m_aPlayerDeaths[ i ].nPosition[ 2 ] );
- */
-
- if ( nRendered > 150 )
- break;
- }
- Msg( "\nlisted %d deaths\n", map->m_aPlayerDeaths.Count() );
-}
-
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================
+#if defined( GAME_DLL )
+#include "cbase.h"
+#endif
+#include "ep2_gamestats.h"
+#include "tier1/utlbuffer.h"
+#include "vehicle_base.h"
+#include "tier1/utlstring.h"
+#include "filesystem.h"
+#include "icommandline.h"
+
+static CEP2GameStats s_CEP2GameStats_Singleton;
+CBaseGameStats *g_pEP2GameStats = &s_CEP2GameStats_Singleton;
+
+
+CEP2GameStats::CEP2GameStats( void )
+{
+ Q_memset( m_flInchesRemainder, 0, sizeof( m_flInchesRemainder ) );
+ m_pCurrentMap = NULL;
+ m_dictMapStats.Purge();
+}
+
+const char *CEP2GameStats::GetStatSaveFileName( void )
+{
+ //overriding the default for backwards compatibility with release stat tracking code
+ return "ep2_gamestats.dat";
+}
+
+const char *CEP2GameStats::GetStatUploadRegistryKeyName( void )
+{
+ //overriding the default for backwards compatibility with release stat tracking code
+ return "GameStatsUpload_Ep2";
+}
+
+
+static char const *ep2Maps[] =
+{
+ "ep2_outland_01",
+ "ep2_outland_02",
+ "ep2_outland_03",
+ "ep2_outland_04",
+ "ep2_outland_05",
+ "ep2_outland_06",
+ "ep2_outland_06a",
+ "ep2_outland_07",
+ "ep2_outland_08",
+ "ep2_outland_09",
+ "ep2_outland_10",
+ "ep2_outland_10a",
+ "ep2_outland_11",
+ "ep2_outland_11a",
+ "ep2_outland_12",
+ "ep2_outland_12a"
+};
+
+
+bool CEP2GameStats::UserPlayedAllTheMaps( void )
+{
+ int c = ARRAYSIZE( ep2Maps );
+ for ( int i = 0; i < c; ++i )
+ {
+ int idx = m_BasicStats.m_MapTotals.Find( ep2Maps[ i ] );
+ if( idx == m_BasicStats.m_MapTotals.InvalidIndex() )
+ return false;
+ }
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Destructor
+// Input : -
+//-----------------------------------------------------------------------------
+CEP2GameStats::~CEP2GameStats()
+{
+ m_pCurrentMap = NULL;
+ m_dictMapStats.Purge();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : &SaveBuffer -
+//-----------------------------------------------------------------------------
+void CEP2GameStats::AppendCustomDataToSaveBuffer( CUtlBuffer &SaveBuffer )
+{
+ // Save data per map.
+ for ( int iMap = m_dictMapStats.First(); iMap != m_dictMapStats.InvalidIndex(); iMap = m_dictMapStats.Next( iMap ) )
+ {
+ // Get the current map.
+ Ep2LevelStats_t *pCurrentMap = &m_dictMapStats[iMap];
+ Assert( pCurrentMap );
+ pCurrentMap->AppendToBuffer( SaveBuffer );
+ }
+}
+
+void CEP2GameStats::LoadCustomDataFromBuffer( CUtlBuffer &LoadBuffer )
+{
+ Ep2LevelStats_t::LoadData( m_dictMapStats, LoadBuffer );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CEP2GameStats::Event_LevelInit( void )
+{
+ BaseClass::Event_LevelInit();
+
+ char const *pchTag = NULL;
+ CommandLine()->CheckParm( "-gamestatstag", &pchTag );
+ if ( !pchTag )
+ {
+ pchTag = "";
+ }
+
+ m_pCurrentMap = FindOrAddMapStats( STRING( gpGlobals->mapname ) );
+ m_pCurrentMap->Init( STRING( gpGlobals->mapname ), gpGlobals->curtime, pchTag, gpGlobals->mapversion );
+}
+
+Ep2LevelStats_t::EntityDeathsLump_t *CEP2GameStats::FindDeathsLump( char const *npcName )
+{
+ if ( !m_pCurrentMap )
+ return NULL;
+
+ char const *name = npcName;
+ // Hack to fixup name
+ if ( !Q_stricmp( name, "npc_ministrider" ) )
+ {
+ name = "npc_hunter";
+ }
+
+ if ( Q_strnicmp( name, "npc_", 4 ) )
+ return NULL;
+
+ int idx = m_pCurrentMap->m_dictEntityDeaths.Find( name );
+ if ( idx == m_pCurrentMap->m_dictEntityDeaths.InvalidIndex() )
+ {
+ idx = m_pCurrentMap->m_dictEntityDeaths.Insert( name );
+ }
+
+ return &m_pCurrentMap->m_dictEntityDeaths[ idx ];
+}
+
+Ep2LevelStats_t::WeaponLump_t *CEP2GameStats::FindWeaponsLump( char const *pchWeaponName, bool bPrimary )
+{
+ if ( !m_pCurrentMap )
+ return NULL;
+
+ if ( !pchWeaponName )
+ {
+ AssertOnce( !"FindWeaponsLump pchWeaponName == NULL" );
+ return NULL;
+ }
+
+ char lookup[ 512 ];
+ Q_snprintf( lookup, sizeof( lookup ), "%s_%s", pchWeaponName, bPrimary ? "primary" : "secondary" );
+ int idx = m_pCurrentMap->m_dictWeapons.Find( lookup );
+ if ( idx == m_pCurrentMap->m_dictWeapons.InvalidIndex() )
+ {
+ idx = m_pCurrentMap->m_dictWeapons.Insert( lookup );
+ }
+
+ return &m_pCurrentMap->m_dictWeapons[ idx ];
+}
+
+// Finds the generic stats lump
+Ep2LevelStats_t::GenericStatsLump_t *CEP2GameStats::FindGenericLump( char const *pchStatName )
+{
+ if ( !m_pCurrentMap )
+ return NULL;
+ if ( !pchStatName || !*pchStatName )
+ return NULL;
+
+ int idx = m_pCurrentMap->m_dictGeneric.Find( pchStatName );
+ if ( idx == m_pCurrentMap->m_dictGeneric.InvalidIndex() )
+ {
+ idx = m_pCurrentMap->m_dictGeneric.Insert( pchStatName );
+ }
+
+ return &m_pCurrentMap->m_dictGeneric[ idx ];
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *szMapName -
+// Output : Ep2LevelStats_t
+//-----------------------------------------------------------------------------
+Ep2LevelStats_t *CEP2GameStats::FindOrAddMapStats( const char *szMapName )
+{
+ int iMap = m_dictMapStats.Find( szMapName );
+ if( iMap == m_dictMapStats.InvalidIndex() )
+ {
+ iMap = m_dictMapStats.Insert( szMapName );
+ }
+
+ return &m_dictMapStats[iMap];
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CEP2GameStats::Event_PlayerDamage( CBasePlayer *pBasePlayer, const CTakeDamageInfo &info )
+{
+ BaseClass::Event_PlayerDamage( pBasePlayer, info );
+
+ m_pCurrentMap->m_FloatCounters[ Ep2LevelStats_t::COUNTER_DAMAGETAKEN ] += info.GetDamage();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CEP2GameStats::Event_PlayerKilledOther( CBasePlayer *pAttacker, CBaseEntity *pVictim, const CTakeDamageInfo &info )
+{
+ BaseClass::Event_PlayerKilledOther( pAttacker, pVictim, info );
+
+ if ( pAttacker )
+ {
+ StatsLog( "Attacker: %s\n", pAttacker->GetClassname() );
+ }
+
+ if ( !pVictim )
+ {
+ return;
+ }
+
+ char const *pchVictim = pVictim->GetClassname();
+ Ep2LevelStats_t::EntityDeathsLump_t *lump = FindDeathsLump( pchVictim );
+ if ( lump )
+ {
+ ++lump->m_nBodyCount;
+ StatsLog( "Player has killed %d %s's\n", lump->m_nBodyCount, pchVictim );
+
+ CPropVehicleDriveable *veh = dynamic_cast< CPropVehicleDriveable * >( pAttacker );
+ if ( !veh )
+ veh = dynamic_cast< CPropVehicleDriveable * >( info.GetInflictor() );
+ if ( veh )
+ {
+ CBaseEntity *driver = veh->GetDriver();
+ if ( driver && driver->IsPlayer() )
+ {
+ ++m_pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_VEHICULARHOMICIDES ];
+ StatsLog( " Vehicular homicide [%I64d] of %s's\n", m_pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_VEHICULARHOMICIDES ], pchVictim );
+ }
+ }
+ }
+ else
+ {
+ StatsLog( "Player killed %s (not tracked)\n", pchVictim );
+ }
+}
+
+void CEP2GameStats::Event_Punted( CBaseEntity *pObject )
+{
+ BaseClass::Event_Punted( pObject );
+ ++m_pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_OBJECTSPUNTED ];
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CEP2GameStats::Event_PlayerKilled( CBasePlayer *pPlayer, const CTakeDamageInfo &info )
+{
+ BaseClass::Event_PlayerKilled( pPlayer, info );
+
+ if ( info.GetDamageType() & DMG_FALL )
+ {
+ ++m_pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_FALLINGDEATHS ];
+ }
+
+ Ep2LevelStats_t::PlayerDeathsLump_t death;
+
+ // set the location where the target died
+ const Vector &org = pPlayer->GetAbsOrigin();
+ death.nPosition[ 0 ] = static_cast<short>( org.x );
+ death.nPosition[ 1 ] = static_cast<short>( org.y );
+ death.nPosition[ 2 ] = static_cast<short>( org.z );
+
+ StatsLog( "CEP2GameStats::Event_PlayerKilled at location [%d %d %d]\n", (int)death.nPosition[ 0 ], (int)death.nPosition[ 1 ], (int)death.nPosition[ 2 ] );
+
+ // set the class of the attacker
+ CBaseEntity *pInflictor = info.GetInflictor();
+ CBaseEntity *pKiller = info.GetAttacker();
+
+ if ( pInflictor )
+ {
+ StatsLog( "Inflictor: %s\n", pInflictor->GetClassname() );
+ }
+
+ if ( pKiller )
+ {
+ char const *pchKiller = pKiller->GetClassname();
+ Ep2LevelStats_t::EntityDeathsLump_t *lump = FindDeathsLump( pchKiller );
+ if ( lump )
+ {
+ ++lump->m_nKilledPlayer;
+ StatsLog( "Player has been killed %d times by %s's\n", lump->m_nKilledPlayer, pchKiller );
+ }
+ else
+ {
+ StatsLog( "Player killed by %s (not tracked)\n", pchKiller );
+ }
+ }
+
+ // add it to the list of deaths
+ Ep2LevelStats_t *map = FindOrAddMapStats( STRING( gpGlobals->mapname ) );
+ int slot = map->m_aPlayerDeaths.AddToTail( death );
+
+ Ep2LevelStats_t::SaveGameInfoRecord2_t *rec = map->m_SaveGameInfo.m_pCurrentRecord;
+ if ( rec )
+ {
+ if ( rec->m_nFirstDeathIndex == -1 )
+ {
+ rec->m_nFirstDeathIndex = slot;
+ }
+ ++rec->m_nNumDeaths;
+
+ StatsLog( "Player has died %d times since last save/load\n", rec->m_nNumDeaths );
+ }
+}
+
+void CEP2GameStats::Event_CrateSmashed()
+{
+ BaseClass::Event_CrateSmashed();
+
+ ++m_pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_CRATESSMASHED ];
+}
+
+void CEP2GameStats::Event_PlayerTraveled( CBasePlayer *pBasePlayer, float distanceInInches, bool bInVehicle, bool bSprinting )
+{
+ BaseClass::Event_PlayerTraveled( pBasePlayer, distanceInInches, bInVehicle, bSprinting );
+
+ int iIndex = INVEHICLE;
+ if ( !bInVehicle )
+ {
+ iIndex = bSprinting ? ONFOOTSPRINTING : ONFOOT;
+ }
+
+ m_flInchesRemainder[ iIndex ] += distanceInInches;
+ uint64 intPart = (uint64)m_flInchesRemainder[ iIndex ];
+ m_flInchesRemainder[ iIndex ] -= intPart;
+ if ( intPart > 0 )
+ {
+ if ( bInVehicle )
+ {
+ m_pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_DISTANCE_INVEHICLE ] += intPart;
+ }
+ else
+ {
+ if ( bSprinting )
+ {
+ m_pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_DISTANCE_ONFOOTSPRINTING ] += intPart;
+ }
+ else
+ {
+ m_pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_DISTANCE_ONFOOT ] += intPart;
+ }
+ }
+ }
+
+ Ep2LevelStats_t *map = m_pCurrentMap;
+ if ( !map )
+ return;
+
+ Ep2LevelStats_t::SaveGameInfoRecord2_t *rec = map->m_SaveGameInfo.m_pCurrentRecord;
+
+ if ( rec &&
+ rec->m_nSaveHealth == -1 )
+ {
+ Vector pos = pBasePlayer->GetAbsOrigin();
+ rec->m_nSavePos[ 0 ] = (short)pos.x;
+ rec->m_nSavePos[ 1 ] = (short)pos.y;
+ rec->m_nSavePos[ 2 ] = (short)pos.z;
+ rec->m_nSaveHealth = clamp( pBasePlayer->GetHealth(), 0, 100 );
+ }
+}
+
+void CEP2GameStats::Event_WeaponFired( CBasePlayer *pShooter, bool bPrimary, char const *pchWeaponName )
+{
+ BaseClass::Event_WeaponFired( pShooter, bPrimary, pchWeaponName );
+
+ Ep2LevelStats_t::WeaponLump_t *lump = FindWeaponsLump( pchWeaponName, bPrimary );
+ if ( lump )
+ {
+ ++lump->m_nShots;
+ }
+}
+
+void CEP2GameStats::Event_WeaponHit( CBasePlayer *pShooter, bool bPrimary, char const *pchWeaponName, const CTakeDamageInfo &info )
+{
+ BaseClass::Event_WeaponHit( pShooter, bPrimary, pchWeaponName, info );
+ Ep2LevelStats_t::WeaponLump_t *lump = FindWeaponsLump( pchWeaponName, bPrimary );
+ if ( lump )
+ {
+ ++lump->m_nHits;
+ lump->m_flDamageInflicted += info.GetDamage();
+ }
+}
+
+void CEP2GameStats::Event_SaveGame( void )
+{
+ BaseClass::Event_SaveGame();
+
+ Ep2LevelStats_t *map = m_pCurrentMap;
+ if ( !map )
+ return;
+
+ ++map->m_IntCounters[ Ep2LevelStats_t::COUNTER_SAVES ];
+ StatsLog( " %I64uth save on this map\n", map->m_IntCounters[ Ep2LevelStats_t::COUNTER_SAVES ] );
+
+ char const *pchSaveFile = engine->GetSaveFileName();
+ if ( !pchSaveFile || !pchSaveFile[ 0 ] )
+ return;
+
+ char name[ 512 ];
+ Q_strncpy( name, pchSaveFile, sizeof( name ) );
+ Q_strlower( name );
+ Q_FixSlashes( name );
+
+ unsigned int uFileTime = filesystem->GetFileTime( name, "GAME" );
+ // Latch off previous
+ map->m_SaveGameInfo.Latch( name, uFileTime );
+
+ Ep2LevelStats_t::SaveGameInfoRecord2_t *rec = map->m_SaveGameInfo.m_pCurrentRecord;
+
+ CBasePlayer *pPlayer = UTIL_GetLocalPlayer();
+ if ( pPlayer )
+ {
+ Vector pos = pPlayer->GetAbsOrigin();
+ rec->m_nSavePos[ 0 ] = (short)pos.x;
+ rec->m_nSavePos[ 1 ] = (short)pos.y;
+ rec->m_nSavePos[ 2 ] = (short)pos.z;
+ rec->m_nSaveHealth = clamp( pPlayer->GetHealth(), 0, 100 );
+ rec->m_SaveType = Q_stristr( pchSaveFile, "autosave" ) ?
+ Ep2LevelStats_t::SaveGameInfoRecord2_t::TYPE_AUTOSAVE : Ep2LevelStats_t::SaveGameInfoRecord2_t::TYPE_USERSAVE;
+
+ StatsLog( "save pos %i %i %i w/ health %d\n",
+ rec->m_nSavePos[ 0 ],
+ rec->m_nSavePos[ 1 ],
+ rec->m_nSavePos[ 2 ],
+ rec->m_nSaveHealth );
+
+ }
+}
+
+void CEP2GameStats::Event_LoadGame( void )
+{
+ BaseClass::Event_LoadGame();
+
+ Ep2LevelStats_t *map = m_pCurrentMap;
+ if ( !map )
+ return;
+
+ ++map->m_IntCounters[ Ep2LevelStats_t::COUNTER_LOADS ];
+ StatsLog( " %I64uth load on this map\n", map->m_IntCounters[ Ep2LevelStats_t::COUNTER_LOADS ] );
+
+ char const *pchSaveFile = engine->GetMostRecentlyLoadedFileName();
+ if ( !pchSaveFile || !pchSaveFile[ 0 ] )
+ return;
+
+ char name[ 512 ];
+ Q_snprintf( name, sizeof( name ), "save/%s", pchSaveFile );
+ Q_DefaultExtension( name, IsX360() ? ".360.sav" : ".sav", sizeof( name ) );
+ Q_FixSlashes( name );
+ Q_strlower( name );
+
+ Ep2LevelStats_t::SaveGameInfo_t *pSaveGameInfo = &map->m_SaveGameInfo;
+
+ if ( pSaveGameInfo->m_nCurrentSaveFileTime == 0 ||
+ pSaveGameInfo->m_sCurrentSaveFile != name )
+ {
+ unsigned int uFileTime = filesystem->GetFileTime( name, "GAME" );
+
+ // Latch off previous
+ StatsLog( "Relatching save game file due to time or filename change (%s : %u)\n", name, uFileTime );
+ pSaveGameInfo->Latch( name, uFileTime );
+ }
+}
+
+void CEP2GameStats::Event_FlippedVehicle( CBasePlayer *pDriver, CPropVehicleDriveable *pVehicle )
+{
+ BaseClass::Event_FlippedVehicle( pDriver, pVehicle );
+ ++m_pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_VEHICLE_OVERTURNED ];
+ StatsLog( "%I64u time vehicle overturned\n", m_pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_VEHICLE_OVERTURNED ] );
+}
+
+void CEP2GameStats::Event_PreSaveGameLoaded( char const *pSaveName, bool bInGame )
+{
+ BaseClass::Event_PreSaveGameLoaded( pSaveName, bInGame );
+
+ // Not currently in a level
+ if ( !bInGame )
+ return;
+
+ CBasePlayer *pPlayer = UTIL_GetLocalPlayer();
+ if ( !pPlayer )
+ return;
+
+ // We're loading a saved game while the player is still alive (are they stuck?)
+ if ( pPlayer->IsAlive() )
+ {
+ ++m_pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_LOADGAME_STILLALIVE ];
+ StatsLog( "%I64u game loaded with living player\n", m_pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_LOADGAME_STILLALIVE ] );
+ }
+}
+
+void CEP2GameStats::Event_PlayerEnteredGodMode( CBasePlayer *pBasePlayer )
+{
+ BaseClass::Event_PlayerEnteredGodMode( pBasePlayer );
+ ++m_pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_GODMODES ];
+ StatsLog( "%I64u time entering godmode\n", m_pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_GODMODES ] );
+}
+
+void CEP2GameStats::Event_PlayerEnteredNoClip( CBasePlayer *pBasePlayer )
+{
+ BaseClass::Event_PlayerEnteredNoClip( pBasePlayer );
+ ++m_pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_NOCLIPS ];
+ StatsLog( "%I64u time entering NOCLIP\n", m_pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_NOCLIPS ] );
+}
+
+void CEP2GameStats::Event_DecrementPlayerEnteredNoClip( CBasePlayer *pBasePlayer )
+{
+ BaseClass::Event_DecrementPlayerEnteredNoClip( pBasePlayer );
+ if ( m_pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_NOCLIPS ] > 0 )
+ {
+ --m_pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_NOCLIPS ];
+ }
+ StatsLog( "%I64u decrement entering NOCLIP (entering vehicle doesn't count)\n", m_pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_NOCLIPS ] );
+}
+
+// Generic statistics lump
+void CEP2GameStats::Event_IncrementCountedStatistic( const Vector& vecAbsOrigin, char const *pchStatisticName, float flIncrementAmount )
+{
+ BaseClass::Event_IncrementCountedStatistic( vecAbsOrigin, pchStatisticName, flIncrementAmount );
+
+ // Find the generic lump
+ Ep2LevelStats_t::GenericStatsLump_t *lump = FindGenericLump( pchStatisticName );
+ if ( lump )
+ {
+ lump->m_Pos[ 0 ] = (short)vecAbsOrigin.x;
+ lump->m_Pos[ 1 ] = (short)vecAbsOrigin.y;
+ lump->m_Pos[ 2 ] = (short)vecAbsOrigin.z;
+ lump->m_flCurrentValue += (double)flIncrementAmount;
+ ++lump->m_unCount;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+static void CC_ListDeaths( const CCommand &args )
+{
+ Ep2LevelStats_t *map = s_CEP2GameStats_Singleton.FindOrAddMapStats( STRING( gpGlobals->mapname ) );
+ if ( !map )
+ return;
+
+ int nRendered = 0;
+ for ( int i = map->m_aPlayerDeaths.Count() - 1; i >= 0 ; --i, ++nRendered )
+ {
+ Vector org( map->m_aPlayerDeaths[ i ].nPosition[ 0 ],
+ map->m_aPlayerDeaths[ i ].nPosition[ 1 ],
+ map->m_aPlayerDeaths[ i ].nPosition[ 2 ] + 36.0f );
+
+ // FIXME: This might overflow
+ NDebugOverlay::Box( org, Vector( -8, -8, -8 ), Vector( 8, 8, 8 ), 0, 255, 0, 128, 10.0f );
+
+ /*
+ Msg( "%s killed %s with %s at (%d,%d,%d)\n",
+ g_aClassNames[ map->m_aPlayerDeaths[ i ].iAttackClass ],
+ g_aClassNames[ map->m_aPlayerDeaths[ i ].iTargetClass ],
+ WeaponIdToAlias( map->m_aPlayerDeaths[ i ].iWeapon ),
+ map->m_aPlayerDeaths[ i ].nPosition[ 0 ],
+ map->m_aPlayerDeaths[ i ].nPosition[ 1 ],
+ map->m_aPlayerDeaths[ i ].nPosition[ 2 ] );
+ */
+
+ if ( nRendered > 150 )
+ break;
+ }
+ Msg( "\nlisted %d deaths\n", map->m_aPlayerDeaths.Count() );
+}
+
static ConCommand listDeaths("listdeaths", CC_ListDeaths, "lists player deaths", 0 ); \ No newline at end of file
diff --git a/mp/src/game/server/episodic/ep2_gamestats.h b/mp/src/game/server/episodic/ep2_gamestats.h
index d0eca118..cc36301c 100644
--- a/mp/src/game/server/episodic/ep2_gamestats.h
+++ b/mp/src/game/server/episodic/ep2_gamestats.h
@@ -1,532 +1,532 @@
-//========= Copyright Valve Corporation, All rights reserved. ============//
-//
-// Purpose:
-//
-//=============================================================================
-
-#ifndef EP2_GAMESTATS_H
-#define EP2_GAMESTATS_H
-#ifdef _WIN32
-#pragma once
-#endif
-
-#include "ep1_gamestats.h"
-#include "tier1/utlstring.h"
-
-// EP2 Game Stats
-enum Ep2GameStatsVersions_t
-{
- EP2_GAMESTATS_FILE_VERSION_01 = 001,
- EP2_GAMESTATS_FILE_VERSION_02 = 002,
-
- EP2_GAMESTATS_CURRENT_VERSION = EP2_GAMESTATS_FILE_VERSION_02,
-};
-
-enum Ep2GameStatsLumpIds_t
-{
- EP2STATS_LUMP_HEADER = 1,
- EP2STATS_LUMP_DEATH,
- EP2STATS_LUMP_NPC,
- EP2STATS_LUMP_WEAPON,
- EP2STATS_LUMP_SAVEGAMEINFO,
- EP2STATS_LUMP_TAG,
- EP2STATS_LUMP_GENERIC,
- EP2_MAX_LUMP_COUNT
-};
-
-// EP2 Game Level Stats Data
-struct Ep2LevelStats_t
-{
-public:
- enum FloatCounterTypes_t
- {
- COUNTER_DAMAGETAKEN = 0,
-
- NUM_FLOATCOUNTER_TYPES,
- };
-
- enum IntCounterTypes_t
- {
- COUNTER_CRATESSMASHED = 0,
- COUNTER_OBJECTSPUNTED,
- COUNTER_VEHICULARHOMICIDES,
- COUNTER_DISTANCE_INVEHICLE,
- COUNTER_DISTANCE_ONFOOT,
- COUNTER_DISTANCE_ONFOOTSPRINTING,
- COUNTER_FALLINGDEATHS,
- COUNTER_VEHICLE_OVERTURNED,
- COUNTER_LOADGAME_STILLALIVE,
- COUNTER_LOADS,
- COUNTER_SAVES,
- COUNTER_GODMODES,
- COUNTER_NOCLIPS,
-
- NUM_INTCOUNTER_TYPES,
- };
-
- Ep2LevelStats_t() :
- m_bInitialized( false ),
- m_flLevelStartTime( 0.0f )
- {
- Q_memset( m_IntCounters, 0, sizeof( m_IntCounters ) );
- Q_memset( m_FloatCounters, 0, sizeof( m_FloatCounters ) );
- }
- ~Ep2LevelStats_t()
- {
- }
-
- Ep2LevelStats_t( const Ep2LevelStats_t &other )
- {
- m_bInitialized = other.m_bInitialized;
- m_flLevelStartTime = other.m_flLevelStartTime;
- m_Header = other.m_Header;
- m_aPlayerDeaths = other.m_aPlayerDeaths;
- Q_memcpy( m_IntCounters, other.m_IntCounters, sizeof( m_IntCounters ) );
- Q_memcpy( m_FloatCounters, other.m_FloatCounters, sizeof( m_FloatCounters ) );
- int i;
- for ( i = other.m_dictEntityDeaths.First(); i != other.m_dictEntityDeaths.InvalidIndex(); i = other.m_dictEntityDeaths.Next( i ) )
- {
- m_dictEntityDeaths.Insert( other.m_dictEntityDeaths.GetElementName( i ), other.m_dictEntityDeaths[ i ] );
- }
- for ( i = other.m_dictWeapons.First(); i != other.m_dictWeapons.InvalidIndex(); i = other.m_dictWeapons.Next( i ) )
- {
- m_dictWeapons.Insert( other.m_dictWeapons.GetElementName( i ), other.m_dictWeapons[ i ] );
- }
- m_SaveGameInfo = other.m_SaveGameInfo;
- }
-
- // Create and destroy.
- void Init( const char *pszMapName, float flStartTime, char const *pchTag, int nMapVersion )
- {
- // Initialize.
- m_Header.m_iVersion = EP2_GAMESTATS_CURRENT_VERSION;
- Q_strncpy( m_Header.m_szMapName, pszMapName, sizeof( m_Header.m_szMapName ) );
- m_Header.m_flTime = 0.0f;
-
- // Start the level timer.
- m_flLevelStartTime = flStartTime;
-
- Q_strncpy( m_Tag.m_szTagText, pchTag, sizeof( m_Tag.m_szTagText ) );
- m_Tag.m_nMapVersion = nMapVersion;
- }
-
- void Shutdown( float flEndTime )
- {
- m_Header.m_flTime = flEndTime - m_flLevelStartTime;
- }
-
- void AppendToBuffer( CUtlBuffer &SaveBuffer )
- {
- // Always write out as current version
- m_Header.m_iVersion = EP2_GAMESTATS_CURRENT_VERSION;
-
- // Write out the lumps.
- CBaseGameStats::AppendLump( EP2_MAX_LUMP_COUNT, SaveBuffer, EP2STATS_LUMP_HEADER, 1, sizeof( Ep2LevelStats_t::LevelHeader_t ), static_cast<void*>( &m_Header ) );
- CBaseGameStats::AppendLump( EP2_MAX_LUMP_COUNT, SaveBuffer, EP2STATS_LUMP_TAG, 1, sizeof( Ep2LevelStats_t::Tag_t ), static_cast< void * >( &m_Tag ) );
- CBaseGameStats::AppendLump( EP2_MAX_LUMP_COUNT, SaveBuffer, EP2STATS_LUMP_DEATH, m_aPlayerDeaths.Count(), sizeof( Ep2LevelStats_t::PlayerDeathsLump_t ), static_cast<void*>( m_aPlayerDeaths.Base() ) );
- {
- CUtlBuffer buf;
- buf.Put( (const void *)m_IntCounters, sizeof( m_IntCounters ) );
- buf.Put( (const void *)m_FloatCounters, sizeof( m_FloatCounters ) );
- buf.PutInt( m_dictEntityDeaths.Count() );
- for ( int i = m_dictEntityDeaths.First(); i != m_dictEntityDeaths.InvalidIndex(); i = m_dictEntityDeaths.Next( i ) )
- {
- buf.PutString( m_dictEntityDeaths.GetElementName( i ) );
- buf.Put( (const void *)&m_dictEntityDeaths[ i ], sizeof( Ep2LevelStats_t::EntityDeathsLump_t ) );
- }
- CBaseGameStats::AppendLump( EP2_MAX_LUMP_COUNT, SaveBuffer, EP2STATS_LUMP_NPC, 1, buf.TellPut(), buf.Base() );
- }
- {
- CUtlBuffer buf;
- buf.PutInt( m_dictWeapons.Count() );
- for ( int i = m_dictWeapons.First(); i != m_dictWeapons.InvalidIndex(); i = m_dictWeapons.Next( i ) )
- {
- buf.PutString( m_dictWeapons.GetElementName( i ) );
- buf.Put( (const void *)&m_dictWeapons[ i ], sizeof( Ep2LevelStats_t::WeaponLump_t ) );
- }
- CBaseGameStats::AppendLump( EP2_MAX_LUMP_COUNT, SaveBuffer, EP2STATS_LUMP_WEAPON, 1, buf.TellPut(), buf.Base() );
- }
- {
- CUtlBuffer buf;
- buf.PutString( m_SaveGameInfo.m_sCurrentSaveFile.String() );
- buf.PutInt( m_SaveGameInfo.m_nCurrentSaveFileTime );
- buf.PutInt( m_SaveGameInfo.m_Records.Count() );
- for ( int i = 0 ; i < m_SaveGameInfo.m_Records.Count(); ++i )
- {
- buf.Put( (const void *)&m_SaveGameInfo.m_Records[ i ], sizeof( Ep2LevelStats_t::SaveGameInfoRecord2_t ) );
- }
- CBaseGameStats::AppendLump( EP2_MAX_LUMP_COUNT, SaveBuffer, EP2STATS_LUMP_SAVEGAMEINFO, 1, buf.TellPut(), buf.Base() );
- }
- {
- CUtlBuffer buf;
- buf.PutShort( Ep2LevelStats_t::GenericStatsLump_t::LumpVersion );
- buf.PutInt( m_dictGeneric.Count() );
- for ( int i = m_dictGeneric.First(); i != m_dictGeneric.InvalidIndex(); i = m_dictGeneric.Next( i ) )
- {
- buf.PutString( m_dictGeneric.GetElementName( i ) );
- buf.Put( (const void *)&m_dictGeneric[ i ], sizeof( Ep2LevelStats_t::GenericStatsLump_t ) );
- }
- CBaseGameStats::AppendLump( EP2_MAX_LUMP_COUNT, SaveBuffer, EP2STATS_LUMP_GENERIC, 1, buf.TellPut(), buf.Base() );
- }
- }
-
- static void LoadData( CUtlDict<Ep2LevelStats_t, unsigned short>& items, CUtlBuffer &LoadBuffer )
- {
- // Read the next lump.
- unsigned short iLump = 0;
- unsigned short iLumpCount = 0;
-
- Ep2LevelStats_t *pItem = NULL;
-
- while( CBaseGameStats::GetLumpHeader( EP2_MAX_LUMP_COUNT, LoadBuffer, iLump, iLumpCount, true ) )
- {
- switch ( iLump )
- {
- case EP2STATS_LUMP_HEADER:
- {
- Ep2LevelStats_t::LevelHeader_t header;
- CBaseGameStats::LoadLump( LoadBuffer, iLumpCount, sizeof( Ep2LevelStats_t::LevelHeader_t ), &header );
- pItem = &items[ items.Insert( header.m_szMapName ) ];
- pItem->m_Header = header;
- pItem->m_Tag.Clear();
- Assert( pItem );
- }
- break;
- case EP2STATS_LUMP_TAG:
- {
- Assert( pItem );
- CBaseGameStats::LoadLump( LoadBuffer, iLumpCount, sizeof( Ep2LevelStats_t::Tag_t ), &pItem->m_Tag );
- }
- break;
- case EP2STATS_LUMP_DEATH:
- {
- Assert( pItem );
- pItem->m_aPlayerDeaths.SetCount( iLumpCount );
- CBaseGameStats::LoadLump( LoadBuffer, iLumpCount, sizeof( Ep2LevelStats_t::PlayerDeathsLump_t ), static_cast<void*>( pItem->m_aPlayerDeaths.Base() ) );
- }
- break;
- case EP2STATS_LUMP_NPC:
- {
- Assert( pItem );
- LoadBuffer.Get( ( void * )pItem->m_IntCounters, sizeof( pItem->m_IntCounters ) );
- LoadBuffer.Get( ( void * )pItem->m_FloatCounters, sizeof( pItem->m_FloatCounters ) );
- int c = LoadBuffer.GetInt();
- for ( int i = 0 ; i < c; ++i )
- {
- Ep2LevelStats_t::EntityDeathsLump_t data;
- char npcName[ 512 ];
- LoadBuffer.GetString( npcName, sizeof( npcName ) );
- LoadBuffer.Get( &data, sizeof( data ) );
- pItem->m_dictEntityDeaths.Insert( npcName, data );
- }
- }
- break;
- case EP2STATS_LUMP_WEAPON:
- {
- Assert( pItem );
- int c = LoadBuffer.GetInt();
- for ( int i = 0 ; i < c; ++i )
- {
- Ep2LevelStats_t::WeaponLump_t data;
- char weaponName[ 512 ];
- LoadBuffer.GetString( weaponName, sizeof( weaponName ) );
- LoadBuffer.Get( &data, sizeof( data ) );
- pItem->m_dictWeapons.Insert( weaponName, data );
- }
- }
- break;
- case EP2STATS_LUMP_SAVEGAMEINFO:
- {
- Assert( pItem );
- Ep2LevelStats_t::SaveGameInfo_t *info = &pItem->m_SaveGameInfo;
- char sz[ 512 ];
- LoadBuffer.GetString( sz, sizeof( sz ) );
- info->m_sCurrentSaveFile = sz;
- info->m_nCurrentSaveFileTime = LoadBuffer.GetInt();
- int c = LoadBuffer.GetInt();
- for ( int i = 0 ; i < c; ++i )
- {
- Ep2LevelStats_t::SaveGameInfoRecord2_t rec;
- if ( pItem->m_Header.m_iVersion >= EP2_GAMESTATS_FILE_VERSION_02 )
- {
- LoadBuffer.Get( &rec, sizeof( rec ) );
- }
- else
- {
- size_t s = sizeof( Ep2LevelStats_t::SaveGameInfoRecord_t );
- LoadBuffer.Get( &rec, s );
- }
- info->m_Records.AddToTail( rec );
- }
- info->m_pCurrentRecord = NULL;
- if ( info->m_Records.Count() > 0 )
- {
- info->m_pCurrentRecord = &info->m_Records[ info->m_Records.Count() - 1 ];
- }
- }
- break;
- case EP2STATS_LUMP_GENERIC:
- {
- Assert( pItem );
- int version = LoadBuffer.GetShort();
- if ( version == Ep2LevelStats_t::GenericStatsLump_t::LumpVersion )
- {
- int c = LoadBuffer.GetInt();
- Assert( c < 2 * 1024 * 1024 );
- for ( int i = 0 ; i < c; ++i )
- {
- Ep2LevelStats_t::GenericStatsLump_t data;
- char pchStatName[ 512 ];
- LoadBuffer.GetString( pchStatName, sizeof( pchStatName ) );
- LoadBuffer.Get( &data, sizeof( data ) );
- pItem->m_dictGeneric.Insert( pchStatName, data );
- }
- }
- else
- {
- Error( "Unsupported GenericStatsLump_t::LumpVersion" );
- }
- }
- break;
- }
- }
- }
-
-public:
- // Level header data.
- struct LevelHeader_t
- {
- static const unsigned short LumpId = EP2STATS_LUMP_HEADER; // Lump ids.
- byte m_iVersion; // Version of the game stats file.
- char m_szMapName[64]; // Name of the map.
- float m_flTime; // Time spent in level.
- };
-
- // Simple "tag" applied to all data in database (e.g., "PLAYTEST")
- struct Tag_t
- {
- static const unsigned short LumpId = EP2STATS_LUMP_TAG;
-
- void Clear()
- {
- Q_memset( m_szTagText, 0, sizeof( m_szTagText ) );
- m_nMapVersion = 0;
- }
-
- char m_szTagText[ 8 ];
- int m_nMapVersion;
- };
-
- // Player deaths.
- struct PlayerDeathsLump_t
- {
- static const unsigned short LumpId = EP2STATS_LUMP_DEATH; // Lump ids.
- short nPosition[3]; // Position of death.
-// short iWeapon; // Weapon that killed the player.
-// byte iAttackClass; // Class that killed the player.
-// byte iTargetClass; // Class of the player killed.
- };
-
- struct EntityDeathsLump_t
- {
- static const unsigned short LumpId = EP2STATS_LUMP_NPC;
-
- EntityDeathsLump_t() :
- m_nBodyCount( 0u ),
- m_nKilledPlayer( 0u )
- {
- }
-
- EntityDeathsLump_t( const EntityDeathsLump_t &other )
- {
- m_nBodyCount = other.m_nBodyCount;
- m_nKilledPlayer = other.m_nKilledPlayer;
- }
-
- unsigned int m_nBodyCount; // Number killed by player
- unsigned int m_nKilledPlayer; // Number of times entity killed player
- };
-
- struct WeaponLump_t
- {
- static const unsigned short LumpId = EP2STATS_LUMP_WEAPON;
-
- WeaponLump_t() :
- m_nShots( 0 ),
- m_nHits( 0 ),
- m_flDamageInflicted( 0.0 )
- {
- }
-
- WeaponLump_t( const WeaponLump_t &other )
- {
- m_nShots = other.m_nShots;
- m_nHits = other.m_nHits;
- m_flDamageInflicted = other.m_flDamageInflicted;
- }
-
- unsigned int m_nShots;
- unsigned int m_nHits;
- double m_flDamageInflicted;
- };
-
- struct SaveGameInfoRecord_t
- {
- SaveGameInfoRecord_t() :
- m_nFirstDeathIndex( -1 ),
- m_nNumDeaths( 0 ),
- m_nSaveHealth( -1 )
- {
- Q_memset( m_nSavePos, 0, sizeof( m_nSavePos ) );
- }
-
- int m_nFirstDeathIndex;
- int m_nNumDeaths;
- // Health and player pos from the save file
- short m_nSavePos[ 3 ];
- short m_nSaveHealth;
- };
-
-#pragma pack( 1 )
- // Adds save game type
- struct SaveGameInfoRecord2_t : public SaveGameInfoRecord_t
- {
- enum SaveType_t
- {
- TYPE_UNKNOWN = 0,
- TYPE_AUTOSAVE,
- TYPE_USERSAVE
- };
-
- SaveGameInfoRecord2_t() :
- m_SaveType( (byte)TYPE_UNKNOWN )
- {
- }
-
- byte m_SaveType;
- };
-#pragma pack()
-
- struct SaveGameInfo_t
- {
- static const unsigned short LumpId = EP2STATS_LUMP_SAVEGAMEINFO;
-
- SaveGameInfo_t() :
- m_nCurrentSaveFileTime( 0 ),
- m_pCurrentRecord( NULL )
- {
- }
-
- void Latch( char const *pchSaveName, unsigned int uFileTime )
- {
- m_pCurrentRecord = &m_Records[ m_Records.AddToTail() ];
- m_nCurrentSaveFileTime = uFileTime;
- m_sCurrentSaveFile = pchSaveName;
- }
-
- CUtlVector< SaveGameInfoRecord2_t > m_Records;
- SaveGameInfoRecord2_t *m_pCurrentRecord;
- unsigned int m_nCurrentSaveFileTime;
- CUtlString m_sCurrentSaveFile;
- };
-
- struct GenericStatsLump_t
- {
- static const unsigned short LumpId = EP2STATS_LUMP_GENERIC;
- static const unsigned short LumpVersion = 1;
-
- GenericStatsLump_t() :
- m_unCount( 0u ),
- m_flCurrentValue( 0.0 )
- {
- m_Pos[ 0 ] = m_Pos[ 1 ] = m_Pos[ 2 ] = 0;
- }
-
- short m_Pos[ 3 ];
- unsigned int m_unCount;
- double m_flCurrentValue;
- };
-
- // Data.
- LevelHeader_t m_Header; // Level header.
- Tag_t m_Tag;
- CUtlVector<PlayerDeathsLump_t> m_aPlayerDeaths; // List of player deaths.
- CUtlDict< EntityDeathsLump_t, int > m_dictEntityDeaths;
- CUtlDict< WeaponLump_t, int > m_dictWeapons;
- CUtlDict< GenericStatsLump_t, int > m_dictGeneric;
-
- SaveGameInfo_t m_SaveGameInfo;
- float m_FloatCounters[ NUM_FLOATCOUNTER_TYPES ];
- uint64 m_IntCounters[ NUM_INTCOUNTER_TYPES ];
-
- // Temporary data.
- bool m_bInitialized; // Has the map Map Stat Data been initialized.
- float m_flLevelStartTime;
-};
-
-#if defined( GAME_DLL )
-class CEP2GameStats : public CEP1GameStats
-{
- typedef CEP1GameStats BaseClass;
-
-public:
- CEP2GameStats();
- virtual ~CEP2GameStats();
-
- virtual CBaseGameStats *OnInit( CBaseGameStats *pCurrentGameStats, char const *gamedir ) { return pCurrentGameStats; }
-
- virtual bool UserPlayedAllTheMaps( void );
- virtual const char *GetStatSaveFileName( void );
- virtual const char *GetStatUploadRegistryKeyName( void );
-
- // Buffers.
- virtual void AppendCustomDataToSaveBuffer( CUtlBuffer &SaveBuffer );
- virtual void LoadCustomDataFromBuffer( CUtlBuffer &LoadBuffer );
-
- // Events
- virtual void Event_LevelInit( void );
- virtual void Event_PlayerKilled( CBasePlayer *pPlayer, const CTakeDamageInfo &info );
- virtual void Event_PlayerDamage( CBasePlayer *pBasePlayer, const CTakeDamageInfo &info );
- virtual void Event_PlayerKilledOther( CBasePlayer *pAttacker, CBaseEntity *pVictim, const CTakeDamageInfo &info );
- virtual void Event_CrateSmashed();
- virtual void Event_Punted( CBaseEntity *pObject );
- virtual void Event_PlayerTraveled( CBasePlayer *pBasePlayer, float distanceInInches, bool bInVehicle, bool bSprinting );
- virtual void Event_WeaponFired( CBasePlayer *pShooter, bool bPrimary, char const *pchWeaponName );
- virtual void Event_WeaponHit( CBasePlayer *pShooter, bool bPrimary, char const *pchWeaponName, const CTakeDamageInfo &info );
- virtual void Event_SaveGame( void );
- virtual void Event_LoadGame( void );
- virtual void Event_FlippedVehicle( CBasePlayer *pDriver, CPropVehicleDriveable *pVehicle );
- // Called before .sav file is actually loaded (player should still be in previous level, if any)
- virtual void Event_PreSaveGameLoaded( char const *pSaveName, bool bInGame );
- virtual void Event_PlayerEnteredGodMode( CBasePlayer *pBasePlayer );
- virtual void Event_PlayerEnteredNoClip( CBasePlayer *pBasePlayer );
- virtual void Event_DecrementPlayerEnteredNoClip( CBasePlayer *pBasePlayer );
- // Generic statistics lump
- virtual void Event_IncrementCountedStatistic( const Vector& vecAbsOrigin, char const *pchStatisticName, float flIncrementAmount );
-
-public: //FIXME: temporary used for CC_ListDeaths command
- Ep2LevelStats_t *FindOrAddMapStats( const char *szMapName );
-
-public:
-
- Ep2LevelStats_t::EntityDeathsLump_t *FindDeathsLump( char const *npcName );
- Ep2LevelStats_t::WeaponLump_t *FindWeaponsLump( char const *pchWeaponName, bool bPrimary );
- Ep2LevelStats_t::GenericStatsLump_t *FindGenericLump( char const *pchStatName );
- // Utilities.
- Ep2LevelStats_t *GetCurrentMap( void ) { return m_pCurrentMap; }
-
- Ep2LevelStats_t *m_pCurrentMap;
- CUtlDict<Ep2LevelStats_t, unsigned short> m_dictMapStats;
- enum
- {
- INVEHICLE = 0,
- ONFOOT,
- ONFOOTSPRINTING,
-
- NUM_TRAVEL_TYPES
- };
- float m_flInchesRemainder[ NUM_TRAVEL_TYPES ];
-};
-#endif
-
-#endif // EP2_GAMESTATS_H
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================
+
+#ifndef EP2_GAMESTATS_H
+#define EP2_GAMESTATS_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "ep1_gamestats.h"
+#include "tier1/utlstring.h"
+
+// EP2 Game Stats
+enum Ep2GameStatsVersions_t
+{
+ EP2_GAMESTATS_FILE_VERSION_01 = 001,
+ EP2_GAMESTATS_FILE_VERSION_02 = 002,
+
+ EP2_GAMESTATS_CURRENT_VERSION = EP2_GAMESTATS_FILE_VERSION_02,
+};
+
+enum Ep2GameStatsLumpIds_t
+{
+ EP2STATS_LUMP_HEADER = 1,
+ EP2STATS_LUMP_DEATH,
+ EP2STATS_LUMP_NPC,
+ EP2STATS_LUMP_WEAPON,
+ EP2STATS_LUMP_SAVEGAMEINFO,
+ EP2STATS_LUMP_TAG,
+ EP2STATS_LUMP_GENERIC,
+ EP2_MAX_LUMP_COUNT
+};
+
+// EP2 Game Level Stats Data
+struct Ep2LevelStats_t
+{
+public:
+ enum FloatCounterTypes_t
+ {
+ COUNTER_DAMAGETAKEN = 0,
+
+ NUM_FLOATCOUNTER_TYPES,
+ };
+
+ enum IntCounterTypes_t
+ {
+ COUNTER_CRATESSMASHED = 0,
+ COUNTER_OBJECTSPUNTED,
+ COUNTER_VEHICULARHOMICIDES,
+ COUNTER_DISTANCE_INVEHICLE,
+ COUNTER_DISTANCE_ONFOOT,
+ COUNTER_DISTANCE_ONFOOTSPRINTING,
+ COUNTER_FALLINGDEATHS,
+ COUNTER_VEHICLE_OVERTURNED,
+ COUNTER_LOADGAME_STILLALIVE,
+ COUNTER_LOADS,
+ COUNTER_SAVES,
+ COUNTER_GODMODES,
+ COUNTER_NOCLIPS,
+
+ NUM_INTCOUNTER_TYPES,
+ };
+
+ Ep2LevelStats_t() :
+ m_bInitialized( false ),
+ m_flLevelStartTime( 0.0f )
+ {
+ Q_memset( m_IntCounters, 0, sizeof( m_IntCounters ) );
+ Q_memset( m_FloatCounters, 0, sizeof( m_FloatCounters ) );
+ }
+ ~Ep2LevelStats_t()
+ {
+ }
+
+ Ep2LevelStats_t( const Ep2LevelStats_t &other )
+ {
+ m_bInitialized = other.m_bInitialized;
+ m_flLevelStartTime = other.m_flLevelStartTime;
+ m_Header = other.m_Header;
+ m_aPlayerDeaths = other.m_aPlayerDeaths;
+ Q_memcpy( m_IntCounters, other.m_IntCounters, sizeof( m_IntCounters ) );
+ Q_memcpy( m_FloatCounters, other.m_FloatCounters, sizeof( m_FloatCounters ) );
+ int i;
+ for ( i = other.m_dictEntityDeaths.First(); i != other.m_dictEntityDeaths.InvalidIndex(); i = other.m_dictEntityDeaths.Next( i ) )
+ {
+ m_dictEntityDeaths.Insert( other.m_dictEntityDeaths.GetElementName( i ), other.m_dictEntityDeaths[ i ] );
+ }
+ for ( i = other.m_dictWeapons.First(); i != other.m_dictWeapons.InvalidIndex(); i = other.m_dictWeapons.Next( i ) )
+ {
+ m_dictWeapons.Insert( other.m_dictWeapons.GetElementName( i ), other.m_dictWeapons[ i ] );
+ }
+ m_SaveGameInfo = other.m_SaveGameInfo;
+ }
+
+ // Create and destroy.
+ void Init( const char *pszMapName, float flStartTime, char const *pchTag, int nMapVersion )
+ {
+ // Initialize.
+ m_Header.m_iVersion = EP2_GAMESTATS_CURRENT_VERSION;
+ Q_strncpy( m_Header.m_szMapName, pszMapName, sizeof( m_Header.m_szMapName ) );
+ m_Header.m_flTime = 0.0f;
+
+ // Start the level timer.
+ m_flLevelStartTime = flStartTime;
+
+ Q_strncpy( m_Tag.m_szTagText, pchTag, sizeof( m_Tag.m_szTagText ) );
+ m_Tag.m_nMapVersion = nMapVersion;
+ }
+
+ void Shutdown( float flEndTime )
+ {
+ m_Header.m_flTime = flEndTime - m_flLevelStartTime;
+ }
+
+ void AppendToBuffer( CUtlBuffer &SaveBuffer )
+ {
+ // Always write out as current version
+ m_Header.m_iVersion = EP2_GAMESTATS_CURRENT_VERSION;
+
+ // Write out the lumps.
+ CBaseGameStats::AppendLump( EP2_MAX_LUMP_COUNT, SaveBuffer, EP2STATS_LUMP_HEADER, 1, sizeof( Ep2LevelStats_t::LevelHeader_t ), static_cast<void*>( &m_Header ) );
+ CBaseGameStats::AppendLump( EP2_MAX_LUMP_COUNT, SaveBuffer, EP2STATS_LUMP_TAG, 1, sizeof( Ep2LevelStats_t::Tag_t ), static_cast< void * >( &m_Tag ) );
+ CBaseGameStats::AppendLump( EP2_MAX_LUMP_COUNT, SaveBuffer, EP2STATS_LUMP_DEATH, m_aPlayerDeaths.Count(), sizeof( Ep2LevelStats_t::PlayerDeathsLump_t ), static_cast<void*>( m_aPlayerDeaths.Base() ) );
+ {
+ CUtlBuffer buf;
+ buf.Put( (const void *)m_IntCounters, sizeof( m_IntCounters ) );
+ buf.Put( (const void *)m_FloatCounters, sizeof( m_FloatCounters ) );
+ buf.PutInt( m_dictEntityDeaths.Count() );
+ for ( int i = m_dictEntityDeaths.First(); i != m_dictEntityDeaths.InvalidIndex(); i = m_dictEntityDeaths.Next( i ) )
+ {
+ buf.PutString( m_dictEntityDeaths.GetElementName( i ) );
+ buf.Put( (const void *)&m_dictEntityDeaths[ i ], sizeof( Ep2LevelStats_t::EntityDeathsLump_t ) );
+ }
+ CBaseGameStats::AppendLump( EP2_MAX_LUMP_COUNT, SaveBuffer, EP2STATS_LUMP_NPC, 1, buf.TellPut(), buf.Base() );
+ }
+ {
+ CUtlBuffer buf;
+ buf.PutInt( m_dictWeapons.Count() );
+ for ( int i = m_dictWeapons.First(); i != m_dictWeapons.InvalidIndex(); i = m_dictWeapons.Next( i ) )
+ {
+ buf.PutString( m_dictWeapons.GetElementName( i ) );
+ buf.Put( (const void *)&m_dictWeapons[ i ], sizeof( Ep2LevelStats_t::WeaponLump_t ) );
+ }
+ CBaseGameStats::AppendLump( EP2_MAX_LUMP_COUNT, SaveBuffer, EP2STATS_LUMP_WEAPON, 1, buf.TellPut(), buf.Base() );
+ }
+ {
+ CUtlBuffer buf;
+ buf.PutString( m_SaveGameInfo.m_sCurrentSaveFile.String() );
+ buf.PutInt( m_SaveGameInfo.m_nCurrentSaveFileTime );
+ buf.PutInt( m_SaveGameInfo.m_Records.Count() );
+ for ( int i = 0 ; i < m_SaveGameInfo.m_Records.Count(); ++i )
+ {
+ buf.Put( (const void *)&m_SaveGameInfo.m_Records[ i ], sizeof( Ep2LevelStats_t::SaveGameInfoRecord2_t ) );
+ }
+ CBaseGameStats::AppendLump( EP2_MAX_LUMP_COUNT, SaveBuffer, EP2STATS_LUMP_SAVEGAMEINFO, 1, buf.TellPut(), buf.Base() );
+ }
+ {
+ CUtlBuffer buf;
+ buf.PutShort( Ep2LevelStats_t::GenericStatsLump_t::LumpVersion );
+ buf.PutInt( m_dictGeneric.Count() );
+ for ( int i = m_dictGeneric.First(); i != m_dictGeneric.InvalidIndex(); i = m_dictGeneric.Next( i ) )
+ {
+ buf.PutString( m_dictGeneric.GetElementName( i ) );
+ buf.Put( (const void *)&m_dictGeneric[ i ], sizeof( Ep2LevelStats_t::GenericStatsLump_t ) );
+ }
+ CBaseGameStats::AppendLump( EP2_MAX_LUMP_COUNT, SaveBuffer, EP2STATS_LUMP_GENERIC, 1, buf.TellPut(), buf.Base() );
+ }
+ }
+
+ static void LoadData( CUtlDict<Ep2LevelStats_t, unsigned short>& items, CUtlBuffer &LoadBuffer )
+ {
+ // Read the next lump.
+ unsigned short iLump = 0;
+ unsigned short iLumpCount = 0;
+
+ Ep2LevelStats_t *pItem = NULL;
+
+ while( CBaseGameStats::GetLumpHeader( EP2_MAX_LUMP_COUNT, LoadBuffer, iLump, iLumpCount, true ) )
+ {
+ switch ( iLump )
+ {
+ case EP2STATS_LUMP_HEADER:
+ {
+ Ep2LevelStats_t::LevelHeader_t header;
+ CBaseGameStats::LoadLump( LoadBuffer, iLumpCount, sizeof( Ep2LevelStats_t::LevelHeader_t ), &header );
+ pItem = &items[ items.Insert( header.m_szMapName ) ];
+ pItem->m_Header = header;
+ pItem->m_Tag.Clear();
+ Assert( pItem );
+ }
+ break;
+ case EP2STATS_LUMP_TAG:
+ {
+ Assert( pItem );
+ CBaseGameStats::LoadLump( LoadBuffer, iLumpCount, sizeof( Ep2LevelStats_t::Tag_t ), &pItem->m_Tag );
+ }
+ break;
+ case EP2STATS_LUMP_DEATH:
+ {
+ Assert( pItem );
+ pItem->m_aPlayerDeaths.SetCount( iLumpCount );
+ CBaseGameStats::LoadLump( LoadBuffer, iLumpCount, sizeof( Ep2LevelStats_t::PlayerDeathsLump_t ), static_cast<void*>( pItem->m_aPlayerDeaths.Base() ) );
+ }
+ break;
+ case EP2STATS_LUMP_NPC:
+ {
+ Assert( pItem );
+ LoadBuffer.Get( ( void * )pItem->m_IntCounters, sizeof( pItem->m_IntCounters ) );
+ LoadBuffer.Get( ( void * )pItem->m_FloatCounters, sizeof( pItem->m_FloatCounters ) );
+ int c = LoadBuffer.GetInt();
+ for ( int i = 0 ; i < c; ++i )
+ {
+ Ep2LevelStats_t::EntityDeathsLump_t data;
+ char npcName[ 512 ];
+ LoadBuffer.GetString( npcName, sizeof( npcName ) );
+ LoadBuffer.Get( &data, sizeof( data ) );
+ pItem->m_dictEntityDeaths.Insert( npcName, data );
+ }
+ }
+ break;
+ case EP2STATS_LUMP_WEAPON:
+ {
+ Assert( pItem );
+ int c = LoadBuffer.GetInt();
+ for ( int i = 0 ; i < c; ++i )
+ {
+ Ep2LevelStats_t::WeaponLump_t data;
+ char weaponName[ 512 ];
+ LoadBuffer.GetString( weaponName, sizeof( weaponName ) );
+ LoadBuffer.Get( &data, sizeof( data ) );
+ pItem->m_dictWeapons.Insert( weaponName, data );
+ }
+ }
+ break;
+ case EP2STATS_LUMP_SAVEGAMEINFO:
+ {
+ Assert( pItem );
+ Ep2LevelStats_t::SaveGameInfo_t *info = &pItem->m_SaveGameInfo;
+ char sz[ 512 ];
+ LoadBuffer.GetString( sz, sizeof( sz ) );
+ info->m_sCurrentSaveFile = sz;
+ info->m_nCurrentSaveFileTime = LoadBuffer.GetInt();
+ int c = LoadBuffer.GetInt();
+ for ( int i = 0 ; i < c; ++i )
+ {
+ Ep2LevelStats_t::SaveGameInfoRecord2_t rec;
+ if ( pItem->m_Header.m_iVersion >= EP2_GAMESTATS_FILE_VERSION_02 )
+ {
+ LoadBuffer.Get( &rec, sizeof( rec ) );
+ }
+ else
+ {
+ size_t s = sizeof( Ep2LevelStats_t::SaveGameInfoRecord_t );
+ LoadBuffer.Get( &rec, s );
+ }
+ info->m_Records.AddToTail( rec );
+ }
+ info->m_pCurrentRecord = NULL;
+ if ( info->m_Records.Count() > 0 )
+ {
+ info->m_pCurrentRecord = &info->m_Records[ info->m_Records.Count() - 1 ];
+ }
+ }
+ break;
+ case EP2STATS_LUMP_GENERIC:
+ {
+ Assert( pItem );
+ int version = LoadBuffer.GetShort();
+ if ( version == Ep2LevelStats_t::GenericStatsLump_t::LumpVersion )
+ {
+ int c = LoadBuffer.GetInt();
+ Assert( c < 2 * 1024 * 1024 );
+ for ( int i = 0 ; i < c; ++i )
+ {
+ Ep2LevelStats_t::GenericStatsLump_t data;
+ char pchStatName[ 512 ];
+ LoadBuffer.GetString( pchStatName, sizeof( pchStatName ) );
+ LoadBuffer.Get( &data, sizeof( data ) );
+ pItem->m_dictGeneric.Insert( pchStatName, data );
+ }
+ }
+ else
+ {
+ Error( "Unsupported GenericStatsLump_t::LumpVersion" );
+ }
+ }
+ break;
+ }
+ }
+ }
+
+public:
+ // Level header data.
+ struct LevelHeader_t
+ {
+ static const unsigned short LumpId = EP2STATS_LUMP_HEADER; // Lump ids.
+ byte m_iVersion; // Version of the game stats file.
+ char m_szMapName[64]; // Name of the map.
+ float m_flTime; // Time spent in level.
+ };
+
+ // Simple "tag" applied to all data in database (e.g., "PLAYTEST")
+ struct Tag_t
+ {
+ static const unsigned short LumpId = EP2STATS_LUMP_TAG;
+
+ void Clear()
+ {
+ Q_memset( m_szTagText, 0, sizeof( m_szTagText ) );
+ m_nMapVersion = 0;
+ }
+
+ char m_szTagText[ 8 ];
+ int m_nMapVersion;
+ };
+
+ // Player deaths.
+ struct PlayerDeathsLump_t
+ {
+ static const unsigned short LumpId = EP2STATS_LUMP_DEATH; // Lump ids.
+ short nPosition[3]; // Position of death.
+// short iWeapon; // Weapon that killed the player.
+// byte iAttackClass; // Class that killed the player.
+// byte iTargetClass; // Class of the player killed.
+ };
+
+ struct EntityDeathsLump_t
+ {
+ static const unsigned short LumpId = EP2STATS_LUMP_NPC;
+
+ EntityDeathsLump_t() :
+ m_nBodyCount( 0u ),
+ m_nKilledPlayer( 0u )
+ {
+ }
+
+ EntityDeathsLump_t( const EntityDeathsLump_t &other )
+ {
+ m_nBodyCount = other.m_nBodyCount;
+ m_nKilledPlayer = other.m_nKilledPlayer;
+ }
+
+ unsigned int m_nBodyCount; // Number killed by player
+ unsigned int m_nKilledPlayer; // Number of times entity killed player
+ };
+
+ struct WeaponLump_t
+ {
+ static const unsigned short LumpId = EP2STATS_LUMP_WEAPON;
+
+ WeaponLump_t() :
+ m_nShots( 0 ),
+ m_nHits( 0 ),
+ m_flDamageInflicted( 0.0 )
+ {
+ }
+
+ WeaponLump_t( const WeaponLump_t &other )
+ {
+ m_nShots = other.m_nShots;
+ m_nHits = other.m_nHits;
+ m_flDamageInflicted = other.m_flDamageInflicted;
+ }
+
+ unsigned int m_nShots;
+ unsigned int m_nHits;
+ double m_flDamageInflicted;
+ };
+
+ struct SaveGameInfoRecord_t
+ {
+ SaveGameInfoRecord_t() :
+ m_nFirstDeathIndex( -1 ),
+ m_nNumDeaths( 0 ),
+ m_nSaveHealth( -1 )
+ {
+ Q_memset( m_nSavePos, 0, sizeof( m_nSavePos ) );
+ }
+
+ int m_nFirstDeathIndex;
+ int m_nNumDeaths;
+ // Health and player pos from the save file
+ short m_nSavePos[ 3 ];
+ short m_nSaveHealth;
+ };
+
+#pragma pack( 1 )
+ // Adds save game type
+ struct SaveGameInfoRecord2_t : public SaveGameInfoRecord_t
+ {
+ enum SaveType_t
+ {
+ TYPE_UNKNOWN = 0,
+ TYPE_AUTOSAVE,
+ TYPE_USERSAVE
+ };
+
+ SaveGameInfoRecord2_t() :
+ m_SaveType( (byte)TYPE_UNKNOWN )
+ {
+ }
+
+ byte m_SaveType;
+ };
+#pragma pack()
+
+ struct SaveGameInfo_t
+ {
+ static const unsigned short LumpId = EP2STATS_LUMP_SAVEGAMEINFO;
+
+ SaveGameInfo_t() :
+ m_nCurrentSaveFileTime( 0 ),
+ m_pCurrentRecord( NULL )
+ {
+ }
+
+ void Latch( char const *pchSaveName, unsigned int uFileTime )
+ {
+ m_pCurrentRecord = &m_Records[ m_Records.AddToTail() ];
+ m_nCurrentSaveFileTime = uFileTime;
+ m_sCurrentSaveFile = pchSaveName;
+ }
+
+ CUtlVector< SaveGameInfoRecord2_t > m_Records;
+ SaveGameInfoRecord2_t *m_pCurrentRecord;
+ unsigned int m_nCurrentSaveFileTime;
+ CUtlString m_sCurrentSaveFile;
+ };
+
+ struct GenericStatsLump_t
+ {
+ static const unsigned short LumpId = EP2STATS_LUMP_GENERIC;
+ static const unsigned short LumpVersion = 1;
+
+ GenericStatsLump_t() :
+ m_unCount( 0u ),
+ m_flCurrentValue( 0.0 )
+ {
+ m_Pos[ 0 ] = m_Pos[ 1 ] = m_Pos[ 2 ] = 0;
+ }
+
+ short m_Pos[ 3 ];
+ unsigned int m_unCount;
+ double m_flCurrentValue;
+ };
+
+ // Data.
+ LevelHeader_t m_Header; // Level header.
+ Tag_t m_Tag;
+ CUtlVector<PlayerDeathsLump_t> m_aPlayerDeaths; // List of player deaths.
+ CUtlDict< EntityDeathsLump_t, int > m_dictEntityDeaths;
+ CUtlDict< WeaponLump_t, int > m_dictWeapons;
+ CUtlDict< GenericStatsLump_t, int > m_dictGeneric;
+
+ SaveGameInfo_t m_SaveGameInfo;
+ float m_FloatCounters[ NUM_FLOATCOUNTER_TYPES ];
+ uint64 m_IntCounters[ NUM_INTCOUNTER_TYPES ];
+
+ // Temporary data.
+ bool m_bInitialized; // Has the map Map Stat Data been initialized.
+ float m_flLevelStartTime;
+};
+
+#if defined( GAME_DLL )
+class CEP2GameStats : public CEP1GameStats
+{
+ typedef CEP1GameStats BaseClass;
+
+public:
+ CEP2GameStats();
+ virtual ~CEP2GameStats();
+
+ virtual CBaseGameStats *OnInit( CBaseGameStats *pCurrentGameStats, char const *gamedir ) { return pCurrentGameStats; }
+
+ virtual bool UserPlayedAllTheMaps( void );
+ virtual const char *GetStatSaveFileName( void );
+ virtual const char *GetStatUploadRegistryKeyName( void );
+
+ // Buffers.
+ virtual void AppendCustomDataToSaveBuffer( CUtlBuffer &SaveBuffer );
+ virtual void LoadCustomDataFromBuffer( CUtlBuffer &LoadBuffer );
+
+ // Events
+ virtual void Event_LevelInit( void );
+ virtual void Event_PlayerKilled( CBasePlayer *pPlayer, const CTakeDamageInfo &info );
+ virtual void Event_PlayerDamage( CBasePlayer *pBasePlayer, const CTakeDamageInfo &info );
+ virtual void Event_PlayerKilledOther( CBasePlayer *pAttacker, CBaseEntity *pVictim, const CTakeDamageInfo &info );
+ virtual void Event_CrateSmashed();
+ virtual void Event_Punted( CBaseEntity *pObject );
+ virtual void Event_PlayerTraveled( CBasePlayer *pBasePlayer, float distanceInInches, bool bInVehicle, bool bSprinting );
+ virtual void Event_WeaponFired( CBasePlayer *pShooter, bool bPrimary, char const *pchWeaponName );
+ virtual void Event_WeaponHit( CBasePlayer *pShooter, bool bPrimary, char const *pchWeaponName, const CTakeDamageInfo &info );
+ virtual void Event_SaveGame( void );
+ virtual void Event_LoadGame( void );
+ virtual void Event_FlippedVehicle( CBasePlayer *pDriver, CPropVehicleDriveable *pVehicle );
+ // Called before .sav file is actually loaded (player should still be in previous level, if any)
+ virtual void Event_PreSaveGameLoaded( char const *pSaveName, bool bInGame );
+ virtual void Event_PlayerEnteredGodMode( CBasePlayer *pBasePlayer );
+ virtual void Event_PlayerEnteredNoClip( CBasePlayer *pBasePlayer );
+ virtual void Event_DecrementPlayerEnteredNoClip( CBasePlayer *pBasePlayer );
+ // Generic statistics lump
+ virtual void Event_IncrementCountedStatistic( const Vector& vecAbsOrigin, char const *pchStatisticName, float flIncrementAmount );
+
+public: //FIXME: temporary used for CC_ListDeaths command
+ Ep2LevelStats_t *FindOrAddMapStats( const char *szMapName );
+
+public:
+
+ Ep2LevelStats_t::EntityDeathsLump_t *FindDeathsLump( char const *npcName );
+ Ep2LevelStats_t::WeaponLump_t *FindWeaponsLump( char const *pchWeaponName, bool bPrimary );
+ Ep2LevelStats_t::GenericStatsLump_t *FindGenericLump( char const *pchStatName );
+ // Utilities.
+ Ep2LevelStats_t *GetCurrentMap( void ) { return m_pCurrentMap; }
+
+ Ep2LevelStats_t *m_pCurrentMap;
+ CUtlDict<Ep2LevelStats_t, unsigned short> m_dictMapStats;
+ enum
+ {
+ INVEHICLE = 0,
+ ONFOOT,
+ ONFOOTSPRINTING,
+
+ NUM_TRAVEL_TYPES
+ };
+ float m_flInchesRemainder[ NUM_TRAVEL_TYPES ];
+};
+#endif
+
+#endif // EP2_GAMESTATS_H
diff --git a/mp/src/game/server/episodic/grenade_hopwire.cpp b/mp/src/game/server/episodic/grenade_hopwire.cpp
index 4fff289b..7010c4b0 100644
--- a/mp/src/game/server/episodic/grenade_hopwire.cpp
+++ b/mp/src/game/server/episodic/grenade_hopwire.cpp
@@ -1,581 +1,581 @@
-//========= Copyright Valve Corporation, All rights reserved. ============//
-//
-// Purpose: Gravity well device
-//
-//=====================================================================================//
-
-#include "cbase.h"
-#include "grenade_hopwire.h"
-#include "rope.h"
-#include "rope_shared.h"
-#include "beam_shared.h"
-#include "physics.h"
-#include "physics_saverestore.h"
-#include "explode.h"
-#include "physics_prop_ragdoll.h"
-#include "movevars_shared.h"
-
-// memdbgon must be the last include file in a .cpp file!!!
-#include "tier0/memdbgon.h"
-
-ConVar hopwire_vortex( "hopwire_vortex", "0" );
-ConVar hopwire_trap( "hopwire_trap", "1" );
-ConVar hopwire_strider_kill_dist_h( "hopwire_strider_kill_dist_h", "300" );
-ConVar hopwire_strider_kill_dist_v( "hopwire_strider_kill_dist_v", "256" );
-ConVar hopwire_strider_hits( "hopwire_strider_hits", "1" );
-ConVar hopwire_hopheight( "hopwire_hopheight", "400" );
-
-ConVar g_debug_hopwire( "g_debug_hopwire", "0" );
-
-#define DENSE_BALL_MODEL "models/props_junk/metal_paintcan001b.mdl"
-
-#define MAX_HOP_HEIGHT (hopwire_hopheight.GetFloat()) // Maximum amount the grenade will "hop" upwards when detonated
-
-class CGravityVortexController : public CBaseEntity
-{
- DECLARE_CLASS( CGravityVortexController, CBaseEntity );
- DECLARE_DATADESC();
-
-public:
-
- CGravityVortexController( void ) : m_flEndTime( 0.0f ), m_flRadius( 256 ), m_flStrength( 256 ), m_flMass( 0.0f ) {}
- float GetConsumedMass( void ) const;
-
- static CGravityVortexController *Create( const Vector &origin, float radius, float strength, float duration );
-
-private:
-
- void ConsumeEntity( CBaseEntity *pEnt );
- void PullPlayersInRange( void );
- bool KillNPCInRange( CBaseEntity *pVictim, IPhysicsObject **pPhysObj );
- void CreateDenseBall( void );
- void PullThink( void );
- void StartPull( const Vector &origin, float radius, float strength, float duration );
-
- float m_flMass; // Mass consumed by the vortex
- float m_flEndTime; // Time when the vortex will stop functioning
- float m_flRadius; // Area of effect for the vortex
- float m_flStrength; // Pulling strength of the vortex
-};
-
-//-----------------------------------------------------------------------------
-// Purpose: Returns the amount of mass consumed by the vortex
-//-----------------------------------------------------------------------------
-float CGravityVortexController::GetConsumedMass( void ) const
-{
- return m_flMass;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Adds the entity's mass to the aggregate mass consumed
-//-----------------------------------------------------------------------------
-void CGravityVortexController::ConsumeEntity( CBaseEntity *pEnt )
-{
- // Get our base physics object
- IPhysicsObject *pPhysObject = pEnt->VPhysicsGetObject();
- if ( pPhysObject == NULL )
- return;
-
- // Ragdolls need to report the sum of all their parts
- CRagdollProp *pRagdoll = dynamic_cast< CRagdollProp* >( pEnt );
- if ( pRagdoll != NULL )
- {
- // Find the aggregate mass of the whole ragdoll
- ragdoll_t *pRagdollPhys = pRagdoll->GetRagdoll();
- for ( int j = 0; j < pRagdollPhys->listCount; ++j )
- {
- m_flMass += pRagdollPhys->list[j].pObject->GetMass();
- }
- }
- else
- {
- // Otherwise we just take the normal mass
- m_flMass += pPhysObject->GetMass();
- }
-
- // Destroy the entity
- UTIL_Remove( pEnt );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Causes players within the radius to be sucked in
-//-----------------------------------------------------------------------------
-void CGravityVortexController::PullPlayersInRange( void )
-{
- CBasePlayer *pPlayer = UTIL_GetLocalPlayer();
-
- Vector vecForce = GetAbsOrigin() - pPlayer->WorldSpaceCenter();
- float dist = VectorNormalize( vecForce );
-
- // FIXME: Need a more deterministic method here
- if ( dist < 128.0f )
- {
- // Kill the player (with falling death sound and effects)
- CTakeDamageInfo deathInfo( this, this, GetAbsOrigin(), GetAbsOrigin(), 200, DMG_FALL );
- pPlayer->TakeDamage( deathInfo );
-
- if ( pPlayer->IsAlive() == false )
- {
- color32 black = { 0, 0, 0, 255 };
- UTIL_ScreenFade( pPlayer, black, 0.1f, 0.0f, (FFADE_OUT|FFADE_STAYOUT) );
- return;
- }
- }
-
- // Must be within the radius
- if ( dist > m_flRadius )
- return;
-
- float mass = pPlayer->VPhysicsGetObject()->GetMass();
- float playerForce = m_flStrength * 0.05f;
-
- // Find the pull force
- // NOTE: We might want to make this non-linear to give more of a "grace distance"
- vecForce *= ( 1.0f - ( dist / m_flRadius ) ) * playerForce * mass;
- vecForce[2] *= 0.025f;
-
- pPlayer->SetBaseVelocity( vecForce );
- pPlayer->AddFlag( FL_BASEVELOCITY );
-
- // Make sure the player moves
- if ( vecForce.z > 0 && ( pPlayer->GetFlags() & FL_ONGROUND) )
- {
- pPlayer->SetGroundEntity( NULL );
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Attempts to kill an NPC if it's within range and other criteria
-// Input : *pVictim - NPC to assess
-// **pPhysObj - pointer to the ragdoll created if the NPC is killed
-// Output : bool - whether or not the NPC was killed and the returned pointer is valid
-//-----------------------------------------------------------------------------
-bool CGravityVortexController::KillNPCInRange( CBaseEntity *pVictim, IPhysicsObject **pPhysObj )
-{
- CBaseCombatCharacter *pBCC = pVictim->MyCombatCharacterPointer();
-
- // See if we can ragdoll
- if ( pBCC != NULL && pBCC->CanBecomeRagdoll() )
- {
- // Don't bother with striders
- if ( FClassnameIs( pBCC, "npc_strider" ) )
- return false;
-
- // TODO: Make this an interaction between the NPC and the vortex
-
- // Become ragdoll
- CTakeDamageInfo info( this, this, 1.0f, DMG_GENERIC );
- CBaseEntity *pRagdoll = CreateServerRagdoll( pBCC, 0, info, COLLISION_GROUP_INTERACTIVE_DEBRIS, true );
- pRagdoll->SetCollisionBounds( pVictim->CollisionProp()->OBBMins(), pVictim->CollisionProp()->OBBMaxs() );
-
- // Necessary to cause it to do the appropriate death cleanup
- CTakeDamageInfo ragdollInfo( this, this, 10000.0, DMG_GENERIC | DMG_REMOVENORAGDOLL );
- pVictim->TakeDamage( ragdollInfo );
-
- // Return the pointer to the ragdoll
- *pPhysObj = pRagdoll->VPhysicsGetObject();
- return true;
- }
-
- // Wasn't able to ragdoll this target
- *pPhysObj = NULL;
- return false;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Creates a dense ball with a mass equal to the aggregate mass consumed by the vortex
-//-----------------------------------------------------------------------------
-void CGravityVortexController::CreateDenseBall( void )
-{
- CBaseEntity *pBall = CreateEntityByName( "prop_physics" );
-
- pBall->SetModel( DENSE_BALL_MODEL );
- pBall->SetAbsOrigin( GetAbsOrigin() );
- pBall->Spawn();
-
- IPhysicsObject *pObj = pBall->VPhysicsGetObject();
- if ( pObj != NULL )
- {
- pObj->SetMass( GetConsumedMass() );
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Pulls physical objects towards the vortex center, killing them if they come too near
-//-----------------------------------------------------------------------------
-void CGravityVortexController::PullThink( void )
-{
- // Pull any players close enough to us
- PullPlayersInRange();
-
- Vector mins, maxs;
- mins = GetAbsOrigin() - Vector( m_flRadius, m_flRadius, m_flRadius );
- maxs = GetAbsOrigin() + Vector( m_flRadius, m_flRadius, m_flRadius );
-
- // Draw debug information
- if ( g_debug_hopwire.GetBool() )
- {
- NDebugOverlay::Box( GetAbsOrigin(), mins - GetAbsOrigin(), maxs - GetAbsOrigin(), 0, 255, 0, 16, 4.0f );
- }
-
- CBaseEntity *pEnts[128];
- int numEnts = UTIL_EntitiesInBox( pEnts, 128, mins, maxs, 0 );
-
- for ( int i = 0; i < numEnts; i++ )
- {
- IPhysicsObject *pPhysObject = NULL;
-
- // Attempt to kill and ragdoll any victims in range
- if ( KillNPCInRange( pEnts[i], &pPhysObject ) == false )
- {
- // If we didn't have a valid victim, see if we can just get the vphysics object
- pPhysObject = pEnts[i]->VPhysicsGetObject();
- if ( pPhysObject == NULL )
- continue;
- }
-
- float mass;
-
- CRagdollProp *pRagdoll = dynamic_cast< CRagdollProp* >( pEnts[i] );
- if ( pRagdoll != NULL )
- {
- ragdoll_t *pRagdollPhys = pRagdoll->GetRagdoll();
- mass = 0.0f;
-
- // Find the aggregate mass of the whole ragdoll
- for ( int j = 0; j < pRagdollPhys->listCount; ++j )
- {
- mass += pRagdollPhys->list[j].pObject->GetMass();
- }
- }
- else
- {
- mass = pPhysObject->GetMass();
- }
-
- Vector vecForce = GetAbsOrigin() - pEnts[i]->WorldSpaceCenter();
- Vector vecForce2D = vecForce;
- vecForce2D[2] = 0.0f;
- float dist2D = VectorNormalize( vecForce2D );
- float dist = VectorNormalize( vecForce );
-
- // FIXME: Need a more deterministic method here
- if ( dist < 48.0f )
- {
- ConsumeEntity( pEnts[i] );
- continue;
- }
-
- // Must be within the radius
- if ( dist > m_flRadius )
- continue;
-
- // Find the pull force
- vecForce *= ( 1.0f - ( dist2D / m_flRadius ) ) * m_flStrength * mass;
-
- if ( pEnts[i]->VPhysicsGetObject() )
- {
- // Pull the object in
- pEnts[i]->VPhysicsTakeDamage( CTakeDamageInfo( this, this, vecForce, GetAbsOrigin(), m_flStrength, DMG_BLAST ) );
- }
- }
-
- // Keep going if need-be
- if ( m_flEndTime > gpGlobals->curtime )
- {
- SetThink( &CGravityVortexController::PullThink );
- SetNextThink( gpGlobals->curtime + 0.1f );
- }
- else
- {
- //Msg( "Consumed %.2f kilograms\n", m_flMass );
- //CreateDenseBall();
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Starts the vortex working
-//-----------------------------------------------------------------------------
-void CGravityVortexController::StartPull( const Vector &origin, float radius, float strength, float duration )
-{
- SetAbsOrigin( origin );
- m_flEndTime = gpGlobals->curtime + duration;
- m_flRadius = radius;
- m_flStrength= strength;
-
- SetThink( &CGravityVortexController::PullThink );
- SetNextThink( gpGlobals->curtime + 0.1f );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Creation utility
-//-----------------------------------------------------------------------------
-CGravityVortexController *CGravityVortexController::Create( const Vector &origin, float radius, float strength, float duration )
-{
- // Create an instance of the vortex
- CGravityVortexController *pVortex = (CGravityVortexController *) CreateEntityByName( "vortex_controller" );
- if ( pVortex == NULL )
- return NULL;
-
- // Start the vortex working
- pVortex->StartPull( origin, radius, strength, duration );
-
- return pVortex;
-}
-
-BEGIN_DATADESC( CGravityVortexController )
- DEFINE_FIELD( m_flMass, FIELD_FLOAT ),
- DEFINE_FIELD( m_flEndTime, FIELD_TIME ),
- DEFINE_FIELD( m_flRadius, FIELD_FLOAT ),
- DEFINE_FIELD( m_flStrength, FIELD_FLOAT ),
-
- DEFINE_THINKFUNC( PullThink ),
-END_DATADESC()
-
-LINK_ENTITY_TO_CLASS( vortex_controller, CGravityVortexController );
-
-#define GRENADE_MODEL_CLOSED "models/roller.mdl"
-#define GRENADE_MODEL_OPEN "models/roller_spikes.mdl"
-
-BEGIN_DATADESC( CGrenadeHopwire )
- DEFINE_FIELD( m_hVortexController, FIELD_EHANDLE ),
-
- DEFINE_THINKFUNC( EndThink ),
- DEFINE_THINKFUNC( CombatThink ),
-END_DATADESC()
-
-LINK_ENTITY_TO_CLASS( npc_grenade_hopwire, CGrenadeHopwire );
-
-IMPLEMENT_SERVERCLASS_ST( CGrenadeHopwire, DT_GrenadeHopwire )
-END_SEND_TABLE()
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CGrenadeHopwire::Spawn( void )
-{
- Precache();
-
- SetModel( GRENADE_MODEL_CLOSED );
- SetCollisionGroup( COLLISION_GROUP_PROJECTILE );
-
- CreateVPhysics();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Output : Returns true on success, false on failure.
-//-----------------------------------------------------------------------------
-bool CGrenadeHopwire::CreateVPhysics()
-{
- // Create the object in the physics system
- VPhysicsInitNormal( SOLID_BBOX, 0, false );
- return true;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CGrenadeHopwire::Precache( void )
-{
- // FIXME: Replace
- //PrecacheSound("NPC_Strider.Shoot");
- //PrecacheSound("d3_citadel.weapon_zapper_beam_loop2");
-
- PrecacheModel( GRENADE_MODEL_OPEN );
- PrecacheModel( GRENADE_MODEL_CLOSED );
-
- PrecacheModel( DENSE_BALL_MODEL );
-
- BaseClass::Precache();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : timer -
-//-----------------------------------------------------------------------------
-void CGrenadeHopwire::SetTimer( float timer )
-{
- SetThink( &CBaseGrenade::PreDetonate );
- SetNextThink( gpGlobals->curtime + timer );
-}
-
-#define MAX_STRIDER_KILL_DISTANCE_HORZ (hopwire_strider_kill_dist_h.GetFloat()) // Distance a Strider will be killed if within
-#define MAX_STRIDER_KILL_DISTANCE_VERT (hopwire_strider_kill_dist_v.GetFloat()) // Distance a Strider will be killed if within
-
-#define MAX_STRIDER_STUN_DISTANCE_HORZ (MAX_STRIDER_KILL_DISTANCE_HORZ*2) // Distance a Strider will be stunned if within
-#define MAX_STRIDER_STUN_DISTANCE_VERT (MAX_STRIDER_KILL_DISTANCE_VERT*2) // Distance a Strider will be stunned if within
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CGrenadeHopwire::KillStriders( void )
-{
- CBaseEntity *pEnts[128];
- Vector mins, maxs;
-
- ClearBounds( mins, maxs );
- AddPointToBounds( -Vector( MAX_STRIDER_STUN_DISTANCE_HORZ, MAX_STRIDER_STUN_DISTANCE_HORZ, MAX_STRIDER_STUN_DISTANCE_HORZ ), mins, maxs );
- AddPointToBounds( Vector( MAX_STRIDER_STUN_DISTANCE_HORZ, MAX_STRIDER_STUN_DISTANCE_HORZ, MAX_STRIDER_STUN_DISTANCE_HORZ ), mins, maxs );
- AddPointToBounds( -Vector( MAX_STRIDER_STUN_DISTANCE_VERT, MAX_STRIDER_STUN_DISTANCE_VERT, MAX_STRIDER_STUN_DISTANCE_VERT ), mins, maxs );
- AddPointToBounds( Vector( MAX_STRIDER_STUN_DISTANCE_VERT, MAX_STRIDER_STUN_DISTANCE_VERT, MAX_STRIDER_STUN_DISTANCE_VERT ), mins, maxs );
-
- // FIXME: It's probably much faster to simply iterate over the striders in the map, rather than any entity in the radius - jdw
-
- // Find any striders in range of us
- int numTargets = UTIL_EntitiesInBox( pEnts, ARRAYSIZE( pEnts ), GetAbsOrigin()+mins, GetAbsOrigin()+maxs, FL_NPC );
- float targetDistHorz, targetDistVert;
-
- for ( int i = 0; i < numTargets; i++ )
- {
- // Only affect striders
- if ( FClassnameIs( pEnts[i], "npc_strider" ) == false )
- continue;
-
- // We categorize our spatial relation to the strider in horizontal and vertical terms, so that we can specify both parameters separately
- targetDistHorz = UTIL_DistApprox2D( pEnts[i]->GetAbsOrigin(), GetAbsOrigin() );
- targetDistVert = fabs( pEnts[i]->GetAbsOrigin()[2] - GetAbsOrigin()[2] );
-
- if ( targetDistHorz < MAX_STRIDER_KILL_DISTANCE_HORZ && targetDistHorz < MAX_STRIDER_KILL_DISTANCE_VERT )
- {
- // Kill the strider
- float fracDamage = ( pEnts[i]->GetMaxHealth() / hopwire_strider_hits.GetFloat() ) + 1.0f;
- CTakeDamageInfo killInfo( this, this, fracDamage, DMG_GENERIC );
- Vector killDir = pEnts[i]->GetAbsOrigin() - GetAbsOrigin();
- VectorNormalize( killDir );
-
- killInfo.SetDamageForce( killDir * -1000.0f );
- killInfo.SetDamagePosition( GetAbsOrigin() );
-
- pEnts[i]->TakeDamage( killInfo );
- }
- else if ( targetDistHorz < MAX_STRIDER_STUN_DISTANCE_HORZ && targetDistHorz < MAX_STRIDER_STUN_DISTANCE_VERT )
- {
- // Stun the strider
- CTakeDamageInfo killInfo( this, this, 200.0f, DMG_GENERIC );
- pEnts[i]->TakeDamage( killInfo );
- }
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CGrenadeHopwire::EndThink( void )
-{
- if ( hopwire_vortex.GetBool() )
- {
- EntityMessageBegin( this, true );
- WRITE_BYTE( 1 );
- MessageEnd();
- }
-
- SetThink( &CBaseEntity::SUB_Remove );
- SetNextThink( gpGlobals->curtime + 1.0f );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CGrenadeHopwire::CombatThink( void )
-{
- // Stop the grenade from moving
- AddEFlags( EF_NODRAW );
- AddFlag( FSOLID_NOT_SOLID );
- VPhysicsDestroyObject();
- SetAbsVelocity( vec3_origin );
- SetMoveType( MOVETYPE_NONE );
-
- // Do special behaviors if there are any striders in the area
- KillStriders();
-
- // FIXME: Replace
- //EmitSound("NPC_Strider.Shoot");
- //EmitSound("d3_citadel.weapon_zapper_beam_loop2");
-
- // Quick screen flash
- CBasePlayer *pPlayer = ToBasePlayer( GetThrower() );
- color32 white = { 255,255,255,255 };
- UTIL_ScreenFade( pPlayer, white, 0.2f, 0.0f, FFADE_IN );
-
- // Create the vortex controller to pull entities towards us
- if ( hopwire_vortex.GetBool() )
- {
- m_hVortexController = CGravityVortexController::Create( GetAbsOrigin(), 512, 150, 3.0f );
-
- // Start our client-side effect
- EntityMessageBegin( this, true );
- WRITE_BYTE( 0 );
- MessageEnd();
-
- // Begin to stop in two seconds
- SetThink( &CGrenadeHopwire::EndThink );
- SetNextThink( gpGlobals->curtime + 2.0f );
- }
- else
- {
- // Remove us immediately
- SetThink( &CBaseEntity::SUB_Remove );
- SetNextThink( gpGlobals->curtime + 0.1f );
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CGrenadeHopwire::SetVelocity( const Vector &velocity, const AngularImpulse &angVelocity )
-{
- IPhysicsObject *pPhysicsObject = VPhysicsGetObject();
-
- if ( pPhysicsObject != NULL )
- {
- pPhysicsObject->AddVelocity( &velocity, &angVelocity );
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Hop off the ground to start deployment
-//-----------------------------------------------------------------------------
-void CGrenadeHopwire::Detonate( void )
-{
- SetModel( GRENADE_MODEL_OPEN );
-
- AngularImpulse hopAngle = RandomAngularImpulse( -300, 300 );
-
- //Find out how tall the ceiling is and always try to hop halfway
- trace_t tr;
- UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() + Vector( 0, 0, MAX_HOP_HEIGHT*2 ), MASK_SOLID, this, COLLISION_GROUP_NONE, &tr );
-
- // Jump half the height to the found ceiling
- float hopHeight = MIN( MAX_HOP_HEIGHT, (MAX_HOP_HEIGHT*tr.fraction) );
-
- //Add upwards velocity for the "hop"
- Vector hopVel( 0.0f, 0.0f, hopHeight );
- SetVelocity( hopVel, hopAngle );
-
- // Get the time until the apex of the hop
- float apexTime = sqrt( hopHeight / GetCurrentGravity() );
-
- // Explode at the apex
- SetThink( &CGrenadeHopwire::CombatThink );
- SetNextThink( gpGlobals->curtime + apexTime);
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-CBaseGrenade *HopWire_Create( const Vector &position, const QAngle &angles, const Vector &velocity, const AngularImpulse &angVelocity, CBaseEntity *pOwner, float timer )
-{
- CGrenadeHopwire *pGrenade = (CGrenadeHopwire *) CBaseEntity::Create( "npc_grenade_hopwire", position, angles, pOwner );
-
- // Only set ourselves to detonate on a timer if we're not a trap hopwire
- if ( hopwire_trap.GetBool() == false )
- {
- pGrenade->SetTimer( timer );
- }
-
- pGrenade->SetVelocity( velocity, angVelocity );
- pGrenade->SetThrower( ToBaseCombatCharacter( pOwner ) );
-
- return pGrenade;
-}
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Gravity well device
+//
+//=====================================================================================//
+
+#include "cbase.h"
+#include "grenade_hopwire.h"
+#include "rope.h"
+#include "rope_shared.h"
+#include "beam_shared.h"
+#include "physics.h"
+#include "physics_saverestore.h"
+#include "explode.h"
+#include "physics_prop_ragdoll.h"
+#include "movevars_shared.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+ConVar hopwire_vortex( "hopwire_vortex", "0" );
+ConVar hopwire_trap( "hopwire_trap", "1" );
+ConVar hopwire_strider_kill_dist_h( "hopwire_strider_kill_dist_h", "300" );
+ConVar hopwire_strider_kill_dist_v( "hopwire_strider_kill_dist_v", "256" );
+ConVar hopwire_strider_hits( "hopwire_strider_hits", "1" );
+ConVar hopwire_hopheight( "hopwire_hopheight", "400" );
+
+ConVar g_debug_hopwire( "g_debug_hopwire", "0" );
+
+#define DENSE_BALL_MODEL "models/props_junk/metal_paintcan001b.mdl"
+
+#define MAX_HOP_HEIGHT (hopwire_hopheight.GetFloat()) // Maximum amount the grenade will "hop" upwards when detonated
+
+class CGravityVortexController : public CBaseEntity
+{
+ DECLARE_CLASS( CGravityVortexController, CBaseEntity );
+ DECLARE_DATADESC();
+
+public:
+
+ CGravityVortexController( void ) : m_flEndTime( 0.0f ), m_flRadius( 256 ), m_flStrength( 256 ), m_flMass( 0.0f ) {}
+ float GetConsumedMass( void ) const;
+
+ static CGravityVortexController *Create( const Vector &origin, float radius, float strength, float duration );
+
+private:
+
+ void ConsumeEntity( CBaseEntity *pEnt );
+ void PullPlayersInRange( void );
+ bool KillNPCInRange( CBaseEntity *pVictim, IPhysicsObject **pPhysObj );
+ void CreateDenseBall( void );
+ void PullThink( void );
+ void StartPull( const Vector &origin, float radius, float strength, float duration );
+
+ float m_flMass; // Mass consumed by the vortex
+ float m_flEndTime; // Time when the vortex will stop functioning
+ float m_flRadius; // Area of effect for the vortex
+ float m_flStrength; // Pulling strength of the vortex
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns the amount of mass consumed by the vortex
+//-----------------------------------------------------------------------------
+float CGravityVortexController::GetConsumedMass( void ) const
+{
+ return m_flMass;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Adds the entity's mass to the aggregate mass consumed
+//-----------------------------------------------------------------------------
+void CGravityVortexController::ConsumeEntity( CBaseEntity *pEnt )
+{
+ // Get our base physics object
+ IPhysicsObject *pPhysObject = pEnt->VPhysicsGetObject();
+ if ( pPhysObject == NULL )
+ return;
+
+ // Ragdolls need to report the sum of all their parts
+ CRagdollProp *pRagdoll = dynamic_cast< CRagdollProp* >( pEnt );
+ if ( pRagdoll != NULL )
+ {
+ // Find the aggregate mass of the whole ragdoll
+ ragdoll_t *pRagdollPhys = pRagdoll->GetRagdoll();
+ for ( int j = 0; j < pRagdollPhys->listCount; ++j )
+ {
+ m_flMass += pRagdollPhys->list[j].pObject->GetMass();
+ }
+ }
+ else
+ {
+ // Otherwise we just take the normal mass
+ m_flMass += pPhysObject->GetMass();
+ }
+
+ // Destroy the entity
+ UTIL_Remove( pEnt );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Causes players within the radius to be sucked in
+//-----------------------------------------------------------------------------
+void CGravityVortexController::PullPlayersInRange( void )
+{
+ CBasePlayer *pPlayer = UTIL_GetLocalPlayer();
+
+ Vector vecForce = GetAbsOrigin() - pPlayer->WorldSpaceCenter();
+ float dist = VectorNormalize( vecForce );
+
+ // FIXME: Need a more deterministic method here
+ if ( dist < 128.0f )
+ {
+ // Kill the player (with falling death sound and effects)
+ CTakeDamageInfo deathInfo( this, this, GetAbsOrigin(), GetAbsOrigin(), 200, DMG_FALL );
+ pPlayer->TakeDamage( deathInfo );
+
+ if ( pPlayer->IsAlive() == false )
+ {
+ color32 black = { 0, 0, 0, 255 };
+ UTIL_ScreenFade( pPlayer, black, 0.1f, 0.0f, (FFADE_OUT|FFADE_STAYOUT) );
+ return;
+ }
+ }
+
+ // Must be within the radius
+ if ( dist > m_flRadius )
+ return;
+
+ float mass = pPlayer->VPhysicsGetObject()->GetMass();
+ float playerForce = m_flStrength * 0.05f;
+
+ // Find the pull force
+ // NOTE: We might want to make this non-linear to give more of a "grace distance"
+ vecForce *= ( 1.0f - ( dist / m_flRadius ) ) * playerForce * mass;
+ vecForce[2] *= 0.025f;
+
+ pPlayer->SetBaseVelocity( vecForce );
+ pPlayer->AddFlag( FL_BASEVELOCITY );
+
+ // Make sure the player moves
+ if ( vecForce.z > 0 && ( pPlayer->GetFlags() & FL_ONGROUND) )
+ {
+ pPlayer->SetGroundEntity( NULL );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Attempts to kill an NPC if it's within range and other criteria
+// Input : *pVictim - NPC to assess
+// **pPhysObj - pointer to the ragdoll created if the NPC is killed
+// Output : bool - whether or not the NPC was killed and the returned pointer is valid
+//-----------------------------------------------------------------------------
+bool CGravityVortexController::KillNPCInRange( CBaseEntity *pVictim, IPhysicsObject **pPhysObj )
+{
+ CBaseCombatCharacter *pBCC = pVictim->MyCombatCharacterPointer();
+
+ // See if we can ragdoll
+ if ( pBCC != NULL && pBCC->CanBecomeRagdoll() )
+ {
+ // Don't bother with striders
+ if ( FClassnameIs( pBCC, "npc_strider" ) )
+ return false;
+
+ // TODO: Make this an interaction between the NPC and the vortex
+
+ // Become ragdoll
+ CTakeDamageInfo info( this, this, 1.0f, DMG_GENERIC );
+ CBaseEntity *pRagdoll = CreateServerRagdoll( pBCC, 0, info, COLLISION_GROUP_INTERACTIVE_DEBRIS, true );
+ pRagdoll->SetCollisionBounds( pVictim->CollisionProp()->OBBMins(), pVictim->CollisionProp()->OBBMaxs() );
+
+ // Necessary to cause it to do the appropriate death cleanup
+ CTakeDamageInfo ragdollInfo( this, this, 10000.0, DMG_GENERIC | DMG_REMOVENORAGDOLL );
+ pVictim->TakeDamage( ragdollInfo );
+
+ // Return the pointer to the ragdoll
+ *pPhysObj = pRagdoll->VPhysicsGetObject();
+ return true;
+ }
+
+ // Wasn't able to ragdoll this target
+ *pPhysObj = NULL;
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Creates a dense ball with a mass equal to the aggregate mass consumed by the vortex
+//-----------------------------------------------------------------------------
+void CGravityVortexController::CreateDenseBall( void )
+{
+ CBaseEntity *pBall = CreateEntityByName( "prop_physics" );
+
+ pBall->SetModel( DENSE_BALL_MODEL );
+ pBall->SetAbsOrigin( GetAbsOrigin() );
+ pBall->Spawn();
+
+ IPhysicsObject *pObj = pBall->VPhysicsGetObject();
+ if ( pObj != NULL )
+ {
+ pObj->SetMass( GetConsumedMass() );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Pulls physical objects towards the vortex center, killing them if they come too near
+//-----------------------------------------------------------------------------
+void CGravityVortexController::PullThink( void )
+{
+ // Pull any players close enough to us
+ PullPlayersInRange();
+
+ Vector mins, maxs;
+ mins = GetAbsOrigin() - Vector( m_flRadius, m_flRadius, m_flRadius );
+ maxs = GetAbsOrigin() + Vector( m_flRadius, m_flRadius, m_flRadius );
+
+ // Draw debug information
+ if ( g_debug_hopwire.GetBool() )
+ {
+ NDebugOverlay::Box( GetAbsOrigin(), mins - GetAbsOrigin(), maxs - GetAbsOrigin(), 0, 255, 0, 16, 4.0f );
+ }
+
+ CBaseEntity *pEnts[128];
+ int numEnts = UTIL_EntitiesInBox( pEnts, 128, mins, maxs, 0 );
+
+ for ( int i = 0; i < numEnts; i++ )
+ {
+ IPhysicsObject *pPhysObject = NULL;
+
+ // Attempt to kill and ragdoll any victims in range
+ if ( KillNPCInRange( pEnts[i], &pPhysObject ) == false )
+ {
+ // If we didn't have a valid victim, see if we can just get the vphysics object
+ pPhysObject = pEnts[i]->VPhysicsGetObject();
+ if ( pPhysObject == NULL )
+ continue;
+ }
+
+ float mass;
+
+ CRagdollProp *pRagdoll = dynamic_cast< CRagdollProp* >( pEnts[i] );
+ if ( pRagdoll != NULL )
+ {
+ ragdoll_t *pRagdollPhys = pRagdoll->GetRagdoll();
+ mass = 0.0f;
+
+ // Find the aggregate mass of the whole ragdoll
+ for ( int j = 0; j < pRagdollPhys->listCount; ++j )
+ {
+ mass += pRagdollPhys->list[j].pObject->GetMass();
+ }
+ }
+ else
+ {
+ mass = pPhysObject->GetMass();
+ }
+
+ Vector vecForce = GetAbsOrigin() - pEnts[i]->WorldSpaceCenter();
+ Vector vecForce2D = vecForce;
+ vecForce2D[2] = 0.0f;
+ float dist2D = VectorNormalize( vecForce2D );
+ float dist = VectorNormalize( vecForce );
+
+ // FIXME: Need a more deterministic method here
+ if ( dist < 48.0f )
+ {
+ ConsumeEntity( pEnts[i] );
+ continue;
+ }
+
+ // Must be within the radius
+ if ( dist > m_flRadius )
+ continue;
+
+ // Find the pull force
+ vecForce *= ( 1.0f - ( dist2D / m_flRadius ) ) * m_flStrength * mass;
+
+ if ( pEnts[i]->VPhysicsGetObject() )
+ {
+ // Pull the object in
+ pEnts[i]->VPhysicsTakeDamage( CTakeDamageInfo( this, this, vecForce, GetAbsOrigin(), m_flStrength, DMG_BLAST ) );
+ }
+ }
+
+ // Keep going if need-be
+ if ( m_flEndTime > gpGlobals->curtime )
+ {
+ SetThink( &CGravityVortexController::PullThink );
+ SetNextThink( gpGlobals->curtime + 0.1f );
+ }
+ else
+ {
+ //Msg( "Consumed %.2f kilograms\n", m_flMass );
+ //CreateDenseBall();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Starts the vortex working
+//-----------------------------------------------------------------------------
+void CGravityVortexController::StartPull( const Vector &origin, float radius, float strength, float duration )
+{
+ SetAbsOrigin( origin );
+ m_flEndTime = gpGlobals->curtime + duration;
+ m_flRadius = radius;
+ m_flStrength= strength;
+
+ SetThink( &CGravityVortexController::PullThink );
+ SetNextThink( gpGlobals->curtime + 0.1f );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Creation utility
+//-----------------------------------------------------------------------------
+CGravityVortexController *CGravityVortexController::Create( const Vector &origin, float radius, float strength, float duration )
+{
+ // Create an instance of the vortex
+ CGravityVortexController *pVortex = (CGravityVortexController *) CreateEntityByName( "vortex_controller" );
+ if ( pVortex == NULL )
+ return NULL;
+
+ // Start the vortex working
+ pVortex->StartPull( origin, radius, strength, duration );
+
+ return pVortex;
+}
+
+BEGIN_DATADESC( CGravityVortexController )
+ DEFINE_FIELD( m_flMass, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flEndTime, FIELD_TIME ),
+ DEFINE_FIELD( m_flRadius, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flStrength, FIELD_FLOAT ),
+
+ DEFINE_THINKFUNC( PullThink ),
+END_DATADESC()
+
+LINK_ENTITY_TO_CLASS( vortex_controller, CGravityVortexController );
+
+#define GRENADE_MODEL_CLOSED "models/roller.mdl"
+#define GRENADE_MODEL_OPEN "models/roller_spikes.mdl"
+
+BEGIN_DATADESC( CGrenadeHopwire )
+ DEFINE_FIELD( m_hVortexController, FIELD_EHANDLE ),
+
+ DEFINE_THINKFUNC( EndThink ),
+ DEFINE_THINKFUNC( CombatThink ),
+END_DATADESC()
+
+LINK_ENTITY_TO_CLASS( npc_grenade_hopwire, CGrenadeHopwire );
+
+IMPLEMENT_SERVERCLASS_ST( CGrenadeHopwire, DT_GrenadeHopwire )
+END_SEND_TABLE()
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CGrenadeHopwire::Spawn( void )
+{
+ Precache();
+
+ SetModel( GRENADE_MODEL_CLOSED );
+ SetCollisionGroup( COLLISION_GROUP_PROJECTILE );
+
+ CreateVPhysics();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CGrenadeHopwire::CreateVPhysics()
+{
+ // Create the object in the physics system
+ VPhysicsInitNormal( SOLID_BBOX, 0, false );
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CGrenadeHopwire::Precache( void )
+{
+ // FIXME: Replace
+ //PrecacheSound("NPC_Strider.Shoot");
+ //PrecacheSound("d3_citadel.weapon_zapper_beam_loop2");
+
+ PrecacheModel( GRENADE_MODEL_OPEN );
+ PrecacheModel( GRENADE_MODEL_CLOSED );
+
+ PrecacheModel( DENSE_BALL_MODEL );
+
+ BaseClass::Precache();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : timer -
+//-----------------------------------------------------------------------------
+void CGrenadeHopwire::SetTimer( float timer )
+{
+ SetThink( &CBaseGrenade::PreDetonate );
+ SetNextThink( gpGlobals->curtime + timer );
+}
+
+#define MAX_STRIDER_KILL_DISTANCE_HORZ (hopwire_strider_kill_dist_h.GetFloat()) // Distance a Strider will be killed if within
+#define MAX_STRIDER_KILL_DISTANCE_VERT (hopwire_strider_kill_dist_v.GetFloat()) // Distance a Strider will be killed if within
+
+#define MAX_STRIDER_STUN_DISTANCE_HORZ (MAX_STRIDER_KILL_DISTANCE_HORZ*2) // Distance a Strider will be stunned if within
+#define MAX_STRIDER_STUN_DISTANCE_VERT (MAX_STRIDER_KILL_DISTANCE_VERT*2) // Distance a Strider will be stunned if within
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CGrenadeHopwire::KillStriders( void )
+{
+ CBaseEntity *pEnts[128];
+ Vector mins, maxs;
+
+ ClearBounds( mins, maxs );
+ AddPointToBounds( -Vector( MAX_STRIDER_STUN_DISTANCE_HORZ, MAX_STRIDER_STUN_DISTANCE_HORZ, MAX_STRIDER_STUN_DISTANCE_HORZ ), mins, maxs );
+ AddPointToBounds( Vector( MAX_STRIDER_STUN_DISTANCE_HORZ, MAX_STRIDER_STUN_DISTANCE_HORZ, MAX_STRIDER_STUN_DISTANCE_HORZ ), mins, maxs );
+ AddPointToBounds( -Vector( MAX_STRIDER_STUN_DISTANCE_VERT, MAX_STRIDER_STUN_DISTANCE_VERT, MAX_STRIDER_STUN_DISTANCE_VERT ), mins, maxs );
+ AddPointToBounds( Vector( MAX_STRIDER_STUN_DISTANCE_VERT, MAX_STRIDER_STUN_DISTANCE_VERT, MAX_STRIDER_STUN_DISTANCE_VERT ), mins, maxs );
+
+ // FIXME: It's probably much faster to simply iterate over the striders in the map, rather than any entity in the radius - jdw
+
+ // Find any striders in range of us
+ int numTargets = UTIL_EntitiesInBox( pEnts, ARRAYSIZE( pEnts ), GetAbsOrigin()+mins, GetAbsOrigin()+maxs, FL_NPC );
+ float targetDistHorz, targetDistVert;
+
+ for ( int i = 0; i < numTargets; i++ )
+ {
+ // Only affect striders
+ if ( FClassnameIs( pEnts[i], "npc_strider" ) == false )
+ continue;
+
+ // We categorize our spatial relation to the strider in horizontal and vertical terms, so that we can specify both parameters separately
+ targetDistHorz = UTIL_DistApprox2D( pEnts[i]->GetAbsOrigin(), GetAbsOrigin() );
+ targetDistVert = fabs( pEnts[i]->GetAbsOrigin()[2] - GetAbsOrigin()[2] );
+
+ if ( targetDistHorz < MAX_STRIDER_KILL_DISTANCE_HORZ && targetDistHorz < MAX_STRIDER_KILL_DISTANCE_VERT )
+ {
+ // Kill the strider
+ float fracDamage = ( pEnts[i]->GetMaxHealth() / hopwire_strider_hits.GetFloat() ) + 1.0f;
+ CTakeDamageInfo killInfo( this, this, fracDamage, DMG_GENERIC );
+ Vector killDir = pEnts[i]->GetAbsOrigin() - GetAbsOrigin();
+ VectorNormalize( killDir );
+
+ killInfo.SetDamageForce( killDir * -1000.0f );
+ killInfo.SetDamagePosition( GetAbsOrigin() );
+
+ pEnts[i]->TakeDamage( killInfo );
+ }
+ else if ( targetDistHorz < MAX_STRIDER_STUN_DISTANCE_HORZ && targetDistHorz < MAX_STRIDER_STUN_DISTANCE_VERT )
+ {
+ // Stun the strider
+ CTakeDamageInfo killInfo( this, this, 200.0f, DMG_GENERIC );
+ pEnts[i]->TakeDamage( killInfo );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CGrenadeHopwire::EndThink( void )
+{
+ if ( hopwire_vortex.GetBool() )
+ {
+ EntityMessageBegin( this, true );
+ WRITE_BYTE( 1 );
+ MessageEnd();
+ }
+
+ SetThink( &CBaseEntity::SUB_Remove );
+ SetNextThink( gpGlobals->curtime + 1.0f );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CGrenadeHopwire::CombatThink( void )
+{
+ // Stop the grenade from moving
+ AddEFlags( EF_NODRAW );
+ AddFlag( FSOLID_NOT_SOLID );
+ VPhysicsDestroyObject();
+ SetAbsVelocity( vec3_origin );
+ SetMoveType( MOVETYPE_NONE );
+
+ // Do special behaviors if there are any striders in the area
+ KillStriders();
+
+ // FIXME: Replace
+ //EmitSound("NPC_Strider.Shoot");
+ //EmitSound("d3_citadel.weapon_zapper_beam_loop2");
+
+ // Quick screen flash
+ CBasePlayer *pPlayer = ToBasePlayer( GetThrower() );
+ color32 white = { 255,255,255,255 };
+ UTIL_ScreenFade( pPlayer, white, 0.2f, 0.0f, FFADE_IN );
+
+ // Create the vortex controller to pull entities towards us
+ if ( hopwire_vortex.GetBool() )
+ {
+ m_hVortexController = CGravityVortexController::Create( GetAbsOrigin(), 512, 150, 3.0f );
+
+ // Start our client-side effect
+ EntityMessageBegin( this, true );
+ WRITE_BYTE( 0 );
+ MessageEnd();
+
+ // Begin to stop in two seconds
+ SetThink( &CGrenadeHopwire::EndThink );
+ SetNextThink( gpGlobals->curtime + 2.0f );
+ }
+ else
+ {
+ // Remove us immediately
+ SetThink( &CBaseEntity::SUB_Remove );
+ SetNextThink( gpGlobals->curtime + 0.1f );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CGrenadeHopwire::SetVelocity( const Vector &velocity, const AngularImpulse &angVelocity )
+{
+ IPhysicsObject *pPhysicsObject = VPhysicsGetObject();
+
+ if ( pPhysicsObject != NULL )
+ {
+ pPhysicsObject->AddVelocity( &velocity, &angVelocity );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Hop off the ground to start deployment
+//-----------------------------------------------------------------------------
+void CGrenadeHopwire::Detonate( void )
+{
+ SetModel( GRENADE_MODEL_OPEN );
+
+ AngularImpulse hopAngle = RandomAngularImpulse( -300, 300 );
+
+ //Find out how tall the ceiling is and always try to hop halfway
+ trace_t tr;
+ UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() + Vector( 0, 0, MAX_HOP_HEIGHT*2 ), MASK_SOLID, this, COLLISION_GROUP_NONE, &tr );
+
+ // Jump half the height to the found ceiling
+ float hopHeight = MIN( MAX_HOP_HEIGHT, (MAX_HOP_HEIGHT*tr.fraction) );
+
+ //Add upwards velocity for the "hop"
+ Vector hopVel( 0.0f, 0.0f, hopHeight );
+ SetVelocity( hopVel, hopAngle );
+
+ // Get the time until the apex of the hop
+ float apexTime = sqrt( hopHeight / GetCurrentGravity() );
+
+ // Explode at the apex
+ SetThink( &CGrenadeHopwire::CombatThink );
+ SetNextThink( gpGlobals->curtime + apexTime);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CBaseGrenade *HopWire_Create( const Vector &position, const QAngle &angles, const Vector &velocity, const AngularImpulse &angVelocity, CBaseEntity *pOwner, float timer )
+{
+ CGrenadeHopwire *pGrenade = (CGrenadeHopwire *) CBaseEntity::Create( "npc_grenade_hopwire", position, angles, pOwner );
+
+ // Only set ourselves to detonate on a timer if we're not a trap hopwire
+ if ( hopwire_trap.GetBool() == false )
+ {
+ pGrenade->SetTimer( timer );
+ }
+
+ pGrenade->SetVelocity( velocity, angVelocity );
+ pGrenade->SetThrower( ToBaseCombatCharacter( pOwner ) );
+
+ return pGrenade;
+}
diff --git a/mp/src/game/server/episodic/grenade_hopwire.h b/mp/src/game/server/episodic/grenade_hopwire.h
index 5ae61ce4..c8cd71e9 100644
--- a/mp/src/game/server/episodic/grenade_hopwire.h
+++ b/mp/src/game/server/episodic/grenade_hopwire.h
@@ -1,46 +1,46 @@
-//========= Copyright Valve Corporation, All rights reserved. ============//
-//
-// Purpose:
-//
-//=============================================================================//
-
-#ifndef GRENADE_HOPWIRE_H
-#define GRENADE_HOPWIRE_H
-#ifdef _WIN32
-#pragma once
-#endif
-
-#include "basegrenade_shared.h"
-#include "Sprite.h"
-
-extern ConVar hopwire_trap;
-
-class CGravityVortexController;
-
-class CGrenadeHopwire : public CBaseGrenade
-{
- DECLARE_CLASS( CGrenadeHopwire, CBaseGrenade );
- DECLARE_DATADESC();
- DECLARE_SERVERCLASS();
-
-public:
- void Spawn( void );
- void Precache( void );
- bool CreateVPhysics( void );
- void SetTimer( float timer );
- void SetVelocity( const Vector &velocity, const AngularImpulse &angVelocity );
- void Detonate( void );
-
- void EndThink( void ); // Last think before going away
- void CombatThink( void ); // Makes the main explosion go off
-
-protected:
-
- void KillStriders( void );
-
- CHandle<CGravityVortexController> m_hVortexController;
-};
-
-extern CBaseGrenade *HopWire_Create( const Vector &position, const QAngle &angles, const Vector &velocity, const AngularImpulse &angVelocity, CBaseEntity *pOwner, float timer );
-
-#endif // GRENADE_HOPWIRE_H
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#ifndef GRENADE_HOPWIRE_H
+#define GRENADE_HOPWIRE_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "basegrenade_shared.h"
+#include "Sprite.h"
+
+extern ConVar hopwire_trap;
+
+class CGravityVortexController;
+
+class CGrenadeHopwire : public CBaseGrenade
+{
+ DECLARE_CLASS( CGrenadeHopwire, CBaseGrenade );
+ DECLARE_DATADESC();
+ DECLARE_SERVERCLASS();
+
+public:
+ void Spawn( void );
+ void Precache( void );
+ bool CreateVPhysics( void );
+ void SetTimer( float timer );
+ void SetVelocity( const Vector &velocity, const AngularImpulse &angVelocity );
+ void Detonate( void );
+
+ void EndThink( void ); // Last think before going away
+ void CombatThink( void ); // Makes the main explosion go off
+
+protected:
+
+ void KillStriders( void );
+
+ CHandle<CGravityVortexController> m_hVortexController;
+};
+
+extern CBaseGrenade *HopWire_Create( const Vector &position, const QAngle &angles, const Vector &velocity, const AngularImpulse &angVelocity, CBaseEntity *pOwner, float timer );
+
+#endif // GRENADE_HOPWIRE_H
diff --git a/mp/src/game/server/episodic/npc_advisor.cpp b/mp/src/game/server/episodic/npc_advisor.cpp
index b70ab7ea..1b7fb23f 100644
--- a/mp/src/game/server/episodic/npc_advisor.cpp
+++ b/mp/src/game/server/episodic/npc_advisor.cpp
@@ -1,2051 +1,2051 @@
-//========= Copyright Valve Corporation, All rights reserved. ============//
-//
-// Advisors. Large sluglike aliens with creepy psychic powers!
-//
-//=============================================================================
-
-#include "cbase.h"
-#include "game.h"
-#include "ai_basenpc.h"
-#include "ai_schedule.h"
-#include "ai_hull.h"
-#include "ai_hint.h"
-#include "ai_motor.h"
-#include "ai_navigator.h"
-#include "beam_shared.h"
-#include "hl2_shareddefs.h"
-#include "ai_route.h"
-#include "npcevent.h"
-#include "gib.h"
-#include "ai_interactions.h"
-#include "ndebugoverlay.h"
-#include "physics_saverestore.h"
-#include "saverestore_utlvector.h"
-#include "soundent.h"
-#include "vstdlib/random.h"
-#include "engine/IEngineSound.h"
-#include "movevars_shared.h"
-#include "particle_parse.h"
-#include "weapon_physcannon.h"
-// #include "mathlib/noise.h"
-
-// this file contains the definitions for the message ID constants (eg ADVISOR_MSG_START_BEAM etc)
-#include "npc_advisor_shared.h"
-
-// memdbgon must be the last include file in a .cpp file!!!
-#include "tier0/memdbgon.h"
-
-//
-// Custom activities.
-//
-
-//
-// Skill settings.
-//
-ConVar sk_advisor_health( "sk_advisor_health", "0" );
-ConVar advisor_use_impact_table("advisor_use_impact_table","1",FCVAR_NONE,"If true, advisor will use her custom impact damage table.");
-
-#if NPC_ADVISOR_HAS_BEHAVIOR
-ConVar advisor_throw_velocity( "advisor_throw_velocity", "1100" );
-ConVar advisor_throw_rate( "advisor_throw_rate", "4" ); // Throw an object every 4 seconds.
-ConVar advisor_throw_warn_time( "advisor_throw_warn_time", "1.0" ); // Warn players one second before throwing an object.
-ConVar advisor_throw_lead_prefetch_time ( "advisor_throw_lead_prefetch_time", "0.66", FCVAR_NONE, "Save off the player's velocity this many seconds before throwing.");
-ConVar advisor_throw_stage_distance("advisor_throw_stage_distance","180.0",FCVAR_NONE,"Advisor will try to hold an object this far in front of him just before throwing it at you. Small values will clobber the shield and be very bad.");
-// ConVar advisor_staging_num("advisor_staging_num","1",FCVAR_NONE,"Advisor will queue up this many objects to throw at Gordon.");
-ConVar advisor_throw_clearout_vel("advisor_throw_clearout_vel","200",FCVAR_NONE,"TEMP: velocity with which advisor clears things out of a throwable's way");
-// ConVar advisor_staging_duration("
-
-// how long it will take an object to get hauled to the staging point
-#define STAGING_OBJECT_FALLOFF_TIME 0.15f
-#endif
-
-
-
-//
-// Spawnflags.
-//
-
-//
-// Animation events.
-//
-
-
-#if NPC_ADVISOR_HAS_BEHAVIOR
-//
-// Custom schedules.
-//
-enum
-{
- SCHED_ADVISOR_COMBAT = LAST_SHARED_SCHEDULE,
- SCHED_ADVISOR_IDLE_STAND,
- SCHED_ADVISOR_TOSS_PLAYER
-};
-
-
-//
-// Custom tasks.
-//
-enum
-{
- TASK_ADVISOR_FIND_OBJECTS = LAST_SHARED_TASK,
- TASK_ADVISOR_LEVITATE_OBJECTS,
- TASK_ADVISOR_STAGE_OBJECTS,
- TASK_ADVISOR_BARRAGE_OBJECTS,
-
- TASK_ADVISOR_PIN_PLAYER,
-};
-
-//
-// Custom conditions.
-//
-enum
-{
- COND_ADVISOR_PHASE_INTERRUPT = LAST_SHARED_CONDITION,
-};
-#endif
-
-class CNPC_Advisor;
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-class CAdvisorLevitate : public IMotionEvent
-{
- DECLARE_SIMPLE_DATADESC();
-
-public:
-
- // in the absence of goal entities, we float up before throwing and down after
- inline bool OldStyle( void )
- {
- return !(m_vecGoalPos1.IsValid() && m_vecGoalPos2.IsValid());
- }
-
- virtual simresult_e Simulate( IPhysicsMotionController *pController, IPhysicsObject *pObject, float deltaTime, Vector &linear, AngularImpulse &angular );
-
- EHANDLE m_Advisor; ///< handle to the advisor.
-
- Vector m_vecGoalPos1;
- Vector m_vecGoalPos2;
-
- float m_flFloat;
-};
-
-BEGIN_SIMPLE_DATADESC( CAdvisorLevitate )
- DEFINE_FIELD( m_flFloat, FIELD_FLOAT ),
- DEFINE_FIELD( m_vecGoalPos1, FIELD_POSITION_VECTOR ),
- DEFINE_FIELD( m_vecGoalPos2, FIELD_POSITION_VECTOR ),
- DEFINE_FIELD( m_Advisor, FIELD_EHANDLE ),
-END_DATADESC()
-
-
-
-//-----------------------------------------------------------------------------
-// The advisor class.
-//-----------------------------------------------------------------------------
-class CNPC_Advisor : public CAI_BaseNPC
-{
- DECLARE_CLASS( CNPC_Advisor, CAI_BaseNPC );
-
-#if NPC_ADVISOR_HAS_BEHAVIOR
- DECLARE_SERVERCLASS();
-#endif
-
-public:
-
- //
- // CBaseEntity:
- //
- virtual void Activate();
- virtual void Spawn();
- virtual void Precache();
- virtual void OnRestore();
- virtual void UpdateOnRemove();
-
- virtual int DrawDebugTextOverlays();
-
- //
- // CAI_BaseNPC:
- //
- virtual float MaxYawSpeed() { return 120.0f; }
-
- virtual Class_T Classify();
-
-#if NPC_ADVISOR_HAS_BEHAVIOR
- virtual int GetSoundInterests();
- virtual int SelectSchedule();
- virtual void StartTask( const Task_t *pTask );
- virtual void RunTask( const Task_t *pTask );
- virtual void OnScheduleChange( void );
-#endif
-
- virtual void PainSound( const CTakeDamageInfo &info );
- virtual void DeathSound( const CTakeDamageInfo &info );
- virtual void IdleSound();
- virtual void AlertSound();
-
-#if NPC_ADVISOR_HAS_BEHAVIOR
- virtual bool QueryHearSound( CSound *pSound );
- virtual void GatherConditions( void );
-
- /// true iff I recently threw the given object (not so fast)
- bool DidThrow(const CBaseEntity *pEnt);
-#else
- inline bool DidThrow(const CBaseEntity *pEnt) { return false; }
-#endif
-
- virtual bool IsHeavyDamage( const CTakeDamageInfo &info );
- virtual int OnTakeDamage( const CTakeDamageInfo &info );
-
- virtual const impactdamagetable_t &GetPhysicsImpactDamageTable( void );
- COutputInt m_OnHealthIsNow;
-
-#if NPC_ADVISOR_HAS_BEHAVIOR
-
- DEFINE_CUSTOM_AI;
-
- void InputSetThrowRate( inputdata_t &inputdata );
- void InputWrenchImmediate( inputdata_t &inputdata ); ///< immediately wrench an object into the air
- void InputSetStagingNum( inputdata_t &inputdata );
- void InputPinPlayer( inputdata_t &inputdata );
- void InputTurnBeamOn( inputdata_t &inputdata );
- void InputTurnBeamOff( inputdata_t &inputdata );
- void InputElightOn( inputdata_t &inputdata );
- void InputElightOff( inputdata_t &inputdata );
-
- COutputEvent m_OnPickingThrowable, m_OnThrowWarn, m_OnThrow;
-
- enum { kMaxThrownObjectsTracked = 4 };
-#endif
-
- DECLARE_DATADESC();
-
-protected:
-
-#if NPC_ADVISOR_HAS_BEHAVIOR
- Vector GetThrowFromPos( CBaseEntity *pEnt ); ///< Get the position in which we shall hold an object prior to throwing it
-#endif
-
- bool CanLevitateEntity( CBaseEntity *pEntity, int minMass, int maxMass );
- void StartLevitatingObjects( void );
-
-
-#if NPC_ADVISOR_HAS_BEHAVIOR
- // void PurgeThrownObjects(); ///< clean out the recently thrown objects array
- void AddToThrownObjects(CBaseEntity *pEnt); ///< add to the recently thrown objects array
-
- void HurlObjectAtPlayer( CBaseEntity *pEnt, const Vector &leadVel );
- void PullObjectToStaging( CBaseEntity *pEnt, const Vector &stagingPos );
- CBaseEntity *ThrowObjectPrepare( void );
-
- CBaseEntity *PickThrowable( bool bRequireInView ); ///< choose an object to throw at the player (so it can get stuffed in the handle array)
-
- /// push everything out of the way between an object I'm about to throw and the player.
- void PreHurlClearTheWay( CBaseEntity *pThrowable, const Vector &toPos );
-#endif
-
- CUtlVector<EHANDLE> m_physicsObjects;
- IPhysicsMotionController *m_pLevitateController;
- CAdvisorLevitate m_levitateCallback;
-
- EHANDLE m_hLevitateGoal1;
- EHANDLE m_hLevitateGoal2;
- EHANDLE m_hLevitationArea;
-
-#if NPC_ADVISOR_HAS_BEHAVIOR
- // EHANDLE m_hThrowEnt;
- CUtlVector<EHANDLE> m_hvStagedEnts;
- CUtlVector<EHANDLE> m_hvStagingPositions;
- // todo: write accessor functions for m_hvStagedEnts so that it doesn't have members added and removed willy nilly throughout
- // code (will make the networking below more reliable)
-
- void Write_BeamOn( CBaseEntity *pEnt ); ///< write a message turning a beam on
- void Write_BeamOff( CBaseEntity *pEnt ); ///< write a message turning a beam off
- void Write_AllBeamsOff( void ); ///< tell client to kill all beams
-
- // for the pin-the-player-to-something behavior
- EHANDLE m_hPlayerPinPos;
- float m_playerPinFailsafeTime;
-
- // keep track of up to four objects after we have thrown them, to prevent oscillation or levitation of recently thrown ammo.
- EHANDLE m_haRecentlyThrownObjects[kMaxThrownObjectsTracked];
- float m_flaRecentlyThrownObjectTimes[kMaxThrownObjectsTracked];
-#endif
-
- string_t m_iszLevitateGoal1;
- string_t m_iszLevitateGoal2;
- string_t m_iszLevitationArea;
-
-
-#if NPC_ADVISOR_HAS_BEHAVIOR
- string_t m_iszStagingEntities;
- string_t m_iszPriorityEntityGroupName;
-
- float m_flStagingEnd;
- float m_flThrowPhysicsTime;
- float m_flLastThrowTime;
- float m_flLastPlayerAttackTime; ///< last time the player attacked something.
-
- int m_iStagingNum; ///< number of objects advisor stages at once
- bool m_bWasScripting;
-
- // unsigned char m_pickFailures; // the number of times we have tried to pick a throwable and failed
-
- Vector m_vSavedLeadVel; ///< save off player velocity for leading a bit before actually pelting them.
-#endif
-};
-
-
-LINK_ENTITY_TO_CLASS( npc_advisor, CNPC_Advisor );
-
-BEGIN_DATADESC( CNPC_Advisor )
-
- DEFINE_KEYFIELD( m_iszLevitateGoal1, FIELD_STRING, "levitategoal_bottom" ),
- DEFINE_KEYFIELD( m_iszLevitateGoal2, FIELD_STRING, "levitategoal_top" ),
- DEFINE_KEYFIELD( m_iszLevitationArea, FIELD_STRING, "levitationarea"), ///< we will float all the objects in this volume
-
- DEFINE_PHYSPTR( m_pLevitateController ),
- DEFINE_EMBEDDED( m_levitateCallback ),
- DEFINE_UTLVECTOR( m_physicsObjects, FIELD_EHANDLE ),
-
- DEFINE_FIELD( m_hLevitateGoal1, FIELD_EHANDLE ),
- DEFINE_FIELD( m_hLevitateGoal2, FIELD_EHANDLE ),
- DEFINE_FIELD( m_hLevitationArea, FIELD_EHANDLE ),
-
-#if NPC_ADVISOR_HAS_BEHAVIOR
- DEFINE_KEYFIELD( m_iszStagingEntities, FIELD_STRING, "staging_ent_names"), ///< entities named this constitute the positions to which we stage objects to be thrown
- DEFINE_KEYFIELD( m_iszPriorityEntityGroupName, FIELD_STRING, "priority_grab_name"),
-
- DEFINE_UTLVECTOR( m_hvStagedEnts, FIELD_EHANDLE ),
- DEFINE_UTLVECTOR( m_hvStagingPositions, FIELD_EHANDLE ),
- DEFINE_ARRAY( m_haRecentlyThrownObjects, FIELD_EHANDLE, CNPC_Advisor::kMaxThrownObjectsTracked ),
- DEFINE_ARRAY( m_flaRecentlyThrownObjectTimes, FIELD_TIME, CNPC_Advisor::kMaxThrownObjectsTracked ),
-
- DEFINE_FIELD( m_hPlayerPinPos, FIELD_EHANDLE ),
- DEFINE_FIELD( m_playerPinFailsafeTime, FIELD_TIME ),
-
- // DEFINE_FIELD( m_hThrowEnt, FIELD_EHANDLE ),
- DEFINE_FIELD( m_flThrowPhysicsTime, FIELD_TIME ),
- DEFINE_FIELD( m_flLastPlayerAttackTime, FIELD_TIME ),
- DEFINE_FIELD( m_flStagingEnd, FIELD_TIME ),
- DEFINE_FIELD( m_iStagingNum, FIELD_INTEGER ),
- DEFINE_FIELD( m_bWasScripting, FIELD_BOOLEAN ),
-
- DEFINE_FIELD( m_flLastThrowTime, FIELD_TIME ),
- DEFINE_FIELD( m_vSavedLeadVel, FIELD_VECTOR ),
-
- DEFINE_OUTPUT( m_OnPickingThrowable, "OnPickingThrowable" ),
- DEFINE_OUTPUT( m_OnThrowWarn, "OnThrowWarn" ),
- DEFINE_OUTPUT( m_OnThrow, "OnThrow" ),
- DEFINE_OUTPUT( m_OnHealthIsNow, "OnHealthIsNow" ),
-
- DEFINE_INPUTFUNC( FIELD_FLOAT, "SetThrowRate", InputSetThrowRate ),
- DEFINE_INPUTFUNC( FIELD_STRING, "WrenchImmediate", InputWrenchImmediate ),
- DEFINE_INPUTFUNC( FIELD_INTEGER, "SetStagingNum", InputSetStagingNum),
- DEFINE_INPUTFUNC( FIELD_STRING, "PinPlayer", InputPinPlayer ),
- DEFINE_INPUTFUNC( FIELD_STRING, "BeamOn", InputTurnBeamOn ),
- DEFINE_INPUTFUNC( FIELD_STRING, "BeamOff", InputTurnBeamOff ),
- DEFINE_INPUTFUNC( FIELD_STRING, "ElightOn", InputElightOn ),
- DEFINE_INPUTFUNC( FIELD_STRING, "ElightOff", InputElightOff ),
-#endif
-
-END_DATADESC()
-
-
-
-#if NPC_ADVISOR_HAS_BEHAVIOR
-IMPLEMENT_SERVERCLASS_ST(CNPC_Advisor, DT_NPC_Advisor)
-
-END_SEND_TABLE()
-#endif
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Advisor::Spawn()
-{
- BaseClass::Spawn();
-
-#ifdef _XBOX
- // Always fade the corpse
- AddSpawnFlags( SF_NPC_FADE_CORPSE );
-#endif // _XBOX
-
- Precache();
-
- SetModel( STRING( GetModelName() ) );
-
- m_iHealth = sk_advisor_health.GetFloat();
- m_takedamage = DAMAGE_NO;
-
- SetHullType( HULL_LARGE_CENTERED );
- SetHullSizeNormal();
-
- SetSolid( SOLID_BBOX );
- // AddSolidFlags( FSOLID_NOT_SOLID );
-
- SetMoveType( MOVETYPE_FLY );
-
- m_flFieldOfView = VIEW_FIELD_FULL;
- SetViewOffset( Vector( 0, 0, 80 ) ); // Position of the eyes relative to NPC's origin.
-
- SetBloodColor( BLOOD_COLOR_GREEN );
- m_NPCState = NPC_STATE_NONE;
-
- CapabilitiesClear();
-
- NPCInit();
-
- SetGoalEnt( NULL );
-
- AddEFlags( EFL_NO_DISSOLVE );
-}
-
-
-#if NPC_ADVISOR_HAS_BEHAVIOR
-//-----------------------------------------------------------------------------
-// comparison function for qsort used below. Compares "StagingPriority" keyfield
-//-----------------------------------------------------------------------------
-int __cdecl AdvisorStagingComparator(const EHANDLE *pe1, const EHANDLE *pe2)
-{
- // bool ReadKeyField( const char *varName, variant_t *var );
-
- variant_t var;
- int val1 = 10, val2 = 10; // default priority is ten
-
- // read field one
- if ( pe1->Get()->ReadKeyField( "StagingPriority", &var ) )
- {
- val1 = var.Int();
- }
-
- // read field two
- if ( pe2->Get()->ReadKeyField( "StagingPriority", &var ) )
- {
- val2 = var.Int();
- }
-
- // return comparison (< 0 if pe1<pe2)
- return( val1 - val2 );
-}
-#endif
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-
-#pragma warning(push)
-#pragma warning(disable : 4706)
-
-void CNPC_Advisor::Activate()
-{
- BaseClass::Activate();
-
- m_hLevitateGoal1 = gEntList.FindEntityGeneric( NULL, STRING( m_iszLevitateGoal1 ), this );
- m_hLevitateGoal2 = gEntList.FindEntityGeneric( NULL, STRING( m_iszLevitateGoal2 ), this );
- m_hLevitationArea = gEntList.FindEntityGeneric( NULL, STRING( m_iszLevitationArea ), this );
-
- m_levitateCallback.m_Advisor = this;
-
-#if NPC_ADVISOR_HAS_BEHAVIOR
- // load the staging positions
- CBaseEntity *pEnt = NULL;
- m_hvStagingPositions.EnsureCapacity(6); // reserve six
-
- // conditional assignment: find an entity by name and save it into pEnt. Bail out when none are left.
- while ( pEnt = gEntList.FindEntityByName(pEnt,m_iszStagingEntities) )
- {
- m_hvStagingPositions.AddToTail(pEnt);
- }
-
- // sort the staging positions by their staging number.
- m_hvStagingPositions.Sort( AdvisorStagingComparator );
-
- // positions loaded, null out the m_hvStagedEnts array with exactly as many null spaces
- m_hvStagedEnts.SetCount( m_hvStagingPositions.Count() );
-
- m_iStagingNum = 1;
-
- AssertMsg(m_hvStagingPositions.Count() > 0, "You did not specify any staging positions in the advisor's staging_ent_names !");
-#endif
-}
-#pragma warning(pop)
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Advisor::UpdateOnRemove()
-{
- if ( m_pLevitateController )
- {
- physenv->DestroyMotionController( m_pLevitateController );
- }
-
- BaseClass::UpdateOnRemove();
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Advisor::OnRestore()
-{
- BaseClass::OnRestore();
- StartLevitatingObjects();
-}
-
-
-//-----------------------------------------------------------------------------
-// Returns this monster's classification in the relationship table.
-//-----------------------------------------------------------------------------
-Class_T CNPC_Advisor::Classify()
-{
- return CLASS_COMBINE;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-bool CNPC_Advisor::IsHeavyDamage( const CTakeDamageInfo &info )
-{
- return (info.GetDamage() > 0);
-}
-
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Advisor::StartLevitatingObjects()
-{
- if ( !m_pLevitateController )
- {
- m_pLevitateController = physenv->CreateMotionController( &m_levitateCallback );
- }
-
- m_pLevitateController->ClearObjects();
-
- int nCount = m_physicsObjects.Count();
- for ( int i = 0; i < nCount; i++ )
- {
- CBaseEntity *pEnt = m_physicsObjects.Element( i );
- if ( !pEnt )
- continue;
-
- //NDebugOverlay::Box( pEnt->GetAbsOrigin(), pEnt->CollisionProp()->OBBMins(), pEnt->CollisionProp()->OBBMaxs(), 0, 255, 0, 1, 0.1 );
-
- IPhysicsObject *pPhys = pEnt->VPhysicsGetObject();
- if ( pPhys && pPhys->IsMoveable() )
- {
- m_pLevitateController->AttachObject( pPhys, false );
- pPhys->Wake();
- }
- }
-}
-
-// This function is used by both version of the entity finder below
-bool CNPC_Advisor::CanLevitateEntity( CBaseEntity *pEntity, int minMass, int maxMass )
-{
- if (!pEntity || pEntity->IsNPC())
- return false;
-
- IPhysicsObject *pPhys = pEntity->VPhysicsGetObject();
- if (!pPhys)
- return false;
-
- float mass = pPhys->GetMass();
-
- return ( mass >= minMass &&
- mass <= maxMass &&
- //pEntity->VPhysicsGetObject()->IsAsleep() &&
- pPhys->IsMoveable() /* &&
- !DidThrow(pEntity) */ );
-}
-
-#if NPC_ADVISOR_HAS_BEHAVIOR
-// find an object to throw at the player and start the warning on it. Return object's
-// pointer if we got something. Otherwise, return NULL if nothing left to throw. Will
-// always leave the prepared object at the head of m_hvStagedEnts
-CBaseEntity *CNPC_Advisor::ThrowObjectPrepare()
-{
-
- CBaseEntity *pThrowable = NULL;
- while (m_hvStagedEnts.Count() > 0)
- {
- pThrowable = m_hvStagedEnts[0];
-
- if (pThrowable)
- {
- IPhysicsObject *pPhys = pThrowable->VPhysicsGetObject();
- if ( !pPhys )
- {
- // reject!
-
- Write_BeamOff(m_hvStagedEnts[0]);
- pThrowable = NULL;
- }
- }
-
- // if we still have pThrowable...
- if (pThrowable)
- {
- // we're good
- break;
- }
- else
- {
- m_hvStagedEnts.Remove(0);
- }
- }
-
- if (pThrowable)
- {
- Assert( pThrowable->VPhysicsGetObject() );
-
- // play the sound, attach the light, fire the trigger
- EmitSound( "NPC_Advisor.ObjectChargeUp" );
-
- m_OnThrowWarn.FireOutput(pThrowable,this);
- m_flThrowPhysicsTime = gpGlobals->curtime + advisor_throw_warn_time.GetFloat();
-
- if ( GetEnemy() )
- {
- PreHurlClearTheWay( pThrowable, GetEnemy()->EyePosition() );
- }
-
- return pThrowable;
- }
- else // we had nothing to throw
- {
- return NULL;
- }
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Advisor::StartTask( const Task_t *pTask )
-{
- switch ( pTask->iTask )
- {
- // DVS: TODO: if this gets expensive we can start caching the results and doing it less often.
- case TASK_ADVISOR_FIND_OBJECTS:
- {
- // if we have a trigger volume, use the contents of that. If not, use a hardcoded box (for debugging purposes)
- // in both cases we validate the objects using the same helper funclet just above. When we can count on the
- // trigger vol being there, we can elide the else{} clause here.
-
- CBaseEntity *pVolume = m_hLevitationArea;
- AssertMsg(pVolume, "Combine advisor needs 'levitationarea' key pointing to a trigger volume." );
-
- if (!pVolume)
- {
- TaskFail( "No levitation area found!" );
- break;
- }
-
- touchlink_t *touchroot = ( touchlink_t * )pVolume->GetDataObject( TOUCHLINK );
- if ( touchroot )
- {
-
- m_physicsObjects.RemoveAll();
-
- for ( touchlink_t *link = touchroot->nextLink; link != touchroot; link = link->nextLink )
- {
- CBaseEntity *pTouch = link->entityTouched;
- if ( CanLevitateEntity( pTouch, 10, 220 ) )
- {
- if ( pTouch->GetMoveType() == MOVETYPE_VPHYSICS )
- {
- //Msg( " %d added %s\n", m_physicsObjects.Count(), STRING( list[i]->GetModelName() ) );
- m_physicsObjects.AddToTail( pTouch );
- }
- }
- }
- }
-
- /*
- // this is the old mechanism, using a hardcoded box and an entity enumerator.
- // since deprecated.
-
- else
- {
- CBaseEntity *list[128];
-
- m_physicsObjects.RemoveAll();
-
- //NDebugOverlay::Box( GetAbsOrigin(), Vector( -408, -368, -188 ), Vector( 92, 208, 168 ), 255, 255, 0, 1, 5 );
-
- // one-off class used to determine which entities we want from the UTIL_EntitiesInBox
- class CAdvisorLevitateEntitiesEnum : public CFlaggedEntitiesEnum
- {
- public:
- CAdvisorLevitateEntitiesEnum( CBaseEntity **pList, int listMax, int nMinMass, int nMaxMass )
- : CFlaggedEntitiesEnum( pList, listMax, 0 ),
- m_nMinMass( nMinMass ),
- m_nMaxMass( nMaxMass )
- {
- }
-
- virtual IterationRetval_t EnumElement( IHandleEntity *pHandleEntity )
- {
- CBaseEntity *pEntity = gEntList.GetBaseEntity( pHandleEntity->GetRefEHandle() );
- if ( AdvisorCanLevitateEntity( pEntity, m_nMinMass, m_nMaxMass ) )
- {
- return CFlaggedEntitiesEnum::EnumElement( pHandleEntity );
- }
- return ITERATION_CONTINUE;
- }
-
- int m_nMinMass;
- int m_nMaxMass;
- };
-
- CAdvisorLevitateEntitiesEnum levitateEnum( list, ARRAYSIZE( list ), 10, 220 );
-
- int nCount = UTIL_EntitiesInBox( GetAbsOrigin() - Vector( 554, 368, 188 ), GetAbsOrigin() + Vector( 92, 208, 168 ), &levitateEnum );
- for ( int i = 0; i < nCount; i++ )
- {
- //Msg( "%d found %s\n", m_physicsObjects.Count(), STRING( list[i]->GetModelName() ) );
- if ( list[i]->GetMoveType() == MOVETYPE_VPHYSICS )
- {
- //Msg( " %d added %s\n", m_physicsObjects.Count(), STRING( list[i]->GetModelName() ) );
- m_physicsObjects.AddToTail( list[i] );
- }
- }
- }
- */
-
- if ( m_physicsObjects.Count() > 0 )
- {
- TaskComplete();
- }
- else
- {
- TaskFail( "No physics objects found!" );
- }
-
- break;
- }
-
- case TASK_ADVISOR_LEVITATE_OBJECTS:
- {
- StartLevitatingObjects();
-
- m_flThrowPhysicsTime = gpGlobals->curtime + advisor_throw_rate.GetFloat();
-
- break;
- }
-
- case TASK_ADVISOR_STAGE_OBJECTS:
- {
- // m_pickFailures = 0;
- // clear out previously staged throwables
- /*
- for (int ii = m_hvStagedEnts.Count() - 1; ii >= 0 ; --ii)
- {
- m_hvStagedEnts[ii] = NULL;
- }
- */
- Write_AllBeamsOff();
- m_hvStagedEnts.RemoveAll();
-
- m_OnPickingThrowable.FireOutput(NULL,this);
- m_flStagingEnd = gpGlobals->curtime + pTask->flTaskData;
-
- break;
- }
-
- // we're about to pelt the player with everything. Start the warning effect on the first object.
- case TASK_ADVISOR_BARRAGE_OBJECTS:
- {
-
- CBaseEntity *pThrowable = ThrowObjectPrepare();
-
- if (!pThrowable || m_hvStagedEnts.Count() < 1)
- {
- TaskFail( "Nothing to throw!" );
- return;
- }
-
- m_vSavedLeadVel.Invalidate();
-
- break;
- }
-
- case TASK_ADVISOR_PIN_PLAYER:
- {
-
- // should never be here
- /*
- Assert( m_hPlayerPinPos.IsValid() );
- m_playerPinFailsafeTime = gpGlobals->curtime + 10.0f;
-
- break;
- */
- }
-
- default:
- {
- BaseClass::StartTask( pTask );
- }
- }
-}
-
-
-//-----------------------------------------------------------------------------
-// todo: find a way to guarantee that objects are made pickupable again when bailing out of a task
-//-----------------------------------------------------------------------------
-void CNPC_Advisor::RunTask( const Task_t *pTask )
-{
-
- switch ( pTask->iTask )
- {
- // Raise up the objects that we found and then hold them.
- case TASK_ADVISOR_LEVITATE_OBJECTS:
- {
- float flTimeToThrow = m_flThrowPhysicsTime - gpGlobals->curtime;
- if ( flTimeToThrow < 0 )
- {
- TaskComplete();
- return;
- }
-
- // set the top and bottom on the levitation volume from the entities. If we don't have
- // both, zero it out so that we can use the old-style simpler mechanism.
- if ( m_hLevitateGoal1 && m_hLevitateGoal2 )
- {
- m_levitateCallback.m_vecGoalPos1 = m_hLevitateGoal1->GetAbsOrigin();
- m_levitateCallback.m_vecGoalPos2 = m_hLevitateGoal2->GetAbsOrigin();
- // swap them if necessary (1 must be the bottom)
- if (m_levitateCallback.m_vecGoalPos1.z > m_levitateCallback.m_vecGoalPos2.z)
- {
- swap(m_levitateCallback.m_vecGoalPos1,m_levitateCallback.m_vecGoalPos2);
- }
-
- m_levitateCallback.m_flFloat = 0.06f; // this is an absolute accumulation upon gravity
- }
- else
- {
- m_levitateCallback.m_vecGoalPos1.Invalidate();
- m_levitateCallback.m_vecGoalPos2.Invalidate();
-
- // the below two stanzas are used for old-style floating, which is linked
- // to float up before thrown and down after
- if ( flTimeToThrow > 2.0f )
- {
- m_levitateCallback.m_flFloat = 1.06f;
- }
- else
- {
- m_levitateCallback.m_flFloat = 0.94f;
- }
- }
-
- /*
- // Draw boxes around the objects we're levitating.
- for ( int i = 0; i < m_physicsObjects.Count(); i++ )
- {
- CBaseEntity *pEnt = m_physicsObjects.Element( i );
- if ( !pEnt )
- continue; // The prop has been broken!
-
- IPhysicsObject *pPhys = pEnt->VPhysicsGetObject();
- if ( pPhys && pPhys->IsMoveable() )
- {
- NDebugOverlay::Box( pEnt->GetAbsOrigin(), pEnt->CollisionProp()->OBBMins(), pEnt->CollisionProp()->OBBMaxs(), 0, 255, 0, 1, 0.1 );
- }
- }*/
-
- break;
- }
-
- // Pick a random object that we are levitating. If we have a clear LOS from that object
- // to our enemy's eyes, choose that one to throw. Otherwise, keep looking.
- case TASK_ADVISOR_STAGE_OBJECTS:
- {
- if (m_iStagingNum > m_hvStagingPositions.Count())
- {
- Warning( "Advisor tries to stage %d objects but only has %d positions named %s! Overriding.\n", m_iStagingNum, m_hvStagingPositions.Count(), m_iszStagingEntities );
- m_iStagingNum = m_hvStagingPositions.Count() ;
- }
-
-
-// advisor_staging_num
-
- // in the future i'll distribute the staging chronologically. For now, yank all the objects at once.
- if (m_hvStagedEnts.Count() < m_iStagingNum)
- {
- // pull another object
- bool bDesperate = m_flStagingEnd - gpGlobals->curtime < 0.50f; // less than one half second left
- CBaseEntity *pThrowable = PickThrowable(!bDesperate);
- if (pThrowable)
- {
- // don't let the player take it from me
- IPhysicsObject *pPhys = pThrowable->VPhysicsGetObject();
- if ( pPhys )
- {
- // no pickup!
- pPhys->SetGameFlags(pPhys->GetGameFlags() | FVPHYSICS_NO_PLAYER_PICKUP );;
- }
-
- m_hvStagedEnts.AddToTail( pThrowable );
- Write_BeamOn(pThrowable);
-
-
- DispatchParticleEffect( "advisor_object_charge", PATTACH_ABSORIGIN_FOLLOW,
- pThrowable, 0,
- false );
- }
- }
-
-
- Assert(m_hvStagedEnts.Count() <= m_hvStagingPositions.Count());
-
- // yank all objects into place
- for (int ii = m_hvStagedEnts.Count() - 1 ; ii >= 0 ; --ii)
- {
-
- // just ignore lost objects (if the player destroys one, that's fine, leave a hole)
- CBaseEntity *pThrowable = m_hvStagedEnts[ii];
- if (pThrowable)
- {
- PullObjectToStaging(pThrowable, m_hvStagingPositions[ii]->GetAbsOrigin());
- }
- }
-
- // are we done yet?
- if (gpGlobals->curtime > m_flStagingEnd)
- {
- TaskComplete();
- break;
- }
-
- break;
- }
-
- // Fling the object that we picked at our enemy's eyes!
- case TASK_ADVISOR_BARRAGE_OBJECTS:
- {
- Assert(m_hvStagedEnts.Count() > 0);
-
- // do I still have an enemy?
- if ( !GetEnemy() )
- {
- // no? bail all the objects.
- for (int ii = m_hvStagedEnts.Count() - 1 ; ii >=0 ; --ii)
- {
-
- IPhysicsObject *pPhys = m_hvStagedEnts[ii]->VPhysicsGetObject();
- if ( pPhys )
- {
- pPhys->SetGameFlags(pPhys->GetGameFlags() & (~FVPHYSICS_NO_PLAYER_PICKUP) );
- }
- }
-
- Write_AllBeamsOff();
- m_hvStagedEnts.RemoveAll();
-
- TaskFail( "Lost enemy" );
- return;
- }
-
- // do I still have something to throw at the player?
- CBaseEntity *pThrowable = m_hvStagedEnts[0];
- while (!pThrowable)
- { // player has destroyed whatever I planned to hit him with, get something else
- if (m_hvStagedEnts.Count() > 0)
- {
- pThrowable = ThrowObjectPrepare();
- }
- else
- {
- TaskComplete();
- break;
- }
- }
-
- // If we've gone NULL, then opt out
- if ( pThrowable == NULL )
- {
- TaskComplete();
- break;
- }
-
- if ( (gpGlobals->curtime > m_flThrowPhysicsTime - advisor_throw_lead_prefetch_time.GetFloat()) &&
- !m_vSavedLeadVel.IsValid() )
- {
- // save off the velocity we will use to lead the player a little early, so that if he jukes
- // at the last moment he'll have a better shot of dodging the object.
- m_vSavedLeadVel = GetEnemy()->GetAbsVelocity();
- }
-
- // if it's time to throw something, throw it and go on to the next one.
- if (gpGlobals->curtime > m_flThrowPhysicsTime)
- {
- IPhysicsObject *pPhys = pThrowable->VPhysicsGetObject();
- Assert(pPhys);
-
- pPhys->SetGameFlags(pPhys->GetGameFlags() & (~FVPHYSICS_NO_PLAYER_PICKUP) );
- HurlObjectAtPlayer(pThrowable,Vector(0,0,0)/*m_vSavedLeadVel*/);
- m_flLastThrowTime = gpGlobals->curtime;
- m_flThrowPhysicsTime = gpGlobals->curtime + 0.75f;
- // invalidate saved lead for next time
- m_vSavedLeadVel.Invalidate();
-
- EmitSound( "NPC_Advisor.Blast" );
-
- Write_BeamOff(m_hvStagedEnts[0]);
- m_hvStagedEnts.Remove(0);
- if (!ThrowObjectPrepare())
- {
- TaskComplete();
- break;
- }
- }
- else
- {
- // wait, bide time
- // PullObjectToStaging(pThrowable, m_hvStagingPositions[ii]->GetAbsOrigin());
- }
-
- break;
- }
-
- case TASK_ADVISOR_PIN_PLAYER:
- {
- /*
- // bail out if the pin entity went away.
- CBaseEntity *pPinEnt = m_hPlayerPinPos;
- if (!pPinEnt)
- {
- GetEnemy()->SetGravity(1.0f);
- GetEnemy()->SetMoveType( MOVETYPE_WALK );
- TaskComplete();
- break;
- }
-
- // failsafe: don't do this for more than ten seconds.
- if ( gpGlobals->curtime > m_playerPinFailsafeTime )
- {
- GetEnemy()->SetGravity(1.0f);
- GetEnemy()->SetMoveType( MOVETYPE_WALK );
- Warning( "Advisor did not leave PIN PLAYER mode. Aborting due to ten second failsafe!\n" );
- TaskFail("Advisor did not leave PIN PLAYER mode. Aborting due to ten second failsafe!\n");
- break;
- }
-
- // if the player isn't the enemy, bail out.
- if ( !GetEnemy()->IsPlayer() )
- {
- GetEnemy()->SetGravity(1.0f);
- GetEnemy()->SetMoveType( MOVETYPE_WALK );
- TaskFail( "Player is not the enemy?!" );
- break;
- }
-
- GetEnemy()->SetMoveType( MOVETYPE_FLY );
- GetEnemy()->SetGravity(0);
-
- // use exponential falloff to peg the player to the pin point
- const Vector &desiredPos = pPinEnt->GetAbsOrigin();
- const Vector &playerPos = GetEnemy()->GetAbsOrigin();
-
- Vector displacement = desiredPos - playerPos;
-
- float desiredDisplacementLen = ExponentialDecay(0.250f,gpGlobals->frametime);// * sqrt(displacementLen);
-
- Vector nuPos = playerPos + (displacement * (1.0f - desiredDisplacementLen));
-
- GetEnemy()->SetAbsOrigin( nuPos );
-
- break;
- */
- }
-
- default:
- {
- BaseClass::RunTask( pTask );
- }
- }
-}
-
-
-#endif
-
-// helper function for testing whether or not an avisor is allowed to grab an object
-static bool AdvisorCanPickObject(CBasePlayer *pPlayer, CBaseEntity *pEnt)
-{
- Assert( pPlayer != NULL );
-
- // Is the player carrying something?
- CBaseEntity *pHeldObject = GetPlayerHeldEntity(pPlayer);
-
- if( !pHeldObject )
- {
- pHeldObject = PhysCannonGetHeldEntity( pPlayer->GetActiveWeapon() );
- }
-
- if( pHeldObject == pEnt )
- {
- return false;
- }
-
- if ( pEnt->GetCollisionGroup() == COLLISION_GROUP_DEBRIS )
- {
- return false;
- }
-
- return true;
-}
-
-
-#if NPC_ADVISOR_HAS_BEHAVIOR
-//-----------------------------------------------------------------------------
-// Choose an object to throw.
-// param bRequireInView : if true, only accept objects that are in the player's fov.
-//
-// Can always return NULL.
-// todo priority_grab_name
-//-----------------------------------------------------------------------------
-CBaseEntity *CNPC_Advisor::PickThrowable( bool bRequireInView )
-{
- CBasePlayer *pPlayer = ToBasePlayer( GetEnemy() );
- Assert(pPlayer);
- if (!pPlayer)
- return NULL;
-
- const int numObjs = m_physicsObjects.Count(); ///< total number of physics objects in my system
- if (numObjs < 1)
- return NULL; // bail out if nothing available
-
-
- // used for require-in-view
- Vector eyeForward, eyeOrigin;
- if (pPlayer)
- {
- eyeOrigin = pPlayer->EyePosition();
- pPlayer->EyeVectors(&eyeForward);
- }
- else
- {
- bRequireInView = false;
- }
-
- // filter-and-choose algorithm:
- // build a list of candidates
- Assert(numObjs < 128); /// I'll come back and utlvector this shortly -- wanted easier debugging
- unsigned int candidates[128];
- unsigned int numCandidates = 0;
-
- if (!!m_iszPriorityEntityGroupName) // if the string isn't null
- {
- // first look to see if we have any priority objects.
- for (int ii = 0 ; ii < numObjs ; ++ii )
- {
- CBaseEntity *pThrowEnt = m_physicsObjects[ii];
- // Assert(pThrowEnt);
- if (!pThrowEnt)
- continue;
-
- if (!pThrowEnt->NameMatches(m_iszPriorityEntityGroupName)) // if this is not a priority object
- continue;
-
- bool bCanPick = AdvisorCanPickObject( pPlayer, pThrowEnt ) && !m_hvStagedEnts.HasElement( m_physicsObjects[ii] );
- if (!bCanPick)
- continue;
-
- // bCanPick guaranteed true here
-
- if ( bRequireInView )
- {
- bCanPick = (pThrowEnt->GetAbsOrigin() - eyeOrigin).Dot(eyeForward) > 0;
- }
-
- if ( bCanPick )
- {
- candidates[numCandidates++] = ii;
- }
- }
- }
-
- // if we found no priority objects (or don't have a priority), just grab whatever
- if (numCandidates == 0)
- {
- for (int ii = 0 ; ii < numObjs ; ++ii )
- {
- CBaseEntity *pThrowEnt = m_physicsObjects[ii];
- // Assert(pThrowEnt);
- if (!pThrowEnt)
- continue;
-
- bool bCanPick = AdvisorCanPickObject( pPlayer, pThrowEnt ) && !m_hvStagedEnts.HasElement( m_physicsObjects[ii] );
- if (!bCanPick)
- continue;
-
- // bCanPick guaranteed true here
-
- if ( bRequireInView )
- {
- bCanPick = (pThrowEnt->GetAbsOrigin() - eyeOrigin).Dot(eyeForward) > 0;
- }
-
- if ( bCanPick )
- {
- candidates[numCandidates++] = ii;
- }
- }
- }
-
- if ( numCandidates == 0 )
- return NULL; // must have at least one candidate
-
- // pick a random candidate.
- int nRandomIndex = random->RandomInt( 0, numCandidates - 1 );
- return m_physicsObjects[candidates[nRandomIndex]];
-
-}
-
-/*! \TODO
- Correct bug where Advisor seemed to be throwing stuff at people's feet.
- This is because the object was falling slightly in between the staging
- and when he threw it, and that downward velocity was getting accumulated
- into the throw speed. This is temporarily fixed here by using SetVelocity
- instead of AddVelocity, but the proper fix is to pin the object to its
- staging point during the warn period. That will require maintaining a map
- of throwables to their staging points during the throw task.
-*/
-//-----------------------------------------------------------------------------
-// Impart necessary force on any entity to make it clobber Gordon.
-// Also detaches from levitate controller.
-// The optional lead velocity parameter is for cases when we pre-save off the
-// player's speed, to make last-moment juking more effective
-//-----------------------------------------------------------------------------
-void CNPC_Advisor::HurlObjectAtPlayer( CBaseEntity *pEnt, const Vector &leadVel )
-{
- IPhysicsObject *pPhys = pEnt->VPhysicsGetObject();
-
- //
- // Lead the target accurately. This encourages hiding behind cover
- // and/or catching the thrown physics object!
- //
- Vector vecObjOrigin = pEnt->CollisionProp()->WorldSpaceCenter();
- Vector vecEnemyPos = GetEnemy()->EyePosition();
- // disabled -- no longer compensate for gravity: // vecEnemyPos.y += 12.0f;
-
-// const Vector &leadVel = pLeadVelocity ? *pLeadVelocity : GetEnemy()->GetAbsVelocity();
-
- Vector vecDelta = vecEnemyPos - vecObjOrigin;
- float flDist = vecDelta.Length();
-
- float flVelocity = advisor_throw_velocity.GetFloat();
-
- if ( flVelocity == 0 )
- {
- flVelocity = 1000;
- }
-
- float flFlightTime = flDist / flVelocity;
-
- Vector vecThrowAt = vecEnemyPos + flFlightTime * leadVel;
- Vector vecThrowDir = vecThrowAt - vecObjOrigin;
- VectorNormalize( vecThrowDir );
-
- Vector vecVelocity = flVelocity * vecThrowDir;
- pPhys->SetVelocity( &vecVelocity, NULL );
-
- AddToThrownObjects(pEnt);
-
- m_OnThrow.FireOutput(pEnt,this);
-
-}
-
-
-//-----------------------------------------------------------------------------
-// do a sweep from an object I'm about to throw, to the target, pushing aside
-// anything floating in the way.
-// TODO: this is probably a good profiling candidate.
-//-----------------------------------------------------------------------------
-void CNPC_Advisor::PreHurlClearTheWay( CBaseEntity *pThrowable, const Vector &toPos )
-{
- // look for objects in the way of chucking.
- CBaseEntity *list[128];
- Ray_t ray;
-
-
- float boundingRadius = pThrowable->BoundingRadius();
-
- ray.Init( pThrowable->GetAbsOrigin(), toPos,
- Vector(-boundingRadius,-boundingRadius,-boundingRadius),
- Vector( boundingRadius, boundingRadius, boundingRadius) );
-
- int nFoundCt = UTIL_EntitiesAlongRay( list, 128, ray, 0 );
- AssertMsg(nFoundCt < 128, "Found more than 128 obstructions between advisor and Gordon while throwing. (safe to continue)\n");
-
- // for each thing in the way that I levitate, but is not something I'm staging
- // or throwing, push it aside.
- for (int i = 0 ; i < nFoundCt ; ++i )
- {
- CBaseEntity *obstruction = list[i];
- if ( obstruction != pThrowable &&
- m_physicsObjects.HasElement( obstruction ) && // if it's floating
- !m_hvStagedEnts.HasElement( obstruction ) && // and I'm not staging it
- !DidThrow( obstruction ) ) // and I didn't just throw it
- {
- IPhysicsObject *pPhys = obstruction->VPhysicsGetObject();
- Assert(pPhys);
-
- // this is an object we want to push out of the way. Compute a vector perpendicular
- // to the path of the throwables's travel, and thrust the object along that vector.
- Vector thrust;
- CalcClosestPointOnLine( obstruction->GetAbsOrigin(),
- pThrowable->GetAbsOrigin(),
- toPos,
- thrust );
- // "thrust" is now the closest point on the line to the obstruction.
- // compute the difference to get the direction of impulse
- thrust = obstruction->GetAbsOrigin() - thrust;
-
- // and renormalize it to equal a giant kick out of the way
- // (which I'll say is about ten feet per second -- if we want to be
- // more precise we could do some kind of interpolation based on how
- // far away the object is)
- float thrustLen = thrust.Length();
- if (thrustLen > 0.0001f)
- {
- thrust *= advisor_throw_clearout_vel.GetFloat() / thrustLen;
- }
-
- // heave!
- pPhys->AddVelocity( &thrust, NULL );
- }
- }
-
-/*
-
- // Otherwise only help out a little
- Vector extents = Vector(256, 256, 256);
- Ray_t ray;
- ray.Init( vecStartPoint, vecStartPoint + 2048 * vecVelDir, -extents, extents );
- int nCount = UTIL_EntitiesAlongRay( list, 1024, ray, FL_NPC | FL_CLIENT );
- for ( int i = 0; i < nCount; i++ )
- {
- if ( !IsAttractiveTarget( list[i] ) )
- continue;
-
- VectorSubtract( list[i]->WorldSpaceCenter(), vecStartPoint, vecDelta );
- distance = VectorNormalize( vecDelta );
- flDot = DotProduct( vecDelta, vecVelDir );
-
- if ( flDot > flMaxDot )
- {
- if ( distance < flBestDist )
- {
- pBestTarget = list[i];
- flBestDist = distance;
- }
- }
- }
-
-*/
-
-}
-
-/*
-// commented out because unnecessary: we will do this during the DidThrow check
-
-//-----------------------------------------------------------------------------
-// clean out the recently thrown objects array
-//-----------------------------------------------------------------------------
-void CNPC_Advisor::PurgeThrownObjects()
-{
- float threeSecondsAgo = gpGlobals->curtime - 3.0f; // two seconds ago
-
- for (int ii = 0 ; ii < kMaxThrownObjectsTracked ; ++ii)
- {
- if ( m_haRecentlyThrownObjects[ii].IsValid() &&
- m_flaRecentlyThrownObjectTimes[ii] < threeSecondsAgo )
- {
- m_haRecentlyThrownObjects[ii].Set(NULL);
- }
- }
-
-}
-*/
-
-
-//-----------------------------------------------------------------------------
-// true iff an advisor threw the object in the last three seconds
-//-----------------------------------------------------------------------------
-bool CNPC_Advisor::DidThrow(const CBaseEntity *pEnt)
-{
- // look through all my objects and see if they match this entity. Incidentally if
- // they're more than three seconds old, purge them.
- float threeSecondsAgo = gpGlobals->curtime - 3.0f;
-
- for (int ii = 0 ; ii < kMaxThrownObjectsTracked ; ++ii)
- {
- // if object is old, skip it.
- CBaseEntity *pTestEnt = m_haRecentlyThrownObjects[ii];
-
- if ( pTestEnt )
- {
- if ( m_flaRecentlyThrownObjectTimes[ii] < threeSecondsAgo )
- {
- m_haRecentlyThrownObjects[ii].Set(NULL);
- continue;
- }
- else if (pTestEnt == pEnt)
- {
- return true;
- }
- }
- }
-
- return false;
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Advisor::AddToThrownObjects(CBaseEntity *pEnt)
-{
- Assert(pEnt);
-
- // try to find an empty slot, or if none exists, the oldest object
- int oldestThrownObject = 0;
- for (int ii = 0 ; ii < kMaxThrownObjectsTracked ; ++ii)
- {
- if (m_haRecentlyThrownObjects[ii].IsValid())
- {
- if (m_flaRecentlyThrownObjectTimes[ii] < m_flaRecentlyThrownObjectTimes[oldestThrownObject])
- {
- oldestThrownObject = ii;
- }
- }
- else
- { // just use this one
- oldestThrownObject = ii;
- break;
- }
- }
-
- m_haRecentlyThrownObjects[oldestThrownObject] = pEnt;
- m_flaRecentlyThrownObjectTimes[oldestThrownObject] = gpGlobals->curtime;
-
-}
-
-
-//-----------------------------------------------------------------------------
-// Drag a particular object towards its staging location.
-//-----------------------------------------------------------------------------
-void CNPC_Advisor::PullObjectToStaging( CBaseEntity *pEnt, const Vector &stagingPos )
-{
- IPhysicsObject *pPhys = pEnt->VPhysicsGetObject();
- Assert(pPhys);
-
- Vector curPos = pEnt->CollisionProp()->WorldSpaceCenter();
- Vector displacement = stagingPos - curPos;
-
- // quick and dirty -- use exponential decay to haul the object into place
- // ( a better looking solution would be to use a spring system )
-
- float desiredDisplacementLen = ExponentialDecay(STAGING_OBJECT_FALLOFF_TIME, gpGlobals->frametime);// * sqrt(displacementLen);
-
- Vector vel; AngularImpulse angimp;
- pPhys->GetVelocity(&vel,&angimp);
-
- vel = (1.0f / gpGlobals->frametime)*(displacement * (1.0f - desiredDisplacementLen));
- pPhys->SetVelocity(&vel,&angimp);
-}
-
-
-
-#endif
-
-int CNPC_Advisor::OnTakeDamage( const CTakeDamageInfo &info )
-{
- // Clip our max
- CTakeDamageInfo newInfo = info;
- if ( newInfo.GetDamage() > 20.0f )
- {
- newInfo.SetDamage( 20.0f );
- }
-
- // Hack to make him constantly flinch
- m_flNextFlinchTime = gpGlobals->curtime;
-
- const float oldLastDamageTime = m_flLastDamageTime;
- int retval = BaseClass::OnTakeDamage(newInfo);
-
- // we have a special reporting output
- if ( oldLastDamageTime != gpGlobals->curtime )
- {
- // only fire once per frame
-
- m_OnHealthIsNow.Set( GetHealth(), newInfo.GetAttacker(), this);
- }
-
- return retval;
-}
-
-
-
-
-#if NPC_ADVISOR_HAS_BEHAVIOR
-//-----------------------------------------------------------------------------
-// Returns the best new schedule for this NPC based on current conditions.
-//-----------------------------------------------------------------------------
-int CNPC_Advisor::SelectSchedule()
-{
- if ( IsInAScript() )
- return SCHED_ADVISOR_IDLE_STAND;
-
- switch ( m_NPCState )
- {
- case NPC_STATE_IDLE:
- case NPC_STATE_ALERT:
- {
- return SCHED_ADVISOR_IDLE_STAND;
- }
-
- case NPC_STATE_COMBAT:
- {
- if ( GetEnemy() && GetEnemy()->IsAlive() )
- {
- if ( false /* m_hPlayerPinPos.IsValid() */ )
- return SCHED_ADVISOR_TOSS_PLAYER;
- else
- return SCHED_ADVISOR_COMBAT;
-
- }
-
- return SCHED_ADVISOR_IDLE_STAND;
- }
- }
-
- return BaseClass::SelectSchedule();
-}
-
-
-//-----------------------------------------------------------------------------
-// return the position where an object should be staged before throwing
-//-----------------------------------------------------------------------------
-Vector CNPC_Advisor::GetThrowFromPos( CBaseEntity *pEnt )
-{
- Assert(pEnt);
- Assert(pEnt->VPhysicsGetObject());
- const CCollisionProperty *cProp = pEnt->CollisionProp();
- Assert(cProp);
-
- float effecRadius = cProp->BoundingRadius(); // radius of object (important for kickout)
- float howFarInFront = advisor_throw_stage_distance.GetFloat() + effecRadius * 1.43f;// clamp(lenToPlayer - posDist + effecRadius,effecRadius*2,90.f + effecRadius);
-
- Vector fwd;
- GetVectors(&fwd,NULL,NULL);
-
- return GetAbsOrigin() + fwd*howFarInFront;
-}
-#endif
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Advisor::Precache()
-{
- BaseClass::Precache();
-
- PrecacheModel( STRING( GetModelName() ) );
-
-#if NPC_ADVISOR_HAS_BEHAVIOR
- PrecacheModel( "sprites/lgtning.vmt" );
-#endif
-
- PrecacheScriptSound( "NPC_Advisor.Blast" );
- PrecacheScriptSound( "NPC_Advisor.Gib" );
- PrecacheScriptSound( "NPC_Advisor.Idle" );
- PrecacheScriptSound( "NPC_Advisor.Alert" );
- PrecacheScriptSound( "NPC_Advisor.Die" );
- PrecacheScriptSound( "NPC_Advisor.Pain" );
- PrecacheScriptSound( "NPC_Advisor.ObjectChargeUp" );
- PrecacheParticleSystem( "Advisor_Psychic_Beam" );
- PrecacheParticleSystem( "advisor_object_charge" );
- PrecacheModel("sprites/greenglow1.vmt");
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Advisor::IdleSound()
-{
- EmitSound( "NPC_Advisor.Idle" );
-}
-
-
-void CNPC_Advisor::AlertSound()
-{
- EmitSound( "NPC_Advisor.Alert" );
-}
-
-
-void CNPC_Advisor::PainSound( const CTakeDamageInfo &info )
-{
- EmitSound( "NPC_Advisor.Pain" );
-}
-
-
-void CNPC_Advisor::DeathSound( const CTakeDamageInfo &info )
-{
- EmitSound( "NPC_Advisor.Die" );
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-int CNPC_Advisor::DrawDebugTextOverlays()
-{
- int nOffset = BaseClass::DrawDebugTextOverlays();
- return nOffset;
-}
-
-
-#if NPC_ADVISOR_HAS_BEHAVIOR
-//-----------------------------------------------------------------------------
-// Determines which sounds the advisor cares about.
-//-----------------------------------------------------------------------------
-int CNPC_Advisor::GetSoundInterests()
-{
- return SOUND_WORLD | SOUND_COMBAT | SOUND_PLAYER | SOUND_DANGER;
-}
-
-
-//-----------------------------------------------------------------------------
-// record the last time we heard a combat sound
-//-----------------------------------------------------------------------------
-bool CNPC_Advisor::QueryHearSound( CSound *pSound )
-{
- // Disregard footsteps from our own class type
- CBaseEntity *pOwner = pSound->m_hOwner;
- if ( pOwner && pSound->IsSoundType( SOUND_COMBAT ) && pSound->SoundChannel() != SOUNDENT_CHANNEL_NPC_FOOTSTEP && pSound->m_hOwner.IsValid() && pOwner->IsPlayer() )
- {
- // Msg("Heard player combat.\n");
- m_flLastPlayerAttackTime = gpGlobals->curtime;
- }
-
- return BaseClass::QueryHearSound(pSound);
-}
-
-//-----------------------------------------------------------------------------
-// designer hook for setting throw rate
-//-----------------------------------------------------------------------------
-void CNPC_Advisor::InputSetThrowRate( inputdata_t &inputdata )
-{
- advisor_throw_rate.SetValue(inputdata.value.Float());
-}
-
-void CNPC_Advisor::InputSetStagingNum( inputdata_t &inputdata )
-{
- m_iStagingNum = inputdata.value.Int();
-}
-
-//
-// cause the player to be pinned to a point in space
-//
-void CNPC_Advisor::InputPinPlayer( inputdata_t &inputdata )
-{
- string_t targetname = inputdata.value.StringID();
-
- // null string means designer is trying to unpin the player
- if (!targetname)
- {
- m_hPlayerPinPos = NULL;
- }
-
- // otherwise try to look up the entity and make it a target.
- CBaseEntity *pEnt = gEntList.FindEntityByName(NULL,targetname);
-
- if (pEnt)
- {
- m_hPlayerPinPos = pEnt;
- }
- else
- {
- // if we couldn't find the target, just bail on the behavior.
- Warning("Advisor tried to pin player to %s but that does not exist.\n", targetname.ToCStr());
- m_hPlayerPinPos = NULL;
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CNPC_Advisor::OnScheduleChange( void )
-{
- Write_AllBeamsOff();
- m_hvStagedEnts.RemoveAll();
- BaseClass::OnScheduleChange();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CNPC_Advisor::GatherConditions( void )
-{
- BaseClass::GatherConditions();
-
- // Handle script state changes
- bool bInScript = IsInAScript();
- if ( ( m_bWasScripting && bInScript == false ) || ( m_bWasScripting == false && bInScript ) )
- {
- SetCondition( COND_ADVISOR_PHASE_INTERRUPT );
- }
-
- // Retain this
- m_bWasScripting = bInScript;
-}
-
-//-----------------------------------------------------------------------------
-// designer hook for yanking an object into the air right now
-//-----------------------------------------------------------------------------
-void CNPC_Advisor::InputWrenchImmediate( inputdata_t &inputdata )
-{
- string_t groupname = inputdata.value.StringID();
-
- Assert(!!groupname);
-
- // for all entities with that name that aren't floating, punt them at me and add them to the levitation
-
- CBaseEntity *pEnt = NULL;
-
- const Vector &myPos = GetAbsOrigin() + Vector(0,36.0f,0);
-
- // conditional assignment: find an entity by name and save it into pEnt. Bail out when none are left.
- while ( ( pEnt = gEntList.FindEntityByName(pEnt,groupname) ) != NULL )
- {
- // if I'm not already levitating it, and if I didn't just throw it
- if (!m_physicsObjects.HasElement(pEnt) )
- {
- // add to levitation
- IPhysicsObject *pPhys = pEnt->VPhysicsGetObject();
- if ( pPhys )
- {
- // if the object isn't moveable, make it so.
- if ( !pPhys->IsMoveable() )
- {
- pPhys->EnableMotion( true );
- }
-
- // first, kick it at me
- Vector objectToMe;
- pPhys->GetPosition(&objectToMe,NULL);
- objectToMe = myPos - objectToMe;
- // compute a velocity that will get it here in about a second
- objectToMe /= (1.5f * gpGlobals->frametime);
-
- objectToMe *= random->RandomFloat(0.25f,1.0f);
-
- pPhys->SetVelocity( &objectToMe, NULL );
-
- // add it to tracked physics objects
- m_physicsObjects.AddToTail( pEnt );
-
- m_pLevitateController->AttachObject( pPhys, false );
- pPhys->Wake();
- }
- else
- {
- Warning( "Advisor tried to wrench %s, but it is not moveable!", pEnt->GetEntityName().ToCStr());
- }
- }
- }
-
-}
-
-
-
-//-----------------------------------------------------------------------------
-// write a message turning a beam on
-//-----------------------------------------------------------------------------
-void CNPC_Advisor::Write_BeamOn( CBaseEntity *pEnt )
-{
- Assert( pEnt );
- EntityMessageBegin( this, true );
- WRITE_BYTE( ADVISOR_MSG_START_BEAM );
- WRITE_LONG( pEnt->entindex() );
- MessageEnd();
-}
-
-//-----------------------------------------------------------------------------
-// write a message turning a beam off
-//-----------------------------------------------------------------------------
-void CNPC_Advisor::Write_BeamOff( CBaseEntity *pEnt )
-{
- Assert( pEnt );
- EntityMessageBegin( this, true );
- WRITE_BYTE( ADVISOR_MSG_STOP_BEAM );
- WRITE_LONG( pEnt->entindex() );
- MessageEnd();
-}
-
-//-----------------------------------------------------------------------------
-// tell client to kill all beams
-//-----------------------------------------------------------------------------
-void CNPC_Advisor::Write_AllBeamsOff( void )
-{
- EntityMessageBegin( this, true );
- WRITE_BYTE( ADVISOR_MSG_STOP_ALL_BEAMS );
- MessageEnd();
-}
-
-//-----------------------------------------------------------------------------
-// input wrapper around Write_BeamOn
-//-----------------------------------------------------------------------------
-void CNPC_Advisor::InputTurnBeamOn( inputdata_t &inputdata )
-{
- // inputdata should specify a target
- CBaseEntity *pTarget = gEntList.FindEntityByName( NULL, inputdata.value.StringID() );
- if ( pTarget )
- {
- Write_BeamOn( pTarget );
- }
- else
- {
- Warning("InputTurnBeamOn could not find object %s", inputdata.value.String() );
- }
-}
-
-
-//-----------------------------------------------------------------------------
-// input wrapper around Write_BeamOff
-//-----------------------------------------------------------------------------
-void CNPC_Advisor::InputTurnBeamOff( inputdata_t &inputdata )
-{
- // inputdata should specify a target
- CBaseEntity *pTarget = gEntList.FindEntityByName( NULL, inputdata.value.StringID() );
- if ( pTarget )
- {
- Write_BeamOff( pTarget );
- }
- else
- {
- Warning("InputTurnBeamOn could not find object %s", inputdata.value.String() );
- }
-}
-
-
-void CNPC_Advisor::InputElightOn( inputdata_t &inputdata )
-{
- EntityMessageBegin( this, true );
- WRITE_BYTE( ADVISOR_MSG_START_ELIGHT );
- MessageEnd();
-}
-
-void CNPC_Advisor::InputElightOff( inputdata_t &inputdata )
-{
- EntityMessageBegin( this, true );
- WRITE_BYTE( ADVISOR_MSG_STOP_ELIGHT );
- MessageEnd();
-}
-#endif
-
-
-//==============================================================================================
-// MOTION CALLBACK
-//==============================================================================================
-CAdvisorLevitate::simresult_e CAdvisorLevitate::Simulate( IPhysicsMotionController *pController, IPhysicsObject *pObject, float deltaTime, Vector &linear, AngularImpulse &angular )
-{
- // this function can be optimized to minimize branching if necessary (PPE branch prediction)
- CNPC_Advisor *pAdvisor = static_cast<CNPC_Advisor *>(m_Advisor.Get());
- Assert(pAdvisor);
-
- if ( !OldStyle() )
- { // independent movement of all objects
- // if an object was recently thrown, just zero out its gravity.
- if (pAdvisor->DidThrow(static_cast<CBaseEntity *>(pObject->GetGameData())))
- {
- linear = Vector( 0, 0, GetCurrentGravity() );
-
- return SIM_GLOBAL_ACCELERATION;
- }
- else
- {
- Vector vel; AngularImpulse angvel;
- pObject->GetVelocity(&vel,&angvel);
- Vector pos;
- pObject->GetPosition(&pos,NULL);
- bool bMovingUp = vel.z > 0;
-
- // if above top limit and moving up, move down. if below bottom limit and moving down, move up.
- if (bMovingUp)
- {
- if (pos.z > m_vecGoalPos2.z)
- {
- // turn around move down
- linear = Vector( 0, 0, Square((1.0f - m_flFloat)) * GetCurrentGravity() );
- angular = Vector( 0, -5, 0 );
- }
- else
- { // keep moving up
- linear = Vector( 0, 0, (1.0f + m_flFloat) * GetCurrentGravity() );
- angular = Vector( 0, 0, 10 );
- }
- }
- else
- {
- if (pos.z < m_vecGoalPos1.z)
- {
- // turn around move up
- linear = Vector( 0, 0, Square((1.0f + m_flFloat)) * GetCurrentGravity() );
- angular = Vector( 0, 5, 0 );
- }
- else
- { // keep moving down
- linear = Vector( 0, 0, (1.0f - m_flFloat) * GetCurrentGravity() );
- angular = Vector( 0, 0, 10 );
- }
- }
-
- return SIM_GLOBAL_ACCELERATION;
- }
-
- //NDebugOverlay::Cross3D(pos,24.0f,255,255,0,true,0.04f);
-
- }
- else // old stateless technique
- {
- Warning("Advisor using old-style object movement!\n");
-
- /* // obsolete
- CBaseEntity *pEnt = (CBaseEntity *)pObject->GetGameData();
- Vector vecDir1 = m_vecGoalPos1 - pEnt->GetAbsOrigin();
- VectorNormalize( vecDir1 );
-
- Vector vecDir2 = m_vecGoalPos2 - pEnt->GetAbsOrigin();
- VectorNormalize( vecDir2 );
- */
-
- linear = Vector( 0, 0, m_flFloat * GetCurrentGravity() );// + m_flFloat * 0.5 * ( vecDir1 + vecDir2 );
- angular = Vector( 0, 0, 10 );
-
- return SIM_GLOBAL_ACCELERATION;
- }
-
-}
-
-
-//==============================================================================================
-// ADVISOR PHYSICS DAMAGE TABLE
-//==============================================================================================
-static impactentry_t advisorLinearTable[] =
-{
- { 100*100, 10 },
- { 250*250, 25 },
- { 350*350, 50 },
- { 500*500, 75 },
- { 1000*1000,100 },
-};
-
-static impactentry_t advisorAngularTable[] =
-{
- { 50* 50, 10 },
- { 100*100, 25 },
- { 150*150, 50 },
- { 200*200, 75 },
-};
-
-static impactdamagetable_t gAdvisorImpactDamageTable =
-{
- advisorLinearTable,
- advisorAngularTable,
-
- ARRAYSIZE(advisorLinearTable),
- ARRAYSIZE(advisorAngularTable),
-
- 200*200,// minimum linear speed squared
- 180*180,// minimum angular speed squared (360 deg/s to cause spin/slice damage)
- 15, // can't take damage from anything under 15kg
-
- 10, // anything less than 10kg is "small"
- 5, // never take more than 1 pt of damage from anything under 15kg
- 128*128,// <15kg objects must go faster than 36 in/s to do damage
-
- 45, // large mass in kg
- 2, // large mass scale (anything over 500kg does 4X as much energy to read from damage table)
- 1, // large mass falling scale
- 0, // my min velocity
-};
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Output : const impactdamagetable_t
-//-----------------------------------------------------------------------------
-const impactdamagetable_t &CNPC_Advisor::GetPhysicsImpactDamageTable( void )
-{
- return advisor_use_impact_table.GetBool() ? gAdvisorImpactDamageTable : BaseClass::GetPhysicsImpactDamageTable();
-}
-
-
-
-#if NPC_ADVISOR_HAS_BEHAVIOR
-//-----------------------------------------------------------------------------
-//
-// Schedules
-//
-//-----------------------------------------------------------------------------
-AI_BEGIN_CUSTOM_NPC( npc_advisor, CNPC_Advisor )
-
- DECLARE_TASK( TASK_ADVISOR_FIND_OBJECTS )
- DECLARE_TASK( TASK_ADVISOR_LEVITATE_OBJECTS )
- /*
- DECLARE_TASK( TASK_ADVISOR_PICK_THROW_OBJECT )
- DECLARE_TASK( TASK_ADVISOR_THROW_OBJECT )
- */
-
- DECLARE_CONDITION( COND_ADVISOR_PHASE_INTERRUPT ) // A stage has interrupted us
-
- DECLARE_TASK( TASK_ADVISOR_STAGE_OBJECTS ) // haul all the objects into the throw-from slots
- DECLARE_TASK( TASK_ADVISOR_BARRAGE_OBJECTS ) // hurl all the objects in sequence
-
- DECLARE_TASK( TASK_ADVISOR_PIN_PLAYER ) // pinion the player to a point in space
-
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_ADVISOR_COMBAT,
-
- " Tasks"
- " TASK_ADVISOR_FIND_OBJECTS 0"
- " TASK_ADVISOR_LEVITATE_OBJECTS 0"
- " TASK_ADVISOR_STAGE_OBJECTS 1"
- " TASK_ADVISOR_BARRAGE_OBJECTS 0"
- " "
- " Interrupts"
- " COND_ADVISOR_PHASE_INTERRUPT"
- " COND_ENEMY_DEAD"
- )
-
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_ADVISOR_IDLE_STAND,
-
- " Tasks"
- " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
- " TASK_WAIT 3"
- ""
- " Interrupts"
- " COND_NEW_ENEMY"
- " COND_SEE_FEAR"
- " COND_ADVISOR_PHASE_INTERRUPT"
- )
-
- DEFINE_SCHEDULE
- (
- SCHED_ADVISOR_TOSS_PLAYER,
-
- " Tasks"
- " TASK_ADVISOR_FIND_OBJECTS 0"
- " TASK_ADVISOR_LEVITATE_OBJECTS 0"
- " TASK_ADVISOR_PIN_PLAYER 0"
- " "
- " Interrupts"
- )
-
-AI_END_CUSTOM_NPC()
-#endif
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Advisors. Large sluglike aliens with creepy psychic powers!
+//
+//=============================================================================
+
+#include "cbase.h"
+#include "game.h"
+#include "ai_basenpc.h"
+#include "ai_schedule.h"
+#include "ai_hull.h"
+#include "ai_hint.h"
+#include "ai_motor.h"
+#include "ai_navigator.h"
+#include "beam_shared.h"
+#include "hl2_shareddefs.h"
+#include "ai_route.h"
+#include "npcevent.h"
+#include "gib.h"
+#include "ai_interactions.h"
+#include "ndebugoverlay.h"
+#include "physics_saverestore.h"
+#include "saverestore_utlvector.h"
+#include "soundent.h"
+#include "vstdlib/random.h"
+#include "engine/IEngineSound.h"
+#include "movevars_shared.h"
+#include "particle_parse.h"
+#include "weapon_physcannon.h"
+// #include "mathlib/noise.h"
+
+// this file contains the definitions for the message ID constants (eg ADVISOR_MSG_START_BEAM etc)
+#include "npc_advisor_shared.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+//
+// Custom activities.
+//
+
+//
+// Skill settings.
+//
+ConVar sk_advisor_health( "sk_advisor_health", "0" );
+ConVar advisor_use_impact_table("advisor_use_impact_table","1",FCVAR_NONE,"If true, advisor will use her custom impact damage table.");
+
+#if NPC_ADVISOR_HAS_BEHAVIOR
+ConVar advisor_throw_velocity( "advisor_throw_velocity", "1100" );
+ConVar advisor_throw_rate( "advisor_throw_rate", "4" ); // Throw an object every 4 seconds.
+ConVar advisor_throw_warn_time( "advisor_throw_warn_time", "1.0" ); // Warn players one second before throwing an object.
+ConVar advisor_throw_lead_prefetch_time ( "advisor_throw_lead_prefetch_time", "0.66", FCVAR_NONE, "Save off the player's velocity this many seconds before throwing.");
+ConVar advisor_throw_stage_distance("advisor_throw_stage_distance","180.0",FCVAR_NONE,"Advisor will try to hold an object this far in front of him just before throwing it at you. Small values will clobber the shield and be very bad.");
+// ConVar advisor_staging_num("advisor_staging_num","1",FCVAR_NONE,"Advisor will queue up this many objects to throw at Gordon.");
+ConVar advisor_throw_clearout_vel("advisor_throw_clearout_vel","200",FCVAR_NONE,"TEMP: velocity with which advisor clears things out of a throwable's way");
+// ConVar advisor_staging_duration("
+
+// how long it will take an object to get hauled to the staging point
+#define STAGING_OBJECT_FALLOFF_TIME 0.15f
+#endif
+
+
+
+//
+// Spawnflags.
+//
+
+//
+// Animation events.
+//
+
+
+#if NPC_ADVISOR_HAS_BEHAVIOR
+//
+// Custom schedules.
+//
+enum
+{
+ SCHED_ADVISOR_COMBAT = LAST_SHARED_SCHEDULE,
+ SCHED_ADVISOR_IDLE_STAND,
+ SCHED_ADVISOR_TOSS_PLAYER
+};
+
+
+//
+// Custom tasks.
+//
+enum
+{
+ TASK_ADVISOR_FIND_OBJECTS = LAST_SHARED_TASK,
+ TASK_ADVISOR_LEVITATE_OBJECTS,
+ TASK_ADVISOR_STAGE_OBJECTS,
+ TASK_ADVISOR_BARRAGE_OBJECTS,
+
+ TASK_ADVISOR_PIN_PLAYER,
+};
+
+//
+// Custom conditions.
+//
+enum
+{
+ COND_ADVISOR_PHASE_INTERRUPT = LAST_SHARED_CONDITION,
+};
+#endif
+
+class CNPC_Advisor;
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+class CAdvisorLevitate : public IMotionEvent
+{
+ DECLARE_SIMPLE_DATADESC();
+
+public:
+
+ // in the absence of goal entities, we float up before throwing and down after
+ inline bool OldStyle( void )
+ {
+ return !(m_vecGoalPos1.IsValid() && m_vecGoalPos2.IsValid());
+ }
+
+ virtual simresult_e Simulate( IPhysicsMotionController *pController, IPhysicsObject *pObject, float deltaTime, Vector &linear, AngularImpulse &angular );
+
+ EHANDLE m_Advisor; ///< handle to the advisor.
+
+ Vector m_vecGoalPos1;
+ Vector m_vecGoalPos2;
+
+ float m_flFloat;
+};
+
+BEGIN_SIMPLE_DATADESC( CAdvisorLevitate )
+ DEFINE_FIELD( m_flFloat, FIELD_FLOAT ),
+ DEFINE_FIELD( m_vecGoalPos1, FIELD_POSITION_VECTOR ),
+ DEFINE_FIELD( m_vecGoalPos2, FIELD_POSITION_VECTOR ),
+ DEFINE_FIELD( m_Advisor, FIELD_EHANDLE ),
+END_DATADESC()
+
+
+
+//-----------------------------------------------------------------------------
+// The advisor class.
+//-----------------------------------------------------------------------------
+class CNPC_Advisor : public CAI_BaseNPC
+{
+ DECLARE_CLASS( CNPC_Advisor, CAI_BaseNPC );
+
+#if NPC_ADVISOR_HAS_BEHAVIOR
+ DECLARE_SERVERCLASS();
+#endif
+
+public:
+
+ //
+ // CBaseEntity:
+ //
+ virtual void Activate();
+ virtual void Spawn();
+ virtual void Precache();
+ virtual void OnRestore();
+ virtual void UpdateOnRemove();
+
+ virtual int DrawDebugTextOverlays();
+
+ //
+ // CAI_BaseNPC:
+ //
+ virtual float MaxYawSpeed() { return 120.0f; }
+
+ virtual Class_T Classify();
+
+#if NPC_ADVISOR_HAS_BEHAVIOR
+ virtual int GetSoundInterests();
+ virtual int SelectSchedule();
+ virtual void StartTask( const Task_t *pTask );
+ virtual void RunTask( const Task_t *pTask );
+ virtual void OnScheduleChange( void );
+#endif
+
+ virtual void PainSound( const CTakeDamageInfo &info );
+ virtual void DeathSound( const CTakeDamageInfo &info );
+ virtual void IdleSound();
+ virtual void AlertSound();
+
+#if NPC_ADVISOR_HAS_BEHAVIOR
+ virtual bool QueryHearSound( CSound *pSound );
+ virtual void GatherConditions( void );
+
+ /// true iff I recently threw the given object (not so fast)
+ bool DidThrow(const CBaseEntity *pEnt);
+#else
+ inline bool DidThrow(const CBaseEntity *pEnt) { return false; }
+#endif
+
+ virtual bool IsHeavyDamage( const CTakeDamageInfo &info );
+ virtual int OnTakeDamage( const CTakeDamageInfo &info );
+
+ virtual const impactdamagetable_t &GetPhysicsImpactDamageTable( void );
+ COutputInt m_OnHealthIsNow;
+
+#if NPC_ADVISOR_HAS_BEHAVIOR
+
+ DEFINE_CUSTOM_AI;
+
+ void InputSetThrowRate( inputdata_t &inputdata );
+ void InputWrenchImmediate( inputdata_t &inputdata ); ///< immediately wrench an object into the air
+ void InputSetStagingNum( inputdata_t &inputdata );
+ void InputPinPlayer( inputdata_t &inputdata );
+ void InputTurnBeamOn( inputdata_t &inputdata );
+ void InputTurnBeamOff( inputdata_t &inputdata );
+ void InputElightOn( inputdata_t &inputdata );
+ void InputElightOff( inputdata_t &inputdata );
+
+ COutputEvent m_OnPickingThrowable, m_OnThrowWarn, m_OnThrow;
+
+ enum { kMaxThrownObjectsTracked = 4 };
+#endif
+
+ DECLARE_DATADESC();
+
+protected:
+
+#if NPC_ADVISOR_HAS_BEHAVIOR
+ Vector GetThrowFromPos( CBaseEntity *pEnt ); ///< Get the position in which we shall hold an object prior to throwing it
+#endif
+
+ bool CanLevitateEntity( CBaseEntity *pEntity, int minMass, int maxMass );
+ void StartLevitatingObjects( void );
+
+
+#if NPC_ADVISOR_HAS_BEHAVIOR
+ // void PurgeThrownObjects(); ///< clean out the recently thrown objects array
+ void AddToThrownObjects(CBaseEntity *pEnt); ///< add to the recently thrown objects array
+
+ void HurlObjectAtPlayer( CBaseEntity *pEnt, const Vector &leadVel );
+ void PullObjectToStaging( CBaseEntity *pEnt, const Vector &stagingPos );
+ CBaseEntity *ThrowObjectPrepare( void );
+
+ CBaseEntity *PickThrowable( bool bRequireInView ); ///< choose an object to throw at the player (so it can get stuffed in the handle array)
+
+ /// push everything out of the way between an object I'm about to throw and the player.
+ void PreHurlClearTheWay( CBaseEntity *pThrowable, const Vector &toPos );
+#endif
+
+ CUtlVector<EHANDLE> m_physicsObjects;
+ IPhysicsMotionController *m_pLevitateController;
+ CAdvisorLevitate m_levitateCallback;
+
+ EHANDLE m_hLevitateGoal1;
+ EHANDLE m_hLevitateGoal2;
+ EHANDLE m_hLevitationArea;
+
+#if NPC_ADVISOR_HAS_BEHAVIOR
+ // EHANDLE m_hThrowEnt;
+ CUtlVector<EHANDLE> m_hvStagedEnts;
+ CUtlVector<EHANDLE> m_hvStagingPositions;
+ // todo: write accessor functions for m_hvStagedEnts so that it doesn't have members added and removed willy nilly throughout
+ // code (will make the networking below more reliable)
+
+ void Write_BeamOn( CBaseEntity *pEnt ); ///< write a message turning a beam on
+ void Write_BeamOff( CBaseEntity *pEnt ); ///< write a message turning a beam off
+ void Write_AllBeamsOff( void ); ///< tell client to kill all beams
+
+ // for the pin-the-player-to-something behavior
+ EHANDLE m_hPlayerPinPos;
+ float m_playerPinFailsafeTime;
+
+ // keep track of up to four objects after we have thrown them, to prevent oscillation or levitation of recently thrown ammo.
+ EHANDLE m_haRecentlyThrownObjects[kMaxThrownObjectsTracked];
+ float m_flaRecentlyThrownObjectTimes[kMaxThrownObjectsTracked];
+#endif
+
+ string_t m_iszLevitateGoal1;
+ string_t m_iszLevitateGoal2;
+ string_t m_iszLevitationArea;
+
+
+#if NPC_ADVISOR_HAS_BEHAVIOR
+ string_t m_iszStagingEntities;
+ string_t m_iszPriorityEntityGroupName;
+
+ float m_flStagingEnd;
+ float m_flThrowPhysicsTime;
+ float m_flLastThrowTime;
+ float m_flLastPlayerAttackTime; ///< last time the player attacked something.
+
+ int m_iStagingNum; ///< number of objects advisor stages at once
+ bool m_bWasScripting;
+
+ // unsigned char m_pickFailures; // the number of times we have tried to pick a throwable and failed
+
+ Vector m_vSavedLeadVel; ///< save off player velocity for leading a bit before actually pelting them.
+#endif
+};
+
+
+LINK_ENTITY_TO_CLASS( npc_advisor, CNPC_Advisor );
+
+BEGIN_DATADESC( CNPC_Advisor )
+
+ DEFINE_KEYFIELD( m_iszLevitateGoal1, FIELD_STRING, "levitategoal_bottom" ),
+ DEFINE_KEYFIELD( m_iszLevitateGoal2, FIELD_STRING, "levitategoal_top" ),
+ DEFINE_KEYFIELD( m_iszLevitationArea, FIELD_STRING, "levitationarea"), ///< we will float all the objects in this volume
+
+ DEFINE_PHYSPTR( m_pLevitateController ),
+ DEFINE_EMBEDDED( m_levitateCallback ),
+ DEFINE_UTLVECTOR( m_physicsObjects, FIELD_EHANDLE ),
+
+ DEFINE_FIELD( m_hLevitateGoal1, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_hLevitateGoal2, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_hLevitationArea, FIELD_EHANDLE ),
+
+#if NPC_ADVISOR_HAS_BEHAVIOR
+ DEFINE_KEYFIELD( m_iszStagingEntities, FIELD_STRING, "staging_ent_names"), ///< entities named this constitute the positions to which we stage objects to be thrown
+ DEFINE_KEYFIELD( m_iszPriorityEntityGroupName, FIELD_STRING, "priority_grab_name"),
+
+ DEFINE_UTLVECTOR( m_hvStagedEnts, FIELD_EHANDLE ),
+ DEFINE_UTLVECTOR( m_hvStagingPositions, FIELD_EHANDLE ),
+ DEFINE_ARRAY( m_haRecentlyThrownObjects, FIELD_EHANDLE, CNPC_Advisor::kMaxThrownObjectsTracked ),
+ DEFINE_ARRAY( m_flaRecentlyThrownObjectTimes, FIELD_TIME, CNPC_Advisor::kMaxThrownObjectsTracked ),
+
+ DEFINE_FIELD( m_hPlayerPinPos, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_playerPinFailsafeTime, FIELD_TIME ),
+
+ // DEFINE_FIELD( m_hThrowEnt, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_flThrowPhysicsTime, FIELD_TIME ),
+ DEFINE_FIELD( m_flLastPlayerAttackTime, FIELD_TIME ),
+ DEFINE_FIELD( m_flStagingEnd, FIELD_TIME ),
+ DEFINE_FIELD( m_iStagingNum, FIELD_INTEGER ),
+ DEFINE_FIELD( m_bWasScripting, FIELD_BOOLEAN ),
+
+ DEFINE_FIELD( m_flLastThrowTime, FIELD_TIME ),
+ DEFINE_FIELD( m_vSavedLeadVel, FIELD_VECTOR ),
+
+ DEFINE_OUTPUT( m_OnPickingThrowable, "OnPickingThrowable" ),
+ DEFINE_OUTPUT( m_OnThrowWarn, "OnThrowWarn" ),
+ DEFINE_OUTPUT( m_OnThrow, "OnThrow" ),
+ DEFINE_OUTPUT( m_OnHealthIsNow, "OnHealthIsNow" ),
+
+ DEFINE_INPUTFUNC( FIELD_FLOAT, "SetThrowRate", InputSetThrowRate ),
+ DEFINE_INPUTFUNC( FIELD_STRING, "WrenchImmediate", InputWrenchImmediate ),
+ DEFINE_INPUTFUNC( FIELD_INTEGER, "SetStagingNum", InputSetStagingNum),
+ DEFINE_INPUTFUNC( FIELD_STRING, "PinPlayer", InputPinPlayer ),
+ DEFINE_INPUTFUNC( FIELD_STRING, "BeamOn", InputTurnBeamOn ),
+ DEFINE_INPUTFUNC( FIELD_STRING, "BeamOff", InputTurnBeamOff ),
+ DEFINE_INPUTFUNC( FIELD_STRING, "ElightOn", InputElightOn ),
+ DEFINE_INPUTFUNC( FIELD_STRING, "ElightOff", InputElightOff ),
+#endif
+
+END_DATADESC()
+
+
+
+#if NPC_ADVISOR_HAS_BEHAVIOR
+IMPLEMENT_SERVERCLASS_ST(CNPC_Advisor, DT_NPC_Advisor)
+
+END_SEND_TABLE()
+#endif
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Advisor::Spawn()
+{
+ BaseClass::Spawn();
+
+#ifdef _XBOX
+ // Always fade the corpse
+ AddSpawnFlags( SF_NPC_FADE_CORPSE );
+#endif // _XBOX
+
+ Precache();
+
+ SetModel( STRING( GetModelName() ) );
+
+ m_iHealth = sk_advisor_health.GetFloat();
+ m_takedamage = DAMAGE_NO;
+
+ SetHullType( HULL_LARGE_CENTERED );
+ SetHullSizeNormal();
+
+ SetSolid( SOLID_BBOX );
+ // AddSolidFlags( FSOLID_NOT_SOLID );
+
+ SetMoveType( MOVETYPE_FLY );
+
+ m_flFieldOfView = VIEW_FIELD_FULL;
+ SetViewOffset( Vector( 0, 0, 80 ) ); // Position of the eyes relative to NPC's origin.
+
+ SetBloodColor( BLOOD_COLOR_GREEN );
+ m_NPCState = NPC_STATE_NONE;
+
+ CapabilitiesClear();
+
+ NPCInit();
+
+ SetGoalEnt( NULL );
+
+ AddEFlags( EFL_NO_DISSOLVE );
+}
+
+
+#if NPC_ADVISOR_HAS_BEHAVIOR
+//-----------------------------------------------------------------------------
+// comparison function for qsort used below. Compares "StagingPriority" keyfield
+//-----------------------------------------------------------------------------
+int __cdecl AdvisorStagingComparator(const EHANDLE *pe1, const EHANDLE *pe2)
+{
+ // bool ReadKeyField( const char *varName, variant_t *var );
+
+ variant_t var;
+ int val1 = 10, val2 = 10; // default priority is ten
+
+ // read field one
+ if ( pe1->Get()->ReadKeyField( "StagingPriority", &var ) )
+ {
+ val1 = var.Int();
+ }
+
+ // read field two
+ if ( pe2->Get()->ReadKeyField( "StagingPriority", &var ) )
+ {
+ val2 = var.Int();
+ }
+
+ // return comparison (< 0 if pe1<pe2)
+ return( val1 - val2 );
+}
+#endif
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+
+#pragma warning(push)
+#pragma warning(disable : 4706)
+
+void CNPC_Advisor::Activate()
+{
+ BaseClass::Activate();
+
+ m_hLevitateGoal1 = gEntList.FindEntityGeneric( NULL, STRING( m_iszLevitateGoal1 ), this );
+ m_hLevitateGoal2 = gEntList.FindEntityGeneric( NULL, STRING( m_iszLevitateGoal2 ), this );
+ m_hLevitationArea = gEntList.FindEntityGeneric( NULL, STRING( m_iszLevitationArea ), this );
+
+ m_levitateCallback.m_Advisor = this;
+
+#if NPC_ADVISOR_HAS_BEHAVIOR
+ // load the staging positions
+ CBaseEntity *pEnt = NULL;
+ m_hvStagingPositions.EnsureCapacity(6); // reserve six
+
+ // conditional assignment: find an entity by name and save it into pEnt. Bail out when none are left.
+ while ( pEnt = gEntList.FindEntityByName(pEnt,m_iszStagingEntities) )
+ {
+ m_hvStagingPositions.AddToTail(pEnt);
+ }
+
+ // sort the staging positions by their staging number.
+ m_hvStagingPositions.Sort( AdvisorStagingComparator );
+
+ // positions loaded, null out the m_hvStagedEnts array with exactly as many null spaces
+ m_hvStagedEnts.SetCount( m_hvStagingPositions.Count() );
+
+ m_iStagingNum = 1;
+
+ AssertMsg(m_hvStagingPositions.Count() > 0, "You did not specify any staging positions in the advisor's staging_ent_names !");
+#endif
+}
+#pragma warning(pop)
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Advisor::UpdateOnRemove()
+{
+ if ( m_pLevitateController )
+ {
+ physenv->DestroyMotionController( m_pLevitateController );
+ }
+
+ BaseClass::UpdateOnRemove();
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Advisor::OnRestore()
+{
+ BaseClass::OnRestore();
+ StartLevitatingObjects();
+}
+
+
+//-----------------------------------------------------------------------------
+// Returns this monster's classification in the relationship table.
+//-----------------------------------------------------------------------------
+Class_T CNPC_Advisor::Classify()
+{
+ return CLASS_COMBINE;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+bool CNPC_Advisor::IsHeavyDamage( const CTakeDamageInfo &info )
+{
+ return (info.GetDamage() > 0);
+}
+
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Advisor::StartLevitatingObjects()
+{
+ if ( !m_pLevitateController )
+ {
+ m_pLevitateController = physenv->CreateMotionController( &m_levitateCallback );
+ }
+
+ m_pLevitateController->ClearObjects();
+
+ int nCount = m_physicsObjects.Count();
+ for ( int i = 0; i < nCount; i++ )
+ {
+ CBaseEntity *pEnt = m_physicsObjects.Element( i );
+ if ( !pEnt )
+ continue;
+
+ //NDebugOverlay::Box( pEnt->GetAbsOrigin(), pEnt->CollisionProp()->OBBMins(), pEnt->CollisionProp()->OBBMaxs(), 0, 255, 0, 1, 0.1 );
+
+ IPhysicsObject *pPhys = pEnt->VPhysicsGetObject();
+ if ( pPhys && pPhys->IsMoveable() )
+ {
+ m_pLevitateController->AttachObject( pPhys, false );
+ pPhys->Wake();
+ }
+ }
+}
+
+// This function is used by both version of the entity finder below
+bool CNPC_Advisor::CanLevitateEntity( CBaseEntity *pEntity, int minMass, int maxMass )
+{
+ if (!pEntity || pEntity->IsNPC())
+ return false;
+
+ IPhysicsObject *pPhys = pEntity->VPhysicsGetObject();
+ if (!pPhys)
+ return false;
+
+ float mass = pPhys->GetMass();
+
+ return ( mass >= minMass &&
+ mass <= maxMass &&
+ //pEntity->VPhysicsGetObject()->IsAsleep() &&
+ pPhys->IsMoveable() /* &&
+ !DidThrow(pEntity) */ );
+}
+
+#if NPC_ADVISOR_HAS_BEHAVIOR
+// find an object to throw at the player and start the warning on it. Return object's
+// pointer if we got something. Otherwise, return NULL if nothing left to throw. Will
+// always leave the prepared object at the head of m_hvStagedEnts
+CBaseEntity *CNPC_Advisor::ThrowObjectPrepare()
+{
+
+ CBaseEntity *pThrowable = NULL;
+ while (m_hvStagedEnts.Count() > 0)
+ {
+ pThrowable = m_hvStagedEnts[0];
+
+ if (pThrowable)
+ {
+ IPhysicsObject *pPhys = pThrowable->VPhysicsGetObject();
+ if ( !pPhys )
+ {
+ // reject!
+
+ Write_BeamOff(m_hvStagedEnts[0]);
+ pThrowable = NULL;
+ }
+ }
+
+ // if we still have pThrowable...
+ if (pThrowable)
+ {
+ // we're good
+ break;
+ }
+ else
+ {
+ m_hvStagedEnts.Remove(0);
+ }
+ }
+
+ if (pThrowable)
+ {
+ Assert( pThrowable->VPhysicsGetObject() );
+
+ // play the sound, attach the light, fire the trigger
+ EmitSound( "NPC_Advisor.ObjectChargeUp" );
+
+ m_OnThrowWarn.FireOutput(pThrowable,this);
+ m_flThrowPhysicsTime = gpGlobals->curtime + advisor_throw_warn_time.GetFloat();
+
+ if ( GetEnemy() )
+ {
+ PreHurlClearTheWay( pThrowable, GetEnemy()->EyePosition() );
+ }
+
+ return pThrowable;
+ }
+ else // we had nothing to throw
+ {
+ return NULL;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Advisor::StartTask( const Task_t *pTask )
+{
+ switch ( pTask->iTask )
+ {
+ // DVS: TODO: if this gets expensive we can start caching the results and doing it less often.
+ case TASK_ADVISOR_FIND_OBJECTS:
+ {
+ // if we have a trigger volume, use the contents of that. If not, use a hardcoded box (for debugging purposes)
+ // in both cases we validate the objects using the same helper funclet just above. When we can count on the
+ // trigger vol being there, we can elide the else{} clause here.
+
+ CBaseEntity *pVolume = m_hLevitationArea;
+ AssertMsg(pVolume, "Combine advisor needs 'levitationarea' key pointing to a trigger volume." );
+
+ if (!pVolume)
+ {
+ TaskFail( "No levitation area found!" );
+ break;
+ }
+
+ touchlink_t *touchroot = ( touchlink_t * )pVolume->GetDataObject( TOUCHLINK );
+ if ( touchroot )
+ {
+
+ m_physicsObjects.RemoveAll();
+
+ for ( touchlink_t *link = touchroot->nextLink; link != touchroot; link = link->nextLink )
+ {
+ CBaseEntity *pTouch = link->entityTouched;
+ if ( CanLevitateEntity( pTouch, 10, 220 ) )
+ {
+ if ( pTouch->GetMoveType() == MOVETYPE_VPHYSICS )
+ {
+ //Msg( " %d added %s\n", m_physicsObjects.Count(), STRING( list[i]->GetModelName() ) );
+ m_physicsObjects.AddToTail( pTouch );
+ }
+ }
+ }
+ }
+
+ /*
+ // this is the old mechanism, using a hardcoded box and an entity enumerator.
+ // since deprecated.
+
+ else
+ {
+ CBaseEntity *list[128];
+
+ m_physicsObjects.RemoveAll();
+
+ //NDebugOverlay::Box( GetAbsOrigin(), Vector( -408, -368, -188 ), Vector( 92, 208, 168 ), 255, 255, 0, 1, 5 );
+
+ // one-off class used to determine which entities we want from the UTIL_EntitiesInBox
+ class CAdvisorLevitateEntitiesEnum : public CFlaggedEntitiesEnum
+ {
+ public:
+ CAdvisorLevitateEntitiesEnum( CBaseEntity **pList, int listMax, int nMinMass, int nMaxMass )
+ : CFlaggedEntitiesEnum( pList, listMax, 0 ),
+ m_nMinMass( nMinMass ),
+ m_nMaxMass( nMaxMass )
+ {
+ }
+
+ virtual IterationRetval_t EnumElement( IHandleEntity *pHandleEntity )
+ {
+ CBaseEntity *pEntity = gEntList.GetBaseEntity( pHandleEntity->GetRefEHandle() );
+ if ( AdvisorCanLevitateEntity( pEntity, m_nMinMass, m_nMaxMass ) )
+ {
+ return CFlaggedEntitiesEnum::EnumElement( pHandleEntity );
+ }
+ return ITERATION_CONTINUE;
+ }
+
+ int m_nMinMass;
+ int m_nMaxMass;
+ };
+
+ CAdvisorLevitateEntitiesEnum levitateEnum( list, ARRAYSIZE( list ), 10, 220 );
+
+ int nCount = UTIL_EntitiesInBox( GetAbsOrigin() - Vector( 554, 368, 188 ), GetAbsOrigin() + Vector( 92, 208, 168 ), &levitateEnum );
+ for ( int i = 0; i < nCount; i++ )
+ {
+ //Msg( "%d found %s\n", m_physicsObjects.Count(), STRING( list[i]->GetModelName() ) );
+ if ( list[i]->GetMoveType() == MOVETYPE_VPHYSICS )
+ {
+ //Msg( " %d added %s\n", m_physicsObjects.Count(), STRING( list[i]->GetModelName() ) );
+ m_physicsObjects.AddToTail( list[i] );
+ }
+ }
+ }
+ */
+
+ if ( m_physicsObjects.Count() > 0 )
+ {
+ TaskComplete();
+ }
+ else
+ {
+ TaskFail( "No physics objects found!" );
+ }
+
+ break;
+ }
+
+ case TASK_ADVISOR_LEVITATE_OBJECTS:
+ {
+ StartLevitatingObjects();
+
+ m_flThrowPhysicsTime = gpGlobals->curtime + advisor_throw_rate.GetFloat();
+
+ break;
+ }
+
+ case TASK_ADVISOR_STAGE_OBJECTS:
+ {
+ // m_pickFailures = 0;
+ // clear out previously staged throwables
+ /*
+ for (int ii = m_hvStagedEnts.Count() - 1; ii >= 0 ; --ii)
+ {
+ m_hvStagedEnts[ii] = NULL;
+ }
+ */
+ Write_AllBeamsOff();
+ m_hvStagedEnts.RemoveAll();
+
+ m_OnPickingThrowable.FireOutput(NULL,this);
+ m_flStagingEnd = gpGlobals->curtime + pTask->flTaskData;
+
+ break;
+ }
+
+ // we're about to pelt the player with everything. Start the warning effect on the first object.
+ case TASK_ADVISOR_BARRAGE_OBJECTS:
+ {
+
+ CBaseEntity *pThrowable = ThrowObjectPrepare();
+
+ if (!pThrowable || m_hvStagedEnts.Count() < 1)
+ {
+ TaskFail( "Nothing to throw!" );
+ return;
+ }
+
+ m_vSavedLeadVel.Invalidate();
+
+ break;
+ }
+
+ case TASK_ADVISOR_PIN_PLAYER:
+ {
+
+ // should never be here
+ /*
+ Assert( m_hPlayerPinPos.IsValid() );
+ m_playerPinFailsafeTime = gpGlobals->curtime + 10.0f;
+
+ break;
+ */
+ }
+
+ default:
+ {
+ BaseClass::StartTask( pTask );
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// todo: find a way to guarantee that objects are made pickupable again when bailing out of a task
+//-----------------------------------------------------------------------------
+void CNPC_Advisor::RunTask( const Task_t *pTask )
+{
+
+ switch ( pTask->iTask )
+ {
+ // Raise up the objects that we found and then hold them.
+ case TASK_ADVISOR_LEVITATE_OBJECTS:
+ {
+ float flTimeToThrow = m_flThrowPhysicsTime - gpGlobals->curtime;
+ if ( flTimeToThrow < 0 )
+ {
+ TaskComplete();
+ return;
+ }
+
+ // set the top and bottom on the levitation volume from the entities. If we don't have
+ // both, zero it out so that we can use the old-style simpler mechanism.
+ if ( m_hLevitateGoal1 && m_hLevitateGoal2 )
+ {
+ m_levitateCallback.m_vecGoalPos1 = m_hLevitateGoal1->GetAbsOrigin();
+ m_levitateCallback.m_vecGoalPos2 = m_hLevitateGoal2->GetAbsOrigin();
+ // swap them if necessary (1 must be the bottom)
+ if (m_levitateCallback.m_vecGoalPos1.z > m_levitateCallback.m_vecGoalPos2.z)
+ {
+ swap(m_levitateCallback.m_vecGoalPos1,m_levitateCallback.m_vecGoalPos2);
+ }
+
+ m_levitateCallback.m_flFloat = 0.06f; // this is an absolute accumulation upon gravity
+ }
+ else
+ {
+ m_levitateCallback.m_vecGoalPos1.Invalidate();
+ m_levitateCallback.m_vecGoalPos2.Invalidate();
+
+ // the below two stanzas are used for old-style floating, which is linked
+ // to float up before thrown and down after
+ if ( flTimeToThrow > 2.0f )
+ {
+ m_levitateCallback.m_flFloat = 1.06f;
+ }
+ else
+ {
+ m_levitateCallback.m_flFloat = 0.94f;
+ }
+ }
+
+ /*
+ // Draw boxes around the objects we're levitating.
+ for ( int i = 0; i < m_physicsObjects.Count(); i++ )
+ {
+ CBaseEntity *pEnt = m_physicsObjects.Element( i );
+ if ( !pEnt )
+ continue; // The prop has been broken!
+
+ IPhysicsObject *pPhys = pEnt->VPhysicsGetObject();
+ if ( pPhys && pPhys->IsMoveable() )
+ {
+ NDebugOverlay::Box( pEnt->GetAbsOrigin(), pEnt->CollisionProp()->OBBMins(), pEnt->CollisionProp()->OBBMaxs(), 0, 255, 0, 1, 0.1 );
+ }
+ }*/
+
+ break;
+ }
+
+ // Pick a random object that we are levitating. If we have a clear LOS from that object
+ // to our enemy's eyes, choose that one to throw. Otherwise, keep looking.
+ case TASK_ADVISOR_STAGE_OBJECTS:
+ {
+ if (m_iStagingNum > m_hvStagingPositions.Count())
+ {
+ Warning( "Advisor tries to stage %d objects but only has %d positions named %s! Overriding.\n", m_iStagingNum, m_hvStagingPositions.Count(), m_iszStagingEntities );
+ m_iStagingNum = m_hvStagingPositions.Count() ;
+ }
+
+
+// advisor_staging_num
+
+ // in the future i'll distribute the staging chronologically. For now, yank all the objects at once.
+ if (m_hvStagedEnts.Count() < m_iStagingNum)
+ {
+ // pull another object
+ bool bDesperate = m_flStagingEnd - gpGlobals->curtime < 0.50f; // less than one half second left
+ CBaseEntity *pThrowable = PickThrowable(!bDesperate);
+ if (pThrowable)
+ {
+ // don't let the player take it from me
+ IPhysicsObject *pPhys = pThrowable->VPhysicsGetObject();
+ if ( pPhys )
+ {
+ // no pickup!
+ pPhys->SetGameFlags(pPhys->GetGameFlags() | FVPHYSICS_NO_PLAYER_PICKUP );;
+ }
+
+ m_hvStagedEnts.AddToTail( pThrowable );
+ Write_BeamOn(pThrowable);
+
+
+ DispatchParticleEffect( "advisor_object_charge", PATTACH_ABSORIGIN_FOLLOW,
+ pThrowable, 0,
+ false );
+ }
+ }
+
+
+ Assert(m_hvStagedEnts.Count() <= m_hvStagingPositions.Count());
+
+ // yank all objects into place
+ for (int ii = m_hvStagedEnts.Count() - 1 ; ii >= 0 ; --ii)
+ {
+
+ // just ignore lost objects (if the player destroys one, that's fine, leave a hole)
+ CBaseEntity *pThrowable = m_hvStagedEnts[ii];
+ if (pThrowable)
+ {
+ PullObjectToStaging(pThrowable, m_hvStagingPositions[ii]->GetAbsOrigin());
+ }
+ }
+
+ // are we done yet?
+ if (gpGlobals->curtime > m_flStagingEnd)
+ {
+ TaskComplete();
+ break;
+ }
+
+ break;
+ }
+
+ // Fling the object that we picked at our enemy's eyes!
+ case TASK_ADVISOR_BARRAGE_OBJECTS:
+ {
+ Assert(m_hvStagedEnts.Count() > 0);
+
+ // do I still have an enemy?
+ if ( !GetEnemy() )
+ {
+ // no? bail all the objects.
+ for (int ii = m_hvStagedEnts.Count() - 1 ; ii >=0 ; --ii)
+ {
+
+ IPhysicsObject *pPhys = m_hvStagedEnts[ii]->VPhysicsGetObject();
+ if ( pPhys )
+ {
+ pPhys->SetGameFlags(pPhys->GetGameFlags() & (~FVPHYSICS_NO_PLAYER_PICKUP) );
+ }
+ }
+
+ Write_AllBeamsOff();
+ m_hvStagedEnts.RemoveAll();
+
+ TaskFail( "Lost enemy" );
+ return;
+ }
+
+ // do I still have something to throw at the player?
+ CBaseEntity *pThrowable = m_hvStagedEnts[0];
+ while (!pThrowable)
+ { // player has destroyed whatever I planned to hit him with, get something else
+ if (m_hvStagedEnts.Count() > 0)
+ {
+ pThrowable = ThrowObjectPrepare();
+ }
+ else
+ {
+ TaskComplete();
+ break;
+ }
+ }
+
+ // If we've gone NULL, then opt out
+ if ( pThrowable == NULL )
+ {
+ TaskComplete();
+ break;
+ }
+
+ if ( (gpGlobals->curtime > m_flThrowPhysicsTime - advisor_throw_lead_prefetch_time.GetFloat()) &&
+ !m_vSavedLeadVel.IsValid() )
+ {
+ // save off the velocity we will use to lead the player a little early, so that if he jukes
+ // at the last moment he'll have a better shot of dodging the object.
+ m_vSavedLeadVel = GetEnemy()->GetAbsVelocity();
+ }
+
+ // if it's time to throw something, throw it and go on to the next one.
+ if (gpGlobals->curtime > m_flThrowPhysicsTime)
+ {
+ IPhysicsObject *pPhys = pThrowable->VPhysicsGetObject();
+ Assert(pPhys);
+
+ pPhys->SetGameFlags(pPhys->GetGameFlags() & (~FVPHYSICS_NO_PLAYER_PICKUP) );
+ HurlObjectAtPlayer(pThrowable,Vector(0,0,0)/*m_vSavedLeadVel*/);
+ m_flLastThrowTime = gpGlobals->curtime;
+ m_flThrowPhysicsTime = gpGlobals->curtime + 0.75f;
+ // invalidate saved lead for next time
+ m_vSavedLeadVel.Invalidate();
+
+ EmitSound( "NPC_Advisor.Blast" );
+
+ Write_BeamOff(m_hvStagedEnts[0]);
+ m_hvStagedEnts.Remove(0);
+ if (!ThrowObjectPrepare())
+ {
+ TaskComplete();
+ break;
+ }
+ }
+ else
+ {
+ // wait, bide time
+ // PullObjectToStaging(pThrowable, m_hvStagingPositions[ii]->GetAbsOrigin());
+ }
+
+ break;
+ }
+
+ case TASK_ADVISOR_PIN_PLAYER:
+ {
+ /*
+ // bail out if the pin entity went away.
+ CBaseEntity *pPinEnt = m_hPlayerPinPos;
+ if (!pPinEnt)
+ {
+ GetEnemy()->SetGravity(1.0f);
+ GetEnemy()->SetMoveType( MOVETYPE_WALK );
+ TaskComplete();
+ break;
+ }
+
+ // failsafe: don't do this for more than ten seconds.
+ if ( gpGlobals->curtime > m_playerPinFailsafeTime )
+ {
+ GetEnemy()->SetGravity(1.0f);
+ GetEnemy()->SetMoveType( MOVETYPE_WALK );
+ Warning( "Advisor did not leave PIN PLAYER mode. Aborting due to ten second failsafe!\n" );
+ TaskFail("Advisor did not leave PIN PLAYER mode. Aborting due to ten second failsafe!\n");
+ break;
+ }
+
+ // if the player isn't the enemy, bail out.
+ if ( !GetEnemy()->IsPlayer() )
+ {
+ GetEnemy()->SetGravity(1.0f);
+ GetEnemy()->SetMoveType( MOVETYPE_WALK );
+ TaskFail( "Player is not the enemy?!" );
+ break;
+ }
+
+ GetEnemy()->SetMoveType( MOVETYPE_FLY );
+ GetEnemy()->SetGravity(0);
+
+ // use exponential falloff to peg the player to the pin point
+ const Vector &desiredPos = pPinEnt->GetAbsOrigin();
+ const Vector &playerPos = GetEnemy()->GetAbsOrigin();
+
+ Vector displacement = desiredPos - playerPos;
+
+ float desiredDisplacementLen = ExponentialDecay(0.250f,gpGlobals->frametime);// * sqrt(displacementLen);
+
+ Vector nuPos = playerPos + (displacement * (1.0f - desiredDisplacementLen));
+
+ GetEnemy()->SetAbsOrigin( nuPos );
+
+ break;
+ */
+ }
+
+ default:
+ {
+ BaseClass::RunTask( pTask );
+ }
+ }
+}
+
+
+#endif
+
+// helper function for testing whether or not an avisor is allowed to grab an object
+static bool AdvisorCanPickObject(CBasePlayer *pPlayer, CBaseEntity *pEnt)
+{
+ Assert( pPlayer != NULL );
+
+ // Is the player carrying something?
+ CBaseEntity *pHeldObject = GetPlayerHeldEntity(pPlayer);
+
+ if( !pHeldObject )
+ {
+ pHeldObject = PhysCannonGetHeldEntity( pPlayer->GetActiveWeapon() );
+ }
+
+ if( pHeldObject == pEnt )
+ {
+ return false;
+ }
+
+ if ( pEnt->GetCollisionGroup() == COLLISION_GROUP_DEBRIS )
+ {
+ return false;
+ }
+
+ return true;
+}
+
+
+#if NPC_ADVISOR_HAS_BEHAVIOR
+//-----------------------------------------------------------------------------
+// Choose an object to throw.
+// param bRequireInView : if true, only accept objects that are in the player's fov.
+//
+// Can always return NULL.
+// todo priority_grab_name
+//-----------------------------------------------------------------------------
+CBaseEntity *CNPC_Advisor::PickThrowable( bool bRequireInView )
+{
+ CBasePlayer *pPlayer = ToBasePlayer( GetEnemy() );
+ Assert(pPlayer);
+ if (!pPlayer)
+ return NULL;
+
+ const int numObjs = m_physicsObjects.Count(); ///< total number of physics objects in my system
+ if (numObjs < 1)
+ return NULL; // bail out if nothing available
+
+
+ // used for require-in-view
+ Vector eyeForward, eyeOrigin;
+ if (pPlayer)
+ {
+ eyeOrigin = pPlayer->EyePosition();
+ pPlayer->EyeVectors(&eyeForward);
+ }
+ else
+ {
+ bRequireInView = false;
+ }
+
+ // filter-and-choose algorithm:
+ // build a list of candidates
+ Assert(numObjs < 128); /// I'll come back and utlvector this shortly -- wanted easier debugging
+ unsigned int candidates[128];
+ unsigned int numCandidates = 0;
+
+ if (!!m_iszPriorityEntityGroupName) // if the string isn't null
+ {
+ // first look to see if we have any priority objects.
+ for (int ii = 0 ; ii < numObjs ; ++ii )
+ {
+ CBaseEntity *pThrowEnt = m_physicsObjects[ii];
+ // Assert(pThrowEnt);
+ if (!pThrowEnt)
+ continue;
+
+ if (!pThrowEnt->NameMatches(m_iszPriorityEntityGroupName)) // if this is not a priority object
+ continue;
+
+ bool bCanPick = AdvisorCanPickObject( pPlayer, pThrowEnt ) && !m_hvStagedEnts.HasElement( m_physicsObjects[ii] );
+ if (!bCanPick)
+ continue;
+
+ // bCanPick guaranteed true here
+
+ if ( bRequireInView )
+ {
+ bCanPick = (pThrowEnt->GetAbsOrigin() - eyeOrigin).Dot(eyeForward) > 0;
+ }
+
+ if ( bCanPick )
+ {
+ candidates[numCandidates++] = ii;
+ }
+ }
+ }
+
+ // if we found no priority objects (or don't have a priority), just grab whatever
+ if (numCandidates == 0)
+ {
+ for (int ii = 0 ; ii < numObjs ; ++ii )
+ {
+ CBaseEntity *pThrowEnt = m_physicsObjects[ii];
+ // Assert(pThrowEnt);
+ if (!pThrowEnt)
+ continue;
+
+ bool bCanPick = AdvisorCanPickObject( pPlayer, pThrowEnt ) && !m_hvStagedEnts.HasElement( m_physicsObjects[ii] );
+ if (!bCanPick)
+ continue;
+
+ // bCanPick guaranteed true here
+
+ if ( bRequireInView )
+ {
+ bCanPick = (pThrowEnt->GetAbsOrigin() - eyeOrigin).Dot(eyeForward) > 0;
+ }
+
+ if ( bCanPick )
+ {
+ candidates[numCandidates++] = ii;
+ }
+ }
+ }
+
+ if ( numCandidates == 0 )
+ return NULL; // must have at least one candidate
+
+ // pick a random candidate.
+ int nRandomIndex = random->RandomInt( 0, numCandidates - 1 );
+ return m_physicsObjects[candidates[nRandomIndex]];
+
+}
+
+/*! \TODO
+ Correct bug where Advisor seemed to be throwing stuff at people's feet.
+ This is because the object was falling slightly in between the staging
+ and when he threw it, and that downward velocity was getting accumulated
+ into the throw speed. This is temporarily fixed here by using SetVelocity
+ instead of AddVelocity, but the proper fix is to pin the object to its
+ staging point during the warn period. That will require maintaining a map
+ of throwables to their staging points during the throw task.
+*/
+//-----------------------------------------------------------------------------
+// Impart necessary force on any entity to make it clobber Gordon.
+// Also detaches from levitate controller.
+// The optional lead velocity parameter is for cases when we pre-save off the
+// player's speed, to make last-moment juking more effective
+//-----------------------------------------------------------------------------
+void CNPC_Advisor::HurlObjectAtPlayer( CBaseEntity *pEnt, const Vector &leadVel )
+{
+ IPhysicsObject *pPhys = pEnt->VPhysicsGetObject();
+
+ //
+ // Lead the target accurately. This encourages hiding behind cover
+ // and/or catching the thrown physics object!
+ //
+ Vector vecObjOrigin = pEnt->CollisionProp()->WorldSpaceCenter();
+ Vector vecEnemyPos = GetEnemy()->EyePosition();
+ // disabled -- no longer compensate for gravity: // vecEnemyPos.y += 12.0f;
+
+// const Vector &leadVel = pLeadVelocity ? *pLeadVelocity : GetEnemy()->GetAbsVelocity();
+
+ Vector vecDelta = vecEnemyPos - vecObjOrigin;
+ float flDist = vecDelta.Length();
+
+ float flVelocity = advisor_throw_velocity.GetFloat();
+
+ if ( flVelocity == 0 )
+ {
+ flVelocity = 1000;
+ }
+
+ float flFlightTime = flDist / flVelocity;
+
+ Vector vecThrowAt = vecEnemyPos + flFlightTime * leadVel;
+ Vector vecThrowDir = vecThrowAt - vecObjOrigin;
+ VectorNormalize( vecThrowDir );
+
+ Vector vecVelocity = flVelocity * vecThrowDir;
+ pPhys->SetVelocity( &vecVelocity, NULL );
+
+ AddToThrownObjects(pEnt);
+
+ m_OnThrow.FireOutput(pEnt,this);
+
+}
+
+
+//-----------------------------------------------------------------------------
+// do a sweep from an object I'm about to throw, to the target, pushing aside
+// anything floating in the way.
+// TODO: this is probably a good profiling candidate.
+//-----------------------------------------------------------------------------
+void CNPC_Advisor::PreHurlClearTheWay( CBaseEntity *pThrowable, const Vector &toPos )
+{
+ // look for objects in the way of chucking.
+ CBaseEntity *list[128];
+ Ray_t ray;
+
+
+ float boundingRadius = pThrowable->BoundingRadius();
+
+ ray.Init( pThrowable->GetAbsOrigin(), toPos,
+ Vector(-boundingRadius,-boundingRadius,-boundingRadius),
+ Vector( boundingRadius, boundingRadius, boundingRadius) );
+
+ int nFoundCt = UTIL_EntitiesAlongRay( list, 128, ray, 0 );
+ AssertMsg(nFoundCt < 128, "Found more than 128 obstructions between advisor and Gordon while throwing. (safe to continue)\n");
+
+ // for each thing in the way that I levitate, but is not something I'm staging
+ // or throwing, push it aside.
+ for (int i = 0 ; i < nFoundCt ; ++i )
+ {
+ CBaseEntity *obstruction = list[i];
+ if ( obstruction != pThrowable &&
+ m_physicsObjects.HasElement( obstruction ) && // if it's floating
+ !m_hvStagedEnts.HasElement( obstruction ) && // and I'm not staging it
+ !DidThrow( obstruction ) ) // and I didn't just throw it
+ {
+ IPhysicsObject *pPhys = obstruction->VPhysicsGetObject();
+ Assert(pPhys);
+
+ // this is an object we want to push out of the way. Compute a vector perpendicular
+ // to the path of the throwables's travel, and thrust the object along that vector.
+ Vector thrust;
+ CalcClosestPointOnLine( obstruction->GetAbsOrigin(),
+ pThrowable->GetAbsOrigin(),
+ toPos,
+ thrust );
+ // "thrust" is now the closest point on the line to the obstruction.
+ // compute the difference to get the direction of impulse
+ thrust = obstruction->GetAbsOrigin() - thrust;
+
+ // and renormalize it to equal a giant kick out of the way
+ // (which I'll say is about ten feet per second -- if we want to be
+ // more precise we could do some kind of interpolation based on how
+ // far away the object is)
+ float thrustLen = thrust.Length();
+ if (thrustLen > 0.0001f)
+ {
+ thrust *= advisor_throw_clearout_vel.GetFloat() / thrustLen;
+ }
+
+ // heave!
+ pPhys->AddVelocity( &thrust, NULL );
+ }
+ }
+
+/*
+
+ // Otherwise only help out a little
+ Vector extents = Vector(256, 256, 256);
+ Ray_t ray;
+ ray.Init( vecStartPoint, vecStartPoint + 2048 * vecVelDir, -extents, extents );
+ int nCount = UTIL_EntitiesAlongRay( list, 1024, ray, FL_NPC | FL_CLIENT );
+ for ( int i = 0; i < nCount; i++ )
+ {
+ if ( !IsAttractiveTarget( list[i] ) )
+ continue;
+
+ VectorSubtract( list[i]->WorldSpaceCenter(), vecStartPoint, vecDelta );
+ distance = VectorNormalize( vecDelta );
+ flDot = DotProduct( vecDelta, vecVelDir );
+
+ if ( flDot > flMaxDot )
+ {
+ if ( distance < flBestDist )
+ {
+ pBestTarget = list[i];
+ flBestDist = distance;
+ }
+ }
+ }
+
+*/
+
+}
+
+/*
+// commented out because unnecessary: we will do this during the DidThrow check
+
+//-----------------------------------------------------------------------------
+// clean out the recently thrown objects array
+//-----------------------------------------------------------------------------
+void CNPC_Advisor::PurgeThrownObjects()
+{
+ float threeSecondsAgo = gpGlobals->curtime - 3.0f; // two seconds ago
+
+ for (int ii = 0 ; ii < kMaxThrownObjectsTracked ; ++ii)
+ {
+ if ( m_haRecentlyThrownObjects[ii].IsValid() &&
+ m_flaRecentlyThrownObjectTimes[ii] < threeSecondsAgo )
+ {
+ m_haRecentlyThrownObjects[ii].Set(NULL);
+ }
+ }
+
+}
+*/
+
+
+//-----------------------------------------------------------------------------
+// true iff an advisor threw the object in the last three seconds
+//-----------------------------------------------------------------------------
+bool CNPC_Advisor::DidThrow(const CBaseEntity *pEnt)
+{
+ // look through all my objects and see if they match this entity. Incidentally if
+ // they're more than three seconds old, purge them.
+ float threeSecondsAgo = gpGlobals->curtime - 3.0f;
+
+ for (int ii = 0 ; ii < kMaxThrownObjectsTracked ; ++ii)
+ {
+ // if object is old, skip it.
+ CBaseEntity *pTestEnt = m_haRecentlyThrownObjects[ii];
+
+ if ( pTestEnt )
+ {
+ if ( m_flaRecentlyThrownObjectTimes[ii] < threeSecondsAgo )
+ {
+ m_haRecentlyThrownObjects[ii].Set(NULL);
+ continue;
+ }
+ else if (pTestEnt == pEnt)
+ {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Advisor::AddToThrownObjects(CBaseEntity *pEnt)
+{
+ Assert(pEnt);
+
+ // try to find an empty slot, or if none exists, the oldest object
+ int oldestThrownObject = 0;
+ for (int ii = 0 ; ii < kMaxThrownObjectsTracked ; ++ii)
+ {
+ if (m_haRecentlyThrownObjects[ii].IsValid())
+ {
+ if (m_flaRecentlyThrownObjectTimes[ii] < m_flaRecentlyThrownObjectTimes[oldestThrownObject])
+ {
+ oldestThrownObject = ii;
+ }
+ }
+ else
+ { // just use this one
+ oldestThrownObject = ii;
+ break;
+ }
+ }
+
+ m_haRecentlyThrownObjects[oldestThrownObject] = pEnt;
+ m_flaRecentlyThrownObjectTimes[oldestThrownObject] = gpGlobals->curtime;
+
+}
+
+
+//-----------------------------------------------------------------------------
+// Drag a particular object towards its staging location.
+//-----------------------------------------------------------------------------
+void CNPC_Advisor::PullObjectToStaging( CBaseEntity *pEnt, const Vector &stagingPos )
+{
+ IPhysicsObject *pPhys = pEnt->VPhysicsGetObject();
+ Assert(pPhys);
+
+ Vector curPos = pEnt->CollisionProp()->WorldSpaceCenter();
+ Vector displacement = stagingPos - curPos;
+
+ // quick and dirty -- use exponential decay to haul the object into place
+ // ( a better looking solution would be to use a spring system )
+
+ float desiredDisplacementLen = ExponentialDecay(STAGING_OBJECT_FALLOFF_TIME, gpGlobals->frametime);// * sqrt(displacementLen);
+
+ Vector vel; AngularImpulse angimp;
+ pPhys->GetVelocity(&vel,&angimp);
+
+ vel = (1.0f / gpGlobals->frametime)*(displacement * (1.0f - desiredDisplacementLen));
+ pPhys->SetVelocity(&vel,&angimp);
+}
+
+
+
+#endif
+
+int CNPC_Advisor::OnTakeDamage( const CTakeDamageInfo &info )
+{
+ // Clip our max
+ CTakeDamageInfo newInfo = info;
+ if ( newInfo.GetDamage() > 20.0f )
+ {
+ newInfo.SetDamage( 20.0f );
+ }
+
+ // Hack to make him constantly flinch
+ m_flNextFlinchTime = gpGlobals->curtime;
+
+ const float oldLastDamageTime = m_flLastDamageTime;
+ int retval = BaseClass::OnTakeDamage(newInfo);
+
+ // we have a special reporting output
+ if ( oldLastDamageTime != gpGlobals->curtime )
+ {
+ // only fire once per frame
+
+ m_OnHealthIsNow.Set( GetHealth(), newInfo.GetAttacker(), this);
+ }
+
+ return retval;
+}
+
+
+
+
+#if NPC_ADVISOR_HAS_BEHAVIOR
+//-----------------------------------------------------------------------------
+// Returns the best new schedule for this NPC based on current conditions.
+//-----------------------------------------------------------------------------
+int CNPC_Advisor::SelectSchedule()
+{
+ if ( IsInAScript() )
+ return SCHED_ADVISOR_IDLE_STAND;
+
+ switch ( m_NPCState )
+ {
+ case NPC_STATE_IDLE:
+ case NPC_STATE_ALERT:
+ {
+ return SCHED_ADVISOR_IDLE_STAND;
+ }
+
+ case NPC_STATE_COMBAT:
+ {
+ if ( GetEnemy() && GetEnemy()->IsAlive() )
+ {
+ if ( false /* m_hPlayerPinPos.IsValid() */ )
+ return SCHED_ADVISOR_TOSS_PLAYER;
+ else
+ return SCHED_ADVISOR_COMBAT;
+
+ }
+
+ return SCHED_ADVISOR_IDLE_STAND;
+ }
+ }
+
+ return BaseClass::SelectSchedule();
+}
+
+
+//-----------------------------------------------------------------------------
+// return the position where an object should be staged before throwing
+//-----------------------------------------------------------------------------
+Vector CNPC_Advisor::GetThrowFromPos( CBaseEntity *pEnt )
+{
+ Assert(pEnt);
+ Assert(pEnt->VPhysicsGetObject());
+ const CCollisionProperty *cProp = pEnt->CollisionProp();
+ Assert(cProp);
+
+ float effecRadius = cProp->BoundingRadius(); // radius of object (important for kickout)
+ float howFarInFront = advisor_throw_stage_distance.GetFloat() + effecRadius * 1.43f;// clamp(lenToPlayer - posDist + effecRadius,effecRadius*2,90.f + effecRadius);
+
+ Vector fwd;
+ GetVectors(&fwd,NULL,NULL);
+
+ return GetAbsOrigin() + fwd*howFarInFront;
+}
+#endif
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Advisor::Precache()
+{
+ BaseClass::Precache();
+
+ PrecacheModel( STRING( GetModelName() ) );
+
+#if NPC_ADVISOR_HAS_BEHAVIOR
+ PrecacheModel( "sprites/lgtning.vmt" );
+#endif
+
+ PrecacheScriptSound( "NPC_Advisor.Blast" );
+ PrecacheScriptSound( "NPC_Advisor.Gib" );
+ PrecacheScriptSound( "NPC_Advisor.Idle" );
+ PrecacheScriptSound( "NPC_Advisor.Alert" );
+ PrecacheScriptSound( "NPC_Advisor.Die" );
+ PrecacheScriptSound( "NPC_Advisor.Pain" );
+ PrecacheScriptSound( "NPC_Advisor.ObjectChargeUp" );
+ PrecacheParticleSystem( "Advisor_Psychic_Beam" );
+ PrecacheParticleSystem( "advisor_object_charge" );
+ PrecacheModel("sprites/greenglow1.vmt");
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Advisor::IdleSound()
+{
+ EmitSound( "NPC_Advisor.Idle" );
+}
+
+
+void CNPC_Advisor::AlertSound()
+{
+ EmitSound( "NPC_Advisor.Alert" );
+}
+
+
+void CNPC_Advisor::PainSound( const CTakeDamageInfo &info )
+{
+ EmitSound( "NPC_Advisor.Pain" );
+}
+
+
+void CNPC_Advisor::DeathSound( const CTakeDamageInfo &info )
+{
+ EmitSound( "NPC_Advisor.Die" );
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+int CNPC_Advisor::DrawDebugTextOverlays()
+{
+ int nOffset = BaseClass::DrawDebugTextOverlays();
+ return nOffset;
+}
+
+
+#if NPC_ADVISOR_HAS_BEHAVIOR
+//-----------------------------------------------------------------------------
+// Determines which sounds the advisor cares about.
+//-----------------------------------------------------------------------------
+int CNPC_Advisor::GetSoundInterests()
+{
+ return SOUND_WORLD | SOUND_COMBAT | SOUND_PLAYER | SOUND_DANGER;
+}
+
+
+//-----------------------------------------------------------------------------
+// record the last time we heard a combat sound
+//-----------------------------------------------------------------------------
+bool CNPC_Advisor::QueryHearSound( CSound *pSound )
+{
+ // Disregard footsteps from our own class type
+ CBaseEntity *pOwner = pSound->m_hOwner;
+ if ( pOwner && pSound->IsSoundType( SOUND_COMBAT ) && pSound->SoundChannel() != SOUNDENT_CHANNEL_NPC_FOOTSTEP && pSound->m_hOwner.IsValid() && pOwner->IsPlayer() )
+ {
+ // Msg("Heard player combat.\n");
+ m_flLastPlayerAttackTime = gpGlobals->curtime;
+ }
+
+ return BaseClass::QueryHearSound(pSound);
+}
+
+//-----------------------------------------------------------------------------
+// designer hook for setting throw rate
+//-----------------------------------------------------------------------------
+void CNPC_Advisor::InputSetThrowRate( inputdata_t &inputdata )
+{
+ advisor_throw_rate.SetValue(inputdata.value.Float());
+}
+
+void CNPC_Advisor::InputSetStagingNum( inputdata_t &inputdata )
+{
+ m_iStagingNum = inputdata.value.Int();
+}
+
+//
+// cause the player to be pinned to a point in space
+//
+void CNPC_Advisor::InputPinPlayer( inputdata_t &inputdata )
+{
+ string_t targetname = inputdata.value.StringID();
+
+ // null string means designer is trying to unpin the player
+ if (!targetname)
+ {
+ m_hPlayerPinPos = NULL;
+ }
+
+ // otherwise try to look up the entity and make it a target.
+ CBaseEntity *pEnt = gEntList.FindEntityByName(NULL,targetname);
+
+ if (pEnt)
+ {
+ m_hPlayerPinPos = pEnt;
+ }
+ else
+ {
+ // if we couldn't find the target, just bail on the behavior.
+ Warning("Advisor tried to pin player to %s but that does not exist.\n", targetname.ToCStr());
+ m_hPlayerPinPos = NULL;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_Advisor::OnScheduleChange( void )
+{
+ Write_AllBeamsOff();
+ m_hvStagedEnts.RemoveAll();
+ BaseClass::OnScheduleChange();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_Advisor::GatherConditions( void )
+{
+ BaseClass::GatherConditions();
+
+ // Handle script state changes
+ bool bInScript = IsInAScript();
+ if ( ( m_bWasScripting && bInScript == false ) || ( m_bWasScripting == false && bInScript ) )
+ {
+ SetCondition( COND_ADVISOR_PHASE_INTERRUPT );
+ }
+
+ // Retain this
+ m_bWasScripting = bInScript;
+}
+
+//-----------------------------------------------------------------------------
+// designer hook for yanking an object into the air right now
+//-----------------------------------------------------------------------------
+void CNPC_Advisor::InputWrenchImmediate( inputdata_t &inputdata )
+{
+ string_t groupname = inputdata.value.StringID();
+
+ Assert(!!groupname);
+
+ // for all entities with that name that aren't floating, punt them at me and add them to the levitation
+
+ CBaseEntity *pEnt = NULL;
+
+ const Vector &myPos = GetAbsOrigin() + Vector(0,36.0f,0);
+
+ // conditional assignment: find an entity by name and save it into pEnt. Bail out when none are left.
+ while ( ( pEnt = gEntList.FindEntityByName(pEnt,groupname) ) != NULL )
+ {
+ // if I'm not already levitating it, and if I didn't just throw it
+ if (!m_physicsObjects.HasElement(pEnt) )
+ {
+ // add to levitation
+ IPhysicsObject *pPhys = pEnt->VPhysicsGetObject();
+ if ( pPhys )
+ {
+ // if the object isn't moveable, make it so.
+ if ( !pPhys->IsMoveable() )
+ {
+ pPhys->EnableMotion( true );
+ }
+
+ // first, kick it at me
+ Vector objectToMe;
+ pPhys->GetPosition(&objectToMe,NULL);
+ objectToMe = myPos - objectToMe;
+ // compute a velocity that will get it here in about a second
+ objectToMe /= (1.5f * gpGlobals->frametime);
+
+ objectToMe *= random->RandomFloat(0.25f,1.0f);
+
+ pPhys->SetVelocity( &objectToMe, NULL );
+
+ // add it to tracked physics objects
+ m_physicsObjects.AddToTail( pEnt );
+
+ m_pLevitateController->AttachObject( pPhys, false );
+ pPhys->Wake();
+ }
+ else
+ {
+ Warning( "Advisor tried to wrench %s, but it is not moveable!", pEnt->GetEntityName().ToCStr());
+ }
+ }
+ }
+
+}
+
+
+
+//-----------------------------------------------------------------------------
+// write a message turning a beam on
+//-----------------------------------------------------------------------------
+void CNPC_Advisor::Write_BeamOn( CBaseEntity *pEnt )
+{
+ Assert( pEnt );
+ EntityMessageBegin( this, true );
+ WRITE_BYTE( ADVISOR_MSG_START_BEAM );
+ WRITE_LONG( pEnt->entindex() );
+ MessageEnd();
+}
+
+//-----------------------------------------------------------------------------
+// write a message turning a beam off
+//-----------------------------------------------------------------------------
+void CNPC_Advisor::Write_BeamOff( CBaseEntity *pEnt )
+{
+ Assert( pEnt );
+ EntityMessageBegin( this, true );
+ WRITE_BYTE( ADVISOR_MSG_STOP_BEAM );
+ WRITE_LONG( pEnt->entindex() );
+ MessageEnd();
+}
+
+//-----------------------------------------------------------------------------
+// tell client to kill all beams
+//-----------------------------------------------------------------------------
+void CNPC_Advisor::Write_AllBeamsOff( void )
+{
+ EntityMessageBegin( this, true );
+ WRITE_BYTE( ADVISOR_MSG_STOP_ALL_BEAMS );
+ MessageEnd();
+}
+
+//-----------------------------------------------------------------------------
+// input wrapper around Write_BeamOn
+//-----------------------------------------------------------------------------
+void CNPC_Advisor::InputTurnBeamOn( inputdata_t &inputdata )
+{
+ // inputdata should specify a target
+ CBaseEntity *pTarget = gEntList.FindEntityByName( NULL, inputdata.value.StringID() );
+ if ( pTarget )
+ {
+ Write_BeamOn( pTarget );
+ }
+ else
+ {
+ Warning("InputTurnBeamOn could not find object %s", inputdata.value.String() );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// input wrapper around Write_BeamOff
+//-----------------------------------------------------------------------------
+void CNPC_Advisor::InputTurnBeamOff( inputdata_t &inputdata )
+{
+ // inputdata should specify a target
+ CBaseEntity *pTarget = gEntList.FindEntityByName( NULL, inputdata.value.StringID() );
+ if ( pTarget )
+ {
+ Write_BeamOff( pTarget );
+ }
+ else
+ {
+ Warning("InputTurnBeamOn could not find object %s", inputdata.value.String() );
+ }
+}
+
+
+void CNPC_Advisor::InputElightOn( inputdata_t &inputdata )
+{
+ EntityMessageBegin( this, true );
+ WRITE_BYTE( ADVISOR_MSG_START_ELIGHT );
+ MessageEnd();
+}
+
+void CNPC_Advisor::InputElightOff( inputdata_t &inputdata )
+{
+ EntityMessageBegin( this, true );
+ WRITE_BYTE( ADVISOR_MSG_STOP_ELIGHT );
+ MessageEnd();
+}
+#endif
+
+
+//==============================================================================================
+// MOTION CALLBACK
+//==============================================================================================
+CAdvisorLevitate::simresult_e CAdvisorLevitate::Simulate( IPhysicsMotionController *pController, IPhysicsObject *pObject, float deltaTime, Vector &linear, AngularImpulse &angular )
+{
+ // this function can be optimized to minimize branching if necessary (PPE branch prediction)
+ CNPC_Advisor *pAdvisor = static_cast<CNPC_Advisor *>(m_Advisor.Get());
+ Assert(pAdvisor);
+
+ if ( !OldStyle() )
+ { // independent movement of all objects
+ // if an object was recently thrown, just zero out its gravity.
+ if (pAdvisor->DidThrow(static_cast<CBaseEntity *>(pObject->GetGameData())))
+ {
+ linear = Vector( 0, 0, GetCurrentGravity() );
+
+ return SIM_GLOBAL_ACCELERATION;
+ }
+ else
+ {
+ Vector vel; AngularImpulse angvel;
+ pObject->GetVelocity(&vel,&angvel);
+ Vector pos;
+ pObject->GetPosition(&pos,NULL);
+ bool bMovingUp = vel.z > 0;
+
+ // if above top limit and moving up, move down. if below bottom limit and moving down, move up.
+ if (bMovingUp)
+ {
+ if (pos.z > m_vecGoalPos2.z)
+ {
+ // turn around move down
+ linear = Vector( 0, 0, Square((1.0f - m_flFloat)) * GetCurrentGravity() );
+ angular = Vector( 0, -5, 0 );
+ }
+ else
+ { // keep moving up
+ linear = Vector( 0, 0, (1.0f + m_flFloat) * GetCurrentGravity() );
+ angular = Vector( 0, 0, 10 );
+ }
+ }
+ else
+ {
+ if (pos.z < m_vecGoalPos1.z)
+ {
+ // turn around move up
+ linear = Vector( 0, 0, Square((1.0f + m_flFloat)) * GetCurrentGravity() );
+ angular = Vector( 0, 5, 0 );
+ }
+ else
+ { // keep moving down
+ linear = Vector( 0, 0, (1.0f - m_flFloat) * GetCurrentGravity() );
+ angular = Vector( 0, 0, 10 );
+ }
+ }
+
+ return SIM_GLOBAL_ACCELERATION;
+ }
+
+ //NDebugOverlay::Cross3D(pos,24.0f,255,255,0,true,0.04f);
+
+ }
+ else // old stateless technique
+ {
+ Warning("Advisor using old-style object movement!\n");
+
+ /* // obsolete
+ CBaseEntity *pEnt = (CBaseEntity *)pObject->GetGameData();
+ Vector vecDir1 = m_vecGoalPos1 - pEnt->GetAbsOrigin();
+ VectorNormalize( vecDir1 );
+
+ Vector vecDir2 = m_vecGoalPos2 - pEnt->GetAbsOrigin();
+ VectorNormalize( vecDir2 );
+ */
+
+ linear = Vector( 0, 0, m_flFloat * GetCurrentGravity() );// + m_flFloat * 0.5 * ( vecDir1 + vecDir2 );
+ angular = Vector( 0, 0, 10 );
+
+ return SIM_GLOBAL_ACCELERATION;
+ }
+
+}
+
+
+//==============================================================================================
+// ADVISOR PHYSICS DAMAGE TABLE
+//==============================================================================================
+static impactentry_t advisorLinearTable[] =
+{
+ { 100*100, 10 },
+ { 250*250, 25 },
+ { 350*350, 50 },
+ { 500*500, 75 },
+ { 1000*1000,100 },
+};
+
+static impactentry_t advisorAngularTable[] =
+{
+ { 50* 50, 10 },
+ { 100*100, 25 },
+ { 150*150, 50 },
+ { 200*200, 75 },
+};
+
+static impactdamagetable_t gAdvisorImpactDamageTable =
+{
+ advisorLinearTable,
+ advisorAngularTable,
+
+ ARRAYSIZE(advisorLinearTable),
+ ARRAYSIZE(advisorAngularTable),
+
+ 200*200,// minimum linear speed squared
+ 180*180,// minimum angular speed squared (360 deg/s to cause spin/slice damage)
+ 15, // can't take damage from anything under 15kg
+
+ 10, // anything less than 10kg is "small"
+ 5, // never take more than 1 pt of damage from anything under 15kg
+ 128*128,// <15kg objects must go faster than 36 in/s to do damage
+
+ 45, // large mass in kg
+ 2, // large mass scale (anything over 500kg does 4X as much energy to read from damage table)
+ 1, // large mass falling scale
+ 0, // my min velocity
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : const impactdamagetable_t
+//-----------------------------------------------------------------------------
+const impactdamagetable_t &CNPC_Advisor::GetPhysicsImpactDamageTable( void )
+{
+ return advisor_use_impact_table.GetBool() ? gAdvisorImpactDamageTable : BaseClass::GetPhysicsImpactDamageTable();
+}
+
+
+
+#if NPC_ADVISOR_HAS_BEHAVIOR
+//-----------------------------------------------------------------------------
+//
+// Schedules
+//
+//-----------------------------------------------------------------------------
+AI_BEGIN_CUSTOM_NPC( npc_advisor, CNPC_Advisor )
+
+ DECLARE_TASK( TASK_ADVISOR_FIND_OBJECTS )
+ DECLARE_TASK( TASK_ADVISOR_LEVITATE_OBJECTS )
+ /*
+ DECLARE_TASK( TASK_ADVISOR_PICK_THROW_OBJECT )
+ DECLARE_TASK( TASK_ADVISOR_THROW_OBJECT )
+ */
+
+ DECLARE_CONDITION( COND_ADVISOR_PHASE_INTERRUPT ) // A stage has interrupted us
+
+ DECLARE_TASK( TASK_ADVISOR_STAGE_OBJECTS ) // haul all the objects into the throw-from slots
+ DECLARE_TASK( TASK_ADVISOR_BARRAGE_OBJECTS ) // hurl all the objects in sequence
+
+ DECLARE_TASK( TASK_ADVISOR_PIN_PLAYER ) // pinion the player to a point in space
+
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_ADVISOR_COMBAT,
+
+ " Tasks"
+ " TASK_ADVISOR_FIND_OBJECTS 0"
+ " TASK_ADVISOR_LEVITATE_OBJECTS 0"
+ " TASK_ADVISOR_STAGE_OBJECTS 1"
+ " TASK_ADVISOR_BARRAGE_OBJECTS 0"
+ " "
+ " Interrupts"
+ " COND_ADVISOR_PHASE_INTERRUPT"
+ " COND_ENEMY_DEAD"
+ )
+
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_ADVISOR_IDLE_STAND,
+
+ " Tasks"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
+ " TASK_WAIT 3"
+ ""
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_SEE_FEAR"
+ " COND_ADVISOR_PHASE_INTERRUPT"
+ )
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_ADVISOR_TOSS_PLAYER,
+
+ " Tasks"
+ " TASK_ADVISOR_FIND_OBJECTS 0"
+ " TASK_ADVISOR_LEVITATE_OBJECTS 0"
+ " TASK_ADVISOR_PIN_PLAYER 0"
+ " "
+ " Interrupts"
+ )
+
+AI_END_CUSTOM_NPC()
+#endif
diff --git a/mp/src/game/server/episodic/npc_combine_cannon.cpp b/mp/src/game/server/episodic/npc_combine_cannon.cpp
index e4c3ba89..18ae490e 100644
--- a/mp/src/game/server/episodic/npc_combine_cannon.cpp
+++ b/mp/src/game/server/episodic/npc_combine_cannon.cpp
@@ -1,1335 +1,1335 @@
-//========= Copyright Valve Corporation, All rights reserved. ============//
-//
-// Purpose:
-//
-// $NoKeywords: $
-//=============================================================================//
-
-#include "cbase.h"
-#include "ai_basenpc.h"
-#include "ammodef.h"
-#include "ai_memory.h"
-#include "weapon_rpg.h"
-#include "effect_color_tables.h"
-#include "te_effect_dispatch.h"
-
-// memdbgon must be the last include file in a .cpp file!!!
-#include "tier0/memdbgon.h"
-
-extern const char* g_pModelNameLaser;
-
-// No model, impervious to damage.
-#define SF_STARTDISABLED (1 << 19)
-
-#define CANNON_PAINT_ENEMY_TIME 1.0f
-#define CANNON_SUBSEQUENT_PAINT_TIME 0.4f
-#define CANNON_PAINT_NPC_TIME_NOISE 1.0f
-
-#define NUM_ANCILLARY_BEAMS 4
-
-int gHaloTexture = 0;
-
-//-----------------------------------------------------------------------------
-//
-// Combine Cannon
-//
-//-----------------------------------------------------------------------------
-class CNPC_Combine_Cannon : public CAI_BaseNPC
-{
- DECLARE_CLASS( CNPC_Combine_Cannon, CAI_BaseNPC );
-
-public:
- CNPC_Combine_Cannon( void );
- virtual void Precache( void );
- virtual void Spawn( void );
- virtual Class_T Classify( void );
- virtual float MaxYawSpeed( void );
- virtual Vector EyePosition( void );
- virtual void UpdateOnRemove( void );
- virtual int OnTakeDamage_Alive( const CTakeDamageInfo &info );
- virtual bool QuerySeeEntity( CBaseEntity *pEntity, bool bOnlyHateOrFearIfNPC = false );
- virtual void StartTask( const Task_t *pTask );
- virtual void RunTask( const Task_t *pTask );
- virtual int RangeAttack1Conditions( float flDot, float flDist );
- virtual int SelectSchedule( void );
- virtual int TranslateSchedule( int scheduleType );
- virtual void PrescheduleThink( void );
- virtual bool FCanCheckAttacks ( void );
- virtual int Restore( IRestore &restore );
- virtual void OnScheduleChange( void );
- virtual bool FVisible( CBaseEntity *pEntity, int traceMask = MASK_BLOCKLOS, CBaseEntity **ppBlocker = NULL );
-
- virtual bool WeaponLOSCondition( const Vector &ownerPos, const Vector &targetPos, bool bSetConditions) { return true; }
-
- virtual int GetSoundInterests( void ) { return (SOUND_PLAYER|SOUND_COMBAT|SOUND_DANGER); }
-
- virtual bool ShouldNotDistanceCull( void ) { return true; }
-
- virtual void UpdateEfficiency( bool bInPVS ) { SetEfficiency( ( GetSleepState() != AISS_AWAKE ) ? AIE_DORMANT : AIE_NORMAL ); SetMoveEfficiency( AIME_NORMAL ); }
-
- virtual const char *GetTracerType( void ) { return "HelicopterTracer"; }
-
-private:
-
- void ScopeGlint( void );
- void AdjustShotPosition( CBaseEntity *pTarget, Vector *vecIn );
-
- float GetRefireTime( void ) { return 0.1f; }
-
- bool IsLaserOn( void ) { return m_pBeam != NULL; }
- bool FireBullet( const Vector &vecTarget, bool bDirectShot );
- Vector DesiredBodyTarget( CBaseEntity *pTarget );
- Vector LeadTarget( CBaseEntity *pTarget );
- Vector GetBulletOrigin( void );
-
- static const char *pAttackSounds[];
-
- void ClearTargetGroup( void );
-
- float GetWaitTimePercentage( float flTime, bool fLinear );
-
- void GetPaintAim( const Vector &vecStart, const Vector &vecGoal, float flParameter, Vector *pProgress );
-
- bool VerifyShot( CBaseEntity *pTarget );
-
- void SetSweepTarget( const char *pszTarget );
-
- // Inputs
- void InputEnableSniper( inputdata_t &inputdata );
- void InputDisableSniper( inputdata_t &inputdata );
-
- void LaserOff( void );
- void LaserOn( const Vector &vecTarget, const Vector &vecDeviance );
-
- void PaintTarget( const Vector &vecTarget, float flPaintTime );
-
-private:
-
- void CreateLaser( void );
- void CreateAncillaryBeams( void );
- void UpdateAncillaryBeams( float flConvergencePerc, const Vector &vecOrigin, const Vector &vecBasis );
-
- int m_iAmmoType;
- float m_flBarrageDuration;
- Vector m_vecPaintCursor;
- float m_flPaintTime;
-
- CHandle<CBeam> m_pBeam;
- CHandle<CBeam> m_pAncillaryBeams[NUM_ANCILLARY_BEAMS];
- EHANDLE m_hBarrageTarget;
-
- bool m_fEnabled;
- Vector m_vecPaintStart; // used to track where a sweep starts for the purpose of interpolating.
- float m_flTimeLastAttackedPlayer;
- float m_flTimeLastShotMissed;
- float m_flSightDist;
-
- DEFINE_CUSTOM_AI;
-
- DECLARE_DATADESC();
-};
-
-LINK_ENTITY_TO_CLASS( npc_combine_cannon, CNPC_Combine_Cannon );
-
-//=========================================================
-//=========================================================
-BEGIN_DATADESC( CNPC_Combine_Cannon )
-
- DEFINE_FIELD( m_fEnabled, FIELD_BOOLEAN ),
- DEFINE_FIELD( m_vecPaintStart, FIELD_POSITION_VECTOR ),
- DEFINE_FIELD( m_flPaintTime, FIELD_TIME ),
- DEFINE_FIELD( m_vecPaintCursor, FIELD_POSITION_VECTOR ),
- DEFINE_FIELD( m_pBeam, FIELD_EHANDLE ),
- DEFINE_FIELD( m_flTimeLastAttackedPlayer, FIELD_TIME ),
- DEFINE_FIELD( m_flTimeLastShotMissed, FIELD_TIME ),
- DEFINE_FIELD( m_iAmmoType, FIELD_INTEGER ),
- DEFINE_FIELD( m_flBarrageDuration, FIELD_TIME ),
- DEFINE_FIELD( m_hBarrageTarget, FIELD_EHANDLE ),
-
- DEFINE_ARRAY( m_pAncillaryBeams, FIELD_EHANDLE, NUM_ANCILLARY_BEAMS ),
-
- DEFINE_KEYFIELD( m_flSightDist, FIELD_FLOAT, "sightdist" ),
- // Inputs
- DEFINE_INPUTFUNC( FIELD_VOID, "EnableSniper", InputEnableSniper ),
- DEFINE_INPUTFUNC( FIELD_VOID, "DisableSniper", InputDisableSniper ),
-
-END_DATADESC()
-
-
-//=========================================================
-// Private conditions
-//=========================================================
-enum Sniper_Conds
-{
- COND_CANNON_ENABLED = LAST_SHARED_CONDITION,
- COND_CANNON_DISABLED,
- COND_CANNON_NO_SHOT,
-};
-
-
-//=========================================================
-// schedules
-//=========================================================
-enum
-{
- SCHED_CANNON_CAMP = LAST_SHARED_SCHEDULE,
- SCHED_CANNON_ATTACK,
- SCHED_CANNON_DISABLEDWAIT,
- SCHED_CANNON_SNAPATTACK,
-};
-
-//=========================================================
-// tasks
-//=========================================================
-enum
-{
- TASK_CANNON_PAINT_ENEMY = LAST_SHARED_TASK,
- TASK_CANNON_PAINT_DECOY,
- TASK_CANNON_ATTACK_CURSOR,
-};
-
-//-----------------------------------------------------------------------------
-// Constructor
-//-----------------------------------------------------------------------------
-CNPC_Combine_Cannon::CNPC_Combine_Cannon( void ) :
- m_pBeam( NULL ),
- m_hBarrageTarget( NULL )
-{
-#ifdef _DEBUG
- m_vecPaintCursor.Init();
- m_vecPaintStart.Init();
-#endif
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-bool CNPC_Combine_Cannon::QuerySeeEntity( CBaseEntity *pEntity, bool bOnlyHateOrFearIfNPC )
-{
- Disposition_t disp = IRelationType(pEntity);
- if ( disp != D_HT )
- {
- // Don't bother with anything I wouldn't shoot.
- return false;
- }
-
- if ( !FInViewCone(pEntity) )
- {
- // Yes, this does call FInViewCone twice a frame for all entities checked for
- // visibility, but doing this allows us to cut out a bunch of traces that would
- // be done by VerifyShot for entities that aren't even in our viewcone.
- return false;
- }
-
- if ( VerifyShot( pEntity ) )
- return BaseClass::QuerySeeEntity( pEntity, bOnlyHateOrFearIfNPC );
-
- return false;
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Hide the beams
-//-----------------------------------------------------------------------------
-void CNPC_Combine_Cannon::LaserOff( void )
-{
- if ( m_pBeam != NULL )
- {
- m_pBeam->TurnOn();
- }
-
- for ( int i = 0; i < NUM_ANCILLARY_BEAMS; i++ )
- {
- if ( m_pAncillaryBeams[i] == NULL )
- continue;
-
- m_pAncillaryBeams[i]->TurnOn();
- }
-
- SetNextThink( gpGlobals->curtime + 0.1f );
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Switch on the laser and point it at a direction
-//-----------------------------------------------------------------------------
-void CNPC_Combine_Cannon::LaserOn( const Vector &vecTarget, const Vector &vecDeviance )
-{
- if ( m_pBeam != NULL )
- {
- m_pBeam->TurnOff();
-
- // Don't aim right at the guy right now.
- Vector vecInitialAim;
-
- if( vecDeviance == vec3_origin )
- {
- // Start the aim where it last left off!
- vecInitialAim = m_vecPaintCursor;
- }
- else
- {
- vecInitialAim = vecTarget;
- }
-
- vecInitialAim.x += random->RandomFloat( -vecDeviance.x, vecDeviance.x );
- vecInitialAim.y += random->RandomFloat( -vecDeviance.y, vecDeviance.y );
- vecInitialAim.z += random->RandomFloat( -vecDeviance.z, vecDeviance.z );
-
- m_pBeam->SetStartPos( GetBulletOrigin() );
- m_pBeam->SetEndPos( vecInitialAim );
-
- m_vecPaintStart = vecInitialAim;
- }
-
- for ( int i = 0; i < NUM_ANCILLARY_BEAMS; i++ )
- {
- if ( m_pAncillaryBeams[i] == NULL )
- continue;
-
- m_pAncillaryBeams[i]->TurnOff();
- }
-}
-
-//-----------------------------------------------------------------------------
-// Crikey!
-//-----------------------------------------------------------------------------
-float CNPC_Combine_Cannon::GetWaitTimePercentage( float flTime, bool fLinear )
-{
- float flElapsedTime;
- float flTimeParameter;
-
- flElapsedTime = flTime - (GetWaitFinishTime() - gpGlobals->curtime);
-
- flTimeParameter = ( flElapsedTime / flTime );
-
- if( fLinear )
- {
- return flTimeParameter;
- }
- else
- {
- return (1 + sin( (M_PI * flTimeParameter) - (M_PI / 2) ) ) / 2;
- }
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Combine_Cannon::GetPaintAim( const Vector &vecStart, const Vector &vecGoal, float flParameter, Vector *pProgress )
-{
- // Quaternions
- Vector vecIdealDir;
- QAngle vecIdealAngles;
- QAngle vecCurrentAngles;
- Vector vecCurrentDir;
- Vector vecBulletOrigin = GetBulletOrigin();
-
- // vecIdealDir is where the gun should be aimed when the painting
- // time is up. This can be approximate. This is only for drawing the
- // laser, not actually aiming the weapon. A large discrepancy will look
- // bad, though.
- vecIdealDir = vecGoal - vecBulletOrigin;
- VectorNormalize(vecIdealDir);
-
- // Now turn vecIdealDir into angles!
- VectorAngles( vecIdealDir, vecIdealAngles );
-
- // This is the vector of the beam's current aim.
- vecCurrentDir = m_vecPaintStart - vecBulletOrigin;
- VectorNormalize(vecCurrentDir);
-
- // Turn this to angles, too.
- VectorAngles( vecCurrentDir, vecCurrentAngles );
-
- Quaternion idealQuat;
- Quaternion currentQuat;
- Quaternion aimQuat;
-
- AngleQuaternion( vecIdealAngles, idealQuat );
- AngleQuaternion( vecCurrentAngles, currentQuat );
-
- QuaternionSlerp( currentQuat, idealQuat, flParameter, aimQuat );
-
- QuaternionAngles( aimQuat, vecCurrentAngles );
-
- // Rebuild the current aim vector.
- AngleVectors( vecCurrentAngles, &vecCurrentDir );
-
- *pProgress = vecCurrentDir;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CNPC_Combine_Cannon::CreateLaser( void )
-{
- if ( m_pBeam != NULL )
- return;
-
- m_pBeam = CBeam::BeamCreate( g_pModelNameLaser, 2.0f );
-
- m_pBeam->SetColor( 0, 100, 255 );
-
- m_pBeam->PointsInit( vec3_origin, GetBulletOrigin() );
- m_pBeam->SetBrightness( 255 );
- m_pBeam->SetNoise( 0 );
- m_pBeam->SetWidth( 1.0f );
- m_pBeam->SetEndWidth( 0 );
- m_pBeam->SetScrollRate( 0 );
- m_pBeam->SetFadeLength( 0 );
- m_pBeam->SetHaloTexture( gHaloTexture );
- m_pBeam->SetHaloScale( 16.0f );
-
- // Think faster while painting
- SetNextThink( gpGlobals->curtime + 0.02f );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CNPC_Combine_Cannon::CreateAncillaryBeams( void )
-{
- for ( int i = 0; i < NUM_ANCILLARY_BEAMS; i++ )
- {
- if ( m_pAncillaryBeams[i] != NULL )
- continue;
-
- m_pAncillaryBeams[i] = CBeam::BeamCreate( g_pModelNameLaser, 2.0f );
- m_pAncillaryBeams[i]->SetColor( 0, 100, 255 );
-
- m_pAncillaryBeams[i]->PointsInit( vec3_origin, GetBulletOrigin() );
- m_pAncillaryBeams[i]->SetBrightness( 255 );
- m_pAncillaryBeams[i]->SetNoise( 0 );
- m_pAncillaryBeams[i]->SetWidth( 1.0f );
- m_pAncillaryBeams[i]->SetEndWidth( 0 );
- m_pAncillaryBeams[i]->SetScrollRate( 0 );
- m_pAncillaryBeams[i]->SetFadeLength( 0 );
- m_pAncillaryBeams[i]->SetHaloTexture( gHaloTexture );
- m_pAncillaryBeams[i]->SetHaloScale( 16.0f );
- m_pAncillaryBeams[i]->TurnOff();
- }
-}
-
-#define LINE_LENGTH 1600.0f
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : flConvergencePerc -
-// vecBasis -
-//-----------------------------------------------------------------------------
-void CNPC_Combine_Cannon::UpdateAncillaryBeams( float flConvergencePerc, const Vector &vecOrigin, const Vector &vecBasis )
-{
- // Multiple beams deviate from the basis direction by a certain number of degrees and "converge"
- // at the basis vector over a duration of time, the position in that duration expressed by
- // flConvergencePerc. The beams are most deviated at 0 and fully converged at 1.
-
- float flRotationOffset = (2*M_PI)/(float)NUM_ANCILLARY_BEAMS; // Degrees separating each beam, in radians
- float flDeviation = DEG2RAD(90) * ( 1.0f - flConvergencePerc );
- float flOffset;
- Vector vecFinal;
- Vector vecOffset;
-
- matrix3x4_t matRotate;
- QAngle vecAngles;
- VectorAngles( vecBasis, vecAngles );
- vecAngles[PITCH] += 90.0f;
- AngleMatrix( vecAngles, vecOrigin, matRotate );
-
- trace_t tr;
-
- float flScale = LINE_LENGTH * flDeviation;
-
- // For each beam, find its offset and trace outwards to place its endpoint
- for ( int i = 0; i < NUM_ANCILLARY_BEAMS; i++ )
- {
- if ( flConvergencePerc >= 0.99f )
- {
- m_pAncillaryBeams[i]->TurnOn();
- continue;
- }
-
- m_pAncillaryBeams[i]->TurnOff();
-
- // Find the number of radians offset we are
- flOffset = (float) i * flRotationOffset + DEG2RAD( 30.0f );
- flOffset += (M_PI/8.0f) * sin( gpGlobals->curtime * 3.0f );
-
- // Construct a circle that's also offset by the line's length
- vecOffset.x = cos( flOffset ) * flScale;
- vecOffset.y = sin( flOffset ) * flScale;
- vecOffset.z = LINE_LENGTH;
-
- // Rotate this whole thing into the space of the basis vector
- VectorRotate( vecOffset, matRotate, vecFinal );
- VectorNormalize( vecFinal );
-
- // Trace a line down that vector to find where we'll eventually stop our line
- UTIL_TraceLine( vecOrigin, vecOrigin + ( vecFinal * LINE_LENGTH ), MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
-
- // Move the beam to that position
- m_pAncillaryBeams[i]->SetBrightness( 255.0f * flConvergencePerc );
- m_pAncillaryBeams[i]->SetEndPos( tr.startpos );
- m_pAncillaryBeams[i]->SetStartPos( tr.endpos );
- }
-}
-
-//-----------------------------------------------------------------------------
-// Sweep the laser sight towards the point where the gun should be aimed
-//-----------------------------------------------------------------------------
-void CNPC_Combine_Cannon::PaintTarget( const Vector &vecTarget, float flPaintTime )
-{
- // vecStart is the barrel of the gun (or the laser sight)
- Vector vecStart = GetBulletOrigin();
-
- // keep painttime from hitting 0 exactly.
- flPaintTime = MAX( flPaintTime, 0.000001f );
-
- // Find out where we are in the arc of the paint duration
- float flPaintPerc = GetWaitTimePercentage( flPaintTime, false );
-
- ScopeGlint();
-
- // Find out where along our line we're painting
- Vector vecCurrentDir;
- float flInterp = RemapValClamped( flPaintPerc, 0.0f, 0.5f, 0.0f, 1.0f );
- flInterp = clamp( flInterp, 0.0f, 1.0f );
- GetPaintAim( m_vecPaintStart, vecTarget, flInterp, &vecCurrentDir );
-
-#define THRESHOLD 0.9f
- float flNoiseScale;
-
- if ( flPaintPerc >= THRESHOLD )
- {
- flNoiseScale = 1 - (1 / (1 - THRESHOLD)) * ( flPaintPerc - THRESHOLD );
- }
- else if ( flPaintPerc <= 1 - THRESHOLD )
- {
- flNoiseScale = flPaintPerc / (1 - THRESHOLD);
- }
- else
- {
- flNoiseScale = 1;
- }
-
- // mult by P
- vecCurrentDir.x += flNoiseScale * ( sin( 3 * M_PI * gpGlobals->curtime ) * 0.0006 );
- vecCurrentDir.y += flNoiseScale * ( sin( 2 * M_PI * gpGlobals->curtime + 0.5 * M_PI ) * 0.0006 );
- vecCurrentDir.z += flNoiseScale * ( sin( 1.5 * M_PI * gpGlobals->curtime + M_PI ) * 0.0006 );
-
- // Find where our center is
- trace_t tr;
- UTIL_TraceLine( vecStart, vecStart + vecCurrentDir * 8192, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
- m_vecPaintCursor = tr.endpos;
-
- // Update our beam position
- m_pBeam->SetEndPos( tr.startpos );
- m_pBeam->SetStartPos( tr.endpos );
- m_pBeam->SetBrightness( 255.0f * flPaintPerc );
- m_pBeam->RelinkBeam();
-
- // Find points around that center point and make our designators converge at that point over time
- UpdateAncillaryBeams( flPaintPerc, vecStart, vecCurrentDir );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CNPC_Combine_Cannon::OnScheduleChange( void )
-{
- LaserOff();
-
- m_hBarrageTarget = NULL;
-
- BaseClass::OnScheduleChange();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CNPC_Combine_Cannon::Precache( void )
-{
- PrecacheModel("models/combine_soldier.mdl");
- PrecacheModel("effects/bluelaser1.vmt");
-
- gHaloTexture = PrecacheModel("sprites/light_glow03.vmt");
-
- PrecacheScriptSound( "NPC_Combine_Cannon.FireBullet" );
-
- BaseClass::Precache();
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CNPC_Combine_Cannon::Spawn( void )
-{
- Precache();
-
- /// HACK:
- SetModel( "models/combine_soldier.mdl" );
-
- // Setup our ancillary beams but keep them hidden for now
- CreateLaser();
- CreateAncillaryBeams();
-
- m_iAmmoType = GetAmmoDef()->Index( "CombineHeavyCannon" );
-
- SetHullType( HULL_HUMAN );
- SetHullSizeNormal();
-
- UTIL_SetSize( this, Vector( -16, -16 , 0 ), Vector( 16, 16, 64 ) );
-
- SetSolid( SOLID_BBOX );
- AddSolidFlags( FSOLID_NOT_STANDABLE );
- SetMoveType( MOVETYPE_FLY );
- m_bloodColor = DONT_BLEED;
- m_iHealth = 10;
- m_flFieldOfView = DOT_45DEGREE;
- m_NPCState = NPC_STATE_NONE;
-
- if( HasSpawnFlags( SF_STARTDISABLED ) )
- {
- m_fEnabled = false;
- }
- else
- {
- m_fEnabled = true;
- }
-
- CapabilitiesClear();
- CapabilitiesAdd( bits_CAP_INNATE_RANGE_ATTACK1 | bits_CAP_SIMPLE_RADIUS_DAMAGE );
-
- m_HackedGunPos = Vector ( 0, 0, 0 );
-
- AddSpawnFlags( SF_NPC_LONG_RANGE | SF_NPC_ALWAYSTHINK );
-
- NPCInit();
-
- // Limit our look distance
- SetDistLook( m_flSightDist );
-
- AddEffects( EF_NODRAW );
- AddSolidFlags( FSOLID_NOT_SOLID );
-
- // Point the cursor straight ahead so that the sniper's
- // first sweep of the laser doesn't look weird.
- Vector vecForward;
- AngleVectors( GetLocalAngles(), &vecForward );
- m_vecPaintCursor = GetBulletOrigin() + vecForward * 1024;
-
- // none!
- GetEnemies()->SetFreeKnowledgeDuration( 0.0f );
- GetEnemies()->SetEnemyDiscardTime( 2.0f );
-
- m_flTimeLastAttackedPlayer = 0.0f;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-Class_T CNPC_Combine_Cannon::Classify( void )
-{
- if ( m_fEnabled )
- return CLASS_COMBINE;
-
- return CLASS_NONE;
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-Vector CNPC_Combine_Cannon::GetBulletOrigin( void )
-{
- return GetAbsOrigin();
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Nothing kills the cannon but entity I/O
-//-----------------------------------------------------------------------------
-int CNPC_Combine_Cannon::OnTakeDamage_Alive( const CTakeDamageInfo &info )
-{
- // We are invulnerable to normal attacks for the moment
- return 0;
-}
-
-//---------------------------------------------------------
-// Purpose:
-//---------------------------------------------------------
-void CNPC_Combine_Cannon::UpdateOnRemove( void )
-{
- // Remove the main laser
- if ( m_pBeam != NULL )
- {
- UTIL_Remove( m_pBeam);
- m_pBeam = NULL;
- }
-
- // Remove our ancillary beams
- for ( int i = 0; i < NUM_ANCILLARY_BEAMS; i++ )
- {
- if ( m_pAncillaryBeams[i] == NULL )
- continue;
-
- UTIL_Remove( m_pAncillaryBeams[i] );
- m_pAncillaryBeams[i] = NULL;
- }
-
- BaseClass::UpdateOnRemove();
-}
-
-//---------------------------------------------------------
-// Purpose:
-//---------------------------------------------------------
-int CNPC_Combine_Cannon::SelectSchedule ( void )
-{
- // Fire at our target
- if( GetEnemy() && HasCondition( COND_CAN_RANGE_ATTACK1 ) )
- return SCHED_RANGE_ATTACK1;
-
- // Wait for a target
- // TODO: Sweep like a sniper?
- return SCHED_COMBAT_STAND;
-}
-
-//---------------------------------------------------------
-// Purpose:
-//---------------------------------------------------------
-bool CNPC_Combine_Cannon::FCanCheckAttacks ( void )
-{
- return true;
-}
-
-
-//---------------------------------------------------------
-//---------------------------------------------------------
-bool CNPC_Combine_Cannon::VerifyShot( CBaseEntity *pTarget )
-{
- trace_t tr;
-
- Vector vecTarget = DesiredBodyTarget( pTarget );
- UTIL_TraceLine( GetBulletOrigin(), vecTarget, MASK_SHOT, pTarget, COLLISION_GROUP_NONE, &tr );
-
- if( tr.fraction != 1.0 )
- {
- if( pTarget->IsPlayer() )
- {
- // if the target is the player, do another trace to see if we can shoot his eyeposition. This should help
- // improve sniper responsiveness in cases where the player is hiding his chest from the sniper with his
- // head in full view.
- UTIL_TraceLine( GetBulletOrigin(), pTarget->EyePosition(), MASK_SHOT, pTarget, COLLISION_GROUP_NONE, &tr );
-
- if( tr.fraction == 1.0 )
- {
- return true;
- }
- }
-
- // Trace hit something.
- if( tr.m_pEnt )
- {
- if( tr.m_pEnt->m_takedamage == DAMAGE_YES )
- {
- // Just shoot it if I can hurt it. Probably a breakable or glass pane.
- return true;
- }
- }
-
- return false;
- }
- else
- {
- return true;
- }
-}
-
-//---------------------------------------------------------
-//---------------------------------------------------------
-int CNPC_Combine_Cannon::RangeAttack1Conditions( float flDot, float flDist )
-{
- if ( GetNextAttack() > gpGlobals->curtime )
- return COND_NONE;
-
- if ( HasCondition( COND_SEE_ENEMY ) && !HasCondition( COND_ENEMY_OCCLUDED ) )
- {
- if ( VerifyShot( GetEnemy() ) )
- {
- // Can see the enemy, have a clear shot to his midsection
- ClearCondition( COND_CANNON_NO_SHOT );
- return COND_CAN_RANGE_ATTACK1;
- }
- else
- {
- // Can see the enemy, but can't take a shot at his midsection
- SetCondition( COND_CANNON_NO_SHOT );
- return COND_NONE;
- }
- }
-
- return COND_NONE;
-}
-
-
-//---------------------------------------------------------
-//---------------------------------------------------------
-int CNPC_Combine_Cannon::TranslateSchedule( int scheduleType )
-{
- switch( scheduleType )
- {
- case SCHED_RANGE_ATTACK1:
- return SCHED_CANNON_ATTACK;
- break;
- }
- return BaseClass::TranslateSchedule( scheduleType );
-}
-
-//---------------------------------------------------------
-//---------------------------------------------------------
-void CNPC_Combine_Cannon::ScopeGlint( void )
-{
- CEffectData data;
-
- data.m_vOrigin = GetAbsOrigin();
- data.m_vNormal = vec3_origin;
- data.m_vAngles = vec3_angle;
- data.m_nColor = COMMAND_POINT_BLUE;
-
- DispatchEffect( "CommandPointer", data );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : *vecIn -
-//-----------------------------------------------------------------------------
-void CNPC_Combine_Cannon::AdjustShotPosition( CBaseEntity *pTarget, Vector *vecIn )
-{
- if ( pTarget == NULL || vecIn == NULL )
- return;
-
- Vector low = pTarget->WorldSpaceCenter() - ( pTarget->WorldSpaceCenter() - pTarget->GetAbsOrigin() ) * .25;
- Vector high = pTarget->EyePosition();
- Vector delta = high - low;
- Vector result = low + delta * 0.5;
-
- // Only take the height
- (*vecIn)[2] = result[2];
-}
-
-//---------------------------------------------------------
-// This starts the bullet state machine. The actual effects
-// of the bullet will happen later. This function schedules
-// those effects.
-//
-// fDirectShot indicates whether the bullet is a "direct shot"
-// that is - fired with the intent that it will strike the
-// enemy. Otherwise, the bullet is intended to strike a
-// decoy object or nothing at all in particular.
-//---------------------------------------------------------
-bool CNPC_Combine_Cannon::FireBullet( const Vector &vecTarget, bool bDirectShot )
-{
- Vector vecBulletOrigin = GetBulletOrigin();
- Vector vecDir = ( vecTarget - vecBulletOrigin );
- VectorNormalize( vecDir );
-
- FireBulletsInfo_t info;
- info.m_iShots = 1;
- info.m_iTracerFreq = 1.0f;
- info.m_vecDirShooting = vecDir;
- info.m_vecSrc = vecBulletOrigin;
- info.m_flDistance = MAX_TRACE_LENGTH;
- info.m_pAttacker = this;
- info.m_iAmmoType = m_iAmmoType;
- info.m_iPlayerDamage = 20.0f;
- info.m_vecSpread = Vector( 0.015f, 0.015f, 0.015f ); // medium cone
-
- FireBullets( info );
-
- EmitSound( "NPC_Combine_Cannon.FireBullet" );
-
- // Don't attack for a certain amount of time
- SetNextAttack( gpGlobals->curtime + GetRefireTime() );
-
- // Sniper had to be aiming here to fire here, so make it the cursor
- m_vecPaintCursor = vecTarget;
-
- LaserOff();
-
- return true;
-}
-
-//---------------------------------------------------------
-//---------------------------------------------------------
-void CNPC_Combine_Cannon::StartTask( const Task_t *pTask )
-{
- switch( pTask->iTask )
- {
- case TASK_CANNON_ATTACK_CURSOR:
- break;
-
- case TASK_RANGE_ATTACK1:
- // Setup the information for this barrage
- m_flBarrageDuration = gpGlobals->curtime + random->RandomFloat( 0.25f, 0.5f );
- m_hBarrageTarget = GetEnemy();
- break;
-
- case TASK_CANNON_PAINT_ENEMY:
- {
- if ( GetEnemy()->IsPlayer() )
- {
- float delay = random->RandomFloat( 0.0f, 0.3f );
-
- if ( ( gpGlobals->curtime - m_flTimeLastAttackedPlayer ) < 1.0f )
- {
- SetWait( CANNON_SUBSEQUENT_PAINT_TIME );
- m_flPaintTime = CANNON_SUBSEQUENT_PAINT_TIME;
- }
- else
- {
- SetWait( CANNON_PAINT_ENEMY_TIME + delay );
- m_flPaintTime = CANNON_PAINT_ENEMY_TIME + delay;
- }
- }
- else
- {
- // Use a random time
- m_flPaintTime = CANNON_PAINT_ENEMY_TIME + random->RandomFloat( 0, CANNON_PAINT_NPC_TIME_NOISE );
- SetWait( m_flPaintTime );
- }
-
- // Try to start the laser where the player can't miss seeing it!
- Vector vecCursor;
- AngleVectors( GetEnemy()->GetLocalAngles(), &vecCursor );
- vecCursor *= 300;
- vecCursor += GetEnemy()->EyePosition();
- LaserOn( vecCursor, Vector( 16, 16, 16 ) );
- }
- break;
-
- default:
- BaseClass::StartTask( pTask );
- break;
- }
-}
-
-//---------------------------------------------------------
-//---------------------------------------------------------
-void CNPC_Combine_Cannon::RunTask( const Task_t *pTask )
-{
- switch( pTask->iTask )
- {
- case TASK_CANNON_ATTACK_CURSOR:
- if( FireBullet( m_vecPaintCursor, true ) )
- {
- TaskComplete();
- }
- break;
-
- case TASK_RANGE_ATTACK1:
- {
- // Where we're focusing our fire
- Vector vecTarget = ( m_hBarrageTarget == NULL ) ? m_vecPaintCursor : LeadTarget( m_hBarrageTarget );
-
- // Fire at enemy
- if ( FireBullet( vecTarget, true ) )
- {
- bool bPlayerIsEnemy = ( m_hBarrageTarget && m_hBarrageTarget->IsPlayer() );
- bool bBarrageFinished = m_flBarrageDuration < gpGlobals->curtime;
- bool bNoShot = ( QuerySeeEntity( m_hBarrageTarget ) == false ); // FIXME: Store this info off better
- bool bSeePlayer = HasCondition( COND_SEE_PLAYER );
-
- // Treat the player differently to normal NPCs
- if ( bPlayerIsEnemy )
- {
- // Store the last time we shot for doing an abbreviated attack telegraph
- m_flTimeLastAttackedPlayer = gpGlobals->curtime;
-
- // If we've got no shot and we're done with our current barrage
- if ( bNoShot && bBarrageFinished )
- {
- TaskComplete();
- }
- }
- else if ( bBarrageFinished || bSeePlayer )
- {
- // Done with the barrage or we saw the player as a better target
- TaskComplete();
- }
- }
- }
- break;
-
- case TASK_CANNON_PAINT_ENEMY:
- {
- // See if we're done painting our target
- if ( IsWaitFinished() )
- {
- TaskComplete();
- }
-
- // Continue to paint the target
- PaintTarget( LeadTarget( GetEnemy() ), m_flPaintTime );
- }
- break;
-
- default:
- BaseClass::RunTask( pTask );
- break;
- }
-}
-
-
-//-----------------------------------------------------------------------------
-// The sniper throws away the circular list of old decoys when we restore.
-//-----------------------------------------------------------------------------
-int CNPC_Combine_Cannon::Restore( IRestore &restore )
-{
- return BaseClass::Restore( restore );
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//
-//
-//-----------------------------------------------------------------------------
-float CNPC_Combine_Cannon::MaxYawSpeed( void )
-{
- return 60;
-}
-
-//---------------------------------------------------------
-//---------------------------------------------------------
-void CNPC_Combine_Cannon::PrescheduleThink( void )
-{
- BaseClass::PrescheduleThink();
-
- // NOTE: We'll deal with this on the client
- // Think faster if the beam is on, this gives the beam higher resolution.
- if( m_pBeam )
- {
- SetNextThink( gpGlobals->curtime + 0.03 );
- }
- else
- {
- SetNextThink( gpGlobals->curtime + 0.1f );
- }
-
- // If the enemy has just stepped into view, or we've acquired a new enemy,
- // Record the last time we've seen the enemy as right now.
- //
- // If the enemy has been out of sight for a full second, mark him eluded.
- if( GetEnemy() != NULL )
- {
- if( gpGlobals->curtime - GetEnemies()->LastTimeSeen( GetEnemy() ) > 30 )
- {
- // Stop pestering enemies after 30 seconds of frustration.
- GetEnemies()->ClearMemory( GetEnemy() );
- SetEnemy(NULL);
- }
- }
-}
-
-
-//---------------------------------------------------------
-//---------------------------------------------------------
-Vector CNPC_Combine_Cannon::EyePosition( void )
-{
- return GetAbsOrigin();
-}
-
-//---------------------------------------------------------
-//---------------------------------------------------------
-Vector CNPC_Combine_Cannon::DesiredBodyTarget( CBaseEntity *pTarget )
-{
- // By default, aim for the center
- Vector vecTarget = pTarget->WorldSpaceCenter();
-
- float flTimeSinceLastMiss = gpGlobals->curtime - m_flTimeLastShotMissed;
-
- if( pTarget->GetFlags() & FL_CLIENT )
- {
- if( !BaseClass::FVisible( vecTarget ) )
- {
- // go to the player's eyes if his center is concealed.
- // Bump up an inch so the player's not looking straight down a beam.
- vecTarget = pTarget->EyePosition() + Vector( 0, 0, 1 );
- }
- }
- else
- {
- if( pTarget->Classify() == CLASS_HEADCRAB )
- {
- // Headcrabs are tiny inside their boxes.
- vecTarget = pTarget->GetAbsOrigin();
- vecTarget.z += 4.0;
- }
- else if( pTarget->Classify() == CLASS_ZOMBIE )
- {
- if( flTimeSinceLastMiss > 0.0f && flTimeSinceLastMiss < 4.0f && hl2_episodic.GetBool() )
- {
- vecTarget = pTarget->BodyTarget( GetBulletOrigin(), false );
- }
- else
- {
- // Shoot zombies in the headcrab
- vecTarget = pTarget->HeadTarget( GetBulletOrigin() );
- }
- }
- else if( pTarget->Classify() == CLASS_ANTLION )
- {
- // Shoot about a few inches above the origin. This makes it easy to hit antlions
- // even if they are on their backs.
- vecTarget = pTarget->GetAbsOrigin();
- vecTarget.z += 18.0f;
- }
- else if( pTarget->Classify() == CLASS_EARTH_FAUNA )
- {
- // Shoot birds in the center
- }
- else
- {
- // Shoot NPCs in the chest
- vecTarget.z += 8.0f;
- }
- }
-
- return vecTarget;
-}
-
-//---------------------------------------------------------
-//---------------------------------------------------------
-Vector CNPC_Combine_Cannon::LeadTarget( CBaseEntity *pTarget )
-{
- if ( pTarget != NULL )
- {
- Vector vecFuturePos;
- UTIL_PredictedPosition( pTarget, 0.05f, &vecFuturePos );
- AdjustShotPosition( pTarget, &vecFuturePos );
-
- return vecFuturePos;
- }
-
- return vec3_origin;
-}
-
-//---------------------------------------------------------
-//---------------------------------------------------------
-void CNPC_Combine_Cannon::InputEnableSniper( inputdata_t &inputdata )
-{
- ClearCondition( COND_CANNON_DISABLED );
- SetCondition( COND_CANNON_ENABLED );
-
- m_fEnabled = true;
-}
-
-
-//---------------------------------------------------------
-//---------------------------------------------------------
-void CNPC_Combine_Cannon::InputDisableSniper( inputdata_t &inputdata )
-{
- ClearCondition( COND_CANNON_ENABLED );
- SetCondition( COND_CANNON_DISABLED );
-
- m_fEnabled = false;
-}
-
-//---------------------------------------------------------
-// See all NPC's easily.
-//
-// Only see the player if you can trace to both of his
-// eyeballs. That is, allow the player to peek around corners.
-// This is a little more expensive than the base class' check!
-//---------------------------------------------------------
-#define CANNON_EYE_DIST 0.75
-#define CANNON_TARGET_VERTICAL_OFFSET Vector( 0, 0, 5 );
-bool CNPC_Combine_Cannon::FVisible( CBaseEntity *pEntity, int traceMask, CBaseEntity **ppBlocker )
-{
- // NPC
- if ( pEntity->IsPlayer() == false )
- return BaseClass::FVisible( pEntity, traceMask, ppBlocker );
-
- if ( pEntity->GetFlags() & FL_NOTARGET )
- return false;
-
- Vector vecVerticalOffset;
- Vector vecRight;
- Vector vecEye;
- trace_t tr;
-
- if( fabs( GetAbsOrigin().z - pEntity->WorldSpaceCenter().z ) <= 120.f )
- {
- // If the player is around the same elevation, look straight at his eyes.
- // At the same elevation, the vertical peeking allowance makes it too easy
- // for a player to dispatch the sniper from cover.
- vecVerticalOffset = vec3_origin;
- }
- else
- {
- // Otherwise, look at a spot below his eyes. This allows the player to back away
- // from his cover a bit and have a peek at the sniper without being detected.
- vecVerticalOffset = CANNON_TARGET_VERTICAL_OFFSET;
- }
-
- AngleVectors( pEntity->GetLocalAngles(), NULL, &vecRight, NULL );
-
- vecEye = vecRight * CANNON_EYE_DIST - vecVerticalOffset;
- UTIL_TraceLine( EyePosition(), pEntity->EyePosition() + vecEye, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
-
-#if 0
- NDebugOverlay::Line(EyePosition(), tr.endpos, 0,255,0, true, 0.1);
-#endif
-
- bool fCheckFailed = false;
-
- if ( tr.fraction != 1.0 && tr.m_pEnt != pEntity )
- {
- fCheckFailed = true;
- }
-
- // Don't check the other eye if the first eye failed.
- if( !fCheckFailed )
- {
- vecEye = -vecRight * CANNON_EYE_DIST - vecVerticalOffset;
- UTIL_TraceLine( EyePosition(), pEntity->EyePosition() + vecEye, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
-
-#if 0
- NDebugOverlay::Line(EyePosition(), tr.endpos, 0,255,0, true, 0.1);
-#endif
-
- if ( tr.fraction != 1.0 && tr.m_pEnt != pEntity )
- {
- fCheckFailed = true;
- }
- }
-
- if( !fCheckFailed )
- {
- // Can see the player.
- return true;
- }
-
- // Now, if the check failed, see if the player is ducking and has recently
- // fired a muzzleflash. If yes, see if you'd be able to see the player if
- // they were standing in their current position instead of ducking. Since
- // the sniper doesn't have a clear shot in this situation, he will harrass
- // near the player.
- CBasePlayer *pPlayer;
-
- pPlayer = ToBasePlayer( pEntity );
-
- if( (pPlayer->GetFlags() & FL_DUCKING) && pPlayer->MuzzleFlashTime() > gpGlobals->curtime )
- {
- vecEye = pPlayer->EyePosition() + Vector( 0, 0, 32 );
- UTIL_TraceLine( EyePosition(), vecEye, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
-
- if( tr.fraction != 1.0 )
- {
- // Everything failed.
- if (ppBlocker)
- {
- *ppBlocker = tr.m_pEnt;
- }
- return false;
- }
- else
- {
- // Fake being able to see the player.
- return true;
- }
- }
-
- if (ppBlocker)
- {
- *ppBlocker = tr.m_pEnt;
- }
-
- return false;
-}
-
-
-//-----------------------------------------------------------------------------
-//
-// Schedules
-//
-//-----------------------------------------------------------------------------
-
-AI_BEGIN_CUSTOM_NPC( npc_combine_cannon, CNPC_Combine_Cannon )
-
- DECLARE_CONDITION( COND_CANNON_ENABLED );
- DECLARE_CONDITION( COND_CANNON_DISABLED );
- DECLARE_CONDITION( COND_CANNON_NO_SHOT );
-
- DECLARE_TASK( TASK_CANNON_PAINT_ENEMY );
- DECLARE_TASK( TASK_CANNON_ATTACK_CURSOR );
-
- //=========================================================
- // CAMP
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_CANNON_CAMP,
-
- " Tasks"
- " TASK_WAIT 1"
- " "
- " Interrupts"
- " COND_NEW_ENEMY"
- " COND_ENEMY_DEAD"
- " COND_CAN_RANGE_ATTACK1"
- " COND_HEAR_DANGER"
- " COND_CANNON_DISABLED"
- )
-
- //=========================================================
- // ATTACK
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_CANNON_ATTACK,
-
- " Tasks"
- " TASK_CANNON_PAINT_ENEMY 0"
- " TASK_RANGE_ATTACK1 0"
- " "
- " Interrupts"
- " COND_HEAR_DANGER"
- " COND_CANNON_DISABLED"
- )
-
- //=========================================================
- // ATTACK
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_CANNON_SNAPATTACK,
-
- " Tasks"
- " TASK_CANNON_ATTACK_CURSOR 0"
- " "
- " Interrupts"
- " COND_ENEMY_OCCLUDED"
- " COND_ENEMY_DEAD"
- " COND_NEW_ENEMY"
- " COND_HEAR_DANGER"
- " COND_CANNON_DISABLED"
- )
-
- //=========================================================
- // Sniper is allowed to process a couple conditions while
- // disabled, but mostly he waits until he's enabled.
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_CANNON_DISABLEDWAIT,
-
- " Tasks"
- " TASK_WAIT 0.5"
- " "
- " Interrupts"
- " COND_CANNON_ENABLED"
- " COND_NEW_ENEMY"
- " COND_ENEMY_DEAD"
- )
-
-AI_END_CUSTOM_NPC()
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "ai_basenpc.h"
+#include "ammodef.h"
+#include "ai_memory.h"
+#include "weapon_rpg.h"
+#include "effect_color_tables.h"
+#include "te_effect_dispatch.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+extern const char* g_pModelNameLaser;
+
+// No model, impervious to damage.
+#define SF_STARTDISABLED (1 << 19)
+
+#define CANNON_PAINT_ENEMY_TIME 1.0f
+#define CANNON_SUBSEQUENT_PAINT_TIME 0.4f
+#define CANNON_PAINT_NPC_TIME_NOISE 1.0f
+
+#define NUM_ANCILLARY_BEAMS 4
+
+int gHaloTexture = 0;
+
+//-----------------------------------------------------------------------------
+//
+// Combine Cannon
+//
+//-----------------------------------------------------------------------------
+class CNPC_Combine_Cannon : public CAI_BaseNPC
+{
+ DECLARE_CLASS( CNPC_Combine_Cannon, CAI_BaseNPC );
+
+public:
+ CNPC_Combine_Cannon( void );
+ virtual void Precache( void );
+ virtual void Spawn( void );
+ virtual Class_T Classify( void );
+ virtual float MaxYawSpeed( void );
+ virtual Vector EyePosition( void );
+ virtual void UpdateOnRemove( void );
+ virtual int OnTakeDamage_Alive( const CTakeDamageInfo &info );
+ virtual bool QuerySeeEntity( CBaseEntity *pEntity, bool bOnlyHateOrFearIfNPC = false );
+ virtual void StartTask( const Task_t *pTask );
+ virtual void RunTask( const Task_t *pTask );
+ virtual int RangeAttack1Conditions( float flDot, float flDist );
+ virtual int SelectSchedule( void );
+ virtual int TranslateSchedule( int scheduleType );
+ virtual void PrescheduleThink( void );
+ virtual bool FCanCheckAttacks ( void );
+ virtual int Restore( IRestore &restore );
+ virtual void OnScheduleChange( void );
+ virtual bool FVisible( CBaseEntity *pEntity, int traceMask = MASK_BLOCKLOS, CBaseEntity **ppBlocker = NULL );
+
+ virtual bool WeaponLOSCondition( const Vector &ownerPos, const Vector &targetPos, bool bSetConditions) { return true; }
+
+ virtual int GetSoundInterests( void ) { return (SOUND_PLAYER|SOUND_COMBAT|SOUND_DANGER); }
+
+ virtual bool ShouldNotDistanceCull( void ) { return true; }
+
+ virtual void UpdateEfficiency( bool bInPVS ) { SetEfficiency( ( GetSleepState() != AISS_AWAKE ) ? AIE_DORMANT : AIE_NORMAL ); SetMoveEfficiency( AIME_NORMAL ); }
+
+ virtual const char *GetTracerType( void ) { return "HelicopterTracer"; }
+
+private:
+
+ void ScopeGlint( void );
+ void AdjustShotPosition( CBaseEntity *pTarget, Vector *vecIn );
+
+ float GetRefireTime( void ) { return 0.1f; }
+
+ bool IsLaserOn( void ) { return m_pBeam != NULL; }
+ bool FireBullet( const Vector &vecTarget, bool bDirectShot );
+ Vector DesiredBodyTarget( CBaseEntity *pTarget );
+ Vector LeadTarget( CBaseEntity *pTarget );
+ Vector GetBulletOrigin( void );
+
+ static const char *pAttackSounds[];
+
+ void ClearTargetGroup( void );
+
+ float GetWaitTimePercentage( float flTime, bool fLinear );
+
+ void GetPaintAim( const Vector &vecStart, const Vector &vecGoal, float flParameter, Vector *pProgress );
+
+ bool VerifyShot( CBaseEntity *pTarget );
+
+ void SetSweepTarget( const char *pszTarget );
+
+ // Inputs
+ void InputEnableSniper( inputdata_t &inputdata );
+ void InputDisableSniper( inputdata_t &inputdata );
+
+ void LaserOff( void );
+ void LaserOn( const Vector &vecTarget, const Vector &vecDeviance );
+
+ void PaintTarget( const Vector &vecTarget, float flPaintTime );
+
+private:
+
+ void CreateLaser( void );
+ void CreateAncillaryBeams( void );
+ void UpdateAncillaryBeams( float flConvergencePerc, const Vector &vecOrigin, const Vector &vecBasis );
+
+ int m_iAmmoType;
+ float m_flBarrageDuration;
+ Vector m_vecPaintCursor;
+ float m_flPaintTime;
+
+ CHandle<CBeam> m_pBeam;
+ CHandle<CBeam> m_pAncillaryBeams[NUM_ANCILLARY_BEAMS];
+ EHANDLE m_hBarrageTarget;
+
+ bool m_fEnabled;
+ Vector m_vecPaintStart; // used to track where a sweep starts for the purpose of interpolating.
+ float m_flTimeLastAttackedPlayer;
+ float m_flTimeLastShotMissed;
+ float m_flSightDist;
+
+ DEFINE_CUSTOM_AI;
+
+ DECLARE_DATADESC();
+};
+
+LINK_ENTITY_TO_CLASS( npc_combine_cannon, CNPC_Combine_Cannon );
+
+//=========================================================
+//=========================================================
+BEGIN_DATADESC( CNPC_Combine_Cannon )
+
+ DEFINE_FIELD( m_fEnabled, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_vecPaintStart, FIELD_POSITION_VECTOR ),
+ DEFINE_FIELD( m_flPaintTime, FIELD_TIME ),
+ DEFINE_FIELD( m_vecPaintCursor, FIELD_POSITION_VECTOR ),
+ DEFINE_FIELD( m_pBeam, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_flTimeLastAttackedPlayer, FIELD_TIME ),
+ DEFINE_FIELD( m_flTimeLastShotMissed, FIELD_TIME ),
+ DEFINE_FIELD( m_iAmmoType, FIELD_INTEGER ),
+ DEFINE_FIELD( m_flBarrageDuration, FIELD_TIME ),
+ DEFINE_FIELD( m_hBarrageTarget, FIELD_EHANDLE ),
+
+ DEFINE_ARRAY( m_pAncillaryBeams, FIELD_EHANDLE, NUM_ANCILLARY_BEAMS ),
+
+ DEFINE_KEYFIELD( m_flSightDist, FIELD_FLOAT, "sightdist" ),
+ // Inputs
+ DEFINE_INPUTFUNC( FIELD_VOID, "EnableSniper", InputEnableSniper ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "DisableSniper", InputDisableSniper ),
+
+END_DATADESC()
+
+
+//=========================================================
+// Private conditions
+//=========================================================
+enum Sniper_Conds
+{
+ COND_CANNON_ENABLED = LAST_SHARED_CONDITION,
+ COND_CANNON_DISABLED,
+ COND_CANNON_NO_SHOT,
+};
+
+
+//=========================================================
+// schedules
+//=========================================================
+enum
+{
+ SCHED_CANNON_CAMP = LAST_SHARED_SCHEDULE,
+ SCHED_CANNON_ATTACK,
+ SCHED_CANNON_DISABLEDWAIT,
+ SCHED_CANNON_SNAPATTACK,
+};
+
+//=========================================================
+// tasks
+//=========================================================
+enum
+{
+ TASK_CANNON_PAINT_ENEMY = LAST_SHARED_TASK,
+ TASK_CANNON_PAINT_DECOY,
+ TASK_CANNON_ATTACK_CURSOR,
+};
+
+//-----------------------------------------------------------------------------
+// Constructor
+//-----------------------------------------------------------------------------
+CNPC_Combine_Cannon::CNPC_Combine_Cannon( void ) :
+ m_pBeam( NULL ),
+ m_hBarrageTarget( NULL )
+{
+#ifdef _DEBUG
+ m_vecPaintCursor.Init();
+ m_vecPaintStart.Init();
+#endif
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+bool CNPC_Combine_Cannon::QuerySeeEntity( CBaseEntity *pEntity, bool bOnlyHateOrFearIfNPC )
+{
+ Disposition_t disp = IRelationType(pEntity);
+ if ( disp != D_HT )
+ {
+ // Don't bother with anything I wouldn't shoot.
+ return false;
+ }
+
+ if ( !FInViewCone(pEntity) )
+ {
+ // Yes, this does call FInViewCone twice a frame for all entities checked for
+ // visibility, but doing this allows us to cut out a bunch of traces that would
+ // be done by VerifyShot for entities that aren't even in our viewcone.
+ return false;
+ }
+
+ if ( VerifyShot( pEntity ) )
+ return BaseClass::QuerySeeEntity( pEntity, bOnlyHateOrFearIfNPC );
+
+ return false;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Hide the beams
+//-----------------------------------------------------------------------------
+void CNPC_Combine_Cannon::LaserOff( void )
+{
+ if ( m_pBeam != NULL )
+ {
+ m_pBeam->TurnOn();
+ }
+
+ for ( int i = 0; i < NUM_ANCILLARY_BEAMS; i++ )
+ {
+ if ( m_pAncillaryBeams[i] == NULL )
+ continue;
+
+ m_pAncillaryBeams[i]->TurnOn();
+ }
+
+ SetNextThink( gpGlobals->curtime + 0.1f );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Switch on the laser and point it at a direction
+//-----------------------------------------------------------------------------
+void CNPC_Combine_Cannon::LaserOn( const Vector &vecTarget, const Vector &vecDeviance )
+{
+ if ( m_pBeam != NULL )
+ {
+ m_pBeam->TurnOff();
+
+ // Don't aim right at the guy right now.
+ Vector vecInitialAim;
+
+ if( vecDeviance == vec3_origin )
+ {
+ // Start the aim where it last left off!
+ vecInitialAim = m_vecPaintCursor;
+ }
+ else
+ {
+ vecInitialAim = vecTarget;
+ }
+
+ vecInitialAim.x += random->RandomFloat( -vecDeviance.x, vecDeviance.x );
+ vecInitialAim.y += random->RandomFloat( -vecDeviance.y, vecDeviance.y );
+ vecInitialAim.z += random->RandomFloat( -vecDeviance.z, vecDeviance.z );
+
+ m_pBeam->SetStartPos( GetBulletOrigin() );
+ m_pBeam->SetEndPos( vecInitialAim );
+
+ m_vecPaintStart = vecInitialAim;
+ }
+
+ for ( int i = 0; i < NUM_ANCILLARY_BEAMS; i++ )
+ {
+ if ( m_pAncillaryBeams[i] == NULL )
+ continue;
+
+ m_pAncillaryBeams[i]->TurnOff();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Crikey!
+//-----------------------------------------------------------------------------
+float CNPC_Combine_Cannon::GetWaitTimePercentage( float flTime, bool fLinear )
+{
+ float flElapsedTime;
+ float flTimeParameter;
+
+ flElapsedTime = flTime - (GetWaitFinishTime() - gpGlobals->curtime);
+
+ flTimeParameter = ( flElapsedTime / flTime );
+
+ if( fLinear )
+ {
+ return flTimeParameter;
+ }
+ else
+ {
+ return (1 + sin( (M_PI * flTimeParameter) - (M_PI / 2) ) ) / 2;
+ }
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Combine_Cannon::GetPaintAim( const Vector &vecStart, const Vector &vecGoal, float flParameter, Vector *pProgress )
+{
+ // Quaternions
+ Vector vecIdealDir;
+ QAngle vecIdealAngles;
+ QAngle vecCurrentAngles;
+ Vector vecCurrentDir;
+ Vector vecBulletOrigin = GetBulletOrigin();
+
+ // vecIdealDir is where the gun should be aimed when the painting
+ // time is up. This can be approximate. This is only for drawing the
+ // laser, not actually aiming the weapon. A large discrepancy will look
+ // bad, though.
+ vecIdealDir = vecGoal - vecBulletOrigin;
+ VectorNormalize(vecIdealDir);
+
+ // Now turn vecIdealDir into angles!
+ VectorAngles( vecIdealDir, vecIdealAngles );
+
+ // This is the vector of the beam's current aim.
+ vecCurrentDir = m_vecPaintStart - vecBulletOrigin;
+ VectorNormalize(vecCurrentDir);
+
+ // Turn this to angles, too.
+ VectorAngles( vecCurrentDir, vecCurrentAngles );
+
+ Quaternion idealQuat;
+ Quaternion currentQuat;
+ Quaternion aimQuat;
+
+ AngleQuaternion( vecIdealAngles, idealQuat );
+ AngleQuaternion( vecCurrentAngles, currentQuat );
+
+ QuaternionSlerp( currentQuat, idealQuat, flParameter, aimQuat );
+
+ QuaternionAngles( aimQuat, vecCurrentAngles );
+
+ // Rebuild the current aim vector.
+ AngleVectors( vecCurrentAngles, &vecCurrentDir );
+
+ *pProgress = vecCurrentDir;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_Combine_Cannon::CreateLaser( void )
+{
+ if ( m_pBeam != NULL )
+ return;
+
+ m_pBeam = CBeam::BeamCreate( g_pModelNameLaser, 2.0f );
+
+ m_pBeam->SetColor( 0, 100, 255 );
+
+ m_pBeam->PointsInit( vec3_origin, GetBulletOrigin() );
+ m_pBeam->SetBrightness( 255 );
+ m_pBeam->SetNoise( 0 );
+ m_pBeam->SetWidth( 1.0f );
+ m_pBeam->SetEndWidth( 0 );
+ m_pBeam->SetScrollRate( 0 );
+ m_pBeam->SetFadeLength( 0 );
+ m_pBeam->SetHaloTexture( gHaloTexture );
+ m_pBeam->SetHaloScale( 16.0f );
+
+ // Think faster while painting
+ SetNextThink( gpGlobals->curtime + 0.02f );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_Combine_Cannon::CreateAncillaryBeams( void )
+{
+ for ( int i = 0; i < NUM_ANCILLARY_BEAMS; i++ )
+ {
+ if ( m_pAncillaryBeams[i] != NULL )
+ continue;
+
+ m_pAncillaryBeams[i] = CBeam::BeamCreate( g_pModelNameLaser, 2.0f );
+ m_pAncillaryBeams[i]->SetColor( 0, 100, 255 );
+
+ m_pAncillaryBeams[i]->PointsInit( vec3_origin, GetBulletOrigin() );
+ m_pAncillaryBeams[i]->SetBrightness( 255 );
+ m_pAncillaryBeams[i]->SetNoise( 0 );
+ m_pAncillaryBeams[i]->SetWidth( 1.0f );
+ m_pAncillaryBeams[i]->SetEndWidth( 0 );
+ m_pAncillaryBeams[i]->SetScrollRate( 0 );
+ m_pAncillaryBeams[i]->SetFadeLength( 0 );
+ m_pAncillaryBeams[i]->SetHaloTexture( gHaloTexture );
+ m_pAncillaryBeams[i]->SetHaloScale( 16.0f );
+ m_pAncillaryBeams[i]->TurnOff();
+ }
+}
+
+#define LINE_LENGTH 1600.0f
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : flConvergencePerc -
+// vecBasis -
+//-----------------------------------------------------------------------------
+void CNPC_Combine_Cannon::UpdateAncillaryBeams( float flConvergencePerc, const Vector &vecOrigin, const Vector &vecBasis )
+{
+ // Multiple beams deviate from the basis direction by a certain number of degrees and "converge"
+ // at the basis vector over a duration of time, the position in that duration expressed by
+ // flConvergencePerc. The beams are most deviated at 0 and fully converged at 1.
+
+ float flRotationOffset = (2*M_PI)/(float)NUM_ANCILLARY_BEAMS; // Degrees separating each beam, in radians
+ float flDeviation = DEG2RAD(90) * ( 1.0f - flConvergencePerc );
+ float flOffset;
+ Vector vecFinal;
+ Vector vecOffset;
+
+ matrix3x4_t matRotate;
+ QAngle vecAngles;
+ VectorAngles( vecBasis, vecAngles );
+ vecAngles[PITCH] += 90.0f;
+ AngleMatrix( vecAngles, vecOrigin, matRotate );
+
+ trace_t tr;
+
+ float flScale = LINE_LENGTH * flDeviation;
+
+ // For each beam, find its offset and trace outwards to place its endpoint
+ for ( int i = 0; i < NUM_ANCILLARY_BEAMS; i++ )
+ {
+ if ( flConvergencePerc >= 0.99f )
+ {
+ m_pAncillaryBeams[i]->TurnOn();
+ continue;
+ }
+
+ m_pAncillaryBeams[i]->TurnOff();
+
+ // Find the number of radians offset we are
+ flOffset = (float) i * flRotationOffset + DEG2RAD( 30.0f );
+ flOffset += (M_PI/8.0f) * sin( gpGlobals->curtime * 3.0f );
+
+ // Construct a circle that's also offset by the line's length
+ vecOffset.x = cos( flOffset ) * flScale;
+ vecOffset.y = sin( flOffset ) * flScale;
+ vecOffset.z = LINE_LENGTH;
+
+ // Rotate this whole thing into the space of the basis vector
+ VectorRotate( vecOffset, matRotate, vecFinal );
+ VectorNormalize( vecFinal );
+
+ // Trace a line down that vector to find where we'll eventually stop our line
+ UTIL_TraceLine( vecOrigin, vecOrigin + ( vecFinal * LINE_LENGTH ), MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
+
+ // Move the beam to that position
+ m_pAncillaryBeams[i]->SetBrightness( 255.0f * flConvergencePerc );
+ m_pAncillaryBeams[i]->SetEndPos( tr.startpos );
+ m_pAncillaryBeams[i]->SetStartPos( tr.endpos );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Sweep the laser sight towards the point where the gun should be aimed
+//-----------------------------------------------------------------------------
+void CNPC_Combine_Cannon::PaintTarget( const Vector &vecTarget, float flPaintTime )
+{
+ // vecStart is the barrel of the gun (or the laser sight)
+ Vector vecStart = GetBulletOrigin();
+
+ // keep painttime from hitting 0 exactly.
+ flPaintTime = MAX( flPaintTime, 0.000001f );
+
+ // Find out where we are in the arc of the paint duration
+ float flPaintPerc = GetWaitTimePercentage( flPaintTime, false );
+
+ ScopeGlint();
+
+ // Find out where along our line we're painting
+ Vector vecCurrentDir;
+ float flInterp = RemapValClamped( flPaintPerc, 0.0f, 0.5f, 0.0f, 1.0f );
+ flInterp = clamp( flInterp, 0.0f, 1.0f );
+ GetPaintAim( m_vecPaintStart, vecTarget, flInterp, &vecCurrentDir );
+
+#define THRESHOLD 0.9f
+ float flNoiseScale;
+
+ if ( flPaintPerc >= THRESHOLD )
+ {
+ flNoiseScale = 1 - (1 / (1 - THRESHOLD)) * ( flPaintPerc - THRESHOLD );
+ }
+ else if ( flPaintPerc <= 1 - THRESHOLD )
+ {
+ flNoiseScale = flPaintPerc / (1 - THRESHOLD);
+ }
+ else
+ {
+ flNoiseScale = 1;
+ }
+
+ // mult by P
+ vecCurrentDir.x += flNoiseScale * ( sin( 3 * M_PI * gpGlobals->curtime ) * 0.0006 );
+ vecCurrentDir.y += flNoiseScale * ( sin( 2 * M_PI * gpGlobals->curtime + 0.5 * M_PI ) * 0.0006 );
+ vecCurrentDir.z += flNoiseScale * ( sin( 1.5 * M_PI * gpGlobals->curtime + M_PI ) * 0.0006 );
+
+ // Find where our center is
+ trace_t tr;
+ UTIL_TraceLine( vecStart, vecStart + vecCurrentDir * 8192, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
+ m_vecPaintCursor = tr.endpos;
+
+ // Update our beam position
+ m_pBeam->SetEndPos( tr.startpos );
+ m_pBeam->SetStartPos( tr.endpos );
+ m_pBeam->SetBrightness( 255.0f * flPaintPerc );
+ m_pBeam->RelinkBeam();
+
+ // Find points around that center point and make our designators converge at that point over time
+ UpdateAncillaryBeams( flPaintPerc, vecStart, vecCurrentDir );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_Combine_Cannon::OnScheduleChange( void )
+{
+ LaserOff();
+
+ m_hBarrageTarget = NULL;
+
+ BaseClass::OnScheduleChange();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_Combine_Cannon::Precache( void )
+{
+ PrecacheModel("models/combine_soldier.mdl");
+ PrecacheModel("effects/bluelaser1.vmt");
+
+ gHaloTexture = PrecacheModel("sprites/light_glow03.vmt");
+
+ PrecacheScriptSound( "NPC_Combine_Cannon.FireBullet" );
+
+ BaseClass::Precache();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_Combine_Cannon::Spawn( void )
+{
+ Precache();
+
+ /// HACK:
+ SetModel( "models/combine_soldier.mdl" );
+
+ // Setup our ancillary beams but keep them hidden for now
+ CreateLaser();
+ CreateAncillaryBeams();
+
+ m_iAmmoType = GetAmmoDef()->Index( "CombineHeavyCannon" );
+
+ SetHullType( HULL_HUMAN );
+ SetHullSizeNormal();
+
+ UTIL_SetSize( this, Vector( -16, -16 , 0 ), Vector( 16, 16, 64 ) );
+
+ SetSolid( SOLID_BBOX );
+ AddSolidFlags( FSOLID_NOT_STANDABLE );
+ SetMoveType( MOVETYPE_FLY );
+ m_bloodColor = DONT_BLEED;
+ m_iHealth = 10;
+ m_flFieldOfView = DOT_45DEGREE;
+ m_NPCState = NPC_STATE_NONE;
+
+ if( HasSpawnFlags( SF_STARTDISABLED ) )
+ {
+ m_fEnabled = false;
+ }
+ else
+ {
+ m_fEnabled = true;
+ }
+
+ CapabilitiesClear();
+ CapabilitiesAdd( bits_CAP_INNATE_RANGE_ATTACK1 | bits_CAP_SIMPLE_RADIUS_DAMAGE );
+
+ m_HackedGunPos = Vector ( 0, 0, 0 );
+
+ AddSpawnFlags( SF_NPC_LONG_RANGE | SF_NPC_ALWAYSTHINK );
+
+ NPCInit();
+
+ // Limit our look distance
+ SetDistLook( m_flSightDist );
+
+ AddEffects( EF_NODRAW );
+ AddSolidFlags( FSOLID_NOT_SOLID );
+
+ // Point the cursor straight ahead so that the sniper's
+ // first sweep of the laser doesn't look weird.
+ Vector vecForward;
+ AngleVectors( GetLocalAngles(), &vecForward );
+ m_vecPaintCursor = GetBulletOrigin() + vecForward * 1024;
+
+ // none!
+ GetEnemies()->SetFreeKnowledgeDuration( 0.0f );
+ GetEnemies()->SetEnemyDiscardTime( 2.0f );
+
+ m_flTimeLastAttackedPlayer = 0.0f;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+Class_T CNPC_Combine_Cannon::Classify( void )
+{
+ if ( m_fEnabled )
+ return CLASS_COMBINE;
+
+ return CLASS_NONE;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+Vector CNPC_Combine_Cannon::GetBulletOrigin( void )
+{
+ return GetAbsOrigin();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Nothing kills the cannon but entity I/O
+//-----------------------------------------------------------------------------
+int CNPC_Combine_Cannon::OnTakeDamage_Alive( const CTakeDamageInfo &info )
+{
+ // We are invulnerable to normal attacks for the moment
+ return 0;
+}
+
+//---------------------------------------------------------
+// Purpose:
+//---------------------------------------------------------
+void CNPC_Combine_Cannon::UpdateOnRemove( void )
+{
+ // Remove the main laser
+ if ( m_pBeam != NULL )
+ {
+ UTIL_Remove( m_pBeam);
+ m_pBeam = NULL;
+ }
+
+ // Remove our ancillary beams
+ for ( int i = 0; i < NUM_ANCILLARY_BEAMS; i++ )
+ {
+ if ( m_pAncillaryBeams[i] == NULL )
+ continue;
+
+ UTIL_Remove( m_pAncillaryBeams[i] );
+ m_pAncillaryBeams[i] = NULL;
+ }
+
+ BaseClass::UpdateOnRemove();
+}
+
+//---------------------------------------------------------
+// Purpose:
+//---------------------------------------------------------
+int CNPC_Combine_Cannon::SelectSchedule ( void )
+{
+ // Fire at our target
+ if( GetEnemy() && HasCondition( COND_CAN_RANGE_ATTACK1 ) )
+ return SCHED_RANGE_ATTACK1;
+
+ // Wait for a target
+ // TODO: Sweep like a sniper?
+ return SCHED_COMBAT_STAND;
+}
+
+//---------------------------------------------------------
+// Purpose:
+//---------------------------------------------------------
+bool CNPC_Combine_Cannon::FCanCheckAttacks ( void )
+{
+ return true;
+}
+
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+bool CNPC_Combine_Cannon::VerifyShot( CBaseEntity *pTarget )
+{
+ trace_t tr;
+
+ Vector vecTarget = DesiredBodyTarget( pTarget );
+ UTIL_TraceLine( GetBulletOrigin(), vecTarget, MASK_SHOT, pTarget, COLLISION_GROUP_NONE, &tr );
+
+ if( tr.fraction != 1.0 )
+ {
+ if( pTarget->IsPlayer() )
+ {
+ // if the target is the player, do another trace to see if we can shoot his eyeposition. This should help
+ // improve sniper responsiveness in cases where the player is hiding his chest from the sniper with his
+ // head in full view.
+ UTIL_TraceLine( GetBulletOrigin(), pTarget->EyePosition(), MASK_SHOT, pTarget, COLLISION_GROUP_NONE, &tr );
+
+ if( tr.fraction == 1.0 )
+ {
+ return true;
+ }
+ }
+
+ // Trace hit something.
+ if( tr.m_pEnt )
+ {
+ if( tr.m_pEnt->m_takedamage == DAMAGE_YES )
+ {
+ // Just shoot it if I can hurt it. Probably a breakable or glass pane.
+ return true;
+ }
+ }
+
+ return false;
+ }
+ else
+ {
+ return true;
+ }
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+int CNPC_Combine_Cannon::RangeAttack1Conditions( float flDot, float flDist )
+{
+ if ( GetNextAttack() > gpGlobals->curtime )
+ return COND_NONE;
+
+ if ( HasCondition( COND_SEE_ENEMY ) && !HasCondition( COND_ENEMY_OCCLUDED ) )
+ {
+ if ( VerifyShot( GetEnemy() ) )
+ {
+ // Can see the enemy, have a clear shot to his midsection
+ ClearCondition( COND_CANNON_NO_SHOT );
+ return COND_CAN_RANGE_ATTACK1;
+ }
+ else
+ {
+ // Can see the enemy, but can't take a shot at his midsection
+ SetCondition( COND_CANNON_NO_SHOT );
+ return COND_NONE;
+ }
+ }
+
+ return COND_NONE;
+}
+
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+int CNPC_Combine_Cannon::TranslateSchedule( int scheduleType )
+{
+ switch( scheduleType )
+ {
+ case SCHED_RANGE_ATTACK1:
+ return SCHED_CANNON_ATTACK;
+ break;
+ }
+ return BaseClass::TranslateSchedule( scheduleType );
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+void CNPC_Combine_Cannon::ScopeGlint( void )
+{
+ CEffectData data;
+
+ data.m_vOrigin = GetAbsOrigin();
+ data.m_vNormal = vec3_origin;
+ data.m_vAngles = vec3_angle;
+ data.m_nColor = COMMAND_POINT_BLUE;
+
+ DispatchEffect( "CommandPointer", data );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *vecIn -
+//-----------------------------------------------------------------------------
+void CNPC_Combine_Cannon::AdjustShotPosition( CBaseEntity *pTarget, Vector *vecIn )
+{
+ if ( pTarget == NULL || vecIn == NULL )
+ return;
+
+ Vector low = pTarget->WorldSpaceCenter() - ( pTarget->WorldSpaceCenter() - pTarget->GetAbsOrigin() ) * .25;
+ Vector high = pTarget->EyePosition();
+ Vector delta = high - low;
+ Vector result = low + delta * 0.5;
+
+ // Only take the height
+ (*vecIn)[2] = result[2];
+}
+
+//---------------------------------------------------------
+// This starts the bullet state machine. The actual effects
+// of the bullet will happen later. This function schedules
+// those effects.
+//
+// fDirectShot indicates whether the bullet is a "direct shot"
+// that is - fired with the intent that it will strike the
+// enemy. Otherwise, the bullet is intended to strike a
+// decoy object or nothing at all in particular.
+//---------------------------------------------------------
+bool CNPC_Combine_Cannon::FireBullet( const Vector &vecTarget, bool bDirectShot )
+{
+ Vector vecBulletOrigin = GetBulletOrigin();
+ Vector vecDir = ( vecTarget - vecBulletOrigin );
+ VectorNormalize( vecDir );
+
+ FireBulletsInfo_t info;
+ info.m_iShots = 1;
+ info.m_iTracerFreq = 1.0f;
+ info.m_vecDirShooting = vecDir;
+ info.m_vecSrc = vecBulletOrigin;
+ info.m_flDistance = MAX_TRACE_LENGTH;
+ info.m_pAttacker = this;
+ info.m_iAmmoType = m_iAmmoType;
+ info.m_iPlayerDamage = 20.0f;
+ info.m_vecSpread = Vector( 0.015f, 0.015f, 0.015f ); // medium cone
+
+ FireBullets( info );
+
+ EmitSound( "NPC_Combine_Cannon.FireBullet" );
+
+ // Don't attack for a certain amount of time
+ SetNextAttack( gpGlobals->curtime + GetRefireTime() );
+
+ // Sniper had to be aiming here to fire here, so make it the cursor
+ m_vecPaintCursor = vecTarget;
+
+ LaserOff();
+
+ return true;
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+void CNPC_Combine_Cannon::StartTask( const Task_t *pTask )
+{
+ switch( pTask->iTask )
+ {
+ case TASK_CANNON_ATTACK_CURSOR:
+ break;
+
+ case TASK_RANGE_ATTACK1:
+ // Setup the information for this barrage
+ m_flBarrageDuration = gpGlobals->curtime + random->RandomFloat( 0.25f, 0.5f );
+ m_hBarrageTarget = GetEnemy();
+ break;
+
+ case TASK_CANNON_PAINT_ENEMY:
+ {
+ if ( GetEnemy()->IsPlayer() )
+ {
+ float delay = random->RandomFloat( 0.0f, 0.3f );
+
+ if ( ( gpGlobals->curtime - m_flTimeLastAttackedPlayer ) < 1.0f )
+ {
+ SetWait( CANNON_SUBSEQUENT_PAINT_TIME );
+ m_flPaintTime = CANNON_SUBSEQUENT_PAINT_TIME;
+ }
+ else
+ {
+ SetWait( CANNON_PAINT_ENEMY_TIME + delay );
+ m_flPaintTime = CANNON_PAINT_ENEMY_TIME + delay;
+ }
+ }
+ else
+ {
+ // Use a random time
+ m_flPaintTime = CANNON_PAINT_ENEMY_TIME + random->RandomFloat( 0, CANNON_PAINT_NPC_TIME_NOISE );
+ SetWait( m_flPaintTime );
+ }
+
+ // Try to start the laser where the player can't miss seeing it!
+ Vector vecCursor;
+ AngleVectors( GetEnemy()->GetLocalAngles(), &vecCursor );
+ vecCursor *= 300;
+ vecCursor += GetEnemy()->EyePosition();
+ LaserOn( vecCursor, Vector( 16, 16, 16 ) );
+ }
+ break;
+
+ default:
+ BaseClass::StartTask( pTask );
+ break;
+ }
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+void CNPC_Combine_Cannon::RunTask( const Task_t *pTask )
+{
+ switch( pTask->iTask )
+ {
+ case TASK_CANNON_ATTACK_CURSOR:
+ if( FireBullet( m_vecPaintCursor, true ) )
+ {
+ TaskComplete();
+ }
+ break;
+
+ case TASK_RANGE_ATTACK1:
+ {
+ // Where we're focusing our fire
+ Vector vecTarget = ( m_hBarrageTarget == NULL ) ? m_vecPaintCursor : LeadTarget( m_hBarrageTarget );
+
+ // Fire at enemy
+ if ( FireBullet( vecTarget, true ) )
+ {
+ bool bPlayerIsEnemy = ( m_hBarrageTarget && m_hBarrageTarget->IsPlayer() );
+ bool bBarrageFinished = m_flBarrageDuration < gpGlobals->curtime;
+ bool bNoShot = ( QuerySeeEntity( m_hBarrageTarget ) == false ); // FIXME: Store this info off better
+ bool bSeePlayer = HasCondition( COND_SEE_PLAYER );
+
+ // Treat the player differently to normal NPCs
+ if ( bPlayerIsEnemy )
+ {
+ // Store the last time we shot for doing an abbreviated attack telegraph
+ m_flTimeLastAttackedPlayer = gpGlobals->curtime;
+
+ // If we've got no shot and we're done with our current barrage
+ if ( bNoShot && bBarrageFinished )
+ {
+ TaskComplete();
+ }
+ }
+ else if ( bBarrageFinished || bSeePlayer )
+ {
+ // Done with the barrage or we saw the player as a better target
+ TaskComplete();
+ }
+ }
+ }
+ break;
+
+ case TASK_CANNON_PAINT_ENEMY:
+ {
+ // See if we're done painting our target
+ if ( IsWaitFinished() )
+ {
+ TaskComplete();
+ }
+
+ // Continue to paint the target
+ PaintTarget( LeadTarget( GetEnemy() ), m_flPaintTime );
+ }
+ break;
+
+ default:
+ BaseClass::RunTask( pTask );
+ break;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// The sniper throws away the circular list of old decoys when we restore.
+//-----------------------------------------------------------------------------
+int CNPC_Combine_Cannon::Restore( IRestore &restore )
+{
+ return BaseClass::Restore( restore );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//
+//
+//-----------------------------------------------------------------------------
+float CNPC_Combine_Cannon::MaxYawSpeed( void )
+{
+ return 60;
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+void CNPC_Combine_Cannon::PrescheduleThink( void )
+{
+ BaseClass::PrescheduleThink();
+
+ // NOTE: We'll deal with this on the client
+ // Think faster if the beam is on, this gives the beam higher resolution.
+ if( m_pBeam )
+ {
+ SetNextThink( gpGlobals->curtime + 0.03 );
+ }
+ else
+ {
+ SetNextThink( gpGlobals->curtime + 0.1f );
+ }
+
+ // If the enemy has just stepped into view, or we've acquired a new enemy,
+ // Record the last time we've seen the enemy as right now.
+ //
+ // If the enemy has been out of sight for a full second, mark him eluded.
+ if( GetEnemy() != NULL )
+ {
+ if( gpGlobals->curtime - GetEnemies()->LastTimeSeen( GetEnemy() ) > 30 )
+ {
+ // Stop pestering enemies after 30 seconds of frustration.
+ GetEnemies()->ClearMemory( GetEnemy() );
+ SetEnemy(NULL);
+ }
+ }
+}
+
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+Vector CNPC_Combine_Cannon::EyePosition( void )
+{
+ return GetAbsOrigin();
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+Vector CNPC_Combine_Cannon::DesiredBodyTarget( CBaseEntity *pTarget )
+{
+ // By default, aim for the center
+ Vector vecTarget = pTarget->WorldSpaceCenter();
+
+ float flTimeSinceLastMiss = gpGlobals->curtime - m_flTimeLastShotMissed;
+
+ if( pTarget->GetFlags() & FL_CLIENT )
+ {
+ if( !BaseClass::FVisible( vecTarget ) )
+ {
+ // go to the player's eyes if his center is concealed.
+ // Bump up an inch so the player's not looking straight down a beam.
+ vecTarget = pTarget->EyePosition() + Vector( 0, 0, 1 );
+ }
+ }
+ else
+ {
+ if( pTarget->Classify() == CLASS_HEADCRAB )
+ {
+ // Headcrabs are tiny inside their boxes.
+ vecTarget = pTarget->GetAbsOrigin();
+ vecTarget.z += 4.0;
+ }
+ else if( pTarget->Classify() == CLASS_ZOMBIE )
+ {
+ if( flTimeSinceLastMiss > 0.0f && flTimeSinceLastMiss < 4.0f && hl2_episodic.GetBool() )
+ {
+ vecTarget = pTarget->BodyTarget( GetBulletOrigin(), false );
+ }
+ else
+ {
+ // Shoot zombies in the headcrab
+ vecTarget = pTarget->HeadTarget( GetBulletOrigin() );
+ }
+ }
+ else if( pTarget->Classify() == CLASS_ANTLION )
+ {
+ // Shoot about a few inches above the origin. This makes it easy to hit antlions
+ // even if they are on their backs.
+ vecTarget = pTarget->GetAbsOrigin();
+ vecTarget.z += 18.0f;
+ }
+ else if( pTarget->Classify() == CLASS_EARTH_FAUNA )
+ {
+ // Shoot birds in the center
+ }
+ else
+ {
+ // Shoot NPCs in the chest
+ vecTarget.z += 8.0f;
+ }
+ }
+
+ return vecTarget;
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+Vector CNPC_Combine_Cannon::LeadTarget( CBaseEntity *pTarget )
+{
+ if ( pTarget != NULL )
+ {
+ Vector vecFuturePos;
+ UTIL_PredictedPosition( pTarget, 0.05f, &vecFuturePos );
+ AdjustShotPosition( pTarget, &vecFuturePos );
+
+ return vecFuturePos;
+ }
+
+ return vec3_origin;
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+void CNPC_Combine_Cannon::InputEnableSniper( inputdata_t &inputdata )
+{
+ ClearCondition( COND_CANNON_DISABLED );
+ SetCondition( COND_CANNON_ENABLED );
+
+ m_fEnabled = true;
+}
+
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+void CNPC_Combine_Cannon::InputDisableSniper( inputdata_t &inputdata )
+{
+ ClearCondition( COND_CANNON_ENABLED );
+ SetCondition( COND_CANNON_DISABLED );
+
+ m_fEnabled = false;
+}
+
+//---------------------------------------------------------
+// See all NPC's easily.
+//
+// Only see the player if you can trace to both of his
+// eyeballs. That is, allow the player to peek around corners.
+// This is a little more expensive than the base class' check!
+//---------------------------------------------------------
+#define CANNON_EYE_DIST 0.75
+#define CANNON_TARGET_VERTICAL_OFFSET Vector( 0, 0, 5 );
+bool CNPC_Combine_Cannon::FVisible( CBaseEntity *pEntity, int traceMask, CBaseEntity **ppBlocker )
+{
+ // NPC
+ if ( pEntity->IsPlayer() == false )
+ return BaseClass::FVisible( pEntity, traceMask, ppBlocker );
+
+ if ( pEntity->GetFlags() & FL_NOTARGET )
+ return false;
+
+ Vector vecVerticalOffset;
+ Vector vecRight;
+ Vector vecEye;
+ trace_t tr;
+
+ if( fabs( GetAbsOrigin().z - pEntity->WorldSpaceCenter().z ) <= 120.f )
+ {
+ // If the player is around the same elevation, look straight at his eyes.
+ // At the same elevation, the vertical peeking allowance makes it too easy
+ // for a player to dispatch the sniper from cover.
+ vecVerticalOffset = vec3_origin;
+ }
+ else
+ {
+ // Otherwise, look at a spot below his eyes. This allows the player to back away
+ // from his cover a bit and have a peek at the sniper without being detected.
+ vecVerticalOffset = CANNON_TARGET_VERTICAL_OFFSET;
+ }
+
+ AngleVectors( pEntity->GetLocalAngles(), NULL, &vecRight, NULL );
+
+ vecEye = vecRight * CANNON_EYE_DIST - vecVerticalOffset;
+ UTIL_TraceLine( EyePosition(), pEntity->EyePosition() + vecEye, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
+
+#if 0
+ NDebugOverlay::Line(EyePosition(), tr.endpos, 0,255,0, true, 0.1);
+#endif
+
+ bool fCheckFailed = false;
+
+ if ( tr.fraction != 1.0 && tr.m_pEnt != pEntity )
+ {
+ fCheckFailed = true;
+ }
+
+ // Don't check the other eye if the first eye failed.
+ if( !fCheckFailed )
+ {
+ vecEye = -vecRight * CANNON_EYE_DIST - vecVerticalOffset;
+ UTIL_TraceLine( EyePosition(), pEntity->EyePosition() + vecEye, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
+
+#if 0
+ NDebugOverlay::Line(EyePosition(), tr.endpos, 0,255,0, true, 0.1);
+#endif
+
+ if ( tr.fraction != 1.0 && tr.m_pEnt != pEntity )
+ {
+ fCheckFailed = true;
+ }
+ }
+
+ if( !fCheckFailed )
+ {
+ // Can see the player.
+ return true;
+ }
+
+ // Now, if the check failed, see if the player is ducking and has recently
+ // fired a muzzleflash. If yes, see if you'd be able to see the player if
+ // they were standing in their current position instead of ducking. Since
+ // the sniper doesn't have a clear shot in this situation, he will harrass
+ // near the player.
+ CBasePlayer *pPlayer;
+
+ pPlayer = ToBasePlayer( pEntity );
+
+ if( (pPlayer->GetFlags() & FL_DUCKING) && pPlayer->MuzzleFlashTime() > gpGlobals->curtime )
+ {
+ vecEye = pPlayer->EyePosition() + Vector( 0, 0, 32 );
+ UTIL_TraceLine( EyePosition(), vecEye, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
+
+ if( tr.fraction != 1.0 )
+ {
+ // Everything failed.
+ if (ppBlocker)
+ {
+ *ppBlocker = tr.m_pEnt;
+ }
+ return false;
+ }
+ else
+ {
+ // Fake being able to see the player.
+ return true;
+ }
+ }
+
+ if (ppBlocker)
+ {
+ *ppBlocker = tr.m_pEnt;
+ }
+
+ return false;
+}
+
+
+//-----------------------------------------------------------------------------
+//
+// Schedules
+//
+//-----------------------------------------------------------------------------
+
+AI_BEGIN_CUSTOM_NPC( npc_combine_cannon, CNPC_Combine_Cannon )
+
+ DECLARE_CONDITION( COND_CANNON_ENABLED );
+ DECLARE_CONDITION( COND_CANNON_DISABLED );
+ DECLARE_CONDITION( COND_CANNON_NO_SHOT );
+
+ DECLARE_TASK( TASK_CANNON_PAINT_ENEMY );
+ DECLARE_TASK( TASK_CANNON_ATTACK_CURSOR );
+
+ //=========================================================
+ // CAMP
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_CANNON_CAMP,
+
+ " Tasks"
+ " TASK_WAIT 1"
+ " "
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_ENEMY_DEAD"
+ " COND_CAN_RANGE_ATTACK1"
+ " COND_HEAR_DANGER"
+ " COND_CANNON_DISABLED"
+ )
+
+ //=========================================================
+ // ATTACK
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_CANNON_ATTACK,
+
+ " Tasks"
+ " TASK_CANNON_PAINT_ENEMY 0"
+ " TASK_RANGE_ATTACK1 0"
+ " "
+ " Interrupts"
+ " COND_HEAR_DANGER"
+ " COND_CANNON_DISABLED"
+ )
+
+ //=========================================================
+ // ATTACK
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_CANNON_SNAPATTACK,
+
+ " Tasks"
+ " TASK_CANNON_ATTACK_CURSOR 0"
+ " "
+ " Interrupts"
+ " COND_ENEMY_OCCLUDED"
+ " COND_ENEMY_DEAD"
+ " COND_NEW_ENEMY"
+ " COND_HEAR_DANGER"
+ " COND_CANNON_DISABLED"
+ )
+
+ //=========================================================
+ // Sniper is allowed to process a couple conditions while
+ // disabled, but mostly he waits until he's enabled.
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_CANNON_DISABLEDWAIT,
+
+ " Tasks"
+ " TASK_WAIT 0.5"
+ " "
+ " Interrupts"
+ " COND_CANNON_ENABLED"
+ " COND_NEW_ENEMY"
+ " COND_ENEMY_DEAD"
+ )
+
+AI_END_CUSTOM_NPC()
diff --git a/mp/src/game/server/episodic/npc_hunter.cpp b/mp/src/game/server/episodic/npc_hunter.cpp
index 9d7dfd3e..d55053bc 100644
--- a/mp/src/game/server/episodic/npc_hunter.cpp
+++ b/mp/src/game/server/episodic/npc_hunter.cpp
@@ -1,7764 +1,7764 @@
-//========= Copyright Valve Corporation, All rights reserved. ============//
-//
-// Small, fast version of the strider. Goes where striders cannot, such
-// as into buildings. Best killed with physics objects and explosives.
-//
-//=============================================================================
-
-#include "cbase.h"
-#include "npc_strider.h"
-#include "npc_hunter.h"
-#include "ai_behavior_follow.h"
-#include "ai_moveprobe.h"
-#include "ai_senses.h"
-#include "ai_speech.h"
-#include "ai_task.h"
-#include "ai_default.h"
-#include "ai_schedule.h"
-#include "ai_hull.h"
-#include "ai_baseactor.h"
-#include "ai_waypoint.h"
-#include "ai_link.h"
-#include "ai_hint.h"
-#include "ai_squadslot.h"
-#include "ai_squad.h"
-#include "ai_tacticalservices.h"
-#include "beam_shared.h"
-#include "datacache/imdlcache.h"
-#include "eventqueue.h"
-#include "gib.h"
-#include "globalstate.h"
-#include "hierarchy.h"
-#include "movevars_shared.h"
-#include "npcevent.h"
-#include "saverestore_utlvector.h"
-#include "particle_parse.h"
-#include "te_particlesystem.h"
-#include "sceneentity.h"
-#include "shake.h"
-#include "soundenvelope.h"
-#include "soundent.h"
-#include "SpriteTrail.h"
-#include "IEffects.h"
-#include "engine/IEngineSound.h"
-#include "bone_setup.h"
-#include "studio.h"
-#include "ai_route.h"
-#include "ammodef.h"
-#include "npc_bullseye.h"
-#include "physobj.h"
-#include "ai_memory.h"
-#include "collisionutils.h"
-#include "shot_manipulator.h"
-#include "steamjet.h"
-#include "physics_prop_ragdoll.h"
-#include "vehicle_base.h"
-#include "coordsize.h"
-#include "hl2_shareddefs.h"
-#include "te_effect_dispatch.h"
-#include "beam_flags.h"
-#include "prop_combine_ball.h"
-#include "explode.h"
-#include "weapon_physcannon.h"
-#include "weapon_striderbuster.h"
-#include "monstermaker.h"
-#include "weapon_rpg.h"
-
-// memdbgon must be the last include file in a .cpp file!!!
-#include "tier0/memdbgon.h"
-
-class CNPC_Hunter;
-
-
-static const char *HUNTER_FLECHETTE_MODEL = "models/weapons/hunter_flechette.mdl";
-
-// Think contexts
-static const char *HUNTER_BLEED_THINK = "HunterBleed";
-static const char *HUNTER_ZAP_THINK = "HunterZap";
-static const char *HUNTER_JOSTLE_VEHICLE_THINK = "HunterJostle";
-
-
-ConVar sk_hunter_health( "sk_hunter_health", "210" );
-
-// Melee attacks
-ConVar sk_hunter_dmg_one_slash( "sk_hunter_dmg_one_slash", "20" );
-ConVar sk_hunter_dmg_charge( "sk_hunter_dmg_charge", "20" );
-
-// Flechette volley attack
-ConVar hunter_flechette_max_range( "hunter_flechette_max_range", "1200" );
-ConVar hunter_flechette_min_range( "hunter_flechette_min_range", "100" );
-ConVar hunter_flechette_volley_size( "hunter_flechette_volley_size", "8" );
-ConVar hunter_flechette_speed( "hunter_flechette_speed", "2000" );
-ConVar sk_hunter_dmg_flechette( "sk_hunter_dmg_flechette", "4.0" );
-ConVar sk_hunter_flechette_explode_dmg( "sk_hunter_flechette_explode_dmg", "12.0" );
-ConVar sk_hunter_flechette_explode_radius( "sk_hunter_flechette_explode_radius", "128.0" );
-ConVar hunter_flechette_explode_delay( "hunter_flechette_explode_delay", "2.5" );
-ConVar hunter_flechette_delay( "hunter_flechette_delay", "0.1" );
-ConVar hunter_first_flechette_delay( "hunter_first_flechette_delay", "0.5" );
-ConVar hunter_flechette_max_concurrent_volleys( "hunter_flechette_max_concurrent_volleys", "2" );
-ConVar hunter_flechette_volley_start_min_delay( "hunter_flechette_volley_start_min_delay", ".25" );
-ConVar hunter_flechette_volley_start_max_delay( "hunter_flechette_volley_start_max_delay", ".95" );
-ConVar hunter_flechette_volley_end_min_delay( "hunter_flechette_volley_end_min_delay", "1" );
-ConVar hunter_flechette_volley_end_max_delay( "hunter_flechette_volley_end_max_delay", "2" );
-ConVar hunter_flechette_test( "hunter_flechette_test", "0" );
-ConVar hunter_clamp_shots( "hunter_clamp_shots", "1" );
-ConVar hunter_cheap_explosions( "hunter_cheap_explosions", "1" );
-
-// Damage received
-ConVar sk_hunter_bullet_damage_scale( "sk_hunter_bullet_damage_scale", "0.6" );
-ConVar sk_hunter_charge_damage_scale( "sk_hunter_charge_damage_scale", "2.0" );
-ConVar sk_hunter_buckshot_damage_scale( "sk_hunter_buckshot_damage_scale", "0.5" );
-ConVar sk_hunter_vehicle_damage_scale( "sk_hunter_vehicle_damage_scale", "2.2" );
-ConVar sk_hunter_dmg_from_striderbuster( "sk_hunter_dmg_from_striderbuster", "150" );
-ConVar sk_hunter_citizen_damage_scale( "sk_hunter_citizen_damage_scale", "0.3" );
-
-ConVar hunter_allow_dissolve( "hunter_allow_dissolve", "1" );
-ConVar hunter_random_expressions( "hunter_random_expressions", "0" );
-ConVar hunter_show_weapon_los_z( "hunter_show_weapon_los_z", "0" );
-ConVar hunter_show_weapon_los_condition( "hunter_show_weapon_los_condition", "0" );
-
-ConVar hunter_melee_delay( "hunter_melee_delay", "2.0" );
-
-// Bullrush charge.
-ConVar hunter_charge( "hunter_charge", "1" );
-ConVar hunter_charge_min_delay( "hunter_charge_min_delay", "10.0" );
-ConVar hunter_charge_pct( "hunter_charge_pct", "25" );
-ConVar hunter_charge_test( "hunter_charge_test", "0" );
-
-// Vehicle dodging.
-ConVar hunter_dodge_warning( "hunter_dodge_warning", "1.1" );
-ConVar hunter_dodge_warning_width( "hunter_dodge_warning_width", "180" );
-ConVar hunter_dodge_warning_cone( "hunter_dodge_warning_cone", ".5" );
-ConVar hunter_dodge_debug( "hunter_dodge_debug", "0" );
-
-// Jostle vehicles when hit by them
-ConVar hunter_jostle_car_min_speed( "hunter_jostle_car_min_speed", "100" ); // If hit by a car going at least this fast, jostle the car
-ConVar hunter_jostle_car_max_speed( "hunter_jostle_car_max_speed", "600" ); // Used for determining jostle scale
-
-ConVar hunter_free_knowledge( "hunter_free_knowledge", "10.0" );
-ConVar hunter_plant_adjust_z( "hunter_plant_adjust_z", "12" );
-
-ConVar hunter_disable_patrol( "hunter_disable_patrol", "0" );
-
-// Dealing with striderbusters
-ConVar hunter_hate_held_striderbusters( "hunter_hate_held_striderbusters", "1" );
-ConVar hunter_hate_thrown_striderbusters( "hunter_hate_thrown_striderbusters", "1" );
-ConVar hunter_hate_attached_striderbusters( "hunter_hate_attached_striderbusters", "1" );
-ConVar hunter_hate_held_striderbusters_delay( "hunter_hate_held_striderbusters_delay", "0.5" );
-ConVar hunter_hate_held_striderbusters_tolerance( "hunter_hate_held_striderbusters_tolerance", "2000.0" );
-ConVar hunter_hate_thrown_striderbusters_tolerance( "hunter_hate_thrown_striderbusters_tolerance", "300.0" );
-ConVar hunter_seek_thrown_striderbusters_tolerance( "hunter_seek_thrown_striderbusters_tolerance", "400.0" );
-ConVar hunter_retreat_striderbusters( "hunter_retreat_striderbusters", "1", FCVAR_NONE, "If true, the hunter will retreat when a buster is glued to him." );
-
-ConVar hunter_allow_nav_jump( "hunter_allow_nav_jump", "0" );
-ConVar g_debug_hunter_charge( "g_debug_hunter_charge", "0" );
-
-ConVar hunter_stand_still( "hunter_stand_still", "0" ); // used for debugging, keeps them rooted in place
-
-ConVar hunter_siege_frequency( "hunter_siege_frequency", "12" );
-
-#define HUNTER_FOV_DOT 0.0 // 180 degree field of view
-#define HUNTER_CHARGE_MIN 256
-#define HUNTER_CHARGE_MAX 1024
-#define HUNTER_FACE_ENEMY_DIST 512.0f
-#define HUNTER_MELEE_REACH 80
-#define HUNTER_BLOOD_LEFT_FOOT 0
-#define HUNTER_IGNORE_ENEMY_TIME 5 // How long the hunter will ignore another enemy when distracted by the player.
-
-#define HUNTER_FACING_DOT 0.8 // The angle within which we start shooting
-#define HUNTER_SHOOT_MAX_YAW_DEG 60.0f // Once shooting, clamp to +/- these degrees of yaw deflection as our target moves
-#define HUNTER_SHOOT_MAX_YAW_COS 0.5f // The cosine of the above angle
-
-#define HUNTER_FLECHETTE_WARN_TIME 1.0f
-
-#define HUNTER_SEE_ENEMY_TIME_INVALID -1
-
-#define NUM_FLECHETTE_VOLLEY_ON_FOLLOW 4
-
-#define HUNTER_SIEGE_MAX_DIST_MODIFIER 2.0f
-
-//-----------------------------------------------------------------------------
-// Animation events
-//-----------------------------------------------------------------------------
-int AE_HUNTER_FOOTSTEP_LEFT;
-int AE_HUNTER_FOOTSTEP_RIGHT;
-int AE_HUNTER_FOOTSTEP_BACK;
-int AE_HUNTER_MELEE_ANNOUNCE;
-int AE_HUNTER_MELEE_ATTACK_LEFT;
-int AE_HUNTER_MELEE_ATTACK_RIGHT;
-int AE_HUNTER_DIE;
-int AE_HUNTER_SPRAY_BLOOD;
-int AE_HUNTER_START_EXPRESSION;
-int AE_HUNTER_END_EXPRESSION;
-
-
-//-----------------------------------------------------------------------------
-// Interactions.
-//-----------------------------------------------------------------------------
-int g_interactionHunterFoundEnemy = 0;
-
-
-//-----------------------------------------------------------------------------
-// Local stuff.
-//-----------------------------------------------------------------------------
-static string_t s_iszStriderClassname;
-static string_t s_iszStriderBusterClassname;
-static string_t s_iszMagnadeClassname;
-static string_t s_iszPhysPropClassname;
-static string_t s_iszHuntersToRunOver;
-
-
-//-----------------------------------------------------------------------------
-// Custom Activities
-//-----------------------------------------------------------------------------
-Activity ACT_HUNTER_DEPLOYRA2;
-Activity ACT_HUNTER_DODGER;
-Activity ACT_HUNTER_DODGEL;
-Activity ACT_HUNTER_GESTURE_SHOOT;
-Activity ACT_HUNTER_FLINCH_STICKYBOMB;
-Activity ACT_HUNTER_STAGGER;
-Activity ACT_HUNTER_MELEE_ATTACK1_VS_PLAYER;
-Activity ACT_DI_HUNTER_MELEE;
-Activity ACT_DI_HUNTER_THROW;
-Activity ACT_HUNTER_ANGRY;
-Activity ACT_HUNTER_WALK_ANGRY;
-Activity ACT_HUNTER_FOUND_ENEMY;
-Activity ACT_HUNTER_FOUND_ENEMY_ACK;
-Activity ACT_HUNTER_CHARGE_START;
-Activity ACT_HUNTER_CHARGE_RUN;
-Activity ACT_HUNTER_CHARGE_STOP;
-Activity ACT_HUNTER_CHARGE_CRASH;
-Activity ACT_HUNTER_CHARGE_HIT;
-Activity ACT_HUNTER_RANGE_ATTACK2_UNPLANTED;
-Activity ACT_HUNTER_IDLE_PLANTED;
-Activity ACT_HUNTER_FLINCH_N;
-Activity ACT_HUNTER_FLINCH_S;
-Activity ACT_HUNTER_FLINCH_E;
-Activity ACT_HUNTER_FLINCH_W;
-
-
-//-----------------------------------------------------------------------------
-// Squad slots
-//-----------------------------------------------------------------------------
-enum SquadSlot_t
-{
- SQUAD_SLOT_HUNTER_CHARGE = LAST_SHARED_SQUADSLOT,
- SQUAD_SLOT_HUNTER_FLANK_FIRST,
- SQUAD_SLOT_HUNTER_FLANK_LAST = SQUAD_SLOT_HUNTER_FLANK_FIRST,
- SQUAD_SLOT_RUN_SHOOT,
-};
-
-#define HUNTER_FOLLOW_DISTANCE 2000.0f
-#define HUNTER_FOLLOW_DISTANCE_SQR (HUNTER_FOLLOW_DISTANCE * HUNTER_FOLLOW_DISTANCE)
-
-#define HUNTER_RUNDOWN_SQUADDATA 0
-
-
-//-----------------------------------------------------------------------------
-// We're doing this quite a lot, so this makes the check a lot faster since
-// we don't have to compare strings.
-//-----------------------------------------------------------------------------
-bool IsStriderBuster( CBaseEntity *pEntity )
-{
- if ( !pEntity )
- return false;
-
- if( pEntity->m_iClassname == s_iszStriderBusterClassname ||
- pEntity->m_iClassname == s_iszMagnadeClassname)
- return true;
-
- return false;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-bool HateThisStriderBuster( CBaseEntity *pTarget )
-{
- if ( StriderBuster_WasKnockedOffStrider(pTarget) )
- return false;
-
- if ( pTarget->VPhysicsGetObject() )
- {
- if ( hunter_hate_held_striderbusters.GetBool() ||
- hunter_hate_thrown_striderbusters.GetBool() ||
- hunter_hate_attached_striderbusters.GetBool() )
- {
- if ( ( pTarget->VPhysicsGetObject()->GetGameFlags() & ( FVPHYSICS_PLAYER_HELD | FVPHYSICS_WAS_THROWN ) ) )
- {
- return true;
- }
-
- if ( StriderBuster_IsAttachedStriderBuster( pTarget ) )
- {
- return true;
- }
- }
- }
-
- return false;
-}
-
-
-//-----------------------------------------------------------------------------
-// The hunter can fire a volley of explosive flechettes.
-//-----------------------------------------------------------------------------
-static const char *s_szHunterFlechetteBubbles = "HunterFlechetteBubbles";
-static const char *s_szHunterFlechetteSeekThink = "HunterFlechetteSeekThink";
-static const char *s_szHunterFlechetteDangerSoundThink = "HunterFlechetteDangerSoundThink";
-static const char *s_szHunterFlechetteSpriteTrail = "sprites/bluelaser1.vmt";
-static int s_nHunterFlechetteImpact = -2;
-static int s_nFlechetteFuseAttach = -1;
-
-#define FLECHETTE_AIR_VELOCITY 2500
-
-class CHunterFlechette : public CPhysicsProp, public IParentPropInteraction
-{
- DECLARE_CLASS( CHunterFlechette, CPhysicsProp );
-
-public:
-
- CHunterFlechette();
- ~CHunterFlechette();
-
- Class_T Classify() { return CLASS_NONE; }
-
- bool WasThrownBack()
- {
- return m_bThrownBack;
- }
-
-public:
-
- void Spawn();
- void Activate();
- void Precache();
- void Shoot( Vector &vecVelocity, bool bBright );
- void SetSeekTarget( CBaseEntity *pTargetEntity );
- void Explode();
-
- bool CreateVPhysics();
-
- unsigned int PhysicsSolidMaskForEntity() const;
- static CHunterFlechette *FlechetteCreate( const Vector &vecOrigin, const QAngle &angAngles, CBaseEntity *pentOwner = NULL );
-
- // IParentPropInteraction
- void OnParentCollisionInteraction( parentCollisionInteraction_t eType, int index, gamevcollisionevent_t *pEvent );
- void OnParentPhysGunDrop( CBasePlayer *pPhysGunUser, PhysGunDrop_t Reason );
-
-protected:
-
- void SetupGlobalModelData();
-
- void StickTo( CBaseEntity *pOther, trace_t &tr );
-
- void BubbleThink();
- void DangerSoundThink();
- void ExplodeThink();
- void DopplerThink();
- void SeekThink();
-
- bool CreateSprites( bool bBright );
-
- void FlechetteTouch( CBaseEntity *pOther );
-
- Vector m_vecShootPosition;
- EHANDLE m_hSeekTarget;
- bool m_bThrownBack;
-
- DECLARE_DATADESC();
- //DECLARE_SERVERCLASS();
-};
-
-LINK_ENTITY_TO_CLASS( hunter_flechette, CHunterFlechette );
-
-BEGIN_DATADESC( CHunterFlechette )
-
- DEFINE_THINKFUNC( BubbleThink ),
- DEFINE_THINKFUNC( DangerSoundThink ),
- DEFINE_THINKFUNC( ExplodeThink ),
- DEFINE_THINKFUNC( DopplerThink ),
- DEFINE_THINKFUNC( SeekThink ),
-
- DEFINE_ENTITYFUNC( FlechetteTouch ),
-
- DEFINE_FIELD( m_vecShootPosition, FIELD_POSITION_VECTOR ),
- DEFINE_FIELD( m_hSeekTarget, FIELD_EHANDLE ),
- DEFINE_FIELD( m_bThrownBack, FIELD_BOOLEAN ),
-
-END_DATADESC()
-
-//IMPLEMENT_SERVERCLASS_ST( CHunterFlechette, DT_HunterFlechette )
-//END_SEND_TABLE()
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-CHunterFlechette *CHunterFlechette::FlechetteCreate( const Vector &vecOrigin, const QAngle &angAngles, CBaseEntity *pentOwner )
-{
- // Create a new entity with CHunterFlechette private data
- CHunterFlechette *pFlechette = (CHunterFlechette *)CreateEntityByName( "hunter_flechette" );
- UTIL_SetOrigin( pFlechette, vecOrigin );
- pFlechette->SetAbsAngles( angAngles );
- pFlechette->Spawn();
- pFlechette->Activate();
- pFlechette->SetOwnerEntity( pentOwner );
-
- return pFlechette;
-}
-
-
-//------------------------------------------------------------------------------
-//------------------------------------------------------------------------------
-void CC_Hunter_Shoot_Flechette( const CCommand& args )
-{
- MDLCACHE_CRITICAL_SECTION();
-
- bool allowPrecache = CBaseEntity::IsPrecacheAllowed();
- CBaseEntity::SetAllowPrecache( true );
-
- CBasePlayer *pPlayer = UTIL_GetCommandClient();
-
- QAngle angEye = pPlayer->EyeAngles();
- CHunterFlechette *entity = CHunterFlechette::FlechetteCreate( pPlayer->EyePosition(), angEye, pPlayer );
- if ( entity )
- {
- entity->Precache();
- DispatchSpawn( entity );
-
- // Shoot the flechette.
- Vector forward;
- pPlayer->EyeVectors( &forward );
- forward *= 2000.0f;
- entity->Shoot( forward, false );
- }
-
- CBaseEntity::SetAllowPrecache( allowPrecache );
-}
-
-static ConCommand ent_create("hunter_shoot_flechette", CC_Hunter_Shoot_Flechette, "Fires a hunter flechette where the player is looking.", FCVAR_GAMEDLL | FCVAR_CHEAT);
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-CHunterFlechette::CHunterFlechette()
-{
- UseClientSideAnimation();
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-CHunterFlechette::~CHunterFlechette()
-{
-}
-
-
-//-----------------------------------------------------------------------------
-// If set, the flechette will seek unerringly toward the target as it flies.
-//-----------------------------------------------------------------------------
-void CHunterFlechette::SetSeekTarget( CBaseEntity *pTargetEntity )
-{
- if ( pTargetEntity )
- {
- m_hSeekTarget = pTargetEntity;
- SetContextThink( &CHunterFlechette::SeekThink, gpGlobals->curtime, s_szHunterFlechetteSeekThink );
- }
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-bool CHunterFlechette::CreateVPhysics()
-{
- // Create the object in the physics system
- VPhysicsInitNormal( SOLID_BBOX, FSOLID_NOT_STANDABLE, false );
-
- return true;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-unsigned int CHunterFlechette::PhysicsSolidMaskForEntity() const
-{
- return ( BaseClass::PhysicsSolidMaskForEntity() | CONTENTS_HITBOX ) & ~CONTENTS_GRATE;
-}
-
-
-//-----------------------------------------------------------------------------
-// Called from CPropPhysics code when we're attached to a physics object.
-//-----------------------------------------------------------------------------
-void CHunterFlechette::OnParentCollisionInteraction( parentCollisionInteraction_t eType, int index, gamevcollisionevent_t *pEvent )
-{
- if ( eType == COLLISIONINTER_PARENT_FIRST_IMPACT )
- {
- m_bThrownBack = true;
- Explode();
- }
-}
-
-void CHunterFlechette::OnParentPhysGunDrop( CBasePlayer *pPhysGunUser, PhysGunDrop_t Reason )
-{
- m_bThrownBack = true;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-bool CHunterFlechette::CreateSprites( bool bBright )
-{
- if ( bBright )
- {
- DispatchParticleEffect( "hunter_flechette_trail_striderbuster", PATTACH_ABSORIGIN_FOLLOW, this );
- }
- else
- {
- DispatchParticleEffect( "hunter_flechette_trail", PATTACH_ABSORIGIN_FOLLOW, this );
- }
-
- return true;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CHunterFlechette::Spawn()
-{
- Precache( );
-
- SetModel( HUNTER_FLECHETTE_MODEL );
- SetMoveType( MOVETYPE_FLYGRAVITY, MOVECOLLIDE_FLY_CUSTOM );
- UTIL_SetSize( this, -Vector(1,1,1), Vector(1,1,1) );
- SetSolid( SOLID_BBOX );
- SetGravity( 0.05f );
- SetCollisionGroup( COLLISION_GROUP_PROJECTILE );
-
- // Make sure we're updated if we're underwater
- UpdateWaterState();
-
- SetTouch( &CHunterFlechette::FlechetteTouch );
-
- // Make us glow until we've hit the wall
- m_nSkin = 1;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CHunterFlechette::Activate()
-{
- BaseClass::Activate();
- SetupGlobalModelData();
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CHunterFlechette::SetupGlobalModelData()
-{
- if ( s_nHunterFlechetteImpact == -2 )
- {
- s_nHunterFlechetteImpact = LookupSequence( "impact" );
- s_nFlechetteFuseAttach = LookupAttachment( "attach_fuse" );
- }
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CHunterFlechette::Precache()
-{
- PrecacheModel( HUNTER_FLECHETTE_MODEL );
- PrecacheModel( "sprites/light_glow02_noz.vmt" );
-
- PrecacheScriptSound( "NPC_Hunter.FlechetteNearmiss" );
- PrecacheScriptSound( "NPC_Hunter.FlechetteHitBody" );
- PrecacheScriptSound( "NPC_Hunter.FlechetteHitWorld" );
- PrecacheScriptSound( "NPC_Hunter.FlechettePreExplode" );
- PrecacheScriptSound( "NPC_Hunter.FlechetteExplode" );
-
- PrecacheParticleSystem( "hunter_flechette_trail_striderbuster" );
- PrecacheParticleSystem( "hunter_flechette_trail" );
- PrecacheParticleSystem( "hunter_projectile_explosion_1" );
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CHunterFlechette::StickTo( CBaseEntity *pOther, trace_t &tr )
-{
- EmitSound( "NPC_Hunter.FlechetteHitWorld" );
-
- SetMoveType( MOVETYPE_NONE );
-
- if ( !pOther->IsWorld() )
- {
- SetParent( pOther );
- SetSolid( SOLID_NONE );
- SetSolidFlags( FSOLID_NOT_SOLID );
- }
-
- // Do an impact effect.
- //Vector vecDir = GetAbsVelocity();
- //float speed = VectorNormalize( vecDir );
-
- //Vector vForward;
- //AngleVectors( GetAbsAngles(), &vForward );
- //VectorNormalize ( vForward );
-
- //CEffectData data;
- //data.m_vOrigin = tr.endpos;
- //data.m_vNormal = vForward;
- //data.m_nEntIndex = 0;
- //DispatchEffect( "BoltImpact", data );
-
- Vector vecVelocity = GetAbsVelocity();
- bool bAttachedToBuster = StriderBuster_OnFlechetteAttach( pOther, vecVelocity );
-
- SetTouch( NULL );
-
- // We're no longer flying. Stop checking for water volumes.
- SetContextThink( NULL, 0, s_szHunterFlechetteBubbles );
-
- // Stop seeking.
- m_hSeekTarget = NULL;
- SetContextThink( NULL, 0, s_szHunterFlechetteSeekThink );
-
- // Get ready to explode.
- if ( !bAttachedToBuster )
- {
- SetThink( &CHunterFlechette::DangerSoundThink );
- SetNextThink( gpGlobals->curtime + (hunter_flechette_explode_delay.GetFloat() - HUNTER_FLECHETTE_WARN_TIME) );
- }
- else
- {
- DangerSoundThink();
- }
-
- // Play our impact animation.
- ResetSequence( s_nHunterFlechetteImpact );
-
- static int s_nImpactCount = 0;
- s_nImpactCount++;
- if ( s_nImpactCount & 0x01 )
- {
- UTIL_ImpactTrace( &tr, DMG_BULLET );
-
- // Shoot some sparks
- if ( UTIL_PointContents( GetAbsOrigin() ) != CONTENTS_WATER)
- {
- g_pEffects->Sparks( GetAbsOrigin() );
- }
- }
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CHunterFlechette::FlechetteTouch( CBaseEntity *pOther )
-{
- if ( pOther->IsSolidFlagSet(FSOLID_VOLUME_CONTENTS | FSOLID_TRIGGER) )
- {
- // Some NPCs are triggers that can take damage (like antlion grubs). We should hit them.
- if ( ( pOther->m_takedamage == DAMAGE_NO ) || ( pOther->m_takedamage == DAMAGE_EVENTS_ONLY ) )
- return;
- }
-
- if ( FClassnameIs( pOther, "hunter_flechette" ) )
- return;
-
- trace_t tr;
- tr = BaseClass::GetTouchTrace();
-
- if ( pOther->m_takedamage != DAMAGE_NO )
- {
- Vector vecNormalizedVel = GetAbsVelocity();
-
- ClearMultiDamage();
- VectorNormalize( vecNormalizedVel );
-
- float flDamage = sk_hunter_dmg_flechette.GetFloat();
- CBreakable *pBreak = dynamic_cast <CBreakable *>(pOther);
- if ( pBreak && ( pBreak->GetMaterialType() == matGlass ) )
- {
- flDamage = MAX( pOther->GetHealth(), flDamage );
- }
-
- CTakeDamageInfo dmgInfo( this, GetOwnerEntity(), flDamage, DMG_DISSOLVE | DMG_NEVERGIB );
- CalculateMeleeDamageForce( &dmgInfo, vecNormalizedVel, tr.endpos, 0.7f );
- dmgInfo.SetDamagePosition( tr.endpos );
- pOther->DispatchTraceAttack( dmgInfo, vecNormalizedVel, &tr );
-
- ApplyMultiDamage();
-
- // Keep going through breakable glass.
- if ( pOther->GetCollisionGroup() == COLLISION_GROUP_BREAKABLE_GLASS )
- return;
-
- SetAbsVelocity( Vector( 0, 0, 0 ) );
-
- // play body "thwack" sound
- EmitSound( "NPC_Hunter.FlechetteHitBody" );
-
- StopParticleEffects( this );
-
- Vector vForward;
- AngleVectors( GetAbsAngles(), &vForward );
- VectorNormalize ( vForward );
-
- trace_t tr2;
- UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() + vForward * 128, MASK_BLOCKLOS, pOther, COLLISION_GROUP_NONE, &tr2 );
-
- if ( tr2.fraction != 1.0f )
- {
- //NDebugOverlay::Box( tr2.endpos, Vector( -16, -16, -16 ), Vector( 16, 16, 16 ), 0, 255, 0, 0, 10 );
- //NDebugOverlay::Box( GetAbsOrigin(), Vector( -16, -16, -16 ), Vector( 16, 16, 16 ), 0, 0, 255, 0, 10 );
-
- if ( tr2.m_pEnt == NULL || ( tr2.m_pEnt && tr2.m_pEnt->GetMoveType() == MOVETYPE_NONE ) )
- {
- CEffectData data;
-
- data.m_vOrigin = tr2.endpos;
- data.m_vNormal = vForward;
- data.m_nEntIndex = tr2.fraction != 1.0f;
-
- //DispatchEffect( "BoltImpact", data );
- }
- }
-
- if ( ( ( pOther->GetMoveType() == MOVETYPE_VPHYSICS ) || ( pOther->GetMoveType() == MOVETYPE_PUSH ) ) && ( ( pOther->GetHealth() > 0 ) || ( pOther->m_takedamage == DAMAGE_EVENTS_ONLY ) ) )
- {
- CPhysicsProp *pProp = dynamic_cast<CPhysicsProp *>( pOther );
- if ( pProp )
- {
- pProp->SetInteraction( PROPINTER_PHYSGUN_NOTIFY_CHILDREN );
- }
-
- // We hit a physics object that survived the impact. Stick to it.
- StickTo( pOther, tr );
- }
- else
- {
- SetTouch( NULL );
- SetThink( NULL );
- SetContextThink( NULL, 0, s_szHunterFlechetteBubbles );
-
- UTIL_Remove( this );
- }
- }
- else
- {
- // See if we struck the world
- if ( pOther->GetMoveType() == MOVETYPE_NONE && !( tr.surface.flags & SURF_SKY ) )
- {
- // We hit a physics object that survived the impact. Stick to it.
- StickTo( pOther, tr );
- }
- else if( pOther->GetMoveType() == MOVETYPE_PUSH && FClassnameIs(pOther, "func_breakable") )
- {
- // We hit a func_breakable, stick to it.
- // The MOVETYPE_PUSH is a micro-optimization to cut down on the classname checks.
- StickTo( pOther, tr );
- }
- else
- {
- // Put a mark unless we've hit the sky
- if ( ( tr.surface.flags & SURF_SKY ) == false )
- {
- UTIL_ImpactTrace( &tr, DMG_BULLET );
- }
-
- UTIL_Remove( this );
- }
- }
-}
-
-
-//-----------------------------------------------------------------------------
-// Fixup flechette position when seeking towards a striderbuster.
-//-----------------------------------------------------------------------------
-void CHunterFlechette::SeekThink()
-{
- if ( m_hSeekTarget )
- {
- Vector vecBodyTarget = m_hSeekTarget->BodyTarget( GetAbsOrigin() );
-
- Vector vecClosest;
- CalcClosestPointOnLineSegment( GetAbsOrigin(), m_vecShootPosition, vecBodyTarget, vecClosest, NULL );
-
- Vector vecDelta = vecBodyTarget - m_vecShootPosition;
- VectorNormalize( vecDelta );
-
- QAngle angShoot;
- VectorAngles( vecDelta, angShoot );
-
- float flSpeed = hunter_flechette_speed.GetFloat();
- if ( !flSpeed )
- {
- flSpeed = 2500.0f;
- }
-
- Vector vecVelocity = vecDelta * flSpeed;
- Teleport( &vecClosest, &angShoot, &vecVelocity );
-
- SetNextThink( gpGlobals->curtime, s_szHunterFlechetteSeekThink );
- }
-}
-
-
-//-----------------------------------------------------------------------------
-// Play a near miss sound as we travel past the player.
-//-----------------------------------------------------------------------------
-void CHunterFlechette::DopplerThink()
-{
- CBasePlayer *pPlayer = AI_GetSinglePlayer();
- if ( !pPlayer )
- return;
-
- Vector vecVelocity = GetAbsVelocity();
- VectorNormalize( vecVelocity );
-
- float flMyDot = DotProduct( vecVelocity, GetAbsOrigin() );
- float flPlayerDot = DotProduct( vecVelocity, pPlayer->GetAbsOrigin() );
-
- if ( flPlayerDot <= flMyDot )
- {
- EmitSound( "NPC_Hunter.FlechetteNearMiss" );
-
- // We've played the near miss sound and we're not seeking. Stop thinking.
- SetThink( NULL );
- }
- else
- {
- SetNextThink( gpGlobals->curtime );
- }
-}
-
-
-//-----------------------------------------------------------------------------
-// Think every 0.1 seconds to make bubbles if we're flying through water.
-//-----------------------------------------------------------------------------
-void CHunterFlechette::BubbleThink()
-{
- SetNextThink( gpGlobals->curtime + 0.1f, s_szHunterFlechetteBubbles );
-
- if ( GetWaterLevel() == 0 )
- return;
-
- UTIL_BubbleTrail( GetAbsOrigin() - GetAbsVelocity() * 0.1f, GetAbsOrigin(), 5 );
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CHunterFlechette::Shoot( Vector &vecVelocity, bool bBrightFX )
-{
- CreateSprites( bBrightFX );
-
- m_vecShootPosition = GetAbsOrigin();
-
- SetAbsVelocity( vecVelocity );
-
- SetThink( &CHunterFlechette::DopplerThink );
- SetNextThink( gpGlobals->curtime );
-
- SetContextThink( &CHunterFlechette::BubbleThink, gpGlobals->curtime + 0.1, s_szHunterFlechetteBubbles );
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CHunterFlechette::DangerSoundThink()
-{
- EmitSound( "NPC_Hunter.FlechettePreExplode" );
-
- CSoundEnt::InsertSound( SOUND_DANGER|SOUND_CONTEXT_EXCLUDE_COMBINE, GetAbsOrigin(), 150.0f, 0.5, this );
- SetThink( &CHunterFlechette::ExplodeThink );
- SetNextThink( gpGlobals->curtime + HUNTER_FLECHETTE_WARN_TIME );
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CHunterFlechette::ExplodeThink()
-{
- Explode();
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CHunterFlechette::Explode()
-{
- SetSolid( SOLID_NONE );
-
- // Don't catch self in own explosion!
- m_takedamage = DAMAGE_NO;
-
- EmitSound( "NPC_Hunter.FlechetteExplode" );
-
- // Move the explosion effect to the tip to reduce intersection with the world.
- Vector vecFuse;
- GetAttachment( s_nFlechetteFuseAttach, vecFuse );
- DispatchParticleEffect( "hunter_projectile_explosion_1", vecFuse, GetAbsAngles(), NULL );
-
- int nDamageType = DMG_DISSOLVE;
-
- // Perf optimization - only every other explosion makes a physics force. This is
- // hardly noticeable since flechettes usually explode in clumps.
- static int s_nExplosionCount = 0;
- s_nExplosionCount++;
- if ( ( s_nExplosionCount & 0x01 ) && hunter_cheap_explosions.GetBool() )
- {
- nDamageType |= DMG_PREVENT_PHYSICS_FORCE;
- }
-
- RadiusDamage( CTakeDamageInfo( this, GetOwnerEntity(), sk_hunter_flechette_explode_dmg.GetFloat(), nDamageType ), GetAbsOrigin(), sk_hunter_flechette_explode_radius.GetFloat(), CLASS_NONE, NULL );
-
- AddEffects( EF_NODRAW );
-
- SetThink( &CBaseEntity::SUB_Remove );
- SetNextThink( gpGlobals->curtime + 0.1f );
-}
-
-
-//-----------------------------------------------------------------------------
-// Calculate & apply damage & force for a charge to a target.
-// Done outside of the hunter because we need to do this inside a trace filter.
-//-----------------------------------------------------------------------------
-void Hunter_ApplyChargeDamage( CBaseEntity *pHunter, CBaseEntity *pTarget, float flDamage )
-{
- Vector attackDir = ( pTarget->WorldSpaceCenter() - pHunter->WorldSpaceCenter() );
- VectorNormalize( attackDir );
- Vector offset = RandomVector( -32, 32 ) + pTarget->WorldSpaceCenter();
-
- // Generate enough force to make a 75kg guy move away at 700 in/sec
- Vector vecForce = attackDir * ImpulseScale( 75, 700 );
-
- // Deal the damage
- CTakeDamageInfo info( pHunter, pHunter, vecForce, offset, flDamage, DMG_CLUB );
- pTarget->TakeDamage( info );
-}
-
-
-//-----------------------------------------------------------------------------
-// A simple trace filter class to skip small moveable physics objects
-//-----------------------------------------------------------------------------
-class CHunterTraceFilterSkipPhysics : public CTraceFilter
-{
-public:
- // It does have a base, but we'll never network anything below here..
- DECLARE_CLASS_NOBASE( CHunterTraceFilterSkipPhysics );
-
- CHunterTraceFilterSkipPhysics( const IHandleEntity *passentity, int collisionGroup, float minMass )
- : m_pPassEnt(passentity), m_collisionGroup(collisionGroup), m_minMass(minMass)
- {
- }
- virtual bool ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask )
- {
- if ( !StandardFilterRules( pHandleEntity, contentsMask ) )
- return false;
-
- if ( !PassServerEntityFilter( pHandleEntity, m_pPassEnt ) )
- return false;
-
- // Don't test if the game code tells us we should ignore this collision...
- CBaseEntity *pEntity = EntityFromEntityHandle( pHandleEntity );
- if ( pEntity )
- {
- if ( !pEntity->ShouldCollide( m_collisionGroup, contentsMask ) )
- return false;
-
- if ( !g_pGameRules->ShouldCollide( m_collisionGroup, pEntity->GetCollisionGroup() ) )
- return false;
-
- // don't test small moveable physics objects (unless it's an NPC)
- if ( !pEntity->IsNPC() && pEntity->GetMoveType() == MOVETYPE_VPHYSICS )
- {
- float entMass = PhysGetEntityMass( pEntity ) ;
- if ( entMass < m_minMass )
- {
- if ( entMass < m_minMass * 0.666f || pEntity->CollisionProp()->BoundingRadius() < (assert_cast<const CAI_BaseNPC *>(EntityFromEntityHandle( m_pPassEnt )))->GetHullHeight() )
- {
- return false;
- }
- }
- }
-
- // If we hit an antlion, don't stop, but kill it
- if ( pEntity->Classify() == CLASS_ANTLION )
- {
- CBaseEntity *pHunter = (CBaseEntity *)EntityFromEntityHandle( m_pPassEnt );
- Hunter_ApplyChargeDamage( pHunter, pEntity, pEntity->GetHealth() );
- return false;
- }
- }
-
- return true;
- }
-
-private:
- const IHandleEntity *m_pPassEnt;
- int m_collisionGroup;
- float m_minMass;
-};
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-inline void HunterTraceHull_SkipPhysics( const Vector &vecAbsStart, const Vector &vecAbsEnd, const Vector &hullMin,
- const Vector &hullMax, unsigned int mask, const CBaseEntity *ignore,
- int collisionGroup, trace_t *ptr, float minMass )
-{
- Ray_t ray;
- ray.Init( vecAbsStart, vecAbsEnd, hullMin, hullMax );
- CHunterTraceFilterSkipPhysics traceFilter( ignore, collisionGroup, minMass );
- enginetrace->TraceRay( ray, mask, &traceFilter, ptr );
-}
-
-
-//-----------------------------------------------------------------------------
-// Hunter follow behavior
-//-----------------------------------------------------------------------------
-class CAI_HunterEscortBehavior : public CAI_FollowBehavior
-{
-public:
- DECLARE_CLASS( CAI_HunterEscortBehavior, CAI_FollowBehavior );
-
- CAI_HunterEscortBehavior() :
- BaseClass( AI_FollowParams_t( AIF_HUNTER, true ) ),
- m_flTimeEscortReturn( 0 ),
- m_bEnabled( false )
- {
- }
-
- CNPC_Hunter *GetOuter() { return (CNPC_Hunter *)( BaseClass::GetOuter() ); }
-
- void SetEscortTarget( CNPC_Strider *pLeader, bool fFinishCurSchedule = false );
- CNPC_Strider * GetEscortTarget() { return (CNPC_Strider *)GetFollowTarget(); }
-
- bool FarFromFollowTarget()
- {
- return ( GetFollowTarget() && (GetAbsOrigin() - GetFollowTarget()->GetAbsOrigin()).LengthSqr() > HUNTER_FOLLOW_DISTANCE_SQR );
- }
-
- void DrawDebugGeometryOverlays();
- bool ShouldFollow();
- void BuildScheduleTestBits();
-
- void BeginScheduleSelection();
-
- void GatherConditions();
- void GatherConditionsNotActive();
- int SelectSchedule();
- int FollowCallBaseSelectSchedule();
- void StartTask( const Task_t *pTask );
- void RunTask( const Task_t *pTask );
-
- void CheckBreakEscort();
-
- void OnDamage( const CTakeDamageInfo &info );
- static void DistributeFreeHunters();
- static void FindFreeHunters( CUtlVector<CNPC_Hunter *> *pFreeHunters );
-
- float m_flTimeEscortReturn;
- CSimpleSimTimer m_FollowAttackTimer;
- bool m_bEnabled;
-
- static float gm_flLastDefendSound; // not saved and loaded, it's okay to yell again after a load
-
- //---------------------------------
-
- DECLARE_DATADESC();
-};
-
-
-BEGIN_DATADESC( CAI_HunterEscortBehavior )
- DEFINE_FIELD( m_flTimeEscortReturn, FIELD_TIME ),
- DEFINE_EMBEDDED( m_FollowAttackTimer ),
- DEFINE_FIELD( m_bEnabled, FIELD_BOOLEAN ),
-END_DATADESC();
-
-float CAI_HunterEscortBehavior::gm_flLastDefendSound;
-
-//-----------------------------------------------------------------------------
-// Hunter PHYSICS DAMAGE TABLE
-//-----------------------------------------------------------------------------
-#define HUNTER_MIN_PHYSICS_DAMAGE 10
-
-static impactentry_t s_HunterLinearTable[] =
-{
- { 150*150, 75 },
- { 350*350, 105 },
- { 1000*1000, 300 },
-};
-
-static impactentry_t s_HunterAngularTable[] =
-{
- { 100*100, 75 },
- { 200*200, 105 },
- { 300*300, 300 },
-};
-
-impactdamagetable_t s_HunterImpactDamageTable =
-{
- s_HunterLinearTable,
- s_HunterAngularTable,
-
- ARRAYSIZE(s_HunterLinearTable),
- ARRAYSIZE(s_HunterAngularTable),
-
- 24*24, // minimum linear speed squared
- 360*360, // minimum angular speed squared (360 deg/s to cause spin/slice damage)
- 5, // can't take damage from anything under 5kg
-
- 10, // anything less than 10kg is "small"
- HUNTER_MIN_PHYSICS_DAMAGE, // never take more than 10 pts of damage from anything under 10kg
- 36*36, // <10kg objects must go faster than 36 in/s to do damage
-
- VPHYSICS_LARGE_OBJECT_MASS, // large mass in kg
- 4, // large mass scale (anything over 500kg does 4X as much energy to read from damage table)
- 5, // large mass falling scale (emphasize falling/crushing damage over sideways impacts since the stress will kill you anyway)
- 0.0f, // min vel
-};
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-class CNPC_Hunter : public CAI_BaseActor
-{
- DECLARE_CLASS( CNPC_Hunter, CAI_BaseActor );
-
-public:
- CNPC_Hunter();
- ~CNPC_Hunter();
-
- //---------------------------------
-
- void Precache();
- void Spawn();
- void PostNPCInit();
- void Activate();
- void UpdateOnRemove();
- void OnRestore();
- bool CreateBehaviors();
- void IdleSound();
- bool ShouldPlayIdleSound();
- bool CanBecomeRagdoll();
- Activity GetDeathActivity();
- void StopLoopingSounds();
-
- const impactdamagetable_t &GetPhysicsImpactDamageTable();
-
- Class_T Classify();
- Vector BodyTarget( const Vector &posSrc, bool bNoisy /*= true*/ );
-
- int DrawDebugTextOverlays();
- void DrawDebugGeometryOverlays();
-
- void UpdateEfficiency( bool bInPVS );
-
- //---------------------------------
-
- virtual Vector GetNodeViewOffset() { return BaseClass::GetDefaultEyeOffset(); }
-
- int GetSoundInterests();
-
- bool IsInLargeOutdoorMap();
-
- //---------------------------------
- // CAI_BaseActor
- //---------------------------------
- const char *SelectRandomExpressionForState( NPC_STATE state );
- void PlayExpressionForState( NPC_STATE state );
-
- //---------------------------------
- // CBaseAnimating
- //---------------------------------
- float GetIdealAccel() const { return GetIdealSpeed(); }
-
- //---------------------------------
- // Behavior
- //---------------------------------
- void NPCThink();
- void PrescheduleThink();
- void GatherConditions();
- void CollectSiegeTargets();
- void ManageSiegeTargets();
- void KillCurrentSiegeTarget();
- bool QueryHearSound( CSound *pSound );
- void OnSeeEntity( CBaseEntity *pEntity );
- void CheckFlinches() {} // Hunter handles on own
- void BuildScheduleTestBits();
- NPC_STATE SelectIdealState();
- int SelectSchedule();
- int SelectCombatSchedule();
- int SelectSiegeSchedule();
- int TranslateSchedule( int scheduleType );
- void StartTask( const Task_t *pTask );
- void RunTask( const Task_t *pTask );
- Activity NPC_TranslateActivity( Activity baseAct );
- void OnChangeActivity( Activity eNewActivity );
-
- void HandleAnimEvent( animevent_t *pEvent );
- bool HandleInteraction(int interactionType, void *data, CBaseCombatCharacter *pSourceEnt);
-
- void PlayerHasIlluminatedNPC( CBasePlayer *pPlayer, float flDot );
-
- void AddEntityRelationship( CBaseEntity *pEntity, Disposition_t nDisposition, int nPriority );
- float EnemyDistTolerance() { return 100.0f; }
-
- bool ScheduledMoveToGoalEntity( int scheduleType, CBaseEntity *pGoalEntity, Activity movementActivity );
-
- void OnChangeHintGroup( string_t oldGroup, string_t newGroup );
-
- bool IsUsingSiegeTargets() { return m_iszSiegeTargetName != NULL_STRING; }
-
- //---------------------------------
- // Inputs
- //---------------------------------
- void InputDodge( inputdata_t &inputdata );
- void InputFlankEnemy( inputdata_t &inputdata );
- void InputDisableShooting( inputdata_t &inputdata );
- void InputEnableShooting( inputdata_t &inputdata );
- void InputFollowStrider( inputdata_t &inputdata );
- void InputUseSiegeTargets( inputdata_t &inputdata );
- void InputEnableSquadShootDelay( inputdata_t &inputdata );
- void InputDisableSquadShootDelay( inputdata_t &inputdata );
- void InputEnableUnplantedShooting( inputdata_t &inputdata );
- void InputDisableUnplantedShooting( inputdata_t &inputdata );
-
- //---------------------------------
- // Combat
- //---------------------------------
- bool FVisible( CBaseEntity *pEntity, int traceMask = MASK_BLOCKLOS, CBaseEntity **ppBlocker = NULL );
- bool IsValidEnemy( CBaseEntity *pEnemy );
-
- Disposition_t IRelationType( CBaseEntity *pTarget );
- int IRelationPriority( CBaseEntity *pTarget );
-
- void SetSquad( CAI_Squad *pSquad );
-
- bool UpdateEnemyMemory( CBaseEntity *pEnemy, const Vector &position, CBaseEntity *pInformer = NULL );
-
- int RangeAttack1Conditions( float flDot, float flDist );
- int RangeAttack2Conditions( float flDot, float flDist );
-
- int MeleeAttack1Conditions ( float flDot, float flDist );
- int MeleeAttack1ConditionsVsEnemyInVehicle( CBaseCombatCharacter *pEnemy, float flDot );
-
- int MeleeAttack2Conditions( float flDot, float flDist );
-
- bool WeaponLOSCondition(const Vector &ownerPos, const Vector &targetPos, bool bSetConditions);
- bool TestShootPosition(const Vector &vecShootPos, const Vector &targetPos );
-
- Vector Weapon_ShootPosition();
-
- CBaseEntity * MeleeAttack( float flDist, int iDamage, QAngle &qaViewPunch, Vector &vecVelocityPunch, int BloodOrigin );
-
- void MakeTracer( const Vector &vecTracerSrc, const trace_t &tr, int iTracerType );
- void DoMuzzleFlash( int nAttachment );
-
- bool CanShootThrough( const trace_t &tr, const Vector &vecTarget );
-
- int CountRangedAttackers();
- void DelayRangedAttackers( float minDelay, float maxDelay, bool bForced = false );
-
- //---------------------------------
- // Sounds & speech
- //---------------------------------
- void AlertSound();
- void PainSound( const CTakeDamageInfo &info );
- void DeathSound( const CTakeDamageInfo &info );
-
- //---------------------------------
- // Damage handling
- //---------------------------------
- void TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator );
- bool IsHeavyDamage( const CTakeDamageInfo &info );
- int OnTakeDamage( const CTakeDamageInfo &info );
- int OnTakeDamage_Alive( const CTakeDamageInfo &info );
- void Event_Killed( const CTakeDamageInfo &info );
-
- void StartBleeding();
- inline bool IsBleeding() { return m_bIsBleeding; }
- void Explode();
-
- void SetupGlobalModelData();
-
- //---------------------------------
- // Navigation & Movement
- //---------------------------------
- bool OverrideMoveFacing( const AILocalMoveGoal_t &move, float flInterval );
- float MaxYawSpeed();
- bool IsJumpLegal(const Vector &startPos, const Vector &apex, const Vector &endPos) const;
- float GetJumpGravity() const { return 3.0f; }
- bool ShouldProbeCollideAgainstEntity( CBaseEntity *pEntity );
- void TaskFail( AI_TaskFailureCode_t code );
- void TaskFail( const char *pszGeneralFailText ) { TaskFail( MakeFailCode( pszGeneralFailText ) ); }
-
- CAI_BaseNPC * GetEntity() { return this; }
-
- //---------------------------------
- // Magnade
- //---------------------------------
- void StriderBusterAttached( CBaseEntity *pAttached );
- void StriderBusterDetached( CBaseEntity *pAttached );
-
-private:
-
- void ConsiderFlinching( const CTakeDamageInfo &info );
-
- void TaskFindDodgeActivity();
-
- void GatherChargeConditions();
- void GatherIndoorOutdoorConditions();
-
- // Charge attack.
- bool ShouldCharge( const Vector &startPos, const Vector &endPos, bool useTime, bool bCheckForCancel );
- void ChargeLookAhead();
- float ChargeSteer();
- bool EnemyIsRightInFrontOfMe( CBaseEntity **pEntity );
- void ChargeDamage( CBaseEntity *pTarget );
- bool HandleChargeImpact( Vector vecImpact, CBaseEntity *pEntity );
-
- void BeginVolley( int nNum, float flStartTime );
- bool ShootFlechette( CBaseEntity *pTargetEntity, bool bSingleShot );
- bool ShouldSeekTarget( CBaseEntity *pTargetEntity, bool bStriderBuster );
- void GetShootDir( Vector &vecDir, const Vector &vecSrc, CBaseEntity *pTargetEntity, bool bStriderbuster, int nShotNum, bool bSingleShot );
- bool ClampShootDir( Vector &vecDir );
-
- void SetAim( const Vector &aimDir, float flInterval );
- void RelaxAim( float flInterval );
- void UpdateAim();
- void UpdateEyes();
- void LockBothEyes( float flDuration );
- void UnlockBothEyes( float flDuration );
-
- void TeslaThink();
- void BleedThink();
- void JostleVehicleThink();
-
- void FollowStrider( const char *szStrider );
- void FollowStrider( CNPC_Strider * pStrider );
- int NumHuntersInMySquad();
-
- bool CanPlantHere( const Vector &vecPos );
-
- //---------------------------------
- // Foot handling
- //---------------------------------
- Vector LeftFootHit( float eventtime );
- Vector RightFootHit( float eventtime );
- Vector BackFootHit( float eventtime );
-
- void FootFX( const Vector &origin );
-
- CBaseEntity *GetEnemyVehicle();
- bool IsCorporealEnemy( CBaseEntity *pEnemy );
-
- void PhysicsDamageEffect( const Vector &vecPos, const Vector &vecDir );
- bool PlayerFlashlightOnMyEyes( CBasePlayer *pPlayer );
-
- //-----------------------------------------------------
- // Conditions, Schedules, Tasks
- //-----------------------------------------------------
- enum
- {
- SCHED_HUNTER_RANGE_ATTACK1 = BaseClass::NEXT_SCHEDULE,
- SCHED_HUNTER_RANGE_ATTACK2,
- SCHED_HUNTER_MELEE_ATTACK1,
- SCHED_HUNTER_DODGE,
- SCHED_HUNTER_CHASE_ENEMY,
- SCHED_HUNTER_CHASE_ENEMY_MELEE,
- SCHED_HUNTER_COMBAT_FACE,
- SCHED_HUNTER_FLANK_ENEMY,
- SCHED_HUNTER_CHANGE_POSITION,
- SCHED_HUNTER_CHANGE_POSITION_FINISH,
- SCHED_HUNTER_SIDESTEP,
- SCHED_HUNTER_PATROL,
- SCHED_HUNTER_FLINCH_STICKYBOMB,
- SCHED_HUNTER_STAGGER,
- SCHED_HUNTER_PATROL_RUN,
- SCHED_HUNTER_TAKE_COVER_FROM_ENEMY,
- SCHED_HUNTER_HIDE_UNDER_COVER,
- SCHED_HUNTER_FAIL_IMMEDIATE, // instant fail without waiting
- SCHED_HUNTER_CHARGE_ENEMY,
- SCHED_HUNTER_FAIL_CHARGE_ENEMY,
- SCHED_HUNTER_FOUND_ENEMY,
- SCHED_HUNTER_FOUND_ENEMY_ACK,
- SCHED_HUNTER_RANGE_ATTACK2_VS_STRIDERBUSTER,
- SCHED_HUNTER_RANGE_ATTACK2_VS_STRIDERBUSTER_LATENT,
- SCHED_HUNTER_GOTO_HINT,
- SCHED_HUNTER_CLEAR_HINTNODE,
- SCHED_HUNTER_FAIL_DODGE,
- SCHED_HUNTER_SIEGE_STAND,
- SCHED_HUNTER_CHANGE_POSITION_SIEGE,
-
- TASK_HUNTER_AIM = BaseClass::NEXT_TASK,
- TASK_HUNTER_FIND_DODGE_POSITION,
- TASK_HUNTER_DODGE,
- TASK_HUNTER_PRE_RANGE_ATTACK2,
- TASK_HUNTER_SHOOT_COMMIT,
- TASK_HUNTER_BEGIN_FLANK,
- TASK_HUNTER_ANNOUNCE_FLANK,
- TASK_HUNTER_STAGGER,
- TASK_HUNTER_CORNERED_TIMER,
- TASK_HUNTER_FIND_SIDESTEP_POSITION,
- TASK_HUNTER_CHARGE,
- TASK_HUNTER_CHARGE_DELAY,
- TASK_HUNTER_FINISH_RANGE_ATTACK,
- TASK_HUNTER_WAIT_FOR_MOVEMENT_FACING_ENEMY,
-
- COND_HUNTER_SHOULD_PATROL = BaseClass::NEXT_CONDITION,
- COND_HUNTER_FORCED_FLANK_ENEMY,
- COND_HUNTER_FORCED_DODGE,
- COND_HUNTER_CAN_CHARGE_ENEMY,
- COND_HUNTER_HIT_BY_STICKYBOMB,
- COND_HUNTER_STAGGERED,
- COND_HUNTER_IS_INDOORS,
- COND_HUNTER_SEE_STRIDERBUSTER,
- COND_HUNTER_INCOMING_VEHICLE,
- COND_HUNTER_NEW_HINTGROUP,
- COND_HUNTER_CANT_PLANT,
- COND_HUNTER_SQUADMATE_FOUND_ENEMY,
- };
-
- enum HunterEyeStates_t
- {
- HUNTER_EYE_STATE_TOP_LOCKED = 0,
- HUNTER_EYE_STATE_BOTTOM_LOCKED,
- HUNTER_EYE_STATE_BOTH_LOCKED,
- HUNTER_EYE_STATE_BOTH_UNLOCKED,
- };
-
- string_t m_iszFollowTarget; // Name of the strider we should follow.
- CSimpleStopwatch m_BeginFollowDelay;
-
- int m_nKillingDamageType;
- HunterEyeStates_t m_eEyeState;
-
- float m_aimYaw;
- float m_aimPitch;
-
- float m_flShootAllowInterruptTime;
- float m_flNextChargeTime; // Prevents us from doing our threat display too often.
- float m_flNextDamageTime;
- float m_flNextSideStepTime;
-
- CSimpleSimTimer m_HeavyDamageDelay;
- CSimpleSimTimer m_FlinchTimer;
- CSimpleSimTimer m_EyeSwitchTimer; // Controls how often we switch which eye is focusing on our enemy.
-
- bool m_bTopMuzzle; // Used to alternate between top muzzle FX and bottom muzzle FX.
- bool m_bEnableSquadShootDelay;
- bool m_bIsBleeding;
-
- Activity m_eDodgeActivity;
- CSimpleSimTimer m_RundownDelay;
- CSimpleSimTimer m_IgnoreVehicleTimer;
-
- bool m_bDisableShooting; // Range attack disabled via an input. Used for scripting melee attacks.
-
- bool m_bFlashlightInEyes; // The player is shining the flashlight on our eyes.
- float m_flPupilDilateTime; // When to dilate our pupils if the flashlight is no longer on our eyes.
-
- Vector m_vecEnemyLastSeen;
- Vector m_vecLastCanPlantHerePos;
- Vector m_vecStaggerDir;
-
- bool m_bPlanted;
- bool m_bLastCanPlantHere;
- bool m_bMissLeft;
- bool m_bEnableUnplantedShooting;
-
- static float gm_flMinigunDistZ;
- static Vector gm_vecLocalRelativePositionMinigun;
-
- static int gm_nTopGunAttachment;
- static int gm_nBottomGunAttachment;
- static int gm_nAimYawPoseParam;
- static int gm_nAimPitchPoseParam;
- static int gm_nBodyYawPoseParam;
- static int gm_nBodyPitchPoseParam;
- static int gm_nStaggerYawPoseParam;
- static int gm_nHeadCenterAttachment;
- static int gm_nHeadBottomAttachment;
- static float gm_flHeadRadius;
-
- static int gm_nUnplantedNode;
- static int gm_nPlantedNode;
-
- CAI_HunterEscortBehavior m_EscortBehavior;
-
- int m_nFlechettesQueued;
- int m_nClampedShots; // The number of consecutive shots fired at an out-of-max yaw target.
-
- float m_flNextRangeAttack2Time; // Time when we can fire another volley of flechettes.
- float m_flNextFlechetteTime; // Time to fire the next flechette in this volley.
-
- float m_flNextMeleeTime;
- float m_flTeslaStopTime;
-
- string_t m_iszCurrentExpression;
-
- // buster fu
- CUtlVector< EHANDLE > m_hAttachedBusters; // List of busters attached to us
- float m_fCorneredTimer; ///< hunter was cornered when fleeing player; it won't flee again until this time
-
- CSimpleSimTimer m_CheckHintGroupTimer;
-
- DEFINE_CUSTOM_AI;
-
- DECLARE_DATADESC();
-
- friend class CAI_HunterEscortBehavior;
- friend class CHunterMaker;
-
- bool m_bInLargeOutdoorMap;
- float m_flTimeSawEnemyAgain;
-
- // Sounds
- //CSoundPatch *m_pGunFiringSound;
-
- CUtlVector<EHANDLE> m_pSiegeTargets;
- string_t m_iszSiegeTargetName;
- float m_flTimeNextSiegeTargetAttack;
- EHANDLE m_hCurrentSiegeTarget;
-
- EHANDLE m_hHitByVehicle;
-};
-
-
-LINK_ENTITY_TO_CLASS( npc_hunter, CNPC_Hunter );
-
-
-BEGIN_DATADESC( CNPC_Hunter )
-
- DEFINE_KEYFIELD( m_iszFollowTarget, FIELD_STRING, "FollowTarget" ),
-
- DEFINE_FIELD( m_aimYaw, FIELD_FLOAT ),
- DEFINE_FIELD( m_aimPitch, FIELD_FLOAT ),
-
- DEFINE_FIELD( m_flShootAllowInterruptTime, FIELD_TIME ),
- DEFINE_FIELD( m_flNextChargeTime, FIELD_TIME ),
- //DEFINE_FIELD( m_flNextDamageTime, FIELD_TIME ),
- DEFINE_FIELD( m_flNextSideStepTime, FIELD_TIME ),
-
- DEFINE_EMBEDDED( m_HeavyDamageDelay ),
- DEFINE_EMBEDDED( m_FlinchTimer ),
-
- DEFINE_FIELD( m_eEyeState, FIELD_INTEGER ),
-
- DEFINE_FIELD( m_bTopMuzzle, FIELD_BOOLEAN ),
- DEFINE_FIELD( m_bEnableSquadShootDelay, FIELD_BOOLEAN ),
- DEFINE_FIELD( m_bIsBleeding, FIELD_BOOLEAN ),
-
- DEFINE_FIELD( m_bDisableShooting, FIELD_BOOLEAN ),
- DEFINE_FIELD( m_bFlashlightInEyes, FIELD_BOOLEAN ),
- DEFINE_FIELD( m_flPupilDilateTime, FIELD_TIME ),
-
- DEFINE_FIELD( m_vecEnemyLastSeen, FIELD_POSITION_VECTOR ),
- DEFINE_FIELD( m_vecLastCanPlantHerePos, FIELD_POSITION_VECTOR ),
- DEFINE_FIELD( m_vecStaggerDir, FIELD_VECTOR ),
-
- DEFINE_FIELD( m_bPlanted, FIELD_BOOLEAN ),
- DEFINE_FIELD( m_bLastCanPlantHere, FIELD_BOOLEAN ),
- //DEFINE_FIELD( m_bMissLeft, FIELD_BOOLEAN ),
- DEFINE_FIELD( m_bEnableUnplantedShooting, FIELD_BOOLEAN ),
-
- DEFINE_FIELD( m_nKillingDamageType, FIELD_INTEGER ),
- DEFINE_FIELD( m_eDodgeActivity, FIELD_INTEGER ),
- DEFINE_EMBEDDED( m_RundownDelay ),
- DEFINE_EMBEDDED( m_IgnoreVehicleTimer ),
-
- DEFINE_FIELD( m_flNextMeleeTime, FIELD_TIME ),
- DEFINE_FIELD( m_flTeslaStopTime, FIELD_TIME ),
-
- // (auto saved by AI)
- //DEFINE_FIELD( m_EscortBehavior, FIELD_EMBEDDED ),
-
- DEFINE_FIELD( m_iszCurrentExpression, FIELD_STRING ),
-
- DEFINE_FIELD( m_fCorneredTimer, FIELD_TIME),
-
- DEFINE_EMBEDDED( m_CheckHintGroupTimer ),
-
- // (Recomputed in Precache())
- //DEFINE_FIELD( m_bInLargeOutdoorMap, FIELD_BOOLEAN ),
- DEFINE_FIELD( m_flTimeSawEnemyAgain, FIELD_TIME ),
-
- //DEFINE_SOUNDPATCH( m_pGunFiringSound ),
-
- //DEFINE_UTLVECTOR( m_pSiegeTarget, FIELD_EHANDLE ),
- DEFINE_FIELD( m_iszSiegeTargetName, FIELD_STRING ),
- DEFINE_FIELD( m_flTimeNextSiegeTargetAttack, FIELD_TIME ),
- DEFINE_FIELD( m_hCurrentSiegeTarget, FIELD_EHANDLE ),
- DEFINE_FIELD( m_hHitByVehicle, FIELD_EHANDLE ),
-
- DEFINE_EMBEDDED( m_BeginFollowDelay ),
- DEFINE_EMBEDDED( m_EyeSwitchTimer ),
-
- DEFINE_FIELD( m_nFlechettesQueued, FIELD_INTEGER ),
- DEFINE_FIELD( m_nClampedShots, FIELD_INTEGER ),
- DEFINE_FIELD( m_flNextRangeAttack2Time, FIELD_TIME ),
- DEFINE_FIELD( m_flNextFlechetteTime, FIELD_TIME ),
- DEFINE_UTLVECTOR( m_hAttachedBusters, FIELD_EHANDLE ),
- DEFINE_UTLVECTOR( m_pSiegeTargets, FIELD_EHANDLE ),
-
- // inputs
- DEFINE_INPUTFUNC( FIELD_VOID, "Dodge", InputDodge ),
- DEFINE_INPUTFUNC( FIELD_VOID, "FlankEnemy", InputFlankEnemy ),
- DEFINE_INPUTFUNC( FIELD_STRING, "DisableShooting", InputDisableShooting ),
- DEFINE_INPUTFUNC( FIELD_STRING, "EnableShooting", InputEnableShooting ),
- DEFINE_INPUTFUNC( FIELD_STRING, "FollowStrider", InputFollowStrider ),
- DEFINE_INPUTFUNC( FIELD_STRING, "UseSiegeTargets", InputUseSiegeTargets ),
- DEFINE_INPUTFUNC( FIELD_VOID, "EnableSquadShootDelay", InputEnableSquadShootDelay ),
- DEFINE_INPUTFUNC( FIELD_VOID, "DisableSquadShootDelay", InputDisableSquadShootDelay ),
- DEFINE_INPUTFUNC( FIELD_VOID, "EnableUnplantedShooting", InputEnableUnplantedShooting ),
- DEFINE_INPUTFUNC( FIELD_VOID, "DisableUnplantedShooting", InputDisableUnplantedShooting ),
-
- // Function Pointers
- DEFINE_THINKFUNC( TeslaThink ),
- DEFINE_THINKFUNC( BleedThink ),
- DEFINE_THINKFUNC( JostleVehicleThink ),
-
-END_DATADESC()
-
-//-----------------------------------------------------------------------------
-
-int CNPC_Hunter::gm_nUnplantedNode = 0;
-int CNPC_Hunter::gm_nPlantedNode = 0;
-
-int CNPC_Hunter::gm_nAimYawPoseParam = -1;
-int CNPC_Hunter::gm_nAimPitchPoseParam = -1;
-int CNPC_Hunter::gm_nBodyYawPoseParam = -1;
-int CNPC_Hunter::gm_nBodyPitchPoseParam = -1;
-int CNPC_Hunter::gm_nStaggerYawPoseParam = -1;
-int CNPC_Hunter::gm_nHeadCenterAttachment = -1;
-int CNPC_Hunter::gm_nHeadBottomAttachment = -1;
-float CNPC_Hunter::gm_flHeadRadius = 0;
-
-int CNPC_Hunter::gm_nTopGunAttachment = -1;
-int CNPC_Hunter::gm_nBottomGunAttachment = -1;
-
-float CNPC_Hunter::gm_flMinigunDistZ;
-Vector CNPC_Hunter::gm_vecLocalRelativePositionMinigun;
-
-//-----------------------------------------------------------------------------
-
-static CUtlVector<CNPC_Hunter *> g_Hunters;
-float g_TimeLastDistributeFreeHunters = -1;
-const float FREE_HUNTER_DISTRIBUTE_INTERVAL = 2;
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-CNPC_Hunter::CNPC_Hunter()
-{
- g_Hunters.AddToTail( this );
- g_TimeLastDistributeFreeHunters = -1;
- m_flTimeSawEnemyAgain = HUNTER_SEE_ENEMY_TIME_INVALID;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-CNPC_Hunter::~CNPC_Hunter()
-{
- g_Hunters.FindAndRemove( this );
- g_TimeLastDistributeFreeHunters = -1;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::Precache()
-{
- PrecacheModel( "models/hunter.mdl" );
- PropBreakablePrecacheAll( MAKE_STRING("models/hunter.mdl") );
-
- PrecacheScriptSound( "NPC_Hunter.Idle" );
- PrecacheScriptSound( "NPC_Hunter.Scan" );
- PrecacheScriptSound( "NPC_Hunter.Alert" );
- PrecacheScriptSound( "NPC_Hunter.Pain" );
- PrecacheScriptSound( "NPC_Hunter.PreCharge" );
- PrecacheScriptSound( "NPC_Hunter.Angry" );
- PrecacheScriptSound( "NPC_Hunter.Death" );
- PrecacheScriptSound( "NPC_Hunter.FireMinigun" );
- PrecacheScriptSound( "NPC_Hunter.Footstep" );
- PrecacheScriptSound( "NPC_Hunter.BackFootstep" );
- PrecacheScriptSound( "NPC_Hunter.FlechetteVolleyWarn" );
- PrecacheScriptSound( "NPC_Hunter.FlechetteShoot" );
- PrecacheScriptSound( "NPC_Hunter.FlechetteShootLoop" );
- PrecacheScriptSound( "NPC_Hunter.FlankAnnounce" );
- PrecacheScriptSound( "NPC_Hunter.MeleeAnnounce" );
- PrecacheScriptSound( "NPC_Hunter.MeleeHit" );
- PrecacheScriptSound( "NPC_Hunter.TackleAnnounce" );
- PrecacheScriptSound( "NPC_Hunter.TackleHit" );
- PrecacheScriptSound( "NPC_Hunter.ChargeHitEnemy" );
- PrecacheScriptSound( "NPC_Hunter.ChargeHitWorld" );
- PrecacheScriptSound( "NPC_Hunter.FoundEnemy" );
- PrecacheScriptSound( "NPC_Hunter.FoundEnemyAck" );
- PrecacheScriptSound( "NPC_Hunter.DefendStrider" );
- PrecacheScriptSound( "NPC_Hunter.HitByVehicle" );
-
- PrecacheParticleSystem( "hunter_muzzle_flash" );
- PrecacheParticleSystem( "blood_impact_synth_01" );
- PrecacheParticleSystem( "blood_impact_synth_01_arc_parent" );
- PrecacheParticleSystem( "blood_spurt_synth_01" );
- PrecacheParticleSystem( "blood_drip_synth_01" );
-
- PrecacheInstancedScene( "scenes/npc/hunter/hunter_scan.vcd" );
- PrecacheInstancedScene( "scenes/npc/hunter/hunter_eyeclose.vcd" );
- PrecacheInstancedScene( "scenes/npc/hunter/hunter_roar.vcd" );
- PrecacheInstancedScene( "scenes/npc/hunter/hunter_pain.vcd" );
- PrecacheInstancedScene( "scenes/npc/hunter/hunter_eyedarts_top.vcd" );
- PrecacheInstancedScene( "scenes/npc/hunter/hunter_eyedarts_bottom.vcd" );
-
- PrecacheMaterial( "effects/water_highlight" );
-
- UTIL_PrecacheOther( "hunter_flechette" );
- UTIL_PrecacheOther( "sparktrail" );
-
- m_bInLargeOutdoorMap = false;
- if( !Q_strnicmp( STRING(gpGlobals->mapname), "ep2_outland_12", 14) )
- {
- m_bInLargeOutdoorMap = true;
- }
-
- BaseClass::Precache();
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::Spawn()
-{
- Precache();
-
- SetModel( "models/hunter.mdl" );
- BaseClass::Spawn();
-
- //m_debugOverlays |= OVERLAY_NPC_ROUTE_BIT | OVERLAY_BBOX_BIT | OVERLAY_PIVOT_BIT;
-
- SetHullType( HULL_MEDIUM_TALL );
- SetHullSizeNormal();
- SetDefaultEyeOffset();
-
- SetNavType( NAV_GROUND );
- m_flGroundSpeed = 500;
- m_NPCState = NPC_STATE_NONE;
-
- SetBloodColor( DONT_BLEED );
-
- m_iHealth = m_iMaxHealth = sk_hunter_health.GetInt();
-
- m_flFieldOfView = HUNTER_FOV_DOT;
-
- SetSolid( SOLID_BBOX );
- AddSolidFlags( FSOLID_NOT_STANDABLE );
- SetMoveType( MOVETYPE_STEP );
-
- SetupGlobalModelData();
-
- CapabilitiesAdd( bits_CAP_MOVE_GROUND | bits_CAP_SQUAD | bits_CAP_ANIMATEDFACE );
- CapabilitiesAdd( bits_CAP_INNATE_RANGE_ATTACK1 | bits_CAP_INNATE_RANGE_ATTACK2 | bits_CAP_INNATE_MELEE_ATTACK1 );
- CapabilitiesAdd( bits_CAP_SKIP_NAV_GROUND_CHECK );
-
- if ( !hunter_allow_dissolve.GetBool() )
- {
- AddEFlags( EFL_NO_DISSOLVE );
- }
-
- if( hunter_allow_nav_jump.GetBool() )
- {
- CapabilitiesAdd( bits_CAP_MOVE_JUMP );
- }
-
- NPCInit();
-
- m_bEnableSquadShootDelay = true;
-
- m_flDistTooFar = hunter_flechette_max_range.GetFloat();
-
- // Discard time must be greater than free knowledge duration. Make it double.
- float freeKnowledge = hunter_free_knowledge.GetFloat();
- if ( freeKnowledge < GetEnemies()->GetEnemyDiscardTime() )
- {
- GetEnemies()->SetEnemyDiscardTime( MAX( freeKnowledge + 0.1, AI_DEF_ENEMY_DISCARD_TIME ) );
- }
- GetEnemies()->SetFreeKnowledgeDuration( freeKnowledge );
-
- // Find out what strider we should follow, if any.
- if ( m_iszFollowTarget != NULL_STRING )
- {
- m_BeginFollowDelay.Set( .1 ); // Allow time for strider to spawn
- }
-
- //if ( !m_pGunFiringSound )
- //{
- // CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
- // CPASAttenuationFilter filter( this );
- //
- // m_pGunFiringSound = controller.SoundCreate( filter, entindex(), "NPC_Hunter.FlechetteShootLoop" );
- // controller.Play( m_pGunFiringSound, 0.0, 100 );
- //}
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::UpdateEfficiency( bool bInPVS )
-{
- SetEfficiency( ( GetSleepState() != AISS_AWAKE ) ? AIE_DORMANT : AIE_NORMAL );
- SetMoveEfficiency( AIME_NORMAL );
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-bool CNPC_Hunter::CreateBehaviors()
-{
- AddBehavior( &m_EscortBehavior );
-
- return BaseClass::CreateBehaviors();
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::SetupGlobalModelData()
-{
- if ( gm_nBodyYawPoseParam != -1 )
- return;
-
- gm_nAimYawPoseParam = LookupPoseParameter( "aim_yaw" );
- gm_nAimPitchPoseParam = LookupPoseParameter( "aim_pitch" );
-
- gm_nBodyYawPoseParam = LookupPoseParameter( "body_yaw" );
- gm_nBodyPitchPoseParam = LookupPoseParameter( "body_pitch" );
-
- gm_nTopGunAttachment = LookupAttachment( "top_eye" );
- gm_nBottomGunAttachment = LookupAttachment( "bottom_eye" );
- gm_nStaggerYawPoseParam = LookupAttachment( "stagger_yaw" );
-
- gm_nHeadCenterAttachment = LookupAttachment( "head_center" );
- gm_nHeadBottomAttachment = LookupAttachment( "head_radius_measure" );
-
- // Measure the radius of the head.
- Vector vecHeadCenter;
- Vector vecHeadBottom;
- GetAttachment( gm_nHeadCenterAttachment, vecHeadCenter );
- GetAttachment( gm_nHeadBottomAttachment, vecHeadBottom );
- gm_flHeadRadius = ( vecHeadCenter - vecHeadBottom ).Length();
-
- int nSequence = SelectWeightedSequence( ACT_HUNTER_RANGE_ATTACK2_UNPLANTED );
- gm_nUnplantedNode = GetEntryNode( nSequence );
-
- nSequence = SelectWeightedSequence( ACT_RANGE_ATTACK2 );
- gm_nPlantedNode = GetEntryNode( nSequence );
-
- CollisionProp()->SetSurroundingBoundsType( USE_HITBOXES );
-}
-
-
-//-----------------------------------------------------------------------------
-// Shuts down looping sounds when we are killed in combat or deleted.
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::StopLoopingSounds()
-{
- BaseClass::StopLoopingSounds();
-
- //if ( m_pGunFiringSound )
- //{
- // CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
- // controller.SoundDestroy( m_pGunFiringSound );
- // m_pGunFiringSound = NULL;
- //}
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::OnRestore()
-{
- BaseClass::OnRestore();
- SetupGlobalModelData();
- CreateVPhysics();
-
- if ( IsBleeding() )
- {
- StartBleeding();
- }
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::IdleSound()
-{
- if ( HasCondition( COND_LOST_ENEMY ) )
- {
- EmitSound( "NPC_Hunter.Scan" );
- }
- else
- {
- EmitSound( "NPC_Hunter.Idle" );
- }
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-bool CNPC_Hunter::ShouldPlayIdleSound()
-{
- if ( random->RandomInt(0, 99) == 0 && !HasSpawnFlags( SF_NPC_GAG ) )
- return true;
-
- return false;
-}
-
-
-//-----------------------------------------------------------------------------
-// Stay facing our enemy when close enough.
-//-----------------------------------------------------------------------------
-bool CNPC_Hunter::OverrideMoveFacing( const AILocalMoveGoal_t &move, float flInterval )
-{
- if ( GetActivity() == ACT_TRANSITION )
- {
- // No turning while in transitions.
- return true;
- }
-
- bool bSideStepping = IsCurSchedule( SCHED_HUNTER_SIDESTEP, false );
-
- // FIXME: this will break scripted sequences that walk when they have an enemy
- if ( GetEnemy() &&
- ( bSideStepping ||
- ( ( ( GetNavigator()->GetMovementActivity() == ACT_RUN ) || ( GetNavigator()->GetMovementActivity() == ACT_WALK ) ) &&
- !IsCurSchedule( SCHED_HUNTER_TAKE_COVER_FROM_ENEMY, false ) ) ) )
- {
- Vector vecEnemyLKP = GetEnemyLKP();
-
- // Face my enemy if we're close enough
- if ( bSideStepping || UTIL_DistApprox( vecEnemyLKP, GetAbsOrigin() ) < HUNTER_FACE_ENEMY_DIST )
- {
- AddFacingTarget( GetEnemy(), vecEnemyLKP, 1.0, 0.2 );
- }
- }
-
- return BaseClass::OverrideMoveFacing( move, flInterval );
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::PostNPCInit()
-{
- BaseClass::PostNPCInit();
-
- IPhysicsObject *pPhysObject = VPhysicsGetObject();
- Assert( pPhysObject );
- if ( pPhysObject )
- {
- pPhysObject->SetMass( 600.0 );
- }
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::Activate()
-{
- BaseClass::Activate();
-
- s_iszStriderBusterClassname = AllocPooledString( "weapon_striderbuster" );
- s_iszStriderClassname = AllocPooledString( "npc_strider" );
- s_iszMagnadeClassname = AllocPooledString( "npc_grenade_magna" );
- s_iszPhysPropClassname = AllocPooledString( "prop_physics" );
- s_iszHuntersToRunOver = AllocPooledString( "hunters_to_run_over" );
-
- // If no one has initialized the hunters to run over counter, just zero it out.
- if ( !GlobalEntity_IsInTable( s_iszHuntersToRunOver ) )
- {
- GlobalEntity_Add( s_iszHuntersToRunOver, gpGlobals->mapname, GLOBAL_ON );
- GlobalEntity_SetCounter( s_iszHuntersToRunOver, 0 );
- }
-
- CMissile::AddCustomDetonator( this, ( GetHullMaxs().AsVector2D() - GetHullMins().AsVector2D() ).Length() * 0.5, GetHullHeight() );
-
- SetupGlobalModelData();
-
- if ( gm_flMinigunDistZ == 0 )
- {
- // Have to create a virgin hunter to ensure proper pose
- CNPC_Hunter *pHunter = (CNPC_Hunter *)CreateEntityByName( "npc_hunter" );
- Assert(pHunter);
- pHunter->Spawn();
-
- pHunter->SetActivity( ACT_WALK );
- pHunter->InvalidateBoneCache();
-
- // Currently just using the gun for the vertical component!
- Vector defEyePos;
- pHunter->GetAttachment( "minigunbase", defEyePos );
- gm_flMinigunDistZ = defEyePos.z - pHunter->GetAbsOrigin().z;
-
- Vector position;
- pHunter->GetAttachment( gm_nTopGunAttachment, position );
- VectorITransform( position, pHunter->EntityToWorldTransform(), gm_vecLocalRelativePositionMinigun );
- UTIL_Remove( pHunter );
- }
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::UpdateOnRemove()
-{
- CMissile::RemoveCustomDetonator( this );
- BaseClass::UpdateOnRemove();
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-Class_T CNPC_Hunter::Classify()
-{
- return CLASS_COMBINE_HUNTER;
-}
-
-//-----------------------------------------------------------------------------
-// Compensate for the hunter's long legs by moving the bodytarget up to his head.
-//-----------------------------------------------------------------------------
-Vector CNPC_Hunter::BodyTarget( const Vector &posSrc, bool bNoisy /*= true*/ )
-{
- Vector vecResult;
- QAngle vecAngle;
- GetAttachment( gm_nHeadCenterAttachment, vecResult, vecAngle );
-
- if ( bNoisy )
- {
- float rand1 = random->RandomFloat( 0, gm_flHeadRadius ) + random->RandomFloat( 0, gm_flHeadRadius );
- return vecResult + RandomVector( -rand1, rand1 );
- }
-
- return vecResult;
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-int CNPC_Hunter::DrawDebugTextOverlays()
-{
- int text_offset = BaseClass::DrawDebugTextOverlays();
-
- if (m_debugOverlays & OVERLAY_TEXT_BIT)
- {
- EntityText( text_offset, CFmtStr("%s", m_bPlanted ? "Planted" : "Unplanted" ), 0 );
- text_offset++;
-
- EntityText( text_offset, CFmtStr("Eye state: %d", m_eEyeState ), 0 );
- text_offset++;
-
- if( IsUsingSiegeTargets() )
- {
- EntityText( text_offset, CFmtStr("Next Siege Attempt:%f", m_flTimeNextSiegeTargetAttack - gpGlobals->curtime ), 0 );
- text_offset++;
- }
- }
-
- return text_offset;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::LockBothEyes( float flDuration )
-{
- m_eEyeState = HUNTER_EYE_STATE_BOTH_LOCKED;
- m_EyeSwitchTimer.Set( flDuration );
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::UnlockBothEyes( float flDuration )
-{
- m_eEyeState = HUNTER_EYE_STATE_BOTH_UNLOCKED;
- m_EyeSwitchTimer.Set( flDuration );
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::OnChangeActivity( Activity eNewActivity )
-{
- m_EyeSwitchTimer.Force();
-
- BaseClass::OnChangeActivity( eNewActivity );
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::UpdateEyes()
-{
- // If the eyes are controlled by a script, do nothing.
- if ( GetState() == NPC_STATE_SCRIPT )
- return;
-
- if ( m_EyeSwitchTimer.Expired() )
- {
- RemoveActorFromScriptedScenes( this, false );
-
- if ( GetActivity() == ACT_IDLE )
- {
- // Idles have eye motion baked in.
- m_eEyeState = HUNTER_EYE_STATE_BOTH_LOCKED;
- }
- else if ( GetEnemy() == NULL )
- {
- m_eEyeState = HUNTER_EYE_STATE_BOTH_UNLOCKED;
- }
- else if ( m_eEyeState == HUNTER_EYE_STATE_BOTH_LOCKED )
- {
- if ( random->RandomInt( 0, 1 ) == 0 )
- {
- m_eEyeState = HUNTER_EYE_STATE_TOP_LOCKED;
- }
- else
- {
- m_eEyeState = HUNTER_EYE_STATE_BOTTOM_LOCKED;
- }
- }
- else if ( m_eEyeState == HUNTER_EYE_STATE_TOP_LOCKED )
- {
- m_eEyeState = HUNTER_EYE_STATE_BOTTOM_LOCKED;
- }
- else if ( m_eEyeState == HUNTER_EYE_STATE_BOTTOM_LOCKED )
- {
- m_eEyeState = HUNTER_EYE_STATE_TOP_LOCKED;
- }
-
- if ( ( m_eEyeState == HUNTER_EYE_STATE_BOTTOM_LOCKED ) || ( m_eEyeState == HUNTER_EYE_STATE_BOTH_UNLOCKED ) )
- {
- SetExpression( "scenes/npc/hunter/hunter_eyedarts_top.vcd" );
- }
-
- if ( ( m_eEyeState == HUNTER_EYE_STATE_TOP_LOCKED ) || ( m_eEyeState == HUNTER_EYE_STATE_BOTH_UNLOCKED ) )
- {
- SetExpression( "scenes/npc/hunter/hunter_eyedarts_bottom.vcd" );
- }
-
- m_EyeSwitchTimer.Set( random->RandomFloat( 1.0f, 3.0f ) );
- }
-
- /*Vector vecEyePos;
- Vector vecEyeDir;
-
- GetAttachment( gm_nTopGunAttachment, vecEyePos, &vecEyeDir );
- NDebugOverlay::Line( vecEyePos, vecEyePos + vecEyeDir * 36, 255, 0, 0, 0, 0.1 );
-
- GetAttachment( gm_nBottomGunAttachment, vecEyePos, &vecEyeDir );
- NDebugOverlay::Line( vecEyePos, vecEyePos + vecEyeDir * 36, 255, 0, 0, 0, 0.1 );*/
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::NPCThink()
-{
- BaseClass::NPCThink();
-
- // Update our planted/unplanted state.
- m_bPlanted = ( GetEntryNode( GetSequence() ) == gm_nPlantedNode );
-
- UpdateAim();
- UpdateEyes();
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::PrescheduleThink()
-{
- BaseClass::PrescheduleThink();
-
- if ( m_BeginFollowDelay.Expired() )
- {
- FollowStrider( STRING( m_iszFollowTarget ) );
- m_BeginFollowDelay.Stop();
- }
-
- m_EscortBehavior.CheckBreakEscort();
-
- // If we're being blinded by the flashlight, see if we should stop
- if ( m_bFlashlightInEyes )
- {
- if ( m_flPupilDilateTime < gpGlobals->curtime )
- {
- CBasePlayer *pPlayer = UTIL_PlayerByIndex( 1 );
- if ( ( pPlayer && !pPlayer->IsIlluminatedByFlashlight( this, NULL ) ) || !PlayerFlashlightOnMyEyes( pPlayer ) )
- {
- //Msg( "NOT SHINING FLASHLIGHT ON ME\n" );
-
- // Remove the actor from the flashlight scene
- RemoveActorFromScriptedScenes( this, true, false, "scenes/npc/hunter/hunter_eyeclose.vcd" );
- m_bFlashlightInEyes = false;
- }
- }
- }
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::GatherChargeConditions()
-{
- ClearCondition( COND_HUNTER_CAN_CHARGE_ENEMY );
-
- if ( !hunter_charge.GetBool() )
- return;
-
- if ( !GetEnemy() )
- return;
-
- if ( GetHintGroup() != NULL_STRING )
- return;
-
- if ( !HasCondition( COND_SEE_ENEMY ) )
- return;
-
- if ( !hunter_charge_test.GetBool() && gpGlobals->curtime < m_flNextChargeTime )
- return;
-
- // No charging Alyx or Barney
- if( GetEnemy()->Classify() == CLASS_PLAYER_ALLY_VITAL )
- return;
-
- if ( m_EscortBehavior.GetEscortTarget() && GetEnemy()->MyCombatCharacterPointer() && !GetEnemy()->MyCombatCharacterPointer()->FInViewCone( this ) )
- return;
-
- if ( ShouldCharge( GetAbsOrigin(), GetEnemy()->GetAbsOrigin(), true, false ) )
- {
- SetCondition( COND_HUNTER_CAN_CHARGE_ENEMY );
- }
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::GatherConditions()
-{
- GatherIndoorOutdoorConditions();
- GatherChargeConditions();
-
- BaseClass::GatherConditions();
-
- // Enemy LKP that doesn't get updated by the free knowledge code.
- // Used for shooting at where our enemy was when we can't see them.
- ClearCondition( COND_HUNTER_INCOMING_VEHICLE );
- if ( m_IgnoreVehicleTimer.Expired() && GetEnemy() && HasCondition( COND_SEE_ENEMY ) )
- {
- CBaseEntity *pVehicle = GetEnemyVehicle();
- if ( ( pVehicle ) && ( GlobalEntity_GetCounter( s_iszHuntersToRunOver ) <= 0 ) )
- {
- static float timeDrawnArrow;
-
- // Extrapolate the position of the vehicle and see if it's heading toward us.
- float predictTime = hunter_dodge_warning.GetFloat();
- Vector2D vecFuturePos = pVehicle->GetAbsOrigin().AsVector2D() + pVehicle->GetSmoothedVelocity().AsVector2D() * predictTime;
- if ( pVehicle->GetSmoothedVelocity().LengthSqr() > Square( 200 ) )
- {
- float t = 0;
- Vector2D vDirMovement = pVehicle->GetSmoothedVelocity().AsVector2D();
- if ( hunter_dodge_debug.GetBool() )
- {
- NDebugOverlay::Line( pVehicle->GetAbsOrigin(), pVehicle->GetAbsOrigin() + pVehicle->GetSmoothedVelocity(), 255, 255, 255, true, .1 );
- }
- vDirMovement.NormalizeInPlace();
- Vector2D vDirToHunter = GetAbsOrigin().AsVector2D() - pVehicle->GetAbsOrigin().AsVector2D();
- vDirToHunter.NormalizeInPlace();
- if ( DotProduct2D( vDirMovement, vDirToHunter ) > hunter_dodge_warning_cone.GetFloat() &&
- CalcDistanceSqrToLine2D( GetAbsOrigin().AsVector2D(), pVehicle->GetAbsOrigin().AsVector2D(), vecFuturePos, &t ) < Square( hunter_dodge_warning_width.GetFloat() * .5 ) &&
- t > 0.0 && t < 1.0 )
- {
- if ( fabs( predictTime - hunter_dodge_warning.GetFloat() ) < .05 || random->RandomInt( 0, 3 ) )
- {
- SetCondition( COND_HUNTER_INCOMING_VEHICLE );
- }
- else
- {
- if ( hunter_dodge_debug. GetBool() )
- {
- Msg( "Hunter %d failing dodge (ignore)\n", entindex() );
- }
- }
-
- if ( hunter_dodge_debug. GetBool() )
- {
- NDebugOverlay::Cross3D( EyePosition(), 100, 255, 255, 255, true, .1 );
- if ( timeDrawnArrow != gpGlobals->curtime )
- {
- timeDrawnArrow = gpGlobals->curtime;
- Vector vEndpoint( vecFuturePos.x, vecFuturePos.y, UTIL_GetLocalPlayer()->WorldSpaceCenter().z - 24 );
- NDebugOverlay::HorzArrow( UTIL_GetLocalPlayer()->WorldSpaceCenter() - Vector(0, 0, 24), vEndpoint, hunter_dodge_warning_width.GetFloat(), 255, 0, 0, 64, true, .1 );
- }
- }
- }
- else if ( hunter_dodge_debug.GetBool() )
- {
- if ( t <= 0 )
- {
- NDebugOverlay::Cross3D( EyePosition(), 100, 0, 0, 255, true, .1 );
- }
- else
- {
- NDebugOverlay::Cross3D( EyePosition(), 100, 0, 255, 255, true, .1 );
- }
- }
- }
- else if ( hunter_dodge_debug.GetBool() )
- {
- NDebugOverlay::Cross3D( EyePosition(), 100, 0, 255, 0, true, .1 );
- }
- if ( hunter_dodge_debug. GetBool() )
- {
- if ( timeDrawnArrow != gpGlobals->curtime )
- {
- timeDrawnArrow = gpGlobals->curtime;
- Vector vEndpoint( vecFuturePos.x, vecFuturePos.y, UTIL_GetLocalPlayer()->WorldSpaceCenter().z - 24 );
- NDebugOverlay::HorzArrow( UTIL_GetLocalPlayer()->WorldSpaceCenter() - Vector(0, 0, 24), vEndpoint, hunter_dodge_warning_width.GetFloat(), 127, 127, 127, 64, true, .1 );
- }
- }
-
- }
-
- m_vecEnemyLastSeen = GetEnemy()->GetAbsOrigin();
- }
-
- if( !HasCondition(COND_ENEMY_OCCLUDED) )
- {
- // m_flTimeSawEnemyAgain always tells us what time I first saw this
- // enemy again after some period of not seeing them. This is used to
- // compute how long the enemy has been visible to me THIS TIME.
- // Every time I lose sight of the enemy this time is set invalid until
- // I see the enemy again and record that time.
- if( m_flTimeSawEnemyAgain == HUNTER_SEE_ENEMY_TIME_INVALID )
- {
- m_flTimeSawEnemyAgain = gpGlobals->curtime;
- }
- }
- else
- {
- m_flTimeSawEnemyAgain = HUNTER_SEE_ENEMY_TIME_INVALID;
- }
-
- ManageSiegeTargets();
-}
-
-//-----------------------------------------------------------------------------
-// Search all entities in the map
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::CollectSiegeTargets()
-{
- CBaseEntity *pTarget = gEntList.FindEntityByName( NULL, m_iszSiegeTargetName );
-
- while( pTarget != NULL )
- {
- if( pTarget->Classify() == CLASS_BULLSEYE )
- {
- m_pSiegeTargets.AddToTail( pTarget );
- }
-
- pTarget = gEntList.FindEntityByName( pTarget, m_iszSiegeTargetName );
- };
-
- if( m_pSiegeTargets.Count() < 1 )
- {
- m_iszSiegeTargetName = NULL_STRING; // And stop trying!
- }
-}
-
-//-----------------------------------------------------------------------------
-// For use when Hunters are outside and the player is inside a structure
-// Create a temporary bullseye in a location that makes it seem like
-// I am aware of the location of a player I cannot see. (Then fire at
-// at this bullseye, thus laying 'siege' to the part of the building he
-// is in.) The locations are copied from suitable info_target entities.
-// (these should be placed in exterior windows and doorways so that
-// the Hunter fires into the building through these apertures)
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::ManageSiegeTargets()
-{
- if( gpGlobals->curtime < m_flTimeNextSiegeTargetAttack )
- return;
-
- if( m_pSiegeTargets.Count() == 0 )
- {
- // If my list of siege targets is empty, go and cache all of them now
- // so that I don't have to search the world every time.
- CollectSiegeTargets();
-
- if( m_pSiegeTargets.Count() == 0 )
- return;
- }
-
- m_flTimeNextSiegeTargetAttack = gpGlobals->curtime + (hunter_siege_frequency.GetFloat() * RandomFloat( 0.8f, 1.2f) );
- CBasePlayer *pPlayer = AI_GetSinglePlayer();
-
- // Start by assuming we are not going to create a siege target
- bool bCreateSiegeTarget = false;
- if( GetEnemy() == NULL )
- {
- // If I have no enemy at all, give it a try.
- bCreateSiegeTarget = true;
- }
-
- if( bCreateSiegeTarget )
- {
- // We've decided that the situation calls for a siege target. So, we dig through all of my siege targets and
- // take the closest one to the player that the player can see! (Obey they bullseye's FOV)
- float flClosestDistSqr = Square( 1200.0f ); // Only use siege targets within 100 feet of player
- CBaseEntity *pSiegeTargetLocation = NULL;
- int iTraces = 0;
- for( int i = 0 ; i < m_pSiegeTargets.Count() ; i++ )
- {
- CBaseEntity *pCandidate = m_pSiegeTargets[i];
- if ( pCandidate == NULL )
- continue;
-
- float flDistSqr = pCandidate->GetAbsOrigin().DistToSqr(pPlayer->GetAbsOrigin());
-
- if( flDistSqr < flClosestDistSqr )
- {
- // CollectSiegeTargets() guarantees my list is populated only with bullseye entities.
- CNPC_Bullseye *pBullseye = dynamic_cast<CNPC_Bullseye*>(pCandidate);
- if( !pBullseye->FInViewCone(this) )
- continue;
-
- if( pPlayer->FVisible(pCandidate) )
- {
- iTraces++;// Only counting these as a loose perf measurement
- flClosestDistSqr = flDistSqr;
- pSiegeTargetLocation = pCandidate;
- }
- }
- }
-
- if( pSiegeTargetLocation != NULL )
- {
- // Ditch any leftover siege target.
- KillCurrentSiegeTarget();
-
- // Create a bullseye that will live for 20 seconds. If we can't attack it within 20 seconds, it's probably
- // out of reach anyone, so have it clean itself up after that long.
- CBaseEntity *pSiegeTarget = CreateCustomTarget( pSiegeTargetLocation->GetAbsOrigin(), 20.0f );
- pSiegeTarget->SetName( MAKE_STRING("siegetarget") );
-
- m_hCurrentSiegeTarget.Set( pSiegeTarget );
-
- AddEntityRelationship( pSiegeTarget, D_HT, 1 );
- GetEnemies()->UpdateMemory( GetNavigator()->GetNetwork(), pSiegeTarget, pSiegeTarget->GetAbsOrigin(), 0.0f, true );
- AI_EnemyInfo_t *pMemory = GetEnemies()->Find( pSiegeTarget );
-
- if( pMemory )
- {
- // Pretend we've known about this target longer than we really have so that our AI doesn't waste time running ALERT schedules.
- pMemory->timeFirstSeen = gpGlobals->curtime - 5.0f;
- pMemory->timeLastSeen = gpGlobals->curtime - 1.0f;
- }
- }
- }
-}
-
-//-----------------------------------------------------------------------------
-// Destroy the bullseye that we're using as a temporary target
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::KillCurrentSiegeTarget()
-{
- if ( m_hCurrentSiegeTarget )
- {
- GetEnemies()->ClearMemory( m_hCurrentSiegeTarget );
-
- UTIL_Remove( m_hCurrentSiegeTarget );
- m_hCurrentSiegeTarget.Set( NULL );
- }
-}
-
-//-----------------------------------------------------------------------------
-// Return true if this NPC can hear the specified sound
-//-----------------------------------------------------------------------------
-bool CNPC_Hunter::QueryHearSound( CSound *pSound )
-{
- if ( pSound->SoundContext() & SOUND_CONTEXT_EXCLUDE_COMBINE )
- return false;
-
- if ( pSound->SoundContext() & SOUND_CONTEXT_PLAYER_VEHICLE )
- return false;
-
- return BaseClass::QueryHearSound( pSound );
-}
-
-
-//-----------------------------------------------------------------------------
-// This is a fairly bogus heuristic right now, but it works on 06a and 12 (sjb)
-//
-// Better options: Trace infinitely and check the material we hit for sky
-// Put some leaf info in the BSP
-// Use volumes in the levels? (yucky for designers)
-//-----------------------------------------------------------------------------
-// TODO: use this or nuke it!
-void CNPC_Hunter::GatherIndoorOutdoorConditions()
-{
- // Check indoor/outdoor before calling base class, since base class calls our
- // RangeAttackConditions() functions, and we want those functions to know
- // whether we're indoors or out.
- trace_t tr;
-
- UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() + Vector( 0, 0, 40.0f * 12.0f ), MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
- if( tr.fraction < 1.0f )
- {
- SetCondition( COND_HUNTER_IS_INDOORS );
- }
- else
- {
- ClearCondition( COND_HUNTER_IS_INDOORS );
- }
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::BuildScheduleTestBits()
-{
- BaseClass::BuildScheduleTestBits();
-
- if ( m_lifeState != LIFE_ALIVE )
- {
- return;
- }
-
- // Our range attack is uninterruptable for the first few seconds.
- if ( IsCurSchedule( SCHED_HUNTER_RANGE_ATTACK2, false ) && ( gpGlobals->curtime < m_flShootAllowInterruptTime ) )
- {
- ClearCustomInterruptConditions();
- SetCustomInterruptCondition( COND_HEAVY_DAMAGE );
- }
- else if ( IsCurSchedule( SCHED_HUNTER_RANGE_ATTACK2, false ) && ( GetActivity() == ACT_TRANSITION ) )
- {
- // Don't stop unplanting just because we can range attack again.
- ClearCustomInterruptCondition( COND_CAN_RANGE_ATTACK1 );
- ClearCustomInterruptCondition( COND_CAN_RANGE_ATTACK2 );
- }
- else if ( !IsInLargeOutdoorMap() && IsCurSchedule( SCHED_HUNTER_FLANK_ENEMY, false ) && GetEnemy() != NULL )
- {
- if( HasCondition(COND_CAN_RANGE_ATTACK2) && m_flTimeSawEnemyAgain != HUNTER_SEE_ENEMY_TIME_INVALID )
- {
- if( (gpGlobals->curtime - m_flTimeSawEnemyAgain) >= 2.0f )
- {
- // When we're running flank behavior, wait a moment AFTER being able to see the enemy before
- // breaking my schedule to range attack. This helps assure that the hunter gets well inside
- // the room before stopping to attack. Otherwise the Hunter may stop immediately in the doorway
- // and stop the progress of any hunters behind it.
- SetCustomInterruptCondition( COND_CAN_RANGE_ATTACK2 );
- }
- }
- }
-
- // If our enemy is anything but a striderbuster, drop everything if we see one.
- if ( !IsStriderBuster( GetEnemy() ) )
- {
- SetCustomInterruptCondition( COND_HUNTER_SEE_STRIDERBUSTER );
- }
-
- // If we're not too busy, allow ourselves to ACK found enemy signals.
- if ( !GetEnemy() )
- {
- SetCustomInterruptCondition( COND_HUNTER_SQUADMATE_FOUND_ENEMY );
- }
-
- // Interrupt everything if we need to dodge.
- if ( !IsCurSchedule( SCHED_HUNTER_DODGE, false ) &&
- !IsCurSchedule( SCHED_HUNTER_STAGGER, false ) &&
- !IsCurSchedule( SCHED_ALERT_FACE_BESTSOUND, false ) )
- {
- SetCustomInterruptCondition( COND_HUNTER_INCOMING_VEHICLE );
- SetCustomInterruptCondition( COND_HEAR_PHYSICS_DANGER );
- SetCustomInterruptCondition( COND_HUNTER_FORCED_DODGE );
- }
-
- // Always interrupt on a flank command.
- SetCustomInterruptCondition( COND_HUNTER_FORCED_FLANK_ENEMY );
-
- // Always interrupt if staggered.
- SetCustomInterruptCondition( COND_HUNTER_STAGGERED );
-
- // Always interrupt if hit by a sticky bomb.
- SetCustomInterruptCondition( COND_HUNTER_HIT_BY_STICKYBOMB );
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-static bool IsMovablePhysicsObject( CBaseEntity *pEntity )
-{
- return pEntity && pEntity->GetMoveType() == MOVETYPE_VPHYSICS && pEntity->VPhysicsGetObject() && pEntity->VPhysicsGetObject()->IsMoveable();
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-NPC_STATE CNPC_Hunter::SelectIdealState()
-{
- switch ( m_NPCState )
- {
- case NPC_STATE_COMBAT:
- {
- if ( GetEnemy() == NULL )
- {
- if ( !HasCondition( COND_ENEMY_DEAD ) && !hunter_disable_patrol.GetBool() )
- {
- // Lost track of my enemy. Patrol.
- SetCondition( COND_HUNTER_SHOULD_PATROL );
- }
-
- return NPC_STATE_ALERT;
- }
- else if ( HasCondition( COND_ENEMY_DEAD ) )
- {
- // dvs: TODO: announce enemy kills?
- //AnnounceEnemyKill(GetEnemy());
- }
- }
-
- default:
- {
- return BaseClass::SelectIdealState();
- }
- }
-
- return GetIdealState();
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-bool CNPC_Hunter::ShouldCharge( const Vector &startPos, const Vector &endPos, bool useTime, bool bCheckForCancel )
-{
- // Must have a target
- if ( !GetEnemy() )
- return false;
-
- // Don't check the distance once we start charging
- if ( !bCheckForCancel && !hunter_charge_test.GetBool() )
- {
- float distance = ( startPos.AsVector2D() - endPos.AsVector2D() ).LengthSqr();
-
- // Must be within our tolerance range
- if ( ( distance < Square(HUNTER_CHARGE_MIN) ) || ( distance > Square(HUNTER_CHARGE_MAX) ) )
- return false;
- }
-
- // FIXME: We'd like to exclude small physics objects from this check!
-
- // We only need to hit the endpos with the edge of our bounding box
- Vector vecDir = endPos - startPos;
- VectorNormalize( vecDir );
- float flWidth = WorldAlignSize().x * 0.5;
- Vector vecTargetPos = endPos - (vecDir * flWidth);
-
- // See if we can directly move there
- AIMoveTrace_t moveTrace;
- GetMoveProbe()->MoveLimit( NAV_GROUND, startPos, vecTargetPos, MASK_NPCSOLID_BRUSHONLY, GetEnemy(), &moveTrace );
-
- // Draw the probe
- if ( g_debug_hunter_charge.GetInt() == 1 )
- {
- Vector enemyDir = (vecTargetPos - startPos);
- float enemyDist = VectorNormalize( enemyDir );
-
- NDebugOverlay::BoxDirection( startPos, GetHullMins(), GetHullMaxs() + Vector(enemyDist,0,0), enemyDir, 0, 255, 0, 8, 1.0f );
- }
-
- // If we're not blocked, charge
- if ( IsMoveBlocked( moveTrace ) )
- {
- // Don't allow it if it's too close to us
- if ( UTIL_DistApprox( WorldSpaceCenter(), moveTrace.vEndPosition ) < HUNTER_CHARGE_MIN )
- return false;
-
- // Allow some special cases to not block us
- if ( moveTrace.pObstruction != NULL )
- {
- // If we've hit the world, see if it's a cliff
- if ( moveTrace.pObstruction == GetContainingEntity( INDEXENT(0) ) )
- {
- // Can't be too far above/below the target
- if ( fabs( moveTrace.vEndPosition.z - vecTargetPos.z ) > StepHeight() )
- return false;
-
- // Allow it if we got pretty close
- if ( UTIL_DistApprox( moveTrace.vEndPosition, vecTargetPos ) < 64 )
- return true;
- }
-
- // Hit things that will take damage
- if ( moveTrace.pObstruction->m_takedamage != DAMAGE_NO )
- return true;
-
- // Hit things that will move
- if ( moveTrace.pObstruction->GetMoveType() == MOVETYPE_VPHYSICS )
- return true;
- }
-
- return false;
- }
-
- float zDelta = endPos.z - moveTrace.vEndPosition.z;
- if ( fabsf(zDelta) > GetHullHeight() * 0.7)
- {
- return false;
- }
-
- return true;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-bool CNPC_Hunter::HandleInteraction(int interactionType, void *data, CBaseCombatCharacter *pSourceEnt)
-{
- if ( ( pSourceEnt != this ) && ( interactionType == g_interactionHunterFoundEnemy ) )
- {
- SetCondition( COND_HUNTER_SQUADMATE_FOUND_ENEMY );
- return true;
- }
-
- return BaseClass::HandleInteraction( interactionType, data, pSourceEnt );
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-int CNPC_Hunter::SelectCombatSchedule()
-{
- // If we're here with no enemy, patrol and hope we find one.
- CBaseEntity *pEnemy = GetEnemy();
- if ( pEnemy == NULL )
- {
- if ( !hunter_disable_patrol.GetBool() )
- return SCHED_HUNTER_PATROL_RUN;
- else
- return SCHED_ALERT_STAND;
- }
-
- if ( hunter_flechette_test.GetBool() )
- {
- if ( HasCondition( COND_CAN_RANGE_ATTACK2 ) )
- {
- return SCHED_HUNTER_RANGE_ATTACK2;
- }
- return SCHED_COMBAT_FACE;
- }
-
- bool bStriderBuster = IsStriderBuster( pEnemy );
- if ( bStriderBuster )
- {
- if ( gpGlobals->curtime - CAI_HunterEscortBehavior::gm_flLastDefendSound > 10.0 )
- {
- EmitSound( "NPC_Hunter.DefendStrider" );
- CAI_HunterEscortBehavior::gm_flLastDefendSound = gpGlobals->curtime;
- }
-
- if ( HasCondition( COND_CAN_RANGE_ATTACK2 ) || HasCondition( COND_NOT_FACING_ATTACK ) )
- {
- return SCHED_HUNTER_RANGE_ATTACK2;
- }
- return SCHED_ESTABLISH_LINE_OF_FIRE;
- }
-
- // Certain behaviors, like flanking and melee attacks, only make sense on visible,
- // corporeal enemies (NOT bullseyes).
- bool bIsCorporealEnemy = IsCorporealEnemy( pEnemy );
-
- // Take a quick swipe at our enemy if able to do so.
- if ( bIsCorporealEnemy && HasCondition( COND_CAN_MELEE_ATTACK1 ) )
- {
- return SCHED_HUNTER_MELEE_ATTACK1;
- }
-
- // React to newly acquired enemies.
- if ( bIsCorporealEnemy && HasCondition( COND_NEW_ENEMY ) )
- {
- AI_EnemyInfo_t *pEnemyInfo = GetEnemies()->Find( pEnemy );
-
- if ( GetSquad() && pEnemyInfo && ( pEnemyInfo->timeFirstSeen == pEnemyInfo->timeAtFirstHand ) )
- {
- GetSquad()->BroadcastInteraction( g_interactionHunterFoundEnemy, NULL, this );
-
- // First contact for my squad.
- return SCHED_HUNTER_FOUND_ENEMY;
- }
- }
-
- if ( HasCondition( COND_HUNTER_SQUADMATE_FOUND_ENEMY ) )
- {
- // A squadmate found an enemy. Respond to their call.
- return SCHED_HUNTER_FOUND_ENEMY_ACK;
- }
-
- // Fire a flechette volley. Ignore squad slots if we're attacking a striderbuster.
- // See if there is an opportunity to charge.
- if ( !bStriderBuster && bIsCorporealEnemy && HasCondition( COND_HUNTER_CAN_CHARGE_ENEMY ) )
- {
- if ( hunter_charge_test.GetBool() || random->RandomInt( 1, 100 ) < hunter_charge_pct.GetInt() )
- {
- if ( hunter_charge_test.GetBool() || OccupyStrategySlot( SQUAD_SLOT_HUNTER_CHARGE ) )
- {
- return SCHED_HUNTER_CHARGE_ENEMY;
- }
- }
- }
-
- if ( HasCondition( COND_CAN_RANGE_ATTACK2 ) )
- {
- if ( bStriderBuster || CountRangedAttackers() < hunter_flechette_max_concurrent_volleys.GetInt() )
- {
- DelayRangedAttackers( hunter_flechette_volley_start_min_delay.GetFloat(), hunter_flechette_volley_start_max_delay.GetFloat(), true );
- return SCHED_HUNTER_RANGE_ATTACK2;
- }
- }
-
- if ( pEnemy->GetGroundEntity() == this )
- {
- return SCHED_HUNTER_MELEE_ATTACK1;
- }
-
- if ( HasCondition( COND_TOO_CLOSE_TO_ATTACK ) )
- {
- return SCHED_MOVE_AWAY_FROM_ENEMY;
- }
-
- // Sidestep every so often if my enemy is nearby and facing me.
-/*
- if ( gpGlobals->curtime > m_flNextSideStepTime )
- {
- if ( HasCondition( COND_ENEMY_FACING_ME ) && ( UTIL_DistApprox( GetEnemy()->GetAbsOrigin(), GetAbsOrigin() ) < HUNTER_FACE_ENEMY_DIST ) )
- {
- m_flNextSideStepTime = gpGlobals->curtime + random->RandomFloat( 1.0f, 3.0f );
- return SCHED_HUNTER_SIDESTEP;
- }
- }
-*/
- if ( HasCondition( COND_HEAVY_DAMAGE ) && ( gpGlobals->curtime > m_flNextSideStepTime ) )
- {
- m_flNextSideStepTime = gpGlobals->curtime + random->RandomFloat( 1.0f, 3.0f );
- return SCHED_HUNTER_SIDESTEP;
- }
-
- if ( !bStriderBuster && bIsCorporealEnemy )
- {
- if ( HasCondition( COND_HUNTER_CAN_CHARGE_ENEMY ) )
- {
- if ( OccupyStrategySlot( SQUAD_SLOT_HUNTER_CHARGE ) )
- {
- return SCHED_HUNTER_CHARGE_ENEMY;
- }
-/*
- else
- {
- return SCHED_HUNTER_SIDESTEP;
- }
-*/
- }
-
- // Try to be a flanker.
- if ( ( NumHuntersInMySquad() > 1 ) && OccupyStrategySlotRange( SQUAD_SLOT_HUNTER_FLANK_FIRST, SQUAD_SLOT_HUNTER_FLANK_LAST ) )
- {
- return SCHED_HUNTER_FLANK_ENEMY;
- }
- }
-
- // Can't see my enemy.
- if ( HasCondition( COND_ENEMY_OCCLUDED ) || HasCondition( COND_ENEMY_TOO_FAR ) || HasCondition( COND_TOO_FAR_TO_ATTACK ) || HasCondition( COND_NOT_FACING_ATTACK ) )
- {
- return SCHED_HUNTER_CHASE_ENEMY;
- }
-
- if ( HasCondition( COND_HUNTER_CANT_PLANT ) )
- {
- return SCHED_ESTABLISH_LINE_OF_FIRE;
- }
-
- //if ( HasCondition( COND_ENEMY_OCCLUDED ) && IsCurSchedule( SCHED_RANGE_ATTACK1, false ) )
- //{
- // return SCHED_HUNTER_COMBAT_FACE;
- //}
-
- return SCHED_HUNTER_CHANGE_POSITION;
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-int CNPC_Hunter::SelectSiegeSchedule()
-{
- bool bHasEnemy = (GetEnemy() != NULL);
-
- if( bHasEnemy )
- {
- // We have an enemy, so we should be making every effort to attack it.
- if( !HasCondition(COND_SEE_ENEMY) || !HasCondition(COND_CAN_RANGE_ATTACK2) )
- return SCHED_ESTABLISH_LINE_OF_FIRE;
-
- if( HasCondition(COND_CAN_RANGE_ATTACK2) )
- return SCHED_HUNTER_RANGE_ATTACK2;
-
- return SCHED_HUNTER_SIEGE_STAND;
- }
- else
- {
- // Otherwise we are loitering in siege mode. Break line of sight with the player
- // if they expose our position.
- if( HasCondition( COND_SEE_PLAYER ) )
- return SCHED_HUNTER_CHANGE_POSITION_SIEGE;
- }
-
- return SCHED_HUNTER_SIEGE_STAND;
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-int CNPC_Hunter::SelectSchedule()
-{
- if ( hunter_stand_still.GetBool() )
- {
- m_bPlanted = false;
- return SCHED_IDLE_STAND;
- }
-
- if ( HasCondition( COND_HUNTER_FORCED_DODGE ) )
- return SCHED_HUNTER_DODGE;
-
- if ( HasCondition( COND_HUNTER_NEW_HINTGROUP ) || ( GetHintGroup() != NULL_STRING && m_CheckHintGroupTimer.Expired() ) )
- {
- CAI_Hint *pHint;
- CHintCriteria criteria;
- criteria.SetGroup( GetHintGroup() );
- criteria.SetFlag( bits_HINT_NODE_NEAREST );
-
- if ( HasCondition( COND_HUNTER_NEW_HINTGROUP ) )
- {
- ClearCondition( COND_HUNTER_NEW_HINTGROUP );
- if ( GetEnemy() )
- {
- pHint = CAI_HintManager::FindHint( NULL, GetEnemy()->GetAbsOrigin(), criteria );
- }
- else
- {
- pHint = CAI_HintManager::FindHint( GetAbsOrigin(), criteria );
- }
-
- if ( pHint )
- {
- pHint->Lock( this );
- }
- }
- else
- {
- pHint = CAI_HintManager::FindHint( GetAbsOrigin(), criteria );
- if ( pHint )
- {
- if ( (pHint->GetAbsOrigin() - GetAbsOrigin()).Length2DSqr() < Square( 20*12 ) )
- {
- m_CheckHintGroupTimer.Set( 5 );
- pHint = NULL;
- }
- else
- {
- m_CheckHintGroupTimer.Set( 15 );
- }
- }
- }
-
- if ( pHint )
- {
- SetHintNode( pHint );
- return SCHED_HUNTER_GOTO_HINT;
- }
- }
-
- if ( HasCondition( COND_HUNTER_INCOMING_VEHICLE ) )
- {
- if ( m_RundownDelay.Expired() )
- {
- int iRundownCounter = 0;
- if ( GetSquad() )
- {
- GetSquad()->GetSquadData( HUNTER_RUNDOWN_SQUADDATA, &iRundownCounter );
- }
-
- if ( iRundownCounter % 2 == 0 )
- {
- for ( int i = 0; i < g_Hunters.Count(); i++ )
- {
- if ( g_Hunters[i] != this )
- {
- g_Hunters[i]->m_RundownDelay.Set( 3 );
- g_Hunters[i]->m_IgnoreVehicleTimer.Force();
- }
- }
- m_IgnoreVehicleTimer.Set( hunter_dodge_warning.GetFloat() * 4 );
- if ( hunter_dodge_debug.GetBool() )
- {
- Msg( "Hunter %d rundown\n", entindex() );
- }
-
- if ( HasCondition( COND_SEE_ENEMY ) )
- {
- if ( m_bPlanted && HasCondition( COND_CAN_RANGE_ATTACK2 ) )
- {
- return SCHED_HUNTER_RANGE_ATTACK2;
- }
- else if ( random->RandomInt( 0, 1 ) )
- {
- return SCHED_HUNTER_CHARGE_ENEMY;
- }
- else
- {
- return SCHED_MOVE_AWAY;
- }
- }
- else
- {
- SetTarget( UTIL_GetLocalPlayer() );
- return SCHED_TARGET_FACE;
- }
- }
- else
- {
- if ( hunter_dodge_debug.GetBool() )
- {
- Msg( "Hunter %d safe from rundown\n", entindex() );
- }
- for ( int i = 0; i < g_Hunters.Count(); i++ )
- {
- g_Hunters[i]->m_RundownDelay.Set( 4 );
- g_Hunters[i]->m_IgnoreVehicleTimer.Force();
- }
- if ( GetSquad() )
- {
- GetSquad()->SetSquadData( HUNTER_RUNDOWN_SQUADDATA, iRundownCounter + 1 );
- }
- }
- }
-
- if ( HasCondition( COND_SEE_ENEMY ) )
- {
- if ( hunter_dodge_debug.GetBool() )
- {
- Msg( "Hunter %d try dodge\n", entindex() );
- }
- return SCHED_HUNTER_DODGE;
- }
- else
- {
- SetTarget( UTIL_GetLocalPlayer() );
- return SCHED_TARGET_FACE;
- }
-
- CSound *pBestSound = GetBestSound( SOUND_PHYSICS_DANGER );
- if ( pBestSound && ( pBestSound->SoundContext() & SOUND_CONTEXT_PLAYER_VEHICLE ) )
- {
- return SCHED_ALERT_FACE_BESTSOUND;
- }
- }
-
- if ( HasCondition( COND_HUNTER_FORCED_FLANK_ENEMY ) )
- {
- return SCHED_HUNTER_FLANK_ENEMY;
- }
-
- if ( HasCondition( COND_HUNTER_STAGGERED ) /*|| HasCondition( COND_HUNTER_HIT_BY_STICKYBOMB )*/ )
- {
- return SCHED_HUNTER_STAGGER;
- }
-
- // Now that we're past all of the forced reactions to things, if we're running the siege
- // behavior, go pick an appropriate siege schedule UNLESS we have an enemy. If we have
- // an enemy, we should focus on attacking that enemy.
- if( IsUsingSiegeTargets() )
- {
- return SelectSiegeSchedule();
- }
-
- // back away if there's a magnade glued to my head.
- if ( hunter_retreat_striderbusters.GetBool() /*&& GetEnemy() && ( GetEnemy()->IsPlayer() )*/
- && (m_hAttachedBusters.Count() > 0)
- && m_fCorneredTimer < gpGlobals->curtime)
- {
- return SCHED_HUNTER_TAKE_COVER_FROM_ENEMY;
- }
-
- if ( !BehaviorSelectSchedule() )
- {
- switch ( GetState() )
- {
- case NPC_STATE_IDLE:
- {
- return SCHED_HUNTER_PATROL;
- }
-
- case NPC_STATE_ALERT:
- {
- if ( HasCondition( COND_HUNTER_SHOULD_PATROL ) )
- return SCHED_HUNTER_PATROL;
-
- break;
- }
-
- case NPC_STATE_COMBAT:
- {
- return SelectCombatSchedule();
- }
- }
- }
-
- return BaseClass::SelectSchedule();
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-int CNPC_Hunter::TranslateSchedule( int scheduleType )
-{
- switch ( scheduleType )
- {
- case SCHED_RANGE_ATTACK1:
- {
- return SCHED_HUNTER_RANGE_ATTACK1;
- }
-
- case SCHED_RANGE_ATTACK2:
- case SCHED_HUNTER_RANGE_ATTACK2:
- {
- if ( scheduleType == SCHED_RANGE_ATTACK2 )
- {
- Msg( "HUNTER IGNORING SQUAD SLOTS\n" );
- }
-
- if ( IsStriderBuster( GetEnemy() ) )
- {
- // Attack as FAST as possible. The point is to shoot down the buster.
- return SCHED_HUNTER_RANGE_ATTACK2_VS_STRIDERBUSTER;
- }
-
- return SCHED_HUNTER_RANGE_ATTACK2;
- }
-
- case SCHED_MELEE_ATTACK1:
- {
- return SCHED_HUNTER_MELEE_ATTACK1;
- }
-
- case SCHED_ESTABLISH_LINE_OF_FIRE_FALLBACK:
- {
- return SCHED_HUNTER_CHANGE_POSITION;
- }
-
- case SCHED_ALERT_STAND:
- {
- if ( !hunter_disable_patrol.GetBool() )
- return SCHED_HUNTER_PATROL_RUN;
- break;
- }
-
- case SCHED_COMBAT_FACE:
- {
- return SCHED_HUNTER_COMBAT_FACE;
- }
-
- case SCHED_HUNTER_PATROL:
- {
- if ( hunter_disable_patrol.GetBool() )
- {
- return SCHED_IDLE_STAND;
- }
- break;
- }
- }
-
- return BaseClass::TranslateSchedule( scheduleType );
-}
-
-
-//-----------------------------------------------------------------------------
-// catch blockage while escaping magnade
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::TaskFail( AI_TaskFailureCode_t code )
-{
- if ( IsCurSchedule( SCHED_HUNTER_TAKE_COVER_FROM_ENEMY, false ) && ( code == FAIL_NO_ROUTE_BLOCKED ) )
- {
- // cornered!
- if ( m_fCorneredTimer < gpGlobals->curtime )
- {
- m_fCorneredTimer = gpGlobals->curtime + 6.0f;
- }
- }
-
- BaseClass::TaskFail( code );
-}
-
-
-//-----------------------------------------------------------------------------
-// The player is speeding toward us in a vehicle! Find a good activity for dodging.
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::TaskFindDodgeActivity()
-{
- if ( GetEnemy() == NULL )
- {
- TaskFail( "No enemy to dodge" );
- return;
- }
-
- Vector vecUp;
- Vector vecRight;
- GetVectors( NULL, &vecRight, &vecUp );
-
- // TODO: find most perpendicular 8-way dodge when we get the anims
- Vector vecEnemyDir = GetEnemy()->GetAbsOrigin() - GetAbsOrigin();
- //Vector vecDir = CrossProduct( vecEnemyDir, vecUp );
- VectorNormalize( vecEnemyDir );
- if ( fabs( DotProduct( vecEnemyDir, vecRight ) ) > 0.7 )
- {
- TaskFail( "Can't dodge, enemy approaching perpendicularly" );
- return;
- }
-
- // Check left or right randomly first.
- bool bDodgeLeft = false;
- CBaseEntity *pVehicle = GetEnemyVehicle();
- if ( pVehicle )
- {
- Ray_t enemyRay;
- Ray_t perpendicularRay;
- enemyRay.Init( pVehicle->GetAbsOrigin(), pVehicle->GetAbsOrigin() + pVehicle->GetSmoothedVelocity() );
- Vector vPerpendicularPt = vecEnemyDir;
- vPerpendicularPt.y = -vPerpendicularPt.y;
- perpendicularRay.Init( GetAbsOrigin(), GetAbsOrigin() + vPerpendicularPt );
-
- enemyRay.m_Start.z = enemyRay.m_Delta.z = enemyRay.m_StartOffset.z;
- perpendicularRay.m_Start.z = perpendicularRay.m_Delta.z = perpendicularRay.m_StartOffset.z;
-
- float t, s;
-
- IntersectRayWithRay( perpendicularRay, enemyRay, t, s );
-
- if ( t > 0 )
- {
- bDodgeLeft = true;
- }
- }
- else if ( random->RandomInt( 0, 1 ) == 0 )
- {
- bDodgeLeft = true;
- }
-
- bool bFoundDir = false;
- int nTries = 0;
-
- while ( !bFoundDir && ( nTries < 2 ) )
- {
- // Pick a dodge activity to try.
- if ( bDodgeLeft )
- {
- m_eDodgeActivity = ACT_HUNTER_DODGEL;
- }
- else
- {
- m_eDodgeActivity = ACT_HUNTER_DODGER;
- }
-
- // See where the dodge will put us.
- Vector vecLocalDelta;
- int nSeq = SelectWeightedSequence( m_eDodgeActivity );
- GetSequenceLinearMotion( nSeq, &vecLocalDelta );
-
- // Transform the sequence delta into local space.
- matrix3x4_t fRotateMatrix;
- AngleMatrix( GetLocalAngles(), fRotateMatrix );
- Vector vecDelta;
- VectorRotate( vecLocalDelta, fRotateMatrix, vecDelta );
-
- // Trace a bit high so this works better on uneven terrain.
- Vector testHullMins = GetHullMins();
- testHullMins.z += ( StepHeight() * 2 );
-
- // See if all is clear in that direction.
- trace_t tr;
- HunterTraceHull_SkipPhysics( GetAbsOrigin(), GetAbsOrigin() + vecDelta, testHullMins, GetHullMaxs(), MASK_NPCSOLID, this, GetCollisionGroup(), &tr, VPhysicsGetObject()->GetMass() * 0.5f );
-
- // TODO: dodge anyway if we'll make it a certain percentage of the way through the dodge?
- if ( tr.fraction == 1.0f )
- {
- //NDebugOverlay::SweptBox( GetAbsOrigin(), GetAbsOrigin() + vecDelta, testHullMins, GetHullMaxs(), QAngle( 0, 0, 0 ), 0, 255, 0, 128, 5 );
- bFoundDir = true;
- TaskComplete();
- }
- else
- {
- //NDebugOverlay::SweptBox( GetAbsOrigin(), GetAbsOrigin() + vecDelta, testHullMins, GetHullMaxs(), QAngle( 0, 0, 0 ), 255, 0, 0, 128, 5 );
- nTries++;
- bDodgeLeft = !bDodgeLeft;
- }
- }
-
- if ( nTries < 2 )
- {
- TaskComplete();
- }
- else
- {
- TaskFail( "Couldn't find dodge position\n" );
- }
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::StartTask( const Task_t *pTask )
-{
- switch ( pTask->iTask )
- {
- case TASK_HUNTER_FINISH_RANGE_ATTACK:
- {
- if( GetEnemy() != NULL && GetEnemy()->Classify() == CLASS_PLAYER_ALLY_VITAL )
- {
- // Just finished shooting at Alyx! So forget her for a little while and get back on the player
- // !!!LATER - make sure there's someone else in enemy memory to go bother.
- GetEnemies()->SetTimeValidEnemy( GetEnemy(), gpGlobals->curtime + 10.0f );
- }
-
- if( m_hCurrentSiegeTarget )
- {
- // We probably just fired at our siege target, so dump it.
- KillCurrentSiegeTarget();
- }
-
- TaskComplete();
- }
-
- case TASK_HUNTER_WAIT_FOR_MOVEMENT_FACING_ENEMY:
- {
- ChainStartTask( TASK_WAIT_FOR_MOVEMENT, pTask->flTaskData );
- break;
- }
-
- case TASK_HUNTER_BEGIN_FLANK:
- {
- if ( IsInSquad() && GetSquad()->NumMembers() > 1 )
- {
- // Flank relative to the other shooter in our squad.
- // If there's no other shooter, just flank relative to any squad member.
- AISquadIter_t iter;
- CAI_BaseNPC *pNPC = GetSquad()->GetFirstMember( &iter );
- while ( pNPC == this )
- {
- pNPC = GetSquad()->GetNextMember( &iter );
- }
-
- m_vSavePosition = pNPC->GetAbsOrigin();
- }
- else
- {
- // Flank relative to our current position.
- m_vSavePosition = GetAbsOrigin();
- }
-
- TaskComplete();
- break;
- }
-
- case TASK_HUNTER_ANNOUNCE_FLANK:
- {
- EmitSound( "NPC_Hunter.FlankAnnounce" );
- TaskComplete();
- break;
- }
-
- case TASK_HUNTER_DODGE:
- {
- if ( hunter_dodge_debug. GetBool() )
- {
- Msg( "Hunter %d dodging\n", entindex() );
- }
- SetIdealActivity( m_eDodgeActivity );
- break;
- }
-
- // Guarantee a certain delay between volleys. If we aren't already planted,
- // the plant transition animation will take care of that.
- case TASK_HUNTER_PRE_RANGE_ATTACK2:
- {
- if ( !m_bPlanted || ( GetEnemy() && IsStriderBuster( GetEnemy() ) ) )
- {
- TaskComplete();
- }
- else
- {
- SetIdealActivity( ACT_HUNTER_ANGRY );
- }
- break;
- }
-
- case TASK_HUNTER_SHOOT_COMMIT:
- {
- // We're committing to shooting. Don't allow interrupts until after we've shot a bit (see TASK_RANGE_ATTACK1).
- m_flShootAllowInterruptTime = gpGlobals->curtime + 100.0f;
- TaskComplete();
- break;
- }
-
- case TASK_RANGE_ATTACK2:
- {
- if ( GetEnemy() )
- {
- bool bIsBuster = IsStriderBuster( GetEnemy() );
- if ( bIsBuster )
- {
- AddFacingTarget( GetEnemy(), GetEnemy()->GetAbsOrigin() + GetEnemy()->GetSmoothedVelocity() * .5, 1.0, 0.8 );
- }
-
- // Start the firing sound.
- //CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
- //controller.SoundChangeVolume( m_pGunFiringSound, 1.0, hunter_first_flechette_delay.GetFloat() );
-
- SetIdealActivity( ACT_RANGE_ATTACK2 );
-
- // Decide how many shots to fire.
- int nShots = hunter_flechette_volley_size.GetInt();
- if ( g_pGameRules->IsSkillLevel( SKILL_EASY ) )
- {
- nShots--;
- }
-
- // Decide when to fire the first shot.
- float initialDelay = hunter_first_flechette_delay.GetFloat();
- if ( bIsBuster )
- {
- initialDelay = 0; //*= 0.5;
- }
-
- BeginVolley( nShots, gpGlobals->curtime + initialDelay );
-
- // In case we need to miss on purpose, pick a direction now.
- m_bMissLeft = false;
- if ( random->RandomInt( 0, 1 ) == 0 )
- {
- m_bMissLeft = true;
- }
-
- LockBothEyes( initialDelay + ( nShots * hunter_flechette_delay.GetFloat() ) );
- }
- else
- {
- TaskFail( FAIL_NO_ENEMY );
- }
-
- break;
- }
-
- case TASK_HUNTER_STAGGER:
- {
- // Stagger in the direction the impact force would push us.
- VMatrix worldToLocalRotation = EntityToWorldTransform();
- Vector vecLocalStaggerDir = worldToLocalRotation.InverseTR().ApplyRotation( m_vecStaggerDir );
-
- float flStaggerYaw = VecToYaw( vecLocalStaggerDir );
- SetPoseParameter( gm_nStaggerYawPoseParam, flStaggerYaw );
-
- // Go straight there!
- SetActivity( ACT_RESET );
- SetActivity( ( Activity )ACT_HUNTER_STAGGER );
- break;
- }
-
- case TASK_MELEE_ATTACK1:
- {
- SetLastAttackTime( gpGlobals->curtime );
-
- if ( GetEnemy() && GetEnemy()->IsPlayer() )
- {
- ResetIdealActivity( ( Activity )ACT_HUNTER_MELEE_ATTACK1_VS_PLAYER );
- }
- else
- {
- ResetIdealActivity( ACT_MELEE_ATTACK1 );
- }
-
- break;
- }
-
- case TASK_HUNTER_CORNERED_TIMER:
- {
- m_fCorneredTimer = gpGlobals->curtime + pTask->flTaskData;
-
- break;
- }
-
- case TASK_HUNTER_FIND_SIDESTEP_POSITION:
- {
- if ( GetEnemy() == NULL )
- {
- TaskFail( "No enemy to sidestep" );
- }
- else
- {
- Vector vecUp;
- GetVectors( NULL, NULL, &vecUp );
-
- Vector vecEnemyDir = GetEnemy()->GetAbsOrigin() - GetAbsOrigin();
- Vector vecDir = CrossProduct( vecEnemyDir, vecUp );
- VectorNormalize( vecDir );
-
- // Sidestep left or right randomly.
- if ( random->RandomInt( 0, 1 ) == 0 )
- {
- vecDir *= -1;
- }
-
- // Start high and then trace down so that it works on uneven terrain.
- Vector vecPos = GetAbsOrigin() + Vector( 0, 0, 64 ) + random->RandomFloat( 120, 200 ) * vecDir;
-
- // Try to find the ground at the sidestep position.
- trace_t tr;
- UTIL_TraceLine( vecPos, vecPos + Vector( 0, 0, -128 ), MASK_NPCSOLID, NULL, COLLISION_GROUP_NONE, &tr );
- if ( tr.fraction < 1.0f )
- {
- //NDebugOverlay::Line( vecPos, tr.endpos, 0, 255, 0, true, 10 );
-
- m_vSavePosition = tr.endpos;
-
- TaskComplete();
- }
- else
- {
- TaskFail( "Couldn't find sidestep position\n" );
- }
- }
-
- break;
- }
-
- case TASK_HUNTER_FIND_DODGE_POSITION:
- {
- TaskFindDodgeActivity();
- break;
- }
-
- case TASK_HUNTER_CHARGE:
- {
- SetIdealActivity( ( Activity )ACT_HUNTER_CHARGE_START );
- break;
- }
-
- case TASK_HUNTER_CHARGE_DELAY:
- {
- m_flNextChargeTime = gpGlobals->curtime + pTask->flTaskData;
- TaskComplete();
- break;
- }
-
- case TASK_DIE:
- {
- GetNavigator()->StopMoving();
- ResetActivity();
- SetIdealActivity( GetDeathActivity() );
- m_lifeState = LIFE_DYING;
-
- break;
- }
-
- //case TASK_HUNTER_END_FLANK:
- //{
- //
- //}
-
- default:
- {
- BaseClass::StartTask( pTask );
- break;
- }
- }
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::RunTask( const Task_t *pTask )
-{
- switch ( pTask->iTask )
- {
- case TASK_HUNTER_PRE_RANGE_ATTACK2:
- {
- if ( IsActivityFinished() )
- {
- TaskComplete();
- }
- break;
- }
-
- case TASK_RANGE_ATTACK2:
- {
- if( !hunter_hate_thrown_striderbusters.GetBool() && GetEnemy() != NULL && IsStriderBuster( GetEnemy() ) )
- {
- if( !IsValidEnemy(GetEnemy()) )
- {
- TaskFail("No longer hate this StriderBuster");
- }
- }
-
- bool bIsBuster = IsStriderBuster( GetEnemy() );
- if ( bIsBuster )
- {
- Vector vFuturePosition = GetEnemy()->GetAbsOrigin() + GetEnemy()->GetSmoothedVelocity() * .3;
- AddFacingTarget( GetEnemy(), vFuturePosition, 1.0, 0.8 );
-
- Vector2D vToFuturePositon = ( vFuturePosition.AsVector2D() - GetAbsOrigin().AsVector2D() );
- vToFuturePositon.NormalizeInPlace();
- Vector2D facingDir = BodyDirection2D().AsVector2D();
-
- float flDot = DotProduct2D( vToFuturePositon, facingDir );
-
- if ( flDot < .4 )
- {
- GetMotor()->SetIdealYawToTarget( vFuturePosition );
- GetMotor()->UpdateYaw();
- break;
- }
- }
-
- if ( gpGlobals->curtime >= m_flNextFlechetteTime )
- {
- // Must have an enemy and a shot queued up.
- bool bDone = false;
- if ( GetEnemy() != NULL && m_nFlechettesQueued > 0 )
- {
- if ( ShootFlechette( GetEnemy(), false ) )
- {
- m_nClampedShots++;
- }
- else
- {
- m_nClampedShots = 0;
- }
-
- m_nFlechettesQueued--;
-
- // If we fired three or more clamped shots in a row, call it quits so we don't look dumb.
- if ( ( m_nClampedShots >= 3 ) || ( m_nFlechettesQueued == 0 ) )
- {
- bDone = true;
- }
- else
- {
- // More shooting to do. Schedule our next flechette.
- m_flNextFlechetteTime = gpGlobals->curtime + hunter_flechette_delay.GetFloat();
- }
- }
- else
- {
- bDone = true;
- }
-
- if ( bDone )
- {
- // Stop the firing sound.
- //CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
- //controller.SoundChangeVolume( m_pGunFiringSound, 0.0f, 0.1f );
-
- DelayRangedAttackers( hunter_flechette_volley_end_min_delay.GetFloat(), hunter_flechette_volley_end_max_delay.GetFloat(), true );
- TaskComplete();
- }
- }
-
- break;
- }
-
- case TASK_GET_PATH_TO_ENEMY_LOS:
- {
- ChainRunTask( TASK_GET_PATH_TO_ENEMY_LKP_LOS, pTask->flTaskData );
- break;
- }
-
- case TASK_HUNTER_DODGE:
- {
- AutoMovement();
-
- if ( IsActivityFinished() )
- {
- TaskComplete();
- }
- break;
- }
-
- case TASK_HUNTER_CORNERED_TIMER:
- {
- TaskComplete();
- break;
- }
-
- case TASK_HUNTER_STAGGER:
- {
- if ( IsActivityFinished() )
- {
- TaskComplete();
- }
- break;
- }
-
- case TASK_HUNTER_CHARGE:
- {
- Activity eActivity = GetActivity();
-
- // See if we're trying to stop after hitting/missing our target
- if ( eActivity == ACT_HUNTER_CHARGE_STOP || eActivity == ACT_HUNTER_CHARGE_CRASH )
- {
- if ( IsActivityFinished() )
- {
- m_flNextChargeTime = gpGlobals->curtime + hunter_charge_min_delay.GetFloat() + random->RandomFloat( 0, 2.5 ) + random->RandomFloat( 0, 2.5 );
- float delayMultiplier = ( g_pGameRules->IsSkillLevel( SKILL_EASY ) ) ? 1.5 : 1.0;
- float groupDelay = gpGlobals->curtime + ( 2.0 + random->RandomFloat( 0, 2 ) ) * delayMultiplier;
- for ( int i = 0; i < g_Hunters.Count(); i++ )
- {
- if ( g_Hunters[i] != this && g_Hunters[i]->m_flNextChargeTime < groupDelay )
- {
- g_Hunters[i]->m_flNextChargeTime = groupDelay;
- }
- }
- TaskComplete();
- return;
- }
-
- // Still in the process of slowing down. Run movement until it's done.
- AutoMovement();
- return;
- }
-
- // Check for manual transition
- if ( ( eActivity == ACT_HUNTER_CHARGE_START ) && ( IsActivityFinished() ) )
- {
- SetIdealActivity( ACT_HUNTER_CHARGE_RUN );
- }
-
- // See if we're still running
- if ( eActivity == ACT_HUNTER_CHARGE_RUN || eActivity == ACT_HUNTER_CHARGE_START )
- {
- if ( HasCondition( COND_NEW_ENEMY ) || HasCondition( COND_LOST_ENEMY ) || HasCondition( COND_ENEMY_DEAD ) )
- {
- SetIdealActivity( ACT_HUNTER_CHARGE_STOP );
- return;
- }
- else
- {
- if ( GetEnemy() != NULL )
- {
- Vector goalDir = ( GetEnemy()->GetAbsOrigin() - GetAbsOrigin() );
- VectorNormalize( goalDir );
-
- if ( DotProduct( BodyDirection2D(), goalDir ) < 0.25f )
- {
- SetIdealActivity( ACT_HUNTER_CHARGE_STOP );
- }
- }
- }
- }
-
- // Steer towards our target
- float idealYaw;
- if ( GetEnemy() == NULL )
- {
- idealYaw = GetMotor()->GetIdealYaw();
- }
- else
- {
- idealYaw = CalcIdealYaw( GetEnemy()->GetAbsOrigin() );
- }
-
- // Add in our steering offset
- idealYaw += ChargeSteer();
-
- // Turn to face
- GetMotor()->SetIdealYawAndUpdate( idealYaw );
-
- // See if we're going to run into anything soon
- ChargeLookAhead();
-
- // Let our animations simply move us forward. Keep the result
- // of the movement so we know whether we've hit our target.
- AIMoveTrace_t moveTrace;
- if ( AutoMovement( GetEnemy(), &moveTrace ) == false )
- {
- // Only stop if we hit the world
- if ( HandleChargeImpact( moveTrace.vEndPosition, moveTrace.pObstruction ) )
- {
- // If we're starting up, this is an error
- if ( eActivity == ACT_HUNTER_CHARGE_START )
- {
- TaskFail( "Unable to make initial movement of charge\n" );
- return;
- }
-
- // Crash unless we're trying to stop already
- if ( eActivity != ACT_HUNTER_CHARGE_STOP )
- {
- if ( moveTrace.fStatus == AIMR_BLOCKED_WORLD && moveTrace.vHitNormal == vec3_origin )
- {
- SetIdealActivity( ACT_HUNTER_CHARGE_STOP );
- }
- else
- {
- // Shake the screen
- if ( moveTrace.fStatus != AIMR_BLOCKED_NPC )
- {
- EmitSound( "NPC_Hunter.ChargeHitWorld" );
- UTIL_ScreenShake( GetAbsOrigin(), 16.0f, 4.0f, 1.0f, 400.0f, SHAKE_START );
- }
- SetIdealActivity( ACT_HUNTER_CHARGE_CRASH );
- }
- }
- }
- else if ( moveTrace.pObstruction )
- {
- // If we hit another hunter, stop
- if ( moveTrace.pObstruction->Classify() == CLASS_COMBINE_HUNTER )
- {
- // Crash unless we're trying to stop already
- if ( eActivity != ACT_HUNTER_CHARGE_STOP )
- {
- SetIdealActivity( ACT_HUNTER_CHARGE_STOP );
- }
- }
- // If we hit an antlion, don't stop, but kill it
- // We never have hunters and antlions together, but you never know.
- else if (moveTrace.pObstruction->Classify() == CLASS_ANTLION )
- {
- if ( FClassnameIs( moveTrace.pObstruction, "npc_antlionguard" ) )
- {
- // Crash unless we're trying to stop already
- if ( eActivity != ACT_HUNTER_CHARGE_STOP )
- {
- SetIdealActivity( ACT_HUNTER_CHARGE_STOP );
- }
- }
- else
- {
- Hunter_ApplyChargeDamage( this, moveTrace.pObstruction, moveTrace.pObstruction->GetHealth() );
- }
- }
- }
- }
-
- break;
- }
-
- case TASK_HUNTER_WAIT_FOR_MOVEMENT_FACING_ENEMY:
- {
- if ( GetEnemy() )
- {
- Vector vecEnemyLKP = GetEnemyLKP();
- AddFacingTarget( GetEnemy(), vecEnemyLKP, 1.0, 0.8 );
- }
- ChainRunTask( TASK_WAIT_FOR_MOVEMENT, pTask->flTaskData );
- break;
- }
-
- default:
- {
- BaseClass::RunTask( pTask );
- break;
- }
- }
-}
-
-
-//-----------------------------------------------------------------------------
-// Return true if our charge target is right in front of the hunter.
-//-----------------------------------------------------------------------------
-bool CNPC_Hunter::EnemyIsRightInFrontOfMe( CBaseEntity **pEntity )
-{
- if ( !GetEnemy() )
- return false;
-
- if ( (GetEnemy()->WorldSpaceCenter() - WorldSpaceCenter()).LengthSqr() < (156*156) )
- {
- Vector vecLOS = ( GetEnemy()->GetAbsOrigin() - GetAbsOrigin() );
- vecLOS.z = 0;
- VectorNormalize( vecLOS );
- Vector vBodyDir = BodyDirection2D();
- if ( DotProduct( vecLOS, vBodyDir ) > 0.8 )
- {
- // He's in front of me, and close. Make sure he's not behind a wall.
- trace_t tr;
- UTIL_TraceHull( WorldSpaceCenter(), GetEnemy()->WorldSpaceCenter(), GetHullMins() * 0.5, GetHullMaxs() * 0.5, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr );
- if ( tr.m_pEnt == GetEnemy() )
- {
- *pEntity = tr.m_pEnt;
- return true;
- }
- }
- }
-
- return false;
-}
-
-
-//-----------------------------------------------------------------------------
-// While charging, look ahead and see if we're going to run into anything.
-// If we are, start the gesture so it looks like we're anticipating the hit.
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::ChargeLookAhead( void )
-{
-#if 0
- trace_t tr;
- Vector vecForward;
- GetVectors( &vecForward, NULL, NULL );
- Vector vecTestPos = GetAbsOrigin() + ( vecForward * m_flGroundSpeed * 0.75 );
- Vector testHullMins = GetHullMins();
- testHullMins.z += (StepHeight() * 2);
- HunterTraceHull_SkipPhysics( GetAbsOrigin(), vecTestPos, testHullMins, GetHullMaxs(), MASK_SHOT_HULL, this, COLLISION_GROUP_NONE, &tr, VPhysicsGetObject()->GetMass() * 0.5 );
-
- //NDebugOverlay::Box( tr.startpos, testHullMins, GetHullMaxs(), 0, 255, 0, true, 0.1f );
- //NDebugOverlay::Box( vecTestPos, testHullMins, GetHullMaxs(), 255, 0, 0, true, 0.1f );
-
- if ( tr.fraction != 1.0 )
- {
- // dvs: TODO:
- // Start playing the hit animation
- //AddGesture( ACT_HUNTER_CHARGE_ANTICIPATION );
- }
-#endif
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-float CNPC_Hunter::ChargeSteer()
-{
- trace_t tr;
- Vector testPos, steer, forward, right;
- QAngle angles;
- const float testLength = m_flGroundSpeed * 0.15f;
-
- //Get our facing
- GetVectors( &forward, &right, NULL );
-
- steer = forward;
-
- const float faceYaw = UTIL_VecToYaw( forward );
-
- //Offset right
- VectorAngles( forward, angles );
- angles[YAW] += 45.0f;
- AngleVectors( angles, &forward );
-
- // Probe out
- testPos = GetAbsOrigin() + ( forward * testLength );
-
- // Offset by step height
- Vector testHullMins = GetHullMins();
- testHullMins.z += (StepHeight() * 2);
-
- // Probe
- HunterTraceHull_SkipPhysics( GetAbsOrigin(), testPos, testHullMins, GetHullMaxs(), MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr, VPhysicsGetObject()->GetMass() * 0.5f );
-
- // Debug info
- if ( g_debug_hunter_charge.GetInt() == 1 )
- {
- if ( tr.fraction == 1.0f )
- {
- NDebugOverlay::BoxDirection( GetAbsOrigin(), testHullMins, GetHullMaxs() + Vector(testLength,0,0), forward, 0, 255, 0, 8, 0.1f );
- }
- else
- {
- NDebugOverlay::BoxDirection( GetAbsOrigin(), testHullMins, GetHullMaxs() + Vector(testLength,0,0), forward, 255, 0, 0, 8, 0.1f );
- }
- }
-
- // Add in this component
- steer += ( right * 0.5f ) * ( 1.0f - tr.fraction );
-
- // Offset left
- angles[YAW] -= 90.0f;
- AngleVectors( angles, &forward );
-
- // Probe out
- testPos = GetAbsOrigin() + ( forward * testLength );
- HunterTraceHull_SkipPhysics( GetAbsOrigin(), testPos, testHullMins, GetHullMaxs(), MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr, VPhysicsGetObject()->GetMass() * 0.5f );
-
- // Debug
- if ( g_debug_hunter_charge.GetInt() == 1 )
- {
- if ( tr.fraction == 1.0f )
- {
- NDebugOverlay::BoxDirection( GetAbsOrigin(), testHullMins, GetHullMaxs() + Vector(testLength,0,0), forward, 0, 255, 0, 8, 0.1f );
- }
- else
- {
- NDebugOverlay::BoxDirection( GetAbsOrigin(), testHullMins, GetHullMaxs() + Vector(testLength,0,0), forward, 255, 0, 0, 8, 0.1f );
- }
- }
-
- // Add in this component
- steer -= ( right * 0.5f ) * ( 1.0f - tr.fraction );
-
- // Debug
- if ( g_debug_hunter_charge.GetInt() == 1 )
- {
- NDebugOverlay::Line( GetAbsOrigin(), GetAbsOrigin() + ( steer * 512.0f ), 255, 255, 0, true, 0.1f );
- NDebugOverlay::Cross3D( GetAbsOrigin() + ( steer * 512.0f ), Vector(2,2,2), -Vector(2,2,2), 255, 255, 0, true, 0.1f );
-
- NDebugOverlay::Line( GetAbsOrigin(), GetAbsOrigin() + ( BodyDirection3D() * 256.0f ), 255, 0, 255, true, 0.1f );
- NDebugOverlay::Cross3D( GetAbsOrigin() + ( BodyDirection3D() * 256.0f ), Vector(2,2,2), -Vector(2,2,2), 255, 0, 255, true, 0.1f );
- }
-
- return UTIL_AngleDiff( UTIL_VecToYaw( steer ), faceYaw );
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::ChargeDamage( CBaseEntity *pTarget )
-{
- if ( pTarget == NULL )
- return;
-
- CBasePlayer *pPlayer = ToBasePlayer( pTarget );
-
- if ( pPlayer != NULL )
- {
- //Kick the player angles
- pPlayer->ViewPunch( QAngle( 20, 20, -30 ) );
-
- Vector dir = pPlayer->WorldSpaceCenter() - WorldSpaceCenter();
- VectorNormalize( dir );
- dir.z = 0.0f;
-
- Vector vecNewVelocity = dir * 250.0f;
- vecNewVelocity[2] += 128.0f;
- pPlayer->SetAbsVelocity( vecNewVelocity );
-
- color32 red = {128,0,0,128};
- UTIL_ScreenFade( pPlayer, red, 1.0f, 0.1f, FFADE_IN );
- }
-
- // Player takes less damage
- float flDamage = ( pPlayer == NULL ) ? 250 : sk_hunter_dmg_charge.GetFloat();
-
- // If it's being held by the player, break that bond
- Pickup_ForcePlayerToDropThisObject( pTarget );
-
- // Calculate the physics force
- Hunter_ApplyChargeDamage( this, pTarget, flDamage );
-}
-
-
-//-----------------------------------------------------------------------------
-// Handles the hunter charging into something. Returns true if it hit the world.
-//-----------------------------------------------------------------------------
-bool CNPC_Hunter::HandleChargeImpact( Vector vecImpact, CBaseEntity *pEntity )
-{
- // Cause a shock wave from this point which will disrupt nearby physics objects
- //ImpactShock( vecImpact, 128, 350 );
-
- // Did we hit anything interesting?
- if ( !pEntity || pEntity->IsWorld() )
- {
- // Robin: Due to some of the finicky details in the motor, the hunter will hit
- // the world when it is blocked by our enemy when trying to step up
- // during a moveprobe. To get around this, we see if the enemy's within
- // a volume in front of the hunter when we hit the world, and if he is,
- // we hit him anyway.
- EnemyIsRightInFrontOfMe( &pEntity );
-
- // Did we manage to find him? If not, increment our charge miss count and abort.
- if ( pEntity->IsWorld() )
- {
- return true;
- }
- }
-
- // Hit anything we don't like
- if ( IRelationType( pEntity ) == D_HT && ( GetNextAttack() < gpGlobals->curtime ) )
- {
- EmitSound( "NPC_Hunter.ChargeHitEnemy" );
-
- // dvs: TODO:
- //if ( !IsPlayingGesture( ACT_HUNTER_CHARGE_HIT ) )
- //{
- // RestartGesture( ACT_HUNTER_CHARGE_HIT );
- //}
-
- ChargeDamage( pEntity );
-
- if ( !pEntity->IsNPC() )
- {
- pEntity->ApplyAbsVelocityImpulse( ( BodyDirection2D() * 400 ) + Vector( 0, 0, 200 ) );
- }
-
- if ( !pEntity->IsAlive() && GetEnemy() == pEntity )
- {
- SetEnemy( NULL );
- }
-
- SetNextAttack( gpGlobals->curtime + 2.0f );
-
- if ( !pEntity->IsAlive() || !pEntity->IsNPC() )
- {
- SetIdealActivity( ACT_HUNTER_CHARGE_STOP );
- return false;
- }
- else
- return true;
-
- }
-
- // Hit something we don't hate. If it's not moveable, crash into it.
- if ( pEntity->GetMoveType() == MOVETYPE_NONE || pEntity->GetMoveType() == MOVETYPE_PUSH )
- {
- CBreakable *pBreakable = dynamic_cast<CBreakable *>(pEntity);
- if ( pBreakable && pBreakable->IsBreakable() && pBreakable->m_takedamage == DAMAGE_YES && pBreakable->GetHealth() > 0 )
- {
- ChargeDamage( pEntity );
- }
- return true;
- }
-
- // If it's a vphysics object that's too heavy, crash into it too.
- if ( pEntity->GetMoveType() == MOVETYPE_VPHYSICS )
- {
- IPhysicsObject *pPhysics = pEntity->VPhysicsGetObject();
- if ( pPhysics )
- {
- // If the object is being held by the player, knock it out of his hands
- if ( pPhysics->GetGameFlags() & FVPHYSICS_PLAYER_HELD )
- {
- Pickup_ForcePlayerToDropThisObject( pEntity );
- return false;
- }
-
- if ( !pPhysics->IsMoveable() )
- return true;
-
- float entMass = PhysGetEntityMass( pEntity ) ;
- float minMass = VPhysicsGetObject()->GetMass() * 0.5f;
- if ( entMass < minMass )
- {
- if ( entMass < minMass * 0.666f || pEntity->CollisionProp()->BoundingRadius() < GetHullHeight() )
- {
- if ( pEntity->GetHealth() > 0 )
- {
- CBreakableProp *pBreakable = dynamic_cast<CBreakableProp *>(pEntity);
- if ( pBreakable && pBreakable->m_takedamage == DAMAGE_YES && pBreakable->GetHealth() > 0 && pBreakable->GetHealth() <= 50 )
- {
- ChargeDamage( pEntity );
- }
- }
- pEntity->SetNavIgnore( 2.0 );
- return false;
- }
- }
- return true;
-
- }
- }
-
- return false;
-}
-
-
-//-------------------------------------------------------------------------------------------------
-//-------------------------------------------------------------------------------------------------
-void CNPC_Hunter::Explode()
-{
- Vector velocity = vec3_origin;
- AngularImpulse angVelocity = RandomAngularImpulse( -150, 150 );
-
- PropBreakableCreateAll( GetModelIndex(), NULL, EyePosition(), GetAbsAngles(), velocity, angVelocity, 1.0, 150, COLLISION_GROUP_NPC, this );
-
- ExplosionCreate( EyePosition(), GetAbsAngles(), this, 500, 256, (SF_ENVEXPLOSION_NOPARTICLES|SF_ENVEXPLOSION_NOSPARKS|SF_ENVEXPLOSION_NODLIGHTS|SF_ENVEXPLOSION_NODAMAGE|SF_ENVEXPLOSION_NOSMOKE), false );
-
- // Create liquid fountain gushtacular effect here!
- CEffectData data;
-
- data.m_vOrigin = EyePosition();
- data.m_vNormal = Vector( 0, 0, 1 );
- data.m_flScale = 4.0f;
-
- DispatchEffect( "StriderBlood", data );
-
- // Go away
- m_lifeState = LIFE_DEAD;
-
- SetThink( &CNPC_Hunter::SUB_Remove );
- SetNextThink( gpGlobals->curtime + 0.1f );
-
- AddEffects( EF_NODRAW );
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-Activity CNPC_Hunter::NPC_TranslateActivity( Activity baseAct )
-{
- if ( ( baseAct == ACT_WALK ) || ( baseAct == ACT_RUN ) )
- {
- if ( GetEnemy() )
- {
- Vector vecEnemyLKP = GetEnemyLKP();
-
- // Only start facing when we're close enough
- if ( UTIL_DistApprox( vecEnemyLKP, GetAbsOrigin() ) < HUNTER_FACE_ENEMY_DIST )
- {
- return (Activity)ACT_HUNTER_WALK_ANGRY;
- }
- }
- }
- else if ( ( baseAct == ACT_IDLE ) && m_bPlanted )
- {
- return ( Activity )ACT_HUNTER_IDLE_PLANTED;
- }
- else if ( baseAct == ACT_RANGE_ATTACK2 )
- {
- if ( !m_bPlanted && ( m_bEnableUnplantedShooting || IsStriderBuster( GetEnemy() ) ) )
- {
- return (Activity)ACT_HUNTER_RANGE_ATTACK2_UNPLANTED;
- }
- }
-
- return BaseClass::NPC_TranslateActivity( baseAct );
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::HandleAnimEvent( animevent_t *pEvent )
-{
- Vector footPosition;
- QAngle angles;
-
- if ( pEvent->event == AE_HUNTER_FOOTSTEP_LEFT )
- {
- LeftFootHit( pEvent->eventtime );
- return;
- }
-
- if ( pEvent->event == AE_HUNTER_FOOTSTEP_RIGHT )
- {
- RightFootHit( pEvent->eventtime );
- return;
- }
-
- if ( pEvent->event == AE_HUNTER_FOOTSTEP_BACK )
- {
- BackFootHit( pEvent->eventtime );
- return;
- }
-
- if ( pEvent->event == AE_HUNTER_START_EXPRESSION )
- {
- if ( pEvent->options && Q_strlen( pEvent->options ) )
- {
- //m_iszCurrentExpression = AllocPooledString( pEvent->options );
- //SetExpression( pEvent->options );
- }
- return;
- }
-
- if ( pEvent->event == AE_HUNTER_END_EXPRESSION )
- {
- if ( pEvent->options && Q_strlen( pEvent->options ) )
- {
- //m_iszCurrentExpression = NULL_STRING;
- //RemoveActorFromScriptedScenes( this, true, false, pEvent->options );
- }
- return;
- }
-
- if ( pEvent->event == AE_HUNTER_MELEE_ANNOUNCE )
- {
- EmitSound( "NPC_Hunter.MeleeAnnounce" );
- return;
- }
-
- if ( pEvent->event == AE_HUNTER_MELEE_ATTACK_LEFT )
- {
- Vector right, forward, dir;
- AngleVectors( GetLocalAngles(), &forward, &right, NULL );
-
- right = right * -100;
- forward = forward * 600;
- dir = right + forward;
- QAngle angle( 25, 30, -20 );
-
- MeleeAttack( HUNTER_MELEE_REACH, sk_hunter_dmg_one_slash.GetFloat(), angle, dir, HUNTER_BLOOD_LEFT_FOOT );
- return;
- }
-
- if ( pEvent->event == AE_HUNTER_MELEE_ATTACK_RIGHT )
- {
- Vector right, forward,dir;
- AngleVectors( GetLocalAngles(), &forward, &right, NULL );
-
- right = right * 100;
- forward = forward * 600;
- dir = right + forward;
-
- QAngle angle( 25, -30, 20 );
-
- MeleeAttack( HUNTER_MELEE_REACH, sk_hunter_dmg_one_slash.GetFloat(), angle, dir, HUNTER_BLOOD_LEFT_FOOT );
- return;
- }
-
- if ( pEvent->event == AE_HUNTER_SPRAY_BLOOD )
- {
- Vector vecOrigin;
- Vector vecDir;
-
- // spray blood from the attachment point
- bool bGotAttachment = false;
- if ( pEvent->options )
- {
- QAngle angDir;
- if ( GetAttachment( pEvent->options, vecOrigin, angDir ) )
- {
- bGotAttachment = true;
- AngleVectors( angDir, &vecDir, NULL, NULL );
- }
- }
-
- // fall back to our center, tracing forward
- if ( !bGotAttachment )
- {
- vecOrigin = WorldSpaceCenter();
- GetVectors( &vecDir, NULL, NULL );
- }
-
- UTIL_BloodSpray( vecOrigin, vecDir, BLOOD_COLOR_RED, 4, FX_BLOODSPRAY_ALL );
-
- for ( int i = 0 ; i < 3 ; i++ )
- {
- Vector vecTraceDir = vecDir;
- vecTraceDir.x += random->RandomFloat( -0.1, 0.1 );
- vecTraceDir.y += random->RandomFloat( -0.1, 0.1 );
- vecTraceDir.z += random->RandomFloat( -0.1, 0.1 );
-
- trace_t tr;
- AI_TraceLine( vecOrigin, vecOrigin + ( vecTraceDir * 192.0f ), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr );
- if ( tr.fraction != 1.0 )
- {
- UTIL_BloodDecalTrace( &tr, BLOOD_COLOR_RED );
- }
- }
-
- return;
- }
-
- BaseClass::HandleAnimEvent( pEvent );
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::AddEntityRelationship( CBaseEntity *pEntity, Disposition_t nDisposition, int nPriority )
-{
- if ( nDisposition == D_HT && pEntity->ClassMatches("npc_bullseye") )
- UpdateEnemyMemory( pEntity, pEntity->GetAbsOrigin() );
- BaseClass::AddEntityRelationship( pEntity, nDisposition, nPriority );
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-bool CNPC_Hunter::ScheduledMoveToGoalEntity( int scheduleType, CBaseEntity *pGoalEntity, Activity movementActivity )
-{
- if ( IsCurSchedule( SCHED_HUNTER_RANGE_ATTACK1, false ) )
- {
- SetGoalEnt( pGoalEntity );
- return true;
- }
- return BaseClass::ScheduledMoveToGoalEntity( scheduleType, pGoalEntity, movementActivity );
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::OnChangeHintGroup( string_t oldGroup, string_t newGroup )
-{
- SetCondition( COND_HUNTER_NEW_HINTGROUP );
- m_CheckHintGroupTimer.Set( 10 );
-}
-
-
-//-----------------------------------------------------------------------------
-// Tells whether any given hunter is in a squad that contains other hunters.
-// This is useful for preventing timid behavior for Hunters that are not
-// supported by other hunters.
-//
-// NOTE: This counts the self! So a hunter that is alone in his squad
-// receives a result of 1.
-//-----------------------------------------------------------------------------
-int CNPC_Hunter::NumHuntersInMySquad()
-{
- AISquadIter_t iter;
- CAI_BaseNPC *pSquadmate = m_pSquad ? m_pSquad->GetFirstMember( &iter ) : NULL;
-
- if( !pSquadmate )
- {
- // Not in a squad at all, but the caller is not concerned with that. Just
- // tell them that we're in a squad of one (ourself)
- return 1;
- }
-
- int count = 0;
-
- while ( pSquadmate )
- {
- if( pSquadmate->m_iClassname == m_iClassname )
- count++;
-
- pSquadmate = m_pSquad->GetNextMember( &iter );
- }
-
- return count;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::FollowStrider( const char *szStrider )
-{
- if ( !szStrider )
- return;
-
- CBaseEntity *pEnt = gEntList.FindEntityByName( NULL, szStrider, this );
- CNPC_Strider *pStrider = dynamic_cast <CNPC_Strider *>( pEnt );
- FollowStrider(pStrider);
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::FollowStrider( CNPC_Strider * pStrider )
-{
- if ( !IsAlive() )
- {
- return;
- }
-
- if ( pStrider )
- {
- if ( m_EscortBehavior.GetFollowTarget() != pStrider )
- {
- m_iszFollowTarget = pStrider->GetEntityName();
- if ( m_iszFollowTarget == NULL_STRING )
- {
- m_iszFollowTarget = AllocPooledString( "unnamed_strider" );
- }
- m_EscortBehavior.SetEscortTarget( pStrider );
- }
- }
- else
- {
- DevWarning("Hunter set to follow entity %s that is not a strider\n", STRING( m_iszFollowTarget ) );
- m_iszFollowTarget = AllocPooledString( "unknown_strider" );
- }
-}
-
-void CAI_HunterEscortBehavior::SetEscortTarget( CNPC_Strider *pStrider, bool fFinishCurSchedule )
-{
- m_bEnabled = true;
-
- if ( GetOuter()->GetSquad() )
- {
- GetOuter()->GetSquad()->RemoveFromSquad( GetOuter() );
- }
-
- for ( int i = 0; i < g_Hunters.Count(); i++ )
- {
- if ( g_Hunters[i]->m_EscortBehavior.GetFollowTarget() == pStrider )
- {
- Assert( g_Hunters[i]->GetSquad() );
- g_Hunters[i]->GetSquad()->AddToSquad( GetOuter() );
- break;
- }
- }
-
- if ( !GetOuter()->GetSquad() )
- {
- GetOuter()->AddToSquad( AllocPooledString( CFmtStr( "%s_hunter_squad", STRING( pStrider->GetEntityName() ) ) ) );
- }
-
- BaseClass::SetFollowTarget( pStrider );
- m_flTimeEscortReturn = gpGlobals->curtime;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::InputEnableUnplantedShooting( inputdata_t &inputdata )
-{
- m_bEnableUnplantedShooting = true;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::InputDisableUnplantedShooting( inputdata_t &inputdata )
-{
- m_bEnableUnplantedShooting = false;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::InputFollowStrider( inputdata_t &inputdata )
-{
- m_iszFollowTarget = inputdata.value.StringID();
- if ( m_iszFollowTarget == s_iszStriderClassname )
- {
- m_EscortBehavior.m_bEnabled = true;
- m_iszFollowTarget = NULL_STRING;
- }
- m_BeginFollowDelay.Start( .1 ); // Allow time for strider to spawn
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::InputUseSiegeTargets( inputdata_t &inputdata )
-{
- m_iszSiegeTargetName = inputdata.value.StringID();
- m_flTimeNextSiegeTargetAttack = gpGlobals->curtime + random->RandomFloat( 1, hunter_siege_frequency.GetFloat() );
-
- if( m_iszSiegeTargetName == NULL_STRING )
- {
- // Turning the feature off. Restore m_flDistTooFar to default.
- m_flDistTooFar = hunter_flechette_max_range.GetFloat();
- m_pSiegeTargets.RemoveAll();
- }
- else
- {
- // We're going into siege mode. Adjust range accordingly.
- m_flDistTooFar = hunter_flechette_max_range.GetFloat() * HUNTER_SIEGE_MAX_DIST_MODIFIER;
- }
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::InputDodge( inputdata_t &inputdata )
-{
- SetCondition( COND_HUNTER_FORCED_DODGE );
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::InputFlankEnemy( inputdata_t &inputdata )
-{
- SetCondition( COND_HUNTER_FORCED_FLANK_ENEMY );
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::InputDisableShooting( inputdata_t &inputdata )
-{
- m_bDisableShooting = true;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::InputEnableShooting( inputdata_t &inputdata )
-{
- m_bDisableShooting = false;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::InputEnableSquadShootDelay( inputdata_t &inputdata )
-{
- m_bEnableSquadShootDelay = true;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::InputDisableSquadShootDelay( inputdata_t &inputdata )
-{
- m_bEnableSquadShootDelay = false;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-bool CNPC_Hunter::FVisible( CBaseEntity *pEntity, int traceMask, CBaseEntity **ppBlocker )
-{
- return BaseClass::FVisible( pEntity, traceMask, ppBlocker );
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-bool CNPC_Hunter::IsValidEnemy( CBaseEntity *pTarget )
-{
- if ( IsStriderBuster( pTarget) )
- {
- if ( !m_EscortBehavior.m_bEnabled || !m_EscortBehavior.GetEscortTarget() )
- {
- // We only hate striderbusters when we are actively protecting a strider.
- return false;
- }
-
- if ( pTarget->VPhysicsGetObject() )
- {
- if ( ( pTarget->VPhysicsGetObject()->GetGameFlags() & FVPHYSICS_PLAYER_HELD ) &&
- hunter_hate_held_striderbusters.GetBool() )
- {
- if ( gpGlobals->curtime - StriderBuster_GetPickupTime( pTarget ) > hunter_hate_held_striderbusters_delay.GetFloat())
- {
- if ( StriderBuster_NumFlechettesAttached( pTarget ) <= 2 )
- {
- if ( m_EscortBehavior.GetEscortTarget() &&
- ( m_EscortBehavior.GetEscortTarget()->GetAbsOrigin().AsVector2D() - pTarget->GetAbsOrigin().AsVector2D() ).LengthSqr() < Square( hunter_hate_held_striderbusters_tolerance.GetFloat() ) )
- {
- return true;
- }
- }
- }
- return false;
- }
-
- bool bThrown = ( pTarget->VPhysicsGetObject()->GetGameFlags() & FVPHYSICS_WAS_THROWN ) != 0;
- bool bAttached = StriderBuster_IsAttachedStriderBuster( pTarget );
-
- if ( ( bThrown && !bAttached ) && hunter_hate_thrown_striderbusters.GetBool() )
- {
- float t;
- float dist = CalcDistanceSqrToLineSegment2D( m_EscortBehavior.GetEscortTarget()->GetAbsOrigin().AsVector2D(),
- pTarget->GetAbsOrigin().AsVector2D(),
- pTarget->GetAbsOrigin().AsVector2D() + pTarget->GetSmoothedVelocity().AsVector2D(), &t );
-
- if ( t > 0 && dist < Square( hunter_hate_thrown_striderbusters_tolerance.GetFloat() ))
- {
- return true;
- }
- return false;
- }
-
- if ( bAttached && StriderBuster_IsAttachedStriderBuster( pTarget, m_EscortBehavior.GetEscortTarget() ) && hunter_hate_attached_striderbusters.GetBool() )
- {
- return true;
- }
- }
- return false;
- }
-
- return BaseClass::IsValidEnemy( pTarget );
-}
-
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-Disposition_t CNPC_Hunter::IRelationType( CBaseEntity *pTarget )
-{
- if ( !pTarget )
- return D_NU;
-
- if ( IsStriderBuster( pTarget ) )
- {
- if ( HateThisStriderBuster( pTarget ) )
- return D_HT;
-
- return D_NU;
- }
-
- if ( hunter_retreat_striderbusters.GetBool() )
- {
- if ( pTarget->IsPlayer() && (m_hAttachedBusters.Count() > 0) )
- {
- return D_FR;
- }
- }
-
- return BaseClass::IRelationType( pTarget );
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-int CNPC_Hunter::IRelationPriority( CBaseEntity *pTarget )
-{
- if ( IsStriderBuster( pTarget ) )
- {
- // If we're here, we already know that we hate striderbusters.
- return 1000.0f;
- }
-
- return BaseClass::IRelationPriority( pTarget );
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::SetSquad( CAI_Squad *pSquad )
-{
- BaseClass::SetSquad( pSquad );
- if ( pSquad && pSquad->NumMembers() == 1 )
- {
- pSquad->SetSquadData( HUNTER_RUNDOWN_SQUADDATA, 0 );
- }
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::OnSeeEntity( CBaseEntity *pEntity )
-{
- BaseClass::OnSeeEntity(pEntity);
-
- if ( IsStriderBuster( pEntity ) && IsValidEnemy( pEntity ) )
- {
- SetCondition( COND_HUNTER_SEE_STRIDERBUSTER );
- }
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-bool CNPC_Hunter::UpdateEnemyMemory( CBaseEntity *pEnemy, const Vector &position, CBaseEntity *pInformer )
-{
- //EmitSound( "NPC_Hunter.Alert" );
- return BaseClass::UpdateEnemyMemory( pEnemy, position, pInformer );
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-bool CNPC_Hunter::CanPlantHere( const Vector &vecPos )
-{
- // TODO: cache results?
- //if ( vecPos == m_vecLastCanPlantHerePos )
- //{
- // return m_bLastCanPlantHere;
- //}
-
- Vector vecMins = GetHullMins();
- Vector vecMaxs = GetHullMaxs();
-
- vecMins.x -= 16;
- vecMins.y -= 16;
-
- vecMaxs.x += 16;
- vecMaxs.y += 16;
- vecMaxs.z -= hunter_plant_adjust_z.GetInt();
-
- bool bResult = false;
-
- trace_t tr;
- UTIL_TraceHull( vecPos, vecPos, vecMins, vecMaxs, MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr );
- if ( tr.startsolid )
- {
- // Try again, tracing down from above.
- Vector vecStart = vecPos;
- vecStart.z += hunter_plant_adjust_z.GetInt();
-
- UTIL_TraceHull( vecStart, vecPos, vecMins, vecMaxs, MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr );
- }
-
- if ( tr.startsolid )
- {
- //NDebugOverlay::Box( vecPos, vecMins, vecMaxs, 255, 0, 0, 0, 0 );
- }
- else
- {
- //NDebugOverlay::Box( vecPos, vecMins, vecMaxs, 0, 255, 0, 0, 0 );
- bResult = true;
- }
-
- // Cache the results in case we ask again for the same spot.
- //m_vecLastCanPlantHerePos = vecPos;
- //m_bLastCanPlantHere = bResult;
-
- return bResult;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-int CNPC_Hunter::MeleeAttack1ConditionsVsEnemyInVehicle( CBaseCombatCharacter *pEnemy, float flDot )
-{
- if( !IsCorporealEnemy( GetEnemy() ) )
- return COND_NONE;
-
- // Try and trace a box to the player, and if I hit the vehicle, attack it
- Vector vecDelta = (pEnemy->WorldSpaceCenter() - WorldSpaceCenter());
- VectorNormalize( vecDelta );
- trace_t tr;
- AI_TraceHull( WorldSpaceCenter(), WorldSpaceCenter() + (vecDelta * 64), -Vector(8,8,8), Vector(8,8,8), MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
- if ( tr.fraction != 1.0 && tr.m_pEnt == pEnemy->GetVehicleEntity() )
- {
- // We're near the vehicle. Are we facing it?
- if (flDot < 0.7)
- return COND_NOT_FACING_ATTACK;
-
- return COND_CAN_MELEE_ATTACK1;
- }
-
- return COND_TOO_FAR_TO_ATTACK;
-}
-
-
-//-----------------------------------------------------------------------------
-// For innate melee attack
-//-----------------------------------------------------------------------------
-int CNPC_Hunter::MeleeAttack1Conditions ( float flDot, float flDist )
-{
- if ( !IsCorporealEnemy( GetEnemy() ) )
- return COND_NONE;
-
- if ( ( gpGlobals->curtime < m_flNextMeleeTime ) && // allow berzerk bashing if cornered
- !( m_hAttachedBusters.Count() > 0 && gpGlobals->curtime < m_fCorneredTimer ) )
- {
- return COND_NONE;
- }
-
- if ( GetEnemy()->Classify() == CLASS_PLAYER_ALLY_VITAL )
- {
- return COND_NONE;
- }
-
- if ( flDist > HUNTER_MELEE_REACH )
- {
- // Translate a hit vehicle into its passenger if found
- if ( GetEnemy() != NULL )
- {
- CBaseCombatCharacter *pCCEnemy = GetEnemy()->MyCombatCharacterPointer();
- if ( pCCEnemy != NULL && pCCEnemy->IsInAVehicle() )
- {
- return MeleeAttack1ConditionsVsEnemyInVehicle( pCCEnemy, flDot );
- }
-
-#if defined(HL2_DLL) && !defined(HL2MP)
- // If the player is holding an object, knock it down.
- if ( GetEnemy()->IsPlayer() )
- {
- CBasePlayer *pPlayer = ToBasePlayer( GetEnemy() );
-
- Assert( pPlayer != NULL );
-
- // Is the player carrying something?
- CBaseEntity *pObject = GetPlayerHeldEntity(pPlayer);
-
- if ( !pObject )
- {
- pObject = PhysCannonGetHeldEntity( pPlayer->GetActiveWeapon() );
- }
-
- if ( pObject )
- {
- float flDist = pObject->WorldSpaceCenter().DistTo( WorldSpaceCenter() );
-
- if ( flDist <= HUNTER_MELEE_REACH )
- {
- return COND_CAN_MELEE_ATTACK1;
- }
- }
- }
-#endif
- }
-
- return COND_TOO_FAR_TO_ATTACK;
- }
-
- if (flDot < 0.7)
- {
- return COND_NOT_FACING_ATTACK;
- }
-
- // Build a cube-shaped hull, the same hull that MeleeAttack is going to use.
- Vector vecMins = GetHullMins();
- Vector vecMaxs = GetHullMaxs();
- vecMins.z = vecMins.x;
- vecMaxs.z = vecMaxs.x;
-
- Vector forward;
- GetVectors( &forward, NULL, NULL );
-
- trace_t tr;
- AI_TraceHull( WorldSpaceCenter(), WorldSpaceCenter() + forward * HUNTER_MELEE_REACH, vecMins, vecMaxs, MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr );
-
- if ( tr.fraction == 1.0 || !tr.m_pEnt )
- {
- // This attack would miss completely. Trick the hunter into moving around some more.
- return COND_TOO_FAR_TO_ATTACK;
- }
-
- if ( tr.m_pEnt == GetEnemy() || tr.m_pEnt->IsNPC() || (tr.m_pEnt->m_takedamage == DAMAGE_YES && (dynamic_cast<CBreakableProp*>(tr.m_pEnt))) )
- {
- // Let the hunter swipe at his enemy if he's going to hit them.
- // Also let him swipe at NPC's that happen to be between the hunter and the enemy.
- // This makes mobs of hunters seem more rowdy since it doesn't leave guys in the back row standing around.
- // Also let him swipe at things that takedamage, under the assumptions that they can be broken.
- return COND_CAN_MELEE_ATTACK1;
- }
-
- // dvs TODO: incorporate this
- /*if ( tr.m_pEnt->IsBSPModel() )
- {
- // The trace hit something solid, but it's not the enemy. If this item is closer to the hunter than
- // the enemy is, treat this as an obstruction.
- Vector vecToEnemy = GetEnemy()->WorldSpaceCenter() - WorldSpaceCenter();
- Vector vecTrace = tr.endpos - tr.startpos;
-
- if ( vecTrace.Length2DSqr() < vecToEnemy.Length2DSqr() )
- {
- return COND_HUNTER_LOCAL_MELEE_OBSTRUCTION;
- }
- }*/
-
- if ( !tr.m_pEnt->IsWorld() && GetEnemy() && GetEnemy()->GetGroundEntity() == tr.m_pEnt )
- {
- // Try to swat whatever the player is standing on instead of acting like a dill.
- return COND_CAN_MELEE_ATTACK1;
- }
-
- // Move around some more
- return COND_TOO_FAR_TO_ATTACK;
-}
-
-
-//-----------------------------------------------------------------------------
-// For innate melee attack
-//-----------------------------------------------------------------------------
-int CNPC_Hunter::MeleeAttack2Conditions ( float flDot, float flDist )
-{
- return COND_NONE;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-bool CNPC_Hunter::IsCorporealEnemy( CBaseEntity *pEnemy )
-{
- if( !pEnemy )
- return false;
-
- // Generally speaking, don't melee attack anything the player can't see.
- if( pEnemy->IsEffectActive( EF_NODRAW ) )
- return false;
-
- // Don't flank, melee attack striderbusters.
- if ( IsStriderBuster( pEnemy ) )
- return false;
-
- return true;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-int CNPC_Hunter::RangeAttack1Conditions( float flDot, float flDist )
-{
- return COND_NONE;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-int CNPC_Hunter::RangeAttack2Conditions( float flDot, float flDist )
-{
- bool bIsBuster = IsStriderBuster( GetEnemy() );
- bool bIsPerfectBullseye = ( GetEnemy() && dynamic_cast<CNPC_Bullseye *>(GetEnemy()) && ((CNPC_Bullseye *)GetEnemy())->UsePerfectAccuracy() );
-
- if ( !bIsPerfectBullseye && !bIsBuster && !hunter_flechette_test.GetBool() && ( gpGlobals->curtime < m_flNextRangeAttack2Time ) )
- {
- return COND_NONE;
- }
-
- if ( m_bDisableShooting )
- {
- return COND_NONE;
- }
-
- if ( !HasCondition( COND_SEE_ENEMY ) )
- {
- return COND_NONE;
- }
-
- float flMaxFlechetteRange = hunter_flechette_max_range.GetFloat();
-
- if ( IsUsingSiegeTargets() )
- {
- flMaxFlechetteRange *= HUNTER_SIEGE_MAX_DIST_MODIFIER;
- }
-
- if ( !bIsBuster && ( flDist > flMaxFlechetteRange ) )
- {
- return COND_TOO_FAR_TO_ATTACK;
- }
- else if ( !bIsBuster && ( !GetEnemy() || !GetEnemy()->ClassMatches( "npc_bullseye" ) ) && flDist < hunter_flechette_min_range.GetFloat() )
- {
- return COND_TOO_CLOSE_TO_ATTACK;
- }
- else if ( flDot < HUNTER_FACING_DOT )
- {
- return COND_NOT_FACING_ATTACK;
- }
-
- if ( !bIsBuster && !m_bEnableUnplantedShooting && !hunter_flechette_test.GetBool() && !CanPlantHere( GetAbsOrigin() ) )
- {
- return COND_HUNTER_CANT_PLANT;
- }
-
- return COND_CAN_RANGE_ATTACK2;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-bool CNPC_Hunter::WeaponLOSCondition(const Vector &ownerPos, const Vector &targetPos, bool bSetConditions)
-{
- CBaseEntity *pTargetEnt;
-
- pTargetEnt = GetEnemy();
-
- trace_t tr;
- Vector vFrom = ownerPos + GetViewOffset();
- AI_TraceLine( vFrom, targetPos, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
-
- if ( ( pTargetEnt && tr.m_pEnt == pTargetEnt) || tr.fraction == 1.0 || CanShootThrough( tr, targetPos ) )
- {
- static Vector vMins( -2.0, -2.0, -2.0 );
- static Vector vMaxs( -vMins);
- // Hit the enemy, or hit nothing (traced all the way to a nonsolid enemy like a bullseye)
- AI_TraceHull( vFrom - Vector( 0, 0, 18 ), targetPos, vMins, vMaxs, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr);
-
- if ( ( pTargetEnt && tr.m_pEnt == pTargetEnt) || tr.fraction == 1.0 || CanShootThrough( tr, targetPos ) )
- {
- if ( hunter_show_weapon_los_condition.GetBool() )
- {
- NDebugOverlay::Line( vFrom, targetPos, 255, 0, 255, false, 0.1 );
- NDebugOverlay::Line( vFrom - Vector( 0, 0, 18 ), targetPos, 0, 0, 255, false, 0.1 );
- }
- return true;
- }
- }
- else if ( bSetConditions )
- {
- SetCondition( COND_WEAPON_SIGHT_OCCLUDED );
- SetEnemyOccluder( tr.m_pEnt );
- }
-
- return false;
-}
-
-//-----------------------------------------------------------------------------
-// Look in front and see if the claw hit anything.
-//
-// Input : flDist distance to trace
-// iDamage damage to do if attack hits
-// vecViewPunch camera punch (if attack hits player)
-// vecVelocityPunch velocity punch (if attack hits player)
-//
-// Output : The entity hit by claws. NULL if nothing.
-//-----------------------------------------------------------------------------
-CBaseEntity *CNPC_Hunter::MeleeAttack( float flDist, int iDamage, QAngle &qaViewPunch, Vector &vecVelocityPunch, int BloodOrigin )
-{
- // Added test because claw attack anim sometimes used when for cases other than melee
- if ( GetEnemy() )
- {
- trace_t tr;
- AI_TraceHull( WorldSpaceCenter(), GetEnemy()->WorldSpaceCenter(), -Vector(8,8,8), Vector(8,8,8), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr );
-
- if ( tr.fraction < 1.0f )
- return NULL;
- }
-
- //
- // Trace out a cubic section of our hull and see what we hit.
- //
- Vector vecMins = GetHullMins();
- Vector vecMaxs = GetHullMaxs();
- vecMins.z = vecMins.x;
- vecMaxs.z = vecMaxs.x;
-
- CBaseEntity *pHurt = CheckTraceHullAttack( flDist, vecMins, vecMaxs, iDamage, DMG_SLASH );
-
- if ( pHurt )
- {
- EmitSound( "NPC_Hunter.MeleeHit" );
- EmitSound( "NPC_Hunter.TackleHit" );
-
- CBasePlayer *pPlayer = ToBasePlayer( pHurt );
-
- if ( pPlayer != NULL && !(pPlayer->GetFlags() & FL_GODMODE ) )
- {
- pPlayer->ViewPunch( qaViewPunch );
- pPlayer->VelocityPunch( vecVelocityPunch );
-
- // Shake the screen
- UTIL_ScreenShake( pPlayer->GetAbsOrigin(), 100.0, 1.5, 1.0, 2, SHAKE_START );
-
- // Red damage indicator
- color32 red = { 128, 0, 0, 128 };
- UTIL_ScreenFade( pPlayer, red, 1.0f, 0.1f, FFADE_IN );
-
- /*if ( UTIL_ShouldShowBlood( pPlayer->BloodColor() ) )
- {
- // Spray some of the player's blood on the hunter.
- trace_t tr;
-
- Vector vecHunterEyePos; // = EyePosition();
- QAngle angDiscard;
- GetBonePosition( LookupBone( "MiniStrider.top_eye_bone" ), vecHunterEyePos, angDiscard );
-
- Vector vecPlayerEyePos = pPlayer->EyePosition();
-
- Vector vecDir = vecHunterEyePos - vecPlayerEyePos;
- float flLen = VectorNormalize( vecDir );
-
- Vector vecStart = vecPlayerEyePos - ( vecDir * 64 );
- Vector vecEnd = vecPlayerEyePos + ( vecDir * ( flLen + 64 ) );
-
- NDebugOverlay::HorzArrow( vecStart, vecEnd, 16, 255, 255, 0, 255, false, 10 );
-
- UTIL_TraceLine( vecStart, vecEnd, MASK_SHOT, pPlayer, COLLISION_GROUP_NONE, &tr );
-
- if ( tr.m_pEnt )
- {
- Msg( "Hit %s!!!\n", tr.m_pEnt->GetDebugName() );
- UTIL_DecalTrace( &tr, "Blood" );
- }
- }*/
- }
- else if ( !pPlayer )
- {
- if ( IsMovablePhysicsObject( pHurt ) )
- {
- // If it's a vphysics object that's too heavy, crash into it too.
- IPhysicsObject *pPhysics = pHurt->VPhysicsGetObject();
- if ( pPhysics )
- {
- // If the object is being held by the player, break it or make them drop it.
- if ( pPhysics->GetGameFlags() & FVPHYSICS_PLAYER_HELD )
- {
- // If it's breakable, break it.
- if ( pHurt->m_takedamage == DAMAGE_YES )
- {
- CBreakableProp *pBreak = dynamic_cast<CBreakableProp*>(pHurt);
- if ( pBreak )
- {
- CTakeDamageInfo info( this, this, 20, DMG_SLASH );
- pBreak->Break( this, info );
- }
- }
- }
- }
- }
-
- if ( UTIL_ShouldShowBlood(pHurt->BloodColor()) )
- {
- // Hit an NPC. Bleed them!
- Vector vecBloodPos;
-
- switch ( BloodOrigin )
- {
- case HUNTER_BLOOD_LEFT_FOOT:
- {
- if ( GetAttachment( "blood_left", vecBloodPos ) )
- {
- SpawnBlood( vecBloodPos, g_vecAttackDir, pHurt->BloodColor(), MIN( iDamage, 30 ) );
- }
-
- break;
- }
- }
- }
- }
- }
- else
- {
- // TODO:
- //AttackMissSound();
- }
-
- m_flNextMeleeTime = gpGlobals->curtime + hunter_melee_delay.GetFloat();
-
- return pHurt;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-bool CNPC_Hunter::TestShootPosition(const Vector &vecShootPos, const Vector &targetPos )
-{
- if ( !CanPlantHere(vecShootPos ) )
- {
- return false;
- }
-
- return BaseClass::TestShootPosition( vecShootPos, targetPos );
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-Vector CNPC_Hunter::Weapon_ShootPosition( )
-{
- matrix3x4_t gunMatrix;
- GetAttachment( gm_nTopGunAttachment, gunMatrix );
-
- Vector vecShootPos;
- MatrixGetColumn( gunMatrix, 3, vecShootPos );
-
- return vecShootPos;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::MakeTracer( const Vector &vecTracerSrc, const trace_t &tr, int iTracerType )
-{
- float flTracerDist;
- Vector vecDir;
- Vector vecEndPos;
-
- vecDir = tr.endpos - vecTracerSrc;
-
- flTracerDist = VectorNormalize( vecDir );
-
- int nAttachment = LookupAttachment( "MiniGun" );
-
- UTIL_Tracer( vecTracerSrc, tr.endpos, nAttachment, TRACER_FLAG_USEATTACHMENT, 5000, true, "HunterTracer" );
-}
-
-
-//-----------------------------------------------------------------------------
-// Trace didn't hit the intended target, but should the hunter
-// shoot anyway? We use this to get the hunter to destroy
-// breakables that are between him and his target.
-//-----------------------------------------------------------------------------
-bool CNPC_Hunter::CanShootThrough( const trace_t &tr, const Vector &vecTarget )
-{
- if ( !tr.m_pEnt )
- {
- return false;
- }
-
- if ( !tr.m_pEnt->GetHealth() )
- {
- return false;
- }
-
- // Don't try to shoot through allies.
- CAI_BaseNPC *pNPC = tr.m_pEnt->MyNPCPointer();
- if ( pNPC && ( IRelationType( pNPC ) == D_LI ) )
- {
- return false;
- }
-
- // Would a trace ignoring this entity continue to the target?
- trace_t continuedTrace;
- AI_TraceLine( tr.endpos, vecTarget, MASK_SHOT, tr.m_pEnt, COLLISION_GROUP_NONE, &continuedTrace );
-
- if ( continuedTrace.fraction != 1.0 )
- {
- if ( continuedTrace.m_pEnt != GetEnemy() )
- {
- return false;
- }
- }
-
- return true;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-int CNPC_Hunter::GetSoundInterests()
-{
- return SOUND_WORLD | SOUND_COMBAT | SOUND_PLAYER | SOUND_DANGER | SOUND_PHYSICS_DANGER | SOUND_PLAYER_VEHICLE | SOUND_BULLET_IMPACT | SOUND_MOVE_AWAY;
-}
-
-//-----------------------------------------------------------------------------
-// Tells us whether the Hunter is acting in a large, outdoor map,
-// currently only ep2_outland_12. This allows us to create logic
-// branches here in the AI code so that we can make choices that
-// tailor behavior to larger and smaller maps.
-//-----------------------------------------------------------------------------
-bool CNPC_Hunter::IsInLargeOutdoorMap()
-{
- return m_bInLargeOutdoorMap;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::AlertSound()
-{
- EmitSound( "NPC_Hunter.Alert" );
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::PainSound( const CTakeDamageInfo &info )
-{
- if ( gpGlobals->curtime > m_flNextDamageTime )
- {
- EmitSound( "NPC_Hunter.Pain" );
- m_flNextDamageTime = gpGlobals->curtime + random->RandomFloat( 0.5, 1.2 );
- }
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::DeathSound( const CTakeDamageInfo &info )
-{
- EmitSound( "NPC_Hunter.Death" );
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::TraceAttack( const CTakeDamageInfo &inputInfo, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator )
-{
- CTakeDamageInfo info = inputInfo;
-
- // Even though the damage might not hurt us, we want to react to it
- // if it's from the player.
- if ( info.GetAttacker()->IsPlayer() )
- {
- if ( !HasMemory( bits_MEMORY_PROVOKED ) )
- {
- GetEnemies()->ClearMemory( info.GetAttacker() );
- Remember( bits_MEMORY_PROVOKED );
- SetCondition( COND_LIGHT_DAMAGE );
- }
- }
-
- // HUnters have special resisitance to some types of damage.
- if ( ( info.GetDamageType() & DMG_BULLET ) ||
- ( info.GetDamageType() & DMG_BUCKSHOT ) ||
- ( info.GetDamageType() & DMG_CLUB ) ||
- ( info.GetDamageType() & DMG_NEVERGIB ) )
- {
- float flScale = 1.0;
-
- if ( info.GetDamageType() & DMG_BUCKSHOT )
- {
- flScale = sk_hunter_buckshot_damage_scale.GetFloat();
- }
- else if ( ( info.GetDamageType() & DMG_BULLET ) || ( info.GetDamageType() & DMG_NEVERGIB ) )
- {
- // Hunters resist most bullet damage, but they are actually vulnerable to .357 rounds,
- // since players regard that weapon as one of the game's truly powerful weapons.
- if( info.GetAmmoType() == GetAmmoDef()->Index("357") )
- {
- flScale = 1.16f;
- }
- else
- {
- flScale = sk_hunter_bullet_damage_scale.GetFloat();
- }
- }
-
- if ( GetActivity() == ACT_HUNTER_CHARGE_RUN )
- {
- flScale *= sk_hunter_charge_damage_scale.GetFloat();
- }
-
- if ( flScale != 0 )
- {
- float flDamage = info.GetDamage() * flScale;
- info.SetDamage( flDamage );
- }
-
- QAngle vecAngles;
- VectorAngles( ptr->plane.normal, vecAngles );
- DispatchParticleEffect( "blood_impact_synth_01", ptr->endpos, vecAngles );
- DispatchParticleEffect( "blood_impact_synth_01_arc_parent", PATTACH_POINT_FOLLOW, this, gm_nHeadCenterAttachment );
- }
-
- BaseClass::TraceAttack( info, vecDir, ptr, pAccumulator );
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-const impactdamagetable_t &CNPC_Hunter::GetPhysicsImpactDamageTable()
-{
- return s_HunterImpactDamageTable;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::PhysicsDamageEffect( const Vector &vecPos, const Vector &vecDir )
-{
- CEffectData data;
- data.m_vOrigin = vecPos;
- data.m_vNormal = vecDir;
- DispatchEffect( "HunterDamage", data );
-
- if ( random->RandomInt( 0, 1 ) == 0 )
- {
- CBaseEntity *pTrail = CreateEntityByName( "sparktrail" );
- pTrail->SetOwnerEntity( this );
- pTrail->Spawn();
- }
-}
-
-
-//-----------------------------------------------------------------------------
-// We were hit by a strider buster. Do the tesla effect on our hitboxes.
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::TeslaThink()
-{
- CEffectData data;
- data.m_nEntIndex = entindex();
- data.m_flMagnitude = 3;
- data.m_flScale = 0.5f;
- DispatchEffect( "TeslaHitboxes", data );
- EmitSound( "RagdollBoogie.Zap" );
-
- if ( gpGlobals->curtime < m_flTeslaStopTime )
- {
- SetContextThink( &CNPC_Hunter::TeslaThink, gpGlobals->curtime + random->RandomFloat( 0.1f, 0.3f ), HUNTER_ZAP_THINK );
- }
-}
-
-
-//-----------------------------------------------------------------------------
-// Our health is low. Show damage effects.
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::BleedThink()
-{
- // Spurt blood from random points on the hunter's head.
- Vector vecOrigin;
- QAngle angDir;
- GetAttachment( gm_nHeadCenterAttachment, vecOrigin, angDir );
-
- Vector vecDir = RandomVector( -1, 1 );
- VectorNormalize( vecDir );
- VectorAngles( vecDir, Vector( 0, 0, 1 ), angDir );
-
- vecDir *= gm_flHeadRadius;
- DispatchParticleEffect( "blood_spurt_synth_01", vecOrigin + vecDir, angDir );
-
- SetNextThink( gpGlobals->curtime + random->RandomFloat( 0.6, 1.5 ), HUNTER_BLEED_THINK );
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-bool CNPC_Hunter::IsHeavyDamage( const CTakeDamageInfo &info )
-{
- if ( info.GetDamage() < 45 )
- {
- return false;
- }
-
- if ( info.GetDamage() < 180 )
- {
- if ( !m_HeavyDamageDelay.Expired() || !BaseClass::IsHeavyDamage( info ) )
- {
- return false;
- }
- }
-
- m_HeavyDamageDelay.Set( 15, 25 );
- return true;
-
-}
-
-
-//-----------------------------------------------------------------------------
-// We've taken some damage. Maybe we should flinch because of it.
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::ConsiderFlinching( const CTakeDamageInfo &info )
-{
- if ( !m_FlinchTimer.Expired() )
- {
- // Someone is whaling on us. Push out the timer so we don't keep flinching.
- m_FlinchTimer.Set( random->RandomFloat( 0.3 ) );
- return;
- }
-
- if ( GetState() == NPC_STATE_SCRIPT )
- {
- return;
- }
-
- Activity eGesture = ACT_HUNTER_FLINCH_N;
-
- Vector forward;
- GetVectors( &forward, NULL, NULL );
-
- Vector vecForceDir = info.GetDamageForce();
- VectorNormalize( vecForceDir );
-
- float flDot = DotProduct( forward, vecForceDir );
-
- if ( flDot > 0.707 )
- {
- // flinch forward
- eGesture = ACT_HUNTER_FLINCH_N;
- }
- else if ( flDot < -0.707 )
- {
- // flinch back
- eGesture = ACT_HUNTER_FLINCH_S;
- }
- else
- {
- // flinch left or right
- Vector cross = CrossProduct( forward, vecForceDir );
-
- if ( cross.z > 0 )
- {
- eGesture = ACT_HUNTER_FLINCH_W;
- }
- else
- {
- eGesture = ACT_HUNTER_FLINCH_E;
- }
- }
-
- if ( !IsPlayingGesture( eGesture ) )
- {
- RestartGesture( eGesture );
- m_FlinchTimer.Set( random->RandomFloat( 0.3, 1.0 ) );
- }
-}
-
-
-//-----------------------------------------------------------------------------
-// This is done from a think function because when the hunter is killed,
-// the physics code puts the vehicle's pre-collision velocity back so the jostle
-// is basically discared.
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::JostleVehicleThink()
-{
- CBaseEntity *pInflictor = m_hHitByVehicle;
- if ( !pInflictor )
- return;
-
- Vector vecVelDir = pInflictor->GetSmoothedVelocity();
- float flSpeed = VectorNormalize( vecVelDir );
- Vector vecForce = CrossProduct( vecVelDir, Vector( 0, 0, 1 ) );
- if ( DotProduct( vecForce, GetAbsOrigin() ) < DotProduct( vecForce, pInflictor->GetAbsOrigin() ) )
- {
- // We're to the left of the vehicle that's hitting us.
- vecForce *= -1;
- }
-
- VectorNormalize( vecForce );
- vecForce.z = 1.0;
-
- float flForceScale = RemapValClamped( flSpeed, hunter_jostle_car_min_speed.GetFloat(), hunter_jostle_car_max_speed.GetFloat(), 50.0f, 150.0f );
-
- vecForce *= ( flForceScale * pInflictor->VPhysicsGetObject()->GetMass() );
-
- pInflictor->VPhysicsGetObject()->ApplyForceOffset( vecForce, WorldSpaceCenter() );
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-int CNPC_Hunter::OnTakeDamage( const CTakeDamageInfo &info )
-{
- CTakeDamageInfo myInfo = info;
-
- if ( ( info.GetDamageType() & DMG_CRUSH ) && !( info.GetDamageType() & DMG_VEHICLE ) )
- {
- // Don't take damage from physics objects that weren't thrown by the player.
- CBaseEntity *pInflictor = info.GetInflictor();
-
- IPhysicsObject *pObj = pInflictor->VPhysicsGetObject();
- //Assert( pObj );
-
- if ( !pObj || !pInflictor->HasPhysicsAttacker( 4.0 ) )
- {
- myInfo.SetDamage( 0 );
- }
- else
- {
- // Physics objects that have flechettes stuck in them spoof
- // a flechette hitting us so we dissolve when killed and award
- // the achievement of killing a hunter with its flechettes.
- CUtlVector<CBaseEntity *> children;
- GetAllChildren( pInflictor, children );
- for (int i = 0; i < children.Count(); i++ )
- {
- CBaseEntity *pent = children.Element( i );
- if ( dynamic_cast<CHunterFlechette *>( pent ) )
- {
- myInfo.SetInflictor( pent );
- myInfo.SetDamageType( myInfo.GetDamageType() | DMG_DISSOLVE );
- }
- }
- }
- }
-
- return BaseClass::OnTakeDamage( myInfo );
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-int CNPC_Hunter::OnTakeDamage_Alive( const CTakeDamageInfo &info )
-{
- CTakeDamageInfo myInfo = info;
-
- // don't take damage from my own weapons!!!
- // Exception: I "own" a magnade if it's glued to me.
- CBaseEntity *pInflictor = info.GetInflictor();
- CBaseEntity *pAttacker = info.GetAttacker();
- if ( pInflictor )
- {
- if ( IsStriderBuster( pInflictor ) )
- {
- // Get a tesla effect on our hitboxes for a little while.
- SetContextThink( &CNPC_Hunter::TeslaThink, gpGlobals->curtime, HUNTER_ZAP_THINK );
- m_flTeslaStopTime = gpGlobals->curtime + 2.0f;
-
- myInfo.SetDamage( sk_hunter_dmg_from_striderbuster.GetFloat() ) ;
-
- SetCondition( COND_HUNTER_STAGGERED );
- }
- else if ( pInflictor->ClassMatches( GetClassname() ) && !( info.GetDamageType() == DMG_GENERIC ) )
- {
- return 0;
- }
- else if ( pInflictor->ClassMatches( "hunter_flechette" ) )
- {
- if ( !( ( CHunterFlechette *)pInflictor )->WasThrownBack() )
- {
- // Flechettes only hurt us if they were thrown back at us by the player. This prevents
- // hunters from hurting themselves when they walk into their own flechette clusters.
- return 0;
- }
- }
- }
-
- if ( m_EscortBehavior.GetFollowTarget() && m_EscortBehavior.GetFollowTarget() == pAttacker )
- {
- return 0;
- }
-
- bool bHitByUnoccupiedCar = false;
- if ( ( ( info.GetDamageType() & DMG_CRUSH ) && ( pAttacker && pAttacker->IsPlayer() ) ) ||
- ( info.GetDamageType() & DMG_VEHICLE ) )
- {
- // myInfo, not info! it may have been modified above.
- float flDamage = myInfo.GetDamage();
- if ( flDamage < HUNTER_MIN_PHYSICS_DAMAGE )
- {
- //DevMsg( "hunter: <<<< ZERO PHYSICS DAMAGE: %f\n", flDamage );
- myInfo.SetDamage( 0 );
- }
- else
- {
- CBaseEntity *pInflictor = info.GetInflictor();
- if ( ( info.GetDamageType() & DMG_VEHICLE ) ||
- ( pInflictor && pInflictor->GetServerVehicle() &&
- ( ( bHitByUnoccupiedCar = ( dynamic_cast<CPropVehicleDriveable *>(pInflictor) && static_cast<CPropVehicleDriveable *>(pInflictor)->GetDriver() == NULL ) ) == false ) ) )
- {
- // Adjust the damage from vehicles.
- flDamage *= sk_hunter_vehicle_damage_scale.GetFloat();
- myInfo.SetDamage( flDamage );
-
- // Apply a force to jostle the vehicle that hit us.
- // Pick a force direction based on which side we're on relative to the vehicle's motion.
- Vector vecVelDir = pInflictor->GetSmoothedVelocity();
- if ( vecVelDir.Length() >= hunter_jostle_car_min_speed.GetFloat() )
- {
- EmitSound( "NPC_Hunter.HitByVehicle" );
- m_hHitByVehicle = pInflictor;
- SetContextThink( &CNPC_Hunter::JostleVehicleThink, gpGlobals->curtime, HUNTER_JOSTLE_VEHICLE_THINK );
- }
- }
-
- if ( !bHitByUnoccupiedCar )
- {
- SetCondition( COND_HUNTER_STAGGERED );
- }
- }
-
- //DevMsg( "hunter: >>>> PHYSICS DAMAGE: %f (was %f)\n", flDamage, info.GetDamage() );
- }
-
- // Show damage effects if we actually took damage.
- if ( ( myInfo.GetDamageType() & ( DMG_CRUSH | DMG_BLAST ) ) && ( myInfo.GetDamage() > 0 ) )
- {
- if ( !bHitByUnoccupiedCar )
- SetCondition( COND_HUNTER_STAGGERED );
- }
-
- if ( HasCondition( COND_HUNTER_STAGGERED ) )
- {
- // Throw a bunch of gibs out
- Vector vecForceDir = -myInfo.GetDamageForce();
- VectorNormalize( vecForceDir );
- PhysicsDamageEffect( myInfo.GetDamagePosition(), vecForceDir );
-
- // Stagger away from the direction the damage came from.
- m_vecStaggerDir = myInfo.GetDamageForce();
- VectorNormalize( m_vecStaggerDir );
- }
-
- // Take less damage from citizens and Alyx, otherwise hunters go down too easily.
- float flScale = 1.0;
-
- if ( pAttacker &&
- ( ( pAttacker->Classify() == CLASS_CITIZEN_REBEL ) ||
- ( pAttacker->Classify() == CLASS_PLAYER_ALLY ) ||
- ( pAttacker->Classify() == CLASS_PLAYER_ALLY_VITAL ) ) )
- {
- flScale *= sk_hunter_citizen_damage_scale.GetFloat();
- }
-
- if ( flScale != 0 )
- {
- // We're taking a nonzero amount of damage.
-
- // If we're not staggering, consider flinching!
- if ( !HasCondition( COND_HUNTER_STAGGERED ) )
- {
- ConsiderFlinching( info );
- }
-
- if( pAttacker && pAttacker->IsPlayer() )
- {
- // This block of code will distract the Hunter back to the player if the
- // player does harm to the Hunter but is not the Hunter's current enemy.
- // This is achieved by updating the Hunter's enemy memory of the player and
- // making the Hunter's current enemy invalid for a short time.
- if( !GetEnemy() || !GetEnemy()->IsPlayer() )
- {
- UpdateEnemyMemory( pAttacker, pAttacker->GetAbsOrigin(), this );
-
- if( GetEnemy() )
- {
- // Gotta forget about this person for a little bit.
- GetEnemies()->SetTimeValidEnemy( GetEnemy(), gpGlobals->curtime + HUNTER_IGNORE_ENEMY_TIME );
- }
- }
- }
-
- float flDamage = myInfo.GetDamage() * flScale;
- myInfo.SetDamage( flDamage );
- }
-
- int nRet = BaseClass::OnTakeDamage_Alive( myInfo );
-
- m_EscortBehavior.OnDamage( myInfo );
-
- // Spark at 30% health.
- if ( !IsBleeding() && ( GetHealth() <= sk_hunter_health.GetInt() * 0.3 ) )
- {
- StartBleeding();
- }
-
- if ( IsUsingSiegeTargets() && info.GetAttacker() != NULL && info.GetAttacker()->IsPlayer() )
- {
- // Defend myself. Try to siege attack immediately.
- m_flTimeNextSiegeTargetAttack = gpGlobals->curtime;
- }
-
- return nRet;
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::Event_Killed( const CTakeDamageInfo &info )
-{
- // Remember the killing blow to make decisions about ragdolling.
- m_nKillingDamageType = info.GetDamageType();
-
- if ( m_EscortBehavior.GetFollowTarget() )
- {
- if ( AIGetNumFollowers( m_EscortBehavior.GetFollowTarget(), m_iClassname ) == 1 )
- {
- m_EscortBehavior.GetEscortTarget()->AlertSound();
- if ( info.GetAttacker() && info.GetAttacker()->IsPlayer() )
- {
- m_EscortBehavior.GetEscortTarget()->UpdateEnemyMemory( UTIL_GetLocalPlayer(), UTIL_GetLocalPlayer()->GetAbsOrigin(), this );
- }
- }
- }
-
- if ( info.GetDamageType() & DMG_VEHICLE )
- {
- bool bWasRunDown = false;
- int iRundownCounter = 0;
- if ( GetSquad() )
- {
- if ( !m_IgnoreVehicleTimer.Expired() )
- {
- GetSquad()->GetSquadData( HUNTER_RUNDOWN_SQUADDATA, &iRundownCounter );
- GetSquad()->SetSquadData( HUNTER_RUNDOWN_SQUADDATA, iRundownCounter + 1 );
- bWasRunDown = true;
- }
- }
-
- if ( hunter_dodge_debug.GetBool() )
- Msg( "Hunter %d was%s run down\n", entindex(), ( bWasRunDown ) ? "" : " not" );
-
- // Death by vehicle! Decrement the hunters to run over counter.
- // When the counter reaches zero hunters will start dodging.
- if ( GlobalEntity_GetCounter( s_iszHuntersToRunOver ) > 0 )
- {
- GlobalEntity_AddToCounter( s_iszHuntersToRunOver, -1 );
- }
- }
-
- // Stop all our thinks
- SetContextThink( NULL, 0, HUNTER_BLEED_THINK );
-
- StopParticleEffects( this );
-
- BaseClass::Event_Killed( info );
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::StartBleeding()
-{
- // Do this even if we're already bleeding (see OnRestore).
- m_bIsBleeding = true;
-
- // Start gushing blood from our... anus or something.
- DispatchParticleEffect( "blood_drip_synth_01", PATTACH_POINT_FOLLOW, this, gm_nHeadBottomAttachment );
-
- // Emit spurts of our blood
- SetContextThink( &CNPC_Hunter::BleedThink, gpGlobals->curtime + 0.1, HUNTER_BLEED_THINK );
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-float CNPC_Hunter::MaxYawSpeed()
-{
- if ( IsStriderBuster( GetEnemy() ) )
- {
- return 60;
- }
-
- if ( GetActivity() == ACT_HUNTER_ANGRY )
- return 0;
-
- if ( GetActivity() == ACT_HUNTER_CHARGE_RUN )
- return 5;
-
- if ( GetActivity() == ACT_HUNTER_IDLE_PLANTED )
- return 0;
-
- if ( GetActivity() == ACT_HUNTER_RANGE_ATTACK2_UNPLANTED )
- return 180;
-
- switch ( GetActivity() )
- {
- case ACT_RANGE_ATTACK2:
- {
- return 0;
- }
-
- case ACT_90_LEFT:
- case ACT_90_RIGHT:
- {
- return 45;
- }
-
- case ACT_TURN_LEFT:
- case ACT_TURN_RIGHT:
- {
- return 45;
- }
-
- case ACT_WALK:
- {
- return 25;
- }
-
- default:
- {
- return 35;
- }
- }
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-bool CNPC_Hunter::IsJumpLegal(const Vector &startPos, const Vector &apex, const Vector &endPos) const
-{
- float MAX_JUMP_RISE = 220.0f;
- float MAX_JUMP_DISTANCE = 512.0f;
- float MAX_JUMP_DROP = 384.0f;
-
- trace_t tr;
- UTIL_TraceHull( startPos, startPos, GetHullMins(), GetHullMaxs(), MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr );
- if ( tr.startsolid )
- {
- // Trying to start a jump in solid! Consider checking for this in CAI_MoveProbe::JumpMoveLimit.
- Assert( 0 );
- return false;
- }
-
- if ( BaseClass::IsJumpLegal( startPos, apex, endPos, MAX_JUMP_RISE, MAX_JUMP_DROP, MAX_JUMP_DISTANCE ) )
- {
- return true;
- }
- return false;
-}
-
-
-//-----------------------------------------------------------------------------
-// Let the probe know I can run through small debris
-// Stolen shamelessly from the Antlion Guard
-//-----------------------------------------------------------------------------
-bool CNPC_Hunter::ShouldProbeCollideAgainstEntity( CBaseEntity *pEntity )
-{
- if ( s_iszPhysPropClassname != pEntity->m_iClassname )
- return BaseClass::ShouldProbeCollideAgainstEntity( pEntity );
-
- if ( pEntity->GetMoveType() == MOVETYPE_VPHYSICS )
- {
- IPhysicsObject *pPhysObj = pEntity->VPhysicsGetObject();
-
- if( pPhysObj && pPhysObj->GetMass() <= 500.0f )
- {
- return false;
- }
- }
-
- return BaseClass::ShouldProbeCollideAgainstEntity( pEntity );
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::DoMuzzleFlash( int nAttachment )
-{
- BaseClass::DoMuzzleFlash();
-
- DispatchParticleEffect( "hunter_muzzle_flash", PATTACH_POINT_FOLLOW, this, nAttachment );
-
- // Dispatch the elight
- CEffectData data;
- data.m_nAttachmentIndex = nAttachment;
- data.m_nEntIndex = entindex();
- DispatchEffect( "HunterMuzzleFlash", data );
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-int CNPC_Hunter::CountRangedAttackers()
-{
- CBaseEntity *pEnemy = GetEnemy();
- if ( !pEnemy )
- {
- return 0;
- }
-
- int nAttackers = 0;
- for ( int i = 0; i < g_Hunters.Count(); i++ )
- {
- CNPC_Hunter *pOtherHunter = g_Hunters[i];
- if ( pOtherHunter->GetEnemy() == pEnemy )
- {
- if ( pOtherHunter->IsCurSchedule( SCHED_HUNTER_RANGE_ATTACK2 ) )
- {
- nAttackers++;
- }
- }
- }
- return nAttackers;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::DelayRangedAttackers( float minDelay, float maxDelay, bool bForced )
-{
- float delayMultiplier = ( g_pGameRules->IsSkillLevel( SKILL_EASY ) ) ? 1.25 : 1.0;
- if ( !m_bEnableSquadShootDelay && !bForced )
- {
- m_flNextRangeAttack2Time = gpGlobals->curtime + random->RandomFloat( minDelay, maxDelay ) * delayMultiplier;
- return;
- }
-
- CBaseEntity *pEnemy = GetEnemy();
- for ( int i = 0; i < g_Hunters.Count(); i++ )
- {
- CNPC_Hunter *pOtherHunter = g_Hunters[i];
- if ( pOtherHunter->GetEnemy() == pEnemy )
- {
- float nextTime = gpGlobals->curtime + random->RandomFloat( minDelay, maxDelay ) * delayMultiplier;
- if ( nextTime > pOtherHunter->m_flNextRangeAttack2Time )
- pOtherHunter->m_flNextRangeAttack2Time = nextTime;
- }
- }
-}
-
-
-//-----------------------------------------------------------------------------
-// Given a target to shoot at, decide where to aim.
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::GetShootDir( Vector &vecDir, const Vector &vecSrc, CBaseEntity *pTargetEntity, bool bStriderBuster, int nShotNum, bool bSingleShot )
-{
- //RestartGesture( ACT_HUNTER_GESTURE_SHOOT );
-
- EmitSound( "NPC_Hunter.FlechetteShoot" );
-
- Vector vecBodyTarget;
-
- if( pTargetEntity->Classify() == CLASS_PLAYER_ALLY_VITAL )
- {
- // Shooting at Alyx, most likely (in EP2). The attack is designed to displace
- // her, not necessarily actually harm her. So shoot at the area around her feet.
- vecBodyTarget = pTargetEntity->GetAbsOrigin();
- }
- else
- {
- vecBodyTarget = pTargetEntity->BodyTarget( vecSrc );
- }
-
- Vector vecTarget = vecBodyTarget;
-
- Vector vecDelta = pTargetEntity->GetAbsOrigin() - GetAbsOrigin();
- float flDist = vecDelta.Length();
-
- if ( !bStriderBuster )
- {
- // If we're not firing at a strider buster, miss in an entertaining way for the
- // first three shots of each volley.
- if ( ( nShotNum < 3 ) && ( flDist > 200 ) )
- {
- Vector vecTargetForward;
- Vector vecTargetRight;
- pTargetEntity->GetVectors( &vecTargetForward, &vecTargetRight, NULL );
-
- Vector vecForward;
- GetVectors( &vecForward, NULL, NULL );
-
- float flDot = DotProduct( vecTargetForward, vecForward );
-
- if ( flDot < -0.8f )
- {
- // Our target is facing us, shoot the ground between us.
- float flPerc = 0.7 + ( 0.1 * nShotNum );
- vecTarget = GetAbsOrigin() + ( flPerc * ( pTargetEntity->GetAbsOrigin() - GetAbsOrigin() ) );
- }
- else if ( flDot > 0.8f )
- {
- // Our target is facing away from us, shoot to the left or right.
- Vector vecMissDir = vecTargetRight;
- if ( m_bMissLeft )
- {
- vecMissDir *= -1.0f;
- }
-
- vecTarget = pTargetEntity->EyePosition() + ( 36.0f * ( 3 - nShotNum ) ) * vecMissDir;
- }
- else
- {
- // Our target is facing vaguely perpendicular to us, shoot across their view.
- vecTarget = pTargetEntity->EyePosition() + ( 36.0f * ( 3 - nShotNum ) ) * vecTargetForward;
- }
- }
- // If we can't see them, shoot where we last saw them.
- else if ( !HasCondition( COND_SEE_ENEMY ) )
- {
- Vector vecDelta = vecTarget - pTargetEntity->GetAbsOrigin();
- vecTarget = m_vecEnemyLastSeen + vecDelta;
- }
- }
- else
- {
- // If we're firing at a striderbuster, lead it.
- float flSpeed = hunter_flechette_speed.GetFloat();
- if ( !flSpeed )
- {
- flSpeed = 2500.0f;
- }
-
- flSpeed *= 1.5;
-
- float flDeltaTime = flDist / flSpeed;
- vecTarget = vecTarget + flDeltaTime * pTargetEntity->GetSmoothedVelocity();
- }
-
- vecDir = vecTarget - vecSrc;
- VectorNormalize( vecDir );
-}
-
-
-//-----------------------------------------------------------------------------
-// Ensures that we don't exceed our pitch/yaw limits when shooting flechettes.
-// Returns true if we had to clamp, false if not.
-//-----------------------------------------------------------------------------
-bool CNPC_Hunter::ClampShootDir( Vector &vecDir )
-{
- Vector vecDir2D = vecDir;
- vecDir2D.z = 0;
-
- Vector vecForward;
- GetVectors( &vecForward, NULL, NULL );
-
- Vector vecForward2D = vecForward;
- vecForward2D.z = 0;
-
- float flDot = DotProduct( vecForward2D, vecDir2D );
- if ( flDot >= HUNTER_SHOOT_MAX_YAW_COS )
- {
- // No need to clamp.
- return false;
- }
-
- Vector vecAxis;
- CrossProduct( vecDir, vecForward, vecAxis );
- VectorNormalize( vecAxis );
-
- Quaternion q;
- AxisAngleQuaternion( vecAxis, -HUNTER_SHOOT_MAX_YAW_DEG, q );
-
- matrix3x4_t rot;
- QuaternionMatrix( q, rot );
- VectorRotate( vecForward, rot, vecDir );
- VectorNormalize( vecDir );
-
- return true;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-bool CNPC_Hunter::ShouldSeekTarget( CBaseEntity *pTargetEntity, bool bStriderBuster )
-{
- bool bSeek = false;
-
- if ( bStriderBuster )
- {
- bool bSeek = false;
-
- if ( pTargetEntity->VPhysicsGetObject() && ( pTargetEntity->VPhysicsGetObject()->GetGameFlags() & FVPHYSICS_PLAYER_HELD ) )
- {
- bSeek = true;
- }
- else if ( StriderBuster_NumFlechettesAttached( pTargetEntity ) == 0 )
- {
- if ( StriderBuster_IsAttachedStriderBuster(pTargetEntity) )
- {
- bSeek = true;
- }
- else if ( hunter_seek_thrown_striderbusters_tolerance.GetFloat() > 0.0 )
- {
- CNPC_Strider *pEscortTarget = m_EscortBehavior.GetEscortTarget();
- if ( pEscortTarget && ( pEscortTarget->GetAbsOrigin() - pTargetEntity->GetAbsOrigin() ).LengthSqr() < Square( hunter_seek_thrown_striderbusters_tolerance.GetFloat() ) )
- {
- bSeek = true;
- }
- }
- }
- }
-
- return bSeek;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::BeginVolley( int nNum, float flStartTime )
-{
- m_nFlechettesQueued = nNum;
- m_nClampedShots = 0;
- m_flNextFlechetteTime = flStartTime;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-bool CNPC_Hunter::ShootFlechette( CBaseEntity *pTargetEntity, bool bSingleShot )
-{
- if ( !pTargetEntity )
- {
- Assert( false );
- return false;
- }
-
- int nShotNum = hunter_flechette_volley_size.GetInt() - m_nFlechettesQueued;
-
- bool bStriderBuster = IsStriderBuster( pTargetEntity );
-
- // Choose the next muzzle to shoot from.
- Vector vecSrc;
- QAngle angMuzzle;
-
- if ( m_bTopMuzzle )
- {
- GetAttachment( gm_nTopGunAttachment, vecSrc, angMuzzle );
- DoMuzzleFlash( gm_nTopGunAttachment );
- }
- else
- {
- GetAttachment( gm_nBottomGunAttachment, vecSrc, angMuzzle );
- DoMuzzleFlash( gm_nBottomGunAttachment );
- }
-
- m_bTopMuzzle = !m_bTopMuzzle;
-
- Vector vecDir;
- GetShootDir( vecDir, vecSrc, pTargetEntity, bStriderBuster, nShotNum, bSingleShot );
-
- bool bClamped = false;
- if ( hunter_clamp_shots.GetBool() )
- {
- bClamped = ClampShootDir( vecDir );
- }
-
- CShotManipulator manipulator( vecDir );
- Vector vecShoot;
-
- if( IsUsingSiegeTargets() && nShotNum >= 2 && (nShotNum % 2) == 0 )
- {
- // Near perfect accuracy for these three shots, so they are likely to fly right into the windows.
- // NOTE! In siege behavior in the map that this behavior was designed for (ep2_outland_10), the
- // Hunters will only ever shoot at siege targets in siege mode. If you allow Hunters in siege mode
- // to attack players or other NPCs, this accuracy bonus will apply unless we apply a bit more logic to it.
- vecShoot = manipulator.ApplySpread( VECTOR_CONE_1DEGREES * 0.5, 1.0f );
- }
- else
- {
- vecShoot = manipulator.ApplySpread( VECTOR_CONE_4DEGREES, 1.0f );
- }
-
- QAngle angShoot;
- VectorAngles( vecShoot, angShoot );
-
- CHunterFlechette *pFlechette = CHunterFlechette::FlechetteCreate( vecSrc, angShoot, this );
-
- pFlechette->AddEffects( EF_NOSHADOW );
-
- vecShoot *= hunter_flechette_speed.GetFloat();
-
- pFlechette->Shoot( vecShoot, bStriderBuster );
-
- if ( ShouldSeekTarget( pTargetEntity, bStriderBuster ) )
- {
- pFlechette->SetSeekTarget( pTargetEntity );
- }
-
- if( nShotNum == 1 && pTargetEntity->Classify() == CLASS_PLAYER_ALLY_VITAL )
- {
- // Make this person afraid and react to ME, not to the flechettes.
- // Otherwise they could be scared into running towards the hunter.
- CSoundEnt::InsertSound( SOUND_DANGER|SOUND_CONTEXT_REACT_TO_SOURCE|SOUND_CONTEXT_EXCLUDE_COMBINE, pTargetEntity->EyePosition(), 180.0f, 2.0f, this );
- }
-
- return bClamped;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-Vector CNPC_Hunter::LeftFootHit( float eventtime )
-{
- Vector footPosition;
-
- GetAttachment( "left foot", footPosition );
- CPASAttenuationFilter filter( this );
- EmitSound( filter, entindex(), "NPC_Hunter.Footstep", &footPosition, eventtime );
-
- FootFX( footPosition );
-
- return footPosition;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-Vector CNPC_Hunter::RightFootHit( float eventtime )
-{
- Vector footPosition;
-
- GetAttachment( "right foot", footPosition );
- CPASAttenuationFilter filter( this );
- EmitSound( filter, entindex(), "NPC_Hunter.Footstep", &footPosition, eventtime );
- FootFX( footPosition );
-
- return footPosition;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-Vector CNPC_Hunter::BackFootHit( float eventtime )
-{
- Vector footPosition;
-
- GetAttachment( "back foot", footPosition );
- CPASAttenuationFilter filter( this );
- EmitSound( filter, entindex(), "NPC_Hunter.BackFootstep", &footPosition, eventtime );
- FootFX( footPosition );
-
- return footPosition;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::FootFX( const Vector &origin )
-{
- return;
-
- // dvs TODO: foot dust? probably too expensive for these guys
- /*trace_t tr;
- AI_TraceLine( origin, origin - Vector(0,0,100), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr );
- float yaw = random->RandomInt(0,120);
- for ( int i = 0; i < 3; i++ )
- {
- Vector dir = UTIL_YawToVector( yaw + i*120 ) * 10;
- VectorNormalize( dir );
- dir.z = 0.25;
- VectorNormalize( dir );
- g_pEffects->Dust( tr.endpos, dir, 12, 50 );
- }*/
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-CBaseEntity *CNPC_Hunter::GetEnemyVehicle()
-{
- if ( GetEnemy() == NULL )
- return NULL;
-
- CBaseCombatCharacter *pCCEnemy = GetEnemy()->MyCombatCharacterPointer();
- if ( pCCEnemy != NULL )
- return pCCEnemy->GetVehicleEntity();
-
- return NULL;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::DrawDebugGeometryOverlays()
-{
- if (m_debugOverlays & OVERLAY_BBOX_BIT)
- {
- float flViewRange = acos(0.8);
- Vector vEyeDir = EyeDirection2D( );
- Vector vLeftDir, vRightDir;
- float fSin, fCos;
- SinCos( flViewRange, &fSin, &fCos );
-
- vLeftDir.x = vEyeDir.x * fCos - vEyeDir.y * fSin;
- vLeftDir.y = vEyeDir.x * fSin + vEyeDir.y * fCos;
- vLeftDir.z = vEyeDir.z;
- fSin = sin(-flViewRange);
- fCos = cos(-flViewRange);
- vRightDir.x = vEyeDir.x * fCos - vEyeDir.y * fSin;
- vRightDir.y = vEyeDir.x * fSin + vEyeDir.y * fCos;
- vRightDir.z = vEyeDir.z;
-
- int nSeq = GetSequence();
- if ( ( GetEntryNode( nSeq ) == gm_nPlantedNode ) && ( GetExitNode( nSeq ) == gm_nPlantedNode ) )
- {
- // planted - green
- NDebugOverlay::Box( GetAbsOrigin(), GetHullMins(), GetHullMaxs(), 0, 255, 0, 128, 0 );
- }
- else if ( ( GetEntryNode( nSeq ) == gm_nUnplantedNode ) && ( GetExitNode( nSeq ) == gm_nUnplantedNode ) )
- {
- // unplanted - blue
- NDebugOverlay::Box( GetAbsOrigin(), GetHullMins(), GetHullMaxs(), 0, 0, 255, 128, 0 );
- }
- else if ( ( GetEntryNode( nSeq ) == gm_nUnplantedNode ) && ( GetExitNode( nSeq ) == gm_nPlantedNode ) )
- {
- // planting transition - cyan
- NDebugOverlay::Box( GetAbsOrigin(), GetHullMins(), GetHullMaxs(), 0, 255, 255, 128, 0 );
- }
- else if ( ( GetEntryNode( nSeq ) == gm_nPlantedNode ) && ( GetExitNode( nSeq ) == gm_nUnplantedNode ) )
- {
- // unplanting transition - purple
- NDebugOverlay::Box( GetAbsOrigin(), GetHullMins(), GetHullMaxs(), 255, 0, 255, 128, 0 );
- }
- else
- {
- // unknown / other node - red
- Msg( "UNKNOWN: %s\n", GetSequenceName( GetSequence() ) );
- NDebugOverlay::Box( GetAbsOrigin(), GetHullMins(), GetHullMaxs(), 255, 0, 0, 128, 0 );
- }
-
- NDebugOverlay::BoxDirection(EyePosition(), Vector(0,0,-1), Vector(200,0,1), vLeftDir, 255, 0, 0, 50, 0 );
- NDebugOverlay::BoxDirection(EyePosition(), Vector(0,0,-1), Vector(200,0,1), vRightDir, 255, 0, 0, 50, 0 );
- NDebugOverlay::BoxDirection(EyePosition(), Vector(0,0,-1), Vector(200,0,1), vEyeDir, 0, 255, 0, 50, 0 );
- NDebugOverlay::Box(EyePosition(), -Vector(2,2,2), Vector(2,2,2), 0, 255, 0, 128, 0 );
- }
-
- m_EscortBehavior.DrawDebugGeometryOverlays();
-
- BaseClass::DrawDebugGeometryOverlays();
-}
-
-
-//-----------------------------------------------------------------------------
-// Player has illuminated this NPC with the flashlight
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::PlayerHasIlluminatedNPC( CBasePlayer *pPlayer, float flDot )
-{
- if ( m_bFlashlightInEyes )
- return;
-
- // Ignore the flashlight if it's not shining at my eyes
- if ( PlayerFlashlightOnMyEyes( pPlayer ) )
- {
- //Msg( ">>>> SHINING FLASHLIGHT ON ME\n" );
- m_bFlashlightInEyes = true;
- SetExpression( "scenes/npc/hunter/hunter_eyeclose.vcd" );
- m_flPupilDilateTime = gpGlobals->curtime + 0.2f;
- }
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-bool CNPC_Hunter::PlayerFlashlightOnMyEyes( CBasePlayer *pPlayer )
-{
- Vector vecEyes, vecEyeForward, vecPlayerForward;
- GetAttachment( gm_nTopGunAttachment, vecEyes, &vecEyeForward );
- pPlayer->EyeVectors( &vecPlayerForward );
-
- Vector vecToEyes = (vecEyes - pPlayer->EyePosition());
- //float flDist = VectorNormalize( vecToEyes );
-
- float flDot = DotProduct( vecPlayerForward, vecToEyes );
- if ( flDot < 0.98 )
- return false;
-
- // Check facing to ensure we're in front of her
- Vector los = ( pPlayer->EyePosition() - EyePosition() );
- los.z = 0;
- VectorNormalize( los );
- Vector facingDir = EyeDirection2D();
- flDot = DotProduct( los, facingDir );
- return ( flDot > 0.3 );
-}
-
-
-//-----------------------------------------------------------------------------
-// Return a random expression for the specified state to play over
-// the state's expression loop.
-//-----------------------------------------------------------------------------
-const char *CNPC_Hunter::SelectRandomExpressionForState( NPC_STATE state )
-{
- if ( m_bFlashlightInEyes )
- return NULL;
-
- if ( !hunter_random_expressions.GetBool() )
- return NULL;
-
- char *szExpressions[4] =
- {
- "scenes/npc/hunter/hunter_scan.vcd",
- "scenes/npc/hunter/hunter_eyeclose.vcd",
- "scenes/npc/hunter/hunter_roar.vcd",
- "scenes/npc/hunter/hunter_pain.vcd"
- };
-
- int nIndex = random->RandomInt( 0, 3 );
- //Msg( "RANDOM Expression: %s\n", szExpressions[nIndex] );
- return szExpressions[nIndex];
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::PlayExpressionForState( NPC_STATE state )
-{
- if ( m_bFlashlightInEyes )
- {
- return;
- }
-
- BaseClass::PlayExpressionForState( state );
-}
-
-
-//-----------------------------------------------------------------------------
-// TODO: remove if we're not doing striderbuster stuff
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::StriderBusterAttached( CBaseEntity *pAttached )
-{
- // Add another to the list
- m_hAttachedBusters.AddToTail( pAttached );
-
- SetCondition( COND_HUNTER_HIT_BY_STICKYBOMB );
- if (m_hAttachedBusters.Count() == 1)
- {
- EmitSound( "NPC_Hunter.Alert" );
- }
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::StriderBusterDetached( CBaseEntity *pAttached )
-{
- int elem = m_hAttachedBusters.Find(pAttached);
- if (elem >= 0)
- {
- m_hAttachedBusters.FastRemove(elem);
- }
-}
-
-
-//-----------------------------------------------------------------------------
-// Set direction that the hunter aims his body and eyes (guns).
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::SetAim( const Vector &aimDir, float flInterval )
-{
- QAngle angDir;
- VectorAngles( aimDir, angDir );
- float curPitch = GetPoseParameter( gm_nBodyPitchPoseParam );
- float curYaw = GetPoseParameter( gm_nBodyYawPoseParam );
-
- float newPitch;
- float newYaw;
-
- if ( GetEnemy() )
- {
- // clamp and dampen movement
- newPitch = curPitch + 0.8 * UTIL_AngleDiff( UTIL_ApproachAngle( angDir.x, curPitch, 20 ), curPitch );
-
- float flRelativeYaw = UTIL_AngleDiff( angDir.y, GetAbsAngles().y );
- newYaw = curYaw + UTIL_AngleDiff( flRelativeYaw, curYaw );
- }
- else
- {
- // Sweep your weapon more slowly if you're not fighting someone
- newPitch = curPitch + 0.6 * UTIL_AngleDiff( UTIL_ApproachAngle( angDir.x, curPitch, 20 ), curPitch );
-
- float flRelativeYaw = UTIL_AngleDiff( angDir.y, GetAbsAngles().y );
- newYaw = curYaw + 0.6 * UTIL_AngleDiff( flRelativeYaw, curYaw );
- }
-
- newPitch = AngleNormalize( newPitch );
- newYaw = AngleNormalize( newYaw );
-
- //Msg( "pitch=%f, yaw=%f\n", newPitch, newYaw );
-
- SetPoseParameter( gm_nAimPitchPoseParam, 0 );
- SetPoseParameter( gm_nAimYawPoseParam, 0 );
-
- SetPoseParameter( gm_nBodyPitchPoseParam, clamp( newPitch, -45, 45 ) );
- SetPoseParameter( gm_nBodyYawPoseParam, clamp( newYaw, -45, 45 ) );
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::RelaxAim( float flInterval )
-{
- float curPitch = GetPoseParameter( gm_nBodyPitchPoseParam );
- float curYaw = GetPoseParameter( gm_nBodyYawPoseParam );
-
- // dampen existing aim
- float newPitch = AngleNormalize( UTIL_ApproachAngle( 0, curPitch, 3 ) );
- float newYaw = AngleNormalize( UTIL_ApproachAngle( 0, curYaw, 2 ) );
-
- SetPoseParameter( gm_nAimPitchPoseParam, 0 );
- SetPoseParameter( gm_nAimYawPoseParam, 0 );
-
- SetPoseParameter( gm_nBodyPitchPoseParam, clamp( newPitch, -45, 45 ) );
- SetPoseParameter( gm_nBodyYawPoseParam, clamp( newYaw, -45, 45 ) );
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::UpdateAim()
-{
- if ( !GetModelPtr() || !GetModelPtr()->SequencesAvailable() )
- return;
-
- float flInterval = GetAnimTimeInterval();
-
- // Some activities look bad if we're giving our enemy the stinkeye.
- int eActivity = GetActivity();
-
- if ( GetEnemy() &&
- GetState() != NPC_STATE_SCRIPT &&
- ( eActivity != ACT_HUNTER_CHARGE_CRASH ) &&
- ( eActivity != ACT_HUNTER_CHARGE_HIT ) )
- {
- Vector vecShootOrigin;
-
- vecShootOrigin = Weapon_ShootPosition();
- Vector vecShootDir = GetShootEnemyDir( vecShootOrigin, false );
-
- SetAim( vecShootDir, flInterval );
- }
- else
- {
- RelaxAim( flInterval );
- }
-}
-
-
-//-----------------------------------------------------------------------------
-// Don't become a ragdoll until we've finished our death anim
-//-----------------------------------------------------------------------------
-bool CNPC_Hunter::CanBecomeRagdoll()
-{
- return ( m_nKillingDamageType & DMG_CRUSH ) ||
- IsCurSchedule( SCHED_DIE, false ) || // Finished playing death anim, time to ragdoll
- IsCurSchedule( SCHED_HUNTER_CHARGE_ENEMY, false ) || // While moving, it looks better to ragdoll instantly
- IsCurSchedule( SCHED_SCRIPTED_RUN, false ) ||
- ( GetActivity() == ACT_WALK ) || ( GetActivity() == ACT_RUN ) ||
- GetCurSchedule() == NULL; // Failsafe
-}
-
-
-//-----------------------------------------------------------------------------
-// Determines the best type of death anim to play based on how we died.
-//-----------------------------------------------------------------------------
-Activity CNPC_Hunter::GetDeathActivity()
-{
- return ACT_DIESIMPLE;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CAI_HunterEscortBehavior::OnDamage( const CTakeDamageInfo &info )
-{
- if ( info.GetDamage() > 0 && info.GetAttacker()->IsPlayer() &&
- GetFollowTarget() && ( AIGetNumFollowers( GetFollowTarget() ) > 1 ) &&
- ( GetOuter()->GetSquad()->GetSquadSoundWaitTime() <= gpGlobals->curtime ) ) // && !FarFromFollowTarget()
- {
- // Start the clock ticking. We'll return the the strider when the timer elapses.
- m_flTimeEscortReturn = gpGlobals->curtime + random->RandomFloat( 15.0f, 25.0f );
- GetOuter()->GetSquad()->SetSquadSoundWaitTime( m_flTimeEscortReturn + 1.0 ); // prevent others from breaking escort
- }
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CAI_HunterEscortBehavior::BuildScheduleTestBits()
-{
- BaseClass::BuildScheduleTestBits();
-
- if ( ( m_flTimeEscortReturn != 0 ) && ( gpGlobals->curtime > m_flTimeEscortReturn ) )
- {
- // We're delinquent! Return to strider!
- GetOuter()->ClearCustomInterruptCondition( COND_NEW_ENEMY );
- GetOuter()->ClearCustomInterruptCondition( COND_SEE_ENEMY );
- GetOuter()->ClearCustomInterruptCondition( COND_SEE_HATE );
- GetOuter()->ClearCustomInterruptCondition( COND_CAN_RANGE_ATTACK1 );
- GetOuter()->ClearCustomInterruptCondition( COND_CAN_RANGE_ATTACK2 );
- }
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CAI_HunterEscortBehavior::CheckBreakEscort()
-{
- if ( m_flTimeEscortReturn != 0 && ( FarFromFollowTarget() || gpGlobals->curtime >= m_flTimeEscortReturn ) )
- {
- if ( FarFromFollowTarget() )
- {
- m_flTimeEscortReturn = gpGlobals->curtime;
- }
- else
- {
- m_flTimeEscortReturn = 0;
- }
- if ( GetOuter()->GetSquad() )
- {
- GetOuter()->GetSquad()->SetSquadSoundWaitTime( gpGlobals->curtime + random->RandomFloat( 5.0f, 12.0f ) );
- }
- }
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CAI_HunterEscortBehavior::GatherConditionsNotActive( void )
-{
- if ( m_bEnabled )
- {
- DistributeFreeHunters();
- }
-
- BaseClass::GatherConditionsNotActive();
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CAI_HunterEscortBehavior::GatherConditions( void )
-{
- m_bEnabled = true;
-
- DistributeFreeHunters();
-
- BaseClass::GatherConditions();
-
- if ( GetEnemy() && GetEnemy()->IsPlayer() && HasCondition( COND_SEE_ENEMY ) )
- {
- if ( GetOuter()->GetSquad()->GetSquadSoundWaitTime() <= gpGlobals->curtime && ((CBasePlayer *)GetEnemy())->IsInAVehicle() )
- {
- m_flTimeEscortReturn = gpGlobals->curtime + random->RandomFloat( 15.0f, 25.0f );
- GetOuter()->GetSquad()->SetSquadSoundWaitTime( m_flTimeEscortReturn + 1.0 ); // prevent others from breaking escort
- }
- }
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-bool CAI_HunterEscortBehavior::ShouldFollow()
-{
- if ( IsStriderBuster( GetEnemy() ) )
- return false;
-
- if ( HasCondition( COND_HEAR_PHYSICS_DANGER ) )
- return false;
-
- if ( m_flTimeEscortReturn <= gpGlobals->curtime )
- {
- return CAI_FollowBehavior::ShouldFollow();
- }
-
- return false;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CAI_HunterEscortBehavior::BeginScheduleSelection()
-{
- BaseClass::BeginScheduleSelection();
- Assert( m_SavedDistTooFar == GetOuter()->m_flDistTooFar );
- GetOuter()->m_flDistTooFar *= 2;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-int CAI_HunterEscortBehavior::SelectSchedule()
-{
- if( m_FollowDelay.IsRunning() && !m_FollowDelay.Expired() )
- {
- return FollowCallBaseSelectSchedule();
- }
- return BaseClass::SelectSchedule();
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-int CAI_HunterEscortBehavior::FollowCallBaseSelectSchedule()
-{
- if ( GetOuter()->GetState() == NPC_STATE_COMBAT )
- {
- return GetOuter()->SelectCombatSchedule();
- }
-
- return BaseClass::FollowCallBaseSelectSchedule();
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CAI_HunterEscortBehavior::StartTask( const Task_t *pTask )
-{
- switch( pTask->iTask )
- {
- case TASK_MOVE_TO_FOLLOW_POSITION:
- {
- if ( GetEnemy() )
- {
- if ( GetOuter()->OccupyStrategySlot( SQUAD_SLOT_RUN_SHOOT ) )
- {
- if ( GetOuter()->GetSquad()->GetSquadMemberNearestTo( GetEnemy()->GetAbsOrigin() ) == GetOuter() )
- {
- GetOuter()->BeginVolley( NUM_FLECHETTE_VOLLEY_ON_FOLLOW, gpGlobals->curtime + 1.0 + random->RandomFloat( 0, .25 ) + random->RandomFloat( 0, .25 ) );
- }
- else
- {
- GetOuter()->VacateStrategySlot();
- }
- }
- }
- BaseClass::StartTask( pTask );
- break;
- }
-
- default:
- BaseClass::StartTask( pTask );
- }
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CAI_HunterEscortBehavior::RunTask( const Task_t *pTask )
-{
- switch( pTask->iTask )
- {
- case TASK_MOVE_TO_FOLLOW_POSITION:
- {
- if ( !GetFollowTarget() )
- {
- TaskFail( FAIL_NO_TARGET );
- }
- else
- {
- if ( GetEnemy() )
- {
- CNPC_Hunter *pHunter = GetOuter();
- Vector vecEnemyLKP = pHunter->GetEnemyLKP();
- pHunter->AddFacingTarget( pHunter->GetEnemy(), vecEnemyLKP, 1.0, 0.8 );
- bool bVacate = false;
-
- bool bHasSlot = pHunter->HasStrategySlot( SQUAD_SLOT_RUN_SHOOT );
- if ( HasCondition( COND_SEE_ENEMY ) )
- {
- float maxDist = hunter_flechette_max_range.GetFloat() * 3;
- float distSq = ( pHunter->GetAbsOrigin() - pHunter->GetEnemy()->GetAbsOrigin() ).Length2DSqr();
-
- if ( distSq < Square( maxDist ) )
- {
- if ( gpGlobals->curtime >= pHunter->m_flNextFlechetteTime )
- {
- if ( !bHasSlot )
- {
- if ( GetOuter()->OccupyStrategySlot( SQUAD_SLOT_RUN_SHOOT ) )
- {
- if ( GetOuter()->GetSquad()->GetSquadMemberNearestTo( GetEnemy()->GetAbsOrigin() ) == GetOuter() )
- {
- bHasSlot = true;
- }
- else
- {
- GetOuter()->VacateStrategySlot();
- }
- }
- }
-
- if ( bHasSlot )
- {
- // Start the firing sound.
- //CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
- //if ( controller.SoundGetVolume( pHunter->m_pGunFiringSound ) == 0.0f )
- //{
- // controller.SoundChangeVolume( pHunter->m_pGunFiringSound, 1.0f, 0.0f );
- //}
-
- pHunter->ShootFlechette( GetEnemy(), true );
-
- if ( --pHunter->m_nFlechettesQueued > 0 )
- {
- pHunter->m_flNextFlechetteTime = gpGlobals->curtime + hunter_flechette_delay.GetFloat();
- }
- else
- {
- // Stop the firing sound.
- //CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
- //controller.SoundChangeVolume( pHunter->m_pGunFiringSound, 0, 0.01f );
-
- bVacate = true;
- pHunter->BeginVolley( NUM_FLECHETTE_VOLLEY_ON_FOLLOW, gpGlobals->curtime + 1.0 + random->RandomFloat( 0, .25 ) + random->RandomFloat( 0, .25 ) );
- }
- }
- }
- }
- else if ( bHasSlot )
- {
- bVacate = true;
- }
- }
- else if ( bHasSlot )
- {
- bVacate = true;
- }
-
- if ( bVacate )
- {
- pHunter->VacateStrategySlot();
- }
- }
-
- if ( m_FollowAttackTimer.Expired() && IsFollowTargetInRange( .8 ))
- {
- m_FollowAttackTimer.Set( 8, 24 );
- TaskComplete();
- }
- else
- {
- BaseClass::RunTask( pTask );
- }
- }
- break;
- }
-
- default:
- BaseClass::RunTask( pTask );
- }
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CAI_HunterEscortBehavior::FindFreeHunters( CUtlVector<CNPC_Hunter *> *pFreeHunters )
-{
- pFreeHunters->EnsureCapacity( g_Hunters.Count() );
- int i;
-
- for ( i = 0; i < g_Hunters.Count(); i++ )
- {
- CNPC_Hunter *pHunter = g_Hunters[i];
- if ( pHunter->IsAlive() && pHunter->m_EscortBehavior.m_bEnabled )
- {
- if ( pHunter->m_EscortBehavior.GetFollowTarget() == NULL || !pHunter->m_EscortBehavior.GetFollowTarget()->IsAlive() )
- {
- pFreeHunters->AddToTail( pHunter);
- }
- }
- }
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CAI_HunterEscortBehavior::DistributeFreeHunters()
-{
- if ( g_TimeLastDistributeFreeHunters != -1 && gpGlobals->curtime - g_TimeLastDistributeFreeHunters < FREE_HUNTER_DISTRIBUTE_INTERVAL )
- {
- return;
- }
-
- g_TimeLastDistributeFreeHunters = gpGlobals->curtime;
-
- CUtlVector<CNPC_Hunter *> freeHunters;
- int i;
- FindFreeHunters( &freeHunters );
-
- CAI_BaseNPC **ppNPCs = g_AI_Manager.AccessAIs();
- for ( i = 0; i < g_AI_Manager.NumAIs() && freeHunters.Count(); i++ )
- {
- int nToAdd;
- CNPC_Strider *pStrider = ( ppNPCs[i]->IsAlive() ) ? dynamic_cast<CNPC_Strider *>( ppNPCs[i] ) : NULL;
- if ( pStrider && !pStrider->CarriedByDropship() )
- {
- if ( ( nToAdd = 3 - AIGetNumFollowers( pStrider ) ) > 0 )
- {
- for ( int j = freeHunters.Count() - 1; j >= 0 && nToAdd > 0; --j )
- {
- DevMsg( "npc_hunter %d assigned to npc_strider %d\n", freeHunters[j]->entindex(), pStrider->entindex() );
- freeHunters[j]->FollowStrider( pStrider );
- freeHunters.FastRemove( j );
- nToAdd--;
- }
- }
- }
- }
-
- for ( i = 0; i < freeHunters.Count(); i++ )
- {
- //DevMsg( "npc_hunter %d assigned to free_hunters_squad\n", freeHunters[i]->entindex() );
- freeHunters[i]->m_EscortBehavior.SetFollowTarget( NULL );
- freeHunters[i]->AddToSquad( AllocPooledString( "free_hunters_squad" ) );
- }
-
-#if 0
- CBaseEntity *pHunterMaker = gEntList.FindEntityByClassname( NULL, "npc_hunter_maker" ); // TODO: this picks the same one every time!
- if ( pHunterMaker )
- {
- for ( i = 0; i < freeHunters.Count(); i++ )
- {
- freeHunters[i]->m_EscortBehavior.SetFollowTarget( pHunterMaker );
- }
- }
-#endif
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CAI_HunterEscortBehavior::DrawDebugGeometryOverlays()
-{
- if ( !GetFollowTarget() )
- return;
-
- Vector vecFollowPos = GetGoalPosition();
- if ( FarFromFollowTarget() )
- {
- if ( gpGlobals->curtime >= m_flTimeEscortReturn )
- {
- NDebugOverlay::HorzArrow( GetOuter()->GetAbsOrigin(), vecFollowPos, 16.0f, 255, 0, 0, 0, true, 0 );
- }
- else
- {
- NDebugOverlay::HorzArrow( GetOuter()->GetAbsOrigin(), vecFollowPos, 16.0f, 255, 255, 0, 0, true, 0 );
- }
- }
- else
- {
- NDebugOverlay::HorzArrow( GetOuter()->GetAbsOrigin(), vecFollowPos, 16.0f, 0, 255, 0, 0, true, 0 );
- }
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-bool Hunter_IsHunter(CBaseEntity *pEnt)
-{
- return dynamic_cast<CNPC_Hunter *>(pEnt) != NULL;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void Hunter_StriderBusterLaunched( CBaseEntity *pBuster )
-{
- CAI_BaseNPC **ppAIs = g_AI_Manager.AccessAIs();
- int nAIs = g_AI_Manager.NumAIs();
-
- for ( int i = 0; i < nAIs; i++ )
- {
- CAI_BaseNPC *pNPC = ppAIs[ i ];
- if ( pNPC && ( pNPC->Classify() == CLASS_COMBINE_HUNTER ) && pNPC->m_lifeState == LIFE_ALIVE )
- {
- if ( !pNPC->GetEnemy() || !IsStriderBuster( pNPC->GetEnemy() ) )
- {
- Vector vecDelta = pNPC->GetAbsOrigin() - pBuster->GetAbsOrigin();
- if ( vecDelta.Length2DSqr() < 9437184.0f ) // 3072 * 3072
- {
- pNPC->SetEnemy( pBuster );
- pNPC->SetState( NPC_STATE_COMBAT );
- pNPC->UpdateEnemyMemory( pBuster, pBuster->GetAbsOrigin() );
-
- // Stop whatever we're doing.
- pNPC->SetCondition( COND_SCHEDULE_DONE );
- }
- }
- }
- }
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void Hunter_StriderBusterAttached( CBaseEntity *pHunter, CBaseEntity *pAttached )
-{
- Assert(dynamic_cast<CNPC_Hunter *>(pHunter));
-
- static_cast<CNPC_Hunter *>(pHunter)->StriderBusterAttached(pAttached);
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void Hunter_StriderBusterDetached( CBaseEntity *pHunter, CBaseEntity *pAttached )
-{
- Assert(dynamic_cast<CNPC_Hunter *>(pHunter));
-
- static_cast<CNPC_Hunter *>(pHunter)->StriderBusterDetached(pAttached);
-}
-
-//-------------------------------------------------------------------------------------------------
-//
-// ep2_outland_12 custom npc makers
-//
-//-------------------------------------------------------------------------------------------------
-
-class CHunterMaker : public CTemplateNPCMaker
-{
- typedef CTemplateNPCMaker BaseClass;
-public:
- void MakeMultipleNPCS( int nNPCs )
- {
- const float MIN_HEALTH_PCT = 0.2;
-
- CUtlVector<CNPC_Hunter *> candidates;
- CUtlVectorFixed<CNPC_Hunter *, 3> freeHunters;
- CAI_HunterEscortBehavior::FindFreeHunters( &candidates );
-
- freeHunters.EnsureCapacity( 3 );
- int i;
-
- for ( i = 0; i < candidates.Count() && freeHunters.Count() < 3; i++ )
- {
- if ( candidates[i]->GetHealth() > candidates[i]->GetMaxHealth() * MIN_HEALTH_PCT )
- {
- freeHunters.AddToTail( candidates[i] );
- }
- }
-
- int nRequested = nNPCs;
- if ( nNPCs < 3 )
- {
- nNPCs = MIN( 3, nNPCs + freeHunters.Count() );
- }
-
- int nSummoned = 0;
- for ( i = 0; i < freeHunters.Count() && nNPCs; i++ )
- {
- freeHunters[i]->m_EscortBehavior.SetFollowTarget( this ); // this will make them not "free"
- freeHunters[i]->SetName( m_iszTemplateName ); // this will force the hunter to get the FollowStrider input
- nNPCs--;
- nSummoned++;
- }
-
- DevMsg( "Requested %d to spawn, Summoning %d free hunters, spawning %d new hunters\n", nRequested, nSummoned, nNPCs );
- if ( nNPCs )
- {
- BaseClass::MakeMultipleNPCS( nNPCs );
- }
- }
-};
-
-LINK_ENTITY_TO_CLASS( npc_hunter_maker, CHunterMaker );
-
-
-//-------------------------------------------------------------------------------------------------
-//
-// Schedules
-//
-//-------------------------------------------------------------------------------------------------
-AI_BEGIN_CUSTOM_NPC( npc_hunter, CNPC_Hunter )
-
- DECLARE_TASK( TASK_HUNTER_AIM )
- DECLARE_TASK( TASK_HUNTER_FIND_DODGE_POSITION )
- DECLARE_TASK( TASK_HUNTER_DODGE )
- DECLARE_TASK( TASK_HUNTER_PRE_RANGE_ATTACK2 )
- DECLARE_TASK( TASK_HUNTER_SHOOT_COMMIT )
- DECLARE_TASK( TASK_HUNTER_ANNOUNCE_FLANK )
- DECLARE_TASK( TASK_HUNTER_BEGIN_FLANK )
- DECLARE_TASK( TASK_HUNTER_STAGGER )
- DECLARE_TASK( TASK_HUNTER_CORNERED_TIMER )
- DECLARE_TASK( TASK_HUNTER_FIND_SIDESTEP_POSITION )
- DECLARE_TASK( TASK_HUNTER_CHARGE )
- DECLARE_TASK( TASK_HUNTER_FINISH_RANGE_ATTACK )
- DECLARE_TASK( TASK_HUNTER_WAIT_FOR_MOVEMENT_FACING_ENEMY )
- DECLARE_TASK( TASK_HUNTER_CHARGE_DELAY )
-
- DECLARE_ACTIVITY( ACT_HUNTER_DEPLOYRA2 )
- DECLARE_ACTIVITY( ACT_HUNTER_DODGER )
- DECLARE_ACTIVITY( ACT_HUNTER_DODGEL )
- DECLARE_ACTIVITY( ACT_HUNTER_GESTURE_SHOOT )
- DECLARE_ACTIVITY( ACT_HUNTER_FLINCH_STICKYBOMB )
- DECLARE_ACTIVITY( ACT_HUNTER_STAGGER )
- DECLARE_ACTIVITY( ACT_DI_HUNTER_MELEE )
- DECLARE_ACTIVITY( ACT_DI_HUNTER_THROW )
- DECLARE_ACTIVITY( ACT_HUNTER_MELEE_ATTACK1_VS_PLAYER )
- DECLARE_ACTIVITY( ACT_HUNTER_ANGRY )
- DECLARE_ACTIVITY( ACT_HUNTER_WALK_ANGRY )
- DECLARE_ACTIVITY( ACT_HUNTER_FOUND_ENEMY )
- DECLARE_ACTIVITY( ACT_HUNTER_FOUND_ENEMY_ACK )
- DECLARE_ACTIVITY( ACT_HUNTER_CHARGE_START )
- DECLARE_ACTIVITY( ACT_HUNTER_CHARGE_RUN )
- DECLARE_ACTIVITY( ACT_HUNTER_CHARGE_STOP )
- DECLARE_ACTIVITY( ACT_HUNTER_CHARGE_CRASH )
- DECLARE_ACTIVITY( ACT_HUNTER_CHARGE_HIT )
- DECLARE_ACTIVITY( ACT_HUNTER_RANGE_ATTACK2_UNPLANTED )
- DECLARE_ACTIVITY( ACT_HUNTER_IDLE_PLANTED )
- DECLARE_ACTIVITY( ACT_HUNTER_FLINCH_N )
- DECLARE_ACTIVITY( ACT_HUNTER_FLINCH_S )
- DECLARE_ACTIVITY( ACT_HUNTER_FLINCH_E )
- DECLARE_ACTIVITY( ACT_HUNTER_FLINCH_W )
-
- DECLARE_INTERACTION( g_interactionHunterFoundEnemy );
-
- DECLARE_SQUADSLOT( SQUAD_SLOT_HUNTER_CHARGE )
- DECLARE_SQUADSLOT( SQUAD_SLOT_HUNTER_FLANK_FIRST )
- DECLARE_SQUADSLOT( SQUAD_SLOT_RUN_SHOOT )
-
- DECLARE_CONDITION( COND_HUNTER_SHOULD_PATROL )
- DECLARE_CONDITION( COND_HUNTER_FORCED_FLANK_ENEMY )
- DECLARE_CONDITION( COND_HUNTER_CAN_CHARGE_ENEMY )
- DECLARE_CONDITION( COND_HUNTER_STAGGERED )
- DECLARE_CONDITION( COND_HUNTER_IS_INDOORS )
- DECLARE_CONDITION( COND_HUNTER_HIT_BY_STICKYBOMB )
- DECLARE_CONDITION( COND_HUNTER_SEE_STRIDERBUSTER )
- DECLARE_CONDITION( COND_HUNTER_FORCED_DODGE )
- DECLARE_CONDITION( COND_HUNTER_INCOMING_VEHICLE )
- DECLARE_CONDITION( COND_HUNTER_NEW_HINTGROUP )
- DECLARE_CONDITION( COND_HUNTER_CANT_PLANT )
- DECLARE_CONDITION( COND_HUNTER_SQUADMATE_FOUND_ENEMY )
-
- DECLARE_ANIMEVENT( AE_HUNTER_FOOTSTEP_LEFT )
- DECLARE_ANIMEVENT( AE_HUNTER_FOOTSTEP_RIGHT )
- DECLARE_ANIMEVENT( AE_HUNTER_FOOTSTEP_BACK )
- DECLARE_ANIMEVENT( AE_HUNTER_MELEE_ANNOUNCE )
- DECLARE_ANIMEVENT( AE_HUNTER_MELEE_ATTACK_LEFT )
- DECLARE_ANIMEVENT( AE_HUNTER_MELEE_ATTACK_RIGHT )
- DECLARE_ANIMEVENT( AE_HUNTER_DIE )
- DECLARE_ANIMEVENT( AE_HUNTER_SPRAY_BLOOD )
- DECLARE_ANIMEVENT( AE_HUNTER_START_EXPRESSION )
- DECLARE_ANIMEVENT( AE_HUNTER_END_EXPRESSION )
-
- //=========================================================
- // Attack (Deploy/shoot/finish)
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_HUNTER_RANGE_ATTACK1,
-
- " Tasks"
- " TASK_STOP_MOVING 0"
- " TASK_HUNTER_SHOOT_COMMIT 0"
- " TASK_RANGE_ATTACK1 0"
- " "
- " Interrupts"
- " COND_NEW_ENEMY"
- " COND_ENEMY_DEAD"
- " COND_LOST_ENEMY"
- " COND_ENEMY_OCCLUDED"
- " COND_WEAPON_SIGHT_OCCLUDED"
- " COND_TOO_CLOSE_TO_ATTACK"
- " COND_TOO_FAR_TO_ATTACK"
- " COND_NOT_FACING_ATTACK"
- )
-
- //=========================================================
- // Attack (Deploy/shoot/finish)
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_HUNTER_RANGE_ATTACK2,
-
- " Tasks"
- " TASK_STOP_MOVING 0"
- " TASK_HUNTER_PRE_RANGE_ATTACK2 0"
- " TASK_HUNTER_SHOOT_COMMIT 0"
- " TASK_RANGE_ATTACK2 0"
- " TASK_HUNTER_FINISH_RANGE_ATTACK 0"
- " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
- " TASK_WAIT 0.4"
- " TASK_WAIT_RANDOM 0.2"
- " "
- " Interrupts"
- " COND_NEW_ENEMY"
- )
-
- //=========================================================
- // Shoot at striderbuster. Distinct from generic range attack
- // because of BuildScheduleTestBits.
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_HUNTER_RANGE_ATTACK2_VS_STRIDERBUSTER,
-
- " Tasks"
- " TASK_STOP_MOVING 0"
- " TASK_HUNTER_SHOOT_COMMIT 0"
- " TASK_RANGE_ATTACK2 0"
- " "
- " Interrupts"
- )
-
- //=========================================================
- // Shoot at striderbuster with a little latency beforehand
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_HUNTER_RANGE_ATTACK2_VS_STRIDERBUSTER_LATENT,
-
- " Tasks"
- " TASK_STOP_MOVING 0"
- " TASK_HUNTER_SHOOT_COMMIT 0"
- " TASK_WAIT 0.2"
- " TASK_PLAY_SEQUENCE_FACE_ENEMY ACTIVITY:ACT_RANGE_ATTACK2"
- " TASK_RANGE_ATTACK2 0"
- " "
- " Interrupts"
- )
-
- //=========================================================
- // Dodge Incoming vehicle
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_HUNTER_DODGE,
-
- " Tasks"
- " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_HUNTER_FAIL_DODGE"
- " TASK_HUNTER_FIND_DODGE_POSITION 0"
- " TASK_HUNTER_DODGE 0"
- ""
- " Interrupts"
- )
-
- //=========================================================
- // Dodge Incoming vehicle
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_HUNTER_FAIL_DODGE,
-
- " Tasks"
- " TASK_STOP_MOVING 0"
- " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
- " TASK_FACE_ENEMY 0"
- ""
- " Interrupts"
- )
-
- //==================================================
- // > SCHED_HUNTER_CHARGE_ENEMY
- // Rush at my enemy and head-butt them.
- //==================================================
- DEFINE_SCHEDULE
- (
- SCHED_HUNTER_CHARGE_ENEMY,
-
- " Tasks"
- " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_HUNTER_FAIL_CHARGE_ENEMY"
- " TASK_STOP_MOVING 0"
- " TASK_FACE_ENEMY 0"
- " TASK_HUNTER_CHARGE 0"
- ""
- " Interrupts"
- " COND_TASK_FAILED"
- " COND_ENEMY_DEAD"
- )
-
- DEFINE_SCHEDULE
- (
- SCHED_HUNTER_FAIL_CHARGE_ENEMY,
-
- " Tasks"
- " TASK_HUNTER_CHARGE_DELAY 10"
- )
-
- //=========================================================
- // Chase the enemy with intent to claw
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_HUNTER_CHASE_ENEMY_MELEE,
-
- " Tasks"
- " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_ESTABLISH_LINE_OF_FIRE"
- " TASK_STOP_MOVING 0"
- " TASK_GET_CHASE_PATH_TO_ENEMY 300"
- " TASK_RUN_PATH 0"
- " TASK_WAIT_FOR_MOVEMENT 0"
- " TASK_FACE_ENEMY 0"
- ""
- " Interrupts"
- " COND_NEW_ENEMY"
- " COND_ENEMY_DEAD"
- " COND_ENEMY_UNREACHABLE"
- " COND_CAN_MELEE_ATTACK1"
- " COND_CAN_MELEE_ATTACK2"
- //" COND_TOO_CLOSE_TO_ATTACK"
- " COND_LOST_ENEMY"
- )
-
- //=========================================================
- // Chase my enemy, shoot or claw when possible to do so.
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_HUNTER_CHASE_ENEMY,
-
- " Tasks"
- " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_ESTABLISH_LINE_OF_FIRE"
- " TASK_STOP_MOVING 0"
- " TASK_GET_CHASE_PATH_TO_ENEMY 300"
- " TASK_RUN_PATH 0"
- " TASK_WAIT_FOR_MOVEMENT 0"
- " TASK_FACE_ENEMY 0"
- ""
- " Interrupts"
- " COND_NEW_ENEMY"
- " COND_ENEMY_DEAD"
- " COND_ENEMY_UNREACHABLE"
- " COND_CAN_RANGE_ATTACK1"
- " COND_CAN_RANGE_ATTACK2"
- " COND_CAN_MELEE_ATTACK1"
- " COND_CAN_MELEE_ATTACK2"
- " COND_TOO_CLOSE_TO_ATTACK"
- " COND_LOST_ENEMY"
- )
-
- //=========================================================
- // Move to a flanking position, then shoot if possible.
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_HUNTER_FLANK_ENEMY,
-
- " Tasks"
- " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_ESTABLISH_LINE_OF_FIRE"
- " TASK_STOP_MOVING 0"
- " TASK_HUNTER_BEGIN_FLANK 0"
- " TASK_GET_FLANK_ARC_PATH_TO_ENEMY_LOS 30"
- " TASK_HUNTER_ANNOUNCE_FLANK 0"
- " TASK_RUN_PATH 0"
- " TASK_WAIT_FOR_MOVEMENT 0"
- " TASK_FACE_ENEMY 0"
- //" TASK_HUNTER_END_FLANK 0"
- ""
- " Interrupts"
- " COND_NEW_ENEMY"
- //" COND_CAN_RANGE_ATTACK1"
- //" COND_CAN_RANGE_ATTACK2"
- " COND_CAN_MELEE_ATTACK1"
- " COND_CAN_MELEE_ATTACK2"
- " COND_ENEMY_DEAD"
- " COND_ENEMY_UNREACHABLE"
- " COND_TOO_CLOSE_TO_ATTACK"
- " COND_LOST_ENEMY"
- )
-
- //=========================================================
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_HUNTER_COMBAT_FACE,
-
- " Tasks"
- " TASK_STOP_MOVING 0"
- " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
- " TASK_WAIT_FACE_ENEMY 1"
- ""
- " Interrupts"
- " COND_CAN_RANGE_ATTACK1"
- " COND_CAN_RANGE_ATTACK2"
- " COND_CAN_MELEE_ATTACK1"
- " COND_CAN_MELEE_ATTACK2"
- " COND_NEW_ENEMY"
- " COND_ENEMY_DEAD"
- )
-
- //=========================================================
- // Like the base class, only don't stop in the middle of
- // swinging if the enemy is killed, hides, or new enemy.
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_HUNTER_MELEE_ATTACK1,
-
- " Tasks"
- " TASK_STOP_MOVING 0"
- " TASK_FACE_ENEMY 0"
- " TASK_MELEE_ATTACK1 0"
- //" TASK_SET_SCHEDULE SCHEDULE:SCHED_HUNTER_POST_MELEE_WAIT"
- ""
- " Interrupts"
- )
-
- //=========================================================
- // In a fight with nothing to do. Make busy!
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_HUNTER_CHANGE_POSITION,
-
- " Tasks"
- " TASK_STOP_MOVING 0"
- " TASK_WANDER 720432" // 6 feet to 36 feet
- " TASK_RUN_PATH 0"
- " TASK_HUNTER_WAIT_FOR_MOVEMENT_FACING_ENEMY 0"
- " TASK_STOP_MOVING 0"
- " TASK_SET_SCHEDULE SCHEDULE:SCHED_HUNTER_CHANGE_POSITION_FINISH"
- ""
- " Interrupts"
- " COND_ENEMY_DEAD"
- " COND_CAN_MELEE_ATTACK1"
- " COND_CAN_MELEE_ATTACK2"
- " COND_HEAR_DANGER"
- " COND_HEAR_MOVE_AWAY"
- " COND_NEW_ENEMY"
- )
-
- //=========================================================
- // In a fight with nothing to do. Make busy!
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_HUNTER_CHANGE_POSITION_FINISH,
-
- " Tasks"
- " TASK_FACE_ENEMY 0"
- " TASK_WAIT_FACE_ENEMY_RANDOM 5"
- ""
- " Interrupts"
- " COND_ENEMY_DEAD"
- " COND_CAN_RANGE_ATTACK1"
- " COND_CAN_RANGE_ATTACK2"
- " COND_CAN_MELEE_ATTACK1"
- " COND_CAN_MELEE_ATTACK2"
- " COND_HEAR_DANGER"
- " COND_HEAR_MOVE_AWAY"
- " COND_NEW_ENEMY"
- )
-
- //=========================================================
- // In a fight with nothing to do. Make busy!
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_HUNTER_SIDESTEP,
-
- " Tasks"
- " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_HUNTER_FAIL_IMMEDIATE" // used because sched_fail includes a one second pause. ick!
- " TASK_STOP_MOVING 0"
- " TASK_HUNTER_FIND_SIDESTEP_POSITION 0"
- " TASK_GET_PATH_TO_SAVEPOSITION 0"
- " TASK_RUN_PATH 0"
- " TASK_WAIT_FOR_MOVEMENT 0"
- " TASK_FACE_ENEMY 0"
- ""
- " Interrupts"
- )
-
- //=========================================================
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_HUNTER_PATROL,
-
- " Tasks"
- " TASK_STOP_MOVING 0"
- " TASK_WANDER 720432" // 6 feet to 36 feet
- " TASK_WALK_PATH 0"
- " TASK_WAIT_FOR_MOVEMENT 0"
- " TASK_STOP_MOVING 0"
- " TASK_FACE_REASONABLE 0"
- " TASK_WAIT_RANDOM 3"
- ""
- " Interrupts"
- " COND_ENEMY_DEAD"
- " COND_LIGHT_DAMAGE"
- " COND_HEAVY_DAMAGE"
- " COND_HEAR_DANGER"
- " COND_HEAR_COMBAT"
- " COND_HEAR_PLAYER"
- " COND_HEAR_BULLET_IMPACT"
- " COND_HEAR_MOVE_AWAY"
- " COND_NEW_ENEMY"
- " COND_SEE_ENEMY"
- " COND_CAN_RANGE_ATTACK1"
- " COND_CAN_RANGE_ATTACK2"
- " COND_CAN_MELEE_ATTACK1"
- " COND_CAN_MELEE_ATTACK2"
- )
-
- //=========================================================
- // Stagger because I got hit by something heavy
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_HUNTER_STAGGER,
-
- " Tasks"
- " TASK_HUNTER_STAGGER 0"
- ""
- " Interrupts"
- )
-
- //=========================================================
- // Run around randomly until we detect an enemy
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_HUNTER_PATROL_RUN,
-
- " Tasks"
- " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_COMBAT_FACE"
- " TASK_SET_ROUTE_SEARCH_TIME 5" // Spend 5 seconds trying to build a path if stuck
- " TASK_GET_PATH_TO_RANDOM_NODE 200"
- " TASK_RUN_PATH 0"
- " TASK_WAIT_FOR_MOVEMENT 0"
- ""
- " Interrupts"
- " COND_CAN_RANGE_ATTACK1 "
- " COND_CAN_RANGE_ATTACK2 "
- " COND_CAN_MELEE_ATTACK1 "
- " COND_CAN_MELEE_ATTACK2"
- " COND_GIVE_WAY"
- " COND_NEW_ENEMY"
- " COND_HEAR_COMBAT"
- " COND_HEAR_DANGER"
- " COND_HEAR_PLAYER"
- " COND_LIGHT_DAMAGE"
- " COND_HEAVY_DAMAGE"
- )
-
- //=========================================================
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_HUNTER_TAKE_COVER_FROM_ENEMY,
-
- " Tasks"
- " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_HUNTER_CHASE_ENEMY_MELEE"
- " TASK_HUNTER_CORNERED_TIMER 10.0"
- " TASK_WAIT 0.0"
- // " TASK_SET_TOLERANCE_DISTANCE 24"
- // " TASK_FIND_COVER_FROM_ENEMY 0"
- " TASK_FIND_FAR_NODE_COVER_FROM_ENEMY 200.0"
- " TASK_RUN_PATH 0"
- " TASK_HUNTER_CORNERED_TIMER 0.0"
- // " TASK_CLEAR_FAIL_SCHEDULE 0" // not used because sched_fail includes a one second pause. ick!
- " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_HUNTER_FAIL_IMMEDIATE"
- " TASK_WAIT_FOR_MOVEMENT 0"
- " TASK_REMEMBER MEMORY:INCOVER"
- " TASK_SET_SCHEDULE SCHEDULE:SCHED_HUNTER_HIDE_UNDER_COVER"
- /*
- " TASK_FACE_ENEMY 0"
- " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE" // Translated to cover
- " TASK_WAIT 1"
- */
- ""
- " Interrupts"
- " COND_NEW_ENEMY"
- " COND_HEAR_DANGER"
- )
-
- //=========================================================
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_HUNTER_HIDE_UNDER_COVER,
-
- " Tasks"
- " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_HUNTER_FAIL_IMMEDIATE" // used because sched_fail includes a one second pause. ick!
- " TASK_FACE_ENEMY 0"
- " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE" // Translated to cover
- " TASK_WAIT 1"
- ""
- " Interrupts"
- " COND_NEW_ENEMY"
- " COND_HEAR_DANGER"
- " COND_HAVE_ENEMY_LOS"
- )
-
- //=========================================================
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_HUNTER_FOUND_ENEMY,
-
- " Tasks"
- " TASK_STOP_MOVING 0"
- " TASK_FACE_ENEMY 0"
- " TASK_PLAY_SEQUENCE_FACE_ENEMY ACTIVITY:ACT_HUNTER_FOUND_ENEMY"
- ""
- " Interrupts"
- " COND_LIGHT_DAMAGE"
- " COND_HEAVY_DAMAGE"
- )
-
- //=========================================================
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_HUNTER_FOUND_ENEMY_ACK,
-
- " Tasks"
- " TASK_STOP_MOVING 0"
- " TASK_WAIT_RANDOM 0.75"
- " TASK_FACE_ENEMY 0"
- " TASK_PLAY_SEQUENCE_FACE_ENEMY ACTIVITY:ACT_HUNTER_FOUND_ENEMY_ACK"
- ""
- " Interrupts"
- " COND_LIGHT_DAMAGE"
- " COND_HEAVY_DAMAGE"
- )
-
- //=========================================================
- // An empty schedule that immediately bails out, faster than
- // SCHED_FAIL which stops moving and waits for one second.
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_HUNTER_FAIL_IMMEDIATE,
-
- " Tasks"
- " TASK_WAIT 0"
-
- )
-
- DEFINE_SCHEDULE
- (
- SCHED_HUNTER_GOTO_HINT,
- " Tasks"
- " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_HUNTER_CLEAR_HINTNODE" // used because sched_fail includes a one second pause. ick!
- " TASK_GET_PATH_TO_HINTNODE 1"
- " TASK_WAIT_FOR_MOVEMENT 0"
- " TASK_CLEAR_HINTNODE 0"
- ""
- ""
- " Interrupts"
- )
-
- DEFINE_SCHEDULE
- (
- SCHED_HUNTER_CLEAR_HINTNODE,
- " Tasks"
- " TASK_CLEAR_HINTNODE 0"
- ""
- ""
- " Interrupts"
- )
-
- DEFINE_SCHEDULE
- (
- SCHED_HUNTER_SIEGE_STAND,
- " Tasks"
- " TASK_STOP_MOVING 0"
- " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
- " TASK_FACE_PLAYER 0"
- " TASK_WAIT 10"
- " TASK_WAIT_RANDOM 2"
- " TASK_SET_SCHEDULE SCHEDULE:SCHED_HUNTER_CHANGE_POSITION_SIEGE"
- ""
- ""
- " Interrupts"
- " COND_SEE_PLAYER"
- " COND_NEW_ENEMY"
- )
-
- DEFINE_SCHEDULE
- (
- SCHED_HUNTER_CHANGE_POSITION_SIEGE,
-
- " Tasks"
- " TASK_STOP_MOVING 0"
- " TASK_WANDER 2400480"
- " TASK_RUN_PATH 0"
- " TASK_WAIT_FOR_MOVEMENT 0"
- " TASK_STOP_MOVING 0"
- " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
- " TASK_FACE_PLAYER 0"
- ""
- " Interrupts"
- " COND_NEW_ENEMY"
- )
-
- // formula is MIN_DIST * 10000 + MAX_DIST
-
-AI_END_CUSTOM_NPC()
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Small, fast version of the strider. Goes where striders cannot, such
+// as into buildings. Best killed with physics objects and explosives.
+//
+//=============================================================================
+
+#include "cbase.h"
+#include "npc_strider.h"
+#include "npc_hunter.h"
+#include "ai_behavior_follow.h"
+#include "ai_moveprobe.h"
+#include "ai_senses.h"
+#include "ai_speech.h"
+#include "ai_task.h"
+#include "ai_default.h"
+#include "ai_schedule.h"
+#include "ai_hull.h"
+#include "ai_baseactor.h"
+#include "ai_waypoint.h"
+#include "ai_link.h"
+#include "ai_hint.h"
+#include "ai_squadslot.h"
+#include "ai_squad.h"
+#include "ai_tacticalservices.h"
+#include "beam_shared.h"
+#include "datacache/imdlcache.h"
+#include "eventqueue.h"
+#include "gib.h"
+#include "globalstate.h"
+#include "hierarchy.h"
+#include "movevars_shared.h"
+#include "npcevent.h"
+#include "saverestore_utlvector.h"
+#include "particle_parse.h"
+#include "te_particlesystem.h"
+#include "sceneentity.h"
+#include "shake.h"
+#include "soundenvelope.h"
+#include "soundent.h"
+#include "SpriteTrail.h"
+#include "IEffects.h"
+#include "engine/IEngineSound.h"
+#include "bone_setup.h"
+#include "studio.h"
+#include "ai_route.h"
+#include "ammodef.h"
+#include "npc_bullseye.h"
+#include "physobj.h"
+#include "ai_memory.h"
+#include "collisionutils.h"
+#include "shot_manipulator.h"
+#include "steamjet.h"
+#include "physics_prop_ragdoll.h"
+#include "vehicle_base.h"
+#include "coordsize.h"
+#include "hl2_shareddefs.h"
+#include "te_effect_dispatch.h"
+#include "beam_flags.h"
+#include "prop_combine_ball.h"
+#include "explode.h"
+#include "weapon_physcannon.h"
+#include "weapon_striderbuster.h"
+#include "monstermaker.h"
+#include "weapon_rpg.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+class CNPC_Hunter;
+
+
+static const char *HUNTER_FLECHETTE_MODEL = "models/weapons/hunter_flechette.mdl";
+
+// Think contexts
+static const char *HUNTER_BLEED_THINK = "HunterBleed";
+static const char *HUNTER_ZAP_THINK = "HunterZap";
+static const char *HUNTER_JOSTLE_VEHICLE_THINK = "HunterJostle";
+
+
+ConVar sk_hunter_health( "sk_hunter_health", "210" );
+
+// Melee attacks
+ConVar sk_hunter_dmg_one_slash( "sk_hunter_dmg_one_slash", "20" );
+ConVar sk_hunter_dmg_charge( "sk_hunter_dmg_charge", "20" );
+
+// Flechette volley attack
+ConVar hunter_flechette_max_range( "hunter_flechette_max_range", "1200" );
+ConVar hunter_flechette_min_range( "hunter_flechette_min_range", "100" );
+ConVar hunter_flechette_volley_size( "hunter_flechette_volley_size", "8" );
+ConVar hunter_flechette_speed( "hunter_flechette_speed", "2000" );
+ConVar sk_hunter_dmg_flechette( "sk_hunter_dmg_flechette", "4.0" );
+ConVar sk_hunter_flechette_explode_dmg( "sk_hunter_flechette_explode_dmg", "12.0" );
+ConVar sk_hunter_flechette_explode_radius( "sk_hunter_flechette_explode_radius", "128.0" );
+ConVar hunter_flechette_explode_delay( "hunter_flechette_explode_delay", "2.5" );
+ConVar hunter_flechette_delay( "hunter_flechette_delay", "0.1" );
+ConVar hunter_first_flechette_delay( "hunter_first_flechette_delay", "0.5" );
+ConVar hunter_flechette_max_concurrent_volleys( "hunter_flechette_max_concurrent_volleys", "2" );
+ConVar hunter_flechette_volley_start_min_delay( "hunter_flechette_volley_start_min_delay", ".25" );
+ConVar hunter_flechette_volley_start_max_delay( "hunter_flechette_volley_start_max_delay", ".95" );
+ConVar hunter_flechette_volley_end_min_delay( "hunter_flechette_volley_end_min_delay", "1" );
+ConVar hunter_flechette_volley_end_max_delay( "hunter_flechette_volley_end_max_delay", "2" );
+ConVar hunter_flechette_test( "hunter_flechette_test", "0" );
+ConVar hunter_clamp_shots( "hunter_clamp_shots", "1" );
+ConVar hunter_cheap_explosions( "hunter_cheap_explosions", "1" );
+
+// Damage received
+ConVar sk_hunter_bullet_damage_scale( "sk_hunter_bullet_damage_scale", "0.6" );
+ConVar sk_hunter_charge_damage_scale( "sk_hunter_charge_damage_scale", "2.0" );
+ConVar sk_hunter_buckshot_damage_scale( "sk_hunter_buckshot_damage_scale", "0.5" );
+ConVar sk_hunter_vehicle_damage_scale( "sk_hunter_vehicle_damage_scale", "2.2" );
+ConVar sk_hunter_dmg_from_striderbuster( "sk_hunter_dmg_from_striderbuster", "150" );
+ConVar sk_hunter_citizen_damage_scale( "sk_hunter_citizen_damage_scale", "0.3" );
+
+ConVar hunter_allow_dissolve( "hunter_allow_dissolve", "1" );
+ConVar hunter_random_expressions( "hunter_random_expressions", "0" );
+ConVar hunter_show_weapon_los_z( "hunter_show_weapon_los_z", "0" );
+ConVar hunter_show_weapon_los_condition( "hunter_show_weapon_los_condition", "0" );
+
+ConVar hunter_melee_delay( "hunter_melee_delay", "2.0" );
+
+// Bullrush charge.
+ConVar hunter_charge( "hunter_charge", "1" );
+ConVar hunter_charge_min_delay( "hunter_charge_min_delay", "10.0" );
+ConVar hunter_charge_pct( "hunter_charge_pct", "25" );
+ConVar hunter_charge_test( "hunter_charge_test", "0" );
+
+// Vehicle dodging.
+ConVar hunter_dodge_warning( "hunter_dodge_warning", "1.1" );
+ConVar hunter_dodge_warning_width( "hunter_dodge_warning_width", "180" );
+ConVar hunter_dodge_warning_cone( "hunter_dodge_warning_cone", ".5" );
+ConVar hunter_dodge_debug( "hunter_dodge_debug", "0" );
+
+// Jostle vehicles when hit by them
+ConVar hunter_jostle_car_min_speed( "hunter_jostle_car_min_speed", "100" ); // If hit by a car going at least this fast, jostle the car
+ConVar hunter_jostle_car_max_speed( "hunter_jostle_car_max_speed", "600" ); // Used for determining jostle scale
+
+ConVar hunter_free_knowledge( "hunter_free_knowledge", "10.0" );
+ConVar hunter_plant_adjust_z( "hunter_plant_adjust_z", "12" );
+
+ConVar hunter_disable_patrol( "hunter_disable_patrol", "0" );
+
+// Dealing with striderbusters
+ConVar hunter_hate_held_striderbusters( "hunter_hate_held_striderbusters", "1" );
+ConVar hunter_hate_thrown_striderbusters( "hunter_hate_thrown_striderbusters", "1" );
+ConVar hunter_hate_attached_striderbusters( "hunter_hate_attached_striderbusters", "1" );
+ConVar hunter_hate_held_striderbusters_delay( "hunter_hate_held_striderbusters_delay", "0.5" );
+ConVar hunter_hate_held_striderbusters_tolerance( "hunter_hate_held_striderbusters_tolerance", "2000.0" );
+ConVar hunter_hate_thrown_striderbusters_tolerance( "hunter_hate_thrown_striderbusters_tolerance", "300.0" );
+ConVar hunter_seek_thrown_striderbusters_tolerance( "hunter_seek_thrown_striderbusters_tolerance", "400.0" );
+ConVar hunter_retreat_striderbusters( "hunter_retreat_striderbusters", "1", FCVAR_NONE, "If true, the hunter will retreat when a buster is glued to him." );
+
+ConVar hunter_allow_nav_jump( "hunter_allow_nav_jump", "0" );
+ConVar g_debug_hunter_charge( "g_debug_hunter_charge", "0" );
+
+ConVar hunter_stand_still( "hunter_stand_still", "0" ); // used for debugging, keeps them rooted in place
+
+ConVar hunter_siege_frequency( "hunter_siege_frequency", "12" );
+
+#define HUNTER_FOV_DOT 0.0 // 180 degree field of view
+#define HUNTER_CHARGE_MIN 256
+#define HUNTER_CHARGE_MAX 1024
+#define HUNTER_FACE_ENEMY_DIST 512.0f
+#define HUNTER_MELEE_REACH 80
+#define HUNTER_BLOOD_LEFT_FOOT 0
+#define HUNTER_IGNORE_ENEMY_TIME 5 // How long the hunter will ignore another enemy when distracted by the player.
+
+#define HUNTER_FACING_DOT 0.8 // The angle within which we start shooting
+#define HUNTER_SHOOT_MAX_YAW_DEG 60.0f // Once shooting, clamp to +/- these degrees of yaw deflection as our target moves
+#define HUNTER_SHOOT_MAX_YAW_COS 0.5f // The cosine of the above angle
+
+#define HUNTER_FLECHETTE_WARN_TIME 1.0f
+
+#define HUNTER_SEE_ENEMY_TIME_INVALID -1
+
+#define NUM_FLECHETTE_VOLLEY_ON_FOLLOW 4
+
+#define HUNTER_SIEGE_MAX_DIST_MODIFIER 2.0f
+
+//-----------------------------------------------------------------------------
+// Animation events
+//-----------------------------------------------------------------------------
+int AE_HUNTER_FOOTSTEP_LEFT;
+int AE_HUNTER_FOOTSTEP_RIGHT;
+int AE_HUNTER_FOOTSTEP_BACK;
+int AE_HUNTER_MELEE_ANNOUNCE;
+int AE_HUNTER_MELEE_ATTACK_LEFT;
+int AE_HUNTER_MELEE_ATTACK_RIGHT;
+int AE_HUNTER_DIE;
+int AE_HUNTER_SPRAY_BLOOD;
+int AE_HUNTER_START_EXPRESSION;
+int AE_HUNTER_END_EXPRESSION;
+
+
+//-----------------------------------------------------------------------------
+// Interactions.
+//-----------------------------------------------------------------------------
+int g_interactionHunterFoundEnemy = 0;
+
+
+//-----------------------------------------------------------------------------
+// Local stuff.
+//-----------------------------------------------------------------------------
+static string_t s_iszStriderClassname;
+static string_t s_iszStriderBusterClassname;
+static string_t s_iszMagnadeClassname;
+static string_t s_iszPhysPropClassname;
+static string_t s_iszHuntersToRunOver;
+
+
+//-----------------------------------------------------------------------------
+// Custom Activities
+//-----------------------------------------------------------------------------
+Activity ACT_HUNTER_DEPLOYRA2;
+Activity ACT_HUNTER_DODGER;
+Activity ACT_HUNTER_DODGEL;
+Activity ACT_HUNTER_GESTURE_SHOOT;
+Activity ACT_HUNTER_FLINCH_STICKYBOMB;
+Activity ACT_HUNTER_STAGGER;
+Activity ACT_HUNTER_MELEE_ATTACK1_VS_PLAYER;
+Activity ACT_DI_HUNTER_MELEE;
+Activity ACT_DI_HUNTER_THROW;
+Activity ACT_HUNTER_ANGRY;
+Activity ACT_HUNTER_WALK_ANGRY;
+Activity ACT_HUNTER_FOUND_ENEMY;
+Activity ACT_HUNTER_FOUND_ENEMY_ACK;
+Activity ACT_HUNTER_CHARGE_START;
+Activity ACT_HUNTER_CHARGE_RUN;
+Activity ACT_HUNTER_CHARGE_STOP;
+Activity ACT_HUNTER_CHARGE_CRASH;
+Activity ACT_HUNTER_CHARGE_HIT;
+Activity ACT_HUNTER_RANGE_ATTACK2_UNPLANTED;
+Activity ACT_HUNTER_IDLE_PLANTED;
+Activity ACT_HUNTER_FLINCH_N;
+Activity ACT_HUNTER_FLINCH_S;
+Activity ACT_HUNTER_FLINCH_E;
+Activity ACT_HUNTER_FLINCH_W;
+
+
+//-----------------------------------------------------------------------------
+// Squad slots
+//-----------------------------------------------------------------------------
+enum SquadSlot_t
+{
+ SQUAD_SLOT_HUNTER_CHARGE = LAST_SHARED_SQUADSLOT,
+ SQUAD_SLOT_HUNTER_FLANK_FIRST,
+ SQUAD_SLOT_HUNTER_FLANK_LAST = SQUAD_SLOT_HUNTER_FLANK_FIRST,
+ SQUAD_SLOT_RUN_SHOOT,
+};
+
+#define HUNTER_FOLLOW_DISTANCE 2000.0f
+#define HUNTER_FOLLOW_DISTANCE_SQR (HUNTER_FOLLOW_DISTANCE * HUNTER_FOLLOW_DISTANCE)
+
+#define HUNTER_RUNDOWN_SQUADDATA 0
+
+
+//-----------------------------------------------------------------------------
+// We're doing this quite a lot, so this makes the check a lot faster since
+// we don't have to compare strings.
+//-----------------------------------------------------------------------------
+bool IsStriderBuster( CBaseEntity *pEntity )
+{
+ if ( !pEntity )
+ return false;
+
+ if( pEntity->m_iClassname == s_iszStriderBusterClassname ||
+ pEntity->m_iClassname == s_iszMagnadeClassname)
+ return true;
+
+ return false;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+bool HateThisStriderBuster( CBaseEntity *pTarget )
+{
+ if ( StriderBuster_WasKnockedOffStrider(pTarget) )
+ return false;
+
+ if ( pTarget->VPhysicsGetObject() )
+ {
+ if ( hunter_hate_held_striderbusters.GetBool() ||
+ hunter_hate_thrown_striderbusters.GetBool() ||
+ hunter_hate_attached_striderbusters.GetBool() )
+ {
+ if ( ( pTarget->VPhysicsGetObject()->GetGameFlags() & ( FVPHYSICS_PLAYER_HELD | FVPHYSICS_WAS_THROWN ) ) )
+ {
+ return true;
+ }
+
+ if ( StriderBuster_IsAttachedStriderBuster( pTarget ) )
+ {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+
+//-----------------------------------------------------------------------------
+// The hunter can fire a volley of explosive flechettes.
+//-----------------------------------------------------------------------------
+static const char *s_szHunterFlechetteBubbles = "HunterFlechetteBubbles";
+static const char *s_szHunterFlechetteSeekThink = "HunterFlechetteSeekThink";
+static const char *s_szHunterFlechetteDangerSoundThink = "HunterFlechetteDangerSoundThink";
+static const char *s_szHunterFlechetteSpriteTrail = "sprites/bluelaser1.vmt";
+static int s_nHunterFlechetteImpact = -2;
+static int s_nFlechetteFuseAttach = -1;
+
+#define FLECHETTE_AIR_VELOCITY 2500
+
+class CHunterFlechette : public CPhysicsProp, public IParentPropInteraction
+{
+ DECLARE_CLASS( CHunterFlechette, CPhysicsProp );
+
+public:
+
+ CHunterFlechette();
+ ~CHunterFlechette();
+
+ Class_T Classify() { return CLASS_NONE; }
+
+ bool WasThrownBack()
+ {
+ return m_bThrownBack;
+ }
+
+public:
+
+ void Spawn();
+ void Activate();
+ void Precache();
+ void Shoot( Vector &vecVelocity, bool bBright );
+ void SetSeekTarget( CBaseEntity *pTargetEntity );
+ void Explode();
+
+ bool CreateVPhysics();
+
+ unsigned int PhysicsSolidMaskForEntity() const;
+ static CHunterFlechette *FlechetteCreate( const Vector &vecOrigin, const QAngle &angAngles, CBaseEntity *pentOwner = NULL );
+
+ // IParentPropInteraction
+ void OnParentCollisionInteraction( parentCollisionInteraction_t eType, int index, gamevcollisionevent_t *pEvent );
+ void OnParentPhysGunDrop( CBasePlayer *pPhysGunUser, PhysGunDrop_t Reason );
+
+protected:
+
+ void SetupGlobalModelData();
+
+ void StickTo( CBaseEntity *pOther, trace_t &tr );
+
+ void BubbleThink();
+ void DangerSoundThink();
+ void ExplodeThink();
+ void DopplerThink();
+ void SeekThink();
+
+ bool CreateSprites( bool bBright );
+
+ void FlechetteTouch( CBaseEntity *pOther );
+
+ Vector m_vecShootPosition;
+ EHANDLE m_hSeekTarget;
+ bool m_bThrownBack;
+
+ DECLARE_DATADESC();
+ //DECLARE_SERVERCLASS();
+};
+
+LINK_ENTITY_TO_CLASS( hunter_flechette, CHunterFlechette );
+
+BEGIN_DATADESC( CHunterFlechette )
+
+ DEFINE_THINKFUNC( BubbleThink ),
+ DEFINE_THINKFUNC( DangerSoundThink ),
+ DEFINE_THINKFUNC( ExplodeThink ),
+ DEFINE_THINKFUNC( DopplerThink ),
+ DEFINE_THINKFUNC( SeekThink ),
+
+ DEFINE_ENTITYFUNC( FlechetteTouch ),
+
+ DEFINE_FIELD( m_vecShootPosition, FIELD_POSITION_VECTOR ),
+ DEFINE_FIELD( m_hSeekTarget, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_bThrownBack, FIELD_BOOLEAN ),
+
+END_DATADESC()
+
+//IMPLEMENT_SERVERCLASS_ST( CHunterFlechette, DT_HunterFlechette )
+//END_SEND_TABLE()
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+CHunterFlechette *CHunterFlechette::FlechetteCreate( const Vector &vecOrigin, const QAngle &angAngles, CBaseEntity *pentOwner )
+{
+ // Create a new entity with CHunterFlechette private data
+ CHunterFlechette *pFlechette = (CHunterFlechette *)CreateEntityByName( "hunter_flechette" );
+ UTIL_SetOrigin( pFlechette, vecOrigin );
+ pFlechette->SetAbsAngles( angAngles );
+ pFlechette->Spawn();
+ pFlechette->Activate();
+ pFlechette->SetOwnerEntity( pentOwner );
+
+ return pFlechette;
+}
+
+
+//------------------------------------------------------------------------------
+//------------------------------------------------------------------------------
+void CC_Hunter_Shoot_Flechette( const CCommand& args )
+{
+ MDLCACHE_CRITICAL_SECTION();
+
+ bool allowPrecache = CBaseEntity::IsPrecacheAllowed();
+ CBaseEntity::SetAllowPrecache( true );
+
+ CBasePlayer *pPlayer = UTIL_GetCommandClient();
+
+ QAngle angEye = pPlayer->EyeAngles();
+ CHunterFlechette *entity = CHunterFlechette::FlechetteCreate( pPlayer->EyePosition(), angEye, pPlayer );
+ if ( entity )
+ {
+ entity->Precache();
+ DispatchSpawn( entity );
+
+ // Shoot the flechette.
+ Vector forward;
+ pPlayer->EyeVectors( &forward );
+ forward *= 2000.0f;
+ entity->Shoot( forward, false );
+ }
+
+ CBaseEntity::SetAllowPrecache( allowPrecache );
+}
+
+static ConCommand ent_create("hunter_shoot_flechette", CC_Hunter_Shoot_Flechette, "Fires a hunter flechette where the player is looking.", FCVAR_GAMEDLL | FCVAR_CHEAT);
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+CHunterFlechette::CHunterFlechette()
+{
+ UseClientSideAnimation();
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+CHunterFlechette::~CHunterFlechette()
+{
+}
+
+
+//-----------------------------------------------------------------------------
+// If set, the flechette will seek unerringly toward the target as it flies.
+//-----------------------------------------------------------------------------
+void CHunterFlechette::SetSeekTarget( CBaseEntity *pTargetEntity )
+{
+ if ( pTargetEntity )
+ {
+ m_hSeekTarget = pTargetEntity;
+ SetContextThink( &CHunterFlechette::SeekThink, gpGlobals->curtime, s_szHunterFlechetteSeekThink );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+bool CHunterFlechette::CreateVPhysics()
+{
+ // Create the object in the physics system
+ VPhysicsInitNormal( SOLID_BBOX, FSOLID_NOT_STANDABLE, false );
+
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+unsigned int CHunterFlechette::PhysicsSolidMaskForEntity() const
+{
+ return ( BaseClass::PhysicsSolidMaskForEntity() | CONTENTS_HITBOX ) & ~CONTENTS_GRATE;
+}
+
+
+//-----------------------------------------------------------------------------
+// Called from CPropPhysics code when we're attached to a physics object.
+//-----------------------------------------------------------------------------
+void CHunterFlechette::OnParentCollisionInteraction( parentCollisionInteraction_t eType, int index, gamevcollisionevent_t *pEvent )
+{
+ if ( eType == COLLISIONINTER_PARENT_FIRST_IMPACT )
+ {
+ m_bThrownBack = true;
+ Explode();
+ }
+}
+
+void CHunterFlechette::OnParentPhysGunDrop( CBasePlayer *pPhysGunUser, PhysGunDrop_t Reason )
+{
+ m_bThrownBack = true;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+bool CHunterFlechette::CreateSprites( bool bBright )
+{
+ if ( bBright )
+ {
+ DispatchParticleEffect( "hunter_flechette_trail_striderbuster", PATTACH_ABSORIGIN_FOLLOW, this );
+ }
+ else
+ {
+ DispatchParticleEffect( "hunter_flechette_trail", PATTACH_ABSORIGIN_FOLLOW, this );
+ }
+
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CHunterFlechette::Spawn()
+{
+ Precache( );
+
+ SetModel( HUNTER_FLECHETTE_MODEL );
+ SetMoveType( MOVETYPE_FLYGRAVITY, MOVECOLLIDE_FLY_CUSTOM );
+ UTIL_SetSize( this, -Vector(1,1,1), Vector(1,1,1) );
+ SetSolid( SOLID_BBOX );
+ SetGravity( 0.05f );
+ SetCollisionGroup( COLLISION_GROUP_PROJECTILE );
+
+ // Make sure we're updated if we're underwater
+ UpdateWaterState();
+
+ SetTouch( &CHunterFlechette::FlechetteTouch );
+
+ // Make us glow until we've hit the wall
+ m_nSkin = 1;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CHunterFlechette::Activate()
+{
+ BaseClass::Activate();
+ SetupGlobalModelData();
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CHunterFlechette::SetupGlobalModelData()
+{
+ if ( s_nHunterFlechetteImpact == -2 )
+ {
+ s_nHunterFlechetteImpact = LookupSequence( "impact" );
+ s_nFlechetteFuseAttach = LookupAttachment( "attach_fuse" );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CHunterFlechette::Precache()
+{
+ PrecacheModel( HUNTER_FLECHETTE_MODEL );
+ PrecacheModel( "sprites/light_glow02_noz.vmt" );
+
+ PrecacheScriptSound( "NPC_Hunter.FlechetteNearmiss" );
+ PrecacheScriptSound( "NPC_Hunter.FlechetteHitBody" );
+ PrecacheScriptSound( "NPC_Hunter.FlechetteHitWorld" );
+ PrecacheScriptSound( "NPC_Hunter.FlechettePreExplode" );
+ PrecacheScriptSound( "NPC_Hunter.FlechetteExplode" );
+
+ PrecacheParticleSystem( "hunter_flechette_trail_striderbuster" );
+ PrecacheParticleSystem( "hunter_flechette_trail" );
+ PrecacheParticleSystem( "hunter_projectile_explosion_1" );
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CHunterFlechette::StickTo( CBaseEntity *pOther, trace_t &tr )
+{
+ EmitSound( "NPC_Hunter.FlechetteHitWorld" );
+
+ SetMoveType( MOVETYPE_NONE );
+
+ if ( !pOther->IsWorld() )
+ {
+ SetParent( pOther );
+ SetSolid( SOLID_NONE );
+ SetSolidFlags( FSOLID_NOT_SOLID );
+ }
+
+ // Do an impact effect.
+ //Vector vecDir = GetAbsVelocity();
+ //float speed = VectorNormalize( vecDir );
+
+ //Vector vForward;
+ //AngleVectors( GetAbsAngles(), &vForward );
+ //VectorNormalize ( vForward );
+
+ //CEffectData data;
+ //data.m_vOrigin = tr.endpos;
+ //data.m_vNormal = vForward;
+ //data.m_nEntIndex = 0;
+ //DispatchEffect( "BoltImpact", data );
+
+ Vector vecVelocity = GetAbsVelocity();
+ bool bAttachedToBuster = StriderBuster_OnFlechetteAttach( pOther, vecVelocity );
+
+ SetTouch( NULL );
+
+ // We're no longer flying. Stop checking for water volumes.
+ SetContextThink( NULL, 0, s_szHunterFlechetteBubbles );
+
+ // Stop seeking.
+ m_hSeekTarget = NULL;
+ SetContextThink( NULL, 0, s_szHunterFlechetteSeekThink );
+
+ // Get ready to explode.
+ if ( !bAttachedToBuster )
+ {
+ SetThink( &CHunterFlechette::DangerSoundThink );
+ SetNextThink( gpGlobals->curtime + (hunter_flechette_explode_delay.GetFloat() - HUNTER_FLECHETTE_WARN_TIME) );
+ }
+ else
+ {
+ DangerSoundThink();
+ }
+
+ // Play our impact animation.
+ ResetSequence( s_nHunterFlechetteImpact );
+
+ static int s_nImpactCount = 0;
+ s_nImpactCount++;
+ if ( s_nImpactCount & 0x01 )
+ {
+ UTIL_ImpactTrace( &tr, DMG_BULLET );
+
+ // Shoot some sparks
+ if ( UTIL_PointContents( GetAbsOrigin() ) != CONTENTS_WATER)
+ {
+ g_pEffects->Sparks( GetAbsOrigin() );
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CHunterFlechette::FlechetteTouch( CBaseEntity *pOther )
+{
+ if ( pOther->IsSolidFlagSet(FSOLID_VOLUME_CONTENTS | FSOLID_TRIGGER) )
+ {
+ // Some NPCs are triggers that can take damage (like antlion grubs). We should hit them.
+ if ( ( pOther->m_takedamage == DAMAGE_NO ) || ( pOther->m_takedamage == DAMAGE_EVENTS_ONLY ) )
+ return;
+ }
+
+ if ( FClassnameIs( pOther, "hunter_flechette" ) )
+ return;
+
+ trace_t tr;
+ tr = BaseClass::GetTouchTrace();
+
+ if ( pOther->m_takedamage != DAMAGE_NO )
+ {
+ Vector vecNormalizedVel = GetAbsVelocity();
+
+ ClearMultiDamage();
+ VectorNormalize( vecNormalizedVel );
+
+ float flDamage = sk_hunter_dmg_flechette.GetFloat();
+ CBreakable *pBreak = dynamic_cast <CBreakable *>(pOther);
+ if ( pBreak && ( pBreak->GetMaterialType() == matGlass ) )
+ {
+ flDamage = MAX( pOther->GetHealth(), flDamage );
+ }
+
+ CTakeDamageInfo dmgInfo( this, GetOwnerEntity(), flDamage, DMG_DISSOLVE | DMG_NEVERGIB );
+ CalculateMeleeDamageForce( &dmgInfo, vecNormalizedVel, tr.endpos, 0.7f );
+ dmgInfo.SetDamagePosition( tr.endpos );
+ pOther->DispatchTraceAttack( dmgInfo, vecNormalizedVel, &tr );
+
+ ApplyMultiDamage();
+
+ // Keep going through breakable glass.
+ if ( pOther->GetCollisionGroup() == COLLISION_GROUP_BREAKABLE_GLASS )
+ return;
+
+ SetAbsVelocity( Vector( 0, 0, 0 ) );
+
+ // play body "thwack" sound
+ EmitSound( "NPC_Hunter.FlechetteHitBody" );
+
+ StopParticleEffects( this );
+
+ Vector vForward;
+ AngleVectors( GetAbsAngles(), &vForward );
+ VectorNormalize ( vForward );
+
+ trace_t tr2;
+ UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() + vForward * 128, MASK_BLOCKLOS, pOther, COLLISION_GROUP_NONE, &tr2 );
+
+ if ( tr2.fraction != 1.0f )
+ {
+ //NDebugOverlay::Box( tr2.endpos, Vector( -16, -16, -16 ), Vector( 16, 16, 16 ), 0, 255, 0, 0, 10 );
+ //NDebugOverlay::Box( GetAbsOrigin(), Vector( -16, -16, -16 ), Vector( 16, 16, 16 ), 0, 0, 255, 0, 10 );
+
+ if ( tr2.m_pEnt == NULL || ( tr2.m_pEnt && tr2.m_pEnt->GetMoveType() == MOVETYPE_NONE ) )
+ {
+ CEffectData data;
+
+ data.m_vOrigin = tr2.endpos;
+ data.m_vNormal = vForward;
+ data.m_nEntIndex = tr2.fraction != 1.0f;
+
+ //DispatchEffect( "BoltImpact", data );
+ }
+ }
+
+ if ( ( ( pOther->GetMoveType() == MOVETYPE_VPHYSICS ) || ( pOther->GetMoveType() == MOVETYPE_PUSH ) ) && ( ( pOther->GetHealth() > 0 ) || ( pOther->m_takedamage == DAMAGE_EVENTS_ONLY ) ) )
+ {
+ CPhysicsProp *pProp = dynamic_cast<CPhysicsProp *>( pOther );
+ if ( pProp )
+ {
+ pProp->SetInteraction( PROPINTER_PHYSGUN_NOTIFY_CHILDREN );
+ }
+
+ // We hit a physics object that survived the impact. Stick to it.
+ StickTo( pOther, tr );
+ }
+ else
+ {
+ SetTouch( NULL );
+ SetThink( NULL );
+ SetContextThink( NULL, 0, s_szHunterFlechetteBubbles );
+
+ UTIL_Remove( this );
+ }
+ }
+ else
+ {
+ // See if we struck the world
+ if ( pOther->GetMoveType() == MOVETYPE_NONE && !( tr.surface.flags & SURF_SKY ) )
+ {
+ // We hit a physics object that survived the impact. Stick to it.
+ StickTo( pOther, tr );
+ }
+ else if( pOther->GetMoveType() == MOVETYPE_PUSH && FClassnameIs(pOther, "func_breakable") )
+ {
+ // We hit a func_breakable, stick to it.
+ // The MOVETYPE_PUSH is a micro-optimization to cut down on the classname checks.
+ StickTo( pOther, tr );
+ }
+ else
+ {
+ // Put a mark unless we've hit the sky
+ if ( ( tr.surface.flags & SURF_SKY ) == false )
+ {
+ UTIL_ImpactTrace( &tr, DMG_BULLET );
+ }
+
+ UTIL_Remove( this );
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Fixup flechette position when seeking towards a striderbuster.
+//-----------------------------------------------------------------------------
+void CHunterFlechette::SeekThink()
+{
+ if ( m_hSeekTarget )
+ {
+ Vector vecBodyTarget = m_hSeekTarget->BodyTarget( GetAbsOrigin() );
+
+ Vector vecClosest;
+ CalcClosestPointOnLineSegment( GetAbsOrigin(), m_vecShootPosition, vecBodyTarget, vecClosest, NULL );
+
+ Vector vecDelta = vecBodyTarget - m_vecShootPosition;
+ VectorNormalize( vecDelta );
+
+ QAngle angShoot;
+ VectorAngles( vecDelta, angShoot );
+
+ float flSpeed = hunter_flechette_speed.GetFloat();
+ if ( !flSpeed )
+ {
+ flSpeed = 2500.0f;
+ }
+
+ Vector vecVelocity = vecDelta * flSpeed;
+ Teleport( &vecClosest, &angShoot, &vecVelocity );
+
+ SetNextThink( gpGlobals->curtime, s_szHunterFlechetteSeekThink );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Play a near miss sound as we travel past the player.
+//-----------------------------------------------------------------------------
+void CHunterFlechette::DopplerThink()
+{
+ CBasePlayer *pPlayer = AI_GetSinglePlayer();
+ if ( !pPlayer )
+ return;
+
+ Vector vecVelocity = GetAbsVelocity();
+ VectorNormalize( vecVelocity );
+
+ float flMyDot = DotProduct( vecVelocity, GetAbsOrigin() );
+ float flPlayerDot = DotProduct( vecVelocity, pPlayer->GetAbsOrigin() );
+
+ if ( flPlayerDot <= flMyDot )
+ {
+ EmitSound( "NPC_Hunter.FlechetteNearMiss" );
+
+ // We've played the near miss sound and we're not seeking. Stop thinking.
+ SetThink( NULL );
+ }
+ else
+ {
+ SetNextThink( gpGlobals->curtime );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Think every 0.1 seconds to make bubbles if we're flying through water.
+//-----------------------------------------------------------------------------
+void CHunterFlechette::BubbleThink()
+{
+ SetNextThink( gpGlobals->curtime + 0.1f, s_szHunterFlechetteBubbles );
+
+ if ( GetWaterLevel() == 0 )
+ return;
+
+ UTIL_BubbleTrail( GetAbsOrigin() - GetAbsVelocity() * 0.1f, GetAbsOrigin(), 5 );
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CHunterFlechette::Shoot( Vector &vecVelocity, bool bBrightFX )
+{
+ CreateSprites( bBrightFX );
+
+ m_vecShootPosition = GetAbsOrigin();
+
+ SetAbsVelocity( vecVelocity );
+
+ SetThink( &CHunterFlechette::DopplerThink );
+ SetNextThink( gpGlobals->curtime );
+
+ SetContextThink( &CHunterFlechette::BubbleThink, gpGlobals->curtime + 0.1, s_szHunterFlechetteBubbles );
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CHunterFlechette::DangerSoundThink()
+{
+ EmitSound( "NPC_Hunter.FlechettePreExplode" );
+
+ CSoundEnt::InsertSound( SOUND_DANGER|SOUND_CONTEXT_EXCLUDE_COMBINE, GetAbsOrigin(), 150.0f, 0.5, this );
+ SetThink( &CHunterFlechette::ExplodeThink );
+ SetNextThink( gpGlobals->curtime + HUNTER_FLECHETTE_WARN_TIME );
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CHunterFlechette::ExplodeThink()
+{
+ Explode();
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CHunterFlechette::Explode()
+{
+ SetSolid( SOLID_NONE );
+
+ // Don't catch self in own explosion!
+ m_takedamage = DAMAGE_NO;
+
+ EmitSound( "NPC_Hunter.FlechetteExplode" );
+
+ // Move the explosion effect to the tip to reduce intersection with the world.
+ Vector vecFuse;
+ GetAttachment( s_nFlechetteFuseAttach, vecFuse );
+ DispatchParticleEffect( "hunter_projectile_explosion_1", vecFuse, GetAbsAngles(), NULL );
+
+ int nDamageType = DMG_DISSOLVE;
+
+ // Perf optimization - only every other explosion makes a physics force. This is
+ // hardly noticeable since flechettes usually explode in clumps.
+ static int s_nExplosionCount = 0;
+ s_nExplosionCount++;
+ if ( ( s_nExplosionCount & 0x01 ) && hunter_cheap_explosions.GetBool() )
+ {
+ nDamageType |= DMG_PREVENT_PHYSICS_FORCE;
+ }
+
+ RadiusDamage( CTakeDamageInfo( this, GetOwnerEntity(), sk_hunter_flechette_explode_dmg.GetFloat(), nDamageType ), GetAbsOrigin(), sk_hunter_flechette_explode_radius.GetFloat(), CLASS_NONE, NULL );
+
+ AddEffects( EF_NODRAW );
+
+ SetThink( &CBaseEntity::SUB_Remove );
+ SetNextThink( gpGlobals->curtime + 0.1f );
+}
+
+
+//-----------------------------------------------------------------------------
+// Calculate & apply damage & force for a charge to a target.
+// Done outside of the hunter because we need to do this inside a trace filter.
+//-----------------------------------------------------------------------------
+void Hunter_ApplyChargeDamage( CBaseEntity *pHunter, CBaseEntity *pTarget, float flDamage )
+{
+ Vector attackDir = ( pTarget->WorldSpaceCenter() - pHunter->WorldSpaceCenter() );
+ VectorNormalize( attackDir );
+ Vector offset = RandomVector( -32, 32 ) + pTarget->WorldSpaceCenter();
+
+ // Generate enough force to make a 75kg guy move away at 700 in/sec
+ Vector vecForce = attackDir * ImpulseScale( 75, 700 );
+
+ // Deal the damage
+ CTakeDamageInfo info( pHunter, pHunter, vecForce, offset, flDamage, DMG_CLUB );
+ pTarget->TakeDamage( info );
+}
+
+
+//-----------------------------------------------------------------------------
+// A simple trace filter class to skip small moveable physics objects
+//-----------------------------------------------------------------------------
+class CHunterTraceFilterSkipPhysics : public CTraceFilter
+{
+public:
+ // It does have a base, but we'll never network anything below here..
+ DECLARE_CLASS_NOBASE( CHunterTraceFilterSkipPhysics );
+
+ CHunterTraceFilterSkipPhysics( const IHandleEntity *passentity, int collisionGroup, float minMass )
+ : m_pPassEnt(passentity), m_collisionGroup(collisionGroup), m_minMass(minMass)
+ {
+ }
+ virtual bool ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask )
+ {
+ if ( !StandardFilterRules( pHandleEntity, contentsMask ) )
+ return false;
+
+ if ( !PassServerEntityFilter( pHandleEntity, m_pPassEnt ) )
+ return false;
+
+ // Don't test if the game code tells us we should ignore this collision...
+ CBaseEntity *pEntity = EntityFromEntityHandle( pHandleEntity );
+ if ( pEntity )
+ {
+ if ( !pEntity->ShouldCollide( m_collisionGroup, contentsMask ) )
+ return false;
+
+ if ( !g_pGameRules->ShouldCollide( m_collisionGroup, pEntity->GetCollisionGroup() ) )
+ return false;
+
+ // don't test small moveable physics objects (unless it's an NPC)
+ if ( !pEntity->IsNPC() && pEntity->GetMoveType() == MOVETYPE_VPHYSICS )
+ {
+ float entMass = PhysGetEntityMass( pEntity ) ;
+ if ( entMass < m_minMass )
+ {
+ if ( entMass < m_minMass * 0.666f || pEntity->CollisionProp()->BoundingRadius() < (assert_cast<const CAI_BaseNPC *>(EntityFromEntityHandle( m_pPassEnt )))->GetHullHeight() )
+ {
+ return false;
+ }
+ }
+ }
+
+ // If we hit an antlion, don't stop, but kill it
+ if ( pEntity->Classify() == CLASS_ANTLION )
+ {
+ CBaseEntity *pHunter = (CBaseEntity *)EntityFromEntityHandle( m_pPassEnt );
+ Hunter_ApplyChargeDamage( pHunter, pEntity, pEntity->GetHealth() );
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+private:
+ const IHandleEntity *m_pPassEnt;
+ int m_collisionGroup;
+ float m_minMass;
+};
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+inline void HunterTraceHull_SkipPhysics( const Vector &vecAbsStart, const Vector &vecAbsEnd, const Vector &hullMin,
+ const Vector &hullMax, unsigned int mask, const CBaseEntity *ignore,
+ int collisionGroup, trace_t *ptr, float minMass )
+{
+ Ray_t ray;
+ ray.Init( vecAbsStart, vecAbsEnd, hullMin, hullMax );
+ CHunterTraceFilterSkipPhysics traceFilter( ignore, collisionGroup, minMass );
+ enginetrace->TraceRay( ray, mask, &traceFilter, ptr );
+}
+
+
+//-----------------------------------------------------------------------------
+// Hunter follow behavior
+//-----------------------------------------------------------------------------
+class CAI_HunterEscortBehavior : public CAI_FollowBehavior
+{
+public:
+ DECLARE_CLASS( CAI_HunterEscortBehavior, CAI_FollowBehavior );
+
+ CAI_HunterEscortBehavior() :
+ BaseClass( AI_FollowParams_t( AIF_HUNTER, true ) ),
+ m_flTimeEscortReturn( 0 ),
+ m_bEnabled( false )
+ {
+ }
+
+ CNPC_Hunter *GetOuter() { return (CNPC_Hunter *)( BaseClass::GetOuter() ); }
+
+ void SetEscortTarget( CNPC_Strider *pLeader, bool fFinishCurSchedule = false );
+ CNPC_Strider * GetEscortTarget() { return (CNPC_Strider *)GetFollowTarget(); }
+
+ bool FarFromFollowTarget()
+ {
+ return ( GetFollowTarget() && (GetAbsOrigin() - GetFollowTarget()->GetAbsOrigin()).LengthSqr() > HUNTER_FOLLOW_DISTANCE_SQR );
+ }
+
+ void DrawDebugGeometryOverlays();
+ bool ShouldFollow();
+ void BuildScheduleTestBits();
+
+ void BeginScheduleSelection();
+
+ void GatherConditions();
+ void GatherConditionsNotActive();
+ int SelectSchedule();
+ int FollowCallBaseSelectSchedule();
+ void StartTask( const Task_t *pTask );
+ void RunTask( const Task_t *pTask );
+
+ void CheckBreakEscort();
+
+ void OnDamage( const CTakeDamageInfo &info );
+ static void DistributeFreeHunters();
+ static void FindFreeHunters( CUtlVector<CNPC_Hunter *> *pFreeHunters );
+
+ float m_flTimeEscortReturn;
+ CSimpleSimTimer m_FollowAttackTimer;
+ bool m_bEnabled;
+
+ static float gm_flLastDefendSound; // not saved and loaded, it's okay to yell again after a load
+
+ //---------------------------------
+
+ DECLARE_DATADESC();
+};
+
+
+BEGIN_DATADESC( CAI_HunterEscortBehavior )
+ DEFINE_FIELD( m_flTimeEscortReturn, FIELD_TIME ),
+ DEFINE_EMBEDDED( m_FollowAttackTimer ),
+ DEFINE_FIELD( m_bEnabled, FIELD_BOOLEAN ),
+END_DATADESC();
+
+float CAI_HunterEscortBehavior::gm_flLastDefendSound;
+
+//-----------------------------------------------------------------------------
+// Hunter PHYSICS DAMAGE TABLE
+//-----------------------------------------------------------------------------
+#define HUNTER_MIN_PHYSICS_DAMAGE 10
+
+static impactentry_t s_HunterLinearTable[] =
+{
+ { 150*150, 75 },
+ { 350*350, 105 },
+ { 1000*1000, 300 },
+};
+
+static impactentry_t s_HunterAngularTable[] =
+{
+ { 100*100, 75 },
+ { 200*200, 105 },
+ { 300*300, 300 },
+};
+
+impactdamagetable_t s_HunterImpactDamageTable =
+{
+ s_HunterLinearTable,
+ s_HunterAngularTable,
+
+ ARRAYSIZE(s_HunterLinearTable),
+ ARRAYSIZE(s_HunterAngularTable),
+
+ 24*24, // minimum linear speed squared
+ 360*360, // minimum angular speed squared (360 deg/s to cause spin/slice damage)
+ 5, // can't take damage from anything under 5kg
+
+ 10, // anything less than 10kg is "small"
+ HUNTER_MIN_PHYSICS_DAMAGE, // never take more than 10 pts of damage from anything under 10kg
+ 36*36, // <10kg objects must go faster than 36 in/s to do damage
+
+ VPHYSICS_LARGE_OBJECT_MASS, // large mass in kg
+ 4, // large mass scale (anything over 500kg does 4X as much energy to read from damage table)
+ 5, // large mass falling scale (emphasize falling/crushing damage over sideways impacts since the stress will kill you anyway)
+ 0.0f, // min vel
+};
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+class CNPC_Hunter : public CAI_BaseActor
+{
+ DECLARE_CLASS( CNPC_Hunter, CAI_BaseActor );
+
+public:
+ CNPC_Hunter();
+ ~CNPC_Hunter();
+
+ //---------------------------------
+
+ void Precache();
+ void Spawn();
+ void PostNPCInit();
+ void Activate();
+ void UpdateOnRemove();
+ void OnRestore();
+ bool CreateBehaviors();
+ void IdleSound();
+ bool ShouldPlayIdleSound();
+ bool CanBecomeRagdoll();
+ Activity GetDeathActivity();
+ void StopLoopingSounds();
+
+ const impactdamagetable_t &GetPhysicsImpactDamageTable();
+
+ Class_T Classify();
+ Vector BodyTarget( const Vector &posSrc, bool bNoisy /*= true*/ );
+
+ int DrawDebugTextOverlays();
+ void DrawDebugGeometryOverlays();
+
+ void UpdateEfficiency( bool bInPVS );
+
+ //---------------------------------
+
+ virtual Vector GetNodeViewOffset() { return BaseClass::GetDefaultEyeOffset(); }
+
+ int GetSoundInterests();
+
+ bool IsInLargeOutdoorMap();
+
+ //---------------------------------
+ // CAI_BaseActor
+ //---------------------------------
+ const char *SelectRandomExpressionForState( NPC_STATE state );
+ void PlayExpressionForState( NPC_STATE state );
+
+ //---------------------------------
+ // CBaseAnimating
+ //---------------------------------
+ float GetIdealAccel() const { return GetIdealSpeed(); }
+
+ //---------------------------------
+ // Behavior
+ //---------------------------------
+ void NPCThink();
+ void PrescheduleThink();
+ void GatherConditions();
+ void CollectSiegeTargets();
+ void ManageSiegeTargets();
+ void KillCurrentSiegeTarget();
+ bool QueryHearSound( CSound *pSound );
+ void OnSeeEntity( CBaseEntity *pEntity );
+ void CheckFlinches() {} // Hunter handles on own
+ void BuildScheduleTestBits();
+ NPC_STATE SelectIdealState();
+ int SelectSchedule();
+ int SelectCombatSchedule();
+ int SelectSiegeSchedule();
+ int TranslateSchedule( int scheduleType );
+ void StartTask( const Task_t *pTask );
+ void RunTask( const Task_t *pTask );
+ Activity NPC_TranslateActivity( Activity baseAct );
+ void OnChangeActivity( Activity eNewActivity );
+
+ void HandleAnimEvent( animevent_t *pEvent );
+ bool HandleInteraction(int interactionType, void *data, CBaseCombatCharacter *pSourceEnt);
+
+ void PlayerHasIlluminatedNPC( CBasePlayer *pPlayer, float flDot );
+
+ void AddEntityRelationship( CBaseEntity *pEntity, Disposition_t nDisposition, int nPriority );
+ float EnemyDistTolerance() { return 100.0f; }
+
+ bool ScheduledMoveToGoalEntity( int scheduleType, CBaseEntity *pGoalEntity, Activity movementActivity );
+
+ void OnChangeHintGroup( string_t oldGroup, string_t newGroup );
+
+ bool IsUsingSiegeTargets() { return m_iszSiegeTargetName != NULL_STRING; }
+
+ //---------------------------------
+ // Inputs
+ //---------------------------------
+ void InputDodge( inputdata_t &inputdata );
+ void InputFlankEnemy( inputdata_t &inputdata );
+ void InputDisableShooting( inputdata_t &inputdata );
+ void InputEnableShooting( inputdata_t &inputdata );
+ void InputFollowStrider( inputdata_t &inputdata );
+ void InputUseSiegeTargets( inputdata_t &inputdata );
+ void InputEnableSquadShootDelay( inputdata_t &inputdata );
+ void InputDisableSquadShootDelay( inputdata_t &inputdata );
+ void InputEnableUnplantedShooting( inputdata_t &inputdata );
+ void InputDisableUnplantedShooting( inputdata_t &inputdata );
+
+ //---------------------------------
+ // Combat
+ //---------------------------------
+ bool FVisible( CBaseEntity *pEntity, int traceMask = MASK_BLOCKLOS, CBaseEntity **ppBlocker = NULL );
+ bool IsValidEnemy( CBaseEntity *pEnemy );
+
+ Disposition_t IRelationType( CBaseEntity *pTarget );
+ int IRelationPriority( CBaseEntity *pTarget );
+
+ void SetSquad( CAI_Squad *pSquad );
+
+ bool UpdateEnemyMemory( CBaseEntity *pEnemy, const Vector &position, CBaseEntity *pInformer = NULL );
+
+ int RangeAttack1Conditions( float flDot, float flDist );
+ int RangeAttack2Conditions( float flDot, float flDist );
+
+ int MeleeAttack1Conditions ( float flDot, float flDist );
+ int MeleeAttack1ConditionsVsEnemyInVehicle( CBaseCombatCharacter *pEnemy, float flDot );
+
+ int MeleeAttack2Conditions( float flDot, float flDist );
+
+ bool WeaponLOSCondition(const Vector &ownerPos, const Vector &targetPos, bool bSetConditions);
+ bool TestShootPosition(const Vector &vecShootPos, const Vector &targetPos );
+
+ Vector Weapon_ShootPosition();
+
+ CBaseEntity * MeleeAttack( float flDist, int iDamage, QAngle &qaViewPunch, Vector &vecVelocityPunch, int BloodOrigin );
+
+ void MakeTracer( const Vector &vecTracerSrc, const trace_t &tr, int iTracerType );
+ void DoMuzzleFlash( int nAttachment );
+
+ bool CanShootThrough( const trace_t &tr, const Vector &vecTarget );
+
+ int CountRangedAttackers();
+ void DelayRangedAttackers( float minDelay, float maxDelay, bool bForced = false );
+
+ //---------------------------------
+ // Sounds & speech
+ //---------------------------------
+ void AlertSound();
+ void PainSound( const CTakeDamageInfo &info );
+ void DeathSound( const CTakeDamageInfo &info );
+
+ //---------------------------------
+ // Damage handling
+ //---------------------------------
+ void TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator );
+ bool IsHeavyDamage( const CTakeDamageInfo &info );
+ int OnTakeDamage( const CTakeDamageInfo &info );
+ int OnTakeDamage_Alive( const CTakeDamageInfo &info );
+ void Event_Killed( const CTakeDamageInfo &info );
+
+ void StartBleeding();
+ inline bool IsBleeding() { return m_bIsBleeding; }
+ void Explode();
+
+ void SetupGlobalModelData();
+
+ //---------------------------------
+ // Navigation & Movement
+ //---------------------------------
+ bool OverrideMoveFacing( const AILocalMoveGoal_t &move, float flInterval );
+ float MaxYawSpeed();
+ bool IsJumpLegal(const Vector &startPos, const Vector &apex, const Vector &endPos) const;
+ float GetJumpGravity() const { return 3.0f; }
+ bool ShouldProbeCollideAgainstEntity( CBaseEntity *pEntity );
+ void TaskFail( AI_TaskFailureCode_t code );
+ void TaskFail( const char *pszGeneralFailText ) { TaskFail( MakeFailCode( pszGeneralFailText ) ); }
+
+ CAI_BaseNPC * GetEntity() { return this; }
+
+ //---------------------------------
+ // Magnade
+ //---------------------------------
+ void StriderBusterAttached( CBaseEntity *pAttached );
+ void StriderBusterDetached( CBaseEntity *pAttached );
+
+private:
+
+ void ConsiderFlinching( const CTakeDamageInfo &info );
+
+ void TaskFindDodgeActivity();
+
+ void GatherChargeConditions();
+ void GatherIndoorOutdoorConditions();
+
+ // Charge attack.
+ bool ShouldCharge( const Vector &startPos, const Vector &endPos, bool useTime, bool bCheckForCancel );
+ void ChargeLookAhead();
+ float ChargeSteer();
+ bool EnemyIsRightInFrontOfMe( CBaseEntity **pEntity );
+ void ChargeDamage( CBaseEntity *pTarget );
+ bool HandleChargeImpact( Vector vecImpact, CBaseEntity *pEntity );
+
+ void BeginVolley( int nNum, float flStartTime );
+ bool ShootFlechette( CBaseEntity *pTargetEntity, bool bSingleShot );
+ bool ShouldSeekTarget( CBaseEntity *pTargetEntity, bool bStriderBuster );
+ void GetShootDir( Vector &vecDir, const Vector &vecSrc, CBaseEntity *pTargetEntity, bool bStriderbuster, int nShotNum, bool bSingleShot );
+ bool ClampShootDir( Vector &vecDir );
+
+ void SetAim( const Vector &aimDir, float flInterval );
+ void RelaxAim( float flInterval );
+ void UpdateAim();
+ void UpdateEyes();
+ void LockBothEyes( float flDuration );
+ void UnlockBothEyes( float flDuration );
+
+ void TeslaThink();
+ void BleedThink();
+ void JostleVehicleThink();
+
+ void FollowStrider( const char *szStrider );
+ void FollowStrider( CNPC_Strider * pStrider );
+ int NumHuntersInMySquad();
+
+ bool CanPlantHere( const Vector &vecPos );
+
+ //---------------------------------
+ // Foot handling
+ //---------------------------------
+ Vector LeftFootHit( float eventtime );
+ Vector RightFootHit( float eventtime );
+ Vector BackFootHit( float eventtime );
+
+ void FootFX( const Vector &origin );
+
+ CBaseEntity *GetEnemyVehicle();
+ bool IsCorporealEnemy( CBaseEntity *pEnemy );
+
+ void PhysicsDamageEffect( const Vector &vecPos, const Vector &vecDir );
+ bool PlayerFlashlightOnMyEyes( CBasePlayer *pPlayer );
+
+ //-----------------------------------------------------
+ // Conditions, Schedules, Tasks
+ //-----------------------------------------------------
+ enum
+ {
+ SCHED_HUNTER_RANGE_ATTACK1 = BaseClass::NEXT_SCHEDULE,
+ SCHED_HUNTER_RANGE_ATTACK2,
+ SCHED_HUNTER_MELEE_ATTACK1,
+ SCHED_HUNTER_DODGE,
+ SCHED_HUNTER_CHASE_ENEMY,
+ SCHED_HUNTER_CHASE_ENEMY_MELEE,
+ SCHED_HUNTER_COMBAT_FACE,
+ SCHED_HUNTER_FLANK_ENEMY,
+ SCHED_HUNTER_CHANGE_POSITION,
+ SCHED_HUNTER_CHANGE_POSITION_FINISH,
+ SCHED_HUNTER_SIDESTEP,
+ SCHED_HUNTER_PATROL,
+ SCHED_HUNTER_FLINCH_STICKYBOMB,
+ SCHED_HUNTER_STAGGER,
+ SCHED_HUNTER_PATROL_RUN,
+ SCHED_HUNTER_TAKE_COVER_FROM_ENEMY,
+ SCHED_HUNTER_HIDE_UNDER_COVER,
+ SCHED_HUNTER_FAIL_IMMEDIATE, // instant fail without waiting
+ SCHED_HUNTER_CHARGE_ENEMY,
+ SCHED_HUNTER_FAIL_CHARGE_ENEMY,
+ SCHED_HUNTER_FOUND_ENEMY,
+ SCHED_HUNTER_FOUND_ENEMY_ACK,
+ SCHED_HUNTER_RANGE_ATTACK2_VS_STRIDERBUSTER,
+ SCHED_HUNTER_RANGE_ATTACK2_VS_STRIDERBUSTER_LATENT,
+ SCHED_HUNTER_GOTO_HINT,
+ SCHED_HUNTER_CLEAR_HINTNODE,
+ SCHED_HUNTER_FAIL_DODGE,
+ SCHED_HUNTER_SIEGE_STAND,
+ SCHED_HUNTER_CHANGE_POSITION_SIEGE,
+
+ TASK_HUNTER_AIM = BaseClass::NEXT_TASK,
+ TASK_HUNTER_FIND_DODGE_POSITION,
+ TASK_HUNTER_DODGE,
+ TASK_HUNTER_PRE_RANGE_ATTACK2,
+ TASK_HUNTER_SHOOT_COMMIT,
+ TASK_HUNTER_BEGIN_FLANK,
+ TASK_HUNTER_ANNOUNCE_FLANK,
+ TASK_HUNTER_STAGGER,
+ TASK_HUNTER_CORNERED_TIMER,
+ TASK_HUNTER_FIND_SIDESTEP_POSITION,
+ TASK_HUNTER_CHARGE,
+ TASK_HUNTER_CHARGE_DELAY,
+ TASK_HUNTER_FINISH_RANGE_ATTACK,
+ TASK_HUNTER_WAIT_FOR_MOVEMENT_FACING_ENEMY,
+
+ COND_HUNTER_SHOULD_PATROL = BaseClass::NEXT_CONDITION,
+ COND_HUNTER_FORCED_FLANK_ENEMY,
+ COND_HUNTER_FORCED_DODGE,
+ COND_HUNTER_CAN_CHARGE_ENEMY,
+ COND_HUNTER_HIT_BY_STICKYBOMB,
+ COND_HUNTER_STAGGERED,
+ COND_HUNTER_IS_INDOORS,
+ COND_HUNTER_SEE_STRIDERBUSTER,
+ COND_HUNTER_INCOMING_VEHICLE,
+ COND_HUNTER_NEW_HINTGROUP,
+ COND_HUNTER_CANT_PLANT,
+ COND_HUNTER_SQUADMATE_FOUND_ENEMY,
+ };
+
+ enum HunterEyeStates_t
+ {
+ HUNTER_EYE_STATE_TOP_LOCKED = 0,
+ HUNTER_EYE_STATE_BOTTOM_LOCKED,
+ HUNTER_EYE_STATE_BOTH_LOCKED,
+ HUNTER_EYE_STATE_BOTH_UNLOCKED,
+ };
+
+ string_t m_iszFollowTarget; // Name of the strider we should follow.
+ CSimpleStopwatch m_BeginFollowDelay;
+
+ int m_nKillingDamageType;
+ HunterEyeStates_t m_eEyeState;
+
+ float m_aimYaw;
+ float m_aimPitch;
+
+ float m_flShootAllowInterruptTime;
+ float m_flNextChargeTime; // Prevents us from doing our threat display too often.
+ float m_flNextDamageTime;
+ float m_flNextSideStepTime;
+
+ CSimpleSimTimer m_HeavyDamageDelay;
+ CSimpleSimTimer m_FlinchTimer;
+ CSimpleSimTimer m_EyeSwitchTimer; // Controls how often we switch which eye is focusing on our enemy.
+
+ bool m_bTopMuzzle; // Used to alternate between top muzzle FX and bottom muzzle FX.
+ bool m_bEnableSquadShootDelay;
+ bool m_bIsBleeding;
+
+ Activity m_eDodgeActivity;
+ CSimpleSimTimer m_RundownDelay;
+ CSimpleSimTimer m_IgnoreVehicleTimer;
+
+ bool m_bDisableShooting; // Range attack disabled via an input. Used for scripting melee attacks.
+
+ bool m_bFlashlightInEyes; // The player is shining the flashlight on our eyes.
+ float m_flPupilDilateTime; // When to dilate our pupils if the flashlight is no longer on our eyes.
+
+ Vector m_vecEnemyLastSeen;
+ Vector m_vecLastCanPlantHerePos;
+ Vector m_vecStaggerDir;
+
+ bool m_bPlanted;
+ bool m_bLastCanPlantHere;
+ bool m_bMissLeft;
+ bool m_bEnableUnplantedShooting;
+
+ static float gm_flMinigunDistZ;
+ static Vector gm_vecLocalRelativePositionMinigun;
+
+ static int gm_nTopGunAttachment;
+ static int gm_nBottomGunAttachment;
+ static int gm_nAimYawPoseParam;
+ static int gm_nAimPitchPoseParam;
+ static int gm_nBodyYawPoseParam;
+ static int gm_nBodyPitchPoseParam;
+ static int gm_nStaggerYawPoseParam;
+ static int gm_nHeadCenterAttachment;
+ static int gm_nHeadBottomAttachment;
+ static float gm_flHeadRadius;
+
+ static int gm_nUnplantedNode;
+ static int gm_nPlantedNode;
+
+ CAI_HunterEscortBehavior m_EscortBehavior;
+
+ int m_nFlechettesQueued;
+ int m_nClampedShots; // The number of consecutive shots fired at an out-of-max yaw target.
+
+ float m_flNextRangeAttack2Time; // Time when we can fire another volley of flechettes.
+ float m_flNextFlechetteTime; // Time to fire the next flechette in this volley.
+
+ float m_flNextMeleeTime;
+ float m_flTeslaStopTime;
+
+ string_t m_iszCurrentExpression;
+
+ // buster fu
+ CUtlVector< EHANDLE > m_hAttachedBusters; // List of busters attached to us
+ float m_fCorneredTimer; ///< hunter was cornered when fleeing player; it won't flee again until this time
+
+ CSimpleSimTimer m_CheckHintGroupTimer;
+
+ DEFINE_CUSTOM_AI;
+
+ DECLARE_DATADESC();
+
+ friend class CAI_HunterEscortBehavior;
+ friend class CHunterMaker;
+
+ bool m_bInLargeOutdoorMap;
+ float m_flTimeSawEnemyAgain;
+
+ // Sounds
+ //CSoundPatch *m_pGunFiringSound;
+
+ CUtlVector<EHANDLE> m_pSiegeTargets;
+ string_t m_iszSiegeTargetName;
+ float m_flTimeNextSiegeTargetAttack;
+ EHANDLE m_hCurrentSiegeTarget;
+
+ EHANDLE m_hHitByVehicle;
+};
+
+
+LINK_ENTITY_TO_CLASS( npc_hunter, CNPC_Hunter );
+
+
+BEGIN_DATADESC( CNPC_Hunter )
+
+ DEFINE_KEYFIELD( m_iszFollowTarget, FIELD_STRING, "FollowTarget" ),
+
+ DEFINE_FIELD( m_aimYaw, FIELD_FLOAT ),
+ DEFINE_FIELD( m_aimPitch, FIELD_FLOAT ),
+
+ DEFINE_FIELD( m_flShootAllowInterruptTime, FIELD_TIME ),
+ DEFINE_FIELD( m_flNextChargeTime, FIELD_TIME ),
+ //DEFINE_FIELD( m_flNextDamageTime, FIELD_TIME ),
+ DEFINE_FIELD( m_flNextSideStepTime, FIELD_TIME ),
+
+ DEFINE_EMBEDDED( m_HeavyDamageDelay ),
+ DEFINE_EMBEDDED( m_FlinchTimer ),
+
+ DEFINE_FIELD( m_eEyeState, FIELD_INTEGER ),
+
+ DEFINE_FIELD( m_bTopMuzzle, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_bEnableSquadShootDelay, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_bIsBleeding, FIELD_BOOLEAN ),
+
+ DEFINE_FIELD( m_bDisableShooting, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_bFlashlightInEyes, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_flPupilDilateTime, FIELD_TIME ),
+
+ DEFINE_FIELD( m_vecEnemyLastSeen, FIELD_POSITION_VECTOR ),
+ DEFINE_FIELD( m_vecLastCanPlantHerePos, FIELD_POSITION_VECTOR ),
+ DEFINE_FIELD( m_vecStaggerDir, FIELD_VECTOR ),
+
+ DEFINE_FIELD( m_bPlanted, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_bLastCanPlantHere, FIELD_BOOLEAN ),
+ //DEFINE_FIELD( m_bMissLeft, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_bEnableUnplantedShooting, FIELD_BOOLEAN ),
+
+ DEFINE_FIELD( m_nKillingDamageType, FIELD_INTEGER ),
+ DEFINE_FIELD( m_eDodgeActivity, FIELD_INTEGER ),
+ DEFINE_EMBEDDED( m_RundownDelay ),
+ DEFINE_EMBEDDED( m_IgnoreVehicleTimer ),
+
+ DEFINE_FIELD( m_flNextMeleeTime, FIELD_TIME ),
+ DEFINE_FIELD( m_flTeslaStopTime, FIELD_TIME ),
+
+ // (auto saved by AI)
+ //DEFINE_FIELD( m_EscortBehavior, FIELD_EMBEDDED ),
+
+ DEFINE_FIELD( m_iszCurrentExpression, FIELD_STRING ),
+
+ DEFINE_FIELD( m_fCorneredTimer, FIELD_TIME),
+
+ DEFINE_EMBEDDED( m_CheckHintGroupTimer ),
+
+ // (Recomputed in Precache())
+ //DEFINE_FIELD( m_bInLargeOutdoorMap, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_flTimeSawEnemyAgain, FIELD_TIME ),
+
+ //DEFINE_SOUNDPATCH( m_pGunFiringSound ),
+
+ //DEFINE_UTLVECTOR( m_pSiegeTarget, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_iszSiegeTargetName, FIELD_STRING ),
+ DEFINE_FIELD( m_flTimeNextSiegeTargetAttack, FIELD_TIME ),
+ DEFINE_FIELD( m_hCurrentSiegeTarget, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_hHitByVehicle, FIELD_EHANDLE ),
+
+ DEFINE_EMBEDDED( m_BeginFollowDelay ),
+ DEFINE_EMBEDDED( m_EyeSwitchTimer ),
+
+ DEFINE_FIELD( m_nFlechettesQueued, FIELD_INTEGER ),
+ DEFINE_FIELD( m_nClampedShots, FIELD_INTEGER ),
+ DEFINE_FIELD( m_flNextRangeAttack2Time, FIELD_TIME ),
+ DEFINE_FIELD( m_flNextFlechetteTime, FIELD_TIME ),
+ DEFINE_UTLVECTOR( m_hAttachedBusters, FIELD_EHANDLE ),
+ DEFINE_UTLVECTOR( m_pSiegeTargets, FIELD_EHANDLE ),
+
+ // inputs
+ DEFINE_INPUTFUNC( FIELD_VOID, "Dodge", InputDodge ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "FlankEnemy", InputFlankEnemy ),
+ DEFINE_INPUTFUNC( FIELD_STRING, "DisableShooting", InputDisableShooting ),
+ DEFINE_INPUTFUNC( FIELD_STRING, "EnableShooting", InputEnableShooting ),
+ DEFINE_INPUTFUNC( FIELD_STRING, "FollowStrider", InputFollowStrider ),
+ DEFINE_INPUTFUNC( FIELD_STRING, "UseSiegeTargets", InputUseSiegeTargets ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "EnableSquadShootDelay", InputEnableSquadShootDelay ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "DisableSquadShootDelay", InputDisableSquadShootDelay ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "EnableUnplantedShooting", InputEnableUnplantedShooting ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "DisableUnplantedShooting", InputDisableUnplantedShooting ),
+
+ // Function Pointers
+ DEFINE_THINKFUNC( TeslaThink ),
+ DEFINE_THINKFUNC( BleedThink ),
+ DEFINE_THINKFUNC( JostleVehicleThink ),
+
+END_DATADESC()
+
+//-----------------------------------------------------------------------------
+
+int CNPC_Hunter::gm_nUnplantedNode = 0;
+int CNPC_Hunter::gm_nPlantedNode = 0;
+
+int CNPC_Hunter::gm_nAimYawPoseParam = -1;
+int CNPC_Hunter::gm_nAimPitchPoseParam = -1;
+int CNPC_Hunter::gm_nBodyYawPoseParam = -1;
+int CNPC_Hunter::gm_nBodyPitchPoseParam = -1;
+int CNPC_Hunter::gm_nStaggerYawPoseParam = -1;
+int CNPC_Hunter::gm_nHeadCenterAttachment = -1;
+int CNPC_Hunter::gm_nHeadBottomAttachment = -1;
+float CNPC_Hunter::gm_flHeadRadius = 0;
+
+int CNPC_Hunter::gm_nTopGunAttachment = -1;
+int CNPC_Hunter::gm_nBottomGunAttachment = -1;
+
+float CNPC_Hunter::gm_flMinigunDistZ;
+Vector CNPC_Hunter::gm_vecLocalRelativePositionMinigun;
+
+//-----------------------------------------------------------------------------
+
+static CUtlVector<CNPC_Hunter *> g_Hunters;
+float g_TimeLastDistributeFreeHunters = -1;
+const float FREE_HUNTER_DISTRIBUTE_INTERVAL = 2;
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+CNPC_Hunter::CNPC_Hunter()
+{
+ g_Hunters.AddToTail( this );
+ g_TimeLastDistributeFreeHunters = -1;
+ m_flTimeSawEnemyAgain = HUNTER_SEE_ENEMY_TIME_INVALID;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+CNPC_Hunter::~CNPC_Hunter()
+{
+ g_Hunters.FindAndRemove( this );
+ g_TimeLastDistributeFreeHunters = -1;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::Precache()
+{
+ PrecacheModel( "models/hunter.mdl" );
+ PropBreakablePrecacheAll( MAKE_STRING("models/hunter.mdl") );
+
+ PrecacheScriptSound( "NPC_Hunter.Idle" );
+ PrecacheScriptSound( "NPC_Hunter.Scan" );
+ PrecacheScriptSound( "NPC_Hunter.Alert" );
+ PrecacheScriptSound( "NPC_Hunter.Pain" );
+ PrecacheScriptSound( "NPC_Hunter.PreCharge" );
+ PrecacheScriptSound( "NPC_Hunter.Angry" );
+ PrecacheScriptSound( "NPC_Hunter.Death" );
+ PrecacheScriptSound( "NPC_Hunter.FireMinigun" );
+ PrecacheScriptSound( "NPC_Hunter.Footstep" );
+ PrecacheScriptSound( "NPC_Hunter.BackFootstep" );
+ PrecacheScriptSound( "NPC_Hunter.FlechetteVolleyWarn" );
+ PrecacheScriptSound( "NPC_Hunter.FlechetteShoot" );
+ PrecacheScriptSound( "NPC_Hunter.FlechetteShootLoop" );
+ PrecacheScriptSound( "NPC_Hunter.FlankAnnounce" );
+ PrecacheScriptSound( "NPC_Hunter.MeleeAnnounce" );
+ PrecacheScriptSound( "NPC_Hunter.MeleeHit" );
+ PrecacheScriptSound( "NPC_Hunter.TackleAnnounce" );
+ PrecacheScriptSound( "NPC_Hunter.TackleHit" );
+ PrecacheScriptSound( "NPC_Hunter.ChargeHitEnemy" );
+ PrecacheScriptSound( "NPC_Hunter.ChargeHitWorld" );
+ PrecacheScriptSound( "NPC_Hunter.FoundEnemy" );
+ PrecacheScriptSound( "NPC_Hunter.FoundEnemyAck" );
+ PrecacheScriptSound( "NPC_Hunter.DefendStrider" );
+ PrecacheScriptSound( "NPC_Hunter.HitByVehicle" );
+
+ PrecacheParticleSystem( "hunter_muzzle_flash" );
+ PrecacheParticleSystem( "blood_impact_synth_01" );
+ PrecacheParticleSystem( "blood_impact_synth_01_arc_parent" );
+ PrecacheParticleSystem( "blood_spurt_synth_01" );
+ PrecacheParticleSystem( "blood_drip_synth_01" );
+
+ PrecacheInstancedScene( "scenes/npc/hunter/hunter_scan.vcd" );
+ PrecacheInstancedScene( "scenes/npc/hunter/hunter_eyeclose.vcd" );
+ PrecacheInstancedScene( "scenes/npc/hunter/hunter_roar.vcd" );
+ PrecacheInstancedScene( "scenes/npc/hunter/hunter_pain.vcd" );
+ PrecacheInstancedScene( "scenes/npc/hunter/hunter_eyedarts_top.vcd" );
+ PrecacheInstancedScene( "scenes/npc/hunter/hunter_eyedarts_bottom.vcd" );
+
+ PrecacheMaterial( "effects/water_highlight" );
+
+ UTIL_PrecacheOther( "hunter_flechette" );
+ UTIL_PrecacheOther( "sparktrail" );
+
+ m_bInLargeOutdoorMap = false;
+ if( !Q_strnicmp( STRING(gpGlobals->mapname), "ep2_outland_12", 14) )
+ {
+ m_bInLargeOutdoorMap = true;
+ }
+
+ BaseClass::Precache();
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::Spawn()
+{
+ Precache();
+
+ SetModel( "models/hunter.mdl" );
+ BaseClass::Spawn();
+
+ //m_debugOverlays |= OVERLAY_NPC_ROUTE_BIT | OVERLAY_BBOX_BIT | OVERLAY_PIVOT_BIT;
+
+ SetHullType( HULL_MEDIUM_TALL );
+ SetHullSizeNormal();
+ SetDefaultEyeOffset();
+
+ SetNavType( NAV_GROUND );
+ m_flGroundSpeed = 500;
+ m_NPCState = NPC_STATE_NONE;
+
+ SetBloodColor( DONT_BLEED );
+
+ m_iHealth = m_iMaxHealth = sk_hunter_health.GetInt();
+
+ m_flFieldOfView = HUNTER_FOV_DOT;
+
+ SetSolid( SOLID_BBOX );
+ AddSolidFlags( FSOLID_NOT_STANDABLE );
+ SetMoveType( MOVETYPE_STEP );
+
+ SetupGlobalModelData();
+
+ CapabilitiesAdd( bits_CAP_MOVE_GROUND | bits_CAP_SQUAD | bits_CAP_ANIMATEDFACE );
+ CapabilitiesAdd( bits_CAP_INNATE_RANGE_ATTACK1 | bits_CAP_INNATE_RANGE_ATTACK2 | bits_CAP_INNATE_MELEE_ATTACK1 );
+ CapabilitiesAdd( bits_CAP_SKIP_NAV_GROUND_CHECK );
+
+ if ( !hunter_allow_dissolve.GetBool() )
+ {
+ AddEFlags( EFL_NO_DISSOLVE );
+ }
+
+ if( hunter_allow_nav_jump.GetBool() )
+ {
+ CapabilitiesAdd( bits_CAP_MOVE_JUMP );
+ }
+
+ NPCInit();
+
+ m_bEnableSquadShootDelay = true;
+
+ m_flDistTooFar = hunter_flechette_max_range.GetFloat();
+
+ // Discard time must be greater than free knowledge duration. Make it double.
+ float freeKnowledge = hunter_free_knowledge.GetFloat();
+ if ( freeKnowledge < GetEnemies()->GetEnemyDiscardTime() )
+ {
+ GetEnemies()->SetEnemyDiscardTime( MAX( freeKnowledge + 0.1, AI_DEF_ENEMY_DISCARD_TIME ) );
+ }
+ GetEnemies()->SetFreeKnowledgeDuration( freeKnowledge );
+
+ // Find out what strider we should follow, if any.
+ if ( m_iszFollowTarget != NULL_STRING )
+ {
+ m_BeginFollowDelay.Set( .1 ); // Allow time for strider to spawn
+ }
+
+ //if ( !m_pGunFiringSound )
+ //{
+ // CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+ // CPASAttenuationFilter filter( this );
+ //
+ // m_pGunFiringSound = controller.SoundCreate( filter, entindex(), "NPC_Hunter.FlechetteShootLoop" );
+ // controller.Play( m_pGunFiringSound, 0.0, 100 );
+ //}
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::UpdateEfficiency( bool bInPVS )
+{
+ SetEfficiency( ( GetSleepState() != AISS_AWAKE ) ? AIE_DORMANT : AIE_NORMAL );
+ SetMoveEfficiency( AIME_NORMAL );
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+bool CNPC_Hunter::CreateBehaviors()
+{
+ AddBehavior( &m_EscortBehavior );
+
+ return BaseClass::CreateBehaviors();
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::SetupGlobalModelData()
+{
+ if ( gm_nBodyYawPoseParam != -1 )
+ return;
+
+ gm_nAimYawPoseParam = LookupPoseParameter( "aim_yaw" );
+ gm_nAimPitchPoseParam = LookupPoseParameter( "aim_pitch" );
+
+ gm_nBodyYawPoseParam = LookupPoseParameter( "body_yaw" );
+ gm_nBodyPitchPoseParam = LookupPoseParameter( "body_pitch" );
+
+ gm_nTopGunAttachment = LookupAttachment( "top_eye" );
+ gm_nBottomGunAttachment = LookupAttachment( "bottom_eye" );
+ gm_nStaggerYawPoseParam = LookupAttachment( "stagger_yaw" );
+
+ gm_nHeadCenterAttachment = LookupAttachment( "head_center" );
+ gm_nHeadBottomAttachment = LookupAttachment( "head_radius_measure" );
+
+ // Measure the radius of the head.
+ Vector vecHeadCenter;
+ Vector vecHeadBottom;
+ GetAttachment( gm_nHeadCenterAttachment, vecHeadCenter );
+ GetAttachment( gm_nHeadBottomAttachment, vecHeadBottom );
+ gm_flHeadRadius = ( vecHeadCenter - vecHeadBottom ).Length();
+
+ int nSequence = SelectWeightedSequence( ACT_HUNTER_RANGE_ATTACK2_UNPLANTED );
+ gm_nUnplantedNode = GetEntryNode( nSequence );
+
+ nSequence = SelectWeightedSequence( ACT_RANGE_ATTACK2 );
+ gm_nPlantedNode = GetEntryNode( nSequence );
+
+ CollisionProp()->SetSurroundingBoundsType( USE_HITBOXES );
+}
+
+
+//-----------------------------------------------------------------------------
+// Shuts down looping sounds when we are killed in combat or deleted.
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::StopLoopingSounds()
+{
+ BaseClass::StopLoopingSounds();
+
+ //if ( m_pGunFiringSound )
+ //{
+ // CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+ // controller.SoundDestroy( m_pGunFiringSound );
+ // m_pGunFiringSound = NULL;
+ //}
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::OnRestore()
+{
+ BaseClass::OnRestore();
+ SetupGlobalModelData();
+ CreateVPhysics();
+
+ if ( IsBleeding() )
+ {
+ StartBleeding();
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::IdleSound()
+{
+ if ( HasCondition( COND_LOST_ENEMY ) )
+ {
+ EmitSound( "NPC_Hunter.Scan" );
+ }
+ else
+ {
+ EmitSound( "NPC_Hunter.Idle" );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+bool CNPC_Hunter::ShouldPlayIdleSound()
+{
+ if ( random->RandomInt(0, 99) == 0 && !HasSpawnFlags( SF_NPC_GAG ) )
+ return true;
+
+ return false;
+}
+
+
+//-----------------------------------------------------------------------------
+// Stay facing our enemy when close enough.
+//-----------------------------------------------------------------------------
+bool CNPC_Hunter::OverrideMoveFacing( const AILocalMoveGoal_t &move, float flInterval )
+{
+ if ( GetActivity() == ACT_TRANSITION )
+ {
+ // No turning while in transitions.
+ return true;
+ }
+
+ bool bSideStepping = IsCurSchedule( SCHED_HUNTER_SIDESTEP, false );
+
+ // FIXME: this will break scripted sequences that walk when they have an enemy
+ if ( GetEnemy() &&
+ ( bSideStepping ||
+ ( ( ( GetNavigator()->GetMovementActivity() == ACT_RUN ) || ( GetNavigator()->GetMovementActivity() == ACT_WALK ) ) &&
+ !IsCurSchedule( SCHED_HUNTER_TAKE_COVER_FROM_ENEMY, false ) ) ) )
+ {
+ Vector vecEnemyLKP = GetEnemyLKP();
+
+ // Face my enemy if we're close enough
+ if ( bSideStepping || UTIL_DistApprox( vecEnemyLKP, GetAbsOrigin() ) < HUNTER_FACE_ENEMY_DIST )
+ {
+ AddFacingTarget( GetEnemy(), vecEnemyLKP, 1.0, 0.2 );
+ }
+ }
+
+ return BaseClass::OverrideMoveFacing( move, flInterval );
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::PostNPCInit()
+{
+ BaseClass::PostNPCInit();
+
+ IPhysicsObject *pPhysObject = VPhysicsGetObject();
+ Assert( pPhysObject );
+ if ( pPhysObject )
+ {
+ pPhysObject->SetMass( 600.0 );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::Activate()
+{
+ BaseClass::Activate();
+
+ s_iszStriderBusterClassname = AllocPooledString( "weapon_striderbuster" );
+ s_iszStriderClassname = AllocPooledString( "npc_strider" );
+ s_iszMagnadeClassname = AllocPooledString( "npc_grenade_magna" );
+ s_iszPhysPropClassname = AllocPooledString( "prop_physics" );
+ s_iszHuntersToRunOver = AllocPooledString( "hunters_to_run_over" );
+
+ // If no one has initialized the hunters to run over counter, just zero it out.
+ if ( !GlobalEntity_IsInTable( s_iszHuntersToRunOver ) )
+ {
+ GlobalEntity_Add( s_iszHuntersToRunOver, gpGlobals->mapname, GLOBAL_ON );
+ GlobalEntity_SetCounter( s_iszHuntersToRunOver, 0 );
+ }
+
+ CMissile::AddCustomDetonator( this, ( GetHullMaxs().AsVector2D() - GetHullMins().AsVector2D() ).Length() * 0.5, GetHullHeight() );
+
+ SetupGlobalModelData();
+
+ if ( gm_flMinigunDistZ == 0 )
+ {
+ // Have to create a virgin hunter to ensure proper pose
+ CNPC_Hunter *pHunter = (CNPC_Hunter *)CreateEntityByName( "npc_hunter" );
+ Assert(pHunter);
+ pHunter->Spawn();
+
+ pHunter->SetActivity( ACT_WALK );
+ pHunter->InvalidateBoneCache();
+
+ // Currently just using the gun for the vertical component!
+ Vector defEyePos;
+ pHunter->GetAttachment( "minigunbase", defEyePos );
+ gm_flMinigunDistZ = defEyePos.z - pHunter->GetAbsOrigin().z;
+
+ Vector position;
+ pHunter->GetAttachment( gm_nTopGunAttachment, position );
+ VectorITransform( position, pHunter->EntityToWorldTransform(), gm_vecLocalRelativePositionMinigun );
+ UTIL_Remove( pHunter );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::UpdateOnRemove()
+{
+ CMissile::RemoveCustomDetonator( this );
+ BaseClass::UpdateOnRemove();
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+Class_T CNPC_Hunter::Classify()
+{
+ return CLASS_COMBINE_HUNTER;
+}
+
+//-----------------------------------------------------------------------------
+// Compensate for the hunter's long legs by moving the bodytarget up to his head.
+//-----------------------------------------------------------------------------
+Vector CNPC_Hunter::BodyTarget( const Vector &posSrc, bool bNoisy /*= true*/ )
+{
+ Vector vecResult;
+ QAngle vecAngle;
+ GetAttachment( gm_nHeadCenterAttachment, vecResult, vecAngle );
+
+ if ( bNoisy )
+ {
+ float rand1 = random->RandomFloat( 0, gm_flHeadRadius ) + random->RandomFloat( 0, gm_flHeadRadius );
+ return vecResult + RandomVector( -rand1, rand1 );
+ }
+
+ return vecResult;
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+int CNPC_Hunter::DrawDebugTextOverlays()
+{
+ int text_offset = BaseClass::DrawDebugTextOverlays();
+
+ if (m_debugOverlays & OVERLAY_TEXT_BIT)
+ {
+ EntityText( text_offset, CFmtStr("%s", m_bPlanted ? "Planted" : "Unplanted" ), 0 );
+ text_offset++;
+
+ EntityText( text_offset, CFmtStr("Eye state: %d", m_eEyeState ), 0 );
+ text_offset++;
+
+ if( IsUsingSiegeTargets() )
+ {
+ EntityText( text_offset, CFmtStr("Next Siege Attempt:%f", m_flTimeNextSiegeTargetAttack - gpGlobals->curtime ), 0 );
+ text_offset++;
+ }
+ }
+
+ return text_offset;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::LockBothEyes( float flDuration )
+{
+ m_eEyeState = HUNTER_EYE_STATE_BOTH_LOCKED;
+ m_EyeSwitchTimer.Set( flDuration );
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::UnlockBothEyes( float flDuration )
+{
+ m_eEyeState = HUNTER_EYE_STATE_BOTH_UNLOCKED;
+ m_EyeSwitchTimer.Set( flDuration );
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::OnChangeActivity( Activity eNewActivity )
+{
+ m_EyeSwitchTimer.Force();
+
+ BaseClass::OnChangeActivity( eNewActivity );
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::UpdateEyes()
+{
+ // If the eyes are controlled by a script, do nothing.
+ if ( GetState() == NPC_STATE_SCRIPT )
+ return;
+
+ if ( m_EyeSwitchTimer.Expired() )
+ {
+ RemoveActorFromScriptedScenes( this, false );
+
+ if ( GetActivity() == ACT_IDLE )
+ {
+ // Idles have eye motion baked in.
+ m_eEyeState = HUNTER_EYE_STATE_BOTH_LOCKED;
+ }
+ else if ( GetEnemy() == NULL )
+ {
+ m_eEyeState = HUNTER_EYE_STATE_BOTH_UNLOCKED;
+ }
+ else if ( m_eEyeState == HUNTER_EYE_STATE_BOTH_LOCKED )
+ {
+ if ( random->RandomInt( 0, 1 ) == 0 )
+ {
+ m_eEyeState = HUNTER_EYE_STATE_TOP_LOCKED;
+ }
+ else
+ {
+ m_eEyeState = HUNTER_EYE_STATE_BOTTOM_LOCKED;
+ }
+ }
+ else if ( m_eEyeState == HUNTER_EYE_STATE_TOP_LOCKED )
+ {
+ m_eEyeState = HUNTER_EYE_STATE_BOTTOM_LOCKED;
+ }
+ else if ( m_eEyeState == HUNTER_EYE_STATE_BOTTOM_LOCKED )
+ {
+ m_eEyeState = HUNTER_EYE_STATE_TOP_LOCKED;
+ }
+
+ if ( ( m_eEyeState == HUNTER_EYE_STATE_BOTTOM_LOCKED ) || ( m_eEyeState == HUNTER_EYE_STATE_BOTH_UNLOCKED ) )
+ {
+ SetExpression( "scenes/npc/hunter/hunter_eyedarts_top.vcd" );
+ }
+
+ if ( ( m_eEyeState == HUNTER_EYE_STATE_TOP_LOCKED ) || ( m_eEyeState == HUNTER_EYE_STATE_BOTH_UNLOCKED ) )
+ {
+ SetExpression( "scenes/npc/hunter/hunter_eyedarts_bottom.vcd" );
+ }
+
+ m_EyeSwitchTimer.Set( random->RandomFloat( 1.0f, 3.0f ) );
+ }
+
+ /*Vector vecEyePos;
+ Vector vecEyeDir;
+
+ GetAttachment( gm_nTopGunAttachment, vecEyePos, &vecEyeDir );
+ NDebugOverlay::Line( vecEyePos, vecEyePos + vecEyeDir * 36, 255, 0, 0, 0, 0.1 );
+
+ GetAttachment( gm_nBottomGunAttachment, vecEyePos, &vecEyeDir );
+ NDebugOverlay::Line( vecEyePos, vecEyePos + vecEyeDir * 36, 255, 0, 0, 0, 0.1 );*/
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::NPCThink()
+{
+ BaseClass::NPCThink();
+
+ // Update our planted/unplanted state.
+ m_bPlanted = ( GetEntryNode( GetSequence() ) == gm_nPlantedNode );
+
+ UpdateAim();
+ UpdateEyes();
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::PrescheduleThink()
+{
+ BaseClass::PrescheduleThink();
+
+ if ( m_BeginFollowDelay.Expired() )
+ {
+ FollowStrider( STRING( m_iszFollowTarget ) );
+ m_BeginFollowDelay.Stop();
+ }
+
+ m_EscortBehavior.CheckBreakEscort();
+
+ // If we're being blinded by the flashlight, see if we should stop
+ if ( m_bFlashlightInEyes )
+ {
+ if ( m_flPupilDilateTime < gpGlobals->curtime )
+ {
+ CBasePlayer *pPlayer = UTIL_PlayerByIndex( 1 );
+ if ( ( pPlayer && !pPlayer->IsIlluminatedByFlashlight( this, NULL ) ) || !PlayerFlashlightOnMyEyes( pPlayer ) )
+ {
+ //Msg( "NOT SHINING FLASHLIGHT ON ME\n" );
+
+ // Remove the actor from the flashlight scene
+ RemoveActorFromScriptedScenes( this, true, false, "scenes/npc/hunter/hunter_eyeclose.vcd" );
+ m_bFlashlightInEyes = false;
+ }
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::GatherChargeConditions()
+{
+ ClearCondition( COND_HUNTER_CAN_CHARGE_ENEMY );
+
+ if ( !hunter_charge.GetBool() )
+ return;
+
+ if ( !GetEnemy() )
+ return;
+
+ if ( GetHintGroup() != NULL_STRING )
+ return;
+
+ if ( !HasCondition( COND_SEE_ENEMY ) )
+ return;
+
+ if ( !hunter_charge_test.GetBool() && gpGlobals->curtime < m_flNextChargeTime )
+ return;
+
+ // No charging Alyx or Barney
+ if( GetEnemy()->Classify() == CLASS_PLAYER_ALLY_VITAL )
+ return;
+
+ if ( m_EscortBehavior.GetEscortTarget() && GetEnemy()->MyCombatCharacterPointer() && !GetEnemy()->MyCombatCharacterPointer()->FInViewCone( this ) )
+ return;
+
+ if ( ShouldCharge( GetAbsOrigin(), GetEnemy()->GetAbsOrigin(), true, false ) )
+ {
+ SetCondition( COND_HUNTER_CAN_CHARGE_ENEMY );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::GatherConditions()
+{
+ GatherIndoorOutdoorConditions();
+ GatherChargeConditions();
+
+ BaseClass::GatherConditions();
+
+ // Enemy LKP that doesn't get updated by the free knowledge code.
+ // Used for shooting at where our enemy was when we can't see them.
+ ClearCondition( COND_HUNTER_INCOMING_VEHICLE );
+ if ( m_IgnoreVehicleTimer.Expired() && GetEnemy() && HasCondition( COND_SEE_ENEMY ) )
+ {
+ CBaseEntity *pVehicle = GetEnemyVehicle();
+ if ( ( pVehicle ) && ( GlobalEntity_GetCounter( s_iszHuntersToRunOver ) <= 0 ) )
+ {
+ static float timeDrawnArrow;
+
+ // Extrapolate the position of the vehicle and see if it's heading toward us.
+ float predictTime = hunter_dodge_warning.GetFloat();
+ Vector2D vecFuturePos = pVehicle->GetAbsOrigin().AsVector2D() + pVehicle->GetSmoothedVelocity().AsVector2D() * predictTime;
+ if ( pVehicle->GetSmoothedVelocity().LengthSqr() > Square( 200 ) )
+ {
+ float t = 0;
+ Vector2D vDirMovement = pVehicle->GetSmoothedVelocity().AsVector2D();
+ if ( hunter_dodge_debug.GetBool() )
+ {
+ NDebugOverlay::Line( pVehicle->GetAbsOrigin(), pVehicle->GetAbsOrigin() + pVehicle->GetSmoothedVelocity(), 255, 255, 255, true, .1 );
+ }
+ vDirMovement.NormalizeInPlace();
+ Vector2D vDirToHunter = GetAbsOrigin().AsVector2D() - pVehicle->GetAbsOrigin().AsVector2D();
+ vDirToHunter.NormalizeInPlace();
+ if ( DotProduct2D( vDirMovement, vDirToHunter ) > hunter_dodge_warning_cone.GetFloat() &&
+ CalcDistanceSqrToLine2D( GetAbsOrigin().AsVector2D(), pVehicle->GetAbsOrigin().AsVector2D(), vecFuturePos, &t ) < Square( hunter_dodge_warning_width.GetFloat() * .5 ) &&
+ t > 0.0 && t < 1.0 )
+ {
+ if ( fabs( predictTime - hunter_dodge_warning.GetFloat() ) < .05 || random->RandomInt( 0, 3 ) )
+ {
+ SetCondition( COND_HUNTER_INCOMING_VEHICLE );
+ }
+ else
+ {
+ if ( hunter_dodge_debug. GetBool() )
+ {
+ Msg( "Hunter %d failing dodge (ignore)\n", entindex() );
+ }
+ }
+
+ if ( hunter_dodge_debug. GetBool() )
+ {
+ NDebugOverlay::Cross3D( EyePosition(), 100, 255, 255, 255, true, .1 );
+ if ( timeDrawnArrow != gpGlobals->curtime )
+ {
+ timeDrawnArrow = gpGlobals->curtime;
+ Vector vEndpoint( vecFuturePos.x, vecFuturePos.y, UTIL_GetLocalPlayer()->WorldSpaceCenter().z - 24 );
+ NDebugOverlay::HorzArrow( UTIL_GetLocalPlayer()->WorldSpaceCenter() - Vector(0, 0, 24), vEndpoint, hunter_dodge_warning_width.GetFloat(), 255, 0, 0, 64, true, .1 );
+ }
+ }
+ }
+ else if ( hunter_dodge_debug.GetBool() )
+ {
+ if ( t <= 0 )
+ {
+ NDebugOverlay::Cross3D( EyePosition(), 100, 0, 0, 255, true, .1 );
+ }
+ else
+ {
+ NDebugOverlay::Cross3D( EyePosition(), 100, 0, 255, 255, true, .1 );
+ }
+ }
+ }
+ else if ( hunter_dodge_debug.GetBool() )
+ {
+ NDebugOverlay::Cross3D( EyePosition(), 100, 0, 255, 0, true, .1 );
+ }
+ if ( hunter_dodge_debug. GetBool() )
+ {
+ if ( timeDrawnArrow != gpGlobals->curtime )
+ {
+ timeDrawnArrow = gpGlobals->curtime;
+ Vector vEndpoint( vecFuturePos.x, vecFuturePos.y, UTIL_GetLocalPlayer()->WorldSpaceCenter().z - 24 );
+ NDebugOverlay::HorzArrow( UTIL_GetLocalPlayer()->WorldSpaceCenter() - Vector(0, 0, 24), vEndpoint, hunter_dodge_warning_width.GetFloat(), 127, 127, 127, 64, true, .1 );
+ }
+ }
+
+ }
+
+ m_vecEnemyLastSeen = GetEnemy()->GetAbsOrigin();
+ }
+
+ if( !HasCondition(COND_ENEMY_OCCLUDED) )
+ {
+ // m_flTimeSawEnemyAgain always tells us what time I first saw this
+ // enemy again after some period of not seeing them. This is used to
+ // compute how long the enemy has been visible to me THIS TIME.
+ // Every time I lose sight of the enemy this time is set invalid until
+ // I see the enemy again and record that time.
+ if( m_flTimeSawEnemyAgain == HUNTER_SEE_ENEMY_TIME_INVALID )
+ {
+ m_flTimeSawEnemyAgain = gpGlobals->curtime;
+ }
+ }
+ else
+ {
+ m_flTimeSawEnemyAgain = HUNTER_SEE_ENEMY_TIME_INVALID;
+ }
+
+ ManageSiegeTargets();
+}
+
+//-----------------------------------------------------------------------------
+// Search all entities in the map
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::CollectSiegeTargets()
+{
+ CBaseEntity *pTarget = gEntList.FindEntityByName( NULL, m_iszSiegeTargetName );
+
+ while( pTarget != NULL )
+ {
+ if( pTarget->Classify() == CLASS_BULLSEYE )
+ {
+ m_pSiegeTargets.AddToTail( pTarget );
+ }
+
+ pTarget = gEntList.FindEntityByName( pTarget, m_iszSiegeTargetName );
+ };
+
+ if( m_pSiegeTargets.Count() < 1 )
+ {
+ m_iszSiegeTargetName = NULL_STRING; // And stop trying!
+ }
+}
+
+//-----------------------------------------------------------------------------
+// For use when Hunters are outside and the player is inside a structure
+// Create a temporary bullseye in a location that makes it seem like
+// I am aware of the location of a player I cannot see. (Then fire at
+// at this bullseye, thus laying 'siege' to the part of the building he
+// is in.) The locations are copied from suitable info_target entities.
+// (these should be placed in exterior windows and doorways so that
+// the Hunter fires into the building through these apertures)
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::ManageSiegeTargets()
+{
+ if( gpGlobals->curtime < m_flTimeNextSiegeTargetAttack )
+ return;
+
+ if( m_pSiegeTargets.Count() == 0 )
+ {
+ // If my list of siege targets is empty, go and cache all of them now
+ // so that I don't have to search the world every time.
+ CollectSiegeTargets();
+
+ if( m_pSiegeTargets.Count() == 0 )
+ return;
+ }
+
+ m_flTimeNextSiegeTargetAttack = gpGlobals->curtime + (hunter_siege_frequency.GetFloat() * RandomFloat( 0.8f, 1.2f) );
+ CBasePlayer *pPlayer = AI_GetSinglePlayer();
+
+ // Start by assuming we are not going to create a siege target
+ bool bCreateSiegeTarget = false;
+ if( GetEnemy() == NULL )
+ {
+ // If I have no enemy at all, give it a try.
+ bCreateSiegeTarget = true;
+ }
+
+ if( bCreateSiegeTarget )
+ {
+ // We've decided that the situation calls for a siege target. So, we dig through all of my siege targets and
+ // take the closest one to the player that the player can see! (Obey they bullseye's FOV)
+ float flClosestDistSqr = Square( 1200.0f ); // Only use siege targets within 100 feet of player
+ CBaseEntity *pSiegeTargetLocation = NULL;
+ int iTraces = 0;
+ for( int i = 0 ; i < m_pSiegeTargets.Count() ; i++ )
+ {
+ CBaseEntity *pCandidate = m_pSiegeTargets[i];
+ if ( pCandidate == NULL )
+ continue;
+
+ float flDistSqr = pCandidate->GetAbsOrigin().DistToSqr(pPlayer->GetAbsOrigin());
+
+ if( flDistSqr < flClosestDistSqr )
+ {
+ // CollectSiegeTargets() guarantees my list is populated only with bullseye entities.
+ CNPC_Bullseye *pBullseye = dynamic_cast<CNPC_Bullseye*>(pCandidate);
+ if( !pBullseye->FInViewCone(this) )
+ continue;
+
+ if( pPlayer->FVisible(pCandidate) )
+ {
+ iTraces++;// Only counting these as a loose perf measurement
+ flClosestDistSqr = flDistSqr;
+ pSiegeTargetLocation = pCandidate;
+ }
+ }
+ }
+
+ if( pSiegeTargetLocation != NULL )
+ {
+ // Ditch any leftover siege target.
+ KillCurrentSiegeTarget();
+
+ // Create a bullseye that will live for 20 seconds. If we can't attack it within 20 seconds, it's probably
+ // out of reach anyone, so have it clean itself up after that long.
+ CBaseEntity *pSiegeTarget = CreateCustomTarget( pSiegeTargetLocation->GetAbsOrigin(), 20.0f );
+ pSiegeTarget->SetName( MAKE_STRING("siegetarget") );
+
+ m_hCurrentSiegeTarget.Set( pSiegeTarget );
+
+ AddEntityRelationship( pSiegeTarget, D_HT, 1 );
+ GetEnemies()->UpdateMemory( GetNavigator()->GetNetwork(), pSiegeTarget, pSiegeTarget->GetAbsOrigin(), 0.0f, true );
+ AI_EnemyInfo_t *pMemory = GetEnemies()->Find( pSiegeTarget );
+
+ if( pMemory )
+ {
+ // Pretend we've known about this target longer than we really have so that our AI doesn't waste time running ALERT schedules.
+ pMemory->timeFirstSeen = gpGlobals->curtime - 5.0f;
+ pMemory->timeLastSeen = gpGlobals->curtime - 1.0f;
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Destroy the bullseye that we're using as a temporary target
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::KillCurrentSiegeTarget()
+{
+ if ( m_hCurrentSiegeTarget )
+ {
+ GetEnemies()->ClearMemory( m_hCurrentSiegeTarget );
+
+ UTIL_Remove( m_hCurrentSiegeTarget );
+ m_hCurrentSiegeTarget.Set( NULL );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Return true if this NPC can hear the specified sound
+//-----------------------------------------------------------------------------
+bool CNPC_Hunter::QueryHearSound( CSound *pSound )
+{
+ if ( pSound->SoundContext() & SOUND_CONTEXT_EXCLUDE_COMBINE )
+ return false;
+
+ if ( pSound->SoundContext() & SOUND_CONTEXT_PLAYER_VEHICLE )
+ return false;
+
+ return BaseClass::QueryHearSound( pSound );
+}
+
+
+//-----------------------------------------------------------------------------
+// This is a fairly bogus heuristic right now, but it works on 06a and 12 (sjb)
+//
+// Better options: Trace infinitely and check the material we hit for sky
+// Put some leaf info in the BSP
+// Use volumes in the levels? (yucky for designers)
+//-----------------------------------------------------------------------------
+// TODO: use this or nuke it!
+void CNPC_Hunter::GatherIndoorOutdoorConditions()
+{
+ // Check indoor/outdoor before calling base class, since base class calls our
+ // RangeAttackConditions() functions, and we want those functions to know
+ // whether we're indoors or out.
+ trace_t tr;
+
+ UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() + Vector( 0, 0, 40.0f * 12.0f ), MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
+ if( tr.fraction < 1.0f )
+ {
+ SetCondition( COND_HUNTER_IS_INDOORS );
+ }
+ else
+ {
+ ClearCondition( COND_HUNTER_IS_INDOORS );
+ }
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::BuildScheduleTestBits()
+{
+ BaseClass::BuildScheduleTestBits();
+
+ if ( m_lifeState != LIFE_ALIVE )
+ {
+ return;
+ }
+
+ // Our range attack is uninterruptable for the first few seconds.
+ if ( IsCurSchedule( SCHED_HUNTER_RANGE_ATTACK2, false ) && ( gpGlobals->curtime < m_flShootAllowInterruptTime ) )
+ {
+ ClearCustomInterruptConditions();
+ SetCustomInterruptCondition( COND_HEAVY_DAMAGE );
+ }
+ else if ( IsCurSchedule( SCHED_HUNTER_RANGE_ATTACK2, false ) && ( GetActivity() == ACT_TRANSITION ) )
+ {
+ // Don't stop unplanting just because we can range attack again.
+ ClearCustomInterruptCondition( COND_CAN_RANGE_ATTACK1 );
+ ClearCustomInterruptCondition( COND_CAN_RANGE_ATTACK2 );
+ }
+ else if ( !IsInLargeOutdoorMap() && IsCurSchedule( SCHED_HUNTER_FLANK_ENEMY, false ) && GetEnemy() != NULL )
+ {
+ if( HasCondition(COND_CAN_RANGE_ATTACK2) && m_flTimeSawEnemyAgain != HUNTER_SEE_ENEMY_TIME_INVALID )
+ {
+ if( (gpGlobals->curtime - m_flTimeSawEnemyAgain) >= 2.0f )
+ {
+ // When we're running flank behavior, wait a moment AFTER being able to see the enemy before
+ // breaking my schedule to range attack. This helps assure that the hunter gets well inside
+ // the room before stopping to attack. Otherwise the Hunter may stop immediately in the doorway
+ // and stop the progress of any hunters behind it.
+ SetCustomInterruptCondition( COND_CAN_RANGE_ATTACK2 );
+ }
+ }
+ }
+
+ // If our enemy is anything but a striderbuster, drop everything if we see one.
+ if ( !IsStriderBuster( GetEnemy() ) )
+ {
+ SetCustomInterruptCondition( COND_HUNTER_SEE_STRIDERBUSTER );
+ }
+
+ // If we're not too busy, allow ourselves to ACK found enemy signals.
+ if ( !GetEnemy() )
+ {
+ SetCustomInterruptCondition( COND_HUNTER_SQUADMATE_FOUND_ENEMY );
+ }
+
+ // Interrupt everything if we need to dodge.
+ if ( !IsCurSchedule( SCHED_HUNTER_DODGE, false ) &&
+ !IsCurSchedule( SCHED_HUNTER_STAGGER, false ) &&
+ !IsCurSchedule( SCHED_ALERT_FACE_BESTSOUND, false ) )
+ {
+ SetCustomInterruptCondition( COND_HUNTER_INCOMING_VEHICLE );
+ SetCustomInterruptCondition( COND_HEAR_PHYSICS_DANGER );
+ SetCustomInterruptCondition( COND_HUNTER_FORCED_DODGE );
+ }
+
+ // Always interrupt on a flank command.
+ SetCustomInterruptCondition( COND_HUNTER_FORCED_FLANK_ENEMY );
+
+ // Always interrupt if staggered.
+ SetCustomInterruptCondition( COND_HUNTER_STAGGERED );
+
+ // Always interrupt if hit by a sticky bomb.
+ SetCustomInterruptCondition( COND_HUNTER_HIT_BY_STICKYBOMB );
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+static bool IsMovablePhysicsObject( CBaseEntity *pEntity )
+{
+ return pEntity && pEntity->GetMoveType() == MOVETYPE_VPHYSICS && pEntity->VPhysicsGetObject() && pEntity->VPhysicsGetObject()->IsMoveable();
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+NPC_STATE CNPC_Hunter::SelectIdealState()
+{
+ switch ( m_NPCState )
+ {
+ case NPC_STATE_COMBAT:
+ {
+ if ( GetEnemy() == NULL )
+ {
+ if ( !HasCondition( COND_ENEMY_DEAD ) && !hunter_disable_patrol.GetBool() )
+ {
+ // Lost track of my enemy. Patrol.
+ SetCondition( COND_HUNTER_SHOULD_PATROL );
+ }
+
+ return NPC_STATE_ALERT;
+ }
+ else if ( HasCondition( COND_ENEMY_DEAD ) )
+ {
+ // dvs: TODO: announce enemy kills?
+ //AnnounceEnemyKill(GetEnemy());
+ }
+ }
+
+ default:
+ {
+ return BaseClass::SelectIdealState();
+ }
+ }
+
+ return GetIdealState();
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+bool CNPC_Hunter::ShouldCharge( const Vector &startPos, const Vector &endPos, bool useTime, bool bCheckForCancel )
+{
+ // Must have a target
+ if ( !GetEnemy() )
+ return false;
+
+ // Don't check the distance once we start charging
+ if ( !bCheckForCancel && !hunter_charge_test.GetBool() )
+ {
+ float distance = ( startPos.AsVector2D() - endPos.AsVector2D() ).LengthSqr();
+
+ // Must be within our tolerance range
+ if ( ( distance < Square(HUNTER_CHARGE_MIN) ) || ( distance > Square(HUNTER_CHARGE_MAX) ) )
+ return false;
+ }
+
+ // FIXME: We'd like to exclude small physics objects from this check!
+
+ // We only need to hit the endpos with the edge of our bounding box
+ Vector vecDir = endPos - startPos;
+ VectorNormalize( vecDir );
+ float flWidth = WorldAlignSize().x * 0.5;
+ Vector vecTargetPos = endPos - (vecDir * flWidth);
+
+ // See if we can directly move there
+ AIMoveTrace_t moveTrace;
+ GetMoveProbe()->MoveLimit( NAV_GROUND, startPos, vecTargetPos, MASK_NPCSOLID_BRUSHONLY, GetEnemy(), &moveTrace );
+
+ // Draw the probe
+ if ( g_debug_hunter_charge.GetInt() == 1 )
+ {
+ Vector enemyDir = (vecTargetPos - startPos);
+ float enemyDist = VectorNormalize( enemyDir );
+
+ NDebugOverlay::BoxDirection( startPos, GetHullMins(), GetHullMaxs() + Vector(enemyDist,0,0), enemyDir, 0, 255, 0, 8, 1.0f );
+ }
+
+ // If we're not blocked, charge
+ if ( IsMoveBlocked( moveTrace ) )
+ {
+ // Don't allow it if it's too close to us
+ if ( UTIL_DistApprox( WorldSpaceCenter(), moveTrace.vEndPosition ) < HUNTER_CHARGE_MIN )
+ return false;
+
+ // Allow some special cases to not block us
+ if ( moveTrace.pObstruction != NULL )
+ {
+ // If we've hit the world, see if it's a cliff
+ if ( moveTrace.pObstruction == GetContainingEntity( INDEXENT(0) ) )
+ {
+ // Can't be too far above/below the target
+ if ( fabs( moveTrace.vEndPosition.z - vecTargetPos.z ) > StepHeight() )
+ return false;
+
+ // Allow it if we got pretty close
+ if ( UTIL_DistApprox( moveTrace.vEndPosition, vecTargetPos ) < 64 )
+ return true;
+ }
+
+ // Hit things that will take damage
+ if ( moveTrace.pObstruction->m_takedamage != DAMAGE_NO )
+ return true;
+
+ // Hit things that will move
+ if ( moveTrace.pObstruction->GetMoveType() == MOVETYPE_VPHYSICS )
+ return true;
+ }
+
+ return false;
+ }
+
+ float zDelta = endPos.z - moveTrace.vEndPosition.z;
+ if ( fabsf(zDelta) > GetHullHeight() * 0.7)
+ {
+ return false;
+ }
+
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+bool CNPC_Hunter::HandleInteraction(int interactionType, void *data, CBaseCombatCharacter *pSourceEnt)
+{
+ if ( ( pSourceEnt != this ) && ( interactionType == g_interactionHunterFoundEnemy ) )
+ {
+ SetCondition( COND_HUNTER_SQUADMATE_FOUND_ENEMY );
+ return true;
+ }
+
+ return BaseClass::HandleInteraction( interactionType, data, pSourceEnt );
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+int CNPC_Hunter::SelectCombatSchedule()
+{
+ // If we're here with no enemy, patrol and hope we find one.
+ CBaseEntity *pEnemy = GetEnemy();
+ if ( pEnemy == NULL )
+ {
+ if ( !hunter_disable_patrol.GetBool() )
+ return SCHED_HUNTER_PATROL_RUN;
+ else
+ return SCHED_ALERT_STAND;
+ }
+
+ if ( hunter_flechette_test.GetBool() )
+ {
+ if ( HasCondition( COND_CAN_RANGE_ATTACK2 ) )
+ {
+ return SCHED_HUNTER_RANGE_ATTACK2;
+ }
+ return SCHED_COMBAT_FACE;
+ }
+
+ bool bStriderBuster = IsStriderBuster( pEnemy );
+ if ( bStriderBuster )
+ {
+ if ( gpGlobals->curtime - CAI_HunterEscortBehavior::gm_flLastDefendSound > 10.0 )
+ {
+ EmitSound( "NPC_Hunter.DefendStrider" );
+ CAI_HunterEscortBehavior::gm_flLastDefendSound = gpGlobals->curtime;
+ }
+
+ if ( HasCondition( COND_CAN_RANGE_ATTACK2 ) || HasCondition( COND_NOT_FACING_ATTACK ) )
+ {
+ return SCHED_HUNTER_RANGE_ATTACK2;
+ }
+ return SCHED_ESTABLISH_LINE_OF_FIRE;
+ }
+
+ // Certain behaviors, like flanking and melee attacks, only make sense on visible,
+ // corporeal enemies (NOT bullseyes).
+ bool bIsCorporealEnemy = IsCorporealEnemy( pEnemy );
+
+ // Take a quick swipe at our enemy if able to do so.
+ if ( bIsCorporealEnemy && HasCondition( COND_CAN_MELEE_ATTACK1 ) )
+ {
+ return SCHED_HUNTER_MELEE_ATTACK1;
+ }
+
+ // React to newly acquired enemies.
+ if ( bIsCorporealEnemy && HasCondition( COND_NEW_ENEMY ) )
+ {
+ AI_EnemyInfo_t *pEnemyInfo = GetEnemies()->Find( pEnemy );
+
+ if ( GetSquad() && pEnemyInfo && ( pEnemyInfo->timeFirstSeen == pEnemyInfo->timeAtFirstHand ) )
+ {
+ GetSquad()->BroadcastInteraction( g_interactionHunterFoundEnemy, NULL, this );
+
+ // First contact for my squad.
+ return SCHED_HUNTER_FOUND_ENEMY;
+ }
+ }
+
+ if ( HasCondition( COND_HUNTER_SQUADMATE_FOUND_ENEMY ) )
+ {
+ // A squadmate found an enemy. Respond to their call.
+ return SCHED_HUNTER_FOUND_ENEMY_ACK;
+ }
+
+ // Fire a flechette volley. Ignore squad slots if we're attacking a striderbuster.
+ // See if there is an opportunity to charge.
+ if ( !bStriderBuster && bIsCorporealEnemy && HasCondition( COND_HUNTER_CAN_CHARGE_ENEMY ) )
+ {
+ if ( hunter_charge_test.GetBool() || random->RandomInt( 1, 100 ) < hunter_charge_pct.GetInt() )
+ {
+ if ( hunter_charge_test.GetBool() || OccupyStrategySlot( SQUAD_SLOT_HUNTER_CHARGE ) )
+ {
+ return SCHED_HUNTER_CHARGE_ENEMY;
+ }
+ }
+ }
+
+ if ( HasCondition( COND_CAN_RANGE_ATTACK2 ) )
+ {
+ if ( bStriderBuster || CountRangedAttackers() < hunter_flechette_max_concurrent_volleys.GetInt() )
+ {
+ DelayRangedAttackers( hunter_flechette_volley_start_min_delay.GetFloat(), hunter_flechette_volley_start_max_delay.GetFloat(), true );
+ return SCHED_HUNTER_RANGE_ATTACK2;
+ }
+ }
+
+ if ( pEnemy->GetGroundEntity() == this )
+ {
+ return SCHED_HUNTER_MELEE_ATTACK1;
+ }
+
+ if ( HasCondition( COND_TOO_CLOSE_TO_ATTACK ) )
+ {
+ return SCHED_MOVE_AWAY_FROM_ENEMY;
+ }
+
+ // Sidestep every so often if my enemy is nearby and facing me.
+/*
+ if ( gpGlobals->curtime > m_flNextSideStepTime )
+ {
+ if ( HasCondition( COND_ENEMY_FACING_ME ) && ( UTIL_DistApprox( GetEnemy()->GetAbsOrigin(), GetAbsOrigin() ) < HUNTER_FACE_ENEMY_DIST ) )
+ {
+ m_flNextSideStepTime = gpGlobals->curtime + random->RandomFloat( 1.0f, 3.0f );
+ return SCHED_HUNTER_SIDESTEP;
+ }
+ }
+*/
+ if ( HasCondition( COND_HEAVY_DAMAGE ) && ( gpGlobals->curtime > m_flNextSideStepTime ) )
+ {
+ m_flNextSideStepTime = gpGlobals->curtime + random->RandomFloat( 1.0f, 3.0f );
+ return SCHED_HUNTER_SIDESTEP;
+ }
+
+ if ( !bStriderBuster && bIsCorporealEnemy )
+ {
+ if ( HasCondition( COND_HUNTER_CAN_CHARGE_ENEMY ) )
+ {
+ if ( OccupyStrategySlot( SQUAD_SLOT_HUNTER_CHARGE ) )
+ {
+ return SCHED_HUNTER_CHARGE_ENEMY;
+ }
+/*
+ else
+ {
+ return SCHED_HUNTER_SIDESTEP;
+ }
+*/
+ }
+
+ // Try to be a flanker.
+ if ( ( NumHuntersInMySquad() > 1 ) && OccupyStrategySlotRange( SQUAD_SLOT_HUNTER_FLANK_FIRST, SQUAD_SLOT_HUNTER_FLANK_LAST ) )
+ {
+ return SCHED_HUNTER_FLANK_ENEMY;
+ }
+ }
+
+ // Can't see my enemy.
+ if ( HasCondition( COND_ENEMY_OCCLUDED ) || HasCondition( COND_ENEMY_TOO_FAR ) || HasCondition( COND_TOO_FAR_TO_ATTACK ) || HasCondition( COND_NOT_FACING_ATTACK ) )
+ {
+ return SCHED_HUNTER_CHASE_ENEMY;
+ }
+
+ if ( HasCondition( COND_HUNTER_CANT_PLANT ) )
+ {
+ return SCHED_ESTABLISH_LINE_OF_FIRE;
+ }
+
+ //if ( HasCondition( COND_ENEMY_OCCLUDED ) && IsCurSchedule( SCHED_RANGE_ATTACK1, false ) )
+ //{
+ // return SCHED_HUNTER_COMBAT_FACE;
+ //}
+
+ return SCHED_HUNTER_CHANGE_POSITION;
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+int CNPC_Hunter::SelectSiegeSchedule()
+{
+ bool bHasEnemy = (GetEnemy() != NULL);
+
+ if( bHasEnemy )
+ {
+ // We have an enemy, so we should be making every effort to attack it.
+ if( !HasCondition(COND_SEE_ENEMY) || !HasCondition(COND_CAN_RANGE_ATTACK2) )
+ return SCHED_ESTABLISH_LINE_OF_FIRE;
+
+ if( HasCondition(COND_CAN_RANGE_ATTACK2) )
+ return SCHED_HUNTER_RANGE_ATTACK2;
+
+ return SCHED_HUNTER_SIEGE_STAND;
+ }
+ else
+ {
+ // Otherwise we are loitering in siege mode. Break line of sight with the player
+ // if they expose our position.
+ if( HasCondition( COND_SEE_PLAYER ) )
+ return SCHED_HUNTER_CHANGE_POSITION_SIEGE;
+ }
+
+ return SCHED_HUNTER_SIEGE_STAND;
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+int CNPC_Hunter::SelectSchedule()
+{
+ if ( hunter_stand_still.GetBool() )
+ {
+ m_bPlanted = false;
+ return SCHED_IDLE_STAND;
+ }
+
+ if ( HasCondition( COND_HUNTER_FORCED_DODGE ) )
+ return SCHED_HUNTER_DODGE;
+
+ if ( HasCondition( COND_HUNTER_NEW_HINTGROUP ) || ( GetHintGroup() != NULL_STRING && m_CheckHintGroupTimer.Expired() ) )
+ {
+ CAI_Hint *pHint;
+ CHintCriteria criteria;
+ criteria.SetGroup( GetHintGroup() );
+ criteria.SetFlag( bits_HINT_NODE_NEAREST );
+
+ if ( HasCondition( COND_HUNTER_NEW_HINTGROUP ) )
+ {
+ ClearCondition( COND_HUNTER_NEW_HINTGROUP );
+ if ( GetEnemy() )
+ {
+ pHint = CAI_HintManager::FindHint( NULL, GetEnemy()->GetAbsOrigin(), criteria );
+ }
+ else
+ {
+ pHint = CAI_HintManager::FindHint( GetAbsOrigin(), criteria );
+ }
+
+ if ( pHint )
+ {
+ pHint->Lock( this );
+ }
+ }
+ else
+ {
+ pHint = CAI_HintManager::FindHint( GetAbsOrigin(), criteria );
+ if ( pHint )
+ {
+ if ( (pHint->GetAbsOrigin() - GetAbsOrigin()).Length2DSqr() < Square( 20*12 ) )
+ {
+ m_CheckHintGroupTimer.Set( 5 );
+ pHint = NULL;
+ }
+ else
+ {
+ m_CheckHintGroupTimer.Set( 15 );
+ }
+ }
+ }
+
+ if ( pHint )
+ {
+ SetHintNode( pHint );
+ return SCHED_HUNTER_GOTO_HINT;
+ }
+ }
+
+ if ( HasCondition( COND_HUNTER_INCOMING_VEHICLE ) )
+ {
+ if ( m_RundownDelay.Expired() )
+ {
+ int iRundownCounter = 0;
+ if ( GetSquad() )
+ {
+ GetSquad()->GetSquadData( HUNTER_RUNDOWN_SQUADDATA, &iRundownCounter );
+ }
+
+ if ( iRundownCounter % 2 == 0 )
+ {
+ for ( int i = 0; i < g_Hunters.Count(); i++ )
+ {
+ if ( g_Hunters[i] != this )
+ {
+ g_Hunters[i]->m_RundownDelay.Set( 3 );
+ g_Hunters[i]->m_IgnoreVehicleTimer.Force();
+ }
+ }
+ m_IgnoreVehicleTimer.Set( hunter_dodge_warning.GetFloat() * 4 );
+ if ( hunter_dodge_debug.GetBool() )
+ {
+ Msg( "Hunter %d rundown\n", entindex() );
+ }
+
+ if ( HasCondition( COND_SEE_ENEMY ) )
+ {
+ if ( m_bPlanted && HasCondition( COND_CAN_RANGE_ATTACK2 ) )
+ {
+ return SCHED_HUNTER_RANGE_ATTACK2;
+ }
+ else if ( random->RandomInt( 0, 1 ) )
+ {
+ return SCHED_HUNTER_CHARGE_ENEMY;
+ }
+ else
+ {
+ return SCHED_MOVE_AWAY;
+ }
+ }
+ else
+ {
+ SetTarget( UTIL_GetLocalPlayer() );
+ return SCHED_TARGET_FACE;
+ }
+ }
+ else
+ {
+ if ( hunter_dodge_debug.GetBool() )
+ {
+ Msg( "Hunter %d safe from rundown\n", entindex() );
+ }
+ for ( int i = 0; i < g_Hunters.Count(); i++ )
+ {
+ g_Hunters[i]->m_RundownDelay.Set( 4 );
+ g_Hunters[i]->m_IgnoreVehicleTimer.Force();
+ }
+ if ( GetSquad() )
+ {
+ GetSquad()->SetSquadData( HUNTER_RUNDOWN_SQUADDATA, iRundownCounter + 1 );
+ }
+ }
+ }
+
+ if ( HasCondition( COND_SEE_ENEMY ) )
+ {
+ if ( hunter_dodge_debug.GetBool() )
+ {
+ Msg( "Hunter %d try dodge\n", entindex() );
+ }
+ return SCHED_HUNTER_DODGE;
+ }
+ else
+ {
+ SetTarget( UTIL_GetLocalPlayer() );
+ return SCHED_TARGET_FACE;
+ }
+
+ CSound *pBestSound = GetBestSound( SOUND_PHYSICS_DANGER );
+ if ( pBestSound && ( pBestSound->SoundContext() & SOUND_CONTEXT_PLAYER_VEHICLE ) )
+ {
+ return SCHED_ALERT_FACE_BESTSOUND;
+ }
+ }
+
+ if ( HasCondition( COND_HUNTER_FORCED_FLANK_ENEMY ) )
+ {
+ return SCHED_HUNTER_FLANK_ENEMY;
+ }
+
+ if ( HasCondition( COND_HUNTER_STAGGERED ) /*|| HasCondition( COND_HUNTER_HIT_BY_STICKYBOMB )*/ )
+ {
+ return SCHED_HUNTER_STAGGER;
+ }
+
+ // Now that we're past all of the forced reactions to things, if we're running the siege
+ // behavior, go pick an appropriate siege schedule UNLESS we have an enemy. If we have
+ // an enemy, we should focus on attacking that enemy.
+ if( IsUsingSiegeTargets() )
+ {
+ return SelectSiegeSchedule();
+ }
+
+ // back away if there's a magnade glued to my head.
+ if ( hunter_retreat_striderbusters.GetBool() /*&& GetEnemy() && ( GetEnemy()->IsPlayer() )*/
+ && (m_hAttachedBusters.Count() > 0)
+ && m_fCorneredTimer < gpGlobals->curtime)
+ {
+ return SCHED_HUNTER_TAKE_COVER_FROM_ENEMY;
+ }
+
+ if ( !BehaviorSelectSchedule() )
+ {
+ switch ( GetState() )
+ {
+ case NPC_STATE_IDLE:
+ {
+ return SCHED_HUNTER_PATROL;
+ }
+
+ case NPC_STATE_ALERT:
+ {
+ if ( HasCondition( COND_HUNTER_SHOULD_PATROL ) )
+ return SCHED_HUNTER_PATROL;
+
+ break;
+ }
+
+ case NPC_STATE_COMBAT:
+ {
+ return SelectCombatSchedule();
+ }
+ }
+ }
+
+ return BaseClass::SelectSchedule();
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+int CNPC_Hunter::TranslateSchedule( int scheduleType )
+{
+ switch ( scheduleType )
+ {
+ case SCHED_RANGE_ATTACK1:
+ {
+ return SCHED_HUNTER_RANGE_ATTACK1;
+ }
+
+ case SCHED_RANGE_ATTACK2:
+ case SCHED_HUNTER_RANGE_ATTACK2:
+ {
+ if ( scheduleType == SCHED_RANGE_ATTACK2 )
+ {
+ Msg( "HUNTER IGNORING SQUAD SLOTS\n" );
+ }
+
+ if ( IsStriderBuster( GetEnemy() ) )
+ {
+ // Attack as FAST as possible. The point is to shoot down the buster.
+ return SCHED_HUNTER_RANGE_ATTACK2_VS_STRIDERBUSTER;
+ }
+
+ return SCHED_HUNTER_RANGE_ATTACK2;
+ }
+
+ case SCHED_MELEE_ATTACK1:
+ {
+ return SCHED_HUNTER_MELEE_ATTACK1;
+ }
+
+ case SCHED_ESTABLISH_LINE_OF_FIRE_FALLBACK:
+ {
+ return SCHED_HUNTER_CHANGE_POSITION;
+ }
+
+ case SCHED_ALERT_STAND:
+ {
+ if ( !hunter_disable_patrol.GetBool() )
+ return SCHED_HUNTER_PATROL_RUN;
+ break;
+ }
+
+ case SCHED_COMBAT_FACE:
+ {
+ return SCHED_HUNTER_COMBAT_FACE;
+ }
+
+ case SCHED_HUNTER_PATROL:
+ {
+ if ( hunter_disable_patrol.GetBool() )
+ {
+ return SCHED_IDLE_STAND;
+ }
+ break;
+ }
+ }
+
+ return BaseClass::TranslateSchedule( scheduleType );
+}
+
+
+//-----------------------------------------------------------------------------
+// catch blockage while escaping magnade
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::TaskFail( AI_TaskFailureCode_t code )
+{
+ if ( IsCurSchedule( SCHED_HUNTER_TAKE_COVER_FROM_ENEMY, false ) && ( code == FAIL_NO_ROUTE_BLOCKED ) )
+ {
+ // cornered!
+ if ( m_fCorneredTimer < gpGlobals->curtime )
+ {
+ m_fCorneredTimer = gpGlobals->curtime + 6.0f;
+ }
+ }
+
+ BaseClass::TaskFail( code );
+}
+
+
+//-----------------------------------------------------------------------------
+// The player is speeding toward us in a vehicle! Find a good activity for dodging.
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::TaskFindDodgeActivity()
+{
+ if ( GetEnemy() == NULL )
+ {
+ TaskFail( "No enemy to dodge" );
+ return;
+ }
+
+ Vector vecUp;
+ Vector vecRight;
+ GetVectors( NULL, &vecRight, &vecUp );
+
+ // TODO: find most perpendicular 8-way dodge when we get the anims
+ Vector vecEnemyDir = GetEnemy()->GetAbsOrigin() - GetAbsOrigin();
+ //Vector vecDir = CrossProduct( vecEnemyDir, vecUp );
+ VectorNormalize( vecEnemyDir );
+ if ( fabs( DotProduct( vecEnemyDir, vecRight ) ) > 0.7 )
+ {
+ TaskFail( "Can't dodge, enemy approaching perpendicularly" );
+ return;
+ }
+
+ // Check left or right randomly first.
+ bool bDodgeLeft = false;
+ CBaseEntity *pVehicle = GetEnemyVehicle();
+ if ( pVehicle )
+ {
+ Ray_t enemyRay;
+ Ray_t perpendicularRay;
+ enemyRay.Init( pVehicle->GetAbsOrigin(), pVehicle->GetAbsOrigin() + pVehicle->GetSmoothedVelocity() );
+ Vector vPerpendicularPt = vecEnemyDir;
+ vPerpendicularPt.y = -vPerpendicularPt.y;
+ perpendicularRay.Init( GetAbsOrigin(), GetAbsOrigin() + vPerpendicularPt );
+
+ enemyRay.m_Start.z = enemyRay.m_Delta.z = enemyRay.m_StartOffset.z;
+ perpendicularRay.m_Start.z = perpendicularRay.m_Delta.z = perpendicularRay.m_StartOffset.z;
+
+ float t, s;
+
+ IntersectRayWithRay( perpendicularRay, enemyRay, t, s );
+
+ if ( t > 0 )
+ {
+ bDodgeLeft = true;
+ }
+ }
+ else if ( random->RandomInt( 0, 1 ) == 0 )
+ {
+ bDodgeLeft = true;
+ }
+
+ bool bFoundDir = false;
+ int nTries = 0;
+
+ while ( !bFoundDir && ( nTries < 2 ) )
+ {
+ // Pick a dodge activity to try.
+ if ( bDodgeLeft )
+ {
+ m_eDodgeActivity = ACT_HUNTER_DODGEL;
+ }
+ else
+ {
+ m_eDodgeActivity = ACT_HUNTER_DODGER;
+ }
+
+ // See where the dodge will put us.
+ Vector vecLocalDelta;
+ int nSeq = SelectWeightedSequence( m_eDodgeActivity );
+ GetSequenceLinearMotion( nSeq, &vecLocalDelta );
+
+ // Transform the sequence delta into local space.
+ matrix3x4_t fRotateMatrix;
+ AngleMatrix( GetLocalAngles(), fRotateMatrix );
+ Vector vecDelta;
+ VectorRotate( vecLocalDelta, fRotateMatrix, vecDelta );
+
+ // Trace a bit high so this works better on uneven terrain.
+ Vector testHullMins = GetHullMins();
+ testHullMins.z += ( StepHeight() * 2 );
+
+ // See if all is clear in that direction.
+ trace_t tr;
+ HunterTraceHull_SkipPhysics( GetAbsOrigin(), GetAbsOrigin() + vecDelta, testHullMins, GetHullMaxs(), MASK_NPCSOLID, this, GetCollisionGroup(), &tr, VPhysicsGetObject()->GetMass() * 0.5f );
+
+ // TODO: dodge anyway if we'll make it a certain percentage of the way through the dodge?
+ if ( tr.fraction == 1.0f )
+ {
+ //NDebugOverlay::SweptBox( GetAbsOrigin(), GetAbsOrigin() + vecDelta, testHullMins, GetHullMaxs(), QAngle( 0, 0, 0 ), 0, 255, 0, 128, 5 );
+ bFoundDir = true;
+ TaskComplete();
+ }
+ else
+ {
+ //NDebugOverlay::SweptBox( GetAbsOrigin(), GetAbsOrigin() + vecDelta, testHullMins, GetHullMaxs(), QAngle( 0, 0, 0 ), 255, 0, 0, 128, 5 );
+ nTries++;
+ bDodgeLeft = !bDodgeLeft;
+ }
+ }
+
+ if ( nTries < 2 )
+ {
+ TaskComplete();
+ }
+ else
+ {
+ TaskFail( "Couldn't find dodge position\n" );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::StartTask( const Task_t *pTask )
+{
+ switch ( pTask->iTask )
+ {
+ case TASK_HUNTER_FINISH_RANGE_ATTACK:
+ {
+ if( GetEnemy() != NULL && GetEnemy()->Classify() == CLASS_PLAYER_ALLY_VITAL )
+ {
+ // Just finished shooting at Alyx! So forget her for a little while and get back on the player
+ // !!!LATER - make sure there's someone else in enemy memory to go bother.
+ GetEnemies()->SetTimeValidEnemy( GetEnemy(), gpGlobals->curtime + 10.0f );
+ }
+
+ if( m_hCurrentSiegeTarget )
+ {
+ // We probably just fired at our siege target, so dump it.
+ KillCurrentSiegeTarget();
+ }
+
+ TaskComplete();
+ }
+
+ case TASK_HUNTER_WAIT_FOR_MOVEMENT_FACING_ENEMY:
+ {
+ ChainStartTask( TASK_WAIT_FOR_MOVEMENT, pTask->flTaskData );
+ break;
+ }
+
+ case TASK_HUNTER_BEGIN_FLANK:
+ {
+ if ( IsInSquad() && GetSquad()->NumMembers() > 1 )
+ {
+ // Flank relative to the other shooter in our squad.
+ // If there's no other shooter, just flank relative to any squad member.
+ AISquadIter_t iter;
+ CAI_BaseNPC *pNPC = GetSquad()->GetFirstMember( &iter );
+ while ( pNPC == this )
+ {
+ pNPC = GetSquad()->GetNextMember( &iter );
+ }
+
+ m_vSavePosition = pNPC->GetAbsOrigin();
+ }
+ else
+ {
+ // Flank relative to our current position.
+ m_vSavePosition = GetAbsOrigin();
+ }
+
+ TaskComplete();
+ break;
+ }
+
+ case TASK_HUNTER_ANNOUNCE_FLANK:
+ {
+ EmitSound( "NPC_Hunter.FlankAnnounce" );
+ TaskComplete();
+ break;
+ }
+
+ case TASK_HUNTER_DODGE:
+ {
+ if ( hunter_dodge_debug. GetBool() )
+ {
+ Msg( "Hunter %d dodging\n", entindex() );
+ }
+ SetIdealActivity( m_eDodgeActivity );
+ break;
+ }
+
+ // Guarantee a certain delay between volleys. If we aren't already planted,
+ // the plant transition animation will take care of that.
+ case TASK_HUNTER_PRE_RANGE_ATTACK2:
+ {
+ if ( !m_bPlanted || ( GetEnemy() && IsStriderBuster( GetEnemy() ) ) )
+ {
+ TaskComplete();
+ }
+ else
+ {
+ SetIdealActivity( ACT_HUNTER_ANGRY );
+ }
+ break;
+ }
+
+ case TASK_HUNTER_SHOOT_COMMIT:
+ {
+ // We're committing to shooting. Don't allow interrupts until after we've shot a bit (see TASK_RANGE_ATTACK1).
+ m_flShootAllowInterruptTime = gpGlobals->curtime + 100.0f;
+ TaskComplete();
+ break;
+ }
+
+ case TASK_RANGE_ATTACK2:
+ {
+ if ( GetEnemy() )
+ {
+ bool bIsBuster = IsStriderBuster( GetEnemy() );
+ if ( bIsBuster )
+ {
+ AddFacingTarget( GetEnemy(), GetEnemy()->GetAbsOrigin() + GetEnemy()->GetSmoothedVelocity() * .5, 1.0, 0.8 );
+ }
+
+ // Start the firing sound.
+ //CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+ //controller.SoundChangeVolume( m_pGunFiringSound, 1.0, hunter_first_flechette_delay.GetFloat() );
+
+ SetIdealActivity( ACT_RANGE_ATTACK2 );
+
+ // Decide how many shots to fire.
+ int nShots = hunter_flechette_volley_size.GetInt();
+ if ( g_pGameRules->IsSkillLevel( SKILL_EASY ) )
+ {
+ nShots--;
+ }
+
+ // Decide when to fire the first shot.
+ float initialDelay = hunter_first_flechette_delay.GetFloat();
+ if ( bIsBuster )
+ {
+ initialDelay = 0; //*= 0.5;
+ }
+
+ BeginVolley( nShots, gpGlobals->curtime + initialDelay );
+
+ // In case we need to miss on purpose, pick a direction now.
+ m_bMissLeft = false;
+ if ( random->RandomInt( 0, 1 ) == 0 )
+ {
+ m_bMissLeft = true;
+ }
+
+ LockBothEyes( initialDelay + ( nShots * hunter_flechette_delay.GetFloat() ) );
+ }
+ else
+ {
+ TaskFail( FAIL_NO_ENEMY );
+ }
+
+ break;
+ }
+
+ case TASK_HUNTER_STAGGER:
+ {
+ // Stagger in the direction the impact force would push us.
+ VMatrix worldToLocalRotation = EntityToWorldTransform();
+ Vector vecLocalStaggerDir = worldToLocalRotation.InverseTR().ApplyRotation( m_vecStaggerDir );
+
+ float flStaggerYaw = VecToYaw( vecLocalStaggerDir );
+ SetPoseParameter( gm_nStaggerYawPoseParam, flStaggerYaw );
+
+ // Go straight there!
+ SetActivity( ACT_RESET );
+ SetActivity( ( Activity )ACT_HUNTER_STAGGER );
+ break;
+ }
+
+ case TASK_MELEE_ATTACK1:
+ {
+ SetLastAttackTime( gpGlobals->curtime );
+
+ if ( GetEnemy() && GetEnemy()->IsPlayer() )
+ {
+ ResetIdealActivity( ( Activity )ACT_HUNTER_MELEE_ATTACK1_VS_PLAYER );
+ }
+ else
+ {
+ ResetIdealActivity( ACT_MELEE_ATTACK1 );
+ }
+
+ break;
+ }
+
+ case TASK_HUNTER_CORNERED_TIMER:
+ {
+ m_fCorneredTimer = gpGlobals->curtime + pTask->flTaskData;
+
+ break;
+ }
+
+ case TASK_HUNTER_FIND_SIDESTEP_POSITION:
+ {
+ if ( GetEnemy() == NULL )
+ {
+ TaskFail( "No enemy to sidestep" );
+ }
+ else
+ {
+ Vector vecUp;
+ GetVectors( NULL, NULL, &vecUp );
+
+ Vector vecEnemyDir = GetEnemy()->GetAbsOrigin() - GetAbsOrigin();
+ Vector vecDir = CrossProduct( vecEnemyDir, vecUp );
+ VectorNormalize( vecDir );
+
+ // Sidestep left or right randomly.
+ if ( random->RandomInt( 0, 1 ) == 0 )
+ {
+ vecDir *= -1;
+ }
+
+ // Start high and then trace down so that it works on uneven terrain.
+ Vector vecPos = GetAbsOrigin() + Vector( 0, 0, 64 ) + random->RandomFloat( 120, 200 ) * vecDir;
+
+ // Try to find the ground at the sidestep position.
+ trace_t tr;
+ UTIL_TraceLine( vecPos, vecPos + Vector( 0, 0, -128 ), MASK_NPCSOLID, NULL, COLLISION_GROUP_NONE, &tr );
+ if ( tr.fraction < 1.0f )
+ {
+ //NDebugOverlay::Line( vecPos, tr.endpos, 0, 255, 0, true, 10 );
+
+ m_vSavePosition = tr.endpos;
+
+ TaskComplete();
+ }
+ else
+ {
+ TaskFail( "Couldn't find sidestep position\n" );
+ }
+ }
+
+ break;
+ }
+
+ case TASK_HUNTER_FIND_DODGE_POSITION:
+ {
+ TaskFindDodgeActivity();
+ break;
+ }
+
+ case TASK_HUNTER_CHARGE:
+ {
+ SetIdealActivity( ( Activity )ACT_HUNTER_CHARGE_START );
+ break;
+ }
+
+ case TASK_HUNTER_CHARGE_DELAY:
+ {
+ m_flNextChargeTime = gpGlobals->curtime + pTask->flTaskData;
+ TaskComplete();
+ break;
+ }
+
+ case TASK_DIE:
+ {
+ GetNavigator()->StopMoving();
+ ResetActivity();
+ SetIdealActivity( GetDeathActivity() );
+ m_lifeState = LIFE_DYING;
+
+ break;
+ }
+
+ //case TASK_HUNTER_END_FLANK:
+ //{
+ //
+ //}
+
+ default:
+ {
+ BaseClass::StartTask( pTask );
+ break;
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::RunTask( const Task_t *pTask )
+{
+ switch ( pTask->iTask )
+ {
+ case TASK_HUNTER_PRE_RANGE_ATTACK2:
+ {
+ if ( IsActivityFinished() )
+ {
+ TaskComplete();
+ }
+ break;
+ }
+
+ case TASK_RANGE_ATTACK2:
+ {
+ if( !hunter_hate_thrown_striderbusters.GetBool() && GetEnemy() != NULL && IsStriderBuster( GetEnemy() ) )
+ {
+ if( !IsValidEnemy(GetEnemy()) )
+ {
+ TaskFail("No longer hate this StriderBuster");
+ }
+ }
+
+ bool bIsBuster = IsStriderBuster( GetEnemy() );
+ if ( bIsBuster )
+ {
+ Vector vFuturePosition = GetEnemy()->GetAbsOrigin() + GetEnemy()->GetSmoothedVelocity() * .3;
+ AddFacingTarget( GetEnemy(), vFuturePosition, 1.0, 0.8 );
+
+ Vector2D vToFuturePositon = ( vFuturePosition.AsVector2D() - GetAbsOrigin().AsVector2D() );
+ vToFuturePositon.NormalizeInPlace();
+ Vector2D facingDir = BodyDirection2D().AsVector2D();
+
+ float flDot = DotProduct2D( vToFuturePositon, facingDir );
+
+ if ( flDot < .4 )
+ {
+ GetMotor()->SetIdealYawToTarget( vFuturePosition );
+ GetMotor()->UpdateYaw();
+ break;
+ }
+ }
+
+ if ( gpGlobals->curtime >= m_flNextFlechetteTime )
+ {
+ // Must have an enemy and a shot queued up.
+ bool bDone = false;
+ if ( GetEnemy() != NULL && m_nFlechettesQueued > 0 )
+ {
+ if ( ShootFlechette( GetEnemy(), false ) )
+ {
+ m_nClampedShots++;
+ }
+ else
+ {
+ m_nClampedShots = 0;
+ }
+
+ m_nFlechettesQueued--;
+
+ // If we fired three or more clamped shots in a row, call it quits so we don't look dumb.
+ if ( ( m_nClampedShots >= 3 ) || ( m_nFlechettesQueued == 0 ) )
+ {
+ bDone = true;
+ }
+ else
+ {
+ // More shooting to do. Schedule our next flechette.
+ m_flNextFlechetteTime = gpGlobals->curtime + hunter_flechette_delay.GetFloat();
+ }
+ }
+ else
+ {
+ bDone = true;
+ }
+
+ if ( bDone )
+ {
+ // Stop the firing sound.
+ //CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+ //controller.SoundChangeVolume( m_pGunFiringSound, 0.0f, 0.1f );
+
+ DelayRangedAttackers( hunter_flechette_volley_end_min_delay.GetFloat(), hunter_flechette_volley_end_max_delay.GetFloat(), true );
+ TaskComplete();
+ }
+ }
+
+ break;
+ }
+
+ case TASK_GET_PATH_TO_ENEMY_LOS:
+ {
+ ChainRunTask( TASK_GET_PATH_TO_ENEMY_LKP_LOS, pTask->flTaskData );
+ break;
+ }
+
+ case TASK_HUNTER_DODGE:
+ {
+ AutoMovement();
+
+ if ( IsActivityFinished() )
+ {
+ TaskComplete();
+ }
+ break;
+ }
+
+ case TASK_HUNTER_CORNERED_TIMER:
+ {
+ TaskComplete();
+ break;
+ }
+
+ case TASK_HUNTER_STAGGER:
+ {
+ if ( IsActivityFinished() )
+ {
+ TaskComplete();
+ }
+ break;
+ }
+
+ case TASK_HUNTER_CHARGE:
+ {
+ Activity eActivity = GetActivity();
+
+ // See if we're trying to stop after hitting/missing our target
+ if ( eActivity == ACT_HUNTER_CHARGE_STOP || eActivity == ACT_HUNTER_CHARGE_CRASH )
+ {
+ if ( IsActivityFinished() )
+ {
+ m_flNextChargeTime = gpGlobals->curtime + hunter_charge_min_delay.GetFloat() + random->RandomFloat( 0, 2.5 ) + random->RandomFloat( 0, 2.5 );
+ float delayMultiplier = ( g_pGameRules->IsSkillLevel( SKILL_EASY ) ) ? 1.5 : 1.0;
+ float groupDelay = gpGlobals->curtime + ( 2.0 + random->RandomFloat( 0, 2 ) ) * delayMultiplier;
+ for ( int i = 0; i < g_Hunters.Count(); i++ )
+ {
+ if ( g_Hunters[i] != this && g_Hunters[i]->m_flNextChargeTime < groupDelay )
+ {
+ g_Hunters[i]->m_flNextChargeTime = groupDelay;
+ }
+ }
+ TaskComplete();
+ return;
+ }
+
+ // Still in the process of slowing down. Run movement until it's done.
+ AutoMovement();
+ return;
+ }
+
+ // Check for manual transition
+ if ( ( eActivity == ACT_HUNTER_CHARGE_START ) && ( IsActivityFinished() ) )
+ {
+ SetIdealActivity( ACT_HUNTER_CHARGE_RUN );
+ }
+
+ // See if we're still running
+ if ( eActivity == ACT_HUNTER_CHARGE_RUN || eActivity == ACT_HUNTER_CHARGE_START )
+ {
+ if ( HasCondition( COND_NEW_ENEMY ) || HasCondition( COND_LOST_ENEMY ) || HasCondition( COND_ENEMY_DEAD ) )
+ {
+ SetIdealActivity( ACT_HUNTER_CHARGE_STOP );
+ return;
+ }
+ else
+ {
+ if ( GetEnemy() != NULL )
+ {
+ Vector goalDir = ( GetEnemy()->GetAbsOrigin() - GetAbsOrigin() );
+ VectorNormalize( goalDir );
+
+ if ( DotProduct( BodyDirection2D(), goalDir ) < 0.25f )
+ {
+ SetIdealActivity( ACT_HUNTER_CHARGE_STOP );
+ }
+ }
+ }
+ }
+
+ // Steer towards our target
+ float idealYaw;
+ if ( GetEnemy() == NULL )
+ {
+ idealYaw = GetMotor()->GetIdealYaw();
+ }
+ else
+ {
+ idealYaw = CalcIdealYaw( GetEnemy()->GetAbsOrigin() );
+ }
+
+ // Add in our steering offset
+ idealYaw += ChargeSteer();
+
+ // Turn to face
+ GetMotor()->SetIdealYawAndUpdate( idealYaw );
+
+ // See if we're going to run into anything soon
+ ChargeLookAhead();
+
+ // Let our animations simply move us forward. Keep the result
+ // of the movement so we know whether we've hit our target.
+ AIMoveTrace_t moveTrace;
+ if ( AutoMovement( GetEnemy(), &moveTrace ) == false )
+ {
+ // Only stop if we hit the world
+ if ( HandleChargeImpact( moveTrace.vEndPosition, moveTrace.pObstruction ) )
+ {
+ // If we're starting up, this is an error
+ if ( eActivity == ACT_HUNTER_CHARGE_START )
+ {
+ TaskFail( "Unable to make initial movement of charge\n" );
+ return;
+ }
+
+ // Crash unless we're trying to stop already
+ if ( eActivity != ACT_HUNTER_CHARGE_STOP )
+ {
+ if ( moveTrace.fStatus == AIMR_BLOCKED_WORLD && moveTrace.vHitNormal == vec3_origin )
+ {
+ SetIdealActivity( ACT_HUNTER_CHARGE_STOP );
+ }
+ else
+ {
+ // Shake the screen
+ if ( moveTrace.fStatus != AIMR_BLOCKED_NPC )
+ {
+ EmitSound( "NPC_Hunter.ChargeHitWorld" );
+ UTIL_ScreenShake( GetAbsOrigin(), 16.0f, 4.0f, 1.0f, 400.0f, SHAKE_START );
+ }
+ SetIdealActivity( ACT_HUNTER_CHARGE_CRASH );
+ }
+ }
+ }
+ else if ( moveTrace.pObstruction )
+ {
+ // If we hit another hunter, stop
+ if ( moveTrace.pObstruction->Classify() == CLASS_COMBINE_HUNTER )
+ {
+ // Crash unless we're trying to stop already
+ if ( eActivity != ACT_HUNTER_CHARGE_STOP )
+ {
+ SetIdealActivity( ACT_HUNTER_CHARGE_STOP );
+ }
+ }
+ // If we hit an antlion, don't stop, but kill it
+ // We never have hunters and antlions together, but you never know.
+ else if (moveTrace.pObstruction->Classify() == CLASS_ANTLION )
+ {
+ if ( FClassnameIs( moveTrace.pObstruction, "npc_antlionguard" ) )
+ {
+ // Crash unless we're trying to stop already
+ if ( eActivity != ACT_HUNTER_CHARGE_STOP )
+ {
+ SetIdealActivity( ACT_HUNTER_CHARGE_STOP );
+ }
+ }
+ else
+ {
+ Hunter_ApplyChargeDamage( this, moveTrace.pObstruction, moveTrace.pObstruction->GetHealth() );
+ }
+ }
+ }
+ }
+
+ break;
+ }
+
+ case TASK_HUNTER_WAIT_FOR_MOVEMENT_FACING_ENEMY:
+ {
+ if ( GetEnemy() )
+ {
+ Vector vecEnemyLKP = GetEnemyLKP();
+ AddFacingTarget( GetEnemy(), vecEnemyLKP, 1.0, 0.8 );
+ }
+ ChainRunTask( TASK_WAIT_FOR_MOVEMENT, pTask->flTaskData );
+ break;
+ }
+
+ default:
+ {
+ BaseClass::RunTask( pTask );
+ break;
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Return true if our charge target is right in front of the hunter.
+//-----------------------------------------------------------------------------
+bool CNPC_Hunter::EnemyIsRightInFrontOfMe( CBaseEntity **pEntity )
+{
+ if ( !GetEnemy() )
+ return false;
+
+ if ( (GetEnemy()->WorldSpaceCenter() - WorldSpaceCenter()).LengthSqr() < (156*156) )
+ {
+ Vector vecLOS = ( GetEnemy()->GetAbsOrigin() - GetAbsOrigin() );
+ vecLOS.z = 0;
+ VectorNormalize( vecLOS );
+ Vector vBodyDir = BodyDirection2D();
+ if ( DotProduct( vecLOS, vBodyDir ) > 0.8 )
+ {
+ // He's in front of me, and close. Make sure he's not behind a wall.
+ trace_t tr;
+ UTIL_TraceHull( WorldSpaceCenter(), GetEnemy()->WorldSpaceCenter(), GetHullMins() * 0.5, GetHullMaxs() * 0.5, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr );
+ if ( tr.m_pEnt == GetEnemy() )
+ {
+ *pEntity = tr.m_pEnt;
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+
+//-----------------------------------------------------------------------------
+// While charging, look ahead and see if we're going to run into anything.
+// If we are, start the gesture so it looks like we're anticipating the hit.
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::ChargeLookAhead( void )
+{
+#if 0
+ trace_t tr;
+ Vector vecForward;
+ GetVectors( &vecForward, NULL, NULL );
+ Vector vecTestPos = GetAbsOrigin() + ( vecForward * m_flGroundSpeed * 0.75 );
+ Vector testHullMins = GetHullMins();
+ testHullMins.z += (StepHeight() * 2);
+ HunterTraceHull_SkipPhysics( GetAbsOrigin(), vecTestPos, testHullMins, GetHullMaxs(), MASK_SHOT_HULL, this, COLLISION_GROUP_NONE, &tr, VPhysicsGetObject()->GetMass() * 0.5 );
+
+ //NDebugOverlay::Box( tr.startpos, testHullMins, GetHullMaxs(), 0, 255, 0, true, 0.1f );
+ //NDebugOverlay::Box( vecTestPos, testHullMins, GetHullMaxs(), 255, 0, 0, true, 0.1f );
+
+ if ( tr.fraction != 1.0 )
+ {
+ // dvs: TODO:
+ // Start playing the hit animation
+ //AddGesture( ACT_HUNTER_CHARGE_ANTICIPATION );
+ }
+#endif
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+float CNPC_Hunter::ChargeSteer()
+{
+ trace_t tr;
+ Vector testPos, steer, forward, right;
+ QAngle angles;
+ const float testLength = m_flGroundSpeed * 0.15f;
+
+ //Get our facing
+ GetVectors( &forward, &right, NULL );
+
+ steer = forward;
+
+ const float faceYaw = UTIL_VecToYaw( forward );
+
+ //Offset right
+ VectorAngles( forward, angles );
+ angles[YAW] += 45.0f;
+ AngleVectors( angles, &forward );
+
+ // Probe out
+ testPos = GetAbsOrigin() + ( forward * testLength );
+
+ // Offset by step height
+ Vector testHullMins = GetHullMins();
+ testHullMins.z += (StepHeight() * 2);
+
+ // Probe
+ HunterTraceHull_SkipPhysics( GetAbsOrigin(), testPos, testHullMins, GetHullMaxs(), MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr, VPhysicsGetObject()->GetMass() * 0.5f );
+
+ // Debug info
+ if ( g_debug_hunter_charge.GetInt() == 1 )
+ {
+ if ( tr.fraction == 1.0f )
+ {
+ NDebugOverlay::BoxDirection( GetAbsOrigin(), testHullMins, GetHullMaxs() + Vector(testLength,0,0), forward, 0, 255, 0, 8, 0.1f );
+ }
+ else
+ {
+ NDebugOverlay::BoxDirection( GetAbsOrigin(), testHullMins, GetHullMaxs() + Vector(testLength,0,0), forward, 255, 0, 0, 8, 0.1f );
+ }
+ }
+
+ // Add in this component
+ steer += ( right * 0.5f ) * ( 1.0f - tr.fraction );
+
+ // Offset left
+ angles[YAW] -= 90.0f;
+ AngleVectors( angles, &forward );
+
+ // Probe out
+ testPos = GetAbsOrigin() + ( forward * testLength );
+ HunterTraceHull_SkipPhysics( GetAbsOrigin(), testPos, testHullMins, GetHullMaxs(), MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr, VPhysicsGetObject()->GetMass() * 0.5f );
+
+ // Debug
+ if ( g_debug_hunter_charge.GetInt() == 1 )
+ {
+ if ( tr.fraction == 1.0f )
+ {
+ NDebugOverlay::BoxDirection( GetAbsOrigin(), testHullMins, GetHullMaxs() + Vector(testLength,0,0), forward, 0, 255, 0, 8, 0.1f );
+ }
+ else
+ {
+ NDebugOverlay::BoxDirection( GetAbsOrigin(), testHullMins, GetHullMaxs() + Vector(testLength,0,0), forward, 255, 0, 0, 8, 0.1f );
+ }
+ }
+
+ // Add in this component
+ steer -= ( right * 0.5f ) * ( 1.0f - tr.fraction );
+
+ // Debug
+ if ( g_debug_hunter_charge.GetInt() == 1 )
+ {
+ NDebugOverlay::Line( GetAbsOrigin(), GetAbsOrigin() + ( steer * 512.0f ), 255, 255, 0, true, 0.1f );
+ NDebugOverlay::Cross3D( GetAbsOrigin() + ( steer * 512.0f ), Vector(2,2,2), -Vector(2,2,2), 255, 255, 0, true, 0.1f );
+
+ NDebugOverlay::Line( GetAbsOrigin(), GetAbsOrigin() + ( BodyDirection3D() * 256.0f ), 255, 0, 255, true, 0.1f );
+ NDebugOverlay::Cross3D( GetAbsOrigin() + ( BodyDirection3D() * 256.0f ), Vector(2,2,2), -Vector(2,2,2), 255, 0, 255, true, 0.1f );
+ }
+
+ return UTIL_AngleDiff( UTIL_VecToYaw( steer ), faceYaw );
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::ChargeDamage( CBaseEntity *pTarget )
+{
+ if ( pTarget == NULL )
+ return;
+
+ CBasePlayer *pPlayer = ToBasePlayer( pTarget );
+
+ if ( pPlayer != NULL )
+ {
+ //Kick the player angles
+ pPlayer->ViewPunch( QAngle( 20, 20, -30 ) );
+
+ Vector dir = pPlayer->WorldSpaceCenter() - WorldSpaceCenter();
+ VectorNormalize( dir );
+ dir.z = 0.0f;
+
+ Vector vecNewVelocity = dir * 250.0f;
+ vecNewVelocity[2] += 128.0f;
+ pPlayer->SetAbsVelocity( vecNewVelocity );
+
+ color32 red = {128,0,0,128};
+ UTIL_ScreenFade( pPlayer, red, 1.0f, 0.1f, FFADE_IN );
+ }
+
+ // Player takes less damage
+ float flDamage = ( pPlayer == NULL ) ? 250 : sk_hunter_dmg_charge.GetFloat();
+
+ // If it's being held by the player, break that bond
+ Pickup_ForcePlayerToDropThisObject( pTarget );
+
+ // Calculate the physics force
+ Hunter_ApplyChargeDamage( this, pTarget, flDamage );
+}
+
+
+//-----------------------------------------------------------------------------
+// Handles the hunter charging into something. Returns true if it hit the world.
+//-----------------------------------------------------------------------------
+bool CNPC_Hunter::HandleChargeImpact( Vector vecImpact, CBaseEntity *pEntity )
+{
+ // Cause a shock wave from this point which will disrupt nearby physics objects
+ //ImpactShock( vecImpact, 128, 350 );
+
+ // Did we hit anything interesting?
+ if ( !pEntity || pEntity->IsWorld() )
+ {
+ // Robin: Due to some of the finicky details in the motor, the hunter will hit
+ // the world when it is blocked by our enemy when trying to step up
+ // during a moveprobe. To get around this, we see if the enemy's within
+ // a volume in front of the hunter when we hit the world, and if he is,
+ // we hit him anyway.
+ EnemyIsRightInFrontOfMe( &pEntity );
+
+ // Did we manage to find him? If not, increment our charge miss count and abort.
+ if ( pEntity->IsWorld() )
+ {
+ return true;
+ }
+ }
+
+ // Hit anything we don't like
+ if ( IRelationType( pEntity ) == D_HT && ( GetNextAttack() < gpGlobals->curtime ) )
+ {
+ EmitSound( "NPC_Hunter.ChargeHitEnemy" );
+
+ // dvs: TODO:
+ //if ( !IsPlayingGesture( ACT_HUNTER_CHARGE_HIT ) )
+ //{
+ // RestartGesture( ACT_HUNTER_CHARGE_HIT );
+ //}
+
+ ChargeDamage( pEntity );
+
+ if ( !pEntity->IsNPC() )
+ {
+ pEntity->ApplyAbsVelocityImpulse( ( BodyDirection2D() * 400 ) + Vector( 0, 0, 200 ) );
+ }
+
+ if ( !pEntity->IsAlive() && GetEnemy() == pEntity )
+ {
+ SetEnemy( NULL );
+ }
+
+ SetNextAttack( gpGlobals->curtime + 2.0f );
+
+ if ( !pEntity->IsAlive() || !pEntity->IsNPC() )
+ {
+ SetIdealActivity( ACT_HUNTER_CHARGE_STOP );
+ return false;
+ }
+ else
+ return true;
+
+ }
+
+ // Hit something we don't hate. If it's not moveable, crash into it.
+ if ( pEntity->GetMoveType() == MOVETYPE_NONE || pEntity->GetMoveType() == MOVETYPE_PUSH )
+ {
+ CBreakable *pBreakable = dynamic_cast<CBreakable *>(pEntity);
+ if ( pBreakable && pBreakable->IsBreakable() && pBreakable->m_takedamage == DAMAGE_YES && pBreakable->GetHealth() > 0 )
+ {
+ ChargeDamage( pEntity );
+ }
+ return true;
+ }
+
+ // If it's a vphysics object that's too heavy, crash into it too.
+ if ( pEntity->GetMoveType() == MOVETYPE_VPHYSICS )
+ {
+ IPhysicsObject *pPhysics = pEntity->VPhysicsGetObject();
+ if ( pPhysics )
+ {
+ // If the object is being held by the player, knock it out of his hands
+ if ( pPhysics->GetGameFlags() & FVPHYSICS_PLAYER_HELD )
+ {
+ Pickup_ForcePlayerToDropThisObject( pEntity );
+ return false;
+ }
+
+ if ( !pPhysics->IsMoveable() )
+ return true;
+
+ float entMass = PhysGetEntityMass( pEntity ) ;
+ float minMass = VPhysicsGetObject()->GetMass() * 0.5f;
+ if ( entMass < minMass )
+ {
+ if ( entMass < minMass * 0.666f || pEntity->CollisionProp()->BoundingRadius() < GetHullHeight() )
+ {
+ if ( pEntity->GetHealth() > 0 )
+ {
+ CBreakableProp *pBreakable = dynamic_cast<CBreakableProp *>(pEntity);
+ if ( pBreakable && pBreakable->m_takedamage == DAMAGE_YES && pBreakable->GetHealth() > 0 && pBreakable->GetHealth() <= 50 )
+ {
+ ChargeDamage( pEntity );
+ }
+ }
+ pEntity->SetNavIgnore( 2.0 );
+ return false;
+ }
+ }
+ return true;
+
+ }
+ }
+
+ return false;
+}
+
+
+//-------------------------------------------------------------------------------------------------
+//-------------------------------------------------------------------------------------------------
+void CNPC_Hunter::Explode()
+{
+ Vector velocity = vec3_origin;
+ AngularImpulse angVelocity = RandomAngularImpulse( -150, 150 );
+
+ PropBreakableCreateAll( GetModelIndex(), NULL, EyePosition(), GetAbsAngles(), velocity, angVelocity, 1.0, 150, COLLISION_GROUP_NPC, this );
+
+ ExplosionCreate( EyePosition(), GetAbsAngles(), this, 500, 256, (SF_ENVEXPLOSION_NOPARTICLES|SF_ENVEXPLOSION_NOSPARKS|SF_ENVEXPLOSION_NODLIGHTS|SF_ENVEXPLOSION_NODAMAGE|SF_ENVEXPLOSION_NOSMOKE), false );
+
+ // Create liquid fountain gushtacular effect here!
+ CEffectData data;
+
+ data.m_vOrigin = EyePosition();
+ data.m_vNormal = Vector( 0, 0, 1 );
+ data.m_flScale = 4.0f;
+
+ DispatchEffect( "StriderBlood", data );
+
+ // Go away
+ m_lifeState = LIFE_DEAD;
+
+ SetThink( &CNPC_Hunter::SUB_Remove );
+ SetNextThink( gpGlobals->curtime + 0.1f );
+
+ AddEffects( EF_NODRAW );
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+Activity CNPC_Hunter::NPC_TranslateActivity( Activity baseAct )
+{
+ if ( ( baseAct == ACT_WALK ) || ( baseAct == ACT_RUN ) )
+ {
+ if ( GetEnemy() )
+ {
+ Vector vecEnemyLKP = GetEnemyLKP();
+
+ // Only start facing when we're close enough
+ if ( UTIL_DistApprox( vecEnemyLKP, GetAbsOrigin() ) < HUNTER_FACE_ENEMY_DIST )
+ {
+ return (Activity)ACT_HUNTER_WALK_ANGRY;
+ }
+ }
+ }
+ else if ( ( baseAct == ACT_IDLE ) && m_bPlanted )
+ {
+ return ( Activity )ACT_HUNTER_IDLE_PLANTED;
+ }
+ else if ( baseAct == ACT_RANGE_ATTACK2 )
+ {
+ if ( !m_bPlanted && ( m_bEnableUnplantedShooting || IsStriderBuster( GetEnemy() ) ) )
+ {
+ return (Activity)ACT_HUNTER_RANGE_ATTACK2_UNPLANTED;
+ }
+ }
+
+ return BaseClass::NPC_TranslateActivity( baseAct );
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::HandleAnimEvent( animevent_t *pEvent )
+{
+ Vector footPosition;
+ QAngle angles;
+
+ if ( pEvent->event == AE_HUNTER_FOOTSTEP_LEFT )
+ {
+ LeftFootHit( pEvent->eventtime );
+ return;
+ }
+
+ if ( pEvent->event == AE_HUNTER_FOOTSTEP_RIGHT )
+ {
+ RightFootHit( pEvent->eventtime );
+ return;
+ }
+
+ if ( pEvent->event == AE_HUNTER_FOOTSTEP_BACK )
+ {
+ BackFootHit( pEvent->eventtime );
+ return;
+ }
+
+ if ( pEvent->event == AE_HUNTER_START_EXPRESSION )
+ {
+ if ( pEvent->options && Q_strlen( pEvent->options ) )
+ {
+ //m_iszCurrentExpression = AllocPooledString( pEvent->options );
+ //SetExpression( pEvent->options );
+ }
+ return;
+ }
+
+ if ( pEvent->event == AE_HUNTER_END_EXPRESSION )
+ {
+ if ( pEvent->options && Q_strlen( pEvent->options ) )
+ {
+ //m_iszCurrentExpression = NULL_STRING;
+ //RemoveActorFromScriptedScenes( this, true, false, pEvent->options );
+ }
+ return;
+ }
+
+ if ( pEvent->event == AE_HUNTER_MELEE_ANNOUNCE )
+ {
+ EmitSound( "NPC_Hunter.MeleeAnnounce" );
+ return;
+ }
+
+ if ( pEvent->event == AE_HUNTER_MELEE_ATTACK_LEFT )
+ {
+ Vector right, forward, dir;
+ AngleVectors( GetLocalAngles(), &forward, &right, NULL );
+
+ right = right * -100;
+ forward = forward * 600;
+ dir = right + forward;
+ QAngle angle( 25, 30, -20 );
+
+ MeleeAttack( HUNTER_MELEE_REACH, sk_hunter_dmg_one_slash.GetFloat(), angle, dir, HUNTER_BLOOD_LEFT_FOOT );
+ return;
+ }
+
+ if ( pEvent->event == AE_HUNTER_MELEE_ATTACK_RIGHT )
+ {
+ Vector right, forward,dir;
+ AngleVectors( GetLocalAngles(), &forward, &right, NULL );
+
+ right = right * 100;
+ forward = forward * 600;
+ dir = right + forward;
+
+ QAngle angle( 25, -30, 20 );
+
+ MeleeAttack( HUNTER_MELEE_REACH, sk_hunter_dmg_one_slash.GetFloat(), angle, dir, HUNTER_BLOOD_LEFT_FOOT );
+ return;
+ }
+
+ if ( pEvent->event == AE_HUNTER_SPRAY_BLOOD )
+ {
+ Vector vecOrigin;
+ Vector vecDir;
+
+ // spray blood from the attachment point
+ bool bGotAttachment = false;
+ if ( pEvent->options )
+ {
+ QAngle angDir;
+ if ( GetAttachment( pEvent->options, vecOrigin, angDir ) )
+ {
+ bGotAttachment = true;
+ AngleVectors( angDir, &vecDir, NULL, NULL );
+ }
+ }
+
+ // fall back to our center, tracing forward
+ if ( !bGotAttachment )
+ {
+ vecOrigin = WorldSpaceCenter();
+ GetVectors( &vecDir, NULL, NULL );
+ }
+
+ UTIL_BloodSpray( vecOrigin, vecDir, BLOOD_COLOR_RED, 4, FX_BLOODSPRAY_ALL );
+
+ for ( int i = 0 ; i < 3 ; i++ )
+ {
+ Vector vecTraceDir = vecDir;
+ vecTraceDir.x += random->RandomFloat( -0.1, 0.1 );
+ vecTraceDir.y += random->RandomFloat( -0.1, 0.1 );
+ vecTraceDir.z += random->RandomFloat( -0.1, 0.1 );
+
+ trace_t tr;
+ AI_TraceLine( vecOrigin, vecOrigin + ( vecTraceDir * 192.0f ), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr );
+ if ( tr.fraction != 1.0 )
+ {
+ UTIL_BloodDecalTrace( &tr, BLOOD_COLOR_RED );
+ }
+ }
+
+ return;
+ }
+
+ BaseClass::HandleAnimEvent( pEvent );
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::AddEntityRelationship( CBaseEntity *pEntity, Disposition_t nDisposition, int nPriority )
+{
+ if ( nDisposition == D_HT && pEntity->ClassMatches("npc_bullseye") )
+ UpdateEnemyMemory( pEntity, pEntity->GetAbsOrigin() );
+ BaseClass::AddEntityRelationship( pEntity, nDisposition, nPriority );
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+bool CNPC_Hunter::ScheduledMoveToGoalEntity( int scheduleType, CBaseEntity *pGoalEntity, Activity movementActivity )
+{
+ if ( IsCurSchedule( SCHED_HUNTER_RANGE_ATTACK1, false ) )
+ {
+ SetGoalEnt( pGoalEntity );
+ return true;
+ }
+ return BaseClass::ScheduledMoveToGoalEntity( scheduleType, pGoalEntity, movementActivity );
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::OnChangeHintGroup( string_t oldGroup, string_t newGroup )
+{
+ SetCondition( COND_HUNTER_NEW_HINTGROUP );
+ m_CheckHintGroupTimer.Set( 10 );
+}
+
+
+//-----------------------------------------------------------------------------
+// Tells whether any given hunter is in a squad that contains other hunters.
+// This is useful for preventing timid behavior for Hunters that are not
+// supported by other hunters.
+//
+// NOTE: This counts the self! So a hunter that is alone in his squad
+// receives a result of 1.
+//-----------------------------------------------------------------------------
+int CNPC_Hunter::NumHuntersInMySquad()
+{
+ AISquadIter_t iter;
+ CAI_BaseNPC *pSquadmate = m_pSquad ? m_pSquad->GetFirstMember( &iter ) : NULL;
+
+ if( !pSquadmate )
+ {
+ // Not in a squad at all, but the caller is not concerned with that. Just
+ // tell them that we're in a squad of one (ourself)
+ return 1;
+ }
+
+ int count = 0;
+
+ while ( pSquadmate )
+ {
+ if( pSquadmate->m_iClassname == m_iClassname )
+ count++;
+
+ pSquadmate = m_pSquad->GetNextMember( &iter );
+ }
+
+ return count;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::FollowStrider( const char *szStrider )
+{
+ if ( !szStrider )
+ return;
+
+ CBaseEntity *pEnt = gEntList.FindEntityByName( NULL, szStrider, this );
+ CNPC_Strider *pStrider = dynamic_cast <CNPC_Strider *>( pEnt );
+ FollowStrider(pStrider);
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::FollowStrider( CNPC_Strider * pStrider )
+{
+ if ( !IsAlive() )
+ {
+ return;
+ }
+
+ if ( pStrider )
+ {
+ if ( m_EscortBehavior.GetFollowTarget() != pStrider )
+ {
+ m_iszFollowTarget = pStrider->GetEntityName();
+ if ( m_iszFollowTarget == NULL_STRING )
+ {
+ m_iszFollowTarget = AllocPooledString( "unnamed_strider" );
+ }
+ m_EscortBehavior.SetEscortTarget( pStrider );
+ }
+ }
+ else
+ {
+ DevWarning("Hunter set to follow entity %s that is not a strider\n", STRING( m_iszFollowTarget ) );
+ m_iszFollowTarget = AllocPooledString( "unknown_strider" );
+ }
+}
+
+void CAI_HunterEscortBehavior::SetEscortTarget( CNPC_Strider *pStrider, bool fFinishCurSchedule )
+{
+ m_bEnabled = true;
+
+ if ( GetOuter()->GetSquad() )
+ {
+ GetOuter()->GetSquad()->RemoveFromSquad( GetOuter() );
+ }
+
+ for ( int i = 0; i < g_Hunters.Count(); i++ )
+ {
+ if ( g_Hunters[i]->m_EscortBehavior.GetFollowTarget() == pStrider )
+ {
+ Assert( g_Hunters[i]->GetSquad() );
+ g_Hunters[i]->GetSquad()->AddToSquad( GetOuter() );
+ break;
+ }
+ }
+
+ if ( !GetOuter()->GetSquad() )
+ {
+ GetOuter()->AddToSquad( AllocPooledString( CFmtStr( "%s_hunter_squad", STRING( pStrider->GetEntityName() ) ) ) );
+ }
+
+ BaseClass::SetFollowTarget( pStrider );
+ m_flTimeEscortReturn = gpGlobals->curtime;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::InputEnableUnplantedShooting( inputdata_t &inputdata )
+{
+ m_bEnableUnplantedShooting = true;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::InputDisableUnplantedShooting( inputdata_t &inputdata )
+{
+ m_bEnableUnplantedShooting = false;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::InputFollowStrider( inputdata_t &inputdata )
+{
+ m_iszFollowTarget = inputdata.value.StringID();
+ if ( m_iszFollowTarget == s_iszStriderClassname )
+ {
+ m_EscortBehavior.m_bEnabled = true;
+ m_iszFollowTarget = NULL_STRING;
+ }
+ m_BeginFollowDelay.Start( .1 ); // Allow time for strider to spawn
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::InputUseSiegeTargets( inputdata_t &inputdata )
+{
+ m_iszSiegeTargetName = inputdata.value.StringID();
+ m_flTimeNextSiegeTargetAttack = gpGlobals->curtime + random->RandomFloat( 1, hunter_siege_frequency.GetFloat() );
+
+ if( m_iszSiegeTargetName == NULL_STRING )
+ {
+ // Turning the feature off. Restore m_flDistTooFar to default.
+ m_flDistTooFar = hunter_flechette_max_range.GetFloat();
+ m_pSiegeTargets.RemoveAll();
+ }
+ else
+ {
+ // We're going into siege mode. Adjust range accordingly.
+ m_flDistTooFar = hunter_flechette_max_range.GetFloat() * HUNTER_SIEGE_MAX_DIST_MODIFIER;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::InputDodge( inputdata_t &inputdata )
+{
+ SetCondition( COND_HUNTER_FORCED_DODGE );
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::InputFlankEnemy( inputdata_t &inputdata )
+{
+ SetCondition( COND_HUNTER_FORCED_FLANK_ENEMY );
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::InputDisableShooting( inputdata_t &inputdata )
+{
+ m_bDisableShooting = true;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::InputEnableShooting( inputdata_t &inputdata )
+{
+ m_bDisableShooting = false;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::InputEnableSquadShootDelay( inputdata_t &inputdata )
+{
+ m_bEnableSquadShootDelay = true;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::InputDisableSquadShootDelay( inputdata_t &inputdata )
+{
+ m_bEnableSquadShootDelay = false;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+bool CNPC_Hunter::FVisible( CBaseEntity *pEntity, int traceMask, CBaseEntity **ppBlocker )
+{
+ return BaseClass::FVisible( pEntity, traceMask, ppBlocker );
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+bool CNPC_Hunter::IsValidEnemy( CBaseEntity *pTarget )
+{
+ if ( IsStriderBuster( pTarget) )
+ {
+ if ( !m_EscortBehavior.m_bEnabled || !m_EscortBehavior.GetEscortTarget() )
+ {
+ // We only hate striderbusters when we are actively protecting a strider.
+ return false;
+ }
+
+ if ( pTarget->VPhysicsGetObject() )
+ {
+ if ( ( pTarget->VPhysicsGetObject()->GetGameFlags() & FVPHYSICS_PLAYER_HELD ) &&
+ hunter_hate_held_striderbusters.GetBool() )
+ {
+ if ( gpGlobals->curtime - StriderBuster_GetPickupTime( pTarget ) > hunter_hate_held_striderbusters_delay.GetFloat())
+ {
+ if ( StriderBuster_NumFlechettesAttached( pTarget ) <= 2 )
+ {
+ if ( m_EscortBehavior.GetEscortTarget() &&
+ ( m_EscortBehavior.GetEscortTarget()->GetAbsOrigin().AsVector2D() - pTarget->GetAbsOrigin().AsVector2D() ).LengthSqr() < Square( hunter_hate_held_striderbusters_tolerance.GetFloat() ) )
+ {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ bool bThrown = ( pTarget->VPhysicsGetObject()->GetGameFlags() & FVPHYSICS_WAS_THROWN ) != 0;
+ bool bAttached = StriderBuster_IsAttachedStriderBuster( pTarget );
+
+ if ( ( bThrown && !bAttached ) && hunter_hate_thrown_striderbusters.GetBool() )
+ {
+ float t;
+ float dist = CalcDistanceSqrToLineSegment2D( m_EscortBehavior.GetEscortTarget()->GetAbsOrigin().AsVector2D(),
+ pTarget->GetAbsOrigin().AsVector2D(),
+ pTarget->GetAbsOrigin().AsVector2D() + pTarget->GetSmoothedVelocity().AsVector2D(), &t );
+
+ if ( t > 0 && dist < Square( hunter_hate_thrown_striderbusters_tolerance.GetFloat() ))
+ {
+ return true;
+ }
+ return false;
+ }
+
+ if ( bAttached && StriderBuster_IsAttachedStriderBuster( pTarget, m_EscortBehavior.GetEscortTarget() ) && hunter_hate_attached_striderbusters.GetBool() )
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ return BaseClass::IsValidEnemy( pTarget );
+}
+
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+Disposition_t CNPC_Hunter::IRelationType( CBaseEntity *pTarget )
+{
+ if ( !pTarget )
+ return D_NU;
+
+ if ( IsStriderBuster( pTarget ) )
+ {
+ if ( HateThisStriderBuster( pTarget ) )
+ return D_HT;
+
+ return D_NU;
+ }
+
+ if ( hunter_retreat_striderbusters.GetBool() )
+ {
+ if ( pTarget->IsPlayer() && (m_hAttachedBusters.Count() > 0) )
+ {
+ return D_FR;
+ }
+ }
+
+ return BaseClass::IRelationType( pTarget );
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+int CNPC_Hunter::IRelationPriority( CBaseEntity *pTarget )
+{
+ if ( IsStriderBuster( pTarget ) )
+ {
+ // If we're here, we already know that we hate striderbusters.
+ return 1000.0f;
+ }
+
+ return BaseClass::IRelationPriority( pTarget );
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::SetSquad( CAI_Squad *pSquad )
+{
+ BaseClass::SetSquad( pSquad );
+ if ( pSquad && pSquad->NumMembers() == 1 )
+ {
+ pSquad->SetSquadData( HUNTER_RUNDOWN_SQUADDATA, 0 );
+ }
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::OnSeeEntity( CBaseEntity *pEntity )
+{
+ BaseClass::OnSeeEntity(pEntity);
+
+ if ( IsStriderBuster( pEntity ) && IsValidEnemy( pEntity ) )
+ {
+ SetCondition( COND_HUNTER_SEE_STRIDERBUSTER );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+bool CNPC_Hunter::UpdateEnemyMemory( CBaseEntity *pEnemy, const Vector &position, CBaseEntity *pInformer )
+{
+ //EmitSound( "NPC_Hunter.Alert" );
+ return BaseClass::UpdateEnemyMemory( pEnemy, position, pInformer );
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+bool CNPC_Hunter::CanPlantHere( const Vector &vecPos )
+{
+ // TODO: cache results?
+ //if ( vecPos == m_vecLastCanPlantHerePos )
+ //{
+ // return m_bLastCanPlantHere;
+ //}
+
+ Vector vecMins = GetHullMins();
+ Vector vecMaxs = GetHullMaxs();
+
+ vecMins.x -= 16;
+ vecMins.y -= 16;
+
+ vecMaxs.x += 16;
+ vecMaxs.y += 16;
+ vecMaxs.z -= hunter_plant_adjust_z.GetInt();
+
+ bool bResult = false;
+
+ trace_t tr;
+ UTIL_TraceHull( vecPos, vecPos, vecMins, vecMaxs, MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr );
+ if ( tr.startsolid )
+ {
+ // Try again, tracing down from above.
+ Vector vecStart = vecPos;
+ vecStart.z += hunter_plant_adjust_z.GetInt();
+
+ UTIL_TraceHull( vecStart, vecPos, vecMins, vecMaxs, MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr );
+ }
+
+ if ( tr.startsolid )
+ {
+ //NDebugOverlay::Box( vecPos, vecMins, vecMaxs, 255, 0, 0, 0, 0 );
+ }
+ else
+ {
+ //NDebugOverlay::Box( vecPos, vecMins, vecMaxs, 0, 255, 0, 0, 0 );
+ bResult = true;
+ }
+
+ // Cache the results in case we ask again for the same spot.
+ //m_vecLastCanPlantHerePos = vecPos;
+ //m_bLastCanPlantHere = bResult;
+
+ return bResult;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+int CNPC_Hunter::MeleeAttack1ConditionsVsEnemyInVehicle( CBaseCombatCharacter *pEnemy, float flDot )
+{
+ if( !IsCorporealEnemy( GetEnemy() ) )
+ return COND_NONE;
+
+ // Try and trace a box to the player, and if I hit the vehicle, attack it
+ Vector vecDelta = (pEnemy->WorldSpaceCenter() - WorldSpaceCenter());
+ VectorNormalize( vecDelta );
+ trace_t tr;
+ AI_TraceHull( WorldSpaceCenter(), WorldSpaceCenter() + (vecDelta * 64), -Vector(8,8,8), Vector(8,8,8), MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
+ if ( tr.fraction != 1.0 && tr.m_pEnt == pEnemy->GetVehicleEntity() )
+ {
+ // We're near the vehicle. Are we facing it?
+ if (flDot < 0.7)
+ return COND_NOT_FACING_ATTACK;
+
+ return COND_CAN_MELEE_ATTACK1;
+ }
+
+ return COND_TOO_FAR_TO_ATTACK;
+}
+
+
+//-----------------------------------------------------------------------------
+// For innate melee attack
+//-----------------------------------------------------------------------------
+int CNPC_Hunter::MeleeAttack1Conditions ( float flDot, float flDist )
+{
+ if ( !IsCorporealEnemy( GetEnemy() ) )
+ return COND_NONE;
+
+ if ( ( gpGlobals->curtime < m_flNextMeleeTime ) && // allow berzerk bashing if cornered
+ !( m_hAttachedBusters.Count() > 0 && gpGlobals->curtime < m_fCorneredTimer ) )
+ {
+ return COND_NONE;
+ }
+
+ if ( GetEnemy()->Classify() == CLASS_PLAYER_ALLY_VITAL )
+ {
+ return COND_NONE;
+ }
+
+ if ( flDist > HUNTER_MELEE_REACH )
+ {
+ // Translate a hit vehicle into its passenger if found
+ if ( GetEnemy() != NULL )
+ {
+ CBaseCombatCharacter *pCCEnemy = GetEnemy()->MyCombatCharacterPointer();
+ if ( pCCEnemy != NULL && pCCEnemy->IsInAVehicle() )
+ {
+ return MeleeAttack1ConditionsVsEnemyInVehicle( pCCEnemy, flDot );
+ }
+
+#if defined(HL2_DLL) && !defined(HL2MP)
+ // If the player is holding an object, knock it down.
+ if ( GetEnemy()->IsPlayer() )
+ {
+ CBasePlayer *pPlayer = ToBasePlayer( GetEnemy() );
+
+ Assert( pPlayer != NULL );
+
+ // Is the player carrying something?
+ CBaseEntity *pObject = GetPlayerHeldEntity(pPlayer);
+
+ if ( !pObject )
+ {
+ pObject = PhysCannonGetHeldEntity( pPlayer->GetActiveWeapon() );
+ }
+
+ if ( pObject )
+ {
+ float flDist = pObject->WorldSpaceCenter().DistTo( WorldSpaceCenter() );
+
+ if ( flDist <= HUNTER_MELEE_REACH )
+ {
+ return COND_CAN_MELEE_ATTACK1;
+ }
+ }
+ }
+#endif
+ }
+
+ return COND_TOO_FAR_TO_ATTACK;
+ }
+
+ if (flDot < 0.7)
+ {
+ return COND_NOT_FACING_ATTACK;
+ }
+
+ // Build a cube-shaped hull, the same hull that MeleeAttack is going to use.
+ Vector vecMins = GetHullMins();
+ Vector vecMaxs = GetHullMaxs();
+ vecMins.z = vecMins.x;
+ vecMaxs.z = vecMaxs.x;
+
+ Vector forward;
+ GetVectors( &forward, NULL, NULL );
+
+ trace_t tr;
+ AI_TraceHull( WorldSpaceCenter(), WorldSpaceCenter() + forward * HUNTER_MELEE_REACH, vecMins, vecMaxs, MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr );
+
+ if ( tr.fraction == 1.0 || !tr.m_pEnt )
+ {
+ // This attack would miss completely. Trick the hunter into moving around some more.
+ return COND_TOO_FAR_TO_ATTACK;
+ }
+
+ if ( tr.m_pEnt == GetEnemy() || tr.m_pEnt->IsNPC() || (tr.m_pEnt->m_takedamage == DAMAGE_YES && (dynamic_cast<CBreakableProp*>(tr.m_pEnt))) )
+ {
+ // Let the hunter swipe at his enemy if he's going to hit them.
+ // Also let him swipe at NPC's that happen to be between the hunter and the enemy.
+ // This makes mobs of hunters seem more rowdy since it doesn't leave guys in the back row standing around.
+ // Also let him swipe at things that takedamage, under the assumptions that they can be broken.
+ return COND_CAN_MELEE_ATTACK1;
+ }
+
+ // dvs TODO: incorporate this
+ /*if ( tr.m_pEnt->IsBSPModel() )
+ {
+ // The trace hit something solid, but it's not the enemy. If this item is closer to the hunter than
+ // the enemy is, treat this as an obstruction.
+ Vector vecToEnemy = GetEnemy()->WorldSpaceCenter() - WorldSpaceCenter();
+ Vector vecTrace = tr.endpos - tr.startpos;
+
+ if ( vecTrace.Length2DSqr() < vecToEnemy.Length2DSqr() )
+ {
+ return COND_HUNTER_LOCAL_MELEE_OBSTRUCTION;
+ }
+ }*/
+
+ if ( !tr.m_pEnt->IsWorld() && GetEnemy() && GetEnemy()->GetGroundEntity() == tr.m_pEnt )
+ {
+ // Try to swat whatever the player is standing on instead of acting like a dill.
+ return COND_CAN_MELEE_ATTACK1;
+ }
+
+ // Move around some more
+ return COND_TOO_FAR_TO_ATTACK;
+}
+
+
+//-----------------------------------------------------------------------------
+// For innate melee attack
+//-----------------------------------------------------------------------------
+int CNPC_Hunter::MeleeAttack2Conditions ( float flDot, float flDist )
+{
+ return COND_NONE;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+bool CNPC_Hunter::IsCorporealEnemy( CBaseEntity *pEnemy )
+{
+ if( !pEnemy )
+ return false;
+
+ // Generally speaking, don't melee attack anything the player can't see.
+ if( pEnemy->IsEffectActive( EF_NODRAW ) )
+ return false;
+
+ // Don't flank, melee attack striderbusters.
+ if ( IsStriderBuster( pEnemy ) )
+ return false;
+
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+int CNPC_Hunter::RangeAttack1Conditions( float flDot, float flDist )
+{
+ return COND_NONE;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+int CNPC_Hunter::RangeAttack2Conditions( float flDot, float flDist )
+{
+ bool bIsBuster = IsStriderBuster( GetEnemy() );
+ bool bIsPerfectBullseye = ( GetEnemy() && dynamic_cast<CNPC_Bullseye *>(GetEnemy()) && ((CNPC_Bullseye *)GetEnemy())->UsePerfectAccuracy() );
+
+ if ( !bIsPerfectBullseye && !bIsBuster && !hunter_flechette_test.GetBool() && ( gpGlobals->curtime < m_flNextRangeAttack2Time ) )
+ {
+ return COND_NONE;
+ }
+
+ if ( m_bDisableShooting )
+ {
+ return COND_NONE;
+ }
+
+ if ( !HasCondition( COND_SEE_ENEMY ) )
+ {
+ return COND_NONE;
+ }
+
+ float flMaxFlechetteRange = hunter_flechette_max_range.GetFloat();
+
+ if ( IsUsingSiegeTargets() )
+ {
+ flMaxFlechetteRange *= HUNTER_SIEGE_MAX_DIST_MODIFIER;
+ }
+
+ if ( !bIsBuster && ( flDist > flMaxFlechetteRange ) )
+ {
+ return COND_TOO_FAR_TO_ATTACK;
+ }
+ else if ( !bIsBuster && ( !GetEnemy() || !GetEnemy()->ClassMatches( "npc_bullseye" ) ) && flDist < hunter_flechette_min_range.GetFloat() )
+ {
+ return COND_TOO_CLOSE_TO_ATTACK;
+ }
+ else if ( flDot < HUNTER_FACING_DOT )
+ {
+ return COND_NOT_FACING_ATTACK;
+ }
+
+ if ( !bIsBuster && !m_bEnableUnplantedShooting && !hunter_flechette_test.GetBool() && !CanPlantHere( GetAbsOrigin() ) )
+ {
+ return COND_HUNTER_CANT_PLANT;
+ }
+
+ return COND_CAN_RANGE_ATTACK2;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+bool CNPC_Hunter::WeaponLOSCondition(const Vector &ownerPos, const Vector &targetPos, bool bSetConditions)
+{
+ CBaseEntity *pTargetEnt;
+
+ pTargetEnt = GetEnemy();
+
+ trace_t tr;
+ Vector vFrom = ownerPos + GetViewOffset();
+ AI_TraceLine( vFrom, targetPos, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
+
+ if ( ( pTargetEnt && tr.m_pEnt == pTargetEnt) || tr.fraction == 1.0 || CanShootThrough( tr, targetPos ) )
+ {
+ static Vector vMins( -2.0, -2.0, -2.0 );
+ static Vector vMaxs( -vMins);
+ // Hit the enemy, or hit nothing (traced all the way to a nonsolid enemy like a bullseye)
+ AI_TraceHull( vFrom - Vector( 0, 0, 18 ), targetPos, vMins, vMaxs, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr);
+
+ if ( ( pTargetEnt && tr.m_pEnt == pTargetEnt) || tr.fraction == 1.0 || CanShootThrough( tr, targetPos ) )
+ {
+ if ( hunter_show_weapon_los_condition.GetBool() )
+ {
+ NDebugOverlay::Line( vFrom, targetPos, 255, 0, 255, false, 0.1 );
+ NDebugOverlay::Line( vFrom - Vector( 0, 0, 18 ), targetPos, 0, 0, 255, false, 0.1 );
+ }
+ return true;
+ }
+ }
+ else if ( bSetConditions )
+ {
+ SetCondition( COND_WEAPON_SIGHT_OCCLUDED );
+ SetEnemyOccluder( tr.m_pEnt );
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Look in front and see if the claw hit anything.
+//
+// Input : flDist distance to trace
+// iDamage damage to do if attack hits
+// vecViewPunch camera punch (if attack hits player)
+// vecVelocityPunch velocity punch (if attack hits player)
+//
+// Output : The entity hit by claws. NULL if nothing.
+//-----------------------------------------------------------------------------
+CBaseEntity *CNPC_Hunter::MeleeAttack( float flDist, int iDamage, QAngle &qaViewPunch, Vector &vecVelocityPunch, int BloodOrigin )
+{
+ // Added test because claw attack anim sometimes used when for cases other than melee
+ if ( GetEnemy() )
+ {
+ trace_t tr;
+ AI_TraceHull( WorldSpaceCenter(), GetEnemy()->WorldSpaceCenter(), -Vector(8,8,8), Vector(8,8,8), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr );
+
+ if ( tr.fraction < 1.0f )
+ return NULL;
+ }
+
+ //
+ // Trace out a cubic section of our hull and see what we hit.
+ //
+ Vector vecMins = GetHullMins();
+ Vector vecMaxs = GetHullMaxs();
+ vecMins.z = vecMins.x;
+ vecMaxs.z = vecMaxs.x;
+
+ CBaseEntity *pHurt = CheckTraceHullAttack( flDist, vecMins, vecMaxs, iDamage, DMG_SLASH );
+
+ if ( pHurt )
+ {
+ EmitSound( "NPC_Hunter.MeleeHit" );
+ EmitSound( "NPC_Hunter.TackleHit" );
+
+ CBasePlayer *pPlayer = ToBasePlayer( pHurt );
+
+ if ( pPlayer != NULL && !(pPlayer->GetFlags() & FL_GODMODE ) )
+ {
+ pPlayer->ViewPunch( qaViewPunch );
+ pPlayer->VelocityPunch( vecVelocityPunch );
+
+ // Shake the screen
+ UTIL_ScreenShake( pPlayer->GetAbsOrigin(), 100.0, 1.5, 1.0, 2, SHAKE_START );
+
+ // Red damage indicator
+ color32 red = { 128, 0, 0, 128 };
+ UTIL_ScreenFade( pPlayer, red, 1.0f, 0.1f, FFADE_IN );
+
+ /*if ( UTIL_ShouldShowBlood( pPlayer->BloodColor() ) )
+ {
+ // Spray some of the player's blood on the hunter.
+ trace_t tr;
+
+ Vector vecHunterEyePos; // = EyePosition();
+ QAngle angDiscard;
+ GetBonePosition( LookupBone( "MiniStrider.top_eye_bone" ), vecHunterEyePos, angDiscard );
+
+ Vector vecPlayerEyePos = pPlayer->EyePosition();
+
+ Vector vecDir = vecHunterEyePos - vecPlayerEyePos;
+ float flLen = VectorNormalize( vecDir );
+
+ Vector vecStart = vecPlayerEyePos - ( vecDir * 64 );
+ Vector vecEnd = vecPlayerEyePos + ( vecDir * ( flLen + 64 ) );
+
+ NDebugOverlay::HorzArrow( vecStart, vecEnd, 16, 255, 255, 0, 255, false, 10 );
+
+ UTIL_TraceLine( vecStart, vecEnd, MASK_SHOT, pPlayer, COLLISION_GROUP_NONE, &tr );
+
+ if ( tr.m_pEnt )
+ {
+ Msg( "Hit %s!!!\n", tr.m_pEnt->GetDebugName() );
+ UTIL_DecalTrace( &tr, "Blood" );
+ }
+ }*/
+ }
+ else if ( !pPlayer )
+ {
+ if ( IsMovablePhysicsObject( pHurt ) )
+ {
+ // If it's a vphysics object that's too heavy, crash into it too.
+ IPhysicsObject *pPhysics = pHurt->VPhysicsGetObject();
+ if ( pPhysics )
+ {
+ // If the object is being held by the player, break it or make them drop it.
+ if ( pPhysics->GetGameFlags() & FVPHYSICS_PLAYER_HELD )
+ {
+ // If it's breakable, break it.
+ if ( pHurt->m_takedamage == DAMAGE_YES )
+ {
+ CBreakableProp *pBreak = dynamic_cast<CBreakableProp*>(pHurt);
+ if ( pBreak )
+ {
+ CTakeDamageInfo info( this, this, 20, DMG_SLASH );
+ pBreak->Break( this, info );
+ }
+ }
+ }
+ }
+ }
+
+ if ( UTIL_ShouldShowBlood(pHurt->BloodColor()) )
+ {
+ // Hit an NPC. Bleed them!
+ Vector vecBloodPos;
+
+ switch ( BloodOrigin )
+ {
+ case HUNTER_BLOOD_LEFT_FOOT:
+ {
+ if ( GetAttachment( "blood_left", vecBloodPos ) )
+ {
+ SpawnBlood( vecBloodPos, g_vecAttackDir, pHurt->BloodColor(), MIN( iDamage, 30 ) );
+ }
+
+ break;
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ // TODO:
+ //AttackMissSound();
+ }
+
+ m_flNextMeleeTime = gpGlobals->curtime + hunter_melee_delay.GetFloat();
+
+ return pHurt;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+bool CNPC_Hunter::TestShootPosition(const Vector &vecShootPos, const Vector &targetPos )
+{
+ if ( !CanPlantHere(vecShootPos ) )
+ {
+ return false;
+ }
+
+ return BaseClass::TestShootPosition( vecShootPos, targetPos );
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+Vector CNPC_Hunter::Weapon_ShootPosition( )
+{
+ matrix3x4_t gunMatrix;
+ GetAttachment( gm_nTopGunAttachment, gunMatrix );
+
+ Vector vecShootPos;
+ MatrixGetColumn( gunMatrix, 3, vecShootPos );
+
+ return vecShootPos;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::MakeTracer( const Vector &vecTracerSrc, const trace_t &tr, int iTracerType )
+{
+ float flTracerDist;
+ Vector vecDir;
+ Vector vecEndPos;
+
+ vecDir = tr.endpos - vecTracerSrc;
+
+ flTracerDist = VectorNormalize( vecDir );
+
+ int nAttachment = LookupAttachment( "MiniGun" );
+
+ UTIL_Tracer( vecTracerSrc, tr.endpos, nAttachment, TRACER_FLAG_USEATTACHMENT, 5000, true, "HunterTracer" );
+}
+
+
+//-----------------------------------------------------------------------------
+// Trace didn't hit the intended target, but should the hunter
+// shoot anyway? We use this to get the hunter to destroy
+// breakables that are between him and his target.
+//-----------------------------------------------------------------------------
+bool CNPC_Hunter::CanShootThrough( const trace_t &tr, const Vector &vecTarget )
+{
+ if ( !tr.m_pEnt )
+ {
+ return false;
+ }
+
+ if ( !tr.m_pEnt->GetHealth() )
+ {
+ return false;
+ }
+
+ // Don't try to shoot through allies.
+ CAI_BaseNPC *pNPC = tr.m_pEnt->MyNPCPointer();
+ if ( pNPC && ( IRelationType( pNPC ) == D_LI ) )
+ {
+ return false;
+ }
+
+ // Would a trace ignoring this entity continue to the target?
+ trace_t continuedTrace;
+ AI_TraceLine( tr.endpos, vecTarget, MASK_SHOT, tr.m_pEnt, COLLISION_GROUP_NONE, &continuedTrace );
+
+ if ( continuedTrace.fraction != 1.0 )
+ {
+ if ( continuedTrace.m_pEnt != GetEnemy() )
+ {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+int CNPC_Hunter::GetSoundInterests()
+{
+ return SOUND_WORLD | SOUND_COMBAT | SOUND_PLAYER | SOUND_DANGER | SOUND_PHYSICS_DANGER | SOUND_PLAYER_VEHICLE | SOUND_BULLET_IMPACT | SOUND_MOVE_AWAY;
+}
+
+//-----------------------------------------------------------------------------
+// Tells us whether the Hunter is acting in a large, outdoor map,
+// currently only ep2_outland_12. This allows us to create logic
+// branches here in the AI code so that we can make choices that
+// tailor behavior to larger and smaller maps.
+//-----------------------------------------------------------------------------
+bool CNPC_Hunter::IsInLargeOutdoorMap()
+{
+ return m_bInLargeOutdoorMap;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::AlertSound()
+{
+ EmitSound( "NPC_Hunter.Alert" );
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::PainSound( const CTakeDamageInfo &info )
+{
+ if ( gpGlobals->curtime > m_flNextDamageTime )
+ {
+ EmitSound( "NPC_Hunter.Pain" );
+ m_flNextDamageTime = gpGlobals->curtime + random->RandomFloat( 0.5, 1.2 );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::DeathSound( const CTakeDamageInfo &info )
+{
+ EmitSound( "NPC_Hunter.Death" );
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::TraceAttack( const CTakeDamageInfo &inputInfo, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator )
+{
+ CTakeDamageInfo info = inputInfo;
+
+ // Even though the damage might not hurt us, we want to react to it
+ // if it's from the player.
+ if ( info.GetAttacker()->IsPlayer() )
+ {
+ if ( !HasMemory( bits_MEMORY_PROVOKED ) )
+ {
+ GetEnemies()->ClearMemory( info.GetAttacker() );
+ Remember( bits_MEMORY_PROVOKED );
+ SetCondition( COND_LIGHT_DAMAGE );
+ }
+ }
+
+ // HUnters have special resisitance to some types of damage.
+ if ( ( info.GetDamageType() & DMG_BULLET ) ||
+ ( info.GetDamageType() & DMG_BUCKSHOT ) ||
+ ( info.GetDamageType() & DMG_CLUB ) ||
+ ( info.GetDamageType() & DMG_NEVERGIB ) )
+ {
+ float flScale = 1.0;
+
+ if ( info.GetDamageType() & DMG_BUCKSHOT )
+ {
+ flScale = sk_hunter_buckshot_damage_scale.GetFloat();
+ }
+ else if ( ( info.GetDamageType() & DMG_BULLET ) || ( info.GetDamageType() & DMG_NEVERGIB ) )
+ {
+ // Hunters resist most bullet damage, but they are actually vulnerable to .357 rounds,
+ // since players regard that weapon as one of the game's truly powerful weapons.
+ if( info.GetAmmoType() == GetAmmoDef()->Index("357") )
+ {
+ flScale = 1.16f;
+ }
+ else
+ {
+ flScale = sk_hunter_bullet_damage_scale.GetFloat();
+ }
+ }
+
+ if ( GetActivity() == ACT_HUNTER_CHARGE_RUN )
+ {
+ flScale *= sk_hunter_charge_damage_scale.GetFloat();
+ }
+
+ if ( flScale != 0 )
+ {
+ float flDamage = info.GetDamage() * flScale;
+ info.SetDamage( flDamage );
+ }
+
+ QAngle vecAngles;
+ VectorAngles( ptr->plane.normal, vecAngles );
+ DispatchParticleEffect( "blood_impact_synth_01", ptr->endpos, vecAngles );
+ DispatchParticleEffect( "blood_impact_synth_01_arc_parent", PATTACH_POINT_FOLLOW, this, gm_nHeadCenterAttachment );
+ }
+
+ BaseClass::TraceAttack( info, vecDir, ptr, pAccumulator );
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+const impactdamagetable_t &CNPC_Hunter::GetPhysicsImpactDamageTable()
+{
+ return s_HunterImpactDamageTable;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::PhysicsDamageEffect( const Vector &vecPos, const Vector &vecDir )
+{
+ CEffectData data;
+ data.m_vOrigin = vecPos;
+ data.m_vNormal = vecDir;
+ DispatchEffect( "HunterDamage", data );
+
+ if ( random->RandomInt( 0, 1 ) == 0 )
+ {
+ CBaseEntity *pTrail = CreateEntityByName( "sparktrail" );
+ pTrail->SetOwnerEntity( this );
+ pTrail->Spawn();
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// We were hit by a strider buster. Do the tesla effect on our hitboxes.
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::TeslaThink()
+{
+ CEffectData data;
+ data.m_nEntIndex = entindex();
+ data.m_flMagnitude = 3;
+ data.m_flScale = 0.5f;
+ DispatchEffect( "TeslaHitboxes", data );
+ EmitSound( "RagdollBoogie.Zap" );
+
+ if ( gpGlobals->curtime < m_flTeslaStopTime )
+ {
+ SetContextThink( &CNPC_Hunter::TeslaThink, gpGlobals->curtime + random->RandomFloat( 0.1f, 0.3f ), HUNTER_ZAP_THINK );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Our health is low. Show damage effects.
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::BleedThink()
+{
+ // Spurt blood from random points on the hunter's head.
+ Vector vecOrigin;
+ QAngle angDir;
+ GetAttachment( gm_nHeadCenterAttachment, vecOrigin, angDir );
+
+ Vector vecDir = RandomVector( -1, 1 );
+ VectorNormalize( vecDir );
+ VectorAngles( vecDir, Vector( 0, 0, 1 ), angDir );
+
+ vecDir *= gm_flHeadRadius;
+ DispatchParticleEffect( "blood_spurt_synth_01", vecOrigin + vecDir, angDir );
+
+ SetNextThink( gpGlobals->curtime + random->RandomFloat( 0.6, 1.5 ), HUNTER_BLEED_THINK );
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+bool CNPC_Hunter::IsHeavyDamage( const CTakeDamageInfo &info )
+{
+ if ( info.GetDamage() < 45 )
+ {
+ return false;
+ }
+
+ if ( info.GetDamage() < 180 )
+ {
+ if ( !m_HeavyDamageDelay.Expired() || !BaseClass::IsHeavyDamage( info ) )
+ {
+ return false;
+ }
+ }
+
+ m_HeavyDamageDelay.Set( 15, 25 );
+ return true;
+
+}
+
+
+//-----------------------------------------------------------------------------
+// We've taken some damage. Maybe we should flinch because of it.
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::ConsiderFlinching( const CTakeDamageInfo &info )
+{
+ if ( !m_FlinchTimer.Expired() )
+ {
+ // Someone is whaling on us. Push out the timer so we don't keep flinching.
+ m_FlinchTimer.Set( random->RandomFloat( 0.3 ) );
+ return;
+ }
+
+ if ( GetState() == NPC_STATE_SCRIPT )
+ {
+ return;
+ }
+
+ Activity eGesture = ACT_HUNTER_FLINCH_N;
+
+ Vector forward;
+ GetVectors( &forward, NULL, NULL );
+
+ Vector vecForceDir = info.GetDamageForce();
+ VectorNormalize( vecForceDir );
+
+ float flDot = DotProduct( forward, vecForceDir );
+
+ if ( flDot > 0.707 )
+ {
+ // flinch forward
+ eGesture = ACT_HUNTER_FLINCH_N;
+ }
+ else if ( flDot < -0.707 )
+ {
+ // flinch back
+ eGesture = ACT_HUNTER_FLINCH_S;
+ }
+ else
+ {
+ // flinch left or right
+ Vector cross = CrossProduct( forward, vecForceDir );
+
+ if ( cross.z > 0 )
+ {
+ eGesture = ACT_HUNTER_FLINCH_W;
+ }
+ else
+ {
+ eGesture = ACT_HUNTER_FLINCH_E;
+ }
+ }
+
+ if ( !IsPlayingGesture( eGesture ) )
+ {
+ RestartGesture( eGesture );
+ m_FlinchTimer.Set( random->RandomFloat( 0.3, 1.0 ) );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// This is done from a think function because when the hunter is killed,
+// the physics code puts the vehicle's pre-collision velocity back so the jostle
+// is basically discared.
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::JostleVehicleThink()
+{
+ CBaseEntity *pInflictor = m_hHitByVehicle;
+ if ( !pInflictor )
+ return;
+
+ Vector vecVelDir = pInflictor->GetSmoothedVelocity();
+ float flSpeed = VectorNormalize( vecVelDir );
+ Vector vecForce = CrossProduct( vecVelDir, Vector( 0, 0, 1 ) );
+ if ( DotProduct( vecForce, GetAbsOrigin() ) < DotProduct( vecForce, pInflictor->GetAbsOrigin() ) )
+ {
+ // We're to the left of the vehicle that's hitting us.
+ vecForce *= -1;
+ }
+
+ VectorNormalize( vecForce );
+ vecForce.z = 1.0;
+
+ float flForceScale = RemapValClamped( flSpeed, hunter_jostle_car_min_speed.GetFloat(), hunter_jostle_car_max_speed.GetFloat(), 50.0f, 150.0f );
+
+ vecForce *= ( flForceScale * pInflictor->VPhysicsGetObject()->GetMass() );
+
+ pInflictor->VPhysicsGetObject()->ApplyForceOffset( vecForce, WorldSpaceCenter() );
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+int CNPC_Hunter::OnTakeDamage( const CTakeDamageInfo &info )
+{
+ CTakeDamageInfo myInfo = info;
+
+ if ( ( info.GetDamageType() & DMG_CRUSH ) && !( info.GetDamageType() & DMG_VEHICLE ) )
+ {
+ // Don't take damage from physics objects that weren't thrown by the player.
+ CBaseEntity *pInflictor = info.GetInflictor();
+
+ IPhysicsObject *pObj = pInflictor->VPhysicsGetObject();
+ //Assert( pObj );
+
+ if ( !pObj || !pInflictor->HasPhysicsAttacker( 4.0 ) )
+ {
+ myInfo.SetDamage( 0 );
+ }
+ else
+ {
+ // Physics objects that have flechettes stuck in them spoof
+ // a flechette hitting us so we dissolve when killed and award
+ // the achievement of killing a hunter with its flechettes.
+ CUtlVector<CBaseEntity *> children;
+ GetAllChildren( pInflictor, children );
+ for (int i = 0; i < children.Count(); i++ )
+ {
+ CBaseEntity *pent = children.Element( i );
+ if ( dynamic_cast<CHunterFlechette *>( pent ) )
+ {
+ myInfo.SetInflictor( pent );
+ myInfo.SetDamageType( myInfo.GetDamageType() | DMG_DISSOLVE );
+ }
+ }
+ }
+ }
+
+ return BaseClass::OnTakeDamage( myInfo );
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+int CNPC_Hunter::OnTakeDamage_Alive( const CTakeDamageInfo &info )
+{
+ CTakeDamageInfo myInfo = info;
+
+ // don't take damage from my own weapons!!!
+ // Exception: I "own" a magnade if it's glued to me.
+ CBaseEntity *pInflictor = info.GetInflictor();
+ CBaseEntity *pAttacker = info.GetAttacker();
+ if ( pInflictor )
+ {
+ if ( IsStriderBuster( pInflictor ) )
+ {
+ // Get a tesla effect on our hitboxes for a little while.
+ SetContextThink( &CNPC_Hunter::TeslaThink, gpGlobals->curtime, HUNTER_ZAP_THINK );
+ m_flTeslaStopTime = gpGlobals->curtime + 2.0f;
+
+ myInfo.SetDamage( sk_hunter_dmg_from_striderbuster.GetFloat() ) ;
+
+ SetCondition( COND_HUNTER_STAGGERED );
+ }
+ else if ( pInflictor->ClassMatches( GetClassname() ) && !( info.GetDamageType() == DMG_GENERIC ) )
+ {
+ return 0;
+ }
+ else if ( pInflictor->ClassMatches( "hunter_flechette" ) )
+ {
+ if ( !( ( CHunterFlechette *)pInflictor )->WasThrownBack() )
+ {
+ // Flechettes only hurt us if they were thrown back at us by the player. This prevents
+ // hunters from hurting themselves when they walk into their own flechette clusters.
+ return 0;
+ }
+ }
+ }
+
+ if ( m_EscortBehavior.GetFollowTarget() && m_EscortBehavior.GetFollowTarget() == pAttacker )
+ {
+ return 0;
+ }
+
+ bool bHitByUnoccupiedCar = false;
+ if ( ( ( info.GetDamageType() & DMG_CRUSH ) && ( pAttacker && pAttacker->IsPlayer() ) ) ||
+ ( info.GetDamageType() & DMG_VEHICLE ) )
+ {
+ // myInfo, not info! it may have been modified above.
+ float flDamage = myInfo.GetDamage();
+ if ( flDamage < HUNTER_MIN_PHYSICS_DAMAGE )
+ {
+ //DevMsg( "hunter: <<<< ZERO PHYSICS DAMAGE: %f\n", flDamage );
+ myInfo.SetDamage( 0 );
+ }
+ else
+ {
+ CBaseEntity *pInflictor = info.GetInflictor();
+ if ( ( info.GetDamageType() & DMG_VEHICLE ) ||
+ ( pInflictor && pInflictor->GetServerVehicle() &&
+ ( ( bHitByUnoccupiedCar = ( dynamic_cast<CPropVehicleDriveable *>(pInflictor) && static_cast<CPropVehicleDriveable *>(pInflictor)->GetDriver() == NULL ) ) == false ) ) )
+ {
+ // Adjust the damage from vehicles.
+ flDamage *= sk_hunter_vehicle_damage_scale.GetFloat();
+ myInfo.SetDamage( flDamage );
+
+ // Apply a force to jostle the vehicle that hit us.
+ // Pick a force direction based on which side we're on relative to the vehicle's motion.
+ Vector vecVelDir = pInflictor->GetSmoothedVelocity();
+ if ( vecVelDir.Length() >= hunter_jostle_car_min_speed.GetFloat() )
+ {
+ EmitSound( "NPC_Hunter.HitByVehicle" );
+ m_hHitByVehicle = pInflictor;
+ SetContextThink( &CNPC_Hunter::JostleVehicleThink, gpGlobals->curtime, HUNTER_JOSTLE_VEHICLE_THINK );
+ }
+ }
+
+ if ( !bHitByUnoccupiedCar )
+ {
+ SetCondition( COND_HUNTER_STAGGERED );
+ }
+ }
+
+ //DevMsg( "hunter: >>>> PHYSICS DAMAGE: %f (was %f)\n", flDamage, info.GetDamage() );
+ }
+
+ // Show damage effects if we actually took damage.
+ if ( ( myInfo.GetDamageType() & ( DMG_CRUSH | DMG_BLAST ) ) && ( myInfo.GetDamage() > 0 ) )
+ {
+ if ( !bHitByUnoccupiedCar )
+ SetCondition( COND_HUNTER_STAGGERED );
+ }
+
+ if ( HasCondition( COND_HUNTER_STAGGERED ) )
+ {
+ // Throw a bunch of gibs out
+ Vector vecForceDir = -myInfo.GetDamageForce();
+ VectorNormalize( vecForceDir );
+ PhysicsDamageEffect( myInfo.GetDamagePosition(), vecForceDir );
+
+ // Stagger away from the direction the damage came from.
+ m_vecStaggerDir = myInfo.GetDamageForce();
+ VectorNormalize( m_vecStaggerDir );
+ }
+
+ // Take less damage from citizens and Alyx, otherwise hunters go down too easily.
+ float flScale = 1.0;
+
+ if ( pAttacker &&
+ ( ( pAttacker->Classify() == CLASS_CITIZEN_REBEL ) ||
+ ( pAttacker->Classify() == CLASS_PLAYER_ALLY ) ||
+ ( pAttacker->Classify() == CLASS_PLAYER_ALLY_VITAL ) ) )
+ {
+ flScale *= sk_hunter_citizen_damage_scale.GetFloat();
+ }
+
+ if ( flScale != 0 )
+ {
+ // We're taking a nonzero amount of damage.
+
+ // If we're not staggering, consider flinching!
+ if ( !HasCondition( COND_HUNTER_STAGGERED ) )
+ {
+ ConsiderFlinching( info );
+ }
+
+ if( pAttacker && pAttacker->IsPlayer() )
+ {
+ // This block of code will distract the Hunter back to the player if the
+ // player does harm to the Hunter but is not the Hunter's current enemy.
+ // This is achieved by updating the Hunter's enemy memory of the player and
+ // making the Hunter's current enemy invalid for a short time.
+ if( !GetEnemy() || !GetEnemy()->IsPlayer() )
+ {
+ UpdateEnemyMemory( pAttacker, pAttacker->GetAbsOrigin(), this );
+
+ if( GetEnemy() )
+ {
+ // Gotta forget about this person for a little bit.
+ GetEnemies()->SetTimeValidEnemy( GetEnemy(), gpGlobals->curtime + HUNTER_IGNORE_ENEMY_TIME );
+ }
+ }
+ }
+
+ float flDamage = myInfo.GetDamage() * flScale;
+ myInfo.SetDamage( flDamage );
+ }
+
+ int nRet = BaseClass::OnTakeDamage_Alive( myInfo );
+
+ m_EscortBehavior.OnDamage( myInfo );
+
+ // Spark at 30% health.
+ if ( !IsBleeding() && ( GetHealth() <= sk_hunter_health.GetInt() * 0.3 ) )
+ {
+ StartBleeding();
+ }
+
+ if ( IsUsingSiegeTargets() && info.GetAttacker() != NULL && info.GetAttacker()->IsPlayer() )
+ {
+ // Defend myself. Try to siege attack immediately.
+ m_flTimeNextSiegeTargetAttack = gpGlobals->curtime;
+ }
+
+ return nRet;
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::Event_Killed( const CTakeDamageInfo &info )
+{
+ // Remember the killing blow to make decisions about ragdolling.
+ m_nKillingDamageType = info.GetDamageType();
+
+ if ( m_EscortBehavior.GetFollowTarget() )
+ {
+ if ( AIGetNumFollowers( m_EscortBehavior.GetFollowTarget(), m_iClassname ) == 1 )
+ {
+ m_EscortBehavior.GetEscortTarget()->AlertSound();
+ if ( info.GetAttacker() && info.GetAttacker()->IsPlayer() )
+ {
+ m_EscortBehavior.GetEscortTarget()->UpdateEnemyMemory( UTIL_GetLocalPlayer(), UTIL_GetLocalPlayer()->GetAbsOrigin(), this );
+ }
+ }
+ }
+
+ if ( info.GetDamageType() & DMG_VEHICLE )
+ {
+ bool bWasRunDown = false;
+ int iRundownCounter = 0;
+ if ( GetSquad() )
+ {
+ if ( !m_IgnoreVehicleTimer.Expired() )
+ {
+ GetSquad()->GetSquadData( HUNTER_RUNDOWN_SQUADDATA, &iRundownCounter );
+ GetSquad()->SetSquadData( HUNTER_RUNDOWN_SQUADDATA, iRundownCounter + 1 );
+ bWasRunDown = true;
+ }
+ }
+
+ if ( hunter_dodge_debug.GetBool() )
+ Msg( "Hunter %d was%s run down\n", entindex(), ( bWasRunDown ) ? "" : " not" );
+
+ // Death by vehicle! Decrement the hunters to run over counter.
+ // When the counter reaches zero hunters will start dodging.
+ if ( GlobalEntity_GetCounter( s_iszHuntersToRunOver ) > 0 )
+ {
+ GlobalEntity_AddToCounter( s_iszHuntersToRunOver, -1 );
+ }
+ }
+
+ // Stop all our thinks
+ SetContextThink( NULL, 0, HUNTER_BLEED_THINK );
+
+ StopParticleEffects( this );
+
+ BaseClass::Event_Killed( info );
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::StartBleeding()
+{
+ // Do this even if we're already bleeding (see OnRestore).
+ m_bIsBleeding = true;
+
+ // Start gushing blood from our... anus or something.
+ DispatchParticleEffect( "blood_drip_synth_01", PATTACH_POINT_FOLLOW, this, gm_nHeadBottomAttachment );
+
+ // Emit spurts of our blood
+ SetContextThink( &CNPC_Hunter::BleedThink, gpGlobals->curtime + 0.1, HUNTER_BLEED_THINK );
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+float CNPC_Hunter::MaxYawSpeed()
+{
+ if ( IsStriderBuster( GetEnemy() ) )
+ {
+ return 60;
+ }
+
+ if ( GetActivity() == ACT_HUNTER_ANGRY )
+ return 0;
+
+ if ( GetActivity() == ACT_HUNTER_CHARGE_RUN )
+ return 5;
+
+ if ( GetActivity() == ACT_HUNTER_IDLE_PLANTED )
+ return 0;
+
+ if ( GetActivity() == ACT_HUNTER_RANGE_ATTACK2_UNPLANTED )
+ return 180;
+
+ switch ( GetActivity() )
+ {
+ case ACT_RANGE_ATTACK2:
+ {
+ return 0;
+ }
+
+ case ACT_90_LEFT:
+ case ACT_90_RIGHT:
+ {
+ return 45;
+ }
+
+ case ACT_TURN_LEFT:
+ case ACT_TURN_RIGHT:
+ {
+ return 45;
+ }
+
+ case ACT_WALK:
+ {
+ return 25;
+ }
+
+ default:
+ {
+ return 35;
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+bool CNPC_Hunter::IsJumpLegal(const Vector &startPos, const Vector &apex, const Vector &endPos) const
+{
+ float MAX_JUMP_RISE = 220.0f;
+ float MAX_JUMP_DISTANCE = 512.0f;
+ float MAX_JUMP_DROP = 384.0f;
+
+ trace_t tr;
+ UTIL_TraceHull( startPos, startPos, GetHullMins(), GetHullMaxs(), MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr );
+ if ( tr.startsolid )
+ {
+ // Trying to start a jump in solid! Consider checking for this in CAI_MoveProbe::JumpMoveLimit.
+ Assert( 0 );
+ return false;
+ }
+
+ if ( BaseClass::IsJumpLegal( startPos, apex, endPos, MAX_JUMP_RISE, MAX_JUMP_DROP, MAX_JUMP_DISTANCE ) )
+ {
+ return true;
+ }
+ return false;
+}
+
+
+//-----------------------------------------------------------------------------
+// Let the probe know I can run through small debris
+// Stolen shamelessly from the Antlion Guard
+//-----------------------------------------------------------------------------
+bool CNPC_Hunter::ShouldProbeCollideAgainstEntity( CBaseEntity *pEntity )
+{
+ if ( s_iszPhysPropClassname != pEntity->m_iClassname )
+ return BaseClass::ShouldProbeCollideAgainstEntity( pEntity );
+
+ if ( pEntity->GetMoveType() == MOVETYPE_VPHYSICS )
+ {
+ IPhysicsObject *pPhysObj = pEntity->VPhysicsGetObject();
+
+ if( pPhysObj && pPhysObj->GetMass() <= 500.0f )
+ {
+ return false;
+ }
+ }
+
+ return BaseClass::ShouldProbeCollideAgainstEntity( pEntity );
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::DoMuzzleFlash( int nAttachment )
+{
+ BaseClass::DoMuzzleFlash();
+
+ DispatchParticleEffect( "hunter_muzzle_flash", PATTACH_POINT_FOLLOW, this, nAttachment );
+
+ // Dispatch the elight
+ CEffectData data;
+ data.m_nAttachmentIndex = nAttachment;
+ data.m_nEntIndex = entindex();
+ DispatchEffect( "HunterMuzzleFlash", data );
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+int CNPC_Hunter::CountRangedAttackers()
+{
+ CBaseEntity *pEnemy = GetEnemy();
+ if ( !pEnemy )
+ {
+ return 0;
+ }
+
+ int nAttackers = 0;
+ for ( int i = 0; i < g_Hunters.Count(); i++ )
+ {
+ CNPC_Hunter *pOtherHunter = g_Hunters[i];
+ if ( pOtherHunter->GetEnemy() == pEnemy )
+ {
+ if ( pOtherHunter->IsCurSchedule( SCHED_HUNTER_RANGE_ATTACK2 ) )
+ {
+ nAttackers++;
+ }
+ }
+ }
+ return nAttackers;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::DelayRangedAttackers( float minDelay, float maxDelay, bool bForced )
+{
+ float delayMultiplier = ( g_pGameRules->IsSkillLevel( SKILL_EASY ) ) ? 1.25 : 1.0;
+ if ( !m_bEnableSquadShootDelay && !bForced )
+ {
+ m_flNextRangeAttack2Time = gpGlobals->curtime + random->RandomFloat( minDelay, maxDelay ) * delayMultiplier;
+ return;
+ }
+
+ CBaseEntity *pEnemy = GetEnemy();
+ for ( int i = 0; i < g_Hunters.Count(); i++ )
+ {
+ CNPC_Hunter *pOtherHunter = g_Hunters[i];
+ if ( pOtherHunter->GetEnemy() == pEnemy )
+ {
+ float nextTime = gpGlobals->curtime + random->RandomFloat( minDelay, maxDelay ) * delayMultiplier;
+ if ( nextTime > pOtherHunter->m_flNextRangeAttack2Time )
+ pOtherHunter->m_flNextRangeAttack2Time = nextTime;
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Given a target to shoot at, decide where to aim.
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::GetShootDir( Vector &vecDir, const Vector &vecSrc, CBaseEntity *pTargetEntity, bool bStriderBuster, int nShotNum, bool bSingleShot )
+{
+ //RestartGesture( ACT_HUNTER_GESTURE_SHOOT );
+
+ EmitSound( "NPC_Hunter.FlechetteShoot" );
+
+ Vector vecBodyTarget;
+
+ if( pTargetEntity->Classify() == CLASS_PLAYER_ALLY_VITAL )
+ {
+ // Shooting at Alyx, most likely (in EP2). The attack is designed to displace
+ // her, not necessarily actually harm her. So shoot at the area around her feet.
+ vecBodyTarget = pTargetEntity->GetAbsOrigin();
+ }
+ else
+ {
+ vecBodyTarget = pTargetEntity->BodyTarget( vecSrc );
+ }
+
+ Vector vecTarget = vecBodyTarget;
+
+ Vector vecDelta = pTargetEntity->GetAbsOrigin() - GetAbsOrigin();
+ float flDist = vecDelta.Length();
+
+ if ( !bStriderBuster )
+ {
+ // If we're not firing at a strider buster, miss in an entertaining way for the
+ // first three shots of each volley.
+ if ( ( nShotNum < 3 ) && ( flDist > 200 ) )
+ {
+ Vector vecTargetForward;
+ Vector vecTargetRight;
+ pTargetEntity->GetVectors( &vecTargetForward, &vecTargetRight, NULL );
+
+ Vector vecForward;
+ GetVectors( &vecForward, NULL, NULL );
+
+ float flDot = DotProduct( vecTargetForward, vecForward );
+
+ if ( flDot < -0.8f )
+ {
+ // Our target is facing us, shoot the ground between us.
+ float flPerc = 0.7 + ( 0.1 * nShotNum );
+ vecTarget = GetAbsOrigin() + ( flPerc * ( pTargetEntity->GetAbsOrigin() - GetAbsOrigin() ) );
+ }
+ else if ( flDot > 0.8f )
+ {
+ // Our target is facing away from us, shoot to the left or right.
+ Vector vecMissDir = vecTargetRight;
+ if ( m_bMissLeft )
+ {
+ vecMissDir *= -1.0f;
+ }
+
+ vecTarget = pTargetEntity->EyePosition() + ( 36.0f * ( 3 - nShotNum ) ) * vecMissDir;
+ }
+ else
+ {
+ // Our target is facing vaguely perpendicular to us, shoot across their view.
+ vecTarget = pTargetEntity->EyePosition() + ( 36.0f * ( 3 - nShotNum ) ) * vecTargetForward;
+ }
+ }
+ // If we can't see them, shoot where we last saw them.
+ else if ( !HasCondition( COND_SEE_ENEMY ) )
+ {
+ Vector vecDelta = vecTarget - pTargetEntity->GetAbsOrigin();
+ vecTarget = m_vecEnemyLastSeen + vecDelta;
+ }
+ }
+ else
+ {
+ // If we're firing at a striderbuster, lead it.
+ float flSpeed = hunter_flechette_speed.GetFloat();
+ if ( !flSpeed )
+ {
+ flSpeed = 2500.0f;
+ }
+
+ flSpeed *= 1.5;
+
+ float flDeltaTime = flDist / flSpeed;
+ vecTarget = vecTarget + flDeltaTime * pTargetEntity->GetSmoothedVelocity();
+ }
+
+ vecDir = vecTarget - vecSrc;
+ VectorNormalize( vecDir );
+}
+
+
+//-----------------------------------------------------------------------------
+// Ensures that we don't exceed our pitch/yaw limits when shooting flechettes.
+// Returns true if we had to clamp, false if not.
+//-----------------------------------------------------------------------------
+bool CNPC_Hunter::ClampShootDir( Vector &vecDir )
+{
+ Vector vecDir2D = vecDir;
+ vecDir2D.z = 0;
+
+ Vector vecForward;
+ GetVectors( &vecForward, NULL, NULL );
+
+ Vector vecForward2D = vecForward;
+ vecForward2D.z = 0;
+
+ float flDot = DotProduct( vecForward2D, vecDir2D );
+ if ( flDot >= HUNTER_SHOOT_MAX_YAW_COS )
+ {
+ // No need to clamp.
+ return false;
+ }
+
+ Vector vecAxis;
+ CrossProduct( vecDir, vecForward, vecAxis );
+ VectorNormalize( vecAxis );
+
+ Quaternion q;
+ AxisAngleQuaternion( vecAxis, -HUNTER_SHOOT_MAX_YAW_DEG, q );
+
+ matrix3x4_t rot;
+ QuaternionMatrix( q, rot );
+ VectorRotate( vecForward, rot, vecDir );
+ VectorNormalize( vecDir );
+
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+bool CNPC_Hunter::ShouldSeekTarget( CBaseEntity *pTargetEntity, bool bStriderBuster )
+{
+ bool bSeek = false;
+
+ if ( bStriderBuster )
+ {
+ bool bSeek = false;
+
+ if ( pTargetEntity->VPhysicsGetObject() && ( pTargetEntity->VPhysicsGetObject()->GetGameFlags() & FVPHYSICS_PLAYER_HELD ) )
+ {
+ bSeek = true;
+ }
+ else if ( StriderBuster_NumFlechettesAttached( pTargetEntity ) == 0 )
+ {
+ if ( StriderBuster_IsAttachedStriderBuster(pTargetEntity) )
+ {
+ bSeek = true;
+ }
+ else if ( hunter_seek_thrown_striderbusters_tolerance.GetFloat() > 0.0 )
+ {
+ CNPC_Strider *pEscortTarget = m_EscortBehavior.GetEscortTarget();
+ if ( pEscortTarget && ( pEscortTarget->GetAbsOrigin() - pTargetEntity->GetAbsOrigin() ).LengthSqr() < Square( hunter_seek_thrown_striderbusters_tolerance.GetFloat() ) )
+ {
+ bSeek = true;
+ }
+ }
+ }
+ }
+
+ return bSeek;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::BeginVolley( int nNum, float flStartTime )
+{
+ m_nFlechettesQueued = nNum;
+ m_nClampedShots = 0;
+ m_flNextFlechetteTime = flStartTime;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+bool CNPC_Hunter::ShootFlechette( CBaseEntity *pTargetEntity, bool bSingleShot )
+{
+ if ( !pTargetEntity )
+ {
+ Assert( false );
+ return false;
+ }
+
+ int nShotNum = hunter_flechette_volley_size.GetInt() - m_nFlechettesQueued;
+
+ bool bStriderBuster = IsStriderBuster( pTargetEntity );
+
+ // Choose the next muzzle to shoot from.
+ Vector vecSrc;
+ QAngle angMuzzle;
+
+ if ( m_bTopMuzzle )
+ {
+ GetAttachment( gm_nTopGunAttachment, vecSrc, angMuzzle );
+ DoMuzzleFlash( gm_nTopGunAttachment );
+ }
+ else
+ {
+ GetAttachment( gm_nBottomGunAttachment, vecSrc, angMuzzle );
+ DoMuzzleFlash( gm_nBottomGunAttachment );
+ }
+
+ m_bTopMuzzle = !m_bTopMuzzle;
+
+ Vector vecDir;
+ GetShootDir( vecDir, vecSrc, pTargetEntity, bStriderBuster, nShotNum, bSingleShot );
+
+ bool bClamped = false;
+ if ( hunter_clamp_shots.GetBool() )
+ {
+ bClamped = ClampShootDir( vecDir );
+ }
+
+ CShotManipulator manipulator( vecDir );
+ Vector vecShoot;
+
+ if( IsUsingSiegeTargets() && nShotNum >= 2 && (nShotNum % 2) == 0 )
+ {
+ // Near perfect accuracy for these three shots, so they are likely to fly right into the windows.
+ // NOTE! In siege behavior in the map that this behavior was designed for (ep2_outland_10), the
+ // Hunters will only ever shoot at siege targets in siege mode. If you allow Hunters in siege mode
+ // to attack players or other NPCs, this accuracy bonus will apply unless we apply a bit more logic to it.
+ vecShoot = manipulator.ApplySpread( VECTOR_CONE_1DEGREES * 0.5, 1.0f );
+ }
+ else
+ {
+ vecShoot = manipulator.ApplySpread( VECTOR_CONE_4DEGREES, 1.0f );
+ }
+
+ QAngle angShoot;
+ VectorAngles( vecShoot, angShoot );
+
+ CHunterFlechette *pFlechette = CHunterFlechette::FlechetteCreate( vecSrc, angShoot, this );
+
+ pFlechette->AddEffects( EF_NOSHADOW );
+
+ vecShoot *= hunter_flechette_speed.GetFloat();
+
+ pFlechette->Shoot( vecShoot, bStriderBuster );
+
+ if ( ShouldSeekTarget( pTargetEntity, bStriderBuster ) )
+ {
+ pFlechette->SetSeekTarget( pTargetEntity );
+ }
+
+ if( nShotNum == 1 && pTargetEntity->Classify() == CLASS_PLAYER_ALLY_VITAL )
+ {
+ // Make this person afraid and react to ME, not to the flechettes.
+ // Otherwise they could be scared into running towards the hunter.
+ CSoundEnt::InsertSound( SOUND_DANGER|SOUND_CONTEXT_REACT_TO_SOURCE|SOUND_CONTEXT_EXCLUDE_COMBINE, pTargetEntity->EyePosition(), 180.0f, 2.0f, this );
+ }
+
+ return bClamped;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+Vector CNPC_Hunter::LeftFootHit( float eventtime )
+{
+ Vector footPosition;
+
+ GetAttachment( "left foot", footPosition );
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "NPC_Hunter.Footstep", &footPosition, eventtime );
+
+ FootFX( footPosition );
+
+ return footPosition;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+Vector CNPC_Hunter::RightFootHit( float eventtime )
+{
+ Vector footPosition;
+
+ GetAttachment( "right foot", footPosition );
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "NPC_Hunter.Footstep", &footPosition, eventtime );
+ FootFX( footPosition );
+
+ return footPosition;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+Vector CNPC_Hunter::BackFootHit( float eventtime )
+{
+ Vector footPosition;
+
+ GetAttachment( "back foot", footPosition );
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "NPC_Hunter.BackFootstep", &footPosition, eventtime );
+ FootFX( footPosition );
+
+ return footPosition;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::FootFX( const Vector &origin )
+{
+ return;
+
+ // dvs TODO: foot dust? probably too expensive for these guys
+ /*trace_t tr;
+ AI_TraceLine( origin, origin - Vector(0,0,100), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr );
+ float yaw = random->RandomInt(0,120);
+ for ( int i = 0; i < 3; i++ )
+ {
+ Vector dir = UTIL_YawToVector( yaw + i*120 ) * 10;
+ VectorNormalize( dir );
+ dir.z = 0.25;
+ VectorNormalize( dir );
+ g_pEffects->Dust( tr.endpos, dir, 12, 50 );
+ }*/
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+CBaseEntity *CNPC_Hunter::GetEnemyVehicle()
+{
+ if ( GetEnemy() == NULL )
+ return NULL;
+
+ CBaseCombatCharacter *pCCEnemy = GetEnemy()->MyCombatCharacterPointer();
+ if ( pCCEnemy != NULL )
+ return pCCEnemy->GetVehicleEntity();
+
+ return NULL;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::DrawDebugGeometryOverlays()
+{
+ if (m_debugOverlays & OVERLAY_BBOX_BIT)
+ {
+ float flViewRange = acos(0.8);
+ Vector vEyeDir = EyeDirection2D( );
+ Vector vLeftDir, vRightDir;
+ float fSin, fCos;
+ SinCos( flViewRange, &fSin, &fCos );
+
+ vLeftDir.x = vEyeDir.x * fCos - vEyeDir.y * fSin;
+ vLeftDir.y = vEyeDir.x * fSin + vEyeDir.y * fCos;
+ vLeftDir.z = vEyeDir.z;
+ fSin = sin(-flViewRange);
+ fCos = cos(-flViewRange);
+ vRightDir.x = vEyeDir.x * fCos - vEyeDir.y * fSin;
+ vRightDir.y = vEyeDir.x * fSin + vEyeDir.y * fCos;
+ vRightDir.z = vEyeDir.z;
+
+ int nSeq = GetSequence();
+ if ( ( GetEntryNode( nSeq ) == gm_nPlantedNode ) && ( GetExitNode( nSeq ) == gm_nPlantedNode ) )
+ {
+ // planted - green
+ NDebugOverlay::Box( GetAbsOrigin(), GetHullMins(), GetHullMaxs(), 0, 255, 0, 128, 0 );
+ }
+ else if ( ( GetEntryNode( nSeq ) == gm_nUnplantedNode ) && ( GetExitNode( nSeq ) == gm_nUnplantedNode ) )
+ {
+ // unplanted - blue
+ NDebugOverlay::Box( GetAbsOrigin(), GetHullMins(), GetHullMaxs(), 0, 0, 255, 128, 0 );
+ }
+ else if ( ( GetEntryNode( nSeq ) == gm_nUnplantedNode ) && ( GetExitNode( nSeq ) == gm_nPlantedNode ) )
+ {
+ // planting transition - cyan
+ NDebugOverlay::Box( GetAbsOrigin(), GetHullMins(), GetHullMaxs(), 0, 255, 255, 128, 0 );
+ }
+ else if ( ( GetEntryNode( nSeq ) == gm_nPlantedNode ) && ( GetExitNode( nSeq ) == gm_nUnplantedNode ) )
+ {
+ // unplanting transition - purple
+ NDebugOverlay::Box( GetAbsOrigin(), GetHullMins(), GetHullMaxs(), 255, 0, 255, 128, 0 );
+ }
+ else
+ {
+ // unknown / other node - red
+ Msg( "UNKNOWN: %s\n", GetSequenceName( GetSequence() ) );
+ NDebugOverlay::Box( GetAbsOrigin(), GetHullMins(), GetHullMaxs(), 255, 0, 0, 128, 0 );
+ }
+
+ NDebugOverlay::BoxDirection(EyePosition(), Vector(0,0,-1), Vector(200,0,1), vLeftDir, 255, 0, 0, 50, 0 );
+ NDebugOverlay::BoxDirection(EyePosition(), Vector(0,0,-1), Vector(200,0,1), vRightDir, 255, 0, 0, 50, 0 );
+ NDebugOverlay::BoxDirection(EyePosition(), Vector(0,0,-1), Vector(200,0,1), vEyeDir, 0, 255, 0, 50, 0 );
+ NDebugOverlay::Box(EyePosition(), -Vector(2,2,2), Vector(2,2,2), 0, 255, 0, 128, 0 );
+ }
+
+ m_EscortBehavior.DrawDebugGeometryOverlays();
+
+ BaseClass::DrawDebugGeometryOverlays();
+}
+
+
+//-----------------------------------------------------------------------------
+// Player has illuminated this NPC with the flashlight
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::PlayerHasIlluminatedNPC( CBasePlayer *pPlayer, float flDot )
+{
+ if ( m_bFlashlightInEyes )
+ return;
+
+ // Ignore the flashlight if it's not shining at my eyes
+ if ( PlayerFlashlightOnMyEyes( pPlayer ) )
+ {
+ //Msg( ">>>> SHINING FLASHLIGHT ON ME\n" );
+ m_bFlashlightInEyes = true;
+ SetExpression( "scenes/npc/hunter/hunter_eyeclose.vcd" );
+ m_flPupilDilateTime = gpGlobals->curtime + 0.2f;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+bool CNPC_Hunter::PlayerFlashlightOnMyEyes( CBasePlayer *pPlayer )
+{
+ Vector vecEyes, vecEyeForward, vecPlayerForward;
+ GetAttachment( gm_nTopGunAttachment, vecEyes, &vecEyeForward );
+ pPlayer->EyeVectors( &vecPlayerForward );
+
+ Vector vecToEyes = (vecEyes - pPlayer->EyePosition());
+ //float flDist = VectorNormalize( vecToEyes );
+
+ float flDot = DotProduct( vecPlayerForward, vecToEyes );
+ if ( flDot < 0.98 )
+ return false;
+
+ // Check facing to ensure we're in front of her
+ Vector los = ( pPlayer->EyePosition() - EyePosition() );
+ los.z = 0;
+ VectorNormalize( los );
+ Vector facingDir = EyeDirection2D();
+ flDot = DotProduct( los, facingDir );
+ return ( flDot > 0.3 );
+}
+
+
+//-----------------------------------------------------------------------------
+// Return a random expression for the specified state to play over
+// the state's expression loop.
+//-----------------------------------------------------------------------------
+const char *CNPC_Hunter::SelectRandomExpressionForState( NPC_STATE state )
+{
+ if ( m_bFlashlightInEyes )
+ return NULL;
+
+ if ( !hunter_random_expressions.GetBool() )
+ return NULL;
+
+ char *szExpressions[4] =
+ {
+ "scenes/npc/hunter/hunter_scan.vcd",
+ "scenes/npc/hunter/hunter_eyeclose.vcd",
+ "scenes/npc/hunter/hunter_roar.vcd",
+ "scenes/npc/hunter/hunter_pain.vcd"
+ };
+
+ int nIndex = random->RandomInt( 0, 3 );
+ //Msg( "RANDOM Expression: %s\n", szExpressions[nIndex] );
+ return szExpressions[nIndex];
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::PlayExpressionForState( NPC_STATE state )
+{
+ if ( m_bFlashlightInEyes )
+ {
+ return;
+ }
+
+ BaseClass::PlayExpressionForState( state );
+}
+
+
+//-----------------------------------------------------------------------------
+// TODO: remove if we're not doing striderbuster stuff
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::StriderBusterAttached( CBaseEntity *pAttached )
+{
+ // Add another to the list
+ m_hAttachedBusters.AddToTail( pAttached );
+
+ SetCondition( COND_HUNTER_HIT_BY_STICKYBOMB );
+ if (m_hAttachedBusters.Count() == 1)
+ {
+ EmitSound( "NPC_Hunter.Alert" );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::StriderBusterDetached( CBaseEntity *pAttached )
+{
+ int elem = m_hAttachedBusters.Find(pAttached);
+ if (elem >= 0)
+ {
+ m_hAttachedBusters.FastRemove(elem);
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Set direction that the hunter aims his body and eyes (guns).
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::SetAim( const Vector &aimDir, float flInterval )
+{
+ QAngle angDir;
+ VectorAngles( aimDir, angDir );
+ float curPitch = GetPoseParameter( gm_nBodyPitchPoseParam );
+ float curYaw = GetPoseParameter( gm_nBodyYawPoseParam );
+
+ float newPitch;
+ float newYaw;
+
+ if ( GetEnemy() )
+ {
+ // clamp and dampen movement
+ newPitch = curPitch + 0.8 * UTIL_AngleDiff( UTIL_ApproachAngle( angDir.x, curPitch, 20 ), curPitch );
+
+ float flRelativeYaw = UTIL_AngleDiff( angDir.y, GetAbsAngles().y );
+ newYaw = curYaw + UTIL_AngleDiff( flRelativeYaw, curYaw );
+ }
+ else
+ {
+ // Sweep your weapon more slowly if you're not fighting someone
+ newPitch = curPitch + 0.6 * UTIL_AngleDiff( UTIL_ApproachAngle( angDir.x, curPitch, 20 ), curPitch );
+
+ float flRelativeYaw = UTIL_AngleDiff( angDir.y, GetAbsAngles().y );
+ newYaw = curYaw + 0.6 * UTIL_AngleDiff( flRelativeYaw, curYaw );
+ }
+
+ newPitch = AngleNormalize( newPitch );
+ newYaw = AngleNormalize( newYaw );
+
+ //Msg( "pitch=%f, yaw=%f\n", newPitch, newYaw );
+
+ SetPoseParameter( gm_nAimPitchPoseParam, 0 );
+ SetPoseParameter( gm_nAimYawPoseParam, 0 );
+
+ SetPoseParameter( gm_nBodyPitchPoseParam, clamp( newPitch, -45, 45 ) );
+ SetPoseParameter( gm_nBodyYawPoseParam, clamp( newYaw, -45, 45 ) );
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::RelaxAim( float flInterval )
+{
+ float curPitch = GetPoseParameter( gm_nBodyPitchPoseParam );
+ float curYaw = GetPoseParameter( gm_nBodyYawPoseParam );
+
+ // dampen existing aim
+ float newPitch = AngleNormalize( UTIL_ApproachAngle( 0, curPitch, 3 ) );
+ float newYaw = AngleNormalize( UTIL_ApproachAngle( 0, curYaw, 2 ) );
+
+ SetPoseParameter( gm_nAimPitchPoseParam, 0 );
+ SetPoseParameter( gm_nAimYawPoseParam, 0 );
+
+ SetPoseParameter( gm_nBodyPitchPoseParam, clamp( newPitch, -45, 45 ) );
+ SetPoseParameter( gm_nBodyYawPoseParam, clamp( newYaw, -45, 45 ) );
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::UpdateAim()
+{
+ if ( !GetModelPtr() || !GetModelPtr()->SequencesAvailable() )
+ return;
+
+ float flInterval = GetAnimTimeInterval();
+
+ // Some activities look bad if we're giving our enemy the stinkeye.
+ int eActivity = GetActivity();
+
+ if ( GetEnemy() &&
+ GetState() != NPC_STATE_SCRIPT &&
+ ( eActivity != ACT_HUNTER_CHARGE_CRASH ) &&
+ ( eActivity != ACT_HUNTER_CHARGE_HIT ) )
+ {
+ Vector vecShootOrigin;
+
+ vecShootOrigin = Weapon_ShootPosition();
+ Vector vecShootDir = GetShootEnemyDir( vecShootOrigin, false );
+
+ SetAim( vecShootDir, flInterval );
+ }
+ else
+ {
+ RelaxAim( flInterval );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Don't become a ragdoll until we've finished our death anim
+//-----------------------------------------------------------------------------
+bool CNPC_Hunter::CanBecomeRagdoll()
+{
+ return ( m_nKillingDamageType & DMG_CRUSH ) ||
+ IsCurSchedule( SCHED_DIE, false ) || // Finished playing death anim, time to ragdoll
+ IsCurSchedule( SCHED_HUNTER_CHARGE_ENEMY, false ) || // While moving, it looks better to ragdoll instantly
+ IsCurSchedule( SCHED_SCRIPTED_RUN, false ) ||
+ ( GetActivity() == ACT_WALK ) || ( GetActivity() == ACT_RUN ) ||
+ GetCurSchedule() == NULL; // Failsafe
+}
+
+
+//-----------------------------------------------------------------------------
+// Determines the best type of death anim to play based on how we died.
+//-----------------------------------------------------------------------------
+Activity CNPC_Hunter::GetDeathActivity()
+{
+ return ACT_DIESIMPLE;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CAI_HunterEscortBehavior::OnDamage( const CTakeDamageInfo &info )
+{
+ if ( info.GetDamage() > 0 && info.GetAttacker()->IsPlayer() &&
+ GetFollowTarget() && ( AIGetNumFollowers( GetFollowTarget() ) > 1 ) &&
+ ( GetOuter()->GetSquad()->GetSquadSoundWaitTime() <= gpGlobals->curtime ) ) // && !FarFromFollowTarget()
+ {
+ // Start the clock ticking. We'll return the the strider when the timer elapses.
+ m_flTimeEscortReturn = gpGlobals->curtime + random->RandomFloat( 15.0f, 25.0f );
+ GetOuter()->GetSquad()->SetSquadSoundWaitTime( m_flTimeEscortReturn + 1.0 ); // prevent others from breaking escort
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CAI_HunterEscortBehavior::BuildScheduleTestBits()
+{
+ BaseClass::BuildScheduleTestBits();
+
+ if ( ( m_flTimeEscortReturn != 0 ) && ( gpGlobals->curtime > m_flTimeEscortReturn ) )
+ {
+ // We're delinquent! Return to strider!
+ GetOuter()->ClearCustomInterruptCondition( COND_NEW_ENEMY );
+ GetOuter()->ClearCustomInterruptCondition( COND_SEE_ENEMY );
+ GetOuter()->ClearCustomInterruptCondition( COND_SEE_HATE );
+ GetOuter()->ClearCustomInterruptCondition( COND_CAN_RANGE_ATTACK1 );
+ GetOuter()->ClearCustomInterruptCondition( COND_CAN_RANGE_ATTACK2 );
+ }
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CAI_HunterEscortBehavior::CheckBreakEscort()
+{
+ if ( m_flTimeEscortReturn != 0 && ( FarFromFollowTarget() || gpGlobals->curtime >= m_flTimeEscortReturn ) )
+ {
+ if ( FarFromFollowTarget() )
+ {
+ m_flTimeEscortReturn = gpGlobals->curtime;
+ }
+ else
+ {
+ m_flTimeEscortReturn = 0;
+ }
+ if ( GetOuter()->GetSquad() )
+ {
+ GetOuter()->GetSquad()->SetSquadSoundWaitTime( gpGlobals->curtime + random->RandomFloat( 5.0f, 12.0f ) );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CAI_HunterEscortBehavior::GatherConditionsNotActive( void )
+{
+ if ( m_bEnabled )
+ {
+ DistributeFreeHunters();
+ }
+
+ BaseClass::GatherConditionsNotActive();
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CAI_HunterEscortBehavior::GatherConditions( void )
+{
+ m_bEnabled = true;
+
+ DistributeFreeHunters();
+
+ BaseClass::GatherConditions();
+
+ if ( GetEnemy() && GetEnemy()->IsPlayer() && HasCondition( COND_SEE_ENEMY ) )
+ {
+ if ( GetOuter()->GetSquad()->GetSquadSoundWaitTime() <= gpGlobals->curtime && ((CBasePlayer *)GetEnemy())->IsInAVehicle() )
+ {
+ m_flTimeEscortReturn = gpGlobals->curtime + random->RandomFloat( 15.0f, 25.0f );
+ GetOuter()->GetSquad()->SetSquadSoundWaitTime( m_flTimeEscortReturn + 1.0 ); // prevent others from breaking escort
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+bool CAI_HunterEscortBehavior::ShouldFollow()
+{
+ if ( IsStriderBuster( GetEnemy() ) )
+ return false;
+
+ if ( HasCondition( COND_HEAR_PHYSICS_DANGER ) )
+ return false;
+
+ if ( m_flTimeEscortReturn <= gpGlobals->curtime )
+ {
+ return CAI_FollowBehavior::ShouldFollow();
+ }
+
+ return false;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CAI_HunterEscortBehavior::BeginScheduleSelection()
+{
+ BaseClass::BeginScheduleSelection();
+ Assert( m_SavedDistTooFar == GetOuter()->m_flDistTooFar );
+ GetOuter()->m_flDistTooFar *= 2;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+int CAI_HunterEscortBehavior::SelectSchedule()
+{
+ if( m_FollowDelay.IsRunning() && !m_FollowDelay.Expired() )
+ {
+ return FollowCallBaseSelectSchedule();
+ }
+ return BaseClass::SelectSchedule();
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+int CAI_HunterEscortBehavior::FollowCallBaseSelectSchedule()
+{
+ if ( GetOuter()->GetState() == NPC_STATE_COMBAT )
+ {
+ return GetOuter()->SelectCombatSchedule();
+ }
+
+ return BaseClass::FollowCallBaseSelectSchedule();
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CAI_HunterEscortBehavior::StartTask( const Task_t *pTask )
+{
+ switch( pTask->iTask )
+ {
+ case TASK_MOVE_TO_FOLLOW_POSITION:
+ {
+ if ( GetEnemy() )
+ {
+ if ( GetOuter()->OccupyStrategySlot( SQUAD_SLOT_RUN_SHOOT ) )
+ {
+ if ( GetOuter()->GetSquad()->GetSquadMemberNearestTo( GetEnemy()->GetAbsOrigin() ) == GetOuter() )
+ {
+ GetOuter()->BeginVolley( NUM_FLECHETTE_VOLLEY_ON_FOLLOW, gpGlobals->curtime + 1.0 + random->RandomFloat( 0, .25 ) + random->RandomFloat( 0, .25 ) );
+ }
+ else
+ {
+ GetOuter()->VacateStrategySlot();
+ }
+ }
+ }
+ BaseClass::StartTask( pTask );
+ break;
+ }
+
+ default:
+ BaseClass::StartTask( pTask );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CAI_HunterEscortBehavior::RunTask( const Task_t *pTask )
+{
+ switch( pTask->iTask )
+ {
+ case TASK_MOVE_TO_FOLLOW_POSITION:
+ {
+ if ( !GetFollowTarget() )
+ {
+ TaskFail( FAIL_NO_TARGET );
+ }
+ else
+ {
+ if ( GetEnemy() )
+ {
+ CNPC_Hunter *pHunter = GetOuter();
+ Vector vecEnemyLKP = pHunter->GetEnemyLKP();
+ pHunter->AddFacingTarget( pHunter->GetEnemy(), vecEnemyLKP, 1.0, 0.8 );
+ bool bVacate = false;
+
+ bool bHasSlot = pHunter->HasStrategySlot( SQUAD_SLOT_RUN_SHOOT );
+ if ( HasCondition( COND_SEE_ENEMY ) )
+ {
+ float maxDist = hunter_flechette_max_range.GetFloat() * 3;
+ float distSq = ( pHunter->GetAbsOrigin() - pHunter->GetEnemy()->GetAbsOrigin() ).Length2DSqr();
+
+ if ( distSq < Square( maxDist ) )
+ {
+ if ( gpGlobals->curtime >= pHunter->m_flNextFlechetteTime )
+ {
+ if ( !bHasSlot )
+ {
+ if ( GetOuter()->OccupyStrategySlot( SQUAD_SLOT_RUN_SHOOT ) )
+ {
+ if ( GetOuter()->GetSquad()->GetSquadMemberNearestTo( GetEnemy()->GetAbsOrigin() ) == GetOuter() )
+ {
+ bHasSlot = true;
+ }
+ else
+ {
+ GetOuter()->VacateStrategySlot();
+ }
+ }
+ }
+
+ if ( bHasSlot )
+ {
+ // Start the firing sound.
+ //CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+ //if ( controller.SoundGetVolume( pHunter->m_pGunFiringSound ) == 0.0f )
+ //{
+ // controller.SoundChangeVolume( pHunter->m_pGunFiringSound, 1.0f, 0.0f );
+ //}
+
+ pHunter->ShootFlechette( GetEnemy(), true );
+
+ if ( --pHunter->m_nFlechettesQueued > 0 )
+ {
+ pHunter->m_flNextFlechetteTime = gpGlobals->curtime + hunter_flechette_delay.GetFloat();
+ }
+ else
+ {
+ // Stop the firing sound.
+ //CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+ //controller.SoundChangeVolume( pHunter->m_pGunFiringSound, 0, 0.01f );
+
+ bVacate = true;
+ pHunter->BeginVolley( NUM_FLECHETTE_VOLLEY_ON_FOLLOW, gpGlobals->curtime + 1.0 + random->RandomFloat( 0, .25 ) + random->RandomFloat( 0, .25 ) );
+ }
+ }
+ }
+ }
+ else if ( bHasSlot )
+ {
+ bVacate = true;
+ }
+ }
+ else if ( bHasSlot )
+ {
+ bVacate = true;
+ }
+
+ if ( bVacate )
+ {
+ pHunter->VacateStrategySlot();
+ }
+ }
+
+ if ( m_FollowAttackTimer.Expired() && IsFollowTargetInRange( .8 ))
+ {
+ m_FollowAttackTimer.Set( 8, 24 );
+ TaskComplete();
+ }
+ else
+ {
+ BaseClass::RunTask( pTask );
+ }
+ }
+ break;
+ }
+
+ default:
+ BaseClass::RunTask( pTask );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CAI_HunterEscortBehavior::FindFreeHunters( CUtlVector<CNPC_Hunter *> *pFreeHunters )
+{
+ pFreeHunters->EnsureCapacity( g_Hunters.Count() );
+ int i;
+
+ for ( i = 0; i < g_Hunters.Count(); i++ )
+ {
+ CNPC_Hunter *pHunter = g_Hunters[i];
+ if ( pHunter->IsAlive() && pHunter->m_EscortBehavior.m_bEnabled )
+ {
+ if ( pHunter->m_EscortBehavior.GetFollowTarget() == NULL || !pHunter->m_EscortBehavior.GetFollowTarget()->IsAlive() )
+ {
+ pFreeHunters->AddToTail( pHunter);
+ }
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CAI_HunterEscortBehavior::DistributeFreeHunters()
+{
+ if ( g_TimeLastDistributeFreeHunters != -1 && gpGlobals->curtime - g_TimeLastDistributeFreeHunters < FREE_HUNTER_DISTRIBUTE_INTERVAL )
+ {
+ return;
+ }
+
+ g_TimeLastDistributeFreeHunters = gpGlobals->curtime;
+
+ CUtlVector<CNPC_Hunter *> freeHunters;
+ int i;
+ FindFreeHunters( &freeHunters );
+
+ CAI_BaseNPC **ppNPCs = g_AI_Manager.AccessAIs();
+ for ( i = 0; i < g_AI_Manager.NumAIs() && freeHunters.Count(); i++ )
+ {
+ int nToAdd;
+ CNPC_Strider *pStrider = ( ppNPCs[i]->IsAlive() ) ? dynamic_cast<CNPC_Strider *>( ppNPCs[i] ) : NULL;
+ if ( pStrider && !pStrider->CarriedByDropship() )
+ {
+ if ( ( nToAdd = 3 - AIGetNumFollowers( pStrider ) ) > 0 )
+ {
+ for ( int j = freeHunters.Count() - 1; j >= 0 && nToAdd > 0; --j )
+ {
+ DevMsg( "npc_hunter %d assigned to npc_strider %d\n", freeHunters[j]->entindex(), pStrider->entindex() );
+ freeHunters[j]->FollowStrider( pStrider );
+ freeHunters.FastRemove( j );
+ nToAdd--;
+ }
+ }
+ }
+ }
+
+ for ( i = 0; i < freeHunters.Count(); i++ )
+ {
+ //DevMsg( "npc_hunter %d assigned to free_hunters_squad\n", freeHunters[i]->entindex() );
+ freeHunters[i]->m_EscortBehavior.SetFollowTarget( NULL );
+ freeHunters[i]->AddToSquad( AllocPooledString( "free_hunters_squad" ) );
+ }
+
+#if 0
+ CBaseEntity *pHunterMaker = gEntList.FindEntityByClassname( NULL, "npc_hunter_maker" ); // TODO: this picks the same one every time!
+ if ( pHunterMaker )
+ {
+ for ( i = 0; i < freeHunters.Count(); i++ )
+ {
+ freeHunters[i]->m_EscortBehavior.SetFollowTarget( pHunterMaker );
+ }
+ }
+#endif
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CAI_HunterEscortBehavior::DrawDebugGeometryOverlays()
+{
+ if ( !GetFollowTarget() )
+ return;
+
+ Vector vecFollowPos = GetGoalPosition();
+ if ( FarFromFollowTarget() )
+ {
+ if ( gpGlobals->curtime >= m_flTimeEscortReturn )
+ {
+ NDebugOverlay::HorzArrow( GetOuter()->GetAbsOrigin(), vecFollowPos, 16.0f, 255, 0, 0, 0, true, 0 );
+ }
+ else
+ {
+ NDebugOverlay::HorzArrow( GetOuter()->GetAbsOrigin(), vecFollowPos, 16.0f, 255, 255, 0, 0, true, 0 );
+ }
+ }
+ else
+ {
+ NDebugOverlay::HorzArrow( GetOuter()->GetAbsOrigin(), vecFollowPos, 16.0f, 0, 255, 0, 0, true, 0 );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+bool Hunter_IsHunter(CBaseEntity *pEnt)
+{
+ return dynamic_cast<CNPC_Hunter *>(pEnt) != NULL;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void Hunter_StriderBusterLaunched( CBaseEntity *pBuster )
+{
+ CAI_BaseNPC **ppAIs = g_AI_Manager.AccessAIs();
+ int nAIs = g_AI_Manager.NumAIs();
+
+ for ( int i = 0; i < nAIs; i++ )
+ {
+ CAI_BaseNPC *pNPC = ppAIs[ i ];
+ if ( pNPC && ( pNPC->Classify() == CLASS_COMBINE_HUNTER ) && pNPC->m_lifeState == LIFE_ALIVE )
+ {
+ if ( !pNPC->GetEnemy() || !IsStriderBuster( pNPC->GetEnemy() ) )
+ {
+ Vector vecDelta = pNPC->GetAbsOrigin() - pBuster->GetAbsOrigin();
+ if ( vecDelta.Length2DSqr() < 9437184.0f ) // 3072 * 3072
+ {
+ pNPC->SetEnemy( pBuster );
+ pNPC->SetState( NPC_STATE_COMBAT );
+ pNPC->UpdateEnemyMemory( pBuster, pBuster->GetAbsOrigin() );
+
+ // Stop whatever we're doing.
+ pNPC->SetCondition( COND_SCHEDULE_DONE );
+ }
+ }
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void Hunter_StriderBusterAttached( CBaseEntity *pHunter, CBaseEntity *pAttached )
+{
+ Assert(dynamic_cast<CNPC_Hunter *>(pHunter));
+
+ static_cast<CNPC_Hunter *>(pHunter)->StriderBusterAttached(pAttached);
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void Hunter_StriderBusterDetached( CBaseEntity *pHunter, CBaseEntity *pAttached )
+{
+ Assert(dynamic_cast<CNPC_Hunter *>(pHunter));
+
+ static_cast<CNPC_Hunter *>(pHunter)->StriderBusterDetached(pAttached);
+}
+
+//-------------------------------------------------------------------------------------------------
+//
+// ep2_outland_12 custom npc makers
+//
+//-------------------------------------------------------------------------------------------------
+
+class CHunterMaker : public CTemplateNPCMaker
+{
+ typedef CTemplateNPCMaker BaseClass;
+public:
+ void MakeMultipleNPCS( int nNPCs )
+ {
+ const float MIN_HEALTH_PCT = 0.2;
+
+ CUtlVector<CNPC_Hunter *> candidates;
+ CUtlVectorFixed<CNPC_Hunter *, 3> freeHunters;
+ CAI_HunterEscortBehavior::FindFreeHunters( &candidates );
+
+ freeHunters.EnsureCapacity( 3 );
+ int i;
+
+ for ( i = 0; i < candidates.Count() && freeHunters.Count() < 3; i++ )
+ {
+ if ( candidates[i]->GetHealth() > candidates[i]->GetMaxHealth() * MIN_HEALTH_PCT )
+ {
+ freeHunters.AddToTail( candidates[i] );
+ }
+ }
+
+ int nRequested = nNPCs;
+ if ( nNPCs < 3 )
+ {
+ nNPCs = MIN( 3, nNPCs + freeHunters.Count() );
+ }
+
+ int nSummoned = 0;
+ for ( i = 0; i < freeHunters.Count() && nNPCs; i++ )
+ {
+ freeHunters[i]->m_EscortBehavior.SetFollowTarget( this ); // this will make them not "free"
+ freeHunters[i]->SetName( m_iszTemplateName ); // this will force the hunter to get the FollowStrider input
+ nNPCs--;
+ nSummoned++;
+ }
+
+ DevMsg( "Requested %d to spawn, Summoning %d free hunters, spawning %d new hunters\n", nRequested, nSummoned, nNPCs );
+ if ( nNPCs )
+ {
+ BaseClass::MakeMultipleNPCS( nNPCs );
+ }
+ }
+};
+
+LINK_ENTITY_TO_CLASS( npc_hunter_maker, CHunterMaker );
+
+
+//-------------------------------------------------------------------------------------------------
+//
+// Schedules
+//
+//-------------------------------------------------------------------------------------------------
+AI_BEGIN_CUSTOM_NPC( npc_hunter, CNPC_Hunter )
+
+ DECLARE_TASK( TASK_HUNTER_AIM )
+ DECLARE_TASK( TASK_HUNTER_FIND_DODGE_POSITION )
+ DECLARE_TASK( TASK_HUNTER_DODGE )
+ DECLARE_TASK( TASK_HUNTER_PRE_RANGE_ATTACK2 )
+ DECLARE_TASK( TASK_HUNTER_SHOOT_COMMIT )
+ DECLARE_TASK( TASK_HUNTER_ANNOUNCE_FLANK )
+ DECLARE_TASK( TASK_HUNTER_BEGIN_FLANK )
+ DECLARE_TASK( TASK_HUNTER_STAGGER )
+ DECLARE_TASK( TASK_HUNTER_CORNERED_TIMER )
+ DECLARE_TASK( TASK_HUNTER_FIND_SIDESTEP_POSITION )
+ DECLARE_TASK( TASK_HUNTER_CHARGE )
+ DECLARE_TASK( TASK_HUNTER_FINISH_RANGE_ATTACK )
+ DECLARE_TASK( TASK_HUNTER_WAIT_FOR_MOVEMENT_FACING_ENEMY )
+ DECLARE_TASK( TASK_HUNTER_CHARGE_DELAY )
+
+ DECLARE_ACTIVITY( ACT_HUNTER_DEPLOYRA2 )
+ DECLARE_ACTIVITY( ACT_HUNTER_DODGER )
+ DECLARE_ACTIVITY( ACT_HUNTER_DODGEL )
+ DECLARE_ACTIVITY( ACT_HUNTER_GESTURE_SHOOT )
+ DECLARE_ACTIVITY( ACT_HUNTER_FLINCH_STICKYBOMB )
+ DECLARE_ACTIVITY( ACT_HUNTER_STAGGER )
+ DECLARE_ACTIVITY( ACT_DI_HUNTER_MELEE )
+ DECLARE_ACTIVITY( ACT_DI_HUNTER_THROW )
+ DECLARE_ACTIVITY( ACT_HUNTER_MELEE_ATTACK1_VS_PLAYER )
+ DECLARE_ACTIVITY( ACT_HUNTER_ANGRY )
+ DECLARE_ACTIVITY( ACT_HUNTER_WALK_ANGRY )
+ DECLARE_ACTIVITY( ACT_HUNTER_FOUND_ENEMY )
+ DECLARE_ACTIVITY( ACT_HUNTER_FOUND_ENEMY_ACK )
+ DECLARE_ACTIVITY( ACT_HUNTER_CHARGE_START )
+ DECLARE_ACTIVITY( ACT_HUNTER_CHARGE_RUN )
+ DECLARE_ACTIVITY( ACT_HUNTER_CHARGE_STOP )
+ DECLARE_ACTIVITY( ACT_HUNTER_CHARGE_CRASH )
+ DECLARE_ACTIVITY( ACT_HUNTER_CHARGE_HIT )
+ DECLARE_ACTIVITY( ACT_HUNTER_RANGE_ATTACK2_UNPLANTED )
+ DECLARE_ACTIVITY( ACT_HUNTER_IDLE_PLANTED )
+ DECLARE_ACTIVITY( ACT_HUNTER_FLINCH_N )
+ DECLARE_ACTIVITY( ACT_HUNTER_FLINCH_S )
+ DECLARE_ACTIVITY( ACT_HUNTER_FLINCH_E )
+ DECLARE_ACTIVITY( ACT_HUNTER_FLINCH_W )
+
+ DECLARE_INTERACTION( g_interactionHunterFoundEnemy );
+
+ DECLARE_SQUADSLOT( SQUAD_SLOT_HUNTER_CHARGE )
+ DECLARE_SQUADSLOT( SQUAD_SLOT_HUNTER_FLANK_FIRST )
+ DECLARE_SQUADSLOT( SQUAD_SLOT_RUN_SHOOT )
+
+ DECLARE_CONDITION( COND_HUNTER_SHOULD_PATROL )
+ DECLARE_CONDITION( COND_HUNTER_FORCED_FLANK_ENEMY )
+ DECLARE_CONDITION( COND_HUNTER_CAN_CHARGE_ENEMY )
+ DECLARE_CONDITION( COND_HUNTER_STAGGERED )
+ DECLARE_CONDITION( COND_HUNTER_IS_INDOORS )
+ DECLARE_CONDITION( COND_HUNTER_HIT_BY_STICKYBOMB )
+ DECLARE_CONDITION( COND_HUNTER_SEE_STRIDERBUSTER )
+ DECLARE_CONDITION( COND_HUNTER_FORCED_DODGE )
+ DECLARE_CONDITION( COND_HUNTER_INCOMING_VEHICLE )
+ DECLARE_CONDITION( COND_HUNTER_NEW_HINTGROUP )
+ DECLARE_CONDITION( COND_HUNTER_CANT_PLANT )
+ DECLARE_CONDITION( COND_HUNTER_SQUADMATE_FOUND_ENEMY )
+
+ DECLARE_ANIMEVENT( AE_HUNTER_FOOTSTEP_LEFT )
+ DECLARE_ANIMEVENT( AE_HUNTER_FOOTSTEP_RIGHT )
+ DECLARE_ANIMEVENT( AE_HUNTER_FOOTSTEP_BACK )
+ DECLARE_ANIMEVENT( AE_HUNTER_MELEE_ANNOUNCE )
+ DECLARE_ANIMEVENT( AE_HUNTER_MELEE_ATTACK_LEFT )
+ DECLARE_ANIMEVENT( AE_HUNTER_MELEE_ATTACK_RIGHT )
+ DECLARE_ANIMEVENT( AE_HUNTER_DIE )
+ DECLARE_ANIMEVENT( AE_HUNTER_SPRAY_BLOOD )
+ DECLARE_ANIMEVENT( AE_HUNTER_START_EXPRESSION )
+ DECLARE_ANIMEVENT( AE_HUNTER_END_EXPRESSION )
+
+ //=========================================================
+ // Attack (Deploy/shoot/finish)
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HUNTER_RANGE_ATTACK1,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_HUNTER_SHOOT_COMMIT 0"
+ " TASK_RANGE_ATTACK1 0"
+ " "
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_ENEMY_DEAD"
+ " COND_LOST_ENEMY"
+ " COND_ENEMY_OCCLUDED"
+ " COND_WEAPON_SIGHT_OCCLUDED"
+ " COND_TOO_CLOSE_TO_ATTACK"
+ " COND_TOO_FAR_TO_ATTACK"
+ " COND_NOT_FACING_ATTACK"
+ )
+
+ //=========================================================
+ // Attack (Deploy/shoot/finish)
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HUNTER_RANGE_ATTACK2,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_HUNTER_PRE_RANGE_ATTACK2 0"
+ " TASK_HUNTER_SHOOT_COMMIT 0"
+ " TASK_RANGE_ATTACK2 0"
+ " TASK_HUNTER_FINISH_RANGE_ATTACK 0"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
+ " TASK_WAIT 0.4"
+ " TASK_WAIT_RANDOM 0.2"
+ " "
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ )
+
+ //=========================================================
+ // Shoot at striderbuster. Distinct from generic range attack
+ // because of BuildScheduleTestBits.
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HUNTER_RANGE_ATTACK2_VS_STRIDERBUSTER,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_HUNTER_SHOOT_COMMIT 0"
+ " TASK_RANGE_ATTACK2 0"
+ " "
+ " Interrupts"
+ )
+
+ //=========================================================
+ // Shoot at striderbuster with a little latency beforehand
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HUNTER_RANGE_ATTACK2_VS_STRIDERBUSTER_LATENT,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_HUNTER_SHOOT_COMMIT 0"
+ " TASK_WAIT 0.2"
+ " TASK_PLAY_SEQUENCE_FACE_ENEMY ACTIVITY:ACT_RANGE_ATTACK2"
+ " TASK_RANGE_ATTACK2 0"
+ " "
+ " Interrupts"
+ )
+
+ //=========================================================
+ // Dodge Incoming vehicle
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HUNTER_DODGE,
+
+ " Tasks"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_HUNTER_FAIL_DODGE"
+ " TASK_HUNTER_FIND_DODGE_POSITION 0"
+ " TASK_HUNTER_DODGE 0"
+ ""
+ " Interrupts"
+ )
+
+ //=========================================================
+ // Dodge Incoming vehicle
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HUNTER_FAIL_DODGE,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
+ " TASK_FACE_ENEMY 0"
+ ""
+ " Interrupts"
+ )
+
+ //==================================================
+ // > SCHED_HUNTER_CHARGE_ENEMY
+ // Rush at my enemy and head-butt them.
+ //==================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HUNTER_CHARGE_ENEMY,
+
+ " Tasks"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_HUNTER_FAIL_CHARGE_ENEMY"
+ " TASK_STOP_MOVING 0"
+ " TASK_FACE_ENEMY 0"
+ " TASK_HUNTER_CHARGE 0"
+ ""
+ " Interrupts"
+ " COND_TASK_FAILED"
+ " COND_ENEMY_DEAD"
+ )
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_HUNTER_FAIL_CHARGE_ENEMY,
+
+ " Tasks"
+ " TASK_HUNTER_CHARGE_DELAY 10"
+ )
+
+ //=========================================================
+ // Chase the enemy with intent to claw
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HUNTER_CHASE_ENEMY_MELEE,
+
+ " Tasks"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_ESTABLISH_LINE_OF_FIRE"
+ " TASK_STOP_MOVING 0"
+ " TASK_GET_CHASE_PATH_TO_ENEMY 300"
+ " TASK_RUN_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " TASK_FACE_ENEMY 0"
+ ""
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_ENEMY_DEAD"
+ " COND_ENEMY_UNREACHABLE"
+ " COND_CAN_MELEE_ATTACK1"
+ " COND_CAN_MELEE_ATTACK2"
+ //" COND_TOO_CLOSE_TO_ATTACK"
+ " COND_LOST_ENEMY"
+ )
+
+ //=========================================================
+ // Chase my enemy, shoot or claw when possible to do so.
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HUNTER_CHASE_ENEMY,
+
+ " Tasks"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_ESTABLISH_LINE_OF_FIRE"
+ " TASK_STOP_MOVING 0"
+ " TASK_GET_CHASE_PATH_TO_ENEMY 300"
+ " TASK_RUN_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " TASK_FACE_ENEMY 0"
+ ""
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_ENEMY_DEAD"
+ " COND_ENEMY_UNREACHABLE"
+ " COND_CAN_RANGE_ATTACK1"
+ " COND_CAN_RANGE_ATTACK2"
+ " COND_CAN_MELEE_ATTACK1"
+ " COND_CAN_MELEE_ATTACK2"
+ " COND_TOO_CLOSE_TO_ATTACK"
+ " COND_LOST_ENEMY"
+ )
+
+ //=========================================================
+ // Move to a flanking position, then shoot if possible.
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HUNTER_FLANK_ENEMY,
+
+ " Tasks"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_ESTABLISH_LINE_OF_FIRE"
+ " TASK_STOP_MOVING 0"
+ " TASK_HUNTER_BEGIN_FLANK 0"
+ " TASK_GET_FLANK_ARC_PATH_TO_ENEMY_LOS 30"
+ " TASK_HUNTER_ANNOUNCE_FLANK 0"
+ " TASK_RUN_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " TASK_FACE_ENEMY 0"
+ //" TASK_HUNTER_END_FLANK 0"
+ ""
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ //" COND_CAN_RANGE_ATTACK1"
+ //" COND_CAN_RANGE_ATTACK2"
+ " COND_CAN_MELEE_ATTACK1"
+ " COND_CAN_MELEE_ATTACK2"
+ " COND_ENEMY_DEAD"
+ " COND_ENEMY_UNREACHABLE"
+ " COND_TOO_CLOSE_TO_ATTACK"
+ " COND_LOST_ENEMY"
+ )
+
+ //=========================================================
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HUNTER_COMBAT_FACE,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
+ " TASK_WAIT_FACE_ENEMY 1"
+ ""
+ " Interrupts"
+ " COND_CAN_RANGE_ATTACK1"
+ " COND_CAN_RANGE_ATTACK2"
+ " COND_CAN_MELEE_ATTACK1"
+ " COND_CAN_MELEE_ATTACK2"
+ " COND_NEW_ENEMY"
+ " COND_ENEMY_DEAD"
+ )
+
+ //=========================================================
+ // Like the base class, only don't stop in the middle of
+ // swinging if the enemy is killed, hides, or new enemy.
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HUNTER_MELEE_ATTACK1,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_FACE_ENEMY 0"
+ " TASK_MELEE_ATTACK1 0"
+ //" TASK_SET_SCHEDULE SCHEDULE:SCHED_HUNTER_POST_MELEE_WAIT"
+ ""
+ " Interrupts"
+ )
+
+ //=========================================================
+ // In a fight with nothing to do. Make busy!
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HUNTER_CHANGE_POSITION,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_WANDER 720432" // 6 feet to 36 feet
+ " TASK_RUN_PATH 0"
+ " TASK_HUNTER_WAIT_FOR_MOVEMENT_FACING_ENEMY 0"
+ " TASK_STOP_MOVING 0"
+ " TASK_SET_SCHEDULE SCHEDULE:SCHED_HUNTER_CHANGE_POSITION_FINISH"
+ ""
+ " Interrupts"
+ " COND_ENEMY_DEAD"
+ " COND_CAN_MELEE_ATTACK1"
+ " COND_CAN_MELEE_ATTACK2"
+ " COND_HEAR_DANGER"
+ " COND_HEAR_MOVE_AWAY"
+ " COND_NEW_ENEMY"
+ )
+
+ //=========================================================
+ // In a fight with nothing to do. Make busy!
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HUNTER_CHANGE_POSITION_FINISH,
+
+ " Tasks"
+ " TASK_FACE_ENEMY 0"
+ " TASK_WAIT_FACE_ENEMY_RANDOM 5"
+ ""
+ " Interrupts"
+ " COND_ENEMY_DEAD"
+ " COND_CAN_RANGE_ATTACK1"
+ " COND_CAN_RANGE_ATTACK2"
+ " COND_CAN_MELEE_ATTACK1"
+ " COND_CAN_MELEE_ATTACK2"
+ " COND_HEAR_DANGER"
+ " COND_HEAR_MOVE_AWAY"
+ " COND_NEW_ENEMY"
+ )
+
+ //=========================================================
+ // In a fight with nothing to do. Make busy!
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HUNTER_SIDESTEP,
+
+ " Tasks"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_HUNTER_FAIL_IMMEDIATE" // used because sched_fail includes a one second pause. ick!
+ " TASK_STOP_MOVING 0"
+ " TASK_HUNTER_FIND_SIDESTEP_POSITION 0"
+ " TASK_GET_PATH_TO_SAVEPOSITION 0"
+ " TASK_RUN_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " TASK_FACE_ENEMY 0"
+ ""
+ " Interrupts"
+ )
+
+ //=========================================================
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HUNTER_PATROL,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_WANDER 720432" // 6 feet to 36 feet
+ " TASK_WALK_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " TASK_STOP_MOVING 0"
+ " TASK_FACE_REASONABLE 0"
+ " TASK_WAIT_RANDOM 3"
+ ""
+ " Interrupts"
+ " COND_ENEMY_DEAD"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ " COND_HEAR_DANGER"
+ " COND_HEAR_COMBAT"
+ " COND_HEAR_PLAYER"
+ " COND_HEAR_BULLET_IMPACT"
+ " COND_HEAR_MOVE_AWAY"
+ " COND_NEW_ENEMY"
+ " COND_SEE_ENEMY"
+ " COND_CAN_RANGE_ATTACK1"
+ " COND_CAN_RANGE_ATTACK2"
+ " COND_CAN_MELEE_ATTACK1"
+ " COND_CAN_MELEE_ATTACK2"
+ )
+
+ //=========================================================
+ // Stagger because I got hit by something heavy
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HUNTER_STAGGER,
+
+ " Tasks"
+ " TASK_HUNTER_STAGGER 0"
+ ""
+ " Interrupts"
+ )
+
+ //=========================================================
+ // Run around randomly until we detect an enemy
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HUNTER_PATROL_RUN,
+
+ " Tasks"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_COMBAT_FACE"
+ " TASK_SET_ROUTE_SEARCH_TIME 5" // Spend 5 seconds trying to build a path if stuck
+ " TASK_GET_PATH_TO_RANDOM_NODE 200"
+ " TASK_RUN_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ ""
+ " Interrupts"
+ " COND_CAN_RANGE_ATTACK1 "
+ " COND_CAN_RANGE_ATTACK2 "
+ " COND_CAN_MELEE_ATTACK1 "
+ " COND_CAN_MELEE_ATTACK2"
+ " COND_GIVE_WAY"
+ " COND_NEW_ENEMY"
+ " COND_HEAR_COMBAT"
+ " COND_HEAR_DANGER"
+ " COND_HEAR_PLAYER"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ )
+
+ //=========================================================
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HUNTER_TAKE_COVER_FROM_ENEMY,
+
+ " Tasks"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_HUNTER_CHASE_ENEMY_MELEE"
+ " TASK_HUNTER_CORNERED_TIMER 10.0"
+ " TASK_WAIT 0.0"
+ // " TASK_SET_TOLERANCE_DISTANCE 24"
+ // " TASK_FIND_COVER_FROM_ENEMY 0"
+ " TASK_FIND_FAR_NODE_COVER_FROM_ENEMY 200.0"
+ " TASK_RUN_PATH 0"
+ " TASK_HUNTER_CORNERED_TIMER 0.0"
+ // " TASK_CLEAR_FAIL_SCHEDULE 0" // not used because sched_fail includes a one second pause. ick!
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_HUNTER_FAIL_IMMEDIATE"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " TASK_REMEMBER MEMORY:INCOVER"
+ " TASK_SET_SCHEDULE SCHEDULE:SCHED_HUNTER_HIDE_UNDER_COVER"
+ /*
+ " TASK_FACE_ENEMY 0"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE" // Translated to cover
+ " TASK_WAIT 1"
+ */
+ ""
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_HEAR_DANGER"
+ )
+
+ //=========================================================
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HUNTER_HIDE_UNDER_COVER,
+
+ " Tasks"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_HUNTER_FAIL_IMMEDIATE" // used because sched_fail includes a one second pause. ick!
+ " TASK_FACE_ENEMY 0"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE" // Translated to cover
+ " TASK_WAIT 1"
+ ""
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_HEAR_DANGER"
+ " COND_HAVE_ENEMY_LOS"
+ )
+
+ //=========================================================
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HUNTER_FOUND_ENEMY,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_FACE_ENEMY 0"
+ " TASK_PLAY_SEQUENCE_FACE_ENEMY ACTIVITY:ACT_HUNTER_FOUND_ENEMY"
+ ""
+ " Interrupts"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ )
+
+ //=========================================================
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HUNTER_FOUND_ENEMY_ACK,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_WAIT_RANDOM 0.75"
+ " TASK_FACE_ENEMY 0"
+ " TASK_PLAY_SEQUENCE_FACE_ENEMY ACTIVITY:ACT_HUNTER_FOUND_ENEMY_ACK"
+ ""
+ " Interrupts"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ )
+
+ //=========================================================
+ // An empty schedule that immediately bails out, faster than
+ // SCHED_FAIL which stops moving and waits for one second.
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HUNTER_FAIL_IMMEDIATE,
+
+ " Tasks"
+ " TASK_WAIT 0"
+
+ )
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_HUNTER_GOTO_HINT,
+ " Tasks"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_HUNTER_CLEAR_HINTNODE" // used because sched_fail includes a one second pause. ick!
+ " TASK_GET_PATH_TO_HINTNODE 1"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " TASK_CLEAR_HINTNODE 0"
+ ""
+ ""
+ " Interrupts"
+ )
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_HUNTER_CLEAR_HINTNODE,
+ " Tasks"
+ " TASK_CLEAR_HINTNODE 0"
+ ""
+ ""
+ " Interrupts"
+ )
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_HUNTER_SIEGE_STAND,
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
+ " TASK_FACE_PLAYER 0"
+ " TASK_WAIT 10"
+ " TASK_WAIT_RANDOM 2"
+ " TASK_SET_SCHEDULE SCHEDULE:SCHED_HUNTER_CHANGE_POSITION_SIEGE"
+ ""
+ ""
+ " Interrupts"
+ " COND_SEE_PLAYER"
+ " COND_NEW_ENEMY"
+ )
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_HUNTER_CHANGE_POSITION_SIEGE,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_WANDER 2400480"
+ " TASK_RUN_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " TASK_STOP_MOVING 0"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
+ " TASK_FACE_PLAYER 0"
+ ""
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ )
+
+ // formula is MIN_DIST * 10000 + MAX_DIST
+
+AI_END_CUSTOM_NPC()
diff --git a/mp/src/game/server/episodic/npc_hunter.h b/mp/src/game/server/episodic/npc_hunter.h
index 812d8f3c..b68cedff 100644
--- a/mp/src/game/server/episodic/npc_hunter.h
+++ b/mp/src/game/server/episodic/npc_hunter.h
@@ -1,25 +1,25 @@
-//========= Copyright Valve Corporation, All rights reserved. ============//
-//
-// Purpose: Expose an IsAHunter function
-//
-//=============================================================================//
-
-#ifndef NPC_HUNTER_H
-#define NPC_HUNTER_H
-
-#if defined( _WIN32 )
-#pragma once
-#endif
-
-class CBaseEntity;
-
-/// true if given entity pointer is a hunter.
-bool Hunter_IsHunter(CBaseEntity *pEnt);
-
-// call throughs for member functions
-
-void Hunter_StriderBusterAttached( CBaseEntity *pHunter, CBaseEntity *pAttached );
-void Hunter_StriderBusterDetached( CBaseEntity *pHunter, CBaseEntity *pAttached );
-void Hunter_StriderBusterLaunched( CBaseEntity *pBuster );
-
-#endif
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Expose an IsAHunter function
+//
+//=============================================================================//
+
+#ifndef NPC_HUNTER_H
+#define NPC_HUNTER_H
+
+#if defined( _WIN32 )
+#pragma once
+#endif
+
+class CBaseEntity;
+
+/// true if given entity pointer is a hunter.
+bool Hunter_IsHunter(CBaseEntity *pEnt);
+
+// call throughs for member functions
+
+void Hunter_StriderBusterAttached( CBaseEntity *pHunter, CBaseEntity *pAttached );
+void Hunter_StriderBusterDetached( CBaseEntity *pHunter, CBaseEntity *pAttached );
+void Hunter_StriderBusterLaunched( CBaseEntity *pBuster );
+
+#endif
diff --git a/mp/src/game/server/episodic/npc_magnusson.cpp b/mp/src/game/server/episodic/npc_magnusson.cpp
index fdd34ba0..31008353 100644
--- a/mp/src/game/server/episodic/npc_magnusson.cpp
+++ b/mp/src/game/server/episodic/npc_magnusson.cpp
@@ -1,127 +1,127 @@
-//========= Copyright Valve Corporation, All rights reserved. ============//
-//
-// Purpose: Dr. Magnusson, a grumpy bastard who builds satellites and rockets
-// at the White Forest missile silo. Instantly unlikeable, he is also
-// the inventor of the Magnusson Device aka "strider buster", which
-// is purported to resemble his cantelope-like head.
-//
-//=============================================================================
-
-
-//-----------------------------------------------------------------------------
-// Generic NPC - purely for scripted sequence work.
-//-----------------------------------------------------------------------------
-#include "cbase.h"
-#include "npcevent.h"
-#include "ai_basenpc.h"
-#include "ai_hull.h"
-#include "ai_baseactor.h"
-
-// memdbgon must be the last include file in a .cpp file!!!
-#include "tier0/memdbgon.h"
-
-//-----------------------------------------------------------------------------
-// NPC's Anim Events Go Here
-//-----------------------------------------------------------------------------
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-class CNPC_Magnusson : public CAI_BaseActor
-{
-public:
- DECLARE_CLASS( CNPC_Magnusson, CAI_BaseActor );
-
- void Spawn( void );
- void Precache( void );
- Class_T Classify ( void );
- void HandleAnimEvent( animevent_t *pEvent );
- int GetSoundInterests ( void );
-};
-
-LINK_ENTITY_TO_CLASS( npc_magnusson, CNPC_Magnusson );
-
-
-//-----------------------------------------------------------------------------
-// Classify - indicates this NPC's place in the
-// relationship table.
-//-----------------------------------------------------------------------------
-Class_T CNPC_Magnusson::Classify ( void )
-{
- return CLASS_PLAYER_ALLY_VITAL;
-}
-
-
-//-----------------------------------------------------------------------------
-// HandleAnimEvent - catches the NPC-specific messages
-// that occur when tagged animation frames are played.
-//-----------------------------------------------------------------------------
-void CNPC_Magnusson::HandleAnimEvent( animevent_t *pEvent )
-{
- switch( pEvent->event )
- {
- case 1:
- default:
- BaseClass::HandleAnimEvent( pEvent );
- break;
- }
-}
-
-//-----------------------------------------------------------------------------
-// GetSoundInterests - generic NPC can't hear.
-//-----------------------------------------------------------------------------
-int CNPC_Magnusson::GetSoundInterests ( void )
-{
- return NULL;
-}
-
-//-----------------------------------------------------------------------------
-// Spawn
-//-----------------------------------------------------------------------------
-void CNPC_Magnusson::Spawn()
-{
- // Allow custom model usage (mostly for monitors)
- char *szModel = (char *)STRING( GetModelName() );
- if (!szModel || !*szModel)
- {
- szModel = "models/magnusson.mdl";
- SetModelName( AllocPooledString(szModel) );
- }
-
- Precache();
- SetModel( szModel );
-
- BaseClass::Spawn();
-
- SetHullType(HULL_HUMAN);
- SetHullSizeNormal();
-
- SetSolid( SOLID_BBOX );
- AddSolidFlags( FSOLID_NOT_STANDABLE );
- SetMoveType( MOVETYPE_STEP );
- SetBloodColor( BLOOD_COLOR_RED );
- m_iHealth = 8;
- m_flFieldOfView = 0.5;// indicates the width of this NPC's forward view cone ( as a dotproduct result )
- m_NPCState = NPC_STATE_NONE;
-
- CapabilitiesAdd( bits_CAP_MOVE_GROUND | bits_CAP_OPEN_DOORS | bits_CAP_ANIMATEDFACE | bits_CAP_TURN_HEAD );
- CapabilitiesAdd( bits_CAP_FRIENDLY_DMG_IMMUNE );
-
- AddEFlags( EFL_NO_DISSOLVE | EFL_NO_MEGAPHYSCANNON_RAGDOLL | EFL_NO_PHYSCANNON_INTERACTION );
-
- NPCInit();
-}
-
-//-----------------------------------------------------------------------------
-// Precache - precaches all resources this NPC needs
-//-----------------------------------------------------------------------------
-void CNPC_Magnusson::Precache()
-{
- PrecacheModel( STRING( GetModelName() ) );
-
- BaseClass::Precache();
-}
-
-//-----------------------------------------------------------------------------
-// AI Schedules Specific to this NPC
-//-----------------------------------------------------------------------------
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Dr. Magnusson, a grumpy bastard who builds satellites and rockets
+// at the White Forest missile silo. Instantly unlikeable, he is also
+// the inventor of the Magnusson Device aka "strider buster", which
+// is purported to resemble his cantelope-like head.
+//
+//=============================================================================
+
+
+//-----------------------------------------------------------------------------
+// Generic NPC - purely for scripted sequence work.
+//-----------------------------------------------------------------------------
+#include "cbase.h"
+#include "npcevent.h"
+#include "ai_basenpc.h"
+#include "ai_hull.h"
+#include "ai_baseactor.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+//-----------------------------------------------------------------------------
+// NPC's Anim Events Go Here
+//-----------------------------------------------------------------------------
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CNPC_Magnusson : public CAI_BaseActor
+{
+public:
+ DECLARE_CLASS( CNPC_Magnusson, CAI_BaseActor );
+
+ void Spawn( void );
+ void Precache( void );
+ Class_T Classify ( void );
+ void HandleAnimEvent( animevent_t *pEvent );
+ int GetSoundInterests ( void );
+};
+
+LINK_ENTITY_TO_CLASS( npc_magnusson, CNPC_Magnusson );
+
+
+//-----------------------------------------------------------------------------
+// Classify - indicates this NPC's place in the
+// relationship table.
+//-----------------------------------------------------------------------------
+Class_T CNPC_Magnusson::Classify ( void )
+{
+ return CLASS_PLAYER_ALLY_VITAL;
+}
+
+
+//-----------------------------------------------------------------------------
+// HandleAnimEvent - catches the NPC-specific messages
+// that occur when tagged animation frames are played.
+//-----------------------------------------------------------------------------
+void CNPC_Magnusson::HandleAnimEvent( animevent_t *pEvent )
+{
+ switch( pEvent->event )
+ {
+ case 1:
+ default:
+ BaseClass::HandleAnimEvent( pEvent );
+ break;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// GetSoundInterests - generic NPC can't hear.
+//-----------------------------------------------------------------------------
+int CNPC_Magnusson::GetSoundInterests ( void )
+{
+ return NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Spawn
+//-----------------------------------------------------------------------------
+void CNPC_Magnusson::Spawn()
+{
+ // Allow custom model usage (mostly for monitors)
+ char *szModel = (char *)STRING( GetModelName() );
+ if (!szModel || !*szModel)
+ {
+ szModel = "models/magnusson.mdl";
+ SetModelName( AllocPooledString(szModel) );
+ }
+
+ Precache();
+ SetModel( szModel );
+
+ BaseClass::Spawn();
+
+ SetHullType(HULL_HUMAN);
+ SetHullSizeNormal();
+
+ SetSolid( SOLID_BBOX );
+ AddSolidFlags( FSOLID_NOT_STANDABLE );
+ SetMoveType( MOVETYPE_STEP );
+ SetBloodColor( BLOOD_COLOR_RED );
+ m_iHealth = 8;
+ m_flFieldOfView = 0.5;// indicates the width of this NPC's forward view cone ( as a dotproduct result )
+ m_NPCState = NPC_STATE_NONE;
+
+ CapabilitiesAdd( bits_CAP_MOVE_GROUND | bits_CAP_OPEN_DOORS | bits_CAP_ANIMATEDFACE | bits_CAP_TURN_HEAD );
+ CapabilitiesAdd( bits_CAP_FRIENDLY_DMG_IMMUNE );
+
+ AddEFlags( EFL_NO_DISSOLVE | EFL_NO_MEGAPHYSCANNON_RAGDOLL | EFL_NO_PHYSCANNON_INTERACTION );
+
+ NPCInit();
+}
+
+//-----------------------------------------------------------------------------
+// Precache - precaches all resources this NPC needs
+//-----------------------------------------------------------------------------
+void CNPC_Magnusson::Precache()
+{
+ PrecacheModel( STRING( GetModelName() ) );
+
+ BaseClass::Precache();
+}
+
+//-----------------------------------------------------------------------------
+// AI Schedules Specific to this NPC
+//-----------------------------------------------------------------------------
diff --git a/mp/src/game/server/episodic/npc_puppet.cpp b/mp/src/game/server/episodic/npc_puppet.cpp
index ff0e9e85..8d4f3669 100644
--- a/mp/src/game/server/episodic/npc_puppet.cpp
+++ b/mp/src/game/server/episodic/npc_puppet.cpp
@@ -1,121 +1,121 @@
-//========= Copyright Valve Corporation, All rights reserved. ============//
-//
-// Purpose: NPC Puppet
-//
-//=============================================================================
-
-#include "cbase.h"
-#include "ai_basenpc.h"
-
-// Must be the last file included
-#include "memdbgon.h"
-
-class CNPC_Puppet : public CAI_BaseNPC
-{
- DECLARE_CLASS( CNPC_Puppet, CAI_BaseNPC );
-public:
-
- virtual void Spawn( void );
- virtual void Precache( void );
-
- void InputSetAnimationTarget( inputdata_t &inputdata );
-
-private:
-
- string_t m_sAnimTargetname;
- string_t m_sAnimAttachmentName;
-
- CNetworkVar( EHANDLE, m_hAnimationTarget ); // NPC that will drive what animation we're playing
- CNetworkVar( int, m_nTargetAttachment ); // Attachment point to match to on the target
-
- DECLARE_DATADESC();
- DECLARE_SERVERCLASS();
-};
-
-LINK_ENTITY_TO_CLASS( npc_puppet, CNPC_Puppet );
-
-BEGIN_DATADESC( CNPC_Puppet )
- DEFINE_KEYFIELD( m_sAnimTargetname, FIELD_STRING, "animationtarget" ),
- DEFINE_KEYFIELD( m_sAnimAttachmentName, FIELD_STRING, "attachmentname" ),
-
- DEFINE_FIELD( m_nTargetAttachment, FIELD_INTEGER ),
- DEFINE_FIELD( m_hAnimationTarget, FIELD_EHANDLE ),
- DEFINE_INPUTFUNC( FIELD_STRING, "SetAnimationTarget", InputSetAnimationTarget ),
-END_DATADESC()
-
-IMPLEMENT_SERVERCLASS_ST( CNPC_Puppet, DT_NPC_Puppet )
- SendPropEHandle( SENDINFO( m_hAnimationTarget ) ),
- SendPropInt( SENDINFO( m_nTargetAttachment) ),
-END_SEND_TABLE()
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CNPC_Puppet::Precache( void )
-{
- BaseClass::Precache();
- PrecacheModel( STRING( GetModelName() ) );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CNPC_Puppet::Spawn( void )
-{
- BaseClass::Spawn();
-
- Precache();
-
- SetModel( STRING( GetModelName() ) );
-
- NPCInit();
-
- SetHealth( 100 );
-
- // Find our animation target
- CBaseEntity *pTarget = gEntList.FindEntityByName( NULL, m_sAnimTargetname );
- m_hAnimationTarget = pTarget;
- if ( pTarget )
- {
- CBaseAnimating *pAnimating = pTarget->GetBaseAnimating();
- if ( pAnimating )
- {
- m_nTargetAttachment = pAnimating->LookupAttachment( STRING( m_sAnimAttachmentName ) );
- }
- }
-
- // Always be scripted
- SetInAScript( true );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : &inputdata -
-//-----------------------------------------------------------------------------
-void CNPC_Puppet::InputSetAnimationTarget( inputdata_t &inputdata )
-{
- // Take the new name
- m_sAnimTargetname = MAKE_STRING( inputdata.value.String() );
-
- // Find our animation target
- CBaseEntity *pTarget = gEntList.FindEntityByName( NULL, m_sAnimTargetname );
- if ( pTarget == NULL )
- {
- Warning("Failed to find animation target %s for npc_puppet (%s)\n", STRING( m_sAnimTargetname ), STRING( GetEntityName() ) );
- return;
- }
-
- m_hAnimationTarget = pTarget;
-
- CBaseAnimating *pAnimating = pTarget->GetBaseAnimating();
- if ( pAnimating )
- {
- // Cache off our target attachment
- m_nTargetAttachment = pAnimating->LookupAttachment( STRING( m_sAnimAttachmentName ) );
- }
-
- // Stuff us at the owner's core for visibility reasons
- SetParent( pTarget );
- SetLocalOrigin( vec3_origin );
- SetLocalAngles( vec3_angle );
-}
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: NPC Puppet
+//
+//=============================================================================
+
+#include "cbase.h"
+#include "ai_basenpc.h"
+
+// Must be the last file included
+#include "memdbgon.h"
+
+class CNPC_Puppet : public CAI_BaseNPC
+{
+ DECLARE_CLASS( CNPC_Puppet, CAI_BaseNPC );
+public:
+
+ virtual void Spawn( void );
+ virtual void Precache( void );
+
+ void InputSetAnimationTarget( inputdata_t &inputdata );
+
+private:
+
+ string_t m_sAnimTargetname;
+ string_t m_sAnimAttachmentName;
+
+ CNetworkVar( EHANDLE, m_hAnimationTarget ); // NPC that will drive what animation we're playing
+ CNetworkVar( int, m_nTargetAttachment ); // Attachment point to match to on the target
+
+ DECLARE_DATADESC();
+ DECLARE_SERVERCLASS();
+};
+
+LINK_ENTITY_TO_CLASS( npc_puppet, CNPC_Puppet );
+
+BEGIN_DATADESC( CNPC_Puppet )
+ DEFINE_KEYFIELD( m_sAnimTargetname, FIELD_STRING, "animationtarget" ),
+ DEFINE_KEYFIELD( m_sAnimAttachmentName, FIELD_STRING, "attachmentname" ),
+
+ DEFINE_FIELD( m_nTargetAttachment, FIELD_INTEGER ),
+ DEFINE_FIELD( m_hAnimationTarget, FIELD_EHANDLE ),
+ DEFINE_INPUTFUNC( FIELD_STRING, "SetAnimationTarget", InputSetAnimationTarget ),
+END_DATADESC()
+
+IMPLEMENT_SERVERCLASS_ST( CNPC_Puppet, DT_NPC_Puppet )
+ SendPropEHandle( SENDINFO( m_hAnimationTarget ) ),
+ SendPropInt( SENDINFO( m_nTargetAttachment) ),
+END_SEND_TABLE()
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_Puppet::Precache( void )
+{
+ BaseClass::Precache();
+ PrecacheModel( STRING( GetModelName() ) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_Puppet::Spawn( void )
+{
+ BaseClass::Spawn();
+
+ Precache();
+
+ SetModel( STRING( GetModelName() ) );
+
+ NPCInit();
+
+ SetHealth( 100 );
+
+ // Find our animation target
+ CBaseEntity *pTarget = gEntList.FindEntityByName( NULL, m_sAnimTargetname );
+ m_hAnimationTarget = pTarget;
+ if ( pTarget )
+ {
+ CBaseAnimating *pAnimating = pTarget->GetBaseAnimating();
+ if ( pAnimating )
+ {
+ m_nTargetAttachment = pAnimating->LookupAttachment( STRING( m_sAnimAttachmentName ) );
+ }
+ }
+
+ // Always be scripted
+ SetInAScript( true );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : &inputdata -
+//-----------------------------------------------------------------------------
+void CNPC_Puppet::InputSetAnimationTarget( inputdata_t &inputdata )
+{
+ // Take the new name
+ m_sAnimTargetname = MAKE_STRING( inputdata.value.String() );
+
+ // Find our animation target
+ CBaseEntity *pTarget = gEntList.FindEntityByName( NULL, m_sAnimTargetname );
+ if ( pTarget == NULL )
+ {
+ Warning("Failed to find animation target %s for npc_puppet (%s)\n", STRING( m_sAnimTargetname ), STRING( GetEntityName() ) );
+ return;
+ }
+
+ m_hAnimationTarget = pTarget;
+
+ CBaseAnimating *pAnimating = pTarget->GetBaseAnimating();
+ if ( pAnimating )
+ {
+ // Cache off our target attachment
+ m_nTargetAttachment = pAnimating->LookupAttachment( STRING( m_sAnimAttachmentName ) );
+ }
+
+ // Stuff us at the owner's core for visibility reasons
+ SetParent( pTarget );
+ SetLocalOrigin( vec3_origin );
+ SetLocalAngles( vec3_angle );
+}
diff --git a/mp/src/game/server/episodic/prop_scalable.cpp b/mp/src/game/server/episodic/prop_scalable.cpp
index 300ffed9..a3115357 100644
--- a/mp/src/game/server/episodic/prop_scalable.cpp
+++ b/mp/src/game/server/episodic/prop_scalable.cpp
@@ -1,150 +1,150 @@
-//========= Copyright Valve Corporation, All rights reserved. ============//
-//
-// Purpose: Big pulsating ball inside the core of the citadel
-//
-//=============================================================================//
-
-#include "cbase.h"
-#include "baseentity.h"
-
-#define COREBALL_MODEL "models/props_combine/coreball.mdl"
-
-class CPropScalable : public CBaseAnimating
-{
-public:
- DECLARE_CLASS( CPropScalable, CBaseAnimating );
- DECLARE_DATADESC();
- DECLARE_SERVERCLASS();
-
- CPropScalable();
-
- virtual void Spawn( void );
- virtual void Precache( void );
-
- CNetworkVar( float, m_flScaleX );
- CNetworkVar( float, m_flScaleY );
- CNetworkVar( float, m_flScaleZ );
-
- CNetworkVar( float, m_flLerpTimeX );
- CNetworkVar( float, m_flLerpTimeY );
- CNetworkVar( float, m_flLerpTimeZ );
-
- CNetworkVar( float, m_flGoalTimeX );
- CNetworkVar( float, m_flGoalTimeY );
- CNetworkVar( float, m_flGoalTimeZ );
-
- void InputSetScaleX( inputdata_t &inputdata );
- void InputSetScaleY( inputdata_t &inputdata );
- void InputSetScaleZ( inputdata_t &inputdata );
-};
-
-LINK_ENTITY_TO_CLASS( prop_coreball, CPropScalable );
-LINK_ENTITY_TO_CLASS( prop_scalable, CPropScalable );
-
-BEGIN_DATADESC( CPropScalable )
- DEFINE_INPUTFUNC( FIELD_VECTOR, "SetScaleX", InputSetScaleX ),
- DEFINE_INPUTFUNC( FIELD_VECTOR, "SetScaleY", InputSetScaleY ),
- DEFINE_INPUTFUNC( FIELD_VECTOR, "SetScaleZ", InputSetScaleZ ),
-
- DEFINE_FIELD( m_flScaleX, FIELD_FLOAT ),
- DEFINE_FIELD( m_flScaleY, FIELD_FLOAT ),
- DEFINE_FIELD( m_flScaleZ, FIELD_FLOAT ),
-
- DEFINE_FIELD( m_flLerpTimeX, FIELD_FLOAT ),
- DEFINE_FIELD( m_flLerpTimeY, FIELD_FLOAT ),
- DEFINE_FIELD( m_flLerpTimeZ, FIELD_FLOAT ),
-
- DEFINE_FIELD( m_flGoalTimeX, FIELD_FLOAT ),
- DEFINE_FIELD( m_flGoalTimeY, FIELD_FLOAT ),
- DEFINE_FIELD( m_flGoalTimeZ, FIELD_FLOAT ),
-END_DATADESC()
-
-IMPLEMENT_SERVERCLASS_ST( CPropScalable, DT_PropScalable )
- SendPropFloat( SENDINFO(m_flScaleX), 0, SPROP_NOSCALE ),
- SendPropFloat( SENDINFO(m_flScaleY), 0, SPROP_NOSCALE ),
- SendPropFloat( SENDINFO(m_flScaleZ), 0, SPROP_NOSCALE ),
-
- SendPropFloat( SENDINFO(m_flLerpTimeX), 0, SPROP_NOSCALE ),
- SendPropFloat( SENDINFO(m_flLerpTimeY), 0, SPROP_NOSCALE ),
- SendPropFloat( SENDINFO(m_flLerpTimeZ), 0, SPROP_NOSCALE ),
-
- SendPropFloat( SENDINFO(m_flGoalTimeX), 0, SPROP_NOSCALE ),
- SendPropFloat( SENDINFO(m_flGoalTimeY), 0, SPROP_NOSCALE ),
- SendPropFloat( SENDINFO(m_flGoalTimeZ), 0, SPROP_NOSCALE ),
-END_SEND_TABLE()
-
-CPropScalable::CPropScalable( void )
-{
- m_flScaleX = 1.0f;
- m_flScaleY = 1.0f;
- m_flScaleZ = 1.0f;
-
- UseClientSideAnimation();
-}
-
-void CPropScalable::Spawn( void )
-{
- // Stomp our model name if we're the coreball (legacy)
- if ( FClassnameIs( this, "prop_coreball" ) )
- {
- PrecacheModel( COREBALL_MODEL );
- SetModel( COREBALL_MODEL );
- }
- else
- {
- char *szModel = (char *)STRING( GetModelName() );
- if (!szModel || !*szModel)
- {
- Warning( "prop_scalable at %.0f %.0f %0.f missing modelname\n", GetAbsOrigin().x, GetAbsOrigin().y, GetAbsOrigin().z );
- UTIL_Remove( this );
- return;
- }
-
- PrecacheModel( szModel );
- SetModel( szModel );
- }
-
- SetMoveType( MOVETYPE_NONE );
-
- BaseClass::Spawn();
-
- AddEffects( EF_NOSHADOW );
-
- SetSequence( 0 );
- SetPlaybackRate( 1.0f );
-}
-
-void CPropScalable::Precache( void )
-{
- BaseClass::Precache();
-}
-
-void CPropScalable::InputSetScaleX( inputdata_t &inputdata )
-{
- Vector vecScale;
- inputdata.value.Vector3D( vecScale );
-
- m_flScaleX = vecScale.x;
- m_flLerpTimeX = vecScale.y;
- m_flGoalTimeX = gpGlobals->curtime;
-}
-
-void CPropScalable::InputSetScaleY( inputdata_t &inputdata )
-{
- Vector vecScale;
- inputdata.value.Vector3D( vecScale );
-
- m_flScaleY = vecScale.x;
- m_flLerpTimeY = vecScale.y;
- m_flGoalTimeY = gpGlobals->curtime;
-}
-
-void CPropScalable::InputSetScaleZ( inputdata_t &inputdata )
-{
- Vector vecScale;
- inputdata.value.Vector3D( vecScale );
-
- m_flScaleZ = vecScale.x;
- m_flLerpTimeZ = vecScale.y;
- m_flGoalTimeZ = gpGlobals->curtime;
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Big pulsating ball inside the core of the citadel
+//
+//=============================================================================//
+
+#include "cbase.h"
+#include "baseentity.h"
+
+#define COREBALL_MODEL "models/props_combine/coreball.mdl"
+
+class CPropScalable : public CBaseAnimating
+{
+public:
+ DECLARE_CLASS( CPropScalable, CBaseAnimating );
+ DECLARE_DATADESC();
+ DECLARE_SERVERCLASS();
+
+ CPropScalable();
+
+ virtual void Spawn( void );
+ virtual void Precache( void );
+
+ CNetworkVar( float, m_flScaleX );
+ CNetworkVar( float, m_flScaleY );
+ CNetworkVar( float, m_flScaleZ );
+
+ CNetworkVar( float, m_flLerpTimeX );
+ CNetworkVar( float, m_flLerpTimeY );
+ CNetworkVar( float, m_flLerpTimeZ );
+
+ CNetworkVar( float, m_flGoalTimeX );
+ CNetworkVar( float, m_flGoalTimeY );
+ CNetworkVar( float, m_flGoalTimeZ );
+
+ void InputSetScaleX( inputdata_t &inputdata );
+ void InputSetScaleY( inputdata_t &inputdata );
+ void InputSetScaleZ( inputdata_t &inputdata );
+};
+
+LINK_ENTITY_TO_CLASS( prop_coreball, CPropScalable );
+LINK_ENTITY_TO_CLASS( prop_scalable, CPropScalable );
+
+BEGIN_DATADESC( CPropScalable )
+ DEFINE_INPUTFUNC( FIELD_VECTOR, "SetScaleX", InputSetScaleX ),
+ DEFINE_INPUTFUNC( FIELD_VECTOR, "SetScaleY", InputSetScaleY ),
+ DEFINE_INPUTFUNC( FIELD_VECTOR, "SetScaleZ", InputSetScaleZ ),
+
+ DEFINE_FIELD( m_flScaleX, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flScaleY, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flScaleZ, FIELD_FLOAT ),
+
+ DEFINE_FIELD( m_flLerpTimeX, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flLerpTimeY, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flLerpTimeZ, FIELD_FLOAT ),
+
+ DEFINE_FIELD( m_flGoalTimeX, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flGoalTimeY, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flGoalTimeZ, FIELD_FLOAT ),
+END_DATADESC()
+
+IMPLEMENT_SERVERCLASS_ST( CPropScalable, DT_PropScalable )
+ SendPropFloat( SENDINFO(m_flScaleX), 0, SPROP_NOSCALE ),
+ SendPropFloat( SENDINFO(m_flScaleY), 0, SPROP_NOSCALE ),
+ SendPropFloat( SENDINFO(m_flScaleZ), 0, SPROP_NOSCALE ),
+
+ SendPropFloat( SENDINFO(m_flLerpTimeX), 0, SPROP_NOSCALE ),
+ SendPropFloat( SENDINFO(m_flLerpTimeY), 0, SPROP_NOSCALE ),
+ SendPropFloat( SENDINFO(m_flLerpTimeZ), 0, SPROP_NOSCALE ),
+
+ SendPropFloat( SENDINFO(m_flGoalTimeX), 0, SPROP_NOSCALE ),
+ SendPropFloat( SENDINFO(m_flGoalTimeY), 0, SPROP_NOSCALE ),
+ SendPropFloat( SENDINFO(m_flGoalTimeZ), 0, SPROP_NOSCALE ),
+END_SEND_TABLE()
+
+CPropScalable::CPropScalable( void )
+{
+ m_flScaleX = 1.0f;
+ m_flScaleY = 1.0f;
+ m_flScaleZ = 1.0f;
+
+ UseClientSideAnimation();
+}
+
+void CPropScalable::Spawn( void )
+{
+ // Stomp our model name if we're the coreball (legacy)
+ if ( FClassnameIs( this, "prop_coreball" ) )
+ {
+ PrecacheModel( COREBALL_MODEL );
+ SetModel( COREBALL_MODEL );
+ }
+ else
+ {
+ char *szModel = (char *)STRING( GetModelName() );
+ if (!szModel || !*szModel)
+ {
+ Warning( "prop_scalable at %.0f %.0f %0.f missing modelname\n", GetAbsOrigin().x, GetAbsOrigin().y, GetAbsOrigin().z );
+ UTIL_Remove( this );
+ return;
+ }
+
+ PrecacheModel( szModel );
+ SetModel( szModel );
+ }
+
+ SetMoveType( MOVETYPE_NONE );
+
+ BaseClass::Spawn();
+
+ AddEffects( EF_NOSHADOW );
+
+ SetSequence( 0 );
+ SetPlaybackRate( 1.0f );
+}
+
+void CPropScalable::Precache( void )
+{
+ BaseClass::Precache();
+}
+
+void CPropScalable::InputSetScaleX( inputdata_t &inputdata )
+{
+ Vector vecScale;
+ inputdata.value.Vector3D( vecScale );
+
+ m_flScaleX = vecScale.x;
+ m_flLerpTimeX = vecScale.y;
+ m_flGoalTimeX = gpGlobals->curtime;
+}
+
+void CPropScalable::InputSetScaleY( inputdata_t &inputdata )
+{
+ Vector vecScale;
+ inputdata.value.Vector3D( vecScale );
+
+ m_flScaleY = vecScale.x;
+ m_flLerpTimeY = vecScale.y;
+ m_flGoalTimeY = gpGlobals->curtime;
+}
+
+void CPropScalable::InputSetScaleZ( inputdata_t &inputdata )
+{
+ Vector vecScale;
+ inputdata.value.Vector3D( vecScale );
+
+ m_flScaleZ = vecScale.x;
+ m_flLerpTimeZ = vecScale.y;
+ m_flGoalTimeZ = gpGlobals->curtime;
} \ No newline at end of file
diff --git a/mp/src/game/server/episodic/vehicle_jeep_episodic.cpp b/mp/src/game/server/episodic/vehicle_jeep_episodic.cpp
index 345284df..368f1b9f 100644
--- a/mp/src/game/server/episodic/vehicle_jeep_episodic.cpp
+++ b/mp/src/game/server/episodic/vehicle_jeep_episodic.cpp
@@ -1,1764 +1,1764 @@
-//========= Copyright Valve Corporation, All rights reserved. ============//
-//
-//
-//
-//=============================================================================
-
-#include "cbase.h"
-#include "vehicle_jeep_episodic.h"
-#include "collisionutils.h"
-#include "npc_alyx_episodic.h"
-#include "particle_parse.h"
-#include "particle_system.h"
-#include "hl2_player.h"
-#include "in_buttons.h"
-#include "vphysics/friction.h"
-#include "vphysicsupdateai.h"
-#include "physics_npc_solver.h"
-#include "Sprite.h"
-#include "weapon_striderbuster.h"
-#include "npc_strider.h"
-#include "vguiscreen.h"
-#include "hl2_vehicle_radar.h"
-#include "props.h"
-#include "ai_dynamiclink.h"
-
-extern ConVar phys_upimpactforcescale;
-
-ConVar jalopy_blocked_exit_max_speed( "jalopy_blocked_exit_max_speed", "50" );
-
-#define JEEP_AMMOCRATE_HITGROUP 5
-#define JEEP_AMMO_CRATE_CLOSE_DELAY 2.0f
-
-// Bodygroups
-#define JEEP_RADAR_BODYGROUP 1
-#define JEEP_HOPPER_BODYGROUP 2
-#define JEEP_CARBAR_BODYGROUP 3
-
-#define RADAR_PANEL_MATERIAL "vgui/screens/radar"
-#define RADAR_PANEL_WRITEZ "engine/writez"
-
-static const char *s_szHazardSprite = "sprites/light_glow01.vmt";
-
-enum
-{
- RADAR_MODE_NORMAL = 0,
- RADAR_MODE_STICKY,
-};
-
-//=========================================================
-//=========================================================
-class CRadarTarget : public CPointEntity
-{
- DECLARE_CLASS( CRadarTarget, CPointEntity );
-
-public:
- void Spawn();
-
- bool IsDisabled() { return m_bDisabled; }
- int GetType() { return m_iType; }
- int GetMode() { return m_iMode; }
- void InputEnable( inputdata_t &inputdata );
- void InputDisable( inputdata_t &inputdata );
- int ObjectCaps();
-
-private:
- bool m_bDisabled;
- int m_iType;
- int m_iMode;
-
-public:
- float m_flRadius;
-
- DECLARE_DATADESC();
-};
-
-LINK_ENTITY_TO_CLASS( info_radar_target, CRadarTarget );
-
-BEGIN_DATADESC( CRadarTarget )
- DEFINE_KEYFIELD( m_bDisabled, FIELD_BOOLEAN, "StartDisabled" ),
- DEFINE_KEYFIELD( m_flRadius, FIELD_FLOAT, "radius" ),
- DEFINE_KEYFIELD( m_iType, FIELD_INTEGER, "type" ),
- DEFINE_KEYFIELD( m_iMode, FIELD_INTEGER, "mode" ),
- DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ),
- DEFINE_INPUTFUNC( FIELD_VOID, "Disable",InputDisable ),
-END_DATADESC();
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CRadarTarget::Spawn()
-{
- BaseClass::Spawn();
-
- AddEffects( EF_NODRAW );
- SetMoveType( MOVETYPE_NONE );
- SetSolid( SOLID_NONE );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CRadarTarget::InputEnable( inputdata_t &inputdata )
-{
- m_bDisabled = false;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CRadarTarget::InputDisable( inputdata_t &inputdata )
-{
- m_bDisabled = true;
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-int CRadarTarget::ObjectCaps()
-{
- return BaseClass::ObjectCaps() | FCAP_ACROSS_TRANSITION;
-}
-
-
-
-
-//
-// Trigger which detects entities placed in the cargo hold of the jalopy
-//
-
-class CVehicleCargoTrigger : public CBaseEntity
-{
- DECLARE_CLASS( CVehicleCargoTrigger, CBaseEntity );
-
-public:
-
- //
- // Creates a trigger with the specified bounds
-
- static CVehicleCargoTrigger *Create( const Vector &vecOrigin, const Vector &vecMins, const Vector &vecMaxs, CBaseEntity *pOwner )
- {
- CVehicleCargoTrigger *pTrigger = (CVehicleCargoTrigger *) CreateEntityByName( "trigger_vehicle_cargo" );
- if ( pTrigger == NULL )
- return NULL;
-
- UTIL_SetOrigin( pTrigger, vecOrigin );
- UTIL_SetSize( pTrigger, vecMins, vecMaxs );
- pTrigger->SetOwnerEntity( pOwner );
- pTrigger->SetParent( pOwner );
-
- pTrigger->Spawn();
-
- return pTrigger;
- }
-
- //
- // Handles the trigger touching its intended quarry
-
- void CargoTouch( CBaseEntity *pOther )
- {
- // Cannot be ignoring touches
- if ( ( m_hIgnoreEntity == pOther ) || ( m_flIgnoreDuration >= gpGlobals->curtime ) )
- return;
-
- // Make sure this object is being held by the player
- if ( pOther->VPhysicsGetObject() == NULL || (pOther->VPhysicsGetObject()->GetGameFlags() & FVPHYSICS_PLAYER_HELD) == false )
- return;
-
- if ( StriderBuster_NumFlechettesAttached( pOther ) > 0 )
- return;
-
- AddCargo( pOther );
- }
-
- bool AddCargo( CBaseEntity *pOther )
- {
- // For now, only bother with strider busters
- if ( (FClassnameIs( pOther, "weapon_striderbuster" ) == false) &&
- (FClassnameIs( pOther, "npc_grenade_magna" ) == false)
- )
- return false;
-
- // Must be a physics prop
- CPhysicsProp *pProp = dynamic_cast<CPhysicsProp *>(pOther);
- if ( pOther == NULL )
- return false;
-
- CPropJeepEpisodic *pJeep = dynamic_cast< CPropJeepEpisodic * >( GetOwnerEntity() );
- if ( pJeep == NULL )
- return false;
-
- // Make the player release the item
- Pickup_ForcePlayerToDropThisObject( pOther );
-
- // Stop colliding with things
- pOther->VPhysicsDestroyObject();
- pOther->SetSolidFlags( FSOLID_NOT_SOLID );
- pOther->SetMoveType( MOVETYPE_NONE );
-
- // Parent the object to our owner
- pOther->SetParent( GetOwnerEntity() );
-
- // The car now owns the entity
- pJeep->AddPropToCargoHold( pProp );
-
- // Notify the buster that it's been added to the cargo hold.
- StriderBuster_OnAddToCargoHold( pProp );
-
- // Stop touching this item
- Disable();
-
- return true;
- }
-
- //
- // Setup the entity
-
- void Spawn( void )
- {
- BaseClass::Spawn();
-
- SetSolid( SOLID_BBOX );
- SetSolidFlags( FSOLID_TRIGGER | FSOLID_NOT_SOLID );
-
- SetTouch( &CVehicleCargoTrigger::CargoTouch );
- }
-
- void Activate()
- {
- BaseClass::Activate();
- SetSolidFlags( FSOLID_TRIGGER | FSOLID_NOT_SOLID ); // Fixes up old savegames
- }
-
- //
- // When we've stopped touching this entity, we ignore it
-
- void EndTouch( CBaseEntity *pOther )
- {
- if ( pOther == m_hIgnoreEntity )
- {
- m_hIgnoreEntity = NULL;
- }
-
- BaseClass::EndTouch( pOther );
- }
-
- //
- // Disables the trigger for a set duration
-
- void IgnoreTouches( CBaseEntity *pIgnoreEntity )
- {
- m_hIgnoreEntity = pIgnoreEntity;
- m_flIgnoreDuration = gpGlobals->curtime + 0.5f;
- }
-
- void Disable( void )
- {
- SetTouch( NULL );
- }
-
- void Enable( void )
- {
- SetTouch( &CVehicleCargoTrigger::CargoTouch );
- }
-
-protected:
-
- float m_flIgnoreDuration;
- CHandle <CBaseEntity> m_hIgnoreEntity;
-
- DECLARE_DATADESC();
-};
-
-LINK_ENTITY_TO_CLASS( trigger_vehicle_cargo, CVehicleCargoTrigger );
-
-BEGIN_DATADESC( CVehicleCargoTrigger )
- DEFINE_FIELD( m_flIgnoreDuration, FIELD_TIME ),
- DEFINE_FIELD( m_hIgnoreEntity, FIELD_EHANDLE ),
- DEFINE_ENTITYFUNC( CargoTouch ),
-END_DATADESC();
-
-//
-// Transition reference point for the vehicle
-//
-
-class CInfoTargetVehicleTransition : public CPointEntity
-{
-public:
- DECLARE_CLASS( CInfoTargetVehicleTransition, CPointEntity );
-
- void Enable( void ) { m_bDisabled = false; }
- void Disable( void ) { m_bDisabled = true; }
-
- bool IsDisabled( void ) const { return m_bDisabled; }
-
-private:
-
- void InputEnable( inputdata_t &data ) { Enable(); }
- void InputDisable( inputdata_t &data ) { Disable(); }
-
- bool m_bDisabled;
-
- DECLARE_DATADESC();
-};
-
-BEGIN_DATADESC( CInfoTargetVehicleTransition )
- DEFINE_KEYFIELD( m_bDisabled, FIELD_BOOLEAN, "StartDisabled" ),
-
- DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ),
- DEFINE_INPUTFUNC( FIELD_VOID, "Disable",InputDisable ),
-END_DATADESC();
-
-LINK_ENTITY_TO_CLASS( info_target_vehicle_transition, CInfoTargetVehicleTransition );
-
-//
-// CPropJeepEpisodic
-//
-
-LINK_ENTITY_TO_CLASS( prop_vehicle_jeep, CPropJeepEpisodic );
-
-BEGIN_DATADESC( CPropJeepEpisodic )
-
- DEFINE_FIELD( m_bEntranceLocked, FIELD_BOOLEAN ),
- DEFINE_FIELD( m_bExitLocked, FIELD_BOOLEAN ),
- DEFINE_FIELD( m_hCargoProp, FIELD_EHANDLE ),
- DEFINE_FIELD( m_hCargoTrigger, FIELD_EHANDLE ),
- DEFINE_FIELD( m_bAddingCargo, FIELD_BOOLEAN ),
- DEFINE_ARRAY( m_hWheelDust, FIELD_EHANDLE, NUM_WHEEL_EFFECTS ),
- DEFINE_ARRAY( m_hWheelWater, FIELD_EHANDLE, NUM_WHEEL_EFFECTS ),
- DEFINE_ARRAY( m_hHazardLights, FIELD_EHANDLE, NUM_HAZARD_LIGHTS ),
- DEFINE_FIELD( m_flCargoStartTime, FIELD_TIME ),
- DEFINE_FIELD( m_bBlink, FIELD_BOOLEAN ),
- DEFINE_FIELD( m_bRadarEnabled, FIELD_BOOLEAN ),
- DEFINE_FIELD( m_bRadarDetectsEnemies, FIELD_BOOLEAN ),
- DEFINE_FIELD( m_hRadarScreen, FIELD_EHANDLE ),
- DEFINE_FIELD( m_hLinkControllerFront, FIELD_EHANDLE ),
- DEFINE_FIELD( m_hLinkControllerRear, FIELD_EHANDLE ),
- DEFINE_KEYFIELD( m_bBusterHopperVisible, FIELD_BOOLEAN, "CargoVisible" ),
- // m_flNextAvoidBroadcastTime
- DEFINE_FIELD( m_flNextWaterSound, FIELD_TIME ),
- DEFINE_FIELD( m_flNextRadarUpdateTime, FIELD_TIME ),
- DEFINE_FIELD( m_iNumRadarContacts, FIELD_INTEGER ),
- DEFINE_ARRAY( m_vecRadarContactPos, FIELD_POSITION_VECTOR, RADAR_MAX_CONTACTS ),
- DEFINE_ARRAY( m_iRadarContactType, FIELD_INTEGER, RADAR_MAX_CONTACTS ),
-
- DEFINE_THINKFUNC( HazardBlinkThink ),
-
- DEFINE_OUTPUT( m_OnCompanionEnteredVehicle, "OnCompanionEnteredVehicle" ),
- DEFINE_OUTPUT( m_OnCompanionExitedVehicle, "OnCompanionExitedVehicle" ),
- DEFINE_OUTPUT( m_OnHostileEnteredVehicle, "OnHostileEnteredVehicle" ),
- DEFINE_OUTPUT( m_OnHostileExitedVehicle, "OnHostileExitedVehicle" ),
-
- DEFINE_INPUTFUNC( FIELD_VOID, "LockEntrance", InputLockEntrance ),
- DEFINE_INPUTFUNC( FIELD_VOID, "UnlockEntrance", InputUnlockEntrance ),
- DEFINE_INPUTFUNC( FIELD_VOID, "LockExit", InputLockExit ),
- DEFINE_INPUTFUNC( FIELD_VOID, "UnlockExit", InputUnlockExit ),
- DEFINE_INPUTFUNC( FIELD_VOID, "EnableRadar", InputEnableRadar ),
- DEFINE_INPUTFUNC( FIELD_VOID, "DisableRadar", InputDisableRadar ),
- DEFINE_INPUTFUNC( FIELD_VOID, "EnableRadarDetectEnemies", InputEnableRadarDetectEnemies ),
- DEFINE_INPUTFUNC( FIELD_VOID, "AddBusterToCargo", InputAddBusterToCargo ),
- DEFINE_INPUTFUNC( FIELD_VOID, "OutsideTransition", InputOutsideTransition ),
- DEFINE_INPUTFUNC( FIELD_VOID, "DisablePhysGun", InputDisablePhysGun ),
- DEFINE_INPUTFUNC( FIELD_VOID, "EnablePhysGun", InputEnablePhysGun ),
- DEFINE_INPUTFUNC( FIELD_VOID, "CreateLinkController", InputCreateLinkController ),
- DEFINE_INPUTFUNC( FIELD_VOID, "DestroyLinkController", InputDestroyLinkController ),
-
- DEFINE_INPUTFUNC( FIELD_BOOLEAN, "SetCargoHopperVisibility", InputSetCargoVisibility ),
-
-END_DATADESC();
-
-IMPLEMENT_SERVERCLASS_ST(CPropJeepEpisodic, DT_CPropJeepEpisodic)
- //CNetworkVar( int, m_iNumRadarContacts );
- SendPropInt( SENDINFO(m_iNumRadarContacts), 8 ),
-
- //CNetworkArray( Vector, m_vecRadarContactPos, RADAR_MAX_CONTACTS );
- SendPropArray( SendPropVector( SENDINFO_ARRAY(m_vecRadarContactPos), -1, SPROP_COORD), m_vecRadarContactPos ),
-
- //CNetworkArray( int, m_iRadarContactType, RADAR_MAX_CONTACTS );
- SendPropArray( SendPropInt(SENDINFO_ARRAY(m_iRadarContactType), RADAR_CONTACT_TYPE_BITS ), m_iRadarContactType ),
-END_SEND_TABLE()
-
-
-//=============================================================================
-// Episodic jeep
-
-CPropJeepEpisodic::CPropJeepEpisodic( void ) :
-m_bEntranceLocked( false ),
-m_bExitLocked( false ),
-m_bAddingCargo( false ),
-m_flNextAvoidBroadcastTime( 0.0f )
-{
- m_bHasGun = false;
- m_bUnableToFire = true;
- m_bRadarDetectsEnemies = false;
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CPropJeepEpisodic::UpdateOnRemove( void )
-{
- BaseClass::UpdateOnRemove();
-
- // Kill our wheel dust
- for ( int i = 0; i < NUM_WHEEL_EFFECTS; i++ )
- {
- if ( m_hWheelDust[i] != NULL )
- {
- UTIL_Remove( m_hWheelDust[i] );
- }
-
- if ( m_hWheelWater[i] != NULL )
- {
- UTIL_Remove( m_hWheelWater[i] );
- }
- }
-
- DestroyHazardLights();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CPropJeepEpisodic::Precache( void )
-{
- PrecacheMaterial( RADAR_PANEL_MATERIAL );
- PrecacheMaterial( RADAR_PANEL_WRITEZ );
- PrecacheModel( s_szHazardSprite );
- PrecacheScriptSound( "JNK_Radar_Ping_Friendly" );
- PrecacheScriptSound( "Physics.WaterSplash" );
-
- PrecacheParticleSystem( "WheelDust" );
- PrecacheParticleSystem( "WheelSplash" );
-
- BaseClass::Precache();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : *pPlayer -
-//-----------------------------------------------------------------------------
-void CPropJeepEpisodic::EnterVehicle( CBaseCombatCharacter *pPassenger )
-{
- BaseClass::EnterVehicle( pPassenger );
-
- // Turn our hazards off!
- DestroyHazardLights();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CPropJeepEpisodic::Spawn( void )
-{
- BaseClass::Spawn();
-
- SetBlocksLOS( false );
-
- CBasePlayer *pPlayer = UTIL_GetLocalPlayer();
- if ( pPlayer != NULL )
- {
- pPlayer->m_Local.m_iHideHUD |= HIDEHUD_VEHICLE_CROSSHAIR;
- }
-
-
- SetBodygroup( JEEP_HOPPER_BODYGROUP, m_bBusterHopperVisible ? 1 : 0);
- CreateCargoTrigger();
-
- // carbar bodygroup is always on
- SetBodygroup( JEEP_CARBAR_BODYGROUP, 1 );
-
- m_bRadarDetectsEnemies = false;
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CPropJeepEpisodic::Activate()
-{
- m_iNumRadarContacts = 0; // Force first contact tone
- BaseClass::Activate();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CPropJeepEpisodic::NPC_FinishedEnterVehicle( CAI_BaseNPC *pPassenger, bool bCompanion )
-{
- // FIXME: This will be moved to the NPCs entering and exiting
- // Fire our outputs
- if ( bCompanion )
- {
- m_OnCompanionEnteredVehicle.FireOutput( this, pPassenger );
- }
- else
- {
- m_OnHostileEnteredVehicle.FireOutput( this, pPassenger );
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CPropJeepEpisodic::NPC_FinishedExitVehicle( CAI_BaseNPC *pPassenger, bool bCompanion )
-{
- // FIXME: This will be moved to the NPCs entering and exiting
- // Fire our outputs
- if ( bCompanion )
- {
- m_OnCompanionExitedVehicle.FireOutput( this, pPassenger );
- }
- else
- {
- m_OnHostileExitedVehicle.FireOutput( this, pPassenger );
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : *pPassenger -
-// bCompanion -
-// Output : Returns true on success, false on failure.
-//-----------------------------------------------------------------------------
-bool CPropJeepEpisodic::NPC_CanEnterVehicle( CAI_BaseNPC *pPassenger, bool bCompanion )
-{
- // Must be unlocked
- if ( bCompanion && m_bEntranceLocked )
- return false;
-
- return BaseClass::NPC_CanEnterVehicle( pPassenger, bCompanion );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : *pPassenger -
-// bCompanion -
-// Output : Returns true on success, false on failure.
-//-----------------------------------------------------------------------------
-bool CPropJeepEpisodic::NPC_CanExitVehicle( CAI_BaseNPC *pPassenger, bool bCompanion )
-{
- // Must be unlocked
- if ( bCompanion && m_bExitLocked )
- return false;
-
- return BaseClass::NPC_CanExitVehicle( pPassenger, bCompanion );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CPropJeepEpisodic::InputLockEntrance( inputdata_t &data )
-{
- m_bEntranceLocked = true;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CPropJeepEpisodic::InputUnlockEntrance( inputdata_t &data )
-{
- m_bEntranceLocked = false;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CPropJeepEpisodic::InputLockExit( inputdata_t &data )
-{
- m_bExitLocked = true;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CPropJeepEpisodic::InputUnlockExit( inputdata_t &data )
-{
- m_bExitLocked = false;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Turn on the Jalopy radar device
-//-----------------------------------------------------------------------------
-void CPropJeepEpisodic::InputEnableRadar( inputdata_t &data )
-{
- if( m_bRadarEnabled )
- return; // Already enabled
-
- SetBodygroup( JEEP_RADAR_BODYGROUP, 1 );
-
- SpawnRadarPanel();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Turn off the Jalopy radar device
-//-----------------------------------------------------------------------------
-void CPropJeepEpisodic::InputDisableRadar( inputdata_t &data )
-{
- if( !m_bRadarEnabled )
- return; // Already disabled
-
- SetBodygroup( JEEP_RADAR_BODYGROUP, 0 );
-
- DestroyRadarPanel();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Allow the Jalopy radar to detect Hunters and Striders
-//-----------------------------------------------------------------------------
-void CPropJeepEpisodic::InputEnableRadarDetectEnemies( inputdata_t &data )
-{
- m_bRadarDetectsEnemies = true;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CPropJeepEpisodic::InputAddBusterToCargo( inputdata_t &data )
-{
- if ( m_hCargoProp != NULL)
- {
- ReleasePropFromCargoHold();
- m_hCargoProp = NULL;
- }
-
- CBaseEntity *pNewBomb = CreateEntityByName( "weapon_striderbuster" );
- if ( pNewBomb )
- {
- DispatchSpawn( pNewBomb );
- pNewBomb->Teleport( &m_hCargoTrigger->GetAbsOrigin(), NULL, NULL );
- m_hCargoTrigger->AddCargo( pNewBomb );
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Output : Returns true on success, false on failure.
-//-----------------------------------------------------------------------------
-bool CPropJeepEpisodic::PassengerInTransition( void )
-{
- // FIXME: Big hack - we need a way to bridge this data better
- // TODO: Get a list of passengers we can traverse instead
- CNPC_Alyx *pAlyx = CNPC_Alyx::GetAlyx();
- if ( pAlyx )
- {
- if ( pAlyx->GetPassengerState() == PASSENGER_STATE_ENTERING ||
- pAlyx->GetPassengerState() == PASSENGER_STATE_EXITING )
- return true;
- }
-
- return false;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Override velocity if our passenger is transitioning or we're upside-down
-//-----------------------------------------------------------------------------
-Vector CPropJeepEpisodic::PhysGunLaunchVelocity( const Vector &forward, float flMass )
-{
- // Disallow
- if ( PassengerInTransition() )
- return vec3_origin;
-
- Vector vecPuntDir = BaseClass::PhysGunLaunchVelocity( forward, flMass );
- vecPuntDir.z = 150.0f;
- vecPuntDir *= 600.0f;
- return vecPuntDir;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Rolls the vehicle when its trying to upright itself from a punt
-//-----------------------------------------------------------------------------
-AngularImpulse CPropJeepEpisodic::PhysGunLaunchAngularImpulse( void )
-{
- if ( IsOverturned() )
- return AngularImpulse( 0, 300, 0 );
-
- // Don't spin randomly, always spin reliably
- return AngularImpulse( 0, 0, 0 );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Get the upright strength based on what state we're in
-//-----------------------------------------------------------------------------
-float CPropJeepEpisodic::GetUprightStrength( void )
-{
- // Lesser if overturned
- if ( IsOverturned() )
- return 2.0f;
-
- return 0.0f;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CPropJeepEpisodic::CreateCargoTrigger( void )
-{
- if ( m_hCargoTrigger != NULL )
- return;
-
- int nAttachment = LookupAttachment( "cargo" );
- if ( nAttachment )
- {
- Vector vecAttachOrigin;
- Vector vecForward, vecRight, vecUp;
- GetAttachment( nAttachment, vecAttachOrigin, &vecForward, &vecRight, &vecUp );
-
- // Approx size of the hold
- Vector vecMins( -8.0, -6.0, 0 );
- Vector vecMaxs( 8.0, 6.0, 4.0 );
-
- // NDebugOverlay::BoxDirection( vecAttachOrigin, vecMins, vecMaxs, vecForward, 255, 0, 0, 64, 4.0f );
-
- // Create a trigger that lives for a small amount of time
- m_hCargoTrigger = CVehicleCargoTrigger::Create( vecAttachOrigin, vecMins, vecMaxs, this );
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: If the player uses the jeep while at the back, he gets ammo from the crate instead
-//-----------------------------------------------------------------------------
-void CPropJeepEpisodic::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
-{
- // Fall back and get in the vehicle instead, skip giving ammo
- BaseClass::BaseClass::Use( pActivator, pCaller, useType, value );
-}
-
-#define MIN_WHEEL_DUST_SPEED 5
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CPropJeepEpisodic::UpdateWheelDust( void )
-{
- // See if this wheel should emit dust
- const vehicleparams_t *vehicleData = m_pServerVehicle->GetVehicleParams();
- const vehicle_operatingparams_t *carState = m_pServerVehicle->GetVehicleOperatingParams();
- bool bAllowDust = vehicleData->steering.dustCloud;
-
- // Car must be active
- bool bCarOn = m_VehiclePhysics.IsOn();
-
- // Must be moving quickly enough or skidding along the ground
- bool bCreateDust = ( bCarOn &&
- bAllowDust &&
- ( m_VehiclePhysics.GetSpeed() >= MIN_WHEEL_DUST_SPEED || carState->skidSpeed > DEFAULT_SKID_THRESHOLD ) );
-
- // Update our wheel dust
- Vector vecPos;
- for ( int i = 0; i < NUM_WHEEL_EFFECTS; i++ )
- {
- m_pServerVehicle->GetWheelContactPoint( i, vecPos );
-
- // Make sure the effect is created
- if ( m_hWheelDust[i] == NULL )
- {
- // Create the dust effect in place
- m_hWheelDust[i] = (CParticleSystem *) CreateEntityByName( "info_particle_system" );
- if ( m_hWheelDust[i] == NULL )
- continue;
-
- // Setup our basic parameters
- m_hWheelDust[i]->KeyValue( "start_active", "0" );
- m_hWheelDust[i]->KeyValue( "effect_name", "WheelDust" );
- m_hWheelDust[i]->SetParent( this );
- m_hWheelDust[i]->SetLocalOrigin( vec3_origin );
- DispatchSpawn( m_hWheelDust[i] );
- if ( gpGlobals->curtime > 0.5f )
- m_hWheelDust[i]->Activate();
- }
-
- // Make sure the effect is created
- if ( m_hWheelWater[i] == NULL )
- {
- // Create the dust effect in place
- m_hWheelWater[i] = (CParticleSystem *) CreateEntityByName( "info_particle_system" );
- if ( m_hWheelWater[i] == NULL )
- continue;
-
- // Setup our basic parameters
- m_hWheelWater[i]->KeyValue( "start_active", "0" );
- m_hWheelWater[i]->KeyValue( "effect_name", "WheelSplash" );
- m_hWheelWater[i]->SetParent( this );
- m_hWheelWater[i]->SetLocalOrigin( vec3_origin );
- DispatchSpawn( m_hWheelWater[i] );
- if ( gpGlobals->curtime > 0.5f )
- m_hWheelWater[i]->Activate();
- }
-
- // Turn the dust on or off
- if ( bCreateDust )
- {
- // Angle the dust out away from the wheels
- Vector vecForward, vecRight, vecUp;
- GetVectors( &vecForward, &vecRight, &vecUp );
-
- const vehicle_controlparams_t *vehicleControls = m_pServerVehicle->GetVehicleControlParams();
- float flWheelDir = ( i & 1 ) ? 1.0f : -1.0f;
- QAngle vecAngles;
- vecForward += vecRight * flWheelDir;
- vecForward += vecRight * (vehicleControls->steering*0.5f) * flWheelDir;
- vecForward += vecUp;
- VectorAngles( vecForward, vecAngles );
-
- // NDebugOverlay::Axis( vecPos, vecAngles, 8.0f, true, 0.1f );
-
- if ( m_WaterData.m_bWheelInWater[i] )
- {
- m_hWheelDust[i]->StopParticleSystem();
-
- // Set us up in the right position
- m_hWheelWater[i]->StartParticleSystem();
- m_hWheelWater[i]->SetAbsAngles( vecAngles );
- m_hWheelWater[i]->SetAbsOrigin( vecPos + Vector( 0, 0, 8 ) );
-
- if ( m_flNextWaterSound < gpGlobals->curtime )
- {
- m_flNextWaterSound = gpGlobals->curtime + random->RandomFloat( 0.25f, 1.0f );
- EmitSound( "Physics.WaterSplash" );
- }
- }
- else
- {
- m_hWheelWater[i]->StopParticleSystem();
-
- // Set us up in the right position
- m_hWheelDust[i]->StartParticleSystem();
- m_hWheelDust[i]->SetAbsAngles( vecAngles );
- m_hWheelDust[i]->SetAbsOrigin( vecPos + Vector( 0, 0, 8 ) );
- }
- }
- else
- {
- // Stop emitting
- m_hWheelDust[i]->StopParticleSystem();
- m_hWheelWater[i]->StopParticleSystem();
- }
- }
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-ConVar jalopy_radar_test_ent( "jalopy_radar_test_ent", "none" );
-
-//-----------------------------------------------------------------------------
-// Purpose: Search for things that the radar detects, and stick them in the
-// UTILVector that gets sent to the client for radar display.
-//-----------------------------------------------------------------------------
-void CPropJeepEpisodic::UpdateRadar( bool forceUpdate )
-{
- bool bDetectedDog = false;
-
- if( !m_bRadarEnabled )
- return;
-
- if( !forceUpdate && gpGlobals->curtime < m_flNextRadarUpdateTime )
- return;
-
- // Count the targets on radar. If any more targets come on the radar, we beep.
- int m_iNumOldRadarContacts = m_iNumRadarContacts;
-
- m_flNextRadarUpdateTime = gpGlobals->curtime + RADAR_UPDATE_FREQUENCY;
- m_iNumRadarContacts = 0;
-
- CBaseEntity *pEnt = gEntList.FirstEnt();
- string_t iszRadarTarget = FindPooledString( "info_radar_target" );
- string_t iszStriderName = FindPooledString( "npc_strider" );
- string_t iszHunterName = FindPooledString( "npc_hunter" );
-
- string_t iszTestName = FindPooledString( jalopy_radar_test_ent.GetString() );
-
- Vector vecJalopyOrigin = WorldSpaceCenter();
-
- while( pEnt != NULL )
- {
- int type = RADAR_CONTACT_NONE;
-
- if( pEnt->m_iClassname == iszRadarTarget )
- {
- CRadarTarget *pTarget = dynamic_cast<CRadarTarget*>(pEnt);
-
- if( pTarget != NULL && !pTarget->IsDisabled() )
- {
- if( pTarget->m_flRadius < 0 || vecJalopyOrigin.DistToSqr(pTarget->GetAbsOrigin()) <= Square(pTarget->m_flRadius) )
- {
- // This item has been detected.
- type = pTarget->GetType();
-
- if( type == RADAR_CONTACT_DOG )
- bDetectedDog = true;// used to prevent Alyx talking about the radar (see below)
-
- if( pTarget->GetMode() == RADAR_MODE_STICKY )
- {
- // This beacon was just detected. Now change the radius to infinite
- // so that it will never go off the radar due to distance.
- pTarget->m_flRadius = -1;
- }
- }
- }
- }
- else if ( m_bRadarDetectsEnemies )
- {
- if ( pEnt->m_iClassname == iszStriderName )
- {
- CNPC_Strider *pStrider = dynamic_cast<CNPC_Strider*>(pEnt);
-
- if( !pStrider || !pStrider->CarriedByDropship() )
- {
- // Ignore striders which are carried by dropships.
- type = RADAR_CONTACT_LARGE_ENEMY;
- }
- }
-
- if ( pEnt->m_iClassname == iszHunterName )
- {
- type = RADAR_CONTACT_ENEMY;
- }
- }
-
- if( type != RADAR_CONTACT_NONE )
- {
- Vector vecPos = pEnt->WorldSpaceCenter();
-
- m_vecRadarContactPos.Set( m_iNumRadarContacts, vecPos );
- m_iRadarContactType.Set( m_iNumRadarContacts, type );
- m_iNumRadarContacts++;
-
- if( m_iNumRadarContacts == RADAR_MAX_CONTACTS )
- break;
- }
-
- pEnt = gEntList.NextEnt(pEnt);
- }
-
- if( m_iNumRadarContacts > m_iNumOldRadarContacts )
- {
- // Play a bleepy sound
- if( !bDetectedDog )
- {
- EmitSound( "JNK_Radar_Ping_Friendly" );
- }
-
- //Notify Alyx so she can talk about the radar contact
- CNPC_Alyx *pAlyx = CNPC_Alyx::GetAlyx();
-
- if( !bDetectedDog && pAlyx != NULL && pAlyx->GetVehicle() )
- {
- pAlyx->SpeakIfAllowed( TLK_PASSENGER_NEW_RADAR_CONTACT );
- }
- }
-
- if( bDetectedDog )
- {
- // Update the radar much more frequently when dog is around.
- m_flNextRadarUpdateTime = gpGlobals->curtime + RADAR_UPDATE_FREQUENCY_FAST;
- }
-
- //Msg("Server detected %d objects\n", m_iNumRadarContacts );
-
- CBasePlayer *pPlayer = AI_GetSinglePlayer();
- CSingleUserRecipientFilter filter(pPlayer);
- UserMessageBegin( filter, "UpdateJalopyRadar" );
- WRITE_BYTE( 0 ); // end marker
- MessageEnd(); // send message
-}
-
-ConVar jalopy_cargo_anim_time( "jalopy_cargo_anim_time", "1.0" );
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CPropJeepEpisodic::UpdateCargoEntry( void )
-{
- // Don't bother if we have no prop to move
- if ( m_hCargoProp == NULL )
- return;
-
- // If we're past our animation point, then we're already done
- if ( m_flCargoStartTime + jalopy_cargo_anim_time.GetFloat() < gpGlobals->curtime )
- {
- // Close the hold immediately if we're finished
- if ( m_bAddingCargo )
- {
- m_flAmmoCrateCloseTime = gpGlobals->curtime;
- m_bAddingCargo = false;
- }
-
- return;
- }
-
- // Get our target point
- int nAttachment = LookupAttachment( "cargo" );
- Vector vecTarget, vecOut;
- QAngle vecAngles;
- GetAttachmentLocal( nAttachment, vecTarget, vecAngles );
-
- // Find where we are in the blend and bias it for a fast entry and slow ease-out
- float flPerc = (jalopy_cargo_anim_time.GetFloat()) ? (( gpGlobals->curtime - m_flCargoStartTime ) / jalopy_cargo_anim_time.GetFloat()) : 1.0f;
- flPerc = Bias( flPerc, 0.75f );
- VectorLerp( m_hCargoProp->GetLocalOrigin(), vecTarget, flPerc, vecOut );
-
- // Get our target orientation
- CPhysicsProp *pProp = dynamic_cast<CPhysicsProp *>(m_hCargoProp.Get());
- if ( pProp == NULL )
- return;
-
- // Slerp our quaternions to find where we are this frame
- Quaternion qtTarget;
- QAngle qa( 0, 90, 0 );
- qa += pProp->PreferredCarryAngles();
- AngleQuaternion( qa, qtTarget ); // FIXME: Find the real offset to make this sit properly
- Quaternion qtCurrent;
- AngleQuaternion( pProp->GetLocalAngles(), qtCurrent );
-
- Quaternion qtOut;
- QuaternionSlerp( qtCurrent, qtTarget, flPerc, qtOut );
-
- // Put it back to angles
- QuaternionAngles( qtOut, vecAngles );
-
- // Finally, take these new position
- m_hCargoProp->SetLocalOrigin( vecOut );
- m_hCargoProp->SetLocalAngles( vecAngles );
-
- // Push the closing out into the future to make sure we don't try and close at the same time
- m_flAmmoCrateCloseTime += gpGlobals->frametime;
-}
-
-#define VEHICLE_AVOID_BROADCAST_RATE 0.5f
-
-//-----------------------------------------------------------------------------
-// Purpose: This function isn't really what we want
-//-----------------------------------------------------------------------------
-void CPropJeepEpisodic::CreateAvoidanceZone( void )
-{
- if ( m_flNextAvoidBroadcastTime > gpGlobals->curtime )
- return;
-
- // Only do this when we're stopped
- if ( m_VehiclePhysics.GetSpeed() > 5.0f )
- return;
-
- float flHullRadius = CollisionProp()->BoundingRadius2D();
-
- Vector vecPos;
- CollisionProp()->NormalizedToWorldSpace( Vector( 0.5f, 0.33f, 0.25f ), &vecPos );
- CSoundEnt::InsertSound( SOUND_MOVE_AWAY, vecPos, (flHullRadius*0.4f), VEHICLE_AVOID_BROADCAST_RATE, this );
- // NDebugOverlay::Sphere( vecPos, vec3_angle, flHullRadius*0.4f, 255, 0, 0, 0, true, VEHICLE_AVOID_BROADCAST_RATE );
-
- CollisionProp()->NormalizedToWorldSpace( Vector( 0.5f, 0.66f, 0.25f ), &vecPos );
- CSoundEnt::InsertSound( SOUND_MOVE_AWAY, vecPos, (flHullRadius*0.4f), VEHICLE_AVOID_BROADCAST_RATE, this );
- // NDebugOverlay::Sphere( vecPos, vec3_angle, flHullRadius*0.4f, 255, 0, 0, 0, true, VEHICLE_AVOID_BROADCAST_RATE );
-
- // Don't broadcast again until these are done
- m_flNextAvoidBroadcastTime = gpGlobals->curtime + VEHICLE_AVOID_BROADCAST_RATE;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CPropJeepEpisodic::Think( void )
-{
- BaseClass::Think();
-
- // If our passenger is transitioning, then don't let the player drive off
- CNPC_Alyx *pAlyx = CNPC_Alyx::GetAlyx();
- if ( pAlyx && pAlyx->GetPassengerState() == PASSENGER_STATE_EXITING )
- {
- m_throttleDisableTime = gpGlobals->curtime + 0.25f;
- }
-
- // Update our cargo entering our hold
- UpdateCargoEntry();
-
- // See if the wheel dust should be on or off
- UpdateWheelDust();
-
- // Update the radar, of course.
- UpdateRadar();
-
- if ( m_hCargoTrigger && !m_hCargoProp && !m_hCargoTrigger->m_pfnTouch )
- {
- m_hCargoTrigger->Enable();
- }
-
- CreateAvoidanceZone();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : *pEntity -
-//-----------------------------------------------------------------------------
-void CPropJeepEpisodic::AddPropToCargoHold( CPhysicsProp *pProp )
-{
- // The hold must be empty to add something to it
- if ( m_hCargoProp != NULL )
- {
- Assert( 0 );
- return;
- }
-
- // Take the prop as our cargo
- m_hCargoProp = pProp;
- m_flCargoStartTime = gpGlobals->curtime;
- m_bAddingCargo = true;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Drops the cargo from the hold
-//-----------------------------------------------------------------------------
-void CPropJeepEpisodic::ReleasePropFromCargoHold( void )
-{
- // Pull the object free!
- m_hCargoProp->SetParent( NULL );
- m_hCargoProp->CreateVPhysics();
-
- if ( m_hCargoTrigger )
- {
- m_hCargoTrigger->Enable();
- m_hCargoTrigger->IgnoreTouches( m_hCargoProp );
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: If the player is trying to pull the cargo out of the hold using the physcannon, let him
-// Output : Returns the cargo to pick up, if all the conditions are met
-//-----------------------------------------------------------------------------
-CBaseEntity *CPropJeepEpisodic::OnFailedPhysGunPickup( Vector vPhysgunPos )
-{
- // Make sure we're available to open
- if ( m_hCargoProp != NULL )
- {
- // Player's forward direction
- Vector vecPlayerForward;
- CBasePlayer *pPlayer = AI_GetSinglePlayer();
- if ( pPlayer == NULL )
- return NULL;
-
- pPlayer->EyeVectors( &vecPlayerForward );
-
- // Origin and facing of the cargo hold
- Vector vecCargoOrigin;
- Vector vecCargoForward;
- GetAttachment( "cargo", vecCargoOrigin, &vecCargoForward );
-
- // Direction from the cargo to the player's position
- Vector vecPickupDir = ( vecCargoOrigin - vPhysgunPos );
- float flDist = VectorNormalize( vecPickupDir );
-
- // We need to make sure the player's position is within a cone near the opening and that they're also facing the right way
- bool bInCargoRange = ( (flDist < (15.0f * 12.0f)) && DotProduct( vecCargoForward, vecPickupDir ) < 0.1f );
- bool bFacingCargo = DotProduct( vecPlayerForward, vecPickupDir ) > 0.975f;
-
- // If we're roughly pulling at the item, pick that up
- if ( bInCargoRange && bFacingCargo )
- {
- // Save this for later
- CBaseEntity *pCargo = m_hCargoProp;
-
- // Drop the cargo
- ReleasePropFromCargoHold();
-
- // Forget the item but pass it back as the object to pick up
- m_hCargoProp = NULL;
- return pCargo;
- }
- }
-
- return BaseClass::OnFailedPhysGunPickup( vPhysgunPos );
-}
-
-// adds a collision solver for any small props that are stuck under the vehicle
-static void SolveBlockingProps( CPropJeepEpisodic *pVehicleEntity, IPhysicsObject *pVehiclePhysics )
-{
- CUtlVector<CBaseEntity *> solveList;
- float vehicleMass = pVehiclePhysics->GetMass();
- Vector vehicleUp;
- pVehicleEntity->GetVectors( NULL, NULL, &vehicleUp );
- IPhysicsFrictionSnapshot *pSnapshot = pVehiclePhysics->CreateFrictionSnapshot();
- while ( pSnapshot->IsValid() )
- {
- IPhysicsObject *pOther = pSnapshot->GetObject(1);
- float otherMass = pOther->GetMass();
- CBaseEntity *pOtherEntity = static_cast<CBaseEntity *>(pOther->GetGameData());
- Assert(pOtherEntity);
- if ( pOtherEntity && pOtherEntity->GetMoveType() == MOVETYPE_VPHYSICS && pOther->IsMoveable() && (otherMass*4.0f) < vehicleMass )
- {
- Vector normal;
- pSnapshot->GetSurfaceNormal(normal);
- // this points down in the car's reference frame, then it's probably trapped under the car
- if ( DotProduct(normal, vehicleUp) < -0.9f )
- {
- Vector point, pointLocal;
- pSnapshot->GetContactPoint(point);
- VectorITransform( point, pVehicleEntity->EntityToWorldTransform(), pointLocal );
- Vector bottomPoint = physcollision->CollideGetExtent( pVehiclePhysics->GetCollide(), vec3_origin, vec3_angle, Vector(0,0,-1) );
- // make sure it's under the bottom of the car
- float bottomPlane = DotProduct(bottomPoint,vehicleUp)+8; // 8 inches above bottom
- if ( DotProduct( pointLocal, vehicleUp ) <= bottomPlane )
- {
- //Msg("Solved %s\n", pOtherEntity->GetClassname());
- if ( solveList.Find(pOtherEntity) < 0 )
- {
- solveList.AddToTail(pOtherEntity);
- }
- }
- }
- }
- pSnapshot->NextFrictionData();
- }
- pVehiclePhysics->DestroyFrictionSnapshot( pSnapshot );
- if ( solveList.Count() )
- {
- for ( int i = 0; i < solveList.Count(); i++ )
- {
- EntityPhysics_CreateSolver( pVehicleEntity, solveList[i], true, 4.0f );
- }
- pVehiclePhysics->RecheckContactPoints();
- }
-}
-
-static void SimpleCollisionResponse( Vector velocityIn, const Vector &normal, float coefficientOfRestitution, Vector *pVelocityOut )
-{
- Vector Vn = DotProduct(velocityIn,normal) * normal;
- Vector Vt = velocityIn - Vn;
- *pVelocityOut = Vt - coefficientOfRestitution * Vn;
-}
-
-static void KillBlockingEnemyNPCs( CBasePlayer *pPlayer, CBaseEntity *pVehicleEntity, IPhysicsObject *pVehiclePhysics )
-{
- Vector velocity;
- pVehiclePhysics->GetVelocity( &velocity, NULL );
- float vehicleMass = pVehiclePhysics->GetMass();
-
- // loop through the contacts and look for enemy NPCs that we're pushing on
- CUtlVector<CAI_BaseNPC *> npcList;
- CUtlVector<Vector> forceList;
- CUtlVector<Vector> contactList;
- IPhysicsFrictionSnapshot *pSnapshot = pVehiclePhysics->CreateFrictionSnapshot();
- while ( pSnapshot->IsValid() )
- {
- IPhysicsObject *pOther = pSnapshot->GetObject(1);
- float otherMass = pOther->GetMass();
- CBaseEntity *pOtherEntity = static_cast<CBaseEntity *>(pOther->GetGameData());
- CAI_BaseNPC *pNPC = pOtherEntity ? pOtherEntity->MyNPCPointer() : NULL;
- // Is this an enemy NPC with a small enough mass?
- if ( pNPC && pPlayer->IRelationType(pNPC) != D_LI && ((otherMass*2.0f) < vehicleMass) )
- {
- // accumulate the stress force for this NPC in the lsit
- float force = pSnapshot->GetNormalForce();
- Vector normal;
- pSnapshot->GetSurfaceNormal(normal);
- normal *= force;
- int index = npcList.Find(pNPC);
- if ( index < 0 )
- {
- vphysicsupdateai_t *pUpdate = NULL;
- if ( pNPC->VPhysicsGetObject() && pNPC->VPhysicsGetObject()->GetShadowController() && pNPC->GetMoveType() == MOVETYPE_STEP )
- {
- if ( pNPC->HasDataObjectType(VPHYSICSUPDATEAI) )
- {
- pUpdate = static_cast<vphysicsupdateai_t *>(pNPC->GetDataObject(VPHYSICSUPDATEAI));
- // kill this guy if I've been pushing him for more than half a second and I'm
- // still pushing in his direction
- if ( (gpGlobals->curtime - pUpdate->startUpdateTime) > 0.5f && DotProduct(velocity,normal) > 0)
- {
- index = npcList.AddToTail(pNPC);
- forceList.AddToTail( normal );
- Vector pos;
- pSnapshot->GetContactPoint(pos);
- contactList.AddToTail(pos);
- }
- }
- else
- {
- pUpdate = static_cast<vphysicsupdateai_t *>(pNPC->CreateDataObject( VPHYSICSUPDATEAI ));
- pUpdate->startUpdateTime = gpGlobals->curtime;
- }
- // update based on vphysics for the next second
- // this allows the car to push the NPC
- pUpdate->stopUpdateTime = gpGlobals->curtime + 1.0f;
- float maxAngular;
- pNPC->VPhysicsGetObject()->GetShadowController()->GetMaxSpeed( &pUpdate->savedShadowControllerMaxSpeed, &maxAngular );
- pNPC->VPhysicsGetObject()->GetShadowController()->MaxSpeed( 1.0f, maxAngular );
- }
- }
- else
- {
- forceList[index] += normal;
- }
- }
- pSnapshot->NextFrictionData();
- }
- pVehiclePhysics->DestroyFrictionSnapshot( pSnapshot );
- // now iterate the list and check each cumulative force against the threshold
- if ( npcList.Count() )
- {
- for ( int i = npcList.Count(); --i >= 0; )
- {
- Vector damageForce;
- npcList[i]->VPhysicsGetObject()->GetVelocity( &damageForce, NULL );
- Vector vel;
- pVehiclePhysics->GetVelocityAtPoint( contactList[i], &vel );
- damageForce -= vel;
- Vector normal = forceList[i];
- VectorNormalize(normal);
- SimpleCollisionResponse( damageForce, normal, 1.0, &damageForce );
- damageForce += (normal * 300.0f);
- damageForce *= npcList[i]->VPhysicsGetObject()->GetMass();
- float len = damageForce.Length();
- damageForce.z += len*phys_upimpactforcescale.GetFloat();
- Vector vehicleForce = -damageForce;
-
- CTakeDamageInfo dmgInfo( pVehicleEntity, pVehicleEntity, damageForce, contactList[i], 200.0f, DMG_CRUSH|DMG_VEHICLE );
- npcList[i]->TakeDamage( dmgInfo );
- pVehiclePhysics->ApplyForceOffset( vehicleForce, contactList[i] );
- PhysCollisionSound( pVehicleEntity, npcList[i]->VPhysicsGetObject(), CHAN_BODY, pVehiclePhysics->GetMaterialIndex(), npcList[i]->VPhysicsGetObject()->GetMaterialIndex(), gpGlobals->frametime, 200.0f );
- }
- }
-}
-
-void CPropJeepEpisodic::DriveVehicle( float flFrameTime, CUserCmd *ucmd, int iButtonsDown, int iButtonsReleased )
-{
- /* The car headlight hurts perf, there's no timer to turn it off automatically,
- and we haven't built any gameplay around it.
-
- Furthermore, I don't think I've ever seen a playtester turn it on.
-
- if ( ucmd->impulse == 100 )
- {
- if (HeadlightIsOn())
- {
- HeadlightTurnOff();
- }
- else
- {
- HeadlightTurnOn();
- }
- }*/
-
- if ( ucmd->forwardmove != 0.0f )
- {
- //Msg("Push V: %.2f, %.2f, %.2f\n", ucmd->forwardmove, carState->engineRPM, carState->speed );
- CBasePlayer *pPlayer = ToBasePlayer(GetDriver());
-
- if ( pPlayer && VPhysicsGetObject() )
- {
- KillBlockingEnemyNPCs( pPlayer, this, VPhysicsGetObject() );
- SolveBlockingProps( this, VPhysicsGetObject() );
- }
- }
- BaseClass::DriveVehicle(flFrameTime, ucmd, iButtonsDown, iButtonsReleased);
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CPropJeepEpisodic::CreateHazardLights( void )
-{
- static const char *s_szAttach[NUM_HAZARD_LIGHTS] =
- {
- "rearlight_r",
- "rearlight_l",
- "headlight_r",
- "headlight_l",
- };
-
- // Turn on the hazards!
- for ( int i = 0; i < NUM_HAZARD_LIGHTS; i++ )
- {
- if ( m_hHazardLights[i] == NULL )
- {
- m_hHazardLights[i] = CSprite::SpriteCreate( s_szHazardSprite, GetLocalOrigin(), false );
- if ( m_hHazardLights[i] )
- {
- m_hHazardLights[i]->SetTransparency( kRenderWorldGlow, 255, 220, 40, 255, kRenderFxNoDissipation );
- m_hHazardLights[i]->SetAttachment( this, LookupAttachment( s_szAttach[i] ) );
- m_hHazardLights[i]->SetGlowProxySize( 2.0f );
- m_hHazardLights[i]->TurnOff();
- if ( i < 2 )
- {
- // Rear lights are red
- m_hHazardLights[i]->SetColor( 255, 0, 0 );
- m_hHazardLights[i]->SetScale( 1.0f );
- }
- else
- {
- // Font lights are white
- m_hHazardLights[i]->SetScale( 1.0f );
- }
- }
- }
- }
-
- // We start off
- m_bBlink = false;
-
- // Setup our blink
- SetContextThink( &CPropJeepEpisodic::HazardBlinkThink, gpGlobals->curtime + 0.1f, "HazardBlink" );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CPropJeepEpisodic::DestroyHazardLights( void )
-{
- for ( int i = 0; i < NUM_HAZARD_LIGHTS; i++ )
- {
- if ( m_hHazardLights[i] != NULL )
- {
- UTIL_Remove( m_hHazardLights[i] );
- }
- }
-
- SetContextThink( NULL, gpGlobals->curtime, "HazardBlink" );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : nRole -
-//-----------------------------------------------------------------------------
-void CPropJeepEpisodic::ExitVehicle( int nRole )
-{
- BaseClass::ExitVehicle( nRole );
-
- CreateHazardLights();
-}
-
-void CPropJeepEpisodic::SetBusterHopperVisibility(bool visible)
-{
- // if we're there already do nothing
- if (visible == m_bBusterHopperVisible)
- return;
-
- SetBodygroup( JEEP_HOPPER_BODYGROUP, visible ? 1 : 0);
- m_bBusterHopperVisible = visible;
-}
-
-
-void CPropJeepEpisodic::InputSetCargoVisibility( inputdata_t &data )
-{
- bool visible = data.value.Bool();
-
- SetBusterHopperVisibility( visible );
-}
-
-//-----------------------------------------------------------------------------
-// THIS CODE LIFTED RIGHT OUT OF TF2, to defer the pain of making vgui-on-an-entity
-// code available to all CBaseAnimating.
-//-----------------------------------------------------------------------------
-void CPropJeepEpisodic::SpawnRadarPanel()
-{
- // FIXME: Deal with dynamically resizing control panels?
-
- // If we're attached to an entity, spawn control panels on it instead of use
- CBaseAnimating *pEntityToSpawnOn = this;
- char *pOrgLL = "controlpanel0_ll";
- char *pOrgUR = "controlpanel0_ur";
-
- Assert( pEntityToSpawnOn );
-
- // Lookup the attachment point...
- int nLLAttachmentIndex = pEntityToSpawnOn->LookupAttachment(pOrgLL);
-
- if (nLLAttachmentIndex <= 0)
- {
- return;
- }
-
- int nURAttachmentIndex = pEntityToSpawnOn->LookupAttachment(pOrgUR);
- if (nURAttachmentIndex <= 0)
- {
- return;
- }
-
- const char *pScreenName = "jalopy_radar_panel";
- const char *pScreenClassname = "vgui_screen";
-
- // Compute the screen size from the attachment points...
- matrix3x4_t panelToWorld;
- pEntityToSpawnOn->GetAttachment( nLLAttachmentIndex, panelToWorld );
-
- matrix3x4_t worldToPanel;
- MatrixInvert( panelToWorld, worldToPanel );
-
- // Now get the lower right position + transform into panel space
- Vector lr, lrlocal;
- pEntityToSpawnOn->GetAttachment( nURAttachmentIndex, panelToWorld );
- MatrixGetColumn( panelToWorld, 3, lr );
- VectorTransform( lr, worldToPanel, lrlocal );
-
- float flWidth = lrlocal.x;
- float flHeight = lrlocal.y;
-
- CVGuiScreen *pScreen = CreateVGuiScreen( pScreenClassname, pScreenName, pEntityToSpawnOn, this, nLLAttachmentIndex );
- pScreen->SetActualSize( flWidth, flHeight );
- pScreen->SetActive( true );
- pScreen->SetOverlayMaterial( RADAR_PANEL_WRITEZ );
- pScreen->SetTransparency( true );
-
- m_hRadarScreen.Set( pScreen );
-
- m_bRadarEnabled = true;
- m_iNumRadarContacts = 0;
- m_flNextRadarUpdateTime = gpGlobals->curtime - 1.0f;
-}
-
-//-----------------------------------------------------------------------------
-void CPropJeepEpisodic::DestroyRadarPanel()
-{
- Assert( m_hRadarScreen != NULL );
- m_hRadarScreen->SUB_Remove();
- m_bRadarEnabled = false;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CPropJeepEpisodic::HazardBlinkThink( void )
-{
- if ( m_bBlink )
- {
- for ( int i = 0; i < NUM_HAZARD_LIGHTS; i++ )
- {
- if ( m_hHazardLights[i] )
- {
- m_hHazardLights[i]->SetBrightness( 0, 0.1f );
- }
- }
-
- SetContextThink( &CPropJeepEpisodic::HazardBlinkThink, gpGlobals->curtime + 0.25f, "HazardBlink" );
- }
- else
- {
- for ( int i = 0; i < NUM_HAZARD_LIGHTS; i++ )
- {
- if ( m_hHazardLights[i] )
- {
- m_hHazardLights[i]->SetBrightness( 255, 0.1f );
- m_hHazardLights[i]->TurnOn();
- }
- }
-
- SetContextThink( &CPropJeepEpisodic::HazardBlinkThink, gpGlobals->curtime + 0.5f, "HazardBlink" );
- }
-
- m_bBlink = !m_bBlink;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CPropJeepEpisodic::HandleWater( void )
-{
- // Only check the wheels and engine in water if we have a driver (player).
- if ( !GetDriver() )
- return;
-
- // Update our internal state
- CheckWater();
-
- // Save of data from last think.
- for ( int iWheel = 0; iWheel < JEEP_WHEEL_COUNT; ++iWheel )
- {
- m_WaterData.m_bWheelWasInWater[iWheel] = m_WaterData.m_bWheelInWater[iWheel];
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Report our lock state
-//-----------------------------------------------------------------------------
-int CPropJeepEpisodic::DrawDebugTextOverlays( void )
-{
- int text_offset = BaseClass::DrawDebugTextOverlays();
-
- if ( m_debugOverlays & OVERLAY_TEXT_BIT )
- {
- EntityText( text_offset, CFmtStr("Entrance: %s", m_bEntranceLocked ? "Locked" : "Unlocked" ), 0 );
- text_offset++;
-
- EntityText( text_offset, CFmtStr("Exit: %s", m_bExitLocked ? "Locked" : "Unlocked" ), 0 );
- text_offset++;
- }
-
- return text_offset;
-}
-
-#define TRANSITION_SEARCH_RADIUS (100*12)
-
-//-----------------------------------------------------------------------------
-// Purpose: Teleport the car to a destination that will cause it to transition if it's not going to otherwise
-//-----------------------------------------------------------------------------
-void CPropJeepEpisodic::InputOutsideTransition( inputdata_t &inputdata )
-{
- // Teleport into the new map
- CBasePlayer *pPlayer = AI_GetSinglePlayer();
- Vector vecTeleportPos;
- QAngle vecTeleportAngles;
-
- // Get our bounds
- Vector vecSurroundMins, vecSurroundMaxs;
- CollisionProp()->WorldSpaceSurroundingBounds( &vecSurroundMins, &vecSurroundMaxs );
- vecSurroundMins -= WorldSpaceCenter();
- vecSurroundMaxs -= WorldSpaceCenter();
-
- Vector vecBestPos;
- QAngle vecBestAngles;
-
- CInfoTargetVehicleTransition *pEntity = NULL;
- bool bSucceeded = false;
-
- // Find all entities of the correct name and try and sit where they're at
- while ( ( pEntity = (CInfoTargetVehicleTransition *) gEntList.FindEntityByClassname( pEntity, "info_target_vehicle_transition" ) ) != NULL )
- {
- // Must be enabled
- if ( pEntity->IsDisabled() )
- continue;
-
- // Must be within range
- if ( ( pEntity->GetAbsOrigin() - pPlayer->GetAbsOrigin() ).LengthSqr() > Square( TRANSITION_SEARCH_RADIUS ) )
- continue;
-
- vecTeleportPos = pEntity->GetAbsOrigin();
- vecTeleportAngles = pEntity->GetAbsAngles() + QAngle( 0, -90, 0 ); // Vehicle is always off by 90 degrees
-
- // Rotate to face the destination angles
- Vector vecMins;
- Vector vecMaxs;
- VectorRotate( vecSurroundMins, vecTeleportAngles, vecMins );
- VectorRotate( vecSurroundMaxs, vecTeleportAngles, vecMaxs );
-
- if ( vecMaxs.x < vecMins.x )
- V_swap( vecMins.x, vecMaxs.x );
-
- if ( vecMaxs.y < vecMins.y )
- V_swap( vecMins.y, vecMaxs.y );
-
- if ( vecMaxs.z < vecMins.z )
- V_swap( vecMins.z, vecMaxs.z );
-
- // Move up
- vecTeleportPos.z += ( vecMaxs.z - vecMins.z );
-
- trace_t tr;
- UTIL_TraceHull( vecTeleportPos, vecTeleportPos - Vector( 0, 0, 128 ), vecMins, vecMaxs, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr );
- if ( tr.startsolid == false && tr.allsolid == false && tr.fraction < 1.0f )
- {
- // Store this off
- vecBestPos = tr.endpos;
- vecBestAngles = vecTeleportAngles;
- bSucceeded = true;
-
- // If this point isn't visible, then stop looking and use it
- if ( pPlayer->FInViewCone( tr.endpos ) == false )
- break;
- }
- }
-
- // See if we're finished
- if ( bSucceeded )
- {
- Teleport( &vecTeleportPos, &vecTeleportAngles, NULL );
- return;
- }
-
- // TODO: We found no valid teleport points, so try to find them dynamically
- Warning("No valid vehicle teleport points!\n");
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Stop players punting the car around.
-//-----------------------------------------------------------------------------
-void CPropJeepEpisodic::InputDisablePhysGun( inputdata_t &data )
-{
- AddEFlags( EFL_NO_PHYSCANNON_INTERACTION );
-}
-//-----------------------------------------------------------------------------
-// Purpose: Return to normal
-//-----------------------------------------------------------------------------
-void CPropJeepEpisodic::InputEnablePhysGun( inputdata_t &data )
-{
- RemoveEFlags( EFL_NO_PHYSCANNON_INTERACTION );
-}
-
-//-----------------------------------------------------------------------------
-// Create and parent two radial node link controllers.
-//-----------------------------------------------------------------------------
-void CPropJeepEpisodic::InputCreateLinkController( inputdata_t &data )
-{
- Vector vecFront, vecRear;
- Vector vecWFL, vecWFR; // Front wheels
- Vector vecWRL, vecWRR; // Back wheels
-
- GetAttachment( "wheel_fr", vecWFR );
- GetAttachment( "wheel_fl", vecWFL );
-
- GetAttachment( "wheel_rr", vecWRR );
- GetAttachment( "wheel_rl", vecWRL );
-
- vecFront = (vecWFL + vecWFR) * 0.5f;
- vecRear = (vecWRL + vecWRR) * 0.5f;
-
- float flRadius = ( (vecFront - vecRear).Length() ) * 0.6f;
-
- CAI_RadialLinkController *pLinkController = (CAI_RadialLinkController *)CreateEntityByName( "info_radial_link_controller" );
- if( pLinkController != NULL && m_hLinkControllerFront.Get() == NULL )
- {
- pLinkController->m_flRadius = flRadius;
- pLinkController->Spawn();
- pLinkController->SetAbsOrigin( vecFront );
- pLinkController->SetOwnerEntity( this );
- pLinkController->SetParent( this );
- pLinkController->Activate();
- m_hLinkControllerFront.Set( pLinkController );
-
- //NDebugOverlay::Circle( vecFront, Vector(0,1,0), Vector(1,0,0), flRadius, 255, 255, 255, 128, false, 100 );
- }
-
- pLinkController = (CAI_RadialLinkController *)CreateEntityByName( "info_radial_link_controller" );
- if( pLinkController != NULL && m_hLinkControllerRear.Get() == NULL )
- {
- pLinkController->m_flRadius = flRadius;
- pLinkController->Spawn();
- pLinkController->SetAbsOrigin( vecRear );
- pLinkController->SetOwnerEntity( this );
- pLinkController->SetParent( this );
- pLinkController->Activate();
- m_hLinkControllerRear.Set( pLinkController );
-
- //NDebugOverlay::Circle( vecRear, Vector(0,1,0), Vector(1,0,0), flRadius, 255, 255, 255, 128, false, 100 );
- }
-}
-
-void CPropJeepEpisodic::InputDestroyLinkController( inputdata_t &data )
-{
- if( m_hLinkControllerFront.Get() != NULL )
- {
- CAI_RadialLinkController *pLinkController = dynamic_cast<CAI_RadialLinkController*>(m_hLinkControllerFront.Get());
- if( pLinkController != NULL )
- {
- pLinkController->ModifyNodeLinks(false);
- UTIL_Remove( pLinkController );
- m_hLinkControllerFront.Set(NULL);
- }
- }
-
- if( m_hLinkControllerRear.Get() != NULL )
- {
- CAI_RadialLinkController *pLinkController = dynamic_cast<CAI_RadialLinkController*>(m_hLinkControllerRear.Get());
- if( pLinkController != NULL )
- {
- pLinkController->ModifyNodeLinks(false);
- UTIL_Remove( pLinkController );
- m_hLinkControllerRear.Set(NULL);
- }
- }
-}
-
-
-bool CPropJeepEpisodic::AllowBlockedExit( CBaseCombatCharacter *pPassenger, int nRole )
-{
- // Wait until we've settled down before we resort to blocked exits.
- // This keeps us from doing blocked exits in mid-jump, which can cause mayhem like
- // sticking the player through player clips or into geometry.
- return GetSmoothedVelocity().IsLengthLessThan( jalopy_blocked_exit_max_speed.GetFloat() );
-}
-
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//
+//
+//=============================================================================
+
+#include "cbase.h"
+#include "vehicle_jeep_episodic.h"
+#include "collisionutils.h"
+#include "npc_alyx_episodic.h"
+#include "particle_parse.h"
+#include "particle_system.h"
+#include "hl2_player.h"
+#include "in_buttons.h"
+#include "vphysics/friction.h"
+#include "vphysicsupdateai.h"
+#include "physics_npc_solver.h"
+#include "Sprite.h"
+#include "weapon_striderbuster.h"
+#include "npc_strider.h"
+#include "vguiscreen.h"
+#include "hl2_vehicle_radar.h"
+#include "props.h"
+#include "ai_dynamiclink.h"
+
+extern ConVar phys_upimpactforcescale;
+
+ConVar jalopy_blocked_exit_max_speed( "jalopy_blocked_exit_max_speed", "50" );
+
+#define JEEP_AMMOCRATE_HITGROUP 5
+#define JEEP_AMMO_CRATE_CLOSE_DELAY 2.0f
+
+// Bodygroups
+#define JEEP_RADAR_BODYGROUP 1
+#define JEEP_HOPPER_BODYGROUP 2
+#define JEEP_CARBAR_BODYGROUP 3
+
+#define RADAR_PANEL_MATERIAL "vgui/screens/radar"
+#define RADAR_PANEL_WRITEZ "engine/writez"
+
+static const char *s_szHazardSprite = "sprites/light_glow01.vmt";
+
+enum
+{
+ RADAR_MODE_NORMAL = 0,
+ RADAR_MODE_STICKY,
+};
+
+//=========================================================
+//=========================================================
+class CRadarTarget : public CPointEntity
+{
+ DECLARE_CLASS( CRadarTarget, CPointEntity );
+
+public:
+ void Spawn();
+
+ bool IsDisabled() { return m_bDisabled; }
+ int GetType() { return m_iType; }
+ int GetMode() { return m_iMode; }
+ void InputEnable( inputdata_t &inputdata );
+ void InputDisable( inputdata_t &inputdata );
+ int ObjectCaps();
+
+private:
+ bool m_bDisabled;
+ int m_iType;
+ int m_iMode;
+
+public:
+ float m_flRadius;
+
+ DECLARE_DATADESC();
+};
+
+LINK_ENTITY_TO_CLASS( info_radar_target, CRadarTarget );
+
+BEGIN_DATADESC( CRadarTarget )
+ DEFINE_KEYFIELD( m_bDisabled, FIELD_BOOLEAN, "StartDisabled" ),
+ DEFINE_KEYFIELD( m_flRadius, FIELD_FLOAT, "radius" ),
+ DEFINE_KEYFIELD( m_iType, FIELD_INTEGER, "type" ),
+ DEFINE_KEYFIELD( m_iMode, FIELD_INTEGER, "mode" ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "Disable",InputDisable ),
+END_DATADESC();
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CRadarTarget::Spawn()
+{
+ BaseClass::Spawn();
+
+ AddEffects( EF_NODRAW );
+ SetMoveType( MOVETYPE_NONE );
+ SetSolid( SOLID_NONE );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CRadarTarget::InputEnable( inputdata_t &inputdata )
+{
+ m_bDisabled = false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CRadarTarget::InputDisable( inputdata_t &inputdata )
+{
+ m_bDisabled = true;
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+int CRadarTarget::ObjectCaps()
+{
+ return BaseClass::ObjectCaps() | FCAP_ACROSS_TRANSITION;
+}
+
+
+
+
+//
+// Trigger which detects entities placed in the cargo hold of the jalopy
+//
+
+class CVehicleCargoTrigger : public CBaseEntity
+{
+ DECLARE_CLASS( CVehicleCargoTrigger, CBaseEntity );
+
+public:
+
+ //
+ // Creates a trigger with the specified bounds
+
+ static CVehicleCargoTrigger *Create( const Vector &vecOrigin, const Vector &vecMins, const Vector &vecMaxs, CBaseEntity *pOwner )
+ {
+ CVehicleCargoTrigger *pTrigger = (CVehicleCargoTrigger *) CreateEntityByName( "trigger_vehicle_cargo" );
+ if ( pTrigger == NULL )
+ return NULL;
+
+ UTIL_SetOrigin( pTrigger, vecOrigin );
+ UTIL_SetSize( pTrigger, vecMins, vecMaxs );
+ pTrigger->SetOwnerEntity( pOwner );
+ pTrigger->SetParent( pOwner );
+
+ pTrigger->Spawn();
+
+ return pTrigger;
+ }
+
+ //
+ // Handles the trigger touching its intended quarry
+
+ void CargoTouch( CBaseEntity *pOther )
+ {
+ // Cannot be ignoring touches
+ if ( ( m_hIgnoreEntity == pOther ) || ( m_flIgnoreDuration >= gpGlobals->curtime ) )
+ return;
+
+ // Make sure this object is being held by the player
+ if ( pOther->VPhysicsGetObject() == NULL || (pOther->VPhysicsGetObject()->GetGameFlags() & FVPHYSICS_PLAYER_HELD) == false )
+ return;
+
+ if ( StriderBuster_NumFlechettesAttached( pOther ) > 0 )
+ return;
+
+ AddCargo( pOther );
+ }
+
+ bool AddCargo( CBaseEntity *pOther )
+ {
+ // For now, only bother with strider busters
+ if ( (FClassnameIs( pOther, "weapon_striderbuster" ) == false) &&
+ (FClassnameIs( pOther, "npc_grenade_magna" ) == false)
+ )
+ return false;
+
+ // Must be a physics prop
+ CPhysicsProp *pProp = dynamic_cast<CPhysicsProp *>(pOther);
+ if ( pOther == NULL )
+ return false;
+
+ CPropJeepEpisodic *pJeep = dynamic_cast< CPropJeepEpisodic * >( GetOwnerEntity() );
+ if ( pJeep == NULL )
+ return false;
+
+ // Make the player release the item
+ Pickup_ForcePlayerToDropThisObject( pOther );
+
+ // Stop colliding with things
+ pOther->VPhysicsDestroyObject();
+ pOther->SetSolidFlags( FSOLID_NOT_SOLID );
+ pOther->SetMoveType( MOVETYPE_NONE );
+
+ // Parent the object to our owner
+ pOther->SetParent( GetOwnerEntity() );
+
+ // The car now owns the entity
+ pJeep->AddPropToCargoHold( pProp );
+
+ // Notify the buster that it's been added to the cargo hold.
+ StriderBuster_OnAddToCargoHold( pProp );
+
+ // Stop touching this item
+ Disable();
+
+ return true;
+ }
+
+ //
+ // Setup the entity
+
+ void Spawn( void )
+ {
+ BaseClass::Spawn();
+
+ SetSolid( SOLID_BBOX );
+ SetSolidFlags( FSOLID_TRIGGER | FSOLID_NOT_SOLID );
+
+ SetTouch( &CVehicleCargoTrigger::CargoTouch );
+ }
+
+ void Activate()
+ {
+ BaseClass::Activate();
+ SetSolidFlags( FSOLID_TRIGGER | FSOLID_NOT_SOLID ); // Fixes up old savegames
+ }
+
+ //
+ // When we've stopped touching this entity, we ignore it
+
+ void EndTouch( CBaseEntity *pOther )
+ {
+ if ( pOther == m_hIgnoreEntity )
+ {
+ m_hIgnoreEntity = NULL;
+ }
+
+ BaseClass::EndTouch( pOther );
+ }
+
+ //
+ // Disables the trigger for a set duration
+
+ void IgnoreTouches( CBaseEntity *pIgnoreEntity )
+ {
+ m_hIgnoreEntity = pIgnoreEntity;
+ m_flIgnoreDuration = gpGlobals->curtime + 0.5f;
+ }
+
+ void Disable( void )
+ {
+ SetTouch( NULL );
+ }
+
+ void Enable( void )
+ {
+ SetTouch( &CVehicleCargoTrigger::CargoTouch );
+ }
+
+protected:
+
+ float m_flIgnoreDuration;
+ CHandle <CBaseEntity> m_hIgnoreEntity;
+
+ DECLARE_DATADESC();
+};
+
+LINK_ENTITY_TO_CLASS( trigger_vehicle_cargo, CVehicleCargoTrigger );
+
+BEGIN_DATADESC( CVehicleCargoTrigger )
+ DEFINE_FIELD( m_flIgnoreDuration, FIELD_TIME ),
+ DEFINE_FIELD( m_hIgnoreEntity, FIELD_EHANDLE ),
+ DEFINE_ENTITYFUNC( CargoTouch ),
+END_DATADESC();
+
+//
+// Transition reference point for the vehicle
+//
+
+class CInfoTargetVehicleTransition : public CPointEntity
+{
+public:
+ DECLARE_CLASS( CInfoTargetVehicleTransition, CPointEntity );
+
+ void Enable( void ) { m_bDisabled = false; }
+ void Disable( void ) { m_bDisabled = true; }
+
+ bool IsDisabled( void ) const { return m_bDisabled; }
+
+private:
+
+ void InputEnable( inputdata_t &data ) { Enable(); }
+ void InputDisable( inputdata_t &data ) { Disable(); }
+
+ bool m_bDisabled;
+
+ DECLARE_DATADESC();
+};
+
+BEGIN_DATADESC( CInfoTargetVehicleTransition )
+ DEFINE_KEYFIELD( m_bDisabled, FIELD_BOOLEAN, "StartDisabled" ),
+
+ DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "Disable",InputDisable ),
+END_DATADESC();
+
+LINK_ENTITY_TO_CLASS( info_target_vehicle_transition, CInfoTargetVehicleTransition );
+
+//
+// CPropJeepEpisodic
+//
+
+LINK_ENTITY_TO_CLASS( prop_vehicle_jeep, CPropJeepEpisodic );
+
+BEGIN_DATADESC( CPropJeepEpisodic )
+
+ DEFINE_FIELD( m_bEntranceLocked, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_bExitLocked, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_hCargoProp, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_hCargoTrigger, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_bAddingCargo, FIELD_BOOLEAN ),
+ DEFINE_ARRAY( m_hWheelDust, FIELD_EHANDLE, NUM_WHEEL_EFFECTS ),
+ DEFINE_ARRAY( m_hWheelWater, FIELD_EHANDLE, NUM_WHEEL_EFFECTS ),
+ DEFINE_ARRAY( m_hHazardLights, FIELD_EHANDLE, NUM_HAZARD_LIGHTS ),
+ DEFINE_FIELD( m_flCargoStartTime, FIELD_TIME ),
+ DEFINE_FIELD( m_bBlink, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_bRadarEnabled, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_bRadarDetectsEnemies, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_hRadarScreen, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_hLinkControllerFront, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_hLinkControllerRear, FIELD_EHANDLE ),
+ DEFINE_KEYFIELD( m_bBusterHopperVisible, FIELD_BOOLEAN, "CargoVisible" ),
+ // m_flNextAvoidBroadcastTime
+ DEFINE_FIELD( m_flNextWaterSound, FIELD_TIME ),
+ DEFINE_FIELD( m_flNextRadarUpdateTime, FIELD_TIME ),
+ DEFINE_FIELD( m_iNumRadarContacts, FIELD_INTEGER ),
+ DEFINE_ARRAY( m_vecRadarContactPos, FIELD_POSITION_VECTOR, RADAR_MAX_CONTACTS ),
+ DEFINE_ARRAY( m_iRadarContactType, FIELD_INTEGER, RADAR_MAX_CONTACTS ),
+
+ DEFINE_THINKFUNC( HazardBlinkThink ),
+
+ DEFINE_OUTPUT( m_OnCompanionEnteredVehicle, "OnCompanionEnteredVehicle" ),
+ DEFINE_OUTPUT( m_OnCompanionExitedVehicle, "OnCompanionExitedVehicle" ),
+ DEFINE_OUTPUT( m_OnHostileEnteredVehicle, "OnHostileEnteredVehicle" ),
+ DEFINE_OUTPUT( m_OnHostileExitedVehicle, "OnHostileExitedVehicle" ),
+
+ DEFINE_INPUTFUNC( FIELD_VOID, "LockEntrance", InputLockEntrance ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "UnlockEntrance", InputUnlockEntrance ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "LockExit", InputLockExit ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "UnlockExit", InputUnlockExit ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "EnableRadar", InputEnableRadar ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "DisableRadar", InputDisableRadar ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "EnableRadarDetectEnemies", InputEnableRadarDetectEnemies ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "AddBusterToCargo", InputAddBusterToCargo ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "OutsideTransition", InputOutsideTransition ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "DisablePhysGun", InputDisablePhysGun ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "EnablePhysGun", InputEnablePhysGun ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "CreateLinkController", InputCreateLinkController ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "DestroyLinkController", InputDestroyLinkController ),
+
+ DEFINE_INPUTFUNC( FIELD_BOOLEAN, "SetCargoHopperVisibility", InputSetCargoVisibility ),
+
+END_DATADESC();
+
+IMPLEMENT_SERVERCLASS_ST(CPropJeepEpisodic, DT_CPropJeepEpisodic)
+ //CNetworkVar( int, m_iNumRadarContacts );
+ SendPropInt( SENDINFO(m_iNumRadarContacts), 8 ),
+
+ //CNetworkArray( Vector, m_vecRadarContactPos, RADAR_MAX_CONTACTS );
+ SendPropArray( SendPropVector( SENDINFO_ARRAY(m_vecRadarContactPos), -1, SPROP_COORD), m_vecRadarContactPos ),
+
+ //CNetworkArray( int, m_iRadarContactType, RADAR_MAX_CONTACTS );
+ SendPropArray( SendPropInt(SENDINFO_ARRAY(m_iRadarContactType), RADAR_CONTACT_TYPE_BITS ), m_iRadarContactType ),
+END_SEND_TABLE()
+
+
+//=============================================================================
+// Episodic jeep
+
+CPropJeepEpisodic::CPropJeepEpisodic( void ) :
+m_bEntranceLocked( false ),
+m_bExitLocked( false ),
+m_bAddingCargo( false ),
+m_flNextAvoidBroadcastTime( 0.0f )
+{
+ m_bHasGun = false;
+ m_bUnableToFire = true;
+ m_bRadarDetectsEnemies = false;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPropJeepEpisodic::UpdateOnRemove( void )
+{
+ BaseClass::UpdateOnRemove();
+
+ // Kill our wheel dust
+ for ( int i = 0; i < NUM_WHEEL_EFFECTS; i++ )
+ {
+ if ( m_hWheelDust[i] != NULL )
+ {
+ UTIL_Remove( m_hWheelDust[i] );
+ }
+
+ if ( m_hWheelWater[i] != NULL )
+ {
+ UTIL_Remove( m_hWheelWater[i] );
+ }
+ }
+
+ DestroyHazardLights();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPropJeepEpisodic::Precache( void )
+{
+ PrecacheMaterial( RADAR_PANEL_MATERIAL );
+ PrecacheMaterial( RADAR_PANEL_WRITEZ );
+ PrecacheModel( s_szHazardSprite );
+ PrecacheScriptSound( "JNK_Radar_Ping_Friendly" );
+ PrecacheScriptSound( "Physics.WaterSplash" );
+
+ PrecacheParticleSystem( "WheelDust" );
+ PrecacheParticleSystem( "WheelSplash" );
+
+ BaseClass::Precache();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pPlayer -
+//-----------------------------------------------------------------------------
+void CPropJeepEpisodic::EnterVehicle( CBaseCombatCharacter *pPassenger )
+{
+ BaseClass::EnterVehicle( pPassenger );
+
+ // Turn our hazards off!
+ DestroyHazardLights();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPropJeepEpisodic::Spawn( void )
+{
+ BaseClass::Spawn();
+
+ SetBlocksLOS( false );
+
+ CBasePlayer *pPlayer = UTIL_GetLocalPlayer();
+ if ( pPlayer != NULL )
+ {
+ pPlayer->m_Local.m_iHideHUD |= HIDEHUD_VEHICLE_CROSSHAIR;
+ }
+
+
+ SetBodygroup( JEEP_HOPPER_BODYGROUP, m_bBusterHopperVisible ? 1 : 0);
+ CreateCargoTrigger();
+
+ // carbar bodygroup is always on
+ SetBodygroup( JEEP_CARBAR_BODYGROUP, 1 );
+
+ m_bRadarDetectsEnemies = false;
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CPropJeepEpisodic::Activate()
+{
+ m_iNumRadarContacts = 0; // Force first contact tone
+ BaseClass::Activate();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPropJeepEpisodic::NPC_FinishedEnterVehicle( CAI_BaseNPC *pPassenger, bool bCompanion )
+{
+ // FIXME: This will be moved to the NPCs entering and exiting
+ // Fire our outputs
+ if ( bCompanion )
+ {
+ m_OnCompanionEnteredVehicle.FireOutput( this, pPassenger );
+ }
+ else
+ {
+ m_OnHostileEnteredVehicle.FireOutput( this, pPassenger );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPropJeepEpisodic::NPC_FinishedExitVehicle( CAI_BaseNPC *pPassenger, bool bCompanion )
+{
+ // FIXME: This will be moved to the NPCs entering and exiting
+ // Fire our outputs
+ if ( bCompanion )
+ {
+ m_OnCompanionExitedVehicle.FireOutput( this, pPassenger );
+ }
+ else
+ {
+ m_OnHostileExitedVehicle.FireOutput( this, pPassenger );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pPassenger -
+// bCompanion -
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CPropJeepEpisodic::NPC_CanEnterVehicle( CAI_BaseNPC *pPassenger, bool bCompanion )
+{
+ // Must be unlocked
+ if ( bCompanion && m_bEntranceLocked )
+ return false;
+
+ return BaseClass::NPC_CanEnterVehicle( pPassenger, bCompanion );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pPassenger -
+// bCompanion -
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CPropJeepEpisodic::NPC_CanExitVehicle( CAI_BaseNPC *pPassenger, bool bCompanion )
+{
+ // Must be unlocked
+ if ( bCompanion && m_bExitLocked )
+ return false;
+
+ return BaseClass::NPC_CanExitVehicle( pPassenger, bCompanion );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPropJeepEpisodic::InputLockEntrance( inputdata_t &data )
+{
+ m_bEntranceLocked = true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPropJeepEpisodic::InputUnlockEntrance( inputdata_t &data )
+{
+ m_bEntranceLocked = false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPropJeepEpisodic::InputLockExit( inputdata_t &data )
+{
+ m_bExitLocked = true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPropJeepEpisodic::InputUnlockExit( inputdata_t &data )
+{
+ m_bExitLocked = false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Turn on the Jalopy radar device
+//-----------------------------------------------------------------------------
+void CPropJeepEpisodic::InputEnableRadar( inputdata_t &data )
+{
+ if( m_bRadarEnabled )
+ return; // Already enabled
+
+ SetBodygroup( JEEP_RADAR_BODYGROUP, 1 );
+
+ SpawnRadarPanel();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Turn off the Jalopy radar device
+//-----------------------------------------------------------------------------
+void CPropJeepEpisodic::InputDisableRadar( inputdata_t &data )
+{
+ if( !m_bRadarEnabled )
+ return; // Already disabled
+
+ SetBodygroup( JEEP_RADAR_BODYGROUP, 0 );
+
+ DestroyRadarPanel();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Allow the Jalopy radar to detect Hunters and Striders
+//-----------------------------------------------------------------------------
+void CPropJeepEpisodic::InputEnableRadarDetectEnemies( inputdata_t &data )
+{
+ m_bRadarDetectsEnemies = true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPropJeepEpisodic::InputAddBusterToCargo( inputdata_t &data )
+{
+ if ( m_hCargoProp != NULL)
+ {
+ ReleasePropFromCargoHold();
+ m_hCargoProp = NULL;
+ }
+
+ CBaseEntity *pNewBomb = CreateEntityByName( "weapon_striderbuster" );
+ if ( pNewBomb )
+ {
+ DispatchSpawn( pNewBomb );
+ pNewBomb->Teleport( &m_hCargoTrigger->GetAbsOrigin(), NULL, NULL );
+ m_hCargoTrigger->AddCargo( pNewBomb );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CPropJeepEpisodic::PassengerInTransition( void )
+{
+ // FIXME: Big hack - we need a way to bridge this data better
+ // TODO: Get a list of passengers we can traverse instead
+ CNPC_Alyx *pAlyx = CNPC_Alyx::GetAlyx();
+ if ( pAlyx )
+ {
+ if ( pAlyx->GetPassengerState() == PASSENGER_STATE_ENTERING ||
+ pAlyx->GetPassengerState() == PASSENGER_STATE_EXITING )
+ return true;
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Override velocity if our passenger is transitioning or we're upside-down
+//-----------------------------------------------------------------------------
+Vector CPropJeepEpisodic::PhysGunLaunchVelocity( const Vector &forward, float flMass )
+{
+ // Disallow
+ if ( PassengerInTransition() )
+ return vec3_origin;
+
+ Vector vecPuntDir = BaseClass::PhysGunLaunchVelocity( forward, flMass );
+ vecPuntDir.z = 150.0f;
+ vecPuntDir *= 600.0f;
+ return vecPuntDir;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Rolls the vehicle when its trying to upright itself from a punt
+//-----------------------------------------------------------------------------
+AngularImpulse CPropJeepEpisodic::PhysGunLaunchAngularImpulse( void )
+{
+ if ( IsOverturned() )
+ return AngularImpulse( 0, 300, 0 );
+
+ // Don't spin randomly, always spin reliably
+ return AngularImpulse( 0, 0, 0 );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Get the upright strength based on what state we're in
+//-----------------------------------------------------------------------------
+float CPropJeepEpisodic::GetUprightStrength( void )
+{
+ // Lesser if overturned
+ if ( IsOverturned() )
+ return 2.0f;
+
+ return 0.0f;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPropJeepEpisodic::CreateCargoTrigger( void )
+{
+ if ( m_hCargoTrigger != NULL )
+ return;
+
+ int nAttachment = LookupAttachment( "cargo" );
+ if ( nAttachment )
+ {
+ Vector vecAttachOrigin;
+ Vector vecForward, vecRight, vecUp;
+ GetAttachment( nAttachment, vecAttachOrigin, &vecForward, &vecRight, &vecUp );
+
+ // Approx size of the hold
+ Vector vecMins( -8.0, -6.0, 0 );
+ Vector vecMaxs( 8.0, 6.0, 4.0 );
+
+ // NDebugOverlay::BoxDirection( vecAttachOrigin, vecMins, vecMaxs, vecForward, 255, 0, 0, 64, 4.0f );
+
+ // Create a trigger that lives for a small amount of time
+ m_hCargoTrigger = CVehicleCargoTrigger::Create( vecAttachOrigin, vecMins, vecMaxs, this );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: If the player uses the jeep while at the back, he gets ammo from the crate instead
+//-----------------------------------------------------------------------------
+void CPropJeepEpisodic::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
+{
+ // Fall back and get in the vehicle instead, skip giving ammo
+ BaseClass::BaseClass::Use( pActivator, pCaller, useType, value );
+}
+
+#define MIN_WHEEL_DUST_SPEED 5
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPropJeepEpisodic::UpdateWheelDust( void )
+{
+ // See if this wheel should emit dust
+ const vehicleparams_t *vehicleData = m_pServerVehicle->GetVehicleParams();
+ const vehicle_operatingparams_t *carState = m_pServerVehicle->GetVehicleOperatingParams();
+ bool bAllowDust = vehicleData->steering.dustCloud;
+
+ // Car must be active
+ bool bCarOn = m_VehiclePhysics.IsOn();
+
+ // Must be moving quickly enough or skidding along the ground
+ bool bCreateDust = ( bCarOn &&
+ bAllowDust &&
+ ( m_VehiclePhysics.GetSpeed() >= MIN_WHEEL_DUST_SPEED || carState->skidSpeed > DEFAULT_SKID_THRESHOLD ) );
+
+ // Update our wheel dust
+ Vector vecPos;
+ for ( int i = 0; i < NUM_WHEEL_EFFECTS; i++ )
+ {
+ m_pServerVehicle->GetWheelContactPoint( i, vecPos );
+
+ // Make sure the effect is created
+ if ( m_hWheelDust[i] == NULL )
+ {
+ // Create the dust effect in place
+ m_hWheelDust[i] = (CParticleSystem *) CreateEntityByName( "info_particle_system" );
+ if ( m_hWheelDust[i] == NULL )
+ continue;
+
+ // Setup our basic parameters
+ m_hWheelDust[i]->KeyValue( "start_active", "0" );
+ m_hWheelDust[i]->KeyValue( "effect_name", "WheelDust" );
+ m_hWheelDust[i]->SetParent( this );
+ m_hWheelDust[i]->SetLocalOrigin( vec3_origin );
+ DispatchSpawn( m_hWheelDust[i] );
+ if ( gpGlobals->curtime > 0.5f )
+ m_hWheelDust[i]->Activate();
+ }
+
+ // Make sure the effect is created
+ if ( m_hWheelWater[i] == NULL )
+ {
+ // Create the dust effect in place
+ m_hWheelWater[i] = (CParticleSystem *) CreateEntityByName( "info_particle_system" );
+ if ( m_hWheelWater[i] == NULL )
+ continue;
+
+ // Setup our basic parameters
+ m_hWheelWater[i]->KeyValue( "start_active", "0" );
+ m_hWheelWater[i]->KeyValue( "effect_name", "WheelSplash" );
+ m_hWheelWater[i]->SetParent( this );
+ m_hWheelWater[i]->SetLocalOrigin( vec3_origin );
+ DispatchSpawn( m_hWheelWater[i] );
+ if ( gpGlobals->curtime > 0.5f )
+ m_hWheelWater[i]->Activate();
+ }
+
+ // Turn the dust on or off
+ if ( bCreateDust )
+ {
+ // Angle the dust out away from the wheels
+ Vector vecForward, vecRight, vecUp;
+ GetVectors( &vecForward, &vecRight, &vecUp );
+
+ const vehicle_controlparams_t *vehicleControls = m_pServerVehicle->GetVehicleControlParams();
+ float flWheelDir = ( i & 1 ) ? 1.0f : -1.0f;
+ QAngle vecAngles;
+ vecForward += vecRight * flWheelDir;
+ vecForward += vecRight * (vehicleControls->steering*0.5f) * flWheelDir;
+ vecForward += vecUp;
+ VectorAngles( vecForward, vecAngles );
+
+ // NDebugOverlay::Axis( vecPos, vecAngles, 8.0f, true, 0.1f );
+
+ if ( m_WaterData.m_bWheelInWater[i] )
+ {
+ m_hWheelDust[i]->StopParticleSystem();
+
+ // Set us up in the right position
+ m_hWheelWater[i]->StartParticleSystem();
+ m_hWheelWater[i]->SetAbsAngles( vecAngles );
+ m_hWheelWater[i]->SetAbsOrigin( vecPos + Vector( 0, 0, 8 ) );
+
+ if ( m_flNextWaterSound < gpGlobals->curtime )
+ {
+ m_flNextWaterSound = gpGlobals->curtime + random->RandomFloat( 0.25f, 1.0f );
+ EmitSound( "Physics.WaterSplash" );
+ }
+ }
+ else
+ {
+ m_hWheelWater[i]->StopParticleSystem();
+
+ // Set us up in the right position
+ m_hWheelDust[i]->StartParticleSystem();
+ m_hWheelDust[i]->SetAbsAngles( vecAngles );
+ m_hWheelDust[i]->SetAbsOrigin( vecPos + Vector( 0, 0, 8 ) );
+ }
+ }
+ else
+ {
+ // Stop emitting
+ m_hWheelDust[i]->StopParticleSystem();
+ m_hWheelWater[i]->StopParticleSystem();
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+ConVar jalopy_radar_test_ent( "jalopy_radar_test_ent", "none" );
+
+//-----------------------------------------------------------------------------
+// Purpose: Search for things that the radar detects, and stick them in the
+// UTILVector that gets sent to the client for radar display.
+//-----------------------------------------------------------------------------
+void CPropJeepEpisodic::UpdateRadar( bool forceUpdate )
+{
+ bool bDetectedDog = false;
+
+ if( !m_bRadarEnabled )
+ return;
+
+ if( !forceUpdate && gpGlobals->curtime < m_flNextRadarUpdateTime )
+ return;
+
+ // Count the targets on radar. If any more targets come on the radar, we beep.
+ int m_iNumOldRadarContacts = m_iNumRadarContacts;
+
+ m_flNextRadarUpdateTime = gpGlobals->curtime + RADAR_UPDATE_FREQUENCY;
+ m_iNumRadarContacts = 0;
+
+ CBaseEntity *pEnt = gEntList.FirstEnt();
+ string_t iszRadarTarget = FindPooledString( "info_radar_target" );
+ string_t iszStriderName = FindPooledString( "npc_strider" );
+ string_t iszHunterName = FindPooledString( "npc_hunter" );
+
+ string_t iszTestName = FindPooledString( jalopy_radar_test_ent.GetString() );
+
+ Vector vecJalopyOrigin = WorldSpaceCenter();
+
+ while( pEnt != NULL )
+ {
+ int type = RADAR_CONTACT_NONE;
+
+ if( pEnt->m_iClassname == iszRadarTarget )
+ {
+ CRadarTarget *pTarget = dynamic_cast<CRadarTarget*>(pEnt);
+
+ if( pTarget != NULL && !pTarget->IsDisabled() )
+ {
+ if( pTarget->m_flRadius < 0 || vecJalopyOrigin.DistToSqr(pTarget->GetAbsOrigin()) <= Square(pTarget->m_flRadius) )
+ {
+ // This item has been detected.
+ type = pTarget->GetType();
+
+ if( type == RADAR_CONTACT_DOG )
+ bDetectedDog = true;// used to prevent Alyx talking about the radar (see below)
+
+ if( pTarget->GetMode() == RADAR_MODE_STICKY )
+ {
+ // This beacon was just detected. Now change the radius to infinite
+ // so that it will never go off the radar due to distance.
+ pTarget->m_flRadius = -1;
+ }
+ }
+ }
+ }
+ else if ( m_bRadarDetectsEnemies )
+ {
+ if ( pEnt->m_iClassname == iszStriderName )
+ {
+ CNPC_Strider *pStrider = dynamic_cast<CNPC_Strider*>(pEnt);
+
+ if( !pStrider || !pStrider->CarriedByDropship() )
+ {
+ // Ignore striders which are carried by dropships.
+ type = RADAR_CONTACT_LARGE_ENEMY;
+ }
+ }
+
+ if ( pEnt->m_iClassname == iszHunterName )
+ {
+ type = RADAR_CONTACT_ENEMY;
+ }
+ }
+
+ if( type != RADAR_CONTACT_NONE )
+ {
+ Vector vecPos = pEnt->WorldSpaceCenter();
+
+ m_vecRadarContactPos.Set( m_iNumRadarContacts, vecPos );
+ m_iRadarContactType.Set( m_iNumRadarContacts, type );
+ m_iNumRadarContacts++;
+
+ if( m_iNumRadarContacts == RADAR_MAX_CONTACTS )
+ break;
+ }
+
+ pEnt = gEntList.NextEnt(pEnt);
+ }
+
+ if( m_iNumRadarContacts > m_iNumOldRadarContacts )
+ {
+ // Play a bleepy sound
+ if( !bDetectedDog )
+ {
+ EmitSound( "JNK_Radar_Ping_Friendly" );
+ }
+
+ //Notify Alyx so she can talk about the radar contact
+ CNPC_Alyx *pAlyx = CNPC_Alyx::GetAlyx();
+
+ if( !bDetectedDog && pAlyx != NULL && pAlyx->GetVehicle() )
+ {
+ pAlyx->SpeakIfAllowed( TLK_PASSENGER_NEW_RADAR_CONTACT );
+ }
+ }
+
+ if( bDetectedDog )
+ {
+ // Update the radar much more frequently when dog is around.
+ m_flNextRadarUpdateTime = gpGlobals->curtime + RADAR_UPDATE_FREQUENCY_FAST;
+ }
+
+ //Msg("Server detected %d objects\n", m_iNumRadarContacts );
+
+ CBasePlayer *pPlayer = AI_GetSinglePlayer();
+ CSingleUserRecipientFilter filter(pPlayer);
+ UserMessageBegin( filter, "UpdateJalopyRadar" );
+ WRITE_BYTE( 0 ); // end marker
+ MessageEnd(); // send message
+}
+
+ConVar jalopy_cargo_anim_time( "jalopy_cargo_anim_time", "1.0" );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPropJeepEpisodic::UpdateCargoEntry( void )
+{
+ // Don't bother if we have no prop to move
+ if ( m_hCargoProp == NULL )
+ return;
+
+ // If we're past our animation point, then we're already done
+ if ( m_flCargoStartTime + jalopy_cargo_anim_time.GetFloat() < gpGlobals->curtime )
+ {
+ // Close the hold immediately if we're finished
+ if ( m_bAddingCargo )
+ {
+ m_flAmmoCrateCloseTime = gpGlobals->curtime;
+ m_bAddingCargo = false;
+ }
+
+ return;
+ }
+
+ // Get our target point
+ int nAttachment = LookupAttachment( "cargo" );
+ Vector vecTarget, vecOut;
+ QAngle vecAngles;
+ GetAttachmentLocal( nAttachment, vecTarget, vecAngles );
+
+ // Find where we are in the blend and bias it for a fast entry and slow ease-out
+ float flPerc = (jalopy_cargo_anim_time.GetFloat()) ? (( gpGlobals->curtime - m_flCargoStartTime ) / jalopy_cargo_anim_time.GetFloat()) : 1.0f;
+ flPerc = Bias( flPerc, 0.75f );
+ VectorLerp( m_hCargoProp->GetLocalOrigin(), vecTarget, flPerc, vecOut );
+
+ // Get our target orientation
+ CPhysicsProp *pProp = dynamic_cast<CPhysicsProp *>(m_hCargoProp.Get());
+ if ( pProp == NULL )
+ return;
+
+ // Slerp our quaternions to find where we are this frame
+ Quaternion qtTarget;
+ QAngle qa( 0, 90, 0 );
+ qa += pProp->PreferredCarryAngles();
+ AngleQuaternion( qa, qtTarget ); // FIXME: Find the real offset to make this sit properly
+ Quaternion qtCurrent;
+ AngleQuaternion( pProp->GetLocalAngles(), qtCurrent );
+
+ Quaternion qtOut;
+ QuaternionSlerp( qtCurrent, qtTarget, flPerc, qtOut );
+
+ // Put it back to angles
+ QuaternionAngles( qtOut, vecAngles );
+
+ // Finally, take these new position
+ m_hCargoProp->SetLocalOrigin( vecOut );
+ m_hCargoProp->SetLocalAngles( vecAngles );
+
+ // Push the closing out into the future to make sure we don't try and close at the same time
+ m_flAmmoCrateCloseTime += gpGlobals->frametime;
+}
+
+#define VEHICLE_AVOID_BROADCAST_RATE 0.5f
+
+//-----------------------------------------------------------------------------
+// Purpose: This function isn't really what we want
+//-----------------------------------------------------------------------------
+void CPropJeepEpisodic::CreateAvoidanceZone( void )
+{
+ if ( m_flNextAvoidBroadcastTime > gpGlobals->curtime )
+ return;
+
+ // Only do this when we're stopped
+ if ( m_VehiclePhysics.GetSpeed() > 5.0f )
+ return;
+
+ float flHullRadius = CollisionProp()->BoundingRadius2D();
+
+ Vector vecPos;
+ CollisionProp()->NormalizedToWorldSpace( Vector( 0.5f, 0.33f, 0.25f ), &vecPos );
+ CSoundEnt::InsertSound( SOUND_MOVE_AWAY, vecPos, (flHullRadius*0.4f), VEHICLE_AVOID_BROADCAST_RATE, this );
+ // NDebugOverlay::Sphere( vecPos, vec3_angle, flHullRadius*0.4f, 255, 0, 0, 0, true, VEHICLE_AVOID_BROADCAST_RATE );
+
+ CollisionProp()->NormalizedToWorldSpace( Vector( 0.5f, 0.66f, 0.25f ), &vecPos );
+ CSoundEnt::InsertSound( SOUND_MOVE_AWAY, vecPos, (flHullRadius*0.4f), VEHICLE_AVOID_BROADCAST_RATE, this );
+ // NDebugOverlay::Sphere( vecPos, vec3_angle, flHullRadius*0.4f, 255, 0, 0, 0, true, VEHICLE_AVOID_BROADCAST_RATE );
+
+ // Don't broadcast again until these are done
+ m_flNextAvoidBroadcastTime = gpGlobals->curtime + VEHICLE_AVOID_BROADCAST_RATE;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPropJeepEpisodic::Think( void )
+{
+ BaseClass::Think();
+
+ // If our passenger is transitioning, then don't let the player drive off
+ CNPC_Alyx *pAlyx = CNPC_Alyx::GetAlyx();
+ if ( pAlyx && pAlyx->GetPassengerState() == PASSENGER_STATE_EXITING )
+ {
+ m_throttleDisableTime = gpGlobals->curtime + 0.25f;
+ }
+
+ // Update our cargo entering our hold
+ UpdateCargoEntry();
+
+ // See if the wheel dust should be on or off
+ UpdateWheelDust();
+
+ // Update the radar, of course.
+ UpdateRadar();
+
+ if ( m_hCargoTrigger && !m_hCargoProp && !m_hCargoTrigger->m_pfnTouch )
+ {
+ m_hCargoTrigger->Enable();
+ }
+
+ CreateAvoidanceZone();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pEntity -
+//-----------------------------------------------------------------------------
+void CPropJeepEpisodic::AddPropToCargoHold( CPhysicsProp *pProp )
+{
+ // The hold must be empty to add something to it
+ if ( m_hCargoProp != NULL )
+ {
+ Assert( 0 );
+ return;
+ }
+
+ // Take the prop as our cargo
+ m_hCargoProp = pProp;
+ m_flCargoStartTime = gpGlobals->curtime;
+ m_bAddingCargo = true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Drops the cargo from the hold
+//-----------------------------------------------------------------------------
+void CPropJeepEpisodic::ReleasePropFromCargoHold( void )
+{
+ // Pull the object free!
+ m_hCargoProp->SetParent( NULL );
+ m_hCargoProp->CreateVPhysics();
+
+ if ( m_hCargoTrigger )
+ {
+ m_hCargoTrigger->Enable();
+ m_hCargoTrigger->IgnoreTouches( m_hCargoProp );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: If the player is trying to pull the cargo out of the hold using the physcannon, let him
+// Output : Returns the cargo to pick up, if all the conditions are met
+//-----------------------------------------------------------------------------
+CBaseEntity *CPropJeepEpisodic::OnFailedPhysGunPickup( Vector vPhysgunPos )
+{
+ // Make sure we're available to open
+ if ( m_hCargoProp != NULL )
+ {
+ // Player's forward direction
+ Vector vecPlayerForward;
+ CBasePlayer *pPlayer = AI_GetSinglePlayer();
+ if ( pPlayer == NULL )
+ return NULL;
+
+ pPlayer->EyeVectors( &vecPlayerForward );
+
+ // Origin and facing of the cargo hold
+ Vector vecCargoOrigin;
+ Vector vecCargoForward;
+ GetAttachment( "cargo", vecCargoOrigin, &vecCargoForward );
+
+ // Direction from the cargo to the player's position
+ Vector vecPickupDir = ( vecCargoOrigin - vPhysgunPos );
+ float flDist = VectorNormalize( vecPickupDir );
+
+ // We need to make sure the player's position is within a cone near the opening and that they're also facing the right way
+ bool bInCargoRange = ( (flDist < (15.0f * 12.0f)) && DotProduct( vecCargoForward, vecPickupDir ) < 0.1f );
+ bool bFacingCargo = DotProduct( vecPlayerForward, vecPickupDir ) > 0.975f;
+
+ // If we're roughly pulling at the item, pick that up
+ if ( bInCargoRange && bFacingCargo )
+ {
+ // Save this for later
+ CBaseEntity *pCargo = m_hCargoProp;
+
+ // Drop the cargo
+ ReleasePropFromCargoHold();
+
+ // Forget the item but pass it back as the object to pick up
+ m_hCargoProp = NULL;
+ return pCargo;
+ }
+ }
+
+ return BaseClass::OnFailedPhysGunPickup( vPhysgunPos );
+}
+
+// adds a collision solver for any small props that are stuck under the vehicle
+static void SolveBlockingProps( CPropJeepEpisodic *pVehicleEntity, IPhysicsObject *pVehiclePhysics )
+{
+ CUtlVector<CBaseEntity *> solveList;
+ float vehicleMass = pVehiclePhysics->GetMass();
+ Vector vehicleUp;
+ pVehicleEntity->GetVectors( NULL, NULL, &vehicleUp );
+ IPhysicsFrictionSnapshot *pSnapshot = pVehiclePhysics->CreateFrictionSnapshot();
+ while ( pSnapshot->IsValid() )
+ {
+ IPhysicsObject *pOther = pSnapshot->GetObject(1);
+ float otherMass = pOther->GetMass();
+ CBaseEntity *pOtherEntity = static_cast<CBaseEntity *>(pOther->GetGameData());
+ Assert(pOtherEntity);
+ if ( pOtherEntity && pOtherEntity->GetMoveType() == MOVETYPE_VPHYSICS && pOther->IsMoveable() && (otherMass*4.0f) < vehicleMass )
+ {
+ Vector normal;
+ pSnapshot->GetSurfaceNormal(normal);
+ // this points down in the car's reference frame, then it's probably trapped under the car
+ if ( DotProduct(normal, vehicleUp) < -0.9f )
+ {
+ Vector point, pointLocal;
+ pSnapshot->GetContactPoint(point);
+ VectorITransform( point, pVehicleEntity->EntityToWorldTransform(), pointLocal );
+ Vector bottomPoint = physcollision->CollideGetExtent( pVehiclePhysics->GetCollide(), vec3_origin, vec3_angle, Vector(0,0,-1) );
+ // make sure it's under the bottom of the car
+ float bottomPlane = DotProduct(bottomPoint,vehicleUp)+8; // 8 inches above bottom
+ if ( DotProduct( pointLocal, vehicleUp ) <= bottomPlane )
+ {
+ //Msg("Solved %s\n", pOtherEntity->GetClassname());
+ if ( solveList.Find(pOtherEntity) < 0 )
+ {
+ solveList.AddToTail(pOtherEntity);
+ }
+ }
+ }
+ }
+ pSnapshot->NextFrictionData();
+ }
+ pVehiclePhysics->DestroyFrictionSnapshot( pSnapshot );
+ if ( solveList.Count() )
+ {
+ for ( int i = 0; i < solveList.Count(); i++ )
+ {
+ EntityPhysics_CreateSolver( pVehicleEntity, solveList[i], true, 4.0f );
+ }
+ pVehiclePhysics->RecheckContactPoints();
+ }
+}
+
+static void SimpleCollisionResponse( Vector velocityIn, const Vector &normal, float coefficientOfRestitution, Vector *pVelocityOut )
+{
+ Vector Vn = DotProduct(velocityIn,normal) * normal;
+ Vector Vt = velocityIn - Vn;
+ *pVelocityOut = Vt - coefficientOfRestitution * Vn;
+}
+
+static void KillBlockingEnemyNPCs( CBasePlayer *pPlayer, CBaseEntity *pVehicleEntity, IPhysicsObject *pVehiclePhysics )
+{
+ Vector velocity;
+ pVehiclePhysics->GetVelocity( &velocity, NULL );
+ float vehicleMass = pVehiclePhysics->GetMass();
+
+ // loop through the contacts and look for enemy NPCs that we're pushing on
+ CUtlVector<CAI_BaseNPC *> npcList;
+ CUtlVector<Vector> forceList;
+ CUtlVector<Vector> contactList;
+ IPhysicsFrictionSnapshot *pSnapshot = pVehiclePhysics->CreateFrictionSnapshot();
+ while ( pSnapshot->IsValid() )
+ {
+ IPhysicsObject *pOther = pSnapshot->GetObject(1);
+ float otherMass = pOther->GetMass();
+ CBaseEntity *pOtherEntity = static_cast<CBaseEntity *>(pOther->GetGameData());
+ CAI_BaseNPC *pNPC = pOtherEntity ? pOtherEntity->MyNPCPointer() : NULL;
+ // Is this an enemy NPC with a small enough mass?
+ if ( pNPC && pPlayer->IRelationType(pNPC) != D_LI && ((otherMass*2.0f) < vehicleMass) )
+ {
+ // accumulate the stress force for this NPC in the lsit
+ float force = pSnapshot->GetNormalForce();
+ Vector normal;
+ pSnapshot->GetSurfaceNormal(normal);
+ normal *= force;
+ int index = npcList.Find(pNPC);
+ if ( index < 0 )
+ {
+ vphysicsupdateai_t *pUpdate = NULL;
+ if ( pNPC->VPhysicsGetObject() && pNPC->VPhysicsGetObject()->GetShadowController() && pNPC->GetMoveType() == MOVETYPE_STEP )
+ {
+ if ( pNPC->HasDataObjectType(VPHYSICSUPDATEAI) )
+ {
+ pUpdate = static_cast<vphysicsupdateai_t *>(pNPC->GetDataObject(VPHYSICSUPDATEAI));
+ // kill this guy if I've been pushing him for more than half a second and I'm
+ // still pushing in his direction
+ if ( (gpGlobals->curtime - pUpdate->startUpdateTime) > 0.5f && DotProduct(velocity,normal) > 0)
+ {
+ index = npcList.AddToTail(pNPC);
+ forceList.AddToTail( normal );
+ Vector pos;
+ pSnapshot->GetContactPoint(pos);
+ contactList.AddToTail(pos);
+ }
+ }
+ else
+ {
+ pUpdate = static_cast<vphysicsupdateai_t *>(pNPC->CreateDataObject( VPHYSICSUPDATEAI ));
+ pUpdate->startUpdateTime = gpGlobals->curtime;
+ }
+ // update based on vphysics for the next second
+ // this allows the car to push the NPC
+ pUpdate->stopUpdateTime = gpGlobals->curtime + 1.0f;
+ float maxAngular;
+ pNPC->VPhysicsGetObject()->GetShadowController()->GetMaxSpeed( &pUpdate->savedShadowControllerMaxSpeed, &maxAngular );
+ pNPC->VPhysicsGetObject()->GetShadowController()->MaxSpeed( 1.0f, maxAngular );
+ }
+ }
+ else
+ {
+ forceList[index] += normal;
+ }
+ }
+ pSnapshot->NextFrictionData();
+ }
+ pVehiclePhysics->DestroyFrictionSnapshot( pSnapshot );
+ // now iterate the list and check each cumulative force against the threshold
+ if ( npcList.Count() )
+ {
+ for ( int i = npcList.Count(); --i >= 0; )
+ {
+ Vector damageForce;
+ npcList[i]->VPhysicsGetObject()->GetVelocity( &damageForce, NULL );
+ Vector vel;
+ pVehiclePhysics->GetVelocityAtPoint( contactList[i], &vel );
+ damageForce -= vel;
+ Vector normal = forceList[i];
+ VectorNormalize(normal);
+ SimpleCollisionResponse( damageForce, normal, 1.0, &damageForce );
+ damageForce += (normal * 300.0f);
+ damageForce *= npcList[i]->VPhysicsGetObject()->GetMass();
+ float len = damageForce.Length();
+ damageForce.z += len*phys_upimpactforcescale.GetFloat();
+ Vector vehicleForce = -damageForce;
+
+ CTakeDamageInfo dmgInfo( pVehicleEntity, pVehicleEntity, damageForce, contactList[i], 200.0f, DMG_CRUSH|DMG_VEHICLE );
+ npcList[i]->TakeDamage( dmgInfo );
+ pVehiclePhysics->ApplyForceOffset( vehicleForce, contactList[i] );
+ PhysCollisionSound( pVehicleEntity, npcList[i]->VPhysicsGetObject(), CHAN_BODY, pVehiclePhysics->GetMaterialIndex(), npcList[i]->VPhysicsGetObject()->GetMaterialIndex(), gpGlobals->frametime, 200.0f );
+ }
+ }
+}
+
+void CPropJeepEpisodic::DriveVehicle( float flFrameTime, CUserCmd *ucmd, int iButtonsDown, int iButtonsReleased )
+{
+ /* The car headlight hurts perf, there's no timer to turn it off automatically,
+ and we haven't built any gameplay around it.
+
+ Furthermore, I don't think I've ever seen a playtester turn it on.
+
+ if ( ucmd->impulse == 100 )
+ {
+ if (HeadlightIsOn())
+ {
+ HeadlightTurnOff();
+ }
+ else
+ {
+ HeadlightTurnOn();
+ }
+ }*/
+
+ if ( ucmd->forwardmove != 0.0f )
+ {
+ //Msg("Push V: %.2f, %.2f, %.2f\n", ucmd->forwardmove, carState->engineRPM, carState->speed );
+ CBasePlayer *pPlayer = ToBasePlayer(GetDriver());
+
+ if ( pPlayer && VPhysicsGetObject() )
+ {
+ KillBlockingEnemyNPCs( pPlayer, this, VPhysicsGetObject() );
+ SolveBlockingProps( this, VPhysicsGetObject() );
+ }
+ }
+ BaseClass::DriveVehicle(flFrameTime, ucmd, iButtonsDown, iButtonsReleased);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPropJeepEpisodic::CreateHazardLights( void )
+{
+ static const char *s_szAttach[NUM_HAZARD_LIGHTS] =
+ {
+ "rearlight_r",
+ "rearlight_l",
+ "headlight_r",
+ "headlight_l",
+ };
+
+ // Turn on the hazards!
+ for ( int i = 0; i < NUM_HAZARD_LIGHTS; i++ )
+ {
+ if ( m_hHazardLights[i] == NULL )
+ {
+ m_hHazardLights[i] = CSprite::SpriteCreate( s_szHazardSprite, GetLocalOrigin(), false );
+ if ( m_hHazardLights[i] )
+ {
+ m_hHazardLights[i]->SetTransparency( kRenderWorldGlow, 255, 220, 40, 255, kRenderFxNoDissipation );
+ m_hHazardLights[i]->SetAttachment( this, LookupAttachment( s_szAttach[i] ) );
+ m_hHazardLights[i]->SetGlowProxySize( 2.0f );
+ m_hHazardLights[i]->TurnOff();
+ if ( i < 2 )
+ {
+ // Rear lights are red
+ m_hHazardLights[i]->SetColor( 255, 0, 0 );
+ m_hHazardLights[i]->SetScale( 1.0f );
+ }
+ else
+ {
+ // Font lights are white
+ m_hHazardLights[i]->SetScale( 1.0f );
+ }
+ }
+ }
+ }
+
+ // We start off
+ m_bBlink = false;
+
+ // Setup our blink
+ SetContextThink( &CPropJeepEpisodic::HazardBlinkThink, gpGlobals->curtime + 0.1f, "HazardBlink" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPropJeepEpisodic::DestroyHazardLights( void )
+{
+ for ( int i = 0; i < NUM_HAZARD_LIGHTS; i++ )
+ {
+ if ( m_hHazardLights[i] != NULL )
+ {
+ UTIL_Remove( m_hHazardLights[i] );
+ }
+ }
+
+ SetContextThink( NULL, gpGlobals->curtime, "HazardBlink" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : nRole -
+//-----------------------------------------------------------------------------
+void CPropJeepEpisodic::ExitVehicle( int nRole )
+{
+ BaseClass::ExitVehicle( nRole );
+
+ CreateHazardLights();
+}
+
+void CPropJeepEpisodic::SetBusterHopperVisibility(bool visible)
+{
+ // if we're there already do nothing
+ if (visible == m_bBusterHopperVisible)
+ return;
+
+ SetBodygroup( JEEP_HOPPER_BODYGROUP, visible ? 1 : 0);
+ m_bBusterHopperVisible = visible;
+}
+
+
+void CPropJeepEpisodic::InputSetCargoVisibility( inputdata_t &data )
+{
+ bool visible = data.value.Bool();
+
+ SetBusterHopperVisibility( visible );
+}
+
+//-----------------------------------------------------------------------------
+// THIS CODE LIFTED RIGHT OUT OF TF2, to defer the pain of making vgui-on-an-entity
+// code available to all CBaseAnimating.
+//-----------------------------------------------------------------------------
+void CPropJeepEpisodic::SpawnRadarPanel()
+{
+ // FIXME: Deal with dynamically resizing control panels?
+
+ // If we're attached to an entity, spawn control panels on it instead of use
+ CBaseAnimating *pEntityToSpawnOn = this;
+ char *pOrgLL = "controlpanel0_ll";
+ char *pOrgUR = "controlpanel0_ur";
+
+ Assert( pEntityToSpawnOn );
+
+ // Lookup the attachment point...
+ int nLLAttachmentIndex = pEntityToSpawnOn->LookupAttachment(pOrgLL);
+
+ if (nLLAttachmentIndex <= 0)
+ {
+ return;
+ }
+
+ int nURAttachmentIndex = pEntityToSpawnOn->LookupAttachment(pOrgUR);
+ if (nURAttachmentIndex <= 0)
+ {
+ return;
+ }
+
+ const char *pScreenName = "jalopy_radar_panel";
+ const char *pScreenClassname = "vgui_screen";
+
+ // Compute the screen size from the attachment points...
+ matrix3x4_t panelToWorld;
+ pEntityToSpawnOn->GetAttachment( nLLAttachmentIndex, panelToWorld );
+
+ matrix3x4_t worldToPanel;
+ MatrixInvert( panelToWorld, worldToPanel );
+
+ // Now get the lower right position + transform into panel space
+ Vector lr, lrlocal;
+ pEntityToSpawnOn->GetAttachment( nURAttachmentIndex, panelToWorld );
+ MatrixGetColumn( panelToWorld, 3, lr );
+ VectorTransform( lr, worldToPanel, lrlocal );
+
+ float flWidth = lrlocal.x;
+ float flHeight = lrlocal.y;
+
+ CVGuiScreen *pScreen = CreateVGuiScreen( pScreenClassname, pScreenName, pEntityToSpawnOn, this, nLLAttachmentIndex );
+ pScreen->SetActualSize( flWidth, flHeight );
+ pScreen->SetActive( true );
+ pScreen->SetOverlayMaterial( RADAR_PANEL_WRITEZ );
+ pScreen->SetTransparency( true );
+
+ m_hRadarScreen.Set( pScreen );
+
+ m_bRadarEnabled = true;
+ m_iNumRadarContacts = 0;
+ m_flNextRadarUpdateTime = gpGlobals->curtime - 1.0f;
+}
+
+//-----------------------------------------------------------------------------
+void CPropJeepEpisodic::DestroyRadarPanel()
+{
+ Assert( m_hRadarScreen != NULL );
+ m_hRadarScreen->SUB_Remove();
+ m_bRadarEnabled = false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPropJeepEpisodic::HazardBlinkThink( void )
+{
+ if ( m_bBlink )
+ {
+ for ( int i = 0; i < NUM_HAZARD_LIGHTS; i++ )
+ {
+ if ( m_hHazardLights[i] )
+ {
+ m_hHazardLights[i]->SetBrightness( 0, 0.1f );
+ }
+ }
+
+ SetContextThink( &CPropJeepEpisodic::HazardBlinkThink, gpGlobals->curtime + 0.25f, "HazardBlink" );
+ }
+ else
+ {
+ for ( int i = 0; i < NUM_HAZARD_LIGHTS; i++ )
+ {
+ if ( m_hHazardLights[i] )
+ {
+ m_hHazardLights[i]->SetBrightness( 255, 0.1f );
+ m_hHazardLights[i]->TurnOn();
+ }
+ }
+
+ SetContextThink( &CPropJeepEpisodic::HazardBlinkThink, gpGlobals->curtime + 0.5f, "HazardBlink" );
+ }
+
+ m_bBlink = !m_bBlink;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPropJeepEpisodic::HandleWater( void )
+{
+ // Only check the wheels and engine in water if we have a driver (player).
+ if ( !GetDriver() )
+ return;
+
+ // Update our internal state
+ CheckWater();
+
+ // Save of data from last think.
+ for ( int iWheel = 0; iWheel < JEEP_WHEEL_COUNT; ++iWheel )
+ {
+ m_WaterData.m_bWheelWasInWater[iWheel] = m_WaterData.m_bWheelInWater[iWheel];
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Report our lock state
+//-----------------------------------------------------------------------------
+int CPropJeepEpisodic::DrawDebugTextOverlays( void )
+{
+ int text_offset = BaseClass::DrawDebugTextOverlays();
+
+ if ( m_debugOverlays & OVERLAY_TEXT_BIT )
+ {
+ EntityText( text_offset, CFmtStr("Entrance: %s", m_bEntranceLocked ? "Locked" : "Unlocked" ), 0 );
+ text_offset++;
+
+ EntityText( text_offset, CFmtStr("Exit: %s", m_bExitLocked ? "Locked" : "Unlocked" ), 0 );
+ text_offset++;
+ }
+
+ return text_offset;
+}
+
+#define TRANSITION_SEARCH_RADIUS (100*12)
+
+//-----------------------------------------------------------------------------
+// Purpose: Teleport the car to a destination that will cause it to transition if it's not going to otherwise
+//-----------------------------------------------------------------------------
+void CPropJeepEpisodic::InputOutsideTransition( inputdata_t &inputdata )
+{
+ // Teleport into the new map
+ CBasePlayer *pPlayer = AI_GetSinglePlayer();
+ Vector vecTeleportPos;
+ QAngle vecTeleportAngles;
+
+ // Get our bounds
+ Vector vecSurroundMins, vecSurroundMaxs;
+ CollisionProp()->WorldSpaceSurroundingBounds( &vecSurroundMins, &vecSurroundMaxs );
+ vecSurroundMins -= WorldSpaceCenter();
+ vecSurroundMaxs -= WorldSpaceCenter();
+
+ Vector vecBestPos;
+ QAngle vecBestAngles;
+
+ CInfoTargetVehicleTransition *pEntity = NULL;
+ bool bSucceeded = false;
+
+ // Find all entities of the correct name and try and sit where they're at
+ while ( ( pEntity = (CInfoTargetVehicleTransition *) gEntList.FindEntityByClassname( pEntity, "info_target_vehicle_transition" ) ) != NULL )
+ {
+ // Must be enabled
+ if ( pEntity->IsDisabled() )
+ continue;
+
+ // Must be within range
+ if ( ( pEntity->GetAbsOrigin() - pPlayer->GetAbsOrigin() ).LengthSqr() > Square( TRANSITION_SEARCH_RADIUS ) )
+ continue;
+
+ vecTeleportPos = pEntity->GetAbsOrigin();
+ vecTeleportAngles = pEntity->GetAbsAngles() + QAngle( 0, -90, 0 ); // Vehicle is always off by 90 degrees
+
+ // Rotate to face the destination angles
+ Vector vecMins;
+ Vector vecMaxs;
+ VectorRotate( vecSurroundMins, vecTeleportAngles, vecMins );
+ VectorRotate( vecSurroundMaxs, vecTeleportAngles, vecMaxs );
+
+ if ( vecMaxs.x < vecMins.x )
+ V_swap( vecMins.x, vecMaxs.x );
+
+ if ( vecMaxs.y < vecMins.y )
+ V_swap( vecMins.y, vecMaxs.y );
+
+ if ( vecMaxs.z < vecMins.z )
+ V_swap( vecMins.z, vecMaxs.z );
+
+ // Move up
+ vecTeleportPos.z += ( vecMaxs.z - vecMins.z );
+
+ trace_t tr;
+ UTIL_TraceHull( vecTeleportPos, vecTeleportPos - Vector( 0, 0, 128 ), vecMins, vecMaxs, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr );
+ if ( tr.startsolid == false && tr.allsolid == false && tr.fraction < 1.0f )
+ {
+ // Store this off
+ vecBestPos = tr.endpos;
+ vecBestAngles = vecTeleportAngles;
+ bSucceeded = true;
+
+ // If this point isn't visible, then stop looking and use it
+ if ( pPlayer->FInViewCone( tr.endpos ) == false )
+ break;
+ }
+ }
+
+ // See if we're finished
+ if ( bSucceeded )
+ {
+ Teleport( &vecTeleportPos, &vecTeleportAngles, NULL );
+ return;
+ }
+
+ // TODO: We found no valid teleport points, so try to find them dynamically
+ Warning("No valid vehicle teleport points!\n");
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Stop players punting the car around.
+//-----------------------------------------------------------------------------
+void CPropJeepEpisodic::InputDisablePhysGun( inputdata_t &data )
+{
+ AddEFlags( EFL_NO_PHYSCANNON_INTERACTION );
+}
+//-----------------------------------------------------------------------------
+// Purpose: Return to normal
+//-----------------------------------------------------------------------------
+void CPropJeepEpisodic::InputEnablePhysGun( inputdata_t &data )
+{
+ RemoveEFlags( EFL_NO_PHYSCANNON_INTERACTION );
+}
+
+//-----------------------------------------------------------------------------
+// Create and parent two radial node link controllers.
+//-----------------------------------------------------------------------------
+void CPropJeepEpisodic::InputCreateLinkController( inputdata_t &data )
+{
+ Vector vecFront, vecRear;
+ Vector vecWFL, vecWFR; // Front wheels
+ Vector vecWRL, vecWRR; // Back wheels
+
+ GetAttachment( "wheel_fr", vecWFR );
+ GetAttachment( "wheel_fl", vecWFL );
+
+ GetAttachment( "wheel_rr", vecWRR );
+ GetAttachment( "wheel_rl", vecWRL );
+
+ vecFront = (vecWFL + vecWFR) * 0.5f;
+ vecRear = (vecWRL + vecWRR) * 0.5f;
+
+ float flRadius = ( (vecFront - vecRear).Length() ) * 0.6f;
+
+ CAI_RadialLinkController *pLinkController = (CAI_RadialLinkController *)CreateEntityByName( "info_radial_link_controller" );
+ if( pLinkController != NULL && m_hLinkControllerFront.Get() == NULL )
+ {
+ pLinkController->m_flRadius = flRadius;
+ pLinkController->Spawn();
+ pLinkController->SetAbsOrigin( vecFront );
+ pLinkController->SetOwnerEntity( this );
+ pLinkController->SetParent( this );
+ pLinkController->Activate();
+ m_hLinkControllerFront.Set( pLinkController );
+
+ //NDebugOverlay::Circle( vecFront, Vector(0,1,0), Vector(1,0,0), flRadius, 255, 255, 255, 128, false, 100 );
+ }
+
+ pLinkController = (CAI_RadialLinkController *)CreateEntityByName( "info_radial_link_controller" );
+ if( pLinkController != NULL && m_hLinkControllerRear.Get() == NULL )
+ {
+ pLinkController->m_flRadius = flRadius;
+ pLinkController->Spawn();
+ pLinkController->SetAbsOrigin( vecRear );
+ pLinkController->SetOwnerEntity( this );
+ pLinkController->SetParent( this );
+ pLinkController->Activate();
+ m_hLinkControllerRear.Set( pLinkController );
+
+ //NDebugOverlay::Circle( vecRear, Vector(0,1,0), Vector(1,0,0), flRadius, 255, 255, 255, 128, false, 100 );
+ }
+}
+
+void CPropJeepEpisodic::InputDestroyLinkController( inputdata_t &data )
+{
+ if( m_hLinkControllerFront.Get() != NULL )
+ {
+ CAI_RadialLinkController *pLinkController = dynamic_cast<CAI_RadialLinkController*>(m_hLinkControllerFront.Get());
+ if( pLinkController != NULL )
+ {
+ pLinkController->ModifyNodeLinks(false);
+ UTIL_Remove( pLinkController );
+ m_hLinkControllerFront.Set(NULL);
+ }
+ }
+
+ if( m_hLinkControllerRear.Get() != NULL )
+ {
+ CAI_RadialLinkController *pLinkController = dynamic_cast<CAI_RadialLinkController*>(m_hLinkControllerRear.Get());
+ if( pLinkController != NULL )
+ {
+ pLinkController->ModifyNodeLinks(false);
+ UTIL_Remove( pLinkController );
+ m_hLinkControllerRear.Set(NULL);
+ }
+ }
+}
+
+
+bool CPropJeepEpisodic::AllowBlockedExit( CBaseCombatCharacter *pPassenger, int nRole )
+{
+ // Wait until we've settled down before we resort to blocked exits.
+ // This keeps us from doing blocked exits in mid-jump, which can cause mayhem like
+ // sticking the player through player clips or into geometry.
+ return GetSmoothedVelocity().IsLengthLessThan( jalopy_blocked_exit_max_speed.GetFloat() );
+}
+
diff --git a/mp/src/game/server/episodic/vehicle_jeep_episodic.h b/mp/src/game/server/episodic/vehicle_jeep_episodic.h
index 03be2d24..70cb5894 100644
--- a/mp/src/game/server/episodic/vehicle_jeep_episodic.h
+++ b/mp/src/game/server/episodic/vehicle_jeep_episodic.h
@@ -1,150 +1,150 @@
-//========= Copyright Valve Corporation, All rights reserved. ============//
-//
-// Purpose:
-//
-//=============================================================================
-
-#ifndef VEHICLE_JEEP_EPISODIC_H
-#define VEHICLE_JEEP_EPISODIC_H
-#ifdef _WIN32
-#pragma once
-#endif
-
-#include "vehicle_jeep.h"
-#include "ai_basenpc.h"
-#include "hl2_vehicle_radar.h"
-
-class CParticleSystem;
-class CVehicleCargoTrigger;
-class CSprite;
-
-#define NUM_WHEEL_EFFECTS 2
-#define NUM_HAZARD_LIGHTS 4
-
-//=============================================================================
-// Episodic jeep
-
-class CPropJeepEpisodic : public CPropJeep
-{
- DECLARE_CLASS( CPropJeepEpisodic, CPropJeep );
- DECLARE_SERVERCLASS();
-
-public:
- CPropJeepEpisodic( void );
-
- virtual void Spawn( void );
- virtual void Activate( void );
- virtual void Think( void );
- virtual void UpdateOnRemove( void );
-
- virtual void NPC_FinishedEnterVehicle( CAI_BaseNPC *pPassenger, bool bCompanion );
- virtual void NPC_FinishedExitVehicle( CAI_BaseNPC *pPassenger, bool bCompanion );
-
- virtual bool NPC_CanEnterVehicle( CAI_BaseNPC *pPassenger, bool bCompanion );
- virtual bool NPC_CanExitVehicle( CAI_BaseNPC *pPassenger, bool bCompanion );
- virtual void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
- virtual void Precache( void );
- virtual void EnterVehicle( CBaseCombatCharacter *pPassenger );
- virtual void ExitVehicle( int nRole );
- virtual bool AllowBlockedExit( CBaseCombatCharacter *pPassenger, int nRole );
-
- // Passengers take no damage except what we pass them
- virtual bool PassengerShouldReceiveDamage( CTakeDamageInfo &info )
- {
- if ( GetServerVehicle() && GetServerVehicle()->IsPassengerExiting() )
- return false;
-
- return ( info.GetDamageType() & DMG_VEHICLE ) != 0;
- }
-
- virtual int ObjectCaps( void ) { return (BaseClass::ObjectCaps() | FCAP_NOTIFY_ON_TRANSITION); }
-
- void SpawnRadarPanel();
- void DestroyRadarPanel();
- int NumRadarContacts() { return m_iNumRadarContacts; }
-
- void AddPropToCargoHold( CPhysicsProp *pProp );
-
- virtual CBaseEntity *OnFailedPhysGunPickup( Vector vPhysgunPos );
- virtual void DriveVehicle( float flFrameTime, CUserCmd *ucmd, int iButtonsDown, int iButtonsReleased );
- virtual int DrawDebugTextOverlays( void );
-
- DECLARE_DATADESC();
-
-protected:
- void HazardBlinkThink( void );
- void CreateHazardLights( void );
- void DestroyHazardLights( void );
-
- void UpdateCargoEntry( void );
- void ReleasePropFromCargoHold( void );
- void CreateCargoTrigger( void );
- virtual float GetUprightTime( void ) { return 1.0f; }
- virtual float GetUprightStrength( void );
- virtual bool ShouldPuntUseLaunchForces( PhysGunForce_t reason ) { return ( reason == PHYSGUN_FORCE_PUNTED ); }
- virtual void HandleWater( void );
-
- virtual AngularImpulse PhysGunLaunchAngularImpulse( void );
- virtual Vector PhysGunLaunchVelocity( const Vector &forward, float flMass );
- bool PassengerInTransition( void );
-
- void SetBusterHopperVisibility(bool visible);
-
-private:
-
- void UpdateWheelDust( void );
- void UpdateRadar( bool forceUpdate = false );
-
- void InputLockEntrance( inputdata_t &data );
- void InputUnlockEntrance( inputdata_t &data );
- void InputLockExit( inputdata_t &data );
- void InputUnlockExit( inputdata_t &data );
- void InputEnableRadar( inputdata_t &data );
- void InputDisableRadar( inputdata_t &data );
- void InputEnableRadarDetectEnemies( inputdata_t &data );
- void InputAddBusterToCargo( inputdata_t &data );
- void InputSetCargoVisibility( inputdata_t &data );
- void InputOutsideTransition( inputdata_t &data );
- void InputDisablePhysGun( inputdata_t &data );
- void InputEnablePhysGun( inputdata_t &data );
- void InputCreateLinkController( inputdata_t &data );
- void InputDestroyLinkController( inputdata_t &data );
- void CreateAvoidanceZone( void );
-
- bool m_bEntranceLocked;
- bool m_bExitLocked;
- bool m_bAddingCargo;
- bool m_bBlink;
-
- float m_flCargoStartTime; // Time when the cargo was first added to the vehicle (used for animating into hold)
- float m_flNextAvoidBroadcastTime; // Next time we'll warn entity to move out of us
-
- COutputEvent m_OnCompanionEnteredVehicle; // Passenger has completed entering the vehicle
- COutputEvent m_OnCompanionExitedVehicle; // Passenger has completed exited the vehicle
- COutputEvent m_OnHostileEnteredVehicle; // Passenger has completed entering the vehicle
- COutputEvent m_OnHostileExitedVehicle; // Passenger has completed exited the vehicle
-
- CHandle< CParticleSystem > m_hWheelDust[NUM_WHEEL_EFFECTS];
- CHandle< CParticleSystem > m_hWheelWater[NUM_WHEEL_EFFECTS];
- CHandle< CVehicleCargoTrigger > m_hCargoTrigger;
- CHandle< CPhysicsProp > m_hCargoProp;
-
- CHandle< CSprite > m_hHazardLights[NUM_HAZARD_LIGHTS];
- float m_flNextWaterSound;
-
- bool m_bRadarEnabled;
- bool m_bRadarDetectsEnemies;
- float m_flNextRadarUpdateTime;
- EHANDLE m_hRadarScreen;
-
- EHANDLE m_hLinkControllerFront;
- EHANDLE m_hLinkControllerRear;
-
- bool m_bBusterHopperVisible; // is the hopper assembly visible on the vehicle? please do not set this directly - use the accessor funct.
-
- CNetworkVar( int, m_iNumRadarContacts );
- CNetworkArray( Vector, m_vecRadarContactPos, RADAR_MAX_CONTACTS );
- CNetworkArray( int, m_iRadarContactType, RADAR_MAX_CONTACTS );
-};
-
-#endif // VEHICLE_JEEP_EPISODIC_H
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================
+
+#ifndef VEHICLE_JEEP_EPISODIC_H
+#define VEHICLE_JEEP_EPISODIC_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "vehicle_jeep.h"
+#include "ai_basenpc.h"
+#include "hl2_vehicle_radar.h"
+
+class CParticleSystem;
+class CVehicleCargoTrigger;
+class CSprite;
+
+#define NUM_WHEEL_EFFECTS 2
+#define NUM_HAZARD_LIGHTS 4
+
+//=============================================================================
+// Episodic jeep
+
+class CPropJeepEpisodic : public CPropJeep
+{
+ DECLARE_CLASS( CPropJeepEpisodic, CPropJeep );
+ DECLARE_SERVERCLASS();
+
+public:
+ CPropJeepEpisodic( void );
+
+ virtual void Spawn( void );
+ virtual void Activate( void );
+ virtual void Think( void );
+ virtual void UpdateOnRemove( void );
+
+ virtual void NPC_FinishedEnterVehicle( CAI_BaseNPC *pPassenger, bool bCompanion );
+ virtual void NPC_FinishedExitVehicle( CAI_BaseNPC *pPassenger, bool bCompanion );
+
+ virtual bool NPC_CanEnterVehicle( CAI_BaseNPC *pPassenger, bool bCompanion );
+ virtual bool NPC_CanExitVehicle( CAI_BaseNPC *pPassenger, bool bCompanion );
+ virtual void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
+ virtual void Precache( void );
+ virtual void EnterVehicle( CBaseCombatCharacter *pPassenger );
+ virtual void ExitVehicle( int nRole );
+ virtual bool AllowBlockedExit( CBaseCombatCharacter *pPassenger, int nRole );
+
+ // Passengers take no damage except what we pass them
+ virtual bool PassengerShouldReceiveDamage( CTakeDamageInfo &info )
+ {
+ if ( GetServerVehicle() && GetServerVehicle()->IsPassengerExiting() )
+ return false;
+
+ return ( info.GetDamageType() & DMG_VEHICLE ) != 0;
+ }
+
+ virtual int ObjectCaps( void ) { return (BaseClass::ObjectCaps() | FCAP_NOTIFY_ON_TRANSITION); }
+
+ void SpawnRadarPanel();
+ void DestroyRadarPanel();
+ int NumRadarContacts() { return m_iNumRadarContacts; }
+
+ void AddPropToCargoHold( CPhysicsProp *pProp );
+
+ virtual CBaseEntity *OnFailedPhysGunPickup( Vector vPhysgunPos );
+ virtual void DriveVehicle( float flFrameTime, CUserCmd *ucmd, int iButtonsDown, int iButtonsReleased );
+ virtual int DrawDebugTextOverlays( void );
+
+ DECLARE_DATADESC();
+
+protected:
+ void HazardBlinkThink( void );
+ void CreateHazardLights( void );
+ void DestroyHazardLights( void );
+
+ void UpdateCargoEntry( void );
+ void ReleasePropFromCargoHold( void );
+ void CreateCargoTrigger( void );
+ virtual float GetUprightTime( void ) { return 1.0f; }
+ virtual float GetUprightStrength( void );
+ virtual bool ShouldPuntUseLaunchForces( PhysGunForce_t reason ) { return ( reason == PHYSGUN_FORCE_PUNTED ); }
+ virtual void HandleWater( void );
+
+ virtual AngularImpulse PhysGunLaunchAngularImpulse( void );
+ virtual Vector PhysGunLaunchVelocity( const Vector &forward, float flMass );
+ bool PassengerInTransition( void );
+
+ void SetBusterHopperVisibility(bool visible);
+
+private:
+
+ void UpdateWheelDust( void );
+ void UpdateRadar( bool forceUpdate = false );
+
+ void InputLockEntrance( inputdata_t &data );
+ void InputUnlockEntrance( inputdata_t &data );
+ void InputLockExit( inputdata_t &data );
+ void InputUnlockExit( inputdata_t &data );
+ void InputEnableRadar( inputdata_t &data );
+ void InputDisableRadar( inputdata_t &data );
+ void InputEnableRadarDetectEnemies( inputdata_t &data );
+ void InputAddBusterToCargo( inputdata_t &data );
+ void InputSetCargoVisibility( inputdata_t &data );
+ void InputOutsideTransition( inputdata_t &data );
+ void InputDisablePhysGun( inputdata_t &data );
+ void InputEnablePhysGun( inputdata_t &data );
+ void InputCreateLinkController( inputdata_t &data );
+ void InputDestroyLinkController( inputdata_t &data );
+ void CreateAvoidanceZone( void );
+
+ bool m_bEntranceLocked;
+ bool m_bExitLocked;
+ bool m_bAddingCargo;
+ bool m_bBlink;
+
+ float m_flCargoStartTime; // Time when the cargo was first added to the vehicle (used for animating into hold)
+ float m_flNextAvoidBroadcastTime; // Next time we'll warn entity to move out of us
+
+ COutputEvent m_OnCompanionEnteredVehicle; // Passenger has completed entering the vehicle
+ COutputEvent m_OnCompanionExitedVehicle; // Passenger has completed exited the vehicle
+ COutputEvent m_OnHostileEnteredVehicle; // Passenger has completed entering the vehicle
+ COutputEvent m_OnHostileExitedVehicle; // Passenger has completed exited the vehicle
+
+ CHandle< CParticleSystem > m_hWheelDust[NUM_WHEEL_EFFECTS];
+ CHandle< CParticleSystem > m_hWheelWater[NUM_WHEEL_EFFECTS];
+ CHandle< CVehicleCargoTrigger > m_hCargoTrigger;
+ CHandle< CPhysicsProp > m_hCargoProp;
+
+ CHandle< CSprite > m_hHazardLights[NUM_HAZARD_LIGHTS];
+ float m_flNextWaterSound;
+
+ bool m_bRadarEnabled;
+ bool m_bRadarDetectsEnemies;
+ float m_flNextRadarUpdateTime;
+ EHANDLE m_hRadarScreen;
+
+ EHANDLE m_hLinkControllerFront;
+ EHANDLE m_hLinkControllerRear;
+
+ bool m_bBusterHopperVisible; // is the hopper assembly visible on the vehicle? please do not set this directly - use the accessor funct.
+
+ CNetworkVar( int, m_iNumRadarContacts );
+ CNetworkArray( Vector, m_vecRadarContactPos, RADAR_MAX_CONTACTS );
+ CNetworkArray( int, m_iRadarContactType, RADAR_MAX_CONTACTS );
+};
+
+#endif // VEHICLE_JEEP_EPISODIC_H
diff --git a/mp/src/game/server/episodic/weapon_hopwire.cpp b/mp/src/game/server/episodic/weapon_hopwire.cpp
index c3797fa8..798f6eb8 100644
--- a/mp/src/game/server/episodic/weapon_hopwire.cpp
+++ b/mp/src/game/server/episodic/weapon_hopwire.cpp
@@ -1,508 +1,508 @@
-//========= Copyright Valve Corporation, All rights reserved. ============//
-//
-// Purpose:
-//
-//=============================================================================//
-
-#include "cbase.h"
-#include "basehlcombatweapon.h"
-#include "player.h"
-#include "gamerules.h"
-#include "grenade_frag.h"
-#include "npcevent.h"
-#include "engine/IEngineSound.h"
-#include "items.h"
-#include "in_buttons.h"
-#include "soundent.h"
-#include "grenade_hopwire.h"
-
-// memdbgon must be the last include file in a .cpp file!!!
-#include "tier0/memdbgon.h"
-
-#define GRENADE_TIMER 2.0f //Seconds
-
-#define GRENADE_PAUSED_NO 0
-#define GRENADE_PAUSED_PRIMARY 1
-#define GRENADE_PAUSED_SECONDARY 2
-
-#define GRENADE_RADIUS 4.0f // inches
-
-//-----------------------------------------------------------------------------
-// Fragmentation grenades
-//-----------------------------------------------------------------------------
-class CWeaponHopwire: public CBaseHLCombatWeapon
-{
- DECLARE_CLASS( CWeaponHopwire, CBaseHLCombatWeapon );
-public:
- DECLARE_SERVERCLASS();
-
- void Precache( void );
- void Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCharacter *pOperator );
- void PrimaryAttack( void );
- void SecondaryAttack( void );
- void DecrementAmmo( CBaseCombatCharacter *pOwner );
- void ItemPostFrame( void );
-
- void HandleFireOnEmpty( void );
- bool HasAnyAmmo( void );
- bool Deploy( void );
- bool Holster( CBaseCombatWeapon *pSwitchingTo = NULL );
-
- int CapabilitiesGet( void ) { return bits_CAP_WEAPON_RANGE_ATTACK1; }
-
- bool Reload( void );
-
-private:
- void ThrowGrenade( CBasePlayer *pPlayer );
- void RollGrenade( CBasePlayer *pPlayer );
- void LobGrenade( CBasePlayer *pPlayer );
- // check a throw from vecSrc. If not valid, move the position back along the line to vecEye
- void CheckThrowPosition( CBasePlayer *pPlayer, const Vector &vecEye, Vector &vecSrc );
-
- bool m_bRedraw; //Draw the weapon again after throwing a grenade
-
- int m_AttackPaused;
- bool m_fDrawbackFinished;
-
- CHandle<CGrenadeHopwire> m_hActiveHopWire;
-
- DECLARE_ACTTABLE();
-
- DECLARE_DATADESC();
-};
-
-
-BEGIN_DATADESC( CWeaponHopwire )
- DEFINE_FIELD( m_bRedraw, FIELD_BOOLEAN ),
- DEFINE_FIELD( m_AttackPaused, FIELD_INTEGER ),
- DEFINE_FIELD( m_fDrawbackFinished, FIELD_BOOLEAN ),
- DEFINE_FIELD( m_hActiveHopWire, FIELD_EHANDLE ),
-END_DATADESC()
-
-acttable_t CWeaponHopwire::m_acttable[] =
-{
- { ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_SLAM, true },
-};
-
-IMPLEMENT_ACTTABLE(CWeaponHopwire);
-
-IMPLEMENT_SERVERCLASS_ST(CWeaponHopwire, DT_WeaponHopwire)
-END_SEND_TABLE()
-
-LINK_ENTITY_TO_CLASS( weapon_hopwire, CWeaponHopwire );
-PRECACHE_WEAPON_REGISTER(weapon_hopwire);
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CWeaponHopwire::Precache( void )
-{
- BaseClass::Precache();
-
- UTIL_PrecacheOther( "npc_grenade_hopwire" );
-
- PrecacheScriptSound( "WeaponFrag.Throw" );
- PrecacheScriptSound( "WeaponFrag.Roll" );
-
- m_bRedraw = false;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-bool CWeaponHopwire::Deploy( void )
-{
- m_bRedraw = false;
- m_fDrawbackFinished = false;
-
- return BaseClass::Deploy();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Output : Returns true on success, false on failure.
-//-----------------------------------------------------------------------------
-bool CWeaponHopwire::Holster( CBaseCombatWeapon *pSwitchingTo )
-{
- if ( m_hActiveHopWire != NULL )
- return false;
-
- m_bRedraw = false;
- m_fDrawbackFinished = false;
-
- return BaseClass::Holster( pSwitchingTo );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : *pEvent -
-// *pOperator -
-//-----------------------------------------------------------------------------
-void CWeaponHopwire::Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCharacter *pOperator )
-{
- CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
- bool fThrewGrenade = false;
-
- switch( pEvent->event )
- {
- case EVENT_WEAPON_SEQUENCE_FINISHED:
- m_fDrawbackFinished = true;
- break;
-
- case EVENT_WEAPON_THROW:
- ThrowGrenade( pOwner );
- DecrementAmmo( pOwner );
- fThrewGrenade = true;
- break;
-
- case EVENT_WEAPON_THROW2:
- RollGrenade( pOwner );
- DecrementAmmo( pOwner );
- fThrewGrenade = true;
- break;
-
- case EVENT_WEAPON_THROW3:
- LobGrenade( pOwner );
- DecrementAmmo( pOwner );
- fThrewGrenade = true;
- break;
-
- default:
- BaseClass::Operator_HandleAnimEvent( pEvent, pOperator );
- break;
- }
-
-#define RETHROW_DELAY 0.5
- if( fThrewGrenade )
- {
- m_flNextPrimaryAttack = gpGlobals->curtime + RETHROW_DELAY;
- m_flNextSecondaryAttack = gpGlobals->curtime + RETHROW_DELAY;
- m_flTimeWeaponIdle = FLT_MAX; //NOTE: This is set once the animation has finished up!
-
- // Make a sound designed to scare snipers back into their holes!
- CBaseCombatCharacter *pOwner = GetOwner();
-
- if( pOwner )
- {
- Vector vecSrc = pOwner->Weapon_ShootPosition();
- Vector vecDir;
-
- AngleVectors( pOwner->EyeAngles(), &vecDir );
-
- trace_t tr;
-
- UTIL_TraceLine( vecSrc, vecSrc + vecDir * 1024, MASK_SOLID_BRUSHONLY, pOwner, COLLISION_GROUP_NONE, &tr );
-
- CSoundEnt::InsertSound( SOUND_DANGER_SNIPERONLY, tr.endpos, 384, 0.2, pOwner );
- }
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Override the ammo behavior so we never disallow pulling the weapon out
-// Output : Returns true on success, false on failure.
-//-----------------------------------------------------------------------------
-bool CWeaponHopwire::HasAnyAmmo( void )
-{
- if ( m_hActiveHopWire != NULL )
- return true;
-
- return BaseClass::HasAnyAmmo();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Output : Returns true on success, false on failure.
-//-----------------------------------------------------------------------------
-bool CWeaponHopwire::Reload( void )
-{
- if ( !HasPrimaryAmmo() )
- return false;
-
- if ( ( m_bRedraw ) && ( m_flNextPrimaryAttack <= gpGlobals->curtime ) && ( m_flNextSecondaryAttack <= gpGlobals->curtime ) )
- {
- //Redraw the weapon
- SendWeaponAnim( ACT_VM_DRAW );
-
- //Update our times
- m_flNextPrimaryAttack = gpGlobals->curtime + SequenceDuration();
- m_flNextSecondaryAttack = gpGlobals->curtime + SequenceDuration();
- m_flTimeWeaponIdle = gpGlobals->curtime + SequenceDuration();
-
- //Mark this as done
- m_bRedraw = false;
- }
-
- return true;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CWeaponHopwire::SecondaryAttack( void )
-{
- /*
- if ( m_bRedraw )
- return;
-
- if ( !HasPrimaryAmmo() )
- return;
-
- CBaseCombatCharacter *pOwner = GetOwner();
-
- if ( pOwner == NULL )
- return;
-
- CBasePlayer *pPlayer = ToBasePlayer( pOwner );
-
- if ( pPlayer == NULL )
- return;
-
- // Note that this is a secondary attack and prepare the grenade attack to pause.
- m_AttackPaused = GRENADE_PAUSED_SECONDARY;
- SendWeaponAnim( ACT_VM_PULLBACK_LOW );
-
- // Don't let weapon idle interfere in the middle of a throw!
- m_flTimeWeaponIdle = FLT_MAX;
- m_flNextSecondaryAttack = FLT_MAX;
-
- // If I'm now out of ammo, switch away
- if ( !HasPrimaryAmmo() )
- {
- pPlayer->SwitchToNextBestWeapon( this );
- }
- */
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Allow activation even if this is our last piece of ammo
-//-----------------------------------------------------------------------------
-void CWeaponHopwire::HandleFireOnEmpty( void )
-{
- if ( m_hActiveHopWire!= NULL )
- {
- // FIXME: This toggle is hokey
- m_bRedraw = false;
- PrimaryAttack();
- m_bRedraw = true;
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CWeaponHopwire::PrimaryAttack( void )
-{
- if ( m_bRedraw )
- return;
-
- CBaseCombatCharacter *pOwner = GetOwner();
- if ( pOwner == NULL )
- return;
-
- CBasePlayer *pPlayer = ToBasePlayer( GetOwner() );;
- if ( !pPlayer )
- return;
-
- // See if we're in trap mode
- if ( hopwire_trap.GetBool() && ( m_hActiveHopWire != NULL ) )
- {
- // Spring the trap
- m_hActiveHopWire->Detonate();
- m_hActiveHopWire = NULL;
-
- // Don't allow another throw for awhile
- m_flTimeWeaponIdle = m_flNextPrimaryAttack = gpGlobals->curtime + 2.0f;
-
- return;
- }
-
- // Note that this is a primary attack and prepare the grenade attack to pause.
- /*
- m_AttackPaused = GRENADE_PAUSED_PRIMARY;
- SendWeaponAnim( ACT_VM_PULLBACK_HIGH );
- */
- m_AttackPaused = GRENADE_PAUSED_SECONDARY;
- SendWeaponAnim( ACT_VM_PULLBACK_LOW );
-
- // Put both of these off indefinitely. We do not know how long
- // the player will hold the grenade.
- m_flTimeWeaponIdle = FLT_MAX;
- m_flNextPrimaryAttack = FLT_MAX;
-
- // If I'm now out of ammo, switch away
- /*
- if ( !HasPrimaryAmmo() )
- {
- pPlayer->SwitchToNextBestWeapon( this );
- }
- */
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : *pOwner -
-//-----------------------------------------------------------------------------
-void CWeaponHopwire::DecrementAmmo( CBaseCombatCharacter *pOwner )
-{
- pOwner->RemoveAmmo( 1, m_iPrimaryAmmoType );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CWeaponHopwire::ItemPostFrame( void )
-{
- if( m_fDrawbackFinished )
- {
- CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
-
- if (pOwner)
- {
- switch( m_AttackPaused )
- {
- case GRENADE_PAUSED_PRIMARY:
- if( !(pOwner->m_nButtons & IN_ATTACK) )
- {
- SendWeaponAnim( ACT_VM_THROW );
- m_fDrawbackFinished = false;
- }
- break;
-
- case GRENADE_PAUSED_SECONDARY:
- if( !(pOwner->m_nButtons & (IN_ATTACK|IN_ATTACK2)) )
- {
- //See if we're ducking
- if ( pOwner->m_nButtons & IN_DUCK )
- {
- //Send the weapon animation
- SendWeaponAnim( ACT_VM_SECONDARYATTACK );
- }
- else
- {
- //Send the weapon animation
- SendWeaponAnim( ACT_VM_HAULBACK );
- }
-
- m_fDrawbackFinished = false;
- }
- break;
-
- default:
- break;
- }
- }
- }
-
- BaseClass::ItemPostFrame();
-
- if ( m_bRedraw )
- {
- if ( IsViewModelSequenceFinished() )
- {
- Reload();
- }
- }
-}
-
- // check a throw from vecSrc. If not valid, move the position back along the line to vecEye
-void CWeaponHopwire::CheckThrowPosition( CBasePlayer *pPlayer, const Vector &vecEye, Vector &vecSrc )
-{
- trace_t tr;
-
- UTIL_TraceHull( vecEye, vecSrc, -Vector(GRENADE_RADIUS+2,GRENADE_RADIUS+2,GRENADE_RADIUS+2), Vector(GRENADE_RADIUS+2,GRENADE_RADIUS+2,GRENADE_RADIUS+2),
- pPlayer->PhysicsSolidMaskForEntity(), pPlayer, pPlayer->GetCollisionGroup(), &tr );
-
- if ( tr.DidHit() )
- {
- vecSrc = tr.endpos;
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : *pPlayer -
-//-----------------------------------------------------------------------------
-void CWeaponHopwire::ThrowGrenade( CBasePlayer *pPlayer )
-{
- Vector vecEye = pPlayer->EyePosition();
- Vector vForward, vRight;
-
- pPlayer->EyeVectors( &vForward, &vRight, NULL );
- Vector vecSrc = vecEye + vForward * 18.0f + vRight * 8.0f;
- CheckThrowPosition( pPlayer, vecEye, vecSrc );
- vForward[2] += 0.1f;
-
- Vector vecThrow;
- pPlayer->GetVelocity( &vecThrow, NULL );
- vecThrow += vForward * 1200;
- m_hActiveHopWire = static_cast<CGrenadeHopwire *> (HopWire_Create( vecSrc, vec3_angle, vecThrow, AngularImpulse(600,random->RandomInt(-1200,1200),0), pPlayer, GRENADE_TIMER ));
-
- m_bRedraw = true;
-
- WeaponSound( SINGLE );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : *pPlayer -
-//-----------------------------------------------------------------------------
-void CWeaponHopwire::LobGrenade( CBasePlayer *pPlayer )
-{
- Vector vecEye = pPlayer->EyePosition();
- Vector vForward, vRight;
-
- pPlayer->EyeVectors( &vForward, &vRight, NULL );
- Vector vecSrc = vecEye + vForward * 18.0f + vRight * 8.0f + Vector( 0, 0, -8 );
- CheckThrowPosition( pPlayer, vecEye, vecSrc );
-
- Vector vecThrow;
- pPlayer->GetVelocity( &vecThrow, NULL );
- vecThrow += vForward * 350 + Vector( 0, 0, 50 );
- m_hActiveHopWire = static_cast<CGrenadeHopwire *> (HopWire_Create( vecSrc, vec3_angle, vecThrow, AngularImpulse(200,random->RandomInt(-600,600),0), pPlayer, GRENADE_TIMER ));
-
- WeaponSound( WPN_DOUBLE );
-
- m_bRedraw = true;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : *pPlayer -
-//-----------------------------------------------------------------------------
-void CWeaponHopwire::RollGrenade( CBasePlayer *pPlayer )
-{
- // BUGBUG: Hardcoded grenade width of 4 - better not change the model :)
- Vector vecSrc;
- pPlayer->CollisionProp()->NormalizedToWorldSpace( Vector( 0.5f, 0.5f, 0.0f ), &vecSrc );
- vecSrc.z += GRENADE_RADIUS;
-
- Vector vecFacing = pPlayer->BodyDirection2D( );
- // no up/down direction
- vecFacing.z = 0;
- VectorNormalize( vecFacing );
- trace_t tr;
- UTIL_TraceLine( vecSrc, vecSrc - Vector(0,0,16), MASK_PLAYERSOLID, pPlayer, COLLISION_GROUP_NONE, &tr );
- if ( tr.fraction != 1.0 )
- {
- // compute forward vec parallel to floor plane and roll grenade along that
- Vector tangent;
- CrossProduct( vecFacing, tr.plane.normal, tangent );
- CrossProduct( tr.plane.normal, tangent, vecFacing );
- }
- vecSrc += (vecFacing * 18.0);
- CheckThrowPosition( pPlayer, pPlayer->WorldSpaceCenter(), vecSrc );
-
- Vector vecThrow;
- pPlayer->GetVelocity( &vecThrow, NULL );
- vecThrow += vecFacing * 700;
- // put it on its side
- QAngle orientation(0,pPlayer->GetLocalAngles().y,-90);
- // roll it
- AngularImpulse rotSpeed(0,0,720);
- m_hActiveHopWire = static_cast<CGrenadeHopwire *> (HopWire_Create( vecSrc, orientation, vecThrow, rotSpeed, pPlayer, GRENADE_TIMER ));
-
- WeaponSound( SPECIAL1 );
-
- m_bRedraw = true;
-}
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#include "cbase.h"
+#include "basehlcombatweapon.h"
+#include "player.h"
+#include "gamerules.h"
+#include "grenade_frag.h"
+#include "npcevent.h"
+#include "engine/IEngineSound.h"
+#include "items.h"
+#include "in_buttons.h"
+#include "soundent.h"
+#include "grenade_hopwire.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+#define GRENADE_TIMER 2.0f //Seconds
+
+#define GRENADE_PAUSED_NO 0
+#define GRENADE_PAUSED_PRIMARY 1
+#define GRENADE_PAUSED_SECONDARY 2
+
+#define GRENADE_RADIUS 4.0f // inches
+
+//-----------------------------------------------------------------------------
+// Fragmentation grenades
+//-----------------------------------------------------------------------------
+class CWeaponHopwire: public CBaseHLCombatWeapon
+{
+ DECLARE_CLASS( CWeaponHopwire, CBaseHLCombatWeapon );
+public:
+ DECLARE_SERVERCLASS();
+
+ void Precache( void );
+ void Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCharacter *pOperator );
+ void PrimaryAttack( void );
+ void SecondaryAttack( void );
+ void DecrementAmmo( CBaseCombatCharacter *pOwner );
+ void ItemPostFrame( void );
+
+ void HandleFireOnEmpty( void );
+ bool HasAnyAmmo( void );
+ bool Deploy( void );
+ bool Holster( CBaseCombatWeapon *pSwitchingTo = NULL );
+
+ int CapabilitiesGet( void ) { return bits_CAP_WEAPON_RANGE_ATTACK1; }
+
+ bool Reload( void );
+
+private:
+ void ThrowGrenade( CBasePlayer *pPlayer );
+ void RollGrenade( CBasePlayer *pPlayer );
+ void LobGrenade( CBasePlayer *pPlayer );
+ // check a throw from vecSrc. If not valid, move the position back along the line to vecEye
+ void CheckThrowPosition( CBasePlayer *pPlayer, const Vector &vecEye, Vector &vecSrc );
+
+ bool m_bRedraw; //Draw the weapon again after throwing a grenade
+
+ int m_AttackPaused;
+ bool m_fDrawbackFinished;
+
+ CHandle<CGrenadeHopwire> m_hActiveHopWire;
+
+ DECLARE_ACTTABLE();
+
+ DECLARE_DATADESC();
+};
+
+
+BEGIN_DATADESC( CWeaponHopwire )
+ DEFINE_FIELD( m_bRedraw, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_AttackPaused, FIELD_INTEGER ),
+ DEFINE_FIELD( m_fDrawbackFinished, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_hActiveHopWire, FIELD_EHANDLE ),
+END_DATADESC()
+
+acttable_t CWeaponHopwire::m_acttable[] =
+{
+ { ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_SLAM, true },
+};
+
+IMPLEMENT_ACTTABLE(CWeaponHopwire);
+
+IMPLEMENT_SERVERCLASS_ST(CWeaponHopwire, DT_WeaponHopwire)
+END_SEND_TABLE()
+
+LINK_ENTITY_TO_CLASS( weapon_hopwire, CWeaponHopwire );
+PRECACHE_WEAPON_REGISTER(weapon_hopwire);
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CWeaponHopwire::Precache( void )
+{
+ BaseClass::Precache();
+
+ UTIL_PrecacheOther( "npc_grenade_hopwire" );
+
+ PrecacheScriptSound( "WeaponFrag.Throw" );
+ PrecacheScriptSound( "WeaponFrag.Roll" );
+
+ m_bRedraw = false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CWeaponHopwire::Deploy( void )
+{
+ m_bRedraw = false;
+ m_fDrawbackFinished = false;
+
+ return BaseClass::Deploy();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CWeaponHopwire::Holster( CBaseCombatWeapon *pSwitchingTo )
+{
+ if ( m_hActiveHopWire != NULL )
+ return false;
+
+ m_bRedraw = false;
+ m_fDrawbackFinished = false;
+
+ return BaseClass::Holster( pSwitchingTo );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pEvent -
+// *pOperator -
+//-----------------------------------------------------------------------------
+void CWeaponHopwire::Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCharacter *pOperator )
+{
+ CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
+ bool fThrewGrenade = false;
+
+ switch( pEvent->event )
+ {
+ case EVENT_WEAPON_SEQUENCE_FINISHED:
+ m_fDrawbackFinished = true;
+ break;
+
+ case EVENT_WEAPON_THROW:
+ ThrowGrenade( pOwner );
+ DecrementAmmo( pOwner );
+ fThrewGrenade = true;
+ break;
+
+ case EVENT_WEAPON_THROW2:
+ RollGrenade( pOwner );
+ DecrementAmmo( pOwner );
+ fThrewGrenade = true;
+ break;
+
+ case EVENT_WEAPON_THROW3:
+ LobGrenade( pOwner );
+ DecrementAmmo( pOwner );
+ fThrewGrenade = true;
+ break;
+
+ default:
+ BaseClass::Operator_HandleAnimEvent( pEvent, pOperator );
+ break;
+ }
+
+#define RETHROW_DELAY 0.5
+ if( fThrewGrenade )
+ {
+ m_flNextPrimaryAttack = gpGlobals->curtime + RETHROW_DELAY;
+ m_flNextSecondaryAttack = gpGlobals->curtime + RETHROW_DELAY;
+ m_flTimeWeaponIdle = FLT_MAX; //NOTE: This is set once the animation has finished up!
+
+ // Make a sound designed to scare snipers back into their holes!
+ CBaseCombatCharacter *pOwner = GetOwner();
+
+ if( pOwner )
+ {
+ Vector vecSrc = pOwner->Weapon_ShootPosition();
+ Vector vecDir;
+
+ AngleVectors( pOwner->EyeAngles(), &vecDir );
+
+ trace_t tr;
+
+ UTIL_TraceLine( vecSrc, vecSrc + vecDir * 1024, MASK_SOLID_BRUSHONLY, pOwner, COLLISION_GROUP_NONE, &tr );
+
+ CSoundEnt::InsertSound( SOUND_DANGER_SNIPERONLY, tr.endpos, 384, 0.2, pOwner );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Override the ammo behavior so we never disallow pulling the weapon out
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CWeaponHopwire::HasAnyAmmo( void )
+{
+ if ( m_hActiveHopWire != NULL )
+ return true;
+
+ return BaseClass::HasAnyAmmo();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CWeaponHopwire::Reload( void )
+{
+ if ( !HasPrimaryAmmo() )
+ return false;
+
+ if ( ( m_bRedraw ) && ( m_flNextPrimaryAttack <= gpGlobals->curtime ) && ( m_flNextSecondaryAttack <= gpGlobals->curtime ) )
+ {
+ //Redraw the weapon
+ SendWeaponAnim( ACT_VM_DRAW );
+
+ //Update our times
+ m_flNextPrimaryAttack = gpGlobals->curtime + SequenceDuration();
+ m_flNextSecondaryAttack = gpGlobals->curtime + SequenceDuration();
+ m_flTimeWeaponIdle = gpGlobals->curtime + SequenceDuration();
+
+ //Mark this as done
+ m_bRedraw = false;
+ }
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CWeaponHopwire::SecondaryAttack( void )
+{
+ /*
+ if ( m_bRedraw )
+ return;
+
+ if ( !HasPrimaryAmmo() )
+ return;
+
+ CBaseCombatCharacter *pOwner = GetOwner();
+
+ if ( pOwner == NULL )
+ return;
+
+ CBasePlayer *pPlayer = ToBasePlayer( pOwner );
+
+ if ( pPlayer == NULL )
+ return;
+
+ // Note that this is a secondary attack and prepare the grenade attack to pause.
+ m_AttackPaused = GRENADE_PAUSED_SECONDARY;
+ SendWeaponAnim( ACT_VM_PULLBACK_LOW );
+
+ // Don't let weapon idle interfere in the middle of a throw!
+ m_flTimeWeaponIdle = FLT_MAX;
+ m_flNextSecondaryAttack = FLT_MAX;
+
+ // If I'm now out of ammo, switch away
+ if ( !HasPrimaryAmmo() )
+ {
+ pPlayer->SwitchToNextBestWeapon( this );
+ }
+ */
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Allow activation even if this is our last piece of ammo
+//-----------------------------------------------------------------------------
+void CWeaponHopwire::HandleFireOnEmpty( void )
+{
+ if ( m_hActiveHopWire!= NULL )
+ {
+ // FIXME: This toggle is hokey
+ m_bRedraw = false;
+ PrimaryAttack();
+ m_bRedraw = true;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CWeaponHopwire::PrimaryAttack( void )
+{
+ if ( m_bRedraw )
+ return;
+
+ CBaseCombatCharacter *pOwner = GetOwner();
+ if ( pOwner == NULL )
+ return;
+
+ CBasePlayer *pPlayer = ToBasePlayer( GetOwner() );;
+ if ( !pPlayer )
+ return;
+
+ // See if we're in trap mode
+ if ( hopwire_trap.GetBool() && ( m_hActiveHopWire != NULL ) )
+ {
+ // Spring the trap
+ m_hActiveHopWire->Detonate();
+ m_hActiveHopWire = NULL;
+
+ // Don't allow another throw for awhile
+ m_flTimeWeaponIdle = m_flNextPrimaryAttack = gpGlobals->curtime + 2.0f;
+
+ return;
+ }
+
+ // Note that this is a primary attack and prepare the grenade attack to pause.
+ /*
+ m_AttackPaused = GRENADE_PAUSED_PRIMARY;
+ SendWeaponAnim( ACT_VM_PULLBACK_HIGH );
+ */
+ m_AttackPaused = GRENADE_PAUSED_SECONDARY;
+ SendWeaponAnim( ACT_VM_PULLBACK_LOW );
+
+ // Put both of these off indefinitely. We do not know how long
+ // the player will hold the grenade.
+ m_flTimeWeaponIdle = FLT_MAX;
+ m_flNextPrimaryAttack = FLT_MAX;
+
+ // If I'm now out of ammo, switch away
+ /*
+ if ( !HasPrimaryAmmo() )
+ {
+ pPlayer->SwitchToNextBestWeapon( this );
+ }
+ */
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pOwner -
+//-----------------------------------------------------------------------------
+void CWeaponHopwire::DecrementAmmo( CBaseCombatCharacter *pOwner )
+{
+ pOwner->RemoveAmmo( 1, m_iPrimaryAmmoType );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CWeaponHopwire::ItemPostFrame( void )
+{
+ if( m_fDrawbackFinished )
+ {
+ CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
+
+ if (pOwner)
+ {
+ switch( m_AttackPaused )
+ {
+ case GRENADE_PAUSED_PRIMARY:
+ if( !(pOwner->m_nButtons & IN_ATTACK) )
+ {
+ SendWeaponAnim( ACT_VM_THROW );
+ m_fDrawbackFinished = false;
+ }
+ break;
+
+ case GRENADE_PAUSED_SECONDARY:
+ if( !(pOwner->m_nButtons & (IN_ATTACK|IN_ATTACK2)) )
+ {
+ //See if we're ducking
+ if ( pOwner->m_nButtons & IN_DUCK )
+ {
+ //Send the weapon animation
+ SendWeaponAnim( ACT_VM_SECONDARYATTACK );
+ }
+ else
+ {
+ //Send the weapon animation
+ SendWeaponAnim( ACT_VM_HAULBACK );
+ }
+
+ m_fDrawbackFinished = false;
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+ }
+
+ BaseClass::ItemPostFrame();
+
+ if ( m_bRedraw )
+ {
+ if ( IsViewModelSequenceFinished() )
+ {
+ Reload();
+ }
+ }
+}
+
+ // check a throw from vecSrc. If not valid, move the position back along the line to vecEye
+void CWeaponHopwire::CheckThrowPosition( CBasePlayer *pPlayer, const Vector &vecEye, Vector &vecSrc )
+{
+ trace_t tr;
+
+ UTIL_TraceHull( vecEye, vecSrc, -Vector(GRENADE_RADIUS+2,GRENADE_RADIUS+2,GRENADE_RADIUS+2), Vector(GRENADE_RADIUS+2,GRENADE_RADIUS+2,GRENADE_RADIUS+2),
+ pPlayer->PhysicsSolidMaskForEntity(), pPlayer, pPlayer->GetCollisionGroup(), &tr );
+
+ if ( tr.DidHit() )
+ {
+ vecSrc = tr.endpos;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pPlayer -
+//-----------------------------------------------------------------------------
+void CWeaponHopwire::ThrowGrenade( CBasePlayer *pPlayer )
+{
+ Vector vecEye = pPlayer->EyePosition();
+ Vector vForward, vRight;
+
+ pPlayer->EyeVectors( &vForward, &vRight, NULL );
+ Vector vecSrc = vecEye + vForward * 18.0f + vRight * 8.0f;
+ CheckThrowPosition( pPlayer, vecEye, vecSrc );
+ vForward[2] += 0.1f;
+
+ Vector vecThrow;
+ pPlayer->GetVelocity( &vecThrow, NULL );
+ vecThrow += vForward * 1200;
+ m_hActiveHopWire = static_cast<CGrenadeHopwire *> (HopWire_Create( vecSrc, vec3_angle, vecThrow, AngularImpulse(600,random->RandomInt(-1200,1200),0), pPlayer, GRENADE_TIMER ));
+
+ m_bRedraw = true;
+
+ WeaponSound( SINGLE );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pPlayer -
+//-----------------------------------------------------------------------------
+void CWeaponHopwire::LobGrenade( CBasePlayer *pPlayer )
+{
+ Vector vecEye = pPlayer->EyePosition();
+ Vector vForward, vRight;
+
+ pPlayer->EyeVectors( &vForward, &vRight, NULL );
+ Vector vecSrc = vecEye + vForward * 18.0f + vRight * 8.0f + Vector( 0, 0, -8 );
+ CheckThrowPosition( pPlayer, vecEye, vecSrc );
+
+ Vector vecThrow;
+ pPlayer->GetVelocity( &vecThrow, NULL );
+ vecThrow += vForward * 350 + Vector( 0, 0, 50 );
+ m_hActiveHopWire = static_cast<CGrenadeHopwire *> (HopWire_Create( vecSrc, vec3_angle, vecThrow, AngularImpulse(200,random->RandomInt(-600,600),0), pPlayer, GRENADE_TIMER ));
+
+ WeaponSound( WPN_DOUBLE );
+
+ m_bRedraw = true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pPlayer -
+//-----------------------------------------------------------------------------
+void CWeaponHopwire::RollGrenade( CBasePlayer *pPlayer )
+{
+ // BUGBUG: Hardcoded grenade width of 4 - better not change the model :)
+ Vector vecSrc;
+ pPlayer->CollisionProp()->NormalizedToWorldSpace( Vector( 0.5f, 0.5f, 0.0f ), &vecSrc );
+ vecSrc.z += GRENADE_RADIUS;
+
+ Vector vecFacing = pPlayer->BodyDirection2D( );
+ // no up/down direction
+ vecFacing.z = 0;
+ VectorNormalize( vecFacing );
+ trace_t tr;
+ UTIL_TraceLine( vecSrc, vecSrc - Vector(0,0,16), MASK_PLAYERSOLID, pPlayer, COLLISION_GROUP_NONE, &tr );
+ if ( tr.fraction != 1.0 )
+ {
+ // compute forward vec parallel to floor plane and roll grenade along that
+ Vector tangent;
+ CrossProduct( vecFacing, tr.plane.normal, tangent );
+ CrossProduct( tr.plane.normal, tangent, vecFacing );
+ }
+ vecSrc += (vecFacing * 18.0);
+ CheckThrowPosition( pPlayer, pPlayer->WorldSpaceCenter(), vecSrc );
+
+ Vector vecThrow;
+ pPlayer->GetVelocity( &vecThrow, NULL );
+ vecThrow += vecFacing * 700;
+ // put it on its side
+ QAngle orientation(0,pPlayer->GetLocalAngles().y,-90);
+ // roll it
+ AngularImpulse rotSpeed(0,0,720);
+ m_hActiveHopWire = static_cast<CGrenadeHopwire *> (HopWire_Create( vecSrc, orientation, vecThrow, rotSpeed, pPlayer, GRENADE_TIMER ));
+
+ WeaponSound( SPECIAL1 );
+
+ m_bRedraw = true;
+}
diff --git a/mp/src/game/server/episodic/weapon_oldmanharpoon.cpp b/mp/src/game/server/episodic/weapon_oldmanharpoon.cpp
index f75ff527..497fe225 100644
--- a/mp/src/game/server/episodic/weapon_oldmanharpoon.cpp
+++ b/mp/src/game/server/episodic/weapon_oldmanharpoon.cpp
@@ -1,36 +1,36 @@
-//========= Copyright Valve Corporation, All rights reserved. ============//
-//
-// Purpose:
-//
-//=============================================================================
-
-#include "cbase.h"
-#include "weapon_citizenpackage.h"
-
-//-----------------------------------------------------------------------------
-// Purpose: Old Man Harpoon - Lost Coast.
-//-----------------------------------------------------------------------------
-class CWeaponOldManHarpoon : public CWeaponCitizenPackage
-{
- DECLARE_CLASS( CWeaponOldManHarpoon, CWeaponCitizenPackage );
-public:
- DECLARE_SERVERCLASS();
- DECLARE_DATADESC();
- DECLARE_ACTTABLE();
-};
-
-IMPLEMENT_SERVERCLASS_ST( CWeaponOldManHarpoon, DT_WeaponOldManHarpoon )
-END_SEND_TABLE()
-
-BEGIN_DATADESC( CWeaponOldManHarpoon )
-END_DATADESC()
-
-LINK_ENTITY_TO_CLASS( weapon_oldmanharpoon, CWeaponOldManHarpoon );
-PRECACHE_WEAPON_REGISTER( weapon_oldmanharpoon );
-
-acttable_t CWeaponOldManHarpoon::m_acttable[] =
-{
- { ACT_IDLE, ACT_IDLE_SUITCASE, false },
- { ACT_WALK, ACT_WALK_SUITCASE, false },
-};
-IMPLEMENT_ACTTABLE( CWeaponOldManHarpoon );
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================
+
+#include "cbase.h"
+#include "weapon_citizenpackage.h"
+
+//-----------------------------------------------------------------------------
+// Purpose: Old Man Harpoon - Lost Coast.
+//-----------------------------------------------------------------------------
+class CWeaponOldManHarpoon : public CWeaponCitizenPackage
+{
+ DECLARE_CLASS( CWeaponOldManHarpoon, CWeaponCitizenPackage );
+public:
+ DECLARE_SERVERCLASS();
+ DECLARE_DATADESC();
+ DECLARE_ACTTABLE();
+};
+
+IMPLEMENT_SERVERCLASS_ST( CWeaponOldManHarpoon, DT_WeaponOldManHarpoon )
+END_SEND_TABLE()
+
+BEGIN_DATADESC( CWeaponOldManHarpoon )
+END_DATADESC()
+
+LINK_ENTITY_TO_CLASS( weapon_oldmanharpoon, CWeaponOldManHarpoon );
+PRECACHE_WEAPON_REGISTER( weapon_oldmanharpoon );
+
+acttable_t CWeaponOldManHarpoon::m_acttable[] =
+{
+ { ACT_IDLE, ACT_IDLE_SUITCASE, false },
+ { ACT_WALK, ACT_WALK_SUITCASE, false },
+};
+IMPLEMENT_ACTTABLE( CWeaponOldManHarpoon );
diff --git a/mp/src/game/server/episodic/weapon_striderbuster.cpp b/mp/src/game/server/episodic/weapon_striderbuster.cpp
index ba99a9df..3c766c51 100644
--- a/mp/src/game/server/episodic/weapon_striderbuster.cpp
+++ b/mp/src/game/server/episodic/weapon_striderbuster.cpp
@@ -1,1175 +1,1175 @@
-//========= Copyright Valve Corporation, All rights reserved. ============//
-//
-// An ingenious device. We call it "The Magnusson Device". Not my chosen label,
-// you understand, but it seemed to please the personnel.
-//
-// From your point of view you simply throw it at a strider and then blow it up.
-//
-//=============================================================================
-
-#include "cbase.h"
-#include "props.h"
-#include "vphysics/constraints.h"
-#include "physics_saverestore.h"
-#include "model_types.h"
-#include "ai_utils.h"
-#include "particle_system.h"
-#include "Sprite.h"
-#include "citadel_effects_shared.h"
-#include "soundent.h"
-#include "SpriteTrail.h"
-#include "te_effect_dispatch.h"
-#include "beam_shared.h"
-#include "npc_strider.h"
-#include "npc_hunter.h"
-#include "particle_parse.h"
-#include "gameweaponmanager.h"
-#include "gamestats.h"
-
-extern ConVar hunter_hate_held_striderbusters;
-extern ConVar hunter_hate_thrown_striderbusters;
-extern ConVar hunter_hate_attached_striderbusters;
-
-
-ConVar striderbuster_health( "striderbuster_health", "14" );
-ConVar striderbuster_autoaim_radius( "striderbuster_autoaim_radius", "64.0f" );
-ConVar striderbuster_shot_velocity( "striderbuster_shot_velocity", "2500.0", FCVAR_NONE, "Speed at which launch the bomb from the physcannon" );
-ConVar striderbuster_allow_all_damage( "striderbuster_allow_all_damage", "0", FCVAR_NONE, "If set to '1' the bomb will detonate on any damage taken. Otherwise only the player may trigger it." );
-
-//ConVar striderbuster_magnetic_radius("striderbuster_magnetic_radius","400.0f", FCVAR_NONE,"Maximum distance at which magnade experiences attraction to a target. Set to 0 to disable magnetism.");
-ConVar striderbuster_magnetic_force_strider("striderbuster_magnetic_force_strider", "750000.0f", FCVAR_NONE,"Intensity of magnade's attraction to a strider.");
-ConVar striderbuster_magnetic_force_hunter("striderbuster_magnetic_force_hunter","1750000.0f",FCVAR_NONE,"Intensity of magnade's attraction to a hunter.");
-ConVar striderbuster_falloff_power("striderbuster_falloff_power","4",FCVAR_NONE,"Order of the distance falloff. 1 = linear 2 = quadratic");
-ConVar striderbuster_leg_stick_dist( "striderbuster_leg_stick_dist", "80.0", FCVAR_NONE, "If the buster hits a strider's leg, the max distance from the head at which it sticks anyway." );
-ConVar striderbuster_debugseek( "striderbuster_debugseek", "0" );
-
-ConVar sk_striderbuster_magnet_multiplier( "sk_striderbuster_magnet_multiplier", "2.25" );
-
-ConVar striderbuster_die_detach( "striderbuster_die_detach", "1" ); // Drop off the strider if a hunter shoots me. (Instead of exploding)
-ConVar striderbuster_dive_force( "striderbuster_dive_force", "-200" ); // How much force to apply to a nosediving (dead in the air) striderbuster
-
-ConVar striderbuster_use_particle_flare( "striderbuster_use_particle_flare", "1" );
-
-#define STRIDERBUSTER_FLAG_KNOCKED_OFF_STRIDER 0x00000001 // We were knocked off of a strider after the player attached me.
-
-#define SF_DONT_WEAPON_MANAGE 0x800000
-
-#define STRIDERBUSTER_SPRITE_TRAIL "sprites/bluelaser1.vmt"
-
-string_t g_iszVehicle;
-
-#define BUSTER_PING_SOUND_FREQ 3.0f // How often (seconds) to issue the ping sound to remind players we are attached
-
-static const char *s_pBusterPingThinkContext = "BusterPing";
-
-class CWeaponStriderBuster : public CPhysicsProp
-{
- DECLARE_CLASS( CWeaponStriderBuster, CPhysicsProp );
- DECLARE_DATADESC();
-
-public:
- CWeaponStriderBuster( void );
-
- virtual void Precache( void );
- virtual void Spawn( void );
- virtual void Activate( void );
-
- // Treat as a live target so hunters can attack us
- virtual bool IsAlive() { return true; }
-
- virtual void OnRestore( void );
- virtual void VPhysicsCollision( int index, gamevcollisionevent_t *pEvent );
- virtual void UpdateOnRemove( void );
- virtual int OnTakeDamage( const CTakeDamageInfo &info );
- virtual bool ShouldPuntUseLaunchForces( PhysGunForce_t reason ) { return ( reason == PHYSGUN_FORCE_LAUNCHED ); }
- virtual QAngle PreferredCarryAngles( void ) { return m_CarryAngles; }
- virtual bool HasPreferredCarryAnglesForPlayer( CBasePlayer *pPlayer ) { return true; }
-
- virtual void OnPhysGunPickup( CBasePlayer *pPhysGunUser, PhysGunPickup_t reason );
- virtual void OnPhysGunDrop( CBasePlayer *pPhysGunUser, PhysGunDrop_t Reason );
- virtual Vector PhysGunLaunchVelocity( const Vector &forward, float flMass );
- virtual float GetAutoAimRadius( void ) { return striderbuster_autoaim_radius.GetFloat(); }
- virtual void BusterTouch( CBaseEntity *pOther );
-
- virtual bool ShouldAttractAutoAim( CBaseEntity *pAimingEnt ) { return IsAttachedToStrider(); }
-
- void InputConstraintBroken( inputdata_t &inputdata );
- void BusterFlyThink();
- void BusterDetachThink();
- void BusterPingThink();
-
- void OnAddToCargoHold();
- void OnFlechetteAttach( Vector &vecForceDir );
- int NumFlechettesAttached() { return m_nAttachedFlechettes; }
-
- float GetPickupTime() { return m_PickupTime; }
-
- int GetStriderBusterFlags() { return m_iBusterFlags; } // I added a flags field so we don't have to keep added bools for all of these contingencies (sjb)
-
-private:
-
- void Launch( CBasePlayer *pPhysGunUser );
- void Detonate( void );
- void Shatter( CBaseEntity *pAttacker );
- bool StickToEntity( CBaseEntity *pOther );
- bool CreateConstraintToObject( CBaseEntity *pObject );
- void DestroyConstraint( void );
- bool ShouldStickToEntity( CBaseEntity *pEntity );
- void CreateDestroyedEffect( void );
-
- inline bool IsAttachedToStrider( void ) const;
-
- bool m_bDud;
- bool m_bLaunched;
- bool m_bNoseDiving; // No magnetism, nosedive and break. Hunter flechettes set this.
- int m_nAttachedFlechettes;
- float m_flCollisionSpeedSqr;
- int m_nAttachedBoneFollowerIndex;
- float m_PickupTime;
-
- IPhysicsConstraint *m_pConstraint;
- EHANDLE m_hConstrainedEntity;
-
- CHandle<CSprite> m_hGlowSprite;
- CHandle<CSprite> m_hMainGlow;
-
- //CHandle<CParticleSystem> m_hGlowTrail;
- EHANDLE m_hParticleEffect;
-
- int m_nRingTexture;
-
- QAngle m_CarryAngles;
-
- int m_iBusterFlags;
-
- COutputEvent m_OnAttachToStrider;
- COutputEvent m_OnDetonate;
- COutputEvent m_OnShatter;
- COutputEvent m_OnShotDown;
-
-friend bool StriderBuster_IsAttachedStriderBuster( CBaseEntity *pEntity, CBaseEntity * );
-
-};
-
-LINK_ENTITY_TO_CLASS( prop_stickybomb, CWeaponStriderBuster );
-LINK_ENTITY_TO_CLASS( weapon_striderbuster, CWeaponStriderBuster );
-
-BEGIN_DATADESC( CWeaponStriderBuster )
- DEFINE_KEYFIELD( m_bDud, FIELD_BOOLEAN, "dud" ),
-
- DEFINE_FIELD( m_bLaunched, FIELD_BOOLEAN ),
- DEFINE_FIELD( m_bNoseDiving, FIELD_BOOLEAN ),
- DEFINE_FIELD( m_nAttachedFlechettes, FIELD_INTEGER ),
- DEFINE_FIELD( m_flCollisionSpeedSqr, FIELD_FLOAT ),
- DEFINE_FIELD( m_hConstrainedEntity, FIELD_EHANDLE ),
- DEFINE_FIELD( m_hGlowSprite, FIELD_EHANDLE ),
- DEFINE_FIELD( m_hMainGlow, FIELD_EHANDLE ),
- //DEFINE_FIELD( m_hGlowTrail, FIELD_EHANDLE ),
-
- DEFINE_FIELD( m_nRingTexture, FIELD_INTEGER ),
- DEFINE_FIELD( m_nAttachedBoneFollowerIndex, FIELD_INTEGER ),
-
- DEFINE_FIELD( m_PickupTime, FIELD_TIME ),
-
- DEFINE_FIELD( m_hParticleEffect, FIELD_EHANDLE ),
-
- DEFINE_FIELD( m_CarryAngles, FIELD_VECTOR ),
-
- DEFINE_FIELD( m_iBusterFlags, FIELD_INTEGER ),
- DEFINE_PHYSPTR( m_pConstraint ),
-
- DEFINE_INPUTFUNC( FIELD_VOID, "ConstraintBroken", InputConstraintBroken ),
-
- DEFINE_OUTPUT( m_OnAttachToStrider, "OnAttachToStrider" ),
- DEFINE_OUTPUT( m_OnDetonate, "OnDetonate" ),
- DEFINE_OUTPUT( m_OnShatter, "OnShatter" ),
- DEFINE_OUTPUT( m_OnShotDown, "OnShotDown" ),
-
- DEFINE_ENTITYFUNC( BusterTouch ),
- DEFINE_THINKFUNC( BusterFlyThink ),
- DEFINE_THINKFUNC( BusterDetachThink ),
- DEFINE_THINKFUNC( BusterPingThink ),
-END_DATADESC()
-
-CWeaponStriderBuster::CWeaponStriderBuster( void ) :
- m_pConstraint( NULL ),
- m_flCollisionSpeedSqr( -1.0f ),
- m_hConstrainedEntity( NULL ),
- m_nAttachedBoneFollowerIndex( -1 )
-{
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CWeaponStriderBuster::Precache( void )
-{
- PrecacheScriptSound( "Weapon_StriderBuster.StickToEntity" );
- PrecacheScriptSound( "Weapon_StriderBuster.Detonate" );
- PrecacheScriptSound( "Weapon_StriderBuster.Dud_Detonate" );
- PrecacheScriptSound( "Weapon_StriderBuster.Ping" );
-
- PrecacheModel("sprites/orangeflare1.vmt");
-
- UTIL_PrecacheOther( "env_citadel_energy_core" );
- UTIL_PrecacheOther( "sparktrail" );
-
- m_nRingTexture = PrecacheModel( "sprites/lgtning.vmt" );
-
- PrecacheParticleSystem( "striderbuster_attach" );
- PrecacheParticleSystem( "striderbuster_attached_pulse" );
- PrecacheParticleSystem( "striderbuster_explode_core" );
- PrecacheParticleSystem( "striderbuster_explode_dummy_core" );
- PrecacheParticleSystem( "striderbuster_break_flechette" );
- PrecacheParticleSystem( "striderbuster_trail" );
- PrecacheParticleSystem( "striderbuster_shotdown_trail" );
- PrecacheParticleSystem( "striderbuster_break" );
- PrecacheParticleSystem( "striderbuster_flechette_attached" );
-
- SetModelName( AllocPooledString("models/magnusson_device.mdl") );
- BaseClass::Precache();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CWeaponStriderBuster::Spawn( void )
-{
- SetModelName( AllocPooledString("models/magnusson_device.mdl") );
- BaseClass::Spawn();
-
- // Setup for being shot by the player
- m_takedamage = DAMAGE_EVENTS_ONLY;
-
- // Ignore touches until launched.
- SetTouch ( NULL );
-
- AddFlag( FL_AIMTARGET|FL_OBJECT );
-
- m_hParticleEffect = CreateEntityByName( "info_particle_system" );
- if ( m_hParticleEffect )
- {
- m_hParticleEffect->KeyValue( "start_active", "1" );
- m_hParticleEffect->KeyValue( "effect_name", "striderbuster_smoke" );
- DispatchSpawn( m_hParticleEffect );
- if ( gpGlobals->curtime > 0.2f )
- {
- m_hParticleEffect->Activate();
- }
- m_hParticleEffect->SetAbsOrigin( GetAbsOrigin() );
- m_hParticleEffect->SetParent( this );
- }
-
- SetHealth( striderbuster_health.GetFloat() );
-
- SetNextThink(gpGlobals->curtime + 0.01f);
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CWeaponStriderBuster::Activate( void )
-{
- g_iszVehicle = AllocPooledString( "prop_vehicle_jeep" );
- BaseClass::Activate();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CWeaponStriderBuster::OnRestore( void )
-{
- BaseClass::OnRestore();
-
- // If we have an entity we're attached to, attempt to reconstruct our bone follower setup
- if ( m_hConstrainedEntity != NULL )
- {
- CNPC_Strider *pStrider = dynamic_cast<CNPC_Strider *>(m_hConstrainedEntity.Get());
- if ( pStrider != NULL )
- {
- // Make sure we've done this step or we'll have no controller to attach to
- pStrider->InitBoneFollowers();
-
- // Attempt to make a connection to the same bone follower we attached to previously
- CBoneFollower *pBoneFollower = pStrider->GetBoneFollowerByIndex( m_nAttachedBoneFollowerIndex );
- if ( CreateConstraintToObject( pBoneFollower ) == false )
- {
- Msg( "Failed to reattach to bone follower %d\n", m_nAttachedBoneFollowerIndex );
- }
- }
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CWeaponStriderBuster::DestroyConstraint( void )
-{
- // Destroy the constraint
- if ( m_pConstraint != NULL )
- {
- physenv->DestroyConstraint( m_pConstraint );
- m_pConstraint = NULL;
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Create a constraint between this object and another
-// Input : *pObject - Object to constrain ourselves to
-// Output : Returns true on success, false on failure.
-//-----------------------------------------------------------------------------
-bool CWeaponStriderBuster::CreateConstraintToObject( CBaseEntity *pObject )
-{
- if ( m_pConstraint != NULL )
- {
- // Should we destroy the constraint and make a new one at this point?
- Assert( 0 );
- return false;
- }
-
- if ( pObject == NULL )
- return false;
-
- IPhysicsObject *pPhysObject = pObject->VPhysicsGetObject();
- if ( pPhysObject == NULL )
- return false;
-
- IPhysicsObject *pMyPhysObject = VPhysicsGetObject();
- if ( pPhysObject == NULL )
- return false;
-
- // Create the fixed constraint
- constraint_fixedparams_t fixedConstraint;
- fixedConstraint.Defaults();
- fixedConstraint.InitWithCurrentObjectState( pPhysObject, pMyPhysObject );
-
- IPhysicsConstraint *pConstraint = physenv->CreateFixedConstraint( pPhysObject, pMyPhysObject, NULL, fixedConstraint );
- if ( pConstraint == NULL )
- return false;
-
- // Hold on to us
- m_pConstraint = pConstraint;
- pConstraint->SetGameData( (void *)this );
- m_hConstrainedEntity = pObject->GetOwnerEntity();;
-
- // Disable collisions between the two ents
- PhysDisableObjectCollisions( pPhysObject, pMyPhysObject );
-
- return true;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Physics system has just told us our constraint has been broken
-//-----------------------------------------------------------------------------
-void CWeaponStriderBuster::InputConstraintBroken( inputdata_t &inputdata )
-{
- // Shatter with no real explosion effect
- Shatter( NULL );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CWeaponStriderBuster::UpdateOnRemove( void )
-{
- DestroyConstraint();
-
- if ( m_hGlowSprite != NULL )
- {
- m_hGlowSprite->FadeAndDie( 0.5f );
- m_hGlowSprite = NULL;
- }
-
- if ( m_hParticleEffect )
- {
- UTIL_Remove( m_hParticleEffect );
- }
-
- BaseClass::UpdateOnRemove();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : *pEntity -
-// Output : Returns true on success, false on failure.
-//-----------------------------------------------------------------------------
-bool CWeaponStriderBuster::ShouldStickToEntity( CBaseEntity *pEntity )
-{
- if ( pEntity == NULL )
- return false;
-
- // Must have a follow parent
- CBaseEntity *pFollowParent = pEntity->GetOwnerEntity();
- if ( pFollowParent == NULL )
- return false;
-
- // Must be a strider
- CNPC_Strider *pStrider = dynamic_cast<CNPC_Strider *>(pFollowParent);
- if ( pStrider == NULL )
- return false;
-
- if( m_bNoseDiving )
- return false;
-
- // Don't attach to legs
- CBoneFollower *pFollower = static_cast<CBoneFollower *>(pEntity);
- if ( pStrider->IsLegBoneFollower( pFollower ) )
- {
- Vector vecDelta = pStrider->GetAdjustedOrigin() - GetAbsOrigin();
- if ( vecDelta.Length() > striderbuster_leg_stick_dist.GetFloat() )
- {
- return false;
- }
- }
-
- // Ick, this is kind of ugly, but it's also ugly having to pass pointer into this to avoid multiple castings!
- // Save this to patch up save/restore later
- m_nAttachedBoneFollowerIndex = pStrider->GetBoneFollowerIndex( pFollower );
-
- return true;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Stick to an entity (using hierarchy if we can)
-// Output : Returns true on success, false on failure.
-//-----------------------------------------------------------------------------
-bool CWeaponStriderBuster::StickToEntity( CBaseEntity *pOther )
-{
- // Make sure the object is travelling fast enough to stick
- if ( m_flCollisionSpeedSqr > 50 && !m_bNoseDiving )
- {
- // See if this is a valid strider bit
- if ( ShouldStickToEntity( pOther ) )
- {
- // Attempt to constraint to it
- if ( CreateConstraintToObject( pOther ) )
- {
- // Only works for striders, at the moment
- CBaseEntity *pFollowParent = pOther->GetOwnerEntity();
- if ( pFollowParent == NULL )
- return false;
-
- // Allows us to identify our constrained object later
- SetOwnerEntity( pFollowParent );
-
- // Make a sound
- EmitSound( "Weapon_StriderBuster.StickToEntity" );
-
- DispatchParticleEffect( "striderbuster_attach", GetAbsOrigin(), GetAbsAngles(), NULL );
-
- if( striderbuster_use_particle_flare.GetBool() )
- {
- // We don't have to save any pointers or handles to this because it's parented to the buster.
- // So it will die when the buster dies. Yay.
- CParticleSystem *pFlare = (CParticleSystem *) CreateEntityByName( "info_particle_system" );
-
- if ( pFlare != NULL )
- {
- pFlare->KeyValue( "start_active", "1" );
- pFlare->KeyValue( "effect_name", "striderbuster_attached_pulse" );
- pFlare->SetParent( this );
- pFlare->SetLocalOrigin( vec3_origin );
- DispatchSpawn( pFlare );
- pFlare->Activate();
- }
- }
- else
- {
- // Create a glow sprite
- m_hGlowSprite = CSprite::SpriteCreate( "sprites/orangeflare1.vmt", GetLocalOrigin(), false );
-
- Assert( m_hGlowSprite );
- if ( m_hGlowSprite != NULL )
- {
- m_hGlowSprite->TurnOn();
- m_hGlowSprite->SetTransparency( kRenderWorldGlow, 255, 255, 255, 255, kRenderFxNoDissipation );
- m_hGlowSprite->SetAbsOrigin( GetAbsOrigin() );
- m_hGlowSprite->SetScale( 5.0f );
- m_hGlowSprite->m_nRenderFX = kRenderFxStrobeFaster;
- m_hGlowSprite->SetGlowProxySize( 16.0f );
- m_hGlowSprite->SetParent( this );
- }
- }
-
- // Stop touching things
- SetTouch( NULL );
-
- // Must be a strider
- CNPC_Strider *pStrider = dynamic_cast<CNPC_Strider *>(pFollowParent);
- if ( pStrider == NULL )
- return false;
-
- // Notify the strider we're attaching to him
- pStrider->StriderBusterAttached( this );
-
- m_OnAttachToStrider.FireOutput( this, this );
-
- // Start the ping sound.
- SetContextThink( &CWeaponStriderBuster::BusterPingThink, gpGlobals->curtime + BUSTER_PING_SOUND_FREQ, s_pBusterPingThinkContext );
-
- // Don't autodelete this one!
- WeaponManager_RemoveManaged( this );
-
- return true;
- }
-
- return false;
- }
- }
-
- return false;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Create the explosion effect for the final big boom
-//-----------------------------------------------------------------------------
-void CWeaponStriderBuster::CreateDestroyedEffect( void )
-{
- CBaseEntity *pTrail;
-
- StopParticleEffects( this );
-
- for ( int i = 0; i < 3; i++ )
- {
- pTrail = CreateEntityByName( "sparktrail" );
- pTrail->SetOwnerEntity( this );
- DispatchSpawn( pTrail );
- }
-
- DispatchParticleEffect( "striderbuster_explode_core", GetAbsOrigin(), GetAbsAngles() );
-
- // Create liquid fountain gushtacular effect here!
- CEffectData data;
-
- int nNumSteps = 6;
- float flRadStep = (2*M_PI) / nNumSteps;
- for ( int i = 0; i < nNumSteps; i++ )
- {
- data.m_vOrigin = GetAbsOrigin() + RandomVector( -32.0f, 32.0f );
- data.m_vNormal.x = cos( flRadStep*i );
- data.m_vNormal.y = sin( flRadStep*i );
- data.m_vNormal.z = 0.0f;
- data.m_flScale = ( random->RandomInt( 0, 5 ) == 0 ) ? 1 : 2;
-
- DispatchEffect( "StriderBlood", data );
- }
-
- // More effects
- UTIL_ScreenShake( GetAbsOrigin(), 20.0f, 150.0, 1.0, 1250.0f, SHAKE_START );
-
- data.m_vOrigin = GetAbsOrigin();
- DispatchEffect( "cball_explode", data );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Handle a collision using our special behavior
-//-----------------------------------------------------------------------------
-void CWeaponStriderBuster::VPhysicsCollision( int index, gamevcollisionevent_t *pEvent )
-{
- // Find out what we hit.
- // Don't do anything special if we're already attached to a strider.
- CBaseEntity *pVictim = pEvent->pEntities[!index];
- if ( pVictim == NULL || m_pConstraint != NULL )
- {
- BaseClass::VPhysicsCollision( index, pEvent );
- return;
- }
-
- // Don't attach if we're being held by the player
- if ( VPhysicsGetObject()->GetGameFlags() & FVPHYSICS_PLAYER_HELD )
- {
- BaseClass::VPhysicsCollision( index, pEvent );
- return;
- }
-
- // Save off the speed of the object
- m_flCollisionSpeedSqr = ( pEvent->preVelocity[ index ] ).LengthSqr();
-
- // Break if we hit the world while going fast enough.
- // Launched duds detonate if they hit the world at any speed.
- if ( pVictim->IsWorld() && ( ( m_bDud && m_bLaunched ) || m_flCollisionSpeedSqr > Square( 500 ) ) )
- {
- m_OnShatter.FireOutput( this, this );
- Shatter( pVictim );
- return;
- }
-
- // We'll handle this later in our touch call
- if ( ShouldStickToEntity( pVictim ) )
- return;
-
- // Determine if we should shatter
- CBaseEntity *pOwnerEntity = pVictim->GetOwnerEntity();
- bool bVictimIsStrider = ( ( pOwnerEntity != NULL ) && FClassnameIs( pOwnerEntity, "npc_strider" ) );
-
- // Break if we hit anything other than a strider while going fast enough.
- // Launched duds detonate if they hit anything other than a strider any speed.
- if ( ( bVictimIsStrider == false ) && ( ( m_bDud && m_bLaunched ) || m_flCollisionSpeedSqr > Square( 500 ) ) )
- {
- m_OnShatter.FireOutput( this, this );
- Shatter( pVictim );
- return;
- }
-
- // Just bounce
- BaseClass::VPhysicsCollision( index, pEvent );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Called to see if we should attach to the victim
-// Input : *pOther - the victim
-//-----------------------------------------------------------------------------
-void CWeaponStriderBuster::BusterTouch( CBaseEntity *pOther )
-{
- // Attempt to stick to the entity
- StickToEntity( pOther );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-inline bool CWeaponStriderBuster::IsAttachedToStrider( void ) const
-{
- CBaseEntity *pAttachedEnt = GetOwnerEntity();
- if ( pAttachedEnt && FClassnameIs( pAttachedEnt, "npc_strider" ) )
- return true;
-
- return false;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CWeaponStriderBuster::Detonate( void )
-{
- CBaseEntity *pVictim = GetOwnerEntity();
- if ( !m_bDud && pVictim )
- {
- // Kill the strider (with magic effect)
- CBasePlayer *pPlayer = AI_GetSinglePlayer();
- CTakeDamageInfo info( pPlayer, this, RandomVector( -100.0f, 100.0f ), GetAbsOrigin(), pVictim->GetHealth(), DMG_GENERIC );
- pVictim->TakeDamage( info );
-
- gamestats->Event_WeaponHit( ToBasePlayer( pPlayer ), true, GetClassname(), info );
-
- // Tracker 62293: There's a bug where the inflictor/attacker are reversed when calling TakeDamage above so the player never gets
- // credit for the strider buster kills. The code has a bunch of assumptions lower level, so it's safer to just fix it here by
- // crediting a kill to the player directly.
- gamestats->Event_PlayerKilledOther( pPlayer, pVictim, info );
- }
-
- m_OnDetonate.FireOutput( this, this );
-
- // Explode
- if ( !m_bDud )
- {
- CreateDestroyedEffect();
- EmitSound( "Weapon_StriderBuster.Detonate" );
- }
- else
- {
- DispatchParticleEffect( "striderbuster_explode_dummy_core", GetAbsOrigin(), GetAbsAngles() );
- EmitSound( "Weapon_StriderBuster.Dud_Detonate" );
- }
-
- // Go to bits!
- Shatter( pVictim );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Intercept damage and decide whether or not we want to trigger
-// Input : &info -
-//-----------------------------------------------------------------------------
-int CWeaponStriderBuster::OnTakeDamage( const CTakeDamageInfo &info )
-{
- // If we're attached, any damage from the player makes us trigger
- CBaseEntity *pInflictor = info.GetInflictor();
- CBaseEntity *pAttacker = info.GetAttacker();
- bool bInflictorIsPlayer = ( pInflictor != NULL && pInflictor->IsPlayer() );
- bool bAttackerIsPlayer = ( pAttacker != NULL && pAttacker->IsPlayer() );
-
- if ( GetParent() && GetParent()->ClassMatches( g_iszVehicle ) )
- {
- return 0;
- }
-
- // Only take damage from a player, for the moment
- if ( striderbuster_allow_all_damage.GetBool() || ( IsAttachedToStrider() && ( bAttackerIsPlayer || bInflictorIsPlayer ) ) )
- {
- Detonate();
- return 0;
- }
-
- if ( pAttacker && ( pAttacker->Classify() == CLASS_COMBINE || pAttacker->Classify() == CLASS_COMBINE_HUNTER ) )
- {
- if ( VPhysicsGetObject() && !VPhysicsGetObject()->IsMoveable() )
- {
- return 0;
- }
- }
-
- // Hunters are able to destroy strider busters
- if ( hunter_hate_held_striderbusters.GetBool() || hunter_hate_thrown_striderbusters.GetBool() || hunter_hate_attached_striderbusters.GetBool() )
- {
- if ( ( GetHealth() > 0 ) && ( pInflictor != NULL ) && FClassnameIs( pInflictor, "hunter_flechette" ) )
- {
- //
- // Flechette impacts don't hurt the striderbuster unless it's attached to a strider,
- // but the explosions always do. This is so that held or thrown striderbusters fly
- // awry because of the flechette, but attached striderbusters break instantly to make
- // the hunters more effective at defending the strider.
- //
- if ( IsAttachedToStrider() || !( info.GetDamageType() & DMG_NEVERGIB ) )
- {
- if( striderbuster_die_detach.GetBool() && IsAttachedToStrider() )
- {
- // Make the buster fall off and break.
- m_takedamage = DAMAGE_NO;
-
- CNPC_Strider *pStrider = dynamic_cast<CNPC_Strider *>(GetOwnerEntity());
- Assert( pStrider != NULL );
- pStrider->StriderBusterDetached( this );
- DestroyConstraint();
-
- // Amplify some lateral force.
- Vector vecForce = info.GetDamageForce();
- vecForce.z = 0.0f;
- VPhysicsGetObject()->ApplyForceCenter( vecForce * 5.0f );
-
- SetContextThink( NULL, gpGlobals->curtime, s_pBusterPingThinkContext );
-
- SetThink( &CWeaponStriderBuster::BusterDetachThink );
- SetNextThink( gpGlobals->curtime );
- m_iBusterFlags |= STRIDERBUSTER_FLAG_KNOCKED_OFF_STRIDER;
-
- return 0;
- }
- else
- {
- // Destroy the buster in place
- // Make sure they know it blew up prematurely.
- EmitSound( "Weapon_StriderBuster.Dud_Detonate" );
- DispatchParticleEffect( "striderbuster_break_flechette", GetAbsOrigin(), GetAbsAngles() );
- SetHealth( 0 );
-
- Shatter( info.GetAttacker() );
- return 0;
- }
- }
-
- if ( info.GetDamage() < 5 )
- {
- bool bFirst = ( m_CarryAngles.x == 45 && m_CarryAngles.y == 0 && m_CarryAngles.z == 0);
- float sinTime = sin( gpGlobals->curtime );
- bool bSubtractX = ( bFirst ) ? ( sinTime < 0 ) : ( m_CarryAngles.x < 45 );
-
- m_CarryAngles.x += ( 10.0 + 10.0 * fabsf( sinTime ) + random->RandomFloat( -2.5, 2.5 ) + random->RandomFloat( -2.5, 2.5 ) ) * ( ( bSubtractX ) ? -1.0 : 1.0 );
- m_CarryAngles.y = 15 * ( sin( gpGlobals->curtime ) + cos( gpGlobals->curtime * 0.5 ) ) * .5 + random->RandomFloat( -15, 15 );
- m_CarryAngles.z = 7.5 * ( sin( gpGlobals->curtime ) + sin( gpGlobals->curtime * 2.0 ) ) * .5 + random->RandomFloat( -7.5, 7.5 );
- }
-
- return 1;
- }
- }
-
- // Allow crushing damage
- if ( info.GetDamageType() & DMG_CRUSH )
- return BaseClass::OnTakeDamage( info );
-
- return 0;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CWeaponStriderBuster::OnPhysGunPickup( CBasePlayer *pPhysGunUser, PhysGunPickup_t reason )
-{
- m_PickupTime = gpGlobals->curtime;
- m_CarryAngles.Init( 45, 0, 0 );
- if ( ( reason == PICKED_UP_BY_CANNON ) && ( !HasSpawnFlags( SF_DONT_WEAPON_MANAGE ) ) )
- {
- WeaponManager_RemoveManaged( this );
- }
- else if ( reason == PUNTED_BY_CANNON )
- {
- Launch( pPhysGunUser );
- }
-
- BaseClass::OnPhysGunPickup( pPhysGunUser, reason );
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CWeaponStriderBuster::OnPhysGunDrop( CBasePlayer *pPhysGunUser, PhysGunDrop_t Reason )
-{
- if ( Reason == LAUNCHED_BY_CANNON )
- {
- Launch( pPhysGunUser );
- }
- else if ( ( Reason == DROPPED_BY_CANNON ) && ( !HasSpawnFlags( SF_DONT_WEAPON_MANAGE ) ) )
- {
- // This striderbuster is now fair game for autodeletion.
- WeaponManager_AddManaged( this );
- }
-
- BaseClass::OnPhysGunDrop( pPhysGunUser, Reason );
-}
-
-
-//-----------------------------------------------------------------------------
-// Fling the buster with the physcannon either via punt or launch.
-//-----------------------------------------------------------------------------
-void CWeaponStriderBuster::Launch( CBasePlayer *pPhysGunUser )
-{
- if ( !HasSpawnFlags( SF_DONT_WEAPON_MANAGE ) )
- {
- WeaponManager_RemoveManaged( this );
- }
-
- m_bLaunched = true;
-
- // Notify all nearby hunters that we were launched.
- Hunter_StriderBusterLaunched( this );
-
- // Start up the eye glow
- m_hMainGlow = CSprite::SpriteCreate( "sprites/blueglow1.vmt", GetLocalOrigin(), false );
-
- if ( m_hMainGlow != NULL )
- {
- m_hMainGlow->FollowEntity( this );
- m_hMainGlow->SetTransparency( kRenderGlow, 255, 255, 255, 140, kRenderFxNoDissipation );
- m_hMainGlow->SetScale( 2.0f );
- m_hMainGlow->SetGlowProxySize( 8.0f );
- }
-
- if ( !m_bNoseDiving )
- {
- DispatchParticleEffect( "striderbuster_trail", PATTACH_ABSORIGIN_FOLLOW, this );
- }
- else
- {
- DispatchParticleEffect( "striderbuster_shotdown_trail", PATTACH_ABSORIGIN_FOLLOW, this );
- }
-
- // We get our touch function from the physics system
- SetTouch ( &CWeaponStriderBuster::BusterTouch );
-
- SetThink( &CWeaponStriderBuster::BusterFlyThink );
- SetNextThink( gpGlobals->curtime );
-
- gamestats->Event_WeaponFired( pPhysGunUser, true, GetClassname() );
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : &forward -
-// flMass -
-// Output : Vector
-//-----------------------------------------------------------------------------
-Vector CWeaponStriderBuster::PhysGunLaunchVelocity( const Vector &forward, float flMass )
-{
- return ( striderbuster_shot_velocity.GetFloat() * forward );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CWeaponStriderBuster::Shatter( CBaseEntity *pAttacker )
-{
- if( m_bNoseDiving )
- m_OnShotDown.FireOutput( this, this );
-
- m_takedamage = DAMAGE_YES;
-
- if( !IsAttachedToStrider() )
- {
- // Don't display this particular effect if we're attached to a strider. This effect just gets lost
- // in the big strider explosion anyway, so let's recover some perf.
- DispatchParticleEffect( "striderbuster_break", GetAbsOrigin(), GetAbsAngles() );
- }
-
- // Buster is useless now. Stop thinking, touching.
- SetThink( NULL );
- SetTouch( NULL );
- SetContextThink( NULL, gpGlobals->curtime, s_pBusterPingThinkContext );
-
- // Deal deadly damage to ourselves (DMG_CRUSH is allowed, others are blocked)
- CTakeDamageInfo info( pAttacker, pAttacker, RandomVector( -100, 100 ), GetAbsOrigin(), 100.0f, DMG_CRUSH );
- TakeDamage( info );
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Give the buster a slight attraction to striders.
-// Ported back from the magnade.
-//-----------------------------------------------------------------------------
-void CWeaponStriderBuster::BusterFlyThink()
-{
- if (IsAttachedToStrider())
- return; // early out. Think no more.
-
- // If we're nosediving, forget about magnetism.
- if ( m_bNoseDiving )
- {
- if ( VPhysicsGetObject() )
- VPhysicsGetObject()->ApplyForceCenter( Vector( 0, 0, striderbuster_dive_force.GetFloat() ) );
- SetNextThink(gpGlobals->curtime + 0.01f);
- return;
- }
-
- // seek?
- const float magradius = 38.0 * sk_striderbuster_magnet_multiplier.GetFloat(); // radius of strider hull times multiplier
- if (magradius > 0 &&
- GetMoveType() == MOVETYPE_VPHYSICS &&
- VPhysicsGetObject()
- )
- {
- // find the nearest enemy.
- CBaseEntity *pList[16];
- Vector origin = GetAbsOrigin();
-
- // do a find in box ( a little faster than sphere )
- int count;
- {
- Vector mins,maxs;
- mins = origin;
- mins -= magradius;
-
- maxs = origin;
- maxs += magradius;
-
- count = UTIL_EntitiesInBox(pList, 16, mins, maxs, FL_NPC);
- }
-
- float magradiusSq = Square( magradius );
- float nearestDistSq = magradiusSq + 1;
- int bestFit = -1;
- Vector toTarget; // will be garbage unless something good is found
- CNPC_Strider *pBestStrider = NULL;
-
- for ( int ii = 0 ; ii < count ; ++ii )
- {
- CNPC_Strider *pStrider = dynamic_cast<CNPC_Strider *>(pList[ii]);
- if ( pStrider && !pStrider->CarriedByDropship() ) // ShouldStickToEntity() doesn't work because the strider NPC isn't what we glue to
- {
- // get distance squared
- VectorSubtract( pStrider->GetAdjustedOrigin(), GetAbsOrigin(), toTarget );
-
- //NDebugOverlay::Line( GetAbsOrigin(), GetAbsOrigin() + toTarget, 128, 0, 128, false, 0.1 );
-
- float dSq = toTarget.LengthSqr();
- if (dSq < nearestDistSq)
- {
- bestFit = ii; nearestDistSq = dSq;
- pBestStrider = pStrider;
- }
- }
- }
-
- if (bestFit >= 0) // we found something and should attract towards it. (hysterisis later?)
- {
- if ( striderbuster_debugseek.GetBool() )
- {
- NDebugOverlay::Circle( GetAbsOrigin() + toTarget, magradius, 255, 255, 255, 255, true, .1 );
- NDebugOverlay::Cross3D( GetAbsOrigin() + toTarget, magradius, 255, 255, 255, true, .1 );
- }
-
- // force magnitude.
- float magnitude = GetMass() * striderbuster_magnetic_force_strider.GetFloat();
- int falloff = striderbuster_falloff_power.GetInt();
- switch (falloff)
- {
- case 1:
- VPhysicsGetObject()->ApplyForceCenter( toTarget * (magnitude / nearestDistSq) ); // dividing through by distance squared normalizes toTarget and gives a linear falloff
- break;
- case 2:
- VPhysicsGetObject()->ApplyForceCenter( toTarget * (magnitude / (nearestDistSq * sqrtf(nearestDistSq))) ); // dividing through by distance cubed normalizes toTarget and gives a quadratic falloff
- break;
- case 3:
- VPhysicsGetObject()->ApplyForceCenter( toTarget * (magnitude / (nearestDistSq * nearestDistSq)) ); // dividing through by distance fourth normalizes toTarget and gives a cubic falloff
- break;
- case 4:
- {
- Vector toTarget;
- pBestStrider->GetAttachment( "buster_target", toTarget );
-
- if ( striderbuster_debugseek.GetBool() )
- {
- NDebugOverlay::Cross3D( toTarget, magradius, 255, 0, 255, true, .1 );
- NDebugOverlay::Cross3D( toTarget, magradius, 255, 0, 255, true, .1 );
- }
-
- toTarget -= GetAbsOrigin();
- toTarget.NormalizeInPlace();
- VPhysicsGetObject()->ApplyForceCenter( toTarget * magnitude );
-
- }
- break;
- default: // arbitrary powers
- VPhysicsGetObject()->ApplyForceCenter( toTarget * (magnitude * powf(nearestDistSq,(falloff+1.0f)/2)) ); // square root for distance instead of squared, add one to normalize toTarget
- break;
- }
- }
-
- SetNextThink(gpGlobals->curtime + 0.01f);
- }
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CWeaponStriderBuster::BusterDetachThink()
-{
- SetNextThink( gpGlobals->curtime + 0.1f );
-
- trace_t tr;
- UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() - Vector( 0, 0, 1200), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr );
-
- if( fabs(tr.startpos.z - tr.endpos.z) < 240.0f )
- {
- SetThink(NULL);
- EmitSound( "Weapon_StriderBuster.Dud_Detonate" );
- DispatchParticleEffect( "striderbuster_break_flechette", GetAbsOrigin(), GetAbsAngles() );
- SetHealth( 0 );
- CTakeDamageInfo info;
- info.SetDamage( 1.0f );
- info.SetAttacker( this );
- info.SetInflictor( this );
- Shatter(this);
- }
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CWeaponStriderBuster::BusterPingThink()
-{
- EmitSound( "Weapon_StriderBuster.Ping" );
-
- SetContextThink( &CWeaponStriderBuster::BusterPingThink, gpGlobals->curtime + BUSTER_PING_SOUND_FREQ, s_pBusterPingThinkContext );
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CWeaponStriderBuster::OnAddToCargoHold()
-{
- if ( !HasSpawnFlags( SF_DONT_WEAPON_MANAGE ) )
- {
- WeaponManager_RemoveManaged( this );
- }
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CWeaponStriderBuster::OnFlechetteAttach( Vector &vecFlechetteVelocity )
-{
- if ( m_bLaunched )
- {
- Vector vecForce = vecFlechetteVelocity;
- VectorNormalize( vecForce );
-
- vecForce *= 1000;
- vecForce.z = -5000;
-
- VPhysicsGetObject()->ApplyForceCenter( vecForce );
- }
-
- if ( !GetParent() || !GetParent()->ClassMatches( g_iszVehicle ) )
- {
- if ( !m_bNoseDiving )
- {
- //m_hGlowTrail->StopParticleSystem();
- StopParticleEffects( this );
-
- if( m_iBusterFlags & STRIDERBUSTER_FLAG_KNOCKED_OFF_STRIDER )
- {
- DispatchParticleEffect( "striderbuster_shotdown_trail", PATTACH_ABSORIGIN_FOLLOW, this );
- }
- else
- {
- DispatchParticleEffect( "striderbuster_flechette_attached", PATTACH_ABSORIGIN_FOLLOW, this );
- }
- }
-
- m_bNoseDiving = true;
- }
- m_nAttachedFlechettes++;
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-bool StriderBuster_IsAttachedStriderBuster( CBaseEntity *pEntity, CBaseEntity *pAttachedTo )
-{
- Assert(dynamic_cast<CWeaponStriderBuster *>(pEntity));
- if ( !pAttachedTo )
- return static_cast<CWeaponStriderBuster *>(pEntity)->m_hConstrainedEntity != NULL;
- else
- return static_cast<CWeaponStriderBuster *>(pEntity)->m_hConstrainedEntity == pAttachedTo;
-}
-
-
-//-----------------------------------------------------------------------------
-// Called when the striderbuster is placed in the jalopy's cargo container.
-//-----------------------------------------------------------------------------
-void StriderBuster_OnAddToCargoHold( CBaseEntity *pEntity )
-{
- CWeaponStriderBuster *pBuster = dynamic_cast <CWeaponStriderBuster *>( pEntity );
- if ( pBuster )
- {
- pBuster->OnAddToCargoHold();
- }
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-bool StriderBuster_OnFlechetteAttach( CBaseEntity *pEntity, Vector &vecFlechetteVelocity )
-{
- CWeaponStriderBuster *pBuster = dynamic_cast <CWeaponStriderBuster *>( pEntity );
- if ( pBuster )
- {
- pBuster->OnFlechetteAttach( vecFlechetteVelocity );
- return true;
- }
- return false;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-int StriderBuster_NumFlechettesAttached( CBaseEntity *pEntity )
-{
- CWeaponStriderBuster *pBuster = dynamic_cast <CWeaponStriderBuster *>( pEntity );
- if ( pBuster )
- {
- return pBuster->NumFlechettesAttached();
- }
- return 0;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-float StriderBuster_GetPickupTime( CBaseEntity *pEntity )
-{
- CWeaponStriderBuster *pBuster = dynamic_cast <CWeaponStriderBuster *>( pEntity );
- if ( pBuster )
- {
- return pBuster->GetPickupTime();
- }
- return 0;
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-bool StriderBuster_WasKnockedOffStrider( CBaseEntity *pEntity )
-{
- CWeaponStriderBuster *pBuster = dynamic_cast <CWeaponStriderBuster *>( pEntity );
- if ( pBuster )
- {
- return ((pBuster->GetStriderBusterFlags() & STRIDERBUSTER_FLAG_KNOCKED_OFF_STRIDER) != 0);
- }
-
- return false;
-}
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// An ingenious device. We call it "The Magnusson Device". Not my chosen label,
+// you understand, but it seemed to please the personnel.
+//
+// From your point of view you simply throw it at a strider and then blow it up.
+//
+//=============================================================================
+
+#include "cbase.h"
+#include "props.h"
+#include "vphysics/constraints.h"
+#include "physics_saverestore.h"
+#include "model_types.h"
+#include "ai_utils.h"
+#include "particle_system.h"
+#include "Sprite.h"
+#include "citadel_effects_shared.h"
+#include "soundent.h"
+#include "SpriteTrail.h"
+#include "te_effect_dispatch.h"
+#include "beam_shared.h"
+#include "npc_strider.h"
+#include "npc_hunter.h"
+#include "particle_parse.h"
+#include "gameweaponmanager.h"
+#include "gamestats.h"
+
+extern ConVar hunter_hate_held_striderbusters;
+extern ConVar hunter_hate_thrown_striderbusters;
+extern ConVar hunter_hate_attached_striderbusters;
+
+
+ConVar striderbuster_health( "striderbuster_health", "14" );
+ConVar striderbuster_autoaim_radius( "striderbuster_autoaim_radius", "64.0f" );
+ConVar striderbuster_shot_velocity( "striderbuster_shot_velocity", "2500.0", FCVAR_NONE, "Speed at which launch the bomb from the physcannon" );
+ConVar striderbuster_allow_all_damage( "striderbuster_allow_all_damage", "0", FCVAR_NONE, "If set to '1' the bomb will detonate on any damage taken. Otherwise only the player may trigger it." );
+
+//ConVar striderbuster_magnetic_radius("striderbuster_magnetic_radius","400.0f", FCVAR_NONE,"Maximum distance at which magnade experiences attraction to a target. Set to 0 to disable magnetism.");
+ConVar striderbuster_magnetic_force_strider("striderbuster_magnetic_force_strider", "750000.0f", FCVAR_NONE,"Intensity of magnade's attraction to a strider.");
+ConVar striderbuster_magnetic_force_hunter("striderbuster_magnetic_force_hunter","1750000.0f",FCVAR_NONE,"Intensity of magnade's attraction to a hunter.");
+ConVar striderbuster_falloff_power("striderbuster_falloff_power","4",FCVAR_NONE,"Order of the distance falloff. 1 = linear 2 = quadratic");
+ConVar striderbuster_leg_stick_dist( "striderbuster_leg_stick_dist", "80.0", FCVAR_NONE, "If the buster hits a strider's leg, the max distance from the head at which it sticks anyway." );
+ConVar striderbuster_debugseek( "striderbuster_debugseek", "0" );
+
+ConVar sk_striderbuster_magnet_multiplier( "sk_striderbuster_magnet_multiplier", "2.25" );
+
+ConVar striderbuster_die_detach( "striderbuster_die_detach", "1" ); // Drop off the strider if a hunter shoots me. (Instead of exploding)
+ConVar striderbuster_dive_force( "striderbuster_dive_force", "-200" ); // How much force to apply to a nosediving (dead in the air) striderbuster
+
+ConVar striderbuster_use_particle_flare( "striderbuster_use_particle_flare", "1" );
+
+#define STRIDERBUSTER_FLAG_KNOCKED_OFF_STRIDER 0x00000001 // We were knocked off of a strider after the player attached me.
+
+#define SF_DONT_WEAPON_MANAGE 0x800000
+
+#define STRIDERBUSTER_SPRITE_TRAIL "sprites/bluelaser1.vmt"
+
+string_t g_iszVehicle;
+
+#define BUSTER_PING_SOUND_FREQ 3.0f // How often (seconds) to issue the ping sound to remind players we are attached
+
+static const char *s_pBusterPingThinkContext = "BusterPing";
+
+class CWeaponStriderBuster : public CPhysicsProp
+{
+ DECLARE_CLASS( CWeaponStriderBuster, CPhysicsProp );
+ DECLARE_DATADESC();
+
+public:
+ CWeaponStriderBuster( void );
+
+ virtual void Precache( void );
+ virtual void Spawn( void );
+ virtual void Activate( void );
+
+ // Treat as a live target so hunters can attack us
+ virtual bool IsAlive() { return true; }
+
+ virtual void OnRestore( void );
+ virtual void VPhysicsCollision( int index, gamevcollisionevent_t *pEvent );
+ virtual void UpdateOnRemove( void );
+ virtual int OnTakeDamage( const CTakeDamageInfo &info );
+ virtual bool ShouldPuntUseLaunchForces( PhysGunForce_t reason ) { return ( reason == PHYSGUN_FORCE_LAUNCHED ); }
+ virtual QAngle PreferredCarryAngles( void ) { return m_CarryAngles; }
+ virtual bool HasPreferredCarryAnglesForPlayer( CBasePlayer *pPlayer ) { return true; }
+
+ virtual void OnPhysGunPickup( CBasePlayer *pPhysGunUser, PhysGunPickup_t reason );
+ virtual void OnPhysGunDrop( CBasePlayer *pPhysGunUser, PhysGunDrop_t Reason );
+ virtual Vector PhysGunLaunchVelocity( const Vector &forward, float flMass );
+ virtual float GetAutoAimRadius( void ) { return striderbuster_autoaim_radius.GetFloat(); }
+ virtual void BusterTouch( CBaseEntity *pOther );
+
+ virtual bool ShouldAttractAutoAim( CBaseEntity *pAimingEnt ) { return IsAttachedToStrider(); }
+
+ void InputConstraintBroken( inputdata_t &inputdata );
+ void BusterFlyThink();
+ void BusterDetachThink();
+ void BusterPingThink();
+
+ void OnAddToCargoHold();
+ void OnFlechetteAttach( Vector &vecForceDir );
+ int NumFlechettesAttached() { return m_nAttachedFlechettes; }
+
+ float GetPickupTime() { return m_PickupTime; }
+
+ int GetStriderBusterFlags() { return m_iBusterFlags; } // I added a flags field so we don't have to keep added bools for all of these contingencies (sjb)
+
+private:
+
+ void Launch( CBasePlayer *pPhysGunUser );
+ void Detonate( void );
+ void Shatter( CBaseEntity *pAttacker );
+ bool StickToEntity( CBaseEntity *pOther );
+ bool CreateConstraintToObject( CBaseEntity *pObject );
+ void DestroyConstraint( void );
+ bool ShouldStickToEntity( CBaseEntity *pEntity );
+ void CreateDestroyedEffect( void );
+
+ inline bool IsAttachedToStrider( void ) const;
+
+ bool m_bDud;
+ bool m_bLaunched;
+ bool m_bNoseDiving; // No magnetism, nosedive and break. Hunter flechettes set this.
+ int m_nAttachedFlechettes;
+ float m_flCollisionSpeedSqr;
+ int m_nAttachedBoneFollowerIndex;
+ float m_PickupTime;
+
+ IPhysicsConstraint *m_pConstraint;
+ EHANDLE m_hConstrainedEntity;
+
+ CHandle<CSprite> m_hGlowSprite;
+ CHandle<CSprite> m_hMainGlow;
+
+ //CHandle<CParticleSystem> m_hGlowTrail;
+ EHANDLE m_hParticleEffect;
+
+ int m_nRingTexture;
+
+ QAngle m_CarryAngles;
+
+ int m_iBusterFlags;
+
+ COutputEvent m_OnAttachToStrider;
+ COutputEvent m_OnDetonate;
+ COutputEvent m_OnShatter;
+ COutputEvent m_OnShotDown;
+
+friend bool StriderBuster_IsAttachedStriderBuster( CBaseEntity *pEntity, CBaseEntity * );
+
+};
+
+LINK_ENTITY_TO_CLASS( prop_stickybomb, CWeaponStriderBuster );
+LINK_ENTITY_TO_CLASS( weapon_striderbuster, CWeaponStriderBuster );
+
+BEGIN_DATADESC( CWeaponStriderBuster )
+ DEFINE_KEYFIELD( m_bDud, FIELD_BOOLEAN, "dud" ),
+
+ DEFINE_FIELD( m_bLaunched, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_bNoseDiving, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_nAttachedFlechettes, FIELD_INTEGER ),
+ DEFINE_FIELD( m_flCollisionSpeedSqr, FIELD_FLOAT ),
+ DEFINE_FIELD( m_hConstrainedEntity, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_hGlowSprite, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_hMainGlow, FIELD_EHANDLE ),
+ //DEFINE_FIELD( m_hGlowTrail, FIELD_EHANDLE ),
+
+ DEFINE_FIELD( m_nRingTexture, FIELD_INTEGER ),
+ DEFINE_FIELD( m_nAttachedBoneFollowerIndex, FIELD_INTEGER ),
+
+ DEFINE_FIELD( m_PickupTime, FIELD_TIME ),
+
+ DEFINE_FIELD( m_hParticleEffect, FIELD_EHANDLE ),
+
+ DEFINE_FIELD( m_CarryAngles, FIELD_VECTOR ),
+
+ DEFINE_FIELD( m_iBusterFlags, FIELD_INTEGER ),
+ DEFINE_PHYSPTR( m_pConstraint ),
+
+ DEFINE_INPUTFUNC( FIELD_VOID, "ConstraintBroken", InputConstraintBroken ),
+
+ DEFINE_OUTPUT( m_OnAttachToStrider, "OnAttachToStrider" ),
+ DEFINE_OUTPUT( m_OnDetonate, "OnDetonate" ),
+ DEFINE_OUTPUT( m_OnShatter, "OnShatter" ),
+ DEFINE_OUTPUT( m_OnShotDown, "OnShotDown" ),
+
+ DEFINE_ENTITYFUNC( BusterTouch ),
+ DEFINE_THINKFUNC( BusterFlyThink ),
+ DEFINE_THINKFUNC( BusterDetachThink ),
+ DEFINE_THINKFUNC( BusterPingThink ),
+END_DATADESC()
+
+CWeaponStriderBuster::CWeaponStriderBuster( void ) :
+ m_pConstraint( NULL ),
+ m_flCollisionSpeedSqr( -1.0f ),
+ m_hConstrainedEntity( NULL ),
+ m_nAttachedBoneFollowerIndex( -1 )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CWeaponStriderBuster::Precache( void )
+{
+ PrecacheScriptSound( "Weapon_StriderBuster.StickToEntity" );
+ PrecacheScriptSound( "Weapon_StriderBuster.Detonate" );
+ PrecacheScriptSound( "Weapon_StriderBuster.Dud_Detonate" );
+ PrecacheScriptSound( "Weapon_StriderBuster.Ping" );
+
+ PrecacheModel("sprites/orangeflare1.vmt");
+
+ UTIL_PrecacheOther( "env_citadel_energy_core" );
+ UTIL_PrecacheOther( "sparktrail" );
+
+ m_nRingTexture = PrecacheModel( "sprites/lgtning.vmt" );
+
+ PrecacheParticleSystem( "striderbuster_attach" );
+ PrecacheParticleSystem( "striderbuster_attached_pulse" );
+ PrecacheParticleSystem( "striderbuster_explode_core" );
+ PrecacheParticleSystem( "striderbuster_explode_dummy_core" );
+ PrecacheParticleSystem( "striderbuster_break_flechette" );
+ PrecacheParticleSystem( "striderbuster_trail" );
+ PrecacheParticleSystem( "striderbuster_shotdown_trail" );
+ PrecacheParticleSystem( "striderbuster_break" );
+ PrecacheParticleSystem( "striderbuster_flechette_attached" );
+
+ SetModelName( AllocPooledString("models/magnusson_device.mdl") );
+ BaseClass::Precache();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CWeaponStriderBuster::Spawn( void )
+{
+ SetModelName( AllocPooledString("models/magnusson_device.mdl") );
+ BaseClass::Spawn();
+
+ // Setup for being shot by the player
+ m_takedamage = DAMAGE_EVENTS_ONLY;
+
+ // Ignore touches until launched.
+ SetTouch ( NULL );
+
+ AddFlag( FL_AIMTARGET|FL_OBJECT );
+
+ m_hParticleEffect = CreateEntityByName( "info_particle_system" );
+ if ( m_hParticleEffect )
+ {
+ m_hParticleEffect->KeyValue( "start_active", "1" );
+ m_hParticleEffect->KeyValue( "effect_name", "striderbuster_smoke" );
+ DispatchSpawn( m_hParticleEffect );
+ if ( gpGlobals->curtime > 0.2f )
+ {
+ m_hParticleEffect->Activate();
+ }
+ m_hParticleEffect->SetAbsOrigin( GetAbsOrigin() );
+ m_hParticleEffect->SetParent( this );
+ }
+
+ SetHealth( striderbuster_health.GetFloat() );
+
+ SetNextThink(gpGlobals->curtime + 0.01f);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CWeaponStriderBuster::Activate( void )
+{
+ g_iszVehicle = AllocPooledString( "prop_vehicle_jeep" );
+ BaseClass::Activate();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CWeaponStriderBuster::OnRestore( void )
+{
+ BaseClass::OnRestore();
+
+ // If we have an entity we're attached to, attempt to reconstruct our bone follower setup
+ if ( m_hConstrainedEntity != NULL )
+ {
+ CNPC_Strider *pStrider = dynamic_cast<CNPC_Strider *>(m_hConstrainedEntity.Get());
+ if ( pStrider != NULL )
+ {
+ // Make sure we've done this step or we'll have no controller to attach to
+ pStrider->InitBoneFollowers();
+
+ // Attempt to make a connection to the same bone follower we attached to previously
+ CBoneFollower *pBoneFollower = pStrider->GetBoneFollowerByIndex( m_nAttachedBoneFollowerIndex );
+ if ( CreateConstraintToObject( pBoneFollower ) == false )
+ {
+ Msg( "Failed to reattach to bone follower %d\n", m_nAttachedBoneFollowerIndex );
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CWeaponStriderBuster::DestroyConstraint( void )
+{
+ // Destroy the constraint
+ if ( m_pConstraint != NULL )
+ {
+ physenv->DestroyConstraint( m_pConstraint );
+ m_pConstraint = NULL;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Create a constraint between this object and another
+// Input : *pObject - Object to constrain ourselves to
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CWeaponStriderBuster::CreateConstraintToObject( CBaseEntity *pObject )
+{
+ if ( m_pConstraint != NULL )
+ {
+ // Should we destroy the constraint and make a new one at this point?
+ Assert( 0 );
+ return false;
+ }
+
+ if ( pObject == NULL )
+ return false;
+
+ IPhysicsObject *pPhysObject = pObject->VPhysicsGetObject();
+ if ( pPhysObject == NULL )
+ return false;
+
+ IPhysicsObject *pMyPhysObject = VPhysicsGetObject();
+ if ( pPhysObject == NULL )
+ return false;
+
+ // Create the fixed constraint
+ constraint_fixedparams_t fixedConstraint;
+ fixedConstraint.Defaults();
+ fixedConstraint.InitWithCurrentObjectState( pPhysObject, pMyPhysObject );
+
+ IPhysicsConstraint *pConstraint = physenv->CreateFixedConstraint( pPhysObject, pMyPhysObject, NULL, fixedConstraint );
+ if ( pConstraint == NULL )
+ return false;
+
+ // Hold on to us
+ m_pConstraint = pConstraint;
+ pConstraint->SetGameData( (void *)this );
+ m_hConstrainedEntity = pObject->GetOwnerEntity();;
+
+ // Disable collisions between the two ents
+ PhysDisableObjectCollisions( pPhysObject, pMyPhysObject );
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Physics system has just told us our constraint has been broken
+//-----------------------------------------------------------------------------
+void CWeaponStriderBuster::InputConstraintBroken( inputdata_t &inputdata )
+{
+ // Shatter with no real explosion effect
+ Shatter( NULL );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CWeaponStriderBuster::UpdateOnRemove( void )
+{
+ DestroyConstraint();
+
+ if ( m_hGlowSprite != NULL )
+ {
+ m_hGlowSprite->FadeAndDie( 0.5f );
+ m_hGlowSprite = NULL;
+ }
+
+ if ( m_hParticleEffect )
+ {
+ UTIL_Remove( m_hParticleEffect );
+ }
+
+ BaseClass::UpdateOnRemove();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pEntity -
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CWeaponStriderBuster::ShouldStickToEntity( CBaseEntity *pEntity )
+{
+ if ( pEntity == NULL )
+ return false;
+
+ // Must have a follow parent
+ CBaseEntity *pFollowParent = pEntity->GetOwnerEntity();
+ if ( pFollowParent == NULL )
+ return false;
+
+ // Must be a strider
+ CNPC_Strider *pStrider = dynamic_cast<CNPC_Strider *>(pFollowParent);
+ if ( pStrider == NULL )
+ return false;
+
+ if( m_bNoseDiving )
+ return false;
+
+ // Don't attach to legs
+ CBoneFollower *pFollower = static_cast<CBoneFollower *>(pEntity);
+ if ( pStrider->IsLegBoneFollower( pFollower ) )
+ {
+ Vector vecDelta = pStrider->GetAdjustedOrigin() - GetAbsOrigin();
+ if ( vecDelta.Length() > striderbuster_leg_stick_dist.GetFloat() )
+ {
+ return false;
+ }
+ }
+
+ // Ick, this is kind of ugly, but it's also ugly having to pass pointer into this to avoid multiple castings!
+ // Save this to patch up save/restore later
+ m_nAttachedBoneFollowerIndex = pStrider->GetBoneFollowerIndex( pFollower );
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Stick to an entity (using hierarchy if we can)
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CWeaponStriderBuster::StickToEntity( CBaseEntity *pOther )
+{
+ // Make sure the object is travelling fast enough to stick
+ if ( m_flCollisionSpeedSqr > 50 && !m_bNoseDiving )
+ {
+ // See if this is a valid strider bit
+ if ( ShouldStickToEntity( pOther ) )
+ {
+ // Attempt to constraint to it
+ if ( CreateConstraintToObject( pOther ) )
+ {
+ // Only works for striders, at the moment
+ CBaseEntity *pFollowParent = pOther->GetOwnerEntity();
+ if ( pFollowParent == NULL )
+ return false;
+
+ // Allows us to identify our constrained object later
+ SetOwnerEntity( pFollowParent );
+
+ // Make a sound
+ EmitSound( "Weapon_StriderBuster.StickToEntity" );
+
+ DispatchParticleEffect( "striderbuster_attach", GetAbsOrigin(), GetAbsAngles(), NULL );
+
+ if( striderbuster_use_particle_flare.GetBool() )
+ {
+ // We don't have to save any pointers or handles to this because it's parented to the buster.
+ // So it will die when the buster dies. Yay.
+ CParticleSystem *pFlare = (CParticleSystem *) CreateEntityByName( "info_particle_system" );
+
+ if ( pFlare != NULL )
+ {
+ pFlare->KeyValue( "start_active", "1" );
+ pFlare->KeyValue( "effect_name", "striderbuster_attached_pulse" );
+ pFlare->SetParent( this );
+ pFlare->SetLocalOrigin( vec3_origin );
+ DispatchSpawn( pFlare );
+ pFlare->Activate();
+ }
+ }
+ else
+ {
+ // Create a glow sprite
+ m_hGlowSprite = CSprite::SpriteCreate( "sprites/orangeflare1.vmt", GetLocalOrigin(), false );
+
+ Assert( m_hGlowSprite );
+ if ( m_hGlowSprite != NULL )
+ {
+ m_hGlowSprite->TurnOn();
+ m_hGlowSprite->SetTransparency( kRenderWorldGlow, 255, 255, 255, 255, kRenderFxNoDissipation );
+ m_hGlowSprite->SetAbsOrigin( GetAbsOrigin() );
+ m_hGlowSprite->SetScale( 5.0f );
+ m_hGlowSprite->m_nRenderFX = kRenderFxStrobeFaster;
+ m_hGlowSprite->SetGlowProxySize( 16.0f );
+ m_hGlowSprite->SetParent( this );
+ }
+ }
+
+ // Stop touching things
+ SetTouch( NULL );
+
+ // Must be a strider
+ CNPC_Strider *pStrider = dynamic_cast<CNPC_Strider *>(pFollowParent);
+ if ( pStrider == NULL )
+ return false;
+
+ // Notify the strider we're attaching to him
+ pStrider->StriderBusterAttached( this );
+
+ m_OnAttachToStrider.FireOutput( this, this );
+
+ // Start the ping sound.
+ SetContextThink( &CWeaponStriderBuster::BusterPingThink, gpGlobals->curtime + BUSTER_PING_SOUND_FREQ, s_pBusterPingThinkContext );
+
+ // Don't autodelete this one!
+ WeaponManager_RemoveManaged( this );
+
+ return true;
+ }
+
+ return false;
+ }
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Create the explosion effect for the final big boom
+//-----------------------------------------------------------------------------
+void CWeaponStriderBuster::CreateDestroyedEffect( void )
+{
+ CBaseEntity *pTrail;
+
+ StopParticleEffects( this );
+
+ for ( int i = 0; i < 3; i++ )
+ {
+ pTrail = CreateEntityByName( "sparktrail" );
+ pTrail->SetOwnerEntity( this );
+ DispatchSpawn( pTrail );
+ }
+
+ DispatchParticleEffect( "striderbuster_explode_core", GetAbsOrigin(), GetAbsAngles() );
+
+ // Create liquid fountain gushtacular effect here!
+ CEffectData data;
+
+ int nNumSteps = 6;
+ float flRadStep = (2*M_PI) / nNumSteps;
+ for ( int i = 0; i < nNumSteps; i++ )
+ {
+ data.m_vOrigin = GetAbsOrigin() + RandomVector( -32.0f, 32.0f );
+ data.m_vNormal.x = cos( flRadStep*i );
+ data.m_vNormal.y = sin( flRadStep*i );
+ data.m_vNormal.z = 0.0f;
+ data.m_flScale = ( random->RandomInt( 0, 5 ) == 0 ) ? 1 : 2;
+
+ DispatchEffect( "StriderBlood", data );
+ }
+
+ // More effects
+ UTIL_ScreenShake( GetAbsOrigin(), 20.0f, 150.0, 1.0, 1250.0f, SHAKE_START );
+
+ data.m_vOrigin = GetAbsOrigin();
+ DispatchEffect( "cball_explode", data );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Handle a collision using our special behavior
+//-----------------------------------------------------------------------------
+void CWeaponStriderBuster::VPhysicsCollision( int index, gamevcollisionevent_t *pEvent )
+{
+ // Find out what we hit.
+ // Don't do anything special if we're already attached to a strider.
+ CBaseEntity *pVictim = pEvent->pEntities[!index];
+ if ( pVictim == NULL || m_pConstraint != NULL )
+ {
+ BaseClass::VPhysicsCollision( index, pEvent );
+ return;
+ }
+
+ // Don't attach if we're being held by the player
+ if ( VPhysicsGetObject()->GetGameFlags() & FVPHYSICS_PLAYER_HELD )
+ {
+ BaseClass::VPhysicsCollision( index, pEvent );
+ return;
+ }
+
+ // Save off the speed of the object
+ m_flCollisionSpeedSqr = ( pEvent->preVelocity[ index ] ).LengthSqr();
+
+ // Break if we hit the world while going fast enough.
+ // Launched duds detonate if they hit the world at any speed.
+ if ( pVictim->IsWorld() && ( ( m_bDud && m_bLaunched ) || m_flCollisionSpeedSqr > Square( 500 ) ) )
+ {
+ m_OnShatter.FireOutput( this, this );
+ Shatter( pVictim );
+ return;
+ }
+
+ // We'll handle this later in our touch call
+ if ( ShouldStickToEntity( pVictim ) )
+ return;
+
+ // Determine if we should shatter
+ CBaseEntity *pOwnerEntity = pVictim->GetOwnerEntity();
+ bool bVictimIsStrider = ( ( pOwnerEntity != NULL ) && FClassnameIs( pOwnerEntity, "npc_strider" ) );
+
+ // Break if we hit anything other than a strider while going fast enough.
+ // Launched duds detonate if they hit anything other than a strider any speed.
+ if ( ( bVictimIsStrider == false ) && ( ( m_bDud && m_bLaunched ) || m_flCollisionSpeedSqr > Square( 500 ) ) )
+ {
+ m_OnShatter.FireOutput( this, this );
+ Shatter( pVictim );
+ return;
+ }
+
+ // Just bounce
+ BaseClass::VPhysicsCollision( index, pEvent );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Called to see if we should attach to the victim
+// Input : *pOther - the victim
+//-----------------------------------------------------------------------------
+void CWeaponStriderBuster::BusterTouch( CBaseEntity *pOther )
+{
+ // Attempt to stick to the entity
+ StickToEntity( pOther );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+inline bool CWeaponStriderBuster::IsAttachedToStrider( void ) const
+{
+ CBaseEntity *pAttachedEnt = GetOwnerEntity();
+ if ( pAttachedEnt && FClassnameIs( pAttachedEnt, "npc_strider" ) )
+ return true;
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CWeaponStriderBuster::Detonate( void )
+{
+ CBaseEntity *pVictim = GetOwnerEntity();
+ if ( !m_bDud && pVictim )
+ {
+ // Kill the strider (with magic effect)
+ CBasePlayer *pPlayer = AI_GetSinglePlayer();
+ CTakeDamageInfo info( pPlayer, this, RandomVector( -100.0f, 100.0f ), GetAbsOrigin(), pVictim->GetHealth(), DMG_GENERIC );
+ pVictim->TakeDamage( info );
+
+ gamestats->Event_WeaponHit( ToBasePlayer( pPlayer ), true, GetClassname(), info );
+
+ // Tracker 62293: There's a bug where the inflictor/attacker are reversed when calling TakeDamage above so the player never gets
+ // credit for the strider buster kills. The code has a bunch of assumptions lower level, so it's safer to just fix it here by
+ // crediting a kill to the player directly.
+ gamestats->Event_PlayerKilledOther( pPlayer, pVictim, info );
+ }
+
+ m_OnDetonate.FireOutput( this, this );
+
+ // Explode
+ if ( !m_bDud )
+ {
+ CreateDestroyedEffect();
+ EmitSound( "Weapon_StriderBuster.Detonate" );
+ }
+ else
+ {
+ DispatchParticleEffect( "striderbuster_explode_dummy_core", GetAbsOrigin(), GetAbsAngles() );
+ EmitSound( "Weapon_StriderBuster.Dud_Detonate" );
+ }
+
+ // Go to bits!
+ Shatter( pVictim );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Intercept damage and decide whether or not we want to trigger
+// Input : &info -
+//-----------------------------------------------------------------------------
+int CWeaponStriderBuster::OnTakeDamage( const CTakeDamageInfo &info )
+{
+ // If we're attached, any damage from the player makes us trigger
+ CBaseEntity *pInflictor = info.GetInflictor();
+ CBaseEntity *pAttacker = info.GetAttacker();
+ bool bInflictorIsPlayer = ( pInflictor != NULL && pInflictor->IsPlayer() );
+ bool bAttackerIsPlayer = ( pAttacker != NULL && pAttacker->IsPlayer() );
+
+ if ( GetParent() && GetParent()->ClassMatches( g_iszVehicle ) )
+ {
+ return 0;
+ }
+
+ // Only take damage from a player, for the moment
+ if ( striderbuster_allow_all_damage.GetBool() || ( IsAttachedToStrider() && ( bAttackerIsPlayer || bInflictorIsPlayer ) ) )
+ {
+ Detonate();
+ return 0;
+ }
+
+ if ( pAttacker && ( pAttacker->Classify() == CLASS_COMBINE || pAttacker->Classify() == CLASS_COMBINE_HUNTER ) )
+ {
+ if ( VPhysicsGetObject() && !VPhysicsGetObject()->IsMoveable() )
+ {
+ return 0;
+ }
+ }
+
+ // Hunters are able to destroy strider busters
+ if ( hunter_hate_held_striderbusters.GetBool() || hunter_hate_thrown_striderbusters.GetBool() || hunter_hate_attached_striderbusters.GetBool() )
+ {
+ if ( ( GetHealth() > 0 ) && ( pInflictor != NULL ) && FClassnameIs( pInflictor, "hunter_flechette" ) )
+ {
+ //
+ // Flechette impacts don't hurt the striderbuster unless it's attached to a strider,
+ // but the explosions always do. This is so that held or thrown striderbusters fly
+ // awry because of the flechette, but attached striderbusters break instantly to make
+ // the hunters more effective at defending the strider.
+ //
+ if ( IsAttachedToStrider() || !( info.GetDamageType() & DMG_NEVERGIB ) )
+ {
+ if( striderbuster_die_detach.GetBool() && IsAttachedToStrider() )
+ {
+ // Make the buster fall off and break.
+ m_takedamage = DAMAGE_NO;
+
+ CNPC_Strider *pStrider = dynamic_cast<CNPC_Strider *>(GetOwnerEntity());
+ Assert( pStrider != NULL );
+ pStrider->StriderBusterDetached( this );
+ DestroyConstraint();
+
+ // Amplify some lateral force.
+ Vector vecForce = info.GetDamageForce();
+ vecForce.z = 0.0f;
+ VPhysicsGetObject()->ApplyForceCenter( vecForce * 5.0f );
+
+ SetContextThink( NULL, gpGlobals->curtime, s_pBusterPingThinkContext );
+
+ SetThink( &CWeaponStriderBuster::BusterDetachThink );
+ SetNextThink( gpGlobals->curtime );
+ m_iBusterFlags |= STRIDERBUSTER_FLAG_KNOCKED_OFF_STRIDER;
+
+ return 0;
+ }
+ else
+ {
+ // Destroy the buster in place
+ // Make sure they know it blew up prematurely.
+ EmitSound( "Weapon_StriderBuster.Dud_Detonate" );
+ DispatchParticleEffect( "striderbuster_break_flechette", GetAbsOrigin(), GetAbsAngles() );
+ SetHealth( 0 );
+
+ Shatter( info.GetAttacker() );
+ return 0;
+ }
+ }
+
+ if ( info.GetDamage() < 5 )
+ {
+ bool bFirst = ( m_CarryAngles.x == 45 && m_CarryAngles.y == 0 && m_CarryAngles.z == 0);
+ float sinTime = sin( gpGlobals->curtime );
+ bool bSubtractX = ( bFirst ) ? ( sinTime < 0 ) : ( m_CarryAngles.x < 45 );
+
+ m_CarryAngles.x += ( 10.0 + 10.0 * fabsf( sinTime ) + random->RandomFloat( -2.5, 2.5 ) + random->RandomFloat( -2.5, 2.5 ) ) * ( ( bSubtractX ) ? -1.0 : 1.0 );
+ m_CarryAngles.y = 15 * ( sin( gpGlobals->curtime ) + cos( gpGlobals->curtime * 0.5 ) ) * .5 + random->RandomFloat( -15, 15 );
+ m_CarryAngles.z = 7.5 * ( sin( gpGlobals->curtime ) + sin( gpGlobals->curtime * 2.0 ) ) * .5 + random->RandomFloat( -7.5, 7.5 );
+ }
+
+ return 1;
+ }
+ }
+
+ // Allow crushing damage
+ if ( info.GetDamageType() & DMG_CRUSH )
+ return BaseClass::OnTakeDamage( info );
+
+ return 0;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CWeaponStriderBuster::OnPhysGunPickup( CBasePlayer *pPhysGunUser, PhysGunPickup_t reason )
+{
+ m_PickupTime = gpGlobals->curtime;
+ m_CarryAngles.Init( 45, 0, 0 );
+ if ( ( reason == PICKED_UP_BY_CANNON ) && ( !HasSpawnFlags( SF_DONT_WEAPON_MANAGE ) ) )
+ {
+ WeaponManager_RemoveManaged( this );
+ }
+ else if ( reason == PUNTED_BY_CANNON )
+ {
+ Launch( pPhysGunUser );
+ }
+
+ BaseClass::OnPhysGunPickup( pPhysGunUser, reason );
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CWeaponStriderBuster::OnPhysGunDrop( CBasePlayer *pPhysGunUser, PhysGunDrop_t Reason )
+{
+ if ( Reason == LAUNCHED_BY_CANNON )
+ {
+ Launch( pPhysGunUser );
+ }
+ else if ( ( Reason == DROPPED_BY_CANNON ) && ( !HasSpawnFlags( SF_DONT_WEAPON_MANAGE ) ) )
+ {
+ // This striderbuster is now fair game for autodeletion.
+ WeaponManager_AddManaged( this );
+ }
+
+ BaseClass::OnPhysGunDrop( pPhysGunUser, Reason );
+}
+
+
+//-----------------------------------------------------------------------------
+// Fling the buster with the physcannon either via punt or launch.
+//-----------------------------------------------------------------------------
+void CWeaponStriderBuster::Launch( CBasePlayer *pPhysGunUser )
+{
+ if ( !HasSpawnFlags( SF_DONT_WEAPON_MANAGE ) )
+ {
+ WeaponManager_RemoveManaged( this );
+ }
+
+ m_bLaunched = true;
+
+ // Notify all nearby hunters that we were launched.
+ Hunter_StriderBusterLaunched( this );
+
+ // Start up the eye glow
+ m_hMainGlow = CSprite::SpriteCreate( "sprites/blueglow1.vmt", GetLocalOrigin(), false );
+
+ if ( m_hMainGlow != NULL )
+ {
+ m_hMainGlow->FollowEntity( this );
+ m_hMainGlow->SetTransparency( kRenderGlow, 255, 255, 255, 140, kRenderFxNoDissipation );
+ m_hMainGlow->SetScale( 2.0f );
+ m_hMainGlow->SetGlowProxySize( 8.0f );
+ }
+
+ if ( !m_bNoseDiving )
+ {
+ DispatchParticleEffect( "striderbuster_trail", PATTACH_ABSORIGIN_FOLLOW, this );
+ }
+ else
+ {
+ DispatchParticleEffect( "striderbuster_shotdown_trail", PATTACH_ABSORIGIN_FOLLOW, this );
+ }
+
+ // We get our touch function from the physics system
+ SetTouch ( &CWeaponStriderBuster::BusterTouch );
+
+ SetThink( &CWeaponStriderBuster::BusterFlyThink );
+ SetNextThink( gpGlobals->curtime );
+
+ gamestats->Event_WeaponFired( pPhysGunUser, true, GetClassname() );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : &forward -
+// flMass -
+// Output : Vector
+//-----------------------------------------------------------------------------
+Vector CWeaponStriderBuster::PhysGunLaunchVelocity( const Vector &forward, float flMass )
+{
+ return ( striderbuster_shot_velocity.GetFloat() * forward );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CWeaponStriderBuster::Shatter( CBaseEntity *pAttacker )
+{
+ if( m_bNoseDiving )
+ m_OnShotDown.FireOutput( this, this );
+
+ m_takedamage = DAMAGE_YES;
+
+ if( !IsAttachedToStrider() )
+ {
+ // Don't display this particular effect if we're attached to a strider. This effect just gets lost
+ // in the big strider explosion anyway, so let's recover some perf.
+ DispatchParticleEffect( "striderbuster_break", GetAbsOrigin(), GetAbsAngles() );
+ }
+
+ // Buster is useless now. Stop thinking, touching.
+ SetThink( NULL );
+ SetTouch( NULL );
+ SetContextThink( NULL, gpGlobals->curtime, s_pBusterPingThinkContext );
+
+ // Deal deadly damage to ourselves (DMG_CRUSH is allowed, others are blocked)
+ CTakeDamageInfo info( pAttacker, pAttacker, RandomVector( -100, 100 ), GetAbsOrigin(), 100.0f, DMG_CRUSH );
+ TakeDamage( info );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Give the buster a slight attraction to striders.
+// Ported back from the magnade.
+//-----------------------------------------------------------------------------
+void CWeaponStriderBuster::BusterFlyThink()
+{
+ if (IsAttachedToStrider())
+ return; // early out. Think no more.
+
+ // If we're nosediving, forget about magnetism.
+ if ( m_bNoseDiving )
+ {
+ if ( VPhysicsGetObject() )
+ VPhysicsGetObject()->ApplyForceCenter( Vector( 0, 0, striderbuster_dive_force.GetFloat() ) );
+ SetNextThink(gpGlobals->curtime + 0.01f);
+ return;
+ }
+
+ // seek?
+ const float magradius = 38.0 * sk_striderbuster_magnet_multiplier.GetFloat(); // radius of strider hull times multiplier
+ if (magradius > 0 &&
+ GetMoveType() == MOVETYPE_VPHYSICS &&
+ VPhysicsGetObject()
+ )
+ {
+ // find the nearest enemy.
+ CBaseEntity *pList[16];
+ Vector origin = GetAbsOrigin();
+
+ // do a find in box ( a little faster than sphere )
+ int count;
+ {
+ Vector mins,maxs;
+ mins = origin;
+ mins -= magradius;
+
+ maxs = origin;
+ maxs += magradius;
+
+ count = UTIL_EntitiesInBox(pList, 16, mins, maxs, FL_NPC);
+ }
+
+ float magradiusSq = Square( magradius );
+ float nearestDistSq = magradiusSq + 1;
+ int bestFit = -1;
+ Vector toTarget; // will be garbage unless something good is found
+ CNPC_Strider *pBestStrider = NULL;
+
+ for ( int ii = 0 ; ii < count ; ++ii )
+ {
+ CNPC_Strider *pStrider = dynamic_cast<CNPC_Strider *>(pList[ii]);
+ if ( pStrider && !pStrider->CarriedByDropship() ) // ShouldStickToEntity() doesn't work because the strider NPC isn't what we glue to
+ {
+ // get distance squared
+ VectorSubtract( pStrider->GetAdjustedOrigin(), GetAbsOrigin(), toTarget );
+
+ //NDebugOverlay::Line( GetAbsOrigin(), GetAbsOrigin() + toTarget, 128, 0, 128, false, 0.1 );
+
+ float dSq = toTarget.LengthSqr();
+ if (dSq < nearestDistSq)
+ {
+ bestFit = ii; nearestDistSq = dSq;
+ pBestStrider = pStrider;
+ }
+ }
+ }
+
+ if (bestFit >= 0) // we found something and should attract towards it. (hysterisis later?)
+ {
+ if ( striderbuster_debugseek.GetBool() )
+ {
+ NDebugOverlay::Circle( GetAbsOrigin() + toTarget, magradius, 255, 255, 255, 255, true, .1 );
+ NDebugOverlay::Cross3D( GetAbsOrigin() + toTarget, magradius, 255, 255, 255, true, .1 );
+ }
+
+ // force magnitude.
+ float magnitude = GetMass() * striderbuster_magnetic_force_strider.GetFloat();
+ int falloff = striderbuster_falloff_power.GetInt();
+ switch (falloff)
+ {
+ case 1:
+ VPhysicsGetObject()->ApplyForceCenter( toTarget * (magnitude / nearestDistSq) ); // dividing through by distance squared normalizes toTarget and gives a linear falloff
+ break;
+ case 2:
+ VPhysicsGetObject()->ApplyForceCenter( toTarget * (magnitude / (nearestDistSq * sqrtf(nearestDistSq))) ); // dividing through by distance cubed normalizes toTarget and gives a quadratic falloff
+ break;
+ case 3:
+ VPhysicsGetObject()->ApplyForceCenter( toTarget * (magnitude / (nearestDistSq * nearestDistSq)) ); // dividing through by distance fourth normalizes toTarget and gives a cubic falloff
+ break;
+ case 4:
+ {
+ Vector toTarget;
+ pBestStrider->GetAttachment( "buster_target", toTarget );
+
+ if ( striderbuster_debugseek.GetBool() )
+ {
+ NDebugOverlay::Cross3D( toTarget, magradius, 255, 0, 255, true, .1 );
+ NDebugOverlay::Cross3D( toTarget, magradius, 255, 0, 255, true, .1 );
+ }
+
+ toTarget -= GetAbsOrigin();
+ toTarget.NormalizeInPlace();
+ VPhysicsGetObject()->ApplyForceCenter( toTarget * magnitude );
+
+ }
+ break;
+ default: // arbitrary powers
+ VPhysicsGetObject()->ApplyForceCenter( toTarget * (magnitude * powf(nearestDistSq,(falloff+1.0f)/2)) ); // square root for distance instead of squared, add one to normalize toTarget
+ break;
+ }
+ }
+
+ SetNextThink(gpGlobals->curtime + 0.01f);
+ }
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CWeaponStriderBuster::BusterDetachThink()
+{
+ SetNextThink( gpGlobals->curtime + 0.1f );
+
+ trace_t tr;
+ UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() - Vector( 0, 0, 1200), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr );
+
+ if( fabs(tr.startpos.z - tr.endpos.z) < 240.0f )
+ {
+ SetThink(NULL);
+ EmitSound( "Weapon_StriderBuster.Dud_Detonate" );
+ DispatchParticleEffect( "striderbuster_break_flechette", GetAbsOrigin(), GetAbsAngles() );
+ SetHealth( 0 );
+ CTakeDamageInfo info;
+ info.SetDamage( 1.0f );
+ info.SetAttacker( this );
+ info.SetInflictor( this );
+ Shatter(this);
+ }
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CWeaponStriderBuster::BusterPingThink()
+{
+ EmitSound( "Weapon_StriderBuster.Ping" );
+
+ SetContextThink( &CWeaponStriderBuster::BusterPingThink, gpGlobals->curtime + BUSTER_PING_SOUND_FREQ, s_pBusterPingThinkContext );
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CWeaponStriderBuster::OnAddToCargoHold()
+{
+ if ( !HasSpawnFlags( SF_DONT_WEAPON_MANAGE ) )
+ {
+ WeaponManager_RemoveManaged( this );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CWeaponStriderBuster::OnFlechetteAttach( Vector &vecFlechetteVelocity )
+{
+ if ( m_bLaunched )
+ {
+ Vector vecForce = vecFlechetteVelocity;
+ VectorNormalize( vecForce );
+
+ vecForce *= 1000;
+ vecForce.z = -5000;
+
+ VPhysicsGetObject()->ApplyForceCenter( vecForce );
+ }
+
+ if ( !GetParent() || !GetParent()->ClassMatches( g_iszVehicle ) )
+ {
+ if ( !m_bNoseDiving )
+ {
+ //m_hGlowTrail->StopParticleSystem();
+ StopParticleEffects( this );
+
+ if( m_iBusterFlags & STRIDERBUSTER_FLAG_KNOCKED_OFF_STRIDER )
+ {
+ DispatchParticleEffect( "striderbuster_shotdown_trail", PATTACH_ABSORIGIN_FOLLOW, this );
+ }
+ else
+ {
+ DispatchParticleEffect( "striderbuster_flechette_attached", PATTACH_ABSORIGIN_FOLLOW, this );
+ }
+ }
+
+ m_bNoseDiving = true;
+ }
+ m_nAttachedFlechettes++;
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+bool StriderBuster_IsAttachedStriderBuster( CBaseEntity *pEntity, CBaseEntity *pAttachedTo )
+{
+ Assert(dynamic_cast<CWeaponStriderBuster *>(pEntity));
+ if ( !pAttachedTo )
+ return static_cast<CWeaponStriderBuster *>(pEntity)->m_hConstrainedEntity != NULL;
+ else
+ return static_cast<CWeaponStriderBuster *>(pEntity)->m_hConstrainedEntity == pAttachedTo;
+}
+
+
+//-----------------------------------------------------------------------------
+// Called when the striderbuster is placed in the jalopy's cargo container.
+//-----------------------------------------------------------------------------
+void StriderBuster_OnAddToCargoHold( CBaseEntity *pEntity )
+{
+ CWeaponStriderBuster *pBuster = dynamic_cast <CWeaponStriderBuster *>( pEntity );
+ if ( pBuster )
+ {
+ pBuster->OnAddToCargoHold();
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+bool StriderBuster_OnFlechetteAttach( CBaseEntity *pEntity, Vector &vecFlechetteVelocity )
+{
+ CWeaponStriderBuster *pBuster = dynamic_cast <CWeaponStriderBuster *>( pEntity );
+ if ( pBuster )
+ {
+ pBuster->OnFlechetteAttach( vecFlechetteVelocity );
+ return true;
+ }
+ return false;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+int StriderBuster_NumFlechettesAttached( CBaseEntity *pEntity )
+{
+ CWeaponStriderBuster *pBuster = dynamic_cast <CWeaponStriderBuster *>( pEntity );
+ if ( pBuster )
+ {
+ return pBuster->NumFlechettesAttached();
+ }
+ return 0;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+float StriderBuster_GetPickupTime( CBaseEntity *pEntity )
+{
+ CWeaponStriderBuster *pBuster = dynamic_cast <CWeaponStriderBuster *>( pEntity );
+ if ( pBuster )
+ {
+ return pBuster->GetPickupTime();
+ }
+ return 0;
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+bool StriderBuster_WasKnockedOffStrider( CBaseEntity *pEntity )
+{
+ CWeaponStriderBuster *pBuster = dynamic_cast <CWeaponStriderBuster *>( pEntity );
+ if ( pBuster )
+ {
+ return ((pBuster->GetStriderBusterFlags() & STRIDERBUSTER_FLAG_KNOCKED_OFF_STRIDER) != 0);
+ }
+
+ return false;
+}
diff --git a/mp/src/game/server/episodic/weapon_striderbuster.h b/mp/src/game/server/episodic/weapon_striderbuster.h
index 7859b728..fe670347 100644
--- a/mp/src/game/server/episodic/weapon_striderbuster.h
+++ b/mp/src/game/server/episodic/weapon_striderbuster.h
@@ -1,20 +1,20 @@
-//========= Copyright Valve Corporation, All rights reserved. ============//
-//
-// Helper functions for the striderbuster weapon.
-//
-//=============================================================================
-
-#ifndef WEAPON_STRIDERBUSTER_H
-#define WEAPON_STRIDERBUSTER_H
-#ifdef _WIN32
-#pragma once
-#endif
-
-bool StriderBuster_IsAttachedStriderBuster( CBaseEntity *pEntity, CBaseEntity *pAttachedTo = NULL );
-void StriderBuster_OnAddToCargoHold( CBaseEntity *pEntity );
-bool StriderBuster_OnFlechetteAttach( CBaseEntity *pEntity, Vector &vecForceDir );
-int StriderBuster_NumFlechettesAttached( CBaseEntity *pEntity );
-float StriderBuster_GetPickupTime( CBaseEntity *pEntity );
-bool StriderBuster_WasKnockedOffStrider( CBaseEntity *pEntity );
-
-#endif // WEAPON_STRIDERBUSTER_H
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Helper functions for the striderbuster weapon.
+//
+//=============================================================================
+
+#ifndef WEAPON_STRIDERBUSTER_H
+#define WEAPON_STRIDERBUSTER_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+bool StriderBuster_IsAttachedStriderBuster( CBaseEntity *pEntity, CBaseEntity *pAttachedTo = NULL );
+void StriderBuster_OnAddToCargoHold( CBaseEntity *pEntity );
+bool StriderBuster_OnFlechetteAttach( CBaseEntity *pEntity, Vector &vecForceDir );
+int StriderBuster_NumFlechettesAttached( CBaseEntity *pEntity );
+float StriderBuster_GetPickupTime( CBaseEntity *pEntity );
+bool StriderBuster_WasKnockedOffStrider( CBaseEntity *pEntity );
+
+#endif // WEAPON_STRIDERBUSTER_H