diff options
Diffstat (limited to 'game/server/ai_motor.cpp')
| -rw-r--r-- | game/server/ai_motor.cpp | 1033 |
1 files changed, 1033 insertions, 0 deletions
diff --git a/game/server/ai_motor.cpp b/game/server/ai_motor.cpp new file mode 100644 index 0000000..49f3d5f --- /dev/null +++ b/game/server/ai_motor.cpp @@ -0,0 +1,1033 @@ +//========= 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 ); +} + +//============================================================================= + |