1
0
Fork 0
forked from fte/fteqw
fteqw/engine/server/sv_move.c
Spoike 0fee3a4aea Add ortho lights (still has serious issues that make them unusable on regular maps).
First real attempt at lit water.
Parsing rtlights is now aware of spotlights.
Default cl_yieldcpu to 1, to save cpu for anyone who sets cl_maxfps lower. Added to menus.
presets now include view angle clamping - maps made for quakespasm REQUIRE full pitch angles despite it otherwise being considered a cheat or glitchy (on servers that try to block the cheat).
fix r_fullbrightSkins>=1 issue.

git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@5274 fc73d0e0-1445-4013-8a0c-d673dee63da5
2018-07-22 11:49:37 +00:00

1275 lines
34 KiB
C

/*
Copyright (C) 1996-1997 Id Software, Inc.
This program 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.
This program 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 this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
// sv_move.c -- monster movement
#include "quakedef.h"
#include "pr_common.h"
#if defined(CSQC_DAT) || !defined(CLIENTONLY)
/*
=============
SV_CheckBottom
Returns false if any part of the bottom of the entity is off an edge that
is not a staircase.
=============
*/
int c_yes, c_no;
hull_t *Q1BSP_ChooseHull(model_t *model, int hullnum, vec3_t mins, vec3_t maxs, vec3_t offset);
//this function is axial. major axis determines ground. if it switches slightly, a new axis may become the ground...
qboolean World_CheckBottom (world_t *world, wedict_t *ent, vec3_t up)
{
vec3_t mins, maxs, start, stop;
trace_t trace;
int x, y;
float mid;
int a0,a1,a2; //logical x, y, z
int sign;
mins[0] = fabs(up[0]);
mins[1] = fabs(up[1]);
mins[2] = fabs(up[2]);
if (mins[2] > mins[0] && mins[2] > mins[1])
{
a0 = 0;
a1 = 1;
a2 = 2;
}
else
{
a2 = mins[1] > mins[0];
a0 = 1 - a2;
a1 = 2;
}
sign = (up[a2]>0)?1:-1;
VectorAdd (ent->v->origin, ent->v->mins, mins);
#ifdef Q1BSPS
if (world->worldmodel->fromgame == fg_quake || world->worldmodel->fromgame == fg_halflife)
{
//quake's hulls are weird. sizes are defined as from mins to mins+hullsize. the actual maxs is ignored other than for its size.
hull_t *hull;
hull = Q1BSP_ChooseHull(world->worldmodel, ent->xv->hull, ent->v->mins, ent->v->maxs, start);
VectorAdd (mins, start, mins);
VectorSubtract (mins, hull->clip_mins, maxs);
VectorAdd (maxs, hull->clip_maxs, maxs);
}
else
#endif
VectorAdd (ent->v->origin, ent->v->maxs, maxs);
// if all of the points under the corners are solid world, don't bother
// with the tougher checks
// the corners must be within 16 of the midpoint
start[a2] = (sign<0)?maxs[a2]:mins[a2] - sign;
for (x=0 ; x<=1 ; x++)
for (y=0 ; y<=1 ; y++)
{
start[a0] = x ? maxs[a0] : mins[a0];
start[a1] = y ? maxs[a1] : mins[a1];
if (!(World_PointContents (world, start) & FTECONTENTS_SOLID))
goto realcheck;
}
c_yes++;
return true; // we got out easy
realcheck:
c_no++;
//
// check it for real...
//
start[a2] = (sign<0)?maxs[a2]:mins[a2];
// the midpoint must be within 16 of the bottom
start[a0] = stop[a0] = (mins[a0] + maxs[a0])*0.5;
start[a1] = stop[a1] = (mins[a1] + maxs[a1])*0.5;
stop[a2] = start[a2] - 2*movevars.stepheight*sign;
trace = World_Move (world, start, vec3_origin, vec3_origin, stop, true|MOVE_IGNOREHULL, ent);
if (trace.fraction == 1.0)
return false;
mid = trace.endpos[2];
mid = (mid-start[a2]-(movevars.stepheight*sign)) / (stop[a2]-start[a2]);
// the corners must be within 16 of the midpoint
for (x=0 ; x<=1 ; x++)
for (y=0 ; y<=1 ; y++)
{
start[a0] = stop[a0] = x ? maxs[a0] : mins[a0];
start[a1] = stop[a1] = y ? maxs[a1] : mins[a1];
trace = World_Move (world, start, vec3_origin, vec3_origin, stop, true|MOVE_IGNOREHULL, ent);
if (trace.fraction == 1.0 || trace.fraction > mid)//mid - trace.endpos[2] > movevars.stepheight)
return false;
}
c_yes++;
return true;
}
/*
=============
SV_movestep
Called by monster program code.
The move will be adjusted for slopes and stairs, but if the move isn't
possible, no move is done, false is returned, and
pr_global_struct->trace_normal is set to the normal of the blocking wall
=============
*/
qboolean World_movestep (world_t *world, wedict_t *ent, vec3_t move, vec3_t axis[3], qboolean relink, qboolean noenemy, void (*set_move_trace)(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals, trace_t *trace), struct globalvars_s *set_trace_globs)
{
float dz;
vec3_t oldorg, neworg, end;
trace_t trace;
int i;
wedict_t *enemy = world->edicts;
int eflags = ent->v->flags;
vec3_t eaxis[3];
if (!axis)
{
//fixme?
World_GetEntGravityAxis(ent, eaxis);
axis = eaxis;
}
#ifndef CLIENTONLY
if (progstype != PROG_H2 || world != &sv.world)
#endif
eflags &= ~FLH2_NOZ|FLH2_HUNTFACE;
// try the move
VectorCopy (ent->v->origin, oldorg);
VectorAdd (ent->v->origin, move, neworg);
// flying monsters don't step up
if ((eflags & (FL_SWIM | FL_FLY)) && !(eflags & (FLH2_NOZ|FLH2_HUNTFACE)))
{
// try one move with vertical motion, then one without
for (i=0 ; i<2 ; i++)
{
VectorAdd (ent->v->origin, move, neworg);
if (!noenemy)
{
enemy = (wedict_t*)PROG_TO_EDICT(world->progs, ent->v->enemy);
if (i == 0 && enemy->entnum)
{
VectorSubtract(ent->v->origin, ((wedict_t*)PROG_TO_EDICT(world->progs, ent->v->enemy))->v->origin, end);
dz = DotProduct(end, axis[2]);
if (eflags & FLH2_HUNTFACE) /*get the ent's origin_z to match its victims face*/
dz += ((wedict_t*)PROG_TO_EDICT(world->progs, ent->v->enemy))->v->view_ofs[2];
if (dz > 40)
VectorMA(neworg, -8, axis[2], neworg);
if (dz < 30)
VectorMA(neworg, 8, axis[2], neworg);
}
}
trace = World_Move (world, ent->v->origin, ent->v->mins, ent->v->maxs, neworg, false, ent);
if (set_move_trace)
set_move_trace(world->progs, set_trace_globs, &trace);
if (trace.fraction == 1)
{
if ( (eflags & FL_SWIM) && !(World_PointContents(world, trace.endpos) & FTECONTENTS_FLUID))
continue; // swim monster left water
VectorCopy (trace.endpos, ent->v->origin);
if (relink)
World_LinkEdict (world, ent, true);
return true;
}
if (noenemy || !enemy->entnum)
break;
}
return false;
}
// push down from a step height above the wished position
VectorMA(neworg, movevars.stepheight, axis[2], neworg);
VectorMA(neworg, movevars.stepheight*-2, axis[2], end);
trace = World_Move (world, neworg, ent->v->mins, ent->v->maxs, end, false, ent);
if (set_move_trace)
set_move_trace(world->progs, set_trace_globs, &trace);
if (trace.allsolid)
return false;
if (trace.startsolid)
{
//move up by an extra step, if needed
VectorMA(neworg, -movevars.stepheight, axis[2], neworg);
trace = World_Move (world, neworg, ent->v->mins, ent->v->maxs, end, false, ent);
if (set_move_trace)
set_move_trace(world->progs, set_trace_globs, &trace);
if (trace.allsolid || trace.startsolid)
return false;
}
if (trace.fraction == 1)
{
// if monster had the ground pulled out, go ahead and fall
if ( (int)ent->v->flags & FL_PARTIALGROUND )
{
VectorAdd (ent->v->origin, move, ent->v->origin);
if (relink)
World_LinkEdict (world, ent, true);
ent->v->flags = (int)ent->v->flags & ~FL_ONGROUND;
// Con_Printf ("fall down\n");
return true;
}
return false; // walked off an edge
}
// check point traces down for dangling corners
VectorCopy (trace.endpos, ent->v->origin);
if (!World_CheckBottom (world, ent, axis[2]))
{
if ( (int)ent->v->flags & FL_PARTIALGROUND )
{ // entity had floor mostly pulled out from underneath it
// and is trying to correct
if (relink)
World_LinkEdict (world, ent, true);
return true;
}
VectorCopy (oldorg, ent->v->origin);
return false;
}
if ( (int)ent->v->flags & FL_PARTIALGROUND )
{
// Con_Printf ("back on ground\n");
ent->v->flags = (int)ent->v->flags & ~FL_PARTIALGROUND;
}
ent->v->groundentity = EDICT_TO_PROG(world->progs, trace.ent);
// the move is ok
if (relink)
World_LinkEdict (world, ent, true);
return true;
}
//============================================================================
qboolean World_GetEntGravityAxis(wedict_t *ent, vec3_t axis[3])
{
if (ent->xv->gravitydir[0] || ent->xv->gravitydir[1] || ent->xv->gravitydir[2])
{
void PerpendicularVector( vec3_t dst, const vec3_t src );
VectorNegate(ent->xv->gravitydir, axis[2]);
VectorNormalize(axis[2]);
PerpendicularVector(axis[0], axis[2]);
VectorNormalize(axis[0]);
CrossProduct(axis[2], axis[0], axis[1]);
VectorNormalize(axis[1]);
return true;
}
else
{
VectorSet(axis[0], 1, 0, 0);
VectorSet(axis[1], 0, 1, 0);
VectorSet(axis[2], 0, 0, 1);
return false;
}
}
/*
==============
PF_changeyaw
This was a major timewaster in progs, so it was converted to C
==============
*/
float World_changeyaw (wedict_t *ent)
{
float ideal, current, move, speed;
vec3_t surf[3];
if (World_GetEntGravityAxis(ent, surf))
{
//complex matrix stuff
float mat[16];
float surfm[16], invsurfm[16];
float viewm[16];
vec3_t view[4];
vec3_t vang;
/*calc current view matrix relative to the surface*/
ent->v->angles[PITCH] *= r_meshpitch.value;
AngleVectors(ent->v->angles, view[0], view[1], view[2]);
VectorNegate(view[1], view[1]);
World_GetEntGravityAxis(ent, surf);
Matrix4x4_RM_FromVectors(surfm, surf[0], surf[1], surf[2], vec3_origin);
Matrix3x4_InvertTo4x4_Simple(surfm, invsurfm);
/*calc current view matrix relative to the surface*/
Matrix4x4_RM_FromVectors(viewm, view[0], view[1], view[2], vec3_origin);
Matrix4_Multiply(viewm, invsurfm, mat);
/*convert that back to angles*/
Matrix3x4_RM_ToVectors(mat, view[0], view[1], view[2], view[3]);
VectorAngles(view[0], view[2], vang, true);
/*edit it*/
ideal = ent->v->ideal_yaw;
speed = ent->v->yaw_speed;
move = ideal - anglemod(vang[YAW]);
if (move > 180)
move -= 360;
else if (move < -180)
move += 360;
if (move > 0)
{
if (move > speed)
move = speed;
}
else
{
if (move < -speed)
move = -speed;
}
vang[YAW] = anglemod(vang[YAW] + move);
/*clamp pitch, kill roll. monsters don't pitch/roll.*/
vang[PITCH] = 0;
vang[ROLL] = 0;
move = ideal - vang[YAW];
/*turn those angles back to a matrix*/
AngleVectors(vang, view[0], view[1], view[2]);
VectorNegate(view[1], view[1]);
Matrix4x4_RM_FromVectors(mat, view[0], view[1], view[2], vec3_origin);
/*rotate back into world space*/
Matrix4_Multiply(mat, surfm, viewm);
/*and figure out the final result*/
Matrix3x4_RM_ToVectors(viewm, view[0], view[1], view[2], view[3]);
VectorAngles(view[0], view[2], ent->v->angles, true);
//make sure everything is sane
ent->v->angles[PITCH] = anglemod(ent->v->angles[PITCH]);
ent->v->angles[YAW] = anglemod(ent->v->angles[YAW]);
ent->v->angles[ROLL] = anglemod(ent->v->angles[ROLL]);
return move;
}
//FIXME: gravitydir. reorient the angles to change the yaw with respect to the current ground surface.
current = anglemod( ent->v->angles[1] );
ideal = ent->v->ideal_yaw;
speed = ent->v->yaw_speed;
if (current == ideal)
return 0;
move = ideal - current;
if (ideal > current)
{
if (move >= 180)
move = move - 360;
}
else
{
if (move <= -180)
move = move + 360;
}
if (move > 0)
{
if (move > speed)
move = speed;
}
else
{
if (move < -speed)
move = -speed;
}
ent->v->angles[1] = anglemod (current + move);
return ideal - ent->v->angles[1];
}
/*
======================
SV_StepDirection
Turns to the movement direction, and walks the current distance if
facing it.
======================
*/
qboolean World_StepDirection (world_t *world, wedict_t *ent, float yaw, float dist, vec3_t axis[3])
{
vec3_t move, oldorigin;
float delta, s;
ent->v->ideal_yaw = yaw;
delta = World_changeyaw(ent);
yaw = yaw*M_PI*2 / 360;
s = cos(yaw)*dist;
VectorScale(axis[0], s, move);
s = sin(yaw)*dist;
VectorMA(move, s, axis[1], move);
//FIXME: Hexen2: ent flags & FL_SET_TRACE
VectorCopy (ent->v->origin, oldorigin);
if (World_movestep (world, ent, move, axis, false, false, NULL, NULL))
{
delta = anglemod(delta);
if (delta > 45 && delta < 315)
{ // not turned far enough, so don't take the step
//FIXME: surely this is noticably inefficient?
VectorCopy (oldorigin, ent->v->origin);
}
World_LinkEdict (world, ent, true);
return true;
}
World_LinkEdict (world, ent, true);
return false;
}
/*
======================
SV_FixCheckBottom
======================
*/
void World_FixCheckBottom (wedict_t *ent)
{
// Con_Printf ("SV_FixCheckBottom\n");
ent->v->flags = (int)ent->v->flags | FL_PARTIALGROUND;
}
/*
================
SV_NewChaseDir
================
*/
#define DI_NODIR -1
void World_NewChaseDir (world_t *world, wedict_t *actor, wedict_t *enemy, float dist, vec3_t axis[3])
{
float deltax,deltay;
float d[3];
float tdir, olddir, turnaround;
olddir = anglemod( (int)(actor->v->ideal_yaw/45)*45 );
turnaround = anglemod(olddir - 180);
VectorSubtract(enemy->v->origin, actor->v->origin, d);
deltax = DotProduct(d, axis[0]);
deltay = DotProduct(d, axis[1]);
if (deltax>10)
d[1]= 0;
else if (deltax<-10)
d[1]= 180;
else
d[1]= DI_NODIR;
if (deltay<-10)
d[2]= 270;
else if (deltay>10)
d[2]= 90;
else
d[2]= DI_NODIR;
// try direct route
if (d[1] != DI_NODIR && d[2] != DI_NODIR)
{
if (d[1] == 0)
tdir = d[2] == 90 ? 45 : 315;
else
tdir = d[2] == 90 ? 135 : 215;
if (tdir != turnaround && World_StepDirection(world, actor, tdir, dist, axis))
return;
}
// try other directions
if ( ((rand()&3) & 1) || fabs(deltay)>fabs(deltax))
{
tdir=d[1];
d[1]=d[2];
d[2]=tdir;
}
if (d[1]!=DI_NODIR && d[1]!=turnaround
&& World_StepDirection(world, actor, d[1], dist, axis))
return;
if (d[2]!=DI_NODIR && d[2]!=turnaround
&& World_StepDirection(world, actor, d[2], dist, axis))
return;
/* there is no direct path to the player, so pick another direction */
if (olddir!=DI_NODIR && World_StepDirection(world, actor, olddir, dist, axis))
return;
if (rand()&1) /*randomly determine direction of search*/
{
for (tdir=0 ; tdir<=315 ; tdir += 45)
if (tdir!=turnaround && World_StepDirection(world, actor, tdir, dist, axis) )
return;
}
else
{
for (tdir=315 ; tdir >=0 ; tdir -= 45)
if (tdir!=turnaround && World_StepDirection(world, actor, tdir, dist, axis) )
return;
}
if (turnaround != DI_NODIR && World_StepDirection(world, actor, turnaround, dist, axis) )
return;
actor->v->ideal_yaw = olddir; // can't move
// if a bridge was pulled out from underneath a monster, it may not have
// a valid standing position at all
if (!World_CheckBottom (world, actor, axis[2]))
World_FixCheckBottom (actor);
}
/*
======================
SV_CloseEnough
======================
*/
qboolean World_CloseEnough (wedict_t *ent, wedict_t *goal, float dist)
{
int i;
for (i=0 ; i<3 ; i++)
{
if (goal->v->absmin[i] > ent->v->absmax[i] + dist)
return false;
if (goal->v->absmax[i] < ent->v->absmin[i] - dist)
return false;
}
return true;
}
/*
======================
SV_MoveToGoal
======================
*/
qboolean World_MoveToGoal (world_t *world, wedict_t *ent, float dist)
{
wedict_t *goal;
vec3_t axis[3];
ent = (wedict_t*)PROG_TO_EDICT(world->progs, *world->g.self);
goal = (wedict_t*)PROG_TO_EDICT(world->progs, ent->v->goalentity);
if ( !( (int)ent->v->flags & (FL_ONGROUND|FL_FLY|FL_SWIM) ) )
{
return false;
}
// if the next step hits the enemy, return immediately
if ( PROG_TO_EDICT(world->progs, ent->v->enemy) != (edict_t*)world->edicts && World_CloseEnough (ent, goal, dist) )
return true;
World_GetEntGravityAxis(ent, axis);
// bump around...
if ( (rand()&3)==1 ||
!World_StepDirection (world, ent, ent->v->ideal_yaw, dist, axis))
{
World_NewChaseDir (world, ent, goal, dist, axis);
}
return true;
}
#ifdef ENGINE_ROUTING
cvar_t route_shownodes = CVAR("route_shownodes", "0");
#define LF_EDGE 0x00000001
#define LF_JUMP 0x00000002
#define LF_CROUCH 0x00000004
#define LF_TELEPORT 0x00000008
#define LF_USER 0x7fffff00
#define LF_DESTINATION 0x80000000 //You have reached your destination...
struct waypointnetwork_s
{
size_t refs;
size_t numwaypoints;
model_t *worldmodel;
struct resultnodes_s
{
vec3_t pos;
int linkflags;
} *displaynode;
int displaynodes;
struct waypoint_s
{
vec3_t org;
float radius; //used for picking the closest waypoint. aka proximity weight. also relaxes routes inside the area.
struct wpneighbour_s
{
int node;
float linkcost;//might be much lower in the case of teleports, or expensive if someone wanted it to be a lower priority link.
int linkflags; //LF_*
} *neighbour;
int neighbours;
} waypoints[1];
};
void WayNet_Done(struct waypointnetwork_s *net)
{
if (net)
if (0 == --net->refs)
{
Z_Free(net);
}
}
static qboolean WayNet_TokenizeLine(char **linestart)
{
char *end = *linestart;
if (!*end)
{ //clear it out...
Cmd_TokenizeString("", false, false);
return false;
}
for (; *end; end++)
{
if (*end == '\n')
{
*end++ = 0;
break;
}
}
Cmd_TokenizeString(*linestart, false, false);
*linestart = end;
return true;
}
static struct waypointnetwork_s *WayNet_Begin(void **ctxptr, model_t *worldmodel)
{
struct waypointnetwork_s *net = *ctxptr;
if (!net)
{
char *wf = NULL, *l, *e;
int numwaypoints, maxlinks, numlinks;
struct wpneighbour_s *nextlink;
if (!worldmodel)
return NULL;
if (!wf && !strncmp(worldmodel->name, "maps/", 5))
{
char n[MAX_QPATH];
COM_StripExtension(worldmodel->name+5, n, sizeof(n));
wf = FS_MallocFile(va("data/%s.way", n), FS_GAME, NULL);
}
if (!wf)
wf = FS_MallocFile(va("%s.way", worldmodel->name), FS_GAME, NULL);
if (!wf)
return NULL;
l = wf;
//read the number of waypoints
WayNet_TokenizeLine(&l);
numwaypoints = atoi(Cmd_Argv(0));
//count lines and guess the link count.
for (e = l, maxlinks=0; *e; e++)
if (*e == '\n')
maxlinks++;
maxlinks -= numwaypoints;
net = Z_Malloc(sizeof(*net)-sizeof(net->waypoints) + (numwaypoints*sizeof(struct waypoint_s)) + (maxlinks*sizeof(struct wpneighbour_s)));
net->refs = 1;
net->worldmodel = worldmodel;
*ctxptr = net;
nextlink = (struct wpneighbour_s*)(net->waypoints+numwaypoints);
while (WayNet_TokenizeLine(&l) && net->numwaypoints < numwaypoints)
{
if (!Cmd_Argc())
continue; //a comment line?
net->waypoints[net->numwaypoints].org[0] = atof(Cmd_Argv(0));
net->waypoints[net->numwaypoints].org[1] = atof(Cmd_Argv(1));
net->waypoints[net->numwaypoints].org[2] = atof(Cmd_Argv(2));
net->waypoints[net->numwaypoints].radius = 64;//atof(Cmd_Argv(3));
numlinks = bound(0, atoi(Cmd_Argv(4)), maxlinks);
//make sure the links are valid, and clamp to avoid problems (even if we're then going to mis-parse.
net->waypoints[net->numwaypoints].neighbour = nextlink;
while (numlinks-- > 0 && WayNet_TokenizeLine(&l))
{
if (!Cmd_Argc())
continue; //a comment line?
nextlink[net->waypoints[net->numwaypoints].neighbours].node = atoi(Cmd_Argv(0));
nextlink[net->waypoints[net->numwaypoints].neighbours].linkcost = atof(Cmd_Argv(1));
nextlink[net->waypoints[net->numwaypoints].neighbours++].linkflags = atoi(Cmd_Argv(2));
}
maxlinks -= net->waypoints[net->numwaypoints].neighbours;
nextlink += net->waypoints[net->numwaypoints++].neighbours;
}
BZ_Free(wf);
}
net->refs++;
return net;
}
struct waydist_s
{
int node;
float sdist;
};
int QDECL WayNet_Prioritise(const void *a, const void *b)
{
const struct waydist_s *w1 = a, *w2 = b;
if (w1->sdist < w2->sdist)
return -1;
if (w1->sdist == w2->sdist)
return 0;
return 1;
}
int WayNet_FindNearestNode(struct waypointnetwork_s *net, vec3_t pos)
{
if (net && net->numwaypoints)
{
//we qsort the possible nodes, in an attempt to reduce traces.
struct waydist_s *sortedways = alloca(sizeof(*sortedways) * net->numwaypoints);
size_t u;
vec3_t disp;
float sradius;
trace_t tr;
for (u = 0; u < net->numwaypoints; u++)
{
sortedways[u].node = u;
VectorSubtract(net->waypoints[u].org, pos, disp);
sortedways[u].sdist = DotProduct(disp, disp);
sradius = net->waypoints[u].radius*net->waypoints[u].radius;
if (sortedways[u].sdist < sradius)
sortedways[u].sdist -= sradius; //if we're inside the waypoint's radius, push inwards resulting in negatives, so these are always highly prioritised
}
qsort(sortedways, net->numwaypoints, sizeof(struct waydist_s), WayNet_Prioritise);
//can't trace yet...
if (net->worldmodel->loadstate != MLS_LOADED)
return sortedways[0].node;
for (u = 0; u < net->numwaypoints; u++)
{
if (sortedways[u].sdist > 0)
{ //if we're outside the node, we need to do a trace to make sure we can actually reach it.
net->worldmodel->funcs.NativeTrace(net->worldmodel, 0, NULL, NULL, pos, net->waypoints[sortedways[u].node].org, vec3_origin, vec3_origin, false, MASK_WORLDSOLID, &tr);
if (tr.fraction < 1)
continue; //this node is blocked. just move on to the next.
}
return sortedways[u].node;
}
}
return -1;
}
struct routecalc_s
{
world_t *world;
int spawncount; //so we don't confuse stuff if the map gets restarted.
wedict_t *ed;
// float spawnid; //so the route fails if the ent is removed.
func_t callback;
vec3_t start;
vec3_t end;
int denylinkflags;
int startn;
int endn;
int numresultnodes;
struct resultnodes_s *resultnodes;
struct waypointnetwork_s *waynet;
};
//main thread
void Route_Calculated(void *ctx, void *data, size_t a, size_t b)
{
struct routecalc_s *route = data;
pubprogfuncs_t *prinst = route->world->progs;
//let the gamecode know the results
if (!route->callback)
{
BZ_Free(route->waynet->displaynode);
route->waynet->displaynode = BZ_Malloc(sizeof(struct resultnodes_s) * route->numresultnodes);
route->waynet->displaynodes = route->numresultnodes;
memcpy(route->waynet->displaynode, route->resultnodes, sizeof(struct resultnodes_s) * route->numresultnodes);
}
else if (route->callback && route->world->spawncount == route->spawncount/* && route->spawnid == route->ed->xv->uniquespawnid*/)
{
struct globalvars_s * pr_globals = PR_globals(prinst, PR_CURRENT);
struct resultnodes_s *ptr = prinst->AddressableAlloc(prinst, sizeof(struct resultnodes_s) * route->numresultnodes);
memcpy(ptr, route->resultnodes, sizeof(struct resultnodes_s) * route->numresultnodes);
G_INT(OFS_PARM0) = EDICT_TO_PROG(prinst, route->ed);
VectorCopy(route->end, G_VECTOR(OFS_PARM1));
G_INT(OFS_PARM2) = route->numresultnodes;
G_INT(OFS_PARM3) = (char*)ptr-prinst->stringtable;
PR_ExecuteProgram(prinst, route->callback);
}
//and we're done. destroy everything.
WayNet_Done(route->waynet);
Z_Free(route->resultnodes);
Z_Free(route);
}
//#define FLOODALL
#define COST_INFINITE FLT_MAX
static qboolean Route_Completed(struct routecalc_s *r, int *nodecamefrom)
{
size_t u;
struct waypointnetwork_s *n = r->waynet;
r->resultnodes = Z_Malloc(sizeof(*r->resultnodes)*(n->numwaypoints+1)*3);
r->numresultnodes = 0;
//target point is first. yay.
VectorCopy(r->end, r->resultnodes[0].pos);
r->resultnodes[0].linkflags = LF_DESTINATION;
r->numresultnodes++;
u = r->endn;
for (;;)
{
VectorCopy(n->waypoints[u].org, r->resultnodes[r->numresultnodes].pos);
r->resultnodes[r->numresultnodes].linkflags = 0;
r->numresultnodes++;
if (u == r->startn)
break;
u = nodecamefrom[u];
}
//and include the start point, because we can
VectorCopy(r->start, r->resultnodes[r->numresultnodes].pos);
r->resultnodes[r->numresultnodes].linkflags = 0;
r->numresultnodes++;
return true;
}
#if 1
static float Route_GuessCost(struct routecalc_s *r, float *fromorg)
{ //if we want to guarentee the shortest route, then we MUST always return a value <= to the actual cost here.
//unfortunately we don't know how many teleporters are between the two points.
//on the plus side, a little randomness here means we'll find alternative (longer) routes some times, which will reduce flash points and help flag carriers...
vec3_t disp;
VectorSubtract(r->end, fromorg, disp);
return sqrt(DotProduct(disp,disp));
}
static qboolean Route_Process(struct routecalc_s *r)
{
struct waypointnetwork_s *n = r->waynet;
int opennodes = 0;
int u, j;
float guesscost;
struct opennode_s {
int node;
float cost;
} *open = alloca(sizeof(*open)*n->numwaypoints);
float *nodecost = alloca(sizeof(*nodecost)*n->numwaypoints);
int *nodecamefrom = alloca(sizeof(*nodecamefrom)*n->numwaypoints);
for(u = 0; u < n->numwaypoints; u++)
nodecost[u] = COST_INFINITE;
if (r->startn >= 0)
{
nodecost[r->startn] = 0;
open[0].node = r->startn;
open[0].cost = 0;
opennodes++;
}
while(opennodes)
{
int nodeidx = open[--opennodes].node;
struct waypoint_s *wp = &n->waypoints[nodeidx];
#ifdef _DEBUG
if (nodeidx < 0 || nodeidx >= n->numwaypoints)
{
Con_Printf("Bad node index in open list\n");
return false;
}
#endif
if (nodeidx == r->endn)
{ //we found the end!
return Route_Completed(r, nodecamefrom);
}
for (u = 0; u < wp->neighbours; u++)
{
struct wpneighbour_s *l = &wp->neighbour[u];
int linkidx = l->node;
float realcost = nodecost[nodeidx] + l->linkcost;
#ifdef _DEBUG
if (linkidx < 0 || linkidx >= n->numwaypoints)
{
Con_Printf("Bad node link index in routing table\n");
return false;
}
#endif
if (realcost >= nodecost[linkidx])
continue;
nodecamefrom[linkidx] = nodeidx;
nodecost[linkidx] = realcost;
for (j = opennodes-1; j >= 0; j--)
{
if (open[j].node == linkidx)
break;
}
guesscost = realcost + Route_GuessCost(r, n->waypoints[linkidx].org);
if (j < 0)
{ //not already in the list
//tbh, we should probably just directly bubble in this loop instead of doing the memcpy (with its internal second loop).
for (j = opennodes-1; j >= 0; j--)
if (guesscost <= open[j].cost)
break;
j++;
//move them up
memmove(&open[j+1], &open[j], sizeof(*open)*(opennodes-j));
open[j].node = linkidx;
open[j].cost = guesscost;
opennodes++;
}
else if (guesscost < open[j].cost)
{ //if it got cheaper, be prepared to move the node towards the higher addresses (these will be checked first).
for (; j+1 < opennodes && open[j+1].cost > guesscost; j++)
open[j] = open[j+1];
//okay, so we can't keep going... this is our new slot!
open[j].node = linkidx;
open[j].cost = guesscost;
}
//otherwise it got more expensive, and we don't care about that
}
}
return false;
}
#else
static qboolean Route_Process(struct routecalc_s *r)
{
struct waypointnetwork_s *n = r->waynet;
int opennodes = 0;
int u, j;
//we use an open list in a desperate attempt to avoid recursing the entire network
int *open = alloca(sizeof(*open)*n->numwaypoints);
float *nodecost = alloca(sizeof(*nodecost)*n->numwaypoints);
int *nodecamefrom = alloca(sizeof(*nodecamefrom)*n->numwaypoints);
for(u = 0; u < n->numwaypoints; u++)
nodecost[u] = COST_INFINITE;
nodecost[r->startn] = 0;
open[opennodes++] = r->startn;
while(opennodes)
{
int nodeidx = open[--opennodes];
struct waypoint_s *wp = &n->waypoints[nodeidx];
// if (nodeidx < 0 || nodeidx >= n->numwaypoints)
// return false;
for (u = 0; u < wp->neighbours; u++)
{
struct wpneighbour_s *l = &wp->neighbour[u];
int linkidx = l->node;
float realcost = nodecost[nodeidx] + l->linkcost;
// if (linkidx < 0 || linkidx >= n->numwaypoints)
// return false;
if (realcost >= nodecost[linkidx])
continue;
nodecamefrom[linkidx] = nodeidx;
nodecost[linkidx] = realcost;
for (j = 0; j < opennodes; j++)
{
if (open[j] == linkidx)
break;
}
if (j == opennodes) //not already queued
open[opennodes++] = linkidx;
}
}
if (r->endn >= 0 && nodecost[r->endn] < COST_INFINITE)
{ //we found the end! we can build the route from end to start.
return Route_Completed(r, nodecamefrom);
}
return false;
}
#endif
//worker thread
void Route_Calculate(void *ctx, void *data, size_t a, size_t b)
{
struct routecalc_s *route = data;
//first thing is to find the start+end nodes.
if (route->waynet && route->startn >= 0 && route->endn >= 0 && Route_Process(route))
;
else
{
route->numresultnodes = 0;
route->resultnodes = Z_Malloc(sizeof(*route->resultnodes)*2);
VectorCopy(route->end, route->resultnodes[0].pos);
route->resultnodes[0].linkflags = LF_DESTINATION;
route->numresultnodes++;
VectorCopy(route->start, route->resultnodes[route->numresultnodes].pos);
route->resultnodes[route->numresultnodes].linkflags = 0;
route->numresultnodes++;
}
COM_AddWork(WG_MAIN, Route_Calculated, NULL, route, 0, 0);
}
//void route_linkitem(entity item, int ittype) //-1 to unlink
//void route_choosedest(entity ent, int numitemtypes, float *itemweights)
/*
=============
PF_route_calculate
engine reads+caches the nodes from a file.
the route's nodes must be memfreed on completion.
the first node in the nodelist is the destination.
typedef struct {
vector dest;
int linkflags;
//float anglehint;
} nodeslist_t;
void(entity ent, vector dest, int denylinkflags, void(entity ent, vector dest, int numnodes, nodeslist_t *nodelist) callback) route_calculate = #0;
=============
*/
void QCBUILTIN PF_route_calculate (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
struct routecalc_s *route = Z_Malloc(sizeof(*route));
float *end;
route->world = prinst->parms->user;
route->spawncount = route->world->spawncount;
route->ed = G_WEDICT(prinst, OFS_PARM0);
// route->spawnid = route->ed->xv->uniquespawnid;
end = G_VECTOR(OFS_PARM1);
route->denylinkflags = G_INT(OFS_PARM2);
route->callback = G_INT(OFS_PARM3);
VectorCopy(route->ed->v->origin, route->start);
VectorCopy(end, route->end);
route->waynet = WayNet_Begin(&route->world->waypoints, route->world->worldmodel);
//tracelines use some sequence info to avoid retracing the same brush multiple times.
// this means that we can't reliably trace on worker threads (would break the main thread occasionally).
// so we have to do this here.
//FIXME: find a safe way to NOT do this here.
route->startn = WayNet_FindNearestNode(route->waynet, route->start);
route->endn = WayNet_FindNearestNode(route->waynet, route->end);
COM_AddWork(WG_LOADER, Route_Calculate, NULL, route, 0, 0);
}
#ifndef SERVERONLY
static void Route_Visualise_f(void)
{
extern world_t csqc_world;
vec3_t targ = {atof(Cmd_Argv(1)),atof(Cmd_Argv(2)),atof(Cmd_Argv(3))};
struct routecalc_s *route = Z_Malloc(sizeof(*route));
route->world = &csqc_world;
route->spawncount = route->world->spawncount;
route->ed = route->world->edicts;
// route->spawnid = route->ed->xv->uniquespawnid;
VectorCopy(r_refdef.vieworg, route->start);
VectorCopy(targ, route->end);
route->waynet = WayNet_Begin(&route->world->waypoints, route->world->worldmodel);
//tracelines use some sequence info to avoid retracing the same brush multiple times.
// this means that we can't reliably trace on worker threads (would break the main thread occasionally).
// so we have to do this here.
//FIXME: find a safe way to NOT do this here.
route->startn = WayNet_FindNearestNode(route->waynet, route->start);
route->endn = WayNet_FindNearestNode(route->waynet, route->end);
COM_AddWork(WG_LOADER, Route_Calculate, NULL, route, 0, 0);
}
#include "shader.h"
void PR_Route_Visualise (void)
{
extern world_t csqc_world;
world_t *w = &csqc_world;
struct waypointnetwork_s *wn;
size_t u;
wn = (w && (w->waypoints || route_shownodes.ival))?WayNet_Begin(&w->waypoints, w->worldmodel):NULL;
if (wn)
{
if (route_shownodes.ival)
{
float mat[12] = {1,0,0,0, 0,1,0,0, 0,0,1,0};
shader_t *shader_out = R_RegisterShader("waypointvolume_out", SUF_NONE,
"{\n"
"polygonoffset\n"
"nodepth\n"
"{\n"
"map $whiteimage\n"
"blendfunc add\n"
"rgbgen vertex\n"
"alphagen vertex\n"
"}\n"
"}\n");
shader_t *shader_in = R_RegisterShader("waypointvolume_in", SUF_NONE,
"{\n"
"polygonoffset\n"
"cull disable\n"
"nodepth\n"
"{\n"
"map $whiteimage\n"
"blendfunc add\n"
"rgbgen vertex\n"
"alphagen vertex\n"
"}\n"
"}\n");
float radius;
vec3_t dir;
//should probably use a different colour for the node you're inside.
for (u = 0; u < wn->numwaypoints; u++)
{
mat[3] = wn->waypoints[u].org[0];
mat[7] = wn->waypoints[u].org[1];
mat[11] = wn->waypoints[u].org[2];
radius = wn->waypoints[u].radius;
if (radius <= 0)
radius = 1; //waypoints shouldn't really have a radius of 0, but if they do we'll still want to draw something.
VectorSubtract(wn->waypoints[u].org, r_refdef.vieworg, dir);
if (DotProduct(dir,dir) < radius*radius)
CLQ1_AddOrientedSphere(shader_in, radius, mat, 0.0, 0.1, 0, 1);
else
CLQ1_AddOrientedSphere(shader_out, radius, mat, 0.2, 0.0, 0, 1);
}
for (u = 0; u < wn->numwaypoints; u++)
{
size_t n;
for (n = 0; n < wn->waypoints[u].neighbours; n++)
{
struct waypoint_s *r = wn->waypoints + wn->waypoints[u].neighbour[n].node;
CLQ1_DrawLine(shader_out, wn->waypoints[u].org, r->org, 0, 0, 1, 1);
}
}
}
if (wn->displaynodes)
{ //FIXME: we should probably use beams here
shader_t *shader_route = R_RegisterShader("waypointroute", SUF_NONE,
"{\n"
"polygonoffset\n"
"nodepth\n"
"{\n"
"map $whiteimage\n"
"blendfunc add\n"
"rgbgen vertex\n"
"alphagen vertex\n"
"}\n"
"}\n");
for (u = wn->displaynodes-1; u > 0; u--)
{
vec_t *start = wn->displaynode[u].pos;
vec_t *end = wn->displaynode[u-1].pos;
CLQ1_DrawLine(shader_route, start, end, 0.5, 0.5, 0.5, 1);
}
}
}
WayNet_Done(wn);
}
#endif
//destroys the routing waypoint cache.
void PR_Route_Shutdown (world_t *world)
{
WayNet_Done(world->waypoints);
world->waypoints = NULL;
}
static void Route_Reload_f(void)
{
#if !defined(SERVERONLY) && defined(CSQC_DAT)
extern world_t csqc_world;
PR_Route_Shutdown(&csqc_world);
#endif
#ifndef CLIENTONLY
PR_Route_Shutdown(&sv.world);
#endif
}
void PR_Route_Init (void)
{
#if !defined(SERVERONLY) && defined(CSQC_DAT)
Cvar_Register(&route_shownodes, NULL);
Cmd_AddCommand("route_visualise", Route_Visualise_f);
#endif
Cmd_AddCommand("route_reload", Route_Reload_f);
}
//route_force
//COM_WorkerPartialSync
#endif
#endif