diff options
author | Holger Zickner <[email protected]> | 2004-07-07 19:59:59 +0000 |
---|---|---|
committer | Holger Zickner <[email protected]> | 2004-07-07 19:59:59 +0000 |
commit | 6e23fc1074d1f0c2c2812f4c2e663f5a21a43c20 (patch) | |
tree | 46ecc6d0255c874ba4cd26dc3d0733f785019896 /src/jake2/server/SV.java |
import of Jake2 version sunrisesunrise
Diffstat (limited to 'src/jake2/server/SV.java')
-rw-r--r-- | src/jake2/server/SV.java | 1110 |
1 files changed, 1110 insertions, 0 deletions
diff --git a/src/jake2/server/SV.java b/src/jake2/server/SV.java new file mode 100644 index 0000000..d00b104 --- /dev/null +++ b/src/jake2/server/SV.java @@ -0,0 +1,1110 @@ +/* + * SV.java + * Copyright (C) 2003 + * + * $Id: SV.java,v 1.1 2004-07-07 19:59:46 hzi Exp $ + */ + /* +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. + +*/ +package jake2.server; + +import jake2.*; +import jake2.game.*; +import jake2.qcommon.Com; +import jake2.util.*; +import jake2.client.*; +import jake2.game.*; +import jake2.game.trace_t; + +/** + * SV + */ +public final class SV { + + //file_io + //===================================================================== + //g_phys + + /////////////////////////////////////// + public static edict_t[] SV_TestEntityPosition(edict_t ent) { + trace_t trace; + int mask; + + if (ent.clipmask != 0) + mask = ent.clipmask; + else + mask = Defines.MASK_SOLID; + + trace = GameBase.gi.trace(ent.s.origin, ent.mins, ent.maxs, ent.s.origin, ent, mask); + + if (trace.startsolid) + return GameBase.g_edicts; + + return null; + } + + /////////////////////////////////////// + public static void SV_CheckVelocity(edict_t ent) { + int i; + + // + // bound velocity + // + for (i = 0; i < 3; i++) { + if (ent.velocity[i] > GameBase.sv_maxvelocity.value) + ent.velocity[i] = GameBase.sv_maxvelocity.value; + else if (ent.velocity[i] < -GameBase.sv_maxvelocity.value) + ent.velocity[i] = -GameBase.sv_maxvelocity.value; + } + } + + /** + * Runs thinking code for this frame if necessary. + */ + public static boolean SV_RunThink(edict_t ent) { + float thinktime; + + thinktime = ent.nextthink; + if (thinktime <= 0) + return true; + if (thinktime > GameBase.level.time + 0.001) + return true; + + ent.nextthink = 0; + + if (ent.think == null) + GameBase.gi.error("NULL ent.think"); + + ent.think.think(ent); + + return false; + } + + /** + * Two entities have touched, so run their touch functions. + */ + public static void SV_Impact(edict_t e1, trace_t trace) { + edict_t e2; + // cplane_t backplane; + + e2 = trace.ent; + + if (e1.touch != null && e1.solid != Defines.SOLID_NOT) + e1.touch.touch(e1, e2, trace.plane, trace.surface); + + if (e2.touch != null && e2.solid != Defines.SOLID_NOT) + e2.touch.touch(e2, e1, null, null); + } + + 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; + float[][] planes = new float[3][GameBase.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; + + 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(GameBase.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_Impact(ent, trace); + if (!ent.inuse) + break; // removed by the impact function + + time_left -= time_left * trace.fraction; + + // cliped to another plane + if (numplanes >= GameBase.MAX_CLIP_PLANES) { // this shouldn't really happen + Math3D.VectorCopy(GameBase.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.VectorCompare(planes[i], planes[j]) == 0.0f) { + 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(GameBase.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(GameBase.vec3_origin, ent.velocity); + return blocked; + } + } + + return blocked; + } + + /* + ============ + SV_AddGravity + + ============ + */ + public static void SV_AddGravity(edict_t ent) { + ent.velocity[2] -= ent.gravity * GameBase.sv_gravity.value * Defines.FRAMETIME; + } + + /** + * Does not change the entities velocity at all + */ + public static trace_t SV_PushEntity(edict_t ent, float[] push) { + trace_t trace; + float[] start = { 0, 0, 0 }; + float[] end = { 0, 0, 0 }; + int mask; + + Math3D.VectorCopy(ent.s.origin, start); + Math3D.VectorAdd(start, push, end); + + // FIXME: test this + // a goto statement was replaced. + boolean retry; + + do { + if (ent.clipmask != 0) + mask = ent.clipmask; + else + mask = Defines.MASK_SOLID; + + trace = GameBase.gi.trace(start, ent.mins, ent.maxs, end, ent, mask); + + Math3D.VectorCopy(trace.endpos, ent.s.origin); + GameBase.gi.linkentity(ent); + + retry = false; + 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 + Math3D.VectorCopy(start, ent.s.origin); + GameBase.gi.linkentity(ent); + //goto retry; + retry = true; + } + } + } while (retry); + + if (ent.inuse) + GameBase.G_TouchTriggers(ent); + + return trace; + } + + /* + ============ + SV_Push + + Objects need to be moved back on a failed push, + otherwise riders would continue to slide. + ============ + */ + public static boolean SV_Push(edict_t pusher, float[] move, float[] amove) { + int i, e; + edict_t check, 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.5; + else + temp -= 0.5; + 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(GameBase.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.globals.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_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] += 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_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_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 (int 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 (int ip = GameBase.pushed_p - 1; ip >= 0; ip--) + GameBase.G_TouchTriggers(GameBase.pushed[ip].ent); + + return true; + } + + /* + ================ + SV_Physics_Pusher + + 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_Push(part, move, amove)) + break; // move was blocked + } + } + if (GameBase.pushed_p > Defines.MAX_EDICTS) + GameBase.gi.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_RunThink(part); + } + } + } + + // ================================================================== + + /* + ============= + SV_Physics_None + + Non moving objects can only think + ============= + */ + public static void SV_Physics_None(edict_t ent) { + // regular thinking + SV_RunThink(ent); + } + + /* + ============= + SV_Physics_Noclip + + A moving object that doesn't obey physics + ============= + */ + public static void SV_Physics_Noclip(edict_t ent) { + // regular thinking + if (!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); + } + + /* + ============================================================================== + + TOSS / BOUNCE + + ============================================================================== + */ + + /* + ============= + SV_Physics_Toss + + 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; + boolean wasinwater; + boolean isinwater; + float[] old_origin = { 0, 0, 0 }; + + // regular thinking + 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_CheckVelocity(ent); + + // add gravity + if (ent.movetype != Defines.MOVETYPE_FLY && ent.movetype != Defines.MOVETYPE_FLYMISSILE) + 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_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(GameBase.vec3_origin, ent.velocity); + Math3D.VectorCopy(GameBase.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.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); + } + } + + /* + =============================================================================== + + 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 + + public static void SV_AddRotationalFriction(edict_t ent) { + int n; + float adjustment; + + Math3D.VectorMA(ent.s.angles, Defines.FRAMETIME, ent.avelocity, ent.s.angles); + adjustment = Defines.FRAMETIME * Defines.sv_stopspeed * Defines.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; + } + } + } + + public static void SV_Physics_Step(edict_t ent) { + boolean wasonground; + boolean 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_CheckVelocity(ent); + + if (groundentity != null) + wasonground = true; + else + wasonground = false; + + if (ent.avelocity[0] != 0 || ent.avelocity[1] != 0 || ent.avelocity[2] != 0) + 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_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_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_RunThink(ent); + } + + /* + ============= + 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 + pr_global_struct.trace_normal is set to the normal of the blocking wall + ============= + */ + // FIXME since we need to test end position contents here, can we avoid doing + // it again later in catagorize position? + public static boolean SV_movestep(edict_t ent, float[] move, boolean relink) { + float dz; + float[] oldorg = { 0, 0, 0 }; + float[] neworg = { 0, 0, 0 }; + float[] end = { 0, 0, 0 }; + + trace_t trace = null;// = new trace_t(); + int i; + float stepsize; + float[] test = { 0, 0, 0 }; + int contents; + + // try the move + Math3D.VectorCopy(ent.s.origin, oldorg); + Math3D.VectorAdd(ent.s.origin, move, neworg); + + // flying monsters don't step up + if ((ent.flags & (Defines.FL_SWIM | Defines.FL_FLY)) != 0) { + // try one move with vertical motion, then one without + for (i = 0; i < 2; i++) { + Math3D.VectorAdd(ent.s.origin, move, neworg); + if (i == 0 && ent.enemy != null) { + if (ent.goalentity == null) + ent.goalentity = ent.enemy; + dz = ent.s.origin[2] - ent.goalentity.s.origin[2]; + if (ent.goalentity.client != null) { + if (dz > 40) + neworg[2] -= 8; + if (!((ent.flags & Defines.FL_SWIM) != 0 && (ent.waterlevel < 2))) + if (dz < 30) + neworg[2] += 8; + } else { + if (dz > 8) + neworg[2] -= 8; + else if (dz > 0) + neworg[2] -= dz; + else if (dz < -8) + neworg[2] += 8; + else + neworg[2] += dz; + } + } + trace = GameBase.gi.trace(ent.s.origin, ent.mins, ent.maxs, neworg, ent, Defines.MASK_MONSTERSOLID); + + // fly monsters don't enter water voluntarily + if ((ent.flags & Defines.FL_FLY) != 0) { + if (ent.waterlevel == 0) { + test[0] = trace.endpos[0]; + test[1] = trace.endpos[1]; + test[2] = trace.endpos[2] + ent.mins[2] + 1; + contents = GameBase.gi.pointcontents.pointcontents(test); + if ((contents & Defines.MASK_WATER) != 0) + return false; + } + } + + // swim monsters don't exit water voluntarily + if ((ent.flags & Defines.FL_SWIM) != 0) { + if (ent.waterlevel < 2) { + test[0] = trace.endpos[0]; + test[1] = trace.endpos[1]; + test[2] = trace.endpos[2] + ent.mins[2] + 1; + contents = GameBase.gi.pointcontents.pointcontents(test); + if ((contents & Defines.MASK_WATER) == 0) + return false; + } + } + + if (trace.fraction == 1) { + Math3D.VectorCopy(trace.endpos, ent.s.origin); + if (relink) { + GameBase.gi.linkentity(ent); + GameBase.G_TouchTriggers(ent); + } + return true; + } + + if (ent.enemy == null) + break; + } + + return false; + } + + // push down from a step height above the wished position + if ((ent.monsterinfo.aiflags & Defines.AI_NOSTEP) == 0) + stepsize = GameBase.STEPSIZE; + else + stepsize = 1; + + neworg[2] += stepsize; + Math3D.VectorCopy(neworg, end); + end[2] -= stepsize * 2; + + trace = GameBase.gi.trace(neworg, ent.mins, ent.maxs, end, ent, Defines.MASK_MONSTERSOLID); + + if (trace.allsolid) + return false; + + if (trace.startsolid) { + neworg[2] -= stepsize; + trace = GameBase.gi.trace(neworg, ent.mins, ent.maxs, end, ent, Defines.MASK_MONSTERSOLID); + if (trace.allsolid || trace.startsolid) + return false; + } + + // don't go in to water + if (ent.waterlevel == 0) { + test[0] = trace.endpos[0]; + test[1] = trace.endpos[1]; + test[2] = trace.endpos[2] + ent.mins[2] + 1; + contents = GameBase.gi.pointcontents.pointcontents(test); + + if ((contents & Defines.MASK_WATER) != 0) + return false; + } + + if (trace.fraction == 1) { + // if monster had the ground pulled out, go ahead and fall + if ((ent.flags & Defines.FL_PARTIALGROUND) != 0) { + Math3D.VectorAdd(ent.s.origin, move, ent.s.origin); + if (relink) { + GameBase.gi.linkentity(ent); + GameBase.G_TouchTriggers(ent); + } + ent.groundentity = null; + return true; + } + + return false; // walked off an edge + } + + // check point traces down for dangling corners + Math3D.VectorCopy(trace.endpos, ent.s.origin); + + if (!M.M_CheckBottom(ent)) { + if ((ent.flags & Defines.FL_PARTIALGROUND) != 0) { + // entity had floor mostly pulled out from underneath it + // and is trying to correct + if (relink) { + GameBase.gi.linkentity(ent); + GameBase.G_TouchTriggers(ent); + } + return true; + } + Math3D.VectorCopy(oldorg, ent.s.origin); + return false; + } + + if ((ent.flags & Defines.FL_PARTIALGROUND) != 0) { + ent.flags &= ~Defines.FL_PARTIALGROUND; + } + ent.groundentity = trace.ent; + ent.groundentity_linkcount = trace.ent.linkcount; + + // the move is ok + if (relink) { + GameBase.gi.linkentity(ent); + GameBase.G_TouchTriggers(ent); + } + return true; + } + + /* + ====================== + SV_StepDirection + + Turns to the movement direction, and walks the current distance if + facing it. + + ====================== + */ + public static boolean 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_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; + } + + /* + ====================== + SV_FixCheckBottom + + ====================== + */ + public static void SV_FixCheckBottom(edict_t ent) { + ent.flags |= Defines.FL_PARTIALGROUND; + } + + 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.d("SV_NewChaseDir without enemy!"); + 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] = GameBase.DI_NODIR; + if (deltay < -10) + d[2] = 270; + else if (deltay > 10) + d[2] = 90; + else + d[2] = GameBase.DI_NODIR; + + // try direct route + if (d[1] != GameBase.DI_NODIR && d[2] != GameBase.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 (((Lib.rand() & 3) & 1) != 0 || Math.abs(deltay) > Math.abs(deltax)) { + tdir = d[1]; + d[1] = d[2]; + d[2] = tdir; + } + + if (d[1] != GameBase.DI_NODIR && d[1] != turnaround && SV_StepDirection(actor, d[1], dist)) + return; + + if (d[2] != GameBase.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 != GameBase.DI_NODIR && 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_StepDirection(actor, tdir, dist)) + return; + } else { + for (tdir = 315; tdir >= 0; tdir -= 45) + if (tdir != turnaround && SV_StepDirection(actor, tdir, dist)) + return; + } + + if (turnaround != GameBase.DI_NODIR && 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_FixCheckBottom(actor); + } + + /* + ====================== + SV_CloseEnough + + ====================== + *///ok + public static boolean SV_CloseEnough(edict_t ent, edict_t goal, float dist) { + int i; + + for (i = 0; i < 3; i++) { + if (goal.absmin[i] > ent.absmax[i] + dist) + return false; + if (goal.absmax[i] < ent.absmin[i] - dist) + return false; + } + return true; + } +} |