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.
1033 lines
28 KiB
1033 lines
28 KiB
//========= Copyright 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; |
|
|
|
if ( !bTestZ ) |
|
testFlags |= AITGM_2D; |
|
|
|
#ifdef DEBUG |
|
if ( ai_draw_motor_movement.GetBool() ) |
|
testFlags |= AITGM_DRAW_RESULTS; |
|
#endif |
|
|
|
GetMoveProbe()->TestGroundMove( GetLocalOrigin(), newPos, MASK_NPCSOLID, 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(), MASK_NPCSOLID ) && !GetMoveProbe()->CheckStandPosition( moveTrace.vEndPosition, MASK_NPCSOLID ) ) |
|
{ |
|
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.f, 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; |
|
|
|
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, MASK_NPCSOLID, 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 ) |
|
{ |
|
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() |
|
{ |
|
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 ); |
|
} |
|
|
|
//============================================================================= |
|
|
|
|