summaryrefslogtreecommitdiffstats
path: root/src/jake2/server/SV.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/jake2/server/SV.java')
-rw-r--r--src/jake2/server/SV.java1110
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;
+ }
+}