You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
879 lines
26 KiB
879 lines
26 KiB
![]()
4 years ago
|
//========= 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()
|
||
|
}
|