From 57a6053758433426801723fea6359dff1c31deb0 Mon Sep 17 00:00:00 2001 From: Alan Shen Date: Wed, 14 Oct 2026 19:05:29 -0600 Subject: [PATCH] Break a vent cover in a crawl gap on the bot's path A bot whose path runs through a crouch-height gap closed by a func_breakable vent cover never clears it: the check that nothing stands in front of the breakable sweeps the standing body, which meets the gap's lintel first, so the vent is dropped and the bot presses against it until the stuck monitor gives up. Sweep a crouched body when the standing one meets something else first. The look for breakables also stopped at a drop segment, so a bot leaving a duct by a drop had no path line to sweep. A drop's ledge is walked at the height of the floor above before the fall, so that leg now counts for breakables (the prop detour keeps ground legs only). Seen on ntre_rise_ctg, where ladders 3 and 4 stand in shafts entered through such vents: no bot ever climbed either before this. --- .../server/neo/bot/neo_bot_path_obstacles.cpp | 59 +++++++++++++++---- 1 file changed, 46 insertions(+), 13 deletions(-) diff --git a/src/game/server/neo/bot/neo_bot_path_obstacles.cpp b/src/game/server/neo/bot/neo_bot_path_obstacles.cpp index a5763938c4..254885102e 100644 --- a/src/game/server/neo/bot/neo_bot_path_obstacles.cpp +++ b/src/game/server/neo/bot/neo_bot_path_obstacles.cpp @@ -84,6 +84,26 @@ static void GetLineBounds( const CUtlVector< Vector > &line, float range, Vector } +//---------------------------------------------------------------------------------------------------------------- +// Return true if the body, walking the line up to the end of the given leg, meets the entity before anything else, +// passing through players, who move +static bool IsMetFirst( INextBot *bot, const BodyBox_t &body, const CUtlVector< Vector > &line, int lastLeg, CBaseEntity *entity ) +{ + CTraceFilterNoNPCsOrPlayer filter( bot->GetEntity(), COLLISION_GROUP_NONE ); + for ( int leg = 0; leg <= lastLeg; ++leg ) + { + trace_t result; + UTIL_TraceHull( line[ leg ], line[ leg + 1 ], body.mins, body.maxs, MASK_PLAYERSOLID, &filter, &result ); + if ( result.DidHit() ) + { + return result.m_pEnt == entity; + } + } + + return false; +} + + //---------------------------------------------------------------------------------------------------------------- // The breakable the body meets first walking the line, within PATH_OBSTACLE_BREAKABLE_RANGE, // when nothing else stands in front of it @@ -130,25 +150,32 @@ static CBaseEntity *FindBreakableInWay( INextBot *bot, const BodyBox_t &body, co } // what the body meets first on the way may be a crate, a door or a wall in front of the breakable: - // keep the breakable only if the body reaches it first, passing through players, who move - CTraceFilterNoNPCsOrPlayer filter( bot->GetEntity(), COLLISION_GROUP_NONE ); - for ( int leg = 0; leg <= nearestLeg; ++leg ) + // keep the breakable only if the body reaches it first + if ( IsMetFirst( bot, body, line, nearestLeg, nearest ) ) { - trace_t result; - UTIL_TraceHull( line[ leg ], line[ leg + 1 ], body.mins, body.maxs, MASK_PLAYERSOLID, &filter, &result ); - if ( result.DidHit() ) - { - return ( result.m_pEnt == nearest ) ? nearest : NULL; - } + return nearest; } - return NULL; + // or reaches it crouched, since a vent cover in a crawl gap sits under a lintel the standing body meets first + BodyBox_t crouched = body; + crouched.maxs.z = bot->GetBodyInterface()->GetCrouchHullHeight(); + return IsMetFirst( bot, crouched, line, nearestLeg, nearest ) ? nearest : NULL; } +//---------------------------------------------------------------------------------------------------------------- +// Where the path ahead ends: at the first segment not walked on the ground, +// or at the ledge of a drop, which is walked at the height of the floor above before the fall +enum PathAheadEnd_t +{ + PATH_AHEAD_TO_GROUND_END, + PATH_AHEAD_TO_DROP_LEDGE, +}; + //---------------------------------------------------------------------------------------------------------------- // The path ahead as a line from the bot's feet, over walked segments only, up to the range, // and, given a list for them, the segment each point of the line belongs to -static void GetPathAhead( const PathFollower &path, const Vector &feet, float range, CUtlVector< Vector > *line, CUtlVector< const Path::Segment * > *segments = NULL ) +static void GetPathAhead( const PathFollower &path, const Vector &feet, float range, CUtlVector< Vector > *line, + CUtlVector< const Path::Segment * > *segments = NULL, PathAheadEnd_t end = PATH_AHEAD_TO_GROUND_END ) { line->AddToTail( feet ); if ( segments ) @@ -159,7 +186,8 @@ static void GetPathAhead( const PathFollower &path, const Vector &feet, float ra float length = 0.0f; for ( const Path::Segment *seg = path.GetCurrentGoal(); seg && length < range; seg = path.NextSegment( seg ) ) { - if ( seg->ladder || seg->type != Path::ON_GROUND ) + const bool isDropLedge = ( end == PATH_AHEAD_TO_DROP_LEDGE && seg->type == Path::DROP_DOWN ); + if ( seg->ladder || ( seg->type != Path::ON_GROUND && !isDropLedge ) ) { break; } @@ -179,6 +207,10 @@ static void GetPathAhead( const PathFollower &path, const Vector &feet, float ra length += legLength; line->AddToTail( seg->pos ); + if ( isDropLedge ) + { + break; + } } } @@ -358,7 +390,8 @@ void CNEOBotPathObstacles::LookForBreakable( INextBot *bot, const PathFollower & m_breakable = NULL; CUtlVector< Vector > line; - GetPathAhead( path, bot->GetLocomotionInterface()->GetFeet(), PATH_OBSTACLE_BREAKABLE_RANGE, &line ); + // a vent cover can block the way to a drop's ledge, such as out of a crawl duct + GetPathAhead( path, bot->GetLocomotionInterface()->GetFeet(), PATH_OBSTACLE_BREAKABLE_RANGE, &line, NULL, PATH_AHEAD_TO_DROP_LEDGE ); if ( line.Count() < 2 ) { return;