Implemented fully dynamic off-mesh connections

Phase gates now use connections rather than custom path finding. Much more performant.
This commit is contained in:
RGreenlees 2023-10-24 16:47:03 +01:00 committed by pierow
parent 46efcdaeda
commit 82ea559a7a
8 changed files with 419 additions and 182 deletions

View file

@ -13,6 +13,7 @@ static const float commander_action_cooldown = 1.0f;
static const float min_request_spam_time = 5.0f;
constexpr auto MAX_AI_PATH_SIZE = 512; // Maximum number of points allowed in a path (this should be enough for any sized map)
static const int MAX_NAV_MESHES = 8; // Max number of nav meshes allowed. Currently 3 are used (one for building placement, one for the onos, and a regular one for everyone else)
// NS weapon types. Each number refers to the GoldSrc weapon index
typedef enum
@ -168,6 +169,15 @@ typedef enum _STRUCTUREPURPOSE
} StructurePurpose;
typedef struct _OFF_MESH_CONN
{
int MeshConnectionIndex = -1;
unsigned short ConnectionFlags = 0;
Vector FromLocation = g_vecZero;
Vector ToLocation = g_vecZero;
edict_t* TargetObject = nullptr;
} AvHAIOffMeshConnection;
// Data structure used to track resource nodes in the map
typedef struct _RESOURCE_NODE
{
@ -192,7 +202,7 @@ typedef struct _HIVE_DEFINITION_T
AvHMessageID TechStatus = MESSAGE_NULL; // What tech (if any) is assigned to this hive right now
bool bIsUnderAttack = false; // Is the hive currently under attack? Becomes false if not taken damage for more than 10 seconds
AvHAIResourceNode* HiveResNodeRef = nullptr; // Which resource node (indexes into ResourceNodes array) belongs to this hive?
unsigned int ObstacleRefs[8]; // When in progress or built, will place an obstacle so bots don't try to walk through it
unsigned int ObstacleRefs[MAX_NAV_MESHES]; // When in progress or built, will place an obstacle so bots don't try to walk through it
float NextFloorLocationCheck = 0.0f; // When should the closest navigable point to the hive be calculated? Used to delay the check after a hive is built
AvHTeamNumber OwningTeam = TEAM_IND; // Which team owns this hive currently (TEAM_IND if empty)
@ -254,7 +264,8 @@ typedef struct _AVH_AI_BUILDABLE_STRUCTURE
unsigned int TeamAReachabilityFlags = AI_REACHABILITY_NONE;
unsigned int TeamBReachabilityFlags = AI_REACHABILITY_NONE;
int LastSeen = 0; // Which refresh cycle was this last seen on? Used to determine if the building has been removed from play
unsigned int ObstacleRefs[8]; // References to this structure's obstacles across each nav mesh
unsigned int ObstacleRefs[MAX_NAV_MESHES]; // References to this structure's obstacles across each nav mesh
vector<AvHAIOffMeshConnection> OffMeshConnections; // References to any off-mesh connections this structure is associated with
Vector LastSuccessfulCommanderLocation = g_vecZero; // Tracks the last commander view location where it successfully placed or selected the building
Vector LastSuccessfulCommanderAngle = g_vecZero; // Tracks the last commander input angle ("click" location) used to successfully place or select building
StructurePurpose Purpose = STRUCTURE_PURPOSE_NONE;

View file

@ -272,7 +272,8 @@ void AIDEBUG_DrawBotPath(AvHAIPlayer* pBot)
case SAMPLE_POLYFLAGS_BLOCKED:
UTIL_DrawLine(INDEXENT(1), FromLoc, ToLoc, 128, 128, 128);
break;
case SAMPLE_POLYFLAGS_PHASEGATE:
case SAMPLE_POLYFLAGS_TEAM1PHASEGATE:
case SAMPLE_POLYFLAGS_TEAM2PHASEGATE:
UTIL_DrawLine(INDEXENT(1), FromLoc, ToLoc, 255, 128, 128);
break;
default:

View file

@ -17,6 +17,7 @@
#include "../AvHWeldable.h"
#include "../AvHServerUtil.h"
#include "../AvHGamerules.h"
#include "../../dlls/triggers.h"
@ -104,13 +105,13 @@ struct NavMeshTileHeader
struct OffMeshConnectionDef
{
bool bIsActive = false;
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;
char Area = 0;
short Flag = 0;
unsigned char Area = 0;
unsigned short Flag = 0;
};
struct FastLZCompressor : public dtTileCacheCompressor
@ -192,8 +193,14 @@ struct MeshProcess : public dtTileCacheMeshProcess
unsigned short OffMeshFlags[MAX_OFFMESH_CONNS];
unsigned int OffMeshIDs[MAX_OFFMESH_CONNS];
bool bNavDataDirty = false;
OffMeshConnectionDef ConnectionDefinitions[MAX_OFFMESH_CONNS];
vector<OffMeshConnectionDef> OffMeshConnections;
unsigned int NextUserID = 0;
inline MeshProcess()
{}
@ -202,57 +209,113 @@ struct MeshProcess : public dtTileCacheMeshProcess
}
int AddOffMeshConnectionDef(Vector Start, Vector End, unsigned char area, unsigned short flag, bool bBiDirectional)
void AddOffMeshConnectionDef(Vector Start, Vector End, unsigned char area, unsigned short flag, bool bBiDirectional, AvHAIOffMeshConnection* ConnectionRef)
{
float spos[3] = { Start.x, Start.z, -Start.y };
float epos[3] = { End.x, End.z, -End.y };
OffMeshConnectionDef NewDefinition;
NewDefinition.Area = area;
NewDefinition.bBiDir = bBiDirectional;
NewDefinition.spos[0] = Start.x;
NewDefinition.spos[1] = Start.z;
NewDefinition.spos[2] = -Start.y;
NewDefinition.epos[0] = End.x;
NewDefinition.epos[1] = End.z;
NewDefinition.epos[2] = -End.y;
NewDefinition.Flag = flag;
NewDefinition.Rad = 18.0f;
NewDefinition.UserID = NextUserID;
if (NumOffMeshConns >= MAX_OFFMESH_CONNS) return -1;
float* v = &OffMeshVerts[NumOffMeshConns * 3 * 2];
OffMeshRads[NumOffMeshConns] = 18.0f;
OffMeshDirs[NumOffMeshConns] = bBiDirectional;
OffMeshAreas[NumOffMeshConns] = area;
OffMeshFlags[NumOffMeshConns] = flag;
OffMeshIDs[NumOffMeshConns] = 1000 + NumOffMeshConns;
dtVcopy(&v[0], spos);
dtVcopy(&v[3], epos);
NumOffMeshConns++;
return NumOffMeshConns - 1;
}
void RemoveOffMeshConnectionDef(int Index)
{
if (Index > -1 && Index < MAX_OFFMESH_CONNS)
if (ConnectionRef)
{
NumOffMeshConns--;
float* src = &OffMeshVerts[NumOffMeshConns * 3 * 2];
float* dst = &OffMeshVerts[Index * 3 * 2];
dtVcopy(&dst[0], &src[0]);
dtVcopy(&dst[3], &src[3]);
OffMeshRads[Index] = OffMeshRads[NumOffMeshConns];
OffMeshDirs[Index] = OffMeshDirs[NumOffMeshConns];
OffMeshAreas[Index] = OffMeshAreas[NumOffMeshConns];
OffMeshFlags[Index] = OffMeshFlags[NumOffMeshConns];
ConnectionRef->MeshConnectionIndex = NextUserID;
}
NextUserID++;
OffMeshConnections.push_back(NewDefinition);
bNavDataDirty = true;
};
void RemoveOffMeshConnectionDef(int UserID)
{
for (auto it = OffMeshConnections.begin(); it != OffMeshConnections.end();)
{
if (it->UserID == UserID)
{
it = OffMeshConnections.erase(it);
}
else
{
it++;
}
}
bNavDataDirty = true;
}
void GetOffMeshConnectionPoints(int Index, Vector& OutStartLoc, Vector& OutEndLoc)
void UpdateOffMeshData()
{
int CurrIndex = 0;
int VertIndex = 0;
for (auto it = OffMeshConnections.begin(); it != OffMeshConnections.end(); it++)
{
OffMeshVerts[VertIndex++] = it->spos[0];
OffMeshVerts[VertIndex++] = it->spos[1];
OffMeshVerts[VertIndex++] = it->spos[2];
OffMeshVerts[VertIndex++] = it->epos[0];
OffMeshVerts[VertIndex++] = it->epos[1];
OffMeshVerts[VertIndex++] = it->epos[2];
OffMeshRads[CurrIndex] = it->Rad;
OffMeshDirs[CurrIndex] = it->bBiDir;
OffMeshAreas[CurrIndex] = it->Area;
OffMeshFlags[CurrIndex] = it->Flag;
OffMeshIDs[CurrIndex] = it->UserID;
CurrIndex++;
}
NumOffMeshConns = OffMeshConnections.size();
bNavDataDirty = false;
}
void PopulateOffMeshConnectionVector()
{
OffMeshConnections.clear();
for (int i = 0; i < NumOffMeshConns; i++)
{
float* v = &OffMeshVerts[i*3*2];
Vector StartPos = Vector(v[0], -v[2], v[1]);
Vector EndPos = Vector(v[3], -v[5], v[4]);
AddOffMeshConnectionDef(StartPos, EndPos, OffMeshAreas[i], OffMeshFlags[i], OffMeshDirs[i], nullptr);
}
bNavDataDirty = false;
}
void GetOffMeshConnectionPoints(int UserID, Vector& OutStartLoc, Vector& OutEndLoc)
{
OutStartLoc = ZERO_VECTOR;
OutEndLoc = ZERO_VECTOR;
if (Index > -1 && Index < MAX_OFFMESH_CONNS)
for (auto it = OffMeshConnections.begin(); it != OffMeshConnections.end(); it++)
{
float* src = &OffMeshVerts[Index * 3 * 2];
if (it->UserID == UserID)
{
OutStartLoc.x = it->spos[0];
OutStartLoc.y = -it->spos[2];
OutStartLoc.z = it->spos[1];
OutStartLoc.x = src[0];
OutStartLoc.y = -src[2];
OutStartLoc.z = src[1];
OutEndLoc.x = it->epos[0];
OutEndLoc.y = -it->epos[2];
OutEndLoc.z = it->epos[1];
OutEndLoc.x = src[3];
OutEndLoc.y = -src[5];
OutEndLoc.z = src[4];
}
return;
}
}
}
void DrawAllConnections(float DrawTime)
@ -260,12 +323,12 @@ struct MeshProcess : public dtTileCacheMeshProcess
Vector StartLine = ZERO_VECTOR;
Vector EndLine = ZERO_VECTOR;
for (int i = 0; i < NumOffMeshConns; i++)
for (auto it = OffMeshConnections.begin(); it != OffMeshConnections.end(); it++)
{
Vector StartLine = Vector(OffMeshVerts[i * 6], -OffMeshVerts[(i * 6) + 2], OffMeshVerts[(i * 6) + 1]);
Vector EndLine = Vector(OffMeshVerts[(i * 6) + 3], -OffMeshVerts[(i * 6) + 5], OffMeshVerts[(i * 6) + 4]);
Vector StartLine = Vector(it->spos[0], -it->spos[2], it->spos[1]);
Vector EndLine = Vector(it->epos[0], -it->epos[2], it->epos[1]);
switch (OffMeshFlags[i])
switch (it->Flag)
{
case SAMPLE_POLYFLAGS_WALK:
UTIL_DrawLine(INDEXENT(1), StartLine, EndLine, DrawTime, 255, 255, 255);
@ -282,7 +345,8 @@ struct MeshProcess : public dtTileCacheMeshProcess
case SAMPLE_POLYFLAGS_LADDER:
UTIL_DrawLine(INDEXENT(1), StartLine, EndLine, DrawTime, 0, 0, 255);
break;
case SAMPLE_POLYFLAGS_PHASEGATE:
case SAMPLE_POLYFLAGS_TEAM1PHASEGATE:
case SAMPLE_POLYFLAGS_TEAM2PHASEGATE:
UTIL_DrawLine(INDEXENT(1), StartLine, EndLine, DrawTime, 255, 128, 128);
break;
default:
@ -345,6 +409,11 @@ struct MeshProcess : public dtTileCacheMeshProcess
}
}
if (bNavDataDirty)
{
UpdateOffMeshData();
}
params->offMeshConAreas = OffMeshAreas;
params->offMeshConCount = NumOffMeshConns;
params->offMeshConDir = OffMeshDirs;
@ -800,6 +869,8 @@ bool LoadNavMesh(const char* mapname)
fseek(savedFile, header.OffMeshConVertsOffset, SEEK_SET);
ReadResult = fread(m_tmproc->OffMeshVerts, header.OffMeshConVertsLength, 1, savedFile);
m_tmproc->PopulateOffMeshConnectionVector();
// TODO: Need to pass all off mesh connection verts, areas, flags etc as arrays to m_tmproc. Needs to be exported from recast as such
status = NavMeshes[REGULAR_NAV_MESH].tileCache->init(&header.regularCacheParams, m_talloc, m_tcomp, m_tmproc);
@ -1015,7 +1086,8 @@ void UTIL_PopulateBaseNavProfiles()
BaseNavProfiles[SKULK_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_BLOCKED, 1.0f);
BaseNavProfiles[SKULK_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_FALLDAMAGE, 1.0f);
BaseNavProfiles[SKULK_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_WALLCLIMB, 1.0f);
BaseNavProfiles[SKULK_BASE_NAV_PROFILE].Filters.addExcludeFlags(SAMPLE_POLYFLAGS_PHASEGATE);
BaseNavProfiles[SKULK_BASE_NAV_PROFILE].Filters.addExcludeFlags(SAMPLE_POLYFLAGS_TEAM1PHASEGATE);
BaseNavProfiles[SKULK_BASE_NAV_PROFILE].Filters.addExcludeFlags(SAMPLE_POLYFLAGS_TEAM2PHASEGATE);
BaseNavProfiles[SKULK_BASE_NAV_PROFILE].Filters.addExcludeFlags(SAMPLE_POLYFLAGS_DUCKJUMP);
BaseNavProfiles[SKULK_BASE_NAV_PROFILE].Filters.addExcludeFlags(SAMPLE_POLYFLAGS_WELD);
@ -1030,7 +1102,8 @@ void UTIL_PopulateBaseNavProfiles()
BaseNavProfiles[GORGE_BASE_NAV_PROFILE].Filters.setIncludeFlags(0xFFFF);
BaseNavProfiles[GORGE_BASE_NAV_PROFILE].Filters.setExcludeFlags(0);
BaseNavProfiles[GORGE_BASE_NAV_PROFILE].Filters.addExcludeFlags(SAMPLE_POLYFLAGS_WALLCLIMB);
BaseNavProfiles[GORGE_BASE_NAV_PROFILE].Filters.addExcludeFlags(SAMPLE_POLYFLAGS_PHASEGATE);
BaseNavProfiles[GORGE_BASE_NAV_PROFILE].Filters.addExcludeFlags(SAMPLE_POLYFLAGS_TEAM1PHASEGATE);
BaseNavProfiles[GORGE_BASE_NAV_PROFILE].Filters.addExcludeFlags(SAMPLE_POLYFLAGS_TEAM2PHASEGATE);
BaseNavProfiles[GORGE_BASE_NAV_PROFILE].Filters.addExcludeFlags(SAMPLE_POLYFLAGS_DUCKJUMP);
BaseNavProfiles[GORGE_BASE_NAV_PROFILE].Filters.addExcludeFlags(SAMPLE_POLYFLAGS_WELD);
@ -1045,7 +1118,8 @@ void UTIL_PopulateBaseNavProfiles()
BaseNavProfiles[LERK_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_WALLCLIMB, 1.0f);
BaseNavProfiles[LERK_BASE_NAV_PROFILE].Filters.setIncludeFlags(0xFFFF);
BaseNavProfiles[LERK_BASE_NAV_PROFILE].Filters.setExcludeFlags(0);
BaseNavProfiles[LERK_BASE_NAV_PROFILE].Filters.addExcludeFlags(SAMPLE_POLYFLAGS_PHASEGATE);
BaseNavProfiles[LERK_BASE_NAV_PROFILE].Filters.addExcludeFlags(SAMPLE_POLYFLAGS_TEAM1PHASEGATE);
BaseNavProfiles[LERK_BASE_NAV_PROFILE].Filters.addExcludeFlags(SAMPLE_POLYFLAGS_TEAM2PHASEGATE);
BaseNavProfiles[LERK_BASE_NAV_PROFILE].Filters.addExcludeFlags(SAMPLE_POLYFLAGS_WELD);
BaseNavProfiles[FADE_BASE_NAV_PROFILE].NavMeshIndex = REGULAR_NAV_MESH;
@ -1059,7 +1133,8 @@ void UTIL_PopulateBaseNavProfiles()
BaseNavProfiles[FADE_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_WALLCLIMB, 1.0f);
BaseNavProfiles[FADE_BASE_NAV_PROFILE].Filters.setIncludeFlags(0xFFFF);
BaseNavProfiles[FADE_BASE_NAV_PROFILE].Filters.setExcludeFlags(0);
BaseNavProfiles[FADE_BASE_NAV_PROFILE].Filters.addExcludeFlags(SAMPLE_POLYFLAGS_PHASEGATE);
BaseNavProfiles[FADE_BASE_NAV_PROFILE].Filters.addExcludeFlags(SAMPLE_POLYFLAGS_TEAM1PHASEGATE);
BaseNavProfiles[FADE_BASE_NAV_PROFILE].Filters.addExcludeFlags(SAMPLE_POLYFLAGS_TEAM2PHASEGATE);
BaseNavProfiles[FADE_BASE_NAV_PROFILE].Filters.addExcludeFlags(SAMPLE_POLYFLAGS_WELD);
BaseNavProfiles[ONOS_BASE_NAV_PROFILE].NavMeshIndex = ONOS_NAV_MESH;
@ -1073,7 +1148,8 @@ void UTIL_PopulateBaseNavProfiles()
BaseNavProfiles[ONOS_BASE_NAV_PROFILE].Filters.setIncludeFlags(0xFFFF);
BaseNavProfiles[ONOS_BASE_NAV_PROFILE].Filters.setExcludeFlags(0);
BaseNavProfiles[ONOS_BASE_NAV_PROFILE].Filters.addExcludeFlags(SAMPLE_POLYFLAGS_WALLCLIMB);
BaseNavProfiles[ONOS_BASE_NAV_PROFILE].Filters.addExcludeFlags(SAMPLE_POLYFLAGS_PHASEGATE);
BaseNavProfiles[ONOS_BASE_NAV_PROFILE].Filters.addExcludeFlags(SAMPLE_POLYFLAGS_TEAM1PHASEGATE);
BaseNavProfiles[ONOS_BASE_NAV_PROFILE].Filters.addExcludeFlags(SAMPLE_POLYFLAGS_TEAM2PHASEGATE);
BaseNavProfiles[ONOS_BASE_NAV_PROFILE].Filters.addExcludeFlags(SAMPLE_POLYFLAGS_WELD);
BaseNavProfiles[ONOS_BASE_NAV_PROFILE].Filters.addExcludeFlags(SAMPLE_POLYFLAGS_NOONOS);
@ -1318,85 +1394,6 @@ static float frand()
return (float)rand() / (float)RAND_MAX;
}
dtStatus FindPhaseGatePathToPoint(const nav_profile& NavProfile, Vector FromLocation, Vector ToLocation, bot_path_node* path, int* pathSize, float MaxAcceptableDistance)
{
*pathSize = 0;
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 DT_FAILURE;
}
bot_path_node PathToPhaseStart[MAX_AI_PATH_SIZE];
memset(PathToPhaseStart, 0, sizeof(PathToPhaseStart));
int PhaseStartPathSize = 0;
bot_path_node PathToFinalDestination[MAX_AI_PATH_SIZE];
memset(PathToFinalDestination, 0, sizeof(PathToFinalDestination));
int PhaseEndPathSize = 0;
DeployableSearchFilter PhaseGateSearch;
PhaseGateSearch.DeployableTypes = STRUCTURE_MARINE_PHASEGATE;
PhaseGateSearch.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED;
PhaseGateSearch.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING;
PhaseGateSearch.ReachabilityFlags = AI_REACHABILITY_MARINE;
AvHAIBuildableStructure* StartPhaseGate = AITAC_FindClosestDeployableToLocation(FromLocation, &PhaseGateSearch);
AvHAIBuildableStructure* EndPhaseGate = AITAC_FindClosestDeployableToLocation(ToLocation, &PhaseGateSearch);
if (!StartPhaseGate || !EndPhaseGate || (StartPhaseGate == EndPhaseGate)) { return DT_FAILURE; }
float TotalDist = vDist2DSq(FromLocation, StartPhaseGate->edict->v.origin) + vDist2DSq(EndPhaseGate->edict->v.origin, ToLocation);
if (TotalDist > vDist2DSq(FromLocation, ToLocation)) { return DT_FAILURE; }
dtStatus RouteToFirstPhaseGate = FindPathClosestToPoint(NavProfile, FromLocation, StartPhaseGate->edict->v.origin, PathToPhaseStart, &PhaseStartPathSize, max_ai_use_reach);
if (dtStatusFailed(RouteToFirstPhaseGate))
{
return DT_FAILURE;
}
dtStatus RouteToFinalPoint = FindPathClosestToPoint(NavProfile, EndPhaseGate->edict->v.origin, ToLocation, PathToFinalDestination, &PhaseEndPathSize, MaxAcceptableDistance);
if (dtStatusFailed(RouteToFinalPoint))
{
return DT_FAILURE;
}
// Now we join together the path to the starting phase gate and the path from the phase destination to the end, and add the phase itself in the middle
int CurrPathIndex = 0;
for (int i = 0; i < PhaseStartPathSize; i++)
{
memcpy(&path[CurrPathIndex++], &PathToPhaseStart[i], sizeof(bot_path_node));
}
// Add a node to inform the bot they have to use the phase gate
path[CurrPathIndex].Location = EndPhaseGate->edict->v.origin + Vector(0.0f, 0.0f, 10.0f);
path[CurrPathIndex].area = SAMPLE_POLYAREA_GROUND;
path[CurrPathIndex].flag = SAMPLE_POLYFLAGS_PHASEGATE;
path[CurrPathIndex].poly = UTIL_GetNearestPolyRefForEntity(EndPhaseGate->edict);
path[CurrPathIndex].requiredZ = EndPhaseGate->edict->v.origin.z;
CurrPathIndex++;
// Append the path from the destination phase to the end
for (int i = 1; i < PhaseEndPathSize; i++)
{
memcpy(&path[CurrPathIndex++], &PathToFinalDestination[i], sizeof(bot_path_node));
}
*pathSize = CurrPathIndex;
return DT_SUCCESS;
}
// Special path finding that takes flight movement into account
dtStatus FindFlightPathToPoint(const nav_profile &NavProfile, Vector FromLocation, Vector ToLocation, bot_path_node* path, int* pathSize, float MaxAcceptableDistance)
{
@ -1836,22 +1833,6 @@ dtStatus FindPathClosestToPoint(AvHAIPlayer* pBot, const BotMoveStyle MoveStyle,
return DT_FAILURE;
}
DeployableSearchFilter PGFilter;
PGFilter.DeployableTypes = STRUCTURE_MARINE_PHASEGATE;
PGFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED;
PGFilter.bConsiderPhaseDistance = false;
if (IsPlayerMarine(pBot->Edict) && AITAC_GetNumDeployablesNearLocation(ZERO_VECTOR, &PGFilter) > 1)
{
dtStatus PhaseStatus = FindPhaseGatePathToPoint(pBot->BotNavInfo.NavProfile, pBot->Edict->v.origin, ToLocation, path, pathSize, MaxAcceptableDistance);
if (dtStatusSucceed(PhaseStatus))
{
pBot->BotNavInfo.CurrentPathPoint = 1;
return DT_SUCCESS;
}
}
float pStartPos[3] = { FromLocation.x, FromLocation.z, -FromLocation.y };
float pEndPos[3] = { ToLocation.x, ToLocation.z, -ToLocation.y };
@ -2085,11 +2066,11 @@ bool HasBotReachedPathPoint(const AvHAIPlayer* pBot)
SamplePolyFlags CurrentNavFlag = (SamplePolyFlags)pBot->BotNavInfo.CurrentPath[pBot->BotNavInfo.CurrentPathPoint].flag;
Vector CurrentMoveDest = pBot->BotNavInfo.CurrentPath[pBot->BotNavInfo.CurrentPathPoint].Location;
Vector PrevMoveDest = pBot->BotNavInfo.CurrentPath[pBot->BotNavInfo.CurrentPathPoint - 1].Location;
Vector PrevMoveDest = pBot->BotNavInfo.CurrentPath[pBot->BotNavInfo.CurrentPathPoint].FromLocation;
bool bIsAtFinalPathPoint = (pBot->BotNavInfo.CurrentPathPoint == (pBot->BotNavInfo.PathSize - 1));
Vector ClosestPointToPath = vClosestPointOnLine2D(pBot->BotNavInfo.CurrentPath[pBot->BotNavInfo.CurrentPathPoint - 1].Location, pBot->BotNavInfo.CurrentPath[pBot->BotNavInfo.CurrentPathPoint].Location, pEdict->v.origin);
Vector ClosestPointToPath = vClosestPointOnLine2D(PrevMoveDest, CurrentMoveDest, pEdict->v.origin);
bool bDestIsDirectlyReachable = UTIL_PointIsDirectlyReachable(CurrentPos, CurrentMoveDest);
bool bAtOrPastDestination = vEquals2D(ClosestPointToPath, CurrentMoveDest, 1.0f) && bDestIsDirectlyReachable;
@ -2145,6 +2126,9 @@ bool HasBotReachedPathPoint(const AvHAIPlayer* pBot)
{
return (fabs(pBot->CollisionHullBottomLocation.z - CurrentMoveDest.z) < 50.0f);
}
case SAMPLE_POLYFLAGS_TEAM1PHASEGATE:
case SAMPLE_POLYFLAGS_TEAM2PHASEGATE:
return (vDist2DSq(pBot->CurrentFloorPosition, CurrentMoveDest) < sqrf(32.0f));
default:
return (bAtOrPastDestination && UTIL_QuickTrace(pEdict, pEdict->v.origin, CurrentMoveDest));
}
@ -2961,7 +2945,8 @@ void NewMove(AvHAIPlayer* pBot)
}
}
break;
case SAMPLE_POLYFLAGS_PHASEGATE:
case SAMPLE_POLYFLAGS_TEAM1PHASEGATE:
case SAMPLE_POLYFLAGS_TEAM2PHASEGATE:
PhaseGateMove(pBot, MoveFrom, MoveTo);
break;
default:
@ -3583,7 +3568,7 @@ bool IsBotOffPath(const AvHAIPlayer* pBot)
// If we're trying to use a phase gate, then we're fine as long as there is a phase gate within reach at the start and end teleport points
if (pBot->BotNavInfo.CurrentPath[pBot->BotNavInfo.CurrentPathPoint].flag == SAMPLE_POLYFLAGS_PHASEGATE)
if (pBot->BotNavInfo.CurrentPath[pBot->BotNavInfo.CurrentPathPoint].flag == SAMPLE_POLYFLAGS_TEAM1PHASEGATE || pBot->BotNavInfo.CurrentPath[pBot->BotNavInfo.CurrentPathPoint].flag == SAMPLE_POLYFLAGS_TEAM2PHASEGATE)
{
DeployableSearchFilter PGFilter;
PGFilter.DeployableTypes = STRUCTURE_MARINE_PHASEGATE;
@ -4705,7 +4690,7 @@ bool AbortCurrentMove(AvHAIPlayer* pBot, const Vector NewDestination)
}
}
if (flag == SAMPLE_POLYFLAGS_PHASEGATE)
if (flag == SAMPLE_POLYFLAGS_TEAM1PHASEGATE || flag == SAMPLE_POLYFLAGS_TEAM2PHASEGATE)
{
return true;
}
@ -4842,6 +4827,17 @@ void MarineUpdateBotMoveProfile(AvHAIPlayer* pBot, BotMoveStyle MoveStyle)
}
}
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.getExcludeFlags() & ExcludePhaseGateFlag))
{
pBot->BotNavInfo.bNavProfileChanged = true;
NavProfile->Filters.removeExcludeFlags(IncludePhaseGateFlag);
NavProfile->Filters.addExcludeFlags(ExcludePhaseGateFlag);
}
if (MoveStyle == pBot->BotNavInfo.PreviousMoveStyle) { return; }
pBot->BotNavInfo.PreviousMoveStyle = MoveStyle;
@ -5517,7 +5513,7 @@ void BotFollowPath(AvHAIPlayer* pBot)
Vector TargetMoveLocation = BotNavInfo->CurrentPath[BotNavInfo->CurrentPathPoint].Location;
bool bIsUsingPhaseGate = (BotNavInfo->CurrentPath[BotNavInfo->CurrentPathPoint].flag == SAMPLE_POLYFLAGS_PHASEGATE);
bool bIsUsingPhaseGate = (BotNavInfo->CurrentPath[BotNavInfo->CurrentPathPoint].flag == SAMPLE_POLYFLAGS_TEAM1PHASEGATE || BotNavInfo->CurrentPath[BotNavInfo->CurrentPathPoint].flag == SAMPLE_POLYFLAGS_TEAM2PHASEGATE);
bool bIsJumping = (BotNavInfo->CurrentPath[BotNavInfo->CurrentPathPoint].flag == SAMPLE_POLYFLAGS_JUMP);
@ -7175,35 +7171,61 @@ unsigned char UTIL_GetNextBotCurrentPathArea(AvHAIPlayer* pBot)
return pBot->BotNavInfo.CurrentPath[pBot->BotNavInfo.CurrentPathPoint + 1].area;
}
int UTIL_AddOffMeshConnection(Vector StartLoc, Vector EndLoc, unsigned char area, unsigned char flags, bool bBiDirectional)
void UTIL_AddOffMeshConnection(Vector StartLoc, Vector EndLoc, unsigned char area, unsigned short flags, bool bBiDirectional, AvHAIOffMeshConnection* NewConnectionDef)
{
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;
bool bMeshModified = false;
if (NavMeshes[REGULAR_NAV_MESH].tileCache)
{
NewConnectionDef->MeshConnectionIndex = -1;
MeshProcess* m_tmproc = (MeshProcess*)NavMeshes[REGULAR_NAV_MESH].tileCache->getMeshProcess();
if (m_tmproc)
{
return m_tmproc->AddOffMeshConnectionDef(StartLoc, EndLoc, area, flags, bBiDirectional);
UTIL_OnOffMeshConnectionModified(StartLoc, EndLoc);
m_tmproc->AddOffMeshConnectionDef(ConnStart, ConnEnd, area, flags, bBiDirectional, NewConnectionDef);
if (NewConnectionDef->MeshConnectionIndex > -1) { bMeshModified = true; }
}
}
if (bMeshModified)
{
UTIL_OnOffMeshConnectionModified(ConnStart, ConnEnd);
}
}
void UTIL_RemoveOffMeshConnection(int ConnectionIndex)
void UTIL_RemoveOffMeshConnections(AvHAIOffMeshConnection* NewConnectionDef)
{
Vector StartLoc, EndLoc;
if (NewConnectionDef->MeshConnectionIndex < 0) { return; }
Vector StartLoc, EndLoc;
if (NavMeshes[REGULAR_NAV_MESH].tileCache)
{
MeshProcess* m_tmproc = (MeshProcess*)NavMeshes[REGULAR_NAV_MESH].tileCache->getMeshProcess();
if (m_tmproc)
{
m_tmproc->GetOffMeshConnectionPoints(ConnectionIndex, StartLoc, EndLoc);
m_tmproc->RemoveOffMeshConnectionDef(ConnectionIndex);
m_tmproc->GetOffMeshConnectionPoints(NewConnectionDef->MeshConnectionIndex, StartLoc, EndLoc);
m_tmproc->RemoveOffMeshConnectionDef(NewConnectionDef->MeshConnectionIndex);
NewConnectionDef->MeshConnectionIndex = -1;
}
}
NewConnectionDef->MeshConnectionIndex = -1;
UTIL_OnOffMeshConnectionModified(StartLoc, EndLoc);
}

