mirror of
https://github.com/ENSL/NS.git
synced 2024-12-01 16:51:43 +00:00
7adeda4113
Bots will no longer see cloaked players, and have a chance based on cloak level, movement and size of the players. Bots will also sneak when approaching enemies while cloaked.
9034 lines
No EOL
264 KiB
C++
9034 lines
No EOL
264 KiB
C++
//
|
|
// EvoBot - Neoptolemus' Natural Selection bot, based on Botman's HPB bot template
|
|
//
|
|
// bot_navigation.cpp
|
|
//
|
|
// Handles all bot path finding and movement
|
|
//
|
|
|
|
#include "AvHAINavigation.h"
|
|
#include "AvHAIMath.h"
|
|
#include "AvHAIPlayerUtil.h"
|
|
#include "AvHAIHelper.h"
|
|
#include "AvHAIPlayerManager.h"
|
|
#include "AvHAITactical.h"
|
|
#include "AvHAITask.h"
|
|
#include "AvHAIWeaponHelper.h"
|
|
#include "AvHAIConfig.h"
|
|
|
|
#include "AvHWeldable.h"
|
|
#include "AvHServerUtil.h"
|
|
#include "AvHGamerules.h"
|
|
|
|
#include "../../dlls/triggers.h"
|
|
|
|
#include <stdlib.h>
|
|
#include <math.h>
|
|
|
|
#include "../../dlls/plats.h"
|
|
|
|
#include "DetourNavMesh.h"
|
|
#include "DetourCommon.h"
|
|
#include "DetourTileCache.h"
|
|
#include "DetourTileCacheBuilder.h"
|
|
#include "DetourNavMeshBuilder.h"
|
|
#include "fastlz/fastlz.c"
|
|
#include "DetourAlloc.h"
|
|
|
|
#include <cfloat>
|
|
|
|
vector<nav_door> NavDoors;
|
|
vector<nav_weldable> NavWeldableObstacles;
|
|
vector<AvHAIOffMeshConnection> BaseMapConnections;
|
|
|
|
nav_mesh NavMeshes[MAX_NAV_MESHES] = { }; // Array of nav meshes. Currently only 3 are used (building, onos, and regular)
|
|
nav_profile BaseNavProfiles[MAX_NAV_PROFILES] = { }; // Array of nav profiles
|
|
|
|
AvHAINavMeshStatus NavmeshStatus = NAVMESH_STATUS_PENDING;
|
|
|
|
vector<NavHint> MapNavHints;
|
|
|
|
extern bool bNavMeshModified;
|
|
|
|
bool bTileCacheUpToDate = false;
|
|
|
|
struct NavMeshSetHeader
|
|
{
|
|
int magic;
|
|
int version;
|
|
int numTiles;
|
|
dtNavMeshParams params;
|
|
int MeshBuildOffset;
|
|
};
|
|
|
|
struct TileCacheSetExportHeader
|
|
{
|
|
int magic;
|
|
int version;
|
|
|
|
int numRegularTiles;
|
|
dtNavMeshParams regularMeshParams;
|
|
dtTileCacheParams regularCacheParams;
|
|
|
|
int numOnosTiles;
|
|
dtNavMeshParams onosMeshParams;
|
|
dtTileCacheParams onosCacheParams;
|
|
|
|
int numBuildingTiles;
|
|
dtNavMeshParams buildingMeshParams;
|
|
dtTileCacheParams buildingCacheParams;
|
|
|
|
int regularNavOffset;
|
|
int onosNavOffset;
|
|
int buildingNavOffset;
|
|
|
|
int NumOffMeshCons;
|
|
|
|
int OffMeshConsOffset;
|
|
};
|
|
|
|
struct TileCacheBuildHeader
|
|
{
|
|
int magic;
|
|
int version;
|
|
int numRegularTiles;
|
|
int numOnosTiles;
|
|
int numBuildingTiles;
|
|
|
|
dtNavMeshParams regularMeshParams;
|
|
dtTileCacheParams regularCacheParams;
|
|
|
|
dtNavMeshParams onosMeshParams;
|
|
dtTileCacheParams onosCacheParams;
|
|
|
|
dtNavMeshParams buildingMeshParams;
|
|
dtTileCacheParams buildingCacheParams;
|
|
|
|
int NumSurfTypes;
|
|
int SurfTypesOffset;
|
|
|
|
int NumOffMeshCons;
|
|
int OffMeshConsOffset;
|
|
|
|
int NumConvexVols;
|
|
int ConvexVolOffset;
|
|
|
|
int NumNavHints;
|
|
int NavHintOffset;
|
|
};
|
|
|
|
struct TileCacheTileHeader
|
|
{
|
|
dtCompressedTileRef tileRef;
|
|
int dataSize;
|
|
};
|
|
|
|
struct NavMeshTileHeader
|
|
{
|
|
dtTileRef tileRef;
|
|
int dataSize;
|
|
};
|
|
|
|
struct OffMeshConnectionDef
|
|
{
|
|
unsigned int UserID = 0;
|
|
float spos[3] = { 0.0f, 0.0f, 0.0f };
|
|
float epos[3] = { 0.0f, 0.0f, 0.0f };
|
|
bool bBiDir = false;
|
|
float Rad = 0.0f;
|
|
unsigned char Area = 0;
|
|
unsigned int Flag = 0;
|
|
bool bPendingDelete = false;
|
|
bool bDirty = false;
|
|
};
|
|
|
|
struct FastLZCompressor : public dtTileCacheCompressor
|
|
{
|
|
virtual int maxCompressedSize(const int bufferSize)
|
|
{
|
|
return (int)(bufferSize * 1.05f);
|
|
}
|
|
|
|
virtual dtStatus compress(const unsigned char* buffer, const int bufferSize,
|
|
unsigned char* compressed, const int /*maxCompressedSize*/, int* compressedSize)
|
|
{
|
|
*compressedSize = fastlz_compress((const void* const)buffer, bufferSize, compressed);
|
|
return DT_SUCCESS;
|
|
}
|
|
|
|
virtual dtStatus decompress(const unsigned char* compressed, const int compressedSize,
|
|
unsigned char* buffer, const int maxBufferSize, int* bufferSize)
|
|
{
|
|
*bufferSize = fastlz_decompress(compressed, compressedSize, buffer, maxBufferSize);
|
|
return *bufferSize < 0 ? DT_FAILURE : DT_SUCCESS;
|
|
}
|
|
};
|
|
|
|
struct LinearAllocator : public dtTileCacheAlloc
|
|
{
|
|
unsigned char* buffer;
|
|
size_t capacity;
|
|
size_t top;
|
|
size_t high;
|
|
|
|
LinearAllocator(const size_t cap) : buffer(0), capacity(0), top(0), high(0)
|
|
{
|
|
resize(cap);
|
|
}
|
|
|
|
~LinearAllocator()
|
|
{
|
|
dtFree(buffer);
|
|
}
|
|
|
|
void resize(const size_t cap)
|
|
{
|
|
if (buffer) dtFree(buffer);
|
|
buffer = (unsigned char*)dtAlloc(cap, DT_ALLOC_PERM);
|
|
capacity = cap;
|
|
}
|
|
|
|
virtual void reset()
|
|
{
|
|
high = dtMax(high, top);
|
|
top = 0;
|
|
}
|
|
|
|
virtual void* alloc(const size_t size)
|
|
{
|
|
if (!buffer)
|
|
return 0;
|
|
if (top + size > capacity)
|
|
return 0;
|
|
unsigned char* mem = &buffer[top];
|
|
top += size;
|
|
return mem;
|
|
}
|
|
|
|
virtual void free(void* /*ptr*/)
|
|
{
|
|
// Empty
|
|
}
|
|
};
|
|
|
|
struct MeshProcess : public dtTileCacheMeshProcess
|
|
{
|
|
|
|
inline MeshProcess()
|
|
{}
|
|
|
|
inline void init(OffMeshConnectionDef* OffMeshConnData, int NumConns)
|
|
{
|
|
|
|
}
|
|
|
|
virtual void process(struct dtNavMeshCreateParams* params,
|
|
unsigned char* polyAreas, unsigned int* polyFlags)
|
|
{
|
|
// Update poly flags from areas.
|
|
for (int i = 0; i < params->polyCount; ++i)
|
|
{
|
|
if (polyAreas[i] == DT_TILECACHE_WALKABLE_AREA)
|
|
{
|
|
polyAreas[i] = SAMPLE_POLYAREA_GROUND;
|
|
polyFlags[i] = SAMPLE_POLYFLAGS_WALK;
|
|
}
|
|
else if (polyAreas[i] == DT_TILECACHE_CROUCH_AREA)
|
|
{
|
|
polyAreas[i] = SAMPLE_POLYAREA_CROUCH;
|
|
polyFlags[i] = SAMPLE_POLYFLAGS_WALK;
|
|
}
|
|
else if (polyAreas[i] == DT_TILECACHE_WALLCLIMB_AREA)
|
|
{
|
|
polyAreas[i] = SAMPLE_POLYAREA_GROUND;
|
|
polyFlags[i] = SAMPLE_POLYFLAGS_WALLCLIMB;
|
|
}
|
|
else if (polyAreas[i] == DT_TILECACHE_LADDER_AREA)
|
|
{
|
|
polyAreas[i] = SAMPLE_POLYAREA_GROUND;
|
|
polyFlags[i] = SAMPLE_POLYFLAGS_LADDER;
|
|
}
|
|
else if (polyAreas[i] == DT_TILECACHE_BLOCKED_AREA)
|
|
{
|
|
polyAreas[i] = SAMPLE_POLYAREA_BLOCKED;
|
|
polyFlags[i] = SAMPLE_POLYFLAGS_BLOCKED;
|
|
}
|
|
else if (polyAreas[i] == DT_TILECACHE_TEAM1STRUCTURE_AREA)
|
|
{
|
|
polyAreas[i] = SAMPLE_POLYAREA_STRUCTUREBLOCK;
|
|
polyFlags[i] = SAMPLE_POLYFLAGS_TEAM1STRUCTURE;
|
|
}
|
|
else if (polyAreas[i] == DT_TILECACHE_TEAM2STRUCTURE_AREA)
|
|
{
|
|
polyAreas[i] = SAMPLE_POLYAREA_STRUCTUREBLOCK;
|
|
polyFlags[i] = SAMPLE_POLYFLAGS_TEAM2STRUCTURE;
|
|
}
|
|
else if (polyAreas[i] == DT_TILECACHE_WELD_AREA)
|
|
{
|
|
polyAreas[i] = SAMPLE_POLYAREA_OBSTRUCTION;
|
|
polyFlags[i] = SAMPLE_POLYFLAGS_WELD;
|
|
}
|
|
else if (polyAreas[i] == DT_TILECACHE_DOOR_AREA)
|
|
{
|
|
polyAreas[i] = SAMPLE_POLYAREA_OBSTRUCTION;
|
|
polyFlags[i] = SAMPLE_POLYFLAGS_DOOR;
|
|
}
|
|
}
|
|
|
|
}
|
|
};
|
|
|
|
void AIDEBUG_DrawTemporaryObstacles(float DrawTime)
|
|
{
|
|
const dtNavMesh* m_navMesh = UTIL_GetNavMeshForProfile(GetBaseNavProfile(MARINE_BASE_NAV_PROFILE));
|
|
const dtTileCache* m_tileCache = UTIL_GetTileCacheForProfile(GetBaseNavProfile(MARINE_BASE_NAV_PROFILE));
|
|
|
|
if (m_navMesh)
|
|
{
|
|
int NumObstacles = m_tileCache->getObstacleCount();
|
|
|
|
for (int i = 0; i < NumObstacles; i++)
|
|
{
|
|
const dtTileCacheObstacle* ObstacleRef = m_tileCache->getObstacle(i);
|
|
|
|
if (!ObstacleRef || ObstacleRef->state != DT_OBSTACLE_PROCESSED) { continue; }
|
|
|
|
int r, g, b;
|
|
|
|
|
|
|
|
if (ObstacleRef->type == ObstacleType::DT_OBSTACLE_BOX)
|
|
{
|
|
switch (ObstacleRef->box.area)
|
|
{
|
|
case DT_TILECACHE_NULL_AREA:
|
|
r = 128;
|
|
g = 128;
|
|
b = 128;
|
|
break;
|
|
case DT_TILECACHE_CROUCH_AREA:
|
|
r = 128;
|
|
g = 0;
|
|
b = 0;
|
|
break;
|
|
case DT_TILECACHE_BLOCKED_AREA:
|
|
r = 255;
|
|
g = 255;
|
|
b = 0;
|
|
break;
|
|
case DT_TILECACHE_WALLCLIMB_AREA:
|
|
r = 0;
|
|
g = 128;
|
|
b = 0;
|
|
break;
|
|
case DT_TILECACHE_LADDER_AREA:
|
|
r = 0;
|
|
g = 0;
|
|
b = 255;
|
|
break;
|
|
case DT_TILECACHE_TEAM1STRUCTURE_AREA:
|
|
r = 0;
|
|
g = 0;
|
|
b = 255;
|
|
break;
|
|
case DT_TILECACHE_TEAM2STRUCTURE_AREA:
|
|
r = 0;
|
|
g = 128;
|
|
b = 0;
|
|
break;
|
|
case DT_TILECACHE_WELD_AREA:
|
|
r = 255;
|
|
g = 0;
|
|
b = 0;
|
|
break;
|
|
case DT_TILECACHE_DOOR_AREA:
|
|
r = 0;
|
|
g = 128;
|
|
b = 128;
|
|
break;
|
|
}
|
|
|
|
Vector bMin = Vector(ObstacleRef->box.bmin[0], -ObstacleRef->box.bmin[2], ObstacleRef->box.bmin[1]);
|
|
Vector bMax = Vector(ObstacleRef->box.bmax[0], -ObstacleRef->box.bmax[2], ObstacleRef->box.bmax[1]);
|
|
|
|
UTIL_DrawBox(INDEXENT(1), bMin, bMax, DrawTime, r, g, b);
|
|
continue;
|
|
}
|
|
|
|
if (ObstacleRef->type == ObstacleType::DT_OBSTACLE_CYLINDER)
|
|
{
|
|
switch (ObstacleRef->cylinder.area)
|
|
{
|
|
case DT_TILECACHE_NULL_AREA:
|
|
r = 128;
|
|
g = 128;
|
|
b = 128;
|
|
break;
|
|
case DT_TILECACHE_CROUCH_AREA:
|
|
r = 128;
|
|
g = 0;
|
|
b = 0;
|
|
break;
|
|
case DT_TILECACHE_BLOCKED_AREA:
|
|
r = 255;
|
|
g = 255;
|
|
b = 0;
|
|
break;
|
|
case DT_TILECACHE_WALLCLIMB_AREA:
|
|
r = 0;
|
|
g = 128;
|
|
b = 0;
|
|
break;
|
|
case DT_TILECACHE_LADDER_AREA:
|
|
r = 0;
|
|
g = 0;
|
|
b = 255;
|
|
break;
|
|
case DT_TILECACHE_TEAM1STRUCTURE_AREA:
|
|
r = 0;
|
|
g = 0;
|
|
b = 255;
|
|
break;
|
|
case DT_TILECACHE_TEAM2STRUCTURE_AREA:
|
|
r = 0;
|
|
g = 128;
|
|
b = 0;
|
|
break;
|
|
case DT_TILECACHE_WELD_AREA:
|
|
r = 255;
|
|
g = 0;
|
|
b = 0;
|
|
break;
|
|
case DT_TILECACHE_DOOR_AREA:
|
|
r = 0;
|
|
g = 128;
|
|
b = 128;
|
|
break;
|
|
}
|
|
|
|
float Radius = ObstacleRef->cylinder.radius;
|
|
float Height = ObstacleRef->cylinder.height;
|
|
|
|
// The location of obstacles in Recast are at the bottom of the shape, not the centre
|
|
Vector Centre = Vector(ObstacleRef->cylinder.pos[0], -ObstacleRef->cylinder.pos[2], ObstacleRef->cylinder.pos[1] + (Height * 0.5f));
|
|
|
|
if (vDist2DSq(INDEXENT(1)->v.origin, Centre) > sqrf(UTIL_MetresToGoldSrcUnits(10.0f))) { continue; }
|
|
|
|
Vector bMin = Centre - Vector(Radius, Radius, Height * 0.5f);
|
|
Vector bMax = Centre + Vector(Radius, Radius, (Height * 0.5f));
|
|
|
|
UTIL_DrawBox(INDEXENT(1), bMin, bMax, DrawTime, r, g, b);
|
|
continue;
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
void AIDEBUG_DrawOffMeshConnections(float DrawTime)
|
|
{
|
|
if (NavMeshes[REGULAR_NAV_MESH].tileCache)
|
|
{
|
|
for (int i = 0; i < NavMeshes[REGULAR_NAV_MESH].tileCache->getOffMeshCount(); i++)
|
|
{
|
|
const dtOffMeshConnection* con = NavMeshes[REGULAR_NAV_MESH].tileCache->getOffMeshConnection(i);
|
|
|
|
if (con->state == DT_OFFMESH_EMPTY || con->state == DT_OFFMESH_REMOVING) { continue; }
|
|
|
|
Vector StartLine = Vector(con->pos[0], -con->pos[2], con->pos[1]);
|
|
Vector EndLine = Vector(con->pos[3], -con->pos[5], con->pos[4]);
|
|
switch (con->flags)
|
|
{
|
|
case SAMPLE_POLYFLAGS_WALK:
|
|
UTIL_DrawLine(INDEXENT(1), StartLine, EndLine, DrawTime, 255, 255, 255);
|
|
break;
|
|
case SAMPLE_POLYFLAGS_JUMP:
|
|
UTIL_DrawLine(INDEXENT(1), StartLine, EndLine, DrawTime, 255, 255, 0);
|
|
break;
|
|
case SAMPLE_POLYFLAGS_WALLCLIMB:
|
|
UTIL_DrawLine(INDEXENT(1), StartLine, EndLine, DrawTime, 0, 255, 0);
|
|
break;
|
|
case SAMPLE_POLYFLAGS_FALL:
|
|
UTIL_DrawLine(INDEXENT(1), StartLine, EndLine, DrawTime, 255, 0, 0);
|
|
break;
|
|
case SAMPLE_POLYFLAGS_LADDER:
|
|
UTIL_DrawLine(INDEXENT(1), StartLine, EndLine, DrawTime, 0, 0, 255);
|
|
break;
|
|
case SAMPLE_POLYFLAGS_TEAM1PHASEGATE:
|
|
case SAMPLE_POLYFLAGS_TEAM2PHASEGATE:
|
|
UTIL_DrawLine(INDEXENT(1), StartLine, EndLine, DrawTime, 255, 128, 128);
|
|
break;
|
|
case SAMPLE_POLYFLAGS_DISABLED:
|
|
UTIL_DrawLine(INDEXENT(1), StartLine, EndLine, DrawTime, 128, 128, 128);
|
|
break;
|
|
case SAMPLE_POLYFLAGS_WELD:
|
|
UTIL_DrawLine(INDEXENT(1), StartLine, EndLine, DrawTime, 255, 165, 0);
|
|
break;
|
|
default:
|
|
UTIL_DrawLine(INDEXENT(1), StartLine, EndLine, DrawTime, 0, 255, 255);
|
|
break;
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
bool UTIL_UpdateTileCache()
|
|
{
|
|
bTileCacheUpToDate = true;
|
|
|
|
for (int i = 0; i < MAX_NAV_MESHES; i++)
|
|
{
|
|
if (NavMeshes[i].tileCache)
|
|
{
|
|
bool bUpToDate;
|
|
NavMeshes[i].tileCache->update(0.0f, NavMeshes[i].navMesh, &bUpToDate);
|
|
if (!bUpToDate) { bTileCacheUpToDate = false; }
|
|
}
|
|
}
|
|
|
|
return bTileCacheUpToDate;
|
|
}
|
|
|
|
Vector UTIL_AdjustPointAwayFromNavWall(const Vector Location, const float MaxDistanceFromWall)
|
|
{
|
|
|
|
const dtNavMeshQuery* m_navQuery = UTIL_GetNavMeshQueryForProfile(BaseNavProfiles[SKULK_BASE_NAV_PROFILE]);
|
|
const dtQueryFilter* m_navFilter = &BaseNavProfiles[SKULK_BASE_NAV_PROFILE].Filters;
|
|
|
|
float Pos[3] = { Location.x, Location.z, -Location.y };
|
|
|
|
float HitDist = 0.0f;
|
|
float HitPos[3] = { 0.0f, 0.0f, 0.0f };
|
|
float HitNorm[3] = { 0.0f, 0.0f, 0.0f };
|
|
|
|
dtPolyRef StartPoly = UTIL_GetNearestPolyRefForLocation(Location);
|
|
|
|
dtStatus Result = m_navQuery->findDistanceToWall(StartPoly, Pos, MaxDistanceFromWall, m_navFilter, &HitDist, HitPos, HitNorm);
|
|
|
|
if (dtStatusSucceed(Result))
|
|
{
|
|
float AdjustDistance = MaxDistanceFromWall - HitDist;
|
|
|
|
Vector HitPosVector = Vector(HitPos[0], -HitPos[2], HitPos[1]);
|
|
|
|
Vector AdjustDir = (HitDist > 0.1f) ? UTIL_GetVectorNormal2D(Location - HitPosVector) : Vector(HitNorm[0], -HitNorm[2], HitNorm[1]);
|
|
|
|
Vector AdjustLocation = Location + (AdjustDir * AdjustDistance);
|
|
|
|
float AdjustLoc[3] = { AdjustLocation.x, AdjustLocation.z, -AdjustLocation.y };
|
|
|
|
if (UTIL_TraceNav(BaseNavProfiles[ALL_NAV_PROFILE], Location, AdjustLocation, 0.1f))
|
|
{
|
|
return AdjustLocation;
|
|
}
|
|
else
|
|
{
|
|
return Location;
|
|
}
|
|
}
|
|
|
|
return Location;
|
|
}
|
|
|
|
Vector UTIL_GetNearestPointOnNavWall(AvHAIPlayer* pBot, const float MaxRadius)
|
|
{
|
|
const dtNavMeshQuery* m_navQuery = UTIL_GetNavMeshQueryForProfile(pBot->BotNavInfo.NavProfile);
|
|
const dtQueryFilter* m_navFilter = &pBot->BotNavInfo.NavProfile.Filters;
|
|
|
|
Vector Location = pBot->CurrentFloorPosition;
|
|
|
|
float Pos[3] = { Location.x, Location.z, -Location.y };
|
|
|
|
float HitDist = 0.0f;
|
|
float HitPos[3] = { 0.0f, 0.0f, 0.0f };
|
|
float HitNorm[3] = { 0.0f, 0.0f, 0.0f };
|
|
|
|
dtStatus Result = m_navQuery->findDistanceToWall(pBot->BotNavInfo.CurrentPoly, Pos, MaxRadius, m_navFilter, &HitDist, HitPos, HitNorm);
|
|
|
|
if (dtStatusSucceed(Result) && HitDist > 0.0f)
|
|
{
|
|
Vector HitResult = Vector(HitPos[0], -HitPos[2], HitPos[1]);
|
|
return HitResult;
|
|
}
|
|
|
|
return g_vecZero;
|
|
}
|
|
|
|
Vector UTIL_GetNearestPointOnNavWall(const nav_profile &NavProfile, const Vector Location, const float MaxRadius)
|
|
{
|
|
|
|
const dtNavMeshQuery* m_navQuery = UTIL_GetNavMeshQueryForProfile(NavProfile);
|
|
const dtQueryFilter* m_navFilter = &NavProfile.Filters;
|
|
|
|
// Invalid nav profile
|
|
if (!m_navQuery) { return g_vecZero; }
|
|
|
|
dtPolyRef StartPoly = UTIL_GetNearestPolyRefForLocation(NavProfile, Location);
|
|
|
|
// Not on the nav mesh
|
|
if (StartPoly == 0) { return Location; }
|
|
|
|
float Pos[3] = { Location.x, Location.z, -Location.y };
|
|
|
|
float HitDist = 0.0f;
|
|
float HitPos[3] = { 0.0f, 0.0f, 0.0f };
|
|
float HitNorm[3] = { 0.0f, 0.0f, 0.0f };
|
|
|
|
dtStatus Result = m_navQuery->findDistanceToWall(StartPoly, Pos, MaxRadius, m_navFilter, &HitDist, HitPos, HitNorm);
|
|
|
|
// We hit something
|
|
if (dtStatusSucceed(Result) && HitDist < MaxRadius)
|
|
{
|
|
Vector HitResult = Vector(HitPos[0], -HitPos[2], HitPos[1]);
|
|
return HitResult;
|
|
}
|
|
|
|
// Didn't hit anything
|
|
return g_vecZero;
|
|
}
|
|
|
|
unsigned int UTIL_AddTemporaryObstacle(unsigned int NavMeshIndex, const Vector Location, float Radius, float Height, int area)
|
|
{
|
|
unsigned int ObstacleNum = 0;
|
|
|
|
if (NavMeshes[NavMeshIndex].tileCache)
|
|
{
|
|
float Pos[3] = { Location.x, Location.z - (Height * 0.5f), -Location.y };
|
|
|
|
dtObstacleRef ObsRef = 0;
|
|
NavMeshes[NavMeshIndex].tileCache->addObstacle(Pos, Radius, Height, area, &ObsRef);
|
|
|
|
ObstacleNum = (unsigned int)ObsRef;
|
|
|
|
if (ObstacleNum > 0 && NavMeshIndex != BUILDING_NAV_MESH)
|
|
{
|
|
bNavMeshModified = true;
|
|
}
|
|
}
|
|
|
|
return ObstacleNum;
|
|
}
|
|
|
|
void UTIL_AddStructureTemporaryObstacles(AvHAIBuildableStructure* Structure)
|
|
{
|
|
if (Structure->StructureType == STRUCTURE_MARINE_DEPLOYEDMINE) { return; }
|
|
|
|
bool bCollideWithPlayers = UTIL_ShouldStructureCollide(Structure->StructureType);
|
|
|
|
float Radius = UTIL_GetStructureRadiusForObstruction(Structure->StructureType);
|
|
|
|
// Not all structures collide with players (e.g. phase gate)
|
|
if (bCollideWithPlayers)
|
|
{
|
|
unsigned int area = UTIL_GetAreaForObstruction(Structure->StructureType, Structure->edict);
|
|
|
|
// We add an obstacle for the building nav mesh below
|
|
for (int i = 0; i < BUILDING_NAV_MESH; i++)
|
|
{
|
|
unsigned int NewObstacleRef = UTIL_AddTemporaryObstacle(i, UTIL_GetCentreOfEntity(Structure->edict), Radius, 100.0f, area);
|
|
|
|
if (NewObstacleRef > 0)
|
|
{
|
|
AvHAITempObstacle NewObstacle;
|
|
NewObstacle.NavMeshIndex = i;
|
|
NewObstacle.ObstacleRef = NewObstacleRef;
|
|
|
|
Structure->Obstacles.push_back(NewObstacle);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Always cut a hole in the building nav mesh so we don't try to place anything on top of this structure in future
|
|
unsigned int NewObstacleRef = UTIL_AddTemporaryObstacle(BUILDING_NAV_MESH, UTIL_GetCentreOfEntity(Structure->edict), Radius * 1.1f, 100.0f, DT_TILECACHE_NULL_AREA);
|
|
|
|
if (NewObstacleRef > 0)
|
|
{
|
|
AvHAITempObstacle NewObstacle;
|
|
NewObstacle.NavMeshIndex = BUILDING_NAV_MESH;
|
|
NewObstacle.ObstacleRef = NewObstacleRef;
|
|
|
|
Structure->Obstacles.push_back(NewObstacle);
|
|
}
|
|
|
|
}
|
|
|
|
void UTIL_RemoveStructureTemporaryObstacles(AvHAIBuildableStructure* Structure)
|
|
{
|
|
for (auto it = Structure->Obstacles.begin(); it != Structure->Obstacles.end();)
|
|
{
|
|
int NavMeshIndex = it->NavMeshIndex;
|
|
|
|
if (NavMeshes[NavMeshIndex].tileCache)
|
|
{
|
|
const dtTileCacheObstacle* ObstacleToRemove = NavMeshes[NavMeshIndex].tileCache->getObstacleByRef((dtObstacleRef)it->ObstacleRef);
|
|
|
|
if (ObstacleToRemove)
|
|
{
|
|
dtStatus RemovalStatus = NavMeshes[NavMeshIndex].tileCache->removeObstacle((dtObstacleRef)it->ObstacleRef);
|
|
|
|
if (dtStatusSucceed(RemovalStatus))
|
|
{
|
|
bNavMeshModified = true;
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
|
|
it = Structure->Obstacles.erase(it);
|
|
}
|
|
|
|
}
|
|
|
|
void UTIL_AddTemporaryObstacles(const Vector Location, float Radius, float Height, int area, unsigned int* ObstacleRefArray)
|
|
{
|
|
|
|
float Pos[3] = { Location.x, Location.z - (Height * 0.5f), -Location.y };
|
|
|
|
for (int i = 0; i < MAX_NAV_MESHES; i++)
|
|
{
|
|
ObstacleRefArray[i] = 0;
|
|
|
|
if (NavMeshes[i].tileCache)
|
|
{
|
|
dtObstacleRef ObsRef = 0;
|
|
NavMeshes[i].tileCache->addObstacle(Pos, Radius, Height, area, &ObsRef);
|
|
|
|
ObstacleRefArray[i] = (unsigned int)ObsRef;
|
|
|
|
if ((unsigned int)ObsRef > 0)
|
|
{
|
|
bNavMeshModified = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
unsigned int UTIL_AddTemporaryBoxObstacle(const Vector bMin, const Vector bMax, int area)
|
|
{
|
|
unsigned int ObstacleNum = 0;
|
|
|
|
float bMinf[3] = { bMin.x, bMin.z, -bMin.y };
|
|
float bMaxf[3] = { bMax.x, bMax.z, -bMax.y };
|
|
|
|
for (int i = 0; i < MAX_NAV_MESHES; i++)
|
|
{
|
|
if (NavMeshes[i].tileCache)
|
|
{
|
|
dtObstacleRef ObsRef = 0;
|
|
NavMeshes[i].tileCache->addBoxObstacle(bMinf, bMaxf, area, &ObsRef);
|
|
|
|
ObstacleNum = (unsigned int)ObsRef;
|
|
|
|
if (area == DT_TILECACHE_NULL_AREA || area == DT_TILECACHE_WELD_AREA)
|
|
{
|
|
bNavMeshModified = true;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
return ObstacleNum;
|
|
}
|
|
|
|
void UTIL_RemoveTemporaryObstacle(unsigned int ObstacleRef)
|
|
{
|
|
if (ObstacleRef == 0) { return; }
|
|
|
|
for (int i = 0; i < MAX_NAV_MESHES; i++)
|
|
{
|
|
if (NavMeshes[i].tileCache)
|
|
{
|
|
const dtTileCacheObstacle* ObstacleToRemove = NavMeshes[i].tileCache->getObstacleByRef((dtObstacleRef)ObstacleRef);
|
|
|
|
if (ObstacleToRemove && (ObstacleToRemove->cylinder.area == DT_TILECACHE_NULL_AREA || ObstacleToRemove->cylinder.area == DT_TILECACHE_WELD_AREA))
|
|
{
|
|
bNavMeshModified = true;
|
|
}
|
|
|
|
NavMeshes[i].tileCache->removeObstacle((dtObstacleRef)ObstacleRef);
|
|
}
|
|
}
|
|
}
|
|
|
|
void UTIL_RemoveTemporaryObstacles(unsigned int* ObstacleRefs)
|
|
{
|
|
|
|
for (int i = 0; i < MAX_NAV_MESHES; i++)
|
|
{
|
|
if (NavMeshes[i].tileCache)
|
|
{
|
|
const dtTileCacheObstacle* ObstacleToRemove = NavMeshes[i].tileCache->getObstacleByRef((dtObstacleRef)ObstacleRefs[i]);
|
|
|
|
if (ObstacleToRemove && (ObstacleToRemove->cylinder.area == DT_TILECACHE_NULL_AREA || ObstacleToRemove->cylinder.area == DT_TILECACHE_WELD_AREA))
|
|
{
|
|
bNavMeshModified = true;
|
|
}
|
|
|
|
NavMeshes[i].tileCache->removeObstacle((dtObstacleRef)ObstacleRefs[i]);
|
|
|
|
}
|
|
|
|
ObstacleRefs[i] = 0;
|
|
}
|
|
}
|
|
|
|
void GetFullFilePath(char* buffer, const char* mapname)
|
|
{
|
|
string theMapName = mapname;
|
|
string navPath = string(getModDirectory()) + "/navmeshes/" + mapname + ".nav";
|
|
strcpy(buffer, navPath.c_str());
|
|
}
|
|
|
|
|
|
void ReloadNavMeshes()
|
|
{
|
|
vector<AvHAIPlayer*> AllBots = AIMGR_GetAllAIPlayers();
|
|
|
|
for (auto it = AllBots.begin(); it != AllBots.end(); it++)
|
|
{
|
|
AvHAIPlayer* ThisPlayer = (*it);
|
|
|
|
ClearBotMovement(ThisPlayer);
|
|
}
|
|
AITAC_ClearMapAIData(false);
|
|
UnloadNavMeshes();
|
|
bool bSuccess = LoadNavMesh(STRING(gpGlobals->mapname));
|
|
|
|
if (bSuccess)
|
|
{
|
|
UTIL_PopulateDoors();
|
|
UTIL_PopulateWeldableObstacles();
|
|
|
|
UTIL_UpdateDoors(true);
|
|
|
|
bool bTileCacheFullyUpdated = UTIL_UpdateTileCache();
|
|
|
|
while (!bTileCacheFullyUpdated)
|
|
{
|
|
bTileCacheFullyUpdated = UTIL_UpdateTileCache();
|
|
}
|
|
}
|
|
}
|
|
|
|
void UnloadNavMeshes()
|
|
{
|
|
for (int i = 0; i < MAX_NAV_MESHES; i++)
|
|
{
|
|
if (NavMeshes[i].navMesh)
|
|
{
|
|
dtFreeNavMesh(NavMeshes[i].navMesh);
|
|
NavMeshes[i].navMesh = nullptr;
|
|
}
|
|
|
|
if (NavMeshes[i].navQuery)
|
|
{
|
|
dtFreeNavMeshQuery(NavMeshes[i].navQuery);
|
|
NavMeshes[i].navQuery = nullptr;
|
|
}
|
|
|
|
if (NavMeshes[i].tileCache)
|
|
{
|
|
dtFreeTileCache(NavMeshes[i].tileCache);
|
|
NavMeshes[i].tileCache = nullptr;
|
|
}
|
|
}
|
|
|
|
BaseMapConnections.clear();
|
|
MapNavHints.clear();
|
|
|
|
NavmeshStatus = NAVMESH_STATUS_PENDING;
|
|
}
|
|
|
|
void UnloadNavigationData()
|
|
{
|
|
UnloadNavMeshes();
|
|
|
|
UTIL_ClearDoorData();
|
|
UTIL_ClearWeldablesData();
|
|
|
|
memset(BaseNavProfiles, 0, sizeof(nav_profile));
|
|
|
|
AIMGR_ClearBotData();
|
|
|
|
}
|
|
|
|
bool LoadNavMesh(const char* mapname)
|
|
{
|
|
memset(NavMeshes, 0, sizeof(NavMeshes));
|
|
BaseMapConnections.clear();
|
|
MapNavHints.clear();
|
|
|
|
char filename[256]; // Full path to BSP file
|
|
|
|
GetFullFilePath(filename, mapname);
|
|
|
|
FILE* savedFile = fopen(filename, "rb");
|
|
|
|
if (!savedFile)
|
|
{
|
|
char ErrMsg[256];
|
|
sprintf(ErrMsg, "No nav file found for %s in the navmeshes folder\n", mapname);
|
|
g_engfuncs.pfnServerPrint(ErrMsg);
|
|
g_engfuncs.pfnServerPrint("You will need to create one using the Nav Editor tool in the navmeshes folder, or download one\n");
|
|
return false;
|
|
}
|
|
|
|
LinearAllocator* m_talloc = new LinearAllocator(32000);
|
|
FastLZCompressor* m_tcomp = new FastLZCompressor;
|
|
MeshProcess* m_tmproc = new MeshProcess;
|
|
|
|
// Read header.
|
|
TileCacheBuildHeader header;
|
|
size_t headerReadReturnCode = fread(&header, sizeof(TileCacheBuildHeader), 1, savedFile);
|
|
if (headerReadReturnCode != 1)
|
|
{
|
|
// Error or early EOF
|
|
fclose(savedFile);
|
|
UnloadNavigationData();
|
|
char ErrMsg[256];
|
|
sprintf(ErrMsg, "The nav file found for %s is a different version to the current bot version. Use the Nav Editor to regenerate it\n", mapname);
|
|
g_engfuncs.pfnServerPrint(ErrMsg);
|
|
return false;
|
|
}
|
|
|
|
if (header.magic != TILECACHESET_MAGIC || header.version != TILECACHESET_VERSION)
|
|
{
|
|
fclose(savedFile);
|
|
UnloadNavigationData();
|
|
char ErrMsg[256];
|
|
sprintf(ErrMsg, "The nav file found for %s is a different version to the current bot version. Use the Nav Editor to regenerate it\n", mapname);
|
|
g_engfuncs.pfnServerPrint(ErrMsg);
|
|
return false;
|
|
}
|
|
|
|
dtNavMeshParams* NavMeshParams[3] = { &header.regularMeshParams, &header.onosMeshParams, &header.buildingMeshParams };
|
|
dtTileCacheParams* TileCacheParams[3] = { &header.regularCacheParams, &header.onosCacheParams, &header.buildingCacheParams };
|
|
|
|
for (int i = 0; i <= BUILDING_NAV_MESH; i++)
|
|
{
|
|
NavMeshes[i].navMesh = dtAllocNavMesh();
|
|
|
|
if (!NavMeshes[i].navMesh)
|
|
{
|
|
fclose(savedFile);
|
|
UnloadNavigationData();
|
|
g_engfuncs.pfnServerPrint("Unable to allocate memory for the nav mesh\n");
|
|
return false;
|
|
}
|
|
|
|
dtStatus status = NavMeshes[i].navMesh->init(NavMeshParams[i]);
|
|
if (dtStatusFailed(status))
|
|
{
|
|
fclose(savedFile);
|
|
UnloadNavigationData();
|
|
g_engfuncs.pfnServerPrint("The nav file has been corrupted or is out of date. Use the Nav Editor to regenerate it\n");
|
|
return false;
|
|
}
|
|
|
|
NavMeshes[i].tileCache = dtAllocTileCache();
|
|
if (!NavMeshes[i].tileCache)
|
|
{
|
|
fclose(savedFile);
|
|
UnloadNavigationData();
|
|
return false;
|
|
}
|
|
|
|
status = NavMeshes[i].tileCache->init(TileCacheParams[i], m_talloc, m_tcomp, m_tmproc);
|
|
if (dtStatusFailed(status))
|
|
{
|
|
g_engfuncs.pfnServerPrint("The nav file has been corrupted or is out of date. Use the Nav Editor to regenerate it\n");
|
|
fclose(savedFile);
|
|
UnloadNavigationData();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Read tiles.
|
|
for (int i = 0; i < header.numRegularTiles; ++i)
|
|
{
|
|
TileCacheTileHeader tileHeader;
|
|
size_t tileHeaderReadReturnCode = fread(&tileHeader, sizeof(tileHeader), 1, savedFile);
|
|
if (tileHeaderReadReturnCode != 1)
|
|
{
|
|
fclose(savedFile);
|
|
UnloadNavigationData();
|
|
g_engfuncs.pfnServerPrint("The nav file has been corrupted or is out of date. Use the Nav Editor to regenerate it\n");
|
|
return false;
|
|
}
|
|
if (!tileHeader.tileRef || !tileHeader.dataSize)
|
|
break;
|
|
|
|
unsigned char* data = (unsigned char*)dtAlloc(tileHeader.dataSize, DT_ALLOC_PERM);
|
|
if (!data) break;
|
|
memset(data, 0, tileHeader.dataSize);
|
|
size_t tileDataReadReturnCode = fread(data, tileHeader.dataSize, 1, savedFile);
|
|
if (tileDataReadReturnCode != 1)
|
|
{
|
|
dtFree(data);
|
|
fclose(savedFile);
|
|
UnloadNavigationData();
|
|
g_engfuncs.pfnServerPrint("The nav file has been corrupted or is out of date. Use the Nav Editor to regenerate it\n");
|
|
return false;
|
|
}
|
|
|
|
dtCompressedTileRef tile = 0;
|
|
dtStatus addTileStatus = NavMeshes[REGULAR_NAV_MESH].tileCache->addTile(data, tileHeader.dataSize, DT_COMPRESSEDTILE_FREE_DATA, &tile);
|
|
if (dtStatusFailed(addTileStatus))
|
|
{
|
|
dtFree(data);
|
|
}
|
|
|
|
if (tile)
|
|
NavMeshes[REGULAR_NAV_MESH].tileCache->buildNavMeshTile(tile, NavMeshes[REGULAR_NAV_MESH].navMesh);
|
|
}
|
|
|
|
for (int i = 0; i < header.numOnosTiles; ++i)
|
|
{
|
|
TileCacheTileHeader tileHeader;
|
|
size_t tileHeaderReadReturnCode = fread(&tileHeader, sizeof(tileHeader), 1, savedFile);
|
|
if (tileHeaderReadReturnCode != 1)
|
|
{
|
|
fclose(savedFile);
|
|
UnloadNavigationData();
|
|
g_engfuncs.pfnServerPrint("The nav file has been corrupted or is out of date. Use the Nav Editor to regenerate it\n");
|
|
return false;
|
|
}
|
|
if (!tileHeader.tileRef || !tileHeader.dataSize)
|
|
break;
|
|
|
|
unsigned char* data = (unsigned char*)dtAlloc(tileHeader.dataSize, DT_ALLOC_PERM);
|
|
if (!data) break;
|
|
memset(data, 0, tileHeader.dataSize);
|
|
size_t tileDataReadReturnCode = fread(data, tileHeader.dataSize, 1, savedFile);
|
|
if (tileDataReadReturnCode != 1)
|
|
{
|
|
dtFree(data);
|
|
fclose(savedFile);
|
|
UnloadNavigationData();
|
|
g_engfuncs.pfnServerPrint("The nav file has been corrupted or is out of date. Use the Nav Editor to regenerate it\n");
|
|
return false;
|
|
}
|
|
|
|
dtCompressedTileRef tile = 0;
|
|
dtStatus addTileStatus = NavMeshes[ONOS_NAV_MESH].tileCache->addTile(data, tileHeader.dataSize, DT_COMPRESSEDTILE_FREE_DATA, &tile);
|
|
if (dtStatusFailed(addTileStatus))
|
|
{
|
|
dtFree(data);
|
|
}
|
|
|
|
if (tile)
|
|
NavMeshes[ONOS_NAV_MESH].tileCache->buildNavMeshTile(tile, NavMeshes[ONOS_NAV_MESH].navMesh);
|
|
}
|
|
|
|
for (int i = 0; i < header.numBuildingTiles; ++i)
|
|
{
|
|
TileCacheTileHeader tileHeader;
|
|
size_t tileHeaderReadReturnCode = fread(&tileHeader, sizeof(tileHeader), 1, savedFile);
|
|
if (tileHeaderReadReturnCode != 1)
|
|
{
|
|
fclose(savedFile);
|
|
UnloadNavigationData();
|
|
g_engfuncs.pfnServerPrint("The nav file has been corrupted or is out of date. Use the Nav Editor to regenerate it\n");
|
|
return false;
|
|
}
|
|
if (!tileHeader.tileRef || !tileHeader.dataSize)
|
|
break;
|
|
|
|
unsigned char* data = (unsigned char*)dtAlloc(tileHeader.dataSize, DT_ALLOC_PERM);
|
|
if (!data) break;
|
|
memset(data, 0, tileHeader.dataSize);
|
|
size_t tileDataReadReturnCode = fread(data, tileHeader.dataSize, 1, savedFile);
|
|
if (tileDataReadReturnCode != 1)
|
|
{
|
|
dtFree(data);
|
|
fclose(savedFile);
|
|
UnloadNavigationData();
|
|
g_engfuncs.pfnServerPrint("The nav file has been corrupted or is out of date. Use the Nav Editor to regenerate it\n");
|
|
return false;
|
|
}
|
|
|
|
dtCompressedTileRef tile = 0;
|
|
dtStatus addTileStatus = NavMeshes[BUILDING_NAV_MESH].tileCache->addTile(data, tileHeader.dataSize, DT_COMPRESSEDTILE_FREE_DATA, &tile);
|
|
if (dtStatusFailed(addTileStatus))
|
|
{
|
|
dtFree(data);
|
|
}
|
|
|
|
if (tile)
|
|
NavMeshes[BUILDING_NAV_MESH].tileCache->buildNavMeshTile(tile, NavMeshes[BUILDING_NAV_MESH].navMesh);
|
|
}
|
|
|
|
fseek(savedFile, header.OffMeshConsOffset, SEEK_SET);
|
|
|
|
for (int i = 0; i < header.NumOffMeshCons; i++)
|
|
{
|
|
dtOffMeshConnection def;
|
|
|
|
fread(&def, sizeof(dtOffMeshConnection), 1, savedFile);
|
|
|
|
unsigned char area = def.area;
|
|
|
|
if (def.flags & SAMPLE_POLYFLAGS_WALLCLIMB)
|
|
{
|
|
area = SAMPLE_POLYAREA_WALLCLIMB;
|
|
}
|
|
|
|
if (def.flags & SAMPLE_POLYFLAGS_LADDER)
|
|
{
|
|
area = SAMPLE_POLYAREA_LADDER;
|
|
}
|
|
|
|
if (def.flags & SAMPLE_POLYFLAGS_LIFT)
|
|
{
|
|
area = SAMPLE_POLYAREA_LIFT;
|
|
}
|
|
|
|
AvHAIOffMeshConnection NewMapConnection;
|
|
NewMapConnection.ConnectionFlags = def.flags;
|
|
NewMapConnection.DefaultConnectionFlags = def.flags;
|
|
NewMapConnection.TargetObject = nullptr;
|
|
NewMapConnection.FromLocation = Vector(def.pos[0], -def.pos[2], def.pos[1]);
|
|
NewMapConnection.ToLocation = Vector(def.pos[3], -def.pos[5], def.pos[4]);
|
|
|
|
for (int ii = 0; ii < BUILDING_NAV_MESH; ii++)
|
|
{
|
|
dtOffMeshConnectionRef ref = 0;
|
|
|
|
NavMeshes[ii].tileCache->addOffMeshConnection(&def.pos[0], &def.pos[3], 10.0f, area, def.flags, def.bBiDir, &ref);
|
|
|
|
NewMapConnection.ConnectionRefs[ii] = (unsigned int)ref;
|
|
}
|
|
|
|
BaseMapConnections.push_back(NewMapConnection);
|
|
}
|
|
|
|
fseek(savedFile, header.NavHintOffset, SEEK_SET);
|
|
|
|
for (int i = 0; i < header.NumNavHints; i++)
|
|
{
|
|
LoadNavHint LoadedHint;
|
|
fread(&LoadedHint, sizeof(LoadNavHint), 1, savedFile);
|
|
|
|
NavHint NewHint;
|
|
NewHint.hintType = LoadedHint.hintType;
|
|
NewHint.Position = Vector(LoadedHint.position[0], -LoadedHint.position[2], LoadedHint.position[1]);
|
|
NewHint.OccupyingBuilding = nullptr;
|
|
|
|
MapNavHints.push_back(NewHint);
|
|
}
|
|
|
|
fclose(savedFile);
|
|
|
|
for (int i = 0; i <= BUILDING_NAV_MESH; i++)
|
|
{
|
|
NavMeshes[i].navQuery = dtAllocNavMeshQuery();
|
|
|
|
dtStatus initStatus = NavMeshes[i].navQuery->init(NavMeshes[i].navMesh, 65535);
|
|
|
|
if (dtStatusFailed(initStatus))
|
|
{
|
|
UnloadNavigationData();
|
|
g_engfuncs.pfnServerPrint("The nav file has been corrupted or is out of date. Use the Nav Editor to regenerate it\n");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
char SuccMsg[128];
|
|
sprintf(SuccMsg, "Navigation data for %s loaded successfully\n", mapname);
|
|
g_engfuncs.pfnServerPrint(SuccMsg);
|
|
|
|
|
|
return true;
|
|
}
|
|
|
|
void OnOffMeshConnectionAdded(dtOffMeshConnection* NewConnection)
|
|
{
|
|
for (int i = 0; i <= BUILDING_NAV_MESH; i++)
|
|
{
|
|
if (NavMeshes[i].navMesh && NavMeshes[i].tileCache)
|
|
{
|
|
NavMeshes[i].navMesh->LinkOffMeshConnectionToTiles(NewConnection);
|
|
|
|
dtCompressedTile* ModifiedTile = NavMeshes[i].tileCache->getTileAt(NewConnection->FromTileX, NewConnection->FromTileY, NewConnection->FromTileLayer);
|
|
NavMeshes[i].tileCache->buildNavMeshTile(NavMeshes[i].tileCache->getTileRef(ModifiedTile), NavMeshes[i].navMesh);
|
|
}
|
|
}
|
|
}
|
|
|
|
void UTIL_PopulateBaseNavProfiles()
|
|
{
|
|
memset(BaseNavProfiles, 0, sizeof(BaseNavProfiles));
|
|
|
|
BaseNavProfiles[MARINE_BASE_NAV_PROFILE].NavMeshIndex = REGULAR_NAV_MESH;
|
|
BaseNavProfiles[MARINE_BASE_NAV_PROFILE].bFlyingProfile = false;
|
|
BaseNavProfiles[MARINE_BASE_NAV_PROFILE].ReachabilityFlag = AI_REACHABILITY_MARINE;
|
|
BaseNavProfiles[MARINE_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_GROUND, 1.0f);
|
|
BaseNavProfiles[MARINE_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_OBSTRUCTION, 2.0f);
|
|
BaseNavProfiles[MARINE_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_CROUCH, 2.0f);
|
|
BaseNavProfiles[MARINE_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_BLOCKED, 2.0f);
|
|
BaseNavProfiles[MARINE_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_STRUCTUREBLOCK, 20.0f);
|
|
BaseNavProfiles[MARINE_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_FALLDAMAGE, 10.0f);
|
|
BaseNavProfiles[MARINE_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_LADDER, 1.5f);
|
|
BaseNavProfiles[MARINE_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_LIFT, 3.0f);
|
|
BaseNavProfiles[MARINE_BASE_NAV_PROFILE].Filters.setIncludeFlags(SAMPLE_POLYFLAGS_ALL);
|
|
BaseNavProfiles[MARINE_BASE_NAV_PROFILE].Filters.removeIncludeFlags(SAMPLE_POLYFLAGS_FLY | SAMPLE_POLYFLAGS_WALLCLIMB | SAMPLE_POLYFLAGS_WELD);
|
|
BaseNavProfiles[MARINE_BASE_NAV_PROFILE].Filters.setExcludeFlags(SAMPLE_POLYFLAGS_DISABLED);
|
|
|
|
|
|
BaseNavProfiles[SKULK_BASE_NAV_PROFILE].NavMeshIndex = REGULAR_NAV_MESH;
|
|
BaseNavProfiles[SKULK_BASE_NAV_PROFILE].bFlyingProfile = false;
|
|
BaseNavProfiles[SKULK_BASE_NAV_PROFILE].ReachabilityFlag = AI_REACHABILITY_SKULK;
|
|
BaseNavProfiles[SKULK_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_GROUND, 1.0f);
|
|
BaseNavProfiles[SKULK_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_CROUCH, 1.0f);
|
|
BaseNavProfiles[SKULK_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_OBSTRUCTION, 2.0f);
|
|
BaseNavProfiles[SKULK_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_BLOCKED, 2.0f);
|
|
BaseNavProfiles[SKULK_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_STRUCTUREBLOCK, 20.0f);
|
|
BaseNavProfiles[SKULK_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_LADDER, 1.5f);
|
|
BaseNavProfiles[SKULK_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_WALLCLIMB, 1.0f);
|
|
BaseNavProfiles[SKULK_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_LIFT, 3.0f);
|
|
BaseNavProfiles[SKULK_BASE_NAV_PROFILE].Filters.setIncludeFlags(SAMPLE_POLYFLAGS_ALL);
|
|
BaseNavProfiles[SKULK_BASE_NAV_PROFILE].Filters.setExcludeFlags(SAMPLE_POLYFLAGS_DISABLED);
|
|
BaseNavProfiles[SKULK_BASE_NAV_PROFILE].Filters.removeIncludeFlags(SAMPLE_POLYFLAGS_TEAM1PHASEGATE | SAMPLE_POLYFLAGS_TEAM2PHASEGATE | SAMPLE_POLYFLAGS_DUCKJUMP | SAMPLE_POLYFLAGS_WELD | SAMPLE_POLYFLAGS_FLY);
|
|
BaseNavProfiles[SKULK_BASE_NAV_PROFILE].Filters.addExcludeFlags(SAMPLE_POLYFLAGS_TEAM1PHASEGATE | SAMPLE_POLYFLAGS_TEAM2PHASEGATE | SAMPLE_POLYFLAGS_DUCKJUMP | SAMPLE_POLYFLAGS_WELD | SAMPLE_POLYFLAGS_FLY);
|
|
|
|
|
|
|
|
BaseNavProfiles[GORGE_BASE_NAV_PROFILE].NavMeshIndex = REGULAR_NAV_MESH;
|
|
BaseNavProfiles[GORGE_BASE_NAV_PROFILE].bFlyingProfile = false;
|
|
BaseNavProfiles[GORGE_BASE_NAV_PROFILE].ReachabilityFlag = AI_REACHABILITY_GORGE;
|
|
BaseNavProfiles[GORGE_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_GROUND, 1.0f);
|
|
BaseNavProfiles[GORGE_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_CROUCH, 1.0f);
|
|
BaseNavProfiles[GORGE_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_OBSTRUCTION, 2.0f);
|
|
BaseNavProfiles[GORGE_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_BLOCKED, 2.0f);
|
|
BaseNavProfiles[GORGE_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_FALLDAMAGE, 10.0f);
|
|
BaseNavProfiles[GORGE_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_STRUCTUREBLOCK, 20.0f);
|
|
BaseNavProfiles[GORGE_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_LADDER, 1.5f);
|
|
BaseNavProfiles[GORGE_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_LIFT, 3.0f);
|
|
BaseNavProfiles[GORGE_BASE_NAV_PROFILE].Filters.setIncludeFlags(SAMPLE_POLYFLAGS_ALL);
|
|
BaseNavProfiles[GORGE_BASE_NAV_PROFILE].Filters.setExcludeFlags(SAMPLE_POLYFLAGS_DISABLED);
|
|
BaseNavProfiles[GORGE_BASE_NAV_PROFILE].Filters.removeIncludeFlags(SAMPLE_POLYFLAGS_TEAM1PHASEGATE | SAMPLE_POLYFLAGS_TEAM2PHASEGATE | SAMPLE_POLYFLAGS_DUCKJUMP | SAMPLE_POLYFLAGS_WELD | SAMPLE_POLYFLAGS_FLY | SAMPLE_POLYFLAGS_WALLCLIMB);
|
|
BaseNavProfiles[GORGE_BASE_NAV_PROFILE].Filters.addExcludeFlags(SAMPLE_POLYFLAGS_TEAM1PHASEGATE | SAMPLE_POLYFLAGS_TEAM2PHASEGATE | SAMPLE_POLYFLAGS_DUCKJUMP | SAMPLE_POLYFLAGS_WELD | SAMPLE_POLYFLAGS_FLY | SAMPLE_POLYFLAGS_WALLCLIMB);
|
|
|
|
BaseNavProfiles[LERK_BASE_NAV_PROFILE].NavMeshIndex = REGULAR_NAV_MESH;
|
|
BaseNavProfiles[LERK_BASE_NAV_PROFILE].bFlyingProfile = true;
|
|
BaseNavProfiles[LERK_BASE_NAV_PROFILE].ReachabilityFlag = AI_REACHABILITY_SKULK;
|
|
BaseNavProfiles[LERK_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_GROUND, 1.0f);
|
|
BaseNavProfiles[LERK_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_CROUCH, 1.0f);
|
|
BaseNavProfiles[LERK_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_OBSTRUCTION, 2.0f);
|
|
BaseNavProfiles[LERK_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_BLOCKED, 2.0f);
|
|
BaseNavProfiles[LERK_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_STRUCTUREBLOCK, 20.0f);
|
|
BaseNavProfiles[LERK_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_LADDER, 1.0f);
|
|
BaseNavProfiles[LERK_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_LIFT, 3.0f);
|
|
BaseNavProfiles[LERK_BASE_NAV_PROFILE].Filters.setIncludeFlags(SAMPLE_POLYFLAGS_ALL);
|
|
BaseNavProfiles[LERK_BASE_NAV_PROFILE].Filters.setExcludeFlags(SAMPLE_POLYFLAGS_DISABLED);
|
|
BaseNavProfiles[LERK_BASE_NAV_PROFILE].Filters.removeIncludeFlags(SAMPLE_POLYFLAGS_TEAM1PHASEGATE | SAMPLE_POLYFLAGS_TEAM2PHASEGATE | SAMPLE_POLYFLAGS_WELD);
|
|
BaseNavProfiles[LERK_BASE_NAV_PROFILE].Filters.addExcludeFlags(SAMPLE_POLYFLAGS_TEAM1PHASEGATE | SAMPLE_POLYFLAGS_TEAM2PHASEGATE | SAMPLE_POLYFLAGS_WELD);
|
|
|
|
BaseNavProfiles[FADE_BASE_NAV_PROFILE].NavMeshIndex = REGULAR_NAV_MESH;
|
|
BaseNavProfiles[FADE_BASE_NAV_PROFILE].bFlyingProfile = false;
|
|
BaseNavProfiles[FADE_BASE_NAV_PROFILE].ReachabilityFlag = AI_REACHABILITY_SKULK;
|
|
BaseNavProfiles[FADE_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_GROUND, 1.0f);
|
|
BaseNavProfiles[FADE_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_CROUCH, 1.0f);
|
|
BaseNavProfiles[FADE_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_OBSTRUCTION, 2.0f);
|
|
BaseNavProfiles[FADE_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_CROUCH, 1.5f);
|
|
BaseNavProfiles[FADE_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_BLOCKED, 2.0f);
|
|
BaseNavProfiles[FADE_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_STRUCTUREBLOCK, 20.0f);
|
|
BaseNavProfiles[FADE_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_LADDER, 1.5f);
|
|
BaseNavProfiles[FADE_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_LIFT, 3.0f);
|
|
BaseNavProfiles[FADE_BASE_NAV_PROFILE].Filters.setIncludeFlags(SAMPLE_POLYFLAGS_ALL);
|
|
BaseNavProfiles[FADE_BASE_NAV_PROFILE].Filters.setExcludeFlags(SAMPLE_POLYFLAGS_DISABLED);
|
|
BaseNavProfiles[FADE_BASE_NAV_PROFILE].Filters.removeIncludeFlags(SAMPLE_POLYFLAGS_TEAM1PHASEGATE | SAMPLE_POLYFLAGS_TEAM2PHASEGATE | SAMPLE_POLYFLAGS_WELD | SAMPLE_POLYFLAGS_FLY);
|
|
BaseNavProfiles[FADE_BASE_NAV_PROFILE].Filters.addExcludeFlags(SAMPLE_POLYFLAGS_TEAM1PHASEGATE | SAMPLE_POLYFLAGS_TEAM2PHASEGATE | SAMPLE_POLYFLAGS_WELD | SAMPLE_POLYFLAGS_FLY);
|
|
|
|
BaseNavProfiles[ONOS_BASE_NAV_PROFILE].NavMeshIndex = ONOS_NAV_MESH;
|
|
BaseNavProfiles[ONOS_BASE_NAV_PROFILE].bFlyingProfile = false;
|
|
BaseNavProfiles[ONOS_BASE_NAV_PROFILE].ReachabilityFlag = AI_REACHABILITY_ONOS;
|
|
BaseNavProfiles[ONOS_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_GROUND, 1.0f);
|
|
BaseNavProfiles[ONOS_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_OBSTRUCTION, 2.0f);
|
|
BaseNavProfiles[ONOS_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_CROUCH, 2.0f);
|
|
BaseNavProfiles[ONOS_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_FALLDAMAGE, 10.0f);
|
|
BaseNavProfiles[ONOS_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_BLOCKED, 2.0f);
|
|
BaseNavProfiles[ONOS_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_STRUCTUREBLOCK, 5.0f); // Onos is a wrecking machine, structures shouldn't be such an obstacle for them!
|
|
BaseNavProfiles[ONOS_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_LADDER, 1.5f);
|
|
BaseNavProfiles[ONOS_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_LIFT, 3.0f);
|
|
BaseNavProfiles[ONOS_BASE_NAV_PROFILE].Filters.setIncludeFlags(SAMPLE_POLYFLAGS_ALL);
|
|
BaseNavProfiles[ONOS_BASE_NAV_PROFILE].Filters.setExcludeFlags(SAMPLE_POLYFLAGS_DISABLED);
|
|
BaseNavProfiles[ONOS_BASE_NAV_PROFILE].Filters.removeIncludeFlags(SAMPLE_POLYFLAGS_TEAM1PHASEGATE | SAMPLE_POLYFLAGS_TEAM2PHASEGATE | SAMPLE_POLYFLAGS_DUCKJUMP | SAMPLE_POLYFLAGS_WELD | SAMPLE_POLYFLAGS_FLY | SAMPLE_POLYFLAGS_WALLCLIMB | SAMPLE_POLYFLAGS_NOONOS);
|
|
BaseNavProfiles[ONOS_BASE_NAV_PROFILE].Filters.addExcludeFlags(SAMPLE_POLYFLAGS_TEAM1PHASEGATE | SAMPLE_POLYFLAGS_TEAM2PHASEGATE | SAMPLE_POLYFLAGS_DUCKJUMP | SAMPLE_POLYFLAGS_WELD | SAMPLE_POLYFLAGS_FLY | SAMPLE_POLYFLAGS_WALLCLIMB | SAMPLE_POLYFLAGS_NOONOS);
|
|
|
|
BaseNavProfiles[STRUCTURE_BASE_NAV_PROFILE].NavMeshIndex = BUILDING_NAV_MESH;
|
|
BaseNavProfiles[STRUCTURE_BASE_NAV_PROFILE].Filters.setIncludeFlags(SAMPLE_POLYFLAGS_ALL);
|
|
BaseNavProfiles[STRUCTURE_BASE_NAV_PROFILE].Filters.setExcludeFlags(SAMPLE_POLYFLAGS_DISABLED);
|
|
BaseNavProfiles[STRUCTURE_BASE_NAV_PROFILE].bFlyingProfile = false;
|
|
BaseNavProfiles[STRUCTURE_BASE_NAV_PROFILE].ReachabilityFlag = AI_REACHABILITY_MARINE;
|
|
|
|
BaseNavProfiles[ALL_NAV_PROFILE].NavMeshIndex = REGULAR_NAV_MESH;
|
|
BaseNavProfiles[ALL_NAV_PROFILE].Filters.setIncludeFlags(SAMPLE_POLYFLAGS_ALL);
|
|
BaseNavProfiles[ALL_NAV_PROFILE].Filters.setExcludeFlags(SAMPLE_POLYFLAGS_DISABLED);
|
|
BaseNavProfiles[ALL_NAV_PROFILE].bFlyingProfile = false;
|
|
BaseNavProfiles[ALL_NAV_PROFILE].ReachabilityFlag = AI_REACHABILITY_SKULK;
|
|
}
|
|
|
|
bool loadNavigationData(const char* mapname)
|
|
{
|
|
|
|
UnloadNavigationData();
|
|
|
|
if (!LoadNavMesh(mapname))
|
|
{
|
|
NavmeshStatus = NAVMESH_STATUS_FAILED;
|
|
return false;
|
|
}
|
|
|
|
NavmeshStatus = NAVMESH_STATUS_SUCCESS;
|
|
|
|
UTIL_PopulateBaseNavProfiles();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool NavmeshLoaded()
|
|
{
|
|
return NavMeshes[0].navMesh != nullptr;
|
|
}
|
|
|
|
AvHAINavMeshStatus NAV_GetNavMeshStatus()
|
|
{
|
|
return NavmeshStatus;
|
|
}
|
|
|
|
Vector UTIL_GetRandomPointOnNavmesh(const AvHAIPlayer* pBot)
|
|
{
|
|
const dtNavMeshQuery* m_navQuery = UTIL_GetNavMeshQueryForProfile(pBot->BotNavInfo.NavProfile);
|
|
const dtQueryFilter* m_navFilter = &pBot->BotNavInfo.NavProfile.Filters;
|
|
|
|
if (!m_navQuery)
|
|
{
|
|
return g_vecZero;
|
|
}
|
|
|
|
Vector Result;
|
|
|
|
dtPolyRef refPoly;
|
|
|
|
float result[3];
|
|
memset(result, 0, sizeof(result));
|
|
|
|
dtStatus status = m_navQuery->findRandomPoint(m_navFilter, frand, &refPoly, result);
|
|
|
|
if (dtStatusFailed(status))
|
|
{
|
|
return g_vecZero;
|
|
}
|
|
|
|
Result.x = result[0];
|
|
Result.y = -result[2];
|
|
Result.z = result[1];
|
|
|
|
return Result;
|
|
}
|
|
|
|
Vector UTIL_GetRandomPointOnNavmeshInRadiusOfAreaType(SamplePolyFlags Flag, const Vector origin, const float MaxRadius)
|
|
{
|
|
const dtNavMeshQuery* m_NavQuery = UTIL_GetNavMeshQueryForProfile(BaseNavProfiles[ALL_NAV_PROFILE]);
|
|
|
|
if (!m_NavQuery) { return g_vecZero; }
|
|
|
|
dtQueryFilter filter;
|
|
filter.setExcludeFlags(0);
|
|
filter.setIncludeFlags(Flag);
|
|
|
|
Vector Result = g_vecZero;
|
|
|
|
float pCheckLoc[3] = { origin.x, origin.z, -origin.y };
|
|
|
|
dtPolyRef FoundPoly;
|
|
float NavNearest[3];
|
|
|
|
dtStatus foundPolyResult = m_NavQuery->findNearestPoly(pCheckLoc, pExtents, &filter, &FoundPoly, NavNearest);
|
|
|
|
if (dtStatusFailed(foundPolyResult))
|
|
{
|
|
return g_vecZero;
|
|
}
|
|
|
|
dtPolyRef RandomPoly;
|
|
float RandomPoint[3];
|
|
|
|
dtStatus foundRandomPointResult = m_NavQuery->findRandomPointAroundCircle(FoundPoly, NavNearest, MaxRadius, &filter, frand, &RandomPoly, RandomPoint);
|
|
|
|
|
|
if (dtStatusFailed(foundRandomPointResult))
|
|
{
|
|
return g_vecZero;
|
|
}
|
|
|
|
Result.x = RandomPoint[0];
|
|
Result.y = -RandomPoint[2];
|
|
Result.z = RandomPoint[1];
|
|
|
|
return Result;
|
|
}
|
|
|
|
Vector UTIL_GetRandomPointOnNavmeshInRadius(const nav_profile &NavProfile, const Vector origin, const float MaxRadius)
|
|
{
|
|
const dtNavMeshQuery* m_navQuery = UTIL_GetNavMeshQueryForProfile(NavProfile);
|
|
const dtQueryFilter* m_navFilter = &NavProfile.Filters;
|
|
|
|
if (!m_navQuery) { return g_vecZero; }
|
|
|
|
Vector Result = g_vecZero;
|
|
|
|
float pCheckLoc[3] = { origin.x, origin.z, -origin.y };
|
|
|
|
dtPolyRef FoundPoly;
|
|
float NavNearest[3];
|
|
|
|
dtStatus foundPolyResult = m_navQuery->findNearestPoly(pCheckLoc, pExtents, m_navFilter, &FoundPoly, NavNearest);
|
|
|
|
if (dtStatusFailed(foundPolyResult))
|
|
{
|
|
return g_vecZero;
|
|
}
|
|
|
|
dtPolyRef RandomPoly;
|
|
float RandomPoint[3];
|
|
|
|
dtStatus foundRandomPointResult = m_navQuery->findRandomPointAroundCircle(FoundPoly, NavNearest, MaxRadius, m_navFilter, frand, &RandomPoly, RandomPoint);
|
|
|
|
|
|
if (dtStatusFailed(foundRandomPointResult))
|
|
{
|
|
return g_vecZero;
|
|
}
|
|
|
|
Result.x = RandomPoint[0];
|
|
Result.y = -RandomPoint[2];
|
|
Result.z = RandomPoint[1];
|
|
|
|
return Result;
|
|
}
|
|
|
|
Vector UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(const nav_profile& NavProfile, const Vector origin, const float MaxRadius)
|
|
{
|
|
const dtNavMeshQuery* m_navQuery = UTIL_GetNavMeshQueryForProfile(NavProfile);
|
|
const dtQueryFilter* m_navFilter = &NavProfile.Filters;
|
|
|
|
if (!m_navQuery) { return g_vecZero; }
|
|
|
|
Vector Result = g_vecZero;
|
|
|
|
float pCheckLoc[3] = { origin.x, origin.z, -origin.y };
|
|
|
|
dtPolyRef FoundPoly;
|
|
float NavNearest[3];
|
|
|
|
dtStatus foundPolyResult = m_navQuery->findNearestPoly(pCheckLoc, pExtents, m_navFilter, &FoundPoly, NavNearest);
|
|
|
|
if (dtStatusFailed(foundPolyResult))
|
|
{
|
|
return g_vecZero;
|
|
}
|
|
|
|
dtPolyRef RandomPoly;
|
|
float RandomPoint[3];
|
|
|
|
dtStatus foundRandomPointResult = m_navQuery->findRandomPointAroundCircleIgnoreReachability(FoundPoly, NavNearest, MaxRadius, m_navFilter, frand, &RandomPoly, RandomPoint);
|
|
|
|
|
|
if (dtStatusFailed(foundRandomPointResult))
|
|
{
|
|
return g_vecZero;
|
|
}
|
|
|
|
Result.x = RandomPoint[0];
|
|
Result.y = -RandomPoint[2];
|
|
Result.z = RandomPoint[1];
|
|
|
|
return Result;
|
|
}
|
|
|
|
Vector UTIL_GetRandomPointOnNavmeshInDonut(const nav_profile& NavProfile, const Vector origin, const float MinRadius, const float MaxRadius)
|
|
{
|
|
int maxIterations = 0;
|
|
float MinRadiusSq = sqrf(MinRadius);
|
|
|
|
while (maxIterations < 100)
|
|
{
|
|
Vector StartPoint = UTIL_GetRandomPointOnNavmeshInRadius(NavProfile, origin, MaxRadius);
|
|
|
|
if (vDist2DSq(StartPoint, origin) > MinRadiusSq)
|
|
{
|
|
return StartPoint;
|
|
}
|
|
|
|
maxIterations++;
|
|
}
|
|
|
|
return g_vecZero;
|
|
}
|
|
|
|
Vector UTIL_GetRandomPointOnNavmeshInDonutIgnoreReachability(const nav_profile& NavProfile, const Vector origin, const float MinRadius, const float MaxRadius)
|
|
{
|
|
int maxIterations = 0;
|
|
float MinRadiusSq = sqrf(MinRadius);
|
|
|
|
while (maxIterations < 100)
|
|
{
|
|
Vector StartPoint = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(NavProfile, origin, MaxRadius);
|
|
|
|
if (vDist2DSq(StartPoint, origin) > MinRadiusSq)
|
|
{
|
|
return StartPoint;
|
|
}
|
|
|
|
maxIterations++;
|
|
}
|
|
|
|
return g_vecZero;
|
|
}
|
|
|
|
static float frand()
|
|
{
|
|
return (float)rand() / (float)RAND_MAX;
|
|
}
|
|
|
|
Vector AdjustPointForPathfinding(const Vector Point)
|
|
{
|
|
Vector ProjectedPoint = UTIL_ProjectPointToNavmesh(Point);
|
|
|
|
int PointContents = UTIL_PointContents(ProjectedPoint);
|
|
|
|
if (PointContents == CONTENTS_SOLID)
|
|
{
|
|
int PointContents = UTIL_PointContents(ProjectedPoint + Vector(0.0f, 0.0f, 32.0f));
|
|
|
|
if (PointContents != CONTENTS_SOLID && PointContents != CONTENTS_LADDER)
|
|
{
|
|
Vector TraceStart = ProjectedPoint + Vector(0.0f, 0.0f, 32.0f);
|
|
Vector TraceEnd = TraceStart - Vector(0.0f, 0.0f, 50.0f);
|
|
Vector NewPoint = UTIL_GetHullTraceHitLocation(TraceStart, TraceEnd, point_hull);
|
|
|
|
if (!vIsZero(NewPoint)) { return NewPoint; }
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Vector TraceStart = ProjectedPoint + Vector(0.0f, 0.0f, 5.0f);
|
|
Vector TraceEnd = TraceStart - Vector(0.0f, 0.0f, 32.0f);
|
|
Vector NewPoint = UTIL_GetHullTraceHitLocation(TraceStart, TraceEnd, point_hull);
|
|
|
|
if (!vIsZero(NewPoint)) { return NewPoint; }
|
|
}
|
|
|
|
return ProjectedPoint;
|
|
|
|
}
|
|
|
|
Vector AdjustPointForPathfinding(const Vector Point, const nav_profile& NavProfile)
|
|
{
|
|
Vector ProjectedPoint = UTIL_ProjectPointToNavmesh(Point, Vector(400.0f, 100.0f, 400.0f), NavProfile);
|
|
|
|
int PointContents = UTIL_PointContents(ProjectedPoint);
|
|
|
|
if (PointContents == CONTENTS_SOLID)
|
|
{
|
|
int PointContents = UTIL_PointContents(ProjectedPoint + Vector(0.0f, 0.0f, 32.0f));
|
|
|
|
if (PointContents != CONTENTS_SOLID && PointContents != CONTENTS_LADDER)
|
|
{
|
|
Vector TraceStart = ProjectedPoint + Vector(0.0f, 0.0f, 32.0f);
|
|
Vector TraceEnd = TraceStart - Vector(0.0f, 0.0f, 50.0f);
|
|
Vector NewPoint = UTIL_GetHullTraceHitLocation(TraceStart, TraceEnd, point_hull);
|
|
|
|
if (!vIsZero(NewPoint)) { return NewPoint; }
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Vector TraceStart = ProjectedPoint + Vector(0.0f, 0.0f, 5.0f);
|
|
Vector TraceEnd = TraceStart - Vector(0.0f, 0.0f, 32.0f);
|
|
Vector NewPoint = UTIL_GetHullTraceHitLocation(TraceStart, TraceEnd, point_hull);
|
|
|
|
if (!vIsZero(NewPoint)) { return NewPoint; }
|
|
}
|
|
|
|
return ProjectedPoint;
|
|
}
|
|
|
|
// Special path finding that takes flight movement into account
|
|
dtStatus FindFlightPathToPoint(const nav_profile &NavProfile, Vector FromLocation, Vector ToLocation, vector<bot_path_node>& path, float MaxAcceptableDistance)
|
|
{
|
|
TraceResult directHit;
|
|
|
|
if (UTIL_QuickHullTrace(nullptr, FromLocation, ToLocation, head_hull, false))
|
|
{
|
|
path.clear();
|
|
|
|
bot_path_node NewPathNode;
|
|
NewPathNode.FromLocation = FromLocation;
|
|
NewPathNode.Location = ToLocation;
|
|
NewPathNode.area = SAMPLE_POLYAREA_GROUND;
|
|
NewPathNode.flag = SAMPLE_POLYFLAGS_WALK;
|
|
NewPathNode.poly = 0;
|
|
NewPathNode.requiredZ = ToLocation.z;
|
|
|
|
path.push_back(NewPathNode);
|
|
|
|
return DT_SUCCESS;
|
|
}
|
|
|
|
const dtNavMesh* m_navMesh = UTIL_GetNavMeshForProfile(NavProfile);
|
|
const dtNavMeshQuery* m_navQuery = UTIL_GetNavMeshQueryForProfile(NavProfile);
|
|
const dtQueryFilter* m_navFilter = &NavProfile.Filters;
|
|
|
|
if (!m_navQuery || !m_navMesh || !m_navFilter || vIsZero(FromLocation) || vIsZero(ToLocation))
|
|
{
|
|
return DT_FAILURE;
|
|
}
|
|
|
|
Vector FromFloorLocation = AdjustPointForPathfinding(FromLocation);
|
|
Vector ToFloorLocation = AdjustPointForPathfinding(ToLocation);
|
|
|
|
float pStartPos[3] = { FromFloorLocation.x, FromFloorLocation.z, -FromFloorLocation.y };
|
|
float pEndPos[3] = { ToFloorLocation.x, ToFloorLocation.z, -ToFloorLocation.y };
|
|
|
|
dtStatus status;
|
|
dtPolyRef StartPoly = 0;
|
|
float StartNearest[3] = { 0.0f, 0.0f, 0.0f };
|
|
dtPolyRef EndPoly = 0;
|
|
float EndNearest[3] = { 0.0f, 0.0f, 0.0f };
|
|
dtPolyRef PolyPath[MAX_PATH_POLY];
|
|
dtPolyRef StraightPolyPath[MAX_AI_PATH_SIZE];
|
|
int nPathCount = 0;
|
|
float StraightPath[MAX_AI_PATH_SIZE * 3];
|
|
unsigned char straightPathFlags[MAX_AI_PATH_SIZE];
|
|
memset(straightPathFlags, 0, sizeof(straightPathFlags));
|
|
int nVertCount = 0;
|
|
|
|
// find the start polygon
|
|
status = m_navQuery->findNearestPoly(pStartPos, pExtents, m_navFilter, &StartPoly, StartNearest);
|
|
if (!StartPoly || (status & DT_FAILURE) || (status & DT_STATUS_DETAIL_MASK))
|
|
{
|
|
//BotSay(pBot, "findNearestPoly start failed!");
|
|
return (status & DT_STATUS_DETAIL_MASK); // couldn't find a polygon
|
|
}
|
|
|
|
// find the end polygon
|
|
status = m_navQuery->findNearestPoly(pEndPos, pExtents, m_navFilter, &EndPoly, EndNearest);
|
|
if (!EndPoly || (status & DT_FAILURE) || (status & DT_STATUS_DETAIL_MASK))
|
|
{
|
|
//BotSay(pBot, "findNearestPoly end failed!");
|
|
return (status & DT_STATUS_DETAIL_MASK); // couldn't find a polygon
|
|
}
|
|
|
|
status = m_navQuery->findPath(StartPoly, EndPoly, StartNearest, EndNearest, m_navFilter, PolyPath, &nPathCount, MAX_PATH_POLY);
|
|
|
|
if (nPathCount == 0) { return DT_FAILURE; }
|
|
|
|
if (PolyPath[nPathCount - 1] != EndPoly)
|
|
{
|
|
float epos[3];
|
|
dtVcopy(epos, EndNearest);
|
|
|
|
m_navQuery->closestPointOnPoly(PolyPath[nPathCount - 1], EndNearest, epos, 0);
|
|
|
|
if (dtVdistSqr(EndNearest, epos) > sqrf(MaxAcceptableDistance))
|
|
{
|
|
return DT_FAILURE;
|
|
}
|
|
else
|
|
{
|
|
dtVcopy(EndNearest, epos);
|
|
}
|
|
}
|
|
|
|
status = m_navQuery->findStraightPath(StartNearest, EndNearest, PolyPath, nPathCount, StraightPath, straightPathFlags, StraightPolyPath, &nVertCount, MAX_AI_PATH_SIZE, DT_STRAIGHTPATH_ALL_CROSSINGS);
|
|
if ((status & DT_FAILURE) || (status & DT_STATUS_DETAIL_MASK))
|
|
{
|
|
return (status & DT_STATUS_DETAIL_MASK); // couldn't create a path
|
|
}
|
|
|
|
if (nVertCount == 0)
|
|
{
|
|
return DT_FAILURE; // couldn't find a path
|
|
}
|
|
|
|
path.clear();
|
|
|
|
//vector<bot_path_node> InitialPath;
|
|
//InitialPath.clear();
|
|
|
|
unsigned char CurrArea;
|
|
unsigned char ThisArea;
|
|
|
|
unsigned int CurrFlags;
|
|
unsigned int ThisFlags;
|
|
|
|
m_navMesh->getPolyArea(StraightPolyPath[0], &CurrArea);
|
|
m_navMesh->getPolyFlags(StraightPolyPath[0], &CurrFlags);
|
|
|
|
CurrFlags &= ~(SAMPLE_POLYFLAGS_NOONOS);
|
|
|
|
// At this point we have our path. Copy it to the path store
|
|
int nIndex = 0;
|
|
TraceResult hit;
|
|
Vector TraceStart;
|
|
|
|
for (int nVert = 0; nVert < nVertCount; nVert++)
|
|
{
|
|
Vector NextPathPoint = g_vecZero;
|
|
Vector PrevPoint = (path.size() > 0) ? path.back().Location : FromFloorLocation;
|
|
|
|
// The path point output by Detour uses the OpenGL, right-handed coordinate system. Convert to Goldsrc coordinates
|
|
NextPathPoint.x = StraightPath[nIndex++];
|
|
NextPathPoint.z = StraightPath[nIndex++];
|
|
NextPathPoint.y = -StraightPath[nIndex++];
|
|
|
|
m_navMesh->getPolyArea(StraightPolyPath[nVert], &ThisArea);
|
|
m_navMesh->getPolyFlags(StraightPolyPath[nVert], &ThisFlags);
|
|
|
|
ThisFlags &= ~(SAMPLE_POLYFLAGS_NOONOS);
|
|
|
|
if (ThisArea == SAMPLE_POLYAREA_GROUND || ThisArea == SAMPLE_POLYAREA_CROUCH)
|
|
{
|
|
NextPathPoint = UTIL_AdjustPointAwayFromNavWall(NextPathPoint, 16.0f);
|
|
}
|
|
|
|
AdjustPointForPathfinding(NextPathPoint);
|
|
|
|
NextPathPoint.z += 20.0f;
|
|
|
|
float NewRequiredZ = NextPathPoint.z;
|
|
|
|
bot_path_node NextPathNode;
|
|
|
|
if (CurrFlags == SAMPLE_POLYFLAGS_LIFT)
|
|
{
|
|
NextPathNode.flag = CurrFlags;
|
|
}
|
|
else
|
|
{
|
|
NextPathNode.flag = SAMPLE_POLYFLAGS_WALK;
|
|
}
|
|
|
|
NextPathNode.area = CurrArea;
|
|
NextPathNode.poly = StraightPolyPath[nVert];
|
|
|
|
|
|
if (CurrFlags == SAMPLE_POLYFLAGS_JUMP || CurrFlags == SAMPLE_POLYFLAGS_WALLCLIMB || CurrFlags == SAMPLE_POLYFLAGS_FLY)
|
|
{
|
|
float MaxHeight = (CurrFlags == SAMPLE_POLYFLAGS_JUMP) ? fmaxf(PrevPoint.z, NextPathPoint.z) + 60.0f : UTIL_FindZHeightForWallClimb(PrevPoint, NextPathPoint, head_hull);
|
|
|
|
NextPathNode.requiredZ = MaxHeight;
|
|
NextPathNode.Location = PrevPoint;
|
|
NextPathNode.Location.z = MaxHeight;
|
|
NextPathNode.FromLocation = PrevPoint;
|
|
|
|
PrevPoint = NextPathNode.Location;
|
|
|
|
path.push_back(NextPathNode);
|
|
|
|
NextPathNode.requiredZ = MaxHeight;
|
|
NextPathNode.Location = NextPathPoint;
|
|
NextPathNode.Location.z = MaxHeight;
|
|
NextPathNode.FromLocation = PrevPoint;
|
|
|
|
PrevPoint = NextPathNode.Location;
|
|
|
|
path.push_back(NextPathNode);
|
|
}
|
|
else if (CurrFlags == SAMPLE_POLYFLAGS_LADDER || CurrFlags == SAMPLE_POLYFLAGS_FALL)
|
|
{
|
|
float MaxHeight = fmaxf(PrevPoint.z, NextPathPoint.z);
|
|
|
|
NextPathNode.requiredZ = MaxHeight;
|
|
NextPathNode.Location = (PrevPoint.z < NextPathPoint.z) ? PrevPoint : NextPathPoint;
|
|
NextPathNode.Location.z = MaxHeight;
|
|
NextPathNode.FromLocation = PrevPoint;
|
|
|
|
PrevPoint = NextPathNode.Location;
|
|
|
|
path.push_back(NextPathNode);
|
|
}
|
|
|
|
NextPathNode.requiredZ = NextPathPoint.z;
|
|
NextPathNode.Location = NextPathPoint;
|
|
NextPathNode.FromLocation = PrevPoint;
|
|
|
|
path.push_back(NextPathNode);
|
|
|
|
CurrArea = ThisArea;
|
|
CurrFlags = ThisFlags;
|
|
|
|
}
|
|
|
|
bot_path_node FinalInitialPathNode;
|
|
FinalInitialPathNode.FromLocation = (path.size() > 0) ? path.back().Location : FromLocation;
|
|
FinalInitialPathNode.Location = ToLocation;
|
|
FinalInitialPathNode.area = SAMPLE_POLYAREA_GROUND;
|
|
FinalInitialPathNode.flag = SAMPLE_POLYFLAGS_WALLCLIMB;
|
|
FinalInitialPathNode.poly = 0;
|
|
FinalInitialPathNode.requiredZ = ToLocation.z;
|
|
|
|
path.push_back(FinalInitialPathNode);
|
|
|
|
|
|
return DT_SUCCESS;
|
|
}
|
|
|
|
Vector UTIL_FindHighestSuccessfulTracePoint(const Vector TraceFrom, const Vector TargetPoint, const Vector NextPoint, const float IterationStep, const float MinIdealHeight, const float MaxHeight)
|
|
{
|
|
Vector OriginTrace = TraceFrom;
|
|
float AddedHeight = 0.0f;
|
|
|
|
bool bFoundInitialPoint = false;
|
|
Vector CurrentHighest = ZERO_VECTOR;
|
|
|
|
int NumIterations = (int)ceilf(MaxHeight / IterationStep);
|
|
|
|
Vector CurrentTarget = TargetPoint;
|
|
|
|
for (int i = 0; i <= NumIterations; i++)
|
|
{
|
|
if (!UTIL_QuickTrace(nullptr, TargetPoint, CurrentTarget)) { return CurrentHighest; }
|
|
|
|
if (!UTIL_QuickHullTrace(nullptr, OriginTrace, CurrentTarget, head_hull))
|
|
{
|
|
if (bFoundInitialPoint) { break; }
|
|
}
|
|
else
|
|
{
|
|
bFoundInitialPoint = true;
|
|
if (AddedHeight >= MinIdealHeight)
|
|
{
|
|
return CurrentTarget;
|
|
}
|
|
else
|
|
{
|
|
if (!vIsZero(NextPoint) && UTIL_QuickHullTrace(nullptr, CurrentTarget, NextPoint, head_hull, false))
|
|
{
|
|
CurrentHighest = CurrentTarget;
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
CurrentTarget.z += IterationStep;
|
|
AddedHeight += IterationStep;
|
|
}
|
|
|
|
return CurrentHighest;
|
|
}
|
|
|
|
dtStatus FindPathClosestToPoint(const nav_profile& NavProfile, const Vector FromLocation, const Vector ToLocation, vector<bot_path_node>& path, float MaxAcceptableDistance)
|
|
{
|
|
if (NavProfile.bFlyingProfile)
|
|
{
|
|
return FindFlightPathToPoint(NavProfile, FromLocation, ToLocation, path, MaxAcceptableDistance);
|
|
}
|
|
|
|
const dtNavMeshQuery* m_navQuery = UTIL_GetNavMeshQueryForProfile(NavProfile);
|
|
const dtNavMesh* m_navMesh = UTIL_GetNavMeshForProfile(NavProfile);
|
|
const dtQueryFilter* m_navFilter = &NavProfile.Filters;
|
|
|
|
if (!m_navQuery || !m_navMesh || !m_navFilter || vIsZero(FromLocation) || vIsZero(ToLocation))
|
|
{
|
|
return DT_FAILURE;
|
|
}
|
|
|
|
Vector FromFloorLocation = AdjustPointForPathfinding(FromLocation);
|
|
Vector ToFloorLocation = AdjustPointForPathfinding(ToLocation);
|
|
|
|
float pStartPos[3] = { FromFloorLocation.x, FromFloorLocation.z, -FromFloorLocation.y };
|
|
float pEndPos[3] = { ToFloorLocation.x, ToFloorLocation.z, -ToFloorLocation.y };
|
|
|
|
dtStatus status;
|
|
dtPolyRef StartPoly;
|
|
float StartNearest[3];
|
|
dtPolyRef EndPoly;
|
|
float EndNearest[3];
|
|
dtPolyRef PolyPath[MAX_PATH_POLY];
|
|
dtPolyRef StraightPolyPath[MAX_AI_PATH_SIZE];
|
|
int nPathCount = 0;
|
|
float StraightPath[MAX_AI_PATH_SIZE * 3];
|
|
unsigned char straightPathFlags[MAX_AI_PATH_SIZE];
|
|
memset(straightPathFlags, 0, sizeof(straightPathFlags));
|
|
int nVertCount = 0;
|
|
|
|
// find the start polygon
|
|
status = m_navQuery->findNearestPoly(pStartPos, pExtents, m_navFilter, &StartPoly, StartNearest);
|
|
if ((status & DT_FAILURE) || (status & DT_STATUS_DETAIL_MASK))
|
|
{
|
|
return (status & DT_STATUS_DETAIL_MASK); // couldn't find a polygon
|
|
}
|
|
|
|
// find the end polygon
|
|
status = m_navQuery->findNearestPoly(pEndPos, pExtents, m_navFilter, &EndPoly, EndNearest);
|
|
if ((status & DT_FAILURE) || (status & DT_STATUS_DETAIL_MASK))
|
|
{
|
|
return (status & DT_STATUS_DETAIL_MASK); // couldn't find a polygon
|
|
}
|
|
|
|
status = m_navQuery->findPath(StartPoly, EndPoly, StartNearest, EndNearest, m_navFilter, PolyPath, &nPathCount, MAX_PATH_POLY);
|
|
|
|
if (PolyPath[nPathCount - 1] != EndPoly)
|
|
{
|
|
float epos[3];
|
|
dtVcopy(epos, EndNearest);
|
|
|
|
m_navQuery->closestPointOnPoly(PolyPath[nPathCount - 1], EndNearest, epos, 0);
|
|
|
|
if (dtVdistSqr(EndNearest, epos) > sqrf(MaxAcceptableDistance))
|
|
{
|
|
return DT_FAILURE;
|
|
}
|
|
else
|
|
{
|
|
dtVcopy(EndNearest, epos);
|
|
}
|
|
}
|
|
|
|
status = m_navQuery->findStraightPath(StartNearest, EndNearest, PolyPath, nPathCount, StraightPath, straightPathFlags, StraightPolyPath, &nVertCount, MAX_AI_PATH_SIZE, DT_STRAIGHTPATH_AREA_CROSSINGS);
|
|
if ((status & DT_FAILURE) || (status & DT_STATUS_DETAIL_MASK))
|
|
{
|
|
return (status & DT_STATUS_DETAIL_MASK); // couldn't create a path
|
|
}
|
|
|
|
if (nVertCount == 0)
|
|
{
|
|
return DT_FAILURE; // couldn't find a path
|
|
}
|
|
|
|
path.clear();
|
|
|
|
unsigned int CurrFlags;
|
|
unsigned char CurrArea;
|
|
unsigned char ThisArea;
|
|
unsigned int ThisFlags;
|
|
|
|
m_navMesh->getPolyFlags(StraightPolyPath[0], &CurrFlags);
|
|
m_navMesh->getPolyArea(StraightPolyPath[0], &CurrArea);
|
|
|
|
CurrFlags &= ~(SAMPLE_POLYFLAGS_NOONOS);
|
|
|
|
// At this point we have our path. Copy it to the path store
|
|
int nIndex = 0;
|
|
TraceResult hit;
|
|
Vector TraceStart;
|
|
|
|
Vector NodeFromLocation = FromFloorLocation;
|
|
|
|
for (int nVert = 0; nVert < nVertCount; nVert++)
|
|
{
|
|
bot_path_node NextPathNode;
|
|
|
|
NextPathNode.FromLocation = NodeFromLocation;
|
|
|
|
NextPathNode.Location.x = StraightPath[nIndex++];
|
|
NextPathNode.Location.z = StraightPath[nIndex++];
|
|
NextPathNode.Location.y = -StraightPath[nIndex++];
|
|
|
|
m_navMesh->getPolyArea(StraightPolyPath[nVert], &ThisArea);
|
|
m_navMesh->getPolyFlags(StraightPolyPath[nVert], &ThisFlags);
|
|
|
|
ThisFlags &= ~(SAMPLE_POLYFLAGS_NOONOS);
|
|
|
|
if (ThisArea == SAMPLE_POLYAREA_GROUND || ThisArea == SAMPLE_POLYAREA_CROUCH)
|
|
{
|
|
NextPathNode.Location = UTIL_AdjustPointAwayFromNavWall(NextPathNode.Location, 16.0f);
|
|
}
|
|
|
|
TraceStart.x = NextPathNode.Location.x;
|
|
TraceStart.y = NextPathNode.Location.y;
|
|
TraceStart.z = NextPathNode.Location.z;
|
|
|
|
UTIL_TraceLine(TraceStart, (TraceStart - Vector(0.0f, 0.0f, 100.0f)), ignore_monsters, ignore_glass, nullptr, &hit);
|
|
|
|
if (hit.flFraction < 1.0f)
|
|
{
|
|
NextPathNode.Location = hit.vecEndPos;
|
|
|
|
if (CurrFlags != SAMPLE_POLYFLAGS_JUMP && CurrFlags != SAMPLE_POLYFLAGS_WALLCLIMB)
|
|
{
|
|
NextPathNode.Location.z += 20.0f;
|
|
}
|
|
}
|
|
|
|
NextPathNode.requiredZ = NextPathNode.Location.z;
|
|
|
|
if (CurrFlags == SAMPLE_POLYFLAGS_WALLCLIMB || CurrFlags == SAMPLE_POLYFLAGS_LADDER)
|
|
{
|
|
float NewRequiredZ = UTIL_FindZHeightForWallClimb(NextPathNode.FromLocation, NextPathNode.Location, head_hull);
|
|
//NextPathNode.requiredZ = fmaxf(NewRequiredZ, NextPathNode.Location.z);
|
|
NextPathNode.requiredZ = NewRequiredZ;
|
|
|
|
if (CurrFlags == SAMPLE_POLYFLAGS_LADDER)
|
|
{
|
|
NextPathNode.requiredZ += 5.0f;
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
NextPathNode.requiredZ = NextPathNode.Location.z;
|
|
}
|
|
|
|
NextPathNode.flag = CurrFlags;
|
|
NextPathNode.area = CurrArea;
|
|
NextPathNode.poly = StraightPolyPath[nVert];
|
|
|
|
CurrArea = ThisArea;
|
|
CurrFlags = ThisFlags;
|
|
|
|
NodeFromLocation = NextPathNode.Location;
|
|
|
|
path.push_back(NextPathNode);
|
|
}
|
|
|
|
return DT_SUCCESS;
|
|
}
|
|
|
|
dtStatus FindPathClosestToPoint(AvHAIPlayer* pBot, const BotMoveStyle MoveStyle, const Vector FromLocation, const Vector ToLocation, vector<bot_path_node>& path, float MaxAcceptableDistance)
|
|
{
|
|
if (!pBot) { return DT_FAILURE; }
|
|
|
|
if (pBot->BotNavInfo.NavProfile.bFlyingProfile)
|
|
{
|
|
return FindFlightPathToPoint(pBot->BotNavInfo.NavProfile, FromLocation, ToLocation, path, MaxAcceptableDistance);
|
|
}
|
|
|
|
const dtNavMeshQuery* m_navQuery = UTIL_GetNavMeshQueryForProfile(pBot->BotNavInfo.NavProfile);
|
|
const dtNavMesh* m_navMesh = UTIL_GetNavMeshForProfile(pBot->BotNavInfo.NavProfile);
|
|
const dtQueryFilter* m_navFilter = &pBot->BotNavInfo.NavProfile.Filters;
|
|
|
|
bool bHasWelder = (m_navFilter->getIncludeFlags() & SAMPLE_POLYFLAGS_WELD);
|
|
|
|
if (!m_navQuery || !m_navMesh || !m_navFilter || vIsZero(FromLocation) || vIsZero(ToLocation))
|
|
{
|
|
return DT_FAILURE;
|
|
}
|
|
|
|
Vector FromFloorLocation = AdjustPointForPathfinding(FromLocation);
|
|
Vector ToFloorLocation = AdjustPointForPathfinding(ToLocation);
|
|
|
|
float pStartPos[3] = { FromFloorLocation.x, FromFloorLocation.z, -FromFloorLocation.y };
|
|
float pEndPos[3] = { ToFloorLocation.x, ToFloorLocation.z, -ToFloorLocation.y };
|
|
|
|
dtStatus status;
|
|
dtPolyRef StartPoly;
|
|
float StartNearest[3];
|
|
dtPolyRef EndPoly;
|
|
float EndNearest[3];
|
|
dtPolyRef PolyPath[MAX_PATH_POLY];
|
|
dtPolyRef StraightPolyPath[MAX_AI_PATH_SIZE];
|
|
int nPathCount = 0;
|
|
float StraightPath[MAX_AI_PATH_SIZE * 3];
|
|
unsigned char straightPathFlags[MAX_AI_PATH_SIZE];
|
|
memset(straightPathFlags, 0, sizeof(straightPathFlags));
|
|
int nVertCount = 0;
|
|
|
|
// find the start polygon
|
|
status = m_navQuery->findNearestPoly(pStartPos, pExtents, m_navFilter, &StartPoly, StartNearest);
|
|
if ((status & DT_FAILURE) || (status & DT_STATUS_DETAIL_MASK))
|
|
{
|
|
return (status & DT_STATUS_DETAIL_MASK); // couldn't find a polygon
|
|
}
|
|
|
|
// find the end polygon
|
|
status = m_navQuery->findNearestPoly(pEndPos, pExtents, m_navFilter, &EndPoly, EndNearest);
|
|
if ((status & DT_FAILURE) || (status & DT_STATUS_DETAIL_MASK))
|
|
{
|
|
return (status & DT_STATUS_DETAIL_MASK); // couldn't find a polygon
|
|
}
|
|
|
|
status = m_navQuery->findPath(StartPoly, EndPoly, StartNearest, EndNearest, m_navFilter, PolyPath, &nPathCount, MAX_PATH_POLY);
|
|
|
|
if (PolyPath[nPathCount - 1] != EndPoly)
|
|
{
|
|
float epos[3];
|
|
dtVcopy(epos, EndNearest);
|
|
|
|
m_navQuery->closestPointOnPoly(PolyPath[nPathCount - 1], EndNearest, epos, 0);
|
|
|
|
if (dtVdistSqr(EndNearest, epos) > sqrf(MaxAcceptableDistance))
|
|
{
|
|
return DT_FAILURE;
|
|
}
|
|
else
|
|
{
|
|
dtVcopy(EndNearest, epos);
|
|
}
|
|
}
|
|
|
|
status = m_navQuery->findStraightPath(StartNearest, EndNearest, PolyPath, nPathCount, StraightPath, straightPathFlags, StraightPolyPath, &nVertCount, MAX_AI_PATH_SIZE, DT_STRAIGHTPATH_AREA_CROSSINGS);
|
|
if ((status & DT_FAILURE) || (status & DT_STATUS_DETAIL_MASK))
|
|
{
|
|
return (status & DT_STATUS_DETAIL_MASK); // couldn't create a path
|
|
}
|
|
|
|
if (nVertCount == 0)
|
|
{
|
|
return DT_FAILURE; // couldn't find a path
|
|
}
|
|
|
|
path.clear();
|
|
|
|
unsigned int CurrFlags;
|
|
unsigned char CurrArea;
|
|
|
|
m_navMesh->getPolyFlags(StraightPolyPath[0], &CurrFlags);
|
|
m_navMesh->getPolyArea(StraightPolyPath[0], &CurrArea);
|
|
|
|
CurrFlags &= ~(SAMPLE_POLYFLAGS_NOONOS);
|
|
|
|
// At this point we have our path. Copy it to the path store
|
|
int nIndex = 0;
|
|
TraceResult hit;
|
|
|
|
pBot->BotNavInfo.SpecialMovementFlags = 0;
|
|
|
|
Vector NodeFromLocation = FromFloorLocation;
|
|
|
|
for (int nVert = 0; nVert < nVertCount; nVert++)
|
|
{
|
|
bot_path_node NextPathNode;
|
|
|
|
NextPathNode.FromLocation = NodeFromLocation;
|
|
|
|
// The nav mesh doesn't always align perfectly with the floor, so align each nav point with the floor after generation
|
|
NextPathNode.Location.x = StraightPath[nIndex++];
|
|
NextPathNode.Location.z = StraightPath[nIndex++];
|
|
NextPathNode.Location.y = -StraightPath[nIndex++];
|
|
|
|
NextPathNode.Location = UTIL_AdjustPointAwayFromNavWall(NextPathNode.Location, 16.0f);
|
|
|
|
NextPathNode.Location = AdjustPointForPathfinding(NextPathNode.Location);
|
|
|
|
if ((CurrFlags != SAMPLE_POLYFLAGS_JUMP && CurrFlags != SAMPLE_POLYFLAGS_WALLCLIMB) || NextPathNode.FromLocation.z > NextPathNode.Location.z)
|
|
{
|
|
NextPathNode.Location.z += GetPlayerOriginOffsetFromFloor(pBot->Edict, (CurrArea == SAMPLE_POLYAREA_CROUCH)).z;
|
|
}
|
|
|
|
pBot->BotNavInfo.SpecialMovementFlags |= CurrFlags;
|
|
|
|
if (pBot->BotNavInfo.SpecialMovementFlags & SAMPLE_POLYFLAGS_WELD)
|
|
{
|
|
bool bPing = true;
|
|
}
|
|
|
|
// End alignment to floor
|
|
|
|
// For ladders and wall climbing, calculate the climb height needed to complete the move.
|
|
// This what allows bots to climb over railings without having to explicitly place nav points on the railing itself
|
|
NextPathNode.requiredZ = NextPathNode.Location.z;
|
|
|
|
if (CurrFlags == SAMPLE_POLYFLAGS_WALLCLIMB || CurrFlags == SAMPLE_POLYFLAGS_LADDER)
|
|
{
|
|
int HullNum = GetPlayerHullIndex(pBot->Edict, false);
|
|
Vector FromLocation = (path.size() > 0) ? path.back().Location : pBot->CurrentFloorPosition;
|
|
float NewRequiredZ = UTIL_FindZHeightForWallClimb(FromLocation, NextPathNode.Location, HullNum);
|
|
NextPathNode.requiredZ = fmaxf(NewRequiredZ, NextPathNode.Location.z);
|
|
|
|
if (CurrFlags == SAMPLE_POLYFLAGS_LADDER)
|
|
{
|
|
NextPathNode.requiredZ += 5.0f;
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
NextPathNode.requiredZ = NextPathNode.Location.z;
|
|
}
|
|
|
|
NextPathNode.flag = CurrFlags;
|
|
NextPathNode.area = CurrArea;
|
|
NextPathNode.poly = StraightPolyPath[nVert];
|
|
|
|
m_navMesh->getPolyFlags(StraightPolyPath[nVert], &CurrFlags);
|
|
m_navMesh->getPolyArea(StraightPolyPath[nVert], &CurrArea);
|
|
|
|
CurrFlags &= ~(SAMPLE_POLYFLAGS_NOONOS);
|
|
|
|
NodeFromLocation = NextPathNode.Location;
|
|
|
|
path.push_back(NextPathNode);
|
|
}
|
|
|
|
return DT_SUCCESS;
|
|
}
|
|
|
|
bool UTIL_PointIsReachable(const nav_profile &NavProfile, const Vector FromLocation, const Vector ToLocation, const float MaxAcceptableDistance)
|
|
{
|
|
const dtNavMeshQuery* m_navQuery = UTIL_GetNavMeshQueryForProfile(NavProfile);
|
|
const dtNavMesh* m_navMesh = UTIL_GetNavMeshForProfile(NavProfile);
|
|
const dtQueryFilter* m_navFilter = &NavProfile.Filters;
|
|
|
|
if (!m_navQuery || vIsZero(FromLocation) || vIsZero(ToLocation))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
float pStartPos[3] = { FromLocation.x, FromLocation.z, -FromLocation.y };
|
|
float pEndPos[3] = { ToLocation.x, ToLocation.z, -ToLocation.y };
|
|
|
|
dtStatus status;
|
|
dtPolyRef StartPoly;
|
|
float StartNearest[3];
|
|
dtPolyRef EndPoly;
|
|
float EndNearest[3];
|
|
dtPolyRef PolyPath[MAX_PATH_POLY];
|
|
int nPathCount = 0;
|
|
|
|
float searchExtents[3] = { MaxAcceptableDistance, 50.0f, MaxAcceptableDistance };
|
|
|
|
// find the start polygon
|
|
status = m_navQuery->findNearestPoly(pStartPos, searchExtents, m_navFilter, &StartPoly, StartNearest);
|
|
if ((status & DT_FAILURE) || (status & DT_STATUS_DETAIL_MASK))
|
|
{
|
|
return false; // couldn't find a polygon
|
|
}
|
|
|
|
// find the end polygon
|
|
status = m_navQuery->findNearestPoly(pEndPos, searchExtents, m_navFilter, &EndPoly, EndNearest);
|
|
if ((status & DT_FAILURE) || (status & DT_STATUS_DETAIL_MASK))
|
|
{
|
|
return false; // couldn't find a polygon
|
|
}
|
|
|
|
status = m_navQuery->findPath(StartPoly, EndPoly, StartNearest, EndNearest, m_navFilter, PolyPath, &nPathCount, MAX_PATH_POLY);
|
|
|
|
if (nPathCount == 0)
|
|
{
|
|
return false; // couldn't find a path
|
|
}
|
|
|
|
if (PolyPath[nPathCount - 1] != EndPoly)
|
|
{
|
|
float epos[3];
|
|
dtVcopy(epos, EndNearest);
|
|
|
|
m_navQuery->closestPointOnPoly(PolyPath[nPathCount - 1], EndNearest, epos, 0);
|
|
|
|
return (dtVdistSqr(EndNearest, epos) <= sqrf(MaxAcceptableDistance));
|
|
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool HasBotReachedPathPoint(const AvHAIPlayer* pBot)
|
|
{
|
|
if (pBot->BotNavInfo.CurrentPath.size() == 0 || pBot->BotNavInfo.CurrentPathPoint >= pBot->BotNavInfo.CurrentPath.size())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
bot_path_node CurrentPathNode = pBot->BotNavInfo.CurrentPath[pBot->BotNavInfo.CurrentPathPoint];
|
|
|
|
SamplePolyFlags CurrentNavFlag = (SamplePolyFlags)CurrentPathNode.flag;
|
|
Vector MoveFrom = CurrentPathNode.FromLocation;
|
|
Vector MoveTo = CurrentPathNode.Location;
|
|
float RequiredClimbHeight = CurrentPathNode.requiredZ;
|
|
|
|
Vector NextMoveLocation = ZERO_VECTOR;
|
|
SamplePolyFlags NextMoveFlag = SAMPLE_POLYFLAGS_DISABLED;
|
|
|
|
if ((pBot->BotNavInfo.CurrentPathPoint + 1) < pBot->BotNavInfo.CurrentPath.size())
|
|
{
|
|
bot_path_node NextPathNode = pBot->BotNavInfo.CurrentPath[pBot->BotNavInfo.CurrentPathPoint + 1];
|
|
NextMoveLocation = NextPathNode.Location;
|
|
NextMoveFlag = (SamplePolyFlags)NextPathNode.flag;
|
|
}
|
|
|
|
switch (CurrentNavFlag)
|
|
{
|
|
case SAMPLE_POLYFLAGS_WALK:
|
|
return HasBotCompletedWalkMove(pBot, MoveFrom, MoveTo, NextMoveLocation, NextMoveFlag);
|
|
case SAMPLE_POLYFLAGS_WELD:
|
|
case SAMPLE_POLYFLAGS_DOOR:
|
|
case SAMPLE_POLYFLAGS_TEAM1STRUCTURE:
|
|
case SAMPLE_POLYFLAGS_TEAM2STRUCTURE:
|
|
return HasBotCompletedObstacleMove(pBot, MoveFrom, MoveTo, NextMoveLocation, NextMoveFlag);
|
|
case SAMPLE_POLYFLAGS_LADDER:
|
|
return HasBotCompletedLadderMove(pBot, MoveFrom, MoveTo, NextMoveLocation, NextMoveFlag);
|
|
case SAMPLE_POLYFLAGS_FALL:
|
|
return HasBotCompletedFallMove(pBot, MoveFrom, MoveTo, NextMoveLocation, NextMoveFlag);
|
|
case SAMPLE_POLYFLAGS_WALLCLIMB:
|
|
return HasBotCompletedClimbMove(pBot, MoveFrom, MoveTo, RequiredClimbHeight, NextMoveLocation, NextMoveFlag);
|
|
case SAMPLE_POLYFLAGS_JUMP:
|
|
case SAMPLE_POLYFLAGS_DUCKJUMP:
|
|
case SAMPLE_POLYFLAGS_BLOCKED:
|
|
return HasBotCompletedJumpMove(pBot, MoveFrom, MoveTo, NextMoveLocation, NextMoveFlag);
|
|
case SAMPLE_POLYFLAGS_TEAM1PHASEGATE:
|
|
case SAMPLE_POLYFLAGS_TEAM2PHASEGATE:
|
|
return HasBotCompletedPhaseGateMove(pBot, MoveFrom, MoveTo, NextMoveLocation, NextMoveFlag);
|
|
case SAMPLE_POLYFLAGS_LIFT:
|
|
return HasBotCompletedLiftMove(pBot, MoveFrom, MoveTo, NextMoveLocation, NextMoveFlag);
|
|
default:
|
|
return HasBotCompletedWalkMove(pBot, MoveFrom, MoveTo, NextMoveLocation, NextMoveFlag);
|
|
}
|
|
|
|
return HasBotCompletedWalkMove(pBot, MoveFrom, MoveTo, NextMoveLocation, NextMoveFlag);
|
|
}
|
|
|
|
bool HasBotCompletedWalkMove(const AvHAIPlayer* pBot, Vector MoveStart, Vector MoveEnd, Vector NextMoveDestination, SamplePolyFlags NextMoveFlag)
|
|
{
|
|
bool bNextPointReachable = false;
|
|
|
|
if (NextMoveFlag != SAMPLE_POLYFLAGS_DISABLED)
|
|
{
|
|
bNextPointReachable = UTIL_PointIsDirectlyReachable(pBot->CurrentFloorPosition, NextMoveDestination);
|
|
}
|
|
|
|
return vPointOverlaps3D(MoveEnd, pBot->Edict->v.absmin, pBot->Edict->v.absmax) || (bNextPointReachable && vDist2DSq(pBot->Edict->v.origin, MoveEnd) < sqrf(GetPlayerRadius(pBot->Edict) * 2.0f));
|
|
}
|
|
|
|
bool HasBotCompletedObstacleMove(const AvHAIPlayer* pBot, Vector MoveStart, Vector MoveEnd, Vector NextMoveDestination, SamplePolyFlags NextMoveFlag)
|
|
{
|
|
return vPointOverlaps3D(MoveEnd, pBot->Edict->v.absmin, pBot->Edict->v.absmax);
|
|
}
|
|
|
|
bool HasBotCompletedLadderMove(const AvHAIPlayer* pBot, Vector MoveStart, Vector MoveEnd, Vector NextMoveDestination, SamplePolyFlags NextMoveFlag)
|
|
{
|
|
if (IsPlayerOnLadder(pBot->Edict)) { return false; }
|
|
|
|
if (NextMoveFlag != SAMPLE_POLYFLAGS_DISABLED)
|
|
{
|
|
if (pBot->BotNavInfo.IsOnGround)
|
|
{
|
|
if (UTIL_PointIsDirectlyReachable(pBot->CollisionHullBottomLocation, NextMoveDestination)) { return true; }
|
|
}
|
|
else
|
|
{
|
|
if (vDist2DSq(pBot->Edict->v.origin, MoveEnd) < sqrf(GetPlayerRadius(pBot->Edict)) && UTIL_PointIsDirectlyReachable(pBot->CurrentFloorPosition, NextMoveDestination)) { return true; }
|
|
}
|
|
|
|
}
|
|
|
|
return vPointOverlaps3D(MoveEnd, pBot->Edict->v.absmin, pBot->Edict->v.absmax);
|
|
}
|
|
|
|
bool HasBotCompletedFallMove(const AvHAIPlayer* pBot, Vector MoveStart, Vector MoveEnd, Vector NextMoveDestination, SamplePolyFlags NextMoveFlag)
|
|
{
|
|
if (NextMoveFlag != SAMPLE_POLYFLAGS_DISABLED)
|
|
{
|
|
Vector ThisMoveDir = UTIL_GetVectorNormal2D(MoveEnd - MoveStart);
|
|
Vector NextMoveDir = UTIL_GetVectorNormal2D(NextMoveDestination - MoveEnd);
|
|
|
|
float MoveDot = UTIL_GetDotProduct2D(ThisMoveDir, NextMoveDir);
|
|
|
|
if (MoveDot > 0.0f)
|
|
{
|
|
if (UTIL_PointIsDirectlyReachable(pBot->CurrentFloorPosition, NextMoveDestination)
|
|
&& UTIL_QuickTrace(pBot->Edict, pBot->Edict->v.origin, NextMoveDestination)
|
|
&& fabsf(pBot->CollisionHullBottomLocation.z - MoveEnd.z) < 100.0f) { return true; }
|
|
}
|
|
}
|
|
|
|
return vPointOverlaps3D(MoveEnd, pBot->Edict->v.absmin, pBot->Edict->v.absmax);
|
|
}
|
|
|
|
bool HasBotCompletedClimbMove(const AvHAIPlayer* pBot, Vector MoveStart, Vector MoveEnd, float RequiredClimbHeight, Vector NextMoveDestination, SamplePolyFlags NextMoveFlag)
|
|
{
|
|
Vector PositionInMove = vClosestPointOnLine2D(MoveStart, MoveEnd, pBot->Edict->v.origin);
|
|
|
|
if (!vEquals2D(PositionInMove, MoveEnd, 4.0f)) { return false; }
|
|
|
|
if (pBot->BotNavInfo.IsOnGround)
|
|
{
|
|
return UTIL_PointIsDirectlyReachable(pBot->CurrentFloorPosition, NextMoveDestination);
|
|
}
|
|
else
|
|
{
|
|
if (NextMoveFlag != SAMPLE_POLYFLAGS_DISABLED)
|
|
{
|
|
Vector ThisMoveDir = UTIL_GetVectorNormal2D(MoveEnd - MoveStart);
|
|
Vector NextMoveDir = UTIL_GetVectorNormal2D(NextMoveDestination - MoveEnd);
|
|
|
|
float MoveDot = UTIL_GetDotProduct2D(ThisMoveDir, NextMoveDir);
|
|
|
|
if (MoveDot > 0.0f)
|
|
{
|
|
if (pBot->Edict->v.origin.z >= RequiredClimbHeight && !pBot->BotNavInfo.IsOnGround)
|
|
{
|
|
if (UTIL_QuickTrace(pBot->Edict, pBot->Edict->v.origin, NextMoveDestination)
|
|
&& fabsf(pBot->CollisionHullBottomLocation.z - MoveEnd.z) < 100.0f)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool HasBotCompletedJumpMove(const AvHAIPlayer* pBot, Vector MoveStart, Vector MoveEnd, Vector NextMoveDestination, SamplePolyFlags NextMoveFlag)
|
|
{
|
|
Vector PositionInMove = vClosestPointOnLine2D(MoveStart, MoveEnd, pBot->Edict->v.origin);
|
|
|
|
if (!vEquals2D(PositionInMove, MoveEnd, 2.0f)) { return false; }
|
|
|
|
if (NextMoveFlag != SAMPLE_POLYFLAGS_DISABLED)
|
|
{
|
|
Vector ThisMoveDir = UTIL_GetVectorNormal2D(MoveEnd - MoveStart);
|
|
Vector NextMoveDir = UTIL_GetVectorNormal2D(NextMoveDestination - MoveEnd);
|
|
|
|
float MoveDot = UTIL_GetDotProduct2D(ThisMoveDir, NextMoveDir);
|
|
|
|
if (MoveDot >= 0.0f)
|
|
{
|
|
Vector HullTraceEnd = MoveEnd;
|
|
HullTraceEnd.z = pBot->Edict->v.origin.z;
|
|
|
|
if (UTIL_PointIsDirectlyReachable(pBot->CurrentFloorPosition, NextMoveDestination)
|
|
&& UTIL_QuickHullTrace(pBot->Edict, pBot->Edict->v.origin, HullTraceEnd, head_hull, false)
|
|
&& fabsf(pBot->CollisionHullBottomLocation.z - MoveEnd.z) < 100.0f)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return vPointOverlaps3D(MoveEnd, pBot->Edict->v.absmin, pBot->Edict->v.absmax);
|
|
}
|
|
|
|
bool HasBotCompletedPhaseGateMove(const AvHAIPlayer* pBot, Vector MoveStart, Vector MoveEnd, Vector NextMoveDestination, SamplePolyFlags NextMoveFlag)
|
|
{
|
|
return vPointOverlaps3D(MoveEnd, pBot->Edict->v.absmin, pBot->Edict->v.absmax) || vDist2DSq(pBot->Edict->v.origin, MoveEnd) < sqrf(32.0f);
|
|
}
|
|
|
|
bool HasBotCompletedLiftMove(const AvHAIPlayer* pBot, Vector MoveStart, Vector MoveEnd, Vector NextMoveDestination, SamplePolyFlags NextMoveFlag)
|
|
{
|
|
return vPointOverlaps3D(MoveEnd, pBot->Edict->v.absmin, pBot->Edict->v.absmax);
|
|
}
|
|
|
|
void CheckAndHandleDoorObstruction(AvHAIPlayer* pBot)
|
|
{
|
|
if (pBot->BotNavInfo.CurrentPathPoint >= pBot->BotNavInfo.CurrentPath.size()) { return; }
|
|
|
|
bot_path_node CurrentPathNode = pBot->BotNavInfo.CurrentPath[pBot->BotNavInfo.CurrentPathPoint];
|
|
|
|
edict_t* BlockingDoorEdict = UTIL_GetDoorBlockingPathPoint(pBot->Edict->v.origin, CurrentPathNode.Location, CurrentPathNode.flag, nullptr);
|
|
|
|
if (FNullEnt(BlockingDoorEdict))
|
|
{
|
|
int NumIterations = 0;
|
|
|
|
for (int i = (pBot->BotNavInfo.CurrentPathPoint + 1); i < pBot->BotNavInfo.CurrentPath.size(); i++)
|
|
{
|
|
bot_path_node ThisPathNode = pBot->BotNavInfo.CurrentPath[i];
|
|
|
|
BlockingDoorEdict = UTIL_GetDoorBlockingPathPoint(ThisPathNode.FromLocation, ThisPathNode.Location, ThisPathNode.flag, nullptr);
|
|
|
|
NumIterations++;
|
|
|
|
if (!FNullEnt(BlockingDoorEdict) || NumIterations >= 2)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (FNullEnt(BlockingDoorEdict)) { return; }
|
|
|
|
CBaseToggle* BlockingDoor = dynamic_cast<CBaseToggle*>(CBaseEntity::Instance(BlockingDoorEdict));
|
|
|
|
if (!BlockingDoor)
|
|
{
|
|
AvHWeldable* WeldableRef = dynamic_cast<AvHWeldable*>(CBaseEntity::Instance(BlockingDoorEdict));
|
|
|
|
if (!WeldableRef)
|
|
{
|
|
return;
|
|
}
|
|
|
|
NAV_SetWeldMovementTask(pBot, BlockingDoorEdict, nullptr);
|
|
|
|
return;
|
|
}
|
|
|
|
Vector NearestPoint = UTIL_GetClosestPointOnEntityToLocation(pBot->Edict->v.origin, BlockingDoorEdict);
|
|
|
|
// If the door is in the process of opening or closing, let it finish before doing anything else
|
|
if (BlockingDoor->m_toggle_state == TS_GOING_UP || BlockingDoor->m_toggle_state == TS_GOING_DOWN)
|
|
{
|
|
if (IsPlayerTouchingEntity(pBot->Edict, BlockingDoorEdict))
|
|
{
|
|
Vector MoveDir = UTIL_GetVectorNormal2D(CurrentPathNode.Location - CurrentPathNode.FromLocation);
|
|
|
|
pBot->desiredMovementDir = MoveDir;
|
|
return;
|
|
}
|
|
|
|
if (vDist2DSq(pBot->Edict->v.origin, NearestPoint) < sqrf(UTIL_MetresToGoldSrcUnits(1.5f)))
|
|
{
|
|
// Wait for the door to finish opening
|
|
pBot->desiredMovementDir = g_vecZero;
|
|
BotLookAt(pBot, CurrentPathNode.Location);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// If we're blocked by a door that's open, and its wait time isn't infinite (i.e. it will close shortly) then just wait it out
|
|
if (BlockingDoor->m_toggle_state == TS_AT_TOP && BlockingDoor->m_flWait >= 0.0f)
|
|
{
|
|
// Wait for the door to start closing
|
|
if (vDist2DSq(pBot->Edict->v.origin, NearestPoint) < sqrf(UTIL_MetresToGoldSrcUnits(1.5f)))
|
|
{
|
|
// Wait for the door to finish opening
|
|
pBot->desiredMovementDir = g_vecZero;
|
|
BotLookAt(pBot, BlockingDoorEdict);
|
|
}
|
|
return;
|
|
}
|
|
|
|
nav_door* Door = UTIL_GetNavDoorByEdict(BlockingDoorEdict);
|
|
|
|
if (Door)
|
|
{
|
|
// Door opens just by being directly used
|
|
if (Door->ActivationType == DOOR_USE)
|
|
{
|
|
if (IsPlayerInUseRange(pBot->Edict, Door->DoorEdict))
|
|
{
|
|
if (pBot->Edict->v.oldbuttons & IN_DUCK)
|
|
{
|
|
pBot->Button |= IN_DUCK;
|
|
}
|
|
|
|
BotUseObject(pBot, Door->DoorEdict, false);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
// Door must be shot to open
|
|
if (Door->ActivationType == DOOR_SHOOT)
|
|
{
|
|
BotShootTarget(pBot, GetPlayerCurrentWeapon(pBot->Player), Door->DoorEdict);
|
|
return;
|
|
}
|
|
|
|
|
|
DoorTrigger* Trigger = UTIL_GetNearestDoorTrigger(pBot->CurrentFloorPosition, Door, nullptr, true);
|
|
|
|
// Fail-safe: If the bot cannot reach any trigger for whatever reason, then telepathically trigger one otherwise it will be stuck forever
|
|
if (!Trigger)
|
|
{
|
|
for (auto it = Door->TriggerEnts.begin(); it != Door->TriggerEnts.end(); it++)
|
|
{
|
|
if (it->NextActivationTime > gpGlobals->time)
|
|
{
|
|
Trigger = nullptr;
|
|
break;
|
|
}
|
|
|
|
Trigger = &(*it);
|
|
}
|
|
|
|
if (Trigger)
|
|
{
|
|
Trigger->Entity->Use(pBot->Player, pBot->Player, USE_TOGGLE, 0.0f);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (Trigger && Trigger->NextActivationTime < gpGlobals->time)
|
|
{
|
|
if (Trigger->TriggerType == DOOR_BUTTON)
|
|
{
|
|
Vector UseLocation = UTIL_GetButtonFloorLocation(pBot->Edict->v.origin, Trigger->Edict);
|
|
|
|
NAV_SetUseMovementTask(pBot, Trigger->Edict, Trigger);
|
|
}
|
|
else if (Trigger->TriggerType == DOOR_TRIGGER)
|
|
{
|
|
NAV_SetTouchMovementTask(pBot, Trigger->Edict, Trigger);
|
|
}
|
|
else if (Trigger->TriggerType == DOOR_WELD)
|
|
{
|
|
NAV_SetWeldMovementTask(pBot, Trigger->Edict, Trigger);
|
|
}
|
|
else if (Trigger->TriggerType == DOOR_BREAK)
|
|
{
|
|
NAV_SetBreakMovementTask(pBot, Trigger->Edict, Trigger);
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
edict_t* UTIL_GetDoorBlockingPathPoint(AvHAIPlayer* pBot, bot_path_node* PathNode, edict_t* SearchDoor)
|
|
{
|
|
if (!PathNode) { return nullptr; }
|
|
|
|
Vector FromLoc = PathNode->FromLocation;
|
|
Vector ToLoc = PathNode->Location;
|
|
|
|
TraceResult doorHit;
|
|
|
|
if (PathNode->flag == SAMPLE_POLYFLAGS_LADDER || PathNode->flag == SAMPLE_POLYFLAGS_WALLCLIMB)
|
|
{
|
|
Vector TargetLoc = Vector(FromLoc.x, FromLoc.y, PathNode->requiredZ);
|
|
|
|
if (!FNullEnt(SearchDoor))
|
|
{
|
|
if (vlineIntersectsAABB(FromLoc, TargetLoc, SearchDoor->v.absmin, SearchDoor->v.absmax))
|
|
{
|
|
return SearchDoor;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (auto it = NavDoors.begin(); it != NavDoors.end(); it++)
|
|
{
|
|
if (vlineIntersectsAABB(FromLoc, TargetLoc, it->DoorEdict->v.absmin, it->DoorEdict->v.absmax))
|
|
{
|
|
return it->DoorEdict;
|
|
}
|
|
}
|
|
|
|
for (auto it = NavWeldableObstacles.begin(); it != NavWeldableObstacles.end(); it++)
|
|
{
|
|
if (vlineIntersectsAABB(FromLoc, TargetLoc, it->WeldableEdict->v.absmin, it->WeldableEdict->v.absmax))
|
|
{
|
|
return it->WeldableEdict;
|
|
}
|
|
}
|
|
}
|
|
|
|
Vector TargetLoc2 = Vector(ToLoc.x, ToLoc.y, PathNode->requiredZ);
|
|
|
|
if (!FNullEnt(SearchDoor))
|
|
{
|
|
if (vlineIntersectsAABB(TargetLoc, TargetLoc2, SearchDoor->v.absmin, SearchDoor->v.absmax))
|
|
{
|
|
return SearchDoor;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (auto it = NavDoors.begin(); it != NavDoors.end(); it++)
|
|
{
|
|
if (vlineIntersectsAABB(TargetLoc, TargetLoc2, it->DoorEdict->v.absmin, it->DoorEdict->v.absmax))
|
|
{
|
|
return it->DoorEdict;
|
|
}
|
|
}
|
|
|
|
for (auto it = NavWeldableObstacles.begin(); it != NavWeldableObstacles.end(); it++)
|
|
{
|
|
if (vlineIntersectsAABB(TargetLoc, TargetLoc2, it->WeldableEdict->v.absmin, it->WeldableEdict->v.absmax))
|
|
{
|
|
return it->WeldableEdict;
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
else if (PathNode->flag == SAMPLE_POLYFLAGS_FALL)
|
|
{
|
|
Vector TargetLoc = Vector(ToLoc.x, ToLoc.y, FromLoc.z);
|
|
|
|
if (!FNullEnt(SearchDoor))
|
|
{
|
|
if (vlineIntersectsAABB(FromLoc, TargetLoc, SearchDoor->v.absmin, SearchDoor->v.absmax))
|
|
{
|
|
return SearchDoor;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (auto it = NavDoors.begin(); it != NavDoors.end(); it++)
|
|
{
|
|
if (vlineIntersectsAABB(FromLoc, TargetLoc, it->DoorEdict->v.absmin, it->DoorEdict->v.absmax))
|
|
{
|
|
return it->DoorEdict;
|
|
}
|
|
}
|
|
|
|
for (auto it = NavWeldableObstacles.begin(); it != NavWeldableObstacles.end(); it++)
|
|
{
|
|
if (vlineIntersectsAABB(FromLoc, TargetLoc, it->WeldableEdict->v.absmin, it->WeldableEdict->v.absmax))
|
|
{
|
|
return it->WeldableEdict;
|
|
}
|
|
}
|
|
}
|
|
|
|
Vector NextTargetLoc = ToLoc + Vector(0.0f, 0.0f, 10.0f);
|
|
|
|
if (!FNullEnt(SearchDoor))
|
|
{
|
|
if (vlineIntersectsAABB(TargetLoc, NextTargetLoc, SearchDoor->v.absmin, SearchDoor->v.absmax))
|
|
{
|
|
return SearchDoor;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (auto it = NavDoors.begin(); it != NavDoors.end(); it++)
|
|
{
|
|
if (vlineIntersectsAABB(TargetLoc, NextTargetLoc, it->DoorEdict->v.absmin, it->DoorEdict->v.absmax))
|
|
{
|
|
return it->DoorEdict;
|
|
}
|
|
}
|
|
|
|
for (auto it = NavWeldableObstacles.begin(); it != NavWeldableObstacles.end(); it++)
|
|
{
|
|
if (vlineIntersectsAABB(TargetLoc, NextTargetLoc, it->WeldableEdict->v.absmin, it->WeldableEdict->v.absmax))
|
|
{
|
|
return it->WeldableEdict;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Vector StartTrace = FromLoc + Vector(0.0f, 0.0f, 16.0f);
|
|
Vector EndTrace = ToLoc + Vector(0.0f, 0.0f, 16.0f);
|
|
|
|
if (!FNullEnt(SearchDoor))
|
|
{
|
|
if (vlineIntersectsAABB(StartTrace, EndTrace, SearchDoor->v.absmin, SearchDoor->v.absmax))
|
|
{
|
|
return SearchDoor;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (auto it = NavDoors.begin(); it != NavDoors.end(); it++)
|
|
{
|
|
if (vlineIntersectsAABB(StartTrace, EndTrace, it->DoorEdict->v.absmin, it->DoorEdict->v.absmax))
|
|
{
|
|
return it->DoorEdict;
|
|
}
|
|
}
|
|
|
|
for (auto it = NavWeldableObstacles.begin(); it != NavWeldableObstacles.end(); it++)
|
|
{
|
|
if (vlineIntersectsAABB(StartTrace, EndTrace, it->WeldableEdict->v.absmin, it->WeldableEdict->v.absmax))
|
|
{
|
|
return it->WeldableEdict;
|
|
}
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
edict_t* UTIL_GetBreakableBlockingPathPoint(AvHAIPlayer* pBot, bot_path_node* PathNode, edict_t* SearchBreakable)
|
|
{
|
|
Vector FromLoc = PathNode->FromLocation;
|
|
Vector ToLoc = PathNode->Location;
|
|
|
|
TraceResult breakableHit;
|
|
|
|
if (PathNode->flag == SAMPLE_POLYFLAGS_LADDER || PathNode->flag == SAMPLE_POLYFLAGS_WALLCLIMB)
|
|
{
|
|
Vector TargetLoc = Vector(FromLoc.x, FromLoc.y, PathNode->requiredZ);
|
|
|
|
UTIL_TraceLine(FromLoc, TargetLoc, dont_ignore_monsters, dont_ignore_glass, pBot->Edict->v.pContainingEntity, &breakableHit);
|
|
|
|
if (!FNullEnt(SearchBreakable))
|
|
{
|
|
if (breakableHit.pHit == SearchBreakable) { return breakableHit.pHit; }
|
|
}
|
|
else
|
|
{
|
|
if (!FNullEnt(breakableHit.pHit))
|
|
{
|
|
if (strcmp(STRING(breakableHit.pHit->v.classname), "func_breakable") == 0)
|
|
{
|
|
return breakableHit.pHit;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
Vector TargetLoc2 = Vector(ToLoc.x, ToLoc.y, PathNode->requiredZ);
|
|
|
|
UTIL_TraceLine(TargetLoc, TargetLoc2, dont_ignore_monsters, dont_ignore_glass, pBot->Edict->v.pContainingEntity, &breakableHit);
|
|
|
|
if (!FNullEnt(SearchBreakable))
|
|
{
|
|
if (breakableHit.pHit == SearchBreakable) { return breakableHit.pHit; }
|
|
}
|
|
else
|
|
{
|
|
if (!FNullEnt(breakableHit.pHit))
|
|
{
|
|
if (strcmp(STRING(breakableHit.pHit->v.classname), "func_breakable") == 0)
|
|
{
|
|
return breakableHit.pHit;
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
else if (PathNode->flag == SAMPLE_POLYFLAGS_FALL)
|
|
{
|
|
Vector TargetLoc = Vector(ToLoc.x, ToLoc.y, FromLoc.z);
|
|
|
|
UTIL_TraceLine(FromLoc, TargetLoc, dont_ignore_monsters, dont_ignore_glass, pBot->Edict->v.pContainingEntity, &breakableHit);
|
|
|
|
if (!FNullEnt(SearchBreakable))
|
|
{
|
|
if (breakableHit.pHit == SearchBreakable) { return breakableHit.pHit; }
|
|
}
|
|
else
|
|
{
|
|
if (!FNullEnt(breakableHit.pHit))
|
|
{
|
|
if (strcmp(STRING(breakableHit.pHit->v.classname), "func_breakable") == 0)
|
|
{
|
|
return breakableHit.pHit;
|
|
}
|
|
}
|
|
}
|
|
|
|
UTIL_TraceLine(TargetLoc, ToLoc + Vector(0.0f, 0.0f, 10.0f), dont_ignore_monsters, dont_ignore_glass, pBot->Edict->v.pContainingEntity, &breakableHit);
|
|
|
|
if (!FNullEnt(SearchBreakable))
|
|
{
|
|
if (breakableHit.pHit == SearchBreakable) { return breakableHit.pHit; }
|
|
}
|
|
else
|
|
{
|
|
if (!FNullEnt(breakableHit.pHit))
|
|
{
|
|
if (strcmp(STRING(breakableHit.pHit->v.classname), "func_breakable") == 0)
|
|
{
|
|
return breakableHit.pHit;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
UTIL_TraceLine(FromLoc, ToLoc, dont_ignore_monsters, dont_ignore_glass, pBot->Edict->v.pContainingEntity, &breakableHit);
|
|
|
|
|
|
if (!FNullEnt(SearchBreakable))
|
|
{
|
|
if (breakableHit.pHit == SearchBreakable) { return breakableHit.pHit; }
|
|
}
|
|
else
|
|
{
|
|
if (!FNullEnt(breakableHit.pHit))
|
|
{
|
|
if (strcmp(STRING(breakableHit.pHit->v.classname), "func_breakable") == 0)
|
|
{
|
|
return breakableHit.pHit;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
edict_t* UTIL_GetBreakableBlockingPathPoint(AvHAIPlayer* pBot, const Vector FromLocation, const Vector ToLocation, const unsigned int MovementFlag, edict_t* SearchBreakable)
|
|
{
|
|
Vector FromLoc = FromLocation;
|
|
Vector ToLoc = ToLocation;
|
|
|
|
TraceResult breakableHit;
|
|
|
|
if (MovementFlag == SAMPLE_POLYFLAGS_LADDER || MovementFlag == SAMPLE_POLYFLAGS_WALLCLIMB)
|
|
{
|
|
Vector TargetLoc = Vector(FromLoc.x, FromLoc.y, ToLocation.z);
|
|
|
|
UTIL_TraceLine(FromLoc, TargetLoc, dont_ignore_monsters, dont_ignore_glass, pBot->Edict->v.pContainingEntity, &breakableHit);
|
|
|
|
if (!FNullEnt(SearchBreakable))
|
|
{
|
|
if (breakableHit.pHit == SearchBreakable) { return breakableHit.pHit; }
|
|
}
|
|
else
|
|
{
|
|
if (!FNullEnt(breakableHit.pHit))
|
|
{
|
|
if (strcmp(STRING(breakableHit.pHit->v.classname), "func_breakable") == 0)
|
|
{
|
|
return breakableHit.pHit;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
Vector TargetLoc2 = Vector(ToLoc.x, ToLoc.y, ToLocation.z);
|
|
|
|
UTIL_TraceLine(TargetLoc, TargetLoc2, dont_ignore_monsters, dont_ignore_glass, pBot->Edict->v.pContainingEntity, &breakableHit);
|
|
|
|
if (!FNullEnt(SearchBreakable))
|
|
{
|
|
if (breakableHit.pHit == SearchBreakable) { return breakableHit.pHit; }
|
|
}
|
|
else
|
|
{
|
|
if (!FNullEnt(breakableHit.pHit))
|
|
{
|
|
if (strcmp(STRING(breakableHit.pHit->v.classname), "func_breakable") == 0)
|
|
{
|
|
return breakableHit.pHit;
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
else if (MovementFlag == SAMPLE_POLYFLAGS_FALL)
|
|
{
|
|
Vector TargetLoc = Vector(ToLoc.x, ToLoc.y, FromLoc.z);
|
|
|
|
UTIL_TraceLine(FromLoc, TargetLoc, dont_ignore_monsters, dont_ignore_glass, pBot->Edict->v.pContainingEntity, &breakableHit);
|
|
|
|
if (!FNullEnt(SearchBreakable))
|
|
{
|
|
if (breakableHit.pHit == SearchBreakable) { return breakableHit.pHit; }
|
|
}
|
|
else
|
|
{
|
|
if (!FNullEnt(breakableHit.pHit))
|
|
{
|
|
if (strcmp(STRING(breakableHit.pHit->v.classname), "func_breakable") == 0)
|
|
{
|
|
return breakableHit.pHit;
|
|
}
|
|
}
|
|
}
|
|
|
|
UTIL_TraceLine(TargetLoc, ToLoc + Vector(0.0f, 0.0f, 10.0f), dont_ignore_monsters, dont_ignore_glass, pBot->Edict->v.pContainingEntity, &breakableHit);
|
|
|
|
if (!FNullEnt(SearchBreakable))
|
|
{
|
|
if (breakableHit.pHit == SearchBreakable) { return breakableHit.pHit; }
|
|
}
|
|
else
|
|
{
|
|
if (!FNullEnt(breakableHit.pHit))
|
|
{
|
|
if (strcmp(STRING(breakableHit.pHit->v.classname), "func_breakable") == 0)
|
|
{
|
|
return breakableHit.pHit;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
UTIL_TraceLine(FromLoc, ToLoc, dont_ignore_monsters, dont_ignore_glass, pBot->Edict->v.pContainingEntity, &breakableHit);
|
|
|
|
if (!FNullEnt(SearchBreakable))
|
|
{
|
|
if (breakableHit.pHit == SearchBreakable) { return breakableHit.pHit; }
|
|
}
|
|
else
|
|
{
|
|
if (!FNullEnt(breakableHit.pHit))
|
|
{
|
|
if (strcmp(STRING(breakableHit.pHit->v.classname), "func_breakable") == 0)
|
|
{
|
|
return breakableHit.pHit;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
edict_t* UTIL_GetDoorBlockingPathPoint(const Vector FromLocation, const Vector ToLocation, const unsigned int MovementFlag, edict_t* SearchDoor)
|
|
{
|
|
|
|
Vector FromLoc = FromLocation;
|
|
Vector ToLoc = ToLocation;
|
|
|
|
TraceResult doorHit;
|
|
|
|
if (MovementFlag == SAMPLE_POLYFLAGS_LADDER || MovementFlag == SAMPLE_POLYFLAGS_WALLCLIMB)
|
|
{
|
|
Vector TargetLoc = Vector(FromLoc.x, FromLoc.y, ToLocation.z);
|
|
|
|
if (!FNullEnt(SearchDoor))
|
|
{
|
|
if (vlineIntersectsAABB(FromLoc, TargetLoc, SearchDoor->v.absmin, SearchDoor->v.absmax))
|
|
{
|
|
return SearchDoor;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (auto it = NavDoors.begin(); it != NavDoors.end(); it++)
|
|
{
|
|
if (vlineIntersectsAABB(FromLoc, TargetLoc, it->DoorEdict->v.absmin, it->DoorEdict->v.absmax))
|
|
{
|
|
return it->DoorEdict;
|
|
}
|
|
}
|
|
|
|
for (auto it = NavWeldableObstacles.begin(); it != NavWeldableObstacles.end(); it++)
|
|
{
|
|
if (vlineIntersectsAABB(FromLoc, TargetLoc, it->WeldableEdict->v.absmin, it->WeldableEdict->v.absmax))
|
|
{
|
|
return it->WeldableEdict;
|
|
}
|
|
}
|
|
}
|
|
|
|
Vector TargetLoc2 = Vector(ToLoc.x, ToLoc.y, ToLocation.z);
|
|
|
|
if (!FNullEnt(SearchDoor))
|
|
{
|
|
if (vlineIntersectsAABB(FromLoc, TargetLoc2, SearchDoor->v.absmin, SearchDoor->v.absmax))
|
|
{
|
|
return SearchDoor;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (auto it = NavDoors.begin(); it != NavDoors.end(); it++)
|
|
{
|
|
if (vlineIntersectsAABB(FromLoc, TargetLoc2, it->DoorEdict->v.absmin, it->DoorEdict->v.absmax))
|
|
{
|
|
return it->DoorEdict;
|
|
}
|
|
}
|
|
|
|
for (auto it = NavWeldableObstacles.begin(); it != NavWeldableObstacles.end(); it++)
|
|
{
|
|
if (vlineIntersectsAABB(FromLoc, TargetLoc2, it->WeldableEdict->v.absmin, it->WeldableEdict->v.absmax))
|
|
{
|
|
return it->WeldableEdict;
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
else if (MovementFlag == SAMPLE_POLYFLAGS_FALL)
|
|
{
|
|
Vector TargetLoc = Vector(ToLoc.x, ToLoc.y, FromLoc.z);
|
|
|
|
if (!FNullEnt(SearchDoor))
|
|
{
|
|
if (vlineIntersectsAABB(FromLoc, TargetLoc, SearchDoor->v.absmin, SearchDoor->v.absmax))
|
|
{
|
|
return SearchDoor;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (auto it = NavDoors.begin(); it != NavDoors.end(); it++)
|
|
{
|
|
if (vlineIntersectsAABB(FromLoc, TargetLoc, it->DoorEdict->v.absmin, it->DoorEdict->v.absmax))
|
|
{
|
|
return it->DoorEdict;
|
|
}
|
|
}
|
|
|
|
for (auto it = NavWeldableObstacles.begin(); it != NavWeldableObstacles.end(); it++)
|
|
{
|
|
if (vlineIntersectsAABB(FromLoc, TargetLoc, it->WeldableEdict->v.absmin, it->WeldableEdict->v.absmax))
|
|
{
|
|
return it->WeldableEdict;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!FNullEnt(SearchDoor))
|
|
{
|
|
if (vlineIntersectsAABB(TargetLoc, ToLoc, SearchDoor->v.absmin, SearchDoor->v.absmax))
|
|
{
|
|
return SearchDoor;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (auto it = NavDoors.begin(); it != NavDoors.end(); it++)
|
|
{
|
|
if (vlineIntersectsAABB(TargetLoc, ToLoc, it->DoorEdict->v.absmin, it->DoorEdict->v.absmax))
|
|
{
|
|
return it->DoorEdict;
|
|
}
|
|
}
|
|
|
|
for (auto it = NavWeldableObstacles.begin(); it != NavWeldableObstacles.end(); it++)
|
|
{
|
|
if (vlineIntersectsAABB(TargetLoc, ToLoc, it->WeldableEdict->v.absmin, it->WeldableEdict->v.absmax))
|
|
{
|
|
return it->WeldableEdict;
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
Vector TargetLoc = ToLoc + Vector(0.0f, 0.0f, 10.0f);
|
|
|
|
if (!FNullEnt(SearchDoor))
|
|
{
|
|
if (vlineIntersectsAABB(FromLoc, TargetLoc, SearchDoor->v.absmin, SearchDoor->v.absmax))
|
|
{
|
|
return SearchDoor;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (auto it = NavDoors.begin(); it != NavDoors.end(); it++)
|
|
{
|
|
if (vlineIntersectsAABB(FromLoc, TargetLoc, it->DoorEdict->v.absmin, it->DoorEdict->v.absmax))
|
|
{
|
|
return it->DoorEdict;
|
|
}
|
|
}
|
|
|
|
for (auto it = NavWeldableObstacles.begin(); it != NavWeldableObstacles.end(); it++)
|
|
{
|
|
if (vlineIntersectsAABB(FromLoc, TargetLoc, it->WeldableEdict->v.absmin, it->WeldableEdict->v.absmax))
|
|
{
|
|
return it->WeldableEdict;
|
|
}
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
bool UTIL_IsPathBlockedByDoor(const Vector StartLoc, const Vector EndLoc, edict_t* SearchDoor)
|
|
{
|
|
Vector ValidNavmeshPoint = UTIL_ProjectPointToNavmesh(EndLoc, BaseNavProfiles[ALL_NAV_PROFILE]);
|
|
|
|
if (!ValidNavmeshPoint)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
vector<bot_path_node> TestPath;
|
|
TestPath.clear();
|
|
|
|
// Now we find a path backwards from the valid nav mesh point to our location, trying to get as close as we can to it
|
|
|
|
dtStatus PathFindingStatus = FindPathClosestToPoint(BaseNavProfiles[ALL_NAV_PROFILE], StartLoc, ValidNavmeshPoint, TestPath, 50.0f);
|
|
|
|
if (dtStatusSucceed(PathFindingStatus))
|
|
{
|
|
for (auto it = TestPath.begin(); it != TestPath.end(); it++)
|
|
{
|
|
if (UTIL_GetDoorBlockingPathPoint(nullptr, &(*it), SearchDoor) != nullptr)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
DoorTrigger* UTIL_GetNearestDoorTriggerFromLift(edict_t* LiftEdict, nav_door* Door, CBaseEntity* IgnoreTrigger)
|
|
{
|
|
if (!Door) { return nullptr; }
|
|
|
|
if (Door->TriggerEnts.size() == 0) { return nullptr; }
|
|
|
|
DoorTrigger* NearestTrigger = nullptr;
|
|
float NearestDist = 0.0f;
|
|
|
|
for (auto it = Door->TriggerEnts.begin(); it != Door->TriggerEnts.end(); it++)
|
|
{
|
|
if (!FNullEnt(it->Edict) && it->Entity != IgnoreTrigger && it->bIsActivated)
|
|
{
|
|
Vector ButtonLocation = UTIL_GetClosestPointOnEntityToLocation(UTIL_GetCentreOfEntity(LiftEdict), it->Edict);
|
|
Vector NearestPointOnLift = UTIL_GetClosestPointOnEntityToLocation(ButtonLocation, LiftEdict);
|
|
|
|
float thisDist = vDist3DSq(ButtonLocation, NearestPointOnLift);
|
|
|
|
if (thisDist < sqrf(64.0f))
|
|
{
|
|
if (!NearestTrigger || thisDist < NearestDist)
|
|
{
|
|
NearestTrigger = &(*it);
|
|
NearestDist = thisDist;
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
return NearestTrigger;
|
|
}
|
|
|
|
DoorTrigger* UTIL_GetNearestDoorTrigger(const Vector Location, nav_door* Door, CBaseEntity* IgnoreTrigger, bool bCheckBlockedByDoor)
|
|
{
|
|
if (!Door) { return nullptr; }
|
|
|
|
if (Door->TriggerEnts.size() == 0) { return nullptr; }
|
|
|
|
DoorTrigger* NearestTrigger = nullptr;
|
|
float NearestDist = 0.0f;
|
|
|
|
Vector DoorLocation = UTIL_GetCentreOfEntity(Door->DoorEdict);
|
|
|
|
for (auto it = Door->TriggerEnts.begin(); it != Door->TriggerEnts.end(); it++)
|
|
{
|
|
if (!FNullEnt(it->Edict) && it->Entity != IgnoreTrigger && it->bIsActivated)
|
|
{
|
|
Vector ButtonLocation = UTIL_GetButtonFloorLocation(Location, it->Edict);
|
|
|
|
if ((!bCheckBlockedByDoor || !UTIL_IsPathBlockedByDoor(Location, ButtonLocation, Door->DoorEdict)) && UTIL_PointIsReachable(GetBaseNavProfile(MARINE_BASE_NAV_PROFILE), Location, ButtonLocation, 64.0f))
|
|
{
|
|
float ThisDist = vDist3DSq(Location, ButtonLocation);
|
|
|
|
if (!NearestTrigger || ThisDist < NearestDist)
|
|
{
|
|
NearestTrigger = &(*it);
|
|
NearestDist = ThisDist;
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
return NearestTrigger;
|
|
}
|
|
|
|
void CheckAndHandleBreakableObstruction(AvHAIPlayer* pBot, const Vector MoveFrom, const Vector MoveTo, unsigned int MovementFlags)
|
|
{
|
|
if (pBot->BotNavInfo.CurrentPathPoint >= pBot->BotNavInfo.CurrentPath.size()) { return; }
|
|
|
|
Vector MoveDir = UTIL_GetVectorNormal2D(MoveTo - pBot->Edict->v.origin);
|
|
|
|
if (vIsZero(MoveDir))
|
|
{
|
|
MoveDir = UTIL_GetForwardVector2D(pBot->Edict->v.angles);
|
|
}
|
|
|
|
TraceResult breakableHit;
|
|
|
|
edict_t* BlockingBreakableEdict = nullptr;
|
|
|
|
UTIL_TraceLine(pBot->Edict->v.origin, pBot->Edict->v.origin + (MoveDir * 100.0f), dont_ignore_monsters, dont_ignore_glass, pBot->Edict->v.pContainingEntity, &breakableHit);
|
|
|
|
if (!FNullEnt(breakableHit.pHit))
|
|
{
|
|
if (strcmp(STRING(breakableHit.pHit->v.classname), "func_breakable") == 0)
|
|
{
|
|
BlockingBreakableEdict = breakableHit.pHit;
|
|
}
|
|
}
|
|
|
|
bot_path_node CurrentPathNode = pBot->BotNavInfo.CurrentPath[pBot->BotNavInfo.CurrentPathPoint];
|
|
|
|
if (FNullEnt(BlockingBreakableEdict))
|
|
{
|
|
BlockingBreakableEdict = UTIL_GetBreakableBlockingPathPoint(pBot, &CurrentPathNode, nullptr);
|
|
}
|
|
|
|
if (FNullEnt(BlockingBreakableEdict))
|
|
{
|
|
int NumIterations = 0;
|
|
|
|
for (int i = (pBot->BotNavInfo.CurrentPathPoint + 1); i < pBot->BotNavInfo.CurrentPath.size(); i++)
|
|
{
|
|
bot_path_node ThisPathNode = pBot->BotNavInfo.CurrentPath[i];
|
|
BlockingBreakableEdict = UTIL_GetBreakableBlockingPathPoint(pBot, &ThisPathNode, nullptr);
|
|
|
|
NumIterations++;
|
|
|
|
if (!FNullEnt(BlockingBreakableEdict) || NumIterations >= 2)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (FNullEnt(BlockingBreakableEdict)) { return; }
|
|
|
|
Vector ClosestPoint = UTIL_GetClosestPointOnEntityToLocation(pBot->Edict->v.origin, BlockingBreakableEdict);
|
|
|
|
AvHAIWeapon DesiredWeapon = UTIL_GetPlayerPrimaryWeapon(pBot->Player);
|
|
|
|
if (IsPlayerMarine(pBot->Player))
|
|
{
|
|
DesiredWeapon = BotMarineChooseBestWeapon(pBot, nullptr);
|
|
}
|
|
else
|
|
{
|
|
if (IsPlayerSkulk(pBot->Edict))
|
|
{
|
|
DesiredWeapon = (BlockingBreakableEdict->v.health <= 30) ? WEAPON_SKULK_PARASITE : WEAPON_SKULK_BITE;
|
|
}
|
|
}
|
|
|
|
float DesiredRange = GetMaxIdealWeaponRange(DesiredWeapon);
|
|
|
|
if (vDist2DSq(pBot->Edict->v.origin, ClosestPoint) < sqrf(16.0f))
|
|
{
|
|
if (pBot->Edict->v.oldbuttons & IN_DUCK)
|
|
{
|
|
pBot->Button |= IN_DUCK;
|
|
}
|
|
else
|
|
{
|
|
if (pBot->CurrentEyePosition.z - ClosestPoint.z > 32.0f)
|
|
{
|
|
pBot->Button |= IN_DUCK;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (vDist3DSq(ClosestPoint, pBot->CurrentEyePosition) < sqrf(DesiredRange))
|
|
{
|
|
BotLookAt(pBot, BlockingBreakableEdict);
|
|
|
|
pBot->DesiredMoveWeapon = DesiredWeapon;
|
|
|
|
if (GetPlayerCurrentWeapon(pBot->Player) == DesiredWeapon)
|
|
{
|
|
pBot->Button |= IN_ATTACK;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
void NewMove(AvHAIPlayer* pBot)
|
|
{
|
|
if (pBot->BotNavInfo.CurrentPath.size() == 0 || pBot->BotNavInfo.CurrentPathPoint >= pBot->BotNavInfo.CurrentPath.size())
|
|
{
|
|
return;
|
|
}
|
|
|
|
bot_path_node CurrentPathNode = pBot->BotNavInfo.CurrentPath[pBot->BotNavInfo.CurrentPathPoint];
|
|
|
|
Vector MoveFrom = CurrentPathNode.FromLocation;
|
|
Vector MoveTo = CurrentPathNode.Location;
|
|
|
|
SamplePolyAreas CurrentNavArea = (SamplePolyAreas)CurrentPathNode.area;
|
|
SamplePolyFlags CurrentNavFlags = (SamplePolyFlags)CurrentPathNode.flag;
|
|
|
|
// Used to anticipate if we're about to enter a crouch area so we can start crouching early
|
|
unsigned char NextArea = SAMPLE_POLYAREA_GROUND;
|
|
|
|
if (pBot->BotNavInfo.CurrentPathPoint < pBot->BotNavInfo.CurrentPath.size() - 1)
|
|
{
|
|
bot_path_node NextPathNode = pBot->BotNavInfo.CurrentPath[pBot->BotNavInfo.CurrentPathPoint + 1];
|
|
|
|
NextArea = NextPathNode.area;
|
|
|
|
bool bIsNearNextPoint = (vDist2DSq(pBot->Edict->v.origin, NextPathNode.FromLocation) <= sqrf(50.0f));
|
|
|
|
// Start crouching early if we're about to enter a crouch path point
|
|
if (CanPlayerCrouch(pBot->Edict) && (CurrentNavArea == SAMPLE_POLYAREA_CROUCH || (NextArea == SAMPLE_POLYAREA_CROUCH && bIsNearNextPoint)))
|
|
{
|
|
pBot->Button |= IN_DUCK;
|
|
}
|
|
}
|
|
|
|
switch (CurrentNavFlags)
|
|
{
|
|
case SAMPLE_POLYFLAGS_WALK:
|
|
GroundMove(pBot, MoveFrom, MoveTo);
|
|
break;
|
|
case SAMPLE_POLYFLAGS_FALL:
|
|
FallMove(pBot, MoveFrom, MoveTo);
|
|
break;
|
|
case SAMPLE_POLYFLAGS_JUMP:
|
|
JumpMove(pBot, MoveFrom, MoveTo);
|
|
break;
|
|
case SAMPLE_POLYFLAGS_BLOCKED:
|
|
BlockedMove(pBot, MoveFrom, MoveTo);
|
|
break;
|
|
case SAMPLE_POLYFLAGS_TEAM1STRUCTURE:
|
|
case SAMPLE_POLYFLAGS_TEAM2STRUCTURE:
|
|
StructureBlockedMove(pBot, MoveFrom, MoveTo);
|
|
break;
|
|
case SAMPLE_POLYFLAGS_WALLCLIMB:
|
|
{
|
|
if (PlayerHasWeapon(pBot->Player, WEAPON_FADE_BLINK))
|
|
{
|
|
BlinkClimbMove(pBot, MoveFrom, MoveTo, CurrentPathNode.requiredZ);
|
|
}
|
|
else
|
|
{
|
|
WallClimbMove(pBot, MoveFrom, MoveTo, CurrentPathNode.requiredZ);
|
|
}
|
|
}
|
|
break;
|
|
case SAMPLE_POLYFLAGS_LADDER:
|
|
LadderMove(pBot, MoveFrom, MoveTo, CurrentPathNode.requiredZ, NextArea);
|
|
break;
|
|
case SAMPLE_POLYFLAGS_TEAM1PHASEGATE:
|
|
case SAMPLE_POLYFLAGS_TEAM2PHASEGATE:
|
|
PhaseGateMove(pBot, MoveFrom, MoveTo);
|
|
break;
|
|
case SAMPLE_POLYFLAGS_LIFT:
|
|
LiftMove(pBot, MoveFrom, MoveTo);
|
|
break;
|
|
default:
|
|
GroundMove(pBot, MoveFrom, MoveTo);
|
|
break;
|
|
}
|
|
|
|
if (vIsZero(pBot->LookTargetLocation) && vIsZero(pBot->MoveLookLocation))
|
|
{
|
|
Vector FurthestView = UTIL_GetFurthestVisiblePointOnPath(pBot);
|
|
|
|
if (vIsZero(FurthestView) || vDist2DSq(FurthestView, pBot->CurrentEyePosition) < sqrf(200.0f))
|
|
{
|
|
FurthestView = MoveTo;
|
|
|
|
Vector LookNormal = UTIL_GetVectorNormal2D(FurthestView - pBot->CurrentEyePosition);
|
|
|
|
FurthestView = FurthestView + (LookNormal * 1000.0f);
|
|
}
|
|
|
|
BotLookAt(pBot, FurthestView);
|
|
}
|
|
|
|
// While moving, check to make sure we're not obstructed by a func_breakable, e.g. vent or window.
|
|
CheckAndHandleBreakableObstruction(pBot, MoveFrom, MoveTo, CurrentNavFlags);
|
|
|
|
if (CurrentNavFlags != SAMPLE_POLYFLAGS_LIFT)
|
|
{
|
|
CheckAndHandleDoorObstruction(pBot);
|
|
}
|
|
|
|
}
|
|
|
|
void GroundMove(AvHAIPlayer* pBot, const Vector StartPoint, const Vector EndPoint)
|
|
{
|
|
edict_t* pEdict = pBot->Edict;
|
|
|
|
bot_path_node CurrentPathNode = pBot->BotNavInfo.CurrentPath[pBot->BotNavInfo.CurrentPathPoint];
|
|
|
|
Vector CurrentPos = (pBot->BotNavInfo.IsOnGround) ? pBot->Edict->v.origin : pBot->CurrentFloorPosition;
|
|
|
|
Vector vForward = UTIL_GetVectorNormal2D(EndPoint - CurrentPos);
|
|
|
|
// If we are over our current path point and can't get to it, try walking towards the next path point if we have one, or just directly forwards
|
|
if (vIsZero(vForward))
|
|
{
|
|
if (pBot->BotNavInfo.CurrentPathPoint < pBot->BotNavInfo.CurrentPath.size() - 1)
|
|
{
|
|
bot_path_node NextPathNode = pBot->BotNavInfo.CurrentPath[pBot->BotNavInfo.CurrentPathPoint + 1];
|
|
|
|
vForward = UTIL_GetVectorNormal2D(NextPathNode.Location - CurrentPos);
|
|
}
|
|
else
|
|
{
|
|
vForward = UTIL_GetForwardVector2D(pBot->Edict->v.angles);
|
|
}
|
|
}
|
|
|
|
// Same goes for the right vector, might not be the same as the bot's right
|
|
Vector vRight = UTIL_GetVectorNormal(UTIL_GetCrossProduct(vForward, UP_VECTOR));
|
|
|
|
bool bAdjustingForCollision = false;
|
|
|
|
float PlayerRadius = GetPlayerRadius(pBot->Player) + 2.0f;
|
|
|
|
Vector stTrcLft = CurrentPos - (vRight * PlayerRadius);
|
|
Vector stTrcRt = CurrentPos + (vRight * PlayerRadius);
|
|
Vector endTrcLft = stTrcLft + (vForward * 24.0f);
|
|
Vector endTrcRt = stTrcRt + (vForward * 24.0f);
|
|
|
|
bool bumpLeft = !UTIL_PointIsDirectlyReachable(pBot->BotNavInfo.NavProfile, stTrcLft, endTrcLft);
|
|
bool bumpRight = !UTIL_PointIsDirectlyReachable(pBot->BotNavInfo.NavProfile, stTrcRt, endTrcRt);
|
|
|
|
pBot->desiredMovementDir = vForward;
|
|
|
|
if (bumpRight && !bumpLeft)
|
|
{
|
|
pBot->desiredMovementDir = pBot->desiredMovementDir - vRight;
|
|
}
|
|
else if (bumpLeft && !bumpRight)
|
|
{
|
|
pBot->desiredMovementDir = pBot->desiredMovementDir + vRight;
|
|
}
|
|
else if (bumpLeft && bumpRight)
|
|
{
|
|
stTrcLft.z = pBot->Edict->v.origin.z;
|
|
stTrcRt.z = pBot->Edict->v.origin.z;
|
|
endTrcLft.z = pBot->Edict->v.origin.z;
|
|
endTrcRt.z = pBot->Edict->v.origin.z;
|
|
|
|
if (!UTIL_QuickTrace(pBot->Edict, stTrcLft, endTrcLft))
|
|
{
|
|
pBot->desiredMovementDir = pBot->desiredMovementDir + vRight;
|
|
}
|
|
else
|
|
{
|
|
pBot->desiredMovementDir = pBot->desiredMovementDir - vRight;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
float DistFromLine = vDistanceFromLine2D(StartPoint, EndPoint, CurrentPos);
|
|
|
|
if (DistFromLine > 18.0f)
|
|
{
|
|
float modifier = (float)vPointOnLine(StartPoint, EndPoint, CurrentPos);
|
|
pBot->desiredMovementDir = pBot->desiredMovementDir + (vRight * modifier);
|
|
}
|
|
|
|
float LeapDist = (IsPlayerSkulk(pBot->Edict)) ? UTIL_MetresToGoldSrcUnits(5.0f) : UTIL_MetresToGoldSrcUnits(2.0f);
|
|
|
|
if (IsPlayerFade(pBot->Edict) && CurrentPathNode.area == SAMPLE_POLYAREA_CROUCH)
|
|
{
|
|
LeapDist = UTIL_MetresToGoldSrcUnits(1.0f);
|
|
}
|
|
|
|
bool bIsAmbush = (pBot->BotNavInfo.MoveStyle == MOVESTYLE_AMBUSH);
|
|
|
|
if (!bIsAmbush && CanBotLeap(pBot) && vDist2DSq(pBot->Edict->v.origin, EndPoint) > sqrf(LeapDist) && UTIL_PointIsDirectlyReachable(pBot->BotNavInfo.NavProfile, pBot->Edict->v.origin, EndPoint))
|
|
{
|
|
float CombatWeaponEnergyCost = GetEnergyCostForWeapon(pBot->DesiredCombatWeapon);
|
|
float RequiredEnergy = (CombatWeaponEnergyCost + GetLeapCost(pBot)) - (GetPlayerEnergyRegenPerSecond(pEdict) * 0.5f); // We allow for around .5s of regen time as well
|
|
|
|
if (GetPlayerEnergy(pBot->Edict) >= RequiredEnergy)
|
|
{
|
|
Vector CurrVelocity = UTIL_GetVectorNormal2D(pBot->Edict->v.velocity);
|
|
|
|
float MoveDot = UTIL_GetDotProduct2D(CurrVelocity, vForward);
|
|
|
|
if (MoveDot >= 0.95f)
|
|
{
|
|
BotLeap(pBot, EndPoint);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pBot->desiredMovementDir = UTIL_GetVectorNormal2D(pBot->desiredMovementDir);
|
|
|
|
if (CanPlayerCrouch(pEdict))
|
|
{
|
|
Vector HeadLocation = GetPlayerTopOfCollisionHull(pEdict, false);
|
|
|
|
// Crouch if we have something in our way at head height
|
|
if (!UTIL_QuickTrace(pBot->Edict, HeadLocation, (HeadLocation + (pBot->desiredMovementDir * 50.0f))))
|
|
{
|
|
pBot->Button |= IN_DUCK;
|
|
}
|
|
}
|
|
}
|
|
|
|
void FallMove(AvHAIPlayer* pBot, const Vector StartPoint, const Vector EndPoint)
|
|
{
|
|
Vector vBotOrientation = UTIL_GetVectorNormal2D(EndPoint - pBot->Edict->v.origin);
|
|
Vector vForward = UTIL_GetVectorNormal2D(EndPoint - StartPoint);
|
|
|
|
if (pBot->BotNavInfo.IsOnGround)
|
|
{
|
|
if (vDist2DSq(pBot->Edict->v.origin, EndPoint) > sqrf(GetPlayerRadius(pBot->Player)))
|
|
{
|
|
pBot->desiredMovementDir = vBotOrientation;
|
|
}
|
|
else
|
|
{
|
|
pBot->desiredMovementDir = vForward;
|
|
}
|
|
|
|
bool bCanDuck = (IsPlayerMarine(pBot->Edict) || IsPlayerFade(pBot->Edict) || IsPlayerOnos(pBot->Edict));
|
|
|
|
if (!bCanDuck) { return; }
|
|
|
|
Vector HeadLocation = GetPlayerTopOfCollisionHull(pBot->Edict, false);
|
|
|
|
if (!UTIL_QuickTrace(pBot->Edict, HeadLocation, (HeadLocation + (pBot->desiredMovementDir * 50.0f))))
|
|
{
|
|
pBot->Button |= IN_DUCK;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pBot->desiredMovementDir = vBotOrientation;
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
void StructureBlockedMove(AvHAIPlayer* pBot, const Vector StartPoint, const Vector EndPoint)
|
|
{
|
|
Vector vForward = UTIL_GetVectorNormal2D(EndPoint - StartPoint);
|
|
|
|
pBot->desiredMovementDir = vForward;
|
|
|
|
DeployableSearchFilter BlockingFilter;
|
|
BlockingFilter.DeployableTeam = AIMGR_GetEnemyTeam(pBot->Player->GetTeam());
|
|
BlockingFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(3.0f);
|
|
|
|
vector<AvHAIBuildableStructure> BlockingStructures = AITAC_FindAllDeployables(pBot->Edict->v.origin, &BlockingFilter);
|
|
|
|
AvHAIBuildableStructure CulpritStructure;
|
|
float MinDist = 0.0f;
|
|
|
|
for (auto it = BlockingStructures.begin(); it != BlockingStructures.end(); it++)
|
|
{
|
|
AvHAIBuildableStructure ThisStructure = (*it);
|
|
|
|
float ThisDist = vDistanceFromLine2DSq(StartPoint, EndPoint, ThisStructure.Location);
|
|
|
|
if (FNullEnt(CulpritStructure.edict) || ThisDist < MinDist)
|
|
{
|
|
CulpritStructure = ThisStructure;
|
|
}
|
|
}
|
|
|
|
if (CulpritStructure.IsValid())
|
|
{
|
|
BotMoveLookAt(pBot, CulpritStructure.Location);
|
|
|
|
AvHAIWeapon AttackWeapon = (IsPlayerAlien(pBot->Edict)) ? BotAlienChooseBestWeaponForStructure(pBot, CulpritStructure.edict) : BotMarineChooseBestWeaponForStructure(pBot, CulpritStructure.edict);
|
|
|
|
if (GetPlayerCurrentWeapon(pBot->Player) != AttackWeapon)
|
|
{
|
|
pBot->DesiredMoveWeapon = AttackWeapon;
|
|
}
|
|
else
|
|
{
|
|
BotShootTarget(pBot, AttackWeapon, CulpritStructure.edict);
|
|
}
|
|
}
|
|
}
|
|
|
|
void BlockedMove(AvHAIPlayer* pBot, const Vector StartPoint, const Vector EndPoint)
|
|
{
|
|
Vector vForward = UTIL_GetVectorNormal2D(EndPoint - StartPoint);
|
|
|
|
if (vIsZero(vForward))
|
|
{
|
|
vForward = UTIL_GetForwardVector2D(pBot->Edict->v.angles);
|
|
}
|
|
|
|
pBot->desiredMovementDir = vForward;
|
|
|
|
Vector CurrVelocity = UTIL_GetVectorNormal2D(pBot->Edict->v.velocity);
|
|
|
|
float Dot = UTIL_GetDotProduct2D(vForward, CurrVelocity);
|
|
|
|
Vector FaceDir = UTIL_GetForwardVector2D(pBot->Edict->v.angles);
|
|
|
|
float FaceDot = UTIL_GetDotProduct2D(FaceDir, vForward);
|
|
|
|
// Yes this is cheating, but is it not cheating for humans to have millions of years of evolution
|
|
// driving their ability to judge a jump, while the bots have a single year of coding from a moron?
|
|
if (FaceDot < 0.95f)
|
|
{
|
|
float MoveSpeed = vSize2D(pBot->Edict->v.velocity);
|
|
Vector NewVelocity = vForward * MoveSpeed;
|
|
NewVelocity.z = pBot->Edict->v.velocity.z;
|
|
|
|
pBot->Edict->v.velocity = NewVelocity;
|
|
}
|
|
|
|
BotJump(pBot);
|
|
}
|
|
|
|
void JumpMove(AvHAIPlayer* pBot, const Vector StartPoint, const Vector EndPoint)
|
|
{
|
|
Vector vForward = UTIL_GetVectorNormal2D(EndPoint - pBot->Edict->v.origin);
|
|
|
|
if (vIsZero(vForward))
|
|
{
|
|
vForward = UTIL_GetVectorNormal2D(EndPoint - StartPoint);
|
|
}
|
|
|
|
pBot->desiredMovementDir = vForward;
|
|
|
|
Vector CurrVelocity = UTIL_GetVectorNormal2D(pBot->Edict->v.velocity);
|
|
|
|
float Dot = UTIL_GetDotProduct2D(vForward, CurrVelocity);
|
|
|
|
Vector FaceDir = UTIL_GetForwardVector2D(pBot->Edict->v.angles);
|
|
|
|
float FaceDot = UTIL_GetDotProduct2D(FaceDir, vForward);
|
|
|
|
// Yes this is cheating, but is it not cheating for humans to have millions of years of evolution
|
|
// driving their ability to judge a jump, while the bots have a single year of coding from a moron?
|
|
if (FaceDot < 0.95f)
|
|
{
|
|
float MoveSpeed = vSize2D(pBot->Edict->v.velocity);
|
|
Vector NewVelocity = vForward * MoveSpeed;
|
|
NewVelocity.z = pBot->Edict->v.velocity.z;
|
|
|
|
pBot->Edict->v.velocity = NewVelocity;
|
|
}
|
|
|
|
BotJump(pBot);
|
|
|
|
bool bCanDuck = (IsPlayerMarine(pBot->Edict) || IsPlayerFade(pBot->Edict) || IsPlayerOnos(pBot->Edict));
|
|
|
|
if (!bCanDuck) { return; }
|
|
|
|
Vector HeadLocation = GetPlayerTopOfCollisionHull(pBot->Edict, false);
|
|
|
|
if (!UTIL_QuickTrace(pBot->Edict, HeadLocation, (HeadLocation + (pBot->desiredMovementDir * 50.0f))))
|
|
{
|
|
pBot->Button |= IN_DUCK;
|
|
}
|
|
}
|
|
|
|
void LadderMove(AvHAIPlayer* pBot, const Vector StartPoint, const Vector EndPoint, float RequiredClimbHeight, unsigned char NextArea)
|
|
{
|
|
edict_t* pEdict = pBot->Edict;
|
|
AvHPlayer* AIPlayer = pBot->Player;
|
|
|
|
const Vector vForward = UTIL_GetVectorNormal2D(EndPoint - StartPoint);
|
|
|
|
bool bIsGoingUpLadder = (EndPoint.z > StartPoint.z);
|
|
|
|
// Stop holding crouch if we're a skulk so we can climb up/down
|
|
if (IsPlayerSkulk(pBot->Edict))
|
|
{
|
|
pBot->Button &= ~IN_DUCK;
|
|
|
|
edict_t* Ladder = UTIL_GetNearestLadderAtPoint(StartPoint);
|
|
|
|
if (!FNullEnt(Ladder))
|
|
{
|
|
Vector LadderStart = Ladder->v.absmin;
|
|
Vector LadderEnd = Ladder->v.absmax;
|
|
LadderEnd.z = LadderStart.z;
|
|
|
|
// Basically, if we're directly climbing up or down the ladder, treat it like a wall. The below test will be false if the ladder is to one side
|
|
if (vIntersects2D(StartPoint, EndPoint, LadderStart, LadderEnd))
|
|
{
|
|
if (bIsGoingUpLadder)
|
|
{
|
|
WallClimbMove(pBot, StartPoint, EndPoint, RequiredClimbHeight);
|
|
}
|
|
else
|
|
{
|
|
FallMove(pBot, StartPoint, EndPoint);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (IsPlayerOnLadder(pBot->Edict))
|
|
{
|
|
// We're on the ladder and actively climbing
|
|
Vector CurrentLadderNormal;
|
|
|
|
if (IsPlayerSkulk(pBot->Edict))
|
|
{
|
|
CurrentLadderNormal = UTIL_GetNearestSurfaceNormal(pBot->Edict->v.origin);
|
|
}
|
|
else
|
|
{
|
|
CurrentLadderNormal = UTIL_GetNearestLadderNormal(pBot->CollisionHullBottomLocation + Vector(0.0f, 0.0f, 5.0f));
|
|
}
|
|
|
|
CurrentLadderNormal = UTIL_GetVectorNormal2D(CurrentLadderNormal);
|
|
|
|
if (vIsZero(CurrentLadderNormal))
|
|
{
|
|
|
|
if (EndPoint.z > StartPoint.z)
|
|
{
|
|
CurrentLadderNormal = UTIL_GetVectorNormal2D(StartPoint - EndPoint);
|
|
}
|
|
else
|
|
{
|
|
CurrentLadderNormal = UTIL_GetVectorNormal2D(EndPoint - StartPoint);
|
|
}
|
|
}
|
|
|
|
const Vector LadderRightNormal = UTIL_GetVectorNormal(UTIL_GetCrossProduct(CurrentLadderNormal, UP_VECTOR));
|
|
|
|
Vector ClimbRightNormal = LadderRightNormal;
|
|
|
|
if (bIsGoingUpLadder)
|
|
{
|
|
ClimbRightNormal = -LadderRightNormal;
|
|
}
|
|
else
|
|
{
|
|
if (UTIL_GetDotProduct(CurrentLadderNormal, UP_VECTOR) > 0.5f)
|
|
{
|
|
FallMove(pBot, StartPoint, EndPoint);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (bIsGoingUpLadder)
|
|
{
|
|
|
|
Vector HullTraceTo = EndPoint;
|
|
HullTraceTo.z = pBot->CollisionHullBottomLocation.z;
|
|
|
|
|
|
// We have reached our desired climb height and want to get off the ladder
|
|
if ((pBot->Edict->v.origin.z >= RequiredClimbHeight) && UTIL_QuickHullTrace(pEdict, pEdict->v.origin, Vector(EndPoint.x, EndPoint.y, pEdict->v.origin.z), head_hull))
|
|
{
|
|
// Move directly towards the desired get-off point, looking slightly up still
|
|
pBot->desiredMovementDir = vForward;
|
|
|
|
Vector LookLocation = EndPoint;
|
|
LookLocation.z = pBot->CurrentEyePosition.z + 64.0f;
|
|
|
|
BotMoveLookAt(pBot, LookLocation);
|
|
|
|
// If the get-off point is opposite the ladder, then jump to get to it
|
|
if (UTIL_GetDotProduct(CurrentLadderNormal, vForward) > 0.75f)
|
|
{
|
|
BotJump(pBot);
|
|
}
|
|
|
|
if (!IsPlayerGorge(pEdict) && !IsPlayerLerk(pEdict) && !IsPlayerSkulk(pEdict))
|
|
{
|
|
Vector HeadTraceLocation = GetPlayerTopOfCollisionHull(pEdict, false);
|
|
|
|
bool bHittingHead = !UTIL_QuickTrace(pBot->Edict, HeadTraceLocation, HeadTraceLocation + Vector(0.0f, 0.0f, 10.0f));
|
|
bool bClimbIntoVent = (NextArea == SAMPLE_POLYAREA_CROUCH);
|
|
|
|
if (!IsPlayerSkulk(pBot->Edict) && (bHittingHead || bClimbIntoVent))
|
|
{
|
|
pBot->Button |= IN_DUCK;
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
// This is for cases where the ladder physically doesn't reach the desired get-off point and the bot kind of has to "jump" up off the ladder.
|
|
if (pBot->CollisionHullTopLocation.z >= UTIL_GetNearestLadderTopPoint(pEdict).z)
|
|
{
|
|
pBot->desiredMovementDir = vForward;
|
|
// We look up really far to get maximum launch
|
|
BotMoveLookAt(pBot, EndPoint + Vector(0.0f, 0.0f, 100.0f));
|
|
return;
|
|
}
|
|
|
|
// Still climbing the ladder. Look up, and move left/right on the ladder to avoid any blockages
|
|
|
|
Vector StartLeftTrace = pBot->CollisionHullTopLocation - (ClimbRightNormal * GetPlayerRadius(pBot->Player));
|
|
Vector StartRightTrace = pBot->CollisionHullTopLocation + (ClimbRightNormal * GetPlayerRadius(pBot->Player));
|
|
|
|
bool bBlockedLeft = !UTIL_QuickTrace(pEdict, StartLeftTrace, StartLeftTrace + Vector(0.0f, 0.0f, 32.0f));
|
|
bool bBlockedRight = !UTIL_QuickTrace(pEdict, StartRightTrace, StartRightTrace + Vector(0.0f, 0.0f, 32.0f));
|
|
|
|
// Look up at the top of the ladder
|
|
|
|
// If we are blocked going up the ladder, face the ladder and slide left/right to avoid blockage
|
|
if (bBlockedLeft && !bBlockedRight)
|
|
{
|
|
Vector LookLocation = pBot->Edict->v.origin - (CurrentLadderNormal * 50.0f);
|
|
LookLocation.z = RequiredClimbHeight + 100.0f;
|
|
BotMoveLookAt(pBot, LookLocation);
|
|
|
|
pBot->desiredMovementDir = ClimbRightNormal;
|
|
return;
|
|
}
|
|
|
|
if (bBlockedRight && !bBlockedLeft)
|
|
{
|
|
Vector LookLocation = pBot->Edict->v.origin - (CurrentLadderNormal * 50.0f);
|
|
LookLocation.z = RequiredClimbHeight + 100.0f;
|
|
BotMoveLookAt(pBot, LookLocation);
|
|
|
|
pBot->desiredMovementDir = -ClimbRightNormal;
|
|
return;
|
|
}
|
|
|
|
// Crouch if we're hitting our head on a ceiling
|
|
|
|
|
|
if (!IsPlayerGorge(pEdict) && !IsPlayerLerk(pEdict) && !IsPlayerSkulk(pEdict))
|
|
{
|
|
Vector HeadTraceLocation = GetPlayerTopOfCollisionHull(pEdict, false);
|
|
|
|
bool bHittingHead = !UTIL_QuickTrace(pBot->Edict, HeadTraceLocation, HeadTraceLocation + Vector(0.0f, 0.0f, 10.0f));
|
|
|
|
if (bHittingHead)
|
|
{
|
|
pBot->Button |= IN_DUCK;
|
|
}
|
|
}
|
|
|
|
// We're not blocked by anything
|
|
|
|
// If the get-off point is to the side, look to the side and climb. Otherwise, face the ladder
|
|
|
|
Vector LookLocation = EndPoint;
|
|
|
|
if (!IsPlayerSkulk(pBot->Edict))
|
|
{
|
|
float dot = UTIL_GetDotProduct2D(vForward, LadderRightNormal);
|
|
|
|
// Get-off point is to the side of the ladder rather than right at the top
|
|
if (fabsf(dot) > 0.5f)
|
|
{
|
|
if (dot > 0.0f)
|
|
{
|
|
LookLocation = pBot->Edict->v.origin + (LadderRightNormal * 50.0f);
|
|
}
|
|
else
|
|
{
|
|
LookLocation = pBot->Edict->v.origin - (LadderRightNormal * 50.0f);
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
// Get-off point is at the top of the ladder, so face the ladder
|
|
LookLocation = EndPoint - (CurrentLadderNormal * 50.0f);
|
|
}
|
|
|
|
LookLocation.z += 100.0f;
|
|
}
|
|
else
|
|
{
|
|
// Get-off point is at the top of the ladder, so face the ladder
|
|
LookLocation = UTIL_GetNearestLadderTopPoint(pBot->Edict->v.origin) - (CurrentLadderNormal * 50.0f);
|
|
LookLocation.z += 100.0f;
|
|
}
|
|
|
|
BotMoveLookAt(pBot, LookLocation);
|
|
|
|
if (RequiredClimbHeight > pBot->Edict->v.origin.z || IsPlayerSkulk(pBot->Edict))
|
|
{
|
|
pBot->desiredMovementDir = -CurrentLadderNormal;
|
|
}
|
|
else
|
|
{
|
|
pBot->desiredMovementDir = CurrentLadderNormal;
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
else
|
|
{
|
|
|
|
// We're going down the ladder
|
|
|
|
Vector StartLeftTrace = pBot->CollisionHullBottomLocation - (LadderRightNormal * (GetPlayerRadius(pBot->Player) + 2.0f));
|
|
Vector StartRightTrace = pBot->CollisionHullBottomLocation + (LadderRightNormal * (GetPlayerRadius(pBot->Player) + 2.0f));
|
|
|
|
bool bBlockedLeft = !UTIL_QuickTrace(pEdict, StartLeftTrace, StartLeftTrace - Vector(0.0f, 0.0f, 32.0f));
|
|
bool bBlockedRight = !UTIL_QuickTrace(pEdict, StartRightTrace, StartRightTrace - Vector(0.0f, 0.0f, 32.0f));
|
|
|
|
if (bBlockedLeft && !bBlockedRight)
|
|
{
|
|
pBot->desiredMovementDir = LadderRightNormal;
|
|
return;
|
|
}
|
|
|
|
if (bBlockedRight && !bBlockedLeft)
|
|
{
|
|
pBot->desiredMovementDir = -LadderRightNormal;
|
|
return;
|
|
}
|
|
|
|
if (EndPoint.z > pBot->Edict->v.origin.z || IsPlayerSkulk(pBot->Edict))
|
|
{
|
|
pBot->desiredMovementDir = -CurrentLadderNormal;
|
|
}
|
|
else
|
|
{
|
|
pBot->desiredMovementDir = CurrentLadderNormal;
|
|
}
|
|
|
|
// We're going down the ladder, look ahead on the path or at the bottom of the ladder if we can't
|
|
|
|
Vector LookLocation = EndPoint;
|
|
|
|
if (!IsPlayerSkulk(pBot->Edict))
|
|
{
|
|
Vector FurthestView = UTIL_GetFurthestVisiblePointOnPath(pBot);
|
|
|
|
if (vIsZero(FurthestView))
|
|
{
|
|
LookLocation = EndPoint + (CurrentLadderNormal * 100.0f);
|
|
}
|
|
|
|
// We're close enough to the end that we can jump off the ladder
|
|
if (UTIL_QuickTrace(pBot->Edict, pBot->Edict->v.origin, EndPoint) && (pBot->CollisionHullBottomLocation.z - EndPoint.z < 100.0f))
|
|
{
|
|
BotJump(pBot);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LookLocation = pBot->CurrentEyePosition - (CurrentLadderNormal * 50.0f);
|
|
LookLocation.z -= 100.0f;
|
|
}
|
|
|
|
BotMoveLookAt(pBot, LookLocation);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
// We're not yet on the ladder
|
|
|
|
// We're swimming
|
|
if (pBot->Edict->v.flags & FL_INWATER)
|
|
{
|
|
Vector TargetPoint = UTIL_GetNearestLadderCentrePoint(pBot->Edict->v.origin);
|
|
TargetPoint.z = pBot->Edict->v.origin.z;
|
|
|
|
pBot->desiredMovementDir = UTIL_GetVectorNormal2D(TargetPoint - pBot->Edict->v.origin);
|
|
Vector LookPoint = TargetPoint + Vector(0.0f, 0.0f, 100.0f);
|
|
|
|
BotMoveLookAt(pBot, LookPoint);
|
|
return;
|
|
}
|
|
|
|
if (!pBot->BotNavInfo.IsOnGround && !bIsGoingUpLadder)
|
|
{
|
|
// We're close enough to the end that we can jump off the ladder
|
|
if (UTIL_QuickTrace(pBot->Edict, pBot->Edict->v.origin, EndPoint) && (pBot->CollisionHullBottomLocation.z - EndPoint.z < 100.0f))
|
|
{
|
|
pBot->desiredMovementDir = UTIL_GetVectorNormal2D(EndPoint - pBot->Edict->v.origin);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// If we're going down the ladder and are approaching it, just keep moving towards it
|
|
if ((pBot->BotNavInfo.IsOnGround || IsPlayerSkulk(pBot->Edict)) && !bIsGoingUpLadder)
|
|
{
|
|
if (vDist2DSq(pEdict->v.origin, StartPoint) < sqrf(32.0f))
|
|
{
|
|
pBot->BotNavInfo.bShouldWalk = true;
|
|
}
|
|
|
|
Vector LadderStart = UTIL_GetNearestLadderTopPoint(StartPoint);
|
|
|
|
Vector ApproachDir = UTIL_GetVectorNormal2D(LadderStart - pBot->Edict->v.origin);
|
|
Vector IdealApproachDir = UTIL_GetVectorNormal2D(LadderStart - StartPoint);
|
|
|
|
float DistFromLine = vDistanceFromLine2DSq(StartPoint, LadderStart, pBot->Edict->v.origin);
|
|
|
|
pBot->desiredMovementDir = IdealApproachDir;
|
|
|
|
if (DistFromLine > sqrf(4.0f))
|
|
{
|
|
Vector vRight = UTIL_GetCrossProduct(IdealApproachDir, UP_VECTOR);
|
|
|
|
float modifier = (float)vPointOnLine(StartPoint, LadderStart, pBot->Edict->v.origin);
|
|
pBot->desiredMovementDir = IdealApproachDir + (vRight * modifier);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
Vector nearestLadderTop = UTIL_GetNearestLadderTopPoint(pEdict);
|
|
|
|
if (bIsGoingUpLadder && ((pBot->CollisionHullTopLocation.z > EndPoint.z) || (pBot->Edict->v.origin.z > nearestLadderTop.z)))
|
|
{
|
|
pBot->desiredMovementDir = vForward;
|
|
|
|
if (!UTIL_QuickHullTrace(pEdict, pEdict->v.origin, Vector(EndPoint.x, EndPoint.y, pEdict->v.origin.z), head_hull))
|
|
{
|
|
// Gorges can't duck, so they have to jump to get over any barrier
|
|
if (!IsPlayerGorge(pEdict))
|
|
{
|
|
pBot->Button |= IN_DUCK;
|
|
}
|
|
else
|
|
{
|
|
BotJump(pBot);
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
if (pBot->Edict->v.origin.z < nearestLadderTop.z)
|
|
{
|
|
|
|
Vector nearestLadderPoint = UTIL_GetNearestLadderCentrePoint(pEdict);
|
|
nearestLadderPoint.z = pEdict->v.origin.z;
|
|
pBot->desiredMovementDir = UTIL_GetVectorNormal2D(nearestLadderPoint - pEdict->v.origin);
|
|
|
|
Vector LookPoint = nearestLadderPoint + Vector(0.0f, 0.0f, 20.0f);
|
|
|
|
BotMoveLookAt(pBot, LookPoint);
|
|
}
|
|
}
|
|
|
|
bool UTIL_TriggerHasBeenRecentlyActivated(edict_t* TriggerEntity)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
DoorTrigger* UTIL_GetDoorTriggerByEntity(edict_t* TriggerEntity)
|
|
{
|
|
for (auto door = NavDoors.begin(); door != NavDoors.end(); door++)
|
|
{
|
|
for (auto trig = door->TriggerEnts.begin(); trig != door->TriggerEnts.end(); trig++)
|
|
{
|
|
if (trig->Edict == TriggerEntity) { return &(*trig); }
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void LiftMove(AvHAIPlayer* pBot, const Vector StartPoint, const Vector EndPoint)
|
|
{
|
|
nav_door* NearestLift = UTIL_GetClosestLiftToPoints(StartPoint, EndPoint);
|
|
|
|
if (!NearestLift)
|
|
{
|
|
GroundMove(pBot, StartPoint, EndPoint);
|
|
return;
|
|
}
|
|
|
|
Vector LiftPosition = UTIL_GetCentreOfEntity(NearestLift->DoorEdict);
|
|
|
|
pBot->desiredMovementDir = ZERO_VECTOR;
|
|
|
|
Vector DesiredStartStop = ZERO_VECTOR;
|
|
Vector DesiredEndStop = ZERO_VECTOR;
|
|
float minStartDist = 0.0f;
|
|
float minEndDist = 0.0f;
|
|
|
|
// Find the desired stop point for us to get onto the lift
|
|
for (auto it = NearestLift->StopPoints.begin(); it != NearestLift->StopPoints.end(); it++)
|
|
{
|
|
Vector LiftStopPoint = (*it) + Vector(0.0f, 0.0f, NearestLift->DoorEdict->v.size.z * 0.5f);
|
|
|
|
float thisStartDist = vDist3DSq(LiftStopPoint, StartPoint);
|
|
float thisEndDist = vDist3DSq(LiftStopPoint, EndPoint);
|
|
if (vIsZero(DesiredStartStop) || thisStartDist < minStartDist)
|
|
{
|
|
DesiredStartStop = *it;
|
|
minStartDist = thisStartDist;
|
|
}
|
|
|
|
if (vIsZero(DesiredEndStop) || thisEndDist < minEndDist)
|
|
{
|
|
DesiredEndStop = *it;
|
|
minEndDist = thisEndDist;
|
|
}
|
|
}
|
|
|
|
bool bIsLiftMoving = (NearestLift->DoorEdict->v.velocity.Length() > 0.0f);
|
|
bool bIsLiftMovingToStart = bIsLiftMoving && (vDist3DSq(NearestLift->DoorEntity->m_vecFinalDest, DesiredStartStop) < sqrf(50.0f));
|
|
bool bIsLiftMovingToEnd = bIsLiftMoving && (vDist3DSq(NearestLift->DoorEntity->m_vecFinalDest, DesiredEndStop) < sqrf(50.0f));
|
|
bool bIsLiftAtOrNearStart = (vDist3DSq(LiftPosition, DesiredStartStop) < sqrf(50.0f));
|
|
bool bIsLiftAtOrNearEnd = (vDist3DSq(LiftPosition, DesiredEndStop) < sqrf(50.0f));
|
|
|
|
bool bIsOnLift = (pBot->Edict->v.groundentity == NearestLift->DoorEdict);
|
|
bool bWaitingToEmbark = (!bIsOnLift && vDist3DSq(pBot->Edict->v.origin, StartPoint) < vDist3DSq(pBot->Edict->v.origin, EndPoint)) || (bIsOnLift && !bIsLiftMoving && bIsLiftAtOrNearStart);
|
|
|
|
|
|
// Do nothing if we're on a moving lift
|
|
if (bIsLiftMoving && bIsOnLift)
|
|
{
|
|
Vector LiftEdge = UTIL_GetClosestPointOnEntityToLocation(StartPoint, NearestLift->DoorEdict);
|
|
|
|
bool bFullyOnLift = vDist2DSq(pBot->Edict->v.origin, LiftEdge) > sqrf(GetPlayerRadius(pBot->Player) * 2.0f);
|
|
|
|
if (!bFullyOnLift)
|
|
{
|
|
pBot->desiredMovementDir = UTIL_GetVectorNormal2D(LiftPosition - pBot->Edict->v.origin);
|
|
}
|
|
|
|
if (!UTIL_QuickHullTrace(pBot->Edict, pBot->Edict->v.origin, pBot->CollisionHullTopLocation + Vector(0.0f, 0.0f, 64.0f)))
|
|
{
|
|
pBot->desiredMovementDir = UTIL_GetVectorNormal2D(LiftPosition - pBot->Edict->v.origin);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
// if we've reached our stop, or we can directly get to the end point. Move straight there
|
|
|
|
Vector BotNavPosition = UTIL_ProjectPointToNavmesh(pBot->CollisionHullBottomLocation);
|
|
|
|
BotNavPosition = (vIsZero(BotNavPosition)) ? pBot->CollisionHullBottomLocation : BotNavPosition;
|
|
|
|
if ((bIsOnLift && !bIsLiftMoving && bIsLiftAtOrNearEnd) || UTIL_PointIsDirectlyReachable(BotNavPosition, EndPoint))
|
|
{
|
|
MoveToWithoutNav(pBot, EndPoint);
|
|
return;
|
|
}
|
|
|
|
// We must be either waiting to embark, or we've stopped elsewhere on our journey and need to get the lift moving again
|
|
|
|
// Lift is leaving without us! Get on it quick
|
|
if (bIsLiftMoving && !bIsLiftMovingToStart && bIsLiftAtOrNearStart && !bIsOnLift)
|
|
{
|
|
pBot->desiredMovementDir = UTIL_GetVectorNormal2D(LiftPosition - pBot->CollisionHullBottomLocation);
|
|
BotJump(pBot);
|
|
return;
|
|
}
|
|
|
|
if (bIsLiftMoving)
|
|
{
|
|
if (vDist2DSq(pBot->Edict->v.origin, StartPoint) > sqrf(50.0f))
|
|
{
|
|
NAV_SetMoveMovementTask(pBot, StartPoint, nullptr);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (bIsLiftAtOrNearStart && vEquals(DesiredStartStop, DesiredEndStop))
|
|
{
|
|
if (!bIsOnLift)
|
|
{
|
|
if (vDist2DSq(pBot->Edict->v.origin, StartPoint) > sqrf(50.0f))
|
|
{
|
|
NAV_SetMoveMovementTask(pBot, StartPoint, nullptr);
|
|
return;
|
|
}
|
|
|
|
pBot->desiredMovementDir = UTIL_GetVectorNormal2D(LiftPosition - pBot->CollisionHullBottomLocation);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Lift is stopped somewhere else, summon it
|
|
if (!bIsLiftMoving)
|
|
{
|
|
DoorTrigger* NearestLiftTrigger = nullptr;
|
|
|
|
if (bIsLiftAtOrNearStart)
|
|
{
|
|
float NearestDist = 0.0f;
|
|
|
|
for (auto it = NearestLift->TriggerEnts.begin(); it != NearestLift->TriggerEnts.end(); it++)
|
|
{
|
|
if (it->bIsActivated)
|
|
{
|
|
Vector CheckLocation = UTIL_GetCentreOfEntity(NearestLift->DoorEdict);
|
|
CheckLocation.z = pBot->Edict->v.origin.z;
|
|
|
|
Vector ButtonLocation = UTIL_GetClosestPointOnEntityToLocation(CheckLocation, it->Edict);
|
|
Vector NearestPointOnLift = UTIL_GetClosestPointOnEntityToLocation(ButtonLocation, NearestLift->DoorEdict);
|
|
|
|
NearestPointOnLift.z = pBot->Edict->v.origin.z;
|
|
|
|
float thisDist = vDist3DSq(ButtonLocation, NearestPointOnLift);
|
|
|
|
if (thisDist < sqrf(64.0f))
|
|
{
|
|
if (!NearestLiftTrigger || thisDist < NearestDist)
|
|
{
|
|
NearestLiftTrigger = &(*it);
|
|
NearestDist = thisDist;
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!NearestLiftTrigger)
|
|
{
|
|
NearestLiftTrigger = UTIL_GetNearestDoorTrigger(pBot->Edict->v.origin, NearestLift, nullptr, false);
|
|
}
|
|
|
|
if (NearestLiftTrigger)
|
|
{
|
|
// If the trigger is on cooldown, or the door/train is designed to automatically return without being summoned, then just wait for it to come back
|
|
if (gpGlobals->time < NearestLiftTrigger->NextActivationTime || (NearestLift->DoorType == DOORTYPE_TRAIN && !(NearestLift->DoorEdict->v.spawnflags & SF_TRAIN_WAIT_RETRIGGER)) || (NearestLift->DoorType == DOORTYPE_DOOR && NearestLift->DoorEntity && NearestLift->DoorEntity->GetToggleState() == TS_AT_TOP && NearestLift->DoorEntity->m_flWait > 0.0f && !FBitSet(NearestLift->DoorEdict->v.spawnflags, SF_DOOR_NO_AUTO_RETURN)))
|
|
{
|
|
if (!bIsOnLift && !bIsLiftAtOrNearStart)
|
|
{
|
|
// Make sure we won't be squashed by the lift coming down on us
|
|
if (vBBOverlaps2D(pBot->Edict->v.absmin, pBot->Edict->v.absmax, NearestLift->DoorEdict->v.absmin, NearestLift->DoorEdict->v.absmax))
|
|
{
|
|
pBot->desiredMovementDir = UTIL_GetVectorNormal2D(StartPoint - DesiredStartStop);
|
|
}
|
|
else
|
|
{
|
|
if (vDist2DSq(pBot->Edict->v.origin, StartPoint) > sqrf(32.0f))
|
|
{
|
|
NAV_SetMoveMovementTask(pBot, StartPoint, nullptr);
|
|
}
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
bool bFullyOnLift = false;
|
|
|
|
if (bIsOnLift)
|
|
{
|
|
Vector LiftEdge = UTIL_GetClosestPointOnEntityToLocation(StartPoint, NearestLift->DoorEdict);
|
|
|
|
bFullyOnLift = vDist2DSq(pBot->Edict->v.origin, LiftEdge) > (sqrf(GetPlayerRadius(pBot->Player) * 1.1f) );
|
|
}
|
|
|
|
if (bIsLiftAtOrNearStart && (!bIsOnLift || !bFullyOnLift) )
|
|
{
|
|
pBot->desiredMovementDir = UTIL_GetVectorNormal2D(LiftPosition - StartPoint);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (bIsLiftAtOrNearStart)
|
|
{
|
|
Vector ButtonFloorLocation = UTIL_GetClosestPointOnEntityToLocation(pBot->Edict->v.origin, NearestLiftTrigger->Edict);
|
|
|
|
Vector NearestPointOnLiftToButton = UTIL_GetClosestPointOnEntityToLocation(ButtonFloorLocation, NearestLift->DoorEdict);
|
|
|
|
bool ButtonReachableFromLift = (NearestLiftTrigger->TriggerType == DOOR_TRIGGER) ? vBBOverlaps2D(NearestLiftTrigger->Edict->v.absmin, NearestLiftTrigger->Edict->v.absmax, NearestLift->DoorEdict->v.absmin, NearestLift->DoorEdict->v.absmax) : (!vIsZero(ButtonFloorLocation) && (vDist2DSq(ButtonFloorLocation, NearestPointOnLiftToButton) <= sqrf(64.0f)));
|
|
|
|
if (ButtonReachableFromLift)
|
|
{
|
|
if (NearestLiftTrigger->TriggerType == DOOR_BUTTON)
|
|
{
|
|
if (IsPlayerInUseRange(pBot->Edict, NearestLiftTrigger->Edict))
|
|
{
|
|
BotUseObject(pBot, NearestLiftTrigger->Edict, false);
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pBot->desiredMovementDir = UTIL_GetVectorNormal2D(NearestLiftTrigger->Edict->v.origin - pBot->Edict->v.origin);
|
|
}
|
|
|
|
if (!bIsOnLift)
|
|
{
|
|
pBot->desiredMovementDir = UTIL_GetVectorNormal2D(LiftPosition - pBot->Edict->v.origin);
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
pBot->desiredMovementDir = UTIL_GetVectorNormal2D(ButtonFloorLocation - pBot->Edict->v.origin);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (NearestLiftTrigger->TriggerType == DOOR_BUTTON)
|
|
{
|
|
Vector UseLocation = UTIL_GetButtonFloorLocation(pBot->Edict->v.origin, NearestLiftTrigger->Edict);
|
|
|
|
NAV_SetUseMovementTask(pBot, NearestLiftTrigger->Edict, NearestLiftTrigger);
|
|
}
|
|
else if (NearestLiftTrigger->TriggerType == DOOR_TRIGGER)
|
|
{
|
|
NAV_SetTouchMovementTask(pBot, NearestLiftTrigger->Edict, NearestLiftTrigger);
|
|
}
|
|
else if (NearestLiftTrigger->TriggerType == DOOR_WELD)
|
|
{
|
|
NAV_SetWeldMovementTask(pBot, NearestLiftTrigger->Edict, NearestLiftTrigger);
|
|
}
|
|
else if (NearestLiftTrigger->TriggerType == DOOR_BREAK)
|
|
{
|
|
NAV_SetBreakMovementTask(pBot, NearestLiftTrigger->Edict, NearestLiftTrigger);
|
|
}
|
|
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
if (!bIsOnLift && vDist2DSq(pBot->Edict->v.origin, StartPoint) > sqrf(50.0f) && !bIsLiftAtOrNearStart)
|
|
{
|
|
NAV_SetMoveMovementTask(pBot, StartPoint, nullptr);
|
|
}
|
|
else
|
|
{
|
|
if (!bIsOnLift && bIsLiftAtOrNearStart)
|
|
{
|
|
pBot->desiredMovementDir = UTIL_GetVectorNormal2D(LiftPosition - StartPoint);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
return;
|
|
}
|
|
|
|
}
|
|
|
|
void PhaseGateMove(AvHAIPlayer* pBot, const Vector StartPoint, const Vector EndPoint)
|
|
{
|
|
DeployableSearchFilter PGFilter;
|
|
PGFilter.DeployableTeam = pBot->Player->GetTeam();
|
|
PGFilter.DeployableTypes = STRUCTURE_MARINE_PHASEGATE;
|
|
PGFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(2.0f);
|
|
PGFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED;
|
|
|
|
AvHAIBuildableStructure NearestPhaseGate = AITAC_FindClosestDeployableToLocation(pBot->Edict->v.origin, &PGFilter);
|
|
|
|
if (!NearestPhaseGate.IsValid()) { return; }
|
|
|
|
if (IsPlayerInUseRange(pBot->Edict, NearestPhaseGate.edict))
|
|
{
|
|
BotMoveLookAt(pBot, NearestPhaseGate.edict->v.origin);
|
|
pBot->desiredMovementDir = g_vecZero;
|
|
BotUseObject(pBot, NearestPhaseGate.edict, false);
|
|
|
|
if (vDist2DSq(pBot->Edict->v.origin, NearestPhaseGate.edict->v.origin) < sqrf(16.0f))
|
|
{
|
|
pBot->desiredMovementDir = UTIL_GetForwardVector2D(NearestPhaseGate.edict->v.angles);
|
|
}
|
|
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
pBot->desiredMovementDir = UTIL_GetVectorNormal2D(NearestPhaseGate.edict->v.origin - pBot->Edict->v.origin);
|
|
}
|
|
}
|
|
|
|
bool IsBotOffPath(const AvHAIPlayer* pBot)
|
|
{
|
|
if (pBot->BotNavInfo.CurrentPath.size() == 0 || pBot->BotNavInfo.CurrentPathPoint >= pBot->BotNavInfo.CurrentPath.size())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
bot_path_node CurrentPathNode = pBot->BotNavInfo.CurrentPath[pBot->BotNavInfo.CurrentPathPoint];
|
|
|
|
SamplePolyFlags CurrentNavFlag = (SamplePolyFlags)CurrentPathNode.flag;
|
|
Vector MoveFrom = CurrentPathNode.FromLocation;
|
|
Vector MoveTo = CurrentPathNode.Location;
|
|
|
|
Vector NextMoveLocation = ZERO_VECTOR;
|
|
SamplePolyFlags NextMoveFlag = SAMPLE_POLYFLAGS_DISABLED;
|
|
|
|
if (pBot->BotNavInfo.CurrentPathPoint < pBot->BotNavInfo.CurrentPath.size() - 1)
|
|
{
|
|
bot_path_node NextPathNode = pBot->BotNavInfo.CurrentPath[pBot->BotNavInfo.CurrentPathPoint];
|
|
|
|
NextMoveLocation = NextPathNode.Location;
|
|
NextMoveFlag = (SamplePolyFlags)NextPathNode.flag;
|
|
}
|
|
|
|
|
|
switch (CurrentNavFlag)
|
|
{
|
|
case SAMPLE_POLYFLAGS_WALK:
|
|
return IsBotOffWalkNode(pBot, MoveFrom, MoveTo, NextMoveLocation, NextMoveFlag);
|
|
case SAMPLE_POLYFLAGS_WELD:
|
|
case SAMPLE_POLYFLAGS_DOOR:
|
|
case SAMPLE_POLYFLAGS_TEAM1STRUCTURE:
|
|
case SAMPLE_POLYFLAGS_TEAM2STRUCTURE:
|
|
return IsBotOffObstacleNode(pBot, MoveFrom, MoveTo, NextMoveLocation, NextMoveFlag);
|
|
case SAMPLE_POLYFLAGS_LADDER:
|
|
return IsBotOffLadderNode(pBot, MoveFrom, MoveTo, NextMoveLocation, NextMoveFlag);
|
|
case SAMPLE_POLYFLAGS_FALL:
|
|
return IsBotOffFallNode(pBot, MoveFrom, MoveTo, NextMoveLocation, NextMoveFlag);
|
|
case SAMPLE_POLYFLAGS_WALLCLIMB:
|
|
return IsBotOffClimbNode(pBot, MoveFrom, MoveTo, NextMoveLocation, NextMoveFlag);
|
|
case SAMPLE_POLYFLAGS_JUMP:
|
|
case SAMPLE_POLYFLAGS_DUCKJUMP:
|
|
case SAMPLE_POLYFLAGS_BLOCKED:
|
|
return IsBotOffJumpNode(pBot, MoveFrom, MoveTo, NextMoveLocation, NextMoveFlag);
|
|
case SAMPLE_POLYFLAGS_TEAM1PHASEGATE:
|
|
case SAMPLE_POLYFLAGS_TEAM2PHASEGATE:
|
|
return IsBotOffPhaseGateNode(pBot, MoveFrom, MoveTo, NextMoveLocation, NextMoveFlag);
|
|
case SAMPLE_POLYFLAGS_LIFT:
|
|
return IsBotOffLiftNode(pBot, MoveFrom, MoveTo, NextMoveLocation, NextMoveFlag);
|
|
default:
|
|
return IsBotOffFallNode(pBot, MoveFrom, MoveTo, NextMoveLocation, NextMoveFlag);
|
|
}
|
|
|
|
return IsBotOffFallNode(pBot, MoveFrom, MoveTo, NextMoveLocation, NextMoveFlag);
|
|
|
|
}
|
|
|
|
bool IsBotOffLadderNode(const AvHAIPlayer* pBot, Vector MoveStart, Vector MoveEnd, Vector NextMoveDestination, SamplePolyFlags NextMoveFlag)
|
|
{
|
|
if (!IsPlayerOnLadder(pBot->Edict))
|
|
{
|
|
if (IsPlayerClimbingWall(pBot->Edict)) { return true; }
|
|
|
|
if (pBot->BotNavInfo.IsOnGround)
|
|
{
|
|
if (!UTIL_PointIsDirectlyReachable(GetPlayerBottomOfCollisionHull(pBot->Edict), MoveStart) && !UTIL_PointIsDirectlyReachable(GetPlayerBottomOfCollisionHull(pBot->Edict), MoveEnd)) { return true; }
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool IsBotOffWalkNode(const AvHAIPlayer* pBot, Vector MoveStart, Vector MoveEnd, Vector NextMoveDestination, SamplePolyFlags NextMoveFlag)
|
|
{
|
|
if (!pBot->BotNavInfo.IsOnGround) { return false; }
|
|
|
|
// This shouldn't happen... but does occasionally. Walk moves should always be directly reachable from start to end
|
|
if (!UTIL_PointIsDirectlyReachable(MoveStart, MoveEnd)) { return true; }
|
|
|
|
Vector NearestPointOnLine = vClosestPointOnLine2D(MoveStart, MoveEnd, pBot->Edict->v.origin);
|
|
|
|
if (vDist2DSq(pBot->Edict->v.origin, NearestPointOnLine) > sqrf(GetPlayerRadius(pBot->Edict) * 3.0f)) { return true; }
|
|
|
|
if (!FNullEnt(pBot->Edict->v.groundentity))
|
|
{
|
|
nav_door* Door = UTIL_GetNavDoorByEdict(pBot->Edict->v.groundentity);
|
|
|
|
if (Door) { return false; }
|
|
}
|
|
|
|
if (vEquals2D(NearestPointOnLine, MoveStart) && !UTIL_PointIsDirectlyReachable(pBot->CurrentFloorPosition, MoveStart)) { return true; }
|
|
if (vEquals2D(NearestPointOnLine, MoveEnd) && !UTIL_PointIsDirectlyReachable(pBot->CurrentFloorPosition, MoveEnd)) { return true; }
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
bool IsBotOffFallNode(const AvHAIPlayer* pBot, Vector MoveStart, Vector MoveEnd, Vector NextMoveDestination, SamplePolyFlags NextMoveFlag)
|
|
{
|
|
if (!pBot->BotNavInfo.IsOnGround) { return false; }
|
|
|
|
Vector NearestPointOnLine = vClosestPointOnLine2D(MoveStart, MoveEnd, pBot->Edict->v.origin);
|
|
|
|
if (vDist2DSq(pBot->Edict->v.origin, NearestPointOnLine) > sqrf(GetPlayerRadius(pBot->Edict) * 3.0f)) { return true; }
|
|
|
|
if (!FNullEnt(pBot->Edict->v.groundentity))
|
|
{
|
|
nav_door* Door = UTIL_GetNavDoorByEdict(pBot->Edict->v.groundentity);
|
|
|
|
if (Door) { return false; }
|
|
}
|
|
|
|
if (!UTIL_PointIsDirectlyReachable(pBot->CurrentFloorPosition, MoveStart) && !UTIL_PointIsDirectlyReachable(pBot->CurrentFloorPosition, MoveEnd)) { return true; }
|
|
|
|
return false;
|
|
}
|
|
|
|
bool IsBotOffClimbNode(const AvHAIPlayer* pBot, Vector MoveStart, Vector MoveEnd, Vector NextMoveDestination, SamplePolyFlags NextMoveFlag)
|
|
{
|
|
if (!IsPlayerClimbingWall(pBot->Edict) && (pBot->Edict->v.flags & FL_ONGROUND))
|
|
{
|
|
return (!UTIL_PointIsDirectlyReachable(GetPlayerBottomOfCollisionHull(pBot->Edict), MoveStart) && !UTIL_PointIsDirectlyReachable(GetPlayerBottomOfCollisionHull(pBot->Edict), MoveEnd));
|
|
}
|
|
|
|
Vector ClosestPointOnLine = vClosestPointOnLine2D(MoveStart, MoveEnd, pBot->Edict->v.origin);
|
|
|
|
return vDist2DSq(pBot->Edict->v.origin, ClosestPointOnLine) > sqrf(GetPlayerRadius(pBot->Edict) * 3.0f);
|
|
}
|
|
|
|
bool IsBotOffJumpNode(const AvHAIPlayer* pBot, Vector MoveStart, Vector MoveEnd, Vector NextMoveDestination, SamplePolyFlags NextMoveFlag)
|
|
{
|
|
if (!pBot->BotNavInfo.IsOnGround) { return false; }
|
|
|
|
Vector ClosestPointOnLine = vClosestPointOnLine2D(MoveStart, MoveEnd, pBot->Edict->v.origin);
|
|
|
|
if (vEquals2D(ClosestPointOnLine, MoveStart) || vEquals2D(ClosestPointOnLine, MoveEnd))
|
|
{
|
|
return (!UTIL_PointIsDirectlyReachable(GetPlayerBottomOfCollisionHull(pBot->Edict), MoveStart) && !UTIL_PointIsDirectlyReachable(GetPlayerBottomOfCollisionHull(pBot->Edict), MoveEnd));
|
|
}
|
|
|
|
return vDist2DSq(pBot->Edict->v.origin, ClosestPointOnLine) > sqrf(GetPlayerRadius(pBot->Edict) * 2.0f);
|
|
}
|
|
|
|
bool IsBotOffPhaseGateNode(const AvHAIPlayer* pBot, Vector MoveStart, Vector MoveEnd, Vector NextMoveDestination, SamplePolyFlags NextMoveFlag)
|
|
{
|
|
if (vDist2DSq(pBot->Edict->v.origin, MoveStart) > sqrf(UTIL_MetresToGoldSrcUnits(2.0f)) && vDist2DSq(pBot->Edict->v.origin, MoveEnd) > sqrf(UTIL_MetresToGoldSrcUnits(2.0f))) { return true; }
|
|
|
|
DeployableSearchFilter PGFilter;
|
|
PGFilter.DeployableTeam = pBot->Player->GetTeam();
|
|
PGFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED;
|
|
PGFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING;
|
|
PGFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(2.0f);
|
|
|
|
bool StartPGExists = AITAC_DeployableExistsAtLocation(MoveStart, &PGFilter);
|
|
|
|
if (!StartPGExists) { return true; }
|
|
|
|
bool EndPGExists = AITAC_DeployableExistsAtLocation(MoveEnd, &PGFilter);
|
|
|
|
if (!EndPGExists) { return true; }
|
|
|
|
return false;
|
|
}
|
|
|
|
bool IsBotOffLiftNode(const AvHAIPlayer* pBot, Vector MoveStart, Vector MoveEnd, Vector NextMoveDestination, SamplePolyFlags NextMoveFlag)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool IsBotOffObstacleNode(const AvHAIPlayer* pBot, Vector MoveStart, Vector MoveEnd, Vector NextMoveDestination, SamplePolyFlags NextMoveFlag)
|
|
{
|
|
return IsBotOffJumpNode(pBot, MoveStart, MoveEnd, NextMoveDestination, NextMoveFlag);
|
|
}
|
|
|
|
void BlinkClimbMove(AvHAIPlayer* pBot, const Vector StartPoint, const Vector EndPoint, float RequiredClimbHeight)
|
|
{
|
|
edict_t* pEdict = pBot->Edict;
|
|
|
|
Vector vForward = UTIL_GetVectorNormal2D(EndPoint - StartPoint);
|
|
Vector CheckLine = StartPoint + (vForward * 1000.0f);
|
|
Vector MoveDir = UTIL_GetVectorNormal2D(EndPoint - pBot->Edict->v.origin);
|
|
|
|
Vector PointOnMove = vClosestPointOnLine2D(StartPoint, EndPoint, pEdict->v.origin);
|
|
float DistFromLineSq = vDist2DSq(PointOnMove, pEdict->v.origin);
|
|
|
|
if (vEquals(PointOnMove, StartPoint, 2.0f) && DistFromLineSq > sqrf(8.0f))
|
|
{
|
|
pBot->desiredMovementDir = UTIL_GetVectorNormal2D(StartPoint - pBot->Edict->v.origin);
|
|
return;
|
|
}
|
|
|
|
pBot->desiredMovementDir = MoveDir;
|
|
|
|
// Always duck. It doesn't have any downsides and means we don't have to separately handle vent climbing
|
|
pBot->Button |= IN_DUCK;
|
|
|
|
pBot->DesiredMoveWeapon = WEAPON_FADE_BLINK;
|
|
|
|
// Wait until we have blink equipped before proceeding
|
|
if (GetPlayerCurrentWeapon(pBot->Player) != WEAPON_FADE_BLINK) { return; }
|
|
|
|
// Only blink if we're below the target climb height
|
|
if (pEdict->v.origin.z < RequiredClimbHeight + 32.0f)
|
|
{
|
|
float HeightToClimb = (fabsf(pEdict->v.origin.z - RequiredClimbHeight));
|
|
|
|
Vector CurrVelocity = UTIL_GetVectorNormal2D(pBot->Edict->v.velocity);
|
|
|
|
float Dot = UTIL_GetDotProduct2D(MoveDir, CurrVelocity);
|
|
|
|
Vector FaceDir = UTIL_GetForwardVector2D(pEdict->v.angles);
|
|
|
|
float FaceDot = UTIL_GetDotProduct2D(FaceDir, MoveDir);
|
|
|
|
// Yes this is cheating, but the fades were struggling with zipping off-target when trying to blink
|
|
// Better this than fades getting constantly chewed up by marines because they can't escape properly
|
|
if (FaceDot < 0.95f)
|
|
{
|
|
float MoveSpeed = vSize2D(pBot->Edict->v.velocity);
|
|
Vector NewVelocity = MoveDir * MoveSpeed;
|
|
NewVelocity.z = pBot->Edict->v.velocity.z;
|
|
|
|
pBot->Edict->v.velocity = NewVelocity;
|
|
}
|
|
|
|
float ZDiff = fabs(pEdict->v.origin.z - (RequiredClimbHeight + 72.0f));
|
|
|
|
// We don't want to blast off like a rocket, so only apply enough blink until our upwards velocity is enough to carry us to the desired height
|
|
float DesiredZVelocity = sqrtf(2.0f * GOLDSRC_GRAVITY * (ZDiff + 10.0f));
|
|
|
|
if (pBot->Edict->v.velocity.z < DesiredZVelocity || pBot->Edict->v.velocity.z < 300.0f)
|
|
{
|
|
// We're going to cheat and give the bot the necessary energy to make the move. Better the fade cheats a bit than gets stuck somewhere
|
|
if (GetPlayerEnergy(pBot->Edict) < 0.1f)
|
|
{
|
|
pBot->Player->Energize(0.1f);
|
|
}
|
|
BotMoveLookAt(pBot, EndPoint + Vector(0.0f, 0.0f, 100.0f));
|
|
pBot->Button |= IN_ATTACK2;
|
|
}
|
|
else
|
|
{
|
|
Vector LookAtTarget = EndPoint;
|
|
LookAtTarget.z = pBot->CurrentEyePosition.z;
|
|
BotMoveLookAt(pBot, LookAtTarget);
|
|
}
|
|
}
|
|
}
|
|
|
|
void WallClimbMove(AvHAIPlayer* pBot, const Vector StartPoint, const Vector EndPoint, float RequiredClimbHeight)
|
|
{
|
|
edict_t* pEdict = pBot->Edict;
|
|
|
|
if (UTIL_PointIsDirectlyReachable(pBot->BotNavInfo.NavProfile, pBot->CurrentFloorPosition, EndPoint))
|
|
{
|
|
Vector PointOnMoveLine = vClosestPointOnLine2D(StartPoint, EndPoint, pBot->Edict->v.origin);
|
|
|
|
if (vEquals2D(PointOnMoveLine, EndPoint, 4.0f))
|
|
{
|
|
|
|
// Stop holding crouch if we're a skulk so we can actually climb
|
|
if (IsPlayerSkulk(pBot->Edict))
|
|
{
|
|
pBot->Button &= ~IN_DUCK;
|
|
}
|
|
|
|
pBot->desiredMovementDir = UTIL_GetVectorNormal2D(EndPoint - pBot->CurrentFloorPosition);
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
Vector vForward = UTIL_GetVectorNormal2D(EndPoint - StartPoint);
|
|
Vector vRight = UTIL_GetVectorNormal(UTIL_GetCrossProduct(vForward, UP_VECTOR));
|
|
|
|
pBot->desiredMovementDir = vForward;
|
|
|
|
Vector CheckLine = StartPoint + (vForward * 1000.0f);
|
|
|
|
float DistFromLine = vDistanceFromLine2D(StartPoint, CheckLine, pEdict->v.origin);
|
|
|
|
// Draw an imaginary 2D line between from and to movement, and make sure we're aligned. If we've drifted off to one side, readjust.
|
|
if (DistFromLine > 18.0f)
|
|
{
|
|
float modifier = (float)vPointOnLine(StartPoint, CheckLine, pEdict->v.origin);
|
|
|
|
pBot->desiredMovementDir = UTIL_GetVectorNormal2D(pBot->desiredMovementDir + (vRight * modifier));
|
|
}
|
|
|
|
// Jump if we're on the floor, to give ourselves a boost and remove that momentary pause while "wall-sticking" mode activates if skulk
|
|
if ((pEdict->v.flags & FL_ONGROUND) && !IsPlayerClimbingWall(pBot->Edict))
|
|
{
|
|
Vector CurrentVelocity = UTIL_GetVectorNormal2D(pBot->Edict->v.velocity);
|
|
|
|
float VelocityDot = UTIL_GetDotProduct2D(vForward, CurrentVelocity);
|
|
|
|
if (VelocityDot > 0.7f)
|
|
{
|
|
// This was causing issues in tight areas, rethink this
|
|
//BotJump(pBot);
|
|
}
|
|
}
|
|
|
|
// Stop holding crouch if we're a skulk so we can actually climb
|
|
if (IsPlayerSkulk(pBot->Edict))
|
|
{
|
|
pBot->Button &= ~IN_DUCK;
|
|
}
|
|
|
|
|
|
|
|
float ZDiff = fabs(pEdict->v.origin.z - RequiredClimbHeight);
|
|
Vector AdjustedTargetLocation = EndPoint + (UTIL_GetVectorNormal2D(EndPoint - StartPoint) * 1000.0f);
|
|
Vector DirectAheadView = pBot->CurrentEyePosition + (UTIL_GetVectorNormal2D(AdjustedTargetLocation - pBot->CurrentEyePosition) * 100.0f);
|
|
|
|
Vector ClimbSurfaceNormal = UTIL_GetVectorNormal(EndPoint - StartPoint);
|
|
|
|
Vector LookLocation = g_vecZero;
|
|
|
|
if (ZDiff < 1.0f)
|
|
{
|
|
LookLocation = DirectAheadView;
|
|
}
|
|
else
|
|
{
|
|
// Don't look up/down quite so much as we reach the desired height so we slow down a bit, reduces the chance of over-shooting and climbing right over a vent
|
|
if (pEdict->v.origin.z > RequiredClimbHeight)
|
|
{
|
|
if (ZDiff > 32.0f)
|
|
{
|
|
ClimbSurfaceNormal = ClimbSurfaceNormal - (2.0f * (UTIL_GetDotProduct(ClimbSurfaceNormal, UP_VECTOR) * ClimbSurfaceNormal));
|
|
LookLocation = pBot->CurrentEyePosition + (ClimbSurfaceNormal * 100.0f);
|
|
}
|
|
else
|
|
{
|
|
LookLocation = DirectAheadView - Vector(0.0f, 0.0f, 20.0f);
|
|
//LookLocation = pBot->CurrentEyePosition + (ClimbSurfaceNormal * 100.0f);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (ZDiff > 16.0f)
|
|
{
|
|
ClimbSurfaceNormal = ClimbSurfaceNormal;
|
|
LookLocation = pBot->CurrentEyePosition + (ClimbSurfaceNormal * 100.0f);
|
|
}
|
|
else
|
|
{
|
|
LookLocation = DirectAheadView + Vector(0.0f, 0.0f, 20.0f);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (IsPlayerClimbingWall(pBot->Edict))
|
|
{
|
|
Vector RightDir = UTIL_GetCrossProduct(vForward, UP_VECTOR);
|
|
|
|
Vector LeftCheckStart = pBot->Edict->v.origin - (RightDir * (GetPlayerRadius(pBot->Player) + 2.0f));
|
|
Vector LeftCheckEnd = LeftCheckStart + Vector(0.0f, 0.0f, 50.0f);
|
|
|
|
Vector RightCheckStart = pBot->Edict->v.origin + (RightDir * (GetPlayerRadius(pBot->Player) + 2.0f));
|
|
Vector RightCheckEnd = RightCheckStart + Vector(0.0f, 0.0f, 50.0f);
|
|
|
|
if (!UTIL_QuickTrace(pBot->Edict, LeftCheckStart, LeftCheckEnd))
|
|
{
|
|
if (UTIL_QuickTrace(pBot->Edict, RightCheckStart, RightCheckEnd))
|
|
{
|
|
pBot->desiredMovementDir = UTIL_GetVectorNormal2D(vForward + RightDir);
|
|
}
|
|
}
|
|
else if (!UTIL_QuickTrace(pBot->Edict, RightCheckStart, RightCheckEnd))
|
|
{
|
|
pBot->desiredMovementDir = UTIL_GetVectorNormal2D(vForward - RightDir);
|
|
}
|
|
}
|
|
|
|
BotMoveLookAt(pBot, LookLocation);
|
|
|
|
}
|
|
|
|
void MoveToWithoutNav(AvHAIPlayer* pBot, const Vector Destination)
|
|
{
|
|
Vector CurrentPos = (pBot->BotNavInfo.IsOnGround) ? pBot->Edict->v.origin : pBot->CurrentFloorPosition;
|
|
|
|
const Vector vForward = UTIL_GetVectorNormal2D(Destination - CurrentPos);
|
|
// Same goes for the right vector, might not be the same as the bot's right
|
|
const Vector vRight = UTIL_GetVectorNormal2D(UTIL_GetCrossProduct(vForward, UP_VECTOR));
|
|
|
|
const float PlayerRadius = GetPlayerRadius(pBot->Player);
|
|
|
|
Vector stTrcLft = pBot->Edict->v.origin - (vRight * PlayerRadius);
|
|
Vector stTrcRt = pBot->Edict->v.origin + (vRight * PlayerRadius);
|
|
stTrcLft.z += 2.0f;
|
|
stTrcRt.z += 2.0f;
|
|
|
|
Vector endTrcLft = stTrcLft + (vForward * (PlayerRadius * 2.0f));
|
|
Vector endTrcRt = stTrcRt + (vForward * (PlayerRadius * 2.0f));
|
|
|
|
bool bumpLeft = !UTIL_QuickHullTrace(pBot->Edict, stTrcLft, endTrcLft, head_hull);
|
|
bool bumpRight = !UTIL_QuickHullTrace(pBot->Edict, stTrcRt, endTrcRt, head_hull);
|
|
|
|
pBot->desiredMovementDir = vForward;
|
|
|
|
if (bumpRight && !bumpLeft)
|
|
{
|
|
pBot->desiredMovementDir = pBot->desiredMovementDir - vRight;
|
|
}
|
|
else if (bumpLeft && !bumpRight)
|
|
{
|
|
pBot->desiredMovementDir = pBot->desiredMovementDir + vRight;
|
|
}
|
|
else if (bumpLeft && bumpRight)
|
|
{
|
|
endTrcLft = endTrcLft - (vRight * PlayerRadius);
|
|
endTrcRt = endTrcRt + (vRight * PlayerRadius);
|
|
|
|
if (!UTIL_QuickTrace(pBot->Edict, stTrcLft, endTrcLft))
|
|
{
|
|
pBot->desiredMovementDir = pBot->desiredMovementDir + vRight;
|
|
}
|
|
else
|
|
{
|
|
pBot->desiredMovementDir = pBot->desiredMovementDir - vRight;
|
|
}
|
|
}
|
|
|
|
float DistFromDestination = vDist2DSq(pBot->Edict->v.origin, Destination);
|
|
|
|
if (vIsZero(pBot->LookTargetLocation))
|
|
{
|
|
Vector LookTarget = Destination;
|
|
|
|
if (DistFromDestination < sqrf(200.0f))
|
|
{
|
|
Vector LookNormal = UTIL_GetVectorNormal2D(LookTarget - pBot->CurrentEyePosition);
|
|
|
|
LookTarget = LookTarget + (LookNormal * 1000.0f);
|
|
}
|
|
|
|
BotLookAt(pBot, LookTarget);
|
|
}
|
|
|
|
HandlePlayerAvoidance(pBot, Destination);
|
|
BotMovementInputs(pBot);
|
|
}
|
|
|
|
void MoveDirectlyTo(AvHAIPlayer* pBot, const Vector Destination)
|
|
{
|
|
pBot->BotNavInfo.StuckInfo.bPathFollowFailed = false;
|
|
|
|
if (vIsZero(Destination)) { return; }
|
|
|
|
Vector CurrentPos = (pBot->BotNavInfo.IsOnGround) ? pBot->Edict->v.origin : pBot->CurrentFloorPosition;
|
|
|
|
const Vector vForward = UTIL_GetVectorNormal2D(Destination - CurrentPos);
|
|
// Same goes for the right vector, might not be the same as the bot's right
|
|
const Vector vRight = UTIL_GetVectorNormal2D(UTIL_GetCrossProduct(vForward, UP_VECTOR));
|
|
|
|
const float PlayerRadius = GetPlayerRadius(pBot->Player);
|
|
|
|
Vector stTrcLft = CurrentPos - (vRight * PlayerRadius);
|
|
Vector stTrcRt = CurrentPos + (vRight * PlayerRadius);
|
|
Vector endTrcLft = stTrcLft + (vForward * 24.0f);
|
|
Vector endTrcRt = stTrcRt + (vForward * 24.0f);
|
|
|
|
const bool bumpLeft = !UTIL_PointIsDirectlyReachable(pBot->BotNavInfo.NavProfile, stTrcLft, endTrcLft);
|
|
const bool bumpRight = !UTIL_PointIsDirectlyReachable(pBot->BotNavInfo.NavProfile, stTrcRt, endTrcRt);
|
|
|
|
pBot->desiredMovementDir = vForward;
|
|
|
|
if (bumpRight && !bumpLeft)
|
|
{
|
|
pBot->desiredMovementDir = pBot->desiredMovementDir - vRight;
|
|
}
|
|
else if (bumpLeft && !bumpRight)
|
|
{
|
|
pBot->desiredMovementDir = pBot->desiredMovementDir + vRight;
|
|
}
|
|
else if (bumpLeft && bumpRight)
|
|
{
|
|
stTrcLft.z = pBot->Edict->v.origin.z;
|
|
stTrcRt.z = pBot->Edict->v.origin.z;
|
|
endTrcLft.z = pBot->Edict->v.origin.z;
|
|
endTrcRt.z = pBot->Edict->v.origin.z;
|
|
|
|
if (!UTIL_QuickTrace(pBot->Edict, stTrcLft, endTrcLft))
|
|
{
|
|
pBot->desiredMovementDir = pBot->desiredMovementDir + vRight;
|
|
}
|
|
else
|
|
{
|
|
pBot->desiredMovementDir = pBot->desiredMovementDir - vRight;
|
|
}
|
|
}
|
|
|
|
float DistFromDestination = vDist2DSq(pBot->Edict->v.origin, Destination);
|
|
|
|
if (vIsZero(pBot->LookTargetLocation))
|
|
{
|
|
Vector LookTarget = Destination;
|
|
|
|
if (DistFromDestination < sqrf(200.0f))
|
|
{
|
|
Vector LookNormal = UTIL_GetVectorNormal2D(LookTarget - pBot->CurrentEyePosition);
|
|
|
|
LookTarget = LookTarget + (LookNormal * 1000.0f);
|
|
}
|
|
|
|
BotLookAt(pBot, LookTarget);
|
|
}
|
|
|
|
HandlePlayerAvoidance(pBot, Destination);
|
|
BotMovementInputs(pBot);
|
|
|
|
}
|
|
|
|
|
|
bool UTIL_PointIsDirectlyReachable(const AvHAIPlayer* pBot, const Vector targetPoint)
|
|
{
|
|
const dtNavMeshQuery* m_navQuery = UTIL_GetNavMeshQueryForProfile(pBot->BotNavInfo.NavProfile);
|
|
const dtNavMesh* m_navMesh = UTIL_GetNavMeshForProfile(pBot->BotNavInfo.NavProfile);
|
|
const dtQueryFilter* m_navFilter = &pBot->BotNavInfo.NavProfile.Filters;
|
|
|
|
if (!m_navQuery) { return false; }
|
|
|
|
edict_t* pEdict = pBot->Edict;
|
|
|
|
Vector CurrentPos = (pBot->BotNavInfo.IsOnGround) ? pBot->Edict->v.origin : pBot->CurrentFloorPosition;
|
|
|
|
float pStartPos[3] = { CurrentPos.x, CurrentPos.z, -CurrentPos.y };
|
|
float pEndPos[3] = { targetPoint.x, targetPoint.z, -targetPoint.y };
|
|
|
|
dtPolyRef StartPoly;
|
|
dtPolyRef EndPoly;
|
|
float StartNearest[3];
|
|
float EndNearest[3];
|
|
|
|
float hitDist;
|
|
float HitNormal[3];
|
|
|
|
dtPolyRef PolyPath[MAX_PATH_POLY];
|
|
int pathCount = 0;
|
|
|
|
|
|
dtStatus FoundStartPoly = m_navQuery->findNearestPoly(pStartPos, pReachableExtents, m_navFilter, &StartPoly, StartNearest);
|
|
|
|
if (!dtStatusSucceed(FoundStartPoly))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
dtStatus FoundEndPoly = m_navQuery->findNearestPoly(pEndPos, pReachableExtents, m_navFilter, &EndPoly, EndNearest);
|
|
|
|
if (!dtStatusSucceed(FoundEndPoly))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// All polys are convex, therefore definitely reachable if start and end points are within the same poly
|
|
if (StartPoly == EndPoly) { return true; }
|
|
|
|
m_navQuery->raycast(StartPoly, StartNearest, EndNearest, m_navFilter, &hitDist, HitNormal, PolyPath, &pathCount, MAX_AI_PATH_SIZE);
|
|
|
|
if (hitDist < 1.0f) { return false; }
|
|
|
|
if (EndPoly == PolyPath[pathCount - 1]) { return true; }
|
|
|
|
float ClosestPoint[3] = { 0.0f, 0.0f, 0.0f };
|
|
float Height = 0.0f;
|
|
m_navQuery->closestPointOnPolyBoundary(PolyPath[pathCount - 1], EndNearest, ClosestPoint);
|
|
m_navQuery->getPolyHeight(PolyPath[pathCount - 1], ClosestPoint, &Height);
|
|
|
|
|
|
return (Height == 0.0f || Height == EndNearest[1]);
|
|
|
|
}
|
|
|
|
bool UTIL_PointIsDirectlyReachable(const AvHAIPlayer* pBot, const Vector start, const Vector target)
|
|
{
|
|
const dtNavMeshQuery* m_navQuery = UTIL_GetNavMeshQueryForProfile(pBot->BotNavInfo.NavProfile);
|
|
const dtNavMesh* m_navMesh = UTIL_GetNavMeshForProfile(pBot->BotNavInfo.NavProfile);
|
|
const dtQueryFilter* m_navFilter = &pBot->BotNavInfo.NavProfile.Filters;
|
|
|
|
if (!m_navQuery) { return false; }
|
|
|
|
if (vIsZero(start) || vIsZero(target)) { return false; }
|
|
|
|
float pStartPos[3] = { start.x, start.z, -start.y };
|
|
float pEndPos[3] = { target.x, target.z, -target.y };
|
|
|
|
dtPolyRef StartPoly;
|
|
dtPolyRef EndPoly;
|
|
float StartNearest[3];
|
|
float EndNearest[3];
|
|
|
|
float hitDist;
|
|
float HitNormal[3];
|
|
|
|
dtPolyRef PolyPath[MAX_PATH_POLY];
|
|
int pathCount = 0;
|
|
|
|
dtStatus FoundStartPoly = m_navQuery->findNearestPoly(pStartPos, pReachableExtents, m_navFilter, &StartPoly, StartNearest);
|
|
|
|
if (!dtStatusSucceed(FoundStartPoly))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
dtStatus FoundEndPoly = m_navQuery->findNearestPoly(pEndPos, pReachableExtents, m_navFilter, &EndPoly, EndNearest);
|
|
|
|
if (!dtStatusSucceed(FoundEndPoly))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// All polys are convex, therefore definitely reachable if start and end points are within the same poly
|
|
if (StartPoly == EndPoly) { return true; }
|
|
|
|
m_navQuery->raycast(StartPoly, StartNearest, EndNearest, m_navFilter, &hitDist, HitNormal, PolyPath, &pathCount, MAX_AI_PATH_SIZE);
|
|
|
|
if (hitDist < 1.0f) { return false; }
|
|
|
|
if (EndPoly == PolyPath[pathCount - 1]) { return true; }
|
|
|
|
float ClosestPoint[3] = { 0.0f, 0.0f, 0.0f };
|
|
float Height = 0.0f;
|
|
m_navQuery->closestPointOnPolyBoundary(PolyPath[pathCount - 1], EndNearest, ClosestPoint);
|
|
m_navQuery->getPolyHeight(PolyPath[pathCount - 1], ClosestPoint, &Height);
|
|
|
|
|
|
return (Height == 0.0f || Height == EndNearest[1]);
|
|
|
|
}
|
|
|
|
const dtNavMesh* UTIL_GetNavMeshForProfile(const nav_profile& NavProfile)
|
|
{
|
|
if (NavProfile.NavMeshIndex < 0 || NavProfile.NavMeshIndex >= MAX_NAV_MESHES) { return nullptr; }
|
|
|
|
return NavMeshes[NavProfile.NavMeshIndex].navMesh;
|
|
}
|
|
|
|
const dtNavMeshQuery* UTIL_GetNavMeshQueryForProfile(const nav_profile& NavProfile)
|
|
{
|
|
if (NavProfile.NavMeshIndex < 0 || NavProfile.NavMeshIndex >= MAX_NAV_MESHES) { return nullptr; }
|
|
|
|
return NavMeshes[NavProfile.NavMeshIndex].navQuery;
|
|
}
|
|
|
|
const dtTileCache* UTIL_GetTileCacheForProfile(const nav_profile& NavProfile)
|
|
{
|
|
if (NavProfile.NavMeshIndex < 0 || NavProfile.NavMeshIndex >= MAX_NAV_MESHES) { return nullptr; }
|
|
|
|
return NavMeshes[NavProfile.NavMeshIndex].tileCache;
|
|
}
|
|
|
|
bool UTIL_PointIsDirectlyReachable(const nav_profile &NavProfile, const Vector start, const Vector target)
|
|
{
|
|
const dtNavMesh* m_navMesh = UTIL_GetNavMeshForProfile(NavProfile);
|
|
const dtNavMeshQuery* m_navQuery = UTIL_GetNavMeshQueryForProfile(NavProfile);
|
|
const dtQueryFilter* m_navFilter = &NavProfile.Filters;
|
|
|
|
if (!m_navMesh) { return false; }
|
|
|
|
float pStartPos[3] = { start.x, start.z, -start.y };
|
|
float pEndPos[3] = { target.x, target.z, -target.y };
|
|
|
|
dtPolyRef StartPoly;
|
|
dtPolyRef EndPoly;
|
|
float StartNearest[3] = { 0.0f, 0.0f, 0.0f };
|
|
float EndNearest[3] = { 0.0f, 0.0f, 0.0f };
|
|
|
|
float hitDist;
|
|
float HitNormal[3];
|
|
|
|
dtPolyRef PolyPath[MAX_PATH_POLY];
|
|
int pathCount = 0;
|
|
|
|
|
|
dtStatus FoundStartPoly = m_navQuery->findNearestPoly(pStartPos, pReachableExtents, m_navFilter, &StartPoly, StartNearest);
|
|
|
|
if (!dtStatusSucceed(FoundStartPoly))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
dtStatus FoundEndPoly = m_navQuery->findNearestPoly(pEndPos, pReachableExtents, m_navFilter, &EndPoly, EndNearest);
|
|
|
|
if (!dtStatusSucceed(FoundEndPoly))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// All polys are convex, therefore definitely reachable if start and end points are within the same poly
|
|
if (StartPoly == EndPoly) { return true; }
|
|
|
|
m_navQuery->raycast(StartPoly, StartNearest, EndNearest, m_navFilter, &hitDist, HitNormal, PolyPath, &pathCount, MAX_AI_PATH_SIZE);
|
|
|
|
if (hitDist < 1.0f)
|
|
{
|
|
if (pathCount == 0) { return false; }
|
|
|
|
float epos[3];
|
|
dtVcopy(epos, EndNearest);
|
|
|
|
m_navQuery->closestPointOnPoly(PolyPath[pathCount - 1], EndNearest, epos, 0);
|
|
|
|
if (dtVdistSqr(EndNearest, epos) > sqrf(max_ai_use_reach))
|
|
{
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (EndPoly == PolyPath[pathCount - 1]) { return true; }
|
|
|
|
float ClosestPoint[3] = { 0.0f, 0.0f, 0.0f };
|
|
float Height = 0.0f;
|
|
m_navQuery->closestPointOnPolyBoundary(PolyPath[pathCount - 1], EndNearest, ClosestPoint);
|
|
m_navQuery->getPolyHeight(PolyPath[pathCount - 1], ClosestPoint, &Height);
|
|
|
|
|
|
return (Height == 0.0f || Height == EndNearest[1]);
|
|
}
|
|
|
|
bool UTIL_TraceNav(const nav_profile &NavProfile, const Vector start, const Vector target, const float MaxAcceptableDistance)
|
|
{
|
|
const dtNavMeshQuery* m_navQuery = UTIL_GetNavMeshQueryForProfile(NavProfile);
|
|
const dtNavMesh* m_navMesh = UTIL_GetNavMeshForProfile(NavProfile);
|
|
const dtQueryFilter* m_Filter = &NavProfile.Filters;
|
|
|
|
if (!m_navQuery) { return false; }
|
|
|
|
float pStartPos[3] = { start.x, start.z, -start.y };
|
|
float pEndPos[3] = { target.x, target.z, -target.y };
|
|
|
|
dtPolyRef StartPoly;
|
|
dtPolyRef EndPoly;
|
|
float StartNearest[3] = { 0.0f, 0.0f, 0.0f };
|
|
float EndNearest[3] = { 0.0f, 0.0f, 0.0f };
|
|
|
|
float hitDist;
|
|
float HitNormal[3];
|
|
|
|
dtPolyRef PolyPath[MAX_PATH_POLY];
|
|
int pathCount = 0;
|
|
|
|
float MaxReachableExtents[3] = { MaxAcceptableDistance, 50.0f, MaxAcceptableDistance };
|
|
|
|
dtStatus FoundStartPoly = m_navQuery->findNearestPoly(pStartPos, MaxReachableExtents, m_Filter, &StartPoly, StartNearest);
|
|
|
|
if (!dtStatusSucceed(FoundStartPoly))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
dtStatus FoundEndPoly = m_navQuery->findNearestPoly(pEndPos, MaxReachableExtents, m_Filter, &EndPoly, EndNearest);
|
|
|
|
if (!dtStatusSucceed(FoundEndPoly))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// All polys are convex, therefore definitely reachable if start and end points are within the same poly
|
|
if (StartPoly == EndPoly) { return true; }
|
|
|
|
m_navQuery->raycast(StartPoly, StartNearest, EndNearest, m_Filter, &hitDist, HitNormal, PolyPath, &pathCount, MAX_AI_PATH_SIZE);
|
|
|
|
if (hitDist < 1.0f)
|
|
{
|
|
if (pathCount == 0) { return false; }
|
|
|
|
float epos[3];
|
|
dtVcopy(epos, EndNearest);
|
|
|
|
m_navQuery->closestPointOnPoly(PolyPath[pathCount - 1], EndNearest, epos, 0);
|
|
|
|
if (dtVdistSqr(EndNearest, epos) > sqrf(MaxAcceptableDistance))
|
|
{
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (EndPoly == PolyPath[pathCount - 1]) { return true; }
|
|
|
|
float ClosestPoint[3] = { 0.0f, 0.0f, 0.0f };
|
|
float Height = 0.0f;
|
|
m_navQuery->closestPointOnPolyBoundary(PolyPath[pathCount - 1], EndNearest, ClosestPoint);
|
|
m_navQuery->getPolyHeight(PolyPath[pathCount - 1], ClosestPoint, &Height);
|
|
|
|
|
|
return (Height == 0.0f || Height == EndNearest[1]);
|
|
}
|
|
|
|
void UTIL_TraceNavLine(const nav_profile &NavProfile, const Vector Start, const Vector End, nav_hitresult* HitResult)
|
|
{
|
|
const dtNavMeshQuery* m_navQuery = UTIL_GetNavMeshQueryForProfile(NavProfile);
|
|
const dtNavMesh* m_navMesh = UTIL_GetNavMeshForProfile(NavProfile);
|
|
const dtQueryFilter* m_Filter = &NavProfile.Filters;
|
|
|
|
if (!m_navQuery)
|
|
{
|
|
HitResult->flFraction = 0.0f;
|
|
HitResult->bStartOffMesh = true;
|
|
HitResult->TraceEndPoint = Start;
|
|
return;
|
|
}
|
|
|
|
float pStartPos[3] = { Start.x, Start.z, -Start.y };
|
|
float pEndPos[3] = { End.x, End.z, -End.y };
|
|
|
|
dtPolyRef StartPoly;
|
|
dtPolyRef EndPoly;
|
|
float StartNearest[3] = { 0.0f, 0.0f, 0.0f };
|
|
float EndNearest[3] = { 0.0f, 0.0f, 0.0f };
|
|
|
|
float hitDist;
|
|
float HitNormal[3];
|
|
|
|
dtPolyRef PolyPath[MAX_PATH_POLY];
|
|
int pathCount = 0;
|
|
|
|
float MaxReachableExtents[3] = { 18.0f, 32.0f, 18.0f };
|
|
|
|
dtStatus FoundStartPoly = m_navQuery->findNearestPoly(pStartPos, MaxReachableExtents, m_Filter, &StartPoly, StartNearest);
|
|
|
|
if (!dtStatusSucceed(FoundStartPoly))
|
|
{
|
|
HitResult->flFraction = 0.0f;
|
|
HitResult->bStartOffMesh = true;
|
|
HitResult->TraceEndPoint = Start;
|
|
return;
|
|
}
|
|
|
|
dtStatus FoundEndPoly = m_navQuery->findNearestPoly(pEndPos, MaxReachableExtents, m_Filter, &EndPoly, EndNearest);
|
|
|
|
if (!dtStatusSucceed(FoundEndPoly))
|
|
{
|
|
HitResult->flFraction = 0.0f;
|
|
HitResult->bStartOffMesh = true;
|
|
HitResult->TraceEndPoint = Start;
|
|
return;
|
|
}
|
|
|
|
// All polys are convex, therefore definitely reachable if start and end points are within the same poly
|
|
if (StartPoly == EndPoly)
|
|
{
|
|
|
|
HitResult->flFraction = 1.0f;
|
|
HitResult->bStartOffMesh = false;
|
|
HitResult->TraceEndPoint = Vector(EndNearest[0], -EndNearest[2], EndNearest[1]);
|
|
return;
|
|
}
|
|
|
|
|
|
|
|
m_navQuery->raycast(StartPoly, StartNearest, EndNearest, m_Filter, &hitDist, HitNormal, PolyPath, &pathCount, MAX_AI_PATH_SIZE);
|
|
|
|
HitResult->flFraction = hitDist;
|
|
HitResult->bStartOffMesh = false;
|
|
|
|
Vector HitLocation = g_vecZero;
|
|
|
|
if (hitDist >= 1.0f)
|
|
{
|
|
HitLocation = Vector(EndNearest[0], -EndNearest[2], EndNearest[1]);
|
|
}
|
|
else
|
|
{
|
|
Vector Dir = UTIL_GetVectorNormal(End - Start);
|
|
Vector Point = Start + (Dir * HitResult->flFraction);
|
|
|
|
HitLocation = UTIL_ProjectPointToNavmesh(Point, Vector(100.0f, 100.0f, 100.0f), NavProfile);
|
|
}
|
|
|
|
HitResult->TraceEndPoint = HitLocation;
|
|
}
|
|
|
|
bool UTIL_PointIsDirectlyReachable(const Vector start, const Vector target)
|
|
{
|
|
const dtNavMeshQuery* m_navQuery = UTIL_GetNavMeshQueryForProfile(BaseNavProfiles[ALL_NAV_PROFILE]);
|
|
const dtNavMesh* m_navMesh = UTIL_GetNavMeshForProfile(BaseNavProfiles[ALL_NAV_PROFILE]);
|
|
const dtQueryFilter* m_Filter = &BaseNavProfiles[ALL_NAV_PROFILE].Filters;
|
|
|
|
if (!m_navQuery) { return false; }
|
|
|
|
float pStartPos[3] = { start.x, start.z, -start.y };
|
|
float pEndPos[3] = { target.x, target.z, -target.y };
|
|
|
|
dtPolyRef StartPoly;
|
|
dtPolyRef EndPoly;
|
|
float StartNearest[3] = { 0.0f, 0.0f, 0.0f };
|
|
float EndNearest[3] = { 0.0f, 0.0f, 0.0f };
|
|
|
|
float hitDist;
|
|
float HitNormal[3];
|
|
|
|
dtPolyRef PolyPath[MAX_PATH_POLY];
|
|
int pathCount = 0;
|
|
|
|
|
|
dtStatus FoundStartPoly = m_navQuery->findNearestPoly(pStartPos, pReachableExtents, m_Filter, &StartPoly, StartNearest);
|
|
|
|
if (!dtStatusSucceed(FoundStartPoly))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
dtStatus FoundEndPoly = m_navQuery->findNearestPoly(pEndPos, pReachableExtents, m_Filter, &EndPoly, EndNearest);
|
|
|
|
if (!dtStatusSucceed(FoundEndPoly))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// All polys are convex, therefore definitely reachable if start and end points are within the same poly
|
|
if (StartPoly == EndPoly) { return true; }
|
|
|
|
m_navQuery->raycast(StartPoly, StartNearest, EndNearest, m_Filter, &hitDist, HitNormal, PolyPath, &pathCount, MAX_AI_PATH_SIZE);
|
|
|
|
if (hitDist < 1.0f)
|
|
{
|
|
if (pathCount == 0) { return false; }
|
|
|
|
float epos[3];
|
|
dtVcopy(epos, EndNearest);
|
|
|
|
m_navQuery->closestPointOnPoly(PolyPath[pathCount - 1], EndNearest, epos, 0);
|
|
|
|
if (dtVdistSqr(EndNearest, epos) > sqrf(max_ai_use_reach))
|
|
{
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (EndPoly == PolyPath[pathCount - 1]) { return true; }
|
|
|
|
float ClosestPoint[3] = { 0.0f, 0.0f, 0.0f };
|
|
float Height = 0.0f;
|
|
m_navQuery->closestPointOnPolyBoundary(PolyPath[pathCount - 1], EndNearest, ClosestPoint);
|
|
m_navQuery->getPolyHeight(PolyPath[pathCount - 1], ClosestPoint, &Height);
|
|
|
|
|
|
return (Height == 0.0f || Height == EndNearest[1]);
|
|
}
|
|
|
|
float UTIL_PointIsDirectlyReachable_DEBUG(const Vector start, const Vector target)
|
|
{
|
|
const dtNavMeshQuery* m_navQuery = UTIL_GetNavMeshQueryForProfile(BaseNavProfiles[ALL_NAV_PROFILE]);
|
|
const dtNavMesh* m_navMesh = UTIL_GetNavMeshForProfile(BaseNavProfiles[ALL_NAV_PROFILE]);
|
|
const dtQueryFilter* m_Filter = &BaseNavProfiles[ALL_NAV_PROFILE].Filters;
|
|
|
|
if (!m_navQuery) { return 0.0f; }
|
|
|
|
float pStartPos[3] = { start.x, start.z, -start.y };
|
|
float pEndPos[3] = { target.x, target.z, -target.y };
|
|
|
|
dtPolyRef StartPoly;
|
|
dtPolyRef EndPoly;
|
|
float StartNearest[3];
|
|
float EndNearest[3];
|
|
|
|
float hitDist;
|
|
float HitNormal[3];
|
|
|
|
dtPolyRef PolyPath[MAX_PATH_POLY];
|
|
int pathCount = 0;
|
|
|
|
|
|
dtStatus FoundStartPoly = m_navQuery->findNearestPoly(pStartPos, pReachableExtents, m_Filter, &StartPoly, StartNearest);
|
|
|
|
if (!dtStatusSucceed(FoundStartPoly))
|
|
{
|
|
return 1.1f;
|
|
}
|
|
|
|
dtStatus FoundEndPoly = m_navQuery->findNearestPoly(pEndPos, pReachableExtents, m_Filter, &EndPoly, EndNearest);
|
|
|
|
if (!dtStatusSucceed(FoundEndPoly))
|
|
{
|
|
return 1.2f;
|
|
}
|
|
|
|
// All polys are convex, therefore definitely reachable if start and end points are within the same poly
|
|
if (StartPoly == EndPoly) { return 2.1f; }
|
|
|
|
m_navQuery->raycast(StartPoly, StartNearest, EndNearest, m_Filter, &hitDist, HitNormal, PolyPath, &pathCount, MAX_AI_PATH_SIZE);
|
|
|
|
float ClosestPoint[3] = { 0.0f, 0.0f, 0.0f };
|
|
float Height = 0.0f;
|
|
m_navQuery->closestPointOnPolyBoundary(PolyPath[pathCount - 1], EndNearest, ClosestPoint);
|
|
m_navQuery->getPolyHeight(PolyPath[pathCount - 1], ClosestPoint, &Height);
|
|
|
|
float Dist = dtVdistSqr(EndNearest, ClosestPoint);
|
|
|
|
if (hitDist < 1.0f)
|
|
{
|
|
if (pathCount == 0) { return 1.3f; }
|
|
|
|
float epos[3];
|
|
dtVcopy(epos, EndNearest);
|
|
|
|
m_navQuery->closestPointOnPoly(PolyPath[pathCount - 1], EndNearest, epos, 0);
|
|
|
|
if (dtVdistSqr(EndNearest, epos) > sqrf(max_ai_use_reach))
|
|
{
|
|
return 1.5f;
|
|
}
|
|
else
|
|
{
|
|
return 2.4f;
|
|
}
|
|
|
|
|
|
return 1.3f;
|
|
}
|
|
|
|
if (EndPoly != PolyPath[pathCount - 1])
|
|
{
|
|
if (Height == 0.0f || Height == EndNearest[1])
|
|
{
|
|
return 2.3f;
|
|
}
|
|
return 1.4f;
|
|
}
|
|
|
|
return 2.2f;
|
|
}
|
|
|
|
dtPolyRef UTIL_GetNearestPolyRefForLocation(const nav_profile& NavProfile, const Vector Location)
|
|
{
|
|
const dtNavMeshQuery* m_navQuery = UTIL_GetNavMeshQueryForProfile(NavProfile);
|
|
const dtNavMesh* m_navMesh = UTIL_GetNavMeshForProfile(NavProfile);
|
|
const dtQueryFilter* m_navFilter = &NavProfile.Filters;
|
|
|
|
if (!m_navQuery) { return 0; }
|
|
|
|
float ConvertedFloorCoords[3] = { Location.x, Location.z, -Location.y };
|
|
|
|
float pPolySearchExtents[3] = { 50.0f, 50.0f, 50.0f };
|
|
|
|
dtPolyRef result;
|
|
float nearestPoint[3] = { 0.0f, 0.0f, 0.0f };
|
|
|
|
m_navQuery->findNearestPoly(ConvertedFloorCoords, pPolySearchExtents, m_navFilter, &result, nearestPoint);
|
|
|
|
return result;
|
|
}
|
|
|
|
dtPolyRef UTIL_GetNearestPolyRefForLocation(const Vector Location)
|
|
{
|
|
const dtNavMeshQuery* m_navQuery = UTIL_GetNavMeshQueryForProfile(BaseNavProfiles[ALL_NAV_PROFILE]);
|
|
const dtNavMesh* m_navMesh = UTIL_GetNavMeshForProfile(BaseNavProfiles[ALL_NAV_PROFILE]);
|
|
const dtQueryFilter* m_navFilter = &BaseNavProfiles[ALL_NAV_PROFILE].Filters;
|
|
|
|
if (!m_navQuery) { return 0; }
|
|
|
|
float ConvertedFloorCoords[3] = { Location.x, Location.z, -Location.y };
|
|
|
|
float pPolySearchExtents[3] = { 50.0f, 50.0f, 50.0f };
|
|
|
|
dtPolyRef result;
|
|
float nearestPoint[3] = { 0.0f, 0.0f, 0.0f };
|
|
|
|
m_navQuery->findNearestPoly(ConvertedFloorCoords, pPolySearchExtents, m_navFilter, &result, nearestPoint);
|
|
|
|
return result;
|
|
}
|
|
|
|
dtPolyRef UTIL_GetNearestPolyRefForEntity(const edict_t* Edict)
|
|
{
|
|
const dtNavMeshQuery* m_navQuery = UTIL_GetNavMeshQueryForProfile(BaseNavProfiles[ALL_NAV_PROFILE]);
|
|
const dtNavMesh* m_navMesh = UTIL_GetNavMeshForProfile(BaseNavProfiles[ALL_NAV_PROFILE]);
|
|
const dtQueryFilter* m_navFilter = &BaseNavProfiles[ALL_NAV_PROFILE].Filters;
|
|
|
|
if (!m_navQuery) { return 0; }
|
|
|
|
Vector Floor = UTIL_GetFloorUnderEntity(Edict);
|
|
|
|
float ConvertedFloorCoords[3] = { Floor.x, Floor.z, -Floor.y };
|
|
|
|
float pPolySearchExtents[3] = { 50.0f, 50.0f, 50.0f };
|
|
|
|
dtPolyRef result;
|
|
float nearestPoint[3] = { 0.0f, 0.0f, 0.0f };
|
|
|
|
m_navQuery->findNearestPoly(ConvertedFloorCoords, pPolySearchExtents, m_navFilter, &result, nearestPoint);
|
|
|
|
return result;
|
|
}
|
|
|
|
unsigned char UTIL_GetNavAreaAtLocation(const nav_profile &NavProfile, const Vector Location)
|
|
{
|
|
const dtNavMeshQuery* m_navQuery = UTIL_GetNavMeshQueryForProfile(NavProfile);
|
|
const dtNavMesh* m_navMesh = UTIL_GetNavMeshForProfile(NavProfile);
|
|
const dtQueryFilter* m_navFilter = &NavProfile.Filters;
|
|
|
|
if (!m_navQuery) { return (unsigned char)SAMPLE_POLYAREA_BLOCKED; }
|
|
|
|
Vector TraceHit = UTIL_GetTraceHitLocation(Location + Vector(0.0f, 0.0f, 10.0f), Location - Vector(0.0f, 0.0f, 500.0f));
|
|
|
|
Vector PointToProject = (TraceHit != g_vecZero) ? TraceHit : Location;
|
|
|
|
float pCheckLoc[3] = { PointToProject.x, PointToProject.z, -PointToProject.y };
|
|
|
|
dtPolyRef FoundPoly;
|
|
float NavNearest[3];
|
|
|
|
dtStatus success = m_navQuery->findNearestPoly(pCheckLoc, pReachableExtents, m_navFilter, &FoundPoly, NavNearest);
|
|
|
|
if (dtStatusSucceed(success))
|
|
{
|
|
unsigned char area = 0;
|
|
m_navMesh->getPolyArea(FoundPoly, &area);
|
|
return area;
|
|
}
|
|
else
|
|
{
|
|
return (unsigned char)SAMPLE_POLYAREA_BLOCKED;
|
|
}
|
|
}
|
|
|
|
unsigned char UTIL_GetNavAreaAtLocation(const Vector Location)
|
|
{
|
|
const dtNavMeshQuery* m_navQuery = UTIL_GetNavMeshQueryForProfile(BaseNavProfiles[ALL_NAV_PROFILE]);
|
|
const dtNavMesh* m_navMesh = UTIL_GetNavMeshForProfile(BaseNavProfiles[ALL_NAV_PROFILE]);
|
|
const dtQueryFilter* m_navFilter = &BaseNavProfiles[ALL_NAV_PROFILE].Filters;
|
|
|
|
if (!m_navQuery) { return 0; }
|
|
|
|
Vector TraceHit = UTIL_GetTraceHitLocation(Location + Vector(0.0f, 0.0f, 10.0f), Location - Vector(0.0f, 0.0f, 500.0f));
|
|
|
|
Vector PointToProject = (TraceHit != g_vecZero) ? TraceHit : Location;
|
|
|
|
float pCheckLoc[3] = { PointToProject.x, PointToProject.z, -PointToProject.y };
|
|
|
|
dtPolyRef FoundPoly;
|
|
float NavNearest[3];
|
|
|
|
dtStatus success = m_navQuery->findNearestPoly(pCheckLoc, pReachableExtents, m_navFilter, &FoundPoly, NavNearest);
|
|
|
|
if (dtStatusSucceed(success))
|
|
{
|
|
unsigned char area = 0;
|
|
m_navMesh->getPolyArea(FoundPoly, &area);
|
|
return area;
|
|
}
|
|
else
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
const char* UTIL_NavmeshAreaToChar(const unsigned char Area)
|
|
{
|
|
switch (Area)
|
|
{
|
|
case SAMPLE_POLYAREA_BLOCKED:
|
|
return "Blocked";
|
|
case SAMPLE_POLYAREA_CROUCH:
|
|
return "Crouch";
|
|
case SAMPLE_POLYAREA_GROUND:
|
|
return "Ground";
|
|
default:
|
|
return "INVALID";
|
|
|
|
}
|
|
|
|
return "INVALID";
|
|
}
|
|
|
|
void UTIL_UpdateBotMovementStatus(AvHAIPlayer* pBot)
|
|
{
|
|
if (pBot->Edict->v.movetype != pBot->BotNavInfo.CurrentMoveType)
|
|
{
|
|
if (pBot->BotNavInfo.CurrentMoveType == MOVETYPE_FLY)
|
|
{
|
|
OnBotEndLadder(pBot);
|
|
}
|
|
|
|
|
|
if (pBot->Edict->v.movetype == MOVETYPE_FLY)
|
|
{
|
|
OnBotStartLadder(pBot);
|
|
}
|
|
|
|
pBot->BotNavInfo.CurrentMoveType = pBot->Edict->v.movetype;
|
|
}
|
|
|
|
pBot->BotNavInfo.CurrentPoly = UTIL_GetNearestPolyRefForEntity(pBot->Edict);
|
|
|
|
pBot->CollisionHullBottomLocation = GetPlayerBottomOfCollisionHull(pBot->Edict);
|
|
pBot->CollisionHullTopLocation = GetPlayerTopOfCollisionHull(pBot->Edict);
|
|
}
|
|
|
|
|
|
bool AbortCurrentMove(AvHAIPlayer* pBot, const Vector NewDestination)
|
|
{
|
|
if (pBot->BotNavInfo.CurrentPath.size() == 0 || pBot->BotNavInfo.CurrentPathPoint >= pBot->BotNavInfo.CurrentPath.size() || pBot->BotNavInfo.NavProfile.bFlyingProfile) { return true; }
|
|
|
|
if (IsBotOffPath(pBot) || HasBotReachedPathPoint(pBot)) { return true; }
|
|
|
|
bot_path_node CurrentPathNode = pBot->BotNavInfo.CurrentPath[pBot->BotNavInfo.CurrentPathPoint];
|
|
|
|
Vector MoveFrom = CurrentPathNode.FromLocation;
|
|
Vector MoveTo = CurrentPathNode.Location;
|
|
unsigned int flag = CurrentPathNode.flag;
|
|
|
|
if (flag == SAMPLE_POLYFLAGS_TEAM1PHASEGATE || flag == SAMPLE_POLYFLAGS_TEAM2PHASEGATE || flag == SAMPLE_POLYFLAGS_TEAM1STRUCTURE || flag == SAMPLE_POLYFLAGS_TEAM2STRUCTURE) { return true; }
|
|
|
|
Vector ClosestPointOnLine = vClosestPointOnLine2D(MoveFrom, MoveTo, pBot->Edict->v.origin);
|
|
|
|
bool bAtOrPastMovement = (vEquals2D(ClosestPointOnLine, MoveFrom, 1.0f) && fabsf(pBot->Edict->v.origin.z - MoveFrom.z) <= 50.0f ) || (vEquals2D(ClosestPointOnLine, MoveTo, 1.0f) && fabsf(pBot->Edict->v.origin.z - MoveTo.z) <= 50.0f) ;
|
|
|
|
if ((pBot->Edict->v.flags & FL_ONGROUND) && bAtOrPastMovement)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
Vector DestinationPointOnLine = vClosestPointOnLine(MoveFrom, MoveTo, NewDestination);
|
|
|
|
bool bReverseCourse = (vDist3DSq(DestinationPointOnLine, MoveFrom) < vDist3DSq(DestinationPointOnLine, MoveTo));
|
|
|
|
if (flag == SAMPLE_POLYFLAGS_LIFT)
|
|
{
|
|
if (pBot->BotNavInfo.MovementTask.TaskType != MOVE_TASK_NONE && !vEquals(NewDestination, pBot->BotNavInfo.MovementTask.TaskLocation))
|
|
{
|
|
if (NAV_IsMovementTaskStillValid(pBot))
|
|
{
|
|
NAV_ProgressMovementTask(pBot);
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
NAV_ClearMovementTask(pBot);
|
|
ClearBotPath(pBot);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (bReverseCourse)
|
|
{
|
|
if (UTIL_PointIsDirectlyReachable(pBot->CurrentFloorPosition, MoveFrom)) { return true; }
|
|
LiftMove(pBot, MoveTo, MoveFrom);
|
|
}
|
|
else
|
|
{
|
|
if (UTIL_PointIsDirectlyReachable(pBot->CurrentFloorPosition, MoveTo)) { return true; }
|
|
LiftMove(pBot, MoveFrom, MoveTo);
|
|
}
|
|
}
|
|
|
|
if (flag == SAMPLE_POLYFLAGS_WALK || flag == SAMPLE_POLYFLAGS_WELD || flag == SAMPLE_POLYFLAGS_DOOR)
|
|
{
|
|
if (UTIL_PointIsDirectlyReachable(pBot->CurrentFloorPosition, MoveFrom) || UTIL_PointIsDirectlyReachable(pBot->CurrentFloorPosition, MoveTo))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (bReverseCourse)
|
|
{
|
|
GroundMove(pBot, MoveTo, MoveFrom);
|
|
}
|
|
else
|
|
{
|
|
GroundMove(pBot, MoveFrom, MoveTo);
|
|
}
|
|
}
|
|
|
|
if (flag == SAMPLE_POLYFLAGS_WALLCLIMB)
|
|
{
|
|
if (bReverseCourse)
|
|
{
|
|
FallMove(pBot, MoveTo, MoveFrom);
|
|
}
|
|
else
|
|
{
|
|
if (PlayerHasWeapon(pBot->Player, WEAPON_FADE_BLINK))
|
|
{
|
|
BlinkClimbMove(pBot, MoveFrom, MoveTo, CurrentPathNode.requiredZ);
|
|
}
|
|
else
|
|
{
|
|
WallClimbMove(pBot, MoveFrom, MoveTo, CurrentPathNode.requiredZ);
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
if (flag == SAMPLE_POLYFLAGS_LADDER)
|
|
{
|
|
if (bReverseCourse)
|
|
{
|
|
|
|
LadderMove(pBot, MoveTo, MoveFrom, CurrentPathNode.requiredZ, (unsigned char)SAMPLE_POLYAREA_CROUCH);
|
|
|
|
// We're going DOWN the ladder
|
|
if (MoveTo.z > MoveFrom.z)
|
|
{
|
|
if (pBot->Edict->v.origin.z - MoveFrom.z < 150.0f)
|
|
{
|
|
BotJump(pBot);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
|
|
LadderMove(pBot, MoveFrom, MoveTo, CurrentPathNode.requiredZ, (unsigned char)SAMPLE_POLYAREA_CROUCH);
|
|
|
|
// We're going DOWN the ladder
|
|
if (MoveFrom.z > MoveTo.z)
|
|
{
|
|
if (pBot->Edict->v.origin.z - MoveFrom.z < 150.0f)
|
|
{
|
|
BotJump(pBot);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (flag == SAMPLE_POLYFLAGS_TEAM1PHASEGATE || flag == SAMPLE_POLYFLAGS_TEAM2PHASEGATE)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (flag == SAMPLE_POLYFLAGS_JUMP || flag == SAMPLE_POLYFLAGS_DUCKJUMP || flag == SAMPLE_POLYFLAGS_BLOCKED)
|
|
{
|
|
if (bReverseCourse)
|
|
{
|
|
JumpMove(pBot, MoveTo, MoveFrom);
|
|
}
|
|
else
|
|
{
|
|
JumpMove(pBot, MoveFrom, MoveTo);
|
|
}
|
|
}
|
|
|
|
if (flag == SAMPLE_POLYFLAGS_FALL)
|
|
{
|
|
FallMove(pBot, MoveFrom, MoveTo);
|
|
}
|
|
|
|
BotMovementInputs(pBot);
|
|
|
|
return false;
|
|
}
|
|
|
|
void UpdateBotStuck(AvHAIPlayer* pBot)
|
|
{
|
|
if (vIsZero(pBot->desiredMovementDir) && !pBot->BotNavInfo.StuckInfo.bPathFollowFailed)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!pBot->BotNavInfo.StuckInfo.bPathFollowFailed)
|
|
{
|
|
|
|
bool bIsFollowingPath = (pBot->BotNavInfo.CurrentPath.size() > 0 && pBot->BotNavInfo.CurrentPathPoint < pBot->BotNavInfo.CurrentPath.size());
|
|
|
|
bool bDist3D = pBot->BotNavInfo.NavProfile.bFlyingProfile || (bIsFollowingPath && (pBot->BotNavInfo.CurrentPath[pBot->BotNavInfo.CurrentPathPoint].flag == SAMPLE_POLYFLAGS_LADDER || pBot->BotNavInfo.CurrentPath[pBot->BotNavInfo.CurrentPathPoint].flag == SAMPLE_POLYFLAGS_WALLCLIMB));
|
|
|
|
float DistFromLastPoint = (bDist3D) ? vDist3DSq(pBot->Edict->v.origin, pBot->BotNavInfo.StuckInfo.LastBotPosition) : vDist2DSq(pBot->Edict->v.origin, pBot->BotNavInfo.StuckInfo.LastBotPosition);
|
|
|
|
if (DistFromLastPoint >= sqrf(8.0f))
|
|
{
|
|
pBot->BotNavInfo.StuckInfo.TotalStuckTime = 0.0f;
|
|
pBot->BotNavInfo.StuckInfo.LastBotPosition = pBot->Edict->v.origin;
|
|
}
|
|
else
|
|
{
|
|
pBot->BotNavInfo.StuckInfo.TotalStuckTime += pBot->ThinkDelta;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pBot->BotNavInfo.StuckInfo.TotalStuckTime += pBot->ThinkDelta;
|
|
}
|
|
|
|
if (pBot->BotNavInfo.StuckInfo.TotalStuckTime > 0.25f)
|
|
{
|
|
if (pBot->BotNavInfo.StuckInfo.TotalStuckTime > CONFIG_GetMaxStuckTime())
|
|
{
|
|
BotSuicide(pBot);
|
|
return;
|
|
}
|
|
|
|
if (pBot->BotNavInfo.StuckInfo.TotalStuckTime > 5.0f)
|
|
{
|
|
if (pBot->BotNavInfo.MovementTask.TaskType != MOVE_TASK_NONE)
|
|
{
|
|
NAV_ClearMovementTask(pBot);
|
|
}
|
|
|
|
ClearBotPath(pBot);
|
|
}
|
|
|
|
if (!vIsZero(pBot->desiredMovementDir))
|
|
{
|
|
edict_t* BlockingEntity = UTIL_TraceEntity(pBot->Edict, pBot->Edict->v.origin, (pBot->Edict->v.origin + pBot->desiredMovementDir * 50.0f));
|
|
|
|
if (IsEdictStructure(BlockingEntity))
|
|
{
|
|
pBot->desiredMovementDir = UTIL_GetVectorNormal2D(pBot->desiredMovementDir + UTIL_GetCrossProduct(pBot->desiredMovementDir, UP_VECTOR));
|
|
|
|
BotMovementInputs(pBot);
|
|
}
|
|
|
|
BotJump(pBot);
|
|
|
|
if (!IsPlayerSkulk(pBot->Edict))
|
|
{
|
|
pBot->Button |= IN_DUCK;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void SetBaseNavProfile(AvHAIPlayer* pBot)
|
|
{
|
|
pBot->BotNavInfo.bNavProfileChanged = true;
|
|
|
|
if (IsPlayerMarine(pBot->Player))
|
|
{
|
|
memcpy(&pBot->BotNavInfo.NavProfile, &BaseNavProfiles[MARINE_BASE_NAV_PROFILE], sizeof(nav_profile));
|
|
return;
|
|
}
|
|
|
|
switch (pBot->Edict->v.iuser3)
|
|
{
|
|
case AVH_USER3_ALIEN_PLAYER1:
|
|
memcpy(&pBot->BotNavInfo.NavProfile, &BaseNavProfiles[SKULK_BASE_NAV_PROFILE], sizeof(nav_profile));
|
|
return;
|
|
case AVH_USER3_ALIEN_PLAYER2:
|
|
memcpy(&pBot->BotNavInfo.NavProfile, &BaseNavProfiles[GORGE_BASE_NAV_PROFILE], sizeof(nav_profile));
|
|
return;
|
|
case AVH_USER3_ALIEN_PLAYER3:
|
|
memcpy(&pBot->BotNavInfo.NavProfile, &BaseNavProfiles[LERK_BASE_NAV_PROFILE], sizeof(nav_profile));
|
|
return;
|
|
case AVH_USER3_ALIEN_PLAYER4:
|
|
memcpy(&pBot->BotNavInfo.NavProfile, &BaseNavProfiles[FADE_BASE_NAV_PROFILE], sizeof(nav_profile));
|
|
return;
|
|
case AVH_USER3_ALIEN_PLAYER5:
|
|
memcpy(&pBot->BotNavInfo.NavProfile, &BaseNavProfiles[ONOS_BASE_NAV_PROFILE], sizeof(nav_profile));
|
|
return;
|
|
|
|
}
|
|
}
|
|
|
|
void UpdateBotMoveProfile(AvHAIPlayer* pBot, BotMoveStyle MoveStyle)
|
|
{
|
|
switch (pBot->Edict->v.iuser3)
|
|
{
|
|
case AVH_USER3_MARINE_PLAYER:
|
|
MarineUpdateBotMoveProfile(pBot, MoveStyle);
|
|
break;
|
|
case AVH_USER3_ALIEN_PLAYER1:
|
|
SkulkUpdateBotMoveProfile(pBot, MoveStyle);
|
|
break;
|
|
case AVH_USER3_ALIEN_PLAYER2:
|
|
GorgeUpdateBotMoveProfile(pBot, MoveStyle);
|
|
break;
|
|
case AVH_USER3_ALIEN_PLAYER3:
|
|
LerkUpdateBotMoveProfile(pBot, MoveStyle);
|
|
break;
|
|
case AVH_USER3_ALIEN_PLAYER4:
|
|
FadeUpdateBotMoveProfile(pBot, MoveStyle);
|
|
break;
|
|
case AVH_USER3_ALIEN_PLAYER5:
|
|
OnosUpdateBotMoveProfile(pBot, MoveStyle);
|
|
break;
|
|
}
|
|
|
|
if (pBot->Player->GetTeam() == GetGameRules()->GetTeamANumber())
|
|
{
|
|
pBot->BotNavInfo.NavProfile.Filters.removeExcludeFlags(SAMPLE_POLYFLAGS_TEAM2STRUCTURE);
|
|
pBot->BotNavInfo.NavProfile.Filters.addExcludeFlags(SAMPLE_POLYFLAGS_TEAM1STRUCTURE);
|
|
pBot->BotNavInfo.NavProfile.Filters.removeIncludeFlags(SAMPLE_POLYFLAGS_TEAM1STRUCTURE);
|
|
pBot->BotNavInfo.NavProfile.Filters.addIncludeFlags(SAMPLE_POLYFLAGS_TEAM2STRUCTURE);
|
|
}
|
|
else
|
|
{
|
|
pBot->BotNavInfo.NavProfile.Filters.removeExcludeFlags(SAMPLE_POLYFLAGS_TEAM1STRUCTURE);
|
|
pBot->BotNavInfo.NavProfile.Filters.addExcludeFlags(SAMPLE_POLYFLAGS_TEAM2STRUCTURE);
|
|
pBot->BotNavInfo.NavProfile.Filters.removeIncludeFlags(SAMPLE_POLYFLAGS_TEAM2STRUCTURE);
|
|
pBot->BotNavInfo.NavProfile.Filters.addIncludeFlags(SAMPLE_POLYFLAGS_TEAM1STRUCTURE);
|
|
}
|
|
|
|
}
|
|
|
|
void MarineUpdateBotMoveProfile(AvHAIPlayer* pBot, BotMoveStyle MoveStyle)
|
|
{
|
|
nav_profile* NavProfile = &pBot->BotNavInfo.NavProfile;
|
|
|
|
bool bHasWelder = PlayerHasWeapon(pBot->Player, WEAPON_MARINE_WELDER);
|
|
|
|
if (!bHasWelder)
|
|
{
|
|
AvHAIDroppedItem* NearbyWelder = AITAC_FindClosestItemToLocation(pBot->Edict->v.origin, DEPLOYABLE_ITEM_WELDER, pBot->Player->GetTeam(), pBot->BotNavInfo.NavProfile.ReachabilityFlag, 0.0f, UTIL_MetresToGoldSrcUnits(10.0f), true);
|
|
|
|
bHasWelder = (NearbyWelder != nullptr);
|
|
}
|
|
|
|
// Did our nav profile previously indicate we could go through weldable doors?
|
|
bool bHadWelder = (NavProfile->Filters.getIncludeFlags() & SAMPLE_POLYFLAGS_WELD);
|
|
|
|
if (bHasWelder != bHadWelder)
|
|
{
|
|
pBot->BotNavInfo.bNavProfileChanged = true;
|
|
|
|
if (bHasWelder)
|
|
{
|
|
NavProfile->Filters.addIncludeFlags(SAMPLE_POLYFLAGS_WELD);
|
|
NavProfile->ReachabilityFlag = AI_REACHABILITY_WELDER;
|
|
}
|
|
else
|
|
{
|
|
NavProfile->Filters.removeIncludeFlags(SAMPLE_POLYFLAGS_WELD);
|
|
NavProfile->ReachabilityFlag = AI_REACHABILITY_MARINE;
|
|
}
|
|
}
|
|
|
|
SamplePolyFlags ExcludePhaseGateFlag = (pBot->Player->GetTeam() == GetGameRules()->GetTeamANumber()) ? SAMPLE_POLYFLAGS_TEAM2PHASEGATE : SAMPLE_POLYFLAGS_TEAM1PHASEGATE;
|
|
SamplePolyFlags IncludePhaseGateFlag = (ExcludePhaseGateFlag & SAMPLE_POLYFLAGS_TEAM1PHASEGATE) ? SAMPLE_POLYFLAGS_TEAM2PHASEGATE : SAMPLE_POLYFLAGS_TEAM1PHASEGATE;
|
|
|
|
if (!(NavProfile->Filters.getIncludeFlags() & IncludePhaseGateFlag))
|
|
{
|
|
pBot->BotNavInfo.bNavProfileChanged = true;
|
|
|
|
NavProfile->Filters.addIncludeFlags(IncludePhaseGateFlag);
|
|
NavProfile->Filters.removeIncludeFlags(ExcludePhaseGateFlag);
|
|
}
|
|
|
|
if (MoveStyle == pBot->BotNavInfo.PreviousMoveStyle) { return; }
|
|
|
|
pBot->BotNavInfo.PreviousMoveStyle = MoveStyle;
|
|
pBot->BotNavInfo.bNavProfileChanged = true;
|
|
pBot->BotNavInfo.MoveStyle = MoveStyle;
|
|
|
|
if (MoveStyle == MOVESTYLE_NORMAL)
|
|
{
|
|
NavProfile->Filters.setAreaCost(SAMPLE_POLYAREA_GROUND, 1.0f);
|
|
NavProfile->Filters.setAreaCost(SAMPLE_POLYAREA_CROUCH, 2.0f);
|
|
return;
|
|
}
|
|
|
|
if (MoveStyle == MOVESTYLE_HIDE || MoveStyle == MOVESTYLE_AMBUSH)
|
|
{
|
|
NavProfile->Filters.setAreaCost(SAMPLE_POLYAREA_GROUND, 3.0f);
|
|
NavProfile->Filters.setAreaCost(SAMPLE_POLYAREA_CROUCH, 1.0f);
|
|
return;
|
|
}
|
|
}
|
|
|
|
void SkulkUpdateBotMoveProfile(AvHAIPlayer* pBot, BotMoveStyle MoveStyle)
|
|
{
|
|
if (MoveStyle == pBot->BotNavInfo.PreviousMoveStyle) { return; }
|
|
|
|
pBot->BotNavInfo.MoveStyle = MoveStyle;
|
|
pBot->BotNavInfo.PreviousMoveStyle = MoveStyle;
|
|
|
|
pBot->BotNavInfo.bNavProfileChanged = true;
|
|
|
|
nav_profile* NavProfile = &pBot->BotNavInfo.NavProfile;
|
|
|
|
if (MoveStyle == MOVESTYLE_NORMAL)
|
|
{
|
|
NavProfile->Filters.setAreaCost(SAMPLE_POLYAREA_GROUND, 1.0f);
|
|
NavProfile->Filters.setAreaCost(SAMPLE_POLYAREA_CROUCH, 1.0f);
|
|
return;
|
|
}
|
|
|
|
if (MoveStyle == MOVESTYLE_HIDE || MoveStyle == MOVESTYLE_AMBUSH)
|
|
{
|
|
NavProfile->Filters.setAreaCost(SAMPLE_POLYAREA_GROUND, 10.0f);
|
|
NavProfile->Filters.setAreaCost(SAMPLE_POLYAREA_CROUCH, 1.0f);
|
|
NavProfile->Filters.setAreaCost(SAMPLE_POLYAREA_WALLCLIMB, 1.0f);
|
|
return;
|
|
}
|
|
}
|
|
|
|
void GorgeUpdateBotMoveProfile(AvHAIPlayer* pBot, BotMoveStyle MoveStyle)
|
|
{
|
|
if (MoveStyle == pBot->BotNavInfo.PreviousMoveStyle) { return; }
|
|
|
|
pBot->BotNavInfo.PreviousMoveStyle = MoveStyle;
|
|
|
|
pBot->BotNavInfo.bNavProfileChanged = true;
|
|
pBot->BotNavInfo.MoveStyle = MoveStyle;
|
|
|
|
nav_profile* NavProfile = &pBot->BotNavInfo.NavProfile;
|
|
|
|
if (MoveStyle == MOVESTYLE_NORMAL)
|
|
{
|
|
NavProfile->Filters.setAreaCost(SAMPLE_POLYAREA_GROUND, 1.0f);
|
|
NavProfile->Filters.setAreaCost(SAMPLE_POLYAREA_CROUCH, 1.0f);
|
|
return;
|
|
}
|
|
|
|
if (MoveStyle == MOVESTYLE_HIDE || MoveStyle == MOVESTYLE_AMBUSH)
|
|
{
|
|
NavProfile->Filters.setAreaCost(SAMPLE_POLYAREA_GROUND, 10.0f);
|
|
NavProfile->Filters.setAreaCost(SAMPLE_POLYAREA_CROUCH, 1.0f);
|
|
return;
|
|
}
|
|
}
|
|
|
|
void LerkUpdateBotMoveProfile(AvHAIPlayer* pBot, BotMoveStyle MoveStyle)
|
|
{
|
|
if (MoveStyle == pBot->BotNavInfo.PreviousMoveStyle) { return; }
|
|
|
|
pBot->BotNavInfo.PreviousMoveStyle = MoveStyle;
|
|
|
|
pBot->BotNavInfo.bNavProfileChanged = true;
|
|
pBot->BotNavInfo.MoveStyle = MoveStyle;
|
|
|
|
nav_profile* NavProfile = &pBot->BotNavInfo.NavProfile;
|
|
|
|
if (MoveStyle == MOVESTYLE_NORMAL)
|
|
{
|
|
NavProfile->Filters.setAreaCost(SAMPLE_POLYAREA_GROUND, 1.0f);
|
|
NavProfile->Filters.setAreaCost(SAMPLE_POLYAREA_CROUCH, 1.0f);
|
|
NavProfile->bFlyingProfile = true;
|
|
return;
|
|
}
|
|
|
|
if (MoveStyle == MOVESTYLE_HIDE || MoveStyle == MOVESTYLE_AMBUSH)
|
|
{
|
|
NavProfile->bFlyingProfile = false;
|
|
NavProfile->Filters.setAreaCost(SAMPLE_POLYAREA_GROUND, 10.0f);
|
|
NavProfile->Filters.setAreaCost(SAMPLE_POLYAREA_CROUCH, 1.0f);
|
|
return;
|
|
}
|
|
}
|
|
|
|
void FadeUpdateBotMoveProfile(AvHAIPlayer* pBot, BotMoveStyle MoveStyle)
|
|
{
|
|
if (MoveStyle == pBot->BotNavInfo.PreviousMoveStyle) { return; }
|
|
|
|
pBot->BotNavInfo.PreviousMoveStyle = MoveStyle;
|
|
|
|
pBot->BotNavInfo.bNavProfileChanged = true;
|
|
pBot->BotNavInfo.MoveStyle = MoveStyle;
|
|
|
|
nav_profile* NavProfile = &pBot->BotNavInfo.NavProfile;
|
|
|
|
if (MoveStyle == MOVESTYLE_NORMAL)
|
|
{
|
|
NavProfile->Filters.setAreaCost(SAMPLE_POLYAREA_GROUND, 1.0f);
|
|
NavProfile->Filters.setAreaCost(SAMPLE_POLYAREA_CROUCH, 1.0f);
|
|
return;
|
|
}
|
|
|
|
if (MoveStyle == MOVESTYLE_HIDE || MoveStyle == MOVESTYLE_AMBUSH)
|
|
{
|
|
NavProfile->Filters.setAreaCost(SAMPLE_POLYAREA_GROUND, 10.0f);
|
|
NavProfile->Filters.setAreaCost(SAMPLE_POLYAREA_CROUCH, 1.0f);
|
|
return;
|
|
}
|
|
}
|
|
|
|
void OnosUpdateBotMoveProfile(AvHAIPlayer* pBot, BotMoveStyle MoveStyle)
|
|
{
|
|
// Onos doesn't really do much other than the usual movement
|
|
return;
|
|
}
|
|
|
|
bool NAV_MergeAndUpdatePath(AvHAIPlayer* pBot, std::vector<bot_path_node>& NewPath)
|
|
{
|
|
if (pBot->BotNavInfo.CurrentPath.size() == 0 || pBot->BotNavInfo.CurrentPathPoint >= pBot->BotNavInfo.CurrentPath.size())
|
|
{
|
|
pBot->BotNavInfo.CurrentPath.clear();
|
|
pBot->BotNavInfo.CurrentPath.insert(pBot->BotNavInfo.CurrentPath.end(), NewPath.begin(), NewPath.end());
|
|
pBot->BotNavInfo.CurrentPathPoint = 0;
|
|
return true;
|
|
}
|
|
|
|
std::vector<bot_path_node>::iterator OldPathStart = (pBot->BotNavInfo.CurrentPath.begin() + pBot->BotNavInfo.CurrentPathPoint);
|
|
std::vector<bot_path_node>::iterator OldPathEnd;
|
|
std::vector<bot_path_node>::iterator NewPathStart;
|
|
|
|
for (OldPathEnd = OldPathStart; OldPathEnd != pBot->BotNavInfo.CurrentPath.end(); OldPathEnd++)
|
|
{
|
|
if (OldPathEnd->flag != SAMPLE_POLYFLAGS_WALK)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (OldPathEnd == pBot->BotNavInfo.CurrentPath.end())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
for (NewPathStart = NewPath.begin(); NewPathStart != NewPath.end(); NewPathStart++)
|
|
{
|
|
if (NewPathStart->flag != SAMPLE_POLYFLAGS_WALK)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (NewPathStart == NewPath.end())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!vEquals(OldPathEnd->FromLocation, NewPathStart->FromLocation, 16.0f) || !vEquals(OldPathEnd->Location, NewPathStart->Location, 16.0f))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
OldPathEnd = next(OldPathEnd);
|
|
NewPathStart = next(NewPathStart);
|
|
|
|
for (auto it = OldPathEnd; it != pBot->BotNavInfo.CurrentPath.end();)
|
|
{
|
|
it = pBot->BotNavInfo.CurrentPath.erase(it);
|
|
}
|
|
|
|
pBot->BotNavInfo.CurrentPath.insert(pBot->BotNavInfo.CurrentPath.end(), NewPathStart, NewPath.end());
|
|
return true;
|
|
}
|
|
|
|
bool MoveTo(AvHAIPlayer* pBot, const Vector Destination, const BotMoveStyle MoveStyle, const float MaxAcceptableDist)
|
|
{
|
|
#ifdef DEBUG
|
|
if (pBot == AIMGR_GetDebugAIPlayer())
|
|
{
|
|
bool bBreak = true; // Add a break point here if you want to debug a specific bot
|
|
}
|
|
#endif
|
|
|
|
if (vIsZero(Destination) || (vDist2D(pBot->Edict->v.origin, Destination) <= 6.0f && (fabs(pBot->CollisionHullBottomLocation.z - Destination.z) < 50.0f)))
|
|
{
|
|
pBot->BotNavInfo.StuckInfo.bPathFollowFailed = false;
|
|
ClearBotMovement(pBot);
|
|
return true;
|
|
}
|
|
|
|
nav_status* BotNavInfo = &pBot->BotNavInfo;
|
|
|
|
pBot->BotNavInfo.MoveStyle = MoveStyle;
|
|
UTIL_UpdateBotMovementStatus(pBot);
|
|
|
|
UpdateBotMoveProfile(pBot, MoveStyle);
|
|
|
|
bool bIsFlyingProfile = pBot->BotNavInfo.NavProfile.bFlyingProfile;
|
|
bool bNavProfileChanged = pBot->BotNavInfo.bNavProfileChanged;
|
|
bool bForceRecalculation = (pBot->BotNavInfo.NextForceRecalc > 0.0f && gpGlobals->time >= pBot->BotNavInfo.NextForceRecalc);
|
|
bool bIsPerformingMoveTask = (BotNavInfo->MovementTask.TaskType != MOVE_TASK_NONE && vEquals(Destination, BotNavInfo->MovementTask.TaskLocation, GetPlayerRadius(pBot->Player)));
|
|
bool bEndGoalChanged = (!vEquals(Destination, BotNavInfo->TargetDestination, GetPlayerRadius(pBot->Player)) && !bIsPerformingMoveTask);
|
|
bool bMoveTaskGenerated = (BotNavInfo->MovementTask.TaskType == MOVE_TASK_NONE || (vEquals(BotNavInfo->PathDestination, BotNavInfo->MovementTask.TaskLocation, GetPlayerRadius(pBot->Player))));
|
|
|
|
|
|
// Only recalculate the path if there isn't a path, or something has changed and enough time has elapsed since the last path calculation
|
|
bool bShouldCalculatePath = (bNavProfileChanged || bForceRecalculation || BotNavInfo->CurrentPath.size() == 0 || bEndGoalChanged || !bMoveTaskGenerated);
|
|
|
|
if (bShouldCalculatePath)
|
|
{
|
|
if (!bIsFlyingProfile && !pBot->BotNavInfo.IsOnGround && !IsPlayerClimbingWall(pBot->Edict))
|
|
{
|
|
if (pBot->BotNavInfo.CurrentPath.size() > 0)
|
|
{
|
|
BotFollowPath(pBot);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
dtStatus PathFindingStatus = DT_FAILURE;
|
|
|
|
vector<bot_path_node> PendingPath;
|
|
|
|
if (bIsFlyingProfile)
|
|
{
|
|
PathFindingStatus = FindFlightPathToPoint(pBot->BotNavInfo.NavProfile, pBot->Edict->v.origin, Destination, PendingPath, MaxAcceptableDist);
|
|
}
|
|
else
|
|
{
|
|
Vector NavAdjustedDestination = AdjustPointForPathfinding(Destination);
|
|
if (vIsZero(NavAdjustedDestination)) { return false; }
|
|
|
|
PathFindingStatus = FindPathClosestToPoint(pBot, pBot->BotNavInfo.MoveStyle, pBot->CurrentFloorPosition, NavAdjustedDestination, PendingPath, MaxAcceptableDist);
|
|
}
|
|
|
|
pBot->BotNavInfo.NextForceRecalc = 0.0f;
|
|
pBot->BotNavInfo.bNavProfileChanged = false;
|
|
|
|
if (dtStatusSucceed(PathFindingStatus))
|
|
{
|
|
if (!NAV_MergeAndUpdatePath(pBot, PendingPath))
|
|
{
|
|
if (!AbortCurrentMove(pBot, Destination))
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
ClearBotPath(pBot);
|
|
NAV_ClearMovementTask(pBot);
|
|
pBot->BotNavInfo.CurrentPath.insert(pBot->BotNavInfo.CurrentPath.begin(), PendingPath.begin(), PendingPath.end());
|
|
BotNavInfo->CurrentPathPoint = 0;
|
|
}
|
|
}
|
|
|
|
pBot->BotNavInfo.StuckInfo.bPathFollowFailed = false;
|
|
ClearBotStuckMovement(pBot);
|
|
pBot->BotNavInfo.TotalStuckTime = 0.0f;
|
|
BotNavInfo->PathDestination = Destination;
|
|
|
|
if (!bIsPerformingMoveTask)
|
|
{
|
|
BotNavInfo->ActualMoveDestination = BotNavInfo->CurrentPath.back().Location;
|
|
BotNavInfo->TargetDestination = Destination;
|
|
}
|
|
|
|
|
|
}
|
|
else
|
|
{
|
|
if (pBot->BotNavInfo.CurrentPath.size() == 0)
|
|
{
|
|
pBot->BotNavInfo.StuckInfo.bPathFollowFailed = true;
|
|
|
|
if (!UTIL_PointIsOnNavmesh(pBot->CollisionHullBottomLocation, pBot->BotNavInfo.NavProfile) && !vIsZero(BotNavInfo->LastNavMeshPosition))
|
|
{
|
|
MoveDirectlyTo(pBot, BotNavInfo->LastNavMeshPosition);
|
|
|
|
if (vDist2DSq(pBot->CurrentFloorPosition, BotNavInfo->LastNavMeshPosition) < sqrf(8.0f))
|
|
{
|
|
BotNavInfo->LastNavMeshPosition = g_vecZero;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
if (!vIsZero(BotNavInfo->UnstuckMoveLocation) && vDist2DSq(pBot->CurrentFloorPosition, BotNavInfo->UnstuckMoveLocation) < sqrf(8.0f))
|
|
{
|
|
BotNavInfo->UnstuckMoveLocation = ZERO_VECTOR;
|
|
}
|
|
|
|
if (vIsZero(BotNavInfo->UnstuckMoveLocation))
|
|
{
|
|
BotNavInfo->UnstuckMoveLocation = FindClosestPointBackOnPath(pBot);
|
|
}
|
|
|
|
if (!vIsZero(BotNavInfo->UnstuckMoveLocation))
|
|
{
|
|
MoveDirectlyTo(pBot, BotNavInfo->UnstuckMoveLocation);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!bIsPerformingMoveTask && BotNavInfo->MovementTask.TaskType != MOVE_TASK_NONE)
|
|
{
|
|
if (NAV_IsMovementTaskStillValid(pBot))
|
|
{
|
|
NAV_ProgressMovementTask(pBot);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
NAV_ClearMovementTask(pBot);
|
|
ClearBotPath(pBot);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (BotNavInfo->CurrentPath.size() > 0)
|
|
{
|
|
// If this path requires use of a welder and we don't have one, then find one
|
|
if ((pBot->BotNavInfo.SpecialMovementFlags & SAMPLE_POLYFLAGS_WELD) && !PlayerHasWeapon(pBot->Player, WEAPON_MARINE_WELDER))
|
|
{
|
|
if (pBot->BotNavInfo.MovementTask.TaskType != MOVE_TASK_PICKUP)
|
|
{
|
|
nav_profile BaseProfile = GetBaseNavProfile(MARINE_BASE_NAV_PROFILE);
|
|
|
|
AvHAIDroppedItem* NearestWelder = AITAC_FindClosestItemToLocation(pBot->Edict->v.origin, DEPLOYABLE_ITEM_WELDER, pBot->Player->GetTeam(), BaseProfile.ReachabilityFlag, 0.0f, 0.0f, true);
|
|
|
|
if (NearestWelder)
|
|
{
|
|
NAV_SetPickupMovementTask(pBot, NearestWelder->edict, nullptr);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (pBot->Edict->v.flags & FL_INWATER)
|
|
{
|
|
BotFollowSwimPath(pBot);
|
|
}
|
|
else
|
|
{
|
|
if (bIsFlyingProfile)
|
|
{
|
|
BotFollowFlightPath(pBot, true);
|
|
}
|
|
else
|
|
{
|
|
BotFollowPath(pBot);
|
|
}
|
|
}
|
|
|
|
// Check to ensure BotFollowFlightPath or BotFollowPath haven't cleared the path (will happen if reached end of path)
|
|
if (BotNavInfo->CurrentPathPoint < BotNavInfo->CurrentPath.size())
|
|
{
|
|
HandlePlayerAvoidance(pBot, BotNavInfo->CurrentPath[BotNavInfo->CurrentPathPoint].Location);
|
|
BotMovementInputs(pBot);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
Vector FindClosestPointBackOnPath(AvHAIPlayer* pBot)
|
|
{
|
|
|
|
DeployableSearchFilter ResNodeFilter;
|
|
ResNodeFilter.ReachabilityFlags = pBot->BotNavInfo.NavProfile.ReachabilityFlag;
|
|
|
|
AvHAIResourceNode* NearestResNode = AITAC_FindNearestResourceNodeToLocation(pBot->Edict->v.origin, &ResNodeFilter);
|
|
|
|
Vector ValidNavmeshPoint = AITAC_GetTeamStartingLocation(pBot->Player->GetTeam());
|
|
|
|
if (NearestResNode && vDist2D(pBot->Edict->v.origin, NearestResNode->Location) < vDist2D(pBot->Edict->v.origin, ValidNavmeshPoint))
|
|
{
|
|
ValidNavmeshPoint = NearestResNode->Location;
|
|
}
|
|
|
|
ValidNavmeshPoint = UTIL_ProjectPointToNavmesh(ValidNavmeshPoint, pBot->BotNavInfo.NavProfile);
|
|
|
|
if (vIsZero(ValidNavmeshPoint))
|
|
{
|
|
return g_vecZero;
|
|
}
|
|
|
|
vector<bot_path_node> BackwardsPath;
|
|
BackwardsPath.clear();
|
|
|
|
// Now we find a path backwards from the valid nav mesh point to our location, trying to get as close as we can to it
|
|
|
|
dtStatus BackwardFindingStatus = FindPathClosestToPoint(pBot->BotNavInfo.NavProfile, ValidNavmeshPoint, pBot->CurrentFloorPosition, BackwardsPath, UTIL_MetresToGoldSrcUnits(50.0f));
|
|
|
|
if (dtStatusSucceed(BackwardFindingStatus))
|
|
{
|
|
|
|
Vector NewMoveLocation = prev(BackwardsPath.end())->Location;
|
|
Vector NewMoveFromLocation = prev(BackwardsPath.end())->FromLocation;
|
|
|
|
for (auto it = BackwardsPath.rbegin(); it != BackwardsPath.rend(); it++)
|
|
{
|
|
if (vDist2DSq(pBot->Edict->v.origin, it->Location) > sqrf(GetPlayerRadius(pBot->Edict)) && UTIL_QuickTrace(pBot->Edict, pBot->Edict->v.origin, it->Location))
|
|
{
|
|
NewMoveLocation = it->Location;
|
|
NewMoveFromLocation = it->FromLocation;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!vIsZero(NewMoveLocation))
|
|
{
|
|
if (vDist2DSq(pBot->Edict->v.origin, NewMoveLocation) < sqrf(GetPlayerRadius(pBot->Player)))
|
|
{
|
|
NewMoveLocation = NewMoveLocation - (UTIL_GetVectorNormal2D(NewMoveLocation - NewMoveFromLocation) * 100.0f);
|
|
}
|
|
}
|
|
|
|
return NewMoveLocation;
|
|
}
|
|
|
|
return g_vecZero;
|
|
}
|
|
|
|
Vector FindClosestNavigablePointToDestination(const nav_profile& NavProfile, const Vector FromLocation, const Vector ToLocation, float MaxAcceptableDistance)
|
|
{
|
|
vector<bot_path_node> Path;
|
|
Path.clear();
|
|
|
|
// Now we find a path backwards from the valid nav mesh point to our location, trying to get as close as we can to it
|
|
|
|
dtStatus PathFindingResult = FindPathClosestToPoint(NavProfile, FromLocation, ToLocation, Path, MaxAcceptableDistance);
|
|
|
|
if (dtStatusSucceed(PathFindingResult) && Path.size() > 0)
|
|
{
|
|
return Path.back().Location;
|
|
}
|
|
|
|
return g_vecZero;
|
|
}
|
|
|
|
void SkipAheadInFlightPath(AvHAIPlayer* pBot)
|
|
{
|
|
nav_status* BotNavInfo = &pBot->BotNavInfo;
|
|
|
|
// Early exit if we don't have a path, or we're already on the last path point
|
|
if (BotNavInfo->CurrentPath.size() == 0 || BotNavInfo->CurrentPathPoint >= (pBot->BotNavInfo.CurrentPath.size() - 1)) { return; }
|
|
|
|
vector<bot_path_node>::iterator CurrentPathPoint = (BotNavInfo->CurrentPath.begin() + BotNavInfo->CurrentPathPoint);
|
|
|
|
if (UTIL_QuickHullTrace(pBot->Edict, pBot->Edict->v.origin, prev(BotNavInfo->CurrentPath.end())->Location, head_hull, false))
|
|
{
|
|
pBot->BotNavInfo.CurrentPathPoint = (BotNavInfo->CurrentPath.size() - 1);
|
|
return;
|
|
}
|
|
|
|
// If we are currently in a low area or approaching one, don't try to skip ahead in case it screws us up
|
|
if (CurrentPathPoint->area == SAMPLE_POLYAREA_CROUCH || (next(CurrentPathPoint) != BotNavInfo->CurrentPath.end() && next(CurrentPathPoint)->area == SAMPLE_POLYAREA_CROUCH)) { return; }
|
|
|
|
for (auto it = prev(BotNavInfo->CurrentPath.end()); it != next(CurrentPathPoint); it--)
|
|
{
|
|
Vector NextFlightPoint = UTIL_FindHighestSuccessfulTracePoint(pBot->Edict->v.origin, it->FromLocation, it->Location, 5.0f, 50.0f, 200.0f);
|
|
|
|
// If we can directly reach the end point, set our path point to the end of the path and go for it
|
|
if (!vIsZero(NextFlightPoint))
|
|
{
|
|
it->FromLocation = NextFlightPoint;
|
|
|
|
pBot->BotNavInfo.CurrentPathPoint = distance(BotNavInfo->CurrentPath.begin(), prev(it));
|
|
CurrentPathPoint = (BotNavInfo->CurrentPath.begin() + BotNavInfo->CurrentPathPoint);
|
|
|
|
CurrentPathPoint->FromLocation = pBot->Edict->v.origin;
|
|
CurrentPathPoint->Location = it->FromLocation;
|
|
|
|
|
|
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void BotFollowFlightPath(AvHAIPlayer* pBot, bool bAllowSkip)
|
|
{
|
|
if (pBot->BotNavInfo.CurrentPath.size() == 0 || pBot->BotNavInfo.CurrentPathPoint >= pBot->BotNavInfo.CurrentPath.size())
|
|
{
|
|
ClearBotPath(pBot);
|
|
return;
|
|
}
|
|
|
|
nav_status* BotNavInfo = &pBot->BotNavInfo;
|
|
edict_t* pEdict = pBot->Edict;
|
|
|
|
vector<bot_path_node>::iterator CurrentPathPoint = (BotNavInfo->CurrentPath.begin() + BotNavInfo->CurrentPathPoint);
|
|
|
|
Vector CurrentMoveDest = CurrentPathPoint->Location;
|
|
Vector ClosestPointToPath = vClosestPointOnLine(CurrentPathPoint->FromLocation, CurrentMoveDest, pEdict->v.origin);
|
|
bool bAtOrPastDestination = vEquals(ClosestPointToPath, CurrentMoveDest, 32.0f);
|
|
|
|
// If we've reached our current path point
|
|
if (bAtOrPastDestination)
|
|
{
|
|
// End of the whole path, stop all movement
|
|
if (BotNavInfo->CurrentPathPoint >= (pBot->BotNavInfo.CurrentPath.size() - 1))
|
|
{
|
|
ClearBotMovement(pBot);
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
// Pick the next point in the path
|
|
BotNavInfo->CurrentPathPoint++;
|
|
ClearBotStuck(pBot);
|
|
CurrentPathPoint = (BotNavInfo->CurrentPath.begin() + BotNavInfo->CurrentPathPoint);
|
|
}
|
|
}
|
|
|
|
if (CurrentPathPoint->flag == SAMPLE_POLYFLAGS_LIFT)
|
|
{
|
|
LiftMove(pBot, CurrentPathPoint->FromLocation, CurrentMoveDest);
|
|
return;
|
|
}
|
|
|
|
if (bAllowSkip && CurrentPathPoint->area != SAMPLE_POLYAREA_CROUCH && next(CurrentPathPoint) != BotNavInfo->CurrentPath.end() && next(CurrentPathPoint)->area != SAMPLE_POLYAREA_CROUCH)
|
|
{
|
|
SkipAheadInFlightPath(pBot);
|
|
CurrentPathPoint = (BotNavInfo->CurrentPath.begin() + BotNavInfo->CurrentPathPoint);
|
|
}
|
|
|
|
ClosestPointToPath = vClosestPointOnLine(CurrentPathPoint->FromLocation, CurrentPathPoint->Location, pEdict->v.origin);
|
|
|
|
if (bAllowSkip && vDist3DSq(pBot->Edict->v.origin, ClosestPointToPath) > sqrf(GetPlayerRadius(pBot->Edict) * 3.0f))
|
|
{
|
|
ClearBotPath(pBot);
|
|
return;
|
|
}
|
|
|
|
CurrentMoveDest = CurrentPathPoint->Location;
|
|
Vector MoveFrom = CurrentPathPoint->FromLocation;
|
|
|
|
unsigned char CurrentMoveArea = CurrentPathPoint->area;
|
|
unsigned char NextMoveArea = (next(CurrentPathPoint) != BotNavInfo->CurrentPath.end()) ? next(CurrentPathPoint)->area : CurrentMoveArea;
|
|
|
|
Vector MoveDir = UTIL_GetVectorNormal(CurrentMoveDest - MoveFrom);
|
|
|
|
float CurrentSpeed = vSize3D(pEdict->v.velocity);
|
|
|
|
if (vDist2DSq(pEdict->v.origin, MoveFrom) > sqrf(100.0f) && vDist2DSq(pEdict->v.origin, CurrentMoveDest) > sqrf(100.0f))
|
|
{
|
|
|
|
Vector NewVelocity = MoveDir;
|
|
|
|
if (vDist3DSq(pEdict->v.origin, ClosestPointToPath) > sqrf(16.0f))
|
|
{
|
|
NewVelocity = UTIL_GetVectorNormal((ClosestPointToPath + (MoveDir * 100.0f)) - pEdict->v.origin);
|
|
}
|
|
|
|
NewVelocity = NewVelocity * CurrentSpeed;
|
|
pEdict->v.velocity = NewVelocity;
|
|
|
|
}
|
|
|
|
bool bMustHugGround = (CurrentMoveArea == SAMPLE_POLYAREA_CROUCH || NextMoveArea == SAMPLE_POLYAREA_CROUCH);
|
|
|
|
if (!bMustHugGround || MoveFrom.z <= CurrentMoveDest.z)
|
|
{
|
|
if (CurrentSpeed < 500.f && GetPlayerEnergy(pBot->Edict) > 0.1f)
|
|
{
|
|
if (!(pEdict->v.oldbuttons & IN_JUMP))
|
|
{
|
|
pBot->Button |= IN_JUMP;
|
|
}
|
|
else
|
|
{
|
|
if (gpGlobals->time - BotNavInfo->LastFlapTime < 0.2f)
|
|
{
|
|
pBot->Button |= IN_JUMP;
|
|
}
|
|
else
|
|
{
|
|
BotNavInfo->LastFlapTime = gpGlobals->time;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pBot->Button |= IN_JUMP;
|
|
}
|
|
}
|
|
|
|
Vector LookLocation = CurrentMoveDest;
|
|
|
|
if (pEdict->v.origin.z < CurrentPathPoint->requiredZ)
|
|
{
|
|
LookLocation.z += 32.0f;
|
|
}
|
|
|
|
BotMoveLookAt(pBot, LookLocation);
|
|
|
|
pBot->desiredMovementDir = UTIL_GetForwardVector2D(pEdict->v.v_angle);
|
|
|
|
CheckAndHandleBreakableObstruction(pBot, MoveFrom, CurrentMoveDest, SAMPLE_POLYFLAGS_WALK);
|
|
|
|
CheckAndHandleDoorObstruction(pBot);
|
|
|
|
}
|
|
|
|
void BotFollowSwimPath(AvHAIPlayer* pBot)
|
|
{
|
|
|
|
if (pBot->BotNavInfo.CurrentPath.size() == 0 || pBot->BotNavInfo.CurrentPathPoint >= pBot->BotNavInfo.CurrentPath.size())
|
|
{
|
|
ClearBotPath(pBot);
|
|
return;
|
|
}
|
|
|
|
nav_status* BotNavInfo = &pBot->BotNavInfo;
|
|
edict_t* pEdict = pBot->Edict;
|
|
|
|
vector<bot_path_node>::iterator CurrentPathPoint = (BotNavInfo->CurrentPath.begin() + BotNavInfo->CurrentPathPoint);
|
|
|
|
// If we've reached our current path point
|
|
if (vPointOverlaps3D(CurrentPathPoint->Location, pBot->Edict->v.absmin, pBot->Edict->v.absmax))
|
|
{
|
|
ClearBotStuck(pBot);
|
|
|
|
pBot->BotNavInfo.CurrentPathPoint++;
|
|
|
|
// No more path points, we've reached the end of our path
|
|
if (pBot->BotNavInfo.CurrentPathPoint >= pBot->BotNavInfo.CurrentPath.size())
|
|
{
|
|
ClearBotPath(pBot);
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
CurrentPathPoint = (BotNavInfo->CurrentPath.begin() + BotNavInfo->CurrentPathPoint);
|
|
|
|
if (CurrentPathPoint->flag == SAMPLE_POLYFLAGS_WALK)
|
|
{
|
|
CurrentPathPoint->FromLocation = pBot->Edict->v.origin;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool TargetPointIsInWater = (UTIL_PointContents(CurrentPathPoint->Location) == CONTENTS_WATER || UTIL_PointContents(CurrentPathPoint->Location) == CONTENTS_SLIME);
|
|
|
|
bool bHasNextPoint = (next(CurrentPathPoint) != BotNavInfo->CurrentPath.end());
|
|
bool NextPointInWater = (bHasNextPoint) ? UTIL_PointContents(next(CurrentPathPoint)->Location) == CONTENTS_WATER : TargetPointIsInWater;
|
|
|
|
bool bShouldSurface = (bHasNextPoint && !NextPointInWater && vDist2DSq(pEdict->v.origin, next(CurrentPathPoint)->FromLocation) < sqrf(100.0f));
|
|
|
|
if (TargetPointIsInWater && !bShouldSurface)
|
|
{
|
|
BotMoveLookAt(pBot, CurrentPathPoint->Location);
|
|
pBot->desiredMovementDir = UTIL_GetVectorNormal2D(CurrentPathPoint->Location - pEdict->v.origin);
|
|
|
|
unsigned char NextArea = (next(CurrentPathPoint) != BotNavInfo->CurrentPath.end()) ? next(CurrentPathPoint)->area : SAMPLE_POLYAREA_GROUND;
|
|
|
|
if (CurrentPathPoint->area == SAMPLE_POLYAREA_CROUCH || (NextArea == SAMPLE_POLYAREA_CROUCH && vDist2DSq(pEdict->v.origin, next(CurrentPathPoint)->FromLocation) < sqrf(50.0f)))
|
|
{
|
|
pBot->Button |= IN_DUCK;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
float WaterLevel = UTIL_WaterLevel(pEdict->v.origin, pEdict->v.origin.z, pEdict->v.origin.z + 500.0f);
|
|
|
|
float WaterDiff = WaterLevel - pEdict->v.origin.z;
|
|
|
|
// If we're below the waterline by a significant amount, then swim up to surface before we move on
|
|
if (WaterDiff > 5.0f)
|
|
{
|
|
Vector MoveDir = UTIL_GetVectorNormal2D(CurrentPathPoint->Location - pEdict->v.origin);
|
|
pBot->desiredMovementDir = MoveDir;
|
|
|
|
if (WaterDiff > 10.0f)
|
|
{
|
|
BotMoveLookAt(pBot, pEdict->v.origin + (MoveDir * 5.0f) + Vector(0.0f, 0.0f, 100.0f));
|
|
}
|
|
else
|
|
{
|
|
BotMoveLookAt(pBot, pBot->CurrentEyePosition + (MoveDir * 50.0f) + Vector(0.0f, 0.0f, 50.0f));
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
// We're at the surface, now tackle the path the usual way
|
|
if (pBot->BotNavInfo.NavProfile.bFlyingProfile)
|
|
{
|
|
BotFollowFlightPath(pBot, true);
|
|
}
|
|
else
|
|
{
|
|
BotFollowPath(pBot);
|
|
}
|
|
|
|
}
|
|
|
|
void BotFollowPath(AvHAIPlayer* pBot)
|
|
{
|
|
if (pBot->BotNavInfo.CurrentPath.size() == 0 || pBot->BotNavInfo.CurrentPathPoint >= pBot->BotNavInfo.CurrentPath.size())
|
|
{
|
|
ClearBotPath(pBot);
|
|
return;
|
|
}
|
|
|
|
nav_status* BotNavInfo = &pBot->BotNavInfo;
|
|
edict_t* pEdict = pBot->Edict;
|
|
|
|
|
|
// If we've reached our current path point
|
|
if (HasBotReachedPathPoint(pBot))
|
|
{
|
|
ClearBotStuck(pBot);
|
|
|
|
pBot->BotNavInfo.CurrentPathPoint++;
|
|
|
|
// No more path points, we've reached the end of our path
|
|
if (pBot->BotNavInfo.CurrentPathPoint >= pBot->BotNavInfo.CurrentPath.size())
|
|
{
|
|
ClearBotPath(pBot);
|
|
return;
|
|
}
|
|
}
|
|
|
|
bot_path_node CurrentNode = pBot->BotNavInfo.CurrentPath[pBot->BotNavInfo.CurrentPathPoint];
|
|
|
|
if (IsPlayerStandingOnPlayer(pBot->Edict) && CurrentNode.flag != SAMPLE_POLYFLAGS_WALLCLIMB && CurrentNode.flag != SAMPLE_POLYFLAGS_LADDER)
|
|
{
|
|
if (pBot->Edict->v.groundentity->v.velocity.Length2D() > 10.0f)
|
|
{
|
|
pBot->desiredMovementDir = UTIL_GetVectorNormal2D(-pBot->Edict->v.groundentity->v.velocity);
|
|
return;
|
|
}
|
|
MoveToWithoutNav(pBot, CurrentNode.Location);
|
|
return;
|
|
}
|
|
|
|
vector<AvHPlayer*> PotentialRiders = AITAC_GetAllPlayersOfTeamInArea(pBot->Player->GetTeam(), pBot->Edict->v.origin, pBot->Edict->v.size.Length(), false, pBot->Edict, AVH_USER3_NONE);
|
|
|
|
for (auto it = PotentialRiders.begin(); it != PotentialRiders.end(); it++)
|
|
{
|
|
if ((*it)->pev->groundentity == pBot->Edict)
|
|
{
|
|
if (vDist2DSq(pBot->Edict->v.origin, CurrentNode.FromLocation) > sqrf(GetPlayerRadius(pBot->Edict)))
|
|
{
|
|
MoveToWithoutNav(pBot, CurrentNode.FromLocation);
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
if (pBot->BotNavInfo.CurrentPathPoint > 0)
|
|
{
|
|
bot_path_node PrevNode = pBot->BotNavInfo.CurrentPath[pBot->BotNavInfo.CurrentPathPoint - 1];
|
|
MoveToWithoutNav(pBot, PrevNode.FromLocation);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (IsPlayerLerk(pBot->Edict))
|
|
{
|
|
if (CurrentNode.flag != SAMPLE_POLYFLAGS_WALK && CurrentNode.flag != SAMPLE_POLYFLAGS_LIFT)
|
|
{
|
|
BotFollowFlightPath(pBot, false);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (IsBotOffPath(pBot))
|
|
{
|
|
MoveToWithoutNav(pBot, CurrentNode.Location);
|
|
pBot->BotNavInfo.StuckInfo.bPathFollowFailed = true;
|
|
ClearBotPath(pBot);
|
|
return;
|
|
}
|
|
|
|
pBot->BotNavInfo.StuckInfo.bPathFollowFailed = false;
|
|
|
|
Vector MoveTo = CurrentNode.Location;
|
|
|
|
NewMove(pBot);
|
|
|
|
}
|
|
|
|
void PerformUnstuckMove(AvHAIPlayer* pBot, const Vector MoveDestination)
|
|
{
|
|
|
|
Vector FwdDir = UTIL_GetVectorNormal2D(MoveDestination - pBot->Edict->v.origin);
|
|
pBot->desiredMovementDir = FwdDir;
|
|
|
|
Vector HeadLocation = GetPlayerTopOfCollisionHull(pBot->Edict, false);
|
|
|
|
bool bMustCrouch = false;
|
|
|
|
if (!IsPlayerSkulk(pBot->Edict) && !IsPlayerGorge(pBot->Edict) && !UTIL_QuickTrace(pBot->Edict, HeadLocation, (HeadLocation + (FwdDir * 50.0f))))
|
|
{
|
|
pBot->Button |= IN_DUCK;
|
|
bMustCrouch = true;
|
|
}
|
|
|
|
Vector MoveRightVector = UTIL_GetVectorNormal2D(UTIL_GetCrossProduct(FwdDir, UP_VECTOR));
|
|
|
|
Vector BotRightSide = (pBot->Edict->v.origin + (MoveRightVector * GetPlayerRadius(pBot->Player)));
|
|
Vector BotLeftSide = (pBot->Edict->v.origin - (MoveRightVector * GetPlayerRadius(pBot->Player)));
|
|
|
|
bool bBlockedLeftSide = !UTIL_QuickTrace(pBot->Edict, BotRightSide, BotRightSide + (FwdDir * 50.0f));
|
|
bool bBlockedRightSide = !UTIL_QuickTrace(pBot->Edict, BotLeftSide, BotLeftSide + (FwdDir * 50.0f));
|
|
|
|
if (!bMustCrouch)
|
|
{
|
|
BotJump(pBot);
|
|
}
|
|
|
|
|
|
if (bBlockedRightSide && !bBlockedLeftSide)
|
|
{
|
|
pBot->desiredMovementDir = MoveRightVector;
|
|
return;
|
|
}
|
|
else if (!bBlockedRightSide && bBlockedLeftSide)
|
|
{
|
|
pBot->desiredMovementDir = -MoveRightVector;
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
bBlockedLeftSide = !UTIL_QuickTrace(pBot->Edict, BotRightSide, BotRightSide - (MoveRightVector * 50.0f));
|
|
bBlockedRightSide = !UTIL_QuickTrace(pBot->Edict, BotLeftSide, BotLeftSide + (MoveRightVector * 50.0f));
|
|
|
|
if (bBlockedRightSide)
|
|
{
|
|
pBot->desiredMovementDir = -MoveRightVector;
|
|
}
|
|
else if (bBlockedLeftSide)
|
|
{
|
|
pBot->desiredMovementDir = MoveRightVector;
|
|
}
|
|
else
|
|
{
|
|
pBot->desiredMovementDir = FwdDir;
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
bool BotIsAtLocation(const AvHAIPlayer* pBot, const Vector Destination)
|
|
{
|
|
if (vIsZero(Destination) || !(pBot->Edict->v.flags & FL_ONGROUND)) { return false; }
|
|
|
|
return (vDist2DSq(pBot->Edict->v.origin, Destination) < sqrf(GetPlayerRadius(pBot->Player)) && fabs(pBot->CurrentFloorPosition.z - Destination.z) <= GetPlayerHeight(pBot->Edict, false));
|
|
}
|
|
|
|
Vector UTIL_ProjectPointToNavmesh(const Vector Location)
|
|
{
|
|
const dtNavMeshQuery* m_navQuery = UTIL_GetNavMeshQueryForProfile(BaseNavProfiles[ALL_NAV_PROFILE]);
|
|
const dtNavMesh* m_navMesh = UTIL_GetNavMeshForProfile(BaseNavProfiles[ALL_NAV_PROFILE]);
|
|
const dtQueryFilter* m_navFilter = &BaseNavProfiles[ALL_NAV_PROFILE].Filters;
|
|
|
|
if (!m_navQuery) { return g_vecZero; }
|
|
|
|
Vector PointToProject = Location;
|
|
|
|
float pCheckLoc[3] = { PointToProject.x, PointToProject.z, -PointToProject.y };
|
|
|
|
dtPolyRef FoundPoly;
|
|
float NavNearest[3];
|
|
float Extents[3] = { 400.0f, 400.0f, 400.0f };
|
|
|
|
dtStatus success = m_navQuery->findNearestPoly(pCheckLoc, Extents, m_navFilter, &FoundPoly, NavNearest);
|
|
|
|
if (FoundPoly > 0 && dtStatusSucceed(success))
|
|
{
|
|
return Vector(NavNearest[0], -NavNearest[2], NavNearest[1]);
|
|
}
|
|
else
|
|
{
|
|
int PointContents = UTIL_PointContents(PointToProject);
|
|
|
|
if (PointContents != CONTENTS_SOLID && PointContents != CONTENTS_LADDER)
|
|
{
|
|
Vector TraceHit = UTIL_GetTraceHitLocation(PointToProject + Vector(0.0f, 0.0f, 1.0f), PointToProject - Vector(0.0f, 0.0f, 1000.0f));
|
|
|
|
PointToProject = (TraceHit != g_vecZero) ? TraceHit : Location;
|
|
}
|
|
|
|
float pRecheckLoc[3] = { PointToProject.x, PointToProject.z, -PointToProject.y };
|
|
|
|
dtStatus successRetry = m_navQuery->findNearestPoly(pRecheckLoc, Extents, m_navFilter, &FoundPoly, NavNearest);
|
|
|
|
if (FoundPoly > 0 && dtStatusSucceed(success))
|
|
{
|
|
return Vector(NavNearest[0], -NavNearest[2], NavNearest[1]);
|
|
}
|
|
else
|
|
{
|
|
return g_vecZero;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
Vector UTIL_ProjectPointToNavmesh(const Vector Location, const Vector Extents)
|
|
{
|
|
const dtNavMeshQuery* m_navQuery = UTIL_GetNavMeshQueryForProfile(BaseNavProfiles[ALL_NAV_PROFILE]);
|
|
const dtNavMesh* m_navMesh = UTIL_GetNavMeshForProfile(BaseNavProfiles[ALL_NAV_PROFILE]);
|
|
const dtQueryFilter* m_navFilter = &BaseNavProfiles[ALL_NAV_PROFILE].Filters;
|
|
|
|
if (!m_navQuery) { return g_vecZero; }
|
|
|
|
Vector PointToProject = Location;
|
|
|
|
float pCheckLoc[3] = { PointToProject.x, PointToProject.z, -PointToProject.y };
|
|
|
|
dtPolyRef FoundPoly;
|
|
float NavNearest[3];
|
|
float fExtents[3] = { Extents.x, Extents.z, Extents.y };
|
|
|
|
dtStatus success = m_navQuery->findNearestPoly(pCheckLoc, fExtents, m_navFilter, &FoundPoly, NavNearest);
|
|
|
|
if (FoundPoly > 0 && dtStatusSucceed(success))
|
|
{
|
|
return Vector(NavNearest[0], -NavNearest[2], NavNearest[1]);
|
|
}
|
|
else
|
|
{
|
|
int PointContents = UTIL_PointContents(PointToProject);
|
|
|
|
if (PointContents != CONTENTS_SOLID && PointContents != CONTENTS_LADDER)
|
|
{
|
|
Vector TraceHit = UTIL_GetTraceHitLocation(PointToProject + Vector(0.0f, 0.0f, 1.0f), PointToProject - Vector(0.0f, 0.0f, 1000.0f));
|
|
|
|
PointToProject = (TraceHit != g_vecZero) ? TraceHit : Location;
|
|
}
|
|
|
|
float pRecheckLoc[3] = { PointToProject.x, PointToProject.z, -PointToProject.y };
|
|
|
|
dtStatus successRetry = m_navQuery->findNearestPoly(pRecheckLoc, fExtents, m_navFilter, &FoundPoly, NavNearest);
|
|
|
|
if (FoundPoly > 0 && dtStatusSucceed(success))
|
|
{
|
|
return Vector(NavNearest[0], -NavNearest[2], NavNearest[1]);
|
|
}
|
|
else
|
|
{
|
|
return g_vecZero;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
Vector UTIL_ProjectPointToNavmesh(const Vector Location, const nav_profile &NavProfile)
|
|
{
|
|
const dtNavMeshQuery* m_navQuery = UTIL_GetNavMeshQueryForProfile(NavProfile);
|
|
const dtNavMesh* m_navMesh = UTIL_GetNavMeshForProfile(NavProfile);
|
|
const dtQueryFilter* m_navFilter = &NavProfile.Filters;
|
|
|
|
if (!m_navQuery) { return g_vecZero; }
|
|
|
|
Vector PointToProject = Location;
|
|
|
|
float pCheckLoc[3] = { PointToProject.x, PointToProject.z, -PointToProject.y };
|
|
|
|
dtPolyRef FoundPoly;
|
|
float NavNearest[3];
|
|
|
|
dtStatus success = m_navQuery->findNearestPoly(pCheckLoc, pExtents, m_navFilter, &FoundPoly, NavNearest);
|
|
|
|
if (FoundPoly > 0 && dtStatusSucceed(success))
|
|
{
|
|
return Vector(NavNearest[0], -NavNearest[2], NavNearest[1]);
|
|
}
|
|
else
|
|
{
|
|
int PointContents = UTIL_PointContents(PointToProject);
|
|
|
|
if (PointContents != CONTENTS_SOLID && PointContents != CONTENTS_LADDER)
|
|
{
|
|
Vector TraceHit = UTIL_GetTraceHitLocation(PointToProject + Vector(0.0f, 0.0f, 1.0f), PointToProject - Vector(0.0f, 0.0f, 1000.0f));
|
|
|
|
PointToProject = (TraceHit != g_vecZero) ? TraceHit : Location;
|
|
}
|
|
|
|
float pRecheckLoc[3] = { PointToProject.x, PointToProject.z, -PointToProject.y };
|
|
|
|
dtStatus successRetry = m_navQuery->findNearestPoly(pRecheckLoc, pExtents, m_navFilter, &FoundPoly, NavNearest);
|
|
|
|
if (FoundPoly > 0 && dtStatusSucceed(success))
|
|
{
|
|
return Vector(NavNearest[0], -NavNearest[2], NavNearest[1]);
|
|
}
|
|
else
|
|
{
|
|
return g_vecZero;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
Vector UTIL_ProjectPointToNavmesh(const Vector Location, const Vector Extents, const nav_profile& NavProfile)
|
|
{
|
|
const dtNavMeshQuery* m_navQuery = UTIL_GetNavMeshQueryForProfile(NavProfile);
|
|
const dtNavMesh* m_navMesh = UTIL_GetNavMeshForProfile(NavProfile);
|
|
const dtQueryFilter* m_navFilter = &NavProfile.Filters;
|
|
|
|
if (!m_navQuery) { return g_vecZero; }
|
|
|
|
Vector PointToProject = Location;
|
|
|
|
float pCheckLoc[3] = { PointToProject.x, PointToProject.z, -PointToProject.y };
|
|
|
|
dtPolyRef FoundPoly;
|
|
float NavNearest[3];
|
|
|
|
float fExtents[3] = { Extents.x, Extents.z, Extents.y };
|
|
|
|
dtStatus success = m_navQuery->findNearestPoly(pCheckLoc, fExtents, m_navFilter, &FoundPoly, NavNearest);
|
|
|
|
if (FoundPoly > 0 && dtStatusSucceed(success))
|
|
{
|
|
return Vector(NavNearest[0], -NavNearest[2], NavNearest[1]);
|
|
}
|
|
else
|
|
{
|
|
int PointContents = UTIL_PointContents(PointToProject);
|
|
|
|
if (PointContents != CONTENTS_SOLID && PointContents != CONTENTS_LADDER)
|
|
{
|
|
Vector TraceHit = UTIL_GetTraceHitLocation(PointToProject + Vector(0.0f, 0.0f, 1.0f), PointToProject - Vector(0.0f, 0.0f, 1000.0f));
|
|
|
|
PointToProject = (TraceHit != g_vecZero) ? TraceHit : Location;
|
|
}
|
|
|
|
float pRecheckLoc[3] = { PointToProject.x, PointToProject.z, -PointToProject.y };
|
|
|
|
dtStatus successRetry = m_navQuery->findNearestPoly(pRecheckLoc, fExtents, m_navFilter, &FoundPoly, NavNearest);
|
|
|
|
if (FoundPoly > 0 && dtStatusSucceed(success))
|
|
{
|
|
return Vector(NavNearest[0], -NavNearest[2], NavNearest[1]);
|
|
}
|
|
else
|
|
{
|
|
return g_vecZero;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
bool UTIL_PointIsOnNavmesh(const Vector Location, const nav_profile &NavProfile)
|
|
{
|
|
const dtNavMeshQuery* m_navQuery = UTIL_GetNavMeshQueryForProfile(NavProfile);
|
|
const dtNavMesh* m_navMesh = UTIL_GetNavMeshForProfile(NavProfile);
|
|
const dtQueryFilter* m_navFilter = &NavProfile.Filters;
|
|
|
|
if (!m_navQuery) { return false; }
|
|
|
|
float pCheckLoc[3] = { Location.x, Location.z, -Location.y };
|
|
|
|
dtPolyRef FoundPoly;
|
|
float NavNearest[3];
|
|
|
|
float pCheckExtents[3] = { 5.0f, 50.0f, 5.0f };
|
|
|
|
dtStatus success = m_navQuery->findNearestPoly(pCheckLoc, pCheckExtents, m_navFilter, &FoundPoly, NavNearest);
|
|
|
|
return dtStatusSucceed(success) && FoundPoly > 0;
|
|
|
|
}
|
|
|
|
bool UTIL_PointIsOnNavmesh(const nav_profile& NavProfile, const Vector Location, const Vector SearchExtents)
|
|
{
|
|
const dtNavMeshQuery* m_navQuery = UTIL_GetNavMeshQueryForProfile(NavProfile);
|
|
const dtNavMesh* m_navMesh = UTIL_GetNavMeshForProfile(NavProfile);
|
|
const dtQueryFilter* m_navFilter = &NavProfile.Filters;
|
|
|
|
if (!m_navQuery) { return false; }
|
|
|
|
float pCheckLoc[3] = { Location.x, Location.z, -Location.y };
|
|
|
|
dtPolyRef FoundPoly;
|
|
float NavNearest[3];
|
|
|
|
float pCheckExtents[3] = { SearchExtents.x, SearchExtents.z, SearchExtents.y };
|
|
|
|
dtStatus success = m_navQuery->findNearestPoly(pCheckLoc, pCheckExtents, m_navFilter, &FoundPoly, NavNearest);
|
|
|
|
return dtStatusSucceed(success) && FoundPoly > 0;
|
|
|
|
}
|
|
|
|
void HandlePlayerAvoidance(AvHAIPlayer* pBot, const Vector MoveDestination)
|
|
{
|
|
// Don't handle player avoidance if climbing a wall, ladder or in the air, as it will mess up the move and cause them to get stuck most likely
|
|
if (pBot->Player->IsOnLadder() || IsPlayerClimbingWall(pBot->Edict) || !pBot->BotNavInfo.IsOnGround) { return; }
|
|
|
|
float MyRadius = GetPlayerRadius(pBot->Edict);
|
|
|
|
const Vector BotLocation = pBot->Edict->v.origin;
|
|
const Vector MoveDir = UTIL_GetVectorNormal2D((MoveDestination - pBot->Edict->v.origin));
|
|
|
|
for (int i = 1; i <= gpGlobals->maxClients; i++)
|
|
{
|
|
edict_t* OtherPlayer = INDEXENT(i);
|
|
|
|
if (!FNullEnt(OtherPlayer) && OtherPlayer != pBot->Edict && IsPlayerActiveInGame(OtherPlayer))
|
|
{
|
|
float OtherPlayerRadius = GetPlayerRadius(OtherPlayer);
|
|
|
|
float avoidDistSq = sqrf(MyRadius + OtherPlayerRadius + 16.0f);
|
|
|
|
// Don't do avoidance for a player if they're moving in broadly the same direction as us
|
|
Vector OtherMoveDir = GetPlayerAttemptedMoveDirection(OtherPlayer);
|
|
|
|
if (vDist3DSq(BotLocation, OtherPlayer->v.origin) <= avoidDistSq)
|
|
{
|
|
Vector BlockAngle = UTIL_GetVectorNormal2D(OtherPlayer->v.origin - BotLocation);
|
|
float MoveBlockDot = UTIL_GetDotProduct2D(MoveDir, BlockAngle);
|
|
|
|
// If other player is between us and our destination
|
|
if (MoveBlockDot > 0.0f)
|
|
{
|
|
// If the other player is in the air or on top of us, back up and let them land
|
|
if (!(OtherPlayer->v.flags & FL_ONGROUND) || OtherPlayer->v.groundentity == pBot->Edict)
|
|
{
|
|
pBot->desiredMovementDir = UTIL_GetVectorNormal2D(BotLocation - OtherPlayer->v.origin);
|
|
return;
|
|
}
|
|
|
|
// Determine if we should move left or right to clear them
|
|
Vector MoveRightVector = UTIL_GetCrossProduct(MoveDir, UP_VECTOR);
|
|
|
|
int modifier = vPointOnLine(pBot->Edict->v.origin, MoveDestination, OtherPlayer->v.origin);
|
|
|
|
float OtherPersonDistFromLine = vDistanceFromLine2D(pBot->Edict->v.origin, MoveDestination, OtherPlayer->v.origin);
|
|
|
|
if (modifier == 0) { modifier = 1; }
|
|
|
|
Vector PreferredMoveDir = (MoveRightVector * modifier);
|
|
|
|
float TraceLength = OtherPersonDistFromLine + (fmaxf(MyRadius, OtherPlayerRadius) * 2.0f);
|
|
|
|
|
|
// First see if we have enough room to move in our preferred avoidance direction
|
|
if (UTIL_TraceNav(pBot->BotNavInfo.NavProfile, BotLocation, BotLocation + (PreferredMoveDir * TraceLength), 0.0f))
|
|
{
|
|
pBot->desiredMovementDir = PreferredMoveDir;
|
|
return;
|
|
}
|
|
|
|
// Then try the opposite direction
|
|
if (UTIL_TraceNav(pBot->BotNavInfo.NavProfile, BotLocation, BotLocation - (PreferredMoveDir * TraceLength), 0.0f))
|
|
{
|
|
pBot->desiredMovementDir = -PreferredMoveDir;
|
|
return;
|
|
}
|
|
|
|
// If we have a point we can go back to, and we can reach it, then go for it. Otherwise, keep pushing on and hope the other guy moves
|
|
if (!vIsZero(pBot->BotNavInfo.LastOpenLocation))
|
|
{
|
|
if (UTIL_PointIsReachable(pBot->BotNavInfo.NavProfile, pBot->Edict->v.origin, pBot->BotNavInfo.LastOpenLocation, GetPlayerRadius(pBot->Edict)))
|
|
{
|
|
NAV_SetMoveMovementTask(pBot, pBot->BotNavInfo.LastOpenLocation, nullptr);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
float UTIL_GetPathCostBetweenLocations(const nav_profile &NavProfile , const Vector FromLocation, const Vector ToLocation)
|
|
{
|
|
vector<bot_path_node> path;
|
|
path.clear();
|
|
|
|
dtStatus pathFindResult = FindPathClosestToPoint(NavProfile, FromLocation, ToLocation, path, max_ai_use_reach);
|
|
|
|
if (!dtStatusSucceed(pathFindResult)) { return 0.0f; }
|
|
|
|
int currPathPoint = 1;
|
|
float result = 0.0f;
|
|
|
|
for (auto it = path.begin(); it != path.end(); it++)
|
|
{
|
|
result += vDist2DSq(it->FromLocation, it->Location) * NavProfile.Filters.getAreaCost(it->area);
|
|
}
|
|
|
|
return sqrtf(result);
|
|
}
|
|
|
|
void ClearBotMovement(AvHAIPlayer* pBot)
|
|
{
|
|
pBot->BotNavInfo.TargetDestination = g_vecZero;
|
|
pBot->BotNavInfo.ActualMoveDestination = g_vecZero;
|
|
|
|
ClearBotPath(pBot);
|
|
ClearBotStuck(pBot);
|
|
ClearBotStuckMovement(pBot);
|
|
|
|
pBot->LastPosition = pBot->Edict->v.origin;
|
|
pBot->TimeSinceLastMovement = 0.0f;
|
|
}
|
|
|
|
void ClearBotStuck(AvHAIPlayer* pBot)
|
|
{
|
|
pBot->BotNavInfo.LastDistanceFromDestination = 0.0f;
|
|
pBot->BotNavInfo.LastStuckCheckTime = gpGlobals->time;
|
|
pBot->BotNavInfo.TotalStuckTime = 0.0f;
|
|
pBot->BotNavInfo.UnstuckMoveLocation = g_vecZero;
|
|
pBot->BotNavInfo.StuckCheckMoveLocation = g_vecZero;
|
|
}
|
|
|
|
bool BotRecalcPath(AvHAIPlayer* pBot, const Vector Destination)
|
|
{
|
|
ClearBotPath(pBot);
|
|
|
|
Vector ValidNavmeshPoint = UTIL_ProjectPointToNavmesh(Destination, Vector(max_ai_use_reach, max_ai_use_reach, max_ai_use_reach), pBot->BotNavInfo.NavProfile);
|
|
|
|
// We can't actually get close enough to this point to consider it "reachable"
|
|
if (vIsZero(ValidNavmeshPoint))
|
|
{
|
|
sprintf(pBot->PathStatus, "Could not project destination to navmesh");
|
|
return false;
|
|
}
|
|
|
|
dtStatus FoundPath = FindPathClosestToPoint(pBot, pBot->BotNavInfo.MoveStyle, pBot->CurrentFloorPosition, ValidNavmeshPoint, pBot->BotNavInfo.CurrentPath, max_ai_use_reach);
|
|
|
|
if (dtStatusSucceed(FoundPath) && pBot->BotNavInfo.CurrentPath.size() > 0)
|
|
{
|
|
pBot->BotNavInfo.TargetDestination = Destination;
|
|
pBot->BotNavInfo.ActualMoveDestination = pBot->BotNavInfo.CurrentPath.back().Location;
|
|
|
|
if (next(pBot->BotNavInfo.CurrentPath.begin()) == pBot->BotNavInfo.CurrentPath.end() || vDist2DSq(pBot->BotNavInfo.CurrentPath.front().Location, pBot->Edict->v.origin) > sqrf(GetPlayerRadius(pBot->Player)))
|
|
{
|
|
pBot->BotNavInfo.CurrentPathPoint = 0;
|
|
}
|
|
else
|
|
{
|
|
pBot->BotNavInfo.CurrentPathPoint = 1;
|
|
}
|
|
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
float UTIL_FindZHeightForWallClimb(const Vector ClimbStart, const Vector ClimbEnd, const int HullNum)
|
|
{
|
|
TraceResult hit;
|
|
|
|
Vector StartTrace = ClimbEnd;
|
|
|
|
UTIL_TraceLine(ClimbEnd, ClimbEnd - Vector(0.0f, 0.0f, 50.0f), ignore_monsters, nullptr, &hit);
|
|
|
|
if (hit.fAllSolid || hit.fStartSolid || hit.flFraction < 1.0f)
|
|
{
|
|
StartTrace.z = hit.vecEndPos.z + 18.0f;
|
|
}
|
|
|
|
Vector EndTrace = ClimbStart;
|
|
EndTrace.z = StartTrace.z;
|
|
|
|
Vector CurrTraceStart = StartTrace;
|
|
|
|
UTIL_TraceHull(StartTrace, EndTrace, ignore_monsters, HullNum, nullptr, &hit);
|
|
|
|
if (hit.flFraction >= 1.0f && !hit.fAllSolid && !hit.fStartSolid)
|
|
{
|
|
return StartTrace.z;
|
|
}
|
|
else
|
|
{
|
|
int maxTests = 100;
|
|
int testCount = 0;
|
|
|
|
while ((hit.flFraction < 1.0f || hit.fStartSolid || hit.fAllSolid) && testCount < maxTests)
|
|
{
|
|
CurrTraceStart.z += 1.0f;
|
|
EndTrace.z = CurrTraceStart.z;
|
|
UTIL_TraceHull(CurrTraceStart, EndTrace, ignore_monsters, HullNum, nullptr, &hit);
|
|
testCount++;
|
|
}
|
|
|
|
if (hit.flFraction >= 1.0f && !hit.fStartSolid)
|
|
{
|
|
return CurrTraceStart.z;
|
|
}
|
|
else
|
|
{
|
|
return StartTrace.z;
|
|
}
|
|
}
|
|
|
|
return StartTrace.z;
|
|
}
|
|
|
|
void ClearBotPath(AvHAIPlayer* pBot)
|
|
{
|
|
pBot->BotNavInfo.CurrentPath.clear();
|
|
pBot->BotNavInfo.CurrentPathPoint = 0;
|
|
|
|
pBot->BotNavInfo.SpecialMovementFlags = 0;
|
|
|
|
pBot->BotNavInfo.bNavProfileChanged = false;
|
|
|
|
pBot->BotNavInfo.TargetDestination = g_vecZero;
|
|
pBot->BotNavInfo.PathDestination = g_vecZero;
|
|
}
|
|
|
|
void ClearBotStuckMovement(AvHAIPlayer* pBot)
|
|
{
|
|
pBot->BotNavInfo.UnstuckMoveLocation = g_vecZero;
|
|
}
|
|
|
|
void BotMovementInputs(AvHAIPlayer* pBot)
|
|
{
|
|
if (vIsZero(pBot->desiredMovementDir)) { return; }
|
|
|
|
edict_t* pEdict = pBot->Edict;
|
|
|
|
UTIL_NormalizeVector2D(&pBot->desiredMovementDir);
|
|
|
|
float currentYaw = pBot->Edict->v.v_angle.y;
|
|
float moveDelta = UTIL_VecToAngles(pBot->desiredMovementDir).y;
|
|
float angleDelta = currentYaw - moveDelta;
|
|
|
|
float botSpeed = (pBot->BotNavInfo.bShouldWalk) ? (pBot->Edict->v.maxspeed * 0.4f) : pBot->Edict->v.maxspeed;
|
|
|
|
if (angleDelta < -180.0f)
|
|
{
|
|
angleDelta += 360.0f;
|
|
}
|
|
else if (angleDelta > 180.0f)
|
|
{
|
|
angleDelta -= 360.0f;
|
|
}
|
|
|
|
if (angleDelta >= -22.5f && angleDelta < 22.5f)
|
|
{
|
|
pBot->ForwardMove = botSpeed;
|
|
pBot->SideMove = 0.0f;
|
|
pBot->Button |= IN_FORWARD;
|
|
}
|
|
else if (angleDelta >= 22.5f && angleDelta < 67.5f)
|
|
{
|
|
pBot->ForwardMove = botSpeed;
|
|
pBot->SideMove = botSpeed;
|
|
pBot->Button |= IN_FORWARD;
|
|
pBot->Button |= IN_MOVERIGHT;
|
|
}
|
|
else if (angleDelta >= 67.5f && angleDelta < 112.5f)
|
|
{
|
|
pBot->ForwardMove = 0.0f;
|
|
pBot->SideMove = botSpeed;
|
|
pBot->Button |= IN_MOVERIGHT;
|
|
}
|
|
else if (angleDelta >= 112.5f && angleDelta < 157.5f)
|
|
{
|
|
pBot->ForwardMove = -botSpeed;
|
|
pBot->SideMove = botSpeed;
|
|
pBot->Button |= IN_BACK;
|
|
pBot->Button |= IN_MOVERIGHT;
|
|
}
|
|
else if (angleDelta >= 157.5f || angleDelta <= -157.5f)
|
|
{
|
|
pBot->ForwardMove = -botSpeed;
|
|
pBot->SideMove = 0.0f;
|
|
pBot->Button |= IN_BACK;
|
|
}
|
|
else if (angleDelta >= -157.5f && angleDelta < -112.5f)
|
|
{
|
|
pBot->ForwardMove = -botSpeed;
|
|
pBot->SideMove = -botSpeed;
|
|
pBot->Button |= IN_BACK;
|
|
pBot->Button |= IN_MOVELEFT;
|
|
}
|
|
else if (angleDelta >= -112.5f && angleDelta < -67.5f)
|
|
{
|
|
pBot->ForwardMove = 0.0f;
|
|
pBot->SideMove = -botSpeed;
|
|
pBot->Button |= IN_MOVELEFT;
|
|
}
|
|
else if (angleDelta >= -67.5f && angleDelta < -22.5f)
|
|
{
|
|
pBot->ForwardMove = botSpeed;
|
|
pBot->SideMove = -botSpeed;
|
|
pBot->Button |= IN_FORWARD;
|
|
pBot->Button |= IN_MOVELEFT;
|
|
}
|
|
|
|
if (pBot->BotNavInfo.CurrentPath.size() == 0 || pBot->BotNavInfo.CurrentPathPoint >= pBot->BotNavInfo.CurrentPath.size() || pBot->BotNavInfo.CurrentPath[pBot->BotNavInfo.CurrentPathPoint].flag != SAMPLE_POLYFLAGS_LADDER)
|
|
{
|
|
if (pBot->Player->IsOnLadder())
|
|
{
|
|
BotJump(pBot);
|
|
}
|
|
}
|
|
}
|
|
|
|
void OnBotStartLadder(AvHAIPlayer* pBot)
|
|
{
|
|
pBot->CurrentLadderNormal = UTIL_GetNearestLadderNormal(pBot->Edict);
|
|
}
|
|
|
|
void OnBotEndLadder(AvHAIPlayer* pBot)
|
|
{
|
|
pBot->CurrentLadderNormal = g_vecZero;
|
|
}
|
|
|
|
Vector UTIL_GetFurthestVisiblePointOnPath(const AvHAIPlayer* pBot)
|
|
{
|
|
if (pBot->BotNavInfo.CurrentPath.size() == 0 || pBot->BotNavInfo.CurrentPathPoint >= pBot->BotNavInfo.CurrentPath.size()) { return g_vecZero; }
|
|
|
|
vector<bot_path_node>::const_iterator CurrentPathPoint = (pBot->BotNavInfo.CurrentPath.begin() + pBot->BotNavInfo.CurrentPathPoint);
|
|
|
|
if (CurrentPathPoint == prev(pBot->BotNavInfo.CurrentPath.end()))
|
|
{
|
|
Vector MoveDir = UTIL_GetVectorNormal2D(CurrentPathPoint->Location - pBot->Edict->v.origin);
|
|
return CurrentPathPoint->Location + (MoveDir * 300.0f);
|
|
}
|
|
|
|
Vector FurthestVisiblePoint = CurrentPathPoint->Location;
|
|
FurthestVisiblePoint.z = pBot->CurrentEyePosition.z;
|
|
|
|
for (auto it = next(CurrentPathPoint); it != pBot->BotNavInfo.CurrentPath.end(); it++)
|
|
{
|
|
Vector CheckPoint = it->Location + Vector(0.0f, 0.0f, 32.0f);
|
|
|
|
if (UTIL_QuickTrace(pBot->Edict, pBot->CurrentEyePosition, CheckPoint))
|
|
{
|
|
FurthestVisiblePoint = CheckPoint;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
return FurthestVisiblePoint;
|
|
}
|
|
|
|
Vector UTIL_GetFurthestVisiblePointOnLineWithHull(const Vector ViewerLocation, const Vector LineStart, const Vector LineEnd, int HullNumber)
|
|
{
|
|
Vector Dir = UTIL_GetVectorNormal(LineEnd - LineStart);
|
|
|
|
float Dist = vDist3D(LineStart, LineEnd);
|
|
int Steps = (int)floorf(Dist / 10.0f);
|
|
|
|
if (Steps == 0) { return g_vecZero; }
|
|
|
|
Vector FinalView = g_vecZero;
|
|
Vector ThisView = LineStart;
|
|
|
|
for (int i = 0; i < Steps; i++)
|
|
{
|
|
if (UTIL_QuickHullTrace(NULL, ViewerLocation, ThisView, HullNumber))
|
|
{
|
|
FinalView = ThisView;
|
|
}
|
|
|
|
ThisView = ThisView + (Dir * 10.0f);
|
|
}
|
|
|
|
return FinalView;
|
|
}
|
|
|
|
Vector UTIL_GetFurthestVisiblePointOnPath(const Vector ViewerLocation, vector<bot_path_node>& path, bool bPrecise)
|
|
{
|
|
if (path.size() == 0) { return g_vecZero; }
|
|
|
|
for (auto it = path.rbegin(); it != path.rend(); it++)
|
|
{
|
|
|
|
if (UTIL_QuickTrace(NULL, ViewerLocation, it->Location))
|
|
{
|
|
if (!bPrecise || it == path.rbegin())
|
|
{
|
|
return it->Location;
|
|
}
|
|
else
|
|
{
|
|
Vector FromLoc = it->Location;
|
|
Vector ToLoc = prev(it)->Location;
|
|
|
|
Vector Dir = UTIL_GetVectorNormal(ToLoc - FromLoc);
|
|
|
|
float Dist = vDist3D(FromLoc, ToLoc);
|
|
int Steps = (int)floorf(Dist / 50.0f);
|
|
|
|
if (Steps == 0) { return FromLoc; }
|
|
|
|
Vector FinalView = FromLoc;
|
|
Vector ThisView = FromLoc + (Dir * 50.0f);
|
|
|
|
for (int i = 0; i < Steps; i++)
|
|
{
|
|
if (UTIL_QuickTrace(NULL, ViewerLocation, ThisView))
|
|
{
|
|
FinalView = ThisView;
|
|
}
|
|
|
|
ThisView = ThisView + (Dir * 50.0f);
|
|
}
|
|
|
|
return FinalView;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (bPrecise && it != path.rbegin())
|
|
{
|
|
Vector FromLoc = it->Location;
|
|
Vector ToLoc = prev(it)->Location;
|
|
|
|
Vector Dir = UTIL_GetVectorNormal(ToLoc - FromLoc);
|
|
|
|
float Dist = vDist3D(FromLoc, ToLoc);
|
|
int Steps = (int)floorf(Dist / 50.0f);
|
|
|
|
if (Steps == 0) { continue; }
|
|
|
|
Vector FinalView = g_vecZero;
|
|
Vector ThisView = FromLoc + (Dir * 50.0f);
|
|
|
|
for (int i = 0; i < Steps; i++)
|
|
{
|
|
if (UTIL_QuickTrace(NULL, ViewerLocation, ThisView))
|
|
{
|
|
FinalView = ThisView;
|
|
}
|
|
|
|
ThisView = ThisView + (Dir * 50.0f);
|
|
}
|
|
|
|
if (FinalView != g_vecZero)
|
|
{
|
|
return FinalView;
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
return g_vecZero;
|
|
}
|
|
|
|
Vector UTIL_GetButtonFloorLocation(const Vector UserLocation, edict_t* ButtonEdict)
|
|
{
|
|
Vector ClosestPoint = g_vecZero;
|
|
|
|
if (ButtonEdict->v.size.x > 64.0f || ButtonEdict->v.size.y > 64.0f)
|
|
{
|
|
ClosestPoint = UTIL_GetClosestPointOnEntityToLocation(UserLocation, ButtonEdict);
|
|
}
|
|
else
|
|
{
|
|
ClosestPoint = UTIL_GetCentreOfEntity(ButtonEdict);
|
|
}
|
|
|
|
nav_profile ButtonNavProfile;
|
|
memcpy(&ButtonNavProfile, &BaseNavProfiles[ALL_NAV_PROFILE], sizeof(nav_profile));
|
|
|
|
ButtonNavProfile.Filters.removeIncludeFlags(SAMPLE_POLYFLAGS_WELD);
|
|
ButtonNavProfile.Filters.removeIncludeFlags(SAMPLE_POLYFLAGS_DOOR);
|
|
|
|
Vector ButtonAccessPoint = UTIL_ProjectPointToNavmesh(ClosestPoint, Vector(100.0f, 100.0f, 100.0f), ButtonNavProfile);
|
|
|
|
if (vIsZero(ButtonAccessPoint))
|
|
{
|
|
ButtonAccessPoint = ClosestPoint;
|
|
}
|
|
|
|
Vector PlayerAccessLoc = ButtonAccessPoint;
|
|
|
|
if (ButtonAccessPoint.z > ClosestPoint.z)
|
|
{
|
|
PlayerAccessLoc.z += 18.0f;
|
|
}
|
|
else
|
|
{
|
|
PlayerAccessLoc.z += 36.0f;
|
|
}
|
|
|
|
if (fabsf(PlayerAccessLoc.z - ClosestPoint.z) <= max_player_use_reach)
|
|
{
|
|
return ButtonAccessPoint;
|
|
}
|
|
|
|
Vector NewProjection = ClosestPoint;
|
|
|
|
if (ButtonAccessPoint.z > ClosestPoint.z)
|
|
{
|
|
NewProjection = ClosestPoint - Vector(0.0f, 0.0f, 100.0f);
|
|
}
|
|
else
|
|
{
|
|
NewProjection = ClosestPoint + Vector(0.0f, 0.0f, 100.0f);
|
|
}
|
|
|
|
Vector NewButtonAccessPoint = UTIL_ProjectPointToNavmesh(NewProjection, ButtonNavProfile);
|
|
|
|
if (vIsZero(NewButtonAccessPoint))
|
|
{
|
|
NewButtonAccessPoint = ClosestPoint;
|
|
}
|
|
|
|
return NewButtonAccessPoint;
|
|
}
|
|
|
|
bool UTIL_IsTriggerLinkedToDoor(CBaseEntity* TriggerEntity, vector<CBaseEntity*>& CheckedTriggers, CBaseEntity* Door)
|
|
{
|
|
if (!TriggerEntity || !Door) { return false; }
|
|
|
|
if (TriggerEntity == Door) { return true; }
|
|
|
|
CheckedTriggers.push_back(TriggerEntity);
|
|
|
|
const char* DoorName = STRING(Door->pev->targetname);
|
|
const char* TriggerName = STRING(TriggerEntity->pev->targetname);
|
|
const char* TriggerTarget = STRING(TriggerEntity->pev->target);
|
|
|
|
if (FStrEq(STRING(TriggerEntity->pev->target), DoorName)) { return true; }
|
|
|
|
AvHWeldable* WeldableRef = dynamic_cast<AvHWeldable*>(TriggerEntity);
|
|
|
|
if (WeldableRef)
|
|
{
|
|
string targetString = WeldableRef->GetTargetOnFinish();
|
|
const char* targetOnFinish = targetString.c_str();
|
|
|
|
CBaseEntity* TargetEntity = UTIL_FindEntityByTargetname(NULL, targetOnFinish);
|
|
|
|
if (TargetEntity && TargetEntity != TriggerEntity && UTIL_IsTriggerLinkedToDoor(TargetEntity, CheckedTriggers, Door)) { return true; }
|
|
|
|
return false;
|
|
}
|
|
|
|
CMultiManager* MMRef = dynamic_cast<CMultiManager*>(TriggerEntity);
|
|
|
|
if (MMRef)
|
|
{
|
|
for (int i = 0; i < MMRef->m_cTargets; i++)
|
|
{
|
|
CBaseEntity* MMTargetEntity = UTIL_FindEntityByTargetname(NULL, STRING(MMRef->m_iTargetName[i]));
|
|
|
|
if (!MMTargetEntity) { continue; }
|
|
|
|
if (MMTargetEntity == Door) { return true; }
|
|
|
|
// Already checked this one!
|
|
if (std::find(CheckedTriggers.begin(), CheckedTriggers.end(), MMTargetEntity) != CheckedTriggers.end()) { continue; }
|
|
|
|
if (UTIL_IsTriggerLinkedToDoor(MMTargetEntity, CheckedTriggers, Door)) { return true; }
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
CEnvGlobal* EnvGlobalRef = dynamic_cast<CEnvGlobal*>(TriggerEntity);
|
|
|
|
if (EnvGlobalRef && EnvGlobalRef->m_globalstate)
|
|
{
|
|
const char* EnvGlobalState = STRING(EnvGlobalRef->m_globalstate);
|
|
|
|
FOR_ALL_ENTITIES("multisource", CMultiSource*)
|
|
const char* SourceGlobalState = STRING(theEntity->m_globalstate);
|
|
if (FStrEq(EnvGlobalState, SourceGlobalState))
|
|
{
|
|
if (UTIL_IsTriggerLinkedToDoor(theEntity, CheckedTriggers, Door)) { return true; }
|
|
}
|
|
END_FOR_ALL_ENTITIES("multisource")
|
|
|
|
return false;
|
|
}
|
|
|
|
CMultiSource* MSRef = dynamic_cast<CMultiSource*>(TriggerEntity);
|
|
|
|
if (MSRef && MSRef->m_globalstate)
|
|
{
|
|
const char* targetName = STRING(MSRef->pev->targetname);
|
|
|
|
FOR_ALL_ENTITIES("func_button", CBaseButton*)
|
|
if (theEntity->m_sMaster && FStrEq(STRING(theEntity->m_sMaster), targetName))
|
|
{
|
|
if (std::find(CheckedTriggers.begin(), CheckedTriggers.end(), theEntity) == CheckedTriggers.end() && UTIL_IsTriggerLinkedToDoor(theEntity, CheckedTriggers, Door)) { return true; }
|
|
}
|
|
END_FOR_ALL_ENTITIES("func_button")
|
|
|
|
FOR_ALL_ENTITIES("trigger_once", CBaseTrigger*)
|
|
if (theEntity->m_sMaster && FStrEq(STRING(theEntity->m_sMaster), targetName))
|
|
{
|
|
if (std::find(CheckedTriggers.begin(), CheckedTriggers.end(), theEntity) == CheckedTriggers.end() && UTIL_IsTriggerLinkedToDoor(theEntity, CheckedTriggers, Door)) { return true; }
|
|
}
|
|
END_FOR_ALL_ENTITIES("trigger_once")
|
|
|
|
FOR_ALL_ENTITIES("trigger_multiple", CBaseTrigger*)
|
|
if (theEntity->m_sMaster && FStrEq(STRING(theEntity->m_sMaster), targetName))
|
|
{
|
|
if (std::find(CheckedTriggers.begin(), CheckedTriggers.end(), theEntity) == CheckedTriggers.end() && UTIL_IsTriggerLinkedToDoor(theEntity, CheckedTriggers, Door)) { return true; }
|
|
}
|
|
END_FOR_ALL_ENTITIES("trigger_multiple")
|
|
|
|
return false;
|
|
}
|
|
|
|
CTriggerChangeTarget* TCTRef = dynamic_cast<CTriggerChangeTarget*>(TriggerEntity);
|
|
|
|
if (TCTRef)
|
|
{
|
|
return FStrEq(STRING(TCTRef->GetNewTargetName()), STRING(Door->pev->targetname));
|
|
}
|
|
|
|
CBaseDelay* ToggleRef = dynamic_cast<CBaseDelay*>(TriggerEntity);
|
|
|
|
if (ToggleRef && ToggleRef->pev->target)
|
|
{
|
|
CBaseEntity* TargetEntity = UTIL_FindEntityByTargetname(NULL, STRING(ToggleRef->pev->target));
|
|
|
|
const char* TestTriggerTargetname = STRING(TriggerEntity->pev->targetname);
|
|
const char* ThisTriggerTarget = STRING(TargetEntity->pev->target);
|
|
|
|
// Don't check this if it's targeting a trigger we've already checked
|
|
if (TargetEntity && std::find(CheckedTriggers.begin(), CheckedTriggers.end(), TargetEntity) == CheckedTriggers.end())
|
|
{
|
|
if (TargetEntity && UTIL_IsTriggerLinkedToDoor(TargetEntity, CheckedTriggers, Door)) { return true; }
|
|
}
|
|
|
|
FOR_ALL_ENTITIES("trigger_changetarget", CTriggerChangeTarget*)
|
|
if (theEntity->GetNextTarget() && theEntity->GetNextTarget()->edict() == TriggerEntity->edict() && FStrEq(STRING(theEntity->GetNewTargetName()), STRING(Door->pev->targetname)))
|
|
{
|
|
return true;
|
|
}
|
|
END_FOR_ALL_ENTITIES("trigger_changetarget")
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void UTIL_PopulateAffectedConnectionsForDoor(nav_door* Door)
|
|
{
|
|
Door->AffectedConnections.clear();
|
|
|
|
Vector HalfExtents = (Door->DoorEdict->v.size * 0.5f);
|
|
HalfExtents.x += 16.0f;
|
|
HalfExtents.y += 16.0f;
|
|
HalfExtents.z += 16.0f;
|
|
|
|
for (auto it = BaseMapConnections.begin(); it != BaseMapConnections.end(); it++)
|
|
{
|
|
if (it->ConnectionFlags == SAMPLE_POLYFLAGS_TEAM1PHASEGATE || it->ConnectionFlags == SAMPLE_POLYFLAGS_TEAM2PHASEGATE) { continue; }
|
|
|
|
Vector ConnStart = it->FromLocation + Vector(0.0f, 0.0f, 15.0f);
|
|
Vector ConnEnd = it->ToLocation + Vector(0.0f, 0.0f, 15.0f);
|
|
Vector MidPoint = ConnStart + ((ConnEnd - ConnStart) * 0.5f);
|
|
MidPoint.z = fmaxf(ConnStart.z, ConnEnd.z);
|
|
|
|
for (auto stopIt = Door->StopPoints.begin(); stopIt != Door->StopPoints.end(); stopIt++)
|
|
{
|
|
Vector DoorCentre = (*stopIt);
|
|
|
|
if (vlineIntersectsAABB(ConnStart, MidPoint, DoorCentre - HalfExtents, DoorCentre + HalfExtents))
|
|
{
|
|
Door->AffectedConnections.push_back(&(*it));
|
|
break;
|
|
}
|
|
|
|
if (vlineIntersectsAABB(MidPoint, ConnEnd, DoorCentre - HalfExtents, DoorCentre + HalfExtents))
|
|
{
|
|
Door->AffectedConnections.push_back(&(*it));
|
|
break;
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
void UTIL_PopulateTriggersForEntity(edict_t* Entity, vector<DoorTrigger>& TriggerList)
|
|
{
|
|
CBaseEntity* TriggerRef = NULL;
|
|
CBaseEntity* DoorRef = CBaseEntity::Instance(Entity);
|
|
|
|
if (!DoorRef) { return; }
|
|
|
|
vector<CBaseEntity*> CheckedTriggerList;
|
|
|
|
while ((TriggerRef = UTIL_FindEntityByClassname(TriggerRef, "func_button")) != NULL)
|
|
{
|
|
CheckedTriggerList.clear();
|
|
if (UTIL_IsTriggerLinkedToDoor(TriggerRef, CheckedTriggerList, DoorRef))
|
|
{
|
|
DoorActivationType NewTriggerType = DOOR_BUTTON;
|
|
|
|
DoorTrigger NewTrigger;
|
|
NewTrigger.Entity = TriggerRef;
|
|
NewTrigger.Edict = TriggerRef->edict();
|
|
NewTrigger.ToggleEnt = dynamic_cast<CBaseToggle*>(TriggerRef);
|
|
NewTrigger.TriggerType = NewTriggerType;
|
|
NewTrigger.bIsActivated = (!NewTrigger.ToggleEnt || !NewTrigger.ToggleEnt->IsLockedByMaster());
|
|
|
|
TriggerList.push_back(NewTrigger);
|
|
}
|
|
}
|
|
|
|
TriggerRef = NULL;
|
|
|
|
|
|
while ((TriggerRef = UTIL_FindEntityByClassname(TriggerRef, "avhweldable")) != NULL)
|
|
{
|
|
CheckedTriggerList.clear();
|
|
if (UTIL_IsTriggerLinkedToDoor(TriggerRef, CheckedTriggerList, DoorRef))
|
|
{
|
|
DoorActivationType NewTriggerType = DOOR_WELD;
|
|
|
|
DoorTrigger NewTrigger;
|
|
NewTrigger.Entity = TriggerRef;
|
|
NewTrigger.Edict = TriggerRef->edict();
|
|
NewTrigger.ToggleEnt = dynamic_cast<CBaseToggle*>(TriggerRef);
|
|
NewTrigger.TriggerType = NewTriggerType;
|
|
NewTrigger.bIsActivated = (!NewTrigger.ToggleEnt || !NewTrigger.ToggleEnt->IsLockedByMaster());
|
|
|
|
TriggerList.push_back(NewTrigger);
|
|
}
|
|
}
|
|
|
|
TriggerRef = NULL;
|
|
CheckedTriggerList.clear();
|
|
|
|
while ((TriggerRef = UTIL_FindEntityByClassname(TriggerRef, "func_breakable")) != NULL)
|
|
{
|
|
CheckedTriggerList.clear();
|
|
if (UTIL_IsTriggerLinkedToDoor(TriggerRef, CheckedTriggerList, DoorRef))
|
|
{
|
|
DoorActivationType NewTriggerType = DOOR_BREAK;
|
|
|
|
DoorTrigger NewTrigger;
|
|
NewTrigger.Entity = TriggerRef;
|
|
NewTrigger.Edict = TriggerRef->edict();
|
|
NewTrigger.ToggleEnt = dynamic_cast<CBaseToggle*>(TriggerRef);
|
|
NewTrigger.TriggerType = NewTriggerType;
|
|
NewTrigger.bIsActivated = (!NewTrigger.ToggleEnt || !NewTrigger.ToggleEnt->IsLockedByMaster());
|
|
|
|
TriggerList.push_back(NewTrigger);
|
|
}
|
|
}
|
|
|
|
TriggerRef = NULL;
|
|
|
|
while ((TriggerRef = UTIL_FindEntityByClassname(TriggerRef, "trigger_once")) != NULL)
|
|
{
|
|
CheckedTriggerList.clear();
|
|
if (UTIL_IsTriggerLinkedToDoor(TriggerRef, CheckedTriggerList, DoorRef))
|
|
{
|
|
DoorActivationType NewTriggerType = DOOR_TRIGGER;
|
|
|
|
DoorTrigger NewTrigger;
|
|
NewTrigger.Entity = TriggerRef;
|
|
NewTrigger.Edict = TriggerRef->edict();
|
|
NewTrigger.ToggleEnt = dynamic_cast<CBaseToggle*>(TriggerRef);
|
|
NewTrigger.TriggerType = NewTriggerType;
|
|
NewTrigger.bIsActivated = (!NewTrigger.ToggleEnt || !NewTrigger.ToggleEnt->IsLockedByMaster());
|
|
|
|
TriggerList.push_back(NewTrigger);
|
|
}
|
|
}
|
|
|
|
TriggerRef = NULL;
|
|
|
|
while ((TriggerRef = UTIL_FindEntityByClassname(TriggerRef, "trigger_multiple")) != NULL)
|
|
{
|
|
CheckedTriggerList.clear();
|
|
if (UTIL_IsTriggerLinkedToDoor(TriggerRef, CheckedTriggerList, DoorRef))
|
|
{
|
|
DoorActivationType NewTriggerType = DOOR_TRIGGER;
|
|
|
|
DoorTrigger NewTrigger;
|
|
NewTrigger.Entity = TriggerRef;
|
|
NewTrigger.Edict = TriggerRef->edict();
|
|
NewTrigger.ToggleEnt = dynamic_cast<CBaseToggle*>(TriggerRef);
|
|
NewTrigger.TriggerType = NewTriggerType;
|
|
NewTrigger.bIsActivated = (!NewTrigger.ToggleEnt || !NewTrigger.ToggleEnt->IsLockedByMaster());
|
|
|
|
TriggerList.push_back(NewTrigger);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void UTIL_PopulateWeldableObstacles()
|
|
{
|
|
|
|
CBaseEntity* currWeldable = NULL;
|
|
while (((currWeldable = UTIL_FindEntityByClassname(currWeldable, "avhweldable")) != NULL))
|
|
{
|
|
if (currWeldable->pev->solid == SOLID_BSP)
|
|
{
|
|
nav_weldable NewWeldable;
|
|
NewWeldable.WeldableEdict = currWeldable->edict();
|
|
|
|
float SizeX = currWeldable->pev->size.x;
|
|
float SizeY = currWeldable->pev->size.y;
|
|
float SizeZ = currWeldable->pev->size.z;
|
|
|
|
bool bUseXAxis = (SizeX >= SizeY);
|
|
|
|
float CylinderRadius = fminf(SizeX, SizeY) * 0.5f;
|
|
|
|
CylinderRadius = fmaxf(CylinderRadius, 16.0f);
|
|
|
|
float Ratio = (bUseXAxis) ? (SizeX / (CylinderRadius * 2.0f)) : (SizeY / (CylinderRadius * 2.0f));
|
|
|
|
int NumObstacles = (int)ceil(Ratio);
|
|
|
|
if (NumObstacles > 32) { NumObstacles = 32; }
|
|
|
|
Vector Dir = (bUseXAxis) ? RIGHT_VECTOR : FWD_VECTOR;
|
|
|
|
Vector StartPoint = UTIL_GetCentreOfEntity(currWeldable->edict());
|
|
|
|
if (bUseXAxis)
|
|
{
|
|
StartPoint.x = currWeldable->pev->absmin.x + CylinderRadius;
|
|
}
|
|
else
|
|
{
|
|
StartPoint.y = currWeldable->pev->absmin.y + CylinderRadius;
|
|
}
|
|
|
|
StartPoint.z -= 2.0f;
|
|
|
|
Vector CurrentPoint = StartPoint;
|
|
|
|
NewWeldable.NumObstacles = NumObstacles;
|
|
|
|
for (int ii = 0; ii < NumObstacles; ii++)
|
|
{
|
|
UTIL_AddTemporaryObstacles(CurrentPoint, CylinderRadius, SizeZ, DT_TILECACHE_WELD_AREA, NewWeldable.ObstacleRefs[ii]);
|
|
|
|
if (bUseXAxis)
|
|
{
|
|
CurrentPoint.x += CylinderRadius * 2.0f;
|
|
}
|
|
else
|
|
{
|
|
CurrentPoint.y += CylinderRadius * 2.0f;
|
|
}
|
|
}
|
|
|
|
NavWeldableObstacles.push_back(NewWeldable);
|
|
}
|
|
}
|
|
}
|
|
|
|
void UTIL_ModifyOffMeshConnectionFlag(AvHAIOffMeshConnection* Connection, const unsigned int NewFlag)
|
|
{
|
|
if (!Connection || Connection->ConnectionFlags == NewFlag) { return; }
|
|
|
|
Connection->ConnectionFlags = NewFlag;
|
|
|
|
for (int i = 0; i < BUILDING_NAV_MESH; i++)
|
|
{
|
|
if (NavMeshes[i].tileCache && Connection->ConnectionRefs[i])
|
|
{
|
|
NavMeshes[i].tileCache->modifyOffMeshConnection(Connection->ConnectionRefs[i], NewFlag);
|
|
}
|
|
}
|
|
|
|
bNavMeshModified = true;
|
|
}
|
|
|
|
void UTIL_UpdateDoors(bool bInitial)
|
|
{
|
|
for (auto it = NavDoors.begin(); it != NavDoors.end(); it++)
|
|
{
|
|
nav_door* NavDoor = &(*it);
|
|
DoorActivationType PrevType = it->ActivationType;
|
|
|
|
const char* DoorName = STRING(NavDoor->DoorEdict->v.targetname);
|
|
|
|
UTIL_UpdateDoorTriggers(NavDoor);
|
|
|
|
CBaseToggle* DoorRef = it->DoorEntity;
|
|
|
|
if (!DoorRef) { continue; }
|
|
|
|
if (bInitial)
|
|
{
|
|
UTIL_PopulateAffectedConnectionsForDoor(NavDoor);
|
|
}
|
|
|
|
if (DoorRef->m_toggle_state == TS_GOING_UP || DoorRef->m_toggle_state == TS_GOING_DOWN)
|
|
{
|
|
if (it->NumObstacles > 0)
|
|
{
|
|
for (int ii = 0; ii < it->NumObstacles; ii++)
|
|
{
|
|
UTIL_RemoveTemporaryObstacles(it->ObstacleRefs[ii]);
|
|
}
|
|
|
|
it->NumObstacles = 0;
|
|
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (bInitial || DoorRef->m_toggle_state != it->CurrentState || PrevType != it->ActivationType)
|
|
{
|
|
if (it->NumObstacles > 0)
|
|
{
|
|
for (int ii = 0; ii < it->NumObstacles; ii++)
|
|
{
|
|
UTIL_RemoveTemporaryObstacles(it->ObstacleRefs[ii]);
|
|
}
|
|
|
|
it->NumObstacles = 0;
|
|
|
|
}
|
|
|
|
if (it->ActivationType == DOOR_NONE)
|
|
{
|
|
Vector HalfExtents = (NavDoor->DoorEdict->v.size) * 0.5f;
|
|
HalfExtents.x += 16.0f;
|
|
HalfExtents.y += 16.0f;
|
|
HalfExtents.z += 16.0f;
|
|
|
|
for (auto conIt = NavDoor->AffectedConnections.begin(); conIt != NavDoor->AffectedConnections.end(); conIt++)
|
|
{
|
|
AvHAIOffMeshConnection* ThisConnection = (*conIt);
|
|
|
|
Vector ConnStart = ThisConnection->FromLocation + Vector(0.0f, 0.0f, 15.0f);
|
|
Vector ConnEnd = ThisConnection->ToLocation + Vector(0.0f, 0.0f, 15.0f);
|
|
Vector MidPoint = ConnStart + ((ConnEnd - ConnStart) * 0.5f);
|
|
MidPoint.z = fmaxf(ConnStart.z, ConnEnd.z);
|
|
|
|
Vector DoorCentre = UTIL_GetCentreOfEntity(NavDoor->DoorEdict);
|
|
DoorCentre.z -= 16.0f;
|
|
|
|
bool bThisConnectionAffected = false;
|
|
|
|
Vector NearestPointOnLine = vClosestPointOnLine(ConnStart, MidPoint, DoorCentre);
|
|
if (vPointOverlaps3D(NearestPointOnLine, DoorCentre - HalfExtents, DoorCentre + HalfExtents))
|
|
{
|
|
UTIL_ModifyOffMeshConnectionFlag(ThisConnection, SAMPLE_POLYFLAGS_DISABLED);
|
|
bThisConnectionAffected = true;
|
|
}
|
|
else
|
|
{
|
|
NearestPointOnLine = vClosestPointOnLine(MidPoint, ConnEnd, DoorCentre);
|
|
if (vPointOverlaps3D(NearestPointOnLine, DoorCentre - HalfExtents, DoorCentre + HalfExtents))
|
|
{
|
|
UTIL_ModifyOffMeshConnectionFlag(ThisConnection, SAMPLE_POLYFLAGS_DISABLED);
|
|
bThisConnectionAffected = true;
|
|
}
|
|
}
|
|
|
|
if (!bThisConnectionAffected)
|
|
{
|
|
if (ThisConnection->ConnectionFlags != ThisConnection->DefaultConnectionFlags)
|
|
{
|
|
UTIL_ModifyOffMeshConnectionFlag(ThisConnection, ThisConnection->DefaultConnectionFlags);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
Vector DoorCentre = UTIL_GetCentreOfEntity(it->DoorEdict);
|
|
DoorCentre.z -= 24.0f;
|
|
|
|
dtNavMeshQuery* Query = NavMeshes[BUILDING_NAV_MESH].navQuery;
|
|
nav_profile StructureProfile = GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE);
|
|
|
|
dtPolyRef Polys[8];
|
|
int polyCount;
|
|
|
|
float DoorHalfExtents[3] = { HalfExtents.x, HalfExtents.z, HalfExtents.y };
|
|
float DoorCentreFlt[3] = { DoorCentre.x, DoorCentre.z, -DoorCentre.y };
|
|
|
|
Query->queryPolygons(DoorCentreFlt, DoorHalfExtents, &StructureProfile.Filters, Polys, &polyCount, 8);
|
|
|
|
if (polyCount > 0)
|
|
{
|
|
UTIL_ApplyTempObstaclesToDoor(NavDoor, DT_TILECACHE_NULL_AREA);
|
|
}
|
|
|
|
|
|
}
|
|
else if (it->ActivationType == DOOR_WELD)
|
|
{
|
|
Vector HalfExtents = (NavDoor->DoorEdict->v.size) * 0.5f;
|
|
HalfExtents.x += 16.0f;
|
|
HalfExtents.y += 16.0f;
|
|
HalfExtents.z += 16.0f;
|
|
|
|
for (auto conIt = NavDoor->AffectedConnections.begin(); conIt != NavDoor->AffectedConnections.end(); conIt++)
|
|
{
|
|
AvHAIOffMeshConnection* ThisConnection = (*conIt);
|
|
|
|
Vector ConnStart = ThisConnection->FromLocation + Vector(0.0f, 0.0f, 15.0f);
|
|
Vector ConnEnd = ThisConnection->ToLocation + Vector(0.0f, 0.0f, 15.0f);
|
|
Vector MidPoint = ConnStart + ((ConnEnd - ConnStart) * 0.5f);
|
|
MidPoint.z = fmaxf(ConnStart.z, ConnEnd.z);
|
|
|
|
Vector DoorCentre = UTIL_GetCentreOfEntity(NavDoor->DoorEdict);
|
|
|
|
bool bThisConnectionAffected = false;
|
|
|
|
Vector NearestPointOnLine = vClosestPointOnLine(ConnStart, MidPoint, DoorCentre);
|
|
if (vPointOverlaps3D(NearestPointOnLine, DoorCentre - HalfExtents, DoorCentre + HalfExtents))
|
|
{
|
|
UTIL_ModifyOffMeshConnectionFlag(ThisConnection, SAMPLE_POLYFLAGS_WELD);
|
|
bThisConnectionAffected = true;
|
|
}
|
|
else
|
|
{
|
|
NearestPointOnLine = vClosestPointOnLine(MidPoint, ConnEnd, DoorCentre);
|
|
if (vPointOverlaps3D(NearestPointOnLine, DoorCentre - HalfExtents, DoorCentre + HalfExtents))
|
|
{
|
|
UTIL_ModifyOffMeshConnectionFlag(ThisConnection, SAMPLE_POLYFLAGS_WELD);
|
|
bThisConnectionAffected = true;
|
|
}
|
|
}
|
|
|
|
if (!bThisConnectionAffected)
|
|
{
|
|
if (ThisConnection->ConnectionFlags != ThisConnection->DefaultConnectionFlags)
|
|
{
|
|
UTIL_ModifyOffMeshConnectionFlag(ThisConnection, ThisConnection->DefaultConnectionFlags);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
Vector DoorCentre = UTIL_GetCentreOfEntity(it->DoorEdict);
|
|
DoorCentre.z -= 24.0f;
|
|
|
|
dtNavMeshQuery* Query = NavMeshes[BUILDING_NAV_MESH].navQuery;
|
|
nav_profile StructureProfile = GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE);
|
|
|
|
dtPolyRef Polys[8];
|
|
int polyCount;
|
|
|
|
float DoorHalfExtents[3] = { HalfExtents.x, HalfExtents.z, HalfExtents.y};
|
|
float DoorCentreFlt[3] = { DoorCentre.x, DoorCentre.z, -DoorCentre.y };
|
|
|
|
Query->queryPolygons(DoorCentreFlt, DoorHalfExtents, &StructureProfile.Filters, Polys, &polyCount, 8);
|
|
|
|
if (polyCount > 0)
|
|
{
|
|
UTIL_ApplyTempObstaclesToDoor(NavDoor, DT_TILECACHE_WELD_AREA);
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
Vector DoorCentre = UTIL_GetCentreOfEntity(it->DoorEdict);
|
|
DoorCentre.z -= 24.0f;
|
|
|
|
dtNavMeshQuery* Query = NavMeshes[BUILDING_NAV_MESH].navQuery;
|
|
nav_profile StructureProfile = GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE);
|
|
|
|
dtPolyRef Polys[8];
|
|
int polyCount;
|
|
|
|
float DoorHalfExtents[3] = { it->DoorEdict->v.size.x, it->DoorEdict->v.size.z, it->DoorEdict->v.size.y };
|
|
float DoorCentreFlt[3] = { DoorCentre.x, DoorCentre.z, -DoorCentre.y };
|
|
|
|
Query->queryPolygons(DoorCentreFlt, DoorHalfExtents, &StructureProfile.Filters, Polys, &polyCount, 8);
|
|
|
|
if (polyCount > 0)
|
|
{
|
|
UTIL_ApplyTempObstaclesToDoor(NavDoor, DT_TILECACHE_DOOR_AREA);
|
|
}
|
|
}
|
|
|
|
it->CurrentState = DoorRef->m_toggle_state;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
void UTIL_UpdateWeldableObstacles()
|
|
{
|
|
for (auto it = NavWeldableObstacles.begin(); it != NavWeldableObstacles.end();)
|
|
{
|
|
edict_t* WeldableEdict = it->WeldableEdict;
|
|
|
|
if (FNullEnt(WeldableEdict) || WeldableEdict->v.deadflag != DEAD_NO || WeldableEdict->v.solid != SOLID_BSP)
|
|
{
|
|
for (int ii = 0; ii < it->NumObstacles; ii++)
|
|
{
|
|
UTIL_RemoveTemporaryObstacles(it->ObstacleRefs[ii]);
|
|
}
|
|
|
|
it->NumObstacles = 0;
|
|
|
|
it = NavWeldableObstacles.erase(it);
|
|
}
|
|
else
|
|
{
|
|
it++;
|
|
}
|
|
}
|
|
}
|
|
|
|
void UTIL_ApplyTempObstaclesToDoor(nav_door* DoorRef, const int Area)
|
|
{
|
|
if (!DoorRef) { return; }
|
|
|
|
if (DoorRef->NumObstacles > 0)
|
|
{
|
|
for (int ii = 0; ii < DoorRef->NumObstacles; ii++)
|
|
{
|
|
UTIL_RemoveTemporaryObstacles(DoorRef->ObstacleRefs[ii]);
|
|
}
|
|
|
|
DoorRef->NumObstacles = 0;
|
|
|
|
}
|
|
|
|
if (FNullEnt(DoorRef->DoorEdict) || DoorRef->DoorEdict->free)
|
|
{
|
|
return;
|
|
}
|
|
|
|
float SizeX = DoorRef->DoorEdict->v.size.x;
|
|
float SizeY = DoorRef->DoorEdict->v.size.y;
|
|
float SizeZ = DoorRef->DoorEdict->v.size.z;
|
|
|
|
bool bUseXAxis = (SizeX >= SizeY);
|
|
|
|
float CylinderRadius = fminf(SizeX, SizeY) * 0.5f;
|
|
|
|
float Ratio = (bUseXAxis) ? (SizeX / (CylinderRadius * 2.0f)) : (SizeY / (CylinderRadius * 2.0f));
|
|
|
|
int NumObstacles = (int)ceil(Ratio);
|
|
|
|
if (NumObstacles > 32) { NumObstacles = 32; }
|
|
|
|
Vector Dir = (bUseXAxis) ? RIGHT_VECTOR : FWD_VECTOR;
|
|
|
|
Vector StartPoint = UTIL_GetCentreOfEntity(DoorRef->DoorEdict);
|
|
|
|
if (bUseXAxis)
|
|
{
|
|
StartPoint.x = DoorRef->DoorEdict->v.absmin.x + CylinderRadius;
|
|
}
|
|
else
|
|
{
|
|
StartPoint.y = DoorRef->DoorEdict->v.absmin.y + CylinderRadius;
|
|
}
|
|
|
|
StartPoint.z -= 25.0f;
|
|
|
|
Vector CurrentPoint = StartPoint;
|
|
|
|
DoorRef->NumObstacles = NumObstacles;
|
|
|
|
for (int ii = 0; ii < NumObstacles; ii++)
|
|
{
|
|
UTIL_AddTemporaryObstacles(CurrentPoint, CylinderRadius, SizeZ, Area, DoorRef->ObstacleRefs[ii]);
|
|
|
|
if (bUseXAxis)
|
|
{
|
|
CurrentPoint.x += CylinderRadius * 2.0f;
|
|
}
|
|
else
|
|
{
|
|
CurrentPoint.y += CylinderRadius * 2.0f;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
void UTIL_UpdateDoorTriggers(nav_door* Door)
|
|
{
|
|
// Don't need to do anything if the door can be shot or opened by using it
|
|
if (!Door || Door->ActivationType == DOOR_USE || Door->ActivationType == DOOR_SHOOT) { return; }
|
|
|
|
if (Door->TriggerEnts.size() == 0)
|
|
{
|
|
// No more triggers left, door is dormant
|
|
Door->ActivationType = DOOR_NONE;
|
|
return;
|
|
}
|
|
|
|
DoorActivationType NewActivationType = DOOR_NONE;
|
|
|
|
for (auto it = Door->TriggerEnts.begin(); it != Door->TriggerEnts.end();)
|
|
{
|
|
if (FNullEnt(it->Edict) || it->Edict->free)
|
|
{
|
|
it = Door->TriggerEnts.erase(it);
|
|
continue;
|
|
}
|
|
|
|
if (it->TriggerType == DOOR_WELD)
|
|
{
|
|
AvHWeldable* WeldableRef = dynamic_cast<AvHWeldable*>(it->Entity);
|
|
|
|
if (WeldableRef && WeldableRef->GetIsWelded())
|
|
{
|
|
it = Door->TriggerEnts.erase(it);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (FStrEq(STRING(it->Edict->v.target), STRING(Door->DoorEdict->v.targetname)))
|
|
{
|
|
it->bIsActivated = (it->ToggleEnt) ? !it->ToggleEnt->IsLockedByMaster() : true;
|
|
}
|
|
else
|
|
{
|
|
// Weldables and breakables can't be "deactivated" so assume they are always actived
|
|
if (it->TriggerType == DOOR_WELD || it->TriggerType == DOOR_BREAK)
|
|
{
|
|
it->bIsActivated = true;
|
|
}
|
|
else
|
|
{
|
|
CBaseEntity* ActivationTarget = UTIL_FindEntityByString(NULL, "targetname", STRING(it->Edict->v.target));
|
|
|
|
if (!ActivationTarget)
|
|
{
|
|
it->bIsActivated = true;
|
|
}
|
|
else
|
|
{
|
|
const char* classname = STRING(ActivationTarget->pev->classname);
|
|
vector<CBaseEntity*> CheckedTriggerList;
|
|
it->bIsActivated = UTIL_IsTriggerLinkedToDoor(ActivationTarget, CheckedTriggerList, Door->DoorEntity);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (it->bIsActivated)
|
|
{
|
|
if (it->TriggerType == DOOR_WELD)
|
|
{
|
|
NewActivationType = DOOR_WELD;
|
|
}
|
|
else
|
|
{
|
|
if (NewActivationType != DOOR_WELD)
|
|
{
|
|
NewActivationType = DOOR_TRIGGER;
|
|
}
|
|
}
|
|
}
|
|
|
|
float BaseTriggerDelay = 0.0f;
|
|
float BaseTriggerResetTime = 0.0f;
|
|
|
|
bool bButtonIsToggle = FBitSet(it->Edict->v.spawnflags, SF_DOOR_NO_AUTO_RETURN);
|
|
|
|
if (it->ToggleEnt)
|
|
{
|
|
BaseTriggerDelay = it->ToggleEnt->m_flDelay;
|
|
BaseTriggerResetTime = (bButtonIsToggle) ? 1.0f : it->ToggleEnt->GetDelay();
|
|
}
|
|
|
|
float DoorDelay = (FBitSet(Door->DoorEdict->v.spawnflags, SF_DOOR_NO_AUTO_RETURN)) ? 0.0f : Door->DoorEntity->GetDelay();
|
|
it->ActivationDelay = fmaxf(BaseTriggerDelay, BaseTriggerResetTime) + DoorDelay + 1.0f;
|
|
|
|
if (it->ToggleEnt && it->ToggleEnt->GetToggleState() != it->LastToggleState)
|
|
{
|
|
TOGGLE_STATE NewState = (TOGGLE_STATE)it->ToggleEnt->GetToggleState();
|
|
|
|
if (it->LastToggleState == TS_AT_BOTTOM || (bButtonIsToggle && it->LastToggleState == TS_AT_TOP))
|
|
{
|
|
it->NextActivationTime = gpGlobals->time + fmaxf(it->ActivationDelay, 1.0f);
|
|
}
|
|
|
|
it->LastToggleState = NewState;
|
|
}
|
|
|
|
it++;
|
|
}
|
|
|
|
Door->ActivationType = NewActivationType;
|
|
}
|
|
|
|
void UTIL_ClearDoorData()
|
|
{
|
|
for (auto it = NavDoors.begin(); it != NavDoors.end(); it++)
|
|
{
|
|
if (it->NumObstacles > 0)
|
|
{
|
|
for (int ii = 0; ii < it->NumObstacles; ii++)
|
|
{
|
|
UTIL_RemoveTemporaryObstacles(it->ObstacleRefs[ii]);
|
|
}
|
|
|
|
it->NumObstacles = 0;
|
|
|
|
}
|
|
|
|
it->StopPoints.clear();
|
|
}
|
|
|
|
NavDoors.clear();
|
|
}
|
|
|
|
void UTIL_ClearWeldablesData()
|
|
{
|
|
for (auto it = NavWeldableObstacles.begin(); it != NavWeldableObstacles.end(); it++)
|
|
{
|
|
if (it->NumObstacles > 0)
|
|
{
|
|
for (int ii = 0; ii < it->NumObstacles; ii++)
|
|
{
|
|
UTIL_RemoveTemporaryObstacles(it->ObstacleRefs[ii]);
|
|
}
|
|
|
|
it->NumObstacles = 0;
|
|
|
|
}
|
|
}
|
|
|
|
NavWeldableObstacles.clear();
|
|
}
|
|
|
|
// TODO: This
|
|
void UTIL_PopulateTrainStopPoints(nav_door* TrainDoor)
|
|
{
|
|
CBasePlatTrain* TrainRef = dynamic_cast<CBasePlatTrain*>(TrainDoor->DoorEntity);
|
|
|
|
if (!TrainRef) { return; }
|
|
|
|
CBaseEntity* StartCorner = TrainRef->GetNextTarget();
|
|
|
|
if (!StartCorner)
|
|
{
|
|
// We aren't using path corners, so we're probably a func_plat
|
|
TrainDoor->StopPoints.push_back(UTIL_GetCentreOfEntity(TrainDoor->DoorEdict) + TrainRef->m_vecPosition1);
|
|
TrainDoor->StopPoints.push_back(UTIL_GetCentreOfEntity(TrainDoor->DoorEdict) + TrainRef->m_vecPosition2);
|
|
return;
|
|
}
|
|
|
|
// If the "door" is a func_train, then a path corner is considered a "stop" if flagged to wait for retrigger, or has a delay associated with it
|
|
// Eventually, we probably want to remove this expectation so the bot can use platforms which continuously move
|
|
if (StartCorner->pev->spawnflags & SF_TRAIN_WAIT_RETRIGGER || StartCorner->GetDelay() > 0.0f)
|
|
{
|
|
TrainDoor->StopPoints.push_back(StartCorner->pev->origin);
|
|
}
|
|
|
|
// Populate all path corners at which this func_train stops. Bot will use this to determine when to board the train
|
|
|
|
CBaseEntity* CurrentCorner = StartCorner->GetNextTarget();
|
|
|
|
while (CurrentCorner != NULL && CurrentCorner != StartCorner)
|
|
{
|
|
// Check if the train stops at this path corner, and if so, add it to the stop points array
|
|
if (CurrentCorner->pev->spawnflags & SF_TRAIN_WAIT_RETRIGGER || CurrentCorner->GetDelay() > 0.0f)
|
|
{
|
|
TrainDoor->StopPoints.push_back(CurrentCorner->pev->origin);
|
|
}
|
|
|
|
CurrentCorner = CurrentCorner->GetNextTarget();
|
|
}
|
|
|
|
}
|
|
|
|
void UTIL_PopulateDoors()
|
|
{
|
|
|
|
UTIL_ClearDoorData();
|
|
|
|
vector<CBaseEntity*> DoorsToPopulate;
|
|
DoorsToPopulate.clear();
|
|
|
|
CBaseEntity* currDoor = NULL;
|
|
while ((currDoor = UTIL_FindEntityByClassname(currDoor, "func_door")) != NULL)
|
|
{
|
|
DoorsToPopulate.push_back(currDoor);
|
|
}
|
|
|
|
currDoor = NULL;
|
|
while ((currDoor = UTIL_FindEntityByClassname(currDoor, "func_seethroughdoor")) != NULL)
|
|
{
|
|
DoorsToPopulate.push_back(currDoor);
|
|
}
|
|
|
|
currDoor = NULL;
|
|
while ((currDoor = UTIL_FindEntityByClassname(currDoor, "func_door_rotating")) != NULL)
|
|
{
|
|
DoorsToPopulate.push_back(currDoor);
|
|
}
|
|
|
|
currDoor = NULL;
|
|
while ((currDoor = UTIL_FindEntityByClassname(currDoor, "func_plat")) != NULL)
|
|
{
|
|
DoorsToPopulate.push_back(currDoor);
|
|
}
|
|
|
|
currDoor = NULL;
|
|
while ((currDoor = UTIL_FindEntityByClassname(currDoor, "func_train")) != NULL)
|
|
{
|
|
DoorsToPopulate.push_back(currDoor);
|
|
}
|
|
|
|
for (auto it = DoorsToPopulate.begin(); it != DoorsToPopulate.end(); it++)
|
|
{
|
|
CBaseEntity* DoorEnt = *it;
|
|
|
|
CBaseToggle* ToggleRef = dynamic_cast<CBaseToggle*>(DoorEnt);
|
|
if (!ToggleRef) { continue; }
|
|
|
|
nav_door NewDoor;
|
|
NewDoor.NumObstacles = 0;
|
|
|
|
NewDoor.DoorEntity = ToggleRef;
|
|
NewDoor.DoorEdict = DoorEnt->edict();
|
|
NewDoor.CurrentState = ToggleRef->m_toggle_state;
|
|
NewDoor.DoorName = STRING(NewDoor.DoorEdict->v.targetname);
|
|
|
|
const char* DoorName = STRING(NewDoor.DoorEdict->v.targetname);
|
|
|
|
if (DoorEnt->pev->spawnflags & DOOR_USE_ONLY)
|
|
{
|
|
NewDoor.ActivationType = DOOR_USE;
|
|
}
|
|
else
|
|
{
|
|
NewDoor.TriggerEnts.clear();
|
|
UTIL_PopulateTriggersForEntity(DoorEnt->edict(), NewDoor.TriggerEnts);
|
|
}
|
|
|
|
CBasePlatTrain* TrainRef = dynamic_cast<CBasePlatTrain*>(DoorEnt);
|
|
|
|
if (TrainRef)
|
|
{
|
|
NewDoor.DoorType = DOORTYPE_TRAIN;
|
|
UTIL_PopulateTrainStopPoints(&NewDoor);
|
|
}
|
|
else
|
|
{
|
|
NewDoor.DoorType = DOORTYPE_DOOR;
|
|
if (NewDoor.DoorEdict->v.spawnflags & DOOR_START_OPEN)
|
|
{
|
|
NewDoor.StopPoints.push_back(UTIL_GetCentreOfEntity(NewDoor.DoorEdict) + ToggleRef->m_vecPosition2);
|
|
NewDoor.StopPoints.push_back(UTIL_GetCentreOfEntity(NewDoor.DoorEdict) - ToggleRef->m_vecPosition1);
|
|
}
|
|
else
|
|
{
|
|
NewDoor.StopPoints.push_back(UTIL_GetCentreOfEntity(NewDoor.DoorEdict) + ToggleRef->m_vecPosition1);
|
|
NewDoor.StopPoints.push_back(UTIL_GetCentreOfEntity(NewDoor.DoorEdict) + ToggleRef->m_vecPosition2);
|
|
}
|
|
}
|
|
|
|
|
|
NavDoors.push_back(NewDoor);
|
|
}
|
|
|
|
UTIL_UpdateDoors(true);
|
|
}
|
|
|
|
nav_door* UTIL_GetNavDoorByEdict(const edict_t* DoorEdict)
|
|
{
|
|
if (FNullEnt(DoorEdict)) { return nullptr; }
|
|
|
|
for (auto it = NavDoors.begin(); it != NavDoors.end(); it++)
|
|
{
|
|
if (it->DoorEdict == DoorEdict)
|
|
{
|
|
return &(*it);
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
// TODO: Find the topmost point when open, and topmost point when closed, and see how closely they align to the top and bottom point parameters
|
|
nav_door* UTIL_GetClosestLiftToPoints(const Vector StartPoint, const Vector EndPoint)
|
|
{
|
|
nav_door* Result = nullptr;
|
|
|
|
float minDist = 0.0f;
|
|
|
|
for (auto it = NavDoors.begin(); it != NavDoors.end(); it++)
|
|
{
|
|
float distTopPoint = FLT_MAX;
|
|
float distBottomPoint = FLT_MAX;
|
|
|
|
for (auto stop = it->StopPoints.begin(); stop != it->StopPoints.end(); stop++)
|
|
{
|
|
distTopPoint = fminf(distTopPoint, vDist3DSq(UTIL_GetClosestPointOnEntityToLocation(StartPoint, it->DoorEdict, *stop), StartPoint));
|
|
distBottomPoint = fminf(distBottomPoint, vDist3DSq(UTIL_GetClosestPointOnEntityToLocation(EndPoint, it->DoorEdict, *stop), EndPoint));
|
|
}
|
|
|
|
// Get the average distance from our desired start and end points, whichever scores lowest is probably the lift/train/door we want to ride
|
|
float thisDist = ((distTopPoint + distBottomPoint) * 0.5f);
|
|
|
|
if (!Result || thisDist < minDist)
|
|
{
|
|
Result = &(*it);
|
|
minDist = thisDist;
|
|
}
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
void UTIL_AddOffMeshConnection(Vector StartLoc, Vector EndLoc, unsigned char area, unsigned int flags, bool bBiDirectional, AvHAIOffMeshConnection* RemoveConnectionDef)
|
|
{
|
|
Vector ConnStart, ConnEnd;
|
|
|
|
TraceResult hit;
|
|
UTIL_TraceLine(StartLoc + Vector(0.0f, 0.0f, 5.0f), StartLoc - Vector(0.0f, 0.0f, 100.0f), ignore_monsters, ignore_glass, nullptr, &hit);
|
|
|
|
ConnStart = (hit.flFraction < 1.0f) ? hit.vecEndPos : StartLoc;
|
|
|
|
UTIL_TraceLine(EndLoc + Vector(0.0f, 0.0f, 5.0f), EndLoc - Vector(0.0f, 0.0f, 100.0f), ignore_monsters, ignore_glass, nullptr, &hit);
|
|
|
|
ConnEnd = (hit.flFraction < 1.0f) ? hit.vecEndPos : EndLoc;
|
|
|
|
ConnStart = Vector(ConnStart.x, ConnStart.z, -ConnStart.y);
|
|
ConnEnd = Vector(ConnEnd.x, ConnEnd.z, -ConnEnd.y);
|
|
|
|
for (int i = 0; i < BUILDING_NAV_MESH; i++)
|
|
{
|
|
dtOffMeshConnectionRef ref = 0;
|
|
|
|
NavMeshes[i].tileCache->addOffMeshConnection(ConnStart, ConnEnd, 18.0f, area, flags, bBiDirectional, &ref);
|
|
|
|
RemoveConnectionDef->ConnectionRefs[i] = (unsigned int)ref;
|
|
}
|
|
|
|
bNavMeshModified = true;
|
|
}
|
|
|
|
void UTIL_RemoveOffMeshConnections(AvHAIOffMeshConnection* RemoveConnectionDef)
|
|
{
|
|
for (int i = 0; i < BUILDING_NAV_MESH; i++)
|
|
{
|
|
NavMeshes[i].tileCache->removeOffMeshConnection(RemoveConnectionDef->ConnectionRefs[i]);
|
|
|
|
RemoveConnectionDef->ConnectionRefs[i] = 0;
|
|
}
|
|
|
|
bNavMeshModified = true;
|
|
}
|
|
|
|
const nav_profile GetBaseNavProfile(const int index)
|
|
{
|
|
return BaseNavProfiles[index];
|
|
}
|
|
|
|
const dtOffMeshConnection* DEBUG_FindNearestOffMeshConnectionToPoint(const Vector Point, unsigned int FilterFlags)
|
|
{
|
|
const dtOffMeshConnection* Result = nullptr;
|
|
|
|
if (NavMeshes[REGULAR_NAV_MESH].tileCache)
|
|
{
|
|
float PointConverted[3] = { Point.x, Point.z, -Point.y };
|
|
|
|
float minDist = 0.0f;
|
|
|
|
|
|
for (int i = 0; i < NavMeshes[REGULAR_NAV_MESH].tileCache->getOffMeshCount(); i++)
|
|
{
|
|
const dtOffMeshConnection* con = NavMeshes[REGULAR_NAV_MESH].tileCache->getOffMeshConnection(i);
|
|
|
|
if (!con || con->state == DT_OFFMESH_EMPTY || con->state == DT_OFFMESH_REMOVING || !(con->flags & FilterFlags)) { continue; }
|
|
|
|
float distSpos = dtVdistSqr(PointConverted, &con->pos[0]);
|
|
float distEpos = dtVdistSqr(PointConverted, &con->pos[3]);
|
|
|
|
float thisDist = dtMin(distSpos, distEpos);
|
|
|
|
if (!Result || thisDist < minDist)
|
|
{
|
|
Result = con;
|
|
minDist = thisDist;
|
|
}
|
|
}
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
dtStatus DEBUG_TestFindPath(const nav_profile& NavProfile, const Vector FromLocation, const Vector ToLocation, vector<bot_path_node>& path, float MaxAcceptableDistance)
|
|
{
|
|
const dtNavMeshQuery* m_navQuery = UTIL_GetNavMeshQueryForProfile(NavProfile);
|
|
const dtNavMesh* m_navMesh = UTIL_GetNavMeshForProfile(NavProfile);
|
|
const dtQueryFilter* m_navFilter = &NavProfile.Filters;
|
|
|
|
if (!m_navQuery || !m_navMesh || !m_navFilter || vIsZero(FromLocation) || vIsZero(ToLocation))
|
|
{
|
|
return DT_FAILURE;
|
|
}
|
|
|
|
Vector FromFloorLocation = AdjustPointForPathfinding(FromLocation);
|
|
Vector ToFloorLocation = AdjustPointForPathfinding(ToLocation);
|
|
|
|
float pStartPos[3] = { FromFloorLocation.x, FromFloorLocation.z, -FromFloorLocation.y };
|
|
float pEndPos[3] = { ToFloorLocation.x, ToFloorLocation.z, -ToFloorLocation.y };
|
|
|
|
dtStatus status;
|
|
dtPolyRef StartPoly;
|
|
float StartNearest[3];
|
|
dtPolyRef EndPoly;
|
|
float EndNearest[3];
|
|
dtPolyRef PolyPath[MAX_PATH_POLY];
|
|
dtPolyRef StraightPolyPath[MAX_AI_PATH_SIZE];
|
|
int nPathCount = 0;
|
|
float StraightPath[MAX_AI_PATH_SIZE * 3];
|
|
unsigned char straightPathFlags[MAX_AI_PATH_SIZE];
|
|
memset(straightPathFlags, 0, sizeof(straightPathFlags));
|
|
int nVertCount = 0;
|
|
|
|
// find the start polygon
|
|
status = m_navQuery->findNearestPoly(pStartPos, pExtents, m_navFilter, &StartPoly, StartNearest);
|
|
if ((status & DT_FAILURE) || (status & DT_STATUS_DETAIL_MASK))
|
|
{
|
|
//BotSay(pBot, "findNearestPoly start failed!");
|
|
return (status & DT_STATUS_DETAIL_MASK); // couldn't find a polygon
|
|
}
|
|
|
|
// find the end polygon
|
|
status = m_navQuery->findNearestPoly(pEndPos, pExtents, m_navFilter, &EndPoly, EndNearest);
|
|
if ((status & DT_FAILURE) || (status & DT_STATUS_DETAIL_MASK))
|
|
{
|
|
//BotSay(pBot, "findNearestPoly end failed!");
|
|
return (status & DT_STATUS_DETAIL_MASK); // couldn't find a polygon
|
|
}
|
|
|
|
status = m_navQuery->findPath(StartPoly, EndPoly, StartNearest, EndNearest, m_navFilter, PolyPath, &nPathCount, MAX_PATH_POLY);
|
|
|
|
if (PolyPath[nPathCount - 1] != EndPoly)
|
|
{
|
|
return DT_FAILURE;
|
|
}
|
|
|
|
status = m_navQuery->findStraightPath(StartNearest, EndNearest, PolyPath, nPathCount, StraightPath, straightPathFlags, StraightPolyPath, &nVertCount, MAX_AI_PATH_SIZE, DT_STRAIGHTPATH_AREA_CROSSINGS);
|
|
if ((status & DT_FAILURE) || (status & DT_STATUS_DETAIL_MASK))
|
|
{
|
|
return (status & DT_STATUS_DETAIL_MASK); // couldn't create a path
|
|
}
|
|
|
|
if (nVertCount == 0)
|
|
{
|
|
return DT_FAILURE; // couldn't find a path
|
|
}
|
|
|
|
path.clear();
|
|
|
|
// At this point we have our path. Copy it to the path store
|
|
int nIndex = 0;
|
|
|
|
Vector NodeFromLocation = FromFloorLocation;
|
|
|
|
for (int nVert = 0; nVert < nVertCount; nVert++)
|
|
{
|
|
bot_path_node NextPathNode;
|
|
|
|
NextPathNode.FromLocation = NodeFromLocation;
|
|
|
|
NextPathNode.Location.x = StraightPath[nIndex++];
|
|
NextPathNode.Location.z = StraightPath[nIndex++];
|
|
NextPathNode.Location.y = -StraightPath[nIndex++];
|
|
NextPathNode.area = SAMPLE_POLYAREA_GROUND;
|
|
NextPathNode.flag = SAMPLE_POLYFLAGS_WALK;
|
|
|
|
path.push_back(NextPathNode);
|
|
|
|
NodeFromLocation = NextPathNode.Location;
|
|
}
|
|
|
|
return DT_SUCCESS;
|
|
}
|
|
|
|
void NAV_SetMoveMovementTask(AvHAIPlayer* pBot, Vector MoveLocation, DoorTrigger* TriggerToActivate)
|
|
{
|
|
AvHAIPlayerMoveTask* MoveTask = &pBot->BotNavInfo.MovementTask;
|
|
|
|
if (MoveTask->TaskType == MOVE_TASK_MOVE && vEquals(MoveTask->TaskLocation, MoveLocation)) { return; }
|
|
|
|
if (vDist2DSq(pBot->CurrentFloorPosition, MoveLocation) < sqrf(GetPlayerRadius(pBot->Player)) && fabsf(pBot->CollisionHullBottomLocation.z - MoveLocation.z) < 50.0f) { return; }
|
|
|
|
MoveTask->TaskType = MOVE_TASK_MOVE;
|
|
MoveTask->TaskLocation = MoveLocation;
|
|
|
|
vector<bot_path_node> Path;
|
|
dtStatus PathStatus = FindPathClosestToPoint(pBot->BotNavInfo.NavProfile, pBot->CurrentFloorPosition, MoveLocation, Path, 200.0f);
|
|
|
|
if (dtStatusSucceed(PathStatus) && Path.size() > 0)
|
|
{
|
|
MoveTask->TaskLocation = Path.back().Location;
|
|
}
|
|
}
|
|
|
|
void NAV_SetTouchMovementTask(AvHAIPlayer* pBot, edict_t* EntityToTouch, DoorTrigger* TriggerToActivate)
|
|
{
|
|
AvHAIPlayerMoveTask* MoveTask = &pBot->BotNavInfo.MovementTask;
|
|
|
|
if (MoveTask->TaskType == MOVE_TASK_TOUCH && MoveTask->TaskTarget == EntityToTouch) { return; }
|
|
|
|
MoveTask->TaskType = MOVE_TASK_TOUCH;
|
|
MoveTask->TaskTarget = EntityToTouch;
|
|
MoveTask->TriggerToActivate = TriggerToActivate;
|
|
|
|
vector<bot_path_node> Path;
|
|
dtStatus PathStatus = FindPathClosestToPoint(pBot->BotNavInfo.NavProfile, pBot->CurrentFloorPosition, UTIL_GetCentreOfEntity(EntityToTouch), Path, 200.0f);
|
|
|
|
if (dtStatusSucceed(PathStatus) && Path.size() > 0)
|
|
{
|
|
MoveTask->TaskLocation = Path.back().Location;
|
|
}
|
|
}
|
|
|
|
void NAV_SetUseMovementTask(AvHAIPlayer* pBot, edict_t* EntityToUse, DoorTrigger* TriggerToActivate)
|
|
{
|
|
AvHAIPlayerMoveTask* MoveTask = &pBot->BotNavInfo.MovementTask;
|
|
|
|
if (MoveTask->TaskType == MOVE_TASK_USE && MoveTask->TaskTarget == EntityToUse) { return; }
|
|
|
|
NAV_ClearMovementTask(pBot);
|
|
|
|
MoveTask->TaskType = MOVE_TASK_USE;
|
|
MoveTask->TaskTarget = EntityToUse;
|
|
MoveTask->TriggerToActivate = TriggerToActivate;
|
|
MoveTask->TaskLocation = UTIL_GetButtonFloorLocation(pBot->Edict->v.origin, EntityToUse);
|
|
}
|
|
|
|
void NAV_SetBreakMovementTask(AvHAIPlayer* pBot, edict_t* EntityToBreak, DoorTrigger* TriggerToActivate)
|
|
{
|
|
AvHAIPlayerMoveTask* MoveTask = &pBot->BotNavInfo.MovementTask;
|
|
|
|
if (MoveTask->TaskType == MOVE_TASK_BREAK && MoveTask->TaskTarget == EntityToBreak) { return; }
|
|
|
|
NAV_ClearMovementTask(pBot);
|
|
|
|
MoveTask->TaskType = MOVE_TASK_BREAK;
|
|
MoveTask->TaskTarget = EntityToBreak;
|
|
MoveTask->TriggerToActivate = TriggerToActivate;
|
|
|
|
MoveTask->TaskLocation = UTIL_GetButtonFloorLocation(pBot->Edict->v.origin, EntityToBreak);
|
|
}
|
|
|
|
void NAV_SetWeldMovementTask(AvHAIPlayer* pBot, edict_t* EntityToWeld, DoorTrigger* TriggerToActivate)
|
|
{
|
|
if (IsPlayerAlien(pBot->Edict)) { return; }
|
|
|
|
AvHAIPlayerMoveTask* MoveTask = &pBot->BotNavInfo.MovementTask;
|
|
|
|
if (MoveTask->TaskType == MOVE_TASK_WELD && MoveTask->TaskTarget == EntityToWeld) { return; }
|
|
|
|
NAV_ClearMovementTask(pBot);
|
|
|
|
MoveTask->TaskType = MOVE_TASK_WELD;
|
|
MoveTask->TaskTarget = EntityToWeld;
|
|
MoveTask->TriggerToActivate = TriggerToActivate;
|
|
MoveTask->TaskLocation = UTIL_GetButtonFloorLocation(pBot->Edict->v.origin, EntityToWeld);
|
|
}
|
|
|
|
void NAV_ClearMovementTask(AvHAIPlayer* pBot)
|
|
{
|
|
pBot->BotNavInfo.MovementTask.TaskType = MOVE_TASK_NONE;
|
|
pBot->BotNavInfo.MovementTask.TaskLocation = ZERO_VECTOR;
|
|
pBot->BotNavInfo.MovementTask.TaskTarget = nullptr;
|
|
pBot->BotNavInfo.MovementTask.TriggerToActivate = nullptr;
|
|
}
|
|
|
|
void NAV_ProgressMovementTask(AvHAIPlayer* pBot)
|
|
{
|
|
AvHAIPlayerMoveTask* MoveTask = &pBot->BotNavInfo.MovementTask;
|
|
|
|
if (MoveTask->TaskType == MOVE_TASK_NONE) { return; }
|
|
|
|
if (MoveTask->TaskType == MOVE_TASK_USE)
|
|
{
|
|
if (IsPlayerInUseRange(pBot->Edict, MoveTask->TaskTarget))
|
|
{
|
|
BotUseObject(pBot, MoveTask->TaskTarget, false);
|
|
ClearBotStuck(pBot);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (MoveTask->TaskType == MOVE_TASK_BREAK)
|
|
{
|
|
AvHAIWeapon Weapon = WEAPON_INVALID;
|
|
|
|
if (IsPlayerMarine(pBot->Edict))
|
|
{
|
|
Weapon = BotMarineChooseBestWeaponForStructure(pBot, MoveTask->TaskTarget);
|
|
}
|
|
else
|
|
{
|
|
Weapon = BotAlienChooseBestWeaponForStructure(pBot, MoveTask->TaskTarget);
|
|
}
|
|
|
|
BotAttackResult AttackResult = PerformAttackLOSCheck(pBot, Weapon, MoveTask->TaskTarget);
|
|
|
|
if (AttackResult == ATTACK_SUCCESS)
|
|
{
|
|
// If we were ducking before then keep ducking
|
|
if (pBot->Edict->v.oldbuttons & IN_DUCK)
|
|
{
|
|
pBot->Button |= IN_DUCK;
|
|
}
|
|
|
|
BotShootTarget(pBot, Weapon, MoveTask->TaskTarget);
|
|
|
|
ClearBotStuck(pBot);
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (MoveTask->TaskType == MOVE_TASK_WELD)
|
|
{
|
|
if (IsPlayerInUseRange(pBot->Edict, MoveTask->TaskTarget))
|
|
{
|
|
Vector AimLocation;
|
|
|
|
Vector EntityCentre = UTIL_GetCentreOfEntity(MoveTask->TaskTarget);
|
|
|
|
if (MoveTask->TaskTarget->v.size.Length() < 100.0f)
|
|
{
|
|
AimLocation = EntityCentre;
|
|
}
|
|
else
|
|
{
|
|
Vector BBMin = MoveTask->TaskTarget->v.absmin + Vector(5.0f, 5.0f, 5.0f);
|
|
Vector BBMax = MoveTask->TaskTarget->v.absmax - Vector(5.0f, 5.0f, 5.0f);
|
|
|
|
vScaleBB(BBMin, BBMax, 0.75f);
|
|
|
|
AimLocation = vClosestPointOnBB(pBot->CurrentEyePosition, BBMin, BBMax);
|
|
|
|
if (MoveTask->TaskTarget->v.absmax.z - MoveTask->TaskTarget->v.absmin.z < 100.0f)
|
|
{
|
|
AimLocation.z = EntityCentre.z;
|
|
}
|
|
|
|
}
|
|
|
|
BotMoveLookAt(pBot, AimLocation);
|
|
pBot->DesiredMoveWeapon = WEAPON_MARINE_WELDER;
|
|
|
|
if (GetPlayerCurrentWeapon(pBot->Player) != WEAPON_MARINE_WELDER)
|
|
{
|
|
return;
|
|
}
|
|
|
|
pBot->Button |= IN_ATTACK;
|
|
|
|
ClearBotStuck(pBot);
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
bool bSuccess = MoveTo(pBot, MoveTask->TaskLocation, MOVESTYLE_NORMAL);
|
|
|
|
|
|
}
|
|
|
|
bool NAV_IsMovementTaskStillValid(AvHAIPlayer* pBot)
|
|
{
|
|
AvHAIPlayerMoveTask* MoveTask = &pBot->BotNavInfo.MovementTask;
|
|
|
|
if (MoveTask->TaskType == MOVE_TASK_NONE) { return false; }
|
|
|
|
if (MoveTask->TriggerToActivate)
|
|
{
|
|
if (!MoveTask->TriggerToActivate->bIsActivated) { return false; }
|
|
if (MoveTask->TriggerToActivate->NextActivationTime > gpGlobals->time) { return false; }
|
|
}
|
|
|
|
if (MoveTask->TaskType == MOVE_TASK_MOVE)
|
|
{
|
|
return (vDist2DSq(pBot->Edict->v.origin, MoveTask->TaskLocation) > sqrf(GetPlayerRadius(pBot->Player)) || fabsf(pBot->Edict->v.origin.z - MoveTask->TaskLocation.z) > 50.0f)
|
|
&& UTIL_PointIsReachable(pBot->BotNavInfo.NavProfile, pBot->CurrentFloorPosition, MoveTask->TaskLocation, GetPlayerRadius(pBot->Edict));
|
|
}
|
|
|
|
if (MoveTask->TaskType == MOVE_TASK_USE)
|
|
{
|
|
return MoveTask->TriggerToActivate && MoveTask->TriggerToActivate->bIsActivated && MoveTask->TriggerToActivate->NextActivationTime < gpGlobals->time;
|
|
}
|
|
|
|
if (MoveTask->TaskType == MOVE_TASK_PICKUP)
|
|
{
|
|
return (!FNullEnt(MoveTask->TaskTarget) && !(MoveTask->TaskTarget->v.effects & EF_NODRAW));
|
|
}
|
|
|
|
if (MoveTask->TaskType == MOVE_TASK_TOUCH)
|
|
{
|
|
return (!FNullEnt(MoveTask->TaskTarget) && !IsPlayerTouchingEntity(pBot->Edict, MoveTask->TaskTarget));
|
|
}
|
|
|
|
if (MoveTask->TaskType == MOVE_TASK_BREAK)
|
|
{
|
|
return (!FNullEnt(MoveTask->TaskTarget) && MoveTask->TaskTarget->v.deadflag == DEAD_NO && MoveTask->TaskTarget->v.health > 0.0f);
|
|
}
|
|
|
|
if (MoveTask->TaskType == MOVE_TASK_WELD)
|
|
{
|
|
AvHWeldable* WeldableRef = dynamic_cast<AvHWeldable*>(CBaseEntity::Instance(MoveTask->TaskTarget));
|
|
|
|
if (WeldableRef)
|
|
{
|
|
return !WeldableRef->GetIsWelded();
|
|
}
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
void NAV_SetPickupMovementTask(AvHAIPlayer* pBot, edict_t* ThingToPickup, DoorTrigger* TriggerToActivate)
|
|
{
|
|
AvHAIPlayerMoveTask* MoveTask = &pBot->BotNavInfo.MovementTask;
|
|
|
|
if (MoveTask->TaskType == MOVE_TASK_PICKUP && MoveTask->TaskTarget == ThingToPickup) { return; }
|
|
|
|
NAV_ClearMovementTask(pBot);
|
|
|
|
MoveTask->TaskType = MOVE_TASK_PICKUP;
|
|
MoveTask->TaskTarget = ThingToPickup;
|
|
MoveTask->TriggerToActivate = TriggerToActivate;
|
|
MoveTask->TaskLocation = ThingToPickup->v.origin;
|
|
}
|
|
|
|
vector<NavHint*> NAV_GetHintsOfType(unsigned int HintType, bool bUnoccupiedOnly)
|
|
{
|
|
vector<NavHint*> Result;
|
|
|
|
Result.clear();
|
|
|
|
for (auto it = MapNavHints.begin(); it != MapNavHints.end(); it++)
|
|
{
|
|
if (HintType != STRUCTURE_NONE && !(it->hintType & HintType)) { continue; }
|
|
|
|
if (bUnoccupiedOnly && !FNullEnt(it->OccupyingBuilding)) { continue; }
|
|
|
|
Result.push_back(&(*it));
|
|
}
|
|
|
|
return Result;
|
|
|
|
}
|
|
|
|
vector<NavHint*> NAV_GetHintsOfTypeInRadius(unsigned int HintType, Vector SearchLocation, float Radius, bool bUnoccupiedOnly)
|
|
{
|
|
vector<NavHint*> Result;
|
|
|
|
Result.clear();
|
|
|
|
float SearchRadius = sqrf(Radius);
|
|
|
|
for (auto it = MapNavHints.begin(); it != MapNavHints.end(); it++)
|
|
{
|
|
if (HintType != STRUCTURE_NONE && !(it->hintType & HintType)) { continue; }
|
|
|
|
if (bUnoccupiedOnly && !FNullEnt(it->OccupyingBuilding)) { continue; }
|
|
|
|
if (vDist3DSq(it->Position, SearchLocation) < SearchRadius)
|
|
{
|
|
Result.push_back(&(*it));
|
|
}
|
|
}
|
|
|
|
return Result;
|
|
|
|
} |