aboutsummaryrefslogtreecommitdiff
path: root/mp/src/game/server/ai_blended_movement.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'mp/src/game/server/ai_blended_movement.cpp')
-rw-r--r--mp/src/game/server/ai_blended_movement.cpp3808
1 files changed, 1904 insertions, 1904 deletions
diff --git a/mp/src/game/server/ai_blended_movement.cpp b/mp/src/game/server/ai_blended_movement.cpp
index 0e150702..d1b32d14 100644
--- a/mp/src/game/server/ai_blended_movement.cpp
+++ b/mp/src/game/server/ai_blended_movement.cpp
@@ -1,1904 +1,1904 @@
-//========= Copyright Valve Corporation, All rights reserved. ============//
-//
-// Purpose:
-//
-//=============================================================================//
-
-#include "cbase.h"
-
-#include "movevars_shared.h"
-
-#include "ai_blended_movement.h"
-#include "ai_route.h"
-#include "ai_navigator.h"
-#include "ai_moveprobe.h"
-#include "KeyValues.h"
-
-// memdbgon must be the last include file in a .cpp file!!!
-#include "tier0/memdbgon.h"
-
-//-----------------------------------------------------------------------------
-//
-// class CAI_BlendedMotor
-//
-
-BEGIN_SIMPLE_DATADESC( CAI_BlendedMotor )
- // DEFINE_FIELD( m_bDeceleratingToGoal, FIELD_BOOLEAN ),
-
- // DEFINE_FIELD( m_iPrimaryLayer, FIELD_INTEGER ),
- // DEFINE_FIELD( m_iSecondaryLayer, FIELD_INTEGER ),
-
- // DEFINE_FIELD( m_nPrimarySequence, FIELD_INTEGER ),
- // DEFINE_FIELD( m_nSecondarySequence, FIELD_INTEGER ),
- // DEFINE_FIELD( m_flSecondaryWeight, FIELD_FLOAT ),
-
- // DEFINE_CUSTOM_FIELD( m_nSavedGoalActivity, ActivityDataOps() ),
- // DEFINE_CUSTOM_FIELD( m_nSavedTranslatedGoalActivity, ActivityDataOps() ),
- // DEFINE_FIELD( m_nGoalSequence, FIELD_INTEGER ),
-
- // DEFINE_FIELD( m_nPrevMovementSequence, FIELD_INTEGER ),
- // DEFINE_FIELD( m_nInteriorSequence, FIELD_INTEGER ),
- // DEFINE_FIELD( m_flCurrRate, FIELD_FLOAT ),
- // DEFINE_FIELD( m_flStartCycle, FIELD_FLOAT ),
-
- // m_scriptMove
- // m_scriptTurn
-
- // DEFINE_FIELD( m_flNextTurnGesture, FIELD_TIME ),
- // DEFINE_FIELD( m_prevYaw, FIELD_FLOAT ),
- // DEFINE_FIELD( m_doTurn, FIELD_FLOAT ),
- // DEFINE_FIELD( m_doLeft, FIELD_FLOAT ),
- // DEFINE_FIELD( m_doRight, FIELD_FLOAT ),
- // DEFINE_FIELD( m_flNextTurnAct, FIELD_TIME ),
- // DEFINE_FIELD( m_flPredictiveSpeedAdjust, FIELD_FLOAT ),
- // DEFINE_FIELD( m_flReactiveSpeedAdjust, FIELD_FLOAT ),
- // DEFINE_FIELD( m_vecPrevOrigin1, FIELD_POSITION ),
- // DEFINE_FIELD( m_vecPrevOrigin2, FIELD_POSITION ),
-
-END_DATADESC()
-
-//-------------------------------------
-
-void CAI_BlendedMotor::ResetMoveCalculations()
-{
- BaseClass::ResetMoveCalculations();
- m_scriptMove.RemoveAll();
- m_scriptTurn.RemoveAll();
-}
-
-//-------------------------------------
-
-void CAI_BlendedMotor::MoveStart()
-{
- AI_PROFILE_SCOPE(CAI_BlendedMotor_MoveStart);
-
- if (m_nPrimarySequence == -1)
- {
- m_nPrimarySequence = GetSequence();
- m_flStartCycle = GetCycle();
- m_flCurrRate = 0.4;
-
- // Assert( !GetOuter()->HasMovement( m_nStartSequence ) );
-
- m_nSecondarySequence = -1;
-
- m_iPrimaryLayer = AddLayeredSequence( m_nPrimarySequence, 0 );
- SetLayerWeight( m_iPrimaryLayer, 0.0 );
- SetLayerPlaybackRate( m_iPrimaryLayer, 0.0 );
- SetLayerNoRestore( m_iPrimaryLayer, true );
- SetLayerCycle( m_iPrimaryLayer, m_flStartCycle, m_flStartCycle );
-
- m_flSecondaryWeight = 0.0;
- }
- else
- {
- // suspect that MoveStop() wasn't called when the previous route finished
- // Assert( 0 );
- }
-
-
- if (m_nGoalSequence == ACT_INVALID)
- {
- ResetGoalSequence();
- }
-
- m_vecPrevOrigin2 = GetAbsOrigin();
- m_vecPrevOrigin1 = GetAbsOrigin();
-
- m_bDeceleratingToGoal = false;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-
-void CAI_BlendedMotor::ResetGoalSequence( void )
-{
-
- m_nSavedGoalActivity = GetNavigator()->GetArrivalActivity( );
- if (m_nSavedGoalActivity == ACT_INVALID)
- {
- m_nSavedGoalActivity = GetOuter()->GetStoppedActivity();
- }
-
- m_nSavedTranslatedGoalActivity = GetOuter()->NPC_TranslateActivity( m_nSavedGoalActivity );
-
- m_nGoalSequence = GetNavigator()->GetArrivalSequence( m_nPrimarySequence );
- // Msg("Start %s end %s\n", GetOuter()->GetSequenceName( m_nPrimarySequence ), GetOuter()->GetSequenceName( m_nGoalSequence ) );
-
- m_nGoalSequence = GetInteriorSequence( m_nPrimarySequence );
-
- Assert( m_nGoalSequence != ACT_INVALID );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-
-
-void CAI_BlendedMotor::MoveStop()
-{
- AI_PROFILE_SCOPE(CAI_BlendedMotor_MoveStop);
-
- CAI_Motor::MoveStop();
-
- if (m_iPrimaryLayer != -1)
- {
- RemoveLayer( m_iPrimaryLayer, 0.2, 0.1 );
- m_iPrimaryLayer = -1;
- }
- if (m_iSecondaryLayer != -1)
- {
- RemoveLayer( m_iSecondaryLayer, 0.2, 0.1 );
- m_iSecondaryLayer = -1;
- }
- m_nPrimarySequence = ACT_INVALID;
- m_nSecondarySequence = ACT_INVALID;
- m_nPrevMovementSequence = ACT_INVALID;
- m_nInteriorSequence = ACT_INVALID;
-
- // int nNextSequence = FindTransitionSequence(GetSequence(), m_nIdealSequence, NULL);
-}
-
-void CAI_BlendedMotor::MovePaused()
-{
- CAI_Motor::MovePaused();
- SetMoveScriptAnim( 0.0 );
-}
-
-
-void CAI_BlendedMotor::MoveContinue()
-{
- AI_PROFILE_SCOPE(CAI_BlendedMotor_MoveContinue);
-
- m_nPrimarySequence = GetInteriorSequence( ACT_INVALID );
- m_nGoalSequence = m_nPrimarySequence;
-
- Assert( m_nPrimarySequence != ACT_INVALID );
-
- if (m_nPrimarySequence == ACT_INVALID)
- return;
-
- m_flStartCycle = 0.0;
-
- m_iPrimaryLayer = AddLayeredSequence( m_nPrimarySequence, 0 );
- SetLayerWeight( m_iPrimaryLayer, 0.0 );
- SetLayerPlaybackRate( m_iPrimaryLayer, 0.0 );
- SetLayerNoRestore( m_iPrimaryLayer, true );
- SetLayerCycle( m_iPrimaryLayer, m_flStartCycle, m_flStartCycle );
-
- m_bDeceleratingToGoal = false;
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: for the MoveInterval, interpolate desired speed, calc actual distance traveled
-//-----------------------------------------------------------------------------
-float CAI_BlendedMotor::GetMoveScriptDist( float &flNewSpeed )
-{
- AI_PROFILE_SCOPE(CAI_BlendedMotor_GetMoveScriptDist);
-
- int i;
- float flTotalDist = 0;
- float t = GetMoveInterval();
-
- Assert( m_scriptMove.Count() > 1);
-
- flNewSpeed = 0;
- for (i = 0; i < m_scriptMove.Count()-1; i++)
- {
- if (t < m_scriptMove[i].flTime)
- {
- // get new velocity
- float a = t / m_scriptMove[i].flTime;
- flNewSpeed = m_scriptMove[i].flMaxVelocity * (1 - a) + m_scriptMove[i+1].flMaxVelocity * a;
-
- // get distance traveled over this entry
- flTotalDist += (m_scriptMove[i].flMaxVelocity + flNewSpeed) * 0.5 * t;
- break;
- }
- else
- {
- // used all of entries time, get entries total movement
- flNewSpeed = m_scriptMove[i+1].flMaxVelocity;
- flTotalDist += m_scriptMove[i].flDist;
- t -= m_scriptMove[i].flTime;
- }
- }
-
- return flTotalDist;
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: return the total time that the move script covers
-//-----------------------------------------------------------------------------
-
-float CAI_BlendedMotor::GetMoveScriptTotalTime()
-{
- float flDist = GetNavigator()->GetArrivalDistance();
-
- int i = m_scriptMove.Count() - 1;
-
- if (i < 0)
- return -1;
-
- while (i > 0 && flDist > 1)
- {
- flDist -= m_scriptMove[i].flDist;
- i--;
- }
- return m_scriptMove[i].flElapsedTime;
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: for the MoveInterval, interpolate desired angle
-//-----------------------------------------------------------------------------
-
-float CAI_BlendedMotor::GetMoveScriptYaw( void )
-{
- int i;
-
- // interpolate desired angle
- float flNewYaw = GetAbsAngles().y;
- float t = GetMoveInterval();
- for (i = 0; i < m_scriptTurn.Count()-1; i++)
- {
- if (t < m_scriptTurn[i].flTime)
- {
- // get new direction
- float a = t / m_scriptTurn[i].flTime;
- float deltaYaw = UTIL_AngleDiff( m_scriptTurn[i+1].flYaw, m_scriptTurn[i].flYaw );
- flNewYaw = UTIL_AngleMod( m_scriptTurn[i].flYaw + a * deltaYaw );
- break;
- }
- else
- {
- t -= m_scriptTurn[i].flTime;
- }
- }
-
- return flNewYaw;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: blend in the "idle" or "arrival" animation depending on speed
-//-----------------------------------------------------------------------------
-
-void CAI_BlendedMotor::SetMoveScriptAnim( float flNewSpeed )
-{
- AI_PROFILE_SCOPE(CAI_BlendedMotor_SetMoveScriptAnim);
-
- // don't bother if the npc is dead
- if (!GetOuter()->IsAlive())
- return;
-
- // insert ideal layers
- // FIXME: needs full transitions, as well as starting vs stopping sequences, leaning, etc.
-
- CAI_Navigator *pNavigator = GetNavigator();
-
- SetPlaybackRate( m_flCurrRate );
- // calc weight of idle animation layer that suppresses the run animation
- float flWeight = 0.0f;
- if (GetIdealSpeed() > 0.0f)
- {
- flWeight = 1.0f - (flNewSpeed / (GetIdealSpeed() * GetPlaybackRate()));
- }
- if (flWeight < 0.0f)
- {
- m_flCurrRate = flNewSpeed / GetIdealSpeed();
- m_flCurrRate = clamp( m_flCurrRate, 0.0f, 1.0f );
- SetPlaybackRate( m_flCurrRate );
- flWeight = 0.0;
- }
- // Msg("weight %.3f rate %.3f\n", flWeight, m_flCurrRate );
- m_flCurrRate = MIN( m_flCurrRate + (1.0 - m_flCurrRate) * 0.8f, 1.0f );
-
- if (m_nSavedGoalActivity == ACT_INVALID)
- {
- ResetGoalSequence();
- }
-
- // detect state change
- Activity activity = GetOuter()->NPC_TranslateActivity( m_nSavedGoalActivity );
- if ( activity != m_nSavedTranslatedGoalActivity )
- {
- m_nSavedTranslatedGoalActivity = activity;
- m_nInteriorSequence = ACT_INVALID;
- m_nGoalSequence = pNavigator->GetArrivalSequence( m_nPrimarySequence );
- }
-
- if (m_bDeceleratingToGoal)
- {
- // find that sequence to play when at goal
- m_nGoalSequence = pNavigator->GetArrivalSequence( m_nPrimarySequence );
-
- if (m_nGoalSequence == ACT_INVALID)
- {
- m_nGoalSequence = GetInteriorSequence( m_nPrimarySequence );
- }
-
- Assert( m_nGoalSequence != ACT_INVALID );
- }
-
- if (m_flSecondaryWeight == 1.0 || (m_iSecondaryLayer != -1 && m_nPrimarySequence == m_nSecondarySequence))
- {
- // secondary layer at full strength last time, delete the primary and shift down
- RemoveLayer( m_iPrimaryLayer, 0.0, 0.0 );
-
- m_iPrimaryLayer = m_iSecondaryLayer;
- m_nPrimarySequence = m_nSecondarySequence;
- m_iSecondaryLayer = -1;
- m_nSecondarySequence = ACT_INVALID;
- m_flSecondaryWeight = 0.0;
- }
-
- // look for transition sequence if needed
- if (m_nSecondarySequence == ACT_INVALID)
- {
- if (!m_bDeceleratingToGoal && m_nGoalSequence != GetInteriorSequence( m_nPrimarySequence ))
- {
- // strob interior sequence in case it changed
- m_nGoalSequence = GetInteriorSequence( m_nPrimarySequence );
- }
-
- if (m_nGoalSequence != ACT_INVALID && m_nPrimarySequence != m_nGoalSequence)
- {
- // Msg("From %s to %s\n", GetOuter()->GetSequenceName( m_nPrimarySequence ), GetOuter()->GetSequenceName( m_nGoalSequence ) );
- m_nSecondarySequence = GetOuter()->FindTransitionSequence(m_nPrimarySequence, m_nGoalSequence, NULL);
- if (m_nSecondarySequence == ACT_INVALID)
- m_nSecondarySequence = m_nGoalSequence;
- }
- }
-
- // set blending for
- if (m_nSecondarySequence != ACT_INVALID)
- {
- if (m_iSecondaryLayer == -1)
- {
- m_iSecondaryLayer = AddLayeredSequence( m_nSecondarySequence, 0 );
- SetLayerWeight( m_iSecondaryLayer, 0.0 );
- if (m_nSecondarySequence == m_nGoalSequence)
- {
- SetLayerPlaybackRate( m_iSecondaryLayer, 0.0 );
- }
- else
- {
- SetLayerPlaybackRate( m_iSecondaryLayer, 1.0 );
- }
- SetLayerNoRestore( m_iSecondaryLayer, true );
- m_flSecondaryWeight = 0.0;
- }
-
- m_flSecondaryWeight = MIN( m_flSecondaryWeight + 0.3, 1.0 );
-
- if (m_flSecondaryWeight < 1.0)
- {
- SetLayerWeight( m_iPrimaryLayer, (flWeight - m_flSecondaryWeight * flWeight) / (1.0f - m_flSecondaryWeight * flWeight) );
- SetLayerWeight( m_iSecondaryLayer, flWeight * m_flSecondaryWeight );
- }
- else
- {
- SetLayerWeight( m_iPrimaryLayer, 0.0f );
- SetLayerWeight( m_iSecondaryLayer, flWeight );
- }
- }
- else
- {
- // recreate layer if missing
- if (m_iPrimaryLayer == -1)
- {
- MoveContinue();
- }
-
- // try to catch a stale layer
- if (m_iSecondaryLayer != -1)
- {
- // secondary layer at full strength last time, delete the primary and shift down
- RemoveLayer( m_iSecondaryLayer, 0.0, 0.0 );
- m_iSecondaryLayer = -1;
- m_nSecondarySequence = ACT_INVALID;
- m_flSecondaryWeight = 0.0;
- }
-
- // debounce
- // flWeight = flWeight * 0.5 + 0.5 * GetOuter()->GetLayerWeight( m_iPrimaryLayer );
- SetLayerWeight( m_iPrimaryLayer, flWeight );
- }
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: get the "idle" animation to play as the compliment to the movement animation
-//-----------------------------------------------------------------------------
-int CAI_BlendedMotor::GetInteriorSequence( int fromSequence )
-{
- AI_PROFILE_SCOPE(CAI_BlendedMotor_GetInteriorSequence);
-
- // FIXME: add interior activity to path, just like arrival activity.
- int sequence = GetNavigator()->GetMovementSequence();
-
- if (m_nInteriorSequence != ACT_INVALID && sequence == m_nPrevMovementSequence)
- {
- return m_nInteriorSequence;
- }
-
- m_nPrevMovementSequence = sequence;
-
- KeyValues *seqKeyValues = GetOuter()->GetSequenceKeyValues( sequence );
- // Msg("sequence %d : %s (%d)\n", sequence, GetOuter()->GetSequenceName( sequence ), seqKeyValues != NULL );
- if (seqKeyValues)
- {
- KeyValues *pkvInterior = seqKeyValues->FindKey("interior");
- if (pkvInterior)
- {
- const char *szActivity = pkvInterior->GetString();
-
- Activity activity = ( Activity )GetOuter()->LookupActivity( szActivity );
- if ( activity != ACT_INVALID )
- {
- m_nInteriorSequence = GetOuter()->SelectWeightedSequence( GetOuter()->TranslateActivity( activity ), fromSequence );
- }
- else
- {
- activity = (Activity)GetOuter()->GetActivityID( szActivity );
- if ( activity != ACT_INVALID )
- {
- m_nInteriorSequence = GetOuter()->SelectWeightedSequence( GetOuter()->TranslateActivity( activity ), fromSequence );
- }
- }
-
- if (activity == ACT_INVALID || m_nInteriorSequence == ACT_INVALID)
- {
- m_nInteriorSequence = GetOuter()->LookupSequence( szActivity );
- }
- }
- }
-
- if (m_nInteriorSequence == ACT_INVALID)
- {
- Activity activity = GetNavigator()->GetMovementActivity();
- if (activity == ACT_WALK_AIM || activity == ACT_RUN_AIM)
- {
- activity = ACT_IDLE_ANGRY;
- }
- else
- {
- activity = ACT_IDLE;
- }
- m_nInteriorSequence = GetOuter()->SelectWeightedSequence( GetOuter()->TranslateActivity( activity ), fromSequence );
-
- Assert( m_nInteriorSequence != ACT_INVALID );
- }
-
- return m_nInteriorSequence;
-}
-
-
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Move the npc to the next location on its route.
-//-----------------------------------------------------------------------------
-
-AIMotorMoveResult_t CAI_BlendedMotor::MoveGroundExecute( const AILocalMoveGoal_t &move, AIMoveTrace_t *pTraceResult )
-{
- AI_PROFILE_SCOPE(CAI_BlendedMotor_MoveGroundExecute);
-
- if ( move.curExpectedDist < 0.001 )
- {
- AIMotorMoveResult_t result = BaseClass::MoveGroundExecute( move, pTraceResult );
- // Msg(" BaseClass::MoveGroundExecute() - remaining %.2f\n", GetMoveInterval() );
- SetMoveScriptAnim( 0.0 );
- return result;
- }
-
- BuildMoveScript( move, pTraceResult );
-
- float flNewSpeed = GetCurSpeed();
- float flTotalDist = GetMoveScriptDist( flNewSpeed );
-
- Assert( move.maxDist < 0.01 || flTotalDist > 0.0 );
-
- // --------------------------------------------
- // turn in the direction of movement
- // --------------------------------------------
-
- float flNewYaw = GetMoveScriptYaw( );
-
- // get facing based on movement yaw
- AILocalMoveGoal_t move2 = move;
- move2.facing = UTIL_YawToVector( flNewYaw );
-
- // turn in the direction needed
- MoveFacing( move2 );
-
- // reset actual "sequence" ground speed based current movement sequence, orientation
-
- // FIXME: this should be based on
-
- GetOuter()->m_flGroundSpeed = GetSequenceGroundSpeed( GetSequence());
-
-
-
- /*
- if (1 || flNewSpeed > GetIdealSpeed())
- {
- // DevMsg( "%6.2f : Speed %.1f : %.1f (%.1f) : %d\n", gpGlobals->curtime, flNewSpeed, move.maxDist, move.transitionDist, GetOuter()->m_pHintNode != NULL );
- // DevMsg( "%6.2f : Speed %.1f : %.1f\n", gpGlobals->curtime, flNewSpeed, GetIdealSpeed() );
- }
- */
-
- SetMoveScriptAnim( flNewSpeed );
-
- /*
- if ((GetOuter()->m_debugOverlays & OVERLAY_NPC_SELECTED_BIT))
- {
- DevMsg( "%6.2f : Speed %.1f : %.1f : %.2f\n", gpGlobals->curtime, flNewSpeed, GetIdealSpeed(), flNewSpeed / GetIdealSpeed() );
- }
- */
-
- AIMotorMoveResult_t result = MoveGroundExecuteWalk( move, flNewSpeed, flTotalDist, pTraceResult );
-
- return result;
-
-}
-
-
-
-
-AIMotorMoveResult_t CAI_BlendedMotor::MoveFlyExecute( const AILocalMoveGoal_t &move, AIMoveTrace_t *pTraceResult )
-{
- AI_PROFILE_SCOPE(CAI_BlendedMotor_MoveFlyExecute);
-
- if ( move.curExpectedDist < 0.001 )
- return BaseClass::MoveFlyExecute( move, pTraceResult );
-
- BuildMoveScript( move, pTraceResult );
-
- float flNewSpeed = GetCurSpeed();
- float flTotalDist = GetMoveScriptDist( flNewSpeed );
-
- Assert( move.maxDist < 0.01 || flTotalDist > 0.0 );
-
- // --------------------------------------------
- // turn in the direction of movement
- // --------------------------------------------
-
- float flNewYaw = GetMoveScriptYaw( );
-
- // get facing based on movement yaw
- AILocalMoveGoal_t move2 = move;
- move2.facing = UTIL_YawToVector( flNewYaw );
-
- // turn in the direction needed
- MoveFacing( move2 );
-
- GetOuter()->m_flGroundSpeed = GetSequenceGroundSpeed( GetSequence());
-
- SetMoveScriptAnim( flNewSpeed );
-
- // DevMsg( "%6.2f : Speed %.1f : %.1f\n", gpGlobals->curtime, flNewSpeed, GetIdealSpeed() );
-
- // reset actual "sequence" ground speed based current movement sequence, orientation
-
- // FIXME: the above is redundant with MoveGroundExecute, and the below is a mix of MoveGroundExecuteWalk and MoveFlyExecute
-
- bool bReachingLocalGoal = ( flTotalDist > 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 / flTotalDist) );
- }
- else
- SetMoveInterval( 0 );
- flTotalDist = move.maxDist;
- }
- else
- {
- // use all the time
- SetMoveInterval( 0 );
- }
-
- SetMoveVel( move.dir * flNewSpeed );
-
- // orig
- Vector vecStart, vecEnd;
- vecStart = GetLocalOrigin();
- VectorMA( vecStart, flTotalDist, 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 - flTotalDist) <= 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;
-}
-
-
-
-
-float CAI_BlendedMotor::OverrideMaxYawSpeed( Activity activity )
-{
- // Don't do this is we're locked
- if ( IsYawLocked() )
- return 0.0f;
-
- switch( activity )
- {
- case ACT_TURN_LEFT:
- case ACT_TURN_RIGHT:
- return 45;
- break;
- default:
- if (GetOuter()->IsMoving())
- {
- return 15;
- }
- return 45; // too fast?
- break;
- }
- return -1;
-}
-
-
-
-void CAI_BlendedMotor::UpdateYaw( int speed )
-{
- // Don't do this is we're locked
- if ( IsYawLocked() )
- return;
-
- GetOuter()->UpdateTurnGesture( );
- BaseClass::UpdateYaw( speed );
-}
-
-
-
-void CAI_BlendedMotor::RecalculateYawSpeed()
-{
- // Don't do this is we're locked
- if ( IsYawLocked() )
- {
- SetYawSpeed( 0.0f );
- return;
- }
-
- if (GetOuter()->HasMemory( bits_MEMORY_TURNING ))
- return;
-
- SetYawSpeed( CalcYawSpeed() );
-}
-
-
-//-------------------------------------
-
-
-void CAI_BlendedMotor::MoveClimbStart( const Vector &climbDest, const Vector &climbDir, float climbDist, float yaw )
-{
- // TODO: merge transitions with movement script
- if (m_iPrimaryLayer != -1)
- {
- SetLayerWeight( m_iPrimaryLayer, 0 );
- }
- if (m_iSecondaryLayer != -1)
- {
- SetLayerWeight( m_iSecondaryLayer, 0 );
- }
-
- BaseClass::MoveClimbStart( climbDest, climbDir, climbDist, yaw );
-}
-
-
-//-------------------------------------
-
-
-void CAI_BlendedMotor::MoveJumpStart( const Vector &velocity )
-{
- // TODO: merge transitions with movement script
- if (m_iPrimaryLayer != -1)
- {
- SetLayerWeight( m_iPrimaryLayer, 0 );
- }
- if (m_iSecondaryLayer != -1)
- {
- SetLayerWeight( m_iSecondaryLayer, 0 );
- }
-
- BaseClass::MoveJumpStart( velocity );
-}
-
-
-//-------------------------------------
-
-void CAI_BlendedMotor::BuildMoveScript( const AILocalMoveGoal_t &move, AIMoveTrace_t *pTraceResult )
-{
- m_scriptMove.RemoveAll();
- m_scriptTurn.RemoveAll();
-
- BuildVelocityScript( move );
- BuildTurnScript( move );
-
-/*
- if (GetOuter()->m_debugOverlays & OVERLAY_NPC_SELECTED_BIT)
- {
- int i;
-#if 1
-
- for (i = 1; i < m_scriptMove.Count(); i++)
- {
- NDebugOverlay::Line( m_scriptMove[i-1].vecLocation, m_scriptMove[i].vecLocation, 255,255,255, true, 0.1 );
-
- NDebugOverlay::Box( m_scriptMove[i].vecLocation, Vector( -2, -2, -2 ), Vector( 2, 2, 2 ), 0,255,255, 0, 0.1 );
-
- //NDebugOverlay::Line( m_scriptMove[i].vecLocation, m_scriptMove[i].vecLocation + Vector( 0,0,m_scriptMove[i].flMaxVelocity), 0,255,255, true, 0.1 );
-
- Vector vecMidway = m_scriptMove[i].vecLocation + ((m_scriptMove[i-1].vecLocation - m_scriptMove[i].vecLocation) * 0.5);
- NDebugOverlay::Text( vecMidway, UTIL_VarArgs( "%d", i ), false, 0.1 );
- }
-#endif
-#if 0
- for (i = 1; i < m_scriptTurn.Count(); i++)
- {
- NDebugOverlay::Line( m_scriptTurn[i-1].vecLocation, m_scriptTurn[i].vecLocation, 255,255,255, true, 0.1 );
-
- NDebugOverlay::Box( m_scriptTurn[i].vecLocation, Vector( -2, -2, -2 ), Vector( 2, 2, 2 ), 255,0,0, 0, 0.1 );
-
- NDebugOverlay::Line( m_scriptTurn[i].vecLocation + Vector( 0,0,1), m_scriptTurn[i].vecLocation + Vector( 0,0,1) + UTIL_YawToVector( m_scriptTurn[i].flYaw ) * 32, 255,0,0, true, 0.1 );
- }
-#endif
- }
-*/
-}
-
-
-#define YAWSPEED 150
-
-
-void CAI_BlendedMotor::BuildTurnScript( const AILocalMoveGoal_t &move )
-{
- AI_PROFILE_SCOPE(CAI_BlendedMotor_BuildTurnScript);
-
- int i;
-
- AI_Movementscript_t script;
- script.Init();
-
- // current location
- script.vecLocation = GetAbsOrigin();
- script.flYaw = GetAbsAngles().y;
- m_scriptTurn.AddToTail( script );
-
- //-------------------------
-
- // insert default turn parameters, try to turn 80% to goal at all corners before getting there
- int prev = 0;
- for (i = 0; i < m_scriptMove.Count(); i++)
- {
- AI_Waypoint_t *pCurWaypoint = m_scriptMove[i].pWaypoint;
- if (pCurWaypoint)
- {
- script.Init();
- script.vecLocation = pCurWaypoint->vecLocation;
- script.pWaypoint = pCurWaypoint;
- script.flElapsedTime = m_scriptMove[i].flElapsedTime;
-
- m_scriptTurn[prev].flTime = script.flElapsedTime - m_scriptTurn[prev].flElapsedTime;
-
- if (pCurWaypoint->GetNext())
- {
- Vector d1 = pCurWaypoint->GetNext()->vecLocation - script.vecLocation;
- Vector d2 = script.vecLocation - m_scriptTurn[prev].vecLocation;
-
- d1.z = 0;
- VectorNormalize( d1 );
- d2.z = 0;
- VectorNormalize( d2 );
-
- float y1 = UTIL_VecToYaw( d1 );
- float y2 = UTIL_VecToYaw( d2 );
-
- float deltaYaw = fabs( UTIL_AngleDiff( y1, y2 ) );
-
- if (deltaYaw > 0.1)
- {
- // turn to 80% of goal
- script.flYaw = UTIL_ApproachAngle( y1, y2, deltaYaw * 0.8 );
- m_scriptTurn.AddToTail( script );
- // DevMsg("turn waypoint %.1f %.1f %.1f\n", script.vecLocation.x, script.vecLocation.y, script.vecLocation.z );
- prev++;
- }
- }
- else
- {
- Vector vecDir = GetNavigator()->GetArrivalDirection();
- script.flYaw = UTIL_VecToYaw( vecDir );
- m_scriptTurn.AddToTail( script );
- // DevMsg("turn waypoint %.1f %.1f %.1f\n", script.vecLocation.x, script.vecLocation.y, script.vecLocation.z );
- prev++;
- }
- }
- }
-
- // propagate ending facing back over any nearby nodes
- // FIXME: this needs to minimize total turning, not just local/end turning.
- // depending on waypoint spacing, complexity, it may turn the wrong way!
- for (i = m_scriptTurn.Count()-1; i > 1; i--)
- {
- float deltaYaw = UTIL_AngleDiff( m_scriptTurn[i-1].flYaw, m_scriptTurn[i].flYaw );
-
- float maxYaw = YAWSPEED * m_scriptTurn[i-1].flTime;
-
- if (fabs(deltaYaw) > maxYaw)
- {
- m_scriptTurn[i-1].flYaw = UTIL_ApproachAngle( m_scriptTurn[i-1].flYaw, m_scriptTurn[i].flYaw, maxYaw );
- }
- }
-
- for (i = 0; i < m_scriptTurn.Count() - 1; )
- {
- i = i + BuildTurnScript( i, i + 1 ) + 1;
- }
- //-------------------------
-}
-
-
-
-int CAI_BlendedMotor::BuildTurnScript( int i, int j )
-{
- AI_PROFILE_SCOPE(CAI_BlendedMotor_BuildTurnScript2);
-
- int k;
-
- Vector vecDir = m_scriptTurn[j].vecLocation - m_scriptTurn[i].vecLocation;
- float interiorYaw = UTIL_VecToYaw( vecDir );
-
- float deltaYaw;
-
- deltaYaw = fabs( UTIL_AngleDiff( interiorYaw, m_scriptTurn[i].flYaw ) );
- float t1 = deltaYaw / YAWSPEED;
-
- deltaYaw = fabs( UTIL_AngleDiff( m_scriptTurn[j].flYaw, interiorYaw ) );
- float t2 = deltaYaw / YAWSPEED;
-
- float totalTime = m_scriptTurn[j].flElapsedTime - m_scriptTurn[i].flElapsedTime;
-
- Assert( totalTime > 0 );
-
- if (t1 < 0.01)
- {
- if (t2 > totalTime * 0.8)
- {
- // too close, nothing to do
- return 0;
- }
-
- // go ahead and force yaw
- m_scriptTurn[i].flYaw = interiorYaw;
-
- // we're already aiming close enough to the interior yaw, set the point where we need to blend out
- k = BuildInsertNode( i, totalTime - t2 );
- m_scriptTurn[k].flYaw = interiorYaw;
-
- return 1;
- }
- else if (t2 < 0.01)
- {
- if (t1 > totalTime * 0.8)
- {
- // too close, nothing to do
- return 0;
- }
-
- // we'll finish up aiming close enough to the interior yaw, set the point where we need to blend in
- k = BuildInsertNode( i, t1 );
- m_scriptTurn[k].flYaw = interiorYaw;
-
- return 1;
- }
- else if (t1 + t2 > totalTime)
- {
- // don't bother with interior node
- return 0;
-
- // waypoints need to much turning, ignore interior yaw
- float a = (t1 / (t1 + t2));
- t1 = a * totalTime;
-
- k = BuildInsertNode( i, t1 );
-
- deltaYaw = UTIL_AngleDiff( m_scriptTurn[j].flYaw, m_scriptTurn[i].flYaw );
- m_scriptTurn[k].flYaw = UTIL_ApproachAngle( m_scriptTurn[j].flYaw, m_scriptTurn[i].flYaw, deltaYaw * (1 - a) );
-
- return 1;
- }
- else if (t1 + t2 < totalTime * 0.8)
- {
- // turn to face interior, run a ways, then turn away
- k = BuildInsertNode( i, t1 );
- m_scriptTurn[k].flYaw = interiorYaw;
-
- k = BuildInsertNode( i, t2 );
- m_scriptTurn[k].flYaw = interiorYaw;
-
- return 2;
- }
- return 0;
-}
-
-
-int CAI_BlendedMotor::BuildInsertNode( int i, float flTime )
-{
- AI_Movementscript_t script;
- script.Init();
-
- Assert( flTime > 0.0 );
-
- for (i; i < m_scriptTurn.Count() - 1; i++)
- {
- if (m_scriptTurn[i].flTime < flTime)
- {
- flTime -= m_scriptTurn[i].flTime;
- }
- else
- {
- float a = flTime / m_scriptTurn[i].flTime;
-
- script.flTime = (m_scriptTurn[i].flTime - flTime);
-
- m_scriptTurn[i].flTime = flTime;
-
- script.flElapsedTime = m_scriptTurn[i].flElapsedTime * (1 - a) + m_scriptTurn[i+1].flElapsedTime * a;
-
- script.vecLocation = m_scriptTurn[i].vecLocation * (1 - a) + m_scriptTurn[i+1].vecLocation * a;
-
- m_scriptTurn.InsertAfter( i, script );
-
- return i + 1;
- }
- }
- Assert( 0 );
- return 0;
-}
-
-
-ConVar ai_path_insert_pause_at_obstruction( "ai_path_insert_pause_at_obstruction", "1" );
-ConVar ai_path_adjust_speed_on_immediate_turns( "ai_path_adjust_speed_on_immediate_turns", "1" );
-ConVar ai_path_insert_pause_at_est_end( "ai_path_insert_pause_at_est_end", "1" );
-
-#define MIN_VELOCITY 0.0f
-#define MIN_STEER_DOT 0.0f
-
-void CAI_BlendedMotor::BuildVelocityScript( const AILocalMoveGoal_t &move )
-{
- AI_PROFILE_SCOPE(CAI_BlendedMotor_BuildVelocityScript);
-
- int i;
- float a;
-
- float idealVelocity = GetIdealSpeed();
- if (idealVelocity == 0)
- {
- idealVelocity = 50;
- }
-
- float idealAccel = GetIdealAccel();
- if (idealAccel == 0)
- {
- idealAccel = 100;
- }
-
- AI_Movementscript_t script;
-
- // set current location as start of script
- script.vecLocation = GetAbsOrigin();
- script.flMaxVelocity = GetCurSpeed();
- m_scriptMove.AddToTail( script );
-
- //-------------------------
-
- extern ConVar npc_height_adjust;
- if (npc_height_adjust.GetBool() && move.bHasTraced && move.directTrace.flTotalDist != move.thinkTrace.flTotalDist)
- {
- float flDist = (move.directTrace.vEndPosition - m_scriptMove[0].vecLocation).Length2D();
- float flHeight = move.directTrace.vEndPosition.z - m_scriptMove[0].vecLocation.z;
- float flDelta;
-
- if (flDist > 0)
- {
- flDelta = flHeight / flDist;
- }
- else
- {
- flDelta = 0;
- }
-
- m_flPredictiveSpeedAdjust = 1.1 - fabs( flDelta );
- m_flPredictiveSpeedAdjust = clamp( m_flPredictiveSpeedAdjust, (flHeight > 0.0f) ? 0.5f : 0.8f, 1.0f );
-
- /*
- if ((GetOuter()->m_debugOverlays & OVERLAY_NPC_SELECTED_BIT))
- {
- Msg("m_flPredictiveSpeedAdjust %.3f %.1f %.1f\n", m_flPredictiveSpeedAdjust, flHeight, flDist );
- NDebugOverlay::Box( move.directTrace.vEndPosition, Vector( -2, -2, -2 ), Vector( 2, 2, 2 ), 0,255,255, 0, 0.12 );
- }
- */
- }
- if (npc_height_adjust.GetBool())
- {
- float flDist = (move.thinkTrace.vEndPosition - m_vecPrevOrigin2).Length2D();
- float flHeight = move.thinkTrace.vEndPosition.z - m_vecPrevOrigin2.z;
- float flDelta;
-
- if (flDist > 0)
- {
- flDelta = flHeight / flDist;
- }
- else
- {
- flDelta = 0;
- }
-
- float newSpeedAdjust = 1.1 - fabs( flDelta );
- newSpeedAdjust = clamp( newSpeedAdjust, (flHeight > 0.0f) ? 0.5f : 0.8f, 1.0f );
-
- // debounce speed adjust
- if (newSpeedAdjust < m_flReactiveSpeedAdjust)
- {
- m_flReactiveSpeedAdjust = m_flReactiveSpeedAdjust * 0.2f + newSpeedAdjust * 0.8f;
- }
- else
- {
- m_flReactiveSpeedAdjust = m_flReactiveSpeedAdjust * 0.5f + newSpeedAdjust * 0.5f;
- }
-
- // filter through origins
- m_vecPrevOrigin2 = m_vecPrevOrigin1;
- m_vecPrevOrigin1 = GetAbsOrigin();
-
- /*
- if ((GetOuter()->m_debugOverlays & OVERLAY_NPC_SELECTED_BIT))
- {
- NDebugOverlay::Box( m_vecPrevOrigin2, Vector( -2, -2, -2 ), Vector( 2, 2, 2 ), 255,0,255, 0, 0.12 );
- NDebugOverlay::Box( move.thinkTrace.vEndPosition, Vector( -2, -2, -2 ), Vector( 2, 2, 2 ), 255,0,255, 0, 0.12 );
- Msg("m_flReactiveSpeedAdjust %.3f %.1f %.1f\n", m_flReactiveSpeedAdjust, flHeight, flDist );
- }
- */
- }
-
- idealVelocity = idealVelocity * MIN( m_flReactiveSpeedAdjust, m_flPredictiveSpeedAdjust );
-
- //-------------------------
-
- bool bAddedExpected = false;
-
- // add all waypoint locations and velocities
- AI_Waypoint_t *pCurWaypoint = GetNavigator()->GetPath()->GetCurWaypoint();
-
- // there has to be at least one waypoint
- Assert( pCurWaypoint );
-
- while (pCurWaypoint && (pCurWaypoint->NavType() == NAV_GROUND || pCurWaypoint->NavType() == NAV_FLY) /*&& flTotalDist / idealVelocity < 3.0*/) // limit lookahead to 3 seconds
- {
- script.Init();
- AI_Waypoint_t *pNext = pCurWaypoint->GetNext();
-
- if (ai_path_adjust_speed_on_immediate_turns.GetBool() && !bAddedExpected)
- {
- // hack in next expected immediate location for move
- script.vecLocation = GetAbsOrigin() + move.dir * move.curExpectedDist;
- bAddedExpected = true;
- pNext = pCurWaypoint;
- }
- else
- {
- script.vecLocation = pCurWaypoint->vecLocation;
- script.pWaypoint = pCurWaypoint;
- }
-
- //DevMsg("waypoint %.1f %.1f %.1f\n", script.vecLocation.x, script.vecLocation.y, script.vecLocation.z );
-
- if (pNext)
- {
- switch( pNext->NavType())
- {
- case NAV_GROUND:
- case NAV_FLY:
- {
- Vector d1 = pNext->vecLocation - script.vecLocation;
- Vector d2 = script.vecLocation - m_scriptMove[m_scriptMove.Count()-1].vecLocation;
-
- // remove very short, non terminal ground links
- // FIXME: is this safe? Maybe just check for co-located ground points?
- if (d1.Length2D() < 1.0)
- {
- /*
- if (m_scriptMove.Count() > 1)
- {
- int i = m_scriptMove.Count() - 1;
- m_scriptMove[i].vecLocation = pCurWaypoint->vecLocation;
- m_scriptMove[i].pWaypoint = pCurWaypoint;
- }
- */
- pCurWaypoint = pNext;
- continue;
- }
-
- d1.z = 0;
- VectorNormalize( d1 );
- d2.z = 0;
- VectorNormalize( d2 );
-
- // figure velocity
- float dot = (DotProduct( d1, d2 ) + 0.2);
- if (dot > 0)
- {
- dot = clamp( dot, 0.0f, 1.0f );
- script.flMaxVelocity = idealVelocity * dot;
- }
- else
- {
- script.flMaxVelocity = 0;
- }
- }
- break;
- case NAV_JUMP:
-
- // FIXME: information about what the jump should look like isn't stored in the waypoints
- // this'll need to call
- // GetMoveProbe()->MoveLimit( NAV_JUMP, GetLocalOrigin(), GetPath()->CurWaypointPos(), MASK_NPCSOLID, GetNavTargetEntity(), &moveTrace );
- // to get how far/fast the jump will be, but this is also stateless, so it'd call it per frame.
- // So far it's not clear that the moveprobe doesn't also call this.....
-
- {
- float minJumpHeight = 0;
- float maxHorzVel = MAX( GetCurSpeed(), 100 );
- float gravity = GetCurrentGravity() * GetOuter()->GetGravity();
- Vector vecApex;
- Vector rawJumpVel = GetMoveProbe()->CalcJumpLaunchVelocity(script.vecLocation, pNext->vecLocation, gravity, &minJumpHeight, maxHorzVel, &vecApex );
-
- script.flMaxVelocity = rawJumpVel.Length2D();
- // Msg("%.1f\n", script.flMaxVelocity );
- }
- break;
- case NAV_CLIMB:
- {
- /*
- CAI_Node *pClimbNode = GetNavigator()->GetNetwork()->GetNode(pNext->iNodeID);
-
- check: pClimbNode->m_eNodeInfo
- bits_NODE_CLIMB_BOTTOM,
- bits_NODE_CLIMB_ON,
- bits_NODE_CLIMB_OFF_FORWARD,
- bits_NODE_CLIMB_OFF_LEFT,
- bits_NODE_CLIMB_OFF_RIGHT
- */
-
- script.flMaxVelocity = 0;
- }
- break;
- /*
- case NAV_FLY:
- // FIXME: can there be a NAV_GROUND -> NAV_FLY transition?
- script.flMaxVelocity = 0;
- break;
- */
- }
- }
- else
- {
- script.flMaxVelocity = GetNavigator()->GetArrivalSpeed();
- // Assert( script.flMaxVelocity == 0 );
- }
-
- m_scriptMove.AddToTail( script );
- pCurWaypoint = pNext;
- }
-
-
- //-------------------------
-
- // update distances
- float flTotalDist = 0;
- for (i = 0; i < m_scriptMove.Count() - 1; i++ )
- {
- flTotalDist += m_scriptMove[i].flDist = (m_scriptMove[i+1].vecLocation - m_scriptMove[i].vecLocation).Length2D();
- }
-
- //-------------------------
-
- if ( !m_bDeceleratingToGoal && m_scriptMove.Count() && flTotalDist > 0 )
- {
- float flNeededAccel = DeltaV( m_scriptMove[0].flMaxVelocity, m_scriptMove[m_scriptMove.Count() - 1].flMaxVelocity, flTotalDist );
- m_bDeceleratingToGoal = (flNeededAccel < -idealAccel);
- //Assert( flNeededAccel != idealAccel);
- }
-
- //-------------------------
-
- // insert slowdown points due to blocking
- if (ai_path_insert_pause_at_obstruction.GetBool() && move.directTrace.pObstruction)
- {
- float distToObstruction = (move.directTrace.vEndPosition - m_scriptMove[0].vecLocation).Length2D();
-
- // HACK move obstruction out "stepsize" to account for it being based on stand position and not a trace
- distToObstruction = distToObstruction + 16;
-
- InsertSlowdown( distToObstruction, idealAccel, false );
- }
-
- if (ai_path_insert_pause_at_est_end.GetBool() && GetNavigator()->GetArrivalDistance() > 0.0)
- {
- InsertSlowdown( flTotalDist - GetNavigator()->GetArrivalDistance(), idealAccel, true );
- }
-
- // calc initial velocity based on immediate direction changes
- if ( ai_path_adjust_speed_on_immediate_turns.GetBool() && m_scriptMove.Count() > 1)
- {
- /*
- if ((GetOuter()->m_debugOverlays & OVERLAY_NPC_SELECTED_BIT))
- {
- Vector tmp = m_scriptMove[1].vecLocation - m_scriptMove[0].vecLocation;
- VectorNormalize( tmp );
- NDebugOverlay::Line( m_scriptMove[0].vecLocation + Vector( 0, 0, 10 ), m_scriptMove[0].vecLocation + tmp * 32 + Vector( 0, 0, 10 ), 255,255,255, true, 0.1 );
-
- NDebugOverlay::Line( m_scriptMove[0].vecLocation + Vector( 0, 0, 10 ), m_scriptMove[1].vecLocation + Vector( 0, 0, 10 ), 255,0,0, true, 0.1 );
-
- tmp = GetCurVel();
- VectorNormalize( tmp );
- NDebugOverlay::Line( m_scriptMove[0].vecLocation + Vector( 0, 0, 10 ), m_scriptMove[0].vecLocation + tmp * 32 + Vector( 0, 0, 10 ), 0,0,255, true, 0.1 );
- }
- */
-
- Vector d1 = m_scriptMove[1].vecLocation - m_scriptMove[0].vecLocation;
- d1.z = 0;
- VectorNormalize( d1 );
-
- Vector d2 = GetCurVel();
- d2.z = 0;
- VectorNormalize( d2 );
-
- float dot = (DotProduct( d1, d2 ) + MIN_STEER_DOT);
- dot = clamp( dot, 0.0f, 1.0f );
- m_scriptMove[0].flMaxVelocity = m_scriptMove[0].flMaxVelocity * dot;
- }
-
- // clamp forward velocities
- for (i = 0; i < m_scriptMove.Count() - 1; i++ )
- {
- // find needed acceleration
- float dv = m_scriptMove[i+1].flMaxVelocity - m_scriptMove[i].flMaxVelocity;
-
- if (dv > 0.0)
- {
- // find time, distance to accel to next max vel
- float t1 = dv / idealAccel;
- float d1 = m_scriptMove[i].flMaxVelocity * t1 + 0.5 * (idealAccel) * t1 * t1;
-
- // is there enough distance
- if (d1 > m_scriptMove[i].flDist)
- {
- float r1, r2;
-
- // clamp the next velocity to the possible accel in the given distance
- if (SolveQuadratic( 0.5 * idealAccel, m_scriptMove[i].flMaxVelocity, -m_scriptMove[i].flDist, r1, r2 ))
- {
- m_scriptMove[i+1].flMaxVelocity = m_scriptMove[i].flMaxVelocity + idealAccel * r1;
- }
- }
- }
- }
-
- // clamp decel velocities
- for (i = m_scriptMove.Count() - 1; i > 0; i-- )
- {
- // find needed deceleration
- float dv = m_scriptMove[i].flMaxVelocity - m_scriptMove[i-1].flMaxVelocity;
-
- if (dv < 0.0)
- {
- // find time, distance to decal to next max vel
- float t1 = -dv / idealAccel;
- float d1 = m_scriptMove[i].flMaxVelocity * t1 + 0.5 * (idealAccel) * t1 * t1;
-
- // is there enough distance
- if (d1 > m_scriptMove[i-1].flDist)
- {
- float r1, r2;
-
- // clamp the next velocity to the possible decal in the given distance
- if (SolveQuadratic( 0.5 * idealAccel, m_scriptMove[i].flMaxVelocity, -m_scriptMove[i-1].flDist, r1, r2 ))
- {
- m_scriptMove[i-1].flMaxVelocity = m_scriptMove[i].flMaxVelocity + idealAccel * r1;
- }
- }
- }
- }
-
- /*
- for (i = 0; i < m_scriptMove.Count(); i++)
- {
- NDebugOverlay::Text( m_scriptMove[i].vecLocation, (const char *)CFmtStr( "%.2f ", m_scriptMove[i].flMaxVelocity ), false, 0.1 );
- // DevMsg("%.2f ", m_scriptMove[i].flMaxVelocity );
- }
- // DevMsg("\n");
- */
-
- // insert intermediate ideal velocities
- for (i = 0; i < m_scriptMove.Count() - 1;)
- {
- // accel to ideal
- float t1 = (idealVelocity - m_scriptMove[i].flMaxVelocity) / idealAccel;
- float d1 = m_scriptMove[i].flMaxVelocity * t1 + 0.5 * (idealAccel) * t1 * t1;
-
- // decel from ideal
- float t2 = (idealVelocity - m_scriptMove[i+1].flMaxVelocity) / idealAccel;
- float d2 = m_scriptMove[i+1].flMaxVelocity * t2 + 0.5 * (idealAccel) * t2 * t2;
-
- m_scriptMove[i].flDist = (m_scriptMove[i+1].vecLocation - m_scriptMove[i].vecLocation).Length2D();
-
- // is it possible to accel and decal to idealVelocity between next two nodes
- if (d1 + d2 < m_scriptMove[i].flDist)
- {
- Vector start = m_scriptMove[i].vecLocation;
- Vector end = m_scriptMove[i+1].vecLocation;
- float dist = m_scriptMove[i].flDist;
-
- // insert the two points needed to end accel and start decel
- if (d1 > 1.0 && t1 > 0.1)
- {
- a = d1 / dist;
-
- script.Init();
- script.vecLocation = end * a + start * (1 - a);
- script.flMaxVelocity = idealVelocity;
- m_scriptMove.InsertAfter( i, script );
- i++;
- }
-
- if (dist - d2 > 1.0 && t2 > 0.1)
- {
- // DevMsg("%.2f : ", a );
-
- a = (dist - d2) / dist;
-
- script.Init();
- script.vecLocation = end * a + start * (1 - a);
- script.flMaxVelocity = idealVelocity;
- m_scriptMove.InsertAfter( i, script );
- i++;
- }
-
- i++;
- }
- else
- {
- // check to see if the amount of change needed to reach target is less than the ideal acceleration
- float flNeededAccel = fabs( DeltaV( m_scriptMove[i].flMaxVelocity, m_scriptMove[i+1].flMaxVelocity, m_scriptMove[i].flDist ) );
- if (flNeededAccel < idealAccel)
- {
- // if so, they it's possible to get a bit towards the ideal velocity
- float v1 = m_scriptMove[i].flMaxVelocity;
- float v2 = m_scriptMove[i+1].flMaxVelocity;
- float dist = m_scriptMove[i].flDist;
-
- // based on solving:
- // v1+A*t1-v2-A*t2=0
- // v1*t1+0.5*A*t1*t1+v2*t2+0.5*A*t2*t2-D=0
-
- float tmp = idealAccel*dist+0.5*v1*v1+0.5*v2*v2;
- Assert( tmp >= 0 );
- t1 = (-v1+sqrt( tmp )) / idealAccel;
- t2 = (v1+idealAccel*t1-v2)/idealAccel;
-
- // if this assert hits, write down the v1, v2, dist, and idealAccel numbers and send them to me (Ken).
- // go ahead the comment it out, it's safe, but I'd like to know a test case where it's happening
- //Assert( t1 > 0 && t2 > 0 );
-
- // check to make sure it's really worth it
- if (t1 > 0.0 && t2 > 0.0)
- {
- d1 = v1 * t1 + 0.5 * idealAccel * t1 * t1;
-
- /*
- d2 = v2 * t2 + 0.5 * idealAccel * t2 * t2;
- Assert( fabs( d1 + d2 - dist ) < 0.001 );
- */
-
- float a = d1 / m_scriptMove[i].flDist;
- script.Init();
- script.vecLocation = m_scriptMove[i+1].vecLocation * a + m_scriptMove[i].vecLocation * (1 - a);
- script.flMaxVelocity = m_scriptMove[i].flMaxVelocity + idealAccel * t1;
-
- if (script.flMaxVelocity < idealVelocity)
- {
- // DevMsg("insert %.2f %.2f %.2f\n", m_scriptMove[i].flMaxVelocity, script.flMaxVelocity, m_scriptMove[i+1].flMaxVelocity );
- m_scriptMove.InsertAfter( i, script );
- i += 1;
- }
- }
- }
- i += 1;
- }
- }
-
- // clamp min velocities
- for (i = 0; i < m_scriptMove.Count(); i++)
- {
- m_scriptMove[i].flMaxVelocity = MAX( m_scriptMove[i].flMaxVelocity, MIN_VELOCITY );
- }
-
- // rebuild fields
- m_scriptMove[0].flElapsedTime = 0;
- for (i = 0; i < m_scriptMove.Count() - 1; )
- {
- m_scriptMove[i].flDist = (m_scriptMove[i+1].vecLocation - m_scriptMove[i].vecLocation).Length2D();
-
- if (m_scriptMove[i].flMaxVelocity == 0 && m_scriptMove[i+1].flMaxVelocity == 0)
- {
- // force a minimum velocity
- Assert( 0 );
- m_scriptMove[i+1].flMaxVelocity = 1.0;
- }
-
- float t = m_scriptMove[i].flDist / (0.5 * (m_scriptMove[i].flMaxVelocity + m_scriptMove[i+1].flMaxVelocity));
- m_scriptMove[i].flTime = t;
-
- /*
- if (m_scriptMove[i].flDist < 0.01)
- {
- // Assert( m_scriptMove[i+1].pWaypoint == NULL );
-
- m_scriptMove.Remove( i + 1 );
- continue;
- }
- */
-
- m_scriptMove[i+1].flElapsedTime = m_scriptMove[i].flElapsedTime + m_scriptMove[i].flTime;
-
- i++;
- }
-
- /*
- for (i = 0; i < m_scriptMove.Count(); i++)
- {
- DevMsg("(%.2f : %.2f : %.2f)", m_scriptMove[i].flMaxVelocity, m_scriptMove[i].flDist, m_scriptMove[i].flTime );
- // DevMsg("(%.2f:%.2f)", m_scriptMove[i].flTime, m_scriptMove[i].flElapsedTime );
- }
- DevMsg("\n");
- */
-}
-
-
-
-void CAI_BlendedMotor::InsertSlowdown( float distToObstruction, float idealAccel, bool bAlwaysSlowdown )
-{
- int i;
- AI_Movementscript_t script;
-
- if (distToObstruction <= 0.0)
- return;
-
- for (i = 0; i < m_scriptMove.Count() - 1; i++)
- {
- if (m_scriptMove[i].flDist > 0 && distToObstruction - m_scriptMove[i].flDist < 0)
- {
- float a = distToObstruction / m_scriptMove[i].flDist;
- Assert( a >= 0 && a <= 1);
- script.vecLocation = (1 - a) * m_scriptMove[i].vecLocation + a * m_scriptMove[i+1].vecLocation;
-
- //NDebugOverlay::Line( m_scriptMove[i].vecLocation + Vector( 0, 0, 5 ), script.vecLocation + Vector( 0, 0, 5 ), 0,255,0, true, 0.1 );
- //NDebugOverlay::Line( script.vecLocation + Vector( 0, 0, 5 ), m_scriptMove[i+1].vecLocation + Vector( 0, 0, 5 ), 0,0,255, true, 0.1 );
-
- float r1, r2;
-
- // clamp the next velocity to the possible accel in the given distance
- if (!bAlwaysSlowdown && SolveQuadratic( -0.5 * idealAccel, m_scriptMove[0].flMaxVelocity, -distToObstruction, r1, r2 ))
- {
- script.flMaxVelocity = MAX( 10, m_scriptMove[0].flMaxVelocity - idealAccel * r1 );
- }
- else
- {
- script.flMaxVelocity = 10.0;
- }
-
- script.flMaxVelocity = 1.0; // as much as reasonable
- script.pWaypoint = NULL;
- script.flDist = m_scriptMove[i].flDist - distToObstruction;
- m_scriptMove[i].flDist = distToObstruction;
- m_scriptMove.InsertAfter( i, script );
- break;
- }
- else
- {
- distToObstruction -= m_scriptMove[i].flDist;
- }
- }
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: issues turn gestures when it detects that the body has turned but the feet haven't compensated
-//-----------------------------------------------------------------------------
-
-
-void CAI_BlendedMotor::MaintainTurnActivity( void )
-{
- AI_PROFILE_SCOPE(CAI_BlendedMotor_MaintainTurnActivity);
-
- if (m_flNextTurnGesture > gpGlobals->curtime || m_flNextTurnAct > gpGlobals->curtime || GetOuter()->IsMoving() )
- {
- // clear out turn detection if currently turing or moving
- m_doTurn = m_doRight = m_doLeft = 0;
- if ( GetOuter()->IsMoving())
- {
- m_flNextTurnAct = gpGlobals->curtime + 0.3;
- }
- }
- else
- {
- // detect undirected turns
- if (m_prevYaw != GetAbsAngles().y)
- {
- float diff = UTIL_AngleDiff( m_prevYaw, GetAbsAngles().y );
- if (diff < 0.0)
- {
- m_doLeft += -diff;
- }
- else
- {
- m_doRight += diff;
- }
- m_prevYaw = GetAbsAngles().y;
- }
- // accumulate turn angle, delay response for short turns
- m_doTurn += m_doRight + m_doLeft;
- // accumulate random foot stick clearing
- m_doTurn += random->RandomFloat( 0.4, 0.6 );
- }
-
- if (m_doTurn > 15.0f)
- {
- // mostly a foot stick clear
- int iSeq = ACT_INVALID;
- if (m_doLeft > m_doRight)
- {
- iSeq = SelectWeightedSequence( ACT_GESTURE_TURN_LEFT );
- }
- else
- {
- iSeq = SelectWeightedSequence( ACT_GESTURE_TURN_RIGHT );
- }
- m_doLeft = 0;
- m_doRight = 0;
-
- if (iSeq != ACT_INVALID)
- {
- int iLayer = GetOuter()->AddGestureSequence( iSeq );
- if (iLayer != -1)
- {
- GetOuter()->SetLayerPriority( iLayer, 100 );
- // increase speed if we're getting behind or they're turning quickly
- float rate = random->RandomFloat( 0.8, 1.2 );
- if (m_doTurn > 90.0)
- {
- rate *= 1.5;
- }
- GetOuter()->SetLayerPlaybackRate( iLayer, rate );
- // disable turing for the duration of the gesture
- m_flNextTurnAct = gpGlobals->curtime + GetOuter()->GetLayerDuration( iLayer );
- }
- else
- {
- // too many active gestures, try again in half a second
- m_flNextTurnAct = gpGlobals->curtime + 0.3;
- }
- }
- m_doTurn = m_doRight = m_doLeft = 0;
- }
-}
-
-ConVar scene_flatturn( "scene_flatturn", "1" );
-
-bool CAI_BlendedMotor::AddTurnGesture( float flYD )
-{
-
- // some funky bug with human turn gestures, disable for now
- return false;
-
- // try using a turn gesture
- Activity activity = ACT_INVALID;
- float weight = 1.0;
- float turnCompletion = 1.0;
-
- if (m_flNextTurnGesture > gpGlobals->curtime)
- {
- /*
- if ( GetOuter()->m_debugOverlays & OVERLAY_NPC_SELECTED_BIT )
- {
- Msg( "%.1f : [ %.2f ]\n", flYD, m_flNextTurnAct - gpGlobals->curtime );
- }
- */
- return false;
- }
-
- if ( GetOuter()->IsMoving() || GetOuter()->IsCrouching() )
- {
- return false;
- }
-
- if (fabs( flYD ) < 15)
- {
- return false;
- }
- else if (flYD < -45)
- {
- activity = ACT_GESTURE_TURN_RIGHT90;
- weight = flYD / -90;
- turnCompletion = 0.36;
- }
- else if (flYD < 0)
- {
- activity = ACT_GESTURE_TURN_RIGHT45;
- weight = flYD / -45;
- turnCompletion = 0.4;
- }
- else if (flYD <= 45)
- {
- activity = ACT_GESTURE_TURN_LEFT45;
- weight = flYD / 45;
- turnCompletion = 0.4;
- }
- else
- {
- activity = ACT_GESTURE_TURN_LEFT90;
- weight = flYD / 90;
- turnCompletion = 0.36;
- }
-
- int seq = SelectWeightedSequence( activity );
-
- if (scene_flatturn.GetBool() && GetOuter()->IsCurSchedule( SCHED_SCENE_GENERIC ))
- {
- Activity flatactivity = activity;
-
- if (activity == ACT_GESTURE_TURN_RIGHT90)
- {
- flatactivity = ACT_GESTURE_TURN_RIGHT90_FLAT;
- }
- else if (activity == ACT_GESTURE_TURN_RIGHT45)
- {
- flatactivity = ACT_GESTURE_TURN_RIGHT45_FLAT;
- }
- else if (activity == ACT_GESTURE_TURN_LEFT90)
- {
- flatactivity = ACT_GESTURE_TURN_LEFT90_FLAT;
- }
- else if (activity == ACT_GESTURE_TURN_LEFT45)
- {
- flatactivity = ACT_GESTURE_TURN_LEFT45_FLAT;
- }
-
- if (flatactivity != activity)
- {
- int newseq = SelectWeightedSequence( flatactivity );
- if (newseq != ACTIVITY_NOT_AVAILABLE)
- {
- seq = newseq;
- }
- }
- }
-
- if (seq != ACTIVITY_NOT_AVAILABLE)
- {
- int iLayer = GetOuter()->AddGestureSequence( seq );
- if (iLayer != -1)
- {
- GetOuter()->SetLayerPriority( iLayer, 100 );
- // vary the playback a bit
- SetLayerPlaybackRate( iLayer, 1.0 );
- float actualDuration = GetOuter()->GetLayerDuration( iLayer );
-
- float rate = random->RandomFloat( 0.5f, 1.1f );
- float diff = fabs( flYD );
- float speed = (diff / (turnCompletion * actualDuration / rate)) * 0.1f;
-
- speed = clamp( speed, 15.f, 35.f );
- speed = MIN( speed, diff );
-
- actualDuration = (diff / (turnCompletion * speed)) * 0.1 ;
-
- GetOuter()->SetLayerDuration( iLayer, actualDuration );
-
- SetLayerWeight( iLayer, weight );
-
- SetYawSpeed( speed );
-
- Remember( bits_MEMORY_TURNING );
-
- // don't overlap the turn portion of the gestures, and don't play them too often
- m_flNextTurnGesture = gpGlobals->curtime + MAX( turnCompletion * actualDuration, 0.3 );
-
- /*
- if ( GetOuter()->m_debugOverlays & OVERLAY_NPC_SELECTED_BIT )
- {
- Msg( "%.1f : %.2f %.2f : %.2f (%.2f)\n", flYD, weight, speed, actualDuration, turnCompletion * actualDuration );
- }
- */
- return true;
- }
- else
- {
- return false;
- }
- }
- return false;
-}
-
-
-//-------------------------------------
-
-
-
-#if 0
-Activity CAI_BlendedMotor::GetTransitionActivity( )
-{
- AI_Waypoint_t *waypoint = GetNavigator()->GetPath()->GetTransitionWaypoint();
-
- if ( waypoint->Flags() & bits_WP_TO_GOAL )
- {
- if ( waypoint->activity != ACT_INVALID)
- {
- return waypoint->activity;
- }
-
- return GetStoppedActivity( );
- }
-
- if (waypoint)
- waypoint = waypoint->GetNext();
-
- switch(waypoint->NavType() )
- {
- case NAV_JUMP:
- return ACT_JUMP; // are jumps going to get a movement track added to them?
-
- case NAV_GROUND:
- return GetNavigator()->GetMovementActivity(); // yuck
-
- case NAV_CLIMB:
- return ACT_CLIMB_UP; // depends on specifics of climb node
-
- default:
- return ACT_IDLE;
- }
-}
-#endif
-
-//-------------------------------------
-// Purpose: return a velocity that should be hit at the end of the interval to match goal
-// Input : flInterval - time interval to consider
-// : flGoalDistance - distance to goal
-// : flGoalVelocity - desired velocity at goal
-// : flCurVelocity - current velocity
-// : flIdealVelocity - velocity to go at if goal is too far away
-// : flAccelRate - maximum acceleration/deceleration rate
-// Output : target velocity at time t+flInterval
-//-------------------------------------
-
-float ChangeDistance( float flInterval, float flGoalDistance, float flGoalVelocity, float flCurVelocity, float flIdealVelocity, float flAccelRate, float &flNewDistance, float &flNewVelocity )
-{
- float scale = 1.0;
- if (flGoalDistance < 0)
- {
- flGoalDistance = - flGoalDistance;
- flCurVelocity = -flCurVelocity;
- scale = -1.0;
- }
-
- flNewVelocity = flCurVelocity;
- flNewDistance = 0.0;
-
- // if I'm too close, just go ahead and set the velocity
- if (flGoalDistance < 0.01)
- {
- return flGoalVelocity * scale;
- }
-
- float flGoalAccel = DeltaV( flCurVelocity, flGoalVelocity, flGoalDistance );
-
- flNewVelocity = flCurVelocity;
-
- // --------------------------------------------
- // if goal is close enough try to match the goal velocity, else try to go ideal velocity
- // --------------------------------------------
- if (flGoalAccel < 0 && flGoalAccel < -flAccelRate)
- {
- // I need to slow down;
- flNewVelocity = flCurVelocity + flGoalAccel * flInterval;
- if (flNewVelocity < 0)
- flNewVelocity = 0;
- }
- else if (flGoalAccel > 0 && flGoalAccel >= flAccelRate)
- {
- // I need to speed up
- flNewVelocity = flCurVelocity + flGoalAccel * flInterval;
- if (flNewVelocity > flGoalVelocity)
- flGoalVelocity = flGoalVelocity;
- }
- else if (flNewVelocity < flIdealVelocity)
- {
- // speed up to ideal velocity;
- flNewVelocity = flCurVelocity + flAccelRate * flInterval;
- if (flNewVelocity > flIdealVelocity)
- flNewVelocity = flIdealVelocity;
- // don't overshoot
- if (0.5*(flNewVelocity + flCurVelocity) * flInterval > flGoalDistance)
- {
- flNewVelocity = 0.5 * (2 * flGoalDistance / flInterval - flCurVelocity);
- }
- }
- else if (flNewVelocity > flIdealVelocity)
- {
- // slow down to ideal velocity;
- flNewVelocity = flCurVelocity - flAccelRate * flInterval;
- if (flNewVelocity < flIdealVelocity)
- flNewVelocity = flIdealVelocity;
- }
-
- float flDist = 0.5*(flNewVelocity + flCurVelocity) * flInterval;
-
- if (flDist > flGoalDistance)
- {
- flDist = flGoalDistance;
- flNewVelocity = flGoalVelocity;
- }
-
- flNewVelocity = flNewVelocity * scale;
-
- flNewDistance = (flGoalDistance - flDist) * scale;
-
- return 0.0;
-}
-
-//-----------------------------------------------------------------------------
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#include "cbase.h"
+
+#include "movevars_shared.h"
+
+#include "ai_blended_movement.h"
+#include "ai_route.h"
+#include "ai_navigator.h"
+#include "ai_moveprobe.h"
+#include "KeyValues.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+//-----------------------------------------------------------------------------
+//
+// class CAI_BlendedMotor
+//
+
+BEGIN_SIMPLE_DATADESC( CAI_BlendedMotor )
+ // DEFINE_FIELD( m_bDeceleratingToGoal, FIELD_BOOLEAN ),
+
+ // DEFINE_FIELD( m_iPrimaryLayer, FIELD_INTEGER ),
+ // DEFINE_FIELD( m_iSecondaryLayer, FIELD_INTEGER ),
+
+ // DEFINE_FIELD( m_nPrimarySequence, FIELD_INTEGER ),
+ // DEFINE_FIELD( m_nSecondarySequence, FIELD_INTEGER ),
+ // DEFINE_FIELD( m_flSecondaryWeight, FIELD_FLOAT ),
+
+ // DEFINE_CUSTOM_FIELD( m_nSavedGoalActivity, ActivityDataOps() ),
+ // DEFINE_CUSTOM_FIELD( m_nSavedTranslatedGoalActivity, ActivityDataOps() ),
+ // DEFINE_FIELD( m_nGoalSequence, FIELD_INTEGER ),
+
+ // DEFINE_FIELD( m_nPrevMovementSequence, FIELD_INTEGER ),
+ // DEFINE_FIELD( m_nInteriorSequence, FIELD_INTEGER ),
+ // DEFINE_FIELD( m_flCurrRate, FIELD_FLOAT ),
+ // DEFINE_FIELD( m_flStartCycle, FIELD_FLOAT ),
+
+ // m_scriptMove
+ // m_scriptTurn
+
+ // DEFINE_FIELD( m_flNextTurnGesture, FIELD_TIME ),
+ // DEFINE_FIELD( m_prevYaw, FIELD_FLOAT ),
+ // DEFINE_FIELD( m_doTurn, FIELD_FLOAT ),
+ // DEFINE_FIELD( m_doLeft, FIELD_FLOAT ),
+ // DEFINE_FIELD( m_doRight, FIELD_FLOAT ),
+ // DEFINE_FIELD( m_flNextTurnAct, FIELD_TIME ),
+ // DEFINE_FIELD( m_flPredictiveSpeedAdjust, FIELD_FLOAT ),
+ // DEFINE_FIELD( m_flReactiveSpeedAdjust, FIELD_FLOAT ),
+ // DEFINE_FIELD( m_vecPrevOrigin1, FIELD_POSITION ),
+ // DEFINE_FIELD( m_vecPrevOrigin2, FIELD_POSITION ),
+
+END_DATADESC()
+
+//-------------------------------------
+
+void CAI_BlendedMotor::ResetMoveCalculations()
+{
+ BaseClass::ResetMoveCalculations();
+ m_scriptMove.RemoveAll();
+ m_scriptTurn.RemoveAll();
+}
+
+//-------------------------------------
+
+void CAI_BlendedMotor::MoveStart()
+{
+ AI_PROFILE_SCOPE(CAI_BlendedMotor_MoveStart);
+
+ if (m_nPrimarySequence == -1)
+ {
+ m_nPrimarySequence = GetSequence();
+ m_flStartCycle = GetCycle();
+ m_flCurrRate = 0.4;
+
+ // Assert( !GetOuter()->HasMovement( m_nStartSequence ) );
+
+ m_nSecondarySequence = -1;
+
+ m_iPrimaryLayer = AddLayeredSequence( m_nPrimarySequence, 0 );
+ SetLayerWeight( m_iPrimaryLayer, 0.0 );
+ SetLayerPlaybackRate( m_iPrimaryLayer, 0.0 );
+ SetLayerNoRestore( m_iPrimaryLayer, true );
+ SetLayerCycle( m_iPrimaryLayer, m_flStartCycle, m_flStartCycle );
+
+ m_flSecondaryWeight = 0.0;
+ }
+ else
+ {
+ // suspect that MoveStop() wasn't called when the previous route finished
+ // Assert( 0 );
+ }
+
+
+ if (m_nGoalSequence == ACT_INVALID)
+ {
+ ResetGoalSequence();
+ }
+
+ m_vecPrevOrigin2 = GetAbsOrigin();
+ m_vecPrevOrigin1 = GetAbsOrigin();
+
+ m_bDeceleratingToGoal = false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+
+void CAI_BlendedMotor::ResetGoalSequence( void )
+{
+
+ m_nSavedGoalActivity = GetNavigator()->GetArrivalActivity( );
+ if (m_nSavedGoalActivity == ACT_INVALID)
+ {
+ m_nSavedGoalActivity = GetOuter()->GetStoppedActivity();
+ }
+
+ m_nSavedTranslatedGoalActivity = GetOuter()->NPC_TranslateActivity( m_nSavedGoalActivity );
+
+ m_nGoalSequence = GetNavigator()->GetArrivalSequence( m_nPrimarySequence );
+ // Msg("Start %s end %s\n", GetOuter()->GetSequenceName( m_nPrimarySequence ), GetOuter()->GetSequenceName( m_nGoalSequence ) );
+
+ m_nGoalSequence = GetInteriorSequence( m_nPrimarySequence );
+
+ Assert( m_nGoalSequence != ACT_INVALID );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+
+
+void CAI_BlendedMotor::MoveStop()
+{
+ AI_PROFILE_SCOPE(CAI_BlendedMotor_MoveStop);
+
+ CAI_Motor::MoveStop();
+
+ if (m_iPrimaryLayer != -1)
+ {
+ RemoveLayer( m_iPrimaryLayer, 0.2, 0.1 );
+ m_iPrimaryLayer = -1;
+ }
+ if (m_iSecondaryLayer != -1)
+ {
+ RemoveLayer( m_iSecondaryLayer, 0.2, 0.1 );
+ m_iSecondaryLayer = -1;
+ }
+ m_nPrimarySequence = ACT_INVALID;
+ m_nSecondarySequence = ACT_INVALID;
+ m_nPrevMovementSequence = ACT_INVALID;
+ m_nInteriorSequence = ACT_INVALID;
+
+ // int nNextSequence = FindTransitionSequence(GetSequence(), m_nIdealSequence, NULL);
+}
+
+void CAI_BlendedMotor::MovePaused()
+{
+ CAI_Motor::MovePaused();
+ SetMoveScriptAnim( 0.0 );
+}
+
+
+void CAI_BlendedMotor::MoveContinue()
+{
+ AI_PROFILE_SCOPE(CAI_BlendedMotor_MoveContinue);
+
+ m_nPrimarySequence = GetInteriorSequence( ACT_INVALID );
+ m_nGoalSequence = m_nPrimarySequence;
+
+ Assert( m_nPrimarySequence != ACT_INVALID );
+
+ if (m_nPrimarySequence == ACT_INVALID)
+ return;
+
+ m_flStartCycle = 0.0;
+
+ m_iPrimaryLayer = AddLayeredSequence( m_nPrimarySequence, 0 );
+ SetLayerWeight( m_iPrimaryLayer, 0.0 );
+ SetLayerPlaybackRate( m_iPrimaryLayer, 0.0 );
+ SetLayerNoRestore( m_iPrimaryLayer, true );
+ SetLayerCycle( m_iPrimaryLayer, m_flStartCycle, m_flStartCycle );
+
+ m_bDeceleratingToGoal = false;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: for the MoveInterval, interpolate desired speed, calc actual distance traveled
+//-----------------------------------------------------------------------------
+float CAI_BlendedMotor::GetMoveScriptDist( float &flNewSpeed )
+{
+ AI_PROFILE_SCOPE(CAI_BlendedMotor_GetMoveScriptDist);
+
+ int i;
+ float flTotalDist = 0;
+ float t = GetMoveInterval();
+
+ Assert( m_scriptMove.Count() > 1);
+
+ flNewSpeed = 0;
+ for (i = 0; i < m_scriptMove.Count()-1; i++)
+ {
+ if (t < m_scriptMove[i].flTime)
+ {
+ // get new velocity
+ float a = t / m_scriptMove[i].flTime;
+ flNewSpeed = m_scriptMove[i].flMaxVelocity * (1 - a) + m_scriptMove[i+1].flMaxVelocity * a;
+
+ // get distance traveled over this entry
+ flTotalDist += (m_scriptMove[i].flMaxVelocity + flNewSpeed) * 0.5 * t;
+ break;
+ }
+ else
+ {
+ // used all of entries time, get entries total movement
+ flNewSpeed = m_scriptMove[i+1].flMaxVelocity;
+ flTotalDist += m_scriptMove[i].flDist;
+ t -= m_scriptMove[i].flTime;
+ }
+ }
+
+ return flTotalDist;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: return the total time that the move script covers
+//-----------------------------------------------------------------------------
+
+float CAI_BlendedMotor::GetMoveScriptTotalTime()
+{
+ float flDist = GetNavigator()->GetArrivalDistance();
+
+ int i = m_scriptMove.Count() - 1;
+
+ if (i < 0)
+ return -1;
+
+ while (i > 0 && flDist > 1)
+ {
+ flDist -= m_scriptMove[i].flDist;
+ i--;
+ }
+ return m_scriptMove[i].flElapsedTime;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: for the MoveInterval, interpolate desired angle
+//-----------------------------------------------------------------------------
+
+float CAI_BlendedMotor::GetMoveScriptYaw( void )
+{
+ int i;
+
+ // interpolate desired angle
+ float flNewYaw = GetAbsAngles().y;
+ float t = GetMoveInterval();
+ for (i = 0; i < m_scriptTurn.Count()-1; i++)
+ {
+ if (t < m_scriptTurn[i].flTime)
+ {
+ // get new direction
+ float a = t / m_scriptTurn[i].flTime;
+ float deltaYaw = UTIL_AngleDiff( m_scriptTurn[i+1].flYaw, m_scriptTurn[i].flYaw );
+ flNewYaw = UTIL_AngleMod( m_scriptTurn[i].flYaw + a * deltaYaw );
+ break;
+ }
+ else
+ {
+ t -= m_scriptTurn[i].flTime;
+ }
+ }
+
+ return flNewYaw;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: blend in the "idle" or "arrival" animation depending on speed
+//-----------------------------------------------------------------------------
+
+void CAI_BlendedMotor::SetMoveScriptAnim( float flNewSpeed )
+{
+ AI_PROFILE_SCOPE(CAI_BlendedMotor_SetMoveScriptAnim);
+
+ // don't bother if the npc is dead
+ if (!GetOuter()->IsAlive())
+ return;
+
+ // insert ideal layers
+ // FIXME: needs full transitions, as well as starting vs stopping sequences, leaning, etc.
+
+ CAI_Navigator *pNavigator = GetNavigator();
+
+ SetPlaybackRate( m_flCurrRate );
+ // calc weight of idle animation layer that suppresses the run animation
+ float flWeight = 0.0f;
+ if (GetIdealSpeed() > 0.0f)
+ {
+ flWeight = 1.0f - (flNewSpeed / (GetIdealSpeed() * GetPlaybackRate()));
+ }
+ if (flWeight < 0.0f)
+ {
+ m_flCurrRate = flNewSpeed / GetIdealSpeed();
+ m_flCurrRate = clamp( m_flCurrRate, 0.0f, 1.0f );
+ SetPlaybackRate( m_flCurrRate );
+ flWeight = 0.0;
+ }
+ // Msg("weight %.3f rate %.3f\n", flWeight, m_flCurrRate );
+ m_flCurrRate = MIN( m_flCurrRate + (1.0 - m_flCurrRate) * 0.8f, 1.0f );
+
+ if (m_nSavedGoalActivity == ACT_INVALID)
+ {
+ ResetGoalSequence();
+ }
+
+ // detect state change
+ Activity activity = GetOuter()->NPC_TranslateActivity( m_nSavedGoalActivity );
+ if ( activity != m_nSavedTranslatedGoalActivity )
+ {
+ m_nSavedTranslatedGoalActivity = activity;
+ m_nInteriorSequence = ACT_INVALID;
+ m_nGoalSequence = pNavigator->GetArrivalSequence( m_nPrimarySequence );
+ }
+
+ if (m_bDeceleratingToGoal)
+ {
+ // find that sequence to play when at goal
+ m_nGoalSequence = pNavigator->GetArrivalSequence( m_nPrimarySequence );
+
+ if (m_nGoalSequence == ACT_INVALID)
+ {
+ m_nGoalSequence = GetInteriorSequence( m_nPrimarySequence );
+ }
+
+ Assert( m_nGoalSequence != ACT_INVALID );
+ }
+
+ if (m_flSecondaryWeight == 1.0 || (m_iSecondaryLayer != -1 && m_nPrimarySequence == m_nSecondarySequence))
+ {
+ // secondary layer at full strength last time, delete the primary and shift down
+ RemoveLayer( m_iPrimaryLayer, 0.0, 0.0 );
+
+ m_iPrimaryLayer = m_iSecondaryLayer;
+ m_nPrimarySequence = m_nSecondarySequence;
+ m_iSecondaryLayer = -1;
+ m_nSecondarySequence = ACT_INVALID;
+ m_flSecondaryWeight = 0.0;
+ }
+
+ // look for transition sequence if needed
+ if (m_nSecondarySequence == ACT_INVALID)
+ {
+ if (!m_bDeceleratingToGoal && m_nGoalSequence != GetInteriorSequence( m_nPrimarySequence ))
+ {
+ // strob interior sequence in case it changed
+ m_nGoalSequence = GetInteriorSequence( m_nPrimarySequence );
+ }
+
+ if (m_nGoalSequence != ACT_INVALID && m_nPrimarySequence != m_nGoalSequence)
+ {
+ // Msg("From %s to %s\n", GetOuter()->GetSequenceName( m_nPrimarySequence ), GetOuter()->GetSequenceName( m_nGoalSequence ) );
+ m_nSecondarySequence = GetOuter()->FindTransitionSequence(m_nPrimarySequence, m_nGoalSequence, NULL);
+ if (m_nSecondarySequence == ACT_INVALID)
+ m_nSecondarySequence = m_nGoalSequence;
+ }
+ }
+
+ // set blending for
+ if (m_nSecondarySequence != ACT_INVALID)
+ {
+ if (m_iSecondaryLayer == -1)
+ {
+ m_iSecondaryLayer = AddLayeredSequence( m_nSecondarySequence, 0 );
+ SetLayerWeight( m_iSecondaryLayer, 0.0 );
+ if (m_nSecondarySequence == m_nGoalSequence)
+ {
+ SetLayerPlaybackRate( m_iSecondaryLayer, 0.0 );
+ }
+ else
+ {
+ SetLayerPlaybackRate( m_iSecondaryLayer, 1.0 );
+ }
+ SetLayerNoRestore( m_iSecondaryLayer, true );
+ m_flSecondaryWeight = 0.0;
+ }
+
+ m_flSecondaryWeight = MIN( m_flSecondaryWeight + 0.3, 1.0 );
+
+ if (m_flSecondaryWeight < 1.0)
+ {
+ SetLayerWeight( m_iPrimaryLayer, (flWeight - m_flSecondaryWeight * flWeight) / (1.0f - m_flSecondaryWeight * flWeight) );
+ SetLayerWeight( m_iSecondaryLayer, flWeight * m_flSecondaryWeight );
+ }
+ else
+ {
+ SetLayerWeight( m_iPrimaryLayer, 0.0f );
+ SetLayerWeight( m_iSecondaryLayer, flWeight );
+ }
+ }
+ else
+ {
+ // recreate layer if missing
+ if (m_iPrimaryLayer == -1)
+ {
+ MoveContinue();
+ }
+
+ // try to catch a stale layer
+ if (m_iSecondaryLayer != -1)
+ {
+ // secondary layer at full strength last time, delete the primary and shift down
+ RemoveLayer( m_iSecondaryLayer, 0.0, 0.0 );
+ m_iSecondaryLayer = -1;
+ m_nSecondarySequence = ACT_INVALID;
+ m_flSecondaryWeight = 0.0;
+ }
+
+ // debounce
+ // flWeight = flWeight * 0.5 + 0.5 * GetOuter()->GetLayerWeight( m_iPrimaryLayer );
+ SetLayerWeight( m_iPrimaryLayer, flWeight );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: get the "idle" animation to play as the compliment to the movement animation
+//-----------------------------------------------------------------------------
+int CAI_BlendedMotor::GetInteriorSequence( int fromSequence )
+{
+ AI_PROFILE_SCOPE(CAI_BlendedMotor_GetInteriorSequence);
+
+ // FIXME: add interior activity to path, just like arrival activity.
+ int sequence = GetNavigator()->GetMovementSequence();
+
+ if (m_nInteriorSequence != ACT_INVALID && sequence == m_nPrevMovementSequence)
+ {
+ return m_nInteriorSequence;
+ }
+
+ m_nPrevMovementSequence = sequence;
+
+ KeyValues *seqKeyValues = GetOuter()->GetSequenceKeyValues( sequence );
+ // Msg("sequence %d : %s (%d)\n", sequence, GetOuter()->GetSequenceName( sequence ), seqKeyValues != NULL );
+ if (seqKeyValues)
+ {
+ KeyValues *pkvInterior = seqKeyValues->FindKey("interior");
+ if (pkvInterior)
+ {
+ const char *szActivity = pkvInterior->GetString();
+
+ Activity activity = ( Activity )GetOuter()->LookupActivity( szActivity );
+ if ( activity != ACT_INVALID )
+ {
+ m_nInteriorSequence = GetOuter()->SelectWeightedSequence( GetOuter()->TranslateActivity( activity ), fromSequence );
+ }
+ else
+ {
+ activity = (Activity)GetOuter()->GetActivityID( szActivity );
+ if ( activity != ACT_INVALID )
+ {
+ m_nInteriorSequence = GetOuter()->SelectWeightedSequence( GetOuter()->TranslateActivity( activity ), fromSequence );
+ }
+ }
+
+ if (activity == ACT_INVALID || m_nInteriorSequence == ACT_INVALID)
+ {
+ m_nInteriorSequence = GetOuter()->LookupSequence( szActivity );
+ }
+ }
+ }
+
+ if (m_nInteriorSequence == ACT_INVALID)
+ {
+ Activity activity = GetNavigator()->GetMovementActivity();
+ if (activity == ACT_WALK_AIM || activity == ACT_RUN_AIM)
+ {
+ activity = ACT_IDLE_ANGRY;
+ }
+ else
+ {
+ activity = ACT_IDLE;
+ }
+ m_nInteriorSequence = GetOuter()->SelectWeightedSequence( GetOuter()->TranslateActivity( activity ), fromSequence );
+
+ Assert( m_nInteriorSequence != ACT_INVALID );
+ }
+
+ return m_nInteriorSequence;
+}
+
+
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Move the npc to the next location on its route.
+//-----------------------------------------------------------------------------
+
+AIMotorMoveResult_t CAI_BlendedMotor::MoveGroundExecute( const AILocalMoveGoal_t &move, AIMoveTrace_t *pTraceResult )
+{
+ AI_PROFILE_SCOPE(CAI_BlendedMotor_MoveGroundExecute);
+
+ if ( move.curExpectedDist < 0.001 )
+ {
+ AIMotorMoveResult_t result = BaseClass::MoveGroundExecute( move, pTraceResult );
+ // Msg(" BaseClass::MoveGroundExecute() - remaining %.2f\n", GetMoveInterval() );
+ SetMoveScriptAnim( 0.0 );
+ return result;
+ }
+
+ BuildMoveScript( move, pTraceResult );
+
+ float flNewSpeed = GetCurSpeed();
+ float flTotalDist = GetMoveScriptDist( flNewSpeed );
+
+ Assert( move.maxDist < 0.01 || flTotalDist > 0.0 );
+
+ // --------------------------------------------
+ // turn in the direction of movement
+ // --------------------------------------------
+
+ float flNewYaw = GetMoveScriptYaw( );
+
+ // get facing based on movement yaw
+ AILocalMoveGoal_t move2 = move;
+ move2.facing = UTIL_YawToVector( flNewYaw );
+
+ // turn in the direction needed
+ MoveFacing( move2 );
+
+ // reset actual "sequence" ground speed based current movement sequence, orientation
+
+ // FIXME: this should be based on
+
+ GetOuter()->m_flGroundSpeed = GetSequenceGroundSpeed( GetSequence());
+
+
+
+ /*
+ if (1 || flNewSpeed > GetIdealSpeed())
+ {
+ // DevMsg( "%6.2f : Speed %.1f : %.1f (%.1f) : %d\n", gpGlobals->curtime, flNewSpeed, move.maxDist, move.transitionDist, GetOuter()->m_pHintNode != NULL );
+ // DevMsg( "%6.2f : Speed %.1f : %.1f\n", gpGlobals->curtime, flNewSpeed, GetIdealSpeed() );
+ }
+ */
+
+ SetMoveScriptAnim( flNewSpeed );
+
+ /*
+ if ((GetOuter()->m_debugOverlays & OVERLAY_NPC_SELECTED_BIT))
+ {
+ DevMsg( "%6.2f : Speed %.1f : %.1f : %.2f\n", gpGlobals->curtime, flNewSpeed, GetIdealSpeed(), flNewSpeed / GetIdealSpeed() );
+ }
+ */
+
+ AIMotorMoveResult_t result = MoveGroundExecuteWalk( move, flNewSpeed, flTotalDist, pTraceResult );
+
+ return result;
+
+}
+
+
+
+
+AIMotorMoveResult_t CAI_BlendedMotor::MoveFlyExecute( const AILocalMoveGoal_t &move, AIMoveTrace_t *pTraceResult )
+{
+ AI_PROFILE_SCOPE(CAI_BlendedMotor_MoveFlyExecute);
+
+ if ( move.curExpectedDist < 0.001 )
+ return BaseClass::MoveFlyExecute( move, pTraceResult );
+
+ BuildMoveScript( move, pTraceResult );
+
+ float flNewSpeed = GetCurSpeed();
+ float flTotalDist = GetMoveScriptDist( flNewSpeed );
+
+ Assert( move.maxDist < 0.01 || flTotalDist > 0.0 );
+
+ // --------------------------------------------
+ // turn in the direction of movement
+ // --------------------------------------------
+
+ float flNewYaw = GetMoveScriptYaw( );
+
+ // get facing based on movement yaw
+ AILocalMoveGoal_t move2 = move;
+ move2.facing = UTIL_YawToVector( flNewYaw );
+
+ // turn in the direction needed
+ MoveFacing( move2 );
+
+ GetOuter()->m_flGroundSpeed = GetSequenceGroundSpeed( GetSequence());
+
+ SetMoveScriptAnim( flNewSpeed );
+
+ // DevMsg( "%6.2f : Speed %.1f : %.1f\n", gpGlobals->curtime, flNewSpeed, GetIdealSpeed() );
+
+ // reset actual "sequence" ground speed based current movement sequence, orientation
+
+ // FIXME: the above is redundant with MoveGroundExecute, and the below is a mix of MoveGroundExecuteWalk and MoveFlyExecute
+
+ bool bReachingLocalGoal = ( flTotalDist > 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 / flTotalDist) );
+ }
+ else
+ SetMoveInterval( 0 );
+ flTotalDist = move.maxDist;
+ }
+ else
+ {
+ // use all the time
+ SetMoveInterval( 0 );
+ }
+
+ SetMoveVel( move.dir * flNewSpeed );
+
+ // orig
+ Vector vecStart, vecEnd;
+ vecStart = GetLocalOrigin();
+ VectorMA( vecStart, flTotalDist, 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 - flTotalDist) <= 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;
+}
+
+
+
+
+float CAI_BlendedMotor::OverrideMaxYawSpeed( Activity activity )
+{
+ // Don't do this is we're locked
+ if ( IsYawLocked() )
+ return 0.0f;
+
+ switch( activity )
+ {
+ case ACT_TURN_LEFT:
+ case ACT_TURN_RIGHT:
+ return 45;
+ break;
+ default:
+ if (GetOuter()->IsMoving())
+ {
+ return 15;
+ }
+ return 45; // too fast?
+ break;
+ }
+ return -1;
+}
+
+
+
+void CAI_BlendedMotor::UpdateYaw( int speed )
+{
+ // Don't do this is we're locked
+ if ( IsYawLocked() )
+ return;
+
+ GetOuter()->UpdateTurnGesture( );
+ BaseClass::UpdateYaw( speed );
+}
+
+
+
+void CAI_BlendedMotor::RecalculateYawSpeed()
+{
+ // Don't do this is we're locked
+ if ( IsYawLocked() )
+ {
+ SetYawSpeed( 0.0f );
+ return;
+ }
+
+ if (GetOuter()->HasMemory( bits_MEMORY_TURNING ))
+ return;
+
+ SetYawSpeed( CalcYawSpeed() );
+}
+
+
+//-------------------------------------
+
+
+void CAI_BlendedMotor::MoveClimbStart( const Vector &climbDest, const Vector &climbDir, float climbDist, float yaw )
+{
+ // TODO: merge transitions with movement script
+ if (m_iPrimaryLayer != -1)
+ {
+ SetLayerWeight( m_iPrimaryLayer, 0 );
+ }
+ if (m_iSecondaryLayer != -1)
+ {
+ SetLayerWeight( m_iSecondaryLayer, 0 );
+ }
+
+ BaseClass::MoveClimbStart( climbDest, climbDir, climbDist, yaw );
+}
+
+
+//-------------------------------------
+
+
+void CAI_BlendedMotor::MoveJumpStart( const Vector &velocity )
+{
+ // TODO: merge transitions with movement script
+ if (m_iPrimaryLayer != -1)
+ {
+ SetLayerWeight( m_iPrimaryLayer, 0 );
+ }
+ if (m_iSecondaryLayer != -1)
+ {
+ SetLayerWeight( m_iSecondaryLayer, 0 );
+ }
+
+ BaseClass::MoveJumpStart( velocity );
+}
+
+
+//-------------------------------------
+
+void CAI_BlendedMotor::BuildMoveScript( const AILocalMoveGoal_t &move, AIMoveTrace_t *pTraceResult )
+{
+ m_scriptMove.RemoveAll();
+ m_scriptTurn.RemoveAll();
+
+ BuildVelocityScript( move );
+ BuildTurnScript( move );
+
+/*
+ if (GetOuter()->m_debugOverlays & OVERLAY_NPC_SELECTED_BIT)
+ {
+ int i;
+#if 1
+
+ for (i = 1; i < m_scriptMove.Count(); i++)
+ {
+ NDebugOverlay::Line( m_scriptMove[i-1].vecLocation, m_scriptMove[i].vecLocation, 255,255,255, true, 0.1 );
+
+ NDebugOverlay::Box( m_scriptMove[i].vecLocation, Vector( -2, -2, -2 ), Vector( 2, 2, 2 ), 0,255,255, 0, 0.1 );
+
+ //NDebugOverlay::Line( m_scriptMove[i].vecLocation, m_scriptMove[i].vecLocation + Vector( 0,0,m_scriptMove[i].flMaxVelocity), 0,255,255, true, 0.1 );
+
+ Vector vecMidway = m_scriptMove[i].vecLocation + ((m_scriptMove[i-1].vecLocation - m_scriptMove[i].vecLocation) * 0.5);
+ NDebugOverlay::Text( vecMidway, UTIL_VarArgs( "%d", i ), false, 0.1 );
+ }
+#endif
+#if 0
+ for (i = 1; i < m_scriptTurn.Count(); i++)
+ {
+ NDebugOverlay::Line( m_scriptTurn[i-1].vecLocation, m_scriptTurn[i].vecLocation, 255,255,255, true, 0.1 );
+
+ NDebugOverlay::Box( m_scriptTurn[i].vecLocation, Vector( -2, -2, -2 ), Vector( 2, 2, 2 ), 255,0,0, 0, 0.1 );
+
+ NDebugOverlay::Line( m_scriptTurn[i].vecLocation + Vector( 0,0,1), m_scriptTurn[i].vecLocation + Vector( 0,0,1) + UTIL_YawToVector( m_scriptTurn[i].flYaw ) * 32, 255,0,0, true, 0.1 );
+ }
+#endif
+ }
+*/
+}
+
+
+#define YAWSPEED 150
+
+
+void CAI_BlendedMotor::BuildTurnScript( const AILocalMoveGoal_t &move )
+{
+ AI_PROFILE_SCOPE(CAI_BlendedMotor_BuildTurnScript);
+
+ int i;
+
+ AI_Movementscript_t script;
+ script.Init();
+
+ // current location
+ script.vecLocation = GetAbsOrigin();
+ script.flYaw = GetAbsAngles().y;
+ m_scriptTurn.AddToTail( script );
+
+ //-------------------------
+
+ // insert default turn parameters, try to turn 80% to goal at all corners before getting there
+ int prev = 0;
+ for (i = 0; i < m_scriptMove.Count(); i++)
+ {
+ AI_Waypoint_t *pCurWaypoint = m_scriptMove[i].pWaypoint;
+ if (pCurWaypoint)
+ {
+ script.Init();
+ script.vecLocation = pCurWaypoint->vecLocation;
+ script.pWaypoint = pCurWaypoint;
+ script.flElapsedTime = m_scriptMove[i].flElapsedTime;
+
+ m_scriptTurn[prev].flTime = script.flElapsedTime - m_scriptTurn[prev].flElapsedTime;
+
+ if (pCurWaypoint->GetNext())
+ {
+ Vector d1 = pCurWaypoint->GetNext()->vecLocation - script.vecLocation;
+ Vector d2 = script.vecLocation - m_scriptTurn[prev].vecLocation;
+
+ d1.z = 0;
+ VectorNormalize( d1 );
+ d2.z = 0;
+ VectorNormalize( d2 );
+
+ float y1 = UTIL_VecToYaw( d1 );
+ float y2 = UTIL_VecToYaw( d2 );
+
+ float deltaYaw = fabs( UTIL_AngleDiff( y1, y2 ) );
+
+ if (deltaYaw > 0.1)
+ {
+ // turn to 80% of goal
+ script.flYaw = UTIL_ApproachAngle( y1, y2, deltaYaw * 0.8 );
+ m_scriptTurn.AddToTail( script );
+ // DevMsg("turn waypoint %.1f %.1f %.1f\n", script.vecLocation.x, script.vecLocation.y, script.vecLocation.z );
+ prev++;
+ }
+ }
+ else
+ {
+ Vector vecDir = GetNavigator()->GetArrivalDirection();
+ script.flYaw = UTIL_VecToYaw( vecDir );
+ m_scriptTurn.AddToTail( script );
+ // DevMsg("turn waypoint %.1f %.1f %.1f\n", script.vecLocation.x, script.vecLocation.y, script.vecLocation.z );
+ prev++;
+ }
+ }
+ }
+
+ // propagate ending facing back over any nearby nodes
+ // FIXME: this needs to minimize total turning, not just local/end turning.
+ // depending on waypoint spacing, complexity, it may turn the wrong way!
+ for (i = m_scriptTurn.Count()-1; i > 1; i--)
+ {
+ float deltaYaw = UTIL_AngleDiff( m_scriptTurn[i-1].flYaw, m_scriptTurn[i].flYaw );
+
+ float maxYaw = YAWSPEED * m_scriptTurn[i-1].flTime;
+
+ if (fabs(deltaYaw) > maxYaw)
+ {
+ m_scriptTurn[i-1].flYaw = UTIL_ApproachAngle( m_scriptTurn[i-1].flYaw, m_scriptTurn[i].flYaw, maxYaw );
+ }
+ }
+
+ for (i = 0; i < m_scriptTurn.Count() - 1; )
+ {
+ i = i + BuildTurnScript( i, i + 1 ) + 1;
+ }
+ //-------------------------
+}
+
+
+
+int CAI_BlendedMotor::BuildTurnScript( int i, int j )
+{
+ AI_PROFILE_SCOPE(CAI_BlendedMotor_BuildTurnScript2);
+
+ int k;
+
+ Vector vecDir = m_scriptTurn[j].vecLocation - m_scriptTurn[i].vecLocation;
+ float interiorYaw = UTIL_VecToYaw( vecDir );
+
+ float deltaYaw;
+
+ deltaYaw = fabs( UTIL_AngleDiff( interiorYaw, m_scriptTurn[i].flYaw ) );
+ float t1 = deltaYaw / YAWSPEED;
+
+ deltaYaw = fabs( UTIL_AngleDiff( m_scriptTurn[j].flYaw, interiorYaw ) );
+ float t2 = deltaYaw / YAWSPEED;
+
+ float totalTime = m_scriptTurn[j].flElapsedTime - m_scriptTurn[i].flElapsedTime;
+
+ Assert( totalTime > 0 );
+
+ if (t1 < 0.01)
+ {
+ if (t2 > totalTime * 0.8)
+ {
+ // too close, nothing to do
+ return 0;
+ }
+
+ // go ahead and force yaw
+ m_scriptTurn[i].flYaw = interiorYaw;
+
+ // we're already aiming close enough to the interior yaw, set the point where we need to blend out
+ k = BuildInsertNode( i, totalTime - t2 );
+ m_scriptTurn[k].flYaw = interiorYaw;
+
+ return 1;
+ }
+ else if (t2 < 0.01)
+ {
+ if (t1 > totalTime * 0.8)
+ {
+ // too close, nothing to do
+ return 0;
+ }
+
+ // we'll finish up aiming close enough to the interior yaw, set the point where we need to blend in
+ k = BuildInsertNode( i, t1 );
+ m_scriptTurn[k].flYaw = interiorYaw;
+
+ return 1;
+ }
+ else if (t1 + t2 > totalTime)
+ {
+ // don't bother with interior node
+ return 0;
+
+ // waypoints need to much turning, ignore interior yaw
+ float a = (t1 / (t1 + t2));
+ t1 = a * totalTime;
+
+ k = BuildInsertNode( i, t1 );
+
+ deltaYaw = UTIL_AngleDiff( m_scriptTurn[j].flYaw, m_scriptTurn[i].flYaw );
+ m_scriptTurn[k].flYaw = UTIL_ApproachAngle( m_scriptTurn[j].flYaw, m_scriptTurn[i].flYaw, deltaYaw * (1 - a) );
+
+ return 1;
+ }
+ else if (t1 + t2 < totalTime * 0.8)
+ {
+ // turn to face interior, run a ways, then turn away
+ k = BuildInsertNode( i, t1 );
+ m_scriptTurn[k].flYaw = interiorYaw;
+
+ k = BuildInsertNode( i, t2 );
+ m_scriptTurn[k].flYaw = interiorYaw;
+
+ return 2;
+ }
+ return 0;
+}
+
+
+int CAI_BlendedMotor::BuildInsertNode( int i, float flTime )
+{
+ AI_Movementscript_t script;
+ script.Init();
+
+ Assert( flTime > 0.0 );
+
+ for (i; i < m_scriptTurn.Count() - 1; i++)
+ {
+ if (m_scriptTurn[i].flTime < flTime)
+ {
+ flTime -= m_scriptTurn[i].flTime;
+ }
+ else
+ {
+ float a = flTime / m_scriptTurn[i].flTime;
+
+ script.flTime = (m_scriptTurn[i].flTime - flTime);
+
+ m_scriptTurn[i].flTime = flTime;
+
+ script.flElapsedTime = m_scriptTurn[i].flElapsedTime * (1 - a) + m_scriptTurn[i+1].flElapsedTime * a;
+
+ script.vecLocation = m_scriptTurn[i].vecLocation * (1 - a) + m_scriptTurn[i+1].vecLocation * a;
+
+ m_scriptTurn.InsertAfter( i, script );
+
+ return i + 1;
+ }
+ }
+ Assert( 0 );
+ return 0;
+}
+
+
+ConVar ai_path_insert_pause_at_obstruction( "ai_path_insert_pause_at_obstruction", "1" );
+ConVar ai_path_adjust_speed_on_immediate_turns( "ai_path_adjust_speed_on_immediate_turns", "1" );
+ConVar ai_path_insert_pause_at_est_end( "ai_path_insert_pause_at_est_end", "1" );
+
+#define MIN_VELOCITY 0.0f
+#define MIN_STEER_DOT 0.0f
+
+void CAI_BlendedMotor::BuildVelocityScript( const AILocalMoveGoal_t &move )
+{
+ AI_PROFILE_SCOPE(CAI_BlendedMotor_BuildVelocityScript);
+
+ int i;
+ float a;
+
+ float idealVelocity = GetIdealSpeed();
+ if (idealVelocity == 0)
+ {
+ idealVelocity = 50;
+ }
+
+ float idealAccel = GetIdealAccel();
+ if (idealAccel == 0)
+ {
+ idealAccel = 100;
+ }
+
+ AI_Movementscript_t script;
+
+ // set current location as start of script
+ script.vecLocation = GetAbsOrigin();
+ script.flMaxVelocity = GetCurSpeed();
+ m_scriptMove.AddToTail( script );
+
+ //-------------------------
+
+ extern ConVar npc_height_adjust;
+ if (npc_height_adjust.GetBool() && move.bHasTraced && move.directTrace.flTotalDist != move.thinkTrace.flTotalDist)
+ {
+ float flDist = (move.directTrace.vEndPosition - m_scriptMove[0].vecLocation).Length2D();
+ float flHeight = move.directTrace.vEndPosition.z - m_scriptMove[0].vecLocation.z;
+ float flDelta;
+
+ if (flDist > 0)
+ {
+ flDelta = flHeight / flDist;
+ }
+ else
+ {
+ flDelta = 0;
+ }
+
+ m_flPredictiveSpeedAdjust = 1.1 - fabs( flDelta );
+ m_flPredictiveSpeedAdjust = clamp( m_flPredictiveSpeedAdjust, (flHeight > 0.0f) ? 0.5f : 0.8f, 1.0f );
+
+ /*
+ if ((GetOuter()->m_debugOverlays & OVERLAY_NPC_SELECTED_BIT))
+ {
+ Msg("m_flPredictiveSpeedAdjust %.3f %.1f %.1f\n", m_flPredictiveSpeedAdjust, flHeight, flDist );
+ NDebugOverlay::Box( move.directTrace.vEndPosition, Vector( -2, -2, -2 ), Vector( 2, 2, 2 ), 0,255,255, 0, 0.12 );
+ }
+ */
+ }
+ if (npc_height_adjust.GetBool())
+ {
+ float flDist = (move.thinkTrace.vEndPosition - m_vecPrevOrigin2).Length2D();
+ float flHeight = move.thinkTrace.vEndPosition.z - m_vecPrevOrigin2.z;
+ float flDelta;
+
+ if (flDist > 0)
+ {
+ flDelta = flHeight / flDist;
+ }
+ else
+ {
+ flDelta = 0;
+ }
+
+ float newSpeedAdjust = 1.1 - fabs( flDelta );
+ newSpeedAdjust = clamp( newSpeedAdjust, (flHeight > 0.0f) ? 0.5f : 0.8f, 1.0f );
+
+ // debounce speed adjust
+ if (newSpeedAdjust < m_flReactiveSpeedAdjust)
+ {
+ m_flReactiveSpeedAdjust = m_flReactiveSpeedAdjust * 0.2f + newSpeedAdjust * 0.8f;
+ }
+ else
+ {
+ m_flReactiveSpeedAdjust = m_flReactiveSpeedAdjust * 0.5f + newSpeedAdjust * 0.5f;
+ }
+
+ // filter through origins
+ m_vecPrevOrigin2 = m_vecPrevOrigin1;
+ m_vecPrevOrigin1 = GetAbsOrigin();
+
+ /*
+ if ((GetOuter()->m_debugOverlays & OVERLAY_NPC_SELECTED_BIT))
+ {
+ NDebugOverlay::Box( m_vecPrevOrigin2, Vector( -2, -2, -2 ), Vector( 2, 2, 2 ), 255,0,255, 0, 0.12 );
+ NDebugOverlay::Box( move.thinkTrace.vEndPosition, Vector( -2, -2, -2 ), Vector( 2, 2, 2 ), 255,0,255, 0, 0.12 );
+ Msg("m_flReactiveSpeedAdjust %.3f %.1f %.1f\n", m_flReactiveSpeedAdjust, flHeight, flDist );
+ }
+ */
+ }
+
+ idealVelocity = idealVelocity * MIN( m_flReactiveSpeedAdjust, m_flPredictiveSpeedAdjust );
+
+ //-------------------------
+
+ bool bAddedExpected = false;
+
+ // add all waypoint locations and velocities
+ AI_Waypoint_t *pCurWaypoint = GetNavigator()->GetPath()->GetCurWaypoint();
+
+ // there has to be at least one waypoint
+ Assert( pCurWaypoint );
+
+ while (pCurWaypoint && (pCurWaypoint->NavType() == NAV_GROUND || pCurWaypoint->NavType() == NAV_FLY) /*&& flTotalDist / idealVelocity < 3.0*/) // limit lookahead to 3 seconds
+ {
+ script.Init();
+ AI_Waypoint_t *pNext = pCurWaypoint->GetNext();
+
+ if (ai_path_adjust_speed_on_immediate_turns.GetBool() && !bAddedExpected)
+ {
+ // hack in next expected immediate location for move
+ script.vecLocation = GetAbsOrigin() + move.dir * move.curExpectedDist;
+ bAddedExpected = true;
+ pNext = pCurWaypoint;
+ }
+ else
+ {
+ script.vecLocation = pCurWaypoint->vecLocation;
+ script.pWaypoint = pCurWaypoint;
+ }
+
+ //DevMsg("waypoint %.1f %.1f %.1f\n", script.vecLocation.x, script.vecLocation.y, script.vecLocation.z );
+
+ if (pNext)
+ {
+ switch( pNext->NavType())
+ {
+ case NAV_GROUND:
+ case NAV_FLY:
+ {
+ Vector d1 = pNext->vecLocation - script.vecLocation;
+ Vector d2 = script.vecLocation - m_scriptMove[m_scriptMove.Count()-1].vecLocation;
+
+ // remove very short, non terminal ground links
+ // FIXME: is this safe? Maybe just check for co-located ground points?
+ if (d1.Length2D() < 1.0)
+ {
+ /*
+ if (m_scriptMove.Count() > 1)
+ {
+ int i = m_scriptMove.Count() - 1;
+ m_scriptMove[i].vecLocation = pCurWaypoint->vecLocation;
+ m_scriptMove[i].pWaypoint = pCurWaypoint;
+ }
+ */
+ pCurWaypoint = pNext;
+ continue;
+ }
+
+ d1.z = 0;
+ VectorNormalize( d1 );
+ d2.z = 0;
+ VectorNormalize( d2 );
+
+ // figure velocity
+ float dot = (DotProduct( d1, d2 ) + 0.2);
+ if (dot > 0)
+ {
+ dot = clamp( dot, 0.0f, 1.0f );
+ script.flMaxVelocity = idealVelocity * dot;
+ }
+ else
+ {
+ script.flMaxVelocity = 0;
+ }
+ }
+ break;
+ case NAV_JUMP:
+
+ // FIXME: information about what the jump should look like isn't stored in the waypoints
+ // this'll need to call
+ // GetMoveProbe()->MoveLimit( NAV_JUMP, GetLocalOrigin(), GetPath()->CurWaypointPos(), MASK_NPCSOLID, GetNavTargetEntity(), &moveTrace );
+ // to get how far/fast the jump will be, but this is also stateless, so it'd call it per frame.
+ // So far it's not clear that the moveprobe doesn't also call this.....
+
+ {
+ float minJumpHeight = 0;
+ float maxHorzVel = MAX( GetCurSpeed(), 100 );
+ float gravity = GetCurrentGravity() * GetOuter()->GetGravity();
+ Vector vecApex;
+ Vector rawJumpVel = GetMoveProbe()->CalcJumpLaunchVelocity(script.vecLocation, pNext->vecLocation, gravity, &minJumpHeight, maxHorzVel, &vecApex );
+
+ script.flMaxVelocity = rawJumpVel.Length2D();
+ // Msg("%.1f\n", script.flMaxVelocity );
+ }
+ break;
+ case NAV_CLIMB:
+ {
+ /*
+ CAI_Node *pClimbNode = GetNavigator()->GetNetwork()->GetNode(pNext->iNodeID);
+
+ check: pClimbNode->m_eNodeInfo
+ bits_NODE_CLIMB_BOTTOM,
+ bits_NODE_CLIMB_ON,
+ bits_NODE_CLIMB_OFF_FORWARD,
+ bits_NODE_CLIMB_OFF_LEFT,
+ bits_NODE_CLIMB_OFF_RIGHT
+ */
+
+ script.flMaxVelocity = 0;
+ }
+ break;
+ /*
+ case NAV_FLY:
+ // FIXME: can there be a NAV_GROUND -> NAV_FLY transition?
+ script.flMaxVelocity = 0;
+ break;
+ */
+ }
+ }
+ else
+ {
+ script.flMaxVelocity = GetNavigator()->GetArrivalSpeed();
+ // Assert( script.flMaxVelocity == 0 );
+ }
+
+ m_scriptMove.AddToTail( script );
+ pCurWaypoint = pNext;
+ }
+
+
+ //-------------------------
+
+ // update distances
+ float flTotalDist = 0;
+ for (i = 0; i < m_scriptMove.Count() - 1; i++ )
+ {
+ flTotalDist += m_scriptMove[i].flDist = (m_scriptMove[i+1].vecLocation - m_scriptMove[i].vecLocation).Length2D();
+ }
+
+ //-------------------------
+
+ if ( !m_bDeceleratingToGoal && m_scriptMove.Count() && flTotalDist > 0 )
+ {
+ float flNeededAccel = DeltaV( m_scriptMove[0].flMaxVelocity, m_scriptMove[m_scriptMove.Count() - 1].flMaxVelocity, flTotalDist );
+ m_bDeceleratingToGoal = (flNeededAccel < -idealAccel);
+ //Assert( flNeededAccel != idealAccel);
+ }
+
+ //-------------------------
+
+ // insert slowdown points due to blocking
+ if (ai_path_insert_pause_at_obstruction.GetBool() && move.directTrace.pObstruction)
+ {
+ float distToObstruction = (move.directTrace.vEndPosition - m_scriptMove[0].vecLocation).Length2D();
+
+ // HACK move obstruction out "stepsize" to account for it being based on stand position and not a trace
+ distToObstruction = distToObstruction + 16;
+
+ InsertSlowdown( distToObstruction, idealAccel, false );
+ }
+
+ if (ai_path_insert_pause_at_est_end.GetBool() && GetNavigator()->GetArrivalDistance() > 0.0)
+ {
+ InsertSlowdown( flTotalDist - GetNavigator()->GetArrivalDistance(), idealAccel, true );
+ }
+
+ // calc initial velocity based on immediate direction changes
+ if ( ai_path_adjust_speed_on_immediate_turns.GetBool() && m_scriptMove.Count() > 1)
+ {
+ /*
+ if ((GetOuter()->m_debugOverlays & OVERLAY_NPC_SELECTED_BIT))
+ {
+ Vector tmp = m_scriptMove[1].vecLocation - m_scriptMove[0].vecLocation;
+ VectorNormalize( tmp );
+ NDebugOverlay::Line( m_scriptMove[0].vecLocation + Vector( 0, 0, 10 ), m_scriptMove[0].vecLocation + tmp * 32 + Vector( 0, 0, 10 ), 255,255,255, true, 0.1 );
+
+ NDebugOverlay::Line( m_scriptMove[0].vecLocation + Vector( 0, 0, 10 ), m_scriptMove[1].vecLocation + Vector( 0, 0, 10 ), 255,0,0, true, 0.1 );
+
+ tmp = GetCurVel();
+ VectorNormalize( tmp );
+ NDebugOverlay::Line( m_scriptMove[0].vecLocation + Vector( 0, 0, 10 ), m_scriptMove[0].vecLocation + tmp * 32 + Vector( 0, 0, 10 ), 0,0,255, true, 0.1 );
+ }
+ */
+
+ Vector d1 = m_scriptMove[1].vecLocation - m_scriptMove[0].vecLocation;
+ d1.z = 0;
+ VectorNormalize( d1 );
+
+ Vector d2 = GetCurVel();
+ d2.z = 0;
+ VectorNormalize( d2 );
+
+ float dot = (DotProduct( d1, d2 ) + MIN_STEER_DOT);
+ dot = clamp( dot, 0.0f, 1.0f );
+ m_scriptMove[0].flMaxVelocity = m_scriptMove[0].flMaxVelocity * dot;
+ }
+
+ // clamp forward velocities
+ for (i = 0; i < m_scriptMove.Count() - 1; i++ )
+ {
+ // find needed acceleration
+ float dv = m_scriptMove[i+1].flMaxVelocity - m_scriptMove[i].flMaxVelocity;
+
+ if (dv > 0.0)
+ {
+ // find time, distance to accel to next max vel
+ float t1 = dv / idealAccel;
+ float d1 = m_scriptMove[i].flMaxVelocity * t1 + 0.5 * (idealAccel) * t1 * t1;
+
+ // is there enough distance
+ if (d1 > m_scriptMove[i].flDist)
+ {
+ float r1, r2;
+
+ // clamp the next velocity to the possible accel in the given distance
+ if (SolveQuadratic( 0.5 * idealAccel, m_scriptMove[i].flMaxVelocity, -m_scriptMove[i].flDist, r1, r2 ))
+ {
+ m_scriptMove[i+1].flMaxVelocity = m_scriptMove[i].flMaxVelocity + idealAccel * r1;
+ }
+ }
+ }
+ }
+
+ // clamp decel velocities
+ for (i = m_scriptMove.Count() - 1; i > 0; i-- )
+ {
+ // find needed deceleration
+ float dv = m_scriptMove[i].flMaxVelocity - m_scriptMove[i-1].flMaxVelocity;
+
+ if (dv < 0.0)
+ {
+ // find time, distance to decal to next max vel
+ float t1 = -dv / idealAccel;
+ float d1 = m_scriptMove[i].flMaxVelocity * t1 + 0.5 * (idealAccel) * t1 * t1;
+
+ // is there enough distance
+ if (d1 > m_scriptMove[i-1].flDist)
+ {
+ float r1, r2;
+
+ // clamp the next velocity to the possible decal in the given distance
+ if (SolveQuadratic( 0.5 * idealAccel, m_scriptMove[i].flMaxVelocity, -m_scriptMove[i-1].flDist, r1, r2 ))
+ {
+ m_scriptMove[i-1].flMaxVelocity = m_scriptMove[i].flMaxVelocity + idealAccel * r1;
+ }
+ }
+ }
+ }
+
+ /*
+ for (i = 0; i < m_scriptMove.Count(); i++)
+ {
+ NDebugOverlay::Text( m_scriptMove[i].vecLocation, (const char *)CFmtStr( "%.2f ", m_scriptMove[i].flMaxVelocity ), false, 0.1 );
+ // DevMsg("%.2f ", m_scriptMove[i].flMaxVelocity );
+ }
+ // DevMsg("\n");
+ */
+
+ // insert intermediate ideal velocities
+ for (i = 0; i < m_scriptMove.Count() - 1;)
+ {
+ // accel to ideal
+ float t1 = (idealVelocity - m_scriptMove[i].flMaxVelocity) / idealAccel;
+ float d1 = m_scriptMove[i].flMaxVelocity * t1 + 0.5 * (idealAccel) * t1 * t1;
+
+ // decel from ideal
+ float t2 = (idealVelocity - m_scriptMove[i+1].flMaxVelocity) / idealAccel;
+ float d2 = m_scriptMove[i+1].flMaxVelocity * t2 + 0.5 * (idealAccel) * t2 * t2;
+
+ m_scriptMove[i].flDist = (m_scriptMove[i+1].vecLocation - m_scriptMove[i].vecLocation).Length2D();
+
+ // is it possible to accel and decal to idealVelocity between next two nodes
+ if (d1 + d2 < m_scriptMove[i].flDist)
+ {
+ Vector start = m_scriptMove[i].vecLocation;
+ Vector end = m_scriptMove[i+1].vecLocation;
+ float dist = m_scriptMove[i].flDist;
+
+ // insert the two points needed to end accel and start decel
+ if (d1 > 1.0 && t1 > 0.1)
+ {
+ a = d1 / dist;
+
+ script.Init();
+ script.vecLocation = end * a + start * (1 - a);
+ script.flMaxVelocity = idealVelocity;
+ m_scriptMove.InsertAfter( i, script );
+ i++;
+ }
+
+ if (dist - d2 > 1.0 && t2 > 0.1)
+ {
+ // DevMsg("%.2f : ", a );
+
+ a = (dist - d2) / dist;
+
+ script.Init();
+ script.vecLocation = end * a + start * (1 - a);
+ script.flMaxVelocity = idealVelocity;
+ m_scriptMove.InsertAfter( i, script );
+ i++;
+ }
+
+ i++;
+ }
+ else
+ {
+ // check to see if the amount of change needed to reach target is less than the ideal acceleration
+ float flNeededAccel = fabs( DeltaV( m_scriptMove[i].flMaxVelocity, m_scriptMove[i+1].flMaxVelocity, m_scriptMove[i].flDist ) );
+ if (flNeededAccel < idealAccel)
+ {
+ // if so, they it's possible to get a bit towards the ideal velocity
+ float v1 = m_scriptMove[i].flMaxVelocity;
+ float v2 = m_scriptMove[i+1].flMaxVelocity;
+ float dist = m_scriptMove[i].flDist;
+
+ // based on solving:
+ // v1+A*t1-v2-A*t2=0
+ // v1*t1+0.5*A*t1*t1+v2*t2+0.5*A*t2*t2-D=0
+
+ float tmp = idealAccel*dist+0.5*v1*v1+0.5*v2*v2;
+ Assert( tmp >= 0 );
+ t1 = (-v1+sqrt( tmp )) / idealAccel;
+ t2 = (v1+idealAccel*t1-v2)/idealAccel;
+
+ // if this assert hits, write down the v1, v2, dist, and idealAccel numbers and send them to me (Ken).
+ // go ahead the comment it out, it's safe, but I'd like to know a test case where it's happening
+ //Assert( t1 > 0 && t2 > 0 );
+
+ // check to make sure it's really worth it
+ if (t1 > 0.0 && t2 > 0.0)
+ {
+ d1 = v1 * t1 + 0.5 * idealAccel * t1 * t1;
+
+ /*
+ d2 = v2 * t2 + 0.5 * idealAccel * t2 * t2;
+ Assert( fabs( d1 + d2 - dist ) < 0.001 );
+ */
+
+ float a = d1 / m_scriptMove[i].flDist;
+ script.Init();
+ script.vecLocation = m_scriptMove[i+1].vecLocation * a + m_scriptMove[i].vecLocation * (1 - a);
+ script.flMaxVelocity = m_scriptMove[i].flMaxVelocity + idealAccel * t1;
+
+ if (script.flMaxVelocity < idealVelocity)
+ {
+ // DevMsg("insert %.2f %.2f %.2f\n", m_scriptMove[i].flMaxVelocity, script.flMaxVelocity, m_scriptMove[i+1].flMaxVelocity );
+ m_scriptMove.InsertAfter( i, script );
+ i += 1;
+ }
+ }
+ }
+ i += 1;
+ }
+ }
+
+ // clamp min velocities
+ for (i = 0; i < m_scriptMove.Count(); i++)
+ {
+ m_scriptMove[i].flMaxVelocity = MAX( m_scriptMove[i].flMaxVelocity, MIN_VELOCITY );
+ }
+
+ // rebuild fields
+ m_scriptMove[0].flElapsedTime = 0;
+ for (i = 0; i < m_scriptMove.Count() - 1; )
+ {
+ m_scriptMove[i].flDist = (m_scriptMove[i+1].vecLocation - m_scriptMove[i].vecLocation).Length2D();
+
+ if (m_scriptMove[i].flMaxVelocity == 0 && m_scriptMove[i+1].flMaxVelocity == 0)
+ {
+ // force a minimum velocity
+ Assert( 0 );
+ m_scriptMove[i+1].flMaxVelocity = 1.0;
+ }
+
+ float t = m_scriptMove[i].flDist / (0.5 * (m_scriptMove[i].flMaxVelocity + m_scriptMove[i+1].flMaxVelocity));
+ m_scriptMove[i].flTime = t;
+
+ /*
+ if (m_scriptMove[i].flDist < 0.01)
+ {
+ // Assert( m_scriptMove[i+1].pWaypoint == NULL );
+
+ m_scriptMove.Remove( i + 1 );
+ continue;
+ }
+ */
+
+ m_scriptMove[i+1].flElapsedTime = m_scriptMove[i].flElapsedTime + m_scriptMove[i].flTime;
+
+ i++;
+ }
+
+ /*
+ for (i = 0; i < m_scriptMove.Count(); i++)
+ {
+ DevMsg("(%.2f : %.2f : %.2f)", m_scriptMove[i].flMaxVelocity, m_scriptMove[i].flDist, m_scriptMove[i].flTime );
+ // DevMsg("(%.2f:%.2f)", m_scriptMove[i].flTime, m_scriptMove[i].flElapsedTime );
+ }
+ DevMsg("\n");
+ */
+}
+
+
+
+void CAI_BlendedMotor::InsertSlowdown( float distToObstruction, float idealAccel, bool bAlwaysSlowdown )
+{
+ int i;
+ AI_Movementscript_t script;
+
+ if (distToObstruction <= 0.0)
+ return;
+
+ for (i = 0; i < m_scriptMove.Count() - 1; i++)
+ {
+ if (m_scriptMove[i].flDist > 0 && distToObstruction - m_scriptMove[i].flDist < 0)
+ {
+ float a = distToObstruction / m_scriptMove[i].flDist;
+ Assert( a >= 0 && a <= 1);
+ script.vecLocation = (1 - a) * m_scriptMove[i].vecLocation + a * m_scriptMove[i+1].vecLocation;
+
+ //NDebugOverlay::Line( m_scriptMove[i].vecLocation + Vector( 0, 0, 5 ), script.vecLocation + Vector( 0, 0, 5 ), 0,255,0, true, 0.1 );
+ //NDebugOverlay::Line( script.vecLocation + Vector( 0, 0, 5 ), m_scriptMove[i+1].vecLocation + Vector( 0, 0, 5 ), 0,0,255, true, 0.1 );
+
+ float r1, r2;
+
+ // clamp the next velocity to the possible accel in the given distance
+ if (!bAlwaysSlowdown && SolveQuadratic( -0.5 * idealAccel, m_scriptMove[0].flMaxVelocity, -distToObstruction, r1, r2 ))
+ {
+ script.flMaxVelocity = MAX( 10, m_scriptMove[0].flMaxVelocity - idealAccel * r1 );
+ }
+ else
+ {
+ script.flMaxVelocity = 10.0;
+ }
+
+ script.flMaxVelocity = 1.0; // as much as reasonable
+ script.pWaypoint = NULL;
+ script.flDist = m_scriptMove[i].flDist - distToObstruction;
+ m_scriptMove[i].flDist = distToObstruction;
+ m_scriptMove.InsertAfter( i, script );
+ break;
+ }
+ else
+ {
+ distToObstruction -= m_scriptMove[i].flDist;
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: issues turn gestures when it detects that the body has turned but the feet haven't compensated
+//-----------------------------------------------------------------------------
+
+
+void CAI_BlendedMotor::MaintainTurnActivity( void )
+{
+ AI_PROFILE_SCOPE(CAI_BlendedMotor_MaintainTurnActivity);
+
+ if (m_flNextTurnGesture > gpGlobals->curtime || m_flNextTurnAct > gpGlobals->curtime || GetOuter()->IsMoving() )
+ {
+ // clear out turn detection if currently turing or moving
+ m_doTurn = m_doRight = m_doLeft = 0;
+ if ( GetOuter()->IsMoving())
+ {
+ m_flNextTurnAct = gpGlobals->curtime + 0.3;
+ }
+ }
+ else
+ {
+ // detect undirected turns
+ if (m_prevYaw != GetAbsAngles().y)
+ {
+ float diff = UTIL_AngleDiff( m_prevYaw, GetAbsAngles().y );
+ if (diff < 0.0)
+ {
+ m_doLeft += -diff;
+ }
+ else
+ {
+ m_doRight += diff;
+ }
+ m_prevYaw = GetAbsAngles().y;
+ }
+ // accumulate turn angle, delay response for short turns
+ m_doTurn += m_doRight + m_doLeft;
+ // accumulate random foot stick clearing
+ m_doTurn += random->RandomFloat( 0.4, 0.6 );
+ }
+
+ if (m_doTurn > 15.0f)
+ {
+ // mostly a foot stick clear
+ int iSeq = ACT_INVALID;
+ if (m_doLeft > m_doRight)
+ {
+ iSeq = SelectWeightedSequence( ACT_GESTURE_TURN_LEFT );
+ }
+ else
+ {
+ iSeq = SelectWeightedSequence( ACT_GESTURE_TURN_RIGHT );
+ }
+ m_doLeft = 0;
+ m_doRight = 0;
+
+ if (iSeq != ACT_INVALID)
+ {
+ int iLayer = GetOuter()->AddGestureSequence( iSeq );
+ if (iLayer != -1)
+ {
+ GetOuter()->SetLayerPriority( iLayer, 100 );
+ // increase speed if we're getting behind or they're turning quickly
+ float rate = random->RandomFloat( 0.8, 1.2 );
+ if (m_doTurn > 90.0)
+ {
+ rate *= 1.5;
+ }
+ GetOuter()->SetLayerPlaybackRate( iLayer, rate );
+ // disable turing for the duration of the gesture
+ m_flNextTurnAct = gpGlobals->curtime + GetOuter()->GetLayerDuration( iLayer );
+ }
+ else
+ {
+ // too many active gestures, try again in half a second
+ m_flNextTurnAct = gpGlobals->curtime + 0.3;
+ }
+ }
+ m_doTurn = m_doRight = m_doLeft = 0;
+ }
+}
+
+ConVar scene_flatturn( "scene_flatturn", "1" );
+
+bool CAI_BlendedMotor::AddTurnGesture( float flYD )
+{
+
+ // some funky bug with human turn gestures, disable for now
+ return false;
+
+ // try using a turn gesture
+ Activity activity = ACT_INVALID;
+ float weight = 1.0;
+ float turnCompletion = 1.0;
+
+ if (m_flNextTurnGesture > gpGlobals->curtime)
+ {
+ /*
+ if ( GetOuter()->m_debugOverlays & OVERLAY_NPC_SELECTED_BIT )
+ {
+ Msg( "%.1f : [ %.2f ]\n", flYD, m_flNextTurnAct - gpGlobals->curtime );
+ }
+ */
+ return false;
+ }
+
+ if ( GetOuter()->IsMoving() || GetOuter()->IsCrouching() )
+ {
+ return false;
+ }
+
+ if (fabs( flYD ) < 15)
+ {
+ return false;
+ }
+ else if (flYD < -45)
+ {
+ activity = ACT_GESTURE_TURN_RIGHT90;
+ weight = flYD / -90;
+ turnCompletion = 0.36;
+ }
+ else if (flYD < 0)
+ {
+ activity = ACT_GESTURE_TURN_RIGHT45;
+ weight = flYD / -45;
+ turnCompletion = 0.4;
+ }
+ else if (flYD <= 45)
+ {
+ activity = ACT_GESTURE_TURN_LEFT45;
+ weight = flYD / 45;
+ turnCompletion = 0.4;
+ }
+ else
+ {
+ activity = ACT_GESTURE_TURN_LEFT90;
+ weight = flYD / 90;
+ turnCompletion = 0.36;
+ }
+
+ int seq = SelectWeightedSequence( activity );
+
+ if (scene_flatturn.GetBool() && GetOuter()->IsCurSchedule( SCHED_SCENE_GENERIC ))
+ {
+ Activity flatactivity = activity;
+
+ if (activity == ACT_GESTURE_TURN_RIGHT90)
+ {
+ flatactivity = ACT_GESTURE_TURN_RIGHT90_FLAT;
+ }
+ else if (activity == ACT_GESTURE_TURN_RIGHT45)
+ {
+ flatactivity = ACT_GESTURE_TURN_RIGHT45_FLAT;
+ }
+ else if (activity == ACT_GESTURE_TURN_LEFT90)
+ {
+ flatactivity = ACT_GESTURE_TURN_LEFT90_FLAT;
+ }
+ else if (activity == ACT_GESTURE_TURN_LEFT45)
+ {
+ flatactivity = ACT_GESTURE_TURN_LEFT45_FLAT;
+ }
+
+ if (flatactivity != activity)
+ {
+ int newseq = SelectWeightedSequence( flatactivity );
+ if (newseq != ACTIVITY_NOT_AVAILABLE)
+ {
+ seq = newseq;
+ }
+ }
+ }
+
+ if (seq != ACTIVITY_NOT_AVAILABLE)
+ {
+ int iLayer = GetOuter()->AddGestureSequence( seq );
+ if (iLayer != -1)
+ {
+ GetOuter()->SetLayerPriority( iLayer, 100 );
+ // vary the playback a bit
+ SetLayerPlaybackRate( iLayer, 1.0 );
+ float actualDuration = GetOuter()->GetLayerDuration( iLayer );
+
+ float rate = random->RandomFloat( 0.5f, 1.1f );
+ float diff = fabs( flYD );
+ float speed = (diff / (turnCompletion * actualDuration / rate)) * 0.1f;
+
+ speed = clamp( speed, 15.f, 35.f );
+ speed = MIN( speed, diff );
+
+ actualDuration = (diff / (turnCompletion * speed)) * 0.1 ;
+
+ GetOuter()->SetLayerDuration( iLayer, actualDuration );
+
+ SetLayerWeight( iLayer, weight );
+
+ SetYawSpeed( speed );
+
+ Remember( bits_MEMORY_TURNING );
+
+ // don't overlap the turn portion of the gestures, and don't play them too often
+ m_flNextTurnGesture = gpGlobals->curtime + MAX( turnCompletion * actualDuration, 0.3 );
+
+ /*
+ if ( GetOuter()->m_debugOverlays & OVERLAY_NPC_SELECTED_BIT )
+ {
+ Msg( "%.1f : %.2f %.2f : %.2f (%.2f)\n", flYD, weight, speed, actualDuration, turnCompletion * actualDuration );
+ }
+ */
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+ return false;
+}
+
+
+//-------------------------------------
+
+
+
+#if 0
+Activity CAI_BlendedMotor::GetTransitionActivity( )
+{
+ AI_Waypoint_t *waypoint = GetNavigator()->GetPath()->GetTransitionWaypoint();
+
+ if ( waypoint->Flags() & bits_WP_TO_GOAL )
+ {
+ if ( waypoint->activity != ACT_INVALID)
+ {
+ return waypoint->activity;
+ }
+
+ return GetStoppedActivity( );
+ }
+
+ if (waypoint)
+ waypoint = waypoint->GetNext();
+
+ switch(waypoint->NavType() )
+ {
+ case NAV_JUMP:
+ return ACT_JUMP; // are jumps going to get a movement track added to them?
+
+ case NAV_GROUND:
+ return GetNavigator()->GetMovementActivity(); // yuck
+
+ case NAV_CLIMB:
+ return ACT_CLIMB_UP; // depends on specifics of climb node
+
+ default:
+ return ACT_IDLE;
+ }
+}
+#endif
+
+//-------------------------------------
+// Purpose: return a velocity that should be hit at the end of the interval to match goal
+// Input : flInterval - time interval to consider
+// : flGoalDistance - distance to goal
+// : flGoalVelocity - desired velocity at goal
+// : flCurVelocity - current velocity
+// : flIdealVelocity - velocity to go at if goal is too far away
+// : flAccelRate - maximum acceleration/deceleration rate
+// Output : target velocity at time t+flInterval
+//-------------------------------------
+
+float ChangeDistance( float flInterval, float flGoalDistance, float flGoalVelocity, float flCurVelocity, float flIdealVelocity, float flAccelRate, float &flNewDistance, float &flNewVelocity )
+{
+ float scale = 1.0;
+ if (flGoalDistance < 0)
+ {
+ flGoalDistance = - flGoalDistance;
+ flCurVelocity = -flCurVelocity;
+ scale = -1.0;
+ }
+
+ flNewVelocity = flCurVelocity;
+ flNewDistance = 0.0;
+
+ // if I'm too close, just go ahead and set the velocity
+ if (flGoalDistance < 0.01)
+ {
+ return flGoalVelocity * scale;
+ }
+
+ float flGoalAccel = DeltaV( flCurVelocity, flGoalVelocity, flGoalDistance );
+
+ flNewVelocity = flCurVelocity;
+
+ // --------------------------------------------
+ // if goal is close enough try to match the goal velocity, else try to go ideal velocity
+ // --------------------------------------------
+ if (flGoalAccel < 0 && flGoalAccel < -flAccelRate)
+ {
+ // I need to slow down;
+ flNewVelocity = flCurVelocity + flGoalAccel * flInterval;
+ if (flNewVelocity < 0)
+ flNewVelocity = 0;
+ }
+ else if (flGoalAccel > 0 && flGoalAccel >= flAccelRate)
+ {
+ // I need to speed up
+ flNewVelocity = flCurVelocity + flGoalAccel * flInterval;
+ if (flNewVelocity > flGoalVelocity)
+ flGoalVelocity = flGoalVelocity;
+ }
+ else if (flNewVelocity < flIdealVelocity)
+ {
+ // speed up to ideal velocity;
+ flNewVelocity = flCurVelocity + flAccelRate * flInterval;
+ if (flNewVelocity > flIdealVelocity)
+ flNewVelocity = flIdealVelocity;
+ // don't overshoot
+ if (0.5*(flNewVelocity + flCurVelocity) * flInterval > flGoalDistance)
+ {
+ flNewVelocity = 0.5 * (2 * flGoalDistance / flInterval - flCurVelocity);
+ }
+ }
+ else if (flNewVelocity > flIdealVelocity)
+ {
+ // slow down to ideal velocity;
+ flNewVelocity = flCurVelocity - flAccelRate * flInterval;
+ if (flNewVelocity < flIdealVelocity)
+ flNewVelocity = flIdealVelocity;
+ }
+
+ float flDist = 0.5*(flNewVelocity + flCurVelocity) * flInterval;
+
+ if (flDist > flGoalDistance)
+ {
+ flDist = flGoalDistance;
+ flNewVelocity = flGoalVelocity;
+ }
+
+ flNewVelocity = flNewVelocity * scale;
+
+ flNewDistance = (flGoalDistance - flDist) * scale;
+
+ return 0.0;
+}
+
+//-----------------------------------------------------------------------------