View file

@ -56,11 +56,12 @@ enum SamplePolyFlags
SAMPLE_POLYFLAGS_JUMP = 1 << 5, // Requires a regular jump to traverse
SAMPLE_POLYFLAGS_DUCKJUMP = 1 << 6, // Requires a duck-jump to traverse
SAMPLE_POLYFLAGS_NOONOS = 1 << 7, // This movement is not allowed by onos
SAMPLE_POLYFLAGS_PHASEGATE = 1 << 8, // Requires using a phase gate to traverse
SAMPLE_POLYFLAGS_TEAM1STRUCTURE = 1 << 9, // A team 1 structure is in the way that cannot be jumped over. Impassable to team 1 players
SAMPLE_POLYFLAGS_TEAM2STRUCTURE = 1 << 10, // A team 2 structure is in the way that cannot be jumped over. Impassable to team 2 players
SAMPLE_POLYFLAGS_WELD = 1 << 11, // Requires a welder to get through here
SAMPLE_POLYFLAGS_DOOR = 1 << 12, // Requires a welder to get through here
SAMPLE_POLYFLAGS_TEAM1PHASEGATE = 1 << 8, // Requires using a phase gate to traverse (team 1 only)
SAMPLE_POLYFLAGS_TEAM2PHASEGATE = 1 << 9, // Requires using a phase gate to traverse (team 2 only)
SAMPLE_POLYFLAGS_TEAM1STRUCTURE = 1 << 10, // A team 1 structure is in the way that cannot be jumped over. Impassable to team 1 players (assume cannot teamkill own structures)
SAMPLE_POLYFLAGS_TEAM2STRUCTURE = 1 << 11, // A team 2 structure is in the way that cannot be jumped over. Impassable to team 2 players (assume cannot teamkill own structures)
SAMPLE_POLYFLAGS_WELD = 1 << 12, // Requires a welder to get through here
SAMPLE_POLYFLAGS_DOOR = 1 << 13, // Requires a welder to get through here
SAMPLE_POLYFLAGS_DISABLED = 1 << 15, // Disabled, not usable by anyone
SAMPLE_POLYFLAGS_ALL = 0xffff // All abilities.
@ -92,7 +93,7 @@ typedef struct _NAV_DOOR
{
CBaseToggle* DoorEntity = nullptr;
edict_t* DoorEdict = nullptr; // Reference to the func_door
unsigned int ObstacleRefs[32][8]; // Dynamic obstacle ref. Used to add/remove the obstacle as the door is opened/closed
unsigned int ObstacleRefs[32][MAX_NAV_MESHES]; // Dynamic obstacle ref. Used to add/remove the obstacle as the door is opened/closed
int NumObstacles = 0;
vector<DoorTrigger> TriggerEnts; // Reference to the trigger edicts (e.g. func_trigger, func_button etc.)
DoorActivationType ActivationType = DOOR_NONE; // How the door should be opened
@ -103,7 +104,7 @@ typedef struct _NAV_DOOR
typedef struct _NAV_WELDABLE
{
edict_t* WeldableEdict = nullptr;
unsigned int ObstacleRefs[32][8];
unsigned int ObstacleRefs[32][MAX_NAV_MESHES];
int NumObstacles = 0;
} nav_weldable;
@ -133,7 +134,6 @@ static const int TILECACHESET_VERSION = 1;
static const float pExtents[3] = { 400.0f, 50.0f, 400.0f }; // Default extents (in GoldSrc units) to find the nearest spot on the nav mesh
static const float pReachableExtents[3] = { max_ai_use_reach, max_ai_use_reach, max_ai_use_reach }; // Extents (in GoldSrc units) to determine if something is on the nav mesh
static const int MAX_NAV_MESHES = 8; // Max number of nav meshes allowed. Currently 3 are used (one for building placement, one for the onos, and a regular one for everyone else)
static const int MAX_NAV_PROFILES = 16; // Max number of possible nav profiles. Currently 9 are used (see top of this header file)
static const int REGULAR_NAV_MESH = 0;
@ -293,8 +293,8 @@ void UTIL_RemoveTemporaryObstacle(unsigned int ObstacleRef);
void UTIL_RemoveTemporaryObstacles(unsigned int* ObstacleRefs);
int UTIL_AddOffMeshConnection(Vector StartLoc, Vector EndLoc, unsigned char area, unsigned char flags, bool bBiDirectional);
void UTIL_RemoveOffMeshConnection(int ConnectionIndex);
void UTIL_AddOffMeshConnection(Vector StartLoc, Vector EndLoc, unsigned char area, unsigned short flags, bool bBiDirectional, AvHAIOffMeshConnection* NewConnectionDef);
void UTIL_RemoveOffMeshConnections(AvHAIOffMeshConnection* NewConnectionDef);
void UTIL_OnOffMeshConnectionModified(Vector StartLoc, Vector EndLoc);
@ -332,9 +332,6 @@ void MoveDirectlyTo(AvHAIPlayer* pBot, const Vector Destination);
// Check if there are any players in our way and try to move around them. If we can't, then back up to let them through
void HandlePlayerAvoidance(AvHAIPlayer* pBot, const Vector MoveDestination);
// Special path finding that takes the presence of phase gates into account
dtStatus FindPhaseGatePathToPoint(const nav_profile& NavProfile, Vector FromLocation, Vector ToLocation, bot_path_node* path, int* pathSize, float MaxAcceptableDistance);
// Special path finding that takes the presence of phase gates into account
dtStatus FindFlightPathToPoint(const nav_profile& NavProfile, Vector FromLocation, Vector ToLocation, bot_path_node* path, int* pathSize, float MaxAcceptableDistance);

View file

@ -29,6 +29,9 @@ float AIStartedTime = 0.0f; // Used to give 5-second grace period before adding
extern int m_spriteTexture;
Vector DebugVector1 = ZERO_VECTOR;
Vector DebugVector2 = ZERO_VECTOR;
string BotNames[MAX_PLAYERS] = { "MrRobot",
"Wall-E",
"BeepBoop",
@ -739,4 +742,4 @@ void AIMGR_UpdateAIMapData()
void AIMGR_BotPrecache()
{
m_spriteTexture = PRECACHE_MODEL("sprites/zbeam6.spr");
}
}

View file

@ -52,6 +52,86 @@ extern nav_profile BaseNavProfiles[MAX_NAV_PROFILES]; // Array of nav profiles
bool bNavMeshModified = false;
std::vector<AvHAIBuildableStructure*> AITAC_FindAllDeployables(const Vector& Location, const DeployableSearchFilter* Filter)
{
std::vector<AvHAIBuildableStructure*> Result;
AvHTeamNumber TeamA = GetGameRules()->GetTeamANumber();
AvHTeamNumber TeamB = GetGameRules()->GetTeamBNumber();
float CurrMinDist = 0.0f;
float MinDistSq = sqrf(Filter->MinSearchRadius);
float MaxDistSq = sqrf(Filter->MaxSearchRadius);
bool bUseMinDist = MinDistSq > 0.1f;
bool bUseMaxDist = MaxDistSq > 0.1f;
if (Filter->DeployableTeam == TeamA || Filter->DeployableTeam == TEAM_IND)
{
for (auto& it : TeamAStructureMap)
{
if (it.second.StructureStatusFlags & Filter->ExcludeStatusFlags) { continue; }
if ((it.second.StructureStatusFlags & Filter->IncludeStatusFlags) != Filter->IncludeStatusFlags) { continue; }
if (Filter->ReachabilityFlags != AI_REACHABILITY_NONE)
{
unsigned int StructureReachabilityFlags = (it.second.TeamAReachabilityFlags | it.second.TeamBReachabilityFlags);
if (Filter->ReachabilityTeam != TEAM_IND)
{
StructureReachabilityFlags = (Filter->ReachabilityTeam == TeamA) ? it.second.TeamAReachabilityFlags : it.second.TeamBReachabilityFlags;
}
if (!(StructureReachabilityFlags & Filter->ReachabilityFlags)) { continue; }
}
if (it.second.StructureType & Filter->DeployableTypes)
{
float DistSq = (Filter->bConsiderPhaseDistance) ? sqrf(AITAC_GetPhaseDistanceBetweenPoints(it.second.Location, Location)) : vDist2DSq(it.second.Location, Location);
if ((!bUseMinDist || DistSq >= MinDistSq) && (!bUseMaxDist || DistSq <= MaxDistSq))
{
Result.push_back(&it.second);
}
}
}
}
if (Filter->DeployableTeam == TeamB || Filter->DeployableTeam == TEAM_IND)
{
for (auto& it : TeamBStructureMap)
{
if (it.second.StructureStatusFlags & Filter->ExcludeStatusFlags) { continue; }
if ((it.second.StructureStatusFlags & Filter->IncludeStatusFlags) != Filter->IncludeStatusFlags) { continue; }
if (Filter->ReachabilityFlags != AI_REACHABILITY_NONE)
{
unsigned int StructureReachabilityFlags = (it.second.TeamAReachabilityFlags | it.second.TeamBReachabilityFlags);
if (Filter->ReachabilityTeam != TEAM_IND)
{
StructureReachabilityFlags = (Filter->ReachabilityTeam == TeamA) ? it.second.TeamAReachabilityFlags : it.second.TeamBReachabilityFlags;
}
if (!(StructureReachabilityFlags & Filter->ReachabilityFlags)) { continue; }
}
if (it.second.StructureType & Filter->DeployableTypes)
{
float DistSq = (Filter->bConsiderPhaseDistance) ? sqrf(AITAC_GetPhaseDistanceBetweenPoints(it.second.Location, Location)) : vDist2DSq(it.second.Location, Location);
if ((!bUseMinDist || DistSq >= MinDistSq) && (!bUseMaxDist || DistSq <= MaxDistSq))
{
Result.push_back(&it.second);
}
}
}
}
return Result;
}
bool AITAC_DeployableExistsAtLocation(const Vector& Location, const DeployableSearchFilter* Filter)
{
AvHTeamNumber TeamA = GetGameRules()->GetTeamANumber();
@ -1019,6 +1099,7 @@ void AITAC_RefreshBuildableStructures()
{
if (it->second.LastSeen < StructureRefreshFrame)
{
AITAC_OnStructureDestroyed(&it->second);
UTIL_RemoveTemporaryObstacles(it->second.ObstacleRefs);
it = TeamAStructureMap.erase(it);
}
@ -1037,6 +1118,7 @@ void AITAC_RefreshBuildableStructures()
{
if (it->second.LastSeen < StructureRefreshFrame)
{
AITAC_OnStructureDestroyed(&it->second);
UTIL_RemoveTemporaryObstacles(it->second.ObstacleRefs);
it = TeamBStructureMap.erase(it);
}
@ -1347,6 +1429,10 @@ void AITAC_UpdateBuildableStructure(CBaseEntity* Structure)
BuildingMap[EntIndex].edict = BuildingEdict;
BuildingMap[EntIndex].StructureType = StructureType;
BuildingMap[EntIndex].OffMeshConnections.clear();
memset(&BuildingMap[EntIndex].ObstacleRefs, 0, sizeof(BuildingMap[EntIndex].ObstacleRefs));
bool bShouldCollide = UTIL_ShouldStructureCollide(StructureType);
if (bShouldCollide)
@ -1372,26 +1458,33 @@ void AITAC_UpdateBuildableStructure(CBaseEntity* Structure)
BuildingMap[EntIndex].Location = BaseBuildable->pev->origin;
}
BuildingMap[EntIndex].StructureStatusFlags = STRUCTURE_STATUS_NONE;
unsigned int NewFlags = STRUCTURE_STATUS_NONE;
if (BaseBuildable->GetIsBuilt())
{
BuildingMap[EntIndex].StructureStatusFlags |= STRUCTURE_STATUS_COMPLETED;
if (!(BuildingMap[EntIndex].StructureStatusFlags & STRUCTURE_STATUS_COMPLETED)) {
AITAC_OnStructureCompleted(&BuildingMap[EntIndex]);
}
NewFlags |= STRUCTURE_STATUS_COMPLETED;
}
if (UTIL_IsStructureElectrified(BuildingEdict))
{
BuildingMap[EntIndex].StructureStatusFlags |= STRUCTURE_STATUS_ELECTRIFIED;
NewFlags |= STRUCTURE_STATUS_ELECTRIFIED;
}
if (BuildingEdict->v.iuser4 & MASK_PARASITED)
{
BuildingMap[EntIndex].StructureStatusFlags |= STRUCTURE_STATUS_PARASITED;
NewFlags |= STRUCTURE_STATUS_PARASITED;
}
if (BaseBuildable->GetIsRecycling())
{
BuildingMap[EntIndex].StructureStatusFlags |= STRUCTURE_STATUS_RECYCLING;
if (!(BuildingMap[EntIndex].StructureStatusFlags & STRUCTURE_STATUS_RECYCLING))
{
AITAC_OnStructureBeginRecycling(&BuildingMap[EntIndex]);
}
NewFlags |= STRUCTURE_STATUS_RECYCLING;
}
float NewHealthPercent = (BuildingEdict->v.health / BuildingEdict->v.max_health);
@ -1405,9 +1498,10 @@ void AITAC_UpdateBuildableStructure(CBaseEntity* Structure)
if (gpGlobals->time - BuildingMap[EntIndex].lastDamagedTime < 10.0f)
{
BuildingMap[EntIndex].StructureStatusFlags |= STRUCTURE_STATUS_UNDERATTACK;
NewFlags |= STRUCTURE_STATUS_UNDERATTACK;
}
BuildingMap[EntIndex].StructureStatusFlags = NewFlags;
BuildingMap[EntIndex].LastSeen = StructureRefreshFrame;
}
@ -1446,6 +1540,105 @@ void AITAC_OnStructureCreated(AvHAIBuildableStructure* NewStructure)
}
void AITAC_OnStructureCompleted(AvHAIBuildableStructure* NewStructure)
{
if (NewStructure->StructureType == STRUCTURE_MARINE_PHASEGATE)
{
DeployableSearchFilter Filter;
Filter.DeployableTypes = STRUCTURE_MARINE_PHASEGATE;
Filter.DeployableTeam = (AvHTeamNumber)NewStructure->edict->v.team;
Filter.ReachabilityFlags = AI_REACHABILITY_NONE;
Filter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED;
// Get all other completed phase gates for this team and add bidirectional connections to them
std::vector<AvHAIBuildableStructure*> OtherPhaseGates = AITAC_FindAllDeployables(NewStructure->Location, &Filter);
SamplePolyFlags NewFlag = ((AvHTeamNumber)NewStructure->edict->v.team == GetGameRules()->GetTeamANumber()) ? SAMPLE_POLYFLAGS_TEAM1PHASEGATE : SAMPLE_POLYFLAGS_TEAM2PHASEGATE;
for (auto pg = OtherPhaseGates.begin(); pg != OtherPhaseGates.end(); pg++)
{
// Don't add off-mesh connections to ourselves!
if ((*pg) == NewStructure) { continue; }
AvHAIBuildableStructure* OtherPhaseGate = (*pg);
AvHAIOffMeshConnection NewConnection;
NewConnection.FromLocation = NewStructure->Location;
NewConnection.ToLocation = OtherPhaseGate->Location;
NewConnection.ConnectionFlags = NewFlag;
NewConnection.TargetObject = OtherPhaseGate->edict;
NewConnection.MeshConnectionIndex = -1;
UTIL_AddOffMeshConnection(NewStructure->Location, OtherPhaseGate->Location, SAMPLE_POLYAREA_GROUND, NewFlag, true, &NewConnection);
NewStructure->OffMeshConnections.push_back(NewConnection);
}
}
}
void AITAC_RemovePhaseGateConnections(AvHAIBuildableStructure* SourceGate, AvHAIBuildableStructure* TargetGate)
{
if (!SourceGate || !TargetGate) { return; }
for (auto it = SourceGate->OffMeshConnections.begin(); it != SourceGate->OffMeshConnections.end();)
{
if (it->TargetObject == TargetGate->edict)
{
UTIL_RemoveOffMeshConnections(&(*it));
it = SourceGate->OffMeshConnections.erase(it);
}
else
{
it++;
}
}
}
void AITAC_OnStructureBeginRecycling(AvHAIBuildableStructure* RecyclingStructure)
{
// For phase gates, treat them like they've been destroyed
if (RecyclingStructure->StructureType == STRUCTURE_MARINE_PHASEGATE)
{
AITAC_OnStructureDestroyed(RecyclingStructure);
}
}
void AITAC_OnStructureDestroyed(AvHAIBuildableStructure* DestroyedStructure)
{
if (DestroyedStructure->StructureType == STRUCTURE_MARINE_PHASEGATE)
{
// Eliminate all connections from this phase gate
for (auto it = DestroyedStructure->OffMeshConnections.begin(); it != DestroyedStructure->OffMeshConnections.begin();)
{
UTIL_RemoveOffMeshConnections(&(*it));
it = DestroyedStructure->OffMeshConnections.erase(it);
}
DestroyedStructure->OffMeshConnections.clear();
DeployableSearchFilter Filter;
Filter.DeployableTypes = STRUCTURE_MARINE_PHASEGATE;
Filter.DeployableTeam = (AvHTeamNumber)DestroyedStructure->edict->v.team;
Filter.ReachabilityFlags = AI_REACHABILITY_NONE;
Filter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED;
// Get all other completed phase gates for this team and remove any connections going to this structure
std::vector<AvHAIBuildableStructure*> OtherPhaseGates = AITAC_FindAllDeployables(DestroyedStructure->Location, &Filter);
for (auto it = OtherPhaseGates.begin(); it != OtherPhaseGates.end(); it++)
{
// Don't check for off-mesh connections from ourselves!
if ((*it) == DestroyedStructure) { continue; }
AvHAIBuildableStructure* OtherPhaseGate = (*it);
AITAC_RemovePhaseGateConnections(OtherPhaseGate, DestroyedStructure);
}
}
}
void AITAC_LinkAlienStructureToTask(AvHAIPlayer* pBot, AvHAIBuildableStructure* NewStructure)
{

View file

@ -21,6 +21,7 @@ static const float structure_inventory_refresh_rate = 0.2f;
static const float item_inventory_refresh_rate = 0.1f;
bool AITAC_DeployableExistsAtLocation(const Vector& Location, const DeployableSearchFilter* Filter);
std::vector<AvHAIBuildableStructure*> AITAC_FindAllDeployables(const Vector& Location, const DeployableSearchFilter* Filter);
AvHAIBuildableStructure* AITAC_FindClosestDeployableToLocation(const Vector& Location, const DeployableSearchFilter* Filter);
AvHAIBuildableStructure* AITAC_GetDeployableRefFromEdict(const edict_t* Structure);
AvHAIBuildableStructure* AITAC_GetNearestDeployableDirectlyReachable(AvHAIPlayer* pBot, const Vector Location, const DeployableSearchFilter* Filter);
@ -34,6 +35,9 @@ void AITAC_RefreshReachabilityForStructure(AvHAIBuildableStructure* Structu
void AITAC_RefreshReachabilityForResNode(AvHAIResourceNode* ResNode);
void AITAC_RefreshReachabilityForItem(AvHAIDroppedItem* Item);
void AITAC_OnStructureCreated(AvHAIBuildableStructure* NewStructure);
void AITAC_OnStructureCompleted(AvHAIBuildableStructure* NewStructure);
void AITAC_OnStructureBeginRecycling(AvHAIBuildableStructure* RecyclingStructure);
void AITAC_OnStructureDestroyed(AvHAIBuildableStructure* DestroyedStructure);
void AITAC_LinkDeployedItemToAction(AvHAIPlayer* CommanderBot, const AvHAIDroppedItem* NewItem);
void AITAC_LinkAlienStructureToTask(AvHAIPlayer* pBot, AvHAIBuildableStructure* NewStructure);

View file

@ -1414,6 +1414,12 @@ BOOL AvHGamerules::ClientCommand( CBasePlayer *pPlayer, const char *pcmd )
theSuccess = true;
}
}
else if (FStrEq(pcmd, "drawoffmesh"))
{
AIDEBUG_DrawOffMeshConnections(10.0f);
theSuccess = true;
}
else if (FStrEq(pcmd, "tracedoor"))
{
Vector TraceStart = GetPlayerEyePosition(theAvHPlayer->edict()); // origin + pev->view_ofs