diff --git a/src/game/server/neo/bot/neo_bot_path_cost.cpp b/src/game/server/neo/bot/neo_bot_path_cost.cpp index a008bb235a..d68a9eba07 100644 --- a/src/game/server/neo/bot/neo_bot_path_cost.cpp +++ b/src/game/server/neo/bot/neo_bot_path_cost.cpp @@ -47,9 +47,192 @@ CNEOBotPathCost::CNEOBotPathCost(CNEOBot* me, RouteType routeType) m_bIgnoreReservations = !neo_bot_path_reservation_enable.GetBool(); m_bIgnoreHazards = (me->m_hCommandingPlayer.Get() != nullptr); m_bIgnoreVisibilityExposure = !neo_bot_path_visibility_exposure_enable.GetBool(); + m_vecDuckHullMins = VEC_DUCK_HULL_MIN_SCALED( me ); + m_vecDuckHullMaxs = VEC_DUCK_HULL_MAX_SCALED( me ); + m_bDucksAboveCrouchHeight = ( m_vecDuckHullMaxs.z - m_vecDuckHullMins.z ) > HumanCrouchHeight; m_bInAHurry = ( me->GetIntentionInterface()->ShouldHurry( me ) == ANSWER_YES ); } +//------------------------------------------------------------------------------------------------- +// A crouch area promises HumanCrouchHeight (55 u) of room, but Support ducks to 59 u and the Juggernaut to 75 u, +// so some crouch gaps in the mesh are ones they cannot get through + +static constexpr float NEO_DUCK_LANE_SPACING = 4.0f; // one lane per this much portal width, edge to edge +static constexpr float NEO_DUCK_LANE_REACH = 2.0f; // how far a lane runs into each area +static constexpr float NEO_DUCK_LANE_INSET = 0.5f; // lane ends keep this far inside their area + +// A crouch portal and a ducked hull height: the key of one lane verdict +struct NeoDuckLaneKey_t +{ + unsigned int m_fromId; + unsigned int m_toId; + int m_height; + + bool operator<( const NeoDuckLaneKey_t &other ) const + { + if ( m_fromId != other.m_fromId ) + { + return m_fromId < other.m_fromId; + } + + if ( m_toId != other.m_toId ) + { + return m_toId < other.m_toId; + } + + return m_height < other.m_height; + } +}; + +// The lane traces see only the world and static props, so a verdict holds while the navmesh does +static CUtlMap s_duckLaneVerdicts( DefLessFunc( NeoDuckLaneKey_t ) ); + +// nav_load and nav editing commands renumber areas without a level change, +// so the verdicts are dropped when the area count they were made on changes +static unsigned int s_nDuckLaneAreaCount = 0; + +class CNeoDuckLaneReset : public CAutoGameSystem +{ +public: + CNeoDuckLaneReset() : CAutoGameSystem( "CNeoDuckLaneReset" ) + { + } + + virtual void LevelShutdownPostEntity() override + { + s_duckLaneVerdicts.RemoveAll(); + } +}; + +static CNeoDuckLaneReset s_duckLaneReset; + +// The point of the area nearest (x, y), on the floor under the hull's footprint, +// which can lie a few units off the nav plane +static Vector NeoLaneEnd( const CNavArea *area, float x, float y, const Vector &vecMins, const Vector &vecMaxs ) +{ + const Vector nw = area->GetCorner( NORTH_WEST ); + const Vector se = area->GetCorner( SOUTH_EAST ); + const float flInsetX = MIN( NEO_DUCK_LANE_INSET, ( se.x - nw.x ) / 2.0f ); + const float flInsetY = MIN( NEO_DUCK_LANE_INSET, ( se.y - nw.y ) / 2.0f ); + Vector pos( clamp( x, nw.x + flInsetX, se.x - flInsetX ), clamp( y, nw.y + flInsetY, se.y - flInsetY ), 0.0f ); + pos.z = area->GetZ( pos.x, pos.y ); + + // a footprint-sized slab, 1 u thick, dropped from a step above the nav plane to a step below + CTraceFilterWorldAndPropsOnly filter; + trace_t tr; + UTIL_TraceHull( pos + Vector( 0.0f, 0.0f, StepHeight ), pos - Vector( 0.0f, 0.0f, StepHeight ), + Vector( vecMins.x, vecMins.y, 0.0f ), Vector( vecMaxs.x, vecMaxs.y, 1.0f ), MASK_PLAYERSOLID, &filter, &tr ); + if ( !tr.startsolid && tr.fraction < 1.0f ) + { + pos.z = tr.endpos.z; + } + + return pos; +} + +// Can the hull get from a to b? It is swept with its underside a step above the floor, +// so it ignores a lip or slope lower than a step, as the bot steps over it +static bool NeoLaneClear( const Vector &a, const Vector &b, const Vector &vecMins, const Vector &vecMaxs ) +{ + const Vector mins( vecMins.x, vecMins.y, StepHeight ); + const Vector maxs( vecMaxs.x, vecMaxs.y, vecMaxs.z - vecMins.z ); + const float flTop = MAX( a.z, b.z ); + const Vector legs[] = { a, Vector( a.x, a.y, flTop ), Vector( b.x, b.y, flTop ), b }; + const int nLegs = ARRAYSIZE( legs ); + + CTraceFilterWorldAndPropsOnly filter; + for ( int i = 0; i + 1 < nLegs; ++i ) + { + trace_t tr; + UTIL_TraceHull( legs[i], legs[i + 1], mins, maxs, MASK_PLAYERSOLID, &filter, &tr ); + if ( tr.startsolid || tr.fraction < 1.0f ) + { + return false; + } + } + + return true; +} + +// Does the hull fit through the portal from 'from' into 'to' on at least one lane? +// Lanes run across the portal, each from just inside one area to just inside the other +static bool NeoDuckLaneFits( const CNavArea *from, const CNavArea *to, const Vector &vecMins, const Vector &vecMaxs ) +{ + NavDirType dir = NUM_DIRECTIONS; + for ( int d = 0; d < NUM_DIRECTIONS; ++d ) + { + if ( from->IsConnected( to, (NavDirType)d ) ) + { + dir = (NavDirType)d; + break; + } + } + + if ( dir == NUM_DIRECTIONS ) + { + return true; + } + + Vector center; + float flHalfWidth; + from->ComputePortal( to, dir, ¢er, &flHalfWidth ); + + Vector2D across; + DirectionToVector2D( dir, &across ); + const Vector2D along( across.y != 0.0f ? 1.0f : 0.0f, across.x != 0.0f ? 1.0f : 0.0f ); + const float flSpan = MAX( 0.0f, flHalfWidth - NEO_DUCK_LANE_INSET ); + const int nLanes = 1 + (int)( 2.0f * flSpan / NEO_DUCK_LANE_SPACING ); + for ( int i = 0; i < nLanes; ++i ) + { + const float flOffset = ( nLanes == 1 ) ? 0.0f : -flSpan + 2.0f * flSpan * i / ( nLanes - 1 ); + const float x = center.x + along.x * flOffset; + const float y = center.y + along.y * flOffset; + const float dx = across.x * NEO_DUCK_LANE_REACH; + const float dy = across.y * NEO_DUCK_LANE_REACH; + const Vector a = NeoLaneEnd( from, x - dx, y - dy, vecMins, vecMaxs ); + const Vector b = NeoLaneEnd( to, x + dx, y + dy, vecMins, vecMaxs ); + if ( NeoLaneClear( a, b, vecMins, vecMaxs ) ) + { + return true; + } + } + + return false; +} + +// Is a level step into or out of a crouch area open to this ducked hull? Climbs and drops are not tested +static bool NeoDuckLaneOpen( const CNavArea *from, const CNavArea *to, const Vector &vecMins, const Vector &vecMaxs, + float flStepHeight ) +{ + if ( !from->HasAttributes( NAV_MESH_CROUCH ) && !to->HasAttributes( NAV_MESH_CROUCH ) ) + { + return true; + } + + if ( fabs( from->ComputeAdjacentConnectionHeightChange( to ) ) > flStepHeight ) + { + return true; + } + + if ( TheNavMesh->GetNavAreaCount() != s_nDuckLaneAreaCount ) + { + s_duckLaneVerdicts.RemoveAll(); + s_nDuckLaneAreaCount = TheNavMesh->GetNavAreaCount(); + } + + const float flHeight = vecMaxs.z - vecMins.z; + const NeoDuckLaneKey_t key = { from->GetID(), to->GetID(), (int)flHeight }; + const auto idx = s_duckLaneVerdicts.Find( key ); + if ( idx != s_duckLaneVerdicts.InvalidIndex() ) + { + return s_duckLaneVerdicts[idx]; + } + + const bool bOpen = NeoDuckLaneFits( from, to, vecMins, vecMaxs ); + s_duckLaneVerdicts.Insert( key, bOpen ); + return bOpen; +} + //------------------------------------------------------------------------------------------------- float CNEOBotPathCost::operator()(CNavArea* baseArea, CNavArea* fromArea, const CNavLadder* ladder, const CFuncElevator* elevator, float length) const { @@ -68,6 +251,13 @@ float CNEOBotPathCost::operator()(CNavArea* baseArea, CNavArea* fromArea, const return -1.0f; } + // Support and Juggernaut duck too tall for some crouch gaps + if ( m_bDucksAboveCrouchHeight && !ladder && !elevator + && !NeoDuckLaneOpen( fromArea, area, m_vecDuckHullMins, m_vecDuckHullMaxs, m_stepHeight ) ) + { + return -1.0f; + } + float hazardMultiplier = 1.0f; if ( !m_bIgnoreHazards && ( m_routeType == DEFAULT_ROUTE ) && CNEOBotPathReservations()->IsAreaHazardous(area->GetID(), m_me) ) { @@ -252,4 +442,4 @@ float CNEOBotPathCost::operator()(CNavArea* baseArea, CNavArea* fromArea, const } return cost + fromArea->GetCostSoFar(); -} +} \ No newline at end of file diff --git a/src/game/server/neo/bot/neo_bot_path_cost.h b/src/game/server/neo/bot/neo_bot_path_cost.h index 172df56a31..7f3139e2e6 100644 --- a/src/game/server/neo/bot/neo_bot_path_cost.h +++ b/src/game/server/neo/bot/neo_bot_path_cost.h @@ -34,4 +34,7 @@ class CNEOBotPathCost : public IPathCost float m_stepHeight; float m_maxJumpHeight; float m_maxDropHeight; + Vector m_vecDuckHullMins; + Vector m_vecDuckHullMaxs; + bool m_bDucksAboveCrouchHeight; // Support and Juggernaut, whose ducked hull is taller than a crouch area promises };