From 3d5f73b8be96922c60691e46a5ba5bf63af910a0 Mon Sep 17 00:00:00 2001 From: Blixibon Date: Wed, 13 Oct 2021 16:17:46 -0500 Subject: [PATCH] Added new NPC climbing activities and improved climbing navigation AI --- sp/src/game/server/ai_activity.cpp | 9 ++ sp/src/game/server/ai_basenpc.cpp | 14 ++ sp/src/game/server/ai_basenpc_schedule.cpp | 37 +++++ sp/src/game/server/ai_motor.cpp | 176 ++++++++++++++++++++- sp/src/game/server/ai_motor.h | 4 + sp/src/game/server/ai_navigator.cpp | 18 +++ sp/src/game/server/hl2/npc_fastzombie.cpp | 14 +- sp/src/game/shared/activitylist.cpp | 9 ++ sp/src/game/shared/ai_activity.h | 13 ++ 9 files changed, 290 insertions(+), 4 deletions(-) diff --git a/sp/src/game/server/ai_activity.cpp b/sp/src/game/server/ai_activity.cpp index 7e5f13f9..c467ddc4 100644 --- a/sp/src/game/server/ai_activity.cpp +++ b/sp/src/game/server/ai_activity.cpp @@ -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 } diff --git a/sp/src/game/server/ai_basenpc.cpp b/sp/src/game/server/ai_basenpc.cpp index d2ec72bf..170b5454 100644 --- a/sp/src/game/server/ai_basenpc.cpp +++ b/sp/src/game/server/ai_basenpc.cpp @@ -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 ); diff --git a/sp/src/game/server/ai_basenpc_schedule.cpp b/sp/src/game/server/ai_basenpc_schedule.cpp index cf425fd0..7fc1c13b 100644 --- a/sp/src/game/server/ai_basenpc_schedule.cpp +++ b/sp/src/game/server/ai_basenpc_schedule.cpp @@ -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" ); diff --git a/sp/src/game/server/ai_motor.cpp b/sp/src/game/server/ai_motor.cpp index 49f3d5f4..0db2c6c5 100644 --- a/sp/src/game/server/ai_motor.cpp +++ b/sp/src/game/server/ai_motor.cpp @@ -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 { @@ -314,7 +434,7 @@ AIMoveResult_t CAI_Motor::MoveClimbExecute( const Vector &climbDest, const Vecto climbDist = 0; const float climbTime = climbDist / climbSpeed; - + SetMoveInterval( GetMoveInterval() - climbTime ); SetLocalOrigin( climbDest ); @@ -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 : diff --git a/sp/src/game/server/ai_motor.h b/sp/src/game/server/ai_motor.h index d7f14293..b5f5d297 100644 --- a/sp/src/game/server/ai_motor.h +++ b/sp/src/game/server/ai_motor.h @@ -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 //--------------------------------- diff --git a/sp/src/game/server/ai_navigator.cpp b/sp/src/game/server/ai_navigator.cpp index d136c923..9d6ae06a 100644 --- a/sp/src/game/server/ai_navigator.cpp +++ b/sp/src/game/server/ai_navigator.cpp @@ -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 ) diff --git a/sp/src/game/server/hl2/npc_fastzombie.cpp b/sp/src/game/server/hl2/npc_fastzombie.cpp index 0e15e2a8..be53f344 100644 --- a/sp/src/game/server/hl2/npc_fastzombie.cpp +++ b/sp/src/game/server/hl2/npc_fastzombie.cpp @@ -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 ); } diff --git a/sp/src/game/shared/activitylist.cpp b/sp/src/game/shared/activitylist.cpp index ede1485b..6d3f3cf6 100644 --- a/sp/src/game/shared/activitylist.cpp +++ b/sp/src/game/shared/activitylist.cpp @@ -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" ); } diff --git a/sp/src/game/shared/ai_activity.h b/sp/src/game/shared/ai_activity.h index bfe3eb05..36d07ed3 100644 --- a/sp/src/game/shared/ai_activity.h +++ b/sp/src/game/shared/ai_activity.h @@ -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;