openmohaa/code/fgame/navigate.cpp

3335 lines
85 KiB
C++
Raw Normal View History

2016-03-27 11:49:47 +02:00
/*
===========================================================================
2023-10-22 21:06:43 +02:00
Copyright (C) 2023 the OpenMoHAA team
2016-03-27 11:49:47 +02:00
This file is part of OpenMoHAA source code.
OpenMoHAA source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
OpenMoHAA source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OpenMoHAA source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
===========================================================================
*/
// navigate.cpp: C++ implementation of the A* search algorithm.
//
#include "g_local.h"
#include "navigate.h"
#include "misc.h"
#include "doors.h"
#include "actor.h"
#include "player.h"
2023-04-29 21:56:38 +02:00
#include "debuglines.h"
#include "scriptexception.h"
2016-03-27 11:49:47 +02:00
#define PATHFILE_VERSION 103
2023-10-22 21:06:43 +02:00
int path_checkthisframe;
cvar_t *ai_showroutes;
cvar_t *ai_showroutes_distance;
cvar_t *ai_shownodenums;
cvar_t *ai_shownode;
cvar_t *ai_showallnode;
cvar_t *ai_showpath;
cvar_t *ai_fallheight;
cvar_t *ai_debugpath;
cvar_t *ai_pathchecktime;
cvar_t *ai_pathcheckdist;
static const vec_t *path_start;
static const vec_t *path_end;
static PathNode *Node;
static vec2_t path_totaldir;
static vec3_t path_p;
static vec2_t path_startdir;
2016-03-27 11:49:47 +02:00
2023-10-22 21:06:43 +02:00
static Entity *IgnoreObjects[MAX_GENTITIES];
static int NumIgnoreObjects;
2016-03-27 11:49:47 +02:00
static qboolean pathnodesinitialized = false;
2023-10-22 21:06:43 +02:00
static qboolean loadingarchive = false;
static qboolean pathnodescalculated = false;
int ai_maxnode;
2016-03-27 11:49:47 +02:00
2023-10-22 21:06:43 +02:00
MapCell PathSearch::PathMap[PATHMAP_GRIDSIZE][PATHMAP_GRIDSIZE];
2016-03-27 11:49:47 +02:00
PathNode *PathSearch::open;
2023-10-22 21:06:43 +02:00
int PathSearch::findFrame;
qboolean PathSearch::m_bNodesloaded;
qboolean PathSearch::m_NodeCheckFailed;
int PathSearch::m_LoadIndex;
PathNode *PathSearch::pathnodes[MAX_PATHNODES];
int PathSearch::nodecount;
float PathSearch::total_dist;
2023-02-01 00:28:40 +01:00
const char *PathSearch::last_error;
2016-03-27 11:49:47 +02:00
2023-10-22 21:06:43 +02:00
byte *bulkNavMemory = NULL;
2016-03-27 11:49:47 +02:00
byte *startBulkNavMemory = NULL;
2023-10-22 21:06:43 +02:00
Vector PLAYER_BASE_MIN(-15.5f, -15.5f, 0);
Vector PLAYER_BASE_MAX(15.5f, 15.5f, 0);
Vector testpos[200];
2016-03-27 11:49:47 +02:00
Vector ai_startpath;
Vector ai_endpath;
2023-10-22 21:06:43 +02:00
float NODE_MINS[3] = {-15, -15, 0};
float NODE_MAXS[3] = {15, 15, 94};
float COLOR_PATHNODE_ERROR[3] = {0, 0, 0};
float COLOR_PATHNODE_COVER[3] = {0, 1, 0};
float COLOR_PATHNODE_CORNER_LEFT[3] = {1, 1, 0};
float COLOR_PATHNODE_CORNER_RIGHT[3] = {0.7f, 1, 0};
float COLOR_PATHNODE_SNIPER[3] = {1, 0, 0};
float COLOR_PATHNODE_CRATE[3] = {3, 0, 0};
float COLOR_PATHNODE_CONCEALMENT[3] = {0, 0, 1};
float COLOR_PATHNODE_DUCK[3] = {0, 1, 1};
float COLOR_PATHNODE_DEFAULT[3] = {1, 0, 1};
int testcount = 0;
2016-03-27 11:49:47 +02:00
static ActorPath *test_path = NULL;
struct {
2023-10-22 21:06:43 +02:00
float fMinRangeSquared;
float fMaxRangeSquared;
float fMinAngle;
float fMaxAngle;
} 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 },
2016-03-27 11:49:47 +02:00
};
PathSearch PathManager;
int path_checksthisframe;
PathInfo *PathSearch::GeneratePath(PathInfo *path)
2023-10-22 21:06:43 +02:00
{
PathNode *ParentNode;
pathway_t *pathway;
float dist;
float dir[2];
PathInfo *current_path;
2023-10-22 21:06:43 +02:00
current_path = path;
2023-10-22 21:06:43 +02:00
dir[0] = path_end[0] - Node->m_PathPos[0];
dir[1] = path_end[1] - Node->m_PathPos[1];
2023-10-22 21:06:43 +02:00
dist = VectorNormalize2D(dir);
2023-10-22 21:06:43 +02:00
total_dist = dist + Node->g;
2023-10-22 21:06:43 +02:00
2023-11-09 19:52:53 +01:00
VectorCopy(path_end, current_path->point);
2023-10-22 21:06:43 +02:00
ParentNode = Node->Parent;
if (ParentNode) {
pathway = &ParentNode->Child[Node->pathway];
2023-11-09 19:52:53 +01:00
VectorSub2D(path_end, pathway->pos2, current_path->dir);
current_path->dist = VectorNormalize2D(current_path->dir);
2023-10-22 21:06:43 +02:00
if (path->dist) {
path->bAccurate = false;
2023-11-09 19:52:53 +01:00
current_path++;
}
2023-10-22 21:06:43 +02:00
if (pathway->dist) {
VectorCopy(pathway->pos2, current_path->point);
2023-11-09 19:52:53 +01:00
VectorCopy2D(pathway->dir, current_path->dir);
current_path->dist = pathway->dist;
current_path->bAccurate = true;
assert(current_path->dist > -1e+07 && current_path->dist < 1e+07);
current_path++;
}
2023-10-22 21:06:43 +02:00
for (Node = ParentNode, ParentNode = ParentNode->Parent; ParentNode != NULL;
Node = ParentNode, ParentNode = ParentNode->Parent) {
pathway = &ParentNode->Child[Node->pathway];
if (pathway->dist) {
VectorCopy(pathway->pos2, current_path->point);
2023-11-09 19:52:53 +01:00
VectorCopy2D(pathway->dir, current_path->dir);
current_path->dist = pathway->dist;
assert(current_path->dist > -1e+07 && current_path->dist < 1e+07);
current_path->bAccurate = true;
current_path++;
2023-10-22 21:06:43 +02:00
}
}
VectorCopy(pathway->pos1, current_path->point);
2023-11-09 19:52:53 +01:00
VectorCopy2D(path_startdir, current_path->dir);
current_path->dist = Node->g;
assert(current_path->dist > -1e+07 && current_path->dist < 1e+07);
} else {
2023-11-14 01:07:23 +01:00
VectorCopy2D(path_totaldir, current_path->dir);
2023-11-09 19:52:53 +01:00
path->dist = Node->h;
}
2023-10-22 21:06:43 +02:00
if (current_path->dist) {
current_path->bAccurate = false;
current_path++;
2023-10-22 21:06:43 +02:00
VectorCopy(path_start, current_path->point);
current_path->dist = 0;
VectorClear2D(current_path->dir);
}
2023-10-22 21:06:43 +02:00
current_path->bAccurate = false;
return current_path;
}
2023-10-22 21:06:43 +02:00
PathInfo *PathSearch::GeneratePathNear(PathInfo *path)
{
PathInfo *current_path = path;
pathway_t *pathway;
PathNode *ParentNode;
2023-10-22 21:06:43 +02:00
total_dist = Node->g;
VectorCopy(Node->m_PathPos, path->point);
2023-10-22 21:06:43 +02:00
ParentNode = Node->Parent;
if (ParentNode) {
pathway = &ParentNode->Child[Node->pathway];
2023-10-22 21:06:43 +02:00
if (pathway->dist) {
VectorCopy(pathway->pos2, path->point);
2023-11-09 19:52:53 +01:00
VectorCopy2D(pathway->dir, path->dir);
path->dist = pathway->dist;
2023-10-22 21:06:43 +02:00
current_path->bAccurate = true;
current_path++;
}
2023-10-22 21:06:43 +02:00
for (Node = ParentNode, ParentNode = ParentNode->Parent; ParentNode != NULL;
Node = ParentNode, ParentNode = ParentNode->Parent) {
pathway = &ParentNode->Child[Node->pathway];
if (pathway->dist) {
VectorCopy(pathway->pos2, current_path->point);
2023-11-09 19:52:53 +01:00
VectorCopy2D(pathway->dir, current_path->dir);
current_path->dist = pathway->dist;
current_path->bAccurate = true;
assert(current_path->dist > -1e+07 && current_path->dist < 1e+07);
current_path++;
2023-10-22 21:06:43 +02:00
}
}
VectorCopy(pathway->pos1, current_path->point);
2023-11-09 19:52:53 +01:00
VectorCopy2D(path_startdir, current_path->dir);
current_path->dist = Node->g;
} else {
2023-11-09 19:52:53 +01:00
VectorCopy2D(path_totaldir, current_path->dir);
path->dist = Node->h;
2023-10-22 21:06:43 +02:00
}
if (current_path->dist) {
current_path->bAccurate = false;
current_path++;
2016-03-27 11:49:47 +02:00
VectorCopy(path_start, current_path->point);
VectorClear2D(current_path->dir);
2023-11-09 19:52:53 +01:00
current_path->dist = 0;
}
2016-03-27 11:49:47 +02:00
current_path->bAccurate = false;
return current_path;
}
2016-03-27 11:49:47 +02:00
PathInfo *PathSearch::GeneratePathAway(PathInfo *path)
{
PathInfo *current_path = path;
pathway_t *pathway;
PathNode *ParentNode;
2016-03-27 11:49:47 +02:00
VectorCopy(Node->m_PathPos, path->point);
2016-03-27 11:49:47 +02:00
ParentNode = Node->Parent;
if (ParentNode) {
pathway = &ParentNode->Child[Node->pathway];
2016-03-27 11:49:47 +02:00
if (pathway->dist) {
VectorCopy(pathway->pos2, current_path->point);
VectorCopy2D(pathway->dir, current_path->dir);
current_path->dist = pathway->dist;
2016-03-27 11:49:47 +02:00
current_path->bAccurate = true;
assert(current_path->dist > -1e+07 && current_path->dist < 1e+07);
current_path++;
}
2016-03-27 11:49:47 +02:00
for (Node = ParentNode, ParentNode = ParentNode->Parent; ParentNode != NULL;
Node = ParentNode, ParentNode = ParentNode->Parent) {
pathway = &ParentNode->Child[Node->pathway];
if (pathway->dist) {
VectorCopy(pathway->pos2, current_path->point);
2023-11-09 19:52:53 +01:00
VectorCopy2D(pathway->dir, current_path->dir);
current_path->dist = pathway->dist;
current_path->bAccurate = true;
assert(current_path->dist > -1e+07 && current_path->dist < 1e+07);
current_path++;
}
}
2023-10-22 21:06:43 +02:00
VectorCopy(pathway->pos1, current_path->point);
2023-11-09 19:52:53 +01:00
VectorCopy2D(pathway->pos1, current_path->point);
2016-03-27 11:49:47 +02:00
current_path->dist = Node->g;
2016-03-27 11:49:47 +02:00
if (Node->g) {
current_path->bAccurate = false;
current_path++;
VectorCopy(path_start, current_path->point);
VectorClear2D(current_path->dir);
2023-11-09 19:52:53 +01:00
current_path->dist = 0;
2023-10-22 21:06:43 +02:00
}
} else {
VectorClear2D(path->dir);
path->dist = 0;
2023-10-22 21:06:43 +02:00
}
current_path->bAccurate = false;
return current_path;
}
2023-10-22 21:06:43 +02:00
int PathSearch::FindPath(
const vec3_t start,
const vec3_t end,
Entity *ent,
float maxPath,
const vec3_t vLeashHome,
float fLeashDistSquared,
int fallheight
)
{
int i;
int g;
PathNode *NewNode;
pathway_t *pathway;
PathNode *prev;
PathNode *next;
int f;
vec2_t delta;
PathNode *to;
2023-10-22 21:06:43 +02:00
if (ent) {
if (ent->IsSubclassOfActor()) {
Node = NearestStartNode(start, (SimpleActor *)ent);
2023-10-22 21:06:43 +02:00
} else {
Node = DebugNearestStartNode(start, ent);
2023-10-22 21:06:43 +02:00
}
} else {
Node = DebugNearestStartNode(start);
2023-10-22 21:06:43 +02:00
}
if (!Node) {
last_error = "couldn't find start node";
return 0;
2023-10-22 21:06:43 +02:00
}
to = NearestEndNode(end);
if (!to) {
last_error = "couldn't find end node";
return 0;
2023-10-22 21:06:43 +02:00
}
total_dist = 1e+12f;
2016-03-27 11:49:47 +02:00
if (!maxPath) {
maxPath = 1e+12f;
2023-10-22 21:06:43 +02:00
}
2016-03-27 11:49:47 +02:00
findFrame++;
open = NULL;
2016-03-27 11:49:47 +02:00
VectorSub2D(Node->origin, start, path_startdir);
Node->g = VectorNormalize2D(path_startdir);
2016-03-27 11:49:47 +02:00
VectorSub2D(end, start, path_totaldir);
Node->h = VectorNormalize2D(path_totaldir);
2016-03-27 11:49:47 +02:00
Node->Parent = NULL;
Node->m_Depth = 3;
Node->findCount = findFrame;
Node->inopen = true;
Node->PrevNode = NULL;
Node->NextNode = NULL;
Node->m_PathPos = start;
2016-03-27 11:49:47 +02:00
open = Node;
2016-03-27 11:49:47 +02:00
while (open) {
Node = open;
2023-11-09 21:01:30 +01:00
open->inopen = false;
open = Node->NextNode;
2016-03-27 11:49:47 +02:00
if (open) {
open->PrevNode = NULL;
}
2016-03-27 11:49:47 +02:00
if (Node == to) {
path_start = start;
path_end = end;
return Node->m_Depth;
}
2016-03-27 11:49:47 +02:00
for (i = Node->numChildren - 1; i >= 0; i--) {
vec2_t vDist;
2023-10-22 21:06:43 +02:00
pathway = &Node->Child[i];
2023-10-22 21:06:43 +02:00
NewNode = pathnodes[pathway->node];
2023-10-22 21:06:43 +02:00
if (vLeashHome) {
VectorSub2D(pathway->pos2, vLeashHome, vDist);
if (VectorLength2DSquared(vDist) > fLeashDistSquared) {
continue;
}
}
2023-10-22 21:06:43 +02:00
g = (int)(pathway->dist + Node->g + 1.0f);
2023-10-22 21:06:43 +02:00
if (NewNode->findCount == findFrame) {
if (NewNode->g <= g) {
continue;
}
2023-10-22 21:06:43 +02:00
if (NewNode->inopen) {
NewNode->inopen = false;
next = NewNode->NextNode;
prev = NewNode->PrevNode;
2023-10-22 21:06:43 +02:00
if (next) {
next->PrevNode = prev;
}
2023-10-22 21:06:43 +02:00
if (prev) {
prev->NextNode = next;
} else {
open = next;
}
}
}
2023-10-22 21:06:43 +02:00
VectorSub2D(end, pathway->pos2, delta);
NewNode->h = VectorLength2D(delta);
2023-10-24 19:30:13 +02:00
f = (int)((float)g + NewNode->h);
if (f >= maxPath) {
last_error = "specified path distance exceeded";
return 0;
2023-10-24 19:30:13 +02:00
}
2023-10-22 21:06:43 +02:00
2023-11-09 21:01:30 +01:00
if (pathway->fallheight <= fallheight
&& (!ent || !ent->IsSubclassOfSentient() || !pathway->badPlaceTeam[static_cast<Sentient *>(ent)->m_Team])) {
NewNode->m_Depth = Node->m_Depth + 1;
NewNode->Parent = Node;
NewNode->pathway = i;
NewNode->g = (float)g;
NewNode->f = (float)f;
NewNode->m_PathPos = pathway->pos2;
NewNode->findCount = findFrame;
2023-11-09 21:01:30 +01:00
NewNode->inopen = true;
2023-10-22 21:06:43 +02:00
if (!open) {
NewNode->NextNode = NULL;
NewNode->PrevNode = NULL;
open = NewNode;
continue;
}
2023-10-22 21:06:43 +02:00
if (open->f >= f) {
NewNode->NextNode = open;
NewNode->PrevNode = NULL;
2023-10-22 21:06:43 +02:00
open->PrevNode = NewNode;
open = NewNode;
continue;
}
2023-10-22 21:06:43 +02:00
prev = open;
for (next = open->NextNode; next; next = next->NextNode) {
if (next->f >= f) {
break;
}
prev = next;
}
NewNode->NextNode = next;
if (next) {
next->PrevNode = NewNode;
}
prev->NextNode = NewNode;
NewNode->PrevNode = prev;
}
}
2023-10-22 21:06:43 +02:00
}
last_error = "unreachable path";
return 0;
2023-10-22 21:06:43 +02:00
}
int PathSearch::FindPathNear(
const vec3_t start,
const vec3_t end,
Entity *ent,
float maxPath,
float fRadiusSquared,
const vec3_t vLeashHome,
float fLeashDistSquared,
int fallheight
)
2023-10-22 21:06:43 +02:00
{
int i;
int g;
PathNode *NewNode;
pathway_t *pathway;
PathNode *prev;
PathNode *next;
int f;
vec2_t dir;
vec2_t delta;
2023-10-22 21:06:43 +02:00
if (ent) {
if (ent->IsSubclassOfActor()) {
Node = NearestStartNode(start, (SimpleActor *)ent);
} else {
Node = DebugNearestStartNode(start, ent);
}
2023-10-22 21:06:43 +02:00
} else {
Node = DebugNearestStartNode(start);
2023-10-22 21:06:43 +02:00
}
if (!Node) {
last_error = "no start node";
return 0;
}
2023-10-22 21:06:43 +02:00
2023-11-09 21:01:30 +01:00
total_dist = 1e12f;
2023-10-22 21:06:43 +02:00
if (!maxPath) {
2023-11-09 21:01:30 +01:00
maxPath = 1e12f;
2023-10-22 21:06:43 +02:00
}
findFrame++;
open = NULL;
2023-10-22 21:06:43 +02:00
2023-11-09 21:01:30 +01:00
VectorSub2D(Node->origin, start, path_startdir);
VectorSub2D(end, start, delta);
VectorCopy2D(delta, dir);
2023-10-22 21:06:43 +02:00
Node->inopen = true;
Node->g = VectorNormalize2D(path_startdir);
2023-11-09 21:01:30 +01:00
Node->h = VectorNormalize2D(dir);
Node->Parent = NULL;
Node->m_Depth = 3;
Node->findCount = findFrame;
Node->PrevNode = NULL;
Node->NextNode = NULL;
Node->m_PathPos = start;
2023-10-22 21:06:43 +02:00
open = Node;
2023-10-22 21:06:43 +02:00
2023-11-09 21:01:30 +01:00
while (open) {
Node = open;
2023-11-09 21:01:30 +01:00
open->inopen = false;
open = Node->NextNode;
2023-10-22 21:06:43 +02:00
if (open) {
open->PrevNode = NULL;
}
2023-10-22 21:06:43 +02:00
2023-11-09 21:01:30 +01:00
VectorSub2D(end, Node->m_PathPos, delta);
2023-10-22 21:06:43 +02:00
if (fRadiusSquared >= VectorLength2DSquared(delta)) {
2023-11-09 21:01:30 +01:00
path_start = start;
path_end = end;
return Node->m_Depth;
}
2023-10-22 21:06:43 +02:00
for (i = Node->numChildren - 1; i >= 0; i--) {
pathway = &Node->Child[i];
2023-11-09 21:01:30 +01:00
NewNode = pathnodes[pathway->node];
2023-10-22 21:06:43 +02:00
g = (int)(pathway->dist + Node->g + 1.0f);
2023-10-22 21:06:43 +02:00
if (NewNode->findCount == findFrame) {
2023-11-09 21:01:30 +01:00
if (NewNode->g <= g) {
continue;
}
2023-10-22 21:06:43 +02:00
if (NewNode->inopen) {
NewNode->inopen = false;
next = NewNode->NextNode;
prev = NewNode->PrevNode;
2023-10-22 21:06:43 +02:00
if (next) {
next->PrevNode = prev;
}
2023-10-22 21:06:43 +02:00
if (prev) {
prev->NextNode = next;
} else {
open = next;
}
}
2023-10-22 21:06:43 +02:00
}
2023-11-09 21:01:30 +01:00
VectorSub2D(end, pathway->pos2, delta);
NewNode->h = VectorLength2D(delta);
2023-11-09 21:01:30 +01:00
f = (int)((float)g + NewNode->h);
2023-10-22 21:06:43 +02:00
if (f >= maxPath) {
last_error = "specified path distance exceeded";
return 0;
2023-10-22 21:06:43 +02:00
}
2023-11-09 21:01:30 +01:00
if (pathway->fallheight <= fallheight
&& (!ent || !ent->IsSubclassOfSentient() || !pathway->badPlaceTeam[static_cast<Sentient *>(ent)->m_Team])) {
NewNode->m_Depth = Node->m_Depth + 1;
2023-11-09 21:01:30 +01:00
NewNode->Parent = Node;
NewNode->pathway = i;
NewNode->g = (float)g;
NewNode->f = (float)f;
NewNode->m_PathPos = pathway->pos2;
NewNode->findCount = findFrame;
2023-11-09 21:01:30 +01:00
NewNode->inopen = true;
2023-10-22 21:06:43 +02:00
if (!open) {
NewNode->NextNode = NULL;
NewNode->PrevNode = NULL;
open = NewNode;
continue;
}
2023-10-22 21:06:43 +02:00
if (open->f >= f) {
NewNode->NextNode = open;
2023-11-09 21:01:30 +01:00
NewNode->PrevNode = NULL;
2023-10-22 21:06:43 +02:00
open->PrevNode = NewNode;
open = NewNode;
continue;
}
2023-10-22 21:06:43 +02:00
prev = open;
2023-11-09 21:01:30 +01:00
for (next = open->NextNode; next; next = next->NextNode) {
if (next->f >= f) {
break;
}
prev = next;
}
2023-10-22 21:06:43 +02:00
NewNode->NextNode = next;
if (next) {
next->PrevNode = NewNode;
2023-10-22 21:06:43 +02:00
}
prev->NextNode = NewNode;
NewNode->PrevNode = prev;
2023-10-22 21:06:43 +02:00
}
}
}
2016-03-27 11:49:47 +02:00
2023-11-09 21:01:30 +01:00
last_error = "unreachable path";
return 0;
2016-03-27 11:49:47 +02:00
}
int PathSearch::FindPathAway(
const vec3_t start,
const vec3_t avoid,
const vec3_t vPreferredDir,
Entity *ent,
float fMinSafeDist,
const vec3_t vLeashHome,
float fLeashDistSquared,
int fallheight
)
2016-03-27 11:49:47 +02:00
{
int i;
int g;
PathNode *NewNode;
pathway_t *pathway;
PathNode *prev;
PathNode *next;
int f;
float fBias;
vec2_t delta;
2023-11-09 21:01:30 +01:00
float fMinSafeDistSquared;
fMinSafeDistSquared = fMinSafeDist * fMinSafeDist;
2023-10-22 21:06:43 +02:00
if (ent) {
if (ent->IsSubclassOfActor()) {
Node = NearestStartNode(start, (SimpleActor *)ent);
} else {
Node = DebugNearestStartNode(start, ent);
}
} else {
Node = DebugNearestStartNode(start);
}
2016-03-27 11:49:47 +02:00
if (!Node) {
last_error = "couldn't find start node";
return 0;
}
2016-03-27 11:49:47 +02:00
findFrame++;
open = NULL;
2016-03-27 11:49:47 +02:00
2023-11-09 21:01:30 +01:00
VectorSub2D(Node->origin, start, path_startdir);
VectorSub2D(start, avoid, delta);
2016-03-27 11:49:47 +02:00
fBias = VectorLength2D(vPreferredDir);
Node->inopen = true;
Node->g = VectorNormalize2D(path_startdir);
Node->h = fMinSafeDist - VectorNormalize2D(delta);
Node->h += fBias - DotProduct2D(vPreferredDir, delta);
Node->Parent = NULL;
Node->m_Depth = 2;
Node->findCount = findFrame;
Node->PrevNode = NULL;
Node->NextNode = NULL;
Node->m_PathPos = start;
open = Node;
2023-11-09 21:01:30 +01:00
while (open) {
Node = open;
2023-11-09 21:01:30 +01:00
open->inopen = false;
open = Node->NextNode;
2016-03-27 11:49:47 +02:00
if (open) {
open->PrevNode = NULL;
}
2016-03-27 11:49:47 +02:00
2023-11-09 21:01:30 +01:00
VectorSub2D(Node->m_PathPos, avoid, delta);
2016-03-27 11:49:47 +02:00
if (VectorLength2DSquared(delta) >= fMinSafeDistSquared) {
2023-11-09 21:01:30 +01:00
path_start = start;
return Node->m_Depth;
}
2016-03-27 11:49:47 +02:00
for (i = Node->numChildren - 1; i >= 0; i--) {
vec2_t vDist;
2016-03-27 11:49:47 +02:00
pathway = &Node->Child[i];
2023-11-09 21:01:30 +01:00
NewNode = pathnodes[pathway->node];
2016-03-27 11:49:47 +02:00
if (vLeashHome) {
2023-11-09 21:01:30 +01:00
VectorSub2D(pathway->pos2, vLeashHome, vDist);
if (VectorLength2DSquared(vDist) > fLeashDistSquared) {
continue;
}
}
2016-03-27 11:49:47 +02:00
2023-11-09 21:01:30 +01:00
g = (int)(pathway->dist + Node->g + 1.0f);
2016-03-27 11:49:47 +02:00
2023-11-09 21:01:30 +01:00
if (NewNode->findCount == findFrame) {
if (NewNode->g <= g) {
continue;
}
2016-03-27 11:49:47 +02:00
2023-11-09 21:01:30 +01:00
if (NewNode->inopen) {
NewNode->inopen = false;
next = NewNode->NextNode;
prev = NewNode->PrevNode;
2016-03-27 11:49:47 +02:00
2023-11-09 21:01:30 +01:00
if (next) {
next->PrevNode = prev;
}
2016-03-27 11:49:47 +02:00
2023-11-09 21:01:30 +01:00
if (prev) {
prev->NextNode = next;
} else {
open = next;
}
}
2023-11-09 21:01:30 +01:00
}
2016-03-27 11:49:47 +02:00
VectorSub2D(pathway->pos2, avoid, delta);
NewNode->h = VectorNormalize2D(delta);
NewNode->h += fBias - DotProduct2D(delta, vPreferredDir);
2016-03-27 11:49:47 +02:00
2023-11-09 21:01:30 +01:00
f = (int)((float)g + NewNode->h);
2016-03-27 11:49:47 +02:00
2023-11-09 21:01:30 +01:00
if (pathway->fallheight <= fallheight) {
NewNode->m_Depth = Node->m_Depth + 1;
NewNode->Parent = Node;
NewNode->pathway = i;
NewNode->g = (float)g;
NewNode->f = (float)f;
NewNode->m_PathPos = pathway->pos2;
NewNode->findCount = findFrame;
NewNode->inopen = true;
2016-03-27 11:49:47 +02:00
2023-11-09 21:01:30 +01:00
if (!open) {
NewNode->NextNode = NULL;
NewNode->PrevNode = NULL;
open = NewNode;
continue;
}
2016-03-27 11:49:47 +02:00
2023-11-09 21:01:30 +01:00
if (open->f >= f) {
NewNode->NextNode = open;
NewNode->PrevNode = NULL;
open->PrevNode = NewNode;
open = NewNode;
continue;
}
prev = open;
for (next = open->NextNode; next; next = next->NextNode) {
if (next->f >= f) {
break;
}
2023-11-09 21:01:30 +01:00
prev = next;
}
2016-03-27 11:49:47 +02:00
2023-11-09 21:01:30 +01:00
NewNode->NextNode = next;
if (next) {
next->PrevNode = NewNode;
}
prev->NextNode = NewNode;
NewNode->PrevNode = prev;
}
}
2023-10-22 21:06:43 +02:00
}
2016-03-27 11:49:47 +02:00
2023-11-09 21:01:30 +01:00
last_error = "unreachable path";
return 0;
2016-03-27 11:49:47 +02:00
}
PathNode *PathSearch::FindCornerNodeForWall(
const vec3_t start, const vec3_t end, SimpleActor *ent, float maxPath, const vec4_t plane
)
2016-03-27 11:49:47 +02:00
{
int i, g;
PathNode *NewNode;
pathway_t *pathway;
PathNode *prev, *next;
int f;
vec2_t delta;
2023-11-09 21:01:30 +01:00
vec2_t dir;
2023-10-22 21:06:43 +02:00
Node = NearestStartNode(start, ent);
if (!Node) {
last_error = "couldn't find start node";
return NULL;
2023-10-22 21:06:43 +02:00
}
if (DotProduct(start, plane) - plane[3] < 0.0) {
last_error = "starting point is already behind the wall";
return NULL;
2023-10-22 21:06:43 +02:00
}
2016-03-27 11:49:47 +02:00
if (DotProduct(plane, end) - plane[3] > 0.0) {
last_error = "end point is in front of the wall";
2023-10-22 21:06:43 +02:00
return NULL;
}
2016-03-27 11:49:47 +02:00
2023-11-09 21:01:30 +01:00
total_dist = 1e12f;
2016-03-27 11:49:47 +02:00
if (maxPath == 0.0) {
2023-11-09 21:01:30 +01:00
maxPath = 1e12f;
}
2016-03-27 11:49:47 +02:00
findFrame++;
open = NULL;
2023-10-22 21:06:43 +02:00
VectorSub2D(Node->origin, start, path_startdir);
Node->g = VectorNormalize2D(path_startdir);
2016-03-27 11:49:47 +02:00
VectorSub2D(end, start, path_totaldir);
2023-11-09 21:01:30 +01:00
Node->h = VectorNormalize2D(path_totaldir);
Node->inopen = true;
Node->Parent = NULL;
Node->m_Depth = 3;
Node->findCount = findFrame;
Node->PrevNode = 0;
Node->NextNode = 0;
2023-11-09 21:01:30 +01:00
Node->m_PathPos = start;
open = Node;
2023-11-09 21:01:30 +01:00
while (open) {
Node = open;
2023-11-09 21:01:30 +01:00
open->inopen = false;
open = Node->NextNode;
2016-03-27 11:49:47 +02:00
if (open) {
open->PrevNode = NULL;
}
2023-10-22 21:06:43 +02:00
2023-11-09 21:01:30 +01:00
if (Node->Parent && DotProduct(Node->m_PathPos, plane) - plane[3] < 0) {
VectorSub2D(Node->m_PathPos, start, delta);
2023-10-22 21:06:43 +02:00
2023-11-09 21:01:30 +01:00
if (VectorLength2DSquared(delta) >= 256) {
return Node->Parent;
}
return Node;
2023-10-22 21:06:43 +02:00
}
2023-11-09 21:01:30 +01:00
for (i = Node->numChildren - 1; i >= 0; i--) {
pathway = &Node->Child[i];
2023-11-09 21:01:30 +01:00
NewNode = pathnodes[pathway->node];
g = (int)(pathway->dist + Node->g + 1.0f);
2016-03-27 11:49:47 +02:00
2023-11-09 21:01:30 +01:00
if (NewNode->findCount == findFrame) {
if (NewNode->g <= g) {
continue;
}
2023-10-22 21:06:43 +02:00
2023-11-09 21:01:30 +01:00
if (NewNode->inopen) {
NewNode->inopen = false;
next = NewNode->NextNode;
prev = NewNode->PrevNode;
2016-03-27 11:49:47 +02:00
2023-11-09 21:01:30 +01:00
if (next) {
next->PrevNode = prev;
}
if (prev) {
prev->NextNode = next;
} else {
open = next;
}
}
}
2023-10-22 21:06:43 +02:00
2023-11-09 21:01:30 +01:00
VectorSub2D(end, pathway->pos2, dir);
NewNode->h = VectorNormalize2D(dir);
2023-10-22 21:06:43 +02:00
2023-11-09 21:01:30 +01:00
f = (int)((float)g + NewNode->h);
2023-10-22 21:06:43 +02:00
2023-11-09 21:01:30 +01:00
if (f >= maxPath) {
last_error = "specified path distance exceeded";
return 0;
}
2023-10-22 21:06:43 +02:00
2023-11-09 21:01:30 +01:00
NewNode->m_Depth = Node->m_Depth + 1;
NewNode->Parent = Node;
NewNode->pathway = i;
NewNode->g = (float)g;
NewNode->f = (float)f;
NewNode->m_PathPos = pathway->pos2;
NewNode->findCount = findFrame;
NewNode->inopen = true;
if (!open) {
NewNode->NextNode = NULL;
NewNode->PrevNode = NULL;
open = NewNode;
continue;
}
2023-10-22 21:06:43 +02:00
2023-11-09 21:01:30 +01:00
if (open->f >= f) {
NewNode->NextNode = open;
NewNode->PrevNode = NULL;
2023-10-22 21:06:43 +02:00
2023-11-09 21:01:30 +01:00
open->PrevNode = NewNode;
open = NewNode;
continue;
}
2023-10-22 21:06:43 +02:00
2023-11-09 21:01:30 +01:00
prev = open;
for (next = open->NextNode; next; next = next->NextNode) {
if (next->f >= f) {
break;
}
prev = next;
}
2023-10-22 21:06:43 +02:00
2023-11-09 21:01:30 +01:00
NewNode->NextNode = next;
if (next) {
next->PrevNode = NewNode;
}
prev->NextNode = NewNode;
NewNode->PrevNode = prev;
2023-10-22 21:06:43 +02:00
}
}
2023-11-09 21:01:30 +01:00
last_error = "unreachable path";
2023-10-23 18:57:56 +02:00
return NULL;
2023-10-22 21:06:43 +02:00
}
PathNode *PathSearch::FindCornerNodeForExactPath(SimpleActor *pSelf, Sentient *enemy, float fMaxPath)
2023-10-22 21:06:43 +02:00
{
PathNode *pPathNode[4096];
PathNode *pParentNode;
2023-11-09 21:01:30 +01:00
size_t i, iDepth;
Vector vEnd;
Vector vEyeDelta;
Vector vEyePos;
2023-10-22 21:06:43 +02:00
2023-11-09 21:01:30 +01:00
iDepth = PathSearch::FindPath(enemy->origin, pSelf->origin, pSelf, fMaxPath, 0, 0.0, 100);
if (!iDepth) {
return NULL;
2023-10-22 21:06:43 +02:00
}
2023-11-09 21:01:30 +01:00
vEyePos = pSelf->EyePosition();
vEyeDelta = vEyePos - pSelf->origin;
for (pParentNode = Node->Parent, i = 0; pParentNode; pParentNode = pParentNode->Parent, i++) {
2023-11-09 21:01:30 +01:00
Node = pParentNode;
pPathNode[i] = pParentNode;
}
2023-10-22 21:06:43 +02:00
iDepth = i;
if (!iDepth) {
return NULL;
}
2023-10-22 21:06:43 +02:00
for (i = 1; i < iDepth; i += 2) {
2023-11-09 21:01:30 +01:00
vEnd = vEyeDelta + pPathNode[i]->m_PathPos;
if (!G_SightTrace(
2023-11-09 21:01:30 +01:00
vEyePos, vec_zero, vec_zero, vEnd, pSelf, enemy, MASK_CORNER_NODE, qfalse, "FindCornerNodeFoExactPath 1"
2023-10-22 21:06:43 +02:00
)) {
break;
}
}
2023-11-09 21:01:30 +01:00
i--;
if (i >= iDepth) {
i = iDepth - 1;
return pPathNode[i];
}
if (i) {
vEnd = vEyeDelta + pPathNode[i]->m_PathPos;
if (!G_SightTrace(
vEyePos, vec_zero, vec_zero, vEnd, pSelf, enemy, MASK_CORNER_NODE, qfalse, "FindCornerNodeFoExactPath 2"
)) {
i--;
2023-10-22 21:06:43 +02:00
}
}
2023-11-09 21:01:30 +01:00
return pPathNode[i];
2023-10-22 21:06:43 +02:00
}
void PathSearch::ResetNodes(void)
2016-03-27 11:49:47 +02:00
{
int i;
int x;
int y;
2023-10-22 21:06:43 +02:00
m_bNodesloaded = false;
m_LoadIndex = -1;
2023-10-22 21:06:43 +02:00
2023-11-09 21:01:30 +01:00
if (!startBulkNavMemory && nodecount) {
for (x = PATHMAP_GRIDSIZE - 1; x >= 0; x--) {
for (y = PATHMAP_GRIDSIZE - 1; y >= 0; y--) {
if (PathMap[x][y].nodes) {
gi.Free(PathMap[x][y].nodes);
}
}
2023-10-22 21:06:43 +02:00
}
2016-03-27 11:49:47 +02:00
for (i = 0; i < nodecount; i++) {
if (pathnodes[i]->Child) {
gi.Free(pathnodes[i]->Child);
2023-10-22 21:06:43 +02:00
}
}
}
2016-03-27 11:49:47 +02:00
2024-02-17 00:05:39 +01:00
for (x = 0; x < PATHMAP_GRIDSIZE; x++){
for (y = 0; y < PATHMAP_GRIDSIZE; y++) {
PathMap[x][y].numnodes = 0;
PathMap[x][y].nodes = NULL;
2023-10-22 21:06:43 +02:00
}
}
2023-10-22 21:06:43 +02:00
for (i = 0; i < nodecount; i++) {
delete pathnodes[i];
pathnodes[i] = NULL;
2023-10-22 21:06:43 +02:00
}
nodecount = 0;
// Free the bulk nav' memory
if (startBulkNavMemory) {
gi.Free(startBulkNavMemory);
bulkNavMemory = NULL;
startBulkNavMemory = NULL;
}
2023-10-22 21:06:43 +02:00
}
void PathSearch::UpdatePathwaysForBadPlace(const Vector& origin, float radius, int dir, int team)
2023-10-22 21:06:43 +02:00
{
float radiusSqr;
int i, j, k;
2023-10-22 21:06:43 +02:00
radiusSqr = radius * radius;
2023-10-22 21:06:43 +02:00
for (i = 0; i < nodecount; i++) {
PathNode *node = pathnodes[i];
2023-10-22 21:06:43 +02:00
for (j = node->virtualNumChildren; j > 0; j--) {
pathway_t& pathway = node->Child[j - 1];
if (PointToSegmentDistanceSquared(origin, pathway.pos1, pathway.pos2) < radiusSqr) {
for (k = 0; k < 2; k++) {
if ((1 << k) & team) {
pathway.badPlaceTeam[k] += dir;
}
}
}
}
}
}
2023-10-22 21:06:43 +02:00
void AI_AddNode(PathNode *node)
{
int i = PathSearch::nodecount;
2023-10-22 21:06:43 +02:00
assert(node);
if (i < MAX_PATHNODES) {
if (i > ai_maxnode) {
ai_maxnode = i;
}
PathSearch::pathnodes[i] = node;
node->nodenum = i;
PathSearch::nodecount++;
return;
2023-10-22 21:06:43 +02:00
}
gi.Error(ERR_DROP, "Exceeded MAX_PATHNODES!\n");
}
2023-10-22 21:06:43 +02:00
/*****************************************************************************/
/*QUAKED info_pathnode (1 0 0) (-24 -24 0) (24 24 32) FLEE DUCK COVER DOOR JUMP LADDER
2023-10-22 21:06:43 +02:00
FLEE marks the node as a safe place to flee to. Actor will be removed when it reaches a flee node and is not visible to a player.
2023-10-22 21:06:43 +02:00
DUCK marks the node as a good place to duck behind during weapon fire.
2023-10-22 21:06:43 +02:00
COVER marks the node as a good place to hide behind during weapon fire.
2023-10-22 21:06:43 +02:00
DOOR marks the node as a door node. If an adjacent node has DOOR marked as well, the actor will only use the path if the door in between them is unlocked.
2023-10-22 21:06:43 +02:00
JUMP marks the node as one to jump from when going to the node specified by target.
"target" the pathnode to jump to.
2023-10-22 21:06:43 +02:00
******************************************************************************/
2023-10-22 21:06:43 +02:00
Event EV_Path_SetNodeFlags
(
"spawnflags",
EV_DEFAULT,
"i",
"node_flags",
"Sets the path nodes flags.",
EV_NORMAL
);
2023-10-22 21:06:43 +02:00
// Added in 2.0
Event EV_Path_SetLowWallArc
(
"low_wall_arc",
EV_DEFAULT,
"f",
"arc_half_angle",
"Marks this node as good for low-wall behavior, and gives the arc"
);
2023-10-22 21:06:43 +02:00
CLASS_DECLARATION(SimpleEntity, PathNode, "info_pathnode") {
{&EV_Path_SetNodeFlags, &PathNode::SetNodeFlags },
{&EV_IsTouching, &PathNode::IsTouching },
{&EV_Delete, &PathNode::Remove },
{&EV_Remove, &PathNode::Remove },
{&EV_Path_SetLowWallArc, &PathNode::SetLowWallArc},
{NULL, NULL }
};
2023-10-22 21:06:43 +02:00
static Vector pathNodesChecksum;
static int numLoadNodes = 0;
static int numNodes = 0;
void *PathNode::operator new(size_t size)
{
return PathManager.AllocPathNode();
2023-10-22 21:06:43 +02:00
}
void PathNode::operator delete(void *ptr)
2023-10-22 21:06:43 +02:00
{
return PathManager.FreePathNode(ptr);
}
2023-10-22 21:06:43 +02:00
PathNode::PathNode()
{
entflags |= ECF_PATHNODE;
findCount = 0;
pLastClaimer = NULL;
numChildren = 0;
iAvailableTime = -1;
2023-10-22 21:06:43 +02:00
if (!loadingarchive) {
// our archive function will take care of this stuff
AI_AddNode(this);
nodeflags = 0;
m_fLowWallArc = 0;
pLastClaimer = NULL;
iAvailableTime = -1;
virtualNumChildren = 0;
Child = NULL;
2023-10-22 21:06:43 +02:00
}
}
2023-10-22 21:06:43 +02:00
PathNode::~PathNode()
{
entflags &= ~ECF_PATHNODE;
}
2023-10-22 21:06:43 +02:00
void PathNode::Remove(Event *ev)
{
// Pathnodes mustn't be removed
ScriptError("Not allowed to delete a path node");
2016-03-27 11:49:47 +02:00
}
void PathNode::SetNodeFlags(Event *ev)
2023-08-16 18:48:38 +02:00
{
nodeflags = ev->GetInteger(1);
}
2023-10-22 21:06:43 +02:00
void PathNode::SetLowWallArc(Event *ev)
{
float value = ev->GetFloat(1);
if (value < 0 || value >= 180) {
ScriptError("low_wall_arc must be >= 0 and < 180");
2023-10-22 21:06:43 +02:00
}
m_fLowWallArc = value;
if (!value) {
nodeflags &= ~AI_LOW_WALL_ARC;
} else {
nodeflags |= AI_LOW_WALL_ARC;
2023-10-22 21:06:43 +02:00
}
}
2023-10-22 21:06:43 +02:00
void PathNode::ConnectTo(PathNode *node)
{
Child[virtualNumChildren].node = nodenum;
Child[virtualNumChildren].numBlockers = 0;
Child[virtualNumChildren].badPlaceTeam[0] = 0;
Child[virtualNumChildren].badPlaceTeam[1] = 0;
virtualNumChildren++;
numChildren++;
2023-10-22 21:06:43 +02:00
}
void PathNode::ConnectChild(int i)
2023-10-22 21:06:43 +02:00
{
int j;
pathway_t child = Child[i];
2023-10-22 21:06:43 +02:00
for (j = i - 1; j >= numChildren; j--) {
Child[j + 1] = Child[j];
}
2023-10-22 21:06:43 +02:00
Child[numChildren] = child;
numChildren++;
}
2023-10-22 21:06:43 +02:00
void PathNode::DisconnectChild(int i)
{
int j;
pathway_t child = Child[i];
2023-10-22 21:06:43 +02:00
for (j = i + 1; j < numChildren; j++) {
Child[j - 1] = Child[j];
2023-10-22 21:06:43 +02:00
}
numChildren--;
Child[numChildren] = child;
2023-10-22 21:06:43 +02:00
}
qboolean PathNode::IsTouching(Entity *e1)
2023-10-22 21:06:43 +02:00
{
return e1->absmin[0] <= origin[0] + 15.5f && e1->absmin[1] <= origin[1] + 15.5f
&& e1->absmin[0] <= origin[2] + 94.0f && origin[0] - 15.5f <= e1->absmax[0]
&& origin[1] - 15.5f <= e1->absmax[1] && origin[2] + 0.0f <= e1->absmax[2];
}
2023-10-22 21:06:43 +02:00
void PathNode::IsTouching(Event *ev)
{
Entity *ent = ev->GetEntity(1);
2023-10-22 21:06:43 +02:00
if (!ent) {
ScriptError("IsTouching used with a NULL entity.\n");
}
ev->AddInteger(IsTouching(ev->GetEntity(1)));
2023-10-22 21:06:43 +02:00
}
void PathNode::setOriginEvent(Vector org)
2023-10-22 21:06:43 +02:00
{
if (!PathManager.m_bNodesloaded) {
origin = org;
centroid = org;
}
}
2023-10-22 21:06:43 +02:00
void PathNode::DrawConnections(void)
{
int i;
pathway_t *path;
PathNode *node;
2023-10-22 21:06:43 +02:00
for (i = 0; i < numChildren; i++) {
path = &Child[i];
node = PathSearch::pathnodes[path->node];
2023-10-22 21:06:43 +02:00
G_DebugLine(origin + Vector("0 0 24"), node->origin + Vector("0 0 24"), 0.7f, 0.7f, 0, 1);
2023-10-22 21:06:43 +02:00
}
}
2023-11-09 19:52:53 +01:00
static void droptofloor(Vector& vec, PathNode *node)
{
Vector start, end;
2023-11-09 19:37:25 +01:00
trace_t trace;
start = vec;
start[2] += 36;
end = start;
end[2] -= 2048;
trace = G_Trace(start, PLAYER_BASE_MIN, PLAYER_BASE_MAX, end, NULL, MASK_PATHSOLID, qfalse, "droptofloor");
vec.z = trace.endpos[2];
}
static bool IsValidPathnode(int spawnflags)
2023-10-22 21:06:43 +02:00
{
if ((spawnflags & AI_DUCK) && (spawnflags & AI_COVERFLAGS2)) {
return false;
2023-10-22 21:06:43 +02:00
}
if ((spawnflags & AI_CONCEALMENT) && (spawnflags & AI_SNIPERFLAGS)) {
return false;
2023-10-22 21:06:43 +02:00
}
if ((spawnflags & AI_CORNER_LEFT) && (spawnflags & AI_COVER_LEFT_FLAGS)) {
return false;
2023-10-22 21:06:43 +02:00
}
if ((spawnflags & AI_CORNER_RIGHT) && (spawnflags & AI_COVER_RIGHT_FLAGS)) {
return false;
}
2023-10-22 21:06:43 +02:00
if ((spawnflags & AI_SNIPER) && (spawnflags & AI_CRATEFLAGS)) {
return false;
}
2023-10-22 21:06:43 +02:00
if ((spawnflags & AI_ALL) && (spawnflags & AI_COVERFLAGS3)) {
return false;
2023-10-22 21:06:43 +02:00
}
return true;
}
static void GetPathnodeColor(int spawnflags, vec3_t color)
2023-10-22 21:06:43 +02:00
{
if (IsValidPathnode(spawnflags)) {
if (spawnflags & AI_CORNER_LEFT) {
VectorCopy(COLOR_PATHNODE_CORNER_LEFT, color);
} else if (spawnflags & AI_CORNER_RIGHT) {
VectorCopy(COLOR_PATHNODE_CORNER_RIGHT, color);
} else if (spawnflags & AI_DUCK) {
VectorCopy(COLOR_PATHNODE_DUCK, color);
} else if (spawnflags & AI_SNIPER) {
VectorCopy(COLOR_PATHNODE_SNIPER, color);
} else if (spawnflags & AI_CONCEALMENT) {
VectorCopy(COLOR_PATHNODE_CONCEALMENT, color);
} else if (spawnflags & AI_COVER) {
VectorCopy(COLOR_PATHNODE_COVER, color);
} else if (spawnflags & AI_CRATE) {
VectorCopy(COLOR_PATHNODE_CRATE, color);
} else {
VectorCopy(COLOR_PATHNODE_DEFAULT, color);
2023-10-22 21:06:43 +02:00
}
} else {
VectorCopy(COLOR_PATHNODE_ERROR, color);
2023-10-22 21:06:43 +02:00
}
}
2023-10-22 21:06:43 +02:00
void DrawNode(int iNodeCount)
{
Vector down;
Vector up;
Vector dir;
Vector p1;
Vector p2;
Vector p3;
Vector p4;
Vector q1;
Vector q2;
Vector q3;
Vector q4;
Vector playerorigin;
Vector aStart;
Vector aEnd;
PathNode *node;
PathNode *nodelist[4096];
Vector end;
Vector start;
Vector p;
vec3_t color;
2023-10-22 21:06:43 +02:00
playerorigin = g_entities[0].client->ps.origin;
2023-10-22 21:06:43 +02:00
if (iNodeCount > 4096) {
iNodeCount = 4096;
}
2023-10-22 21:06:43 +02:00
if (ai_showallnode->integer) {
iNodeCount = PathSearch::DebugNearestNodeList2(playerorigin, nodelist, iNodeCount);
} else {
iNodeCount = PathSearch::DebugNearestNodeList(playerorigin, nodelist, iNodeCount);
}
if (iNodeCount) {
for (int i = 0; i < iNodeCount; i++) {
node = nodelist[i];
GetPathnodeColor(node->nodeflags, color);
p1.x = PLAYER_BASE_MAX.x + node->origin.x;
p1.y = PLAYER_BASE_MAX.y + node->origin.y;
p2.x = PLAYER_BASE_MAX.x + node->origin.x;
p2.y = PLAYER_BASE_MIN.y + node->origin.y;
p3.x = PLAYER_BASE_MIN.x + node->origin.x;
p3.y = PLAYER_BASE_MIN.y + node->origin.y;
p4.x = PLAYER_BASE_MIN.x + node->origin.x;
p4.y = PLAYER_BASE_MAX.y + node->origin.y;
start = node->origin + Vector(0, 0, 18);
end = node->origin + Vector(0, 0, 18);
aStart = start;
aEnd[0] = node->origin[0] + cos(M_PI / 180.0f * node->angles[1]) * 16.0f;
aEnd[1] = node->origin[1] + sin(M_PI / 180.0f * node->angles[1]) * 16.0f;
aEnd[2] = end[2];
2023-10-22 21:06:43 +02:00
G_DebugLine(aStart, aEnd, 1, 1, 1, 1);
2023-10-22 21:06:43 +02:00
p1.z = node->origin.z + 36.0f;
p2.z = node->origin.z + 36.0f;
p3.z = node->origin.z + 36.0f;
p4.z = node->origin.z + 36.0f;
2023-10-22 21:06:43 +02:00
G_DebugLine(p1, p2, color[0], color[1], color[2], 1);
G_DebugLine(p2, p3, color[0], color[1], color[2], 1);
G_DebugLine(p3, p4, color[0], color[1], color[2], 1);
G_DebugLine(p4, p1, color[0], color[1], color[2], 1);
2023-10-22 21:06:43 +02:00
q1 = p1;
q2 = p2;
q3 = p3;
q4 = p4;
2023-10-22 21:06:43 +02:00
q1.z = node->origin.z;
q2.z = node->origin.z;
q3.z = node->origin.z;
q4.z = node->origin.z;
2023-10-22 21:06:43 +02:00
G_DebugLine(q1, q2, color[0], color[1], color[2], 1);
G_DebugLine(q2, q3, color[0], color[1], color[2], 1);
G_DebugLine(q3, q4, color[0], color[1], color[2], 1);
G_DebugLine(q4, q1, color[0], color[1], color[2], 1);
G_DebugLine(p1, q1, color[0], color[1], color[2], 1);
G_DebugLine(p2, q2, color[0], color[1], color[2], 1);
G_DebugLine(p3, q3, color[0], color[1], color[2], 1);
G_DebugLine(p4, q4, color[0], color[1], color[2], 1);
2023-10-22 21:06:43 +02:00
}
} else {
G_DebugCircle(playerorigin + Vector(0, 0, 48), 128, 1, 0, 0, 1, true);
2023-10-22 21:06:43 +02:00
}
}
void DrawAllConnections(void)
2023-10-22 21:06:43 +02:00
{
pathway_t *path;
pathway_t *path2;
PathNode *node;
PathNode *to;
Vector down;
Vector up;
Vector dir;
Vector p1;
Vector p2;
Vector p3;
Vector p4;
Vector playerorigin;
qboolean showroutes;
qboolean shownums;
bool reverse;
2023-10-22 21:06:43 +02:00
showroutes = (ai_showroutes->integer != 0);
shownums = (ai_shownodenums->integer != 0);
2023-10-22 21:06:43 +02:00
// Figure out where the camera is
2023-10-22 21:06:43 +02:00
if (!g_entities[0].client) {
2023-10-22 21:06:43 +02:00
return;
}
playerorigin.x = g_entities[0].client->ps.origin[0];
playerorigin.y = g_entities[0].client->ps.origin[1];
playerorigin.z = g_entities[0].client->ps.origin[2];
2023-10-22 21:06:43 +02:00
playerorigin[2] += g_entities[0].client->ps.viewheight;
2023-10-22 21:06:43 +02:00
for (int i = 0; i < PathSearch::nodecount; i++) {
node = PathSearch::pathnodes[i];
2023-10-22 21:06:43 +02:00
if (Vector(node->origin - playerorigin).length() > ai_showroutes_distance->integer) {
continue;
}
2023-10-22 21:06:43 +02:00
if (shownums) {
G_DrawDebugNumber(node->origin + Vector(0, 0, 14), node->nodenum, 1.5, 1, 1, 0);
2023-10-22 21:06:43 +02:00
}
for (int j = 0; j < node->numChildren; j++) {
path = &node->Child[j];
2023-10-22 21:06:43 +02:00
if (path->fallheight > ai_fallheight->integer) {
continue;
}
2023-10-22 21:06:43 +02:00
reverse = false;
to = PathSearch::pathnodes[path->node];
2023-10-22 21:06:43 +02:00
for (int k = to->numChildren - 1; k >= 0; k--) {
path2 = &to->Child[k];
2023-10-22 21:06:43 +02:00
if (path2->fallheight < ai_fallheight->integer && PathSearch::pathnodes[path2->node] == node) {
reverse = true;
break;
}
}
2023-10-22 21:06:43 +02:00
p1 = path->pos1 + Vector(0, 0, 36);
p2 = path->pos2 + Vector(0, 0, 36);
2023-10-22 21:06:43 +02:00
if (node->nodenum < to->nodenum || !reverse) {
// draw connected lines in green
G_DebugLine(p1, p2, 0, 1, 0, 1);
2023-10-22 21:06:43 +02:00
if (!reverse) {
dir = Vector(path->pos2) - Vector(path->pos1);
dir.z = 0;
VectorNormalize(dir);
p3 = dir * 8.0f + dir * 8.0f;
p4 = dir * 8.0f;
p4.z += 8.0f;
G_DebugLine(p1 + p3 + up, p1 + p3 + up - p4, 1, 0, 0, 1);
p4.z -= 16.0f;
G_DebugLine(p1 + p3 + down, p1 + p3 + down - p4, 1, 0, 0, 1);
2023-10-22 21:06:43 +02:00
}
}
}
2023-10-22 21:06:43 +02:00
if (!node->numChildren) {
// Put a little X where the node is to show that it had no connections
p1 = node->origin;
p1.z += 2;
if (node->nodeflags & PATH_DONT_LINK) {
G_DebugCircle(p1, 12, 0, 0, 1, 1, true);
} else {
p2 = Vector(12, 12, 0);
G_DebugLine(p1 - p2, p1 + p2, 1, 0, 0, 1);
p2.x = -12;
G_DebugLine(p1 - p2, p1 + p2, 1, 0, 0, 1);
2023-10-22 21:06:43 +02:00
}
}
}
}
2023-10-22 21:06:43 +02:00
MapCell::MapCell()
{
numnodes = 0;
nodes = NULL;
}
2023-10-22 21:06:43 +02:00
int MapCell::NumNodes(void)
{
return numnodes;
}
2023-10-22 21:06:43 +02:00
/* All
work and no play
makes Jim a dull boy. All
work and no play makes Jim a
dull boy. All work and no play
makes Jim a dull boy. All work and no
play makes Jim a dull boy. All work and
no play makes Jim a dull boy. All work and
no play makes Jim a dull boy. All work and no
play makes Jim a dull boy. All work and no play
makes Jim a dull boy. All work and no play makes
Jim a dull boy. All work and no play makes Jim a
dull boy. All work and no play makes Jim a dull boy.
All work and no play makes Jim a dull boy. All work
and no play makes Jim a dull boy. All work and no play
makes Jim a dull boy. All work and no play makes Jim a
dull boy. All work and no play makes Jim a dull boy. All
work and no play makes Jim a dull boy. All work and no
play makes Jim a dull boy. All work and no play makes Jim
a dull boy. All work and no play makes Jim a dull boy.
All work and no play makes Jim a dull boy. All work and
no play makes Jim a dull boy. All work and no play makes
Jim a dull boy. All work and no play makes Jim a dull
boy. All work and no play makes Jim a dull boy. All work
and no play makes Jim a dull boy. All work and no play
makes Jim a dull boy. All work and no play makes Jim a
dull boy. All work and no play makes Jim a dull boy. All
work and no play makes Jim a dull boy. All work and no
play makes Jim a dull boy. All work and no play makes
Jim a dull boy. All work and no play makes Jim a dull
boy. All work and no play makes Jim a dull boy. All
work and no play makes Jim a dull boy. All work and
no play makes Jim a dull boy. All work and no
play makes Jim a dull boy. All work and no play
makes Jim a dull boy. All work and no play
makes Jim a dull boy. All work and no play
makes Jim a dull boy. All work and no
play makes Jim a dull boy. All work
and no play makes Jim a dull boy.
All work and no play makes
Jim a dull boy. All work
and no play makes
Jim a
*/
2023-10-22 21:06:43 +02:00
CLASS_DECLARATION(Class, PathSearch, NULL) {
{NULL, NULL}
};
PathSearch::PathSearch()
{
memset(pathnodes, 0, sizeof(pathnodes));
open = 0;
findFrame = 0;
}
PathSearch::~PathSearch()
{
ResetNodes();
}
2023-10-22 21:06:43 +02:00
void PathSearch::LoadAddToGrid(int x, int y)
{
MapCell *cell;
2023-10-22 21:06:43 +02:00
2023-11-09 21:01:30 +01:00
cell = GetNodesInCell(x, y);
if (cell) {
cell->numnodes++;
2023-10-22 21:06:43 +02:00
}
}
2023-10-22 21:06:43 +02:00
int PathSearch::NodeCoordinate(float coord)
{
float c;
2023-10-22 21:06:43 +02:00
//return ( ( int )coord + MAX_MAP_BOUNDS - ( PATHMAP_CELLSIZE / 2 ) ) / PATHMAP_CELLSIZE;
2023-10-22 21:06:43 +02:00
c = coord + MAX_MAP_BOUNDS - (PATHMAP_CELLSIZE / 2);
if (c < 0) {
c = coord + MAX_MAP_BOUNDS + (PATHMAP_CELLSIZE / 2) - 1;
2023-10-22 21:06:43 +02:00
}
return (int)c >> 8;
2023-10-22 21:06:43 +02:00
}
int PathSearch::GridCoordinate(float coord)
2023-10-22 21:06:43 +02:00
{
float c;
//return ( ( int )coord + MAX_MAP_BOUNDS ) / PATHMAP_CELLSIZE;
c = coord + MAX_MAP_BOUNDS;
if (c < 0) {
c = coord + MAX_MAP_BOUNDS + PATHMAP_CELLSIZE - 1;
2023-10-22 21:06:43 +02:00
}
return (int)c >> 8;
2023-10-22 21:06:43 +02:00
}
MapCell *PathSearch::GetNodesInCell(int x, int y)
2023-10-22 21:06:43 +02:00
{
if ((x < 0) || (x >= PATHMAP_GRIDSIZE) || (y < 0) || (y >= PATHMAP_GRIDSIZE)) {
return NULL;
2023-10-22 21:06:43 +02:00
}
return &PathMap[x][y];
2023-10-22 21:06:43 +02:00
}
MapCell *PathSearch::GetNodesInCell(const vec3_t pos)
2023-10-22 21:06:43 +02:00
{
int x;
int y;
2023-10-24 19:30:13 +02:00
x = GridCoordinate(pos[0]);
y = GridCoordinate(pos[1]);
2023-10-22 21:06:43 +02:00
return GetNodesInCell(x, y);
}
2023-10-22 21:06:43 +02:00
int PathSearch::DebugNearestNodeList(const vec3_t pos, PathNode **nodelist, int iMaxNodes)
{
PathNode *node;
int i;
MapCell *cell;
int nodes[128];
vec3_t deltas[128];
vec3_t start;
vec3_t end;
2023-10-22 21:06:43 +02:00
cell = GetNodesInCell(pos);
2023-10-22 21:06:43 +02:00
if (!cell) {
return 0;
2023-10-22 21:06:43 +02:00
}
int node_count = NearestNodeSetup(pos, cell, nodes, deltas);
int n = 0;
int j = 0;
2023-10-22 21:06:43 +02:00
for (i = 0; i < node_count && j < iMaxNodes; i++) {
node = pathnodes[cell->nodes[nodes[i]]];
2016-03-27 11:49:47 +02:00
VectorCopy(pos, start);
VectorCopy(pos, end);
2023-10-22 21:06:43 +02:00
VectorAdd(end, deltas[i], end);
2023-10-22 21:06:43 +02:00
Vector vStart = start;
Vector vMins = Vector(-15, -15, 0);
Vector vMaxs = Vector(15, 15, 62);
Vector vEnd = end;
2023-10-22 21:06:43 +02:00
if (G_SightTrace(
vStart,
vMins,
vMaxs,
vEnd,
(gentity_t *)NULL,
(gentity_t *)NULL,
MASK_PATHSOLID,
qtrue,
"PathSearch::DebugNearestNodeList"
)) {
nodelist[n] = node;
n++;
2023-10-22 21:06:43 +02:00
}
}
if (!n && node_count) {
nodelist[0] = pathnodes[cell->nodes[nodes[0]]];
return 1;
} else {
return n;
}
return 0;
2023-10-22 21:06:43 +02:00
}
int PathSearch::DebugNearestNodeList2(const vec3_t pos, PathNode **nodelist, int iMaxNodes)
2023-10-22 21:06:43 +02:00
{
vec3_t delta;
PathNode *node;
float dist;
int n = 0;
int i;
int j;
static float node_dist[MAX_PATHNODES];
int node_count;
2023-10-22 21:06:43 +02:00
node_count = nodecount;
2023-10-22 21:06:43 +02:00
for (i = 0; i < node_count; i++) {
node = pathnodes[i];
2023-10-22 21:06:43 +02:00
if (pos[2] > node->origin[2] + 94.0f) {
continue;
}
2023-10-22 21:06:43 +02:00
if (node->origin[2] > pos[2] + 94.0f) {
continue;
}
2023-10-22 21:06:43 +02:00
delta[0] = node->origin[0] - pos[0];
delta[1] = node->origin[1] - pos[1];
delta[2] = node->origin[2] - pos[2];
2023-10-22 21:06:43 +02:00
dist = VectorLengthSquared(delta);
2023-10-22 21:06:43 +02:00
for (j = n; j > 0; j--) {
if (dist >= node_dist[j - 1]) {
break;
}
2023-10-22 21:06:43 +02:00
node_dist[j] = node_dist[j - 1];
nodelist[j] = nodelist[j - 1];
2023-10-22 21:06:43 +02:00
}
n++;
nodelist[j] = node;
node_dist[j] = dist;
}
2023-10-22 21:06:43 +02:00
return n;
}
PathNode *PathSearch::DebugNearestStartNode(const vec3_t pos, Entity *ent)
{
PathNode *node = NULL;
int i;
MapCell *cell;
int nodes[128];
vec3_t deltas[128];
vec3_t start;
vec3_t end;
int node_count;
2023-10-22 21:06:43 +02:00
cell = GetNodesInCell(pos);
if (!cell) {
return NULL;
2023-10-22 21:06:43 +02:00
}
node_count = NearestNodeSetup(pos, cell, nodes, deltas);
2023-10-22 21:06:43 +02:00
VectorCopy(pos, start);
start[2] += 32.0f;
for (i = 0; i < node_count; i++) {
node = pathnodes[cell->nodes[nodes[i]]];
VectorCopy(start, end);
VectorAdd(end, deltas[nodes[i]], end);
if (G_SightTrace(
start,
Vector(-15, -15, 0),
Vector(15, 15, 62),
end,
ent,
NULL,
MASK_TARGETPATH,
qtrue,
"PathSearch::DebugNearestStartNode"
)) {
return node;
}
2023-10-22 21:06:43 +02:00
}
if (node_count > 0) {
return pathnodes[cell->nodes[nodes[0]]];
}
return NULL;
2023-10-22 21:06:43 +02:00
}
PathNode *PathSearch::NearestStartNode(const vec3_t pos, SimpleActor *ent)
2023-10-22 21:06:43 +02:00
{
PathNode *node = NULL;
int i;
MapCell *cell;
int nodes[128];
vec3_t deltas[128];
vec3_t start;
vec3_t end;
int node_count;
2023-10-22 21:06:43 +02:00
cell = GetNodesInCell(pos);
2023-10-22 21:06:43 +02:00
if (!cell) {
return NULL;
}
2023-10-22 21:06:43 +02:00
node_count = NearestNodeSetup(pos, cell, nodes, deltas);
2023-10-22 21:06:43 +02:00
VectorCopy(pos, start);
start[2] += 32.0f;
2023-10-22 21:06:43 +02:00
for (i = 0; i < node_count; i++) {
node = pathnodes[cell->nodes[nodes[i]]];
2023-10-22 21:06:43 +02:00
VectorAdd(start, deltas[nodes[i]], end);
2023-10-22 21:06:43 +02:00
if (G_SightTrace(
start,
Vector(-15, -15, 0),
Vector(15, 15, 62),
end,
ent,
NULL,
MASK_PATHSOLID,
qtrue,
"PathSearch::NearestStartNode 1"
)) {
ent->m_NearestNode = node;
VectorCopy(end, ent->m_vNearestNodePos);
return node;
}
2023-10-22 21:06:43 +02:00
}
if (ent->m_NearestNode) {
if (G_SightTrace(
start,
Vector(-15, -15, 0),
Vector(15, 15, 62),
ent->m_vNearestNodePos,
ent,
NULL,
MASK_TARGETPATH,
qtrue,
"PathSearch::NearestStartNode 2"
)) {
return ent->m_NearestNode;
}
}
2023-10-22 21:06:43 +02:00
if (node_count > 0) {
return pathnodes[cell->nodes[nodes[0]]];
2023-10-22 21:06:43 +02:00
}
return ent->m_NearestNode;
2023-10-22 21:06:43 +02:00
}
PathNode *PathSearch::NearestEndNode(const vec3_t pos)
2023-10-22 21:06:43 +02:00
{
PathNode *node = NULL;
int i;
MapCell *cell;
int nodes[128];
vec3_t deltas[128];
vec3_t start;
vec3_t end;
int node_count;
2023-10-22 21:06:43 +02:00
cell = GetNodesInCell(pos);
2023-10-22 21:06:43 +02:00
if (!cell) {
return NULL;
}
2023-10-22 21:06:43 +02:00
node_count = NearestNodeSetup(pos, cell, nodes, deltas);
2023-10-22 21:06:43 +02:00
VectorCopy(pos, start);
start[2] += 32.0f;
2023-10-22 21:06:43 +02:00
for (i = 0; i < node_count; i++) {
node = pathnodes[cell->nodes[nodes[i]]];
2023-10-22 21:06:43 +02:00
VectorAdd(start, deltas[nodes[i]], end);
2023-10-22 21:06:43 +02:00
if (G_SightTrace(
start,
Vector(-15, -15, 0),
Vector(15, 15, 62),
end,
(Entity *)nullptr,
(Entity *)nullptr,
MASK_TARGETPATH,
qtrue,
"PathSearch::NearestEndNode"
)) {
return node;
2023-10-22 21:06:43 +02:00
}
}
return NULL;
2023-10-22 21:06:43 +02:00
}
void PathSearch::ShowNodes(void)
2023-10-22 21:06:43 +02:00
{
if (g_entities->client) {
if (ai_shownode->integer) {
DrawNode(ai_shownode->integer);
}
if (ai_showroutes->integer || ai_shownodenums->integer) {
DrawAllConnections();
}
2023-10-22 21:06:43 +02:00
}
if (ai_showpath->integer) {
if (!test_path) {
test_path = new ActorPath;
}
if (ai_showpath->integer == 1) {
ai_startpath = g_entities[0].entity->origin;
}
if (ai_showpath->integer == 2) {
ai_endpath = g_entities[0].entity->origin;
}
if (ai_showpath->integer <= 2) {
test_path->SetFallHeight(ai_fallheight->integer);
test_path->FindPath(ai_startpath, ai_endpath, NULL, 0, NULL, 0);
}
if (ai_showpath->integer == 3) {
if (test_path->CurrentNode()) {
test_path->UpdatePos(g_entities[0].entity->origin);
Vector vStart = g_entities[0].entity->origin + Vector(0, 0, 32);
Vector vEnd = g_entities[0].entity->origin + test_path->CurrentDelta() + Vector(0, 0, 32);
2023-10-22 21:06:43 +02:00
G_DebugLine(vStart, vEnd, 1, 1, 0, 1);
}
2023-10-22 21:06:43 +02:00
}
G_DebugLine(ai_startpath, ai_endpath, 0, 0, 1, 1);
2023-10-22 21:06:43 +02:00
if (test_path->CurrentNode()) {
PathInfo *pos = test_path->CurrentNode();
2023-10-22 21:06:43 +02:00
while (pos != test_path->LastNode()) {
Vector vStart = pos->point + Vector(0, 0, 32);
2023-10-22 21:06:43 +02:00
pos--;
2023-10-22 21:06:43 +02:00
Vector vEnd = pos->point + Vector(0, 0, 32);
2023-10-22 21:06:43 +02:00
G_DebugLine(vStart, vEnd, 1, 0, 0, 1);
}
}
}
}
2023-10-22 21:06:43 +02:00
qboolean PathSearch::ArchiveSaveNodes(void)
{
Archiver arc;
str maptime;
int tempInt;
2023-10-24 19:30:13 +02:00
if (!arc.Create(level.m_pathfile)) {
return qfalse;
}
tempInt = PATHFILE_VERSION;
arc.ArchiveInteger(&tempInt);
maptime = gi.MapTime();
arc.ArchiveString(&maptime);
arc.ArchiveInteger(&m_NodeCheckFailed);
ArchiveStaticSave(arc);
arc.Close();
2023-10-22 21:06:43 +02:00
return true;
}
2023-10-22 21:06:43 +02:00
void PathSearch::ArchiveLoadNodes(void)
{
Archiver arc;
2023-10-23 18:57:56 +02:00
m_LoadIndex = 0;
if (arc.Read(level.m_pathfile, false)) {
int file_version;
str maptime;
2023-10-22 21:06:43 +02:00
// get file values
arc.ArchiveInteger(&file_version);
if (file_version != PATHFILE_VERSION) {
Com_Printf("Expecting version %d path file. Path file is version %d.\n", PATHFILE_VERSION, file_version);
arc.Close();
return;
2023-10-22 21:06:43 +02:00
}
arc.ArchiveString(&maptime);
if (gi.MapTime() == maptime && gi.FS_FileNewer(level.m_mapfile.c_str(), level.m_pathfile.c_str()) <= 0) {
arc.ArchiveInteger(&m_NodeCheckFailed);
2023-10-22 21:06:43 +02:00
if (!g_nodecheck->integer || !m_NodeCheckFailed) {
ArchiveStaticLoad(arc);
m_bNodesloaded = arc.NoErrors();
} else {
gi.Printf("Rebuilding pathnodes to view node errors.\n");
}
} else {
gi.Printf("Pathnodes have changed, rebuilding.\n");
}
}
2023-10-22 21:06:43 +02:00
arc.Close();
}
2023-10-22 21:06:43 +02:00
void PathSearch::Init(void)
{
ai_showroutes = gi.Cvar_Get("ai_showroutes", "0", 0);
ai_showroutes_distance = gi.Cvar_Get("ai_showroutes_distance", "1000", 0);
ai_shownodenums = gi.Cvar_Get("ai_shownodenums", "0", 0);
ai_shownode = gi.Cvar_Get("ai_shownode", "0", 0);
ai_showallnode = gi.Cvar_Get("ai_showallnode", "0", 0);
ai_showpath = gi.Cvar_Get("ai_showpath", "0", 0);
ai_fallheight = gi.Cvar_Get("ai_fallheight", "96", 0);
ai_debugpath = gi.Cvar_Get("ai_debugpath", "0", 0);
ai_pathchecktime = gi.Cvar_Get("ai_pathchecktime", "1.5", CVAR_CHEAT);
ai_pathcheckdist = gi.Cvar_Get("ai_pathcheckdist", "4096", CVAR_CHEAT);
}
2023-10-22 21:06:43 +02:00
void *PathSearch::AllocPathNode(void)
{
if (!bulkNavMemory) {
return gi.Malloc(sizeof(PathNode));
}
bulkNavMemory -= sizeof(PathNode);
return bulkNavMemory;
}
2023-10-22 21:06:43 +02:00
void PathSearch::FreePathNode(void *ptr)
{
if (!bulkNavMemory) {
gi.Free(ptr);
}
}
2023-10-22 21:06:43 +02:00
void PathSearch::PlayerCover(Player *pPlayer)
{
int i;
PathNode *node;
Vector delta;
Entity *pOwner;
2023-10-22 21:06:43 +02:00
for (i = 0; i < nodecount; i++) {
node = pathnodes[i];
2023-10-22 21:06:43 +02:00
if (!node || !(node->nodeflags & AI_COVERFLAGS)) {
continue;
}
2023-10-22 21:06:43 +02:00
pOwner = node->GetClaimHolder();
2023-10-22 21:06:43 +02:00
delta = node->origin - pPlayer->origin;
2023-10-22 21:06:43 +02:00
// Check if we need to cover
if (VectorLengthSquared(delta) > Square(48)) {
if (pOwner == pPlayer) {
node->Relinquish();
2023-10-23 18:57:56 +02:00
}
continue;
}
2023-10-22 21:06:43 +02:00
if (pOwner != pPlayer) {
if (pOwner) {
pOwner->PathnodeClaimRevoked(node);
}
// Player claim the node
node->Claim(pPlayer);
}
}
}
2023-10-22 21:06:43 +02:00
class nodeinfo
{
public:
PathNode *pNode;
float fDistSquared;
};
2023-10-22 21:06:43 +02:00
int node_compare(const void *pe1, const void *pe2)
{
nodeinfo *Pe1 = (nodeinfo *)pe1;
nodeinfo *Pe2 = (nodeinfo *)pe2;
int iConcealment;
2023-10-22 21:06:43 +02:00
iConcealment = (Pe1->pNode->nodeflags & AI_CONCEALMENT_MASK) != 0;
if (Pe2->pNode->nodeflags & AI_CONCEALMENT_MASK) {
--iConcealment;
2023-10-22 21:06:43 +02:00
}
return (
(Pe1->fDistSquared) + (iConcealment << 23)
+ (((Pe1->pNode->nodeflags & AI_CONCEALMENT) - (Pe2->pNode->nodeflags & AI_CONCEALMENT)) << 21)
- (Pe2->fDistSquared)
);
2023-10-22 21:06:43 +02:00
}
int PathSearch::FindPotentialCover(
SimpleActor *pEnt, Vector& vPos, Entity *pEnemy, PathNode **ppFoundNodes, int iMaxFind
2023-10-22 21:06:43 +02:00
)
{
nodeinfo nodes[MAX_PATHNODES];
int nNodes = 0;
int i;
Vector delta;
PathNode *node;
2023-10-22 21:06:43 +02:00
Actor *pActor = static_cast<Actor *>(pEnt);
for (i = 0; i < nodecount; i++) {
node = pathnodes[i];
if (!node) {
continue;
2023-10-22 21:06:43 +02:00
}
if (!(node->nodeflags & AI_COVER_MASK)) {
continue;
}
2023-10-22 21:06:43 +02:00
if (node->IsClaimedByOther(static_cast<Entity *>(pEnt))) {
continue;
}
2023-10-22 21:06:43 +02:00
delta = node->origin - pActor->m_vHome;
if (delta.lengthSquared() > pActor->m_fLeashSquared) {
continue;
}
2023-10-22 21:06:43 +02:00
delta = node->origin - pEnemy->origin;
if (delta.lengthSquared() < pActor->m_fMinDistanceSquared
|| delta.lengthSquared() > pActor->m_fMaxDistanceSquared) {
continue;
}
2023-10-22 21:06:43 +02:00
delta = node->origin - pEnt->origin;
nodes[nNodes].pNode = node;
nodes[nNodes].fDistSquared = delta.lengthSquared();
nNodes++;
}
2023-10-22 21:06:43 +02:00
if (nNodes) {
qsort(nodes, nNodes, sizeof(nodeinfo), node_compare);
2023-10-22 21:06:43 +02:00
if (nNodes > iMaxFind) {
nNodes = iMaxFind;
}
2023-10-22 21:06:43 +02:00
for (i = 0; i < nNodes; i++) {
ppFoundNodes[nNodes - i - 1] = nodes[i].pNode;
}
}
return nNodes;
}
2023-10-22 21:06:43 +02:00
PathNode *PathSearch::FindNearestSniperNode(SimpleActor *pEnt, Vector& vPos, Entity *pEnemy)
{
Actor *pSelf = (Actor *)pEnt;
PathNode *pNode;
Vector delta;
int nNodes = 0;
2023-11-09 21:55:28 +01:00
int i;
nodeinfo nodes[MAX_PATHNODES];
2023-10-22 21:06:43 +02:00
2023-11-09 21:55:28 +01:00
for (i = 0; i < nodecount; i++) {
pNode = pathnodes[i];
2023-11-09 21:55:28 +01:00
if (!pNode) {
continue;
}
if (!(pNode->nodeflags & AI_SNIPER)) {
continue;
}
if (pNode->IsClaimedByOther(pEnt)) {
continue;
2023-10-22 21:06:43 +02:00
}
2023-11-09 21:55:28 +01:00
delta = pNode->origin - pSelf->m_vHome;
if (delta.lengthSquared() > pSelf->m_fLeashSquared) {
continue;
}
delta = pNode->origin - pEnemy->origin;
if (delta.lengthSquared() < pSelf->m_fMinDistanceSquared
|| delta.lengthSquared() > pSelf->m_fMaxDistanceSquared) {
continue;
}
delta = pNode->origin - pSelf->origin;
nodes[nNodes].fDistSquared = delta.lengthSquared();
nodes[nNodes].pNode = pNode;
nNodes++;
}
2023-10-22 21:06:43 +02:00
if (nNodes == 0) {
return NULL;
}
2023-10-22 21:06:43 +02:00
qsort(nodes, nNodes, sizeof(nodeinfo), node_compare);
if (nNodes <= 0) {
return NULL;
}
2023-11-09 21:55:28 +01:00
for (i = 0; i < nNodes; i++) {
pNode = nodes[i].pNode;
2023-11-09 21:55:28 +01:00
if (pSelf->CanSeeFrom(pSelf->eyeposition + pNode->origin, pEnemy)) {
return pNode;
2023-10-22 21:06:43 +02:00
}
2023-11-09 21:55:28 +01:00
pNode->MarkTemporarilyBad();
}
2023-10-22 21:06:43 +02:00
return NULL;
}
2023-10-22 21:06:43 +02:00
PathNode *PathSearch::GetSpawnNode(ClassDef *cls)
{
if (m_bNodesloaded) {
return pathnodes[m_LoadIndex++];
} else {
// Otherwise create a new node
2023-11-09 21:55:28 +01:00
return static_cast<PathNode *>(cls->newInstance());
}
}
2023-10-22 21:06:43 +02:00
void PathSearch::LoadNodes(void)
{
Init();
2023-10-22 21:06:43 +02:00
ArchiveLoadNodes();
}
2023-10-22 21:06:43 +02:00
void PathSearch::CreatePaths(void)
{
int i;
int j;
int x;
int y;
PathNode *node;
Vector start;
Vector end;
gentity_t *ent;
int t1;
int t2;
2023-10-22 21:06:43 +02:00
if (m_bNodesloaded) {
return;
}
2023-10-22 21:06:43 +02:00
if (!nodecount) {
m_bNodesloaded = true;
return;
}
2023-10-22 21:06:43 +02:00
m_NodeCheckFailed = false;
2023-10-22 21:06:43 +02:00
gi.DPrintf(
"***********************************\n"
"***********************************\n"
"\n"
"Creating paths...\n"
"\n"
"***********************************\n"
"***********************************\n"
);
2023-10-22 21:06:43 +02:00
gi.ClearResource();
t1 = gi.Milliseconds();
for (i = 0, ent = g_entities; i < game.maxentities; i++, ent++) {
if (ent->entity && ent->entity->IsSubclassOfDoor()) {
ent->entity->unlink();
}
}
for (x = 0; x < PATHMAP_GRIDSIZE; x++) {
for (y = 0; y < PATHMAP_GRIDSIZE; y++) {
MapCell *cell = &PathMap[x][y];
cell->nodes = (short *)gi.Malloc(PATHMAP_CELLSIZE);
cell->numnodes = 0;
memset(cell->nodes, 0, PATHMAP_CELLSIZE);
}
}
2023-10-22 21:06:43 +02:00
for (i = 0; i < nodecount; i++) {
node = pathnodes[i];
2023-10-22 21:06:43 +02:00
2023-11-09 19:37:25 +01:00
droptofloor(node->origin, node);
node->centroid = node->origin;
2023-10-22 21:06:43 +02:00
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;
}
2023-10-22 21:06:43 +02:00
}
if (!(node->nodeflags & PATH_DONT_LINK)) {
node->Child = (pathway_t *)gi.Malloc(sizeof(pathway_t) * PATHMAP_NODES);
}
}
2023-10-22 21:06:43 +02:00
for (i = 0; i < nodecount; i++) {
node = pathnodes[i];
if (node->nodeflags & PATH_DONT_LINK) {
continue;
2023-10-22 21:06:43 +02:00
}
AddNode(node);
2023-10-22 21:06:43 +02:00
}
for (i = 0; i < nodecount; i++) {
node = pathnodes[i];
2023-10-22 21:06:43 +02:00
if (node->nodeflags & PATH_DONT_LINK) {
continue;
}
2023-10-22 21:06:43 +02:00
Connect(node);
}
for (i = 0, ent = g_entities; i < game.maxentities; i++, ent++) {
if (ent->entity && ent->entity->IsSubclassOfDoor()) {
ent->entity->link();
2023-10-22 21:06:43 +02:00
}
}
gi.DPrintf("\nSaving path nodes to '%s'\n", level.m_pathfile.c_str());
Com_Printf("Archiving\n");
ArchiveSaveNodes();
m_bNodesloaded = true;
Com_Printf("done.\n");
t2 = gi.Milliseconds();
Com_Printf("Path connection: %5.2f seconds\n", (t2 - t1) / 1000.0f);
Com_Printf("Number of nodes: %d\n", nodecount);
gi.ClearResource();
if (g_nodecheck->integer && m_NodeCheckFailed) {
gi.Error(ERR_DROP, "Node check failed");
2023-10-22 21:06:43 +02:00
}
}
2023-10-22 21:06:43 +02:00
void PathSearch::LoadAddToGrid2(PathNode *node, int x, int y)
{
MapCell *cell;
2023-10-22 21:06:43 +02:00
cell = GetNodesInCell(x, y);
2023-10-22 21:06:43 +02:00
if (cell) {
cell->AddNode(node);
}
}
2023-10-22 21:06:43 +02:00
void PathSearch::ArchiveStaticLoad(Archiver& arc)
{
int i;
PathNode *node;
int total_nodes;
int total_children;
int x;
int y;
int size;
2023-10-22 21:06:43 +02:00
loadingarchive = true;
2023-10-22 21:06:43 +02:00
arc.ArchiveInteger(&nodecount);
arc.ArchiveInteger(&total_nodes);
arc.ArchiveInteger(&total_children);
2023-10-22 21:06:43 +02:00
size = total_nodes + total_children * (sizeof(pathway_t) * 2) + nodecount * (sizeof(PathNode) / 2);
size *= sizeof(void *) / 2;
2023-10-22 21:06:43 +02:00
gi.DPrintf("%d memory allocated for navigation.\n", size);
2023-10-22 21:06:43 +02:00
if (size) {
startBulkNavMemory = (byte *)gi.Malloc(size);
} else {
startBulkNavMemory = NULL;
}
2023-10-22 21:06:43 +02:00
bulkNavMemory = startBulkNavMemory + size;
2023-10-22 21:06:43 +02:00
for (i = 0; i < nodecount; i++) {
node = new PathNode;
2023-10-22 21:06:43 +02:00
arc.ArchiveObjectPosition(node);
node->ArchiveStatic(arc);
node->nodenum = i;
2023-10-22 21:06:43 +02:00
pathnodes[i] = node;
2023-10-22 21:06:43 +02:00
if (!(node->nodeflags & PATH_DONT_LINK)) {
x = NodeCoordinate(node->origin[0]);
y = NodeCoordinate(node->origin[1]);
2023-10-22 21:06:43 +02:00
LoadAddToGrid(x, y);
LoadAddToGrid(x + 1, y);
LoadAddToGrid(x, y + 1);
LoadAddToGrid(x + 1, y + 1);
}
}
2023-10-22 21:06:43 +02:00
for (x = 0; x < PATHMAP_GRIDSIZE; x++) {
for (y = 0; y < PATHMAP_GRIDSIZE; y++) {
bulkNavMemory -= PathMap[x][y].numnodes * sizeof(short);
2023-10-22 21:06:43 +02:00
PathMap[x][y].nodes = PathMap[x][y].numnodes ? (short *)bulkNavMemory : NULL;
PathMap[x][y].numnodes = 0;
}
}
2023-10-22 21:06:43 +02:00
for (i = 0; i < nodecount; i++) {
node = pathnodes[i];
2023-10-22 21:06:43 +02:00
if (!(node->nodeflags & PATH_DONT_LINK)) {
x = NodeCoordinate(node->origin[0]);
y = NodeCoordinate(node->origin[1]);
2023-10-22 21:06:43 +02:00
LoadAddToGrid2(node, x, y);
LoadAddToGrid2(node, x + 1, y);
LoadAddToGrid2(node, x, y + 1);
LoadAddToGrid2(node, x + 1, y + 1);
}
}
2023-10-22 21:06:43 +02:00
loadingarchive = false;
}
void PathSearch::ArchiveStaticSave(Archiver& arc)
{
int i;
PathNode *node;
int total_nodes = 0;
int total_children = 0;
int x = 0;
int y = 0;
2023-10-22 21:06:43 +02:00
for (x = 0; x < PATHMAP_GRIDSIZE; x++) {
for (y = 0; y < PATHMAP_GRIDSIZE; y++) {
total_nodes += PathMap[x][y].NumNodes();
}
}
2023-10-22 21:06:43 +02:00
for (i = 0; i < nodecount; i++) {
node = pathnodes[i];
total_children += node->virtualNumChildren;
}
2023-10-22 21:06:43 +02:00
arc.ArchiveInteger(&nodecount);
arc.ArchiveInteger(&total_nodes);
arc.ArchiveInteger(&total_children);
2023-10-22 21:06:43 +02:00
for (i = 0; i < nodecount; i++) {
node = pathnodes[i];
arc.ArchiveObjectPosition(node);
node->ArchiveStatic(arc);
}
}
2023-10-22 21:06:43 +02:00
bool PathSearch::ArchiveDynamic(Archiver& arc)
{
PathNode *node;
int i;
int count;
2023-10-22 21:06:43 +02:00
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;
2023-10-22 21:06:43 +02:00
}
}
2023-10-22 21:06:43 +02:00
for (i = 0; i < nodecount; i++) {
node = PathSearch::pathnodes[i];
node->ArchiveDynamic(arc);
2023-10-22 21:06:43 +02:00
}
return true;
2023-10-22 21:06:43 +02:00
}
void PathSearch::AddToGrid(PathNode *node, int x, int y)
2023-10-22 21:06:43 +02:00
{
MapCell *cell;
2023-10-22 21:06:43 +02:00
cell = GetNodesInCell(x, y);
if (!cell) {
return;
}
if (cell->NumNodes() >= PATHMAP_NODES) {
Com_Printf("^~^~^ PathSearch::AddToGrid: Node overflow at ( %d, %d )\n", x, y);
return;
2023-10-22 21:06:43 +02:00
}
cell->AddNode(node);
2023-10-22 21:06:43 +02:00
}
bool PathSearch::Connect(PathNode *node, int x, int y)
2023-10-22 21:06:43 +02:00
{
MapCell *cell;
int i;
PathNode *node2;
2023-10-22 21:06:43 +02:00
cell = GetNodesInCell(x, y);
2023-10-22 21:06:43 +02:00
if (!cell) {
return true;
2023-10-22 21:06:43 +02:00
}
for (i = 0; i < cell->numnodes; i++) {
node2 = pathnodes[cell->nodes[i]];
2023-10-22 21:06:43 +02:00
if (node2->findCount != findFrame) {
node2->findCount = findFrame;
if (!node->CheckPathTo(node2)) {
return false;
}
}
2023-10-22 21:06:43 +02:00
}
return true;
}
2023-10-22 21:06:43 +02:00
bool PathNode::CheckPathTo(PathNode *node)
{
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",
NUM_PATHSPERNODE,
node->origin[0],
node->origin[1],
node->origin[2]
);
PathSearch::m_NodeCheckFailed = true;
return false;
}
CheckPathToDefault(node, &Child[virtualNumChildren]);
return true;
}
2023-10-22 21:06:43 +02:00
qboolean CheckMove(Vector& origin, Vector& pos, short int *path_fallheight, float size)
{
mmove_t mm;
int i;
float air_z;
float fallheight;
float test_fallheight;
float error;
trace_t trace;
vec3_t dir;
vec3_t end;
2023-10-22 21:06:43 +02:00
memset(&mm, 0, sizeof(mmove_t));
2023-10-22 21:06:43 +02:00
VectorClear(mm.velocity);
VectorCopy(origin, mm.origin);
mm.desired_speed = 150.0f;
mm.entityNum = ENTITYNUM_NONE;
mm.tracemask = MASK_PATHSOLID;
mm.frametime = 0.1f;
mm.desired_dir[0] = pos[0] - origin[0];
mm.desired_dir[1] = pos[1] - origin[1];
VectorNormalize2D(mm.desired_dir);
2023-10-22 21:06:43 +02:00
mm.groundPlane = qfalse;
mm.walking = qfalse;
2023-10-22 21:06:43 +02:00
mm.mins[0] = -size;
mm.mins[1] = -size;
mm.mins[2] = 0;
mm.maxs[0] = size;
mm.maxs[1] = size;
mm.maxs[2] = 94.0f;
2023-10-22 21:06:43 +02:00
testcount = 0;
fallheight = 0.0f;
air_z = mm.origin[2];
2023-10-22 21:06:43 +02:00
for (i = 200; i != 1; i--) {
testpos[i - 1] = mm.origin;
testcount++;
2023-10-23 18:57:56 +02:00
MmoveSingle(&mm);
2023-10-22 21:06:43 +02:00
if (mm.groundPlane) {
test_fallheight = air_z - mm.origin[2];
if (test_fallheight > fallheight) {
if (test_fallheight > 1024.0f) {
return false;
}
fallheight = test_fallheight;
2023-10-22 21:06:43 +02:00
}
air_z = mm.origin[2];
2023-10-22 21:06:43 +02:00
}
2023-10-23 18:57:56 +02:00
VectorSub2D(pos, mm.origin, dir);
if (DotProduct2D(dir, mm.desired_dir) <= 0.1f) {
error = mm.origin[2] - pos[2];
2023-10-22 21:06:43 +02:00
*path_fallheight = (short)fallheight;
if (fabs(error) <= 94.0f) {
if (error <= 0.0f || mm.groundPlane) {
return true;
2023-10-22 21:06:43 +02:00
}
end[0] = mm.origin[0];
end[1] = mm.origin[1];
end[2] = pos[2];
2023-10-22 21:06:43 +02:00
trace = G_Trace(mm.origin, mm.mins, mm.maxs, end, NULL, MASK_PATHSOLID, true, "CheckMove");
test_fallheight = mm.origin[2] - trace.endpos[2];
if (test_fallheight <= 18.0f) {
*path_fallheight = (short)test_fallheight + fallheight;
return test_fallheight + fallheight <= 1024.0f;
2023-10-22 21:06:43 +02:00
}
} else {
return true;
2023-10-22 21:06:43 +02:00
}
if (mm.groundPlane) {
return false;
}
VectorCopy2D(dir, mm.desired_dir);
VectorNormalize2D(mm.desired_dir);
2023-10-22 21:06:43 +02:00
}
if (mm.hit_obstacle) {
break;
2023-10-22 21:06:43 +02:00
}
}
2023-10-24 19:30:13 +02:00
return false;
}
2023-10-22 21:06:43 +02:00
void PathNode::CheckPathToDefault(PathNode *node, pathway_t *pathway)
{
float dist;
vec2_t delta;
Vector start;
Vector end;
2023-10-22 21:06:43 +02:00
VectorSub2D(node->origin, origin, delta);
dist = VectorNormalize2D(delta);
2023-10-22 21:06:43 +02:00
if (dist >= 384.0f) {
return;
}
2023-10-22 21:06:43 +02:00
2023-11-09 19:37:25 +01:00
start = origin;
2023-11-09 19:52:53 +01:00
end = node->origin;
2023-10-23 18:57:56 +02:00
2023-11-09 19:37:25 +01:00
droptofloor(start, this);
droptofloor(end, node);
2023-10-22 21:06:43 +02:00
if (CheckMove(start, end, &pathway->fallheight, 15.5f)) {
pathway->dist = dist;
VectorCopy2D(delta, pathway->dir);
start.copyTo(pathway->pos1);
end.copyTo(pathway->pos2);
2023-11-09 19:37:25 +01:00
ConnectTo(node);
2023-10-22 21:06:43 +02:00
}
}
2023-10-23 18:57:56 +02:00
void MapCell::AddNode(PathNode *node)
{
nodes[numnodes] = node->nodenum;
numnodes++;
2023-10-22 21:06:43 +02:00
}
void PathSearch::AddNode(PathNode *node)
2023-10-22 21:06:43 +02:00
{
int x;
int y;
2023-10-22 21:06:43 +02:00
assert(node);
2023-10-22 21:06:43 +02:00
x = NodeCoordinate(node->origin[0]);
y = NodeCoordinate(node->origin[1]);
2023-10-22 21:06:43 +02:00
AddToGrid(node, x, y);
AddToGrid(node, x + 1, y);
AddToGrid(node, x, y + 1);
AddToGrid(node, x + 1, y + 1);
}
2023-10-22 21:06:43 +02:00
void PathSearch::Connect(PathNode *node)
{
int x;
int y;
2023-10-22 21:06:43 +02:00
findFrame++;
node->findCount = findFrame;
2023-10-22 21:06:43 +02:00
x = GridCoordinate(node->origin[0]);
y = GridCoordinate(node->origin[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;
2023-10-22 21:06:43 +02:00
}
Connect(node, x + 1, y + 1);
2023-10-22 21:06:43 +02:00
}
const_str PathNode::GetSpecialAttack(Actor *pActor)
2023-10-22 21:06:43 +02:00
{
int iSpecialAttack;
const_str csAnimation;
float fRangeSquared;
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;
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;
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 = 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) {
VectorSub2D(pActor->m_Enemy->centroid, origin, vDelta);
} else {
VectorSub2D(pActor->m_vLastEnemyPos, origin, vDelta);
}
fRangeSquared = VectorLength2DSquared(vDelta);
if (fRangeSquared < fMinRangeSquared || fRangeSquared > fMaxRangeSquared) {
return STRING_NULL;
}
fAngle = RAD2DEG(atan2(vDelta[1], vDelta[0])) - angles[1];
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 {
fAngle = fAngle;
}
if (fMinAngle > fMaxAngle) {
if (fAngle < fMinAngle && fAngle > fMaxAngle) {
return STRING_NULL;
}
} else {
if (fAngle < fMinAngle || fAngle > fMaxAngle) {
return STRING_NULL;
}
}
return csAnimation;
}
2023-10-22 21:06:43 +02:00
void PathNode::Claim(Entity *pClaimer)
{
pLastClaimer = pClaimer;
iAvailableTime = 0;
}
2023-10-22 21:06:43 +02:00
Entity *PathNode::GetClaimHolder(void) const
{
if (iAvailableTime) {
return NULL;
} else {
return pLastClaimer;
2023-10-22 21:06:43 +02:00
}
}
void PathNode::Relinquish(void)
2023-10-22 21:06:43 +02:00
{
iAvailableTime = level.inttime + 4000;
}
2023-10-22 21:06:43 +02:00
bool PathNode::IsClaimedByOther(Entity *pPossibleClaimer) const
{
if (pLastClaimer == pPossibleClaimer) {
return false;
}
2023-10-22 21:06:43 +02:00
if (iAvailableTime) {
return (level.inttime < iAvailableTime);
} else {
return (pLastClaimer != NULL);
}
}
2023-10-22 21:06:43 +02:00
void PathNode::MarkTemporarilyBad(void)
{
iAvailableTime = level.inttime + 5000;
pLastClaimer = NULL;
}
2023-10-22 21:06:43 +02:00
void PathNode::Archive(Archiver& arc) {}
2023-10-22 21:06:43 +02:00
void PathNode::ArchiveStatic(Archiver& arc)
{
arc.ArchiveVector(&origin);
arc.ArchiveVector(&centroid);
arc.ArchiveInteger(&nodeflags);
arc.ArchiveInteger(&virtualNumChildren);
2023-10-22 21:06:43 +02:00
numChildren = virtualNumChildren;
2023-10-22 21:06:43 +02:00
if (arc.Loading()) {
bulkNavMemory -= virtualNumChildren * sizeof(pathway_t) * sizeof(pathway_t *);
Child = virtualNumChildren ? (pathway_t *)bulkNavMemory : NULL;
2023-10-22 21:06:43 +02:00
}
for (int i = 0; i < virtualNumChildren; i++) {
arc.ArchiveShort(&Child[i].node);
arc.ArchiveShort(&Child[i].fallheight);
arc.ArchiveFloat(&Child[i].dist);
arc.ArchiveVec2(Child[i].dir);
arc.ArchiveVec3(Child[i].pos1);
arc.ArchiveVec3(Child[i].pos2);
2023-10-22 21:06:43 +02:00
if (arc.Loading()) {
Child[i].numBlockers = 0;
2023-10-22 21:06:43 +02:00
for (int j = 0; j < ARRAY_LEN(Child[i].badPlaceTeam); j++) {
Child[i].badPlaceTeam[j] = 0;
2023-10-22 21:06:43 +02:00
}
}
}
}
2023-10-22 21:06:43 +02:00
void PathNode::ArchiveDynamic(Archiver& arc)
{
SimpleEntity::SimpleArchive(arc);
2023-10-22 21:06:43 +02:00
arc.ArchiveObjectPosition(this);
arc.ArchiveSafePointer(&pLastClaimer);
arc.ArchiveInteger(&iAvailableTime);
arc.ArchiveInteger(&numChildren);
2023-10-22 21:06:43 +02:00
if (numChildren != virtualNumChildren) {
for (int i = 0; i < virtualNumChildren; i++) {
arc.ArchiveByte(&Child[i].numBlockers);
arc.ArchiveShort(&Child[i].node);
arc.ArchiveShort(&Child[i].fallheight);
arc.ArchiveFloat(&Child[i].dist);
arc.ArchiveVec2(Child[i].dir);
arc.ArchiveVec3(Child[i].pos1);
arc.ArchiveVec3(Child[i].pos2);
2023-10-22 21:06:43 +02:00
}
}
}
int PathSearch::NearestNodeSetup(const vec3_t pos, MapCell *cell, int *nodes, vec3_t *deltas)
2023-10-22 21:06:43 +02:00
{
vec3_t delta;
PathNode *node;
float dist;
int n = 0;
int i;
int j;
float node_dist[128];
int node_count;
node_count = cell->numnodes;
for (i = 0; i < node_count; i++) {
node = pathnodes[cell->nodes[i]];
if (pos[2] > node->origin[2] + 94.0f) {
continue;
}
2023-10-24 19:30:13 +02:00
if (pos[2] + 94.0f < node->origin[2]) {
2023-10-22 21:06:43 +02:00
continue;
}
2023-10-23 22:55:35 +02:00
VectorSubtract(node->origin, pos, delta);
2023-10-22 21:06:43 +02:00
VectorCopy(delta, deltas[i]);
dist = VectorLengthSquared(delta);
for (j = n; j > 0; j--) {
if (dist >= node_dist[j - 1]) {
break;
}
node_dist[j] = node_dist[j - 1];
nodes[j] = nodes[j - 1];
}
n++;
nodes[j] = i;
node_dist[j] = dist;
}
return n;
}
PathNode *PathSearch::FindNearestCover(SimpleActor *pEnt, Vector& vPos, Entity *pEnemy)
2023-10-22 21:06:43 +02:00
{
// not found in ida
return NULL;
2023-10-22 21:06:43 +02:00
}
Event EV_AttractiveNode_GetPriority
(
"priority",
EV_DEFAULT,
NULL,
NULL,
"Get the node priority",
EV_GETTER
);
2023-10-22 21:06:43 +02:00
Event EV_AttractiveNode_SetPriority
(
"priority",
EV_DEFAULT,
"i",
"priority",
"Set the node priority",
EV_SETTER
);
2023-10-22 21:06:43 +02:00
Event EV_AttractiveNode_GetDistance
(
"max_dist",
EV_DEFAULT,
NULL,
NULL,
"Get the max distance for this node",
EV_GETTER
);
2023-10-22 21:06:43 +02:00
Event EV_AttractiveNode_SetDistance
(
2023-10-22 21:06:43 +02:00
"max_dist",
EV_DEFAULT,
"f",
"max_dist",
"Set the max distance for this node to be attracted, -1 for unlimited distance.",
EV_SETTER
);
Event EV_AttractiveNode_GetStayTime
(
"stay_time",
EV_DEFAULT,
NULL,
NULL,
"Get the max stay time for this node",
EV_GETTER
2023-10-22 21:06:43 +02:00
);
Event EV_AttractiveNode_SetStayTime
(
"stay_time",
EV_DEFAULT,
"f",
"stay_time",
"Set the maximum stay time AI will stay on this node",
EV_SETTER
2023-10-22 21:06:43 +02:00
);
Event EV_AttractiveNode_GetRespawnTime
(
2023-10-22 21:06:43 +02:00
"respawn_time",
EV_DEFAULT,
NULL,
NULL,
"Get the how much time will this node re-attract already attracted AIs",
EV_GETTER
);
Event EV_AttractiveNode_SetRespawnTime
(
2023-10-22 21:06:43 +02:00
"respawn_time",
EV_DEFAULT,
"f",
"respawn_time",
"Set the how much time will this node re-attract already attracted AIs. The minimum required value is 1, otherwise "
"AI will get stuck.",
EV_SETTER
);
Event EV_AttractiveNode_GetTeam
(
"team",
EV_DEFAULT,
NULL,
NULL,
"Get the attractive node team. 'none' for no team.",
EV_GETTER
2023-10-22 21:06:43 +02:00
);
Event EV_AttractiveNode_SetTeam
(
"team",
EV_DEFAULT,
"s",
"team",
"Set the attractive node team. 'none' for no team.",
EV_SETTER
2023-10-22 21:06:43 +02:00
);
Event EV_AttractiveNode_SetUse("setuse", EV_DEFAULT, "b", "use", "Set if AI should use or not");
CLASS_DECLARATION(SimpleArchivedEntity, AttractiveNode, NULL) {
{&EV_AttractiveNode_GetPriority, &AttractiveNode::GetPriority },
{&EV_AttractiveNode_SetPriority, &AttractiveNode::SetPriority },
{&EV_AttractiveNode_GetDistance, &AttractiveNode::GetDistance },
{&EV_AttractiveNode_SetDistance, &AttractiveNode::SetDistance },
{&EV_AttractiveNode_GetStayTime, &AttractiveNode::GetStayTime },
{&EV_AttractiveNode_SetStayTime, &AttractiveNode::SetStayTime },
{&EV_AttractiveNode_GetRespawnTime, &AttractiveNode::GetRespawnTime},
{&EV_AttractiveNode_SetRespawnTime, &AttractiveNode::SetRespawnTime},
{&EV_AttractiveNode_GetTeam, &AttractiveNode::GetTeam },
{&EV_AttractiveNode_SetTeam, &AttractiveNode::SetTeam },
{&EV_AttractiveNode_SetUse, &AttractiveNode::SetUse },
{NULL, NULL }
2016-03-27 11:49:47 +02:00
};
2023-10-22 21:06:43 +02:00
Container<AttractiveNode *> attractiveNodes;
2016-03-27 11:49:47 +02:00
AttractiveNode::AttractiveNode()
{
2023-10-22 21:06:43 +02:00
m_iPriority = 0; // set to default 0
m_fMaxStayTime = 0; // set to default 0, could be just a pickup
m_fMaxDistance = 1024;
m_fMaxDistanceSquared = m_fMaxDistance * m_fMaxDistance;
m_fRespawnTime = 15.0f; // set to default 15 seconds
m_bUse = false;
m_csTeam = STRING_EMPTY;
m_iTeam = TEAM_NONE;
2016-03-27 11:49:47 +02:00
2023-10-22 21:06:43 +02:00
attractiveNodes.AddObject(this);
2016-03-27 11:49:47 +02:00
}
AttractiveNode::~AttractiveNode()
{
2023-10-22 21:06:43 +02:00
attractiveNodes.RemoveObject(this);
2016-03-27 11:49:47 +02:00
}
2023-10-22 21:06:43 +02:00
bool AttractiveNode::CheckTeam(Sentient *sent)
2016-03-27 11:49:47 +02:00
{
2023-10-22 21:06:43 +02:00
if (!m_iTeam) {
return true;
}
2016-03-27 11:49:47 +02:00
2023-10-22 21:06:43 +02:00
if (sent->IsSubclassOfPlayer()) {
Player *p = (Player *)sent;
2016-03-27 11:49:47 +02:00
2023-10-22 21:06:43 +02:00
if ((m_iTeam == TEAM_FREEFORALL && g_gametype->integer >= GT_TEAM) || p->GetTeam() != m_iTeam) {
return false;
}
} else {
if (m_iTeam == TEAM_ALLIES && sent->m_Team != TEAM_AMERICAN) {
return false;
} else if (m_iTeam == TEAM_AXIS && sent->m_Team != TEAM_GERMAN) {
return false;
}
}
2016-03-27 11:49:47 +02:00
2023-10-22 21:06:43 +02:00
return true;
2016-03-27 11:49:47 +02:00
}
2023-10-22 21:06:43 +02:00
void AttractiveNode::setMaxDist(float dist)
2016-03-27 11:49:47 +02:00
{
2023-10-22 21:06:43 +02:00
m_fMaxDistance = dist;
2016-03-27 11:49:47 +02:00
2023-10-22 21:06:43 +02:00
if (dist < 0) {
m_fMaxDistanceSquared = -1;
} else {
m_fMaxDistanceSquared = dist * dist;
}
2016-03-27 11:49:47 +02:00
}
2023-10-22 21:06:43 +02:00
void AttractiveNode::GetPriority(Event *ev)
2016-03-27 11:49:47 +02:00
{
2023-10-22 21:06:43 +02:00
ev->AddInteger(m_iPriority);
2016-03-27 11:49:47 +02:00
}
2023-10-22 21:06:43 +02:00
void AttractiveNode::SetPriority(Event *ev)
2016-03-27 11:49:47 +02:00
{
2023-10-22 21:06:43 +02:00
m_iPriority = ev->GetInteger(1);
2016-03-27 11:49:47 +02:00
}
2023-10-22 21:06:43 +02:00
void AttractiveNode::GetDistance(Event *ev)
2016-03-27 11:49:47 +02:00
{
2023-10-22 21:06:43 +02:00
ev->AddFloat(m_fMaxDistance);
2016-03-27 11:49:47 +02:00
}
2023-10-22 21:06:43 +02:00
void AttractiveNode::SetDistance(Event *ev)
2016-03-27 11:49:47 +02:00
{
2023-10-22 21:06:43 +02:00
setMaxDist(ev->GetFloat(1));
2016-03-27 11:49:47 +02:00
}
2023-10-22 21:06:43 +02:00
void AttractiveNode::GetStayTime(Event *ev)
2016-03-27 11:49:47 +02:00
{
2023-10-22 21:06:43 +02:00
ev->AddFloat(m_fMaxStayTime);
2016-03-27 11:49:47 +02:00
}
2023-10-22 21:06:43 +02:00
void AttractiveNode::SetStayTime(Event *ev)
2016-03-27 11:49:47 +02:00
{
2023-10-22 21:06:43 +02:00
m_fMaxStayTime = ev->GetFloat(1);
2016-03-27 11:49:47 +02:00
}
2023-10-22 21:06:43 +02:00
void AttractiveNode::GetRespawnTime(Event *ev)
2016-03-27 11:49:47 +02:00
{
2023-10-22 21:06:43 +02:00
ev->AddFloat(m_fRespawnTime);
2016-03-27 11:49:47 +02:00
}
2023-10-22 21:06:43 +02:00
void AttractiveNode::SetRespawnTime(Event *ev)
2016-03-27 11:49:47 +02:00
{
2023-10-22 21:06:43 +02:00
m_fRespawnTime = ev->GetFloat(1);
if (m_fRespawnTime < 1.0f) {
m_fRespawnTime = 1.0f;
}
2016-03-27 11:49:47 +02:00
}
2023-10-22 21:06:43 +02:00
void AttractiveNode::GetTeam(Event *ev)
2016-03-27 11:49:47 +02:00
{
2023-10-22 21:06:43 +02:00
ev->AddConstString(m_csTeam);
2016-03-27 11:49:47 +02:00
}
2023-10-22 21:06:43 +02:00
void AttractiveNode::SetTeam(Event *ev)
2016-03-27 11:49:47 +02:00
{
2023-10-22 21:06:43 +02:00
if (ev->IsNilAt(1)) {
m_csTeam = STRING_EMPTY;
m_iTeam = TEAM_NONE;
return;
}
2016-03-27 11:49:47 +02:00
2023-10-22 21:06:43 +02:00
m_csTeam = ev->GetConstString(1);
2016-03-27 11:49:47 +02:00
2023-10-22 21:06:43 +02:00
switch (m_csTeam) {
case STRING_EMPTY:
m_iTeam = TEAM_NONE;
break;
case STRING_SPECTATOR:
m_iTeam = TEAM_SPECTATOR;
break;
case STRING_FREEFORALL:
m_iTeam = TEAM_FREEFORALL;
break;
case STRING_ALLIES:
case STRING_AMERICAN:
m_iTeam = TEAM_ALLIES;
break;
case STRING_AXIS:
case STRING_GERMAN:
m_iTeam = TEAM_AXIS;
break;
default:
m_iTeam = TEAM_NONE;
ScriptError("Invalid team %s\n", ev->GetString(1).c_str());
}
2016-03-27 11:49:47 +02:00
}
2023-10-22 21:06:43 +02:00
void AttractiveNode::SetUse(Event *ev)
2016-03-27 11:49:47 +02:00
{
2023-10-22 21:06:43 +02:00
m_bUse = ev->GetBoolean(1);
2016-03-27 11:49:47 +02:00
}