You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
961 lines
21 KiB
961 lines
21 KiB
/* |
|
Copyright (C) 1997-2001 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. |
|
|
|
*/ |
|
// g_phys.c |
|
|
|
#include "g_local.h" |
|
|
|
/* |
|
|
|
|
|
pushmove objects do not obey gravity, and do not interact with each other or trigger fields, but block normal movement and push normal objects when they move. |
|
|
|
onground is set for toss objects when they come to a complete rest. it is set for steping or walking objects |
|
|
|
doors, plats, etc are SOLID_BSP, and MOVETYPE_PUSH |
|
bonus items are SOLID_TRIGGER touch, and MOVETYPE_TOSS |
|
corpses are SOLID_NOT and MOVETYPE_TOSS |
|
crates are SOLID_BBOX and MOVETYPE_TOSS |
|
walking monsters are SOLID_SLIDEBOX and MOVETYPE_STEP |
|
flying/floating monsters are SOLID_SLIDEBOX and MOVETYPE_FLY |
|
|
|
solid_edge items only clip against bsp models. |
|
|
|
*/ |
|
|
|
|
|
/* |
|
============ |
|
SV_TestEntityPosition |
|
|
|
============ |
|
*/ |
|
edict_t *SV_TestEntityPosition (edict_t *ent) |
|
{ |
|
trace_t trace; |
|
int mask; |
|
|
|
if (ent->clipmask) |
|
mask = ent->clipmask; |
|
else |
|
mask = MASK_SOLID; |
|
trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, ent->s.origin, ent, mask); |
|
|
|
if (trace.startsolid) |
|
return g_edicts; |
|
|
|
return NULL; |
|
} |
|
|
|
|
|
/* |
|
================ |
|
SV_CheckVelocity |
|
================ |
|
*/ |
|
void SV_CheckVelocity (edict_t *ent) |
|
{ |
|
int i; |
|
|
|
// |
|
// bound velocity |
|
// |
|
for (i=0 ; i<3 ; i++) |
|
{ |
|
if (ent->velocity[i] > sv_maxvelocity->value) |
|
ent->velocity[i] = sv_maxvelocity->value; |
|
else if (ent->velocity[i] < -sv_maxvelocity->value) |
|
ent->velocity[i] = -sv_maxvelocity->value; |
|
} |
|
} |
|
|
|
/* |
|
============= |
|
SV_RunThink |
|
|
|
Runs thinking code for this frame if necessary |
|
============= |
|
*/ |
|
qboolean SV_RunThink (edict_t *ent) |
|
{ |
|
float thinktime; |
|
|
|
thinktime = ent->nextthink; |
|
if (thinktime <= 0) |
|
return true; |
|
if (thinktime > level.time+0.001) |
|
return true; |
|
|
|
ent->nextthink = 0; |
|
if (!ent->think) |
|
gi.error ("NULL ent->think"); |
|
ent->think (ent); |
|
|
|
return false; |
|
} |
|
|
|
/* |
|
================== |
|
SV_Impact |
|
|
|
Two entities have touched, so run their touch functions |
|
================== |
|
*/ |
|
void SV_Impact (edict_t *e1, trace_t *trace) |
|
{ |
|
edict_t *e2; |
|
// cplane_t backplane; |
|
|
|
e2 = trace->ent; |
|
|
|
if (e1->touch && e1->solid != SOLID_NOT) |
|
e1->touch (e1, e2, &trace->plane, trace->surface); |
|
|
|
if (e2->touch && e2->solid != SOLID_NOT) |
|
e2->touch (e2, e1, NULL, NULL); |
|
} |
|
|
|
|
|
/* |
|
================== |
|
ClipVelocity |
|
|
|
Slide off of the impacting object |
|
returns the blocked flags (1 = floor, 2 = step / wall) |
|
================== |
|
*/ |
|
#define STOP_EPSILON 0.1 |
|
|
|
int ClipVelocity (vec3_t in, vec3_t normal, vec3_t out, float overbounce) |
|
{ |
|
float backoff; |
|
float change; |
|
int i, blocked; |
|
|
|
blocked = 0; |
|
if (normal[2] > 0) |
|
blocked |= 1; // floor |
|
if (!normal[2]) |
|
blocked |= 2; // step |
|
|
|
backoff = DotProduct (in, normal) * overbounce; |
|
|
|
for (i=0 ; i<3 ; i++) |
|
{ |
|
change = normal[i]*backoff; |
|
out[i] = in[i] - change; |
|
if (out[i] > -STOP_EPSILON && out[i] < STOP_EPSILON) |
|
out[i] = 0; |
|
} |
|
|
|
return blocked; |
|
} |
|
|
|
|
|
/* |
|
============ |
|
SV_FlyMove |
|
|
|
The basic solid body movement clip that slides along multiple planes |
|
Returns the clipflags if the velocity was modified (hit something solid) |
|
1 = floor |
|
2 = wall / step |
|
4 = dead stop |
|
============ |
|
*/ |
|
#define MAX_CLIP_PLANES 5 |
|
int SV_FlyMove (edict_t *ent, float time, int mask) |
|
{ |
|
edict_t *hit; |
|
int bumpcount, numbumps; |
|
vec3_t dir; |
|
float d; |
|
int numplanes; |
|
vec3_t planes[MAX_CLIP_PLANES]; |
|
vec3_t primal_velocity, original_velocity, new_velocity; |
|
int i, j; |
|
trace_t trace; |
|
vec3_t end; |
|
float time_left; |
|
int blocked; |
|
|
|
numbumps = 4; |
|
|
|
blocked = 0; |
|
VectorCopy (ent->velocity, original_velocity); |
|
VectorCopy (ent->velocity, primal_velocity); |
|
numplanes = 0; |
|
|
|
time_left = time; |
|
|
|
ent->groundentity = NULL; |
|
for (bumpcount=0 ; bumpcount<numbumps ; bumpcount++) |
|
{ |
|
for (i=0 ; i<3 ; i++) |
|
end[i] = ent->s.origin[i] + time_left * ent->velocity[i]; |
|
|
|
trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, end, ent, mask); |
|
|
|
if (trace.allsolid) |
|
{ // entity is trapped in another solid |
|
VectorCopy (vec3_origin, ent->velocity); |
|
return 3; |
|
} |
|
|
|
if (trace.fraction > 0) |
|
{ // actually covered some distance |
|
VectorCopy (trace.endpos, ent->s.origin); |
|
VectorCopy (ent->velocity, original_velocity); |
|
numplanes = 0; |
|
} |
|
|
|
if (trace.fraction == 1) |
|
break; // moved the entire distance |
|
|
|
hit = trace.ent; |
|
|
|
if (trace.plane.normal[2] > 0.7) |
|
{ |
|
blocked |= 1; // floor |
|
if ( hit->solid == SOLID_BSP) |
|
{ |
|
ent->groundentity = hit; |
|
ent->groundentity_linkcount = hit->linkcount; |
|
} |
|
} |
|
if (!trace.plane.normal[2]) |
|
{ |
|
blocked |= 2; // step |
|
} |
|
|
|
// |
|
// run the impact function |
|
// |
|
SV_Impact (ent, &trace); |
|
if (!ent->inuse) |
|
break; // removed by the impact function |
|
|
|
|
|
time_left -= time_left * trace.fraction; |
|
|
|
// cliped to another plane |
|
if (numplanes >= MAX_CLIP_PLANES) |
|
{ // this shouldn't really happen |
|
VectorCopy (vec3_origin, ent->velocity); |
|
return 3; |
|
} |
|
|
|
VectorCopy (trace.plane.normal, planes[numplanes]); |
|
numplanes++; |
|
|
|
// |
|
// modify original_velocity so it parallels all of the clip planes |
|
// |
|
for (i=0 ; i<numplanes ; i++) |
|
{ |
|
ClipVelocity (original_velocity, planes[i], new_velocity, 1); |
|
|
|
for (j=0 ; j<numplanes ; j++) |
|
if ((j != i) && !VectorCompare (planes[i], planes[j])) |
|
{ |
|
if (DotProduct (new_velocity, planes[j]) < 0) |
|
break; // not ok |
|
} |
|
if (j == numplanes) |
|
break; |
|
} |
|
|
|
if (i != numplanes) |
|
{ // go along this plane |
|
VectorCopy (new_velocity, ent->velocity); |
|
} |
|
else |
|
{ // go along the crease |
|
if (numplanes != 2) |
|
{ |
|
// gi.dprintf ("clip velocity, numplanes == %i\n",numplanes); |
|
VectorCopy (vec3_origin, ent->velocity); |
|
return 7; |
|
} |
|
CrossProduct (planes[0], planes[1], dir); |
|
d = DotProduct (dir, ent->velocity); |
|
VectorScale (dir, d, ent->velocity); |
|
} |
|
|
|
// |
|
// if original velocity is against the original velocity, stop dead |
|
// to avoid tiny occilations in sloping corners |
|
// |
|
if (DotProduct (ent->velocity, primal_velocity) <= 0) |
|
{ |
|
VectorCopy (vec3_origin, ent->velocity); |
|
return blocked; |
|
} |
|
} |
|
|
|
return blocked; |
|
} |
|
|
|
|
|
/* |
|
============ |
|
SV_AddGravity |
|
|
|
============ |
|
*/ |
|
void SV_AddGravity (edict_t *ent) |
|
{ |
|
ent->velocity[2] -= ent->gravity * sv_gravity->value * FRAMETIME; |
|
} |
|
|
|
/* |
|
=============================================================================== |
|
|
|
PUSHMOVE |
|
|
|
=============================================================================== |
|
*/ |
|
|
|
/* |
|
============ |
|
SV_PushEntity |
|
|
|
Does not change the entities velocity at all |
|
============ |
|
*/ |
|
trace_t SV_PushEntity (edict_t *ent, vec3_t push) |
|
{ |
|
trace_t trace; |
|
vec3_t start; |
|
vec3_t end; |
|
int mask; |
|
|
|
VectorCopy (ent->s.origin, start); |
|
VectorAdd (start, push, end); |
|
|
|
retry: |
|
if (ent->clipmask) |
|
mask = ent->clipmask; |
|
else |
|
mask = MASK_SOLID; |
|
|
|
trace = gi.trace (start, ent->mins, ent->maxs, end, ent, mask); |
|
|
|
VectorCopy (trace.endpos, ent->s.origin); |
|
gi.linkentity (ent); |
|
|
|
if (trace.fraction != 1.0) |
|
{ |
|
SV_Impact (ent, &trace); |
|
|
|
// if the pushed entity went away and the pusher is still there |
|
if (!trace.ent->inuse && ent->inuse) |
|
{ |
|
// move the pusher back and try again |
|
VectorCopy (start, ent->s.origin); |
|
gi.linkentity (ent); |
|
goto retry; |
|
} |
|
} |
|
|
|
if (ent->inuse) |
|
G_TouchTriggers (ent); |
|
|
|
return trace; |
|
} |
|
|
|
|
|
typedef struct |
|
{ |
|
edict_t *ent; |
|
vec3_t origin; |
|
vec3_t angles; |
|
float deltayaw; |
|
} pushed_t; |
|
pushed_t pushed[MAX_EDICTS], *pushed_p; |
|
|
|
edict_t *obstacle; |
|
|
|
/* |
|
============ |
|
SV_Push |
|
|
|
Objects need to be moved back on a failed push, |
|
otherwise riders would continue to slide. |
|
============ |
|
*/ |
|
qboolean SV_Push (edict_t *pusher, vec3_t move, vec3_t amove) |
|
{ |
|
int i, e; |
|
edict_t *check, *block; |
|
vec3_t mins, maxs; |
|
pushed_t *p; |
|
vec3_t org, org2, move2, forward, right, up; |
|
|
|
// clamp the move to 1/8 units, so the position will |
|
// be accurate for client side prediction |
|
for (i=0 ; i<3 ; i++) |
|
{ |
|
float temp; |
|
temp = move[i]*8.0; |
|
if (temp > 0.0) |
|
temp += 0.5; |
|
else |
|
temp -= 0.5; |
|
move[i] = 0.125 * (int)temp; |
|
} |
|
|
|
// find the bounding box |
|
for (i=0 ; i<3 ; i++) |
|
{ |
|
mins[i] = pusher->absmin[i] + move[i]; |
|
maxs[i] = pusher->absmax[i] + move[i]; |
|
} |
|
|
|
// we need this for pushing things later |
|
VectorSubtract (vec3_origin, amove, org); |
|
AngleVectors (org, forward, right, up); |
|
|
|
// save the pusher's original position |
|
pushed_p->ent = pusher; |
|
VectorCopy (pusher->s.origin, pushed_p->origin); |
|
VectorCopy (pusher->s.angles, pushed_p->angles); |
|
if (pusher->client) |
|
pushed_p->deltayaw = pusher->client->ps.pmove.delta_angles[YAW]; |
|
pushed_p++; |
|
|
|
// move the pusher to it's final position |
|
VectorAdd (pusher->s.origin, move, pusher->s.origin); |
|
VectorAdd (pusher->s.angles, amove, pusher->s.angles); |
|
gi.linkentity (pusher); |
|
|
|
// see if any solid entities are inside the final position |
|
check = g_edicts+1; |
|
for (e = 1; e < globals.num_edicts; e++, check++) |
|
{ |
|
if (!check->inuse) |
|
continue; |
|
if (check->movetype == MOVETYPE_PUSH |
|
|| check->movetype == MOVETYPE_STOP |
|
|| check->movetype == MOVETYPE_NONE |
|
|| check->movetype == MOVETYPE_NOCLIP) |
|
continue; |
|
|
|
if (!check->area.prev) |
|
continue; // not linked in anywhere |
|
|
|
// if the entity is standing on the pusher, it will definitely be moved |
|
if (check->groundentity != pusher) |
|
{ |
|
// see if the ent needs to be tested |
|
if ( check->absmin[0] >= maxs[0] |
|
|| check->absmin[1] >= maxs[1] |
|
|| check->absmin[2] >= maxs[2] |
|
|| check->absmax[0] <= mins[0] |
|
|| check->absmax[1] <= mins[1] |
|
|| check->absmax[2] <= mins[2] ) |
|
continue; |
|
|
|
// see if the ent's bbox is inside the pusher's final position |
|
if (!SV_TestEntityPosition (check)) |
|
continue; |
|
} |
|
|
|
if ((pusher->movetype == MOVETYPE_PUSH) || (check->groundentity == pusher)) |
|
{ |
|
// move this entity |
|
pushed_p->ent = check; |
|
VectorCopy (check->s.origin, pushed_p->origin); |
|
VectorCopy (check->s.angles, pushed_p->angles); |
|
pushed_p++; |
|
|
|
// try moving the contacted entity |
|
VectorAdd (check->s.origin, move, check->s.origin); |
|
if (check->client) |
|
{ // FIXME: doesn't rotate monsters? |
|
check->client->ps.pmove.delta_angles[YAW] += amove[YAW]; |
|
} |
|
|
|
// figure movement due to the pusher's amove |
|
VectorSubtract (check->s.origin, pusher->s.origin, org); |
|
org2[0] = DotProduct (org, forward); |
|
org2[1] = -DotProduct (org, right); |
|
org2[2] = DotProduct (org, up); |
|
VectorSubtract (org2, org, move2); |
|
VectorAdd (check->s.origin, move2, check->s.origin); |
|
|
|
// may have pushed them off an edge |
|
if (check->groundentity != pusher) |
|
check->groundentity = NULL; |
|
|
|
block = SV_TestEntityPosition (check); |
|
if (!block) |
|
{ // pushed ok |
|
gi.linkentity (check); |
|
// impact? |
|
continue; |
|
} |
|
|
|
// if it is ok to leave in the old position, do it |
|
// this is only relevent for riding entities, not pushed |
|
// FIXME: this doesn't acount for rotation |
|
VectorSubtract (check->s.origin, move, check->s.origin); |
|
block = SV_TestEntityPosition (check); |
|
if (!block) |
|
{ |
|
pushed_p--; |
|
continue; |
|
} |
|
} |
|
|
|
// save off the obstacle so we can call the block function |
|
obstacle = check; |
|
|
|
// move back any entities we already moved |
|
// go backwards, so if the same entity was pushed |
|
// twice, it goes back to the original position |
|
for (p=pushed_p-1 ; p>=pushed ; p--) |
|
{ |
|
VectorCopy (p->origin, p->ent->s.origin); |
|
VectorCopy (p->angles, p->ent->s.angles); |
|
if (p->ent->client) |
|
{ |
|
p->ent->client->ps.pmove.delta_angles[YAW] = p->deltayaw; |
|
} |
|
gi.linkentity (p->ent); |
|
} |
|
return false; |
|
} |
|
|
|
//FIXME: is there a better way to handle this? |
|
// see if anything we moved has touched a trigger |
|
for (p=pushed_p-1 ; p>=pushed ; p--) |
|
G_TouchTriggers (p->ent); |
|
|
|
return true; |
|
} |
|
|
|
/* |
|
================ |
|
SV_Physics_Pusher |
|
|
|
Bmodel objects don't interact with each other, but |
|
push all box objects |
|
================ |
|
*/ |
|
void SV_Physics_Pusher (edict_t *ent) |
|
{ |
|
vec3_t move, amove; |
|
edict_t *part, *mv; |
|
|
|
// if not a team captain, so movement will be handled elsewhere |
|
if ( ent->flags & FL_TEAMSLAVE) |
|
return; |
|
|
|
// make sure all team slaves can move before commiting |
|
// any moves or calling any think functions |
|
// if the move is blocked, all moved objects will be backed out |
|
//retry: |
|
pushed_p = pushed; |
|
for (part = ent ; part ; part=part->teamchain) |
|
{ |
|
if (part->velocity[0] || part->velocity[1] || part->velocity[2] || |
|
part->avelocity[0] || part->avelocity[1] || part->avelocity[2] |
|
) |
|
{ // object is moving |
|
VectorScale (part->velocity, FRAMETIME, move); |
|
VectorScale (part->avelocity, FRAMETIME, amove); |
|
|
|
if (!SV_Push (part, move, amove)) |
|
break; // move was blocked |
|
} |
|
} |
|
if (pushed_p > &pushed[MAX_EDICTS]) |
|
gi.error (ERR_FATAL, "pushed_p > &pushed[MAX_EDICTS], memory corrupted"); |
|
|
|
if (part) |
|
{ |
|
// the move failed, bump all nextthink times and back out moves |
|
for (mv = ent ; mv ; mv=mv->teamchain) |
|
{ |
|
if (mv->nextthink > 0) |
|
mv->nextthink += FRAMETIME; |
|
} |
|
|
|
// if the pusher has a "blocked" function, call it |
|
// otherwise, just stay in place until the obstacle is gone |
|
if (part->blocked) |
|
part->blocked (part, obstacle); |
|
#if 0 |
|
// if the pushed entity went away and the pusher is still there |
|
if (!obstacle->inuse && part->inuse) |
|
goto retry; |
|
#endif |
|
} |
|
else |
|
{ |
|
// the move succeeded, so call all think functions |
|
for (part = ent ; part ; part=part->teamchain) |
|
{ |
|
SV_RunThink (part); |
|
} |
|
} |
|
} |
|
|
|
//================================================================== |
|
|
|
/* |
|
============= |
|
SV_Physics_None |
|
|
|
Non moving objects can only think |
|
============= |
|
*/ |
|
void SV_Physics_None (edict_t *ent) |
|
{ |
|
// regular thinking |
|
SV_RunThink (ent); |
|
} |
|
|
|
/* |
|
============= |
|
SV_Physics_Noclip |
|
|
|
A moving object that doesn't obey physics |
|
============= |
|
*/ |
|
void SV_Physics_Noclip (edict_t *ent) |
|
{ |
|
// regular thinking |
|
if (!SV_RunThink (ent)) |
|
return; |
|
|
|
VectorMA (ent->s.angles, FRAMETIME, ent->avelocity, ent->s.angles); |
|
VectorMA (ent->s.origin, FRAMETIME, ent->velocity, ent->s.origin); |
|
|
|
gi.linkentity (ent); |
|
} |
|
|
|
/* |
|
============================================================================== |
|
|
|
TOSS / BOUNCE |
|
|
|
============================================================================== |
|
*/ |
|
|
|
/* |
|
============= |
|
SV_Physics_Toss |
|
|
|
Toss, bounce, and fly movement. When onground, do nothing. |
|
============= |
|
*/ |
|
void SV_Physics_Toss (edict_t *ent) |
|
{ |
|
trace_t trace; |
|
vec3_t move; |
|
float backoff; |
|
edict_t *slave; |
|
qboolean wasinwater; |
|
qboolean isinwater; |
|
vec3_t old_origin; |
|
|
|
// regular thinking |
|
SV_RunThink (ent); |
|
|
|
// if not a team captain, so movement will be handled elsewhere |
|
if ( ent->flags & FL_TEAMSLAVE) |
|
return; |
|
|
|
if (ent->velocity[2] > 0) |
|
ent->groundentity = NULL; |
|
|
|
// check for the groundentity going away |
|
if (ent->groundentity) |
|
if (!ent->groundentity->inuse) |
|
ent->groundentity = NULL; |
|
|
|
// if onground, return without moving |
|
if ( ent->groundentity ) |
|
return; |
|
|
|
VectorCopy (ent->s.origin, old_origin); |
|
|
|
SV_CheckVelocity (ent); |
|
|
|
// add gravity |
|
if (ent->movetype != MOVETYPE_FLY |
|
&& ent->movetype != MOVETYPE_FLYMISSILE) |
|
SV_AddGravity (ent); |
|
|
|
// move angles |
|
VectorMA (ent->s.angles, FRAMETIME, ent->avelocity, ent->s.angles); |
|
|
|
// move origin |
|
VectorScale (ent->velocity, FRAMETIME, move); |
|
trace = SV_PushEntity (ent, move); |
|
if (!ent->inuse) |
|
return; |
|
|
|
if (trace.fraction < 1) |
|
{ |
|
if (ent->movetype == MOVETYPE_BOUNCE) |
|
backoff = 1.5; |
|
else |
|
backoff = 1; |
|
|
|
ClipVelocity (ent->velocity, trace.plane.normal, ent->velocity, backoff); |
|
|
|
// stop if on ground |
|
if (trace.plane.normal[2] > 0.7) |
|
{ |
|
if (ent->velocity[2] < 60 || ent->movetype != MOVETYPE_BOUNCE ) |
|
{ |
|
ent->groundentity = trace.ent; |
|
ent->groundentity_linkcount = trace.ent->linkcount; |
|
VectorCopy (vec3_origin, ent->velocity); |
|
VectorCopy (vec3_origin, ent->avelocity); |
|
} |
|
} |
|
|
|
// if (ent->touch) |
|
// ent->touch (ent, trace.ent, &trace.plane, trace.surface); |
|
} |
|
|
|
// check for water transition |
|
wasinwater = (ent->watertype & MASK_WATER); |
|
ent->watertype = gi.pointcontents (ent->s.origin); |
|
isinwater = ent->watertype & MASK_WATER; |
|
|
|
if (isinwater) |
|
ent->waterlevel = 1; |
|
else |
|
ent->waterlevel = 0; |
|
|
|
if (!wasinwater && isinwater) |
|
gi.positioned_sound (old_origin, g_edicts, CHAN_AUTO, gi.soundindex("misc/h2ohit1.wav"), 1, 1, 0); |
|
else if (wasinwater && !isinwater) |
|
gi.positioned_sound (ent->s.origin, g_edicts, CHAN_AUTO, gi.soundindex("misc/h2ohit1.wav"), 1, 1, 0); |
|
|
|
// move teamslaves |
|
for (slave = ent->teamchain; slave; slave = slave->teamchain) |
|
{ |
|
VectorCopy (ent->s.origin, slave->s.origin); |
|
gi.linkentity (slave); |
|
} |
|
} |
|
|
|
/* |
|
=============================================================================== |
|
|
|
STEPPING MOVEMENT |
|
|
|
=============================================================================== |
|
*/ |
|
|
|
/* |
|
============= |
|
SV_Physics_Step |
|
|
|
Monsters freefall when they don't have a ground entity, otherwise |
|
all movement is done with discrete steps. |
|
|
|
This is also used for objects that have become still on the ground, but |
|
will fall if the floor is pulled out from under them. |
|
FIXME: is this true? |
|
============= |
|
*/ |
|
|
|
//FIXME: hacked in for E3 demo |
|
#define sv_stopspeed 100 |
|
#define sv_friction 6 |
|
#define sv_waterfriction 1 |
|
|
|
void SV_AddRotationalFriction (edict_t *ent) |
|
{ |
|
int n; |
|
float adjustment; |
|
|
|
VectorMA (ent->s.angles, FRAMETIME, ent->avelocity, ent->s.angles); |
|
adjustment = FRAMETIME * sv_stopspeed * sv_friction; |
|
for (n = 0; n < 3; n++) |
|
{ |
|
if (ent->avelocity[n] > 0) |
|
{ |
|
ent->avelocity[n] -= adjustment; |
|
if (ent->avelocity[n] < 0) |
|
ent->avelocity[n] = 0; |
|
} |
|
else |
|
{ |
|
ent->avelocity[n] += adjustment; |
|
if (ent->avelocity[n] > 0) |
|
ent->avelocity[n] = 0; |
|
} |
|
} |
|
} |
|
|
|
void SV_Physics_Step (edict_t *ent) |
|
{ |
|
qboolean wasonground; |
|
qboolean hitsound = false; |
|
float *vel; |
|
float speed, newspeed, control; |
|
float friction; |
|
edict_t *groundentity; |
|
int mask; |
|
|
|
// airborn monsters should always check for ground |
|
if (!ent->groundentity) |
|
M_CheckGround (ent); |
|
|
|
groundentity = ent->groundentity; |
|
|
|
SV_CheckVelocity (ent); |
|
|
|
if (groundentity) |
|
wasonground = true; |
|
else |
|
wasonground = false; |
|
|
|
if (ent->avelocity[0] || ent->avelocity[1] || ent->avelocity[2]) |
|
SV_AddRotationalFriction (ent); |
|
|
|
// add gravity except: |
|
// flying monsters |
|
// swimming monsters who are in the water |
|
if (! wasonground) |
|
if (!(ent->flags & FL_FLY)) |
|
if (!((ent->flags & FL_SWIM) && (ent->waterlevel > 2))) |
|
{ |
|
if (ent->velocity[2] < sv_gravity->value*-0.1) |
|
hitsound = true; |
|
if (ent->waterlevel == 0) |
|
SV_AddGravity (ent); |
|
} |
|
|
|
// friction for flying monsters that have been given vertical velocity |
|
if ((ent->flags & FL_FLY) && (ent->velocity[2] != 0)) |
|
{ |
|
speed = fabs(ent->velocity[2]); |
|
control = speed < sv_stopspeed ? sv_stopspeed : speed; |
|
friction = sv_friction/3; |
|
newspeed = speed - (FRAMETIME * control * friction); |
|
if (newspeed < 0) |
|
newspeed = 0; |
|
newspeed /= speed; |
|
ent->velocity[2] *= newspeed; |
|
} |
|
|
|
// friction for flying monsters that have been given vertical velocity |
|
if ((ent->flags & FL_SWIM) && (ent->velocity[2] != 0)) |
|
{ |
|
speed = fabs(ent->velocity[2]); |
|
control = speed < sv_stopspeed ? sv_stopspeed : speed; |
|
newspeed = speed - (FRAMETIME * control * sv_waterfriction * ent->waterlevel); |
|
if (newspeed < 0) |
|
newspeed = 0; |
|
newspeed /= speed; |
|
ent->velocity[2] *= newspeed; |
|
} |
|
|
|
if (ent->velocity[2] || ent->velocity[1] || ent->velocity[0]) |
|
{ |
|
// apply friction |
|
// let dead monsters who aren't completely onground slide |
|
if ((wasonground) || (ent->flags & (FL_SWIM|FL_FLY))) |
|
if (!(ent->health <= 0.0 && !M_CheckBottom(ent))) |
|
{ |
|
vel = ent->velocity; |
|
speed = sqrt(vel[0]*vel[0] +vel[1]*vel[1]); |
|
if (speed) |
|
{ |
|
friction = sv_friction; |
|
|
|
control = speed < sv_stopspeed ? sv_stopspeed : speed; |
|
newspeed = speed - FRAMETIME*control*friction; |
|
|
|
if (newspeed < 0) |
|
newspeed = 0; |
|
newspeed /= speed; |
|
|
|
vel[0] *= newspeed; |
|
vel[1] *= newspeed; |
|
} |
|
} |
|
|
|
if (ent->svflags & SVF_MONSTER) |
|
mask = MASK_MONSTERSOLID; |
|
else |
|
mask = MASK_SOLID; |
|
SV_FlyMove (ent, FRAMETIME, mask); |
|
|
|
gi.linkentity (ent); |
|
G_TouchTriggers (ent); |
|
if (!ent->inuse) |
|
return; |
|
|
|
if (ent->groundentity) |
|
if (!wasonground) |
|
if (hitsound) |
|
gi.sound (ent, 0, gi.soundindex("world/land.wav"), 1, 1, 0); |
|
} |
|
|
|
// regular thinking |
|
SV_RunThink (ent); |
|
} |
|
|
|
//============================================================================ |
|
/* |
|
================ |
|
G_RunEntity |
|
|
|
================ |
|
*/ |
|
void G_RunEntity (edict_t *ent) |
|
{ |
|
if (ent->prethink) |
|
ent->prethink (ent); |
|
|
|
switch ( (int)ent->movetype) |
|
{ |
|
case MOVETYPE_PUSH: |
|
case MOVETYPE_STOP: |
|
SV_Physics_Pusher (ent); |
|
break; |
|
case MOVETYPE_NONE: |
|
SV_Physics_None (ent); |
|
break; |
|
case MOVETYPE_NOCLIP: |
|
SV_Physics_Noclip (ent); |
|
break; |
|
case MOVETYPE_STEP: |
|
SV_Physics_Step (ent); |
|
break; |
|
case MOVETYPE_TOSS: |
|
case MOVETYPE_BOUNCE: |
|
case MOVETYPE_FLY: |
|
case MOVETYPE_FLYMISSILE: |
|
SV_Physics_Toss (ent); |
|
break; |
|
default: |
|
gi.error ("SV_Physics: bad movetype %i", (int)ent->movetype); |
|
} |
|
}
|
|
|