Added new NPC climbing activities and improved climbing navigation AI

This commit is contained in:
Blixibon 2021-10-13 16:17:46 -05:00
parent 2638dd1d1c
commit 3d5f73b8be
9 changed files with 290 additions and 4 deletions

View File

@ -2254,4 +2254,13 @@ void CAI_BaseNPC::InitDefaultActivitySR(void)
ADD_ACTIVITY_TO_SR( ACT_ARM_RIFLE );
ADD_ACTIVITY_TO_SR( ACT_DISARM_RIFLE );
#endif
#ifdef EXPANDED_NAVIGATION_ACTIVITIES
ADD_ACTIVITY_TO_SR( ACT_CLIMB_ALL );
ADD_ACTIVITY_TO_SR( ACT_CLIMB_IDLE );
ADD_ACTIVITY_TO_SR( ACT_CLIMB_MOUNT_TOP );
ADD_ACTIVITY_TO_SR( ACT_CLIMB_MOUNT_BOTTOM );
ADD_ACTIVITY_TO_SR( ACT_CLIMB_DISMOUNT_BOTTOM );
#endif
}

View File

@ -6593,6 +6593,12 @@ Activity CAI_BaseNPC::NPC_BackupActivity( Activity eNewActivity )
if (eNewActivity == ACT_WALK_ANGRY)
return TranslateActivity(ACT_WALK);
// If one climbing animation isn't available, use the other
if (eNewActivity == ACT_CLIMB_DOWN)
return ACT_CLIMB_UP;
else if (eNewActivity == ACT_CLIMB_UP)
return ACT_CLIMB_DOWN;
// GetCoverActivity() should have this covered.
// ---------------------------------------------
//if (eNewActivity == ACT_COVER)
@ -6609,6 +6615,14 @@ Activity CAI_BaseNPC::NPC_BackupActivity( Activity eNewActivity )
//-----------------------------------------------------------------------------
Activity CAI_BaseNPC::NPC_TranslateActivity( Activity eNewActivity )
{
#ifdef EXPANDED_NAVIGATION_ACTIVITIES
if ( GetNavType() == NAV_CLIMB && eNewActivity == ACT_IDLE )
{
// Schedules which break into idle activities should try to maintain the climbing animation.
return ACT_CLIMB_IDLE;
}
#endif
#ifdef MAPBASE
Assert( eNewActivity != ACT_INVALID );

View File

@ -1363,6 +1363,14 @@ void CAI_BaseNPC::StartTask( const Task_t *pTask )
break;
case TASK_STOP_MOVING:
#ifdef MAPBASE
if ( GetNavType() == NAV_CLIMB )
{
// Don't clear the goal so that the climb can finish
DbgNavMsg( this, "Start TASK_STOP_MOVING with climb workaround\n" );
}
else
#endif
if ( ( GetNavigator()->IsGoalSet() && GetNavigator()->IsGoalActive() ) || GetNavType() == NAV_JUMP )
{
DbgNavMsg( this, "Start TASK_STOP_MOVING\n" );
@ -3348,8 +3356,37 @@ void CAI_BaseNPC::RunTask( const Task_t *pTask )
// a navigation while in the middle of a climb
if (GetNavType() == NAV_CLIMB)
{
#ifdef MAPBASE
if (GetActivity() != ACT_CLIMB_DISMOUNT)
{
// Try to just pause the climb, but dismount if we're in SCHED_FAIL
if (IsCurSchedule( SCHED_FAIL, false ))
{
GetMotor()->MoveClimbStop();
}
else
{
GetMotor()->MoveClimbPause();
}
TaskComplete();
}
else if (IsActivityFinished())
{
// Dismount complete. Fix up our position if we have to
Vector vecTeleportOrigin;
if (GetMotor()->MoveClimbShouldTeleportToSequenceEnd( vecTeleportOrigin ))
{
GetMotor()->MoveClimbStop();
SetLocalOrigin( vecTeleportOrigin );
TaskComplete();
}
}
break;
#else
// wait until you reach the end
break;
#endif
}
DbgNavMsg( this, "TASK_STOP_MOVING Complete\n" );

View File

@ -235,18 +235,47 @@ void CAI_Motor::MoveClimbStart( const Vector &climbDest, const Vector &climbDir
// > code are not reciprocal for all state, and furthermore, stomp
// > other state?
bool bGoingUp = (climbDir.z > 0.01);
#ifdef EXPANDED_NAVIGATION_ACTIVITIES
if ( bGoingUp && GetOuter()->HaveSequenceForActivity( ACT_CLIMB_MOUNT_BOTTOM ) )
{
SetActivity( ACT_CLIMB_MOUNT_BOTTOM );
// Steal m_vecDismount for this
GetOuter()->GetSequenceLinearMotion( GetSequence(), &m_vecDismount );
GetOuter()->SetCycle( GetOuter()->GetMovementFrame( m_vecDismount.z - climbDist ) );
}
else if ( !bGoingUp && GetOuter()->HaveSequenceForActivity( ACT_CLIMB_MOUNT_TOP ) )
{
SetActivity( ACT_CLIMB_MOUNT_TOP );
// Steal m_vecDismount for this
GetOuter()->GetSequenceLinearMotion( GetSequence(), &m_vecDismount );
GetOuter()->SetCycle( GetOuter()->GetMovementFrame( m_vecDismount.z - climbDist ) );
}
else
#endif
if ( fabsf( climbDir.z ) < .1 )
{
SetActivity( GetNavigator()->GetMovementActivity() );
}
else
{
SetActivity( (climbDir.z > -0.01 ) ? ACT_CLIMB_UP : ACT_CLIMB_DOWN );
SetActivity( bGoingUp ? ACT_CLIMB_UP : ACT_CLIMB_DOWN );
}
m_nDismountSequence = SelectWeightedSequence( ACT_CLIMB_DISMOUNT );
if (m_nDismountSequence != ACT_INVALID)
{
#ifdef EXPANDED_NAVIGATION_ACTIVITIES
if ( !bGoingUp )
{
int nBottomDismount = SelectWeightedSequence( ACT_CLIMB_DISMOUNT_BOTTOM );
if (nBottomDismount != ACTIVITY_NOT_AVAILABLE)
m_nDismountSequence = nBottomDismount;
}
#endif
GetOuter()->GetSequenceLinearMotion( m_nDismountSequence, &m_vecDismount );
}
else
@ -262,6 +291,76 @@ void CAI_Motor::MoveClimbStart( const Vector &climbDest, const Vector &climbDir
AIMoveResult_t CAI_Motor::MoveClimbExecute( const Vector &climbDest, const Vector &climbDir, float climbDist, float yaw, int climbNodesLeft )
{
#ifdef EXPANDED_NAVIGATION_ACTIVITIES
if ( (GetActivity() == ACT_CLIMB_MOUNT_TOP || GetActivity() == ACT_CLIMB_MOUNT_BOTTOM) )
{
if (!GetOuter()->IsActivityFinished())
{
// Wait for the mounting to finish
SetGroundEntity( NULL );
}
else
{
// Fix up our position if we have to
Vector vecTeleportOrigin;
if (MoveClimbShouldTeleportToSequenceEnd( vecTeleportOrigin ))
{
SetLocalOrigin( vecTeleportOrigin );
}
// Reset activity and start from the beginning
GetOuter()->ResetActivity();
return MoveClimbExecute( climbDest, climbDir, climbDist, yaw, climbNodesLeft );
}
}
else if ( fabsf( climbDir.z ) > .1 && (GetActivity() != ACT_CLIMB_DISMOUNT && GetActivity() != ACT_CLIMB_DISMOUNT_BOTTOM) )
{
bool bGoingUp = (climbDir.z > -0.01);
if ( GetOuter()->HaveSequenceForActivity( ACT_CLIMB_ALL ) )
{
SetActivity( ACT_CLIMB_ALL );
// TODO: Use UTIL_VecToPitch() instead if move_yaw becomes a true climb yaw and not just an up-down scalar
SetPoseParameter( GetOuter()->LookupPoseMoveYaw(), climbDir.z < 0 ? 180.0 : -180.0 );
}
else
{
Activity desiredActivity = bGoingUp ? ACT_CLIMB_UP : ACT_CLIMB_DOWN;
if ( GetActivity() != desiredActivity )
{
SetActivity( desiredActivity );
}
}
if (m_nDismountSequence != ACT_INVALID)
{
if (climbNodesLeft <= 2 && climbDist < fabs( m_vecDismount.z ))
{
if (bGoingUp)
{
// fixme: No other way to force m_nIdealSequence?
GetOuter()->SetActivity( ACT_CLIMB_DISMOUNT );
GetOuter()->SetCycle( GetOuter()->GetMovementFrame( m_vecDismount.z - climbDist ) );
}
else
{
if (GetSequence() != m_nDismountSequence && GetOuter()->GetSequenceActivity( m_nDismountSequence ) == ACT_CLIMB_DISMOUNT_BOTTOM)
{
SetActivity( ACT_CLIMB_DISMOUNT_BOTTOM );
}
}
}
}
}
else if ( climbDir.Length() == 0 && GetOuter()->GetInstantaneousVelocity() <= 0.01 )
{
// The NPC is somehow stuck climbing with no direction or movement.
// This can be caused by NPCs getting stuck in each other and/or being moved away from the ladder.
// In these cases, the NPC has to be made unstuck, or else they may remain in an immobile climbing state forever.
Warning( "%s had to abort climbing due to no direction or movement\n", GetOuter()->GetDebugName() );
return AIMR_ILLEGAL;
}
#else
if ( fabsf( climbDir.z ) > .1 )
{
if ( GetActivity() != ACT_CLIMB_DISMOUNT )
@ -292,13 +391,34 @@ AIMoveResult_t CAI_Motor::MoveClimbExecute( const Vector &climbDest, const Vecto
}
}
}
#endif
float climbSpeed = GetOuter()->GetInstantaneousVelocity();
if (m_nDismountSequence != ACT_INVALID)
{
// catch situations where the climb mount/dismount finished before reaching goal
#ifdef EXPANDED_NAVIGATION_ACTIVITIES
if ((GetActivity() == ACT_CLIMB_DISMOUNT || GetActivity() == ACT_CLIMB_DISMOUNT_BOTTOM))
{
SetGroundEntity( NULL );
if (GetOuter()->IsActivityFinished())
{
// Fix up our position if we have to
Vector vecTeleportOrigin;
if (MoveClimbShouldTeleportToSequenceEnd( vecTeleportOrigin ))
{
// Just force it to complete
climbDist = 0.0f;
}
climbSpeed = 200.0f;
}
}
#else
climbSpeed = MAX( climbSpeed, 30.0 );
#endif
}
else
{
@ -340,11 +460,63 @@ void CAI_Motor::MoveClimbStop()
else
SetActivity( ACT_IDLE );
#ifdef MAPBASE
// Unlock desired weapon state so NPCs can unholster their weapons again.
GetOuter()->SetDesiredWeaponState( DESIREDWEAPONSTATE_IGNORE );
// Unlock yaw
// TODO: Add yaw locking for when climbing is paused
//SetYawLocked( false );
#endif
GetOuter()->RemoveFlag( FL_FLY );
SetSmoothedVelocity( vec3_origin );
SetGravity( 1.0 );
}
#ifdef MAPBASE
void CAI_Motor::MoveClimbPause()
{
if (GetActivity() != ACT_CLIMB_DISMOUNT
#ifdef EXPANDED_NAVIGATION_ACTIVITIES
&& GetActivity() != ACT_CLIMB_MOUNT_TOP && GetActivity() != ACT_CLIMB_MOUNT_BOTTOM
#endif
)
{
if ( GetActivity() == ACT_CLIMB_ALL )
{
SetPoseParameter( GetOuter()->LookupPoseMoveYaw(), 0.0f );
}
SetSmoothedVelocity( vec3_origin );
}
else
{
// If already dismounting, do nothing
}
}
//-----------------------------------------------------------------------------
// Purpose: This is part of a hack needed in cases where ladder mount/dismount animations collide with the world and don't move properly.
// It's based off of the same code as scripted_sequence's teleportation fixup, although this function only resolves the bone origin and
// returns whether or not teleportation is necessary, as the teleportation is achieved in different ways for different uses of this code.
//-----------------------------------------------------------------------------
bool CAI_Motor::MoveClimbShouldTeleportToSequenceEnd( Vector &teleportOrigin )
{
QAngle new_angle;
GetOuter()->GetBonePosition( 0, teleportOrigin, new_angle );
// Ensure that there is actually a distance needed to teleport there
if ((GetLocalOrigin() - teleportOrigin).Length2DSqr() > Square( 8.0 ))
{
teleportOrigin.z = GetLocalOrigin().z;
return true;
}
return false;
}
#endif
//-----------------------------------------------------------------------------
// Purpose: Motion for jumping
// Input :

View File

@ -62,6 +62,10 @@ public:
virtual void MoveClimbStart( const Vector &climbDest, const Vector &climbDir, float climbDist, float yaw );
virtual AIMoveResult_t MoveClimbExecute( const Vector &climbDest, const Vector &climbDir, float climbDist, float yaw, int climbNodesLeft );
virtual void MoveClimbStop();
#ifdef MAPBASE
virtual void MoveClimbPause();
virtual bool MoveClimbShouldTeleportToSequenceEnd( Vector &teleportOrigin );
#endif
//---------------------------------

View File

@ -1642,6 +1642,15 @@ void CAI_Navigator::MoveCalcBaseGoal( AILocalMoveGoal_t *pMoveGoal )
AI_Waypoint_t *pCurWaypoint = GetPath()->GetCurWaypoint();
if ( pCurWaypoint->GetNext() && pCurWaypoint->GetNext()->NavType() != pCurWaypoint->NavType() )
pMoveGoal->flags |= AILMG_TARGET_IS_TRANSITION;
#ifdef MAPBASE
// TODO: Better place for this code?
if (pMoveGoal->flags & AILMG_TARGET_IS_TRANSITION && pCurWaypoint->GetNext()->NavType() == NAV_CLIMB)
{
// NPCs need to holster their weapons before climbing.
GetOuter()->SetDesiredWeaponState( DESIREDWEAPONSTATE_HOLSTERED );
}
#endif
}
const Task_t *pCurTask = GetOuter()->GetTask();
@ -2591,8 +2600,12 @@ bool CAI_Navigator::Move( float flInterval )
if ( GetNavType() == NAV_CLIMB )
{
#ifdef MAPBASE
GetMotor()->MoveClimbPause();
#else
GetMotor()->MoveClimbStop();
SetNavType( NAV_GROUND );
#endif
}
GetMotor()->MoveStop();
AssertMsg( TaskIsRunning() || TaskIsComplete(), ("Schedule stalled!!\n") );
@ -3880,7 +3893,12 @@ bool CAI_Navigator::GetStoppingPath( CAI_WaypointList * pClippedWaypoints )
AI_Waypoint_t *pCurWaypoint = GetPath()->GetCurWaypoint();
if ( pCurWaypoint )
{
#ifdef EXPANDED_NAVIGATION_ACTIVITIES
// Since regular climb nav can interrupt itself now, only do this when dismounting
bool bMustCompleteCurrent = ( (pCurWaypoint->NavType() == NAV_CLIMB && (GetActivity() == ACT_CLIMB_DISMOUNT || GetActivity() == ACT_CLIMB_MOUNT_TOP)) || pCurWaypoint->NavType() == NAV_JUMP );
#else
bool bMustCompleteCurrent = ( pCurWaypoint->NavType() == NAV_CLIMB || pCurWaypoint->NavType() == NAV_JUMP );
#endif
float distRemaining = GetMotor()->MinStoppingDist( 0 );
if ( bMustCompleteCurrent )

View File

@ -1296,7 +1296,11 @@ void CFastZombie::StartTask( const Task_t *pTask )
CBaseEntity *pEnemy = GetEnemy();
Vector vecJumpDir;
if ( GetActivity() == ACT_CLIMB_UP || GetActivity() == ACT_CLIMB_DOWN )
if ( GetActivity() == ACT_CLIMB_UP || GetActivity() == ACT_CLIMB_DOWN
#ifdef EXPANDED_NAVIGATION_ACTIVITIES
|| GetActivity() == ACT_CLIMB_ALL
#endif
)
{
// Jump off the pipe backwards!
Vector forward;
@ -1449,7 +1453,11 @@ int CFastZombie::TranslateSchedule( int scheduleType )
break;
case SCHED_FASTZOMBIE_UNSTICK_JUMP:
if ( GetActivity() == ACT_CLIMB_UP || GetActivity() == ACT_CLIMB_DOWN || GetActivity() == ACT_CLIMB_DISMOUNT )
if ( GetActivity() == ACT_CLIMB_UP || GetActivity() == ACT_CLIMB_DOWN || GetActivity() == ACT_CLIMB_DISMOUNT
#ifdef EXPANDED_NAVIGATION_ACTIVITIES
|| (GetActivity() >= ACT_CLIMB_ALL && GetActivity() <= ACT_CLIMB_DISMOUNT_BOTTOM)
#endif
)
{
return SCHED_FASTZOMBIE_CLIMBING_UNSTICK_JUMP;
}
@ -1477,8 +1485,10 @@ int CFastZombie::TranslateSchedule( int scheduleType )
//---------------------------------------------------------
Activity CFastZombie::NPC_TranslateActivity( Activity baseAct )
{
#ifndef MAPBASE // Now covered by CAI_BaseNPC::NPC_BackupActivity
if ( baseAct == ACT_CLIMB_DOWN )
return ACT_CLIMB_UP;
#endif
return BaseClass::NPC_TranslateActivity( baseAct );
}

View File

@ -2371,6 +2371,15 @@ void ActivityList_RegisterSharedActivities( void )
REGISTER_SHARED_ACTIVITY( ACT_DISARM_RIFLE );
#endif
#ifdef EXPANDED_NAVIGATION_ACTIVITIES
REGISTER_SHARED_ACTIVITY( ACT_CLIMB_ALL );
REGISTER_SHARED_ACTIVITY( ACT_CLIMB_IDLE );
REGISTER_SHARED_ACTIVITY( ACT_CLIMB_MOUNT_TOP );
REGISTER_SHARED_ACTIVITY( ACT_CLIMB_MOUNT_BOTTOM );
REGISTER_SHARED_ACTIVITY( ACT_CLIMB_DISMOUNT_BOTTOM );
#endif
AssertMsg( g_HighestActivity == LAST_SHARED_ACTIVITY - 1, "Not all activities from ai_activity.h registered in activitylist.cpp" );
}

View File

@ -41,6 +41,10 @@
// This enables a bunch of new activities for Half-Life 2 weapons, including new 357 animations and readiness activities for pistols.
#define EXPANDED_HL2_WEAPON_ACTIVITIES 1
// EXPANDED NAVIGATION ACTIVITIES
// This enables some new navigation-related activities.
#define EXPANDED_NAVIGATION_ACTIVITIES 1
#endif
#define ACTIVITY_NOT_AVAILABLE -1
@ -2243,6 +2247,15 @@ typedef enum
ACT_DISARM_RIFLE,
#endif
#ifdef EXPANDED_NAVIGATION_ACTIVITIES
ACT_CLIMB_ALL, // An actual blend animation which uses pose parameters for direction
ACT_CLIMB_IDLE,
ACT_CLIMB_MOUNT_TOP,
ACT_CLIMB_MOUNT_BOTTOM,
ACT_CLIMB_DISMOUNT_BOTTOM,
#endif
// this is the end of the global activities, private per-monster activities start here.
LAST_SHARED_ACTIVITY,
} Activity;