/** * A moving object that doesn't obey physics. */ public static void SV_Physics_Noclip(edict_t ent) { // regular thinking if (!SV.SV_RunThink(ent)) { return; } Math3D.VectorMA(ent.s.angles, Defines.FRAMETIME, ent.avelocity, ent.s.angles); Math3D.VectorMA(ent.s.origin, Defines.FRAMETIME, ent.velocity, ent.s.origin); GameBase.gi.linkentity(ent); }
/** * Turns to the movement direction, and walks the current distance if facing * it. */ public static bool SV_StepDirection(edict_t ent, float yaw, float dist) { float[] move = { 0, 0, 0 }; float[] oldorigin = { 0, 0, 0 }; float delta; ent.ideal_yaw = yaw; M.M_ChangeYaw(ent); yaw = (float)(yaw * Math.PI * 2 / 360); move[0] = (float)Math.Cos(yaw) * dist; move[1] = (float)Math.Sin(yaw) * dist; move[2] = 0; Math3D.VectorCopy(ent.s.origin, oldorigin); if (SV.SV_movestep(ent, move, false)) { delta = ent.s.angles[Defines.YAW] - ent.ideal_yaw; if (delta > 45 && delta < 315) { // not turned far enough, so don't // take the step Math3D.VectorCopy(oldorigin, ent.s.origin); } GameBase.gi.linkentity(ent); GameBase.G_TouchTriggers(ent); return(true); } GameBase.gi.linkentity(ent); GameBase.G_TouchTriggers(ent); return(false); }
/** * 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? */ public static void SV_Physics_Step(edict_t ent) { bool wasonground; var 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 == null) { M.M_CheckGround(ent); } groundentity = ent.groundentity; SV.SV_CheckVelocity(ent); if (groundentity != null) { wasonground = true; } else { wasonground = false; } if (ent.avelocity[0] != 0 || ent.avelocity[1] != 0 || ent.avelocity[2] != 0) { SV.SV_AddRotationalFriction(ent); } // add gravity except: // flying monsters // swimming monsters who are in the water if (!wasonground) { if (0 == (ent.flags & Defines.FL_FLY)) { if (!((ent.flags & Defines.FL_SWIM) != 0 && ent.waterlevel > 2)) { if (ent.velocity[2] < GameBase.sv_gravity.value * -0.1) { hitsound = true; } if (ent.waterlevel == 0) { SV.SV_AddGravity(ent); } } } } // friction for flying monsters that have been given vertical velocity if ((ent.flags & Defines.FL_FLY) != 0 && ent.velocity[2] != 0) { speed = Math.Abs(ent.velocity[2]); control = speed < Defines.sv_stopspeed ? Defines.sv_stopspeed : speed; friction = Defines.sv_friction / 3; newspeed = speed - Defines.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 & Defines.FL_SWIM) != 0 && ent.velocity[2] != 0) { speed = Math.Abs(ent.velocity[2]); control = speed < Defines.sv_stopspeed ? Defines.sv_stopspeed : speed; newspeed = speed - Defines.FRAMETIME * control * Defines.sv_waterfriction * ent.waterlevel; if (newspeed < 0) { newspeed = 0; } newspeed /= speed; ent.velocity[2] *= newspeed; } if (ent.velocity[2] != 0 || ent.velocity[1] != 0 || ent.velocity[0] != 0) { // apply friction // let dead monsters who aren't completely onground slide if (wasonground || 0 != (ent.flags & (Defines.FL_SWIM | Defines.FL_FLY))) { if (!(ent.health <= 0.0 && !M.M_CheckBottom(ent))) { vel = ent.velocity; speed = (float)Math.Sqrt(vel[0] * vel[0] + vel[1] * vel[1]); if (speed != 0) { friction = Defines.sv_friction; control = speed < Defines.sv_stopspeed ? Defines.sv_stopspeed : speed; newspeed = speed - Defines.FRAMETIME * control * friction; if (newspeed < 0) { newspeed = 0; } newspeed /= speed; vel[0] *= newspeed; vel[1] *= newspeed; } } } if ((ent.svflags & Defines.SVF_MONSTER) != 0) { mask = Defines.MASK_MONSTERSOLID; } else { mask = Defines.MASK_SOLID; } SV.SV_FlyMove(ent, Defines.FRAMETIME, mask); GameBase.gi.linkentity(ent); GameBase.G_TouchTriggers(ent); if (!ent.inuse) { return; } if (ent.groundentity != null) { if (!wasonground) { if (hitsound) { GameBase.gi.sound(ent, 0, GameBase.gi.soundindex("world/land.wav"), 1, 1, 0); } } } } // regular thinking SV.SV_RunThink(ent); }
/** * Toss, bounce, and fly movement. When onground, do nothing. */ public static void SV_Physics_Toss(edict_t ent) { trace_t trace; float[] move = { 0, 0, 0 }; float backoff; edict_t slave; bool wasinwater; bool isinwater; float[] old_origin = { 0, 0, 0 }; // regular thinking SV.SV_RunThink(ent); // if not a team captain, so movement will be handled elsewhere if ((ent.flags & Defines.FL_TEAMSLAVE) != 0) { return; } if (ent.velocity[2] > 0) { ent.groundentity = null; } // check for the groundentity going away if (ent.groundentity != null) { if (!ent.groundentity.inuse) { ent.groundentity = null; } } // if onground, return without moving if (ent.groundentity != null) { return; } Math3D.VectorCopy(ent.s.origin, old_origin); SV.SV_CheckVelocity(ent); // add gravity if (ent.movetype != Defines.MOVETYPE_FLY && ent.movetype != Defines.MOVETYPE_FLYMISSILE) { SV.SV_AddGravity(ent); } // move angles Math3D.VectorMA(ent.s.angles, Defines.FRAMETIME, ent.avelocity, ent.s.angles); // move origin Math3D.VectorScale(ent.velocity, Defines.FRAMETIME, move); trace = SV.SV_PushEntity(ent, move); if (!ent.inuse) { return; } if (trace.fraction < 1) { if (ent.movetype == Defines.MOVETYPE_BOUNCE) { backoff = 1.5f; } else { backoff = 1; } GameBase.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 != Defines.MOVETYPE_BOUNCE) { ent.groundentity = trace.ent; ent.groundentity_linkcount = trace.ent.linkcount; Math3D.VectorCopy(Globals.vec3_origin, ent.velocity); Math3D.VectorCopy(Globals.vec3_origin, ent.avelocity); } } // if (ent.touch) // ent.touch (ent, trace.ent, &trace.plane, trace.surface); } // check for water transition wasinwater = (ent.watertype & Defines.MASK_WATER) != 0; ent.watertype = GameBase.gi.pointcontents(ent.s.origin); isinwater = (ent.watertype & Defines.MASK_WATER) != 0; if (isinwater) { ent.waterlevel = 1; } else { ent.waterlevel = 0; } if (!wasinwater && isinwater) { GameBase.gi.positioned_sound(old_origin, ent, Defines.CHAN_AUTO, GameBase.gi.soundindex("misc/h2ohit1.wav"), 1, 1, 0); } else if (wasinwater && !isinwater) { GameBase.gi.positioned_sound(ent.s.origin, ent, Defines.CHAN_AUTO, GameBase.gi.soundindex("misc/h2ohit1.wav"), 1, 1, 0); } // move teamslaves for (slave = ent.teamchain; slave != null; slave = slave.teamchain) { Math3D.VectorCopy(ent.s.origin, slave.s.origin); GameBase.gi.linkentity(slave); } }
/** * Non moving objects can only think. */ public static void SV_Physics_None(edict_t ent) { // regular thinking SV.SV_RunThink(ent); }
/** * * Bmodel objects don't interact with each other, but push all box objects. */ public static void SV_Physics_Pusher(edict_t ent) { float[] move = { 0, 0, 0 }; float[] amove = { 0, 0, 0 }; edict_t part, mv; // if not a team captain, so movement will be handled elsewhere if ((ent.flags & Defines.FL_TEAMSLAVE) != 0) { 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: GameBase.pushed_p = 0; for (part = ent; part != null; part = part.teamchain) { if (part.velocity[0] != 0 || part.velocity[1] != 0 || part.velocity[2] != 0 || part.avelocity[0] != 0 || part.avelocity[1] != 0 || part.avelocity[2] != 0) { // object // is // moving Math3D.VectorScale(part.velocity, Defines.FRAMETIME, move); Math3D.VectorScale(part.avelocity, Defines.FRAMETIME, amove); if (!SV.SV_Push(part, move, amove)) { break; // move was blocked } } } if (GameBase.pushed_p > Defines.MAX_EDICTS) { SV_GAME.PF_error(Defines.ERR_FATAL, "pushed_p > &pushed[MAX_EDICTS], memory corrupted"); } if (part != null) { // the move failed, bump all nextthink times and back out moves for (mv = ent; mv != null; mv = mv.teamchain) { if (mv.nextthink > 0) { mv.nextthink += Defines.FRAMETIME; } } // if the pusher has a "blocked" function, call it // otherwise, just stay in place until the obstacle is gone if (part.blocked != null) { part.blocked.blocked(part, GameBase.obstacle); } } else { // the move succeeded, so call all think functions for (part = ent; part != null; part = part.teamchain) { SV.SV_RunThink(part); } } }
/** * Objects need to be moved back on a failed push, otherwise riders would * continue to slide. */ public static bool SV_Push(edict_t pusher, float[] move, float[] amove) { int i, e; edict_t check; edict_t[] block; float[] mins = { 0, 0, 0 }; float[] maxs = { 0, 0, 0 }; pushed_t p; float[] org = { 0, 0, 0 }; float[] org2 = { 0, 0, 0 }; float[] move2 = { 0, 0, 0 }; float[] forward = { 0, 0, 0 }; float[] right = { 0, 0, 0 }; float[] up = { 0, 0, 0 }; // 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.0f; if (temp > 0.0) { temp += 0.5f; } else { temp -= 0.5f; } move[i] = 0.125f * (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 Math3D.VectorSubtract(Globals.vec3_origin, amove, org); Math3D.AngleVectors(org, forward, right, up); // save the pusher's original position GameBase.pushed[GameBase.pushed_p].ent = pusher; Math3D.VectorCopy(pusher.s.origin, GameBase.pushed[GameBase.pushed_p].origin); Math3D.VectorCopy(pusher.s.angles, GameBase.pushed[GameBase.pushed_p].angles); if (pusher.client != null) { GameBase.pushed[GameBase.pushed_p].deltayaw = pusher.client.ps.pmove.delta_angles[Defines.YAW]; } GameBase.pushed_p++; // move the pusher to it's final position Math3D.VectorAdd(pusher.s.origin, move, pusher.s.origin); Math3D.VectorAdd(pusher.s.angles, amove, pusher.s.angles); GameBase.gi.linkentity(pusher); // see if any solid entities are inside the final position //check= g_edicts + 1; for (e = 1; e < GameBase.num_edicts; e++) { check = GameBase.g_edicts[e]; if (!check.inuse) { continue; } if (check.movetype == Defines.MOVETYPE_PUSH || check.movetype == Defines.MOVETYPE_STOP || check.movetype == Defines.MOVETYPE_NONE || check.movetype == Defines.MOVETYPE_NOCLIP) { continue; } if (check.area.prev == null) { 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.SV_TestEntityPosition(check) == null) { continue; } } if (pusher.movetype == Defines.MOVETYPE_PUSH || check.groundentity == pusher) { // move this entity GameBase.pushed[GameBase.pushed_p].ent = check; Math3D.VectorCopy(check.s.origin, GameBase.pushed[GameBase.pushed_p].origin); Math3D.VectorCopy(check.s.angles, GameBase.pushed[GameBase.pushed_p].angles); GameBase.pushed_p++; // try moving the contacted entity Math3D.VectorAdd(check.s.origin, move, check.s.origin); if (check.client != null) { // FIXME: doesn't rotate monsters? check.client.ps.pmove.delta_angles[Defines.YAW] += (short)amove[Defines.YAW]; } // figure movement due to the pusher's amove Math3D.VectorSubtract(check.s.origin, pusher.s.origin, org); org2[0] = Math3D.DotProduct(org, forward); org2[1] = -Math3D.DotProduct(org, right); org2[2] = Math3D.DotProduct(org, up); Math3D.VectorSubtract(org2, org, move2); Math3D.VectorAdd(check.s.origin, move2, check.s.origin); // may have pushed them off an edge if (check.groundentity != pusher) { check.groundentity = null; } block = SV.SV_TestEntityPosition(check); if (block == null) { // pushed ok GameBase.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 Math3D.VectorSubtract(check.s.origin, move, check.s.origin); block = SV.SV_TestEntityPosition(check); if (block == null) { GameBase.pushed_p--; continue; } } // save off the obstacle so we can call the block function GameBase.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 (var ip = GameBase.pushed_p - 1; ip >= 0; ip--) { p = GameBase.pushed[ip]; Math3D.VectorCopy(p.origin, p.ent.s.origin); Math3D.VectorCopy(p.angles, p.ent.s.angles); if (p.ent.client != null) { p.ent.client.ps.pmove.delta_angles[Defines.YAW] = (short)p.deltayaw; } GameBase.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 (var ip = GameBase.pushed_p - 1; ip >= 0; ip--) { GameBase.G_TouchTriggers(GameBase.pushed[ip].ent); } return(true); }
public static int SV_FlyMove(edict_t ent, float time, int mask) { edict_t hit; int bumpcount, numbumps; float[] dir = { 0.0f, 0.0f, 0.0f }; float d; int numplanes; var planes = new float[SV.MAX_CLIP_PLANES][]; float[] primal_velocity = { 0.0f, 0.0f, 0.0f }; float[] original_velocity = { 0.0f, 0.0f, 0.0f }; float[] new_velocity = { 0.0f, 0.0f, 0.0f }; int i, j; trace_t trace; float[] end = { 0.0f, 0.0f, 0.0f }; float time_left; int blocked; for (var n = 0; n < planes.Length; n++) { planes[n] = new float[3]; } numbumps = 4; blocked = 0; Math3D.VectorCopy(ent.velocity, original_velocity); Math3D.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 = GameBase.gi.trace(ent.s.origin, ent.mins, ent.maxs, end, ent, mask); if (trace.allsolid) { // entity is trapped in another solid Math3D.VectorCopy(Globals.vec3_origin, ent.velocity); return(3); } if (trace.fraction > 0) { // actually covered some distance Math3D.VectorCopy(trace.endpos, ent.s.origin); Math3D.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 == Defines.SOLID_BSP) { ent.groundentity = hit; ent.groundentity_linkcount = hit.linkcount; } } if (trace.plane.normal[2] == 0.0f) { blocked |= 2; // step } // // run the impact function // SV.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 >= SV.MAX_CLIP_PLANES) { // this shouldn't // really happen Math3D.VectorCopy(Globals.vec3_origin, ent.velocity); return(3); } Math3D.VectorCopy(trace.plane.normal, planes[numplanes]); numplanes++; // // modify original_velocity so it parallels all of the clip planes // for (i = 0; i < numplanes; i++) { GameBase.ClipVelocity(original_velocity, planes[i], new_velocity, 1); for (j = 0; j < numplanes; j++) { if (j != i && !Math3D.VectorEquals(planes[i], planes[j])) { if (Math3D.DotProduct(new_velocity, planes[j]) < 0) { break; // not ok } } } if (j == numplanes) { break; } } if (i != numplanes) { // go along this plane Math3D.VectorCopy(new_velocity, ent.velocity); } else { // go along the crease if (numplanes != 2) { // gi.dprintf ("clip velocity, numplanes == // %i\n",numplanes); Math3D.VectorCopy(Globals.vec3_origin, ent.velocity); return(7); } Math3D.CrossProduct(planes[0], planes[1], dir); d = Math3D.DotProduct(dir, ent.velocity); Math3D.VectorScale(dir, d, ent.velocity); } // // if original velocity is against the original velocity, stop dead // to avoid tiny occilations in sloping corners // if (Math3D.DotProduct(ent.velocity, primal_velocity) <= 0) { Math3D.VectorCopy(Globals.vec3_origin, ent.velocity); return(blocked); } } return(blocked); }
public static void SV_NewChaseDir(edict_t actor, edict_t enemy, float dist) { float deltax, deltay; float[] d = { 0, 0, 0 }; float tdir, olddir, turnaround; //FIXME: how did we get here with no enemy if (enemy == null) { Com.DPrintf("SV_NewChaseDir without enemy!\n"); return; } olddir = Math3D.anglemod((int)(actor.ideal_yaw / 45) * 45); turnaround = Math3D.anglemod(olddir - 180); deltax = enemy.s.origin[0] - actor.s.origin[0]; deltay = enemy.s.origin[1] - actor.s.origin[1]; if (deltax > 10) { d[1] = 0; } else if (deltax < -10) { d[1] = 180; } else { d[1] = SV.DI_NODIR; } if (deltay < -10) { d[2] = 270; } else if (deltay > 10) { d[2] = 90; } else { d[2] = SV.DI_NODIR; } // try direct route if (d[1] != SV.DI_NODIR && d[2] != SV.DI_NODIR) { if (d[1] == 0) { tdir = d[2] == 90 ? 45 : 315; } else { tdir = d[2] == 90 ? 135 : 215; } if (tdir != turnaround && SV.SV_StepDirection(actor, tdir, dist)) { return; } } // try other directions if ((Lib.rand() & 3 & 1) != 0 || Math.Abs(deltay) > Math.Abs(deltax)) { tdir = d[1]; d[1] = d[2]; d[2] = tdir; } if (d[1] != SV.DI_NODIR && d[1] != turnaround && SV.SV_StepDirection(actor, d[1], dist)) { return; } if (d[2] != SV.DI_NODIR && d[2] != turnaround && SV.SV_StepDirection(actor, d[2], dist)) { return; } /* there is no direct path to the player, so pick another direction */ if (olddir != SV.DI_NODIR && SV.SV_StepDirection(actor, olddir, dist)) { return; } if ((Lib.rand() & 1) != 0) /* randomly determine direction of search */ { for (tdir = 0; tdir <= 315; tdir += 45) { if (tdir != turnaround && SV.SV_StepDirection(actor, tdir, dist)) { return; } } } else { for (tdir = 315; tdir >= 0; tdir -= 45) { if (tdir != turnaround && SV.SV_StepDirection(actor, tdir, dist)) { return; } } } if (turnaround != SV.DI_NODIR && SV.SV_StepDirection(actor, turnaround, dist)) { return; } actor.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 (!M.M_CheckBottom(actor)) { SV.SV_FixCheckBottom(actor); } }