From b5dd18a34a7e12039e81f1527f26983fb87f1a1a Mon Sep 17 00:00:00 2001 From: Kit o' Rifty Date: Thu, 3 Aug 2023 00:25:07 -0700 Subject: [PATCH] Add more ways to collect Nav Areas (#42) * Added CNavArea.GetAdjacent/IncomingConnectionLength natives * Deprecate SurroundingAreasCollector in favor of AreasCollector --- extension/extension.cpp | 4 +- extension/extension.h | 4 +- extension/natives/nav.cpp | 68 ++++++- extension/natives/nav/area.cpp | 46 ++++- extension/toolsnav_mesh.h | 282 +++++++++++++++++++++++++++++ product.version | 2 +- scripting/include/cbasenpc/nav.inc | 64 ++++++- 7 files changed, 457 insertions(+), 13 deletions(-) diff --git a/extension/extension.cpp b/extension/extension.cpp index 9015cb2..a0cbf95 100644 --- a/extension/extension.cpp +++ b/extension/extension.cpp @@ -34,7 +34,7 @@ ConVar* sourcemod_version = nullptr; IBaseNPC_Tools* g_pBaseNPCTools = new BaseNPC_Tools_API; std::vector gNatives; -DEFINEHANDLEOBJ(SurroundingAreasCollector, CUtlVector< CNavArea* >); +DEFINEHANDLEOBJ(AreasCollector, CUtlVector< CNavArea* >); ConVar* g_cvDeveloper = nullptr; extern ConVar* NextBotSpeedLookAheadRange; @@ -102,7 +102,7 @@ bool CBaseNPCExt::SDK_OnLoad(char* error, size_t maxlength, bool late) { GETGAMEDATAOFFSET("CBaseEntity::Event_Killed", iOffset); SH_MANUALHOOK_RECONFIGURE(MEvent_Killed, iOffset, 0, 0); - CREATEHANDLETYPE(SurroundingAreasCollector); + CREATEHANDLETYPE(AreasCollector); sharesys->AddDependency(myself, "bintools.ext", true, true); sharesys->AddDependency(myself, "sdktools.ext", true, true); diff --git a/extension/extension.h b/extension/extension.h index dcf1485..cce738f 100644 --- a/extension/extension.h +++ b/extension/extension.h @@ -32,8 +32,8 @@ extern HandleType_t HANDLENAME(PluginPathFollower); extern HandleType_t HANDLENAME(PluginBotReply); extern HandleType_t HANDLENAME(PluginBotEntityFilter); -extern HandleType_t HANDLENAME(SurroundingAreasCollector); -extern HandleType_t HANDLENAME(TSurroundingAreasCollector); +extern HandleType_t HANDLENAME(AreasCollector); +extern HandleType_t HANDLENAME(TAreasCollector); extern HandleType_t g_CellArrayHandle; extern HandleType_t g_KeyValueType; diff --git a/extension/natives/nav.cpp b/extension/natives/nav.cpp index 570f6ef..d968adb 100644 --- a/extension/natives/nav.cpp +++ b/extension/natives/nav.cpp @@ -7,7 +7,7 @@ namespace natives::nav { -namespace surroundingareas { +namespace collector { inline CUtlVector* Get(IPluginContext* context, const cell_t param) { HandleSecurity security; @@ -15,7 +15,7 @@ inline CUtlVector* Get(IPluginContext* context, const cell_t param) { security.pIdentity = myself->GetIdentity(); Handle_t hndlObject = static_cast(param); CUtlVector* collector = nullptr; - READHANDLE(hndlObject, SurroundingAreasCollector, collector) + READHANDLE(hndlObject, AreasCollector, collector) return collector; } @@ -36,6 +36,8 @@ cell_t GetElement(IPluginContext* context, const cell_t* params) { void setup(std::vector& natives) { sp_nativeinfo_t list[] = { + {"AreasCollector.Count", GetCount}, + {"AreasCollector.Get", GetElement}, {"SurroundingAreasCollector.Count", GetCount}, {"SurroundingAreasCollector.Get", GetElement}, }; @@ -94,10 +96,65 @@ cell_t GetNavAreaByID(IPluginContext* context, const cell_t* params) { return PtrToPawnAddress(ToolsNavMesh->GetNavAreaByID(id)); } +class CCollectorAddToTail { +public: + CCollectorAddToTail( CUtlVector* vec ) : m_vec( vec ) {} + bool operator() ( CNavArea *area ) { m_vec->AddToTail(area); return true; } + +private: + CUtlVector* m_vec; +}; + cell_t CollectSurroundingAreas(IPluginContext* context, const cell_t* params) { CUtlVector *pCollector = new CUtlVector; CollectSurroundingAreas( pCollector, (CNavArea*)PawnAddressToPtr(params[2]), sp_ctof(params[3]), sp_ctof(params[4]), sp_ctof(params[5])); - return CREATEHANDLE(SurroundingAreasCollector, pCollector); + return CREATEHANDLE(AreasCollector, pCollector); +} + +cell_t CollectAreasOverlappingExtent(IPluginContext* context, const cell_t* params) { + Vector lo; Vector hi; cell_t* addr; + context->LocalToPhysAddr(params[2], &addr); + PawnVectorToVector(addr, lo); + context->LocalToPhysAddr(params[3], &addr); + PawnVectorToVector(addr, hi); + + Extent extent; + extent.Init(); + extent.lo = lo; + extent.hi = hi; + + CUtlVector *pCollector = new CUtlVector; + CCollectorAddToTail addToTail(pCollector); + + ToolsNavMesh->ForAllAreasOverlappingExtent(addToTail, extent); + return CREATEHANDLE(AreasCollector, pCollector); +} + +cell_t CollectAreasInRadius(IPluginContext* context, const cell_t* params) { + cell_t* posAddr; Vector pos; + context->LocalToPhysAddr(params[2], &posAddr); + PawnVectorToVector(posAddr, pos); + + float radius = sp_ctof(params[3]); + + CUtlVector *pCollector = new CUtlVector; + CCollectorAddToTail addToTail(pCollector); + + ToolsNavMesh->ForAllAreasInRadius(addToTail, pos, radius); + return CREATEHANDLE(AreasCollector, pCollector); +} + +cell_t CollectAreasAlongLine(IPluginContext* context, const cell_t* params) { + CNavArea* startArea = (CNavArea*)PawnAddressToPtr(params[2]); + CNavArea* endArea = (CNavArea*)PawnAddressToPtr(params[3]); + cell_t* reachedEndAddr; + context->LocalToPhysAddr(params[4], &reachedEndAddr); + + CUtlVector *pCollector = new CUtlVector; + CCollectorAddToTail addToTail(pCollector); + + *reachedEndAddr = ToolsNavMesh->ForAllAreasAlongLine(addToTail, startArea, endArea) ? 1 : 0; + return CREATEHANDLE(AreasCollector, pCollector); } class SMPathCost : public IPathCost @@ -199,7 +256,7 @@ cell_t BuildPath(IPluginContext* context, const cell_t* params) { void setup(std::vector& natives) { area::setup(natives); - surroundingareas::setup(natives); + collector::setup(natives); tf::nav::setup(natives); @@ -210,6 +267,9 @@ void setup(std::vector& natives) { {"CNavMesh.IsOutOfDate", IsOutOfDate}, {"CNavMesh.GetNavAreaCount", GetNavAreaCount}, {"CNavMesh.CollectSurroundingAreas", CollectSurroundingAreas}, + {"CNavMesh.CollectAreasOverlappingExtent", CollectAreasOverlappingExtent}, + {"CNavMesh.CollectAreasInRadius", CollectAreasInRadius}, + {"CNavMesh.CollectAreasAlongLine", CollectAreasAlongLine}, {"CNavMesh.GetNavAreaByID", GetNavAreaByID}, {"CNavMesh.GetNearestNavArea", GetNearestNavArea}, {"CNavMesh.BuildPath", BuildPath}, diff --git a/extension/natives/nav/area.cpp b/extension/natives/nav/area.cpp index ac0c434..36fff52 100644 --- a/extension/natives/nav/area.cpp +++ b/extension/natives/nav/area.cpp @@ -571,6 +571,26 @@ cell_t GetAdjacentArea(IPluginContext* context, const cell_t* params) { return PtrToPawnAddress(area->GetAdjacentArea(dir, params[3])); } +cell_t GetAdjacentLength(IPluginContext* context, const cell_t* params) { + auto area = Get(context, params[1]); + if (!area) { + return 0; + } + + NavDirType dir = (NavDirType)params[2]; + if (dir < 0 || dir >= NUM_DIRECTIONS) { + return context->ThrowNativeError("Invalid direction %d", dir); + } + + int i = params[3]; + const NavConnectVector *adjacent = area->GetAdjacentAreas(dir); + if (i < 0 || i >= adjacent->Count()) { + return context->ThrowNativeError("Array index %d is out of bounds (array size: %d)", i, adjacent->Count()); + } + + return sp_ftoc(adjacent->Element(i).length); +} + cell_t GetIncomingConnectionsCount(IPluginContext* context, const cell_t* params) { auto area = Get(context, params[1]); if (!area) { @@ -583,7 +603,7 @@ cell_t GetIncomingConnectionsCount(IPluginContext* context, const cell_t* params return area->GetIncomingConnections(dir)->Count(); } -cell_t GetIncomingConnections(IPluginContext* context, const cell_t* params) { +cell_t GetIncomingConnection(IPluginContext* context, const cell_t* params) { auto area = Get(context, params[1]); if (!area) { return 0; @@ -602,6 +622,26 @@ cell_t GetIncomingConnections(IPluginContext* context, const cell_t* params) { return PtrToPawnAddress(incoming->Element(i).area); } +cell_t GetIncomingConnectionLength(IPluginContext* context, const cell_t* params) { + auto area = Get(context, params[1]); + if (!area) { + return 0; + } + + NavDirType dir = (NavDirType)params[2]; + if (dir < 0 || dir >= NUM_DIRECTIONS) { + return context->ThrowNativeError("Invalid direction %d", dir); + } + + int i = params[3]; + const NavConnectVector *incoming = area->GetIncomingConnections(dir); + if (i < 0 || i >= incoming->Count()) { + return context->ThrowNativeError("Array index %d is out of bounds (array size: %d)", i, incoming->Count()); + } + + return sp_ftoc(incoming->Element(i).length); +} + cell_t IsEdge(IPluginContext* context, const cell_t* params) { auto area = Get(context, params[1]); if (!area) { @@ -881,6 +921,7 @@ void setup(std::vector& natives) { {"CNavArea.GetCenter", GetCenter}, {"CNavArea.GetAdjacentCount", GetAdjacentCount}, {"CNavArea.GetAdjacentArea", GetAdjacentArea}, + {"CNavArea.GetAdjacentLength", GetAdjacentLength}, {"CNavArea.IsConnected", IsConnected}, {"CNavArea.IsOverlappingX", IsOverlappingX}, {"CNavArea.IsOverlappingY", IsOverlappingY}, @@ -889,7 +930,8 @@ void setup(std::vector& natives) { {"CNavArea.IsOverlappingExtent", IsOverlappingExtent}, {"CNavArea.GetExtent", GetExtent}, {"CNavArea.GetIncomingConnectionCount", GetIncomingConnectionsCount}, - {"CNavArea.GetIncomingConnection", GetIncomingConnections}, + {"CNavArea.GetIncomingConnection", GetIncomingConnection}, + {"CNavArea.GetIncomingConnectionLength", GetIncomingConnectionLength}, {"CNavArea.IsEdge", IsEdge}, {"CNavArea.Contains", Contains}, {"CNavArea.ContainsPoint", ContainsPoint}, diff --git a/extension/toolsnav_mesh.h b/extension/toolsnav_mesh.h index 89ad84a..bebd57b 100644 --- a/extension/toolsnav_mesh.h +++ b/extension/toolsnav_mesh.h @@ -114,6 +114,288 @@ class CToolsNavMesh } return true; } + + template < typename Functor > + bool ForAllAreasInRadius( Functor &func, const Vector &pos, float radius ) + { + // use a unique marker for this method, so it can be used within a SearchSurroundingArea() call + static unsigned int searchMarker = RandomInt(0, 1024*1024 ); + + ++searchMarker; + + if ( searchMarker == 0 ) + { + ++searchMarker; + } + + + // get list in cell that contains position + int originX = WorldToGridX( pos.x ); + int originY = WorldToGridY( pos.y ); + int shiftLimit = ceil( radius / m_gridCellSize ); + float radiusSq = radius * radius; + if ( radius == 0.0f ) + { + shiftLimit = MAX( m_gridSizeX, m_gridSizeY ); // range 0 means all areas + } + + for( int x = originX - shiftLimit; x <= originX + shiftLimit; ++x ) + { + if ( x < 0 || x >= m_gridSizeX ) + continue; + + for( int y = originY - shiftLimit; y <= originY + shiftLimit; ++y ) + { + if ( y < 0 || y >= m_gridSizeY ) + continue; + + int iGrid = x + y * m_gridSizeX; + + // find closest area in this cell + for (auto it = m_grid[iGrid].begin(); it != m_grid[iGrid].end(); it++) + { + CNavArea* area = (*it)->GetArea(); + + // skip if we've already visited this area + if ( area->m_nearNavSearchMarker == searchMarker ) + continue; + + // mark as visited + area->m_nearNavSearchMarker = searchMarker; + + float distSq = ( area->GetCenter() - pos ).LengthSqr(); + + if ( ( distSq <= radiusSq ) || ( radiusSq == 0 ) ) + { + if ( func( area ) == false ) + return false; + } + } + } + } + return true; + } + + //--------------------------------------------------------------------------------------------------------------- + /* + * Step through nav mesh along line between startArea and endArea. + * Return true if enumeration reached endArea, false if doesn't reach it (no mesh between, bad connection, etc) + */ + template < typename Functor > + bool ForAllAreasAlongLine( Functor &func, CNavArea *startArea, CNavArea *endArea ) + { + if ( !startArea || !endArea ) + return false; + + if ( startArea == endArea ) + { + func( startArea ); + return true; + } + + Vector start = startArea->GetCenter(); + Vector end = endArea->GetCenter(); + + Vector to = end - start; + float range = to.NormalizeInPlace(); + + const float epsilon = 0.00001f; + + if ( range < epsilon ) + { + func( startArea ); + return true; + } + + if ( abs( to.x ) < epsilon ) + { + NavDirType dir = ( to.y < 0.0f ) ? NORTH : SOUTH; + + CNavArea *area = startArea; + while( area ) + { + func( area ); + + if ( area == endArea ) + return true; + + const NavConnectVector *adjVector = area->GetAdjacentAreas( dir ); + + area = NULL; + + for( int i=0; iCount(); ++i ) + { + CNavArea *adjArea = adjVector->Element(i).area; + + const Vector &adjOrigin = adjArea->GetCorner( NORTH_WEST ); + + if ( adjOrigin.x <= start.x && adjOrigin.x + adjArea->GetSizeX() >= start.x ) + { + area = adjArea; + break; + } + } + } + + return false; + } + else if ( abs( to.y ) < epsilon ) + { + NavDirType dir = ( to.x < 0.0f ) ? WEST : EAST; + + CNavArea *area = startArea; + while( area ) + { + func( area ); + + if ( area == endArea ) + return true; + + const NavConnectVector *adjVector = area->GetAdjacentAreas( dir ); + + area = NULL; + + for( int i=0; iCount(); ++i ) + { + CNavArea *adjArea = adjVector->Element(i).area; + + const Vector &adjOrigin = adjArea->GetCorner( NORTH_WEST ); + + if ( adjOrigin.y <= start.y && adjOrigin.y + adjArea->GetSizeY() >= start.y ) + { + area = adjArea; + break; + } + } + } + + return false; + } + + + CNavArea *area = startArea; + + while( area ) + { + func( area ); + + if ( area == endArea ) + return true; + + const Vector &origin = area->GetCorner( NORTH_WEST ); + float xMin = origin.x; + float xMax = xMin + area->GetSizeX(); + float yMin = origin.y; + float yMax = yMin + area->GetSizeY(); + + // clip ray to area + Vector exit; + NavDirType edge = NUM_DIRECTIONS; + + if ( to.x < 0.0f ) + { + // find Y at west edge intersection + float t = ( xMin - start.x ) / ( end.x - start.x ); + if ( t > 0.0f && t < 1.0f ) + { + float y = start.y + t * ( end.y - start.y ); + if ( y >= yMin && y <= yMax ) + { + // intersects this edge + exit.x = xMin; + exit.y = y; + edge = WEST; + } + } + } + else + { + // find Y at east edge intersection + float t = ( xMax - start.x ) / ( end.x - start.x ); + if ( t > 0.0f && t < 1.0f ) + { + float y = start.y + t * ( end.y - start.y ); + if ( y >= yMin && y <= yMax ) + { + // intersects this edge + exit.x = xMax; + exit.y = y; + edge = EAST; + } + } + } + + if ( edge == NUM_DIRECTIONS ) + { + if ( to.y < 0.0f ) + { + // find X at north edge intersection + float t = ( yMin - start.y ) / ( end.y - start.y ); + if ( t > 0.0f && t < 1.0f ) + { + float x = start.x + t * ( end.x - start.x ); + if ( x >= xMin && x <= xMax ) + { + // intersects this edge + exit.x = x; + exit.y = yMin; + edge = NORTH; + } + } + } + else + { + // find X at south edge intersection + float t = ( yMax - start.y ) / ( end.y - start.y ); + if ( t > 0.0f && t < 1.0f ) + { + float x = start.x + t * ( end.x - start.x ); + if ( x >= xMin && x <= xMax ) + { + // intersects this edge + exit.x = x; + exit.y = yMax; + edge = SOUTH; + } + } + } + } + + if ( edge == NUM_DIRECTIONS ) + break; + + const NavConnectVector *adjVector = area->GetAdjacentAreas( edge ); + + area = NULL; + + for( int i=0; iCount(); ++i ) + { + CNavArea *adjArea = adjVector->Element(i).area; + + const Vector &adjOrigin = adjArea->GetCorner( NORTH_WEST ); + + if ( edge == NORTH || edge == SOUTH ) + { + if ( adjOrigin.x <= exit.x && adjOrigin.x + adjArea->GetSizeX() >= exit.x ) + { + area = adjArea; + break; + } + } + else + { + if ( adjOrigin.y <= exit.y && adjOrigin.y + adjArea->GetSizeY() >= exit.y ) + { + area = adjArea; + break; + } + } + } + } + + return false; + } + private: void AllocateGrid(float minX, float maxX, float minY, float maxY); std::int32_t WorldToGridX(float wx) const; diff --git a/product.version b/product.version index abb1658..81c871d 100644 --- a/product.version +++ b/product.version @@ -1 +1 @@ -1.9.0 \ No newline at end of file +1.10.0 diff --git a/scripting/include/cbasenpc/nav.inc b/scripting/include/cbasenpc/nav.inc index e3d351a..c15d3a2 100644 --- a/scripting/include/cbasenpc/nav.inc +++ b/scripting/include/cbasenpc/nav.inc @@ -159,6 +159,15 @@ enum CNavMesh }; methodmap SurroundingAreasCollector < Handle +{ + #pragma deprecated Use AreasCollector.Get() instead. + public native CNavArea Get(int index); + + #pragma deprecated Use AreasCollector.Count() instead. + public native int Count(); +}; + +methodmap AreasCollector < SurroundingAreasCollector { public native CNavArea Get(int index); public native int Count(); @@ -238,11 +247,41 @@ methodmap CNavMesh * @param travelDistanceLimit Maximum travel distance * @param maxStepUpLimit Maximum step height * @param maxDropDownLimit Maximum drop down height limit - * @return SurroundingAreasCollector iterator. You must delete this + * @return AreasCollector iterator. You must delete this * when you are done with it. */ - public native SurroundingAreasCollector CollectSurroundingAreas(CNavArea startArea, float travelDistanceLimit = 1500.0, float maxStepUpLimit = 18.0, float maxDropDownLimit = 100.0); + public native AreasCollector CollectSurroundingAreas(CNavArea startArea, float travelDistanceLimit = 1500.0, float maxStepUpLimit = 18.0, float maxDropDownLimit = 100.0); + /** + * Collects all areas that overlap the box defined by the given `mins` and `maxs` + * vectors. + * + * @param mins World vector of the minimum corner of the box. + * @param maxs World vector of the maximum corner of the box. + * @return AreasCollector iterator. You must delete this when you are done with it. + */ + public native AreasCollector CollectAreasOverlappingExtent(const float mins[3], const float maxs[3]); + + /** + * Collects all areas whose center is within the radius of the given position. + * + * @param pos Position to search for. + * @param radius Radius + * @return AreasCollector iterator. You must delete this when you are done with it. + */ + public native AreasCollector CollectAreasInRadius(const float pos[3], float radius); + + /** + * Steps through the nav mesh and collects all areas along the line between `startArea` and `endArea`. + * + * @param startArea Start area + * @param endArea End area + * @param reachedEnd Optional. Set to true if collection reached the end area, false if it doesn't reach it + * (no mesh between, bad connection, etc). + * @return AreasCollector iterator. You must delete this when you are done with it. + */ + public native AreasCollector CollectAreasAlongLine(CNavArea startArea, CNavArea endArea, bool &reachedEnd = false); + /** * Given an ID, return the associated area. * @@ -438,6 +477,7 @@ methodmap CNavArea /** * Updates the blocked status of the area. * + * @note This does nothing in Team Fortress 2. * @param force If true, will ignore the throttle timer and update * @param teamID Team to check for blocked status for */ @@ -628,6 +668,16 @@ methodmap CNavArea */ public native CNavArea GetAdjacentArea(NavDirType dir, int i); + /** + * Returns the length of the connection to the adjacent area. + * Length is calculated by distance between the areas' centers. + * + * @param dir Direction + * @param i Index in list + * @return Length of connection + */ + public native float GetAdjacentLength(NavDirType dir, int i); + /** * Pushes a list of areas that this area has an outgoing connection to in the given direction. * @@ -673,6 +723,16 @@ methodmap CNavArea */ public native CNavArea GetIncomingConnection(NavDirType dir, int i); + /** + * Returns the length of the connection from the incoming connection. + * Length is calculated by distance between the areas' centers. + * + * @param dir Direction + * @param i Index in list + * @return Length of connection + */ + public native float GetIncomingConnectionLength(NavDirType dir, int i); + /** * Pushes a list of areas that this area has an outgoing connection to in the given direction. *