131a6be4bc
Add cl_lerp_driftbias and cl_lerp_driftfrac cvars, to tweak drifting. changed defaults to try to reduce clamping. Implement ladders with nq player physics. Fix submodel contents with nq player physics. Implemented drawrotpic_dp for compat (incompatible with fte's earlier implementation) Added con_textfont cvar to set fonts without uglifying menuqc/csqc drawstrings that don't specify explicit fonts. Enemycolor and teamcolor are now true cvars, which means they now work with seta. Move the homedir from CSIDL_PERSONAL to CSIDL_LOCAL_APPDATA, because microsoft apparently scrape all CSIDL_PERSONAL data for upload to their servers, because they don't understand 'personal'. Will still use the old homedir if it exists. Pack signon data without wasting so much, primarily to allow abusive mods to spew larger individual signon messages (hurrah for packet fragmentation). git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@5546 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_PointContentsWorldOnly (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_PointContentsWorldOnly(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
|