q2wf-portable/p_trail.c

2099 lines
48 KiB
C
Executable File

#include "g_local.h"
#include "p_trail.h"
#include "bot_procs.h"
#include "g_items.h"
//-------------------------------------------------------------------------
// Function declarations
void botButtonThink(edict_t *ent);
void WriteTrail(void);
void ReadTrail(void);
void FlagPath(edict_t *ent, int ctf_team);
void ED_CallSpawn(edict_t *ent);
//-------------------------------------------------------------------------
// Data declarations
const int TRAIL_VERSION = 13;
char *flag_path_src_classname = "flag_path_src";
char *misc_teleporter_classname = "misc_teleporter";
char *func_plat_classname = "func_plat";
char *func_button_classname = "func_button";
char *redflag_classname = "redflag";
char *blueflag_classname = "blueflag";
char *item_flag_team1_classname = "item_flag_team1";
char *item_flag_team2_classname = "item_flag_team2";
char *path_beam_classname = "path_beam";
char *player_duck_classname = "player_duck";
char *player_jump_classname = "player_jump";
#ifdef _WIN32
#include <direct.h>
#define PATH_SEP "\\"
#define mkdir(n, m) _mkdir(n)
#define chdir _chdir
#else
#include <unistd.h>
#include <sys/stat.h>
#define PATH_SEP "/"
#endif
const char *up_dir = "..";
const char *relative_dir = "." PATH_SEP;
const char *dir_sep = PATH_SEP;
const char *routes_dir = PATH_SEP "routes";
/*const*/ char *relative_routes_dir_sep = "." PATH_SEP "routes" PATH_SEP;
/*const*/ char *relative_routes_dir = "." PATH_SEP "routes";
const char *routes_dir_sep = PATH_SEP "routes" PATH_SEP;
const char *route_ext = ".rt3";
#ifdef WIN32
const char *route_ext_zipped = ".rtz";
const char *route_ext_zipped_old = "_rt.zipped";
const char *zip_ext = ".zip";
#endif
char *note_grapple_enabled = "............................\nMap contains grapple nodes..\nGrapple-Hook has been ENABLED\n............................\n";
char *debug_no_path_fmt = "Debug_ShowPathToGoal: no path to %s\n";
char *debug_path_length = "PathToEnt: %f units\n";
qboolean trail_active;
int dst;
int src;
float last_looped_warning;
float last_fix_break;
qboolean reading_trail;
float last_laser_route;
qboolean notify_force_grapple;
int trail_version;
int recursion_count;
int trail_portals[TRAIL_PORTAL_SUBDIVISION+1][TRAIL_PORTAL_SUBDIVISION+1][MAX_TRAILS_PER_PORTAL];
int num_trail_portals[TRAIL_PORTAL_SUBDIVISION+1][TRAIL_PORTAL_SUBDIVISION+1];
// These are the vars assumed to be available globally.
extern float last_optimize;
extern edict_t *weapons_head;
extern qboolean nodes_done;
extern qboolean loaded_trail_flag;
extern edict_t *the_client;
extern edict_t *health_head;
extern edict_t *PathToEnt_Node;
extern float last_trail_time;
extern ctf_item_t *ctf_item_head;
extern int last_head;
extern int num_players;
extern edict_t *ammo_head;
extern int trail_head;
extern edict_t *trail[TRAIL_LENGTH];
extern edict_t *PathToEnt_TargetNode;
extern edict_t *bonus_head;
extern edict_t *players[MAX_CLIENTS];
extern int dropped_trail;
extern level_locals_t level;
extern game_import_t gi;
extern edict_t *g_edicts;
extern cvar_t *bot_calc_nodes;
extern cvar_t *bot_debug_nodes;
extern cvar_t *bot_optimize;
extern cvar_t *ctf;
void PlayerTrail_Init(void)
{
for (int i = 0; i < TRAIL_LENGTH; ++i)
{
edict_t *e = trail[i] = gi.TagMalloc(sizeof(edict_t), TAG_LEVEL);
e->classname = "player_trail";
VectorSet(e->mins, -16.f, -16.0f, -24.0);
VectorSet(e->maxs, 16.f, 16.f, 32.f);
e->trail_index = i;
e->routes = gi.TagMalloc(sizeof(routes_t), TAG_LEVEL);
memset(e->paths, -1, sizeof(e->paths));
for (int k = 0; k < TRAIL_LENGTH; ++k)
e->routes->route_path[k] = -1;
}
memset(num_trail_portals, 0, sizeof(num_trail_portals));
trail_head = 0;
trail_active = 1;
dropped_trail = 0;
last_optimize = level.time;
ctf_item_head = NULL;
ReadTrail();
}
edict_t *matching_trail(vec3_t a1)
{
int v4 = GetGridPortal(a1[0]);
int v2 = GetGridPortal(a1[1]);
for (int i = 0; i < num_trail_portals[v4][v2]; ++i)
if (VectorCompare(a1, trail[trail_portals[v4][v2][i]]->s.origin))
return trail[trail_portals[v4][v2][i]];
return NULL;
}
void NodeDebug(char *fmt, ...)
{
if (!bot_debug_nodes->value || reading_trail)
return;
static char buffer[1024];
va_list args;
va_start(args, fmt);
vsnprintf(buffer, sizeof(buffer), fmt, args);
va_end(args);
gi.dprintf("%s", buffer);
}
void PlayerTrail_Add(edict_t *self, vec3_t spot, edict_t *goalent, int nocheck, int calc_routes, int node_type)
{
trace_t tr;
vec3_t end;
if (!trail_active)
return;
if (trail_head >= TRAIL_LENGTH - 1)
{
gi.dprintf("Reach trail limit, unable to create more nodes.\n");
gi.cvar_set("bot_calc_nodes", "0");
return;
}
if (nocheck && matching_trail(spot))
{
NodeDebug("Tried to drop duplicate node, aborted\n");
return;
}
edict_t *e = trail[trail_head];
NodeDebug("Dropping trail - %i\n", trail_head);
VectorCopy(spot, e->s.origin);
e->timestamp = level.time + 0.1;
e->s.angles[1] = self->s.angles[1];
if (self != g_edicts)
{
VectorCopy(self->mins, e->mins);
VectorCopy(self->maxs, e->maxs);
}
e->viewheight = ((e->maxs[2] - e->mins[2]) * 2.0 / 3.0) - 17.0;
e->waterlevel = self->waterlevel;
VectorCopy(self->velocity, e->velocity);
e->goalentity = goalent;
e->node_type = node_type;
VectorCopy(e->s.origin, end);
end[2] -= 8.f;
tr = gi.trace(e->s.origin, e->mins, e->maxs, end, e, MASK_PLAYERSOLID);
e->groundentity = tr.ent;
PlayerTrail_FindPaths(trail_head);
if (calc_routes)
CalcRoutes(trail_head);
FindVisibleItemsFromNode(e);
AddTrailToPortals(e);
self->last_trail_dropped = e;
dropped_trail = 1;
last_head = trail_head++;
}
void PlayerTrail_New(vec3_t spot)
{
if (trail_active)
{
PlayerTrail_Init();
PlayerTrail_Add(g_edicts, spot, NULL, 0, 1, NODE_NORMAL);
}
}
edict_t *PlayerTrail_PickFirst(edict_t *self)
{
if (!trail_active)
return NULL;
int t = trail_head;
for (int i = TRAIL_LENGTH; i && trail[t]->timestamp <= self->monsterinfo.trail_time; --i)
t = (t + 1) & (TRAIL_LENGTH - 1);
if (!visible(self, trail[t]))
if (visible(self, trail[(t - 1) & (TRAIL_LENGTH - 1)]))
return trail[(t - 1) & (TRAIL_LENGTH - 1)];
return trail[t];
}
edict_t *PlayerTrail_PickNext(edict_t *self)
{
if (!trail_active)
return NULL;
int t = trail_head;
for (int i = TRAIL_LENGTH; i && trail[t]->timestamp <= self->monsterinfo.trail_time; --i)
t = (t + 1) & (TRAIL_LENGTH - 1);
return trail[t];
}
edict_t *PlayerTrail_LastSpot(void)
{
return trail[last_head];
}
int GetGridPortal(float pos)
{
if (pos > MAX_MAP_AXIS)
pos = MAX_MAP_AXIS - 1;
else if (pos < -MAX_MAP_AXIS)
pos = -(MAX_MAP_AXIS - 1);
return (int) floor(pos / MAX_MAP_AXIS * 12.0 + 12.0 + 0.5);
}
void AddTrailToPortals(edict_t *trail)
{
int x = GetGridPortal(trail->s.origin[0]);
int y = GetGridPortal(trail->s.origin[1]);
int v1, v2;
trail_portals[x][y][num_trail_portals[x][y]++] = trail->trail_index;
if (x >= trail->s.origin[0] / MAX_MAP_AXIS * 12.0 + 12.0)
{
v1 = -1;
if (x - 1 < 0)
v1 = 0;
else
trail_portals[x - 1][y][num_trail_portals[x - 1][y]++] = trail->trail_index;
}
else
{
v1 = 1;
if (x + 1 >= TRAIL_PORTAL_SUBDIVISION)
v1 = 0;
else
trail_portals[x + 1][y][num_trail_portals[x + 1][y]++] = trail->trail_index;
}
if (y >= trail->s.origin[1] / MAX_MAP_AXIS * 12.0 + 12.0)
{
v2 = -1;
if (y - 1 < 0)
v2 = 0;
else
trail_portals[x - 1][y + TRAIL_PORTAL_SUBDIVISION][num_trail_portals[x - 1][y + TRAIL_PORTAL_SUBDIVISION]++] = trail->trail_index;
}
else
{
v2 = 1;
if (y + 1 >= TRAIL_PORTAL_SUBDIVISION)
v2 = 0;
else
trail_portals[x][y + 1][num_trail_portals[x][y + 1]++] = trail->trail_index;
}
if (!v1 || !v2)
return;
int num = num_trail_portals[v1][(TRAIL_PORTAL_SUBDIVISION + 1) * x + v2 + y]++;
trail_portals[x][(TRAIL_PORTAL_SUBDIVISION + 1) * v1][MAX_TRAILS_PER_PORTAL * v2 + MAX_TRAILS_PER_PORTAL * y + num] = trail->trail_index;
}
void PlayerTrail_FindPaths(int marker)
{
int path_index = 0;
edict_t *ent1 = trail[marker];
memset(ent1->paths, -1, sizeof(ent1->paths));
if (ent1->goalentity)
{
ent1->paths[0] = ent1->goalentity->trail_index;
path_index = 1;
}
int x = GetGridPortal(ent1->s.origin[0]);
int y = GetGridPortal(ent1->s.origin[1]);
int num_trails = num_trail_portals[x][y];
NodeDebug("num_trails - %i\n", num_trails);
for (int j = 0; j < num_trails; ++j)
{
int trail_index = trail_portals[x][y][j];
edict_t *ent2 = trail[trail_index];
if (trail_index != marker
&& (!ent1->goalentity || ent1->goalentity->trail_index != trail_index)
&& (!ent2->goalentity || ent2->goalentity->trail_index != marker)
&& (!ent1->goalentity && !ent2->goalentity && ent2->node_type != NODE_LANDING
|| fabs(ent1->s.origin[2] - ent2->s.origin[2]) <= 48.0)
&& entdist(ent1, ent2) <= 512.0
&& visible_box(ent2, ent1)
&& CanReach(ent2, ent1))
{
if (ent2->node_type != NODE_LANDING && ent2->timestamp != ent1->timestamp)
ent1->paths[path_index++] = trail_index;
if (ent1->node_type != NODE_LANDING)
{
int k;
for (k = 0; k < MAX_PATHS && ent2->paths[k] >= 0; ++k)
;
if (k < MAX_PATHS)
ent2->paths[k] = marker;
}
if (path_index >= MAX_PATHS)
break;
}
}
NodeDebug("FindPaths: %i paths found\n", path_index);
}
int ClosestNodeToEnt(edict_t *self, int check_fullbox, int check_all_nodes)
{
int best_node = -1;
float best_dist = 999999.0;
if (self->item || self->touch == FlagPathTouch)
{
if (self->target_ent)
return self->target_ent->trail_index;
for (int i = 0; i < MAX_PATHS && self->paths[i] != -1; ++i)
{
float dist = entdist(self, trail[self->paths[i]]);
if (dist < best_dist)
{
best_dist = dist;
best_node = self->paths[i];
}
}
if (best_node > -1)
{
self->target_ent = trail[best_node];
return best_node;
}
}
if ((!bot_calc_nodes->value || self->bot_client) && level.time - 0.2 < self->last_closest_time)
return self->last_closest_node;
self->last_closest_node = -1;
if (!(bot_calc_nodes->value && self->client && !self->bot_client || check_all_nodes))
{
if (level.time - 0.2 < self->closest_trail_time)
{
self->last_closest_node = self->closest_trail;
self->last_closest_time = level.time;
return self->closest_trail;
}
else if (self->closest_trail > -1
&& trail[self->closest_trail]->ignore_time <= level.time
&& trail[self->closest_trail]->waterlevel == self->waterlevel
&& (entdist(self, trail[self->closest_trail]) <= 128.0)
&& gi.trace(
self->s.origin,
self->mins,
self->maxs,
trail[self->closest_trail]->s.origin,
self,
MASK_PLAYERSOLID).fraction >= 1.0)
{
self->last_closest_node = self->closest_trail;
self->last_closest_time = level.time;
self->closest_trail_time = level.time;
return self->closest_trail;
}
}
self->closest_trail = -1;
self->closest_trail_time = 0.0;
int x = GetGridPortal(self->s.origin[0]);
int y = GetGridPortal(self->s.origin[1]);
int num_portals = num_trail_portals[x][y];
int portal_index = 0;
if (self->bot_client && num_portals > 30)
{
portal_index = random() * (num_portals - 30);
num_portals = portal_index + 30;
}
while (portal_index < num_portals)
{
int node_index = trail_portals[x][y][portal_index];
edict_t *ent1 = trail[node_index];
if (ent1 != self && ent1->ignore_time <= level.time)
{
float dist = entdist(self, ent1);
if (dist <= best_dist && (check_fullbox || visible_box(ent1, self)) && (!check_fullbox || visible_fullbox(ent1, self)))
{
if (CanReach(self, ent1))
{
best_node = node_index;
best_dist = dist;
if (dist < 64.0)
break;
}
}
}
++portal_index;
}
if (best_node > -1 && self->item)
self->target_ent = trail[best_node];
if (self->client)
{
self->closest_trail = best_node;
self->closest_trail_time = level.time;
self->last_closest_node = best_node;
self->last_closest_time = level.time;
}
return best_node;
}
float PathToEnt(edict_t *self, edict_t *target, int check_fullbox, int check_all_nodes)
{
if (!target)
return -1.0;
qboolean self_has_routes = self->routes != NULL;
qboolean target_has_routes = target->routes != NULL;
if (target->routes)
target_has_routes = true;
else if (entdist(self, target) < 400.0
&& (check_fullbox && visible_fullbox(self, target) || !check_fullbox && visible_box(self, target))
&& CanReach(self, target)
&& (!bot_calc_nodes->value || !target->client || target->bot_client || entdist(self, target) < 64.0))
{
PathToEnt_TargetNode = target;
PathToEnt_Node = target;
return entdist(self, target);
}
int trail_index = -1;
if (target_has_routes)
trail_index = target->trail_index;
if (trail_index < 0)
{
trail_index = ClosestNodeToEnt(target, check_fullbox, 0);
if (trail_index == -1)
return -1.0;
}
if (self_has_routes && self->routes->route_path[trail_index] > -1)
{
PathToEnt_Node = trail[self->routes->route_path[trail_index]];
PathToEnt_TargetNode = trail[trail_index];
unsigned short dist = self->routes->route_dist[trail_index];
return entdist(self, trail[trail_index]) + dist;
}
int closest_node = ClosestNodeToEnt(self, check_fullbox, 1);
if (closest_node == -1 ||
trail[closest_node]->routes->route_path[trail_index] == -1)
return -1.0;
unsigned short v8 = trail[closest_node]->routes->route_dist[trail_index];
PathToEnt_Node = trail[closest_node];
PathToEnt_TargetNode = trail[trail_index];
return v8;
}
void CalcRoutes(int node_index)
{
char Str[1024] = { 0 };
for (int i = 0; i < TRAIL_LENGTH && trail[i]->timestamp != 0.0; ++i)
{
edict_t *trail_ni = trail[node_index];
edict_t *trail_i = trail[i];
if (i != node_index)
{
edict_t *ent1 = NULL;
unsigned short best_dist;
for (int j = 0; j < MAX_PATHS && trail_ni->paths[j] != -1; ++j)
{
vec3_t vec;
if (trail_ni->paths[j] == i)
{
VectorSubtract(trail_i->s.origin, trail_ni->s.origin, vec);
ent1 = trail_i;
best_dist = VectorLength(vec);
break;
}
if (trail[trail_ni->paths[j]]->routes->route_path[i] != -1)
{
VectorSubtract(trail[trail_ni->paths[j]]->s.origin, trail_ni->s.origin, vec);
short dist = VectorLength(vec) + trail[trail_ni->paths[j]]->routes->route_dist[i];
if (ent1)
{
if (dist < best_dist)
{
ent1 = trail[trail_ni->paths[j]];
best_dist = dist;
}
}
else
{
ent1 = trail[trail_ni->paths[j]];
best_dist = dist;
}
}
}
routes_t *ni_routes = trail_ni->routes;
routes_t *i_routes = trail_i->routes;
if (ent1)
{
qboolean found = false;
ni_routes->route_path[i] = ent1->trail_index;
ni_routes->route_dist[i] = best_dist;
if (i == ent1->trail_index && ent1->paths[0] > -1)
{
for (int j = 0; j < MAX_PATHS && ent1->paths[j] != -1; ++j)
{
if (trail[ent1->paths[j]]->trail_index == node_index)
{
found = true;
break;
}
}
}
if (found)
{
i_routes->route_path[node_index] = node_index;
i_routes->route_dist[node_index] = best_dist;
}
else if (i_routes->route_path[ent1->trail_index] <= -1)
{
i_routes->route_path[node_index] = -1;
i_routes->route_dist[node_index] = -2536;
}
else
{
i_routes->route_path[node_index] = i_routes->route_path[ent1->trail_index];
i_routes->route_dist[node_index] = entdist(ent1, trail_i) + i_routes->route_dist[ent1->trail_index];
}
}
else
{
ni_routes->route_path[i] = -1;
ni_routes->route_dist[i] = -2536;
if (bot_debug_nodes->value)
{
if (strlen(Str) >= 0x100)
{
if (Str[strlen(Str)] != '.')
strcat(Str, "..");
}
else
strcat(Str, va("%i ", i));
}
}
}
}
if (strlen(Str))
{
NodeDebug("Node %i has no route to nodes..\n(%s)\n", node_index, Str);
memset(Str, 0, sizeof(Str));
}
}
void OptimizeRouteCache(void)
{
int max_it;
int optimize_num = bot_optimize->value;
if (num_players <= 1)
max_it = 8 * optimize_num;
else
max_it = optimize_num / num_players;
if (!bot_calc_nodes->value)
max_it /= 3;
if (max_it < 100)
max_it = 100;
int total_it = 0;
while (src < TRAIL_LENGTH)
{
if (trail[src]->timestamp == 0.0)
{
src = 0;
dst = 0;
break;
}
if (total_it++ > max_it)
return;
while (dst < TRAIL_LENGTH)
{
if (trail[dst]->timestamp == 0.0)
{
dst = 0;
break;
}
if (trail[src]->routes->route_path[dst] != dst)
{
if (trail[src]->routes->route_path[dst] > -1)
{
short path_index = trail[trail[src]->routes->route_path[dst]]->routes->route_path[dst];
if (path_index == src)
{
if (trail[trail[src]->routes->route_path[dst]]->goalentity != trail[src])
trail[trail[src]->routes->route_path[dst]]->routes->route_path[dst] = -1;
if (trail[src]->goalentity != trail[trail[src]->routes->route_path[dst]])
trail[src]->routes->route_path[dst] = -1;
if (level.time - 0.1 > last_looped_warning)
last_looped_warning = level.time;
}
else if (path_index == -1)
{
trail[src]->routes->route_path[dst] = -1;
last_fix_break = level.time;
if (bot_debug_nodes->value && the_client && num_players <= 1 && level.time - 0.1 > last_optimize)
{
gi.WriteByte(svc_layout);
gi.WriteString("Fixing routes..");
gi.unicast(the_client, true);
last_optimize = level.time;
}
}
}
if (total_it++ > max_it)
return;
if (dst != src && level.time - 2.0 >= last_fix_break && level.time >= 7.0)
{
for (int i = 0; i < MAX_PATHS && trail[src]->paths[i] != -1; ++i)
{
if (trail[src]->paths[i] == dst
|| trail[trail[src]->paths[i]]->routes->route_path[dst] != -1
&& trail[trail[src]->paths[i]]->routes->route_path[dst] != src)
{
++total_it;
float dist = entdist(trail[src], trail[trail[src]->paths[i]]);
if (trail[src]->paths[i] != dst)
dist = trail[trail[src]->paths[i]]->routes->route_dist[dst] + dist;
if (trail[src]->routes->route_path[dst] == -1
|| trail[src]->routes->route_dist[dst] > dist + 32.0)
{
if (bot_debug_nodes->value && the_client && num_players <= 1 && level.time - 0.1 > last_optimize)
{
char layout[1024];
Com_sprintf(layout, 1024,
"xv 10 yv 180 string \"Optimizing node route %i -> %i\" ",
src, dst);
gi.WriteByte(svc_layout);
gi.WriteString(layout);
gi.unicast(the_client, true);
last_optimize = level.time;
}
trail[src]->routes->route_path[dst] = trail[src]->paths[i];
trail[src]->routes->route_dist[dst] = dist;
dropped_trail = 1;
}
}
}
}
}
++dst;
}
if (dst == TRAIL_LENGTH)
dst = 0;
++src;
}
if (src == TRAIL_LENGTH)
src = 0;
}
edict_t *WriteTrailNode(FILE *file, edict_t *node)
{
if (node->ignore)
return node;
fwrite(&node->trail_index, 4u, 1u, file);
if (node->goalentity)
fwrite(&node->goalentity->trail_index, 4u, 1u, file);
else
{
int goal = -1;
fwrite(&goal, 4u, 1u, file);
}
fwrite(node->s.origin, sizeof(node->s.origin), 1u, file);
fwrite(&node->timestamp, sizeof(node->timestamp), 1u, file);
fwrite(node->s.angles, sizeof(node->s.angles), 1u, file);
fwrite(node->mins, sizeof(node->mins), 1u, file);
fwrite(node->maxs, sizeof(node->maxs), 1u, file);
fwrite(node->velocity, sizeof(node->velocity), 1u, file);
fwrite(&node->waterlevel, sizeof(node->waterlevel), 1u, file);
fwrite(&node->node_type, sizeof(node->node_type), 1u, file);
short b = node->paths[0] & 0xFFFF;
char path_id;
for (path_id = 0; path_id < MAX_PATHS && b > -1; ++path_id)
{
b = node->paths[path_id] & 0xFFFF;
fwrite(&b, 2u, 1u, file);
}
b = -2;
fwrite(&b, 2u, 1u, file);
for (int node_id = 0; node_id < TRAIL_LENGTH && trail[node_id]->timestamp != 0.0; ++node_id)
{
if (node->routes->route_path[node_id] >= 0)
{
for (path_id = 0; path_id < MAX_PATHS && node->paths[path_id] != node->routes->route_path[node_id]; ++path_id)
{
if (node->paths[path_id] < 0 || path_id == MAX_PATHS)
{
path_id = -1;
gi.dprintf("ERROR: Unable to locate goal node in ->paths[]\n");
break;
}
}
}
else
path_id = -1;
fwrite(&path_id, sizeof(path_id), 1u, file);
}
path_id = -99;
fwrite(&path_id, sizeof(path_id), 1u, file);
node->ignore = 1;
return node;
}
void WriteTrail(void)
{
if (!(((bot_calc_nodes && bot_calc_nodes->value) || dropped_trail) && trail_head > 30))
return;
cvar_t *game = gi.cvar("game", "", 0);
cvar_t *basedir = gi.cvar("basedir", "", 0);
chdir(basedir->string);
char Destination[256];
strcpy(Destination, relative_dir);
strcat(Destination, game->string);
strcat(Destination, routes_dir);
if (chdir(Destination) == -1)
{
if (mkdir(Destination, 0x1FF))
{
gi.dprintf("ERROR: Unable to create route-table directory\nRoute-table not saved.\n");
return;
}
}
else
{
chdir(up_dir);
chdir(up_dir);
}
strcat(Destination, dir_sep);
strcat(Destination, level.mapname);
strcat(Destination, route_ext);
FILE *Stream = fopen(Destination, "wb");
if (!Stream)
gi.error("Couldn't open %s", Destination);
fwrite(&TRAIL_VERSION, 4u, 1u, Stream);
edict_t *from = G_Find(NULL, FOFS(classname), flag_path_src_classname);
while (from)
{
if (!from->last_goal || !from->target_ent)
continue;
fwrite(&from->skill_level, sizeof(from->skill_level), 1u, Stream);
fwrite(from->s.origin, sizeof(from->s.origin), 1u, Stream);
fwrite(from->last_goal->s.origin, sizeof(from->last_goal->s.origin), 1u, Stream);
fwrite(from->target_ent->s.origin, sizeof(from->target_ent->s.origin), 1u, Stream);
from = G_Find(from, FOFS(classname), flag_path_src_classname);
}
int end_marker = -1;
fwrite(&end_marker, 4u, 1u, Stream);
for (from = G_Find(NULL, FOFS(classname), misc_teleporter_classname); from; from = G_Find(from, FOFS(classname), misc_teleporter_classname))
fwrite(&from->ignore_time, 4u, 1u, Stream);
end_marker = -99;
fwrite(&end_marker, 4u, 1u, Stream);
for (from = G_Find(NULL, FOFS(classname), func_plat_classname); from; from = G_Find(from, FOFS(classname), func_plat_classname))
fwrite(&from->ignore_time, 4u, 1u, Stream);
end_marker = -99;
fwrite(&end_marker, 4u, 1u, Stream);
for (from = G_Find(NULL, FOFS(classname), func_button_classname); from; from = G_Find(from, FOFS(classname), func_button_classname))
fwrite(&from->ignore_time, 4u, 1u, Stream);
end_marker = -99;
fwrite(&end_marker, 4u, 1u, Stream);
from = G_Find(NULL, FOFS(classname), redflag_classname);
if (from)
{
int ctf_team = 1;
fwrite(&ctf_team, sizeof(ctf_team), 1u, Stream);
fwrite(from->s.origin, sizeof(from->s.origin), 1u, Stream);
fwrite(from->s.angles, sizeof(from->s.angles), 1u, Stream);
}
from = G_Find(NULL, FOFS(classname), blueflag_classname);
if (from)
{
int ctf_team = 1;
fwrite(&ctf_team, sizeof(ctf_team), 1u, Stream);
fwrite(from->s.origin, sizeof(from->s.origin), 1u, Stream);
fwrite(from->s.angles, sizeof(from->s.angles), 1u, Stream);
}
end_marker = -1;
fwrite(&end_marker, 4u, 1u, Stream);
for (ctf_item_t *item = ctf_item_head; item; item = item->next)
{
int has_ctf_item = 1;
fwrite(&has_ctf_item, 4u, 1u, Stream);
size_t v2 = strlen(item->classname);
fwrite(item->classname, v2 + 1, 1u, Stream);
fwrite(item->origin, sizeof(item->origin), 1u, Stream);
fwrite(item->angles, sizeof(item->angles), 1u, Stream);
}
end_marker = -1;
fwrite(&end_marker, 4u, 1u, Stream);
for (int i = 0; i < TRAIL_LENGTH; i++)
trail[i]->ignore = 0;
for (int i = 0; i < TRAIL_LENGTH && trail[i]->timestamp != 0.0; ++i)
{
if (trail[i]->goalentity)
{
from = trail[i]->goalentity;
WriteTrailNode(Stream, from);
}
}
for (int i = 0; i < TRAIL_LENGTH; ++i)
{
if (trail[i]->timestamp != 0.0)
{
from = trail[i];
WriteTrailNode(Stream, from);
}
}
fclose(Stream);
/*#ifdef _WIN32 // should this be in here???
strcpy(Destination, relative_dir);
strcat(Destination, game->string);
chdir(Destination);
strcpy(Destination, relative_routes_dir_sep);
strcat(Destination, level.mapname);
strcat(Destination, route_ext);
char dst[256];
strcpy(dst, relative_routes_dir_sep);
strcat(dst, level.mapname);
strcat(dst, route_ext_zipped);
if (G_ZipFile(dst, Destination))
{
remove(Destination);
dst[strlen(dst) - 4] = 0;
strcat(dst, route_ext_zipped_old);
remove(dst);
dst[strlen(dst) - 3] = 0;
remove(dst);
}
else
gi.dprintf("\nError creating ZIP file.\nUnable to compress node-table.\n\n");
#endif*/
chdir(up_dir);
gi.dprintf("Route-table saved.\n");
}
int CalculateRouteDistance(edict_t *src, edict_t *dst)
{
if (src->routes->route_dist[dst->trail_index])
return src->routes->route_dist[dst->trail_index];
if (src->routes->route_path[dst->trail_index] >= 0)
{
int recursion = recursion_count++;
if (recursion <= trail_head)
{
int dist = CalculateRouteDistance(trail[src->routes->route_path[dst->trail_index]], dst);
if (dist <= 0)
{
src->routes->route_path[dst->trail_index] = -1;
return -1;
}
if (dist > 60000)
dist = 60000;
src->routes->route_dist[dst->trail_index] = dist + src->routes->route_dist[src->routes->route_path[dst->trail_index]];
return src->routes->route_dist[dst->trail_index];
}
if (bot_debug_nodes->value)
gi.dprintf("%s: Recursive link (src=%i, dst=%i)\n", __func__, src->trail_index, dst->trail_index);
src->routes->route_path[dst->trail_index] = -1;
return -1;
}
if (bot_debug_nodes->value)
gi.dprintf("%s: Broken chain (src=%i, dst=%i)\n", __func__, src->trail_index, dst->trail_index);
return -1;
}
void CalculateDistances(void)
{
if (!trail_head)
return;
for (int i = 0; i < trail_head; ++i)
{
edict_t *node = trail[i];
for (int j = 0; j < MAX_PATHS && node->paths[j] >= 0; ++j)
{
if (node->node_type == NODE_TELEPORT)
node->routes->route_dist[node->paths[j]] = 1;
else
node->routes->route_dist[node->paths[j]] = entdist(node, trail[node->paths[j]]);
if (!node->routes->route_dist[node->paths[j]])
node->routes->route_dist[node->paths[j]] = 1;
}
}
for (int i = 0; i < trail_head; ++i)
{
edict_t *node = trail[i];
for (int j = 0; j < trail_head; ++j)
{
if (!node->routes->route_dist[j] && node->routes->route_path[j] >= 0)
{
recursion_count = 0;
CalculateRouteDistance(node, trail[j]);
}
}
}
}
FILE *ReadTrailNode(FILE *file, edict_t *node)
{
int goal;
fread(&goal, 4u, 1u, file);
if (goal > -1)
node->goalentity = trail[goal];
fread(node->s.origin, sizeof(node->s.origin), 1u, file);
fread(&node->timestamp, sizeof(node->timestamp), 1u, file);
fread(node->s.angles, sizeof(node->s.angles), 1u, file);
fread(node->mins, sizeof(node->mins), 1u, file);
fread(node->maxs, sizeof(node->maxs), 1u, file);
fread(node->velocity, sizeof(node->velocity), 1u, file);
if (trail_version > 1)
{
fread(&node->waterlevel, 4u, 1u, file);
fread(&node->node_type, 1u, 1u, file);
}
short buf = 0;
for (int i = 0; i < MAX_PATHS; ++i)
{
if (buf != -2)
fread(&buf, 2u, 1u, file);
if (buf == -2)
node->paths[i] = -1;
else
node->paths[i] = buf;
}
while (buf != -2)
{
fread(&buf, 2u, 1u, file);
if (feof(file))
{
gi.error("Unexpected end of route-table\n");
return NULL;
}
}
char path_id;
int i;
for (i = 0; i < TRAIL_LENGTH; ++i)
{
if (trail_version >= 10)
{
fread(&path_id, 1u, 1u, file);
if (path_id == -99)
{
node->routes->route_path[i] = -1;
node->routes->route_dist[i] = 0;
break;
}
if (path_id <= -1)
node->routes->route_path[i] = -1;
else
{
node->routes->route_path[i] = node->paths[path_id];
if (node->paths[path_id] == -1 && bot_debug_nodes->value)
gi.dprintf("Node reference turned out to be empty\n");
}
}
else
{
fread(&buf, 2u, 1u, file);
if (buf == -99)
{
node->routes->route_path[i] = -1;
node->routes->route_dist[i] = 0;
break;
}
for (path_id = 0; path_id < MAX_PATHS && node->paths[path_id] != buf; ++path_id)
;
if (path_id == MAX_PATHS)
node->routes->route_path[i] = -1;
else
node->routes->route_path[i] = buf;
// Unused byte from old file format?
fread(&buf, 1u, 1u, file);
}
node->routes->route_dist[i] = 0;
}
if (trail_version >= 10)
{
while (path_id != -99)
{
fread(&path_id, 1u, 1u, file);
if (feof(file))
return NULL;
}
}
else
{
while (buf != -99)
{
fread(&buf, 2u, 1u, file);
if (feof(file))
return NULL;
}
}
while (i < TRAIL_LENGTH)
{
node->routes->route_path[i] = -1;
node->routes->route_dist[i++] = 0;
}
AddTrailToPortals(node);
vec3_t end;
VectorCopy(node->s.origin, end);
end[2] -= 8.0;
trace_t tr = gi.trace(node->s.origin, node->mins, node->maxs, end, node, MASK_PLAYERSOLID);
if (tr.fraction >= 1.0)
node->groundentity = NULL;
else
node->groundentity = tr.ent;
if (node->node_type == NODE_BUTTON)
{
edict_t *buttonActivator = G_Spawn();
buttonActivator->owner = node;
buttonActivator->think = botButtonThink;
buttonActivator->nextthink = level.time + 2.0;
VectorMA(node->s.origin, -24.0, node->movedir, buttonActivator->s.origin);
}
else if (node->node_type == NODE_GRAPPLE)
{
gi.cvar_forceset("grapple", "1");
if (!ctf->value && notify_force_grapple && !G_Find(NULL, FOFS(classname), item_flag_team1_classname))
{
gi.dprintf("............................\nMap contains grapple nodes..\nGrapple-Hook has been ENABLED\n............................\n");
notify_force_grapple = false;
}
}
return file;
}
void ReadTrail(void)
{
#ifdef _WIN32
qboolean zipped = false;
#endif
qboolean unknown_flag = true;
cvar_t *game = gi.cvar("game", "", 0);
cvar_t *basedir = gi.cvar("basedir", "", 0);
chdir(basedir->string);
char Dir[256] = { 0 };
strcpy(Dir, relative_dir);
strcat(Dir, game->string);
chdir(Dir);
FILE *Stream;
/*#ifdef _WIN32 // should this be in here???
char FileName[256] = { 0 };
strcpy(FileName, relative_routes_dir_sep);
strcat(FileName, level.mapname);
strcat(FileName, route_ext_zipped);
Stream = fopen(FileName, "rb");
if (!Stream)
{
strcpy(FileName, relative_routes_dir_sep);
strcat(FileName, level.mapname);
strcat(FileName, route_ext_zipped_old);
Stream = fopen(FileName, "rb");
}
if (Stream)
{
fclose(Stream);
if (G_UnzipFile(FileName, relative_routes_dir))
zipped = true;
}
#endif*/
chdir(up_dir);
char dst[256] = { 0 };
strcpy(dst, Dir);
strcat(dst, routes_dir_sep);
strcat(dst, level.mapname);
strcat(dst, route_ext);
Stream = fopen(dst, "rb");
if (!Stream)
{
if (!bot_calc_nodes->value)
{
gi.cvar_set("bot_calc_nodes", "1");
gi.dprintf("Unable to load route-table file.\nDynamic node-table generation ENABLED.\n");
}
return;
}
reading_trail = 1;
fread(&trail_version, 4u, 1u, Stream);
if (trail_version < 6)
{
gi.dprintf("Route-table incompatible (v%i), ignoring\n", trail_version);
#ifdef _WIN32
if (zipped)
remove(dst);
#endif
fclose(Stream);
return;
}
if (trail_version > 6)
{
int ctf_team;
fread(&ctf_team, 4u, 1u, Stream);
while (ctf_team > -1)
{
static edict_t temp;
temp.client = NULL;
fread(temp.s.origin, sizeof(temp.s.origin), 1u, Stream);
FlagPath(&temp, ctf_team);
fread(temp.s.origin, sizeof(temp.s.origin), 1u, Stream);
FlagPath(&temp, ctf_team);
fread(temp.s.origin, sizeof(temp.s.origin), 1u, Stream);
FlagPath(&temp, ctf_team);
fread(&ctf_team, sizeof(ctf_team), 1u, Stream);
}
}
if (trail_version >= 4)
{
edict_t *from = G_Find(NULL, FOFS(classname), misc_teleporter_classname);
int ignore_time;
fread(&ignore_time, 4u, 1u, Stream);
while (ignore_time >= 0)
{
if (from)
from->ignore_time = ignore_time;
else
gi.dprintf("Couldn't find teleporter\n");
from = G_Find(from, FOFS(classname), misc_teleporter_classname);
fread(&ignore_time, 4u, 1u, Stream);
}
from = G_Find(NULL, FOFS(classname), func_plat_classname);
fread(&ignore_time, 4u, 1u, Stream);
while (ignore_time >= 0)
{
if (from)
from->ignore_time = ignore_time;
else
gi.dprintf("Couldn't find func_plat\n");
from = G_Find(from, FOFS(classname), func_plat_classname);
fread(&ignore_time, 4u, 1u, Stream);
}
}
if (trail_version > 12)
{
edict_t *from = G_Find(NULL, FOFS(classname), func_button_classname);
int ignore_time;
fread(&ignore_time, 4u, 1u, Stream);
while (ignore_time >= 0)
{
if (from)
from->ignore_time = ignore_time;
else
gi.dprintf("Couldn't find func_button\n");
from = G_Find(from, FOFS(classname), func_button_classname);
fread(&ignore_time, 4u, 1u, Stream);
}
}
if (trail_version > 10)
{
while (1)
{
int ctf_team;
fread(&ctf_team, 4u, 1u, Stream);
if (ctf_team <= -1)
break;
vec3_t origin, angles;
fread(origin, sizeof(origin), 1u, Stream);
fread(angles, sizeof(angles), 1u, Stream);
if (!ctf->value)
continue;
edict_t *flag = G_Spawn();
if (ctf_team == 1)
flag->classname = item_flag_team1_classname;
else
flag->classname = item_flag_team2_classname;
VectorCopy(origin, flag->s.origin);
VectorCopy(angles, flag->s.angles);
ED_CallSpawn(flag);
}
}
if (trail_version > 11)
{
ctf_item_head = NULL;
int ctf_team;
fread(&ctf_team, 4u, 1u, Stream);
while (ctf_team > -1)
{
ctf_item_t *item = gi.TagMalloc(sizeof(ctf_item_t), TAG_LEVEL);
memset(item, 0, sizeof(ctf_item_t));
int classname_len = -1;
do {
fread(&item->classname[++classname_len], 1u, 1u, Stream);
} while (item->classname[classname_len]);
fread(item->origin, sizeof(item->origin), 1u, Stream);
fread(item->angles, sizeof(item->angles), 1u, Stream);
if (!ctf->value)
{
ctf_item_head = NULL;
gi.TagFree(item);
}
else
{
item->next = ctf_item_head;
ctf_item_head = item;
edict_t *ctf_item = G_Spawn();
ctf_item->classname = ctf_item_head->classname;
VectorCopy(ctf_item_head->origin, ctf_item->s.origin);
VectorCopy(ctf_item_head->angles, ctf_item->s.angles);
ED_CallSpawn(ctf_item);
}
fread(&ctf_team, 4u, 1u, Stream);
}
}
int nodes_loaded = 0;
notify_force_grapple = 1;
while (!feof(Stream))
{
int node_id;
if (!fread(&node_id, 4u, 1u, Stream))
break;
edict_t *from = trail[node_id];
Stream = ReadTrailNode(Stream, from);
if (!Stream)
{
remove(dst);
/*#ifdef _WIN32 // should this be in here??
if (zipped)
{
strcpy(FileName, Dir);
strcat(FileName, routes_dir_sep);
strcat(FileName, level.mapname);
strcat(FileName, zip_ext);
remove(FileName);
}
#endif*/
gi.error("End of paths flag not found.\nRoute-table is corrupt, deleting.\n");
return;
}
if (from->node_type == NODE_PLAT)
{
float best_dist = 99999.0;
from->target_ent = NULL;
edict_t *plat = NULL;
while (1)
{
plat = G_Find(plat, FOFS(classname), func_plat_classname);
if (!plat)
break;
vec3_t vec;
VectorSubtract(from->s.origin, plat->mins, vec);
float dist = VectorLength(vec);
if (dist < best_dist)
{
from->target_ent = plat;
best_dist = dist;
}
}
}
else if (unknown_flag)
{
if (from->trail_index)
from->node_type = NODE_LANDING;
else
unknown_flag = false;
}
++nodes_loaded;
if (from->paths[0] == -1)
PlayerTrail_FindPaths(node_id);
if (nodes_loaded >= TRAIL_LENGTH)
break;
}
gi.dprintf("Route-table loaded, %i nodes updated\n", nodes_loaded);
nodes_done = nodes_loaded > 40;
fclose(Stream);
#ifdef _WIN32
if (zipped)
remove(dst);
#endif
last_head = nodes_loaded - 1;
trail_head = nodes_loaded;
for (edict_t *from = G_Find(NULL, FOFS(classname), misc_teleporter_classname); from; from = G_Find(from, FOFS(classname), misc_teleporter_classname))
{
if (!from->ignore_time)
continue;
edict_t *best_node = NULL;
float best_dist = 99999.0;
for (int node_id = 0; node_id < nodes_loaded; ++node_id)
{
float dist = entdist(from, trail[node_id]);
if (dist >= 64.0 || dist >= best_dist)
continue;
best_node = trail[node_id];
best_dist = dist;
}
if (best_node)
best_node->node_type = NODE_TELEPORT;
}
CalculateDistances();
loaded_trail_flag = true;
reading_trail = false;
}
void Debug_ShowPathToGoal(edict_t *self, edict_t *goalent)
{
if (!(level.time - 0.5 >= last_laser_route))
return;
last_laser_route = level.time;
edict_t *from = G_Find(NULL, FOFS(classname), path_beam_classname);
while (from)
{
edict_t *beam = from;
from = G_Find(from, FOFS(classname), path_beam_classname);
G_FreeEdict(beam);
}
if (self->bot_client)
{
float dist;
if (self->goalentity)
{
dist = PathToEnt(self->goalentity, goalent, 0, 1);
PathToEnt_Node = self->goalentity;
}
else
{
dist = PathToEnt(self, goalent, 0, 1);
if (dist == -1.0)
{
gi.dprintf(debug_no_path_fmt, goalent->classname);
return;
}
}
gi.dprintf(debug_path_length, dist);
}
else
{
float dist = PathToEnt(self, goalent, 0, 1);
if (dist == -1.0)
{
gi.dprintf(debug_no_path_fmt, goalent->classname);
return;
}
gi.dprintf(debug_path_length, dist);
}
edict_t *node = PathToEnt_Node;
edict_t *prev = self;
float total_dist = 0.0;
int node_count = 0;
while (node != goalent && total_dist < 1000.0 && node_count < 20)
{
edict_t *laser = DrawLine(self, node->s.origin, prev->s.origin);
if (node->node_type == 5)
gi.dprintf(" (GRAPPLE) ");
gi.dprintf("%i, ", node->trail_index);
gi.linkentity(laser);
if (PathToEnt(node, goalent, 0, 0) == -1.0)
return;
prev = node;
node = PathToEnt_Node;
total_dist = entdist(prev, PathToEnt_Node) + total_dist;
++node_count;
}
DrawLine(self, prev->s.origin, node->s.origin);
gi.dprintf("%i.\n\n", node->trail_index);
}
void CheckMoveForNodes(edict_t *ent)
{
edict_t *target = NULL;
if (!(trail_head < TRAIL_LENGTH - 1 && ent->movetype != MOVETYPE_NOCLIP && ent->solid))
return;
edict_t *goalent = PlayerTrail_LastSpot();
if (trail[0]->timestamp == 0.0)
{
PlayerTrail_Add(ent, ent->s.origin, NULL, 0, 1, NODE_NORMAL);
return;
}
if (!(goalent->trail_index < TRAIL_LENGTH - 1))
return;
goalent = ent->last_trail_dropped;
vec3_t vec;
if (ent->last_trail_dropped)
VectorSubtract(goalent->s.origin, ent->s.origin, vec);
else
{
VectorClear(vec);
int closest = ClosestNodeToEnt(ent, 0, 1);
if (closest <= -1)
{
PlayerTrail_Add(ent, ent->s.origin, NULL, 1, 1, NODE_NORMAL);
goto nocheck;
}
ent->last_trail_dropped = trail[closest];
goalent = ent->last_trail_dropped;
}
if (ent->flags & FL_NO_KNOCKBACK)
{
nocheck:
if (ent->maxs[2] == 32.0 && ent->duck_ent)
{
G_FreeEdict(ent->duck_ent);
ent->duck_ent = NULL;
}
ent->last_max_z = ent->maxs[2];
ent->last_groundentity = ent->groundentity;
if (ent->last_max_z > 4.0 && ent->last_trail_dropped->maxs[2] == 4.0)
ent->last_trail_dropped->flags |= 4u;
return;
}
if (ent->maxs[2] == 4.0 && ent->last_max_z != ent->maxs[2])
{
ent->duck_ent = G_Spawn();
ent->duck_ent->classname = player_duck_classname;
VectorCopy(ent->mins, ent->duck_ent->mins);
VectorCopy(ent->maxs, ent->duck_ent->maxs);
ent->duck_ent->closest_trail_time = 0.0;
ent->duck_ent->closest_trail = 0;
VectorClear(ent->duck_ent->velocity);
VectorCopy(ent->s.origin, ent->duck_ent->s.origin);
goto LABEL_62;
}
if (ent->last_max_z != ent->maxs[2]
&& ent->maxs[2] == 32.0
&& ent->last_trail_dropped->maxs[2] < 32.0
&& !(ent->last_trail_dropped->flags & 4))
{
ent->last_trail_dropped->flags = (ent->last_trail_dropped->flags | 4) & 0xffff;
ent->maxs[2] = 4.0;
PlayerTrail_Add(ent, ent->s.old_origin, NULL, 1, 1, NODE_NORMAL);
last_trail_time = level.time;
ent->maxs[2] = 32.0;
goto nocheck;
}
if (ent->groundentity || !ent->last_groundentity || ent->waterlevel >= 2)
{
if (ent->groundentity || !ent->waterlevel || ent->waterlevel >= 3 || ent->velocity[2] <= 80.0)
{
if (!ent->last_groundentity && (ent->waterlevel > 0 || ent->groundentity))
{
if (ent->jump_ent)
{
qboolean check_fullbox = abs(ent->jump_ent->s.origin[2] - ent->s.origin[2]) > 18;
float dist = PathToEnt(ent->jump_ent, ent, check_fullbox, 1);
if (dist == -1.0 || entdist(ent->jump_ent, ent) * 4.0 < dist || entdist(ent->jump_ent, ent) + 512.0 < dist)
{
if (visible_box(ent, ent->jump_ent) && CanReach(ent, ent->jump_ent))
{
if (ent->maxs[2] == 4.0 && ent->duck_ent)
{
PlayerTrail_Add(ent, ent->duck_ent->s.origin, NULL, 0, 1, NODE_NORMAL);
G_FreeEdict(ent->duck_ent);
ent->duck_ent = NULL;
}
PlayerTrail_Add(ent, ent->jump_ent->s.origin, NULL, 0, 1, NODE_NORMAL);
PlayerTrail_Add(ent, ent->s.origin, NULL, 0, 1, NODE_NORMAL);
G_FreeEdict(ent->jump_ent);
ent->jump_ent = NULL;
last_trail_time = level.time;
}
else
{
NodeDebug("Dropping jump nodes\n");
PlayerTrail_Add(ent, ent->s.origin, NULL, 1, 1, NODE_LANDING);
goalent = ent->last_trail_dropped;
PlayerTrail_Add(ent->jump_ent, ent->jump_ent->s.origin, goalent, 1, 1, ent->jump_ent->flags);
VectorCopy(ent->jump_ent->velocity, ent->last_trail_dropped->velocity);
int node_index = goalent->trail_index;
if (!(ent->jump_ent->flags & FL_NO_KNOCKBACK) && ent->s.origin[2] + 48.0 > ent->jump_ent->s.origin[2])
{
NodeDebug("Adding reverse jump nodes\n");
level.time = level.time - 0.1;
PlayerTrail_Add(ent->jump_ent, ent->jump_ent->s.origin, NULL, 1, 1, NODE_LANDING);
goalent = ent->jump_ent->last_trail_dropped;
PlayerTrail_Add(ent, ent->s.origin, goalent, 1, 1, NODE_NORMAL);
vec3_t vel;
VectorScale(ent->velocity, -2.0, vel);
if (vel[2] <= 200.0)
{
if (vel[2] < 40.0)
vel[2] = 40.0;
}
else
vel[2] = 310.0;
VectorCopy(vel, ent->last_trail_dropped->velocity);
level.time = level.time + 0.1;
}
CalcRoutes(node_index);
G_FreeEdict(ent->jump_ent);
ent->jump_ent = NULL;
}
goto nocheck;
}
}
}
if (ent->jump_ent && (ent->groundentity || ent->waterlevel))
{
G_FreeEdict(ent->jump_ent);
ent->jump_ent = NULL;
}
goto LABEL_62;
}
if (!ent->jump_ent)
{
ent->jump_ent = G_Spawn();
ent->jump_ent->classname = player_jump_classname;
VectorCopy(ent->mins, ent->jump_ent->mins);
VectorCopy(ent->maxs, ent->jump_ent->maxs);
ent->jump_ent->closest_trail_time = 0.0;
ent->jump_ent->closest_trail = 0;
}
VectorCopy(ent->velocity, ent->jump_ent->velocity);
VectorCopy(ent->s.old_origin, ent->jump_ent->s.origin);
ent->jump_ent->waterlevel = ent->waterlevel;
if (ent->client->ctf_grapple)
goto LABEL_27;
ent->jump_ent->flags = NODE_NORMAL;
}
else
{
if (!ent->jump_ent)
{
ent->jump_ent = G_Spawn();
ent->jump_ent->classname = player_jump_classname;
VectorSet(ent->jump_ent->mins, -16.0, -16.0, -24.0);
VectorSet(ent->jump_ent->maxs, 16.0, 16.0, 32.0);
ent->jump_ent->closest_trail_time = 0.0;
ent->jump_ent->closest_trail = 0;
}
VectorCopy(ent->velocity, ent->jump_ent->velocity);
VectorCopy(ent->s.old_origin, ent->jump_ent->s.origin);
ent->jump_ent->waterlevel = ent->waterlevel;
if (ent->client->ctf_grapple)
{
LABEL_27:
ent->jump_ent->flags = NODE_GRAPPLE;
VectorCopy(ent->animate_org, ent->jump_ent->s.origin);
VectorSubtract(ent->client->ctf_grapple->s.origin, ent->animate_org, ent->jump_ent->velocity);
VectorNormalize(ent->jump_ent->velocity);
goto LABEL_62;
}
ent->jump_ent->flags = NODE_NORMAL;
}
LABEL_62:
if ((ent->groundentity || ent->waterlevel > 0) && (VectorLength(vec) > 128.0 || !visible_fullbox(ent, goalent)))
{
float dist = PathToEnt(goalent, ent, 0, 1);
if (dist == -1.0 || dist > 2000.0)
goto LABEL_89;
if (PathToEnt_TargetNode == ent || !(target = PathToEnt_TargetNode))
target = goalent;
if (target && entdist(target, ent) > 128.0)
{
LABEL_89:
if (ent->maxs[2] == 4.0 && ent->duck_ent)
{
PlayerTrail_Add(ent, ent->duck_ent->s.origin, NULL, 0, 1, 0);
G_FreeEdict(ent->duck_ent);
ent->duck_ent = 0;
}
PlayerTrail_Add(ent, ent->s.old_origin, NULL, 0, 1, 0);
last_trail_time = level.time;
}
else if (target && target->routes)
{
vec3_t ent_target_vec, ent_goal_vec;
VectorSubtract(ent->s.origin, target->s.origin, ent_target_vec);
VectorSubtract(ent->s.origin, goalent->s.origin, ent_goal_vec);
if (VectorLength(ent_goal_vec) > VectorLength(ent_target_vec))
ent->last_trail_dropped = target;
}
}
goto nocheck;
}
void CalcItemPaths(edict_t *ent)
{
static vec3_t mins = { -16.f, -16.f, 0.f };
if (level.time >= 3.0)
{
char path_index = 0;
int best_node = -1;
float best_dist = 99999.0;
vec3_t point;
VectorSubtract(ent->s.origin, tv(0.f, 0.f, 10.f), point);
int contents = gi.pointcontents(point);
if (contents & (CONTENTS_SLIME | CONTENTS_LAVA))
return;
int x = GetGridPortal(ent->s.origin[0]);
int y = GetGridPortal(ent->s.origin[1]);
int num = num_trail_portals[x][y];
for (int i = 0; i < num; ++i)
{
int node_index = trail_portals[x][y][i];
trace_t tr = gi.trace(
trail[node_index]->s.origin,
mins,
trail[node_index]->maxs,
ent->s.origin,
NULL,
MASK_SOLID);
vec3_t vec;
VectorSubtract(ent->s.origin, tr.endpos, vec);
if (VectorLength(vec) >= 30.0)
continue;
if (!CanReach(trail[node_index], ent))
continue;
ent->paths[path_index++] = node_index;
float dist = entdist(trail[node_index], ent);
if (dist < best_dist)
{
best_dist = dist;
best_node = node_index;
}
if (path_index >= MAX_PATHS)
break;
}
while (path_index < MAX_PATHS)
ent->paths[path_index++] = -1;
if (best_node <= -1)
ent->movetarget = NULL;
else
ent->movetarget = trail[best_node];
if (ent->item->pickup == Pickup_Weapon)
weapons_head = AddToItemList(ent, weapons_head);
else if (ent->item->pickup == Pickup_Health)
health_head = AddToItemList(ent, health_head);
else if (ent->item->pickup == Pickup_Ammo)
ammo_head = AddToItemList(ent, ammo_head);
else
bonus_head = AddToItemList(ent, bonus_head);
for (int j = 0; j < num_players; ++j)
{
if (!players[j]->bot_client)
continue;
float dist = entdist(ent, players[j]);
if (dist > 384.0)
continue;
if (players[j]->movetarget && entdist(players[j]->movetarget, players[j]) <= dist)
continue;
if (players[j]->enemy
&& players[j]->bot_fire != botBlaster
&& dist >= 128.0
&& (entdist(players[j]->enemy, players[j]) <= dist))
continue;
if (PathToEnt(players[j], ent, 0, 0) == -1.0)
continue;
players[j]->movetarget = ent;
players[j]->goalentity = PathToEnt_Node;
}
}
else
{
char path_index = 0;
int best_node = -1;
float best_dist = 99999.0;
int x = GetGridPortal(ent->s.origin[0]);
int y = GetGridPortal(ent->s.origin[1]);
int num = num_trail_portals[x][y];
for (int i = 0; i < num; ++i)
{
int node_index = trail_portals[x][y][i];
trace_t tr = gi.trace(
trail[node_index]->s.origin,
mins,
trail[node_index]->maxs,
ent->s.origin,
NULL,
MASK_SOLID);
vec3_t vec;
VectorSubtract(ent->s.origin, tr.endpos, vec);
if (VectorLength(vec) >= 30.0)
continue;
ent->paths[path_index++] = node_index;
float dist = entdist(trail[node_index], ent);
if (dist < best_dist)
{
best_dist = dist;
best_node = node_index;
}
if (path_index >= MAX_PATHS)
break;
}
while (path_index < MAX_PATHS)
ent->paths[path_index++] = -1;
if (best_node <= -1)
{
if (ent->count != 2
&& ent->item->tag != ARMOR_SHARD
&& (ent->item->pickup != Pickup_Health || (ent->style & 1)))
{
if (bot_debug_nodes && bot_debug_nodes->value)
{
gi.dprintf("%s not reachable\n", ent->classname);
ent->s.effects |= EF_FLAG1;
ent->s.renderfx |= RF_WEAPONMODEL;
}
ent->movetarget = NULL;
nodes_done = false;
}
}
else
ent->movetarget = trail[best_node];
if (ent->item->pickup == Pickup_Weapon)
weapons_head = AddToItemList(ent, weapons_head);
else if (ent->item->pickup == Pickup_Health)
health_head = AddToItemList(ent, health_head);
else if (ent->item->pickup == Pickup_Ammo)
ammo_head = AddToItemList(ent, ammo_head);
else
bonus_head = AddToItemList(ent, bonus_head);
}
}