mirror of
https://github.com/nzp-team/fteqw.git
synced 2024-11-14 16:31:38 +00:00
960d1c3616
git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@5380 fc73d0e0-1445-4013-8a0c-d673dee63da5
1276 lines
34 KiB
C
1276 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, trace_t *trace))
|
|
{
|
|
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;
|
|
}
|
|
|
|
// 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))
|
|
#if defined(HEXEN2) && defined(HAVE_SERVER)
|
|
//hexen2 has some extra logic for FLH2_HUNTFACE, but its buggy and thus never used.
|
|
//it would be nice to redefine the NOZ flag to instead force noenemy here, but that's not hexen2-compatible and FLH2_NOZ is bound to conflict with some quake mod.
|
|
&& (world != &sv.world || progstype != PROG_H2 || !(eflags & (FLH2_NOZ|FLH2_HUNTFACE)))
|
|
#endif
|
|
)
|
|
{
|
|
// 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 (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, &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, &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, &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))
|
|
{
|
|
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
|
|
#ifndef SERVERONLY
|
|
static cvar_t route_shownodes = CVAR("route_shownodes", "0");
|
|
#endif
|
|
|
|
#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;
|
|
size_t 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;
|
|
size_t 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;
|
|
wedict_t *ed;
|
|
int spawncount; //so we don't confuse stuff if the map gets restarted.
|
|
// 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
|