summaryrefslogtreecommitdiffstats
path: root/src/jake2/server
diff options
context:
space:
mode:
Diffstat (limited to 'src/jake2/server')
-rw-r--r--src/jake2/server/SV.java1110
-rw-r--r--src/jake2/server/SV_CCMDS.java1190
-rw-r--r--src/jake2/server/SV_ENTS.java605
-rw-r--r--src/jake2/server/SV_GAME.java356
-rw-r--r--src/jake2/server/SV_INIT.java498
-rw-r--r--src/jake2/server/SV_MAIN.java1030
-rw-r--r--src/jake2/server/SV_SEND.java562
-rw-r--r--src/jake2/server/SV_USER.java662
-rw-r--r--src/jake2/server/SV_WORLD.java654
-rw-r--r--src/jake2/server/areanode_t.java43
-rw-r--r--src/jake2/server/challenge_t.java39
-rw-r--r--src/jake2/server/client_frame_t.java42
-rw-r--r--src/jake2/server/client_t.java90
-rw-r--r--src/jake2/server/moveclip_t.java44
-rw-r--r--src/jake2/server/scrap.jpage24
-rw-r--r--src/jake2/server/server_static_t.java65
-rw-r--r--src/jake2/server/server_t.java76
17 files changed, 7090 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;
+ }
+}
diff --git a/src/jake2/server/SV_CCMDS.java b/src/jake2/server/SV_CCMDS.java
new file mode 100644
index 0000000..baa1478
--- /dev/null
+++ b/src/jake2/server/SV_CCMDS.java
@@ -0,0 +1,1190 @@
+/*
+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.
+
+*/
+
+// Created on 18.01.2004 by RST.
+// $Id: SV_CCMDS.java,v 1.1 2004-07-07 19:59:47 hzi Exp $
+
+package jake2.server;
+
+import jake2.Globals;
+import jake2.game.*;
+import jake2.qcommon.*;
+import jake2.sys.NET;
+import jake2.sys.Sys;
+import jake2.util.Lib;
+import jake2.util.Vargs;
+
+import java.io.*;
+import java.util.Date;
+
+public class SV_CCMDS extends SV_ENTS {
+
+ /*
+ ===============================================================================
+
+ OPERATOR CONSOLE ONLY COMMANDS
+
+ These commands can only be entered from stdin or by a remote operator datagram
+ ===============================================================================
+ */
+
+ /*
+ ====================
+ SV_SetMaster_f
+
+ Specify a list of master servers
+ ====================
+ */
+ public static void SV_SetMaster_f() {
+ int i, slot;
+
+ // only dedicated servers send heartbeats
+ if (dedicated.value == 0) {
+ Com.Printf("Only dedicated servers use masters.\n");
+ return;
+ }
+
+ // make sure the server is listed public
+ Cvar.Set("public", "1");
+
+ for (i = 1; i < MAX_MASTERS; i++)
+ //memset (&master_adr[i], 0, sizeof(master_adr[i]));
+ master_adr[i] = new netadr_t();
+
+ slot = 1; // slot 0 will always contain the id master
+ for (i = 1; i < Cmd.Argc(); i++) {
+ if (slot == MAX_MASTERS)
+ break;
+
+ if (!NET.StringToAdr(Cmd.Argv(i), master_adr[i])) {
+ Com.Printf("Bad address: " + Cmd.Argv(i) + "\n");
+ continue;
+ }
+ if (master_adr[slot].port == 0)
+ master_adr[slot].port = //BigShort (PORT_MASTER);
+ PORT_MASTER;
+
+ Com.Printf("Master server at " + NET.AdrToString(master_adr[slot]) + "\n");
+
+ Com.Printf("Sending a ping.\n");
+
+ Netchan.OutOfBandPrint(NS_SERVER, master_adr[slot], "ping");
+
+ slot++;
+ }
+
+ svs.last_heartbeat = -9999999;
+ }
+
+ /*
+ ==================
+ SV_SetPlayer
+
+ Sets sv_client and sv_player to the player with idnum Cmd.Argv(1)
+ ==================
+ */
+ public static boolean SV_SetPlayer() {
+ client_t cl;
+ int i;
+ int idnum;
+ String s;
+
+ if (Cmd.Argc() < 2)
+ return false;
+
+ s = Cmd.Argv(1);
+
+ // numeric values are just slot numbers
+ if (s.charAt(0) >= '0' && s.charAt(0) <= '9') {
+ idnum = atoi(Cmd.Argv(1));
+ if (idnum < 0 || idnum >= maxclients.value) {
+ Com.Printf("Bad client slot: " + idnum + "\n");
+ return false;
+ }
+
+ sv_client = svs.clients[idnum];
+ sv_player = sv_client.edict;
+ if (0 == sv_client.state) {
+ Com.Printf("Client " + idnum + " is not active\n");
+ return false;
+ }
+ return true;
+ }
+
+ // check for a name match
+ for (i = 0; i < maxclients.value; i++) {
+ cl = svs.clients[i];
+ if (0 == cl.state)
+ continue;
+ if (0 == strcmp(cl.name, s)) {
+ sv_client = cl;
+ sv_player = sv_client.edict;
+ return true;
+ }
+ }
+
+ Com.Printf("Userid " + s + " is not on the server\n");
+ return false;
+ }
+
+ /*
+ ===============================================================================
+
+ SAVEGAME FILES
+
+ ===============================================================================
+ */
+
+ public static void remove(String name) {
+ try {
+ new File(name).delete();
+ }
+ catch (Exception e) {
+ }
+ }
+
+ /*
+ =====================
+ SV_WipeSavegame
+
+ Delete save/<XXX>/
+ =====================
+ */
+ public static void SV_WipeSavegame(String savename) {
+ //char name[MAX_OSPATH];
+ //char *s;
+
+ String name, s;
+
+ Com.DPrintf("SV_WipeSaveGame(" + savename + ")\n");
+
+ name = FS.Gamedir() + "/save/" + savename + "/server.ssv";
+ remove(name);
+
+ name = FS.Gamedir() + "/save/" + savename + "/game.ssv";
+ remove(name);
+
+ name = FS.Gamedir() + "/save/" + savename + "/*.sav";
+
+ File f = Sys.FindFirst(name, 0, 0);
+ while (f != null) {
+ f.delete();
+ f = Sys.FindNext();
+ }
+ Sys.FindClose();
+
+ name = FS.Gamedir() + "/save/" + savename + "/*.sv2";
+
+ f = Sys.FindFirst(name, 0, 0);
+
+ while (f != null) {
+ f.delete();
+ f = Sys.FindNext();
+ }
+ Sys.FindClose();
+ }
+
+ /*
+ ================
+ CopyFile
+ ================
+ */
+ public static void CopyFile(String src, String dst) {
+ RandomAccessFile f1, f2;
+ int l = -1;
+ byte buffer[] = new byte[65536];
+
+ Com.DPrintf("CopyFile (" + src + ", " + dst + ")\n");
+
+ try {
+ f1 = new RandomAccessFile(src, "r");
+ }
+ catch (Exception e) {
+ return;
+ }
+ try {
+
+ f2 = new RandomAccessFile(dst, "rw");
+ }
+ catch (Exception e) {
+ try {
+ f1.close();
+ }
+ catch (IOException e1) {
+ // TODO Auto-generated catch block
+ e1.printStackTrace();
+ }
+ return;
+ }
+
+ while (true) {
+
+ try {
+ l = f1.read(buffer, 0, 65536);
+ }
+ catch (IOException e1) {
+
+ e1.printStackTrace();
+ }
+ if (l == -1)
+ break;
+ try {
+ f2.write(buffer, 0, l);
+ }
+ catch (IOException e2) {
+
+ e2.printStackTrace();
+ }
+ }
+
+ try {
+ f1.close();
+ }
+ catch (IOException e1) {
+
+ e1.printStackTrace();
+ }
+ try {
+ f2.close();
+ }
+ catch (IOException e2) {
+
+ e2.printStackTrace();
+ }
+ }
+
+ /*
+ ================
+ SV_CopySaveGame
+ ================
+ */
+ public static void SV_CopySaveGame(String src, String dst) {
+ //char name[MAX_OSPATH], name2[MAX_OSPATH];
+ int l, len;
+ File found;
+
+ String name, name2;
+
+ Com.DPrintf("SV_CopySaveGame(" + src + "," + dst + ")\n");
+
+ SV_WipeSavegame(dst);
+
+ // copy the savegame over
+ name = FS.Gamedir() + "/save/" + src + "/server.ssv";
+ name2 = FS.Gamedir() + "/save/" + dst + "/server.ssv";
+ FS.CreatePath(name2);
+ CopyFile(name, name2);
+
+ name = FS.Gamedir() + "/save/" + src + "/game.ssv";
+ name2 = "/save/" + dst + "/game.ssv";
+ CopyFile(name, name2);
+
+ String name1 = FS.Gamedir() + "/save/" + src + "/";
+ len = strlen(name1);
+ name = FS.Gamedir() + "/save/" + src + "/*.sav";
+
+ found = Sys.FindFirst(name, 0, 0);
+
+ while (found != null) {
+ name = name1 + '/' + found.getName();
+ name2 = FS.Gamedir() + "/save/" + dst + "/" + found.getName();
+ CopyFile(name, name2);
+
+ // change sav to sv2
+ //l = strlen(name);
+ //strcpy(name + l - 3, "sv2");
+ //l = strlen(name2);
+ //strcpy(name2 + l - 3, "sv2");
+ name = name.substring(0, name.length() - 3) + "sv2";
+ name2 = name.substring(0, name2.length() - 3) + "sv2";
+
+ CopyFile(name, name2);
+
+ found = Sys.FindNext();
+ }
+ Sys.FindClose();
+ }
+
+ /*
+ ==============
+ SV_WriteLevelFile
+
+ ==============
+ */
+ public static void SV_WriteLevelFile() {
+ //char name[MAX_OSPATH];
+ //FILE * f;
+
+ String name;
+ RandomAccessFile f;
+
+ Com.DPrintf("SV_WriteLevelFile()\n");
+
+ name = FS.Gamedir() + "/save/current/" + sv.name + ".sv2";
+
+ try {
+ f = new RandomAccessFile(name, "rw");
+ }
+ catch (Exception e) {
+ Com.Printf("Failed to open " + name + "\n");
+ return;
+ }
+ try {
+ //fwrite(sv.configstrings, sizeof(sv.configstrings), 1, f);
+ for (int i = 0; i < sv.configstrings.length; i++)
+ Lib.fwriteString(sv.configstrings[i], MAX_QPATH, f);
+
+ CM.CM_WritePortalState(f);
+ f.close();
+ }
+ catch (Exception e) {
+ Com.Printf("IOError in SV_WriteLevelFile: " + e);
+ e.printStackTrace();
+ }
+
+ name = FS.Gamedir() + "/save/current/" + sv.name + ".sav";
+ ge.WriteLevel(name);
+ }
+
+ /*
+ ==============
+ SV_ReadLevelFile
+
+ ==============
+ */
+ public static void SV_ReadLevelFile() {
+ //char name[MAX_OSPATH];
+ String name;
+ RandomAccessFile f;
+
+ Com.DPrintf("SV_ReadLevelFile()\n");
+
+ name = FS.Gamedir() + "/save/current/" + sv.name + ".sv2";
+ try {
+ f = new RandomAccessFile(name, "r");
+ }
+ catch (Exception e) {
+ Com.Printf("Failed to open " + name + "\n");
+ return;
+ }
+ // FS.Read(sv.configstrings, sizeof(sv.configstrings), f);
+ for (int n = 0; n < MAX_CONFIGSTRINGS; n++)
+ sv.configstrings[n] = Lib.freadString(f, MAX_QPATH);
+
+ CM.CM_ReadPortalState(f);
+
+ try {
+ f.close();
+ }
+ catch (IOException e1) {
+ e1.printStackTrace();
+ }
+
+ name = FS.Gamedir() + "/save/current/" + sv.name + ".sav";
+ ge.ReadLevel(name);
+ }
+
+ /*
+ ==============
+ SV_WriteServerFile
+
+ ==============
+ */
+ public static void SV_WriteServerFile(boolean autosave) {
+ RandomAccessFile f;
+ cvar_t var;
+ //char name[MAX_OSPATH], string[128];
+ //char comment[32];
+ //time_t aclock;
+ //struct tm * newtime;
+ String name, string, comment;
+
+ Com.DPrintf("SV_WriteServerFile(" + (autosave ? "true" : "false") + ")\n");
+
+ name = FS.Gamedir() + "/save/current/server.ssv";
+ try {
+ f = new RandomAccessFile(name, "rw");
+ } catch (FileNotFoundException e) {
+ f = null;
+ }
+ if (f == null) {
+ Com.Printf("Couldn't write " + name + "\n");
+ return;
+ }
+ // write the comment field
+ //memset(comment, 0, sizeof(comment));
+
+ if (!autosave) {
+ //time( aclock);
+ //newtime = localtime( aclock);
+ Date newtime = new Date();
+ comment =
+ Com.sprintf(
+ "%2i:%2i %2i/%2i ",
+ new Vargs().add(newtime.getHours()).add(newtime.getMinutes()).add(newtime.getMonth() + 1).add(newtime.getDay()));
+ comment += sv.configstrings[CS_NAME];
+ }
+ else { // autosaved
+ comment = "ENTERING " + sv.configstrings[CS_NAME];
+ }
+
+ try {
+ fwriteString(comment, 32, f);
+ fwriteString(svs.mapcmd, MAX_TOKEN_CHARS, f);
+
+ } catch (IOException e1) {}
+
+ // write the mapcmd
+
+ // write all CVAR_LATCH cvars
+ // these will be things like coop, skill, deathmatch, etc
+ for (var = Globals.cvar_vars; var != null; var = var.next) {
+ if (0 == (var.flags & CVAR_LATCH))
+ continue;
+ if (strlen(var.name) >= MAX_OSPATH - 1 || strlen(var.string) >= 128 - 1) {
+ Com.Printf("Cvar too long: " + var.name + " = " + var.string + "\n");
+ continue;
+ }
+ //memset(name, 0, sizeof(name));
+ //memset(string, 0, sizeof(string));
+ name = var.name;
+ string = var.string;
+ try {
+ fwriteString(name, MAX_OSPATH, f);
+ fwriteString(string, 128, f);
+ } catch (IOException e2) {}
+
+ }
+
+ try {
+ f.close();
+ } catch (IOException e2) {}
+
+ // write game state
+ name = FS.Gamedir() + "/save/current/game.ssv";
+ ge.WriteGame(name, autosave);
+ }
+
+ /*
+ ==============
+ SV_ReadServerFile
+
+ ==============
+ */
+ public static void SV_ReadServerFile() {
+ RandomAccessFile f;
+ //char name[MAX_OSPATH], string[128];
+ //char comment[32];
+ //char mapcmd[MAX_TOKEN_CHARS];
+
+ String name, string, comment, mapcmd;
+
+ Com.DPrintf("SV_ReadServerFile()\n");
+
+ name = FS.Gamedir() + "/save/current/server.ssv";
+ try {
+ f = new RandomAccessFile(name, "r");
+ }
+ catch (FileNotFoundException e1) {
+ Com.Printf("Couldn't read " + name + "\n");
+ e1.printStackTrace();
+ return;
+ }
+ // read the comment field
+ comment = Lib.freadString(f, 32);
+
+ // read the mapcmd
+ mapcmd = Lib.freadString(f, MAX_TOKEN_CHARS);
+
+ // read all CVAR_LATCH cvars
+ // these will be things like coop, skill, deathmatch, etc
+ while (true) {
+ name = Lib.freadString(f, MAX_OSPATH);
+ //if (!fread(name, 1, sizeof(name), f))
+ if (name == null)
+ break;
+ string = Lib.freadString(f, 128);
+ Com.DPrintf("Set " + name + " = " + string + "\n");
+ Cvar.ForceSet(name, string);
+ }
+
+ try {
+ f.close();
+ }
+ catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ // start a new game fresh with new cvars
+ SV_InitGame();
+
+ svs.mapcmd = mapcmd;
+
+ // read game state
+ name = FS.Gamedir() + "/save/current/game.ssv";
+ ge.ReadGame(name);
+ }
+
+ //=========================================================
+
+ /*
+ ==================
+ SV_DemoMap_f
+
+ Puts the server in demo mode on a specific map/cinematic
+ ==================
+ */
+ public static void SV_DemoMap_f() {
+ SV_Map(true, Cmd.Argv(1), false);
+ }
+
+ /*
+ ==================
+ SV_GameMap_f
+
+ Saves the state of the map just being exited and goes to a new map.
+
+ If the initial character of the map string is '*', the next map is
+ in a new unit, so the current savegame directory is cleared of
+ map files.
+
+ Example:
+
+ *inter.cin+jail
+
+ Clears the archived maps, plays the inter.cin cinematic, then
+ goes to map jail.bsp.
+ ==================
+ */
+ public static void SV_GameMap_f() {
+ String map;
+ int i;
+ client_t cl;
+ boolean savedInuse[];
+
+ if (Cmd.Argc() != 2) {
+ Com.Printf("USAGE: gamemap <map>\n");
+ return;
+ }
+
+ Com.DPrintf("SV_GameMap(" + Cmd.Argv(1) + ")\n");
+
+ FS.CreatePath(FS.Gamedir() + "/save/current/");
+
+ // check for clearing the current savegame
+ map = Cmd.Argv(1);
+ if (map.charAt(0) == '*') {
+ // wipe all the *.sav files
+ SV_WipeSavegame("current");
+ }
+ else { // save the map just exited
+ if (sv.state == ss_game) {
+ // clear all the client inuse flags before saving so that
+ // when the level is re-entered, the clients will spawn
+ // at spawn points instead of occupying body shells
+ savedInuse = new boolean[(int) maxclients.value];
+ for (i = 0; i < maxclients.value; i++) {
+ cl = svs.clients[i];
+ savedInuse[i] = cl.edict.inuse;
+ cl.edict.inuse = false;
+ }
+
+ SV_WriteLevelFile();
+
+ // we must restore these for clients to transfer over correctly
+ for (i = 0; i < maxclients.value; i++) {
+ cl = svs.clients[i];
+ cl.edict.inuse = savedInuse[i];
+
+ }
+ savedInuse = null;
+ }
+ }
+
+ // start up the next map
+ SV_Map(false, Cmd.Argv(1), false);
+
+ // archive server state
+ svs.mapcmd = Cmd.Argv(1);
+
+ // copy off the level to the autosave slot
+ if (0 == dedicated.value) {
+
+ //TODO: SV_WriteServerFile!
+ //SV_WriteServerFile(true);
+
+ //SV_CopySaveGame("current", "save0");
+ }
+ }
+
+ /*
+ ==================
+ SV_Map_f
+
+ Goes directly to a given map without any savegame archiving.
+ For development work
+ ==================
+ */
+ public static void SV_Map_f() {
+ String map;
+ //char expanded[MAX_QPATH];
+ String expanded;
+
+ // if not a pcx, demo, or cinematic, check to make sure the level exists
+ map = Cmd.Argv(1);
+ if (!strstr(map, ".")) {
+ expanded = "maps/" + map + ".bsp";
+ if (FS.LoadFile(expanded) == null) {
+ Com.Printf("Can't find " + expanded + "\n");
+ return;
+ }
+ }
+
+ sv.state = ss_dead; // don't save current level when changing
+ //TODO: RST: disabled for debugging
+ //SV_WipeSavegame("current");
+ SV_GameMap_f();
+ }
+
+ /*
+ =====================================================================
+
+ SAVEGAMES
+
+ =====================================================================
+ */
+
+ /*
+ ==============
+ SV_Loadgame_f
+
+ ==============
+ */
+ public static void SV_Loadgame_f() {
+ //char name[MAX_OSPATH];
+ //FILE * f;
+ //char * dir;
+
+ String name;
+ RandomAccessFile f;
+ String dir;
+
+ if (Cmd.Argc() != 2) {
+ Com.Printf("USAGE: loadgame <directory>\n");
+ return;
+ }
+
+ Com.Printf("Loading game...\n");
+
+ dir = Cmd.Argv(1);
+ if (strstr(dir, "..") || strstr(dir, "/") || strstr(dir, "\\")) {
+ Com.Printf("Bad savedir.\n");
+ }
+
+ // make sure the server.ssv file exists
+ name = FS.Gamedir() + "/save/" + Cmd.Argv(1) + "/server.ssv";
+ try {
+ f = new RandomAccessFile(name, "r");
+ }
+ catch (FileNotFoundException e) {
+ Com.Printf("No such savegame: " + name + "\n");
+ return;
+ }
+
+ try {
+ f.close();
+ }
+ catch (IOException e1) {
+ e1.printStackTrace();
+ }
+
+ SV_CopySaveGame(Cmd.Argv(1), "current");
+
+ SV_ReadServerFile();
+
+ // go to the map
+ sv.state = ss_dead; // don't save current level when changing
+ SV_INIT.SV_Map(false, svs.mapcmd, true);
+ }
+
+ /*
+ ==============
+ SV_Savegame_f
+
+ ==============
+ */
+ public static void SV_Savegame_f() {
+ String dir;
+
+ if (sv.state != ss_game) {
+ Com.Printf("You must be in a game to save.\n");
+ return;
+ }
+
+ if (Cmd.Argc() != 2) {
+ Com.Printf("USAGE: savegame <directory>\n");
+ return;
+ }
+
+ if (Cvar.VariableValue("deathmatch") != 0) {
+ Com.Printf("Can't savegame in a deathmatch\n");
+ return;
+ }
+
+ if (0 == strcmp(Cmd.Argv(1), "current")) {
+ Com.Printf("Can't save to 'current'\n");
+ return;
+ }
+
+ if (maxclients.value == 1 && svs.clients[0].edict.client.ps.stats[STAT_HEALTH] <= 0) {
+ Com.Printf("\nCan't savegame while dead!\n");
+ return;
+ }
+
+ dir = Cmd.Argv(1);
+ if (strstr(dir, "..") || strstr(dir, "/") || strstr(dir, "\\")) {
+ Com.Printf("Bad savedir.\n");
+ }
+
+ Com.Printf("Saving game...\n");
+
+ // archive current level, including all client edicts.
+ // when the level is reloaded, they will be shells awaiting
+ // a connecting client
+ SV_WriteLevelFile();
+
+ // save server state
+ try {
+ SV_WriteServerFile(false);
+ }
+ catch (Exception e) {
+ Com.Printf("IOError in SV_WriteServerFile: " + e);
+ }
+
+ // copy it off
+ SV_CopySaveGame("current", dir);
+
+ Com.Printf("Done.\n");
+ }
+
+ //===============================================================
+
+ /*
+ ==================
+ SV_Kick_f
+
+ Kick a user off of the server
+ ==================
+ */
+ public static void SV_Kick_f() {
+ if (!svs.initialized) {
+ Com.Printf("No server running.\n");
+ return;
+ }
+
+ if (Cmd.Argc() != 2) {
+ Com.Printf("Usage: kick <userid>\n");
+ return;
+ }
+
+ if (!SV_SetPlayer())
+ return;
+
+ SV_BroadcastPrintf(PRINT_HIGH, sv_client.name + " was kicked\n");
+ // print directly, because the dropped client won't get the
+ // SV_BroadcastPrintf message
+ SV_ClientPrintf(sv_client, PRINT_HIGH, "You were kicked from the game\n");
+ SV_DropClient(sv_client);
+ sv_client.lastmessage = svs.realtime; // min case there is a funny zombie
+ }
+
+ /*
+ ================
+ SV_Status_f
+ ================
+ */
+ public static void SV_Status_f() {
+ int i, j, l;
+ client_t cl;
+ String s;
+ int ping;
+ if (svs.clients == null) {
+ Com.Printf("No server running.\n");
+ return;
+ }
+ Com.Printf("map : " + sv.name + "\n");
+
+ Com.Printf("num score ping name lastmsg address qport \n");
+ Com.Printf("--- ----- ---- --------------- ------- --------------------- ------\n");
+ for (i = 0; i < maxclients.value; i++) {
+ cl = svs.clients[i];
+ if (0 == cl.state)
+ continue;
+
+ Com.Printf("%3i ", new Vargs().add(i));
+ Com.Printf("%5i ", new Vargs().add(cl.edict.client.ps.stats[STAT_FRAGS]));
+
+ if (cl.state == cs_connected)
+ Com.Printf("CNCT ");
+ else if (cl.state == cs_zombie)
+ Com.Printf("ZMBI ");
+ else {
+ ping = cl.ping < 9999 ? cl.ping : 9999;
+ Com.Printf("%4i ", new Vargs().add(ping));
+ }
+
+ Com.Printf("%s", new Vargs().add(cl.name));
+ l = 16 - strlen(cl.name);
+ for (j = 0; j < l; j++)
+ Com.Printf(" ");
+
+ Com.Printf("%7i ", new Vargs().add(svs.realtime - cl.lastmessage));
+
+ s = NET.AdrToString(cl.netchan.remote_address);
+ Com.Printf(s);
+ l = 22 - strlen(s);
+ for (j = 0; j < l; j++)
+ Com.Printf(" ");
+
+ Com.Printf("%5i", new Vargs().add(cl.netchan.qport));
+
+ Com.Printf("\n");
+ }
+ Com.Printf("\n");
+ }
+
+ /*
+ ==================
+ SV_ConSay_f
+ ==================
+ */
+ public static void SV_ConSay_f() {
+ client_t client;
+ int j;
+ String p;
+ String text; // char[1024];
+
+ if (Cmd.Argc() < 2)
+ return;
+
+ text = "console: ";
+ p = Cmd.Args();
+
+ if (p.charAt(0) == '"') {
+ p = p.substring(1, p.length() - 1);
+ }
+
+ text += p;
+
+ for (j = 0; j < maxclients.value; j++) {
+ client = svs.clients[j];
+ if (client.state != cs_spawned)
+ continue;
+ SV_ClientPrintf(client, PRINT_CHAT, text + "\n");
+ }
+ }
+
+ /*
+ ==================
+ SV_Heartbeat_f
+ ==================
+ */
+ public static void SV_Heartbeat_f() {
+ svs.last_heartbeat = -9999999;
+ }
+
+ /*
+ ===========
+ SV_Serverinfo_f
+
+ Examine or change the serverinfo string
+ ===========
+ */
+ public static void SV_Serverinfo_f() {
+ Com.Printf("Server info settings:\n");
+ Info.Print(Cvar.Serverinfo());
+ }
+
+ /*
+ ===========
+ SV_DumpUser_f
+
+ Examine all a users info strings
+ ===========
+ */
+ public static void SV_DumpUser_f() {
+ if (Cmd.Argc() != 2) {
+ Com.Printf("Usage: info <userid>\n");
+ return;
+ }
+
+ if (!SV_SetPlayer())
+ return;
+
+ Com.Printf("userinfo\n");
+ Com.Printf("--------\n");
+ Info.Print(sv_client.userinfo);
+
+ }
+
+ /*
+ ==============
+ SV_ServerRecord_f
+
+ Begins server demo recording. Every entity and every message will be
+ recorded, but no playerinfo will be stored. Primarily for demo merging.
+ ==============
+ */
+ public static void SV_ServerRecord_f() {
+ //char name[MAX_OSPATH];
+ String name;
+ byte buf_data[] = new byte[32768];
+ sizebuf_t buf = new sizebuf_t();
+ int len;
+ int i;
+
+ if (Cmd.Argc() != 2) {
+ Com.Printf("serverrecord <demoname>\n");
+ return;
+ }
+
+ if (svs.demofile != null) {
+ Com.Printf("Already recording.\n");
+ return;
+ }
+
+ if (sv.state != ss_game) {
+ Com.Printf("You must be in a level to record.\n");
+ return;
+ }
+
+ //
+ // open the demo file
+ //
+ name = FS.Gamedir() + "/demos/" + Cmd.Argv(1) + ".dm2";
+
+ Com.Printf("recording to " + name + ".\n");
+ FS.CreatePath(name);
+ try {
+ svs.demofile = new RandomAccessFile(name, "rw");
+ }
+ catch (Exception e) {
+ Com.Printf("ERROR: couldn't open.\n");
+ return;
+ }
+
+ // setup a buffer to catch all multicasts
+ SZ.Init(svs.demo_multicast, svs.demo_multicast_buf, svs.demo_multicast_buf.length);
+
+ //
+ // write a single giant fake message with all the startup info
+ //
+ SZ.Init(buf, buf_data, buf_data.length);
+
+ //
+ // serverdata needs to go over for all types of servers
+ // to make sure the protocol is right, and to set the gamedir
+ //
+ // send the serverdata
+ MSG.WriteByte(buf, svc_serverdata);
+ MSG.WriteLong(buf, PROTOCOL_VERSION);
+ MSG.WriteLong(buf, svs.spawncount);
+ // 2 means server demo
+ MSG.WriteByte(buf, 2); // demos are always attract loops
+ MSG.WriteString(buf, Cvar.VariableString("gamedir"));
+ MSG.WriteShort(buf, -1);
+ // send full levelname
+ MSG.WriteString(buf, sv.configstrings[CS_NAME]);
+
+ for (i = 0; i < MAX_CONFIGSTRINGS; i++)
+ if (sv.configstrings[i].length() == 0) {
+ MSG.WriteByte(buf, svc_configstring);
+ MSG.WriteShort(buf, i);
+ MSG.WriteString(buf, sv.configstrings[i]);
+ }
+
+ // write it to the demo file
+ Com.DPrintf("signon message length: " + buf.cursize + "\n");
+ len = EndianHandler.swapInt(buf.cursize);
+ //fwrite(len, 4, 1, svs.demofile);
+ //fwrite(buf.data, buf.cursize, 1, svs.demofile);
+ try {
+ svs.demofile.writeInt(len);
+ svs.demofile.write(buf.data);
+ }
+ catch (IOException e1) {
+ // TODO: do quake2 error handling!
+ e1.printStackTrace();
+ }
+
+ // the rest of the demo file will be individual frames
+ }
+
+ /*
+ ==============
+ SV_ServerStop_f
+
+ Ends server demo recording
+ ==============
+ */
+ public static void SV_ServerStop_f() {
+ if (svs.demofile == null) {
+ Com.Printf("Not doing a serverrecord.\n");
+ return;
+ }
+ try {
+ svs.demofile.close();
+ }
+ catch (IOException e) {
+ e.printStackTrace();
+ }
+ svs.demofile = null;
+ Com.Printf("Recording completed.\n");
+ }
+
+ /*
+ ===============
+ SV_KillServer_f
+
+ Kick everyone off, possibly in preparation for a new game
+
+ ===============
+ */
+ public static void SV_KillServer_f() {
+ if (!svs.initialized)
+ return;
+ SV_Shutdown("Server was killed.\n", false);
+ NET.Config(false); // close network sockets
+ }
+
+ /*
+ ===============
+ SV_ServerCommand_f
+
+ Let the game dll handle a command
+ ===============
+ */
+ public static void SV_ServerCommand_f() {
+ if (SV_GAME.ge == null) {
+ Com.Printf("No game loaded.\n");
+ return;
+ }
+
+ SV_GAME.ge.ServerCommand();
+ }
+
+ //===========================================================
+
+ /*
+ ==================
+ SV_InitOperatorCommands
+ ==================
+ */
+ public static void SV_InitOperatorCommands() {
+ Cmd.AddCommand("heartbeat", new xcommand_t() {
+ public void execute() {
+ SV_Heartbeat_f();
+ }
+ });
+ Cmd.AddCommand("kick", new xcommand_t() {
+ public void execute() {
+ SV_Kick_f();
+ }
+ });
+ Cmd.AddCommand("status", new xcommand_t() {
+ public void execute() {
+ SV_Status_f();
+ }
+ });
+ Cmd.AddCommand("serverinfo", new xcommand_t() {
+ public void execute() {
+ SV_Serverinfo_f();
+ }
+ });
+ Cmd.AddCommand("dumpuser", new xcommand_t() {
+ public void execute() {
+ SV_DumpUser_f();
+ }
+ });
+
+ Cmd.AddCommand("map", new xcommand_t() {
+ public void execute() {
+ SV_Map_f();
+ }
+ });
+ Cmd.AddCommand("demomap", new xcommand_t() {
+ public void execute() {
+ SV_DemoMap_f();
+ }
+ });
+ Cmd.AddCommand("gamemap", new xcommand_t() {
+ public void execute() {
+ SV_GameMap_f();
+ }
+ });
+ Cmd.AddCommand("setmaster", new xcommand_t() {
+ public void execute() {
+ SV_SetMaster_f();
+ }
+ });
+
+ if (dedicated.value != 0)
+ Cmd.AddCommand("say", new xcommand_t() {
+ public void execute() {
+ SV_ConSay_f();
+ }
+ });
+
+ Cmd.AddCommand("serverrecord", new xcommand_t() {
+ public void execute() {
+ SV_ServerRecord_f();
+ }
+ });
+ Cmd.AddCommand("serverstop", new xcommand_t() {
+ public void execute() {
+ SV_ServerStop_f();
+ }
+ });
+
+ Cmd.AddCommand("save", new xcommand_t() {
+ public void execute() {
+ SV_Savegame_f();
+ }
+ });
+ Cmd.AddCommand("load", new xcommand_t() {
+ public void execute() {
+ SV_Loadgame_f();
+ }
+ });
+
+ Cmd.AddCommand("killserver", new xcommand_t() {
+ public void execute() {
+ SV_KillServer_f();
+ }
+ });
+
+ Cmd.AddCommand("sv", new xcommand_t() {
+ public void execute() {
+ SV_ServerCommand_f();
+ }
+ });
+ }
+
+}
diff --git a/src/jake2/server/SV_ENTS.java b/src/jake2/server/SV_ENTS.java
new file mode 100644
index 0000000..de27378
--- /dev/null
+++ b/src/jake2/server/SV_ENTS.java
@@ -0,0 +1,605 @@
+/*
+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.
+
+*/
+
+// Created on 17.01.2004 by RST.
+// $Id: SV_ENTS.java,v 1.1 2004-07-07 19:59:45 hzi Exp $
+
+package jake2.server;
+
+import java.io.IOException;
+
+import jake2.*;
+import jake2.client.*;
+import jake2.game.*;
+import jake2.qcommon.*;
+import jake2.render.*;
+import jake2.util.Vargs;
+
+public class SV_ENTS extends SV_USER {
+
+ /*
+ =============================================================================
+
+ Encode a client frame onto the network channel
+
+ =============================================================================
+ */
+
+ /*
+ =============
+ SV_EmitPacketEntities
+
+ Writes a delta update of an entity_state_t list to the message.
+ =============
+ */
+ static void SV_EmitPacketEntities(client_frame_t from, client_frame_t to, sizebuf_t msg) {
+ entity_state_t oldent = null, newent = null;
+ int oldindex, newindex;
+ int oldnum, newnum;
+ int from_num_entities;
+ int bits;
+
+ MSG.WriteByte(msg, svc_packetentities);
+
+ if (from == null)
+ from_num_entities = 0;
+ else
+ from_num_entities = from.num_entities;
+
+ newindex = 0;
+ oldindex = 0;
+ while (newindex < to.num_entities || oldindex < from_num_entities) {
+ if (newindex >= to.num_entities)
+ newnum = 9999;
+ else {
+ newent = svs.client_entities[(to.first_entity + newindex) % svs.num_client_entities];
+ newnum = newent.number;
+ }
+
+ if (oldindex >= from_num_entities)
+ oldnum = 9999;
+ else {
+ oldent = svs.client_entities[(from.first_entity + oldindex) % svs.num_client_entities];
+ oldnum = oldent.number;
+ }
+
+ if (newnum == oldnum) { // delta update from old position
+ // because the force parm is false, this will not result
+ // in any bytes being emited if the entity has not changed at all
+ // note that players are always 'newentities', this updates their oldorigin always
+ // and prevents warping
+ MSG.WriteDeltaEntity(oldent, newent, msg, false, newent.number <= maxclients.value);
+ oldindex++;
+ newindex++;
+ continue;
+ }
+
+ if (newnum < oldnum) { // this is a new entity, send it from the baseline
+ MSG.WriteDeltaEntity(sv.baselines[newnum], newent, msg, true, true);
+ newindex++;
+ continue;
+ }
+
+ if (newnum > oldnum) { // the old entity isn't present in the new message
+ bits = U_REMOVE;
+ if (oldnum >= 256)
+ bits |= U_NUMBER16 | U_MOREBITS1;
+
+ MSG.WriteByte(msg, bits & 255);
+ if ((bits & 0x0000ff00) != 0)
+ MSG.WriteByte(msg, (bits >> 8) & 255);
+
+ if ((bits & U_NUMBER16) != 0)
+ MSG.WriteShort(msg, oldnum);
+ else
+ MSG.WriteByte(msg, oldnum);
+
+ oldindex++;
+ continue;
+ }
+ }
+
+ MSG.WriteShort(msg, 0); // end of packetentities
+
+ }
+
+ /*
+ =============
+ SV_WritePlayerstateToClient
+
+ =============
+ */
+ static void SV_WritePlayerstateToClient(client_frame_t from, client_frame_t to, sizebuf_t msg) {
+ int i;
+ int pflags;
+ player_state_t ps, ops;
+ player_state_t dummy;
+ int statbits;
+
+ ps = to.ps;
+ if (from == null) {
+ //memset (dummy, 0, sizeof(dummy));
+ dummy = new player_state_t();
+ ops = dummy;
+ }
+ else
+ ops = from.ps;
+
+ //
+ // determine what needs to be sent
+ //
+ pflags = 0;
+
+ if (ps.pmove.pm_type != ops.pmove.pm_type)
+ pflags |= PS_M_TYPE;
+
+ if (ps.pmove.origin[0] != ops.pmove.origin[0]
+ || ps.pmove.origin[1] != ops.pmove.origin[1]
+ || ps.pmove.origin[2] != ops.pmove.origin[2])
+ pflags |= PS_M_ORIGIN;
+
+ if (ps.pmove.velocity[0] != ops.pmove.velocity[0]
+ || ps.pmove.velocity[1] != ops.pmove.velocity[1]
+ || ps.pmove.velocity[2] != ops.pmove.velocity[2])
+ pflags |= PS_M_VELOCITY;
+
+ if (ps.pmove.pm_time != ops.pmove.pm_time)
+ pflags |= PS_M_TIME;
+
+ if (ps.pmove.pm_flags != ops.pmove.pm_flags)
+ pflags |= PS_M_FLAGS;
+
+ if (ps.pmove.gravity != ops.pmove.gravity)
+ pflags |= PS_M_GRAVITY;
+
+ if (ps.pmove.delta_angles[0] != ops.pmove.delta_angles[0]
+ || ps.pmove.delta_angles[1] != ops.pmove.delta_angles[1]
+ || ps.pmove.delta_angles[2] != ops.pmove.delta_angles[2])
+ pflags |= PS_M_DELTA_ANGLES;
+
+ if (ps.viewoffset[0] != ops.viewoffset[0] || ps.viewoffset[1] != ops.viewoffset[1] || ps.viewoffset[2] != ops.viewoffset[2])
+ pflags |= PS_VIEWOFFSET;
+
+ if (ps.viewangles[0] != ops.viewangles[0] || ps.viewangles[1] != ops.viewangles[1] || ps.viewangles[2] != ops.viewangles[2])
+ pflags |= PS_VIEWANGLES;
+
+ if (ps.kick_angles[0] != ops.kick_angles[0]
+ || ps.kick_angles[1] != ops.kick_angles[1]
+ || ps.kick_angles[2] != ops.kick_angles[2])
+ pflags |= PS_KICKANGLES;
+
+ if (ps.blend[0] != ops.blend[0] || ps.blend[1] != ops.blend[1] || ps.blend[2] != ops.blend[2] || ps.blend[3] != ops.blend[3])
+ pflags |= PS_BLEND;
+
+ if (ps.fov != ops.fov)
+ pflags |= PS_FOV;
+
+ if (ps.rdflags != ops.rdflags)
+ pflags |= PS_RDFLAGS;
+
+ if (ps.gunframe != ops.gunframe)
+ pflags |= PS_WEAPONFRAME;
+
+ pflags |= PS_WEAPONINDEX;
+
+ //
+ // write it
+ //
+ MSG.WriteByte(msg, svc_playerinfo);
+ MSG.WriteShort(msg, pflags);
+
+ //
+ // write the pmove_state_t
+ //
+ if ((pflags & PS_M_TYPE) != 0)
+ MSG.WriteByte(msg, ps.pmove.pm_type);
+
+ if ((pflags & PS_M_ORIGIN) != 0) {
+ MSG.WriteShort(msg, ps.pmove.origin[0]);
+ MSG.WriteShort(msg, ps.pmove.origin[1]);
+ MSG.WriteShort(msg, ps.pmove.origin[2]);
+ }
+
+ if ((pflags & PS_M_VELOCITY) != 0) {
+ MSG.WriteShort(msg, ps.pmove.velocity[0]);
+ MSG.WriteShort(msg, ps.pmove.velocity[1]);
+ MSG.WriteShort(msg, ps.pmove.velocity[2]);
+ }
+
+ if ((pflags & PS_M_TIME) != 0)
+ MSG.WriteByte(msg, ps.pmove.pm_time);
+
+ if ((pflags & PS_M_FLAGS) != 0)
+ MSG.WriteByte(msg, ps.pmove.pm_flags);
+
+ if ((pflags & PS_M_GRAVITY) != 0)
+ MSG.WriteShort(msg, ps.pmove.gravity);
+
+ if ((pflags & PS_M_DELTA_ANGLES) != 0) {
+ MSG.WriteShort(msg, ps.pmove.delta_angles[0]);
+ MSG.WriteShort(msg, ps.pmove.delta_angles[1]);
+ MSG.WriteShort(msg, ps.pmove.delta_angles[2]);
+ }
+
+ //
+ // write the rest of the player_state_t
+ //
+ if ((pflags & PS_VIEWOFFSET) != 0) {
+ MSG.WriteChar(msg, ps.viewoffset[0] * 4);
+ MSG.WriteChar(msg, ps.viewoffset[1] * 4);
+ MSG.WriteChar(msg, ps.viewoffset[2] * 4);
+ }
+
+ if ((pflags & PS_VIEWANGLES) != 0) {
+ MSG.WriteAngle16(msg, ps.viewangles[0]);
+ MSG.WriteAngle16(msg, ps.viewangles[1]);
+ MSG.WriteAngle16(msg, ps.viewangles[2]);
+ }
+
+ if ((pflags & PS_KICKANGLES) != 0) {
+ MSG.WriteChar(msg, ps.kick_angles[0] * 4);
+ MSG.WriteChar(msg, ps.kick_angles[1] * 4);
+ MSG.WriteChar(msg, ps.kick_angles[2] * 4);
+ }
+
+ if ((pflags & PS_WEAPONINDEX) != 0) {
+ MSG.WriteByte(msg, ps.gunindex);
+ }
+
+ if ((pflags & PS_WEAPONFRAME) != 0) {
+ MSG.WriteByte(msg, ps.gunframe);
+ MSG.WriteChar(msg, ps.gunoffset[0] * 4);
+ MSG.WriteChar(msg, ps.gunoffset[1] * 4);
+ MSG.WriteChar(msg, ps.gunoffset[2] * 4);
+ MSG.WriteChar(msg, ps.gunangles[0] * 4);
+ MSG.WriteChar(msg, ps.gunangles[1] * 4);
+ MSG.WriteChar(msg, ps.gunangles[2] * 4);
+ }
+
+ if ((pflags & PS_BLEND) != 0) {
+ MSG.WriteByte(msg, ps.blend[0] * 255);
+ MSG.WriteByte(msg, ps.blend[1] * 255);
+ MSG.WriteByte(msg, ps.blend[2] * 255);
+ MSG.WriteByte(msg, ps.blend[3] * 255);
+ }
+ if ((pflags & PS_FOV) != 0)
+ MSG.WriteByte(msg, ps.fov);
+ if ((pflags & PS_RDFLAGS) != 0)
+ MSG.WriteByte(msg, ps.rdflags);
+
+ // send stats
+ statbits = 0;
+ for (i = 0; i < MAX_STATS; i++)
+ if (ps.stats[i] != ops.stats[i])
+ statbits |= 1 << i;
+ MSG.WriteLong(msg, statbits);
+ for (i = 0; i < MAX_STATS; i++)
+ if ((statbits & (1 << i)) != 0)
+ MSG.WriteShort(msg, ps.stats[i]);
+ }
+
+ /*
+ ==================
+ SV_WriteFrameToClient
+ ==================
+ */
+ public static void SV_WriteFrameToClient(client_t client, sizebuf_t msg) {
+ client_frame_t frame, oldframe;
+ int lastframe;
+
+ //Com.Printf ("%i . %i\n", new Vargs().add(client.lastframe).add(sv.framenum));
+ // this is the frame we are creating
+ frame = client.frames[sv.framenum & UPDATE_MASK];
+
+ if (client.lastframe <= 0) { // client is asking for a retransmit
+ oldframe = null;
+ lastframe = -1;
+ }
+ else if (
+ sv.framenum - client.lastframe >= (UPDATE_BACKUP - 3)) { // client hasn't gotten a good message through in a long time
+ // Com_Printf ("%s: Delta request from out-of-date packet.\n", client.name);
+ oldframe = null;
+ lastframe = -1;
+ }
+ else { // we have a valid message to delta from
+ oldframe = client.frames[client.lastframe & UPDATE_MASK];
+ lastframe = client.lastframe;
+ }
+
+ MSG.WriteByte(msg, svc_frame);
+ MSG.WriteLong(msg, sv.framenum);
+ MSG.WriteLong(msg, lastframe); // what we are delta'ing from
+ MSG.WriteByte(msg, client.surpressCount); // rate dropped packets
+ client.surpressCount = 0;
+
+ // send over the areabits
+ MSG.WriteByte(msg, frame.areabytes);
+ SZ.Write(msg, frame.areabits, frame.areabytes);
+
+ // delta encode the playerstate
+ SV_WritePlayerstateToClient(oldframe, frame, msg);
+
+ // delta encode the entities
+ SV_EmitPacketEntities(oldframe, frame, msg);
+ }
+
+ /*
+ =============================================================================
+
+ Build a client frame structure
+
+ =============================================================================
+ */
+
+ static byte fatpvs[] = new byte[65536 / 8]; // 32767 is MAX_MAP_LEAFS
+
+ /*
+ ============
+ SV_FatPVS
+
+ The client will interpolate the view position,
+ so we can't use a single PVS point
+ ===========
+ */
+ public static void SV_FatPVS(float[] org) {
+ int leafs[] = new int[64];
+ int i, j, count;
+ int longs;
+ byte src[];
+ float[] mins = { 0, 0, 0 }, maxs = { 0, 0, 0 };
+
+ for (i = 0; i < 3; i++) {
+ mins[i] = org[i] - 8;
+ maxs[i] = org[i] + 8;
+ }
+
+ count = CM.CM_BoxLeafnums(mins, maxs, leafs, 64, null);
+
+ if (count < 1)
+ Com.Error(ERR_FATAL, "SV_FatPVS: count < 1");
+
+ longs = (CM.CM_NumClusters() + 31) >> 5;
+
+ // convert leafs to clusters
+ for (i = 0; i < count; i++)
+ leafs[i] = CM.CM_LeafCluster(leafs[i]);
+
+ memcpy(fatpvs, CM.CM_ClusterPVS(leafs[0]), longs << 2);
+ // or in all the other leaf bits
+ for (i = 1; i < count; i++) {
+ for (j = 0; j < i; j++)
+ if (leafs[i] == leafs[j])
+ break;
+ if (j != i)
+ continue; // already have the cluster we want
+
+ src = CM.CM_ClusterPVS(leafs[i]);
+
+ //for (j=0 ; j<longs ; j++)
+ // ((long *)fatpvs)[j] |= ((long *)src)[j];
+ int k=0;
+ for (j = 0; j < longs; j++) {
+ fatpvs[k] |= src[k++];
+ fatpvs[k] |= src[k++];
+ fatpvs[k] |= src[k++];
+ fatpvs[k] |= src[k++];
+ }
+ }
+ }
+
+ /*
+ =============
+ SV_BuildClientFrame
+
+ Decides which entities are going to be visible to the client, and
+ copies off the playerstat and areabits.
+ =============
+ */
+ public static void SV_BuildClientFrame(client_t client) {
+ int e, i;
+ float[] org = { 0, 0, 0 };
+ edict_t ent;
+ edict_t clent;
+ client_frame_t frame;
+ entity_state_t state;
+ int l;
+ int clientarea, clientcluster;
+ int leafnum;
+ int c_fullsend;
+ byte clientphs[];
+ byte bitvector[];
+
+ clent = client.edict;
+ if (clent.client == null)
+ return; // not in game yet
+
+ // this is the frame we are creating
+ frame = client.frames[sv.framenum & UPDATE_MASK];
+
+ frame.senttime = svs.realtime; // save it for ping calc later
+
+ // find the client's PVS
+ for (i = 0; i < 3; i++)
+ org[i] = clent.client.ps.pmove.origin[i] * 0.125f + clent.client.ps.viewoffset[i];
+
+ leafnum = CM.CM_PointLeafnum(org);
+ clientarea = CM.CM_LeafArea(leafnum);
+ clientcluster = CM.CM_LeafCluster(leafnum);
+
+ // calculate the visible areas
+ frame.areabytes = CM.CM_WriteAreaBits(frame.areabits, clientarea);
+
+ // grab the current player_state_t
+ frame.ps = clent.client.ps;
+
+ SV_FatPVS(org);
+ clientphs = CM.CM_ClusterPHS(clientcluster);
+
+ // build up the list of visible entities
+ frame.num_entities = 0;
+ frame.first_entity = svs.next_client_entities;
+
+ c_fullsend = 0;
+
+ for (e = 1; e < SV_GAME.ge.num_edicts; e++) {
+ ent = SV_GAME.ge.edicts[e];
+
+ // ignore ents without visible models
+ if ((ent.svflags & SVF_NOCLIENT) != 0)
+ continue;
+
+ // ignore ents without visible models unless they have an effect
+ if (0 == ent.s.modelindex && 0 == ent.s.effects && 0 == ent.s.sound && 0 == ent.s.event)
+ continue;
+
+ // ignore if not touching a PV leaf
+ if (ent != clent) {
+ // check area
+ if (!CM.CM_AreasConnected(clientarea, ent.areanum)) { // doors can legally straddle two areas, so
+ // we may need to check another one
+ if (0 == ent.areanum2 || !CM.CM_AreasConnected(clientarea, ent.areanum2))
+ continue; // blocked by a door
+ }
+
+ // beams just check one point for PHS
+ if ((ent.s.renderfx & RF_BEAM) != 0) {
+ l = ent.clusternums[0];
+ if (0 == (clientphs[l >> 3] & (1 << (l & 7))))
+ continue;
+ }
+ else {
+ // FIXME: if an ent has a model and a sound, but isn't
+ // in the PVS, only the PHS, clear the model
+ if (ent.s.sound == 0) {
+ bitvector = fatpvs; //clientphs;
+ }
+ else
+ bitvector = fatpvs;
+
+ if (ent.num_clusters == -1) { // too many leafs for individual check, go by headnode
+ if (!CM.CM_HeadnodeVisible(ent.headnode, bitvector))
+ continue;
+ c_fullsend++;
+ }
+ else { // check individual leafs
+ for (i = 0; i < ent.num_clusters; i++) {
+ l = ent.clusternums[i];
+ if ((bitvector[l >> 3] & (1 << (l & 7))) != 0)
+ break;
+ }
+ if (i == ent.num_clusters)
+ continue; // not visible
+ }
+
+ if (ent.s.modelindex == 0) { // don't send sounds if they will be attenuated away
+ float[] delta = { 0, 0, 0 };
+ float len;
+
+ VectorSubtract(org, ent.s.origin, delta);
+ len = VectorLength(delta);
+ if (len > 400)
+ continue;
+ }
+ }
+ }
+
+ // add it to the circular client_entities array
+ int ix = svs.next_client_entities % svs.num_client_entities;
+ state = svs.client_entities[ix];
+ if (ent.s.number != e) {
+ Com.DPrintf("FIXING ENT.S.NUMBER!!!\n");
+ ent.s.number = e;
+ }
+
+ //*state = ent.s;
+ svs.client_entities[ix] = ent.s;
+
+ // don't mark players missiles as solid
+ if (ent.owner == client.edict)
+ state.solid = 0;
+
+ svs.next_client_entities++;
+ frame.num_entities++;
+ }
+ }
+
+ /*
+ ==================
+ SV_RecordDemoMessage
+
+ Save everything in the world out without deltas.
+ Used for recording footage for merged or assembled demos
+ ==================
+ */
+ public static void SV_RecordDemoMessage() {
+ int e;
+ edict_t ent;
+ entity_state_t nostate = new entity_state_t(null);
+ sizebuf_t buf = new sizebuf_t();
+ byte buf_data[] = new byte[32768];
+ int len;
+
+ if (svs.demofile == null)
+ return;
+
+ //memset (nostate, 0, sizeof(nostate));
+ SZ.Init(buf, buf_data, buf_data.length);
+
+ // write a frame message that doesn't contain a player_state_t
+ MSG.WriteByte(buf, svc_frame);
+ MSG.WriteLong(buf, sv.framenum);
+
+ MSG.WriteByte(buf, svc_packetentities);
+
+ e = 1;
+ ent = SV_GAME.ge.edicts[e];
+
+ while (e < SV_GAME.ge.num_edicts) {
+ // ignore ents without visible models unless they have an effect
+ if (ent.inuse
+ && ent.s.number != 0
+ && (ent.s.modelindex != 0 || ent.s.effects != 0 || ent.s.sound != 0 || ent.s.event != 0)
+ && 0 == (ent.svflags & SVF_NOCLIENT))
+ MSG.WriteDeltaEntity(nostate, ent.s, buf, false, true);
+
+ e++;
+ ent = SV_GAME.ge.edicts[e];
+ }
+
+ MSG.WriteShort(buf, 0); // end of packetentities
+
+ // now add the accumulated multicast information
+ SZ.Write(buf, svs.demo_multicast.data, svs.demo_multicast.cursize);
+ SZ.Clear(svs.demo_multicast);
+
+ // now write the entire message to the file, prefixed by the length
+ len = EndianHandler.swapInt(buf.cursize);
+
+ try {
+ //fwrite (len, 4, 1, svs.demofile);
+ svs.demofile.writeInt(len);
+ //fwrite (buf.data, buf.cursize, 1, svs.demofile);
+ svs.demofile.write(buf.data, 0, buf.cursize);
+ }
+ catch (IOException e1) {
+ Com.Printf("Error writing demo file:" + e);
+ }
+ }
+}
diff --git a/src/jake2/server/SV_GAME.java b/src/jake2/server/SV_GAME.java
new file mode 100644
index 0000000..eba5a84
--- /dev/null
+++ b/src/jake2/server/SV_GAME.java
@@ -0,0 +1,356 @@
+/*
+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.
+
+*/
+
+// Created on 14.01.2004 by RST.
+// $Id: SV_GAME.java,v 1.1 2004-07-07 19:59:47 hzi Exp $
+
+package jake2.server;
+
+import jake2.*;
+import jake2.client.*;
+import jake2.game.*;
+import jake2.qcommon.*;
+import jake2.render.*;
+import jake2.sys.Sys;
+
+public class SV_GAME extends SV_INIT {
+
+ // sv_game.c -- interface to the game dll
+
+ public static game_export_t ge;
+
+ /*
+ ===============
+ PF_Unicast
+
+ Sends the contents of the mutlicast buffer to a single client
+ ===============
+ */
+ public static void PF_Unicast(edict_t ent, boolean reliable) {
+ int p;
+ client_t client;
+
+ if (ent == null)
+ return;
+
+ //p = NUM_FOR_EDICT(ent);
+ p = ent.index;
+ if (p < 1 || p > SV_MAIN.maxclients.value)
+ return;
+
+ client = SV_INIT.svs.clients[p - 1];
+
+ if (reliable)
+ SZ.Write(client.netchan.message, SV_INIT.sv.multicast.data, SV_INIT.sv.multicast.cursize);
+ else
+ SZ.Write(client.datagram, sv.multicast.data, sv.multicast.cursize);
+
+ SZ.Clear(sv.multicast);
+ }
+
+ /*
+ ===============
+ PF_dprintf
+
+ Debug print to server console
+ ===============
+ */
+ public static void PF_dprintf(String fmt) {
+ /*
+ char msg[1024];
+ va_list argptr;
+
+ va_start (argptr,fmt);
+ vsprintf (msg, fmt, argptr);
+ va_end (argptr);
+
+ */
+
+ Com.Printf(fmt);
+ }
+
+ /*
+ ===============
+ PF_cprintf
+
+ Print to a single client
+ ===============
+ */
+ public static void PF_cprintf(edict_t ent, int level, String fmt) {
+ //char msg[1024];
+ //va_list argptr;
+ int n = 0;
+
+ if (ent != null) {
+
+ //n = NUM_FOR_EDICT(ent);
+ n = ent.index;
+ if (n < 1 || n > SV_MAIN.maxclients.value)
+ Com.Error(ERR_DROP, "cprintf to a non-client");
+ }
+
+ // va_start (argptr,fmt);
+ // vsprintf (msg, fmt, argptr);
+ // va_end (argptr);
+
+ if (ent != null)
+ SV_SEND.SV_ClientPrintf(svs.clients[n - 1], level, fmt);
+ else
+ Com.Printf(fmt);
+ }
+
+ /*
+ ===============
+ PF_centerprintf
+
+ centerprint to a single client
+ ===============
+ */
+ public static void PF_centerprintf(edict_t ent, String fmt) {
+ //char msg[1024];
+ //va_list argptr;
+ int n;
+
+ //TODO: NUM_FOR_EDICT
+ //n = NUM_FOR_EDICT(ent);
+ n = ent.index;
+ if (n < 1 || n > SV_MAIN.maxclients.value)
+ return; // Com_Error (ERR_DROP, "centerprintf to a non-client");
+
+ // va_start (argptr,fmt);
+ // vsprintf (msg, fmt, argptr);
+ // va_end (argptr);
+
+ MSG.WriteByte(sv.multicast, svc_centerprint);
+ MSG.WriteString(sv.multicast, fmt);
+ PF_Unicast(ent, true);
+ }
+
+ /*
+ ===============
+ PF_error
+
+ Abort the server with a game error
+ ===============
+ */
+ public static void PF_error(String fmt) {
+ Com.Error(ERR_DROP, "Game Error: " + fmt);
+ }
+
+ public static void PF_error(int level, String fmt) {
+ Com.Error(level, fmt);
+ }
+
+ /*
+ =================
+ PF_setmodel
+
+ Also sets mins and maxs for inline bmodels
+ =================
+ */
+ public static void PF_setmodel(edict_t ent, String name) {
+ int i;
+ cmodel_t mod;
+
+ if (name == null)
+ Com.Error(ERR_DROP, "PF_setmodel: NULL");
+
+ i = SV_ModelIndex(name);
+
+ // ent.model = name;
+ ent.s.modelindex = i;
+
+ // if it is an inline model, get the size information for it
+ if (name.startsWith("*")) {
+ mod = CM.InlineModel(name);
+ VectorCopy(mod.mins, ent.mins);
+ VectorCopy(mod.maxs, ent.maxs);
+ SV_WORLD.SV_LinkEdict(ent);
+ }
+ }
+
+ /*
+ ===============
+ PF_Configstring
+
+ ===============
+ */
+ public static void PF_Configstring(int index, String val) {
+ if (index < 0 || index >= MAX_CONFIGSTRINGS)
+ Com.Error(ERR_DROP, "configstring: bad index " + index + "\n");
+
+ if (val == null)
+ val = "";
+
+ // change the string in sv
+ sv.configstrings[index] = val;
+
+ if (sv.state != ss_loading) { // send the update to everyone
+ SZ.Clear(sv.multicast);
+ MSG.WriteChar(sv.multicast, svc_configstring);
+ MSG.WriteShort(sv.multicast, index);
+ MSG.WriteString(sv.multicast, val);
+
+ SV_SEND.SV_Multicast(vec3_origin, MULTICAST_ALL_R);
+ }
+ }
+
+ public static void PF_WriteChar(int c) {
+ MSG.WriteChar(sv.multicast, c);
+ }
+ public static void PF_WriteByte(int c) {
+ MSG.WriteByte(sv.multicast, c);
+ }
+ public static void PF_WriteShort(int c) {
+ MSG.WriteShort(sv.multicast, c);
+ }
+ public static void PF_WriteLong(int c) {
+ MSG.WriteLong(sv.multicast, c);
+ }
+ public static void PF_WriteFloat(float f) {
+ MSG.WriteFloat(sv.multicast, f);
+ }
+ public static void PF_WriteString(String s) {
+ MSG.WriteString(sv.multicast, s);
+ }
+ public static void PF_WritePos(float[] pos) {
+ MSG.WritePos(sv.multicast, pos);
+ }
+ public static void PF_WriteDir(float[] dir) {
+ MSG.WriteDir(sv.multicast, dir);
+ }
+ public static void PF_WriteAngle(float f) {
+ MSG.WriteAngle(sv.multicast, f);
+ }
+
+ /*
+ =================
+ PF_inPVS
+
+ Also checks portalareas so that doors block sight
+ =================
+ */
+ public static boolean PF_inPVS(float[] p1, float[] p2) {
+ int leafnum;
+ int cluster;
+ int area1, area2;
+ byte mask[];
+
+ leafnum = CM.CM_PointLeafnum(p1);
+ cluster = CM.CM_LeafCluster(leafnum);
+ area1 = CM.CM_LeafArea(leafnum);
+ mask = CM.CM_ClusterPVS(cluster);
+
+ leafnum = CM.CM_PointLeafnum(p2);
+ cluster = CM.CM_LeafCluster(leafnum);
+ area2 = CM.CM_LeafArea(leafnum);
+
+ if (mask != null && (0 == (mask[cluster >> 3] & (1 << (cluster & 7)))))
+ return false;
+
+ if (!CM.CM_AreasConnected(area1, area2))
+ return false; // a door blocks sight
+
+ return true;
+ }
+
+ /*
+ =================
+ PF_inPHS
+
+ Also checks portalareas so that doors block sound
+ =================
+ */
+ public static boolean PF_inPHS(float[] p1, float[] p2) {
+ int leafnum;
+ int cluster;
+ int area1, area2;
+ byte mask[];
+
+ leafnum = CM.CM_PointLeafnum(p1);
+ cluster = CM.CM_LeafCluster(leafnum);
+ area1 = CM.CM_LeafArea(leafnum);
+ mask = CM.CM_ClusterPHS(cluster);
+
+ leafnum = CM.CM_PointLeafnum(p2);
+ cluster = CM.CM_LeafCluster(leafnum);
+ area2 = CM.CM_LeafArea(leafnum);
+
+ if (mask != null && (0 == (mask[cluster >> 3] & (1 << (cluster & 7)))))
+ return false; // more than one bounce away
+ if (!CM.CM_AreasConnected(area1, area2))
+ return false; // a door blocks hearing
+
+ return true;
+ }
+
+ public static void PF_StartSound(edict_t entity, int channel, int sound_num, float volume, float attenuation, float timeofs) {
+ if (null == entity)
+ return;
+
+ //TODO: impl SV_StartSound
+ //SV_SEND.SV_StartSound (null, entity, channel, sound_num, volume, attenuation, timeofs);
+ }
+
+ //==============================================
+
+ /*
+ ===============
+ SV_ShutdownGameProgs
+
+ Called when either the entire server is being killed, or
+ it is changing to a different game directory.
+ ===============
+ */
+ public static void SV_ShutdownGameProgs() {
+ if (ge == null)
+ return;
+ ge.Shutdown();
+ Sys.UnloadGame();
+ ge = null;
+ }
+
+ /*
+ ===============
+ SV_InitGameProgs
+
+ Init the game subsystem for a new map
+ ===============
+ */
+
+ public static void SV_InitGameProgs() {
+
+ // unload anything we have now
+ if (ge != null)
+ SV_ShutdownGameProgs();
+
+ game_import_t gimport = new game_import_t();
+
+ // all functions set in game_export_t (rst)
+ ge = GameBase.GetGameApi(gimport);
+
+ if (ge == null)
+ Com.Error(ERR_DROP, "failed to load game DLL");
+ if (ge.apiversion != GAME_API_VERSION)
+ Com.Error(ERR_DROP, "game is version " + ge.apiversion + " not " + GAME_API_VERSION);
+
+ ge.Init();
+ }
+}
diff --git a/src/jake2/server/SV_INIT.java b/src/jake2/server/SV_INIT.java
new file mode 100644
index 0000000..021c60c
--- /dev/null
+++ b/src/jake2/server/SV_INIT.java
@@ -0,0 +1,498 @@
+/*
+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.
+
+*/
+
+// Created on 14.01.2004 by RST.
+// $Id: SV_INIT.java,v 1.1 2004-07-07 19:59:47 hzi Exp $
+
+package jake2.server;
+
+import java.io.IOException;
+import java.io.RandomAccessFile;
+
+import jake2.*;
+import jake2.client.*;
+import jake2.game.*;
+import jake2.qcommon.*;
+import jake2.render.*;
+import jake2.sys.NET;
+import jake2.util.Lib;
+
+public class SV_INIT extends Globals {
+
+ public static server_static_t svs = new server_static_t(); // persistant server info
+ public static server_t sv = new server_t(); // local server
+
+ /*
+ ================
+ SV_FindIndex
+
+ ================
+ */
+ public static int SV_FindIndex(String name, int start, int max, boolean create) {
+ int i;
+
+ if (name == null || name.length() == 0)
+ return 0;
+
+ for (i = 1; i < max && sv.configstrings[start + i] != null; i++)
+ if (0 == strcmp(sv.configstrings[start + i], name))
+ return i;
+
+ if (!create)
+ return 0;
+
+ if (i == max)
+ Com.Error(ERR_DROP, "*Index: overflow");
+
+ //strncpy (sv.configstrings[start+i], name, sizeof(sv.configstrings[i]));
+ sv.configstrings[start + i] = name;
+
+ if (sv.state != ss_loading) { // send the update to everyone
+ SZ.Clear(sv.multicast);
+ MSG.WriteChar(sv.multicast, svc_configstring);
+ MSG.WriteShort(sv.multicast, start + i);
+ MSG.WriteString(sv.multicast, name);
+ SV_SEND.SV_Multicast(Game.vec3_origin, MULTICAST_ALL_R);
+ }
+
+ return i;
+ }
+
+ public static int SV_ModelIndex(String name) {
+ return SV_FindIndex(name, CS_MODELS, MAX_MODELS, true);
+ }
+
+ public static int SV_SoundIndex(String name) {
+ return SV_FindIndex(name, CS_SOUNDS, MAX_SOUNDS, true);
+ }
+
+ public static int SV_ImageIndex(String name) {
+ return SV_FindIndex(name, CS_IMAGES, MAX_IMAGES, true);
+ }
+
+ /*
+ ================
+ SV_CreateBaseline
+
+ Entity baselines are used to compress the update messages
+ to the clients -- only the fields that differ from the
+ baseline will be transmitted
+ ================
+ */
+ public static void SV_CreateBaseline() {
+ edict_t svent;
+ int entnum;
+
+ for (entnum = 1; entnum < SV_GAME.ge.num_edicts; entnum++) {
+ //svent = EDICT_NUM(entnum);
+ svent = SV_GAME.ge.edicts[entnum];
+
+ if (!svent.inuse)
+ continue;
+ if (0 == svent.s.modelindex && 0 == svent.s.sound && 0 == svent.s.effects)
+ continue;
+ svent.s.number = entnum;
+
+ //
+ // take current state as baseline
+ //
+ VectorCopy(svent.s.origin, svent.s.old_origin);
+ // rst: bugfix
+ sv.baselines[entnum] = svent.s.getClone();
+ }
+ }
+
+ /*
+ =================
+ SV_CheckForSavegame
+ =================
+ */
+ public static void SV_CheckForSavegame() {
+ //char name[MAX_OSPATH];
+ String name;
+ //FILE *f;
+ RandomAccessFile f;
+
+ int i;
+
+ if (SV_MAIN.sv_noreload.value != 0)
+ return;
+
+ if (Cvar.VariableValue("deathmatch") != 0)
+ return;
+
+ name = FS.Gamedir() + "/save/current/" + sv.name + ".sav";
+ try {
+ f = new RandomAccessFile(name, "r");
+ }
+
+ catch (Exception e) {
+ return; // no savegame
+ }
+
+ try {
+ f.close();
+ }
+ catch (IOException e1) {
+ e1.printStackTrace();
+ }
+
+ SV_WORLD.SV_ClearWorld();
+
+ // get configstrings and areaportals
+ SV_CCMDS.SV_ReadLevelFile();
+
+ if (!sv.loadgame) { // coming back to a level after being in a different
+ // level, so run it for ten seconds
+
+ // rlava2 was sending too many lightstyles, and overflowing the
+ // reliable data. temporarily changing the server state to loading
+ // prevents these from being passed down.
+ int previousState; // PGM
+
+ previousState = sv.state; // PGM
+ sv.state = ss_loading; // PGM
+ for (i = 0; i < 100; i++)
+ SV_GAME.ge.RunFrame();
+
+ sv.state = previousState; // PGM
+ }
+ }
+
+ /*
+ ================
+ SV_SpawnServer
+
+ Change the server to a new map, taking all connected
+ clients along with it.
+
+ ================
+ */
+ public static void SV_SpawnServer(
+ String server,
+ String spawnpoint,
+ int serverstate,
+ boolean attractloop,
+ boolean loadgame) {
+ int i;
+ int checksum = 0;
+
+ if (attractloop)
+ Cvar.Set("paused", "0");
+
+ Com.Printf("------- Server Initialization -------\n");
+
+ Com.DPrintf("SpawnServer: " + server + "\n");
+ if (sv.demofile != null)
+ try {
+ sv.demofile.close();
+ }
+ catch (Exception e) {
+ }
+
+ svs.spawncount++; // any partially connected client will be
+ // restarted
+ sv.state = ss_dead;
+
+ Com.SetServerState(sv.state);
+
+ // wipe the entire per-level structure
+ //memset(sv, 0, sizeof(sv));
+ sv = new server_t();
+
+ svs.realtime = 0;
+ sv.loadgame = loadgame;
+ sv.attractloop = attractloop;
+
+ // save name for levels that don't set message
+ sv.configstrings[CS_NAME]= server;
+
+ if (Cvar.VariableValue("deathmatch")!=0) {
+ sv.configstrings[CS_AIRACCEL] = ""+SV_MAIN.sv_airaccelerate.value;
+ PMove.pm_airaccelerate = SV_MAIN.sv_airaccelerate.value;
+ }
+ else {
+ sv.configstrings[CS_AIRACCEL] = "0";
+ PMove.pm_airaccelerate = 0;
+ }
+
+ SZ.Init(sv.multicast, sv.multicast_buf, sv.multicast_buf.length);
+
+ sv.name = server;
+
+ // leave slots at start for clients only
+ for (i = 0; i < SV_MAIN.maxclients.value; i++) {
+ // needs to reconnect
+ if (svs.clients[i].state > cs_connected)
+ svs.clients[i].state = cs_connected;
+ svs.clients[i].lastframe = -1;
+ }
+
+ sv.time = 1000;
+
+ sv.name=server;
+ sv.configstrings[CS_NAME] = server;
+
+ CM.intwrap checksum_iw = new CM.intwrap(checksum);
+
+ if (serverstate != ss_game) {
+ sv.models[1] = CM.CM_LoadMap("", false, checksum_iw); // no real map
+ }
+ else {
+ sv.configstrings[CS_MODELS + 1] = "maps/" + server + ".bsp";
+ sv.models[1] = CM.CM_LoadMap(sv.configstrings[CS_MODELS + 1], false, checksum_iw);
+ }
+ checksum = checksum_iw.i;
+ sv.configstrings[CS_MAPCHECKSUM] = "" + checksum;
+
+ //
+ // clear physics interaction links
+ //
+ SV_WORLD.SV_ClearWorld();
+
+ for (i = 1; i < CM.CM_NumInlineModels(); i++) {
+ sv.configstrings[CS_MODELS + 1 + i] = "*" + i;
+ // copy references
+ sv.models[i + 1] = CM.InlineModel(sv.configstrings[CS_MODELS + 1 + i]);
+ }
+
+ //
+ // spawn the rest of the entities on the map
+ //
+
+ // precache and static commands can be issued during
+ // map initialization
+ sv.state = ss_loading;
+ Com.SetServerState(sv.state);
+
+ // load and spawn all other entities
+ SV_GAME.ge.SpawnEntities(sv.name, CM.CM_EntityString(), spawnpoint);
+
+ // run two frames to allow everything to settle
+ SV_GAME.ge.RunFrame();
+ SV_GAME.ge.RunFrame();
+
+ // all precaches are complete
+ sv.state = serverstate;
+ Com.SetServerState(sv.state);
+
+ // create a baseline for more efficient communications
+ SV_CreateBaseline();
+
+ // check for a savegame
+ SV_CheckForSavegame();
+
+ // set serverinfo variable
+ Cvar.FullSet("mapname", sv.name, CVAR_SERVERINFO | CVAR_NOSET);
+
+ Com.Printf("-------------------------------------\n");
+ }
+
+ /*
+ ==============
+ SV_InitGame
+
+ A brand new game has been started
+ ==============
+ */
+ public static void SV_InitGame() {
+ int i;
+ edict_t ent;
+ //char idmaster[32];
+ String idmaster;
+
+ if (svs.initialized) {
+ // cause any connected clients to reconnect
+ SV_MAIN.SV_Shutdown("Server restarted\n", true);
+ }
+ else {
+ // make sure the client is down
+ CL.Drop();
+ SCR.BeginLoadingPlaque();
+ }
+
+ // get any latched variable changes (maxclients, etc)
+ Cvar.GetLatchedVars();
+
+ svs.initialized = true;
+
+ if (Cvar.VariableValue("coop")!=0 && Cvar.VariableValue("deathmatch")!=0) {
+ Com.Printf("Deathmatch and Coop both set, disabling Coop\n");
+ Cvar.FullSet("coop", "0", CVAR_SERVERINFO | CVAR_LATCH);
+ }
+
+ // dedicated servers are can't be single player and are usually DM
+ // so unless they explicity set coop, force it to deathmatch
+ if (dedicated.value!=0) {
+ if (0==Cvar.VariableValue("coop"))
+ Cvar.FullSet("deathmatch", "1", CVAR_SERVERINFO | CVAR_LATCH);
+ }
+
+ // init clients
+ if (Cvar.VariableValue("deathmatch")!=0) {
+ if (SV_MAIN.maxclients.value <= 1)
+ Cvar.FullSet("maxclients", "8", CVAR_SERVERINFO | CVAR_LATCH);
+ else if (SV_MAIN.maxclients.value > MAX_CLIENTS)
+ Cvar.FullSet("maxclients", "" + MAX_CLIENTS, CVAR_SERVERINFO | CVAR_LATCH);
+ }
+ else if (Cvar.VariableValue("coop")!=0) {
+ if (SV_MAIN.maxclients.value <= 1 || SV_MAIN.maxclients.value > 4)
+ Cvar.FullSet("maxclients", "4", CVAR_SERVERINFO | CVAR_LATCH);
+
+ }
+ else // non-deathmatch, non-coop is one player
+ {
+ Cvar.FullSet("maxclients", "1", CVAR_SERVERINFO | CVAR_LATCH);
+ }
+
+ svs.spawncount = rand();
+ //svs.clients = Z_Malloc(sizeof(client_t) * maxclients.value);
+ svs.clients = new client_t[(int) SV_MAIN.maxclients.value];
+ for (int n=0; n < svs.clients.length; n++)
+ svs.clients[n]= new client_t();
+
+ svs.num_client_entities = ((int) SV_MAIN.maxclients.value) * UPDATE_BACKUP * 64; //ok.
+
+ //svs.client_entities = Z_Malloc(sizeof(entity_state_t) * svs.num_client_entities);
+ svs.client_entities = new entity_state_t[svs.num_client_entities];
+ for (int n=0; n < svs.client_entities.length; n++)
+ svs.client_entities[n] = new entity_state_t(null);
+
+ // init network stuff
+ NET.Config((SV_MAIN.maxclients.value > 1)); //ok!
+
+ // heartbeats will always be sent to the id master
+ svs.last_heartbeat = -99999; // send immediately
+ idmaster = "192.246.40.37:" + PORT_MASTER;
+ NET.StringToAdr(idmaster, SV_MAIN.master_adr[0]);
+
+ // init game
+ SV_GAME.SV_InitGameProgs(); // bis hier alles ok!
+
+ for (i = 0; i < SV_MAIN.maxclients.value; i++) {
+ ent = SV_GAME.ge.edicts[i + 1];
+
+ //ent.s.number = i + 1; //dont need this, ent.s.number already set.
+ svs.clients[i].edict = ent;
+ //memset(& svs.clients[i].lastcmd, 0, sizeof(svs.clients[i].lastcmd));
+ svs.clients[i].lastcmd = new usercmd_t();
+ }
+ }
+
+ /*
+ ======================
+ SV_Map
+
+ the full syntax is:
+
+ map [*]<map>$<startspot>+<nextserver>
+
+ command from the console or progs.
+ Map can also be a.cin, .pcx, or .dm2 file
+ Nextserver is used to allow a cinematic to play, then proceed to
+ another level:
+
+ map tram.cin+jail_e3
+ ======================
+ */
+ public static void SV_Map(boolean attractloop, String levelstring, boolean loadgame) {
+ //char level[MAX_QPATH];
+ //char *ch;
+ int l;
+ //char spawnpoint[MAX_QPATH];
+
+ String level, ch, spawnpoint;
+
+ sv.loadgame = loadgame;
+ sv.attractloop = attractloop;
+
+ if (sv.state == ss_dead && !sv.loadgame)
+ SV_InitGame(); // the game is just starting
+
+ level = levelstring; // bis hier her ok.
+
+ // if there is a + in the map, set nextserver to the remainder
+
+ //was:
+ // ch = strstr(level, "+");
+ // if (ch)
+ // {
+ // *ch = 0;
+ // Cvar_Set ("nextserver", va("gamemap \"%s\"", ch+1));
+ // }
+ // else
+ // Cvar_Set ("nextserver", "");
+
+ int c = level.indexOf('+');
+ if (c != -1) {
+ Cvar.Set("nextserver","gamemap \"" + level.substring(c+1) + "\"");
+ level = level.substring(0, c);
+ }
+ else {
+ Cvar.Set("nextserver", "");
+ }
+
+ //ZOID special hack for end game screen in coop mode
+ if (Cvar.VariableValue("coop")!=0 && !level.equals( "victory.pcx"))
+ Cvar.Set("nextserver", "gamemap \"*base1\"");
+
+ // if there is a $, use the remainder as a spawnpoint
+ int pos = level.indexOf('$');
+ if (pos!=-1) {
+ //* ch = 0;
+ spawnpoint = level.substring(pos + 1);
+ level = level.substring(0,pos);
+
+ }
+ else
+ //spawnpoint[0] = 0;
+ spawnpoint = "";
+
+ // skip the end-of-unit flag if necessary
+ if (level.charAt(0) == '*')
+ level = level.substring(1);
+
+ l = strlen(level);
+ if (l > 4 && level.endsWith(".cin")) {
+ SCR.BeginLoadingPlaque(); // for local system
+ SV_SEND.SV_BroadcastCommand("changing\n");
+ SV_SpawnServer(level, spawnpoint, ss_cinematic, attractloop, loadgame);
+ }
+ else if (l > 4 && level.endsWith(".dm2")) {
+ SCR.BeginLoadingPlaque(); // for local system
+ SV_SEND.SV_BroadcastCommand("changing\n");
+ SV_SpawnServer(level, spawnpoint, ss_demo, attractloop, loadgame);
+ }
+ else if (l > 4 && level.endsWith(".pcx")) {
+ SCR.BeginLoadingPlaque(); // for local system
+ SV_SEND.SV_BroadcastCommand("changing\n");
+ SV_SpawnServer(level, spawnpoint, ss_pic, attractloop, loadgame);
+ }
+ else {
+ SCR.BeginLoadingPlaque(); // for local system
+ SV_SEND.SV_BroadcastCommand("changing\n");
+ SV_SEND.SV_SendClientMessages();
+ SV_SpawnServer(level, spawnpoint, ss_game, attractloop, loadgame);
+ Cbuf.CopyToDefer();
+ }
+
+ SV_SEND.SV_BroadcastCommand("reconnect\n");
+ }
+}
diff --git a/src/jake2/server/SV_MAIN.java b/src/jake2/server/SV_MAIN.java
new file mode 100644
index 0000000..e3059ec
--- /dev/null
+++ b/src/jake2/server/SV_MAIN.java
@@ -0,0 +1,1030 @@
+/*
+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.
+
+*/
+
+// Created on 13.01.2004 by RST.
+// $Id: SV_MAIN.java,v 1.1 2004-07-07 19:59:49 hzi Exp $
+
+package jake2.server;
+
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+import jake2.*;
+import jake2.client.*;
+import jake2.game.*;
+import jake2.qcommon.*;
+import jake2.render.*;
+import jake2.sys.NET;
+import jake2.sys.Sys;
+import jake2.util.Lib;
+
+public class SV_MAIN extends SV_GAME {
+
+ static netadr_t master_adr[] = new netadr_t[MAX_MASTERS]; // address of group servers
+ static {
+ for (int i = 0; i < MAX_MASTERS; i++) {
+ master_adr[i] = new netadr_t();
+ }
+ }
+ public static client_t sv_client; // current client
+
+ public static cvar_t sv_paused;
+ public static cvar_t sv_timedemo;
+
+ public static cvar_t sv_enforcetime;
+
+ public static cvar_t timeout; // seconds without any message
+ public static cvar_t zombietime; // seconds to sink messages after disconnect
+
+ public static cvar_t rcon_password; // password for remote server commands
+
+ public static cvar_t allow_download;
+ public static cvar_t allow_download_players;
+ public static cvar_t allow_download_models;
+ public static cvar_t allow_download_sounds;
+ public static cvar_t allow_download_maps;
+
+ public static cvar_t sv_airaccelerate;
+
+ public static cvar_t sv_noreload; // don't reload level state when reentering
+
+ public static cvar_t maxclients; // FIXME: rename sv_maxclients
+ public static cvar_t sv_showclamp;
+
+ public static cvar_t hostname;
+ public static cvar_t public_server; // should heartbeats be sent
+
+ public static cvar_t sv_reconnect_limit; // minimum seconds between connect messages
+
+ //============================================================================
+
+ /*
+ =====================
+ SV_DropClient
+
+ Called when the player is totally leaving the server, either willingly
+ or unwillingly. This is NOT called if the entire server is quiting
+ or crashing.
+ =====================
+ */
+ public static void SV_DropClient(client_t drop) {
+ // add the disconnect
+ MSG.WriteByte(drop.netchan.message, Defines.svc_disconnect);
+
+ if (drop.state == Defines.cs_spawned) {
+ // call the prog function for removing a client
+ // this will remove the body, among other things
+ SV_GAME.ge.ClientDisconnect(drop.edict);
+ }
+
+ if (drop.download != null) {
+ FS.FreeFile(drop.download);
+ drop.download = null;
+ }
+
+ drop.state = Defines.cs_zombie; // become free in a few seconds
+ drop.name = "";
+ }
+
+ /*
+ ==============================================================================
+
+ CONNECTIONLESS COMMANDS
+
+ ==============================================================================
+ */
+
+ /*
+ ===============
+ SV_StatusString
+
+ Builds the string that is sent as heartbeats and status replies
+ ===============
+ */
+ public static String SV_StatusString() {
+ String player;
+ String status = "";
+ int i;
+ client_t cl;
+ int statusLength;
+ int playerLength;
+
+ status = Cvar.Serverinfo() + "\n";
+
+ for (i = 0; i < maxclients.value; i++) {
+ cl = svs.clients[i];
+ if (cl.state == Defines.cs_connected || cl.state == Defines.cs_spawned) {
+ player = "" + cl.edict.client.ps.stats[Defines.STAT_FRAGS] + " " + cl.ping + "\"" + cl.name + "\"\n";
+
+ playerLength = player.length();
+ statusLength = status.length();
+
+ if (statusLength + playerLength >= 1024)
+ break; // can't hold any more
+
+ status += player;
+ }
+ }
+
+ return status;
+ }
+
+ /*
+ ================
+ SVC_Status
+
+ Responds with all the info that qplug or qspy can see
+ ================
+ */
+ public static void SVC_Status() {
+ Netchan.OutOfBandPrint(NS_SERVER, Netchan.net_from, "print\n" + SV_StatusString());
+ }
+
+ /*
+ ================
+ SVC_Ack
+
+ ================
+ */
+ public static void SVC_Ack() {
+ Com.Printf("Ping acknowledge from " + NET.AdrToString(Netchan.net_from) + "\n");
+ }
+
+ /*
+ ================
+ SVC_Info
+
+ Responds with short info for broadcast scans
+ The second parameter should be the current protocol version number.
+ ================
+ */
+ public static void SVC_Info() {
+ String string;
+ int i, count;
+ int version;
+
+ if (maxclients.value == 1)
+ return; // ignore in single player
+
+ version = atoi(Cmd.Argv(1));
+
+ if (version != PROTOCOL_VERSION)
+ string = hostname.string + ": wrong version\n";
+ else {
+ count = 0;
+ for (i = 0; i < maxclients.value; i++)
+ if (svs.clients[i].state >= cs_connected)
+ count++;
+
+ string = hostname.string + " " + sv.name + " " + count + "/" + (int) maxclients.value + "\n";
+ }
+
+ Netchan.OutOfBandPrint(NS_SERVER, Netchan.net_from, "info\n" + string);
+ }
+
+ /*
+ ================
+ SVC_Ping
+
+ Just responds with an acknowledgement
+ ================
+ */
+ public static void SVC_Ping() {
+ Netchan.OutOfBandPrint(NS_SERVER, Netchan.net_from, "ack");
+ }
+
+ /*
+ =================
+ SVC_GetChallenge
+
+ Returns a challenge number that can be used
+ in a subsequent client_connect command.
+ We do this to prevent denial of service attacks that
+ flood the server with invalid connection IPs. With a
+ challenge, they must give a valid IP address.
+ =================
+ */
+ public static void SVC_GetChallenge() {
+ int i;
+ int oldest;
+ int oldestTime;
+
+ oldest = 0;
+ oldestTime = 0x7fffffff;
+
+ // see if we already have a challenge for this ip
+ for (i = 0; i < MAX_CHALLENGES; i++) {
+ if (NET.NET_CompareBaseAdr(Netchan.net_from, svs.challenges[i].adr))
+ break;
+ if (svs.challenges[i].time < oldestTime) {
+ oldestTime = svs.challenges[i].time;
+ oldest = i;
+ }
+ }
+
+ if (i == MAX_CHALLENGES) {
+ // overwrite the oldest
+ svs.challenges[oldest].challenge = rand() & 0x7fff;
+ svs.challenges[oldest].adr = Netchan.net_from;
+ svs.challenges[oldest].time = (int) Globals.curtime;
+ i = oldest;
+ }
+
+ // send it back
+ Netchan.OutOfBandPrint(NS_SERVER, Netchan.net_from, "challenge " + svs.challenges[i].challenge);
+ }
+
+ /*
+ ==================
+ SVC_DirectConnect
+
+ A connection request that did not come from the master
+ ==================
+ */
+ public static void SVC_DirectConnect() {
+ //char userinfo[MAX_INFO_STRING];
+ String userinfo;
+ netadr_t adr;
+ int i;
+ client_t cl;
+
+ edict_t ent;
+ int edictnum;
+ int version;
+ int qport;
+
+ adr = Netchan.net_from;
+
+ Com.DPrintf("SVC_DirectConnect ()\n");
+
+ version = atoi(Cmd.Argv(1));
+ if (version != PROTOCOL_VERSION) {
+ Netchan.OutOfBandPrint(NS_SERVER, adr, "print\nServer is version " + VERSION + "\n");
+ Com.DPrintf(" rejected connect from version " + version + "\n");
+ return;
+ }
+
+ qport = atoi(Cmd.Argv(2));
+ int challenge = atoi(Cmd.Argv(3));
+ userinfo = Cmd.Argv(4);
+
+ //userinfo[sizeof(userinfo) - 1] = 0;
+
+ // force the IP key/value pair so the game can filter based on ip
+ userinfo = Info.Info_SetValueForKey1(userinfo, "ip", NET.AdrToString(Netchan.net_from));
+
+ // attractloop servers are ONLY for local clients
+ if (sv.attractloop) {
+ if (!NET.IsLocalAddress(adr)) {
+ Com.Printf("Remote connect in attract loop. Ignored.\n");
+ Netchan.OutOfBandPrint(NS_SERVER, adr, "print\nConnection refused.\n");
+ return;
+ }
+ }
+
+ // see if the challenge is valid
+ if (!NET.IsLocalAddress(adr)) {
+ for (i = 0; i < MAX_CHALLENGES; i++) {
+ if (NET.NET_CompareBaseAdr(Netchan.net_from, svs.challenges[i].adr)) {
+ if (challenge == svs.challenges[i].challenge)
+ break; // good
+ Netchan.OutOfBandPrint(NS_SERVER, adr, "print\nBad challenge.\n");
+ return;
+ }
+ }
+ if (i == MAX_CHALLENGES) {
+ Netchan.OutOfBandPrint(NS_SERVER, adr, "print\nNo challenge for address.\n");
+ return;
+ }
+ }
+
+ //newcl = temp;
+ //memset (newcl, 0, sizeof(client_t));
+ //newcl = new client_t();
+
+ // if there is already a slot for this ip, reuse it
+ for (i = 0; i < maxclients.value; i++) {
+ cl = svs.clients[i];
+
+ if (cl.state == cs_free)
+ continue;
+ if (NET.NET_CompareBaseAdr(adr, cl.netchan.remote_address)
+ && (cl.netchan.qport == qport || adr.port == cl.netchan.remote_address.port)) {
+ if (!NET.IsLocalAddress(adr) && (svs.realtime - cl.lastconnect) < ((int) sv_reconnect_limit.value * 1000)) {
+ Com.DPrintf(NET.AdrToString(adr) + ":reconnect rejected : too soon\n");
+ return;
+ }
+ Com.Printf(NET.AdrToString(adr) + ":reconnect\n");
+
+ gotnewcl(i, challenge, userinfo, adr, qport);
+ return;
+ }
+ }
+
+ // find a client slot
+ //newcl = null;
+ int index = -1;
+ for (i = 0; i < maxclients.value; i++) {
+ cl = svs.clients[i];
+ if (cl.state == cs_free) {
+ index = i;
+ break;
+ }
+ }
+ if (index == -1) {
+ Netchan.OutOfBandPrint(NS_SERVER, adr, "print\nServer is full.\n");
+ Com.DPrintf("Rejected a connection.\n");
+ return;
+ }
+ gotnewcl(index, challenge, userinfo, adr, qport);
+ }
+
+ public static void gotnewcl(int i, int challenge, String userinfo, netadr_t adr, int qport) {
+ // build a new connection
+ // accept the new client
+ // this is the only place a client_t is ever initialized
+ //*newcl = temp;
+
+ sv_client = svs.clients[i];
+ //edictnum = (newcl-svs.clients)+1;
+ int edictnum = i + 1;
+ edict_t ent = ge.edicts[edictnum];
+ svs.clients[i].edict = ent;
+ svs.clients[i].challenge = challenge; // save challenge for checksumming
+
+ // get the game a chance to reject this connection or modify the userinfo
+ if (!(ge.ClientConnect(ent, userinfo))) {
+ if (Info.Info_ValueForKey(userinfo, "rejmsg") != null)
+ Netchan.OutOfBandPrint(
+ NS_SERVER,
+ adr,
+ "print\n" + Info.Info_ValueForKey(userinfo, "rejmsg") + "\nConnection refused.\n");
+ else
+ Netchan.OutOfBandPrint(NS_SERVER, adr, "print\nConnection refused.\n");
+ Com.DPrintf("Game rejected a connection.\n");
+ return;
+ }
+
+ // parse some info from the info strings
+ svs.clients[i].userinfo = userinfo;
+ SV_UserinfoChanged(svs.clients[i]);
+
+ // send the connect packet to the client
+ Netchan.OutOfBandPrint(NS_SERVER, adr, "client_connect");
+
+ Netchan.Setup(NS_SERVER, svs.clients[i].netchan, adr, qport);
+
+ svs.clients[i].state = cs_connected;
+
+ SZ.Init(svs.clients[i].datagram, svs.clients[i].datagram_buf, svs.clients[i].datagram_buf.length);
+ svs.clients[i].datagram.allowoverflow = true;
+ svs.clients[i].lastmessage = svs.realtime; // don't timeout
+ svs.clients[i].lastconnect = svs.realtime;
+ Com.DPrintf("new client added.\n");
+ }
+
+ public static int Rcon_Validate() {
+ if (0 == strlen(rcon_password.string))
+ return 0;
+
+ if (0 != strcmp(Cmd.Argv(1), rcon_password.string))
+ return 0;
+
+ return 1;
+ }
+
+ /*
+ ===============
+ SVC_RemoteCommand
+
+ A client issued an rcon command.
+ Shift down the remaining args
+ Redirect all printfs
+ ===============
+ */
+ public static void SVC_RemoteCommand() {
+ int i;
+ //char remaining[1024];
+ String remaining;
+
+ i = Rcon_Validate();
+
+ String msg = new String(net_message.data, 4, -1);
+
+ if (i == 0)
+ Com.Printf("Bad rcon from " + NET.AdrToString(Netchan.net_from) + ":\n" + msg + "\n");
+ else
+ Com.Printf("Rcon from " + NET.AdrToString(Netchan.net_from) + ":\n" + msg + "\n");
+
+ Com.BeginRedirect(RD_PACKET, SV_SEND.sv_outputbuf, SV_OUTPUTBUF_LENGTH, new Com.RD_Flusher() {
+ public void rd_flush(int target, byte[] buffer) {
+ SV_SEND.SV_FlushRedirect(target, buffer);
+ }
+ });
+
+ if (0 == Rcon_Validate()) {
+ Com.Printf("Bad rcon_password.\n");
+ }
+ else {
+ remaining = "";
+
+ for (i = 2; i < Cmd.Argc(); i++) {
+ strcat(remaining, Cmd.Argv(i));
+ strcat(remaining, " ");
+ }
+
+ Cmd.ExecuteString(remaining);
+ }
+
+ Com.EndRedirect();
+ }
+
+ /*
+ =================
+ SV_ConnectionlessPacket
+
+ A connectionless packet has four leading 0xff
+ characters to distinguish it from a game channel.
+ Clients that are in the game can still send
+ connectionless packets.
+ =================
+ */
+ public static void SV_ConnectionlessPacket() {
+ String s;
+ String c;
+
+ MSG.BeginReading(net_message);
+ MSG.ReadLong(net_message); // skip the -1 marker
+
+ s = MSG.ReadStringLine(net_message);
+
+ Cmd.TokenizeString(s.toCharArray(), false);
+
+ c = Cmd.Argv(0);
+ Com.Printf("Packet " + NET.AdrToString(Netchan.net_from) + " : " + c + "\n");
+ //Com.Printf(Lib.hexDump(net_message.data, 64, false) + "\n");
+
+ if (0 == strcmp(c, "ping"))
+ SVC_Ping();
+ else if (0 == strcmp(c, "ack"))
+ SVC_Ack();
+ else if (0 == strcmp(c, "status"))
+ SVC_Status();
+ else if (0 == strcmp(c, "info"))
+ SVC_Info();
+ else if (0 == strcmp(c, "getchallenge"))
+ SVC_GetChallenge();
+ else if (0 == strcmp(c, "connect"))
+ SVC_DirectConnect();
+ else if (0 == strcmp(c, "rcon"))
+ SVC_RemoteCommand();
+ else
+ {
+ Com.Printf("bad connectionless packet from " + NET.AdrToString(Netchan.net_from) + "\n");
+ Com.Printf("[" + s + "]\n");
+ Com.Printf("" + Lib.hexDump(net_message.data, 128, false));
+ }
+ }
+
+ //============================================================================
+
+ /*
+ ===================
+ SV_CalcPings
+
+ Updates the cl.ping variables
+ ===================
+ */
+ public static void SV_CalcPings() {
+ int i, j;
+ client_t cl;
+ int total, count;
+
+ for (i = 0; i < maxclients.value; i++) {
+ cl = svs.clients[i];
+ if (cl.state != cs_spawned)
+ continue;
+
+ total = 0;
+ count = 0;
+ for (j = 0; j < LATENCY_COUNTS; j++) {
+ if (cl.frame_latency[j] > 0) {
+ count++;
+ total += cl.frame_latency[j];
+ }
+ }
+ if (0 == count)
+ cl.ping = 0;
+ else
+ cl.ping = total / count;
+
+ // let the game dll know about the ping
+ cl.edict.client.ping = cl.ping;
+ }
+ }
+
+ /*
+ ===================
+ SV_GiveMsec
+
+ Every few frames, gives all clients an allotment of milliseconds
+ for their command moves. If they exceed it, assume cheating.
+ ===================
+ */
+ public static void SV_GiveMsec() {
+ int i;
+ client_t cl;
+
+ if ((sv.framenum & 15) != 0)
+ return;
+
+ for (i = 0; i < maxclients.value; i++) {
+ cl = svs.clients[i];
+ if (cl.state == cs_free)
+ continue;
+
+ cl.commandMsec = 1800; // 1600 + some slop
+ }
+ }
+
+ /*
+ =================
+ SV_ReadPackets
+ =================
+ */
+ public static void SV_ReadPackets() {
+ int i;
+ client_t cl;
+ int qport =0;
+
+ while (NET.GetPacket(NS_SERVER, Netchan.net_from, net_message)) {
+
+ // check for connectionless packet (0xffffffff) first
+ if ((net_message.data[0] == -1)
+ && (net_message.data[1] == -1)
+ && (net_message.data[2] == -1)
+ && (net_message.data[3] == -1)) {
+ SV_ConnectionlessPacket();
+ continue;
+ }
+
+ // read the qport out of the message so we can fix up
+ // stupid address translating routers
+ MSG.BeginReading(net_message);
+ MSG.ReadLong(net_message); // sequence number
+ MSG.ReadLong(net_message); // sequence number
+ qport = MSG.ReadShort(net_message) & 0xffff;
+
+ // check for packets from connected clients
+ for (i = 0; i < maxclients.value; i++) {
+ cl = svs.clients[i];
+ if (cl.state == cs_free)
+ continue;
+ if (!NET.NET_CompareBaseAdr(Netchan.net_from, cl.netchan.remote_address))
+ continue;
+ if (cl.netchan.qport != qport)
+ continue;
+ if (cl.netchan.remote_address.port != Netchan.net_from.port) {
+ Com.Printf("SV_ReadPackets: fixing up a translated port\n");
+ cl.netchan.remote_address.port = Netchan.net_from.port;
+ }
+
+ if (Netchan.Process(cl.netchan, net_message)) { // this is a valid, sequenced packet, so process it
+ if (cl.state != cs_zombie) {
+ cl.lastmessage = svs.realtime; // don't timeout
+ SV_USER.SV_ExecuteClientMessage(cl);
+ }
+ }
+ break;
+ }
+
+ if (i != maxclients.value)
+ continue;
+ }
+ }
+
+ /*
+ ==================
+ SV_CheckTimeouts
+
+ If a packet has not been received from a client for timeout.value
+ seconds, drop the conneciton. Server frames are used instead of
+ realtime to avoid dropping the local client while debugging.
+
+ When a client is normally dropped, the client_t goes into a zombie state
+ for a few seconds to make sure any final reliable message gets resent
+ if necessary
+ ==================
+ */
+ public static void SV_CheckTimeouts() {
+ int i;
+ client_t cl;
+ int droppoint;
+ int zombiepoint;
+
+ droppoint = (int) (svs.realtime - 1000 * timeout.value);
+ zombiepoint = (int) (svs.realtime - 1000 * zombietime.value);
+
+ for (i = 0; i < maxclients.value; i++) {
+ cl = svs.clients[i];
+ // message times may be wrong across a changelevel
+ if (cl.lastmessage > svs.realtime)
+ cl.lastmessage = svs.realtime;
+
+ if (cl.state == cs_zombie && cl.lastmessage < zombiepoint) {
+ cl.state = cs_free; // can now be reused
+ continue;
+ }
+ if ((cl.state == cs_connected || cl.state == cs_spawned) && cl.lastmessage < droppoint) {
+ SV_SEND.SV_BroadcastPrintf(PRINT_HIGH, cl.name + " timed out\n");
+ SV_DropClient(cl);
+ cl.state = cs_free; // don't bother with zombie state
+ }
+ }
+ }
+
+ /*
+ ================
+ SV_PrepWorldFrame
+
+ This has to be done before the world logic, because
+ player processing happens outside RunWorldFrame
+ ================
+ */
+ public static void SV_PrepWorldFrame() {
+ edict_t ent;
+ int i;
+
+ for (i = 0; i < ge.num_edicts; i++) {
+ ent = SV_GAME.ge.edicts[i];
+ // events only last for a single message
+ ent.s.event = 0;
+ }
+
+ }
+
+ /*
+ =================
+ SV_RunGameFrame
+ =================
+ */
+ public static void SV_RunGameFrame() {
+ if (host_speeds.value != 0)
+ time_before_game = Sys.Milliseconds();
+
+ // we always need to bump framenum, even if we
+ // don't run the world, otherwise the delta
+ // compression can get confused when a client
+ // has the "current" frame
+ sv.framenum++;
+ sv.time = sv.framenum * 100;
+
+ // don't run if paused
+ if (0 == sv_paused.value || maxclients.value > 1) {
+ ge.RunFrame();
+
+ // never get more than one tic behind
+ if (sv.time < svs.realtime) {
+ //if (sv_showclamp.value != 0)
+ Com.Printf("sv highclamp\n");
+ svs.realtime = sv.time;
+ }
+ }
+
+ if (host_speeds.value != 0)
+ time_after_game = Sys.Milliseconds();
+
+ }
+
+ /*
+ ==================
+ SV_Frame
+
+ ==================
+ */
+ public static void SV_Frame(long msec) {
+ Globals.time_before_game = Globals.time_after_game = 0;
+
+ // if server is not active, do nothing
+ if (!svs.initialized)
+ return;
+
+ svs.realtime += msec;
+
+ // keep the random time dependent
+ Lib.rand();
+
+ // check timeouts
+ SV_CheckTimeouts();
+
+ // get packets from clients
+ SV_ReadPackets();
+
+ //if (Game.g_edicts[1] !=null)
+ // Com.p("player at:" + Lib.vtofsbeaty(Game.g_edicts[1].s.origin ));
+
+ // move autonomous things around if enough time has passed
+ if (0== sv_timedemo.value && svs.realtime < sv.time) {
+ // never let the time get too far off
+ if (sv.time - svs.realtime > 100) {
+ //if (sv_showclamp.value != 0)
+ Com.Printf("sv lowclamp\n");
+ svs.realtime = sv.time - 100;
+ }
+ NET.NET_Sleep(sv.time - svs.realtime);
+ return;
+ }
+
+ // update ping based on the last known frame from all clients
+ //TODO: dont need yet
+ SV_CalcPings();
+
+ // give the clients some timeslices
+ //TODO: dont need yet
+ SV_GiveMsec();
+
+ // let everything in the world think and move
+ SV_RunGameFrame();
+
+ // send messages back to the clients that had packets read this frame
+ SV_SEND.SV_SendClientMessages();
+
+ // save the entire world state if recording a serverdemo
+ //TODO: dont need yet
+ //SV_WORLD.SV_RecordDemoMessage();
+
+ // send a heartbeat to the master if needed
+ //TODO: dont need yet
+ Master_Heartbeat();
+
+ // clear teleport flags, etc for next frame
+ SV_PrepWorldFrame();
+
+ }
+
+ //============================================================================
+
+ /*
+ ================
+ Master_Heartbeat
+
+ Send a message to the master every few minutes to
+ let it know we are alive, and log information
+ ================
+ */
+ public static final int HEARTBEAT_SECONDS = 300;
+ public static void Master_Heartbeat() {
+ String string;
+ int i;
+
+ // pgm post3.19 change, cvar pointer not validated before dereferencing
+ if (dedicated == null || 0 == dedicated.value)
+ return; // only dedicated servers send heartbeats
+
+ // pgm post3.19 change, cvar pointer not validated before dereferencing
+ if (null == public_server || 0 == public_server.value)
+ return; // a private dedicated game
+
+ // check for time wraparound
+ if (svs.last_heartbeat > svs.realtime)
+ svs.last_heartbeat = svs.realtime;
+
+ if (svs.realtime - svs.last_heartbeat < HEARTBEAT_SECONDS * 1000)
+ return; // not time to send yet
+
+ svs.last_heartbeat = svs.realtime;
+
+ // send the same string that we would give for a status OOB command
+ string = SV_StatusString();
+
+ // send to group master
+ for (i = 0; i < MAX_MASTERS; i++)
+ if (master_adr[i].port != 0) {
+ Com.Printf("Sending heartbeat to " + NET.AdrToString(master_adr[i]) + "\n");
+ Netchan.OutOfBandPrint(NS_SERVER, master_adr[i], "heartbeat\n" + string);
+ }
+ }
+
+ /*
+ =================
+ Master_Shutdown
+
+ Informs all masters that this server is going down
+ =================
+ */
+ static void Master_Shutdown() {
+ int i;
+
+ // pgm post3.19 change, cvar pointer not validated before dereferencing
+ if (null == dedicated || 0 == dedicated.value)
+ return; // only dedicated servers send heartbeats
+
+ // pgm post3.19 change, cvar pointer not validated before dereferencing
+ if (null == public_server || 0 == public_server.value)
+ return; // a private dedicated game
+
+ // send to group master
+ for (i = 0; i < MAX_MASTERS; i++)
+ if (master_adr[i].port != 0) {
+ if (i > 0)
+ Com.Printf("Sending heartbeat to " + NET.AdrToString(master_adr[i]) + "\n");
+ Netchan.OutOfBandPrint(NS_SERVER, master_adr[i], "shutdown");
+ }
+ }
+
+ //============================================================================
+
+ /*
+ =================
+ SV_UserinfoChanged
+
+ Pull specific info from a newly changed userinfo string
+ into a more C freindly form.
+ =================
+ */
+ public static void SV_UserinfoChanged(client_t cl) {
+ String val;
+ int i;
+
+ // call prog code to allow overrides
+ SV_GAME.ge.ClientUserinfoChanged(cl.edict, cl.userinfo);
+
+ // name for C code
+ cl.name = Info.Info_ValueForKey(cl.userinfo, "name");
+
+ // mask off high bit
+ //TODO: masking for german umlaute
+ //for (i=0 ; i<sizeof(cl.name) ; i++)
+ // cl.name[i] &= 127;
+
+ // rate command
+ val = Info.Info_ValueForKey(cl.userinfo, "rate");
+ if (val.length() > 0) {
+ i = atoi(val);
+ cl.rate = i;
+ if (cl.rate < 100)
+ cl.rate = 100;
+ if (cl.rate > 15000)
+ cl.rate = 15000;
+ }
+ else
+ cl.rate = 5000;
+
+ // msg command
+ val = Info.Info_ValueForKey(cl.userinfo, "msg");
+ if (strlen(val) > 0) {
+ cl.messagelevel = atoi(val);
+ }
+
+ }
+
+ //============================================================================
+
+ /*
+ ===============
+ SV_Init
+
+ Only called at quake2.exe startup, not for each game
+ ===============
+ */
+ public static void SV_Init() {
+ SV_CCMDS.SV_InitOperatorCommands (); //ok.
+
+ rcon_password = Cvar.Get("rcon_password", "", 0);
+ Cvar.Get("skill", "1", 0);
+ Cvar.Get("deathmatch", "0", CVAR_LATCH);
+ Cvar.Get("coop", "0", CVAR_LATCH);
+ Cvar.Get("dmflags", "" + DF_INSTANT_ITEMS, CVAR_SERVERINFO);
+ Cvar.Get("fraglimit", "0", CVAR_SERVERINFO);
+ Cvar.Get("timelimit", "0", CVAR_SERVERINFO);
+ Cvar.Get("cheats", "0", CVAR_SERVERINFO | CVAR_LATCH);
+ Cvar.Get("protocol", "" + PROTOCOL_VERSION, CVAR_SERVERINFO | CVAR_NOSET);
+
+ SV_MAIN.maxclients = Cvar.Get("maxclients", "1", CVAR_SERVERINFO | CVAR_LATCH);
+ hostname = Cvar.Get("hostname", "noname", CVAR_SERVERINFO | CVAR_ARCHIVE);
+ timeout = Cvar.Get("timeout", "125", 0);
+ zombietime = Cvar.Get("zombietime", "2", 0);
+ sv_showclamp = Cvar.Get("showclamp", "0", 0);
+ sv_paused = Cvar.Get("paused", "0", 0);
+ sv_timedemo = Cvar.Get("timedemo", "0", 0);
+ sv_enforcetime = Cvar.Get("sv_enforcetime", "0", 0);
+
+ // TODO: carsten, re-allow downloads per default
+ allow_download = Cvar.Get("allow_download", "0", CVAR_ARCHIVE);
+ allow_download_players = Cvar.Get("allow_download_players", "0", CVAR_ARCHIVE);
+ allow_download_models = Cvar.Get("allow_download_models", "1", CVAR_ARCHIVE);
+ allow_download_sounds = Cvar.Get("allow_download_sounds", "1", CVAR_ARCHIVE);
+ allow_download_maps = Cvar.Get("allow_download_maps", "1", CVAR_ARCHIVE);
+
+ sv_noreload = Cvar.Get("sv_noreload", "0", 0);
+ sv_airaccelerate = Cvar.Get("sv_airaccelerate", "0", CVAR_LATCH);
+ public_server = Cvar.Get("public", "0", 0);
+ sv_reconnect_limit = Cvar.Get("sv_reconnect_limit", "3", CVAR_ARCHIVE);
+
+ SZ.Init(net_message, net_message_buffer, net_message_buffer.length);
+ }
+
+ /*
+ ==================
+ SV_FinalMessage
+
+ Used by SV_Shutdown to send a final message to all
+ connected clients before the server goes down. The messages are sent immediately,
+ not just stuck on the outgoing message list, because the server is going
+ to totally exit after returning from this function.
+ ==================
+ */
+ public static void SV_FinalMessage(String message, boolean reconnect) {
+ int i;
+ client_t cl;
+
+ SZ.Clear(net_message);
+ MSG.WriteByte(net_message, svc_print);
+ MSG.WriteByte(net_message, PRINT_HIGH);
+ MSG.WriteString(net_message, message);
+
+ if (reconnect)
+ MSG.WriteByte(net_message, svc_reconnect);
+ else
+ MSG.WriteByte(net_message, svc_disconnect);
+
+ // send it twice
+ // stagger the packets to crutch operating system limited buffers
+
+ for (i = 0; i < maxclients.value; i++) {
+ cl = svs.clients[i];
+ if (cl.state >= cs_connected)
+ Netchan.Transmit(cl.netchan, net_message.cursize, net_message.data);
+ }
+ for (i = 0; i < maxclients.value; i++) {
+ cl = svs.clients[i];
+ if (cl.state >= cs_connected)
+ Netchan.Transmit(cl.netchan, net_message.cursize, net_message.data);
+ }
+ }
+
+ /*
+ ================
+ SV_Shutdown
+
+ Called when each game quits,
+ before Sys_Quit or Sys_Error
+ ================
+ */
+ public static void SV_Shutdown(String finalmsg, boolean reconnect) {
+ if (svs.clients != null)
+ SV_FinalMessage(finalmsg, reconnect);
+
+ Master_Shutdown();
+
+ SV_GAME.SV_ShutdownGameProgs ();
+
+ // free current level
+ if (sv.demofile != null)
+ try {
+ sv.demofile.close();
+ }
+ catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ //memset (&sv, 0, sizeof(sv));
+ sv = new server_t();
+
+
+ Com.SetServerState (sv.state);
+
+ // free server static data
+ //if (svs.clients!=null)
+ // Z_Free (svs.clients);
+ //if (svs.client_entities)
+ // Z_Free (svs.client_entities);
+
+ if (svs.demofile != null)
+ try {
+ svs.demofile.close();
+ }
+ catch (IOException e1) {
+ e1.printStackTrace();
+ }
+ //memset (&svs, 0, sizeof(svs));
+ svs = new server_static_t();
+ }
+}
diff --git a/src/jake2/server/SV_SEND.java b/src/jake2/server/SV_SEND.java
new file mode 100644
index 0000000..ca6b07e
--- /dev/null
+++ b/src/jake2/server/SV_SEND.java
@@ -0,0 +1,562 @@
+/*
+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.
+
+*/
+
+// Created on 17.01.2004 by RST.
+// $Id: SV_SEND.java,v 1.1 2004-07-07 19:59:49 hzi Exp $
+
+package jake2.server;
+
+import java.io.IOException;
+
+import jake2.*;
+import jake2.client.*;
+import jake2.game.*;
+import jake2.qcommon.*;
+import jake2.render.*;
+
+public class SV_SEND extends SV_MAIN {
+ /*
+ =============================================================================
+
+ Com_Printf redirection
+
+ =============================================================================
+ */
+
+ public static byte sv_outputbuf[] = new byte[SV_OUTPUTBUF_LENGTH];
+
+ public static void SV_FlushRedirect(int sv_redirected, byte outputbuf[]) {
+ if (sv_redirected == RD_PACKET) {
+ String s = ("print\n" + outputbuf);
+ Netchan.Netchan_OutOfBand(NS_SERVER, Netchan.net_from, s.length(), s.getBytes());
+ }
+ else if (sv_redirected == RD_CLIENT) {
+ MSG.WriteByte(SV_MAIN.sv_client.netchan.message, svc_print);
+ MSG.WriteByte(SV_MAIN.sv_client.netchan.message, PRINT_HIGH);
+ MSG.WriteString(SV_MAIN.sv_client.netchan.message, outputbuf);
+ }
+ }
+
+ /*
+ =============================================================================
+
+ EVENT MESSAGES
+
+ =============================================================================
+ */
+
+ /*
+ =================
+ SV_ClientPrintf
+
+ Sends text across to be displayed if the level passes
+ =================
+ */
+ public static void SV_ClientPrintf(client_t cl, int level, String s) {
+ // va_list argptr;
+ // char string[1024];
+ //
+ if (level < cl.messagelevel)
+ return;
+
+ // va_start (argptr,fmt);
+ // vsprintf (string, fmt,argptr);
+ // va_end (argptr);
+
+ MSG.WriteByte(cl.netchan.message, svc_print);
+ MSG.WriteByte(cl.netchan.message, level);
+ MSG.WriteString(cl.netchan.message, s);
+ }
+
+ /*
+ =================
+ SV_BroadcastPrintf
+
+ Sends text to all active clients
+ =================
+ */
+ public static void SV_BroadcastPrintf(int level, String s) {
+ //va_list argptr;
+ //char string[2048];
+ client_t cl;
+ //int i;
+
+ // va_start (argptr,fmt);
+ // vsprintf (string, fmt,argptr);
+ // va_end (argptr);
+
+ // echo to console
+ if (dedicated.value!=0)
+ {
+
+ //char copy[1024];
+ //int i;
+
+ // mask off high bits
+ //for (i=0 ; i<1023 && string[i] ; i++)
+ //copy[i] = string[i]&127;
+ //copy[i] = 0;
+ //Com_Printf ("%s", copy);
+
+ Com.Printf(s);
+ }
+
+ for (int i = 0; i < SV_MAIN.maxclients.value; i++) {
+ cl = SV_MAIN.svs.clients[i];
+ if (level < cl.messagelevel)
+ continue;
+ if (cl.state != cs_spawned)
+ continue;
+ MSG.WriteByte(cl.netchan.message, svc_print);
+ MSG.WriteByte(cl.netchan.message, level);
+ MSG.WriteString(cl.netchan.message, s);
+ }
+ }
+
+ /*
+ =================
+ SV_BroadcastCommand
+
+ Sends text to all active clients
+ =================
+ */
+ public static void SV_BroadcastCommand(String s) {
+ // va_list argptr;
+ // char string[1024];
+
+ if (sv.state == 0)
+ return;
+
+ // va_start (argptr,fmt);
+ // vsprintf (string, fmt,argptr);
+ // va_end (argptr);
+
+ MSG.WriteByte(sv.multicast, svc_stufftext);
+ MSG.WriteString(sv.multicast, s);
+ SV_Multicast(null, MULTICAST_ALL_R);
+ }
+
+ /*
+ =================
+ SV_Multicast
+
+ Sends the contents of sv.multicast to a subset of the clients,
+ then clears sv.multicast.
+
+ MULTICAST_ALL same as broadcast (origin can be null)
+ MULTICAST_PVS send to clients potentially visible from org
+ MULTICAST_PHS send to clients potentially hearable from org
+ =================
+ */
+ public static void SV_Multicast(float[] origin, int to) {
+ client_t client;
+ byte mask[];
+ int leafnum, cluster;
+ int j;
+ boolean reliable;
+ int area1, area2;
+
+ reliable = false;
+
+ if (to != MULTICAST_ALL_R && to != MULTICAST_ALL) {
+ leafnum = CM.CM_PointLeafnum(origin);
+ area1 = CM.CM_LeafArea(leafnum);
+ }
+ else {
+ leafnum = 0; // just to avoid compiler warnings
+ area1 = 0;
+ }
+
+ // if doing a serverrecord, store everything
+ if (svs.demofile != null)
+ SZ.Write(svs.demo_multicast, sv.multicast.data, sv.multicast.cursize);
+
+ switch (to) {
+ case MULTICAST_ALL_R :
+ reliable = true; // intentional fallthrough, no break here
+ case MULTICAST_ALL :
+ leafnum = 0;
+ mask = null;
+ break;
+
+ case MULTICAST_PHS_R :
+ reliable = true; // intentional fallthrough
+ case MULTICAST_PHS :
+ leafnum = CM.CM_PointLeafnum(origin);
+ cluster = CM.CM_LeafCluster(leafnum);
+ mask = CM.CM_ClusterPHS(cluster);
+ break;
+
+ case MULTICAST_PVS_R :
+ reliable = true; // intentional fallthrough
+ case MULTICAST_PVS :
+ leafnum = CM.CM_PointLeafnum(origin);
+ cluster = CM.CM_LeafCluster(leafnum);
+ mask = CM.CM_ClusterPVS(cluster);
+ break;
+
+ default :
+ mask = null;
+ Com.Error(ERR_FATAL, "SV_Multicast: bad to:" + to + "\n");
+ }
+
+ // send the data to all relevent clients
+ for (j = 0; j < maxclients.value; j++) {
+ client = svs.clients[j];
+
+ if (client.state == cs_free || client.state == cs_zombie)
+ continue;
+ if (client.state != cs_spawned && !reliable)
+ continue;
+
+ if (mask != null) {
+ leafnum = CM.CM_PointLeafnum(client.edict.s.origin);
+ cluster = CM.CM_LeafCluster(leafnum);
+ area2 = CM.CM_LeafArea(leafnum);
+ if (!CM.CM_AreasConnected(area1, area2))
+ continue;
+ if (mask != null && (0 == (mask[cluster >> 3] & (1 << (cluster & 7)))))
+ continue;
+ }
+
+ if (reliable)
+ SZ.Write(client.netchan.message, sv.multicast.data, sv.multicast.cursize);
+ else
+ SZ.Write(client.datagram, sv.multicast.data, sv.multicast.cursize);
+ }
+
+ SZ.Clear(sv.multicast);
+ }
+
+ /*
+ ==================
+ SV_StartSound
+
+ Each entity can have eight independant sound sources, like voice,
+ weapon, feet, etc.
+
+ If cahnnel & 8, the sound will be sent to everyone, not just
+ things in the PHS.
+
+ FIXME: if entity isn't in PHS, they must be forced to be sent or
+ have the origin explicitly sent.
+
+ Channel 0 is an auto-allocate channel, the others override anything
+ already running on that entity/channel pair.
+
+ An attenuation of 0 will play full volume everywhere in the level.
+ Larger attenuations will drop off. (max 4 attenuation)
+
+ Timeofs can range from 0.0 to 0.1 to cause sounds to be started
+ later in the frame than they normally would.
+
+ If origin is null, the origin is determined from the entity origin
+ or the midpoint of the entity box for bmodels.
+ ==================
+ */
+ public static void SV_StartSound(
+ float[] origin,
+ edict_t entity,
+ int channel,
+ int soundindex,
+ float volume,
+ float attenuation,
+ float timeofs) {
+ int sendchan;
+ int flags;
+ int i;
+ int ent;
+ float[] origin_v = null;
+ boolean use_phs;
+
+ if (volume < 0 || volume > 1.0)
+ Com.Error(ERR_FATAL, "SV_StartSound: volume = " + volume);
+
+ if (attenuation < 0 || attenuation > 4)
+ Com.Error(ERR_FATAL, "SV_StartSound: attenuation = " + attenuation);
+
+ // if (channel < 0 || channel > 15)
+ // Com_Error (ERR_FATAL, "SV_StartSound: channel = %i", channel);
+
+ if (timeofs < 0 || timeofs > 0.255)
+ Com.Error(ERR_FATAL, "SV_StartSound: timeofs = " + timeofs);
+
+ //ent = NUM_FOR_EDICT(entity);
+ //TODO: somehow risky (dont know if constant correct index)
+ ent = entity.index;
+
+ if ((channel & 8) != 0) // no PHS flag
+ {
+ use_phs = false;
+ channel &= 7;
+ }
+ else
+ use_phs = true;
+
+ sendchan = (ent << 3) | (channel & 7);
+
+ flags = 0;
+ if (volume != Defines.DEFAULT_SOUND_PACKET_VOLUME)
+ flags |= SND_VOLUME;
+ if (attenuation != DEFAULT_SOUND_PACKET_ATTENUATION)
+ flags |= SND_ATTENUATION;
+
+ // the client doesn't know that bmodels have weird origins
+ // the origin can also be explicitly set
+ if ((entity.svflags & SVF_NOCLIENT) != 0 || (entity.solid == SOLID_BSP) || origin != null)
+ flags |= SND_POS;
+
+ // always send the entity number for channel overrides
+ flags |= SND_ENT;
+
+ if (timeofs != 0)
+ flags |= SND_OFFSET;
+
+ // use the entity origin unless it is a bmodel or explicitly specified
+ if (origin == null) {
+ origin = origin_v;
+ if (entity.solid == SOLID_BSP) {
+ for (i = 0; i < 3; i++)
+ origin_v[i] = entity.s.origin[i] + 0.5f * (entity.mins[i] + entity.maxs[i]);
+ }
+ else {
+ VectorCopy(entity.s.origin, origin_v);
+ }
+ }
+
+ MSG.WriteByte(sv.multicast, svc_sound);
+ MSG.WriteByte(sv.multicast, flags);
+ MSG.WriteByte(sv.multicast, soundindex);
+
+ if ((flags & SND_VOLUME) != 0)
+ MSG.WriteByte(sv.multicast, volume * 255);
+ if ((flags & SND_ATTENUATION) != 0)
+ MSG.WriteByte(sv.multicast, attenuation * 64);
+ if ((flags & SND_OFFSET) != 0)
+ MSG.WriteByte(sv.multicast, timeofs * 1000);
+
+ if ((flags & SND_ENT) != 0)
+ MSG.WriteShort(sv.multicast, sendchan);
+
+ if ((flags & SND_POS) != 0)
+ MSG.WritePos(sv.multicast, origin);
+
+ // if the sound doesn't attenuate,send it to everyone
+ // (global radio chatter, voiceovers, etc)
+ if (attenuation == ATTN_NONE)
+ use_phs = false;
+
+ if ((channel & CHAN_RELIABLE) != 0) {
+ if (use_phs)
+ SV_Multicast(origin, MULTICAST_PHS_R);
+ else
+ SV_Multicast(origin, MULTICAST_ALL_R);
+ }
+ else {
+ if (use_phs)
+ SV_Multicast(origin, MULTICAST_PHS);
+ else
+ SV_Multicast(origin, MULTICAST_ALL);
+ }
+ }
+
+ /*
+ ===============================================================================
+
+ FRAME UPDATES
+
+ ===============================================================================
+ */
+
+ /*
+ =======================
+ SV_SendClientDatagram
+ =======================
+ */
+ public static boolean SV_SendClientDatagram(client_t client) {
+ byte msg_buf[] = new byte[MAX_MSGLEN];
+ sizebuf_t msg = new sizebuf_t();
+
+ SV_ENTS.SV_BuildClientFrame(client);
+
+ SZ.Init(msg, msg_buf, msg_buf.length);
+ msg.allowoverflow = true;
+
+ // send over all the relevant entity_state_t
+ // and the player_state_t
+ SV_CCMDS.SV_WriteFrameToClient (client, msg);
+
+ // copy the accumulated multicast datagram
+ // for this client out to the message
+ // it is necessary for this to be after the WriteEntities
+ // so that entity references will be current
+ if (client.datagram.overflowed)
+ Com.Printf("WARNING: datagram overflowed for " + client.name + "\n");
+ else
+ SZ.Write(msg, client.datagram.data, client.datagram.cursize);
+ SZ.Clear(client.datagram);
+
+ if (msg.overflowed) { // must have room left for the packet header
+ Com.Printf("WARNING: msg overflowed for " + client.name + "\n");
+ SZ.Clear(msg);
+ }
+
+ // send the datagram
+ Netchan.Transmit(client.netchan, msg.cursize, msg.data);
+
+ // record the size for rate estimation
+ client.message_size[sv.framenum % RATE_MESSAGES] = msg.cursize;
+
+ return true;
+ }
+
+ /*
+ ==================
+ SV_DemoCompleted
+ ==================
+ */
+ public static void SV_DemoCompleted() {
+ if (sv.demofile != null) {
+ try {
+ sv.demofile.close();
+ }
+ catch (IOException e) {
+ Com.Printf("IOError closing d9emo fiele:" + e);
+ }
+ sv.demofile = null;
+ }
+ SV_ENTS.SV_Nextserver();
+ }
+
+ /*
+ =======================
+ SV_RateDrop
+
+ Returns true if the client is over its current
+ bandwidth estimation and should not be sent another packet
+ =======================
+ */
+ public static boolean SV_RateDrop(client_t c) {
+ int total;
+ int i;
+
+ // never drop over the loopback
+ if (c.netchan.remote_address.type == NA_LOOPBACK)
+ return false;
+
+ total = 0;
+
+ for (i = 0; i < RATE_MESSAGES; i++) {
+ total += c.message_size[i];
+ }
+
+ if (total > c.rate) {
+ c.surpressCount++;
+ c.message_size[sv.framenum % RATE_MESSAGES] = 0;
+ return true;
+ }
+
+ return false;
+ }
+
+ /*
+ =======================
+ SV_SendClientMessages
+ =======================
+ */
+ public static void SV_SendClientMessages() {
+ int i;
+ client_t c;
+ int msglen;
+ byte msgbuf[] = new byte[MAX_MSGLEN];
+ int r;
+
+ msglen = 0;
+
+ // read the next demo message if needed
+ if (sv.state == ss_demo && sv.demofile != null) {
+ if (sv_paused.value != 0)
+ msglen = 0;
+ else {
+ // get the next message
+ //r = fread (&msglen, 4, 1, sv.demofile);
+ try {
+ msglen = EndianHandler.swapInt(sv.demofile.readInt());
+ }
+ catch (Exception e) {
+ SV_DemoCompleted();
+ return;
+ }
+
+ //msglen = LittleLong (msglen);
+ if (msglen == -1) {
+ SV_DemoCompleted();
+ return;
+ }
+ if (msglen > MAX_MSGLEN)
+ Com.Error(ERR_DROP, "SV_SendClientMessages: msglen > MAX_MSGLEN");
+
+ //r = fread (msgbuf, msglen, 1, sv.demofile);
+ r = 0;
+ try {
+ r = sv.demofile.read(msgbuf, 0, msglen);
+ }
+ catch (IOException e1) {
+ Com.Printf("IOError: reading demo file, " + e1);
+ }
+ if (r != msglen) {
+ SV_DemoCompleted();
+ return;
+ }
+ }
+ }
+
+ // send a message to each connected client
+ for (i = 0; i < maxclients.value; i++) {
+ c = svs.clients[i];
+
+ if (c.state == 0)
+ continue;
+ // if the reliable message overflowed,
+ // drop the client
+ if (c.netchan.message.overflowed) {
+ SZ.Clear(c.netchan.message);
+ SZ.Clear(c.datagram);
+ SV_BroadcastPrintf(PRINT_HIGH, c.name + " overflowed\n");
+ SV_DropClient(c);
+ }
+
+ if (sv.state == ss_cinematic || sv.state == ss_demo || sv.state == ss_pic)
+ Netchan.Transmit(c.netchan, msglen, msgbuf);
+ else if (c.state == cs_spawned) {
+ // don't overrun bandwidth
+ if (SV_RateDrop(c))
+ continue;
+
+ SV_SendClientDatagram(c);
+ }
+ else {
+ // just update reliable if needed
+ if (c.netchan.message.cursize != 0 || Globals.curtime - c.netchan.last_sent > 1000)
+ Netchan.Transmit(c.netchan, 0, new byte[0]);
+ }
+ }
+ }
+}
diff --git a/src/jake2/server/SV_USER.java b/src/jake2/server/SV_USER.java
new file mode 100644
index 0000000..7662c30
--- /dev/null
+++ b/src/jake2/server/SV_USER.java
@@ -0,0 +1,662 @@
+/*
+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.
+
+*/
+
+// Created on 17.01.2004 by RST.
+// $Id: SV_USER.java,v 1.1 2004-07-07 19:59:50 hzi Exp $
+
+package jake2.server;
+
+import jake2.game.*;
+import jake2.qcommon.*;
+import jake2.util.Lib;
+
+import java.io.IOException;
+
+public class SV_USER extends SV_SEND {
+
+ static edict_t sv_player;
+
+ /*
+ ============================================================
+
+ USER STRINGCMD EXECUTION
+
+ sv_client and sv_player will be valid.
+ ============================================================
+ */
+
+ /*
+ ==================
+ SV_BeginDemoServer
+ ==================
+ */
+ public static void SV_BeginDemoserver() {
+ String name;
+
+ name = "demos/" + sv.name;
+ try {
+ sv.demofile = FS.FOpenFile(name);
+ }
+ catch (IOException e) {
+ Com.Error(ERR_DROP, "Couldn't open " + name + "\n");
+ }
+ if (sv.demofile == null)
+ Com.Error(ERR_DROP, "Couldn't open " + name + "\n");
+ }
+
+ /*
+ ================
+ SV_New_f
+
+ Sends the first message from the server to a connected client.
+ This will be sent on the initial connection and upon each server load.
+ ================
+ */
+ public static void SV_New_f() {
+ String gamedir;
+ int playernum;
+ edict_t ent;
+
+ Com.DPrintf("New() from " + sv_client.name + "\n");
+
+ if (sv_client.state != cs_connected) {
+ Com.Printf("New not valid -- already spawned\n");
+ return;
+ }
+
+ // demo servers just dump the file message
+ if (sv.state == ss_demo) {
+ SV_BeginDemoserver();
+ return;
+ }
+
+ //
+ // serverdata needs to go over for all types of servers
+ // to make sure the protocol is right, and to set the gamedir
+ //
+ gamedir = Cvar.VariableString("gamedir");
+
+ // send the serverdata
+ MSG.WriteByte(sv_client.netchan.message, svc_serverdata);
+ MSG.WriteInt(sv_client.netchan.message, PROTOCOL_VERSION);
+ MSG.WriteLong(sv_client.netchan.message, svs.spawncount);
+ MSG.WriteByte(sv_client.netchan.message, sv.attractloop ? 1 : 0);
+ MSG.WriteString(sv_client.netchan.message, gamedir);
+
+ if (sv.state == ss_cinematic || sv.state == ss_pic)
+ playernum = -1;
+ else
+ //playernum = sv_client - svs.clients;
+ playernum = sv_client.serverindex;
+
+ MSG.WriteShort(sv_client.netchan.message, playernum);
+
+ // send full levelname
+ MSG.WriteString(sv_client.netchan.message, sv.configstrings[CS_NAME]);
+
+ //
+ // game server
+ //
+ if (sv.state == ss_game) {
+ // set up the entity for the client
+ ent = SV_GAME.ge.edicts[playernum + 1];
+ ent.s.number = playernum + 1;
+ sv_client.edict = ent;
+ sv_client.lastcmd = new usercmd_t();
+
+ // begin fetching configstrings
+ MSG.WriteByte(sv_client.netchan.message, svc_stufftext);
+ MSG.WriteString(sv_client.netchan.message, "cmd configstrings " + svs.spawncount + " 0\n");
+ }
+
+ }
+
+ /*
+ ==================
+ SV_Configstrings_f
+ ==================
+ */
+ public static void SV_Configstrings_f() {
+ int start;
+
+ Com.DPrintf("Configstrings() from " + sv_client.name + "\n");
+
+ if (sv_client.state != cs_connected) {
+ Com.Printf("configstrings not valid -- already spawned\n");
+ return;
+ }
+
+ // handle the case of a level changing while a client was connecting
+ if (atoi(Cmd.Argv(1)) != svs.spawncount) {
+ Com.Printf("SV_Configstrings_f from different level\n");
+ SV_New_f();
+ return;
+ }
+
+ start = atoi(Cmd.Argv(2));
+
+ // write a packet full of data
+
+ while (sv_client.netchan.message.cursize < MAX_MSGLEN / 2 && start < MAX_CONFIGSTRINGS) {
+ if (sv.configstrings[start] != null && sv.configstrings[start].length() != 0) {
+ MSG.WriteByte(sv_client.netchan.message, svc_configstring);
+ MSG.WriteShort(sv_client.netchan.message, start);
+ MSG.WriteString(sv_client.netchan.message, sv.configstrings[start]);
+ }
+ start++;
+ }
+
+ // send next command
+
+ if (start == MAX_CONFIGSTRINGS) {
+ MSG.WriteByte(sv_client.netchan.message, svc_stufftext);
+ MSG.WriteString(sv_client.netchan.message, "cmd baselines " + svs.spawncount + " 0\n");
+ }
+ else {
+ MSG.WriteByte(sv_client.netchan.message, svc_stufftext);
+ MSG.WriteString(sv_client.netchan.message, "cmd configstrings " + svs.spawncount + " " + start + "\n");
+ }
+ }
+
+ /*
+ ==================
+ SV_Baselines_f
+ ==================
+ */
+ public static void SV_Baselines_f() {
+ int start;
+ entity_state_t nullstate;
+ entity_state_t base;
+
+ Com.DPrintf("Baselines() from " + sv_client.name + "\n");
+
+ if (sv_client.state != cs_connected) {
+ Com.Printf("baselines not valid -- already spawned\n");
+ return;
+ }
+
+ // handle the case of a level changing while a client was connecting
+ if (atoi(Cmd.Argv(1)) != svs.spawncount) {
+ Com.Printf("SV_Baselines_f from different level\n");
+ SV_New_f();
+ return;
+ }
+
+ start = atoi(Cmd.Argv(2));
+
+ //memset (&nullstate, 0, sizeof(nullstate));
+ nullstate = new entity_state_t(null);
+
+ // write a packet full of data
+
+ while (sv_client.netchan.message.cursize < MAX_MSGLEN / 2 && start < MAX_EDICTS) {
+ base = sv.baselines[start];
+ if (base.modelindex != 0 || base.sound != 0 || base.effects != 0) {
+ MSG.WriteByte(sv_client.netchan.message, svc_spawnbaseline);
+ MSG.WriteDeltaEntity(nullstate, base, sv_client.netchan.message, true, true);
+ }
+ start++;
+ }
+
+ // send next command
+
+ if (start == MAX_EDICTS) {
+ MSG.WriteByte(sv_client.netchan.message, svc_stufftext);
+ MSG.WriteString(sv_client.netchan.message, "precache " + svs.spawncount + "\n");
+ }
+ else {
+ MSG.WriteByte(sv_client.netchan.message, svc_stufftext);
+ MSG.WriteString(sv_client.netchan.message, "cmd baselines " + svs.spawncount + " " + start + "\n");
+ }
+ }
+
+ /*
+ ==================
+ SV_Begin_f
+ ==================
+ */
+ public static void SV_Begin_f() {
+ Com.DPrintf("Begin() from " + sv_client.name + "\n");
+
+ // handle the case of a level changing while a client was connecting
+ if (atoi(Cmd.Argv(1)) != svs.spawncount) {
+ Com.Printf("SV_Begin_f from different level\n");
+ SV_New_f();
+ return;
+ }
+
+ sv_client.state = cs_spawned;
+
+ // call the game begin function
+ SV_GAME.ge.ClientBegin(sv_player);
+
+ Cbuf.InsertFromDefer();
+ }
+
+ //=============================================================================
+
+ /*
+ ==================
+ SV_NextDownload_f
+ ==================
+ */
+ public static void SV_NextDownload_f() {
+ int r;
+ int percent;
+ int size;
+
+ if (sv_client.download == null)
+ return;
+
+ r = sv_client.downloadsize - sv_client.downloadcount;
+ if (r > 1024)
+ r = 1024;
+
+ MSG.WriteByte(sv_client.netchan.message, svc_download);
+ MSG.WriteShort(sv_client.netchan.message, r);
+
+ sv_client.downloadcount += r;
+ size = sv_client.downloadsize;
+ if (size == 0)
+ size = 1;
+ percent = sv_client.downloadcount * 100 / size;
+ MSG.WriteByte(sv_client.netchan.message, percent);
+ SZ.Write(sv_client.netchan.message, sv_client.download, sv_client.downloadcount - r, r);
+
+ if (sv_client.downloadcount != sv_client.downloadsize)
+ return;
+
+ FS.FreeFile(sv_client.download);
+ sv_client.download = null;
+ }
+
+ /*
+ ==================
+ SV_BeginDownload_f
+ ==================
+ */
+ public static void SV_BeginDownload_f() {
+ String name;
+ int offset = 0;
+
+ name = Cmd.Argv(1);
+
+ if (Cmd.Argc() > 2)
+ offset = atoi(Cmd.Argv(2)); // downloaded offset
+
+ // hacked by zoid to allow more conrol over download
+ // first off, no .. or global allow check
+
+ if (name.indexOf("..") != -1
+ || allow_download.value == 0 // leading dot is no good
+ || name.charAt(0) == '.' // leading slash bad as well, must be in subdir
+ || name.charAt(0) == '/' // next up, skin check
+ || (strncmp(name, "players/", 6) == 0 && 0 == allow_download_players.value) // now models
+ || (strncmp(name, "models/", 6) == 0 && 0 == allow_download_models.value) // now sounds
+ || (strncmp(name, "sound/", 6) == 0
+ && 0 == allow_download_sounds.value) // now maps (note special case for maps, must not be in pak)
+ || (strncmp(name, "maps/", 6) == 0 && 0 == allow_download_maps.value) // MUST be in a subdirectory
+ || name.indexOf('/') == -1) { // don't allow anything with .. path
+ MSG.WriteByte(sv_client.netchan.message, svc_download);
+ MSG.WriteShort(sv_client.netchan.message, -1);
+ MSG.WriteByte(sv_client.netchan.message, 0);
+ return;
+ }
+
+ if (sv_client.download != null)
+ FS.FreeFile(sv_client.download);
+
+ sv_client.download = FS.LoadFile(name);
+ sv_client.downloadsize = sv_client.download.length;
+ sv_client.downloadcount = offset;
+
+ if (offset > sv_client.downloadsize)
+ sv_client.downloadcount = sv_client.downloadsize;
+
+ if (sv_client.download == null // special check for maps, if it came from a pak file, don't allow
+ // download ZOID
+ || (strncmp(name, "maps/", 5) == 0 && FS.file_from_pak != 0)) {
+ Com.DPrintf("Couldn't download " + name + " to " + sv_client.name + "\n");
+ if (sv_client.download != null) {
+ FS.FreeFile(sv_client.download);
+ sv_client.download = null;
+ }
+
+ MSG.WriteByte(sv_client.netchan.message, svc_download);
+ MSG.WriteShort(sv_client.netchan.message, -1);
+ MSG.WriteByte(sv_client.netchan.message, 0);
+ return;
+ }
+
+ SV_NextDownload_f();
+ Com.DPrintf("Downloading " + name + " to " + sv_client.name + "\n");
+ }
+
+ //============================================================================
+
+ /*
+ =================
+ SV_Disconnect_f
+
+ The client is going to disconnect, so remove the connection immediately
+ =================
+ */
+ public static void SV_Disconnect_f() {
+ // SV_EndRedirect ();
+ SV_DropClient(sv_client);
+ }
+
+ /*
+ ==================
+ SV_ShowServerinfo_f
+
+ Dumps the serverinfo info string
+ ==================
+ */
+ public static void SV_ShowServerinfo_f() {
+ Info.Print(Cvar.Serverinfo());
+ }
+
+ public static void SV_Nextserver() {
+ String v;
+
+ //ZOID, ss_pic can be nextserver'd in coop mode
+ if (sv.state == ss_game || (sv.state == ss_pic && 0 == Cvar.VariableValue("coop")))
+ return; // can't nextserver while playing a normal game
+
+ svs.spawncount++; // make sure another doesn't sneak in
+ v = Cvar.VariableString("nextserver");
+ //if (!v[0])
+ if (v.length() == 0)
+ Cbuf.AddText("killserver\n");
+ else {
+ Cbuf.AddText(v);
+ Cbuf.AddText("\n");
+ }
+ Cvar.Set("nextserver", "");
+ }
+
+ /*
+ ==================
+ SV_Nextserver_f
+
+ A cinematic has completed or been aborted by a client, so move
+ to the next server,
+ ==================
+ */
+ public static void SV_Nextserver_f() {
+ if (Lib.atoi(Cmd.Argv(1)) != svs.spawncount) {
+ Com.DPrintf("Nextserver() from wrong level, from " + sv_client.name + "\n");
+ return; // leftover from last server
+ }
+
+ Com.DPrintf("Nextserver() from " + sv_client.name + "\n");
+
+ SV_Nextserver();
+ }
+
+ public static class ucmd_t {
+ public ucmd_t(String n, Runnable r) {
+ name = n;
+ this.r = r;
+ }
+ String name;
+ Runnable r;
+ }
+
+ static ucmd_t u1 = new ucmd_t("new", new Runnable() {
+ public void run() {
+ SV_New_f();
+ }
+ });
+
+ static ucmd_t ucmds[] = {
+ // auto issued
+ new ucmd_t("new", new Runnable() { public void run() { SV_New_f();
+ }
+ }), new ucmd_t("configstrings", new Runnable() {
+ public void run() {
+ SV_Configstrings_f();
+ }
+ }), new ucmd_t("baselines", new Runnable() {
+ public void run() {
+ SV_Baselines_f();
+ }
+ }), new ucmd_t("begin", new Runnable() {
+ public void run() {
+ SV_Begin_f();
+ }
+ }), new ucmd_t("nextserver", new Runnable() {
+ public void run() {
+ SV_Nextserver_f();
+ }
+ }), new ucmd_t("disconnect", new Runnable() {
+ public void run() {
+ SV_Disconnect_f();
+ }
+ }),
+
+ // issued by hand at client consoles
+ new ucmd_t("info", new Runnable() {
+ public void run() {
+ SV_ShowServerinfo_f();
+ }
+ }), new ucmd_t("download", new Runnable() {
+ public void run() {
+ SV_BeginDownload_f();
+ }
+ }), new ucmd_t("nextdl", new Runnable() {
+ public void run() {
+ SV_NextDownload_f();
+ }
+ })
+ };
+
+ /*
+ ==================
+ SV_ExecuteUserCommand
+ ==================
+ */
+ public static void SV_ExecuteUserCommand(String s) {
+ ucmd_t u = null;
+
+ Cmd.TokenizeString(s.toCharArray(), true);
+ sv_player = sv_client.edict;
+
+ // SV_BeginRedirect (RD_CLIENT);
+
+ for (int i = 0; i < ucmds.length; i++) {
+ u = ucmds[i];
+ if (0 == strcmp(Cmd.Argv(0), u.name)) {
+ u.r.run();
+ break;
+ }
+ }
+ if (u.name == null && sv.state == ss_game)
+ SV_GAME.ge.ClientCommand(sv_player);
+
+ // SV_EndRedirect ();
+ }
+
+ /*
+ ===========================================================================
+
+ USER CMD EXECUTION
+
+ ===========================================================================
+ */
+
+ public static void SV_ClientThink(client_t cl, usercmd_t cmd) {
+ cl.commandMsec -= cmd.msec & 0xFF;
+
+ if (cl.commandMsec < 0 && sv_enforcetime.value != 0) {
+ Com.DPrintf("commandMsec underflow from " + cl.name + "\n");
+ return;
+ }
+
+ SV_GAME.ge.ClientThink(cl.edict, cmd);
+ }
+
+ public static final int MAX_STRINGCMDS = 8;
+ /*
+ ===================
+ SV_ExecuteClientMessage
+
+ The current net_message is parsed for the given client
+ ===================
+ */
+ public static void SV_ExecuteClientMessage(client_t cl) {
+ int c;
+ String s;
+
+ usercmd_t nullcmd=new usercmd_t();
+ usercmd_t oldest=new usercmd_t(), oldcmd=new usercmd_t(), newcmd=new usercmd_t();
+ int net_drop;
+ int stringCmdCount;
+ int checksum, calculatedChecksum;
+ int checksumIndex;
+ boolean move_issued;
+ int lastframe;
+
+ sv_client = cl;
+ sv_player = sv_client.edict;
+
+ // only allow one move command
+ move_issued = false;
+ stringCmdCount = 0;
+
+ while (true) {
+ if (net_message.readcount > net_message.cursize) {
+ Com.Printf("SV_ReadClientMessage: bad read:\n");
+ Com.Printf(Lib.hexDump(net_message.data, 32, false));
+ SV_DropClient(cl);
+ return;
+ }
+
+ c = MSG.ReadByte(net_message);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ default :
+ Com.Printf("SV_ReadClientMessage: unknown command char\n");
+ SV_DropClient(cl);
+ return;
+
+ case clc_nop :
+ break;
+
+ case clc_userinfo :
+ cl.userinfo = MSG.ReadString(net_message);
+ SV_MAIN.SV_UserinfoChanged(cl);
+ break;
+
+ case clc_move :
+ if (move_issued)
+ return; // someone is trying to cheat...
+
+ move_issued = true;
+ checksumIndex = net_message.readcount;
+ checksum = MSG.ReadByte(net_message);
+ lastframe = MSG.ReadLong(net_message);
+
+ if (lastframe != cl.lastframe) {
+ cl.lastframe = lastframe;
+ if (cl.lastframe > 0) {
+ cl.frame_latency[cl.lastframe & (LATENCY_COUNTS - 1)] =
+ svs.realtime - cl.frames[cl.lastframe & UPDATE_MASK].senttime;
+ }
+ }
+
+ //memset (nullcmd, 0, sizeof(nullcmd));
+ nullcmd = new usercmd_t();
+ MSG.ReadDeltaUsercmd(net_message, nullcmd, oldest);
+ MSG.ReadDeltaUsercmd(net_message, oldest, oldcmd);
+ MSG.ReadDeltaUsercmd(net_message, oldcmd, newcmd);
+
+ if (cl.state != cs_spawned) {
+ cl.lastframe = -1;
+ break;
+ }
+
+ // if the checksum fails, ignore the rest of the packet
+
+ calculatedChecksum = 0;
+ /*
+ = Com.BlockSequenceCRCByte(
+ net_message.data + checksumIndex + 1,
+ net_message.readcount - checksumIndex - 1,
+ cl.netchan.incoming_sequence);
+ */
+
+ if (calculatedChecksum != checksum) {
+ Com.DPrintf(
+ "Failed command checksum for "
+ + cl.name
+ + " ("
+ + calculatedChecksum
+ + " != "
+ + checksum
+ + ")/"
+ + cl.netchan.incoming_sequence
+ + "\n");
+ return;
+ }
+
+ if (0 == sv_paused.value) {
+ net_drop = cl.netchan.dropped;
+ if (net_drop < 20) {
+
+ //if (net_drop > 2)
+
+ // Com.Printf ("drop %i\n", net_drop);
+ while (net_drop > 2) {
+ SV_ClientThink(cl, cl.lastcmd);
+
+ net_drop--;
+ }
+ if (net_drop > 1)
+ SV_ClientThink(cl, oldest);
+
+ if (net_drop > 0)
+ SV_ClientThink(cl, oldcmd);
+
+ }
+ SV_ClientThink(cl, newcmd);
+ }
+
+ // copy.
+ cl.lastcmd.set(newcmd);
+ break;
+
+ case clc_stringcmd :
+ s = MSG.ReadString(net_message);
+
+ // malicious users may try using too many string commands
+ if (++stringCmdCount < MAX_STRINGCMDS)
+ SV_ExecuteUserCommand(s);
+
+ if (cl.state == cs_zombie)
+ return; // disconnect command
+ break;
+ }
+ }
+ }
+}
diff --git a/src/jake2/server/SV_WORLD.java b/src/jake2/server/SV_WORLD.java
new file mode 100644
index 0000000..6df2512
--- /dev/null
+++ b/src/jake2/server/SV_WORLD.java
@@ -0,0 +1,654 @@
+/*
+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.
+
+*/
+
+// Created on 07.01.2000 by RST.
+// $Id: SV_WORLD.java,v 1.1 2004-07-07 19:59:50 hzi Exp $
+
+package jake2.server;
+
+import jake2.game.edict_t;
+import jake2.game.trace_t;
+
+import jake2.*;
+import jake2.client.*;
+import jake2.game.*;
+import jake2.qcommon.*;
+import jake2.render.*;
+import jake2.util.Vargs;
+
+public class SV_WORLD extends SV_CCMDS {
+
+ // world.c -- world query functions
+
+ //
+ ///*
+ //===============================================================================
+ //
+ //ENTITY AREA CHECKING
+ //
+ //FIXME: this use of "area" is different from the bsp file use
+ //===============================================================================
+ //*/
+
+ //// (type *)STRUCT_FROM_LINK(link_t *link, type, member)
+ //// ent = STRUCT_FROM_LINK(link,entity_t,order)
+
+ //// FIXME: remove this mess!
+ //#define STRUCT_FROM_LINK(l,t,m) ((t *)((byte *)l - (int)&(((t *)0).m)))
+ //
+ //#define EDICT_FROM_AREA(l) STRUCT_FROM_LINK(l,edict_t,area)
+
+ //
+
+ public static void initNodes() {
+ for (int n = 0; n < AREA_NODES; n++)
+ sv_areanodes[n] = new areanode_t();
+ }
+
+ public static areanode_t sv_areanodes[] = new areanode_t[AREA_NODES];
+
+ static {
+ initNodes();
+ }
+ public static int sv_numareanodes;
+
+ public static float area_mins[], area_maxs[];
+ public static edict_t area_list[];
+ public static int area_count, area_maxcount;
+ public static int area_type;
+
+ // ClearLink is used for new headnodes
+ public static void ClearLink(link_t l) {
+ l.prev = l.next = l;
+ }
+
+ public static void RemoveLink(link_t l) {
+ l.next.prev = l.prev;
+ l.prev.next = l.next;
+ }
+
+ public static void InsertLinkBefore(link_t l, link_t before) {
+ l.next = before;
+ l.prev = before.prev;
+ l.prev.next = l;
+ l.next.prev = l;
+ }
+
+ /*
+ ===============
+ SV_CreateAreaNode
+
+ Builds a uniformly subdivided tree for the given world size
+ ===============
+ */
+ public static areanode_t SV_CreateAreaNode(int depth, float[] mins, float[] maxs) {
+ areanode_t anode;
+ float[] size = { 0, 0, 0 };
+ float[] mins1 = { 0, 0, 0 }, maxs1 = { 0, 0, 0 }, mins2 = { 0, 0, 0 }, maxs2 = { 0, 0, 0 };
+
+ Com.Printf("SV_CreateAreaNode " + depth + "\n");
+ anode = sv_areanodes[sv_numareanodes];
+
+ // just for debugging (rst)
+ VectorCopy(mins, anode.mins_rst);
+ VectorCopy(maxs, anode.maxs_rst);
+
+ sv_numareanodes++;
+
+ ClearLink(anode.trigger_edicts);
+ ClearLink(anode.solid_edicts);
+
+ if (depth == AREA_DEPTH) {
+ anode.axis = -1;
+ anode.children[0] = anode.children[1] = null;
+ return anode;
+ }
+
+ VectorSubtract(maxs, mins, size);
+ if (size[0] > size[1])
+ anode.axis = 0;
+ else
+ anode.axis = 1;
+
+ anode.dist = 0.5f * (maxs[anode.axis] + mins[anode.axis]);
+ VectorCopy(mins, mins1);
+ VectorCopy(mins, mins2);
+ VectorCopy(maxs, maxs1);
+ VectorCopy(maxs, maxs2);
+
+ maxs1[anode.axis] = mins2[anode.axis] = anode.dist;
+
+ anode.children[0] = SV_CreateAreaNode(depth + 1, mins2, maxs2);
+ anode.children[1] = SV_CreateAreaNode(depth + 1, mins1, maxs1);
+
+ return anode;
+ }
+
+ /*
+ ===============
+ SV_ClearWorld
+
+ ===============
+ */
+ public static void SV_ClearWorld() {
+ initNodes();
+ sv_numareanodes = 0;
+ SV_CreateAreaNode(0, sv.models[1].mins, sv.models[1].maxs);
+
+ Com.Println("areanodes:" + sv_numareanodes + " (sollten 32 sein).");
+ for (int n = 0; n < sv_numareanodes; n++) {
+ Com.Printf(
+ "|%3i|%2i|%8.2f |%8.2f|%8.2f|%8.2f| %8.2f|%8.2f|%8.2f|\n",
+ new Vargs()
+ .add(n)
+ .add(sv_areanodes[n].axis)
+ .add(sv_areanodes[n].dist)
+ .add(sv_areanodes[n].mins_rst[0])
+ .add(sv_areanodes[n].mins_rst[1])
+ .add(sv_areanodes[n].mins_rst[2])
+ .add(sv_areanodes[n].maxs_rst[0])
+ .add(sv_areanodes[n].maxs_rst[1])
+ .add(sv_areanodes[n].maxs_rst[2]));
+ }
+ }
+
+ /*
+ ===============
+ SV_UnlinkEdict
+
+ ===============
+ */
+ public static void SV_UnlinkEdict(edict_t ent) {
+ if (null == ent.area.prev)
+ return; // not linked in anywhere
+ RemoveLink(ent.area);
+ ent.area.prev = ent.area.next = null;
+ }
+
+ /*
+ ===============
+ SV_LinkEdict
+
+ ===============
+ */
+ public static final int MAX_TOTAL_ENT_LEAFS = 128;
+
+ public static void SV_LinkEdict(edict_t ent) {
+ areanode_t node;
+ // TODO: make static instead of new
+ int leafs[] = new int[MAX_TOTAL_ENT_LEAFS];
+ int clusters[] = new int[MAX_TOTAL_ENT_LEAFS];
+ int num_leafs;
+ int j, k;
+ int area;
+ int topnode = 0;
+
+ if (ent.area.prev != null)
+ SV_UnlinkEdict(ent); // unlink from old position
+
+ if (ent == ge.edicts[0])
+ return; // don't add the world
+
+ if (!ent.inuse)
+ return;
+
+ // set the size
+ VectorSubtract(ent.maxs, ent.mins, ent.size);
+
+ // encode the size into the entity_state for client prediction
+ if (ent.solid == SOLID_BBOX && 0 == (ent.svflags & SVF_DEADMONSTER)) { // assume that x/y are equal and symetric
+ int i = (int) (ent.maxs[0] / 8);
+ if (i < 1)
+ i = 1;
+ if (i > 31)
+ i = 31;
+
+ // z is not symetric
+ j = (int) ((-ent.mins[2]) / 8);
+ if (j < 1)
+ j = 1;
+ if (j > 31)
+ j = 31;
+
+ // and z maxs can be negative...
+ k = (int) ((ent.maxs[2] + 32) / 8);
+ if (k < 1)
+ k = 1;
+ if (k > 63)
+ k = 63;
+
+ ent.s.solid = (k << 10) | (j << 5) | i;
+ }
+ else if (ent.solid == SOLID_BSP) {
+ ent.s.solid = 31; // a solid_bbox will never create this value
+ }
+ else
+ ent.s.solid = 0;
+
+ // set the abs box
+ if (ent.solid == SOLID_BSP && (ent.s.angles[0] != 0 || ent.s.angles[1] != 0 || ent.s.angles[2] != 0)) { // expand for rotation
+ float max, v;
+
+ max = 0;
+ for (int i = 0; i < 3; i++) {
+ v = Math.abs(ent.mins[i]);
+ if (v > max)
+ max = v;
+ v = Math.abs(ent.maxs[i]);
+ if (v > max)
+ max = v;
+ }
+ for (int i = 0; i < 3; i++) {
+ ent.absmin[i] = ent.s.origin[i] - max;
+ ent.absmax[i] = ent.s.origin[i] + max;
+ }
+ }
+ else { // normal
+ VectorAdd(ent.s.origin, ent.mins, ent.absmin);
+ VectorAdd(ent.s.origin, ent.maxs, ent.absmax);
+ }
+
+ // because movement is clipped an epsilon away from an actual edge,
+ // we must fully check even when bounding boxes don't quite touch
+
+ ent.absmin[0]--;
+ ent.absmin[1]--;
+ ent.absmin[2]--;
+ ent.absmax[0]++;
+ ent.absmax[1]++;
+ ent.absmax[2]++;
+
+ // link to PVS leafs
+ ent.num_clusters = 0;
+ ent.areanum = 0;
+ ent.areanum2 = 0;
+
+ //get all leafs, including solids
+
+ CM.intwrap iw = new CM.intwrap(topnode);
+
+ num_leafs = CM.CM_BoxLeafnums(ent.absmin, ent.absmax, leafs, MAX_TOTAL_ENT_LEAFS, iw);
+
+ topnode = iw.i;
+
+ // set areas
+ for (int i = 0; i < num_leafs; i++) {
+ clusters[i] = CM.CM_LeafCluster(leafs[i]);
+ area = CM.CM_LeafArea(leafs[i]);
+ if (area != 0) { // doors may legally straggle two areas,
+ // but nothing should evern need more than that
+ if (ent.areanum != 0 && ent.areanum != area) {
+ if (ent.areanum2 != 0 && ent.areanum2 != area && sv.state == ss_loading)
+ Com.DPrintf("Object touching 3 areas at " + ent.absmin[0] + " " + ent.absmin[1] + " " + ent.absmin[2] + "\n");
+ ent.areanum2 = area;
+ }
+ else
+ ent.areanum = area;
+ }
+ }
+
+ if (num_leafs >= MAX_TOTAL_ENT_LEAFS) { // assume we missed some leafs, and mark by headnode
+ ent.num_clusters = -1;
+ ent.headnode = topnode;
+ }
+ else {
+ ent.num_clusters = 0;
+ for (int i = 0; i < num_leafs; i++) {
+ if (clusters[i] == -1)
+ continue; // not a visible leaf
+ for (j = 0; j < i; j++)
+ if (clusters[j] == clusters[i])
+ break;
+ if (j == i) {
+ if (ent.num_clusters == MAX_ENT_CLUSTERS) { // assume we missed some leafs, and mark by headnode
+ ent.num_clusters = -1;
+ ent.headnode = topnode;
+ break;
+ }
+
+ ent.clusternums[ent.num_clusters++] = clusters[i];
+ }
+ }
+ }
+
+ // if first time, make sure old_origin is valid
+ if (0 == ent.linkcount) {
+ VectorCopy(ent.s.origin, ent.s.old_origin);
+ }
+ ent.linkcount++;
+
+ if (ent.solid == SOLID_NOT)
+ return;
+
+ // find the first node that the ent's box crosses
+ node = sv_areanodes[0];
+
+ while (true) {
+ if (node.axis == -1)
+ break;
+ if (ent.absmin[node.axis] > node.dist)
+ node = node.children[0];
+ else if (ent.absmax[node.axis] < node.dist)
+ node = node.children[1];
+ else
+ break; // crosses the node
+ }
+
+ // link it in
+ if (ent.solid == SOLID_TRIGGER)
+ InsertLinkBefore(ent.area, node.trigger_edicts);
+ else
+ InsertLinkBefore(ent.area, node.solid_edicts);
+
+ }
+
+ /*
+ ====================
+ SV_AreaEdicts_r
+
+ ====================
+ */
+ public static void SV_AreaEdicts_r(areanode_t node) {
+ link_t l, next, start;
+ edict_t check;
+ int count;
+
+ count = 0;
+
+ // touch linked edicts
+ if (area_type == AREA_SOLID)
+ start = node.solid_edicts;
+ else
+ start = node.trigger_edicts;
+
+ for (l = start.next; l != start; l = next) {
+ next = l.next;
+ check = //EDICT_FROM_AREA(l);
+ (edict_t) l.o;
+
+ if (check.solid == SOLID_NOT)
+ continue; // deactivated
+ if (check.absmin[0] > area_maxs[0]
+ || check.absmin[1] > area_maxs[1]
+ || check.absmin[2] > area_maxs[2]
+ || check.absmax[0] < area_mins[0]
+ || check.absmax[1] < area_mins[1]
+ || check.absmax[2] < area_mins[2])
+ continue; // not touching
+
+ if (area_count == area_maxcount) {
+ Com.Printf("SV_AreaEdicts: MAXCOUNT\n");
+ return;
+ }
+
+ area_list[area_count] = check;
+ area_count++;
+ }
+
+ if (node.axis == -1)
+ return; // terminal node
+
+ // recurse down both sides
+ if (area_maxs[node.axis] > node.dist)
+ SV_AreaEdicts_r(node.children[0]);
+
+ if (area_mins[node.axis] < node.dist)
+ SV_AreaEdicts_r(node.children[1]);
+ }
+
+ /*
+ ================
+ SV_AreaEdicts
+ ================
+ */
+ public static int SV_AreaEdicts(float[] mins, float[] maxs, edict_t list[], int maxcount, int areatype) {
+ area_mins = mins;
+ area_maxs = maxs;
+ area_list = list;
+ area_count = 0;
+ area_maxcount = maxcount;
+ area_type = areatype;
+
+ SV_AreaEdicts_r(sv_areanodes[0]);
+
+ /*
+ Com.Printf("found edicts in area:" + area_count + "\n");
+ for (int n = 0; n < area_count; n++) {
+ Com.Printf("%4i : %25s", new Vargs().add(n).add(list[n].classname));
+ }
+ */
+ return area_count;
+ }
+
+ //===========================================================================
+
+ /*
+ =============
+ SV_PointContents
+ =============
+ */
+ public static int SV_PointContents(float[] p) {
+ // TODO: make static instead new
+ edict_t touch[] = new edict_t[MAX_EDICTS], hit;
+ int i, num;
+ int contents, c2;
+ int headnode;
+ float angles[];
+
+ // get base contents from world
+ contents = CM.PointContents(p, sv.models[1].headnode);
+
+ // or in contents from all the other entities
+ num = SV_AreaEdicts(p, p, touch, MAX_EDICTS, AREA_SOLID);
+
+ for (i = 0; i < num; i++) {
+ hit = touch[i];
+
+ // might intersect, so do an exact clip
+ headnode = SV_HullForEntity(hit);
+ angles = hit.s.angles;
+ if (hit.solid != SOLID_BSP)
+ angles = vec3_origin; // boxes don't rotate
+
+ c2 = CM.TransformedPointContents(p, headnode, hit.s.origin, hit.s.angles);
+
+ contents |= c2;
+ }
+
+ return contents;
+ }
+
+ /*
+ ================
+ SV_HullForEntity
+
+ Returns a headnode that can be used for testing or clipping an
+ object of mins/maxs size.
+ Offset is filled in to contain the adjustment that must be added to the
+ testing object's origin to get a point to use with the returned hull.
+ ================
+ */
+ public static int SV_HullForEntity(edict_t ent) {
+
+ cmodel_t model;
+
+ // decide which clipping hull to use, based on the size
+ if (ent.solid == SOLID_BSP) { // explicit hulls in the BSP model
+ model = sv.models[ent.s.modelindex];
+
+ if (null == model)
+ Com.Error(ERR_FATAL, "MOVETYPE_PUSH with a non bsp model");
+
+ return model.headnode;
+ }
+
+ // create a temp hull from bounding box sizes
+
+ return CM.HeadnodeForBox(ent.mins, ent.maxs);
+ }
+
+ //===========================================================================
+
+ /*
+ ====================
+ SV_ClipMoveToEntities
+
+ ====================
+ */
+ public static void SV_ClipMoveToEntities(moveclip_t clip) {
+ int i, num;
+ // TODO: make static instead of new
+ edict_t touchlist[] = new edict_t[MAX_EDICTS];
+ edict_t touch;
+ trace_t trace;
+ int headnode;
+ float angles[];
+
+ num = SV_AreaEdicts(clip.boxmins, clip.boxmaxs, touchlist, MAX_EDICTS, AREA_SOLID);
+
+ // be careful, it is possible to have an entity in this
+ // list removed before we get to it (killtriggered)
+ for (i = 0; i < num; i++) {
+ touch = touchlist[i];
+ if (touch.solid == SOLID_NOT)
+ continue;
+ if (touch == clip.passedict)
+ continue;
+ if (clip.trace.allsolid)
+ return;
+ if (clip.passedict != null) {
+ if (touch.owner == clip.passedict)
+ continue; // don't clip against own missiles
+ if (clip.passedict.owner == touch)
+ continue; // don't clip against owner
+ }
+
+ if (0 == (clip.contentmask & CONTENTS_DEADMONSTER) && 0 != (touch.svflags & SVF_DEADMONSTER))
+ continue;
+
+ // might intersect, so do an exact clip
+ headnode = SV_HullForEntity(touch);
+ angles = touch.s.angles;
+ if (touch.solid != SOLID_BSP)
+ angles = vec3_origin; // boxes don't rotate
+
+ if ((touch.svflags & SVF_MONSTER) != 0)
+ trace =
+ CM.TransformedBoxTrace(
+ clip.start,
+ clip.end,
+ clip.mins2,
+ clip.maxs2,
+ headnode,
+ clip.contentmask,
+ touch.s.origin,
+ angles);
+ else
+ trace =
+ CM.TransformedBoxTrace(
+ clip.start,
+ clip.end,
+ clip.mins,
+ clip.maxs,
+ headnode,
+ clip.contentmask,
+ touch.s.origin,
+ angles);
+
+ if (trace.allsolid || trace.startsolid || trace.fraction < clip.trace.fraction) {
+ trace.ent = touch;
+ if (clip.trace.startsolid) {
+ clip.trace = trace;
+ clip.trace.startsolid = true;
+ }
+ else
+ clip.trace = trace.getClone();
+ }
+ else if (trace.startsolid)
+ clip.trace.startsolid = true;
+ }
+ }
+
+ /*
+ ==================
+ SV_TraceBounds
+ ==================
+ */
+ public static void SV_TraceBounds(float[] start, float[] mins, float[] maxs, float[] end, float[] boxmins, float[] boxmaxs) {
+ int i;
+
+ for (i = 0; i < 3; i++) {
+ if (end[i] > start[i]) {
+ boxmins[i] = start[i] + mins[i] - 1;
+ boxmaxs[i] = end[i] + maxs[i] + 1;
+ }
+ else {
+ boxmins[i] = end[i] + mins[i] - 1;
+ boxmaxs[i] = start[i] + maxs[i] + 1;
+ }
+ }
+
+ }
+
+ /*
+ ==================
+ SV_Trace
+
+ Moves the given mins/maxs volume through the world from start to end.
+
+ Passedict and edicts owned by passedict are explicitly not checked.
+
+ ==================
+ */
+ public static trace_t SV_Trace(float[] start, float[] mins, float[] maxs, float[] end, edict_t passedict, int contentmask) {
+ moveclip_t clip = new moveclip_t();
+
+ if (mins == null)
+ mins = vec3_origin;
+ if (maxs == null)
+ maxs = vec3_origin;
+
+ //memset ( clip, 0, sizeof ( moveclip_t ) );
+
+ // clip to world
+ clip.trace = CM.BoxTrace(start, end, mins, maxs, 0, contentmask);
+ clip.trace.ent = ge.edicts[0];
+ if (clip.trace.fraction == 0)
+ return clip.trace; // blocked by the world
+
+ clip.contentmask = contentmask;
+ clip.start = start;
+ clip.end = end;
+ clip.mins = mins;
+ clip.maxs = maxs;
+ clip.passedict = passedict;
+
+ VectorCopy(mins, clip.mins2);
+ VectorCopy(maxs, clip.maxs2);
+
+ // create the bounding box of the entire move
+ SV_TraceBounds(start, clip.mins2, clip.maxs2, end, clip.boxmins, clip.boxmaxs);
+
+ // clip to other solid entities
+ SV_ClipMoveToEntities(clip);
+
+ return clip.trace;
+ }
+}
diff --git a/src/jake2/server/areanode_t.java b/src/jake2/server/areanode_t.java
new file mode 100644
index 0000000..64e4e3a
--- /dev/null
+++ b/src/jake2/server/areanode_t.java
@@ -0,0 +1,43 @@
+/*
+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.
+
+*/
+
+// Created on 09.12.2003 by RST.
+// $Id: areanode_t.java,v 1.1 2004-07-07 19:59:50 hzi Exp $
+
+package jake2.server;
+
+import jake2.*;
+import jake2.client.*;
+import jake2.game.*;
+import jake2.qcommon.*;
+import jake2.render.*;
+import jake2.server.*;
+
+public class areanode_t {
+ int axis; // -1 = leaf node
+ float dist;
+ areanode_t children[] = new areanode_t[2];
+ link_t trigger_edicts = new link_t(this);
+ link_t solid_edicts = new link_t(this);
+
+ // used for debugging
+ float mins_rst[] = {0,0,0};
+ float maxs_rst[] = {0,0,0};
+}
diff --git a/src/jake2/server/challenge_t.java b/src/jake2/server/challenge_t.java
new file mode 100644
index 0000000..ba1dd36
--- /dev/null
+++ b/src/jake2/server/challenge_t.java
@@ -0,0 +1,39 @@
+/*
+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.
+
+*/
+
+// Created on 14.01.2004 by RST.
+// $Id: challenge_t.java,v 1.1 2004-07-07 19:59:50 hzi Exp $
+
+package jake2.server;
+
+
+import jake2.*;
+import jake2.client.*;
+import jake2.game.*;
+import jake2.qcommon.*;
+import jake2.render.*;
+import jake2.server.*;
+
+public class challenge_t {
+ //mem
+ netadr_t adr = new netadr_t();
+ int challenge;
+ int time;
+}
diff --git a/src/jake2/server/client_frame_t.java b/src/jake2/server/client_frame_t.java
new file mode 100644
index 0000000..3f841da
--- /dev/null
+++ b/src/jake2/server/client_frame_t.java
@@ -0,0 +1,42 @@
+/*
+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.
+
+*/
+
+// Created on 13.01.2004 by RST.
+// $Id: client_frame_t.java,v 1.1 2004-07-07 19:59:50 hzi Exp $
+
+package jake2.server;
+
+
+import jake2.*;
+import jake2.client.*;
+import jake2.game.*;
+import jake2.qcommon.*;
+import jake2.render.*;
+import jake2.server.*;
+
+public class client_frame_t {
+
+ int areabytes;
+ byte areabits[] = new byte[Defines.MAX_MAP_AREAS/8]; // portalarea visibility bits
+ player_state_t ps = new player_state_t();
+ int num_entities;
+ int first_entity; // into the circular sv_packet_entities[]
+ int senttime; // for ping calculations
+}
diff --git a/src/jake2/server/client_t.java b/src/jake2/server/client_t.java
new file mode 100644
index 0000000..2bbadf9
--- /dev/null
+++ b/src/jake2/server/client_t.java
@@ -0,0 +1,90 @@
+/*
+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.
+
+*/
+
+// Created on 13.01.2004 by RST.
+// $Id: client_t.java,v 1.1 2004-07-07 19:59:50 hzi Exp $
+
+package jake2.server;
+
+import jake2.*;
+import jake2.client.*;
+import jake2.game.*;
+import jake2.qcommon.*;
+import jake2.render.*;
+import jake2.server.*;
+
+public class client_t {
+
+ public client_t() {
+ for (int n = 0; n < Defines.UPDATE_BACKUP; n++) {
+ frames[n] = new client_frame_t();
+ }
+ }
+
+ public static final int LATENCY_COUNTS = 16;
+ public static final int RATE_MESSAGES = 10;
+
+ int state;
+
+ //char userinfo[MAX_INFO_STRING]; // name, etc
+ String userinfo = "";
+
+ int lastframe; // for delta compression
+ usercmd_t lastcmd = new usercmd_t(); // for filling in big drops
+
+ int commandMsec; // every seconds this is reset, if user
+ // commands exhaust it, assume time cheating
+
+ int frame_latency[] = new int[LATENCY_COUNTS];
+ int ping;
+
+ int message_size[] = new int[RATE_MESSAGES]; // used to rate drop packets
+ int rate;
+ int surpressCount; // number of messages rate supressed
+
+ // pointer
+ edict_t edict; // EDICT_NUM(clientnum+1)
+
+ //char name[32]; // extracted from userinfo, high bits masked
+ String name = ""; // extracted from userinfo, high bits masked
+
+ int messagelevel; // for filtering printed messages
+
+ // The datagram is written to by sound calls, prints, temp ents, etc.
+ // It can be harmlessly overflowed.
+ sizebuf_t datagram = new sizebuf_t();
+ byte datagram_buf[] = new byte[Defines.MAX_MSGLEN];
+
+ client_frame_t frames[] = new client_frame_t[Defines.UPDATE_BACKUP]; // updates can be delta'd from here
+
+ byte download[]; // file being downloaded
+ int downloadsize; // total bytes (can't use EOF because of paks)
+ int downloadcount; // bytes sent
+
+ int lastmessage; // sv.framenum when packet was last received
+ int lastconnect;
+
+ int challenge; // challenge of this user, randomly generated
+
+ netchan_t netchan = new netchan_t();
+
+ //TODO: this was introduced by rst, since java can't calculate the index out of the address.
+ int serverindex;
+}
diff --git a/src/jake2/server/moveclip_t.java b/src/jake2/server/moveclip_t.java
new file mode 100644
index 0000000..7bec554
--- /dev/null
+++ b/src/jake2/server/moveclip_t.java
@@ -0,0 +1,44 @@
+/*
+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.
+
+*/
+
+// Created on 09.12.2003 by RST.
+// $Id: moveclip_t.java,v 1.1 2004-07-07 19:59:50 hzi Exp $
+
+package jake2.server;
+
+
+import jake2.*;
+import jake2.client.*;
+import jake2.game.*;
+import jake2.qcommon.*;
+import jake2.render.*;
+import jake2.server.*;
+
+public class moveclip_t
+{
+ float [] boxmins={0,0,0}, boxmaxs={0,0,0};// enclose the test object along entire move
+ float [] mins, maxs; // size of the moving object
+ float [] mins2={0,0,0}, maxs2={0,0,0}; // size when clipping against mosnters
+ float [] start, end;
+ // mem
+ trace_t trace = new trace_t();
+ edict_t passedict;
+ int contentmask;
+}
diff --git a/src/jake2/server/scrap.jpage b/src/jake2/server/scrap.jpage
new file mode 100644
index 0000000..1666502
--- /dev/null
+++ b/src/jake2/server/scrap.jpage
@@ -0,0 +1,24 @@
+
+
+String p="\"123456\"";
+
+System.out.println(p.substring(1,p.length()-1));
+
+ String path ="c:/baseq2/game/*.sav";
+ String findbase;
+ String findpattern;
+
+ int i = path.lastIndexOf('/');
+ if (i!=-1)
+ {
+ findbase = path.substring(0,i);
+ findpattern = path.substring(i+1,path.length());
+ }
+ else
+ {
+ findpattern = "*";
+ findbase = path;
+ }
+
+
+ System.out.println(findbase + ":" + findpattern); \ No newline at end of file
diff --git a/src/jake2/server/server_static_t.java b/src/jake2/server/server_static_t.java
new file mode 100644
index 0000000..c9f94a2
--- /dev/null
+++ b/src/jake2/server/server_static_t.java
@@ -0,0 +1,65 @@
+/*
+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.
+
+*/
+
+// Created on 14.01.2004 by RST.
+// $Id: server_static_t.java,v 1.1 2004-07-07 19:59:51 hzi Exp $
+
+package jake2.server;
+
+import jake2.*;
+import jake2.client.*;
+import jake2.game.*;
+import jake2.qcommon.*;
+import jake2.render.*;
+import jake2.server.*;
+
+import java.io.*;
+
+public class server_static_t {
+ public server_static_t() {
+ for (int n = 0; n < Defines.MAX_CHALLENGES; n++) {
+ challenges[n] = new challenge_t();
+ }
+ }
+
+ boolean initialized; // sv_init has completed
+ int realtime; // always increasing, no clamping, etc
+
+ //char mapcmd[MAX_TOKEN_CHARS]; // ie: *intro.cin+base
+ String mapcmd = ""; // ie: *intro.cin+base
+
+ int spawncount; // incremented each server start
+ // used to check late spawns
+
+ client_t clients[]; // [maxclients->value];
+
+ int num_client_entities; // maxclients->value*UPDATE_BACKUP*MAX_PACKET_ENTITIES
+ int next_client_entities; // next client_entity to use
+ entity_state_t client_entities[]; // [num_client_entities]
+
+ int last_heartbeat;
+
+ challenge_t challenges[] = new challenge_t[Defines.MAX_CHALLENGES]; // to prevent invalid IPs from connecting
+
+ // serverrecord values
+ RandomAccessFile demofile;
+ sizebuf_t demo_multicast = new sizebuf_t();
+ byte demo_multicast_buf[] = new byte[Defines.MAX_MSGLEN];
+}
diff --git a/src/jake2/server/server_t.java b/src/jake2/server/server_t.java
new file mode 100644
index 0000000..8e260a2
--- /dev/null
+++ b/src/jake2/server/server_t.java
@@ -0,0 +1,76 @@
+/*
+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.
+
+*/
+
+// Created on 14.01.2004 by RST.
+// $Id: server_t.java,v 1.1 2004-07-07 19:59:51 hzi Exp $
+
+package jake2.server;
+
+import java.io.File;
+import java.io.RandomAccessFile;
+
+import jake2.*;
+import jake2.client.*;
+import jake2.game.*;
+import jake2.qcommon.*;
+import jake2.render.*;
+
+public class server_t {
+
+ public server_t() {
+ models = new cmodel_t[Defines.MAX_MODELS];
+ for (int n = 0; n < Defines.MAX_MODELS; n++)
+ models[n] = new cmodel_t();
+
+ for (int n=0; n < Defines.MAX_EDICTS; n++)
+ baselines[n] = new entity_state_t(null);
+
+ //for (int n=0; n < Defines.MAX_CONFIGSTRINGS; n++)
+ //configstrings[n] = "";
+
+ }
+ //server_state_t state;
+ int state; // precache commands are only valid during load
+
+ boolean attractloop; // running cinematics and demos for the local system only
+ boolean loadgame; // client begins should reuse existing entity
+
+ int time; // always sv.framenum * 100 msec
+ int framenum;
+
+ //char name[MAX_QPATH];
+ String name = ""; // map name, or cinematic name
+
+ //struct cmodel_s *models[MAX_MODELS];
+ cmodel_t models[];
+
+ //char configstrings[MAX_CONFIGSTRINGS][MAX_QPATH];
+ String configstrings[] = new String[Defines.MAX_CONFIGSTRINGS];
+ entity_state_t baselines[] = new entity_state_t[Defines.MAX_EDICTS];
+
+ // the multicast buffer is used to send a message to a set of clients
+ // it is only used to marshall data until SV_Multicast is called
+ sizebuf_t multicast = new sizebuf_t();
+ byte multicast_buf[] = new byte[Defines.MAX_MSGLEN];
+
+ // demo server information
+ RandomAccessFile demofile;
+ boolean timedemo; // don't time sync
+}