#include "g_local.h"
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.cmodel_index, ent->s.origin, ent->mins, ent->maxs, ent->s.origin, ent, mask);
if(trace.startsolid)
return g_edicts;
return NULL;
}
void SV_CheckVelocity(edict_t *ent) {
int i;
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;
}
}
bool 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;
}
void SV_Impact(edict_t *e1, trace_t *trace) {
edict_t *e2;
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);
}
#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; if(!normal[2])
blocked |= 2;
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;
}
#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.cmodel_index, ent->s.origin, ent->mins, ent->maxs, end, ent, mask);
if(trace.allsolid) { VectorCopy(vec3_origin, ent->velocity);
return 3;
}
if(trace.fraction > 0) { VectorCopy(trace.endpos, ent->s.origin);
VectorCopy(ent->velocity, original_velocity);
numplanes = 0;
}
if(trace.fraction == 1)
break;
hit = trace.ent;
if(trace.plane.normal[2] > 0.7) {
blocked |= 1; if(hit->solid == SOLID_BSP) {
ent->groundentity = hit;
ent->groundentity_linkcount = hit->linkcount;
}
}
if(!trace.plane.normal[2]) {
blocked |= 2; }
SV_Impact(ent, &trace);
if(!ent->inuse)
break;
time_left -= time_left * trace.fraction;
if(numplanes >= MAX_CLIP_PLANES) { VectorCopy(vec3_origin, ent->velocity);
return 3;
}
VectorCopy(trace.plane.normal, planes[numplanes]);
numplanes++;
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; }
if(j == numplanes)
break;
}
if(i != numplanes) { VectorCopy(new_velocity, ent->velocity);
} else { if(numplanes != 2) {
VectorCopy(vec3_origin, ent->velocity);
return 7;
}
CrossProduct(planes[0], planes[1], dir);
d = DotProduct(dir, ent->velocity);
VectorScale(dir, d, ent->velocity);
}
if(DotProduct(ent->velocity, primal_velocity) <= 0) {
VectorCopy(vec3_origin, ent->velocity);
return blocked;
}
}
return blocked;
}
void SV_AddGravity(edict_t *ent) { ent->velocity[2] -= ent->gravity * sv_gravity->value * FRAMETIME; }
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(ent->s.cmodel_index, 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(!trace.ent->inuse && ent->inuse) {
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;
bool 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;
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;
}
for(i = 0; i < 3; i++) {
mins[i] = pusher->absmin[i] + move[i];
maxs[i] = pusher->absmax[i] + move[i];
}
VectorSubtract(vec3_origin, amove, org);
AngleVectors(org, forward, right, up);
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++;
VectorAdd(pusher->s.origin, move, pusher->s.origin);
VectorAdd(pusher->s.angles, amove, pusher->s.angles);
gi.linkentity(pusher);
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;
if(check->groundentity != pusher) {
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;
if(!SV_TestEntityPosition(check))
continue;
}
if((pusher->movetype == MOVETYPE_PUSH) || (check->groundentity == pusher)) {
pushed_p->ent = check;
VectorCopy(check->s.origin, pushed_p->origin);
VectorCopy(check->s.angles, pushed_p->angles);
pushed_p++;
VectorAdd(check->s.origin, move, check->s.origin);
if(check->client) { check->client->ps.pmove.delta_angles[YAW] += amove[YAW];
}
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);
if(check->groundentity != pusher)
check->groundentity = NULL;
block = SV_TestEntityPosition(check);
if(!block) { gi.linkentity(check);
continue;
}
VectorSubtract(check->s.origin, move, check->s.origin);
block = SV_TestEntityPosition(check);
if(!block) {
pushed_p--;
continue;
}
}
obstacle = check;
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;
}
for(p = pushed_p - 1; p >= pushed; p--)
G_TouchTriggers(p->ent);
return true;
}
void SV_Physics_Pusher(edict_t *ent) {
vec3_t move, amove;
edict_t *part, *mv;
if(ent->flags & FL_TEAMSLAVE)
return;
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]) { VectorScale(part->velocity, FRAMETIME, move);
VectorScale(part->avelocity, FRAMETIME, amove);
if(!SV_Push(part, move, amove))
break; }
}
if(pushed_p > &pushed[MAX_EDICTS])
gi.error(ERR_FATAL, "pushed_p > &pushed[MAX_EDICTS], memory corrupted");
if(part) {
for(mv = ent; mv; mv = mv->teamchain) {
if(mv->nextthink > 0)
mv->nextthink += FRAMETIME;
}
if(part->blocked)
part->blocked(part, obstacle);
#if 0#endif
} else {
for(part = ent; part; part = part->teamchain) {
SV_RunThink(part);
}
}
}
void SV_Physics_None(edict_t *ent) {
SV_RunThink(ent);
}
void SV_Physics_Noclip(edict_t *ent) {
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);
}
void SV_Physics_Toss(edict_t *ent) {
trace_t trace;
vec3_t move;
float backoff;
edict_t *slave;
bool wasinwater;
bool isinwater;
vec3_t old_origin;
SV_RunThink(ent);
if(ent->flags & FL_TEAMSLAVE)
return;
if(ent->velocity[2] > 0)
ent->groundentity = NULL;
if(ent->groundentity)
if(!ent->groundentity->inuse)
ent->groundentity = NULL;
if(ent->groundentity)
return;
VectorCopy(ent->s.origin, old_origin);
SV_CheckVelocity(ent);
if(ent->movetype != MOVETYPE_FLY && ent->movetype != MOVETYPE_FLYMISSILE)
SV_AddGravity(ent);
VectorMA(ent->s.angles, FRAMETIME, ent->avelocity, ent->s.angles);
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);
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);
}
}
}
wasinwater = (ent->watertype & MASK_WATER);
ent->watertype = gi.pointcontents(ent->s.cmodel_index, 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);
for(slave = ent->teamchain; slave; slave = slave->teamchain) {
VectorCopy(ent->s.origin, slave->s.origin);
gi.linkentity(slave);
}
}
#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) {
bool wasonground;
bool hitsound = false;
float *vel;
float speed, newspeed, control;
float friction;
edict_t *groundentity;
int mask;
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);
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);
}
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;
}
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]) {
if((wasonground) || (ent->flags & (FL_SWIM | FL_FLY)))
if(!(ent_read_health(ent)->value <= 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);
}
SV_RunThink(ent);
}
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);
}
}