1048 lines
29 KiB
C++
1048 lines
29 KiB
C++
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
|
|
//
|
|
// Purpose:
|
|
//
|
|
// $NoKeywords: $
|
|
//=============================================================================//
|
|
|
|
#include "cbase.h"
|
|
|
|
#include "animation.h" // for NOMOTION
|
|
|
|
#include "ai_motor.h"
|
|
#include "ai_navigator.h"
|
|
#include "ai_basenpc.h"
|
|
#include "ai_localnavigator.h"
|
|
#include "ai_moveprobe.h"
|
|
#include "saverestore_utlvector.h"
|
|
|
|
// memdbgon must be the last include file in a .cpp file!!!
|
|
#include "tier0/memdbgon.h"
|
|
|
|
#ifdef DEBUG
|
|
ConVar ai_draw_motor_movement( "ai_draw_motor_movement","0" );
|
|
#endif
|
|
|
|
extern float GetFloorZ(const Vector &origin);
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Use these functions to set breakpoints to find out where movement is failing
|
|
#ifdef DEBUG
|
|
void DebugNoteMovementFailure()
|
|
{
|
|
}
|
|
|
|
// a place to put breakpoints
|
|
#pragma warning(push)
|
|
#pragma warning(disable:4189)
|
|
AIMoveResult_t DbgResult( AIMoveResult_t result )
|
|
{
|
|
if ( result < AIMR_OK )
|
|
{
|
|
int breakHere = 1;
|
|
}
|
|
|
|
switch ( result )
|
|
{
|
|
case AIMR_BLOCKED_ENTITY:
|
|
return AIMR_BLOCKED_ENTITY;
|
|
case AIMR_BLOCKED_WORLD:
|
|
return AIMR_BLOCKED_WORLD;
|
|
case AIMR_BLOCKED_NPC:
|
|
return AIMR_BLOCKED_NPC;
|
|
case AIMR_ILLEGAL:
|
|
return AIMR_ILLEGAL;
|
|
case AIMR_OK:
|
|
return AIMR_OK;
|
|
case AIMR_CHANGE_TYPE:
|
|
return AIMR_CHANGE_TYPE;
|
|
};
|
|
return AIMR_ILLEGAL;
|
|
};
|
|
#endif
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//
|
|
// class CAI_Motor
|
|
//
|
|
|
|
BEGIN_SIMPLE_DATADESC( CAI_Motor )
|
|
// m_flMoveInterval (think transient)
|
|
DEFINE_FIELD( m_IdealYaw, FIELD_FLOAT ),
|
|
DEFINE_FIELD( m_YawSpeed, FIELD_FLOAT ),
|
|
DEFINE_FIELD( m_vecVelocity, FIELD_VECTOR ),
|
|
DEFINE_FIELD( m_vecAngularVelocity, FIELD_VECTOR ),
|
|
DEFINE_FIELD( m_nDismountSequence, FIELD_INTEGER ),
|
|
DEFINE_FIELD( m_vecDismount, FIELD_VECTOR ),
|
|
DEFINE_UTLVECTOR( m_facingQueue, FIELD_EMBEDDED ),
|
|
DEFINE_FIELD( m_bYawLocked, FIELD_BOOLEAN ),
|
|
// m_pMoveProbe
|
|
END_DATADESC()
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
CAI_Motor::CAI_Motor(CAI_BaseNPC *pOuter)
|
|
: CAI_Component( pOuter )
|
|
{
|
|
m_flMoveInterval = 0;
|
|
|
|
m_IdealYaw = 0;
|
|
m_YawSpeed = 0;
|
|
m_vecVelocity = Vector( 0, 0, 0 );
|
|
m_pMoveProbe = NULL;
|
|
m_bYawLocked = false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
CAI_Motor::~CAI_Motor()
|
|
{
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void CAI_Motor::Init( IAI_MovementSink *pMovementServices )
|
|
{
|
|
CAI_ProxyMovementSink::Init( pMovementServices );
|
|
m_pMoveProbe = GetOuter()->GetMoveProbe(); // @TODO (toml 03-30-03): this is a "bad" way to grab this pointer. Components should have an explcit "init" phase.
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Step iteratively toward a destination position
|
|
//-----------------------------------------------------------------------------
|
|
AIMotorMoveResult_t CAI_Motor::MoveGroundStep( const Vector &newPos, CBaseEntity *pMoveTarget, float yaw, bool bAsFarAsCan, bool bTestZ, AIMoveTrace_t *pTraceResult )
|
|
{
|
|
// By definition, this will produce different results than GroundMoveLimit()
|
|
// because there's no guarantee that it will step exactly one step
|
|
|
|
// See how far toward the new position we can step...
|
|
// But don't actually test for ground geometric validity;
|
|
// if it isn't valid, there's not much we can do about it
|
|
AIMoveTrace_t moveTrace;
|
|
unsigned testFlags = AITGM_IGNORE_FLOOR;
|
|
|
|
char *pchHackBoolToInt = (char*)(&bTestZ);
|
|
if ( *pchHackBoolToInt == 2 )
|
|
{
|
|
testFlags |= AITGM_CRAWL_LARGE_STEPS;
|
|
}
|
|
else
|
|
{
|
|
if ( !bTestZ )
|
|
testFlags |= AITGM_2D;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
if ( ai_draw_motor_movement.GetBool() )
|
|
testFlags |= AITGM_DRAW_RESULTS;
|
|
#endif
|
|
|
|
GetMoveProbe()->TestGroundMove( GetLocalOrigin(), newPos, GetOuter()->GetAITraceMask(), testFlags, &moveTrace );
|
|
if ( pTraceResult )
|
|
{
|
|
*pTraceResult = moveTrace;
|
|
}
|
|
|
|
bool bHitTarget = (moveTrace.pObstruction && (pMoveTarget == moveTrace.pObstruction ));
|
|
|
|
// Move forward either if there was no obstruction or if we're told to
|
|
// move as far as we can, regardless
|
|
bool bIsBlocked = IsMoveBlocked(moveTrace.fStatus);
|
|
if ( !bIsBlocked || bAsFarAsCan || bHitTarget )
|
|
{
|
|
#ifdef DEBUG
|
|
if ( GetMoveProbe()->CheckStandPosition( GetLocalOrigin(), GetOuter()->GetAITraceMask() ) && !GetMoveProbe()->CheckStandPosition( moveTrace.vEndPosition, GetOuter()->GetAITraceMask() ) )
|
|
{
|
|
DevMsg( 2, "Warning: AI motor probably given invalid instructions\n" );
|
|
}
|
|
#endif
|
|
|
|
// The true argument here causes it to touch all triggers
|
|
// in the volume swept from the previous position to the current position
|
|
UTIL_SetOrigin(GetOuter(), moveTrace.vEndPosition, true);
|
|
|
|
// check to see if our ground entity has changed
|
|
// NOTE: This is to detect changes in ground entity as the movement code has optimized out
|
|
// ground checks. So now we have to do a simple recheck to make sure we detect when we've
|
|
// stepped onto a new entity.
|
|
if ( GetOuter()->GetFlags() & FL_ONGROUND )
|
|
{
|
|
GetOuter()->PhysicsStepRecheckGround();
|
|
}
|
|
|
|
// skip tiny steps, but notify the shadow object of any large steps
|
|
if ( moveTrace.flStepUpDistance > 0.1f )
|
|
{
|
|
float height = clamp( moveTrace.flStepUpDistance, 0, StepHeight() );
|
|
IPhysicsObject *pPhysicsObject = GetOuter()->VPhysicsGetObject();
|
|
if ( pPhysicsObject )
|
|
{
|
|
IPhysicsShadowController *pShadow = pPhysicsObject->GetShadowController();
|
|
if ( pShadow )
|
|
{
|
|
pShadow->StepUp( height );
|
|
}
|
|
}
|
|
}
|
|
if ( yaw != -1 )
|
|
{
|
|
QAngle angles = GetLocalAngles();
|
|
angles.y = yaw;
|
|
SetLocalAngles( angles );
|
|
}
|
|
if ( bHitTarget )
|
|
return AIM_PARTIAL_HIT_TARGET;
|
|
|
|
if ( !bIsBlocked )
|
|
return AIM_SUCCESS;
|
|
|
|
if ( moveTrace.fStatus == AIMR_BLOCKED_NPC )
|
|
return AIM_PARTIAL_HIT_NPC;
|
|
|
|
return AIM_PARTIAL_HIT_WORLD;
|
|
}
|
|
return AIM_FAILED;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Motion for climbing
|
|
// Input :
|
|
// Output : Returns bits (MoveStatus_b) regarding the move status
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void CAI_Motor::MoveClimbStart( const Vector &climbDest, const Vector &climbDir, float climbDist, float yaw )
|
|
{
|
|
// @Note (toml 06-11-02): the following code is somewhat suspect. It
|
|
// originated in CAI_BaseNPC::MoveClimb() from early June 2002
|
|
// At the very least, state should be restored to original, not
|
|
// slammed.
|
|
//
|
|
// -----Original Message-----
|
|
// From: Jay Stelly
|
|
// Sent: Monday, June 10, 2002 3:57 PM
|
|
// To: Tom Leonard
|
|
// Subject: RE:
|
|
//
|
|
// yes.
|
|
//
|
|
// Also, there is some subtlety to using movetype. I think in
|
|
// general we want to keep things in MOVETYPE_STEP because it
|
|
// implies a bunch of things in the external game physics
|
|
// simulator. There is a flag FL_FLY we use to
|
|
// disable gravity on MOVETYPE_STEP characters.
|
|
//
|
|
// > -----Original Message-----
|
|
// > From: Tom Leonard
|
|
// > Sent: Monday, June 10, 2002 3:55 PM
|
|
// > To: Jay Stelly
|
|
// > Subject:
|
|
// >
|
|
// > Should I worry at all that the following highlighted bits of
|
|
// > code are not reciprocal for all state, and furthermore, stomp
|
|
// > other state?
|
|
|
|
if ( fabsf( climbDir.z ) < .1 )
|
|
{
|
|
SetActivity( GetNavigator()->GetMovementActivity() );
|
|
}
|
|
else
|
|
{
|
|
SetActivity( (climbDir.z > -0.01 ) ? ACT_CLIMB_UP : ACT_CLIMB_DOWN );
|
|
}
|
|
|
|
m_nDismountSequence = SelectWeightedSequence( ACT_CLIMB_DISMOUNT );
|
|
if (m_nDismountSequence != ACT_INVALID)
|
|
{
|
|
GetOuter()->GetSequenceLinearMotion( m_nDismountSequence, &m_vecDismount );
|
|
}
|
|
else
|
|
{
|
|
m_vecDismount.Init();
|
|
}
|
|
|
|
GetOuter()->AddFlag( FL_FLY ); // No gravity
|
|
SetSolid( SOLID_BBOX );
|
|
SetGravity( 0.0 );
|
|
SetGroundEntity( NULL );
|
|
}
|
|
|
|
AIMoveResult_t CAI_Motor::MoveClimbExecute( const Vector &climbDest, const Vector &climbDir, float climbDist, float yaw, int climbNodesLeft )
|
|
{
|
|
if ( fabsf( climbDir.z ) > .1 )
|
|
{
|
|
if ( GetActivity() != ACT_CLIMB_DISMOUNT )
|
|
{
|
|
Activity desiredActivity = (climbDir.z > -0.01 ) ? ACT_CLIMB_UP : ACT_CLIMB_DOWN;
|
|
if ( GetActivity() != desiredActivity )
|
|
{
|
|
SetActivity( desiredActivity );
|
|
}
|
|
}
|
|
|
|
if ( GetActivity() != ACT_CLIMB_UP && GetActivity() != ACT_CLIMB_DOWN && GetActivity() != ACT_CLIMB_DISMOUNT )
|
|
{
|
|
DevMsg( "Climber not in a climb activity!\n" );
|
|
return AIMR_ILLEGAL;
|
|
}
|
|
|
|
if (m_nDismountSequence != ACT_INVALID)
|
|
{
|
|
if (GetActivity() == ACT_CLIMB_UP )
|
|
{
|
|
if (climbNodesLeft <= 2 && climbDist < fabs( m_vecDismount.z ))
|
|
{
|
|
// fixme: No other way to force m_nIdealSequence?
|
|
GetOuter()->SetActivity( ACT_CLIMB_DISMOUNT );
|
|
GetOuter()->SetCycle( GetOuter()->GetMovementFrame( m_vecDismount.z - climbDist ) );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
float climbSpeed = GetOuter()->GetInstantaneousVelocity();
|
|
|
|
if (m_nDismountSequence != ACT_INVALID)
|
|
{
|
|
// catch situations where the climb mount/dismount finished before reaching goal
|
|
climbSpeed = MAX( climbSpeed, 30.0 );
|
|
}
|
|
else
|
|
{
|
|
// FIXME: assume if they don't have a dismount animation then they probably don't really support climbing.
|
|
climbSpeed = 100.0;
|
|
}
|
|
|
|
SetSmoothedVelocity( climbDir * climbSpeed );
|
|
|
|
if ( climbDist < climbSpeed * GetMoveInterval() )
|
|
{
|
|
if (climbDist <= 1e-2)
|
|
climbDist = 0;
|
|
|
|
const float climbTime = climbDist / climbSpeed;
|
|
|
|
SetMoveInterval( GetMoveInterval() - climbTime );
|
|
SetLocalOrigin( climbDest );
|
|
|
|
return AIMR_CHANGE_TYPE;
|
|
}
|
|
else
|
|
{
|
|
SetMoveInterval( 0 );
|
|
}
|
|
|
|
// --------------------------------------------
|
|
// Turn to face the climb
|
|
// --------------------------------------------
|
|
SetIdealYawAndUpdate( yaw );
|
|
|
|
return AIMR_OK;
|
|
}
|
|
|
|
void CAI_Motor::MoveClimbStop()
|
|
{
|
|
if ( GetNavigator()->GetMovementActivity() > ACT_RESET )
|
|
SetActivity( GetNavigator()->GetMovementActivity() );
|
|
else
|
|
SetActivity( ACT_IDLE );
|
|
|
|
GetOuter()->RemoveFlag( FL_FLY );
|
|
SetSmoothedVelocity( vec3_origin );
|
|
SetGravity( 1.0 );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Motion for jumping
|
|
// Input :
|
|
// Output : Returns bits (MoveStatus_b) regarding the move status
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void CAI_Motor::MoveJumpStart( const Vector &velocity )
|
|
{
|
|
// take the npc off the ground and throw them in the air
|
|
SetSmoothedVelocity( velocity );
|
|
SetGravity( GetOuter()->GetJumpGravity() );
|
|
SetGroundEntity( NULL );
|
|
|
|
SetActivity( ACT_JUMP );
|
|
|
|
SetIdealYawAndUpdate( velocity );
|
|
}
|
|
|
|
int CAI_Motor::MoveJumpExecute( )
|
|
{
|
|
// needs to detect being hit
|
|
UpdateYaw( );
|
|
|
|
if (GetOuter()->GetActivity() == ACT_JUMP && GetOuter()->IsActivityFinished())
|
|
{
|
|
SetActivity( ACT_GLIDE );
|
|
}
|
|
|
|
// use all the time
|
|
SetMoveInterval( 0 );
|
|
|
|
return AIMR_OK;
|
|
}
|
|
|
|
AIMoveResult_t CAI_Motor::MoveJumpStop()
|
|
{
|
|
SetSmoothedVelocity( Vector(0,0,0) );
|
|
|
|
if (GetOuter()->GetActivity() == ACT_GLIDE)
|
|
{
|
|
float flTime = GetOuter()->GetGroundChangeTime();
|
|
GetOuter()->AddStepDiscontinuity( flTime, GetAbsOrigin(), GetAbsAngles() );
|
|
|
|
if ( SelectWeightedSequence( ACT_LAND ) == ACT_INVALID )
|
|
return AIMR_CHANGE_TYPE;
|
|
|
|
SetActivity( ACT_LAND );
|
|
// FIXME: find out why the client doesn't interpolate immediatly after sequence change
|
|
// GetOuter()->SetCycle( flTime - gpGlobals->curtime );
|
|
}
|
|
if (GetOuter()->GetActivity() != ACT_LAND || GetOuter()->IsActivityFinished())
|
|
{
|
|
return AIMR_CHANGE_TYPE;
|
|
}
|
|
|
|
SetMoveInterval( 0 );
|
|
|
|
SetGravity( 1.0f );
|
|
|
|
return AIMR_OK;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
float CAI_Motor::GetIdealSpeed() const
|
|
{
|
|
return GetOuter()->GetIdealSpeed();
|
|
}
|
|
|
|
|
|
float CAI_Motor::GetIdealAccel() const
|
|
{
|
|
return GetOuter()->GetIdealAccel();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// how far will I go?
|
|
float CAI_Motor::MinStoppingDist( float flMinResult )
|
|
{
|
|
// FIXME: should this be a constant rate or a constant time like it is now?
|
|
float flDecelRate = GetIdealAccel();
|
|
|
|
if (flDecelRate > 0.0)
|
|
{
|
|
// assuming linear deceleration, how long till my V hits 0?
|
|
float t = GetCurSpeed() / flDecelRate;
|
|
// and how far will I travel? (V * t - 1/2 A t^2)
|
|
float flDist = GetCurSpeed() * t - 0.5 * flDecelRate * t * t;
|
|
|
|
// this should always be some reasonable non-zero distance
|
|
if (flDist > flMinResult)
|
|
return flDist;
|
|
return flMinResult;
|
|
}
|
|
return flMinResult;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: how fast should I be going ideally
|
|
//-----------------------------------------------------------------------------
|
|
float CAI_Motor::IdealVelocity( void )
|
|
{
|
|
// FIXME: this should be a per-entity setting so run speeds are not based on animation speeds
|
|
return GetIdealSpeed() * GetPlaybackRate();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void CAI_Motor::ResetMoveCalculations()
|
|
{
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void CAI_Motor::MoveStart()
|
|
{
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void CAI_Motor::MoveStop()
|
|
{
|
|
memset( &m_vecVelocity, 0, sizeof(m_vecVelocity) );
|
|
GetOuter()->GetLocalNavigator()->ResetMoveCalculations();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void CAI_Motor::MovePaused()
|
|
{
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: what linear accel/decel rate do I need to hit V1 in d distance?
|
|
//-----------------------------------------------------------------------------
|
|
float DeltaV( float v0, float v1, float d )
|
|
{
|
|
return 0.5 * (v1 * v1 - v0 * v0 ) / d;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
float CAI_Motor::CalcIntervalMove()
|
|
{
|
|
// assuming linear acceleration, how far will I travel?
|
|
return 0.5 * (GetCurSpeed() + GetIdealSpeed()) * GetMoveInterval();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Move the npc to the next location on its route.
|
|
// Input : vecDir - Normalized vector indicating the direction of movement.
|
|
// flDistance - distance to move
|
|
// flInterval - Time interval for this movement.
|
|
// flGoalDistance - target distance
|
|
// flGoalVelocity - target velocity
|
|
//-----------------------------------------------------------------------------
|
|
|
|
AIMotorMoveResult_t CAI_Motor::MoveGroundExecute( const AILocalMoveGoal_t &move, AIMoveTrace_t *pTraceResult )
|
|
{
|
|
// --------------------------------------------
|
|
// turn in the direction of movement
|
|
// --------------------------------------------
|
|
MoveFacing( move );
|
|
|
|
// --------------------------------------------
|
|
return MoveGroundExecuteWalk( move, GetIdealSpeed(), CalcIntervalMove(), pTraceResult );
|
|
}
|
|
|
|
|
|
AIMotorMoveResult_t CAI_Motor::MoveGroundExecuteWalk( const AILocalMoveGoal_t &move, float speed, float dist, AIMoveTrace_t *pTraceResult )
|
|
{
|
|
bool bReachingLocalGoal = ( dist > move.maxDist );
|
|
|
|
// can I move farther in this interval than I'm supposed to?
|
|
if ( bReachingLocalGoal )
|
|
{
|
|
if ( !(move.flags & AILMG_CONSUME_INTERVAL) )
|
|
{
|
|
// only use a portion of the time interval
|
|
SetMoveInterval( GetMoveInterval() * (1 - move.maxDist / dist) );
|
|
}
|
|
else
|
|
SetMoveInterval( 0 );
|
|
dist = move.maxDist;
|
|
}
|
|
else
|
|
{
|
|
// use all the time
|
|
SetMoveInterval( 0 );
|
|
}
|
|
|
|
SetMoveVel( move.dir * speed );
|
|
|
|
// --------------------------------------------
|
|
// walk the distance
|
|
// --------------------------------------------
|
|
AIMotorMoveResult_t result = AIM_SUCCESS;
|
|
if ( dist > 0.0 )
|
|
{
|
|
Vector vecFrom = GetLocalOrigin();
|
|
Vector vecTo = vecFrom + move.dir * dist;
|
|
|
|
if ( move.navType == NAV_CRAWL )
|
|
{
|
|
char *pchHackBoolToInt = (char*)(&bReachingLocalGoal);
|
|
*pchHackBoolToInt = 2;
|
|
}
|
|
|
|
result = MoveGroundStep( vecTo, move.pMoveTarget, -1, true, bReachingLocalGoal, pTraceResult );
|
|
|
|
if ( result == AIM_FAILED )
|
|
MoveStop();
|
|
}
|
|
else if ( !OnMoveStalled( move ) )
|
|
{
|
|
result = AIM_FAILED;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Move the npc to the next location on its route.
|
|
// Input : pTargetEnt -
|
|
// vecDir - Normalized vector indicating the direction of movement.
|
|
// flInterval - Time interval for this movement.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
AIMotorMoveResult_t CAI_Motor::MoveFlyExecute( const AILocalMoveGoal_t &move, AIMoveTrace_t *pTraceResult )
|
|
{
|
|
// turn in the direction of movement
|
|
MoveFacing( move );
|
|
|
|
// calc accel/decel rates
|
|
float flNewSpeed = GetIdealSpeed();
|
|
SetMoveVel( move.dir * flNewSpeed );
|
|
|
|
float flTotal = 0.5 * (GetCurSpeed() + flNewSpeed) * GetMoveInterval();
|
|
|
|
float distance = move.maxDist;
|
|
|
|
// can I move farther in this interval than I'm supposed to?
|
|
if (flTotal > distance)
|
|
{
|
|
// only use a portion of the time interval
|
|
SetMoveInterval( GetMoveInterval() * (1 - distance / flTotal) );
|
|
flTotal = distance;
|
|
}
|
|
else
|
|
{
|
|
// use all the time
|
|
SetMoveInterval( 0 );
|
|
}
|
|
|
|
Vector vecStart, vecEnd;
|
|
vecStart = GetLocalOrigin();
|
|
VectorMA( vecStart, flTotal, move.dir, vecEnd );
|
|
|
|
AIMoveTrace_t moveTrace;
|
|
GetMoveProbe()->MoveLimit( NAV_FLY, vecStart, vecEnd, GetOuter()->GetAITraceMask(), NULL, &moveTrace );
|
|
if ( pTraceResult )
|
|
*pTraceResult = moveTrace;
|
|
|
|
// Check for total blockage
|
|
if (fabs(moveTrace.flDistObstructed - flTotal) <= 1e-1)
|
|
{
|
|
// But if we bumped into our target, then we succeeded!
|
|
if ( move.pMoveTarget && (moveTrace.pObstruction == move.pMoveTarget) )
|
|
return AIM_PARTIAL_HIT_TARGET;
|
|
|
|
return AIM_FAILED;
|
|
}
|
|
|
|
// The true argument here causes it to touch all triggers
|
|
// in the volume swept from the previous position to the current position
|
|
UTIL_SetOrigin(GetOuter(), moveTrace.vEndPosition, true);
|
|
|
|
return (IsMoveBlocked(moveTrace.fStatus)) ? AIM_PARTIAL_HIT_WORLD : AIM_SUCCESS;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: turn in the direction of movement
|
|
// Output :
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void CAI_Motor::MoveFacing( const AILocalMoveGoal_t &move )
|
|
{
|
|
if ( GetOuter()->OverrideMoveFacing( move, GetMoveInterval() ) )
|
|
return;
|
|
|
|
// required movement direction
|
|
float flMoveYaw = UTIL_VecToYaw( move.dir );
|
|
|
|
int nSequence = GetSequence();
|
|
float fSequenceMoveYaw = GetSequenceMoveYaw( nSequence );
|
|
if ( fSequenceMoveYaw == NOMOTION )
|
|
{
|
|
fSequenceMoveYaw = 0;
|
|
}
|
|
|
|
if (!HasPoseParameter( nSequence, GetOuter()->LookupPoseMoveYaw() ))
|
|
{
|
|
SetIdealYawAndUpdate( UTIL_AngleMod( flMoveYaw - fSequenceMoveYaw ) );
|
|
}
|
|
else
|
|
{
|
|
// FIXME: move this up to navigator so that path goals can ignore these overrides.
|
|
Vector dir;
|
|
float flInfluence = GetFacingDirection( dir );
|
|
dir = move.facing * (1 - flInfluence) + dir * flInfluence;
|
|
VectorNormalize( dir );
|
|
|
|
// ideal facing direction
|
|
float idealYaw = UTIL_AngleMod( UTIL_VecToYaw( dir ) );
|
|
|
|
// FIXME: facing has important max velocity issues
|
|
SetIdealYawAndUpdate( idealYaw );
|
|
|
|
// find movement direction to compensate for not being turned far enough
|
|
float flDiff = UTIL_AngleDiff( flMoveYaw, GetLocalAngles().y );
|
|
SetPoseParameter( GetOuter()->LookupPoseMoveYaw(), flDiff );
|
|
/*
|
|
if ((GetOuter()->m_debugOverlays & OVERLAY_NPC_SELECTED_BIT))
|
|
{
|
|
DevMsg( "move %.1f : diff %.1f : ideal %.1f\n", flMoveYaw, flDiff, m_IdealYaw );
|
|
}
|
|
*/
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Set the ideal yaw and run the current or specified timestep
|
|
// worth of rotation.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void CAI_Motor::SetIdealYawAndUpdate( float idealYaw, float yawSpeed)
|
|
{
|
|
SetIdealYaw( idealYaw );
|
|
if (yawSpeed == AI_CALC_YAW_SPEED)
|
|
RecalculateYawSpeed();
|
|
else if (yawSpeed != AI_KEEP_YAW_SPEED)
|
|
SetYawSpeed( yawSpeed );
|
|
UpdateYaw(-1);
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void CAI_Motor::RecalculateYawSpeed()
|
|
{
|
|
SetYawSpeed( CalcYawSpeed() );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
float AI_ClampYaw( float yawSpeedPerSec, float current, float target, float time )
|
|
{
|
|
if (current != target)
|
|
{
|
|
float speed = yawSpeedPerSec * time;
|
|
float move = target - current;
|
|
|
|
if (target > current)
|
|
{
|
|
if (move >= 180)
|
|
move = move - 360;
|
|
}
|
|
else
|
|
{
|
|
if (move <= -180)
|
|
move = move + 360;
|
|
}
|
|
|
|
if (move > 0)
|
|
{// turning to the npc's left
|
|
if (move > speed)
|
|
move = speed;
|
|
}
|
|
else
|
|
{// turning to the npc's right
|
|
if (move < -speed)
|
|
move = -speed;
|
|
}
|
|
|
|
return UTIL_AngleMod(current + move);
|
|
}
|
|
|
|
return target;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Turns a npc towards its ideal yaw.
|
|
// Input : yawSpeed - Yaw speed in degrees per 1/10th of a second.
|
|
// flInterval - Time interval to turn, -1 uses time since last think.
|
|
// Output : Returns the number of degrees turned.
|
|
//-----------------------------------------------------------------------------
|
|
void CAI_Motor::UpdateYaw( int yawSpeed )
|
|
{
|
|
// Don't do this if our yaw is locked
|
|
if ( IsYawLocked() )
|
|
return;
|
|
|
|
GetOuter()->SetUpdatedYaw();
|
|
|
|
float ideal, current, newYaw;
|
|
|
|
if ( yawSpeed == -1 )
|
|
yawSpeed = GetYawSpeed();
|
|
|
|
// NOTE: GetIdealYaw() will never exactly be reached because UTIL_AngleMod
|
|
// also truncates the angle to 16 bits of resolution. So lets truncate it here.
|
|
current = UTIL_AngleMod( GetLocalAngles().y );
|
|
ideal = UTIL_AngleMod( GetIdealYaw() );
|
|
|
|
// FIXME: this needs a proper interval
|
|
float dt = MIN( 0.2, gpGlobals->curtime - GetLastThink() );
|
|
|
|
newYaw = AI_ClampYaw( (float)yawSpeed * 10.0, current, ideal, dt );
|
|
|
|
if (newYaw != current)
|
|
{
|
|
QAngle angles = GetLocalAngles();
|
|
angles.y = newYaw;
|
|
SetLocalAngles( angles );
|
|
}
|
|
}
|
|
|
|
|
|
//=========================================================
|
|
// DeltaIdealYaw - returns the difference ( in degrees ) between
|
|
// npc's current yaw and ideal_yaw
|
|
//
|
|
// Positive result is left turn, negative is right turn
|
|
//=========================================================
|
|
float CAI_Motor::DeltaIdealYaw ( void )
|
|
{
|
|
float flCurrentYaw;
|
|
|
|
flCurrentYaw = UTIL_AngleMod( GetLocalAngles().y );
|
|
|
|
if ( flCurrentYaw == GetIdealYaw() )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
|
|
return UTIL_AngleDiff( GetIdealYaw(), flCurrentYaw );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void CAI_Motor::SetIdealYawToTarget( const Vector &target, float noise, float offset )
|
|
{
|
|
float base = CalcIdealYaw( target );
|
|
base += offset;
|
|
if ( noise > 0 )
|
|
{
|
|
noise *= 0.5;
|
|
base += random->RandomFloat( -noise, noise );
|
|
if ( base < 0 )
|
|
base += 360;
|
|
else if ( base >= 360 )
|
|
base -= 360;
|
|
}
|
|
SetIdealYaw( base );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void CAI_Motor::SetIdealYawToTargetAndUpdate( const Vector &target, float yawSpeed )
|
|
{
|
|
SetIdealYawAndUpdate( CalcIdealYaw( target ), yawSpeed );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Keep track of multiple objects that the npc is interested in facing
|
|
//-----------------------------------------------------------------------------
|
|
void CAI_Motor::AddFacingTarget( CBaseEntity *pTarget, float flImportance, float flDuration, float flRamp )
|
|
{
|
|
m_facingQueue.Add( pTarget, flImportance, flDuration, flRamp );
|
|
}
|
|
|
|
|
|
void CAI_Motor::AddFacingTarget( const Vector &vecPosition, float flImportance, float flDuration, float flRamp )
|
|
{
|
|
m_facingQueue.Add( vecPosition, flImportance, flDuration, flRamp );
|
|
}
|
|
|
|
void CAI_Motor::AddFacingTarget( CBaseEntity *pTarget, const Vector &vecPosition, float flImportance, float flDuration, float flRamp )
|
|
{
|
|
m_facingQueue.Add( pTarget, vecPosition, flImportance, flDuration, flRamp );
|
|
}
|
|
|
|
|
|
float CAI_Motor::GetFacingDirection( Vector &vecDir )
|
|
{
|
|
float flTotalInterest = 0.0;
|
|
vecDir = Vector( 0, 0, 0 );
|
|
|
|
int i;
|
|
|
|
// clean up facing targets
|
|
for (i = 0; i < m_facingQueue.Count();)
|
|
{
|
|
if (!m_facingQueue[i].IsActive())
|
|
{
|
|
m_facingQueue.Remove( i );
|
|
}
|
|
else
|
|
{
|
|
i++;
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < m_facingQueue.Count(); i++)
|
|
{
|
|
float flInterest = m_facingQueue[i].Interest( );
|
|
Vector tmp = m_facingQueue[i].GetPosition() - GetAbsOrigin();
|
|
|
|
// NDebugOverlay::Line( m_facingQueue[i].GetPosition(), GetAbsOrigin(), 255, 0, 0, false, 0.1 );
|
|
|
|
VectorNormalize( tmp );
|
|
|
|
vecDir = vecDir * (1 - flInterest) + tmp * flInterest;
|
|
|
|
flTotalInterest = (1 - (1 - flTotalInterest) * (1 - flInterest));
|
|
|
|
VectorNormalize( vecDir );
|
|
}
|
|
|
|
return flTotalInterest;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
AIMoveResult_t CAI_Motor::MoveNormalExecute( const AILocalMoveGoal_t &move )
|
|
{
|
|
AI_PROFILE_SCOPE(CAI_Motor_MoveNormalExecute);
|
|
|
|
// --------------------------------
|
|
|
|
AIMotorMoveResult_t fMotorResult;
|
|
AIMoveTrace_t moveTrace;
|
|
|
|
if ( move.navType == NAV_GROUND || move.navType == NAV_CRAWL )
|
|
{
|
|
fMotorResult = MoveGroundExecute( move, &moveTrace );
|
|
}
|
|
else
|
|
{
|
|
Assert( move.navType == NAV_FLY );
|
|
fMotorResult = MoveFlyExecute( move, &moveTrace );
|
|
}
|
|
|
|
static AIMoveResult_t moveResults[] =
|
|
{
|
|
AIMR_ILLEGAL, // AIM_FAILED
|
|
AIMR_OK, // AIM_SUCCESS
|
|
AIMR_BLOCKED_NPC, // AIM_PARTIAL_HIT_NPC
|
|
AIMR_BLOCKED_WORLD, // AIM_PARTIAL_HIT_WORLD
|
|
AIMR_BLOCKED_WORLD, // AIM_PARTIAL_HIT_TARGET
|
|
};
|
|
Assert( ARRAYSIZE( moveResults ) == AIM_NUM_RESULTS && fMotorResult >= 0 && fMotorResult <= ARRAYSIZE( moveResults ) );
|
|
|
|
AIMoveResult_t result = moveResults[fMotorResult];
|
|
|
|
if ( result != AIMR_OK )
|
|
{
|
|
OnMoveExecuteFailed( move, moveTrace, fMotorResult, &result );
|
|
SetMoveInterval( 0 ); // always consume interval on failure, even if overridden by OnMoveExecuteFailed()
|
|
}
|
|
|
|
return DbgResult( result );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Look ahead my stopping distance, or at least my hull width
|
|
//-----------------------------------------------------------------------------
|
|
float CAI_Motor::MinCheckDist( void )
|
|
{
|
|
// Take the groundspeed into account
|
|
float flMoveDist = GetMoveInterval() * GetIdealSpeed();
|
|
float flMinDist = MAX( MinStoppingDist(), flMoveDist);
|
|
if ( flMinDist < GetHullWidth() )
|
|
flMinDist = GetHullWidth();
|
|
return flMinDist;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
CAI_Navigator *CAI_Motor::GetNavigator( void )
|
|
{
|
|
return GetOuter()->GetNavigator();
|
|
}
|
|
|
|
int CAI_Motor::SelectWeightedSequence ( Activity activity )
|
|
{
|
|
return GetOuter()->SelectWeightedSequence ( activity );
|
|
}
|
|
|
|
float CAI_Motor::GetSequenceGroundSpeed( int iSequence )
|
|
{
|
|
return GetOuter()->GetSequenceGroundSpeed( iSequence );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void CAI_Motor::SetSmoothedVelocity(const Vector &vecVelocity)
|
|
{
|
|
GetOuter()->SetAbsVelocity(vecVelocity);
|
|
}
|
|
|
|
Vector CAI_Motor::GetSmoothedVelocity()
|
|
{
|
|
return GetOuter()->GetSmoothedVelocity();
|
|
}
|
|
|
|
float CAI_Motor::StepHeight() const
|
|
{
|
|
return GetOuter()->StepHeight();
|
|
}
|
|
|
|
bool CAI_Motor::CanStandOn( CBaseEntity *pSurface ) const
|
|
{
|
|
return GetOuter()->CanStandOn( pSurface );
|
|
}
|
|
|
|
float CAI_Motor::CalcIdealYaw( const Vector &vecTarget )
|
|
{
|
|
return GetOuter()->CalcIdealYaw( vecTarget );
|
|
}
|
|
|
|
float CAI_Motor::SetBoneController( int iController, float flValue )
|
|
{
|
|
return GetOuter()->SetBoneController( iController, flValue );
|
|
}
|
|
|
|
float CAI_Motor::GetSequenceMoveYaw( int iSequence )
|
|
{
|
|
return GetOuter()->GetSequenceMoveYaw( iSequence );
|
|
}
|
|
|
|
void CAI_Motor::SetPlaybackRate( float flRate )
|
|
{
|
|
return GetOuter()->SetPlaybackRate( flRate );
|
|
}
|
|
|
|
float CAI_Motor::GetPlaybackRate() const
|
|
{
|
|
return GetOuter()->GetPlaybackRate();
|
|
}
|
|
|
|
float CAI_Motor::SetPoseParameter( const char *szName, float flValue )
|
|
{
|
|
return GetOuter()->SetPoseParameter( szName, flValue );
|
|
}
|
|
|
|
float CAI_Motor::GetPoseParameter( const char *szName )
|
|
{
|
|
return GetOuter()->GetPoseParameter( szName );
|
|
}
|
|
|
|
bool CAI_Motor::HasPoseParameter( int iSequence, const char *szName )
|
|
{
|
|
return GetOuter()->HasPoseParameter( iSequence, szName );
|
|
}
|
|
|
|
float CAI_Motor::SetPoseParameter( int iParameter, float flValue )
|
|
{
|
|
return GetOuter()->SetPoseParameter( iParameter, flValue );
|
|
}
|
|
|
|
bool CAI_Motor::HasPoseParameter( int iSequence, int iParameter )
|
|
{
|
|
return GetOuter()->HasPoseParameter( iSequence, iParameter );
|
|
}
|
|
|
|
void CAI_Motor::SetMoveType( MoveType_t val, MoveCollide_t moveCollide )
|
|
{
|
|
GetOuter()->SetMoveType( val, moveCollide );
|
|
}
|
|
|
|
//=============================================================================
|
|
|