Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
192 changes: 191 additions & 1 deletion src/game/server/neo/bot/neo_bot_path_cost.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<NeoDuckLaneKey_t, bool> 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, &center, &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
{
Expand All @@ -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) )
{
Expand Down Expand Up @@ -252,4 +442,4 @@ float CNEOBotPathCost::operator()(CNavArea* baseArea, CNavArea* fromArea, const
}

return cost + fromArea->GetCostSoFar();
}
}
3 changes: 3 additions & 0 deletions src/game/server/neo/bot/neo_bot_path_cost.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
};
Loading