diff options
author | Holger Zickner <[email protected]> | 2004-07-07 19:59:59 +0000 |
---|---|---|
committer | Holger Zickner <[email protected]> | 2004-07-07 19:59:59 +0000 |
commit | 6e23fc1074d1f0c2c2812f4c2e663f5a21a43c20 (patch) | |
tree | 46ecc6d0255c874ba4cd26dc3d0733f785019896 /src/jake2/server |
import of Jake2 version sunrisesunrise
Diffstat (limited to 'src/jake2/server')
-rw-r--r-- | src/jake2/server/SV.java | 1110 | ||||
-rw-r--r-- | src/jake2/server/SV_CCMDS.java | 1190 | ||||
-rw-r--r-- | src/jake2/server/SV_ENTS.java | 605 | ||||
-rw-r--r-- | src/jake2/server/SV_GAME.java | 356 | ||||
-rw-r--r-- | src/jake2/server/SV_INIT.java | 498 | ||||
-rw-r--r-- | src/jake2/server/SV_MAIN.java | 1030 | ||||
-rw-r--r-- | src/jake2/server/SV_SEND.java | 562 | ||||
-rw-r--r-- | src/jake2/server/SV_USER.java | 662 | ||||
-rw-r--r-- | src/jake2/server/SV_WORLD.java | 654 | ||||
-rw-r--r-- | src/jake2/server/areanode_t.java | 43 | ||||
-rw-r--r-- | src/jake2/server/challenge_t.java | 39 | ||||
-rw-r--r-- | src/jake2/server/client_frame_t.java | 42 | ||||
-rw-r--r-- | src/jake2/server/client_t.java | 90 | ||||
-rw-r--r-- | src/jake2/server/moveclip_t.java | 44 | ||||
-rw-r--r-- | src/jake2/server/scrap.jpage | 24 | ||||
-rw-r--r-- | src/jake2/server/server_static_t.java | 65 | ||||
-rw-r--r-- | src/jake2/server/server_t.java | 76 |
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 +} |