spingle/source/sv_move.c

424 lines
10 KiB
C

/*
Copyright (C) 1996-2001 Id Software, Inc.
Copyright (C) 2002-2009 John Fitzgibbons and others
Copyright (C) 2010-2014 QuakeSpasm developers
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 "q_defs.h"
#define STEPSIZE 18
/*
=============
SV_CheckBottom
Returns false if any part of the bottom of the entity is off an edge that
is not a staircase.
=============
*/
int32_t c_yes, c_no;
bool SV_CheckBottom(edict_t *ent)
{
vec3_t mins, maxs, start, stop;
trace_t trace;
int32_t x, y;
float mid, bottom;
VectorAdd(ED_Vector(ent, ED_origin), ED_Vector(ent, ED_mins), mins);
VectorAdd(ED_Vector(ent, ED_origin), ED_Vector(ent, ED_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[2] = mins[2] - 1;
for(x = 0 ; x <= 1 ; x++)
for(y = 0 ; y <= 1 ; y++)
{
start[0] = x ? maxs[0] : mins[0];
start[1] = y ? maxs[1] : mins[1];
if(SV_PointContents(start) != CONTENTS_SOLID)
goto realcheck;
}
c_yes++;
return true; // we got out easy
realcheck:
c_no++;
//
// check it for real...
//
start[2] = mins[2];
// the midpoint must be within 16 of the bottom
start[0] = stop[0] = (mins[0] + maxs[0]) * 0.5;
start[1] = stop[1] = (mins[1] + maxs[1]) * 0.5;
stop[2] = start[2] - 2 * STEPSIZE;
trace = SV_Move(start, vec3_origin, vec3_origin, stop, true, ent);
if(trace.fraction == 1.0)
return false;
mid = bottom = trace.endpos[2];
// the corners must be within 16 of the midpoint
for(x = 0 ; x <= 1 ; x++)
for(y = 0 ; y <= 1 ; y++)
{
start[0] = stop[0] = x ? maxs[0] : mins[0];
start[1] = stop[1] = y ? maxs[1] : mins[1];
trace = SV_Move(start, vec3_origin, vec3_origin, stop, true, ent);
if(trace.fraction != 1.0 && trace.endpos[2] > bottom)
bottom = trace.endpos[2];
if(trace.fraction == 1.0 || mid - trace.endpos[2] > STEPSIZE)
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 trace_normal is set to the
normal of the blocking wall
=============
*/
bool SV_movestep(edict_t *ent, vec3_t move, bool relink)
{
float dz;
vec3_t oldorg, neworg, end;
trace_t trace;
int32_t i;
edict_t *enemy;
// try the move
VectorCopy(ED_Vector(ent, ED_origin), oldorg);
VectorAdd(ED_Vector(ent, ED_origin), move, neworg);
// flying monsters don't step up
if((int32_t)ED_Float(ent, ED_flags) & (FL_SWIM | FL_FLY))
{
// try one move with vertical motion, then one without
for(i = 0 ; i < 2 ; i++)
{
VectorAdd(ED_Vector(ent, ED_origin), move, neworg);
enemy = ProgEdict(ED_PEdict(ent, ED_enemy));
if(i == 0 && enemy != sv.edicts)
{
dz = ED_Vector(ent, ED_origin)[2] - ED_Vector(ProgEdict(ED_PEdict(ent, ED_enemy)), ED_origin)[2];
if(dz > 40)
neworg[2] -= 8;
if(dz < 30)
neworg[2] += 8;
}
trace = SV_Move(ED_Vector(ent, ED_origin), ED_Vector(ent, ED_mins), ED_Vector(ent, ED_maxs), neworg, false, ent);
if(trace.fraction == 1)
{
if(((int32_t)ED_Float(ent, ED_flags) & FL_SWIM) && SV_PointContents(trace.endpos) == CONTENTS_EMPTY)
return false; // swim monster left water
VectorCopy(trace.endpos, ED_Vector(ent, ED_origin));
if(relink)
SV_LinkEdict(ent, true);
return true;
}
if(enemy == sv.edicts)
break;
}
return false;
}
// push down from a step height above the wished position
neworg[2] += STEPSIZE;
VectorCopy(neworg, end);
end[2] -= STEPSIZE * 2;
trace = SV_Move(neworg, ED_Vector(ent, ED_mins), ED_Vector(ent, ED_maxs), end, false, ent);
if(trace.allsolid)
return false;
if(trace.startsolid)
{
neworg[2] -= STEPSIZE;
trace = SV_Move(neworg, ED_Vector(ent, ED_mins), ED_Vector(ent, ED_maxs), end, false, ent);
if(trace.allsolid || trace.startsolid)
return false;
}
if(trace.fraction == 1)
{
// if monster had the ground pulled out, go ahead and fall
if((int32_t)ED_Float(ent, ED_flags) & FL_PARTIALGROUND)
{
VectorAdd(ED_Vector(ent, ED_origin), move, ED_Vector(ent, ED_origin));
if(relink)
SV_LinkEdict(ent, true);
ED_Float(ent, ED_flags) = (int32_t)ED_Float(ent, ED_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, ED_Vector(ent, ED_origin));
if(!SV_CheckBottom(ent))
{
if((int32_t)ED_Float(ent, ED_flags) & FL_PARTIALGROUND)
{
// entity had floor mostly pulled out from underneath it
// and is trying to correct
if(relink)
SV_LinkEdict(ent, true);
return true;
}
VectorCopy(oldorg, ED_Vector(ent, ED_origin));
return false;
}
if((int32_t)ED_Float(ent, ED_flags) & FL_PARTIALGROUND)
{
// Con_Printf ("back on ground\n");
ED_Float(ent, ED_flags) = (int32_t)ED_Float(ent, ED_flags) & ~FL_PARTIALGROUND;
}
ED_PEdict(ent, ED_groundentity) = EdictProg(trace.ent);
// the move is ok
if(relink)
SV_LinkEdict(ent, true);
return true;
}
//============================================================================
/*
======================
SV_StepDirection
Turns to the movement direction, and walks the current distance if
facing it.
======================
*/
void PF_changeyaw(void);
bool SV_StepDirection(edict_t *ent, float yaw, float dist)
{
vec3_t move, oldorigin;
float delta;
ED_Float(ent, ED_ideal_yaw) = yaw;
PF_changeyaw();
yaw = yaw * PI * 2 / 360;
move[0] = cos(yaw) * dist;
move[1] = sin(yaw) * dist;
move[2] = 0;
VectorCopy(ED_Vector(ent, ED_origin), oldorigin);
if(SV_movestep(ent, move, false))
{
delta = ED_Vector(ent, ED_angles)[YAW] - ED_Float(ent, ED_ideal_yaw);
if(delta > 45 && delta < 315)
{
// not turned far enough, so don't take the step
VectorCopy(oldorigin, ED_Vector(ent, ED_origin));
}
SV_LinkEdict(ent, true);
return true;
}
SV_LinkEdict(ent, true);
return false;
}
/*
======================
SV_FixCheckBottom
======================
*/
void SV_FixCheckBottom(edict_t *ent)
{
// Con_Printf ("SV_FixCheckBottom\n");
ED_Float(ent, ED_flags) = (int32_t)ED_Float(ent, ED_flags) | FL_PARTIALGROUND;
}
/*
================
SV_NewChaseDir
================
*/
#define DI_NODIR -1
void SV_NewChaseDir(edict_t *actor, edict_t *enemy, float dist)
{
float deltax, deltay;
float d[3];
float tdir, olddir, turnaround;
olddir = anglemod((int32_t)(ED_Float(actor, ED_ideal_yaw) / 45) * 45);
turnaround = anglemod(olddir - 180);
deltax = ED_Vector(enemy, ED_origin)[0] - ED_Vector(actor, ED_origin)[0];
deltay = ED_Vector(enemy, ED_origin)[1] - ED_Vector(actor, ED_origin)[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 && SV_StepDirection(actor, tdir, dist))
return;
}
// try other directions
if(((rand() & 3) & 1) || abs((int32_t)deltay) > abs((int32_t)deltax)) // ericw -- explicit int32_t cast to suppress clang suggestion to use fabsf
{
tdir = d[1];
d[1] = d[2];
d[2] = tdir;
}
if(d[1] != DI_NODIR && d[1] != turnaround
&& SV_StepDirection(actor, d[1], dist))
return;
if(d[2] != DI_NODIR && d[2] != turnaround
&& SV_StepDirection(actor, d[2], dist))
return;
/* there is no direct path to the player, so pick another direction */
if(olddir != DI_NODIR && SV_StepDirection(actor, olddir, dist))
return;
if(rand() & 1) /*randomly determine direction of search*/
{
for(tdir = 0 ; tdir <= 315 ; tdir += 45)
if(tdir != turnaround && SV_StepDirection(actor, tdir, dist))
return;
}
else
{
for(tdir = 315 ; tdir >= 0 ; tdir -= 45)
if(tdir != turnaround && SV_StepDirection(actor, tdir, dist))
return;
}
if(turnaround != DI_NODIR && SV_StepDirection(actor, turnaround, dist))
return;
ED_Float(actor, ED_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(!SV_CheckBottom(actor))
SV_FixCheckBottom(actor);
}
/*
======================
SV_CloseEnough
======================
*/
bool SV_CloseEnough(edict_t *ent, edict_t *goal, float dist)
{
int32_t i;
for(i = 0 ; i < 3 ; i++)
{
if(ED_Vector(goal, ED_absmin)[i] > ED_Vector(ent, ED_absmax)[i] + dist)
return false;
if(ED_Vector(goal, ED_absmax)[i] < ED_Vector(ent, ED_absmin)[i] - dist)
return false;
}
return true;
}
/*
======================
SV_MoveToGoal
======================
*/
void SV_MoveToGoal(void)
{
edict_t *ent, *goal;
float dist;
ent = ProgEdict(G_PEdict(GBL_self));
goal = ProgEdict(ED_PEdict(ent, ED_goalentity));
dist = G_Float(GBL_PARM0);
if(!((int32_t)ED_Float(ent, ED_flags) & (FL_ONGROUND | FL_FLY | FL_SWIM)))
{
G_Float(GBL_RETURN) = 0;
return;
}
// if the next step hits the enemy, return immediately
if(ProgEdict(ED_PEdict(ent, ED_enemy)) != sv.edicts && SV_CloseEnough(ent, goal, dist))
return;
// bump around...
if((rand() & 3) == 1 ||
!SV_StepDirection(ent, ED_Float(ent, ED_ideal_yaw), dist))
{
SV_NewChaseDir(ent, goal, dist);
}
}