mirror of
https://github.com/openmoh/openmohaa.git
synced 2025-05-09 12:08:12 +03:00
Refactored navigation with moh 2.0 features
This commit is contained in:
parent
51a0ee56aa
commit
d6b7fecb69
2 changed files with 161 additions and 155 deletions
|
@ -102,12 +102,11 @@ struct {
|
|||
float fMaxRangeSquared;
|
||||
float fMinAngle;
|
||||
float fMaxAngle;
|
||||
}
|
||||
|
||||
g_AttackParms[] = {
|
||||
{64 * 64, 2048 * 2048, 150.0f, 210.0f},
|
||||
{64 * 64, 2048 * 2048, 150.0f, 210.0f},
|
||||
{96 * 96, 2048 * 2048, 320.0f, 40.0f },
|
||||
} g_AttackParms[] = {
|
||||
{Square(64), Square(2048), 150.0f, 210.0f},
|
||||
{Square(64), Square(2048), 150.0f, 210.0f},
|
||||
{Square(96), Square(2048), 320.0f, 40.0f },
|
||||
{Square(96), Square(4096), 0.0f, 0.0f },
|
||||
};
|
||||
|
||||
PathSearch PathManager;
|
||||
|
@ -2098,6 +2097,7 @@ void *PathSearch::AllocPathNode(void)
|
|||
}
|
||||
|
||||
bulkNavMemory -= sizeof(PathNode);
|
||||
return bulkNavMemory;
|
||||
}
|
||||
|
||||
void PathSearch::FreePathNode(void *ptr)
|
||||
|
@ -2338,11 +2338,13 @@ void PathSearch::CreatePaths(void)
|
|||
"***********************************\n"
|
||||
);
|
||||
|
||||
gi.ClearResource();
|
||||
|
||||
t1 = gi.Milliseconds();
|
||||
|
||||
for (i = 0, ent = g_entities; i < game.maxentities; i++, ent++) {
|
||||
if (ent->entity && ent->entity->IsSubclassOfDoor()) {
|
||||
gi.unlinkentity(ent);
|
||||
ent->entity->unlink();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2359,32 +2361,30 @@ void PathSearch::CreatePaths(void)
|
|||
for (i = 0; i < nodecount; i++) {
|
||||
node = pathnodes[i];
|
||||
|
||||
start = node->origin + Vector(0, 0, 36.0f);
|
||||
end = node->origin - Vector(0, 0, 2048.0f);
|
||||
|
||||
droptofloor(node->origin, node);
|
||||
|
||||
node->centroid = node->origin;
|
||||
|
||||
if (node->nodeflags & PATH_DONT_LINK) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (j = i - 1; j >= 0; j--) {
|
||||
PathNode *node2 = pathnodes[j];
|
||||
|
||||
if (node->origin == node2->origin) {
|
||||
Com_Printf(
|
||||
"^~^~^ Duplicate node at (%.2f %.2f %.2f) not linked\n",
|
||||
node->origin[0],
|
||||
node->origin[1],
|
||||
node->origin[2]
|
||||
);
|
||||
node->nodeflags |= PATH_DONT_LINK;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!(node->nodeflags & PATH_DONT_LINK)) {
|
||||
for (j = i - 1; j >= 0; j--) {
|
||||
PathNode *node2 = pathnodes[j];
|
||||
|
||||
if (node2->origin == node->origin) {
|
||||
Com_Printf(
|
||||
"^~^~^ Duplicate node at (%.2f %.2f %.2f) not linked\n",
|
||||
node->origin[0],
|
||||
node->origin[1],
|
||||
node->origin[2]
|
||||
);
|
||||
node->nodeflags |= PATH_DONT_LINK;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!(node->nodeflags & PATH_DONT_LINK)) {
|
||||
node->Child = (pathway_t *)gi.Malloc(sizeof(pathway_t) * PATHMAP_NODES);
|
||||
}
|
||||
node->Child = (pathway_t *)gi.Malloc(sizeof(pathway_t) * PATHMAP_NODES);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2434,11 +2434,7 @@ void PathSearch::LoadAddToGrid2(PathNode *node, int x, int y)
|
|||
{
|
||||
MapCell *cell;
|
||||
|
||||
if (x > PATHMAP_GRIDSIZE || y > PATHMAP_GRIDSIZE) {
|
||||
cell = NULL;
|
||||
} else {
|
||||
cell = &PathMap[x][y];
|
||||
}
|
||||
cell = GetNodesInCell(x, y);
|
||||
|
||||
if (cell) {
|
||||
cell->AddNode(node);
|
||||
|
@ -2557,14 +2553,14 @@ bool PathSearch::ArchiveDynamic(Archiver& arc)
|
|||
int i;
|
||||
int count;
|
||||
|
||||
if (arc.Loading()) {
|
||||
if (arc.Saving()) {
|
||||
arc.ArchiveInteger(&nodecount);
|
||||
} else {
|
||||
arc.ArchiveInteger(&count);
|
||||
if (count != nodecount) {
|
||||
Com_Printf("Path file invalid - cannot load save game\n");
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
arc.ArchiveInteger(&nodecount);
|
||||
}
|
||||
|
||||
for (i = 0; i < nodecount; i++) {
|
||||
|
@ -2579,11 +2575,7 @@ void PathSearch::AddToGrid(PathNode *node, int x, int y)
|
|||
{
|
||||
MapCell *cell;
|
||||
|
||||
if (x > PATHMAP_GRIDSIZE || y > PATHMAP_GRIDSIZE) {
|
||||
cell = NULL;
|
||||
} else {
|
||||
cell = &PathMap[x][y];
|
||||
}
|
||||
cell = GetNodesInCell(x, y);
|
||||
|
||||
if (!cell) {
|
||||
return;
|
||||
|
@ -2591,9 +2583,10 @@ void PathSearch::AddToGrid(PathNode *node, int x, int y)
|
|||
|
||||
if (cell->NumNodes() >= PATHMAP_NODES) {
|
||||
Com_Printf("^~^~^ PathSearch::AddToGrid: Node overflow at ( %d, %d )\n", x, y);
|
||||
} else {
|
||||
cell->AddNode(node);
|
||||
return;
|
||||
}
|
||||
|
||||
cell->AddNode(node);
|
||||
}
|
||||
|
||||
bool PathSearch::Connect(PathNode *node, int x, int y)
|
||||
|
@ -2602,20 +2595,12 @@ bool PathSearch::Connect(PathNode *node, int x, int y)
|
|||
int i;
|
||||
PathNode *node2;
|
||||
|
||||
if (x > PATHMAP_GRIDSIZE || y > PATHMAP_GRIDSIZE) {
|
||||
cell = NULL;
|
||||
} else {
|
||||
cell = &PathMap[x][y];
|
||||
}
|
||||
cell = GetNodesInCell(x, y);
|
||||
|
||||
if (!cell) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (cell->numnodes <= 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (i = 0; i < cell->numnodes; i++) {
|
||||
node2 = pathnodes[cell->nodes[i]];
|
||||
|
||||
|
@ -2633,10 +2618,7 @@ bool PathSearch::Connect(PathNode *node, int x, int y)
|
|||
|
||||
bool PathNode::CheckPathTo(PathNode *node)
|
||||
{
|
||||
if (virtualNumChildren < NUM_PATHSPERNODE) {
|
||||
CheckPathToDefault(node, &Child[virtualNumChildren]);
|
||||
return true;
|
||||
} else {
|
||||
if (virtualNumChildren >= NUM_PATHSPERNODE) {
|
||||
Com_Printf(
|
||||
"^~^~^ %d paths per node at (%.2f %.2f %.2f) exceeded\n - use DONT_LINK on some nodes to conserve cpu and "
|
||||
"memory usage\n",
|
||||
|
@ -2648,6 +2630,9 @@ bool PathNode::CheckPathTo(PathNode *node)
|
|||
PathSearch::m_NodeCheckFailed = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
CheckPathToDefault(node, &Child[virtualNumChildren]);
|
||||
return true;
|
||||
}
|
||||
|
||||
qboolean CheckMove(Vector& origin, Vector& pos, short int *path_fallheight, float size)
|
||||
|
@ -2708,8 +2693,7 @@ qboolean CheckMove(Vector& origin, Vector& pos, short int *path_fallheight, floa
|
|||
air_z = mm.origin[2];
|
||||
}
|
||||
|
||||
dir[0] = pos[0] - mm.origin[0];
|
||||
dir[1] = pos[1] - mm.origin[1];
|
||||
VectorSub2D(pos, mm.origin, dir);
|
||||
|
||||
if (DotProduct2D(dir, mm.desired_dir) <= 0.1f) {
|
||||
error = mm.origin[2] - pos[2];
|
||||
|
@ -2717,15 +2701,11 @@ qboolean CheckMove(Vector& origin, Vector& pos, short int *path_fallheight, floa
|
|||
gi.Printf("error = %f\n", error);
|
||||
|
||||
*path_fallheight = (short)fallheight;
|
||||
if (fabs(error) > 94.0f) {
|
||||
if (mm.groundPlane) {
|
||||
return false;
|
||||
if (fabs(error) <= 94.0f) {
|
||||
if (error <= 0.0f || mm.groundPlane) {
|
||||
return true;
|
||||
}
|
||||
|
||||
mm.desired_dir[0] = dir[0];
|
||||
mm.desired_dir[1] = dir[1];
|
||||
VectorNormalize2D(mm.desired_dir);
|
||||
} else if (error > 0.0f && !mm.groundPlane) {
|
||||
end[0] = mm.origin[0];
|
||||
end[1] = mm.origin[1];
|
||||
end[2] = pos[2];
|
||||
|
@ -2738,22 +2718,20 @@ qboolean CheckMove(Vector& origin, Vector& pos, short int *path_fallheight, floa
|
|||
*path_fallheight = (short)test_fallheight + fallheight;
|
||||
return test_fallheight + fallheight <= 1024.0f;
|
||||
}
|
||||
|
||||
if (mm.groundPlane) {
|
||||
return false;
|
||||
}
|
||||
|
||||
mm.desired_dir[0] = dir[0];
|
||||
mm.desired_dir[1] = dir[1];
|
||||
VectorNormalize2D(mm.desired_dir);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (mm.groundPlane) {
|
||||
return false;
|
||||
}
|
||||
|
||||
VectorCopy2D(dir, mm.desired_dir);
|
||||
VectorNormalize2D(mm.desired_dir);
|
||||
}
|
||||
|
||||
if (mm.hit_obstacle) {
|
||||
gi.DPrintf("obstacle hit\n");
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2763,13 +2741,11 @@ qboolean CheckMove(Vector& origin, Vector& pos, short int *path_fallheight, floa
|
|||
void PathNode::CheckPathToDefault(PathNode *node, pathway_t *pathway)
|
||||
{
|
||||
float dist;
|
||||
float delta[2];
|
||||
vec2_t delta;
|
||||
Vector start;
|
||||
Vector end;
|
||||
|
||||
delta[0] = node->origin[0] - origin[0];
|
||||
delta[1] = node->origin[1] - origin[1];
|
||||
|
||||
VectorSub2D(node->origin, origin, delta);
|
||||
dist = VectorNormalize2D(delta);
|
||||
|
||||
if (dist >= 384.0f) {
|
||||
|
@ -2783,19 +2759,18 @@ void PathNode::CheckPathToDefault(PathNode *node, pathway_t *pathway)
|
|||
droptofloor(end, node);
|
||||
|
||||
if (CheckMove(start, end, &pathway->fallheight, 15.5f)) {
|
||||
pathway->dist = dist;
|
||||
pathway->dir[0] = delta[0];
|
||||
pathway->dir[1] = delta[1];
|
||||
pathway->dist = dist;
|
||||
VectorCopy2D(delta, pathway->dir);
|
||||
start.copyTo(pathway->pos1);
|
||||
end.copyTo(pathway->pos2);
|
||||
ConnectTo(node);
|
||||
}
|
||||
}
|
||||
|
||||
qboolean MapCell::AddNode(PathNode *node)
|
||||
void MapCell::AddNode(PathNode *node)
|
||||
{
|
||||
nodes[numnodes] = (short)node->nodenum;
|
||||
nodes[numnodes] = node->nodenum;
|
||||
numnodes++;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void PathSearch::AddNode(PathNode *node)
|
||||
|
@ -2822,26 +2797,42 @@ void PathSearch::Connect(PathNode *node)
|
|||
findFrame++;
|
||||
node->findCount = findFrame;
|
||||
|
||||
x = NodeCoordinate(node->origin[0]);
|
||||
y = NodeCoordinate(node->origin[1]);
|
||||
x = GridCoordinate(node->origin[0]);
|
||||
y = GridCoordinate(node->origin[1]);
|
||||
|
||||
if (Connect(node, x - 1, y - 1)) {
|
||||
if (Connect(node, x - 1, y)) {
|
||||
if (Connect(node, x - 1, y + 1)) {
|
||||
if (Connect(node, x, y - 1)) {
|
||||
if (Connect(node, x, y)) {
|
||||
if (Connect(node, x, y + 1)) {
|
||||
if (Connect(node, x + 1, y - 1)) {
|
||||
if (Connect(node, x + 1, y)) {
|
||||
Connect(node, x + 1, y + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!Connect(node, x - 1, y - 1)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Connect(node, x - 1, y)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Connect(node, x - 1, y + 1)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Connect(node, x, y - 1)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Connect(node, x, y)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Connect(node, x, y + 1)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Connect(node, x + 1, y - 1)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Connect(node, x + 1, y)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Connect(node, x + 1, y + 1);
|
||||
}
|
||||
|
||||
const_str PathNode::GetSpecialAttack(Actor *pActor)
|
||||
|
@ -2849,72 +2840,86 @@ const_str PathNode::GetSpecialAttack(Actor *pActor)
|
|||
int iSpecialAttack;
|
||||
const_str csAnimation;
|
||||
float fRangeSquared;
|
||||
float vDelta[2];
|
||||
vec2_t vDelta;
|
||||
float fAngle;
|
||||
float fMinRangeSquared;
|
||||
float fMaxRangeSquared;
|
||||
float fMinAngle;
|
||||
float fMaxAngle;
|
||||
|
||||
if (nodeflags & AI_CORNER_LEFT) {
|
||||
iSpecialAttack = 0;
|
||||
csAnimation = STRING_ANIM_CORNERLEFT_SCR;
|
||||
iSpecialAttack = 0;
|
||||
csAnimation = STRING_ANIM_CORNERLEFT_SCR;
|
||||
fMinRangeSquared = g_AttackParms[iSpecialAttack].fMinRangeSquared;
|
||||
fMaxRangeSquared = g_AttackParms[iSpecialAttack].fMaxRangeSquared;
|
||||
fMinAngle = g_AttackParms[iSpecialAttack].fMinAngle;
|
||||
fMaxAngle = g_AttackParms[iSpecialAttack].fMaxAngle;
|
||||
} else if (nodeflags & AI_CORNER_RIGHT) {
|
||||
iSpecialAttack = 1;
|
||||
csAnimation = STRING_ANIM_CORNERRIGHT_SCR;
|
||||
} else {
|
||||
if (nodeflags >= 0) {
|
||||
return STRING_NULL;
|
||||
iSpecialAttack = 1;
|
||||
csAnimation = STRING_ANIM_CORNERRIGHT_SCR;
|
||||
fMinRangeSquared = g_AttackParms[iSpecialAttack].fMinRangeSquared;
|
||||
fMaxRangeSquared = g_AttackParms[iSpecialAttack].fMaxRangeSquared;
|
||||
fMinAngle = g_AttackParms[iSpecialAttack].fMinAngle;
|
||||
fMaxAngle = g_AttackParms[iSpecialAttack].fMaxAngle;
|
||||
} else if (nodeflags & AI_CRATE) {
|
||||
iSpecialAttack = 2;
|
||||
csAnimation = STRING_ANIM_OVERATTACK_SCR;
|
||||
fMinRangeSquared = g_AttackParms[iSpecialAttack].fMinRangeSquared;
|
||||
fMaxRangeSquared = g_AttackParms[iSpecialAttack].fMaxRangeSquared;
|
||||
fMinAngle = g_AttackParms[iSpecialAttack].fMinAngle;
|
||||
fMaxAngle = g_AttackParms[iSpecialAttack].fMaxAngle;
|
||||
} else if (nodeflags & AI_LOW_WALL_ARC) {
|
||||
if (nodeflags & AI_DUCK) {
|
||||
csAnimation = STRING_ANIM_LOWWALL_SCR;
|
||||
} else {
|
||||
csAnimation = STRING_ANIM_HIGHWALL_SCR;
|
||||
}
|
||||
|
||||
iSpecialAttack = 2;
|
||||
csAnimation = STRING_ANIM_OVERATTACK_SCR;
|
||||
iSpecialAttack = 3;
|
||||
fMinRangeSquared = g_AttackParms[iSpecialAttack].fMinRangeSquared;
|
||||
fMaxRangeSquared = g_AttackParms[iSpecialAttack].fMaxRangeSquared;
|
||||
fMinAngle = 360 - m_fLowWallArc;
|
||||
fMaxAngle = m_fLowWallArc;
|
||||
} else {
|
||||
return STRING_NULL;
|
||||
}
|
||||
|
||||
if (pActor->m_Enemy) {
|
||||
vDelta[0] = pActor->m_Enemy->origin[0] - origin[0];
|
||||
vDelta[1] = pActor->m_Enemy->origin[1] - origin[1];
|
||||
VectorSub2D(pActor->m_Enemy->centroid, origin, vDelta);
|
||||
} else {
|
||||
vDelta[0] = pActor->m_vLastEnemyPos[0] - origin[0];
|
||||
vDelta[1] = pActor->m_vLastEnemyPos[1] - origin[1];
|
||||
VectorSub2D(pActor->m_vLastEnemyPos, origin, vDelta);
|
||||
}
|
||||
|
||||
fRangeSquared = vDelta[0] * vDelta[0] + vDelta[1] * vDelta[1];
|
||||
fRangeSquared = VectorLength2DSquared(vDelta);
|
||||
|
||||
if (fRangeSquared < g_AttackParms[iSpecialAttack].fMinRangeSquared
|
||||
|| fRangeSquared > g_AttackParms[iSpecialAttack].fMaxRangeSquared) {
|
||||
if (fRangeSquared < fMinRangeSquared || fRangeSquared > fMaxRangeSquared) {
|
||||
return STRING_NULL;
|
||||
}
|
||||
|
||||
fMinAngle = atan2(vDelta[0], vDelta[1]) * (180.0f / M_PI) - angles[1];
|
||||
fAngle = RAD2DEG(atan2(vDelta[1], vDelta[0])) - angles[1];
|
||||
|
||||
if (fMinAngle > -360.0f) {
|
||||
if (fMinAngle >= 0.0f) {
|
||||
if (fMinAngle >= 720.0f) {
|
||||
fMaxAngle = fMinAngle - 720.0f;
|
||||
} else if (fMinAngle >= 360.0f) {
|
||||
fMaxAngle = fMinAngle - 360.0f;
|
||||
} else {
|
||||
fMaxAngle = fMinAngle;
|
||||
}
|
||||
} else {
|
||||
fMaxAngle = fMinAngle + 360.0f;
|
||||
}
|
||||
if (fAngle <= -360) {
|
||||
fAngle = fAngle + 720.0f;
|
||||
} else if (fAngle < 0) {
|
||||
fAngle = fAngle + 360.0f;
|
||||
} else if (fAngle >= 720) {
|
||||
fAngle = fAngle - 720.0f;
|
||||
} else if (fAngle >= 360) {
|
||||
fAngle = fAngle - 360.0f;
|
||||
} else {
|
||||
fMaxAngle = fMinAngle + 720.0f;
|
||||
fAngle = fAngle;
|
||||
}
|
||||
|
||||
if (g_AttackParms[iSpecialAttack].fMinAngle <= g_AttackParms[iSpecialAttack].fMaxAngle) {
|
||||
if (g_AttackParms[iSpecialAttack].fMinAngle > fMaxAngle) {
|
||||
if (fMinAngle > fMaxAngle) {
|
||||
if (fAngle < fMinAngle && fAngle > fMaxAngle) {
|
||||
return STRING_NULL;
|
||||
}
|
||||
} else {
|
||||
if (g_AttackParms[iSpecialAttack].fMinAngle <= fMaxAngle) {
|
||||
if (fAngle < fMinAngle || fAngle > fMaxAngle) {
|
||||
return STRING_NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (fMaxAngle > g_AttackParms[iSpecialAttack].fMaxAngle) {
|
||||
return STRING_NULL;
|
||||
}
|
||||
|
||||
return csAnimation;
|
||||
}
|
||||
|
||||
|
|
|
@ -89,18 +89,18 @@ typedef struct {
|
|||
short int node;
|
||||
short int fallheight;
|
||||
float dist;
|
||||
float dir[2];
|
||||
float pos1[3];
|
||||
float pos2[3];
|
||||
vec2_t dir;
|
||||
vec3_t pos1;
|
||||
vec3_t pos2;
|
||||
} pathway_t;
|
||||
|
||||
class PathInfo
|
||||
{
|
||||
public:
|
||||
bool bAccurate;
|
||||
float point[3];
|
||||
float dist;
|
||||
float dir[2];
|
||||
bool bAccurate;
|
||||
vec3_t point;
|
||||
float dist;
|
||||
vec2_t dir;
|
||||
|
||||
public:
|
||||
void Archive(Archiver& arc);
|
||||
|
@ -129,9 +129,10 @@ typedef enum {
|
|||
#define AI_SNIPER 64
|
||||
#define AI_CRATE 128
|
||||
// Added in 2.0
|
||||
#define AI_LOW_WALL_ARC 256
|
||||
#define AI_LOW_WALL_ARC 256
|
||||
|
||||
#define AI_COVERFLAGS (AI_CRATE | AI_SNIPER | AI_CORNER_RIGHT | AI_CORNER_LEFT | AI_CONCEALMENT | AI_COVER | AI_LOW_WALL_ARC)
|
||||
#define AI_COVERFLAGS \
|
||||
(AI_CRATE | AI_SNIPER | AI_CORNER_RIGHT | AI_CORNER_LEFT | AI_CONCEALMENT | AI_COVER | AI_LOW_WALL_ARC)
|
||||
#define AI_COVERFLAGS2 (AI_SNIPER | AI_CORNER_RIGHT | AI_CORNER_LEFT | AI_CONCEALMENT)
|
||||
#define AI_COVERFLAGS3 (AI_SNIPER | AI_CORNER_RIGHT | AI_CORNER_LEFT | AI_CONCEALMENT | AI_DUCK | AI_CONCEALMENT)
|
||||
#define AI_SNIPERFLAGS (AI_SNIPER | AI_CORNER_RIGHT | AI_CORNER_LEFT | AI_COVER)
|
||||
|
@ -162,7 +163,7 @@ public:
|
|||
short int pathway;
|
||||
const vec_t *m_PathPos;
|
||||
float dist;
|
||||
float dir[2];
|
||||
vec2_t dir;
|
||||
int nodeflags;
|
||||
SafePtr<Entity> pLastClaimer;
|
||||
int iAvailableTime;
|
||||
|
@ -231,8 +232,8 @@ public:
|
|||
MapCell();
|
||||
~MapCell();
|
||||
|
||||
qboolean AddNode(PathNode *node);
|
||||
int NumNodes(void);
|
||||
void AddNode(PathNode *node);
|
||||
int NumNodes(void);
|
||||
};
|
||||
|
||||
class PathSearch : public Listener
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue