diff --git a/src/game/server/neo/bot/behavior/neo_bot_jgr_juggernaut.cpp b/src/game/server/neo/bot/behavior/neo_bot_jgr_juggernaut.cpp index e8545d6c0c..a9c7817207 100644 --- a/src/game/server/neo/bot/behavior/neo_bot_jgr_juggernaut.cpp +++ b/src/game/server/neo/bot/behavior/neo_bot_jgr_juggernaut.cpp @@ -2,7 +2,6 @@ #include "neo_player.h" #include "neo_gamerules.h" #include "bot/neo_bot.h" -#include "bot/neo_bot_path_compute.h" #include "bot/behavior/neo_bot_jgr_juggernaut.h" #include "bot/behavior/neo_bot_retreat_to_cover.h" #include "weapon_balc.h" @@ -70,7 +69,7 @@ void CNEOBotJgrJuggernaut::RecomputeSeekPath( CNEOBot *me ) m_vGoalPos = pTargetSpawn->GetAbsOrigin(); m_bGoingToTargetEntity = false; - if (CNEOBotPathCompute(me, m_path, m_vGoalPos, DEFAULT_ROUTE) && m_path.IsValid() && m_path.GetResult() == Path::COMPLETE_PATH) + if (TryPathToRoamGoal(me, m_vGoalPos)) { return; } diff --git a/src/game/server/neo/bot/behavior/neo_bot_retreat_to_cover.cpp b/src/game/server/neo/bot/behavior/neo_bot_retreat_to_cover.cpp index 218b61624f..7388fa3b9f 100644 --- a/src/game/server/neo/bot/behavior/neo_bot_retreat_to_cover.cpp +++ b/src/game/server/neo/bot/behavior/neo_bot_retreat_to_cover.cpp @@ -354,6 +354,20 @@ ActionResult< CNEOBot > CNEOBotRetreatToCover::Update( CNEOBot *me, float interv if ( !m_path.IsValid() ) { CNEOBotPathCompute( me, m_path, m_coverArea->GetCenter(), RETREAT_ROUTE ); + + // Cover is chosen where the retreat starts; after a drop off a ledge no route + // may reach it any more, so choose again from here + if ( !CNEOBotPathReachesGoal( m_path ) ) + { + CNavArea *pUnreachableArea = m_coverArea; + m_coverArea = FindCoverArea( me ); + if ( m_coverArea == NULL || m_coverArea == pUnreachableArea ) + { + return Done( "My cover is out of reach, and there is no other cover available!" ); + } + + CNEOBotPathCompute( me, m_path, m_coverArea->GetCenter(), RETREAT_ROUTE ); + } } m_path.Update( me ); diff --git a/src/game/server/neo/bot/behavior/neo_bot_seek_and_destroy.cpp b/src/game/server/neo/bot/behavior/neo_bot_seek_and_destroy.cpp index 8945695c6c..3f0e7ea161 100644 --- a/src/game/server/neo/bot/behavior/neo_bot_seek_and_destroy.cpp +++ b/src/game/server/neo/bot/behavior/neo_bot_seek_and_destroy.cpp @@ -106,8 +106,7 @@ bool CNEOBotSeekAndDestroy::TryPathToCombatSound( CNEOBot *me ) m_combatSoundCommitTimer.Start( sv_neo_bot_seek_and_destroy_combat_sound_commit_time.GetFloat() ); } - if ( CNEOBotPathCompute( me, m_path, m_vCombatSoundSpot, DEFAULT_ROUTE ) - && m_path.IsValid() && m_path.GetResult() == Path::COMPLETE_PATH ) + if ( TryPathToRoamGoal( me, m_vCombatSoundSpot ) ) { m_vGoalPos = m_vCombatSoundSpot; m_bGoingToTargetEntity = false; @@ -128,6 +127,15 @@ bool CNEOBotSeekAndDestroy::TryPathToCombatSound( CNEOBot *me ) } +//--------------------------------------------------------------------------------------------- +// Roam goals are picked at random, so skip one that no route reaches instead of walking at it. A refused +// attempt stays in m_path: the wander loop's last resort keeps it. +bool CNEOBotSeekAndDestroy::TryPathToRoamGoal( CNEOBot *me, const Vector &vGoal ) +{ + return CNEOBotPathCompute( me, m_path, vGoal, DEFAULT_ROUTE ) && CNEOBotPathReachesGoal( m_path ); +} + + //--------------------------------------------------------------------------------------------- CNEOBotSeekAndDestroy::CNEOBotSeekAndDestroy( float duration ) { @@ -592,7 +600,7 @@ void CNEOBotSeekAndDestroy::RecomputeSeekPath( CNEOBot *me ) m_hTargetEntity = pSpawns[RandomInt( 0, pSpawns.Size() - 1 )]; m_bGoingToTargetEntity = true; m_vGoalPos = m_hTargetEntity->WorldSpaceCenter(); - if ( CNEOBotPathCompute( me, m_path, m_vGoalPos, DEFAULT_ROUTE ) && m_path.IsValid() && m_path.GetResult() == Path::COMPLETE_PATH ) + if ( TryPathToRoamGoal( me, m_vGoalPos ) ) return; } } @@ -604,11 +612,12 @@ void CNEOBotSeekAndDestroy::RecomputeSeekPath( CNEOBot *me ) Vector vWanderPoint = TheNavAreas[RandomInt( 0, TheNavAreas.Size() - 1 )]->GetCenter(); m_vGoalPos = vWanderPoint; - if ( CNEOBotPathCompute( me, m_path, vWanderPoint, DEFAULT_ROUTE ) ) + if ( TryPathToRoamGoal( me, vWanderPoint ) ) return; } - m_path.Invalidate(); + // No wander point has a route either (a pocket, or hazards all round): keep the last one's partial or + // straight-line path rather than stand still } diff --git a/src/game/server/neo/bot/behavior/neo_bot_seek_and_destroy.h b/src/game/server/neo/bot/behavior/neo_bot_seek_and_destroy.h index a442d82f53..585322de63 100644 --- a/src/game/server/neo/bot/behavior/neo_bot_seek_and_destroy.h +++ b/src/game/server/neo/bot/behavior/neo_bot_seek_and_destroy.h @@ -36,6 +36,7 @@ class CNEOBotSeekAndDestroy : public Action< CNEOBot > virtual void RecomputeSeekPath( CNEOBot *me ); bool TryPathToCombatSound( CNEOBot *me ); + bool TryPathToRoamGoal( CNEOBot *me, const Vector &vGoal ); virtual bool IsSeekGoalAnObjective() const { return false; } PathFollower m_path; diff --git a/src/game/server/neo/bot/neo_bot_path_compute.cpp b/src/game/server/neo/bot/neo_bot_path_compute.cpp index ff4e1457c3..66330d70ee 100644 --- a/src/game/server/neo/bot/neo_bot_path_compute.cpp +++ b/src/game/server/neo/bot/neo_bot_path_compute.cpp @@ -69,6 +69,24 @@ bool CNEOBotPathCompute(CNEOBot* bot, PathFollower& path, const Vector& goal, Ro return false; } +// When no route reaches the goal and the search got no nearer to it than the bot's own area, +// Path::Compute falls back to a straight line labeled COMPLETE_PATH: two segments in two areas +bool CNEOBotPathReachesGoal(const PathFollower& path) +{ + if (!path.IsValid() || path.GetResult() != Path::COMPLETE_PATH) + { + return false; + } + + const Path::Segment* pFirst = path.FirstSegment(); + const Path::Segment* pLast = path.LastSegment(); + + // A goal inside the bot's own area also gives a two-segment path, + // so where the areas nearest the bot and the goal differ (stacked or overlapping areas) it reads as the fallback + const bool bStraightLineFallback = (path.NextSegment(pFirst) == pLast) && (pFirst->area != pLast->area); + return !bStraightLineFallback; +} + bool CNEOBotPathUpdateChase(CNEOBot* bot, ChasePath& path, CBaseEntity* subject, RouteType route, Vector* pPredictedSubjectPos) { CNEOBotPathCost cost_with_reservations(bot, route); diff --git a/src/game/server/neo/bot/neo_bot_path_compute.h b/src/game/server/neo/bot/neo_bot_path_compute.h index 5fea6b7c47..61bb8be2db 100644 --- a/src/game/server/neo/bot/neo_bot_path_compute.h +++ b/src/game/server/neo/bot/neo_bot_path_compute.h @@ -18,6 +18,10 @@ bool CNEOBotPathCompute bool requireGoalArea = false ); +// True if the path reaches its goal: complete, and not the straight-line fallback +// Path::Compute builds when no route reaches the goal +bool CNEOBotPathReachesGoal(const PathFollower& path); + bool CNEOBotPathUpdateChase ( CNEOBot *bot,