You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1904 lines
51 KiB
1904 lines
51 KiB
//========= 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) |
|
flNewVelocity = 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; |
|
} |
|
|
|
//-----------------------------------------------------------------------------
|
|
|