/* 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->r.clipmask) mask = ent->r.clipmask; else mask = MASK_SOLID; trap_Trace (&trace, ent->s.origin, ent->r.mins, ent->r.maxs, ent->s.origin, ent, mask); if (trace.startsolid) return game.edicts; return NULL; } //================ //SV_CheckVelocity //================ void SV_CheckVelocity (edict_t *ent) { float scale; // // bound velocity // scale = VectorLength ( ent->velocity ); if ( (scale > g_maxvelocity->value) && (scale) ) { scale = g_maxvelocity->value / scale; VectorScale ( ent->velocity, scale, ent->velocity ); } } //============= //SV_RunThink // //Runs thinking code for this frame if necessary //============= qboolean SV_RunThink( edict_t *ent ) { unsigned int thinktime; thinktime = ent->nextthink; if (thinktime <= 0) return qtrue; if (thinktime > level.timemsec+1) return qtrue; ent->nextthink = 0; if (!ent->think) G_Error ("NULL ent->think"); ent->think (ent); return qfalse; } //================== //SV_Impact // //Two entities have touched, so run their touch functions //================== void SV_Impact(edict_t *e1, trace_t *trace) { edict_t *e2; e2 = &game.edicts[trace->ent]; if (e1->touch && e1->r.solid != SOLID_NOT) e1->touch (e1, e2, &trace->plane, trace->surfFlags); if (e2->touch && e2->r.solid != SOLID_NOT) e2->touch (e2, e1, NULL, 0); } //================== //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 ; bumpcounts.origin[i] + time_left * ent->velocity[i]; trap_Trace (&trace, ent->s.origin, ent->r.mins, ent->r.maxs, end, ent, mask); if (trace.allsolid) { // entity is trapped in another solid VectorClear (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 = &game.edicts[trace.ent]; if (trace.plane.normal[2] > 0.7) { blocked |= 1; // floor if (hit->r.solid == SOLID_BSP) { ent->groundentity = hit; ent->groundentity_linkcount = hit->r.linkcount; } } if (!trace.plane.normal[2]) { blocked |= 2; // step } // // run the impact function // SV_Impact (ent, &trace); if (!ent->r.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 VectorClear (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 ; ivelocity); } else { // go along the crease if (numplanes != 2) { VectorClear (ent->velocity); return 7; } CrossProduct (planes[0], planes[1], dir); VectorNormalize (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) { VectorClear (ent->velocity); return blocked; } } return blocked; } //============ //SV_AddGravity // //============ void SV_AddGravity(edict_t *ent) { ent->velocity[2] -= ent->gravity * g_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->r.clipmask) mask = ent->r.clipmask; else mask = MASK_SOLID; trap_Trace (&trace, start, ent->r.mins, ent->r.maxs, end, ent, mask); VectorCopy (trace.endpos, ent->s.origin); trap_LinkEntity (ent); if (trace.fraction != 1.0) { SV_Impact (ent, &trace); // if the pushed entity went away and the pusher is still there if (!game.edicts[trace.ent].r.inuse && ent->r.inuse) { // move the pusher back and try again VectorCopy (start, ent->s.origin); trap_LinkEntity (ent); goto retry; } } if (ent->r.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 axis[3]; vec3_t org, org2, move2; // 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]*16.0; move[i] = (1.0/16.0) * (int)temp; } // find the bounding box for (i=0 ; i<3 ; i++) { mins[i] = pusher->r.absmin[i] + move[i]; maxs[i] = pusher->r.absmax[i] + move[i]; } // we need this for pushing things later VectorNegate (amove, org); AnglesToAxis (org, axis); // 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->r.client) pushed_p->deltayaw = pusher->r.client->ps.pmove.delta_angles[YAW]; pushed_p++; // move the pusher to its final position VectorAdd (pusher->s.origin, move, pusher->s.origin); VectorAdd (pusher->s.angles, amove, pusher->s.angles); trap_LinkEntity (pusher); // see if any solid entities are inside the final position check = game.edicts+1; for (e = 1; e < game.numentities; e++, check++) { if (!check->r.inuse) continue; if (check->movetype == MOVETYPE_PUSH || check->movetype == MOVETYPE_STOP || check->movetype == MOVETYPE_NONE || check->movetype == MOVETYPE_NOCLIP) continue; if (!check->r.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->r.absmin[0] >= maxs[0] || check->r.absmin[1] >= maxs[1] || check->r.absmin[2] >= maxs[2] || check->r.absmax[0] <= mins[0] || check->r.absmax[1] <= mins[1] || check->r.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->r.client) { // FIXME: doesn't rotate monsters? check->r.client->ps.pmove.delta_angles[YAW] += amove[YAW]; } // figure movement due to the pusher's amove VectorSubtract (check->s.origin, pusher->s.origin, org); Matrix_TransformVector (axis, org, org2); 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 trap_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->r.client) { p->ent->r.client->ps.pmove.delta_angles[YAW] = p->deltayaw; } trap_LinkEntity (p->ent); } return qfalse; } //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 qtrue; } //================ //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]) G_Error ("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 += game.framemsec; } // 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 ); trap_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; float d; // 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->r.inuse ) ent->groundentity = NULL; // if onground, return without moving if( ent->groundentity ) return; VectorCopy( ent->s.origin, old_origin ); SV_CheckVelocity(ent); if(ent->accel != 0) { if(ent->accel < 0 && VectorLength(ent->velocity) < 50) { VectorClear(ent->velocity); } else { vec3_t acceldir; VectorNormalize2(ent->velocity, acceldir); VectorScale(acceldir, ent->accel*FRAMETIME, acceldir); VectorAdd(ent->velocity, acceldir, ent->velocity); } 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->r.inuse) return; if(trace.fraction < 1) { if( ent->movetype == MOVETYPE_BOUNCE ) backoff = 1.5; else if( ent->movetype == MOVETYPE_BOUNCEGRENADE ) { cvar_t *g_grenade_backoff = trap_Cvar_Get( "g_grenade_backoff", "1.6", CVAR_ARCHIVE ); backoff = g_grenade_backoff->value; } else backoff = 1; ClipVelocity(ent->velocity, trace.plane.normal, ent->velocity, backoff); // stop if on ground // LA: hopefully will fix grenades bouncing down slopes // method taken from Darkplaces sourcecode if(trace.plane.normal[2] > 0.7) { d = DotProduct(trace.plane.normal, ent->velocity); // wsw: Lardase fix for grenade bouncing in stairs if(fabs(d) < 60 || // was ent->velocity[2] < 60 (ent->movetype != MOVETYPE_BOUNCE && ent->movetype != MOVETYPE_BOUNCEGRENADE) ) { ent->groundentity = &game.edicts[trace.ent]; ent->groundentity_linkcount = ent->groundentity->r.linkcount; VectorClear(ent->velocity); VectorClear(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 = trap_PointContents (ent->s.origin); isinwater = ent->watertype & MASK_WATER; // never allow items in CONTENTS_NODROP if( ent->item && (ent->watertype & CONTENTS_NODROP) ) { // flags are important if( ent->item->type & IT_FLAG ) G_Gametype_CTF_ResetFlag(ent->s.team); G_FreeEdict(ent); return; } if(isinwater) ent->waterlevel = 1; else ent->waterlevel = 0; if(!wasinwater && isinwater) G_PositionedSound(old_origin, game.edicts, CHAN_AUTO, trap_SoundIndex(S_HIT_WATER), 1, 1); else if (wasinwater && !isinwater) G_PositionedSound(ent->s.origin, game.edicts, CHAN_AUTO, trap_SoundIndex(S_HIT_WATER), 1, 1); // move teamslaves for(slave = ent->teamchain; slave; slave = slave->teamchain) { VectorCopy(ent->s.origin, slave->s.origin); trap_LinkEntity(slave); } } //============================================================================ int AI_NPCPhysMove (edict_t *ent, float time, int mask, qboolean step, float bouncescale); void M_Phys_Momentum_AddPush( vec3_t accel, vec3_t pushdir, float push, float mass, float timestep ); void M_Phys_Momentum_AddFriction( float classfriction, float class_stopspeed, vec3_t origin, vec3_t vel, float timestep, vec3_t mins, vec3_t maxs, edict_t *passent, int solidmask ); void SV_Physics_Grenade( edict_t *self ) { int mask = MASK_SOLID; int move; float forwardaccel = 0; float sideaccel = 0; //float upaccel = 0; vec3_t forward, right, up; vec3_t vaccel; //vec3_t xzvelocity; //float speed; qboolean GroundEntity = qfalse; trace_t trace; vec3_t v1; //jalmoveme to defines or something //float RUNaccel = 90;//26 //float CLASS_MAX_velocity; float CLASS_TRACTION = 0.0f; float CLASS_FRICTION = 5.0f; float stopspeed, CLASS_STOPDECCEL = 0; float CLASS_BOUNCESCALE = 1.0f; // regular thinking SV_RunThink(self); //CLASS_MAX_velocity = ((firedef_t *)game.items[WEAP_GRENADELAUNCHER]->info)->speed; forwardaccel = 0.0f; sideaccel = 0.0f; VectorCopy( self->s.origin, self->s.old_origin ); if( self->r.clipmask ) mask = self->r.clipmask; //see if we are on floor (I don't trust ent->groundentity right now) VectorCopy( self->s.origin, v1 ); v1[2] -= 0.25f;//inside floor trap_Trace( &trace, self->s.origin, self->r.mins, self->r.maxs, v1, self, mask ); if( trace.fraction < 1.0f && trace.plane.normal[2] >= 0.7 ) { // We have ground entity GroundEntity = qtrue; } stopspeed = 0.0f; if( GroundEntity == qtrue ) //don't accelerate if not at floor { //intended accel forwardaccel *= CLASS_TRACTION; sideaccel *= CLASS_TRACTION; // if the client is NOT pushing movement keys, it's assumed that the // player will stop moving, not only by friction, but by using their legs to stop if( !forwardaccel && !sideaccel ) { if( self->velocity[0] || self->velocity[1] || self->velocity[2] ) { stopspeed = CLASS_STOPDECCEL * CLASS_TRACTION; } } } else { //air accel forwardaccel *= 0.05f; sideaccel *= 0.05f; } M_Phys_Momentum_AddFriction( CLASS_FRICTION, stopspeed, self->s.origin, self->velocity, FRAMETIME, self->r.mins, self->r.maxs, self, mask ); //calculate accel VectorClear( vaccel ); AngleVectors( tv( 0, self->s.angles[YAW], 0), forward, right, up); M_Phys_Momentum_AddPush( vaccel, forward, forwardaccel, self->mass, FRAMETIME ); M_Phys_Momentum_AddPush( vaccel, right, sideaccel, self->mass, FRAMETIME ); //gravity if( GroundEntity == qtrue ) { if( self->velocity[2] < 0.0f ) self->velocity[2] = 0.0f; } else { if( self->velocity[2] > -g_maxvelocity->value ) self->velocity[2] -= self->gravity * g_gravity->value * FRAMETIME; } //add momentum to velocity VectorAdd( self->velocity, vaccel, self->velocity ); /* //speed limit xzvelocity[0] = self->velocity[0]; xzvelocity[1] = self->velocity[1]; xzvelocity[2] = 0.0f; speed = VectorLength ( xzvelocity ); if( speed > CLASS_MAX_velocity ) { speed = CLASS_MAX_velocity / speed; self->velocity[0] *= speed; self->velocity[1] *= speed; }*/ //move move = AI_NPCPhysMove( self, FRAMETIME, mask, qtrue, CLASS_BOUNCESCALE ); if( move == 3 ) { //VectorCopy( oldorigin, self->s.old_origin ); VectorClear( self->velocity ); trap_LinkEntity(self); return; } //relink trap_LinkEntity(self); G_TouchTriggers(self); return; } //============================================================================ //================ //G_RunEntity // //================ void G_RunEntity( edict_t *ent ) { if( ent->prethink ) ent->prethink(ent); switch( (int)ent->movetype ) { //MbotGame[start] case MOVETYPE_WALK: SV_RunThink(ent); break; //[end] 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_BOUNCEGRENADE: //SV_Physics_Grenade( ent ); SV_Physics_Toss(ent); break; case MOVETYPE_TOSS: case MOVETYPE_BOUNCE: case MOVETYPE_FLY: case MOVETYPE_FLYMISSILE: SV_Physics_Toss(ent); break; default: G_Error( "SV_Physics: bad movetype %i", (int)ent->movetype ); } }