diff options
author | Rene Stoeckel <[email protected]> | 2004-09-22 19:22:16 +0000 |
---|---|---|
committer | Rene Stoeckel <[email protected]> | 2004-09-22 19:22:16 +0000 |
commit | c4fcffe436fbfb5b0f3b7be2e5ee103ec74932f7 (patch) | |
tree | 7c9439ab1d9f5a4fd61bd57c755069007b23e0b6 /src/jake2/server | |
parent | bcb4ac6eefb425d5b0a90009da506361d5739e75 (diff) |
major refactoring in game, server and client package
Diffstat (limited to 'src/jake2/server')
-rw-r--r-- | src/jake2/server/SV.java | 2298 | ||||
-rw-r--r-- | src/jake2/server/SV_CCMDS.java | 390 | ||||
-rw-r--r-- | src/jake2/server/SV_ENTS.java | 1203 | ||||
-rw-r--r-- | src/jake2/server/SV_GAME.java | 617 | ||||
-rw-r--r-- | src/jake2/server/SV_INIT.java | 962 | ||||
-rw-r--r-- | src/jake2/server/SV_MAIN.java | 2034 | ||||
-rw-r--r-- | src/jake2/server/SV_SEND.java | 266 | ||||
-rw-r--r-- | src/jake2/server/SV_USER.java | 1408 | ||||
-rw-r--r-- | src/jake2/server/SV_WORLD.java | 1198 | ||||
-rw-r--r-- | src/jake2/server/server_static_t.java | 98 | ||||
-rw-r--r-- | src/jake2/server/server_t.java | 124 |
11 files changed, 5180 insertions, 5418 deletions
diff --git a/src/jake2/server/SV.java b/src/jake2/server/SV.java index 701f767..a046ef4 100644 --- a/src/jake2/server/SV.java +++ b/src/jake2/server/SV.java @@ -2,32 +2,36 @@ * SV.java * Copyright (C) 2003 * - * $Id: SV.java,v 1.9 2004-08-29 21:39:25 hzi Exp $ + * $Id: SV.java,v 1.10 2004-09-22 19:22:12 salomo Exp $ */ /* -Copyright (C) 1997-2001 Id Software, Inc. + 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 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. + 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. + 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. + 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.Defines; +import jake2.Globals; import jake2.client.M; -import jake2.game.*; +import jake2.game.GameBase; +import jake2.game.edict_t; +import jake2.game.pushed_t; +import jake2.game.trace_t; import jake2.qcommon.Com; import jake2.util.Lib; import jake2.util.Math3D; @@ -35,1175 +39,1095 @@ import jake2.util.Math3D; /** * 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) - Com.Error(Defines.ERR_FATAL, "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; - - 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, GameBase.dummyplane, 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[GameBase.MAX_CLIP_PLANES][3]; - 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= false; - - 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.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) - - SV_GAME.PF_error(Defines.ERR_FATAL, "pushed_p > &pushed[MAX_EDICTS], memory corrupted"); - - if (part != null) { - // the move failed, bump all nextthink times and back out moves - for (mv= ent; mv != null; mv= mv.teamchain) - { - if (mv.nextthink > 0) - mv.nextthink += Defines.FRAMETIME; - } - - // if the pusher has a "blocked" function, call it - // otherwise, just stay in place until the obstacle is gone - if (part.blocked != null) - part.blocked.blocked(part, GameBase.obstacle); - } - else - { // the move succeeded, so call all think functions - for (part= ent; part != null; part= part.teamchain) - { - SV_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.DPrintf("SV_NewChaseDir without enemy!\n"); - return; - } - olddir= Math3D.anglemod((int) (actor.ideal_yaw / 45) * 45); - turnaround= Math3D.anglemod(olddir - 180); - - deltax= enemy.s.origin[0] - actor.s.origin[0]; - deltay= enemy.s.origin[1] - actor.s.origin[1]; - if (deltax > 10) - d[1]= 0; - else if (deltax < -10) - d[1]= 180; - else - d[1]= 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; - } -} +public final class SV { + + /////////////////////////////////////// + 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) + Com.Error(Defines.ERR_FATAL, "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; + + 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, GameBase.dummyplane, 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[GameBase.MAX_CLIP_PLANES][3]; + 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(Globals.vec3_origin, ent.velocity); + return 3; + } + + if (trace.fraction > 0) { // actually covered some distance + Math3D.VectorCopy(trace.endpos, ent.s.origin); + Math3D.VectorCopy(ent.velocity, original_velocity); + numplanes = 0; + } + + if (trace.fraction == 1) + break; // moved the entire distance + + hit = trace.ent; + + if (trace.plane.normal[2] > 0.7) { + blocked |= 1; // floor + if (hit.solid == Defines.SOLID_BSP) { + ent.groundentity = hit; + ent.groundentity_linkcount = hit.linkcount; + } + } + if (trace.plane.normal[2] == 0.0f) { + blocked |= 2; // step + } + + // + // run the impact function + // + SV_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(Globals.vec3_origin, ent.velocity); + return 3; + } + + Math3D.VectorCopy(trace.plane.normal, planes[numplanes]); + numplanes++; + + // + // modify original_velocity so it parallels all of the clip planes + // + for (i = 0; i < numplanes; i++) { + GameBase.ClipVelocity(original_velocity, planes[i], + new_velocity, 1); + + for (j = 0; j < numplanes; j++) + if ((j != i) + && Math3D.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(Globals.vec3_origin, ent.velocity); + return 7; + } + Math3D.CrossProduct(planes[0], planes[1], dir); + d = Math3D.DotProduct(dir, ent.velocity); + Math3D.VectorScale(dir, d, ent.velocity); + } + + // + // if original velocity is against the original velocity, stop dead + // to avoid tiny occilations in sloping corners + // + if (Math3D.DotProduct(ent.velocity, primal_velocity) <= 0) { + Math3D.VectorCopy(Globals.vec3_origin, ent.velocity); + return blocked; + } + } + + return blocked; + } + + /* + * ============ 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 = false; + + 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(Globals.vec3_origin, amove, org); + Math3D.AngleVectors(org, forward, right, up); + + // save the pusher's original position + GameBase.pushed[GameBase.pushed_p].ent = pusher; + Math3D.VectorCopy(pusher.s.origin, + GameBase.pushed[GameBase.pushed_p].origin); + Math3D.VectorCopy(pusher.s.angles, + GameBase.pushed[GameBase.pushed_p].angles); + + if (pusher.client != null) + GameBase.pushed[GameBase.pushed_p].deltayaw = pusher.client.ps.pmove.delta_angles[Defines.YAW]; + + GameBase.pushed_p++; + + // move the pusher to it's final position + Math3D.VectorAdd(pusher.s.origin, move, pusher.s.origin); + Math3D.VectorAdd(pusher.s.angles, amove, pusher.s.angles); + GameBase.gi.linkentity(pusher); + + // see if any solid entities are inside the final position + + //check= g_edicts + 1; + for (e = 1; e < GameBase.num_edicts; e++) { + check = GameBase.g_edicts[e]; + if (!check.inuse) + continue; + if (check.movetype == Defines.MOVETYPE_PUSH + || check.movetype == Defines.MOVETYPE_STOP + || check.movetype == Defines.MOVETYPE_NONE + || check.movetype == Defines.MOVETYPE_NOCLIP) + continue; + + if (check.area.prev == null) + continue; // not linked in anywhere + + // if the entity is standing on the pusher, it will definitely be + // moved + if (check.groundentity != pusher) { + // see if the ent needs to be tested + if (check.absmin[0] >= maxs[0] || check.absmin[1] >= maxs[1] + || check.absmin[2] >= maxs[2] + || check.absmax[0] <= mins[0] + || check.absmax[1] <= mins[1] + || check.absmax[2] <= mins[2]) + continue; + + // see if the ent's bbox is inside the pusher's final position + if (SV_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) + SV_GAME.PF_error(Defines.ERR_FATAL, + "pushed_p > &pushed[MAX_EDICTS], memory corrupted"); + + if (part != null) { + // the move failed, bump all nextthink times and back out moves + for (mv = ent; mv != null; mv = mv.teamchain) { + if (mv.nextthink > 0) + mv.nextthink += Defines.FRAMETIME; + } + + // if the pusher has a "blocked" function, call it + // otherwise, just stay in place until the obstacle is gone + if (part.blocked != null) + part.blocked.blocked(part, GameBase.obstacle); + } else { // the move succeeded, so call all think functions + for (part = ent; part != null; part = part.teamchain) { + SV_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(Globals.vec3_origin, ent.velocity); + Math3D.VectorCopy(Globals.vec3_origin, ent.avelocity); + } + } + + // if (ent.touch) + // ent.touch (ent, trace.ent, &trace.plane, trace.surface); + } + + // check for water transition + wasinwater = (ent.watertype & Defines.MASK_WATER) != 0; + ent.watertype = GameBase.gi.pointcontents.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.DPrintf("SV_NewChaseDir without enemy!\n"); + return; + } + olddir = Math3D.anglemod((int) (actor.ideal_yaw / 45) * 45); + turnaround = Math3D.anglemod(olddir - 180); + + deltax = enemy.s.origin[0] - actor.s.origin[0]; + deltay = enemy.s.origin[1] - actor.s.origin[1]; + if (deltax > 10) + d[1] = 0; + else if (deltax < -10) + d[1] = 180; + else + d[1] = 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; + } +}
\ No newline at end of file diff --git a/src/jake2/server/SV_CCMDS.java b/src/jake2/server/SV_CCMDS.java index e6fd004..38e296e 100644 --- a/src/jake2/server/SV_CCMDS.java +++ b/src/jake2/server/SV_CCMDS.java @@ -19,22 +19,41 @@ 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.11 2004-09-10 19:02:56 salomo Exp $ +// $Id: SV_CCMDS.java,v 1.12 2004-09-22 19:22:12 salomo Exp $ package jake2.server; +import jake2.Defines; import jake2.Globals; -import jake2.game.*; -import jake2.qcommon.*; +import jake2.game.Cmd; +import jake2.game.EndianHandler; +import jake2.game.GameSVCmds; +import jake2.game.GameSave; +import jake2.game.Info; +import jake2.game.cvar_t; +import jake2.qcommon.CM; +import jake2.qcommon.Com; +import jake2.qcommon.Cvar; +import jake2.qcommon.FS; +import jake2.qcommon.MSG; +import jake2.qcommon.Netchan; +import jake2.qcommon.SZ; +import jake2.qcommon.netadr_t; +import jake2.qcommon.sizebuf_t; +import jake2.qcommon.xcommand_t; import jake2.sys.NET; import jake2.sys.Sys; +import jake2.util.Lib; import jake2.util.QuakeFile; import jake2.util.Vargs; -import java.io.*; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.RandomAccessFile; import java.util.Calendar; -public class SV_CCMDS extends SV_ENTS { +public class SV_CCMDS { /* =============================================================================== @@ -56,7 +75,7 @@ public class SV_CCMDS extends SV_ENTS { int i, slot; // only dedicated servers send heartbeats - if (dedicated.value == 0) { + if (Globals.dedicated.value == 0) { Com.Printf("Only dedicated servers use masters.\n"); return; } @@ -64,34 +83,31 @@ public class SV_CCMDS extends SV_ENTS { // make sure the server is listed public Cvar.Set("public", "1"); - for (i= 1; i < MAX_MASTERS; i++) - master_adr[i]= new netadr_t(); + for (i = 1; i < Defines.MAX_MASTERS; i++) + SV_MAIN.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) + slot = 1; // slot 0 will always contain the id master + for (i = 1; i < Cmd.Argc(); i++) { + if (slot == Defines.MAX_MASTERS) break; - if (!NET.StringToAdr(Cmd.Argv(i), master_adr[i])) { + if (!NET.StringToAdr(Cmd.Argv(i), SV_MAIN.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"); + if (SV_MAIN.master_adr[slot].port == 0) + SV_MAIN.master_adr[slot].port = Defines.PORT_MASTER; + Com.Printf("Master server at " + NET.AdrToString(SV_MAIN.master_adr[slot]) + "\n"); Com.Printf("Sending a ping.\n"); - Netchan.OutOfBandPrint(NS_SERVER, master_adr[slot], "ping"); + Netchan.OutOfBandPrint(Defines.NS_SERVER, SV_MAIN.master_adr[slot], "ping"); slot++; } - svs.last_heartbeat= -9999999; + SV_INIT.svs.last_heartbeat = -9999999; } - /* ================== SV_SetPlayer @@ -108,19 +124,19 @@ public class SV_CCMDS extends SV_ENTS { if (Cmd.Argc() < 2) return false; - s= Cmd.Argv(1); + 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) { + idnum = Lib.atoi(Cmd.Argv(1)); + if (idnum < 0 || idnum >= SV_MAIN.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) { + SV_MAIN.sv_client = SV_INIT.svs.clients[idnum]; + SV_USER.sv_player = SV_MAIN.sv_client.edict; + if (0 == SV_MAIN.sv_client.state) { Com.Printf("Client " + idnum + " is not active\n"); return false; } @@ -128,13 +144,13 @@ public class SV_CCMDS extends SV_ENTS { } // check for a name match - for (i= 0; i < maxclients.value; i++) { - cl= svs.clients[i]; + for (i = 0; i < SV_MAIN.maxclients.value; i++) { + cl = SV_INIT.svs.clients[i]; if (0 == cl.state) continue; - if (0 == strcmp(cl.name, s)) { - sv_client= cl; - sv_player= sv_client.edict; + if (0 == Lib.strcmp(cl.name, s)) { + SV_MAIN.sv_client = cl; + SV_USER.sv_player = SV_MAIN.sv_client.edict; return true; } } @@ -142,7 +158,6 @@ public class SV_CCMDS extends SV_ENTS { Com.Printf("Userid " + s + " is not on the server\n"); return false; } - /* =============================================================================== @@ -158,7 +173,6 @@ public class SV_CCMDS extends SV_ENTS { catch (Exception e) { } } - /* ===================== SV_WipeSavegame @@ -172,32 +186,31 @@ public class SV_CCMDS extends SV_ENTS { Com.DPrintf("SV_WipeSaveGame(" + savename + ")\n"); - name= FS.Gamedir() + "/save/" + savename + "/server.ssv"; + name = FS.Gamedir() + "/save/" + savename + "/server.ssv"; remove(name); - name= FS.Gamedir() + "/save/" + savename + "/game.ssv"; + name = FS.Gamedir() + "/save/" + savename + "/game.ssv"; remove(name); - name= FS.Gamedir() + "/save/" + savename + "/*.sav"; + name = FS.Gamedir() + "/save/" + savename + "/*.sav"; - File f= Sys.FindFirst(name, 0, 0); + File f = Sys.FindFirst(name, 0, 0); while (f != null) { f.delete(); - f= Sys.FindNext(); + f = Sys.FindNext(); } Sys.FindClose(); - name= FS.Gamedir() + "/save/" + savename + "/*.sv2"; + name = FS.Gamedir() + "/save/" + savename + "/*.sv2"; - f= Sys.FindFirst(name, 0, 0); + f = Sys.FindFirst(name, 0, 0); while (f != null) { f.delete(); - f= Sys.FindNext(); + f = Sys.FindNext(); } Sys.FindClose(); } - /* ================ CopyFile @@ -205,18 +218,18 @@ public class SV_CCMDS extends SV_ENTS { */ public static void CopyFile(String src, String dst) { RandomAccessFile f1, f2; - int l= -1; - byte buffer[]= new byte[65536]; + int l = -1; + byte buffer[] = new byte[65536]; //Com.DPrintf("CopyFile (" + src + ", " + dst + ")\n"); try { - f1= new RandomAccessFile(src, "r"); + f1 = new RandomAccessFile(src, "r"); } catch (Exception e) { return; } try { - f2= new RandomAccessFile(dst, "rw"); + f2 = new RandomAccessFile(dst, "rw"); } catch (Exception e) { try { @@ -231,7 +244,7 @@ public class SV_CCMDS extends SV_ENTS { while (true) { try { - l= f1.read(buffer, 0, 65536); + l = f1.read(buffer, 0, 65536); } catch (IOException e1) { @@ -263,7 +276,6 @@ public class SV_CCMDS extends SV_ENTS { e2.printStackTrace(); } } - /* ================ SV_CopySaveGame @@ -281,38 +293,37 @@ public class SV_CCMDS extends SV_ENTS { SV_WipeSavegame(dst); // copy the savegame over - name= FS.Gamedir() + "/save/" + src + "/server.ssv"; - name2= FS.Gamedir() + "/save/" + dst + "/server.ssv"; + 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= FS.Gamedir() + "/save/" + dst + "/game.ssv"; + name = FS.Gamedir() + "/save/" + src + "/game.ssv"; + name2 = FS.Gamedir() + "/save/" + dst + "/game.ssv"; CopyFile(name, name2); - String name1= FS.Gamedir() + "/save/" + src + "/"; - len= name1.length(); - name= FS.Gamedir() + "/save/" + src + "/*.sav"; + String name1 = FS.Gamedir() + "/save/" + src + "/"; + len = name1.length(); + name = FS.Gamedir() + "/save/" + src + "/*.sav"; - found= Sys.FindFirst(name, 0, 0); + found = Sys.FindFirst(name, 0, 0); while (found != null) { - name= name1 + found.getName(); - name2= FS.Gamedir() + "/save/" + dst + "/" + found.getName(); + name = name1 + found.getName(); + name2 = FS.Gamedir() + "/save/" + dst + "/" + found.getName(); CopyFile(name, name2); // change sav to sv2 - name= name.substring(0, name.length() - 3) + "sv2"; - name2= name2.substring(0, name2.length() - 3) + "sv2"; + name = name.substring(0, name.length() - 3) + "sv2"; + name2 = name2.substring(0, name2.length() - 3) + "sv2"; CopyFile(name, name2); - found= Sys.FindNext(); + found = Sys.FindNext(); } Sys.FindClose(); } - /* ============== SV_WriteLevelFile @@ -326,13 +337,13 @@ public class SV_CCMDS extends SV_ENTS { Com.DPrintf("SV_WriteLevelFile()\n"); - name= FS.Gamedir() + "/save/current/" + sv.name + ".sv2"; + name = FS.Gamedir() + "/save/current/" + SV_INIT.sv.name + ".sv2"; try { - f= new QuakeFile(name, "rw"); + f = new QuakeFile(name, "rw"); - for (int i= 0; i < MAX_CONFIGSTRINGS; i++) - f.writeString(sv.configstrings[i]); + for (int i = 0; i < Defines.MAX_CONFIGSTRINGS; i++) + f.writeString(SV_INIT.sv.configstrings[i]); CM.CM_WritePortalState(f); f.close(); @@ -342,10 +353,9 @@ public class SV_CCMDS extends SV_ENTS { e.printStackTrace(); } - name= FS.Gamedir() + "/save/current/" + sv.name + ".sav"; + name = FS.Gamedir() + "/save/current/" + SV_INIT.sv.name + ".sav"; GameSave.WriteLevel(name); } - /* ============== SV_ReadLevelFile @@ -359,12 +369,12 @@ public class SV_CCMDS extends SV_ENTS { Com.DPrintf("SV_ReadLevelFile()\n"); - name= FS.Gamedir() + "/save/current/" + sv.name + ".sv2"; + name = FS.Gamedir() + "/save/current/" + SV_INIT.sv.name + ".sv2"; try { - f= new QuakeFile(name, "r"); + f = new QuakeFile(name, "r"); - for (int n= 0; n < MAX_CONFIGSTRINGS; n++) - sv.configstrings[n]= f.readString(); + for (int n = 0; n < Defines.MAX_CONFIGSTRINGS; n++) + SV_INIT.sv.configstrings[n] = f.readString(); CM.CM_ReadPortalState(f); @@ -375,10 +385,9 @@ public class SV_CCMDS extends SV_ENTS { e1.printStackTrace(); } - name= FS.Gamedir() + "/save/current/" + sv.name + ".sav"; + name = FS.Gamedir() + "/save/current/" + SV_INIT.sv.name + ".sav"; GameSave.ReadLevel(name); } - /* ============== SV_WriteServerFile @@ -393,41 +402,42 @@ public class SV_CCMDS extends SV_ENTS { Com.DPrintf("SV_WriteServerFile(" + (autosave ? "true" : "false") + ")\n"); - filename= FS.Gamedir() + "/save/current/server.ssv"; + filename = FS.Gamedir() + "/save/current/server.ssv"; try { - f= new QuakeFile(filename, "rw"); + f = new QuakeFile(filename, "rw"); if (!autosave) { - Calendar c= Calendar.getInstance(); - comment= + Calendar c = Calendar.getInstance(); + comment = Com.sprintf( "%2i:%2i %2i/%2i ", - new Vargs().add(c.get(Calendar.HOUR_OF_DAY)).add(c.get(Calendar.MINUTE)).add(c.get(Calendar.MONTH) + 1).add( + new Vargs().add(c.get(Calendar.HOUR_OF_DAY)).add(c.get(Calendar.MINUTE)).add( + c.get(Calendar.MONTH) + 1).add( c.get(Calendar.DAY_OF_MONTH))); - comment += sv.configstrings[CS_NAME]; + comment += SV_INIT.sv.configstrings[Defines.CS_NAME]; } else { // autosaved - comment= "ENTERING " + sv.configstrings[CS_NAME]; + comment = "ENTERING " + SV_INIT.sv.configstrings[Defines.CS_NAME]; } f.writeString(comment); - f.writeString(svs.mapcmd); + f.writeString(SV_INIT.svs.mapcmd); // 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)) + for (var = Globals.cvar_vars; var != null; var = var.next) { + if (0 == (var.flags & Defines.CVAR_LATCH)) continue; - if (var.name.length() >= MAX_OSPATH - 1 || var.string.length() >= 128 - 1) { + if (var.name.length() >= Defines.MAX_OSPATH - 1 || var.string.length() >= 128 - 1) { Com.Printf("Cvar too long: " + var.name + " = " + var.string + "\n"); continue; } - name= var.name; - string= var.string; + name = var.name; + string = var.string; try { f.writeString(name); f.writeString(string); @@ -445,10 +455,9 @@ public class SV_CCMDS extends SV_ENTS { } // write game state - filename= FS.Gamedir() + "/save/current/game.ssv"; + filename = FS.Gamedir() + "/save/current/game.ssv"; GameSave.WriteGame(filename, autosave); } - /* ============== SV_ReadServerFile @@ -456,31 +465,31 @@ public class SV_CCMDS extends SV_ENTS { ============== */ public static void SV_ReadServerFile() { - String filename, name= "", string, comment, mapcmd; + String filename, name = "", string, comment, mapcmd; try { QuakeFile f; - mapcmd= ""; + mapcmd = ""; Com.DPrintf("SV_ReadServerFile()\n"); - filename= FS.Gamedir() + "/save/current/server.ssv"; + filename = FS.Gamedir() + "/save/current/server.ssv"; - f= new QuakeFile(filename, "r"); + f = new QuakeFile(filename, "r"); // read the comment field - comment= f.readString(); + comment = f.readString(); // read the mapcmd - mapcmd= f.readString(); + mapcmd = f.readString(); // read all CVAR_LATCH cvars // these will be things like coop, skill, deathmatch, etc while (true) { - name= f.readString(); + name = f.readString(); if (name == null) break; - string= f.readString(); + string = f.readString(); Com.DPrintf("Set " + name + " = " + string + "\n"); Cvar.ForceSet(name, string); @@ -489,12 +498,12 @@ public class SV_CCMDS extends SV_ENTS { f.close(); // start a new game fresh with new cvars - SV_InitGame(); + SV_INIT.SV_InitGame(); - svs.mapcmd= mapcmd; + SV_INIT.svs.mapcmd = mapcmd; // read game state - filename= FS.Gamedir() + "/save/current/game.ssv"; + filename = FS.Gamedir() + "/save/current/game.ssv"; GameSave.ReadGame(filename); } catch (Exception e) { @@ -502,7 +511,6 @@ public class SV_CCMDS extends SV_ENTS { e.printStackTrace(); } } - //========================================================= /* @@ -513,9 +521,8 @@ public class SV_CCMDS extends SV_ENTS { ================== */ public static void SV_DemoMap_f() { - SV_Map(true, Cmd.Argv(1), false); + SV_INIT.SV_Map(true, Cmd.Argv(1), false); } - /* ================== SV_GameMap_f @@ -550,48 +557,47 @@ public class SV_CCMDS extends SV_ENTS { FS.CreatePath(FS.Gamedir() + "/save/current/"); // check for clearing the current savegame - map= Cmd.Argv(1); + 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) { + if (SV_INIT.sv.state == Defines.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; + savedInuse = new boolean[(int) SV_MAIN.maxclients.value]; + for (i = 0; i < SV_MAIN.maxclients.value; i++) { + cl = SV_INIT.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]; + for (i = 0; i < SV_MAIN.maxclients.value; i++) { + cl = SV_INIT.svs.clients[i]; + cl.edict.inuse = savedInuse[i]; } - savedInuse= null; + savedInuse = null; } } // start up the next map - SV_Map(false, Cmd.Argv(1), false); + SV_INIT.SV_Map(false, Cmd.Argv(1), false); // archive server state - svs.mapcmd= Cmd.Argv(1); + SV_INIT.svs.mapcmd = Cmd.Argv(1); // copy off the level to the autosave slot - if (0 == dedicated.value) { + if (0 == Globals.dedicated.value) { SV_WriteServerFile(true); SV_CopySaveGame("current", "save0"); } } - /* ================== SV_Map_f @@ -606,9 +612,9 @@ public class SV_CCMDS extends SV_ENTS { String expanded; // if not a pcx, demo, or cinematic, check to make sure the level exists - map= Cmd.Argv(1); + map = Cmd.Argv(1); if (map.indexOf(".") < 0) { - expanded= "maps/" + map + ".bsp"; + expanded = "maps/" + map + ".bsp"; if (FS.LoadFile(expanded) == null) { Com.Printf("Can't find " + expanded + "\n"); @@ -616,12 +622,11 @@ public class SV_CCMDS extends SV_ENTS { } } - sv.state= ss_dead; // don't save current level when changing + SV_INIT.sv.state = Defines.ss_dead; // don't save current level when changing SV_WipeSavegame("current"); SV_GameMap_f(); } - /* ===================================================================== @@ -649,15 +654,15 @@ public class SV_CCMDS extends SV_ENTS { Com.Printf("Loading game...\n"); - dir= Cmd.Argv(1); - if (strstr(dir, "..") || strstr(dir, "/") || strstr(dir, "\\")) { + dir = Cmd.Argv(1); + if (Lib.strstr(dir, "..") || Lib.strstr(dir, "/") || Lib.strstr(dir, "\\")) { Com.Printf("Bad savedir.\n"); } // make sure the server.ssv file exists - name= FS.Gamedir() + "/save/" + Cmd.Argv(1) + "/server.ssv"; + name = FS.Gamedir() + "/save/" + Cmd.Argv(1) + "/server.ssv"; try { - f= new RandomAccessFile(name, "r"); + f = new RandomAccessFile(name, "r"); } catch (FileNotFoundException e) { Com.Printf("No such savegame: " + name + "\n"); @@ -675,10 +680,9 @@ public class SV_CCMDS extends SV_ENTS { 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_INIT.sv.state = Defines.ss_dead; // don't save current level when changing + SV_INIT.SV_Map(false, SV_INIT.svs.mapcmd, true); } - /* ============== SV_Savegame_f @@ -688,7 +692,7 @@ public class SV_CCMDS extends SV_ENTS { public static void SV_Savegame_f() { String dir; - if (sv.state != ss_game) { + if (SV_INIT.sv.state != Defines.ss_game) { Com.Printf("You must be in a game to save.\n"); return; } @@ -703,18 +707,18 @@ public class SV_CCMDS extends SV_ENTS { return; } - if (0 == strcmp(Cmd.Argv(1), "current")) { + if (0 == Lib.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) { + if (SV_MAIN.maxclients.value == 1 && SV_INIT.svs.clients[0].edict.client.ps.stats[Defines.STAT_HEALTH] <= 0) { Com.Printf("\nCan't savegame while dead!\n"); return; } - dir= Cmd.Argv(1); - if (strstr(dir, "..") || strstr(dir, "/") || strstr(dir, "\\")) { + dir = Cmd.Argv(1); + if (Lib.strstr(dir, "..") || Lib.strstr(dir, "/") || Lib.strstr(dir, "\\")) { Com.Printf("Bad savedir.\n"); } @@ -737,7 +741,6 @@ public class SV_CCMDS extends SV_ENTS { SV_CopySaveGame("current", dir); Com.Printf("Done.\n"); } - //=============================================================== /* ================== @@ -747,7 +750,7 @@ public class SV_CCMDS extends SV_ENTS { ================== */ public static void SV_Kick_f() { - if (!svs.initialized) { + if (!SV_INIT.svs.initialized) { Com.Printf("No server running.\n"); return; } @@ -760,14 +763,13 @@ public class SV_CCMDS extends SV_ENTS { if (!SV_SetPlayer()) return; - SV_BroadcastPrintf(PRINT_HIGH, sv_client.name + " was kicked\n"); + SV_SEND.SV_BroadcastPrintf(Defines.PRINT_HIGH, SV_MAIN.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_SEND.SV_ClientPrintf(SV_MAIN.sv_client, Defines.PRINT_HIGH, "You were kicked from the game\n"); + SV_MAIN.SV_DropClient(SV_MAIN.sv_client); + SV_MAIN.sv_client.lastmessage = SV_INIT.svs.realtime; // min case there is a funny zombie } - /* ================ SV_Status_f @@ -778,42 +780,42 @@ public class SV_CCMDS extends SV_ENTS { client_t cl; String s; int ping; - if (svs.clients == null) { + if (SV_INIT.svs.clients == null) { Com.Printf("No server running.\n"); return; } - Com.Printf("map : " + sv.name + "\n"); + Com.Printf("map : " + SV_INIT.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]; + for (i = 0; i < SV_MAIN.maxclients.value; i++) { + cl = SV_INIT.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])); + Com.Printf("%5i ", new Vargs().add(cl.edict.client.ps.stats[Defines.STAT_FRAGS])); - if (cl.state == cs_connected) + if (cl.state == Defines.cs_connected) Com.Printf("CNCT "); - else if (cl.state == cs_zombie) + else if (cl.state == Defines.cs_zombie) Com.Printf("ZMBI "); else { - ping= cl.ping < 9999 ? cl.ping : 9999; + ping = cl.ping < 9999 ? cl.ping : 9999; Com.Printf("%4i ", new Vargs().add(ping)); } Com.Printf("%s", new Vargs().add(cl.name)); - l= 16 - cl.name.length(); - for (j= 0; j < l; j++) + l = 16 - cl.name.length(); + for (j = 0; j < l; j++) Com.Printf(" "); - Com.Printf("%7i ", new Vargs().add(svs.realtime - cl.lastmessage)); + Com.Printf("%7i ", new Vargs().add(SV_INIT.svs.realtime - cl.lastmessage)); - s= NET.AdrToString(cl.netchan.remote_address); + s = NET.AdrToString(cl.netchan.remote_address); Com.Printf(s); - l= 22 - s.length(); - for (j= 0; j < l; j++) + l = 22 - s.length(); + for (j = 0; j < l; j++) Com.Printf(" "); Com.Printf("%5i", new Vargs().add(cl.netchan.qport)); @@ -822,7 +824,6 @@ public class SV_CCMDS extends SV_ENTS { } Com.Printf("\n"); } - /* ================== SV_ConSay_f @@ -837,32 +838,30 @@ public class SV_CCMDS extends SV_ENTS { if (Cmd.Argc() < 2) return; - text= "console: "; - p= Cmd.Args(); + text = "console: "; + p = Cmd.Args(); if (p.charAt(0) == '"') { - p= p.substring(1, p.length() - 1); + 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) + for (j = 0; j < SV_MAIN.maxclients.value; j++) { + client = SV_INIT.svs.clients[j]; + if (client.state != Defines.cs_spawned) continue; - SV_ClientPrintf(client, PRINT_CHAT, text + "\n"); + SV_SEND.SV_ClientPrintf(client, Defines.PRINT_CHAT, text + "\n"); } } - /* ================== SV_Heartbeat_f ================== */ public static void SV_Heartbeat_f() { - svs.last_heartbeat= -9999999; + SV_INIT.svs.last_heartbeat = -9999999; } - /* =========== SV_Serverinfo_f @@ -874,7 +873,6 @@ public class SV_CCMDS extends SV_ENTS { Com.Printf("Server info settings:\n"); Info.Print(Cvar.Serverinfo()); } - /* =========== SV_DumpUser_f @@ -893,10 +891,9 @@ public class SV_CCMDS extends SV_ENTS { Com.Printf("userinfo\n"); Com.Printf("--------\n"); - Info.Print(sv_client.userinfo); + Info.Print(SV_MAIN.sv_client.userinfo); } - /* ============== SV_ServerRecord_f @@ -908,8 +905,8 @@ public class SV_CCMDS extends SV_ENTS { public static void SV_ServerRecord_f() { //char name[MAX_OSPATH]; String name; - byte buf_data[]= new byte[32768]; - sizebuf_t buf= new sizebuf_t(); + byte buf_data[] = new byte[32768]; + sizebuf_t buf = new sizebuf_t(); int len; int i; @@ -918,12 +915,12 @@ public class SV_CCMDS extends SV_ENTS { return; } - if (svs.demofile != null) { + if (SV_INIT.svs.demofile != null) { Com.Printf("Already recording.\n"); return; } - if (sv.state != ss_game) { + if (SV_INIT.sv.state != Defines.ss_game) { Com.Printf("You must be in a level to record.\n"); return; } @@ -931,12 +928,12 @@ public class SV_CCMDS extends SV_ENTS { // // open the demo file // - name= FS.Gamedir() + "/demos/" + Cmd.Argv(1) + ".dm2"; + name = FS.Gamedir() + "/demos/" + Cmd.Argv(1) + ".dm2"; Com.Printf("recording to " + name + ".\n"); FS.CreatePath(name); try { - svs.demofile= new RandomAccessFile(name, "rw"); + SV_INIT.svs.demofile = new RandomAccessFile(name, "rw"); } catch (Exception e) { Com.Printf("ERROR: couldn't open.\n"); @@ -944,7 +941,7 @@ public class SV_CCMDS extends SV_ENTS { } // setup a buffer to catch all multicasts - SZ.Init(svs.demo_multicast, svs.demo_multicast_buf, svs.demo_multicast_buf.length); + SZ.Init(SV_INIT.svs.demo_multicast, SV_INIT.svs.demo_multicast_buf, SV_INIT.svs.demo_multicast_buf.length); // // write a single giant fake message with all the startup info @@ -956,31 +953,31 @@ public class SV_CCMDS extends SV_ENTS { // 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); + MSG.WriteByte(buf, Defines.svc_serverdata); + MSG.WriteLong(buf, Defines.PROTOCOL_VERSION); + MSG.WriteLong(buf, SV_INIT.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]); + MSG.WriteString(buf, SV_INIT.sv.configstrings[Defines.CS_NAME]); - for (i= 0; i < MAX_CONFIGSTRINGS; i++) - if (sv.configstrings[i].length() == 0) { - MSG.WriteByte(buf, svc_configstring); + for (i = 0; i < Defines.MAX_CONFIGSTRINGS; i++) + if (SV_INIT.sv.configstrings[i].length() == 0) { + MSG.WriteByte(buf, Defines.svc_configstring); MSG.WriteShort(buf, i); - MSG.WriteString(buf, sv.configstrings[i]); + MSG.WriteString(buf, SV_INIT.sv.configstrings[i]); } // write it to the demo file Com.DPrintf("signon message length: " + buf.cursize + "\n"); - len= EndianHandler.swapInt(buf.cursize); + 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,0, buf.cursize); + SV_INIT.svs.demofile.writeInt(len); + SV_INIT.svs.demofile.write(buf.data, 0, buf.cursize); } catch (IOException e1) { // TODO: do quake2 error handling! @@ -989,7 +986,6 @@ public class SV_CCMDS extends SV_ENTS { // the rest of the demo file will be individual frames } - /* ============== SV_ServerStop_f @@ -998,20 +994,19 @@ public class SV_CCMDS extends SV_ENTS { ============== */ public static void SV_ServerStop_f() { - if (svs.demofile == null) { + if (SV_INIT.svs.demofile == null) { Com.Printf("Not doing a serverrecord.\n"); return; } try { - svs.demofile.close(); + SV_INIT.svs.demofile.close(); } catch (IOException e) { e.printStackTrace(); } - svs.demofile= null; + SV_INIT.svs.demofile = null; Com.Printf("Recording completed.\n"); } - /* =============== SV_KillServer_f @@ -1021,12 +1016,11 @@ public class SV_CCMDS extends SV_ENTS { =============== */ public static void SV_KillServer_f() { - if (!svs.initialized) + if (!SV_INIT.svs.initialized) return; - SV_Shutdown("Server was killed.\n", false); + SV_MAIN.SV_Shutdown("Server was killed.\n", false); NET.Config(false); // close network sockets } - /* =============== SV_ServerCommand_f @@ -1036,9 +1030,8 @@ public class SV_CCMDS extends SV_ENTS { */ public static void SV_ServerCommand_f() { - Game.ServerCommand(); + GameSVCmds.ServerCommand(); } - //=========================================================== /* @@ -1094,7 +1087,7 @@ public class SV_CCMDS extends SV_ENTS { } }); - if (dedicated.value != 0) + if (Globals.dedicated.value != 0) Cmd.AddCommand("say", new xcommand_t() { public void execute() { SV_ConSay_f(); @@ -1135,5 +1128,4 @@ public class SV_CCMDS extends SV_ENTS { } }); } - } diff --git a/src/jake2/server/SV_ENTS.java b/src/jake2/server/SV_ENTS.java index 8b3db67..706fc27 100644 --- a/src/jake2/server/SV_ENTS.java +++ b/src/jake2/server/SV_ENTS.java @@ -1,603 +1,620 @@ /* -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. - -*/ + * 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.4 2004-08-29 21:39:25 hzi Exp $ - +// $Id: SV_ENTS.java,v 1.5 2004-09-22 19:22:12 salomo Exp $ package jake2.server; +import jake2.Defines; import jake2.game.*; import jake2.qcommon.*; +import jake2.util.Math3D; import java.io.IOException; -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; - // ptr - player_state_t ps, ops; - // mem - 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) { - //ptr - 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]); - - System.arraycopy(CM.CM_ClusterPVS(leafs[0]), 0, fatpvs, 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.set(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 < GameBase.num_edicts; e++) { - ent = GameBase.g_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].set(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 = GameBase.g_edicts[e]; - - while (e < GameBase.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 = GameBase.g_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); - } - } -} +public class SV_ENTS { + + /* + * ============================================================================= + * + * Build a client frame structure + * + * ============================================================================= + */ + + public static byte fatpvs[] = new byte[65536 / 8]; // 32767 is MAX_MAP_LEAFS + + /* + * ============================================================================= + * + * 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, Defines.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 = SV_INIT.svs.client_entities[(to.first_entity + newindex) + % SV_INIT.svs.num_client_entities]; + newnum = newent.number; + } + + if (oldindex >= from_num_entities) + oldnum = 9999; + else { + oldent = SV_INIT.svs.client_entities[(from.first_entity + oldindex) + % SV_INIT.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 <= SV_MAIN.maxclients.value); + oldindex++; + newindex++; + continue; + } + + if (newnum < oldnum) { // this is a new entity, send it from the + // baseline + MSG.WriteDeltaEntity(SV_INIT.sv.baselines[newnum], newent, msg, + true, true); + newindex++; + continue; + } + + if (newnum > oldnum) { // the old entity isn't present in the new + // message + bits = Defines.U_REMOVE; + if (oldnum >= 256) + bits |= Defines.U_NUMBER16 | Defines.U_MOREBITS1; + + MSG.WriteByte(msg, bits & 255); + if ((bits & 0x0000ff00) != 0) + MSG.WriteByte(msg, (bits >> 8) & 255); + + if ((bits & Defines.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; + // ptr + player_state_t ps, ops; + // mem + 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 |= Defines.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 |= Defines.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 |= Defines.PS_M_VELOCITY; + + if (ps.pmove.pm_time != ops.pmove.pm_time) + pflags |= Defines.PS_M_TIME; + + if (ps.pmove.pm_flags != ops.pmove.pm_flags) + pflags |= Defines.PS_M_FLAGS; + + if (ps.pmove.gravity != ops.pmove.gravity) + pflags |= Defines.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 |= Defines.PS_M_DELTA_ANGLES; + + if (ps.viewoffset[0] != ops.viewoffset[0] + || ps.viewoffset[1] != ops.viewoffset[1] + || ps.viewoffset[2] != ops.viewoffset[2]) + pflags |= Defines.PS_VIEWOFFSET; + + if (ps.viewangles[0] != ops.viewangles[0] + || ps.viewangles[1] != ops.viewangles[1] + || ps.viewangles[2] != ops.viewangles[2]) + pflags |= Defines.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 |= Defines.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 |= Defines.PS_BLEND; + + if (ps.fov != ops.fov) + pflags |= Defines.PS_FOV; + + if (ps.rdflags != ops.rdflags) + pflags |= Defines.PS_RDFLAGS; + + if (ps.gunframe != ops.gunframe) + pflags |= Defines.PS_WEAPONFRAME; + + pflags |= Defines.PS_WEAPONINDEX; + + // + // write it + // + MSG.WriteByte(msg, Defines.svc_playerinfo); + MSG.WriteShort(msg, pflags); + + // + // write the pmove_state_t + // + if ((pflags & Defines.PS_M_TYPE) != 0) + MSG.WriteByte(msg, ps.pmove.pm_type); + + if ((pflags & Defines.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 & Defines.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 & Defines.PS_M_TIME) != 0) + MSG.WriteByte(msg, ps.pmove.pm_time); + + if ((pflags & Defines.PS_M_FLAGS) != 0) + MSG.WriteByte(msg, ps.pmove.pm_flags); + + if ((pflags & Defines.PS_M_GRAVITY) != 0) + MSG.WriteShort(msg, ps.pmove.gravity); + + if ((pflags & Defines.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 & Defines.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 & Defines.PS_VIEWANGLES) != 0) { + MSG.WriteAngle16(msg, ps.viewangles[0]); + MSG.WriteAngle16(msg, ps.viewangles[1]); + MSG.WriteAngle16(msg, ps.viewangles[2]); + } + + if ((pflags & Defines.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 & Defines.PS_WEAPONINDEX) != 0) { + MSG.WriteByte(msg, ps.gunindex); + } + + if ((pflags & Defines.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 & Defines.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 & Defines.PS_FOV) != 0) + MSG.WriteByte(msg, ps.fov); + if ((pflags & Defines.PS_RDFLAGS) != 0) + MSG.WriteByte(msg, ps.rdflags); + + // send stats + statbits = 0; + for (i = 0; i < Defines.MAX_STATS; i++) + if (ps.stats[i] != ops.stats[i]) + statbits |= 1 << i; + MSG.WriteLong(msg, statbits); + for (i = 0; i < Defines.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) { + //ptr + 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_INIT.sv.framenum & Defines.UPDATE_MASK]; + if (client.lastframe <= 0) { // client is asking for a retransmit + oldframe = null; + lastframe = -1; + } else if (SV_INIT.sv.framenum - client.lastframe >= (Defines.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 & Defines.UPDATE_MASK]; + lastframe = client.lastframe; + } + + MSG.WriteByte(msg, Defines.svc_frame); + MSG.WriteLong(msg, SV_INIT.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); + } + + /* + * ============ 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(Defines.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]); + + System.arraycopy(CM.CM_ClusterPVS(leafs[0]), 0, SV_ENTS.fatpvs, 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++) { + SV_ENTS.fatpvs[k] |= src[k++]; + SV_ENTS.fatpvs[k] |= src[k++]; + SV_ENTS.fatpvs[k] |= src[k++]; + SV_ENTS.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_INIT.sv.framenum & Defines.UPDATE_MASK]; + + frame.senttime = SV_INIT.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.set(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 = SV_INIT.svs.next_client_entities; + + c_fullsend = 0; + + for (e = 1; e < GameBase.num_edicts; e++) { + ent = GameBase.g_edicts[e]; + + // ignore ents without visible models + if ((ent.svflags & Defines.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 & Defines.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 = SV_ENTS.fatpvs; //clientphs; + } else + bitvector = SV_ENTS.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; + + Math3D.VectorSubtract(org, ent.s.origin, delta); + len = Math3D.VectorLength(delta); + if (len > 400) + continue; + } + } + } + + // add it to the circular client_entities array + int ix = SV_INIT.svs.next_client_entities + % SV_INIT.svs.num_client_entities; + state = SV_INIT.svs.client_entities[ix]; + if (ent.s.number != e) { + Com.DPrintf("FIXING ENT.S.NUMBER!!!\n"); + ent.s.number = e; + } + + //*state = ent.s; + SV_INIT.svs.client_entities[ix].set(ent.s); + + // don't mark players missiles as solid + if (ent.owner == client.edict) + state.solid = 0; + + SV_INIT.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 (SV_INIT.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, Defines.svc_frame); + MSG.WriteLong(buf, SV_INIT.sv.framenum); + + MSG.WriteByte(buf, Defines.svc_packetentities); + + e = 1; + ent = GameBase.g_edicts[e]; + + while (e < GameBase.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 & Defines.SVF_NOCLIENT)) + MSG.WriteDeltaEntity(nostate, ent.s, buf, false, true); + + e++; + ent = GameBase.g_edicts[e]; + } + + MSG.WriteShort(buf, 0); // end of packetentities + + // now add the accumulated multicast information + SZ.Write(buf, SV_INIT.svs.demo_multicast.data, + SV_INIT.svs.demo_multicast.cursize); + SZ.Clear(SV_INIT.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); + SV_INIT.svs.demofile.writeInt(len); + //fwrite (buf.data, buf.cursize, 1, svs.demofile); + SV_INIT.svs.demofile.write(buf.data, 0, buf.cursize); + } catch (IOException e1) { + Com.Printf("Error writing demo file:" + e); + } + } +}
\ No newline at end of file diff --git a/src/jake2/server/SV_GAME.java b/src/jake2/server/SV_GAME.java index 844aacb..33f35b4 100644 --- a/src/jake2/server/SV_GAME.java +++ b/src/jake2/server/SV_GAME.java @@ -1,318 +1,313 @@ /* -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. - -*/ + * 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.7 2004-08-29 21:39:25 hzi Exp $ - +// $Id: SV_GAME.java,v 1.8 2004-09-22 19:22:12 salomo Exp $ package jake2.server; +import jake2.Defines; +import jake2.Globals; import jake2.game.*; import jake2.qcommon.*; - -public class SV_GAME extends SV_INIT { - - - /* - =============== - 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 = 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) { - Com.Printf(fmt); - } - - /* - =============== - PF_cprintf - - Print to a single client - =============== - */ - public static void PF_cprintf(edict_t ent, int level, String fmt) { - - int n = 0; - - if (ent != null) { - n = ent.index; - if (n < 1 || n > SV_MAIN.maxclients.value) - Com.Error(ERR_DROP, "cprintf to a non-client"); - } - - 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) { - int n; - - n = ent.index; - if (n < 1 || n > SV_MAIN.maxclients.value) - return; // Com_Error (ERR_DROP, "centerprintf to a non-client"); - - 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.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); - - // quake2 bugfix - if (cluster == -1) - return false; - 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); - - // quake2 bugfix - if (cluster == -1) - return false; - 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; - 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() { - Game.ShutdownGame(); - } - - /* - =============== - SV_InitGameProgs - - Init the game subsystem for a new map - =============== - */ - - public static void SV_InitGameProgs() { - - // unload anything we have now - SV_ShutdownGameProgs(); - - game_import_t gimport = new game_import_t(); - - // all functions set in game_export_t (rst) - GameBase.GetGameApi(gimport); - - Game.InitGame(); - } -} +import jake2.util.Math3D; + +public class SV_GAME { + + /* + * =============== 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 = 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_INIT.sv.multicast.data, + SV_INIT.sv.multicast.cursize); + + SZ.Clear(SV_INIT.sv.multicast); + } + + /* + * =============== PF_dprintf + * + * Debug print to server console =============== + */ + public static void PF_dprintf(String fmt) { + Com.Printf(fmt); + } + + /* + * =============== PF_cprintf + * + * Print to a single client =============== + */ + public static void PF_cprintf(edict_t ent, int level, String fmt) { + + int n = 0; + + if (ent != null) { + n = ent.index; + if (n < 1 || n > SV_MAIN.maxclients.value) + Com.Error(Defines.ERR_DROP, "cprintf to a non-client"); + } + + if (ent != null) + SV_SEND.SV_ClientPrintf(SV_INIT.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) { + int n; + + n = ent.index; + if (n < 1 || n > SV_MAIN.maxclients.value) + return; // Com_Error (ERR_DROP, "centerprintf to a non-client"); + + MSG.WriteByte(SV_INIT.sv.multicast, Defines.svc_centerprint); + MSG.WriteString(SV_INIT.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(Defines.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(Defines.ERR_DROP, "PF_setmodel: NULL"); + + i = SV_INIT.SV_ModelIndex(name); + + ent.s.modelindex = i; + + // if it is an inline model, get the size information for it + if (name.startsWith("*")) { + mod = CM.InlineModel(name); + Math3D.VectorCopy(mod.mins, ent.mins); + Math3D.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 >= Defines.MAX_CONFIGSTRINGS) + Com.Error(Defines.ERR_DROP, "configstring: bad index " + index + + "\n"); + + if (val == null) + val = ""; + + // change the string in sv + SV_INIT.sv.configstrings[index] = val; + + if (SV_INIT.sv.state != Defines.ss_loading) { // send the update to + // everyone + SZ.Clear(SV_INIT.sv.multicast); + MSG.WriteChar(SV_INIT.sv.multicast, Defines.svc_configstring); + MSG.WriteShort(SV_INIT.sv.multicast, index); + MSG.WriteString(SV_INIT.sv.multicast, val); + + SV_SEND.SV_Multicast(Globals.vec3_origin, Defines.MULTICAST_ALL_R); + } + } + + public static void PF_WriteChar(int c) { + MSG.WriteChar(SV_INIT.sv.multicast, c); + } + + public static void PF_WriteByte(int c) { + MSG.WriteByte(SV_INIT.sv.multicast, c); + } + + public static void PF_WriteShort(int c) { + MSG.WriteShort(SV_INIT.sv.multicast, c); + } + + public static void PF_WriteLong(int c) { + MSG.WriteLong(SV_INIT.sv.multicast, c); + } + + public static void PF_WriteFloat(float f) { + MSG.WriteFloat(SV_INIT.sv.multicast, f); + } + + public static void PF_WriteString(String s) { + MSG.WriteString(SV_INIT.sv.multicast, s); + } + + public static void PF_WritePos(float[] pos) { + MSG.WritePos(SV_INIT.sv.multicast, pos); + } + + public static void PF_WriteDir(float[] dir) { + MSG.WriteDir(SV_INIT.sv.multicast, dir); + } + + public static void PF_WriteAngle(float f) { + MSG.WriteAngle(SV_INIT.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); + + // quake2 bugfix + if (cluster == -1) + return false; + 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); + + // quake2 bugfix + if (cluster == -1) + return false; + 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; + 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() { + GameBase.ShutdownGame(); + } + + /* + * =============== SV_InitGameProgs + * + * Init the game subsystem for a new map =============== + */ + + public static void SV_InitGameProgs() { + + // unload anything we have now + SV_ShutdownGameProgs(); + + game_import_t gimport = new game_import_t(); + + // all functions set in game_export_t (rst) + GameBase.GetGameApi(gimport); + + GameSave.InitGame(); + } +}
\ No newline at end of file diff --git a/src/jake2/server/SV_INIT.java b/src/jake2/server/SV_INIT.java index 2325f22..0c1c5ae 100644 --- a/src/jake2/server/SV_INIT.java +++ b/src/jake2/server/SV_INIT.java @@ -1,493 +1,499 @@ /* -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. - -*/ + * 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.8 2004-08-29 21:39:25 hzi Exp $ - +// $Id: SV_INIT.java,v 1.9 2004-09-22 19:22:12 salomo Exp $ package jake2.server; +import jake2.Defines; import jake2.Globals; import jake2.client.CL; import jake2.client.SCR; import jake2.game.*; import jake2.qcommon.*; import jake2.sys.NET; +import jake2.util.Lib; +import jake2.util.Math3D; import java.io.IOException; import java.io.RandomAccessFile; -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 < GameBase.num_edicts; entnum++) { - //svent = EDICT_NUM(entnum); - svent= GameBase.g_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].set(svent.s); // = svent.s.getClone(); - } - } - - /* - ================= - SV_CheckForSavegame - ================= - */ - public static void SV_CheckForSavegame() { - - String name; - 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; - } - - 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++) - Game.G_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; - - Globals.server_state= 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; - - int iw[]= { checksum }; - - if (serverstate != ss_game) { - sv.models[1]= CM.CM_LoadMap("", false, 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, iw); - } - checksum= iw[0]; - 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; - Globals.server_state= sv.state; - - // load and spawn all other entities - Game.SpawnEntities(sv.name, CM.CM_EntityString(), spawnpoint); - - // run two frames to allow everything to settle - Game.G_RunFrame(); - Game.G_RunFrame(); - - // all precaches are complete - sv.state= serverstate; - Globals.server_state= 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= GameBase.g_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= level.length(); - 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"); - } -} +public class SV_INIT { + + /* + * ================ 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 == Lib.strcmp(sv.configstrings[start + i], name)) + return i; + + if (!create) + return 0; + + if (i == max) + Com.Error(Defines.ERR_DROP, "*Index: overflow"); + + //strncpy (sv.configstrings[start+i], name, + // sizeof(sv.configstrings[i])); + sv.configstrings[start + i] = name; + + if (sv.state != Defines.ss_loading) { // send the update to everyone + SZ.Clear(sv.multicast); + MSG.WriteChar(sv.multicast, Defines.svc_configstring); + MSG.WriteShort(sv.multicast, start + i); + MSG.WriteString(sv.multicast, name); + SV_SEND.SV_Multicast(Globals.vec3_origin, Defines.MULTICAST_ALL_R); + } + + return i; + } + + public static int SV_ModelIndex(String name) { + return SV_FindIndex(name, Defines.CS_MODELS, Defines.MAX_MODELS, true); + } + + public static int SV_SoundIndex(String name) { + return SV_FindIndex(name, Defines.CS_SOUNDS, Defines.MAX_SOUNDS, true); + } + + public static int SV_ImageIndex(String name) { + return SV_FindIndex(name, Defines.CS_IMAGES, Defines.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 < GameBase.num_edicts; entnum++) { + //svent = EDICT_NUM(entnum); + svent = GameBase.g_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 + // + Math3D.VectorCopy(svent.s.origin, svent.s.old_origin); + // rst: bugfix + sv.baselines[entnum].set(svent.s); // = svent.s.getClone(); + } + } + + /* + * ================= SV_CheckForSavegame ================= + */ + public static void SV_CheckForSavegame() { + + String name; + 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; + } + + 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 = Defines.ss_loading; // PGM + for (i = 0; i < 100; i++) + GameBase.G_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 = Defines.ss_dead; + + Globals.server_state = 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[Defines.CS_NAME] = server; + + if (Cvar.VariableValue("deathmatch") != 0) { + sv.configstrings[Defines.CS_AIRACCEL] = "" + + SV_MAIN.sv_airaccelerate.value; + PMove.pm_airaccelerate = SV_MAIN.sv_airaccelerate.value; + } else { + sv.configstrings[Defines.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 > Defines.cs_connected) + svs.clients[i].state = Defines.cs_connected; + svs.clients[i].lastframe = -1; + } + + sv.time = 1000; + + sv.name = server; + sv.configstrings[Defines.CS_NAME] = server; + + int iw[] = { checksum }; + + if (serverstate != Defines.ss_game) { + sv.models[1] = CM.CM_LoadMap("", false, iw); // no real map + } else { + sv.configstrings[Defines.CS_MODELS + 1] = "maps/" + server + ".bsp"; + sv.models[1] = CM.CM_LoadMap( + sv.configstrings[Defines.CS_MODELS + 1], false, iw); + } + checksum = iw[0]; + sv.configstrings[Defines.CS_MAPCHECKSUM] = "" + checksum; + + // + // clear physics interaction links + // + SV_WORLD.SV_ClearWorld(); + + for (i = 1; i < CM.CM_NumInlineModels(); i++) { + sv.configstrings[Defines.CS_MODELS + 1 + i] = "*" + i; + // copy references + sv.models[i + 1] = CM + .InlineModel(sv.configstrings[Defines.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 = Defines.ss_loading; + Globals.server_state = sv.state; + + // load and spawn all other entities + GameSpawn.SpawnEntities(sv.name, CM.CM_EntityString(), spawnpoint); + + // run two frames to allow everything to settle + GameBase.G_RunFrame(); + GameBase.G_RunFrame(); + + // all precaches are complete + sv.state = serverstate; + Globals.server_state = 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, Defines.CVAR_SERVERINFO + | Defines.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", Defines.CVAR_SERVERINFO + | Defines.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 (Globals.dedicated.value != 0) { + if (0 == Cvar.VariableValue("coop")) + Cvar.FullSet("deathmatch", "1", Defines.CVAR_SERVERINFO + | Defines.CVAR_LATCH); + } + + // init clients + if (Cvar.VariableValue("deathmatch") != 0) { + if (SV_MAIN.maxclients.value <= 1) + Cvar.FullSet("maxclients", "8", Defines.CVAR_SERVERINFO + | Defines.CVAR_LATCH); + else if (SV_MAIN.maxclients.value > Defines.MAX_CLIENTS) + Cvar.FullSet("maxclients", "" + Defines.MAX_CLIENTS, + Defines.CVAR_SERVERINFO | Defines.CVAR_LATCH); + } else if (Cvar.VariableValue("coop") != 0) { + if (SV_MAIN.maxclients.value <= 1 || SV_MAIN.maxclients.value > 4) + Cvar.FullSet("maxclients", "4", Defines.CVAR_SERVERINFO + | Defines.CVAR_LATCH); + + } else // non-deathmatch, non-coop is one player + { + Cvar.FullSet("maxclients", "1", Defines.CVAR_SERVERINFO + | Defines.CVAR_LATCH); + } + + svs.spawncount = Lib.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) + * Defines.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:" + Defines.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 = GameBase.g_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 == Defines.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 = level.length(); + if (l > 4 && level.endsWith(".cin")) { + SCR.BeginLoadingPlaque(); // for local system + SV_SEND.SV_BroadcastCommand("changing\n"); + SV_SpawnServer(level, spawnpoint, Defines.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, Defines.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, Defines.ss_pic, attractloop, + loadgame); + } else { + SCR.BeginLoadingPlaque(); // for local system + SV_SEND.SV_BroadcastCommand("changing\n"); + SV_SEND.SV_SendClientMessages(); + SV_SpawnServer(level, spawnpoint, Defines.ss_game, attractloop, + loadgame); + Cbuf.CopyToDefer(); + } + + SV_SEND.SV_BroadcastCommand("reconnect\n"); + } + + public static server_static_t svs = new server_static_t(); // persistant + // server info + + public static server_t sv = new server_t(); // local server +}
\ No newline at end of file diff --git a/src/jake2/server/SV_MAIN.java b/src/jake2/server/SV_MAIN.java index 5800a73..41d5789 100644 --- a/src/jake2/server/SV_MAIN.java +++ b/src/jake2/server/SV_MAIN.java @@ -1,1015 +1,1045 @@ /* -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. - -*/ + * 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.7 2004-09-10 19:02:56 salomo Exp $ - +// $Id: SV_MAIN.java,v 1.8 2004-09-22 19:22:12 salomo Exp $ package jake2.server; import jake2.Defines; import jake2.Globals; -import jake2.game.*; -import jake2.qcommon.*; +import jake2.game.Cmd; +import jake2.game.GameBase; +import jake2.game.Info; +import jake2.game.PlayerClient; +import jake2.game.cvar_t; +import jake2.game.edict_t; +import jake2.qcommon.Com; +import jake2.qcommon.Cvar; +import jake2.qcommon.FS; +import jake2.qcommon.MSG; +import jake2.qcommon.Netchan; +import jake2.qcommon.SZ; +import jake2.qcommon.netadr_t; import jake2.sys.NET; import jake2.sys.Sys; import jake2.util.Lib; import java.io.IOException; -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 - PlayerClient.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() { - 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; - } - } - - // 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= GameBase.g_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 (!(PlayerClient.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 == rcon_password.string.length()) - 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++) { - remaining += Cmd.Argv(i); - 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 < GameBase.num_edicts; i++) { - ent= GameBase.g_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) { - Game.G_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 - PlayerClient.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 (val.length() > 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); - //TODO: set cheats 0 - Cvar.Get("cheats", "1", 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(); - } - - sv= new server_t(); - - Globals.server_state= sv.state; - - if (svs.demofile != null) - try { - svs.demofile.close(); - } - catch (IOException e1) { - e1.printStackTrace(); - } - - svs= new server_static_t(); - } -} +public class SV_MAIN { + + public static netadr_t master_adr[] = new netadr_t[Defines.MAX_MASTERS]; // address + // of + // group + // servers + static { + for (int i = 0; i < Defines.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 + + //============================================================================ + + /* + * ================ 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; + + //============================================================================ + + /* + * ===================== 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 + PlayerClient.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 < SV_MAIN.maxclients.value; i++) { + cl = SV_INIT.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(Defines.NS_SERVER, Globals.net_from, "print\n" + + SV_StatusString()); + } + + /* + * ================ SVC_Ack + * + * ================ + */ + public static void SVC_Ack() { + Com.Printf("Ping acknowledge from " + NET.AdrToString(Globals.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 (SV_MAIN.maxclients.value == 1) + return; // ignore in single player + + version = Lib.atoi(Cmd.Argv(1)); + + if (version != Defines.PROTOCOL_VERSION) + string = SV_MAIN.hostname.string + ": wrong version\n"; + else { + count = 0; + for (i = 0; i < SV_MAIN.maxclients.value; i++) + if (SV_INIT.svs.clients[i].state >= Defines.cs_connected) + count++; + + string = SV_MAIN.hostname.string + " " + SV_INIT.sv.name + " " + + count + "/" + (int) SV_MAIN.maxclients.value + "\n"; + } + + Netchan.OutOfBandPrint(Defines.NS_SERVER, Globals.net_from, "info\n" + + string); + } + + /* + * ================ SVC_Ping + * + * Just responds with an acknowledgement ================ + */ + public static void SVC_Ping() { + Netchan.OutOfBandPrint(Defines.NS_SERVER, Globals.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 < Defines.MAX_CHALLENGES; i++) { + if (NET.NET_CompareBaseAdr(Globals.net_from, + SV_INIT.svs.challenges[i].adr)) + break; + if (SV_INIT.svs.challenges[i].time < oldestTime) { + oldestTime = SV_INIT.svs.challenges[i].time; + oldest = i; + } + } + + if (i == Defines.MAX_CHALLENGES) { + // overwrite the oldest + SV_INIT.svs.challenges[oldest].challenge = Lib.rand() & 0x7fff; + SV_INIT.svs.challenges[oldest].adr = Globals.net_from; + SV_INIT.svs.challenges[oldest].time = (int) Globals.curtime; + i = oldest; + } + + // send it back + Netchan.OutOfBandPrint(Defines.NS_SERVER, Globals.net_from, + "challenge " + SV_INIT.svs.challenges[i].challenge); + } + + /* + * ================== SVC_DirectConnect + * + * A connection request that did not come from the master ================== + */ + public static void SVC_DirectConnect() { + String userinfo; + netadr_t adr; + int i; + client_t cl; + + edict_t ent; + int edictnum; + int version; + int qport; + + adr = Globals.net_from; + + Com.DPrintf("SVC_DirectConnect ()\n"); + + version = Lib.atoi(Cmd.Argv(1)); + if (version != Defines.PROTOCOL_VERSION) { + Netchan.OutOfBandPrint(Defines.NS_SERVER, adr, + "print\nServer is version " + Globals.VERSION + "\n"); + Com.DPrintf(" rejected connect from version " + version + "\n"); + return; + } + + qport = Lib.atoi(Cmd.Argv(2)); + int challenge = Lib.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(Globals.net_from)); + + // attractloop servers are ONLY for local clients + if (SV_INIT.sv.attractloop) { + if (!NET.IsLocalAddress(adr)) { + Com.Printf("Remote connect in attract loop. Ignored.\n"); + Netchan.OutOfBandPrint(Defines.NS_SERVER, adr, + "print\nConnection refused.\n"); + return; + } + } + + // see if the challenge is valid + if (!NET.IsLocalAddress(adr)) { + for (i = 0; i < Defines.MAX_CHALLENGES; i++) { + if (NET.NET_CompareBaseAdr(Globals.net_from, + SV_INIT.svs.challenges[i].adr)) { + if (challenge == SV_INIT.svs.challenges[i].challenge) + break; // good + Netchan.OutOfBandPrint(Defines.NS_SERVER, adr, + "print\nBad challenge.\n"); + return; + } + } + if (i == Defines.MAX_CHALLENGES) { + Netchan.OutOfBandPrint(Defines.NS_SERVER, adr, + "print\nNo challenge for address.\n"); + return; + } + } + + // if there is already a slot for this ip, reuse it + for (i = 0; i < SV_MAIN.maxclients.value; i++) { + cl = SV_INIT.svs.clients[i]; + + if (cl.state == Defines.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) + && (SV_INIT.svs.realtime - cl.lastconnect) < ((int) SV_MAIN.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 < SV_MAIN.maxclients.value; i++) { + cl = SV_INIT.svs.clients[i]; + if (cl.state == Defines.cs_free) { + index = i; + break; + } + } + if (index == -1) { + Netchan.OutOfBandPrint(Defines.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_MAIN.sv_client = SV_INIT.svs.clients[i]; + //edictnum = (newcl-svs.clients)+1; + int edictnum = i + 1; + edict_t ent = GameBase.g_edicts[edictnum]; + SV_INIT.svs.clients[i].edict = ent; + SV_INIT.svs.clients[i].challenge = challenge; // save challenge for + // checksumming + + // get the game a chance to reject this connection or modify the + // userinfo + if (!(PlayerClient.ClientConnect(ent, userinfo))) { + if (Info.Info_ValueForKey(userinfo, "rejmsg") != null) + Netchan.OutOfBandPrint(Defines.NS_SERVER, adr, "print\n" + + Info.Info_ValueForKey(userinfo, "rejmsg") + + "\nConnection refused.\n"); + else + Netchan.OutOfBandPrint(Defines.NS_SERVER, adr, + "print\nConnection refused.\n"); + Com.DPrintf("Game rejected a connection.\n"); + return; + } + + // parse some info from the info strings + SV_INIT.svs.clients[i].userinfo = userinfo; + SV_UserinfoChanged(SV_INIT.svs.clients[i]); + + // send the connect packet to the client + Netchan.OutOfBandPrint(Defines.NS_SERVER, adr, "client_connect"); + + Netchan.Setup(Defines.NS_SERVER, SV_INIT.svs.clients[i].netchan, adr, + qport); + + SV_INIT.svs.clients[i].state = Defines.cs_connected; + + SZ.Init(SV_INIT.svs.clients[i].datagram, + SV_INIT.svs.clients[i].datagram_buf, + SV_INIT.svs.clients[i].datagram_buf.length); + SV_INIT.svs.clients[i].datagram.allowoverflow = true; + SV_INIT.svs.clients[i].lastmessage = SV_INIT.svs.realtime; // don't + // timeout + SV_INIT.svs.clients[i].lastconnect = SV_INIT.svs.realtime; + Com.DPrintf("new client added.\n"); + } + + public static int Rcon_Validate() { + if (0 == SV_MAIN.rcon_password.string.length()) + return 0; + + if (0 != Lib.strcmp(Cmd.Argv(1), SV_MAIN.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(Globals.net_message.data, 4, -1); + + if (i == 0) + Com.Printf("Bad rcon from " + NET.AdrToString(Globals.net_from) + + ":\n" + msg + "\n"); + else + Com.Printf("Rcon from " + NET.AdrToString(Globals.net_from) + ":\n" + + msg + "\n"); + + Com.BeginRedirect(Defines.RD_PACKET, SV_SEND.sv_outputbuf, + Defines.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++) { + remaining += Cmd.Argv(i); + 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(Globals.net_message); + MSG.ReadLong(Globals.net_message); // skip the -1 marker + + s = MSG.ReadStringLine(Globals.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 == Lib.strcmp(c, "ping")) + SVC_Ping(); + else if (0 == Lib.strcmp(c, "ack")) + SVC_Ack(); + else if (0 == Lib.strcmp(c, "status")) + SVC_Status(); + else if (0 == Lib.strcmp(c, "info")) + SVC_Info(); + else if (0 == Lib.strcmp(c, "getchallenge")) + SVC_GetChallenge(); + else if (0 == Lib.strcmp(c, "connect")) + SVC_DirectConnect(); + else if (0 == Lib.strcmp(c, "rcon")) + SVC_RemoteCommand(); + else { + Com.Printf("bad connectionless packet from " + + NET.AdrToString(Globals.net_from) + "\n"); + Com.Printf("[" + s + "]\n"); + Com.Printf("" + Lib.hexDump(Globals.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 < SV_MAIN.maxclients.value; i++) { + cl = SV_INIT.svs.clients[i]; + if (cl.state != Defines.cs_spawned) + continue; + + total = 0; + count = 0; + for (j = 0; j < Defines.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_INIT.sv.framenum & 15) != 0) + return; + + for (i = 0; i < SV_MAIN.maxclients.value; i++) { + cl = SV_INIT.svs.clients[i]; + if (cl.state == Defines.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(Defines.NS_SERVER, Globals.net_from, + Globals.net_message)) { + + // check for connectionless packet (0xffffffff) first + if ((Globals.net_message.data[0] == -1) + && (Globals.net_message.data[1] == -1) + && (Globals.net_message.data[2] == -1) + && (Globals.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(Globals.net_message); + MSG.ReadLong(Globals.net_message); // sequence number + MSG.ReadLong(Globals.net_message); // sequence number + qport = MSG.ReadShort(Globals.net_message) & 0xffff; + + // check for packets from connected clients + for (i = 0; i < SV_MAIN.maxclients.value; i++) { + cl = SV_INIT.svs.clients[i]; + if (cl.state == Defines.cs_free) + continue; + if (!NET.NET_CompareBaseAdr(Globals.net_from, + cl.netchan.remote_address)) + continue; + if (cl.netchan.qport != qport) + continue; + if (cl.netchan.remote_address.port != Globals.net_from.port) { + Com.Printf("SV_ReadPackets: fixing up a translated port\n"); + cl.netchan.remote_address.port = Globals.net_from.port; + } + + if (Netchan.Process(cl.netchan, Globals.net_message)) { + // this is a valid, sequenced packet, so process it + if (cl.state != Defines.cs_zombie) { + cl.lastmessage = SV_INIT.svs.realtime; // don't timeout + SV_USER.SV_ExecuteClientMessage(cl); + } + } + break; + } + + if (i != SV_MAIN.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) (SV_INIT.svs.realtime - 1000 * SV_MAIN.timeout.value); + zombiepoint = (int) (SV_INIT.svs.realtime - 1000 * SV_MAIN.zombietime.value); + + for (i = 0; i < SV_MAIN.maxclients.value; i++) { + cl = SV_INIT.svs.clients[i]; + // message times may be wrong across a changelevel + if (cl.lastmessage > SV_INIT.svs.realtime) + cl.lastmessage = SV_INIT.svs.realtime; + + if (cl.state == Defines.cs_zombie && cl.lastmessage < zombiepoint) { + cl.state = Defines.cs_free; // can now be reused + continue; + } + if ((cl.state == Defines.cs_connected || cl.state == Defines.cs_spawned) + && cl.lastmessage < droppoint) { + SV_SEND.SV_BroadcastPrintf(Defines.PRINT_HIGH, cl.name + + " timed out\n"); + SV_DropClient(cl); + cl.state = Defines.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 < GameBase.num_edicts; i++) { + ent = GameBase.g_edicts[i]; + // events only last for a single message + ent.s.event = 0; + } + + } + + /* + * ================= SV_RunGameFrame ================= + */ + public static void SV_RunGameFrame() { + if (Globals.host_speeds.value != 0) + Globals.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_INIT.sv.framenum++; + SV_INIT.sv.time = SV_INIT.sv.framenum * 100; + + // don't run if paused + if (0 == SV_MAIN.sv_paused.value || SV_MAIN.maxclients.value > 1) { + GameBase.G_RunFrame(); + + // never get more than one tic behind + if (SV_INIT.sv.time < SV_INIT.svs.realtime) { + if (SV_MAIN.sv_showclamp.value != 0) + Com.Printf("sv highclamp\n"); + SV_INIT.svs.realtime = SV_INIT.sv.time; + } + } + + if (Globals.host_speeds.value != 0) + Globals.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 (!SV_INIT.svs.initialized) + return; + + SV_INIT.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_MAIN.sv_timedemo.value + && SV_INIT.svs.realtime < SV_INIT.sv.time) { + // never let the time get too far off + if (SV_INIT.sv.time - SV_INIT.svs.realtime > 100) { + if (SV_MAIN.sv_showclamp.value != 0) + Com.Printf("sv lowclamp\n"); + SV_INIT.svs.realtime = SV_INIT.sv.time - 100; + } + NET.NET_Sleep(SV_INIT.sv.time - SV_INIT.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(); + + } + + public static void Master_Heartbeat() { + String string; + int i; + + // pgm post3.19 change, cvar pointer not validated before dereferencing + if (Globals.dedicated == null || 0 == Globals.dedicated.value) + return; // only dedicated servers send heartbeats + + // pgm post3.19 change, cvar pointer not validated before dereferencing + if (null == SV_MAIN.public_server || 0 == SV_MAIN.public_server.value) + return; // a private dedicated game + + // check for time wraparound + if (SV_INIT.svs.last_heartbeat > SV_INIT.svs.realtime) + SV_INIT.svs.last_heartbeat = SV_INIT.svs.realtime; + + if (SV_INIT.svs.realtime - SV_INIT.svs.last_heartbeat < SV_MAIN.HEARTBEAT_SECONDS * 1000) + return; // not time to send yet + + SV_INIT.svs.last_heartbeat = SV_INIT.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 < Defines.MAX_MASTERS; i++) + if (SV_MAIN.master_adr[i].port != 0) { + Com.Printf("Sending heartbeat to " + + NET.AdrToString(SV_MAIN.master_adr[i]) + "\n"); + Netchan.OutOfBandPrint(Defines.NS_SERVER, + SV_MAIN.master_adr[i], "heartbeat\n" + string); + } + } + + /* + * ================= Master_Shutdown + * + * Informs all masters that this server is going down ================= + */ + public static void Master_Shutdown() { + int i; + + // pgm post3.19 change, cvar pointer not validated before dereferencing + if (null == Globals.dedicated || 0 == Globals.dedicated.value) + return; // only dedicated servers send heartbeats + + // pgm post3.19 change, cvar pointer not validated before dereferencing + if (null == SV_MAIN.public_server || 0 == SV_MAIN.public_server.value) + return; // a private dedicated game + + // send to group master + for (i = 0; i < Defines.MAX_MASTERS; i++) + if (SV_MAIN.master_adr[i].port != 0) { + if (i > 0) + Com.Printf("Sending heartbeat to " + + NET.AdrToString(SV_MAIN.master_adr[i]) + "\n"); + Netchan.OutOfBandPrint(Defines.NS_SERVER, + SV_MAIN.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 + PlayerClient.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 = Lib.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 (val.length() > 0) { + cl.messagelevel = Lib.atoi(val); + } + + } + + //============================================================================ + + /* + * =============== SV_Init + * + * Only called at quake2.exe startup, not for each game =============== + */ + public static void SV_Init() { + SV_CCMDS.SV_InitOperatorCommands(); //ok. + + SV_MAIN.rcon_password = Cvar.Get("rcon_password", "", 0); + Cvar.Get("skill", "1", 0); + Cvar.Get("deathmatch", "0", Defines.CVAR_LATCH); + Cvar.Get("coop", "0", Defines.CVAR_LATCH); + Cvar.Get("dmflags", "" + Defines.DF_INSTANT_ITEMS, + Defines.CVAR_SERVERINFO); + Cvar.Get("fraglimit", "0", Defines.CVAR_SERVERINFO); + Cvar.Get("timelimit", "0", Defines.CVAR_SERVERINFO); + //TODO: set cheats 0 + Cvar.Get("cheats", "1", Defines.CVAR_SERVERINFO | Defines.CVAR_LATCH); + Cvar.Get("protocol", "" + Defines.PROTOCOL_VERSION, + Defines.CVAR_SERVERINFO | Defines.CVAR_NOSET); + + SV_MAIN.maxclients = Cvar.Get("maxclients", "1", + Defines.CVAR_SERVERINFO | Defines.CVAR_LATCH); + SV_MAIN.hostname = Cvar.Get("hostname", "noname", + Defines.CVAR_SERVERINFO | Defines.CVAR_ARCHIVE); + SV_MAIN.timeout = Cvar.Get("timeout", "125", 0); + SV_MAIN.zombietime = Cvar.Get("zombietime", "2", 0); + SV_MAIN.sv_showclamp = Cvar.Get("showclamp", "0", 0); + SV_MAIN.sv_paused = Cvar.Get("paused", "0", 0); + SV_MAIN.sv_timedemo = Cvar.Get("timedemo", "0", 0); + SV_MAIN.sv_enforcetime = Cvar.Get("sv_enforcetime", "0", 0); + + // TODO: carsten, re-allow downloads per default + SV_MAIN.allow_download = Cvar.Get("allow_download", "0", + Defines.CVAR_ARCHIVE); + SV_MAIN.allow_download_players = Cvar.Get("allow_download_players", + "0", Defines.CVAR_ARCHIVE); + SV_MAIN.allow_download_models = Cvar.Get("allow_download_models", "1", + Defines.CVAR_ARCHIVE); + SV_MAIN.allow_download_sounds = Cvar.Get("allow_download_sounds", "1", + Defines.CVAR_ARCHIVE); + SV_MAIN.allow_download_maps = Cvar.Get("allow_download_maps", "1", + Defines.CVAR_ARCHIVE); + + SV_MAIN.sv_noreload = Cvar.Get("sv_noreload", "0", 0); + SV_MAIN.sv_airaccelerate = Cvar.Get("sv_airaccelerate", "0", + Defines.CVAR_LATCH); + SV_MAIN.public_server = Cvar.Get("public", "0", 0); + SV_MAIN.sv_reconnect_limit = Cvar.Get("sv_reconnect_limit", "3", + Defines.CVAR_ARCHIVE); + + SZ.Init(Globals.net_message, Globals.net_message_buffer, + Globals.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(Globals.net_message); + MSG.WriteByte(Globals.net_message, Defines.svc_print); + MSG.WriteByte(Globals.net_message, Defines.PRINT_HIGH); + MSG.WriteString(Globals.net_message, message); + + if (reconnect) + MSG.WriteByte(Globals.net_message, Defines.svc_reconnect); + else + MSG.WriteByte(Globals.net_message, Defines.svc_disconnect); + + // send it twice + // stagger the packets to crutch operating system limited buffers + + for (i = 0; i < SV_MAIN.maxclients.value; i++) { + cl = SV_INIT.svs.clients[i]; + if (cl.state >= Defines.cs_connected) + Netchan.Transmit(cl.netchan, Globals.net_message.cursize, + Globals.net_message.data); + } + for (i = 0; i < SV_MAIN.maxclients.value; i++) { + cl = SV_INIT.svs.clients[i]; + if (cl.state >= Defines.cs_connected) + Netchan.Transmit(cl.netchan, Globals.net_message.cursize, + Globals.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 (SV_INIT.svs.clients != null) + SV_FinalMessage(finalmsg, reconnect); + + Master_Shutdown(); + + SV_GAME.SV_ShutdownGameProgs(); + + // free current level + if (SV_INIT.sv.demofile != null) + try { + SV_INIT.sv.demofile.close(); + } catch (IOException e) { + e.printStackTrace(); + } + + SV_INIT.sv = new server_t(); + + Globals.server_state = SV_INIT.sv.state; + + if (SV_INIT.svs.demofile != null) + try { + SV_INIT.svs.demofile.close(); + } catch (IOException e1) { + e1.printStackTrace(); + } + + SV_INIT.svs = new server_static_t(); + } +}
\ No newline at end of file diff --git a/src/jake2/server/SV_SEND.java b/src/jake2/server/SV_SEND.java index 9b30a99..8488954 100644 --- a/src/jake2/server/SV_SEND.java +++ b/src/jake2/server/SV_SEND.java @@ -19,7 +19,7 @@ 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.5 2004-09-10 19:02:56 salomo Exp $ +// $Id: SV_SEND.java,v 1.6 2004-09-22 19:22:12 salomo Exp $ package jake2.server; @@ -30,8 +30,9 @@ import jake2.client.*; import jake2.game.*; import jake2.qcommon.*; import jake2.render.*; +import jake2.util.Math3D; -public class SV_SEND extends SV_MAIN { +public class SV_SEND { /* ============================================================================= @@ -40,20 +41,19 @@ public class SV_SEND extends SV_MAIN { ============================================================================= */ - public static byte sv_outputbuf[]= new byte[SV_OUTPUTBUF_LENGTH]; + public static byte sv_outputbuf[] = new byte[Defines.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()); + if (sv_redirected == Defines.RD_PACKET) { + String s = ("print\n" + outputbuf); + Netchan.Netchan_OutOfBand(Defines.NS_SERVER, Globals.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); + else if (sv_redirected == Defines.RD_CLIENT) { + MSG.WriteByte(SV_MAIN.sv_client.netchan.message, Defines.svc_print); + MSG.WriteByte(SV_MAIN.sv_client.netchan.message, Defines.PRINT_HIGH); MSG.WriteString(SV_MAIN.sv_client.netchan.message, outputbuf); - } + } } - /* ============================================================================= @@ -74,11 +74,10 @@ public class SV_SEND extends SV_MAIN { if (level < cl.messagelevel) return; - MSG.WriteByte(cl.netchan.message, svc_print); + MSG.WriteByte(cl.netchan.message, Defines.svc_print); MSG.WriteByte(cl.netchan.message, level); MSG.WriteString(cl.netchan.message, s); } - /* ================= SV_BroadcastPrintf @@ -91,23 +90,22 @@ public class SV_SEND extends SV_MAIN { client_t cl; // echo to console - if (dedicated.value != 0) { + if (Globals.dedicated.value != 0) { Com.Printf(s); } - for (int i= 0; i < SV_MAIN.maxclients.value; i++) { - cl= SV_MAIN.svs.clients[i]; + for (int i = 0; i < SV_MAIN.maxclients.value; i++) { + cl = SV_INIT.svs.clients[i]; if (level < cl.messagelevel) continue; - if (cl.state != cs_spawned) + if (cl.state != Defines.cs_spawned) continue; - MSG.WriteByte(cl.netchan.message, svc_print); + MSG.WriteByte(cl.netchan.message, Defines.svc_print); MSG.WriteByte(cl.netchan.message, level); MSG.WriteString(cl.netchan.message, s); } } - /* ================= SV_BroadcastCommand @@ -117,14 +115,13 @@ public class SV_SEND extends SV_MAIN { */ public static void SV_BroadcastCommand(String s) { - if (sv.state == 0) + if (SV_INIT.sv.state == 0) return; - MSG.WriteByte(sv.multicast, svc_stufftext); - MSG.WriteString(sv.multicast, s); - SV_Multicast(null, MULTICAST_ALL_R); + MSG.WriteByte(SV_INIT.sv.multicast, Defines.svc_stufftext); + MSG.WriteString(SV_INIT.sv.multicast, s); + SV_Multicast(null, Defines.MULTICAST_ALL_R); } - /* ================= SV_Multicast @@ -145,63 +142,63 @@ public class SV_SEND extends SV_MAIN { boolean reliable; int area1, area2; - reliable= false; + reliable = false; - if (to != MULTICAST_ALL_R && to != MULTICAST_ALL) { - leafnum= CM.CM_PointLeafnum(origin); - area1= CM.CM_LeafArea(leafnum); + if (to != Defines.MULTICAST_ALL_R && to != Defines.MULTICAST_ALL) { + leafnum = CM.CM_PointLeafnum(origin); + area1 = CM.CM_LeafArea(leafnum); } else { - leafnum= 0; // just to avoid compiler warnings - area1= 0; + 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); + if (SV_INIT.svs.demofile != null) + SZ.Write(SV_INIT.svs.demo_multicast, SV_INIT.sv.multicast.data, SV_INIT.sv.multicast.cursize); switch (to) { - case MULTICAST_ALL_R : - reliable= true; // intentional fallthrough, no break here - case MULTICAST_ALL : - leafnum= 0; - mask= null; + case Defines.MULTICAST_ALL_R : + reliable = true; // intentional fallthrough, no break here + case Defines.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); + case Defines.MULTICAST_PHS_R : + reliable = true; // intentional fallthrough + case Defines.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); + case Defines.MULTICAST_PVS_R : + reliable = true; // intentional fallthrough + case Defines.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"); + mask = null; + Com.Error(Defines.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]; + for (j = 0; j < SV_MAIN.maxclients.value; j++) { + client = SV_INIT.svs.clients[j]; - if (client.state == cs_free || client.state == cs_zombie) + if (client.state == Defines.cs_free || client.state == Defines.cs_zombie) continue; - if (client.state != cs_spawned && !reliable) + if (client.state != Defines.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); + 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; @@ -213,14 +210,13 @@ public class SV_SEND extends SV_MAIN { } if (reliable) - SZ.Write(client.netchan.message, sv.multicast.data, sv.multicast.cursize); + 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.Write(client.datagram, SV_INIT.sv.multicast.data, SV_INIT.sv.multicast.cursize); } - SZ.Clear(sv.multicast); + SZ.Clear(SV_INIT.sv.multicast); } - /* ================== SV_StartSound @@ -259,98 +255,97 @@ public class SV_SEND extends SV_MAIN { int flags; int i; int ent; - float[] origin_v= { 0, 0, 0 }; + float[] origin_v = { 0, 0, 0 }; boolean use_phs; if (volume < 0 || volume > 1.0) - Com.Error(ERR_FATAL, "SV_StartSound: volume = " + volume); + Com.Error(Defines.ERR_FATAL, "SV_StartSound: volume = " + volume); if (attenuation < 0 || attenuation > 4) - Com.Error(ERR_FATAL, "SV_StartSound: attenuation = " + attenuation); + Com.Error(Defines.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); + Com.Error(Defines.ERR_FATAL, "SV_StartSound: timeofs = " + timeofs); - ent= entity.index; + ent = entity.index; // no PHS flag if ((channel & 8) != 0) { - use_phs= false; + use_phs = false; channel &= 7; } else - use_phs= true; + use_phs = true; - sendchan= (ent << 3) | (channel & 7); + sendchan = (ent << 3) | (channel & 7); - flags= 0; + flags = 0; if (volume != Defines.DEFAULT_SOUND_PACKET_VOLUME) - flags |= SND_VOLUME; - if (attenuation != DEFAULT_SOUND_PACKET_ATTENUATION) - flags |= SND_ATTENUATION; + flags |= Defines.SND_VOLUME; + if (attenuation != Defines.DEFAULT_SOUND_PACKET_ATTENUATION) + flags |= Defines.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; + if ((entity.svflags & Defines.SVF_NOCLIENT) != 0 || (entity.solid == Defines.SOLID_BSP) || origin != null) + flags |= Defines.SND_POS; // always send the entity number for channel overrides - flags |= SND_ENT; + flags |= Defines.SND_ENT; if (timeofs != 0) - flags |= SND_OFFSET; + flags |= Defines.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]); + origin = origin_v; + if (entity.solid == Defines.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); + Math3D.VectorCopy(entity.s.origin, origin_v); } } - MSG.WriteByte(sv.multicast, svc_sound); - MSG.WriteByte(sv.multicast, flags); - MSG.WriteByte(sv.multicast, soundindex); + MSG.WriteByte(SV_INIT.sv.multicast, Defines.svc_sound); + MSG.WriteByte(SV_INIT.sv.multicast, flags); + MSG.WriteByte(SV_INIT.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 & Defines.SND_VOLUME) != 0) + MSG.WriteByte(SV_INIT.sv.multicast, volume * 255); + if ((flags & Defines.SND_ATTENUATION) != 0) + MSG.WriteByte(SV_INIT.sv.multicast, attenuation * 64); + if ((flags & Defines.SND_OFFSET) != 0) + MSG.WriteByte(SV_INIT.sv.multicast, timeofs * 1000); - if ((flags & SND_ENT) != 0) - MSG.WriteShort(sv.multicast, sendchan); + if ((flags & Defines.SND_ENT) != 0) + MSG.WriteShort(SV_INIT.sv.multicast, sendchan); - if ((flags & SND_POS) != 0) - MSG.WritePos(sv.multicast, origin); + if ((flags & Defines.SND_POS) != 0) + MSG.WritePos(SV_INIT.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 (attenuation == Defines.ATTN_NONE) + use_phs = false; - if ((channel & CHAN_RELIABLE) != 0) { + if ((channel & Defines.CHAN_RELIABLE) != 0) { if (use_phs) - SV_Multicast(origin, MULTICAST_PHS_R); + SV_Multicast(origin, Defines.MULTICAST_PHS_R); else - SV_Multicast(origin, MULTICAST_ALL_R); + SV_Multicast(origin, Defines.MULTICAST_ALL_R); } else { if (use_phs) - SV_Multicast(origin, MULTICAST_PHS); + SV_Multicast(origin, Defines.MULTICAST_PHS); else - SV_Multicast(origin, MULTICAST_ALL); + SV_Multicast(origin, Defines.MULTICAST_ALL); } } - /* =============================================================================== @@ -365,17 +360,17 @@ public class SV_SEND extends SV_MAIN { ======================= */ public static boolean SV_SendClientDatagram(client_t client) { - byte msg_buf[]= new byte[MAX_MSGLEN]; - sizebuf_t msg= new sizebuf_t(); + byte msg_buf[] = new byte[Defines.MAX_MSGLEN]; + sizebuf_t msg = new sizebuf_t(); SV_ENTS.SV_BuildClientFrame(client); SZ.Init(msg, msg_buf, msg_buf.length); - msg.allowoverflow= true; + msg.allowoverflow = true; // send over all the relevant entity_state_t // and the player_state_t - SV_CCMDS.SV_WriteFrameToClient(client, msg); + SV_ENTS.SV_WriteFrameToClient(client, msg); // copy the accumulated multicast datagram // for this client out to the message @@ -396,29 +391,27 @@ public class SV_SEND extends SV_MAIN { Netchan.Transmit(client.netchan, msg.cursize, msg.data); // record the size for rate estimation - client.message_size[sv.framenum % RATE_MESSAGES]= msg.cursize; + client.message_size[SV_INIT.sv.framenum % Defines.RATE_MESSAGES] = msg.cursize; return true; } - /* ================== SV_DemoCompleted ================== */ public static void SV_DemoCompleted() { - if (sv.demofile != null) { + if (SV_INIT.sv.demofile != null) { try { - sv.demofile.close(); + SV_INIT.sv.demofile.close(); } catch (IOException e) { Com.Printf("IOError closing d9emo fiele:" + e); } - sv.demofile= null; + SV_INIT.sv.demofile = null; } - SV_ENTS.SV_Nextserver(); + SV_USER.SV_Nextserver(); } - /* ======================= SV_RateDrop @@ -432,24 +425,23 @@ public class SV_SEND extends SV_MAIN { int i; // never drop over the loopback - if (c.netchan.remote_address.type == NA_LOOPBACK) + if (c.netchan.remote_address.type == Defines.NA_LOOPBACK) return false; - total= 0; + total = 0; - for (i= 0; i < RATE_MESSAGES; i++) { + for (i = 0; i < Defines.RATE_MESSAGES; i++) { total += c.message_size[i]; } if (total > c.rate) { c.surpressCount++; - c.message_size[sv.framenum % RATE_MESSAGES]= 0; + c.message_size[SV_INIT.sv.framenum % Defines.RATE_MESSAGES] = 0; return true; } return false; } - /* ======================= SV_SendClientMessages @@ -459,20 +451,20 @@ public class SV_SEND extends SV_MAIN { int i; client_t c; int msglen; - byte msgbuf[]= new byte[MAX_MSGLEN]; + byte msgbuf[] = new byte[Defines.MAX_MSGLEN]; int r; - msglen= 0; + msglen = 0; // read the next demo message if needed - if (sv.state == ss_demo && sv.demofile != null) { - if (sv_paused.value != 0) - msglen= 0; + if (SV_INIT.sv.state == Defines.ss_demo && SV_INIT.sv.demofile != null) { + if (SV_MAIN.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()); + msglen = EndianHandler.swapInt(SV_INIT.sv.demofile.readInt()); } catch (Exception e) { SV_DemoCompleted(); @@ -484,13 +476,13 @@ public class SV_SEND extends SV_MAIN { SV_DemoCompleted(); return; } - if (msglen > MAX_MSGLEN) - Com.Error(ERR_DROP, "SV_SendClientMessages: msglen > MAX_MSGLEN"); + if (msglen > Defines.MAX_MSGLEN) + Com.Error(Defines.ERR_DROP, "SV_SendClientMessages: msglen > MAX_MSGLEN"); //r = fread (msgbuf, msglen, 1, sv.demofile); - r= 0; + r = 0; try { - r= sv.demofile.read(msgbuf, 0, msglen); + r = SV_INIT.sv.demofile.read(msgbuf, 0, msglen); } catch (IOException e1) { Com.Printf("IOError: reading demo file, " + e1); @@ -503,8 +495,8 @@ public class SV_SEND extends SV_MAIN { } // send a message to each connected client - for (i= 0; i < maxclients.value; i++) { - c= svs.clients[i]; + for (i = 0; i < SV_MAIN.maxclients.value; i++) { + c = SV_INIT.svs.clients[i]; if (c.state == 0) continue; @@ -513,13 +505,15 @@ public class SV_SEND extends SV_MAIN { 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); + SV_BroadcastPrintf(Defines.PRINT_HIGH, c.name + " overflowed\n"); + SV_MAIN.SV_DropClient(c); } - if (sv.state == ss_cinematic || sv.state == ss_demo || sv.state == ss_pic) + if (SV_INIT.sv.state == Defines.ss_cinematic + || SV_INIT.sv.state == Defines.ss_demo + || SV_INIT.sv.state == Defines.ss_pic) Netchan.Transmit(c.netchan, msglen, msgbuf); - else if (c.state == cs_spawned) { + else if (c.state == Defines.cs_spawned) { // don't overrun bandwidth if (SV_RateDrop(c)) continue; diff --git a/src/jake2/server/SV_USER.java b/src/jake2/server/SV_USER.java index 5a31de8..c03cacf 100644 --- a/src/jake2/server/SV_USER.java +++ b/src/jake2/server/SV_USER.java @@ -1,735 +1,691 @@ /* -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. - -*/ + * 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.5 2004-08-29 21:39:25 hzi Exp $ - +// $Id: SV_USER.java,v 1.6 2004-09-22 19:22:12 salomo Exp $ package jake2.server; -import jake2.game.*; -import jake2.qcommon.*; +import jake2.Defines; +import jake2.Globals; +import jake2.game.Cmd; +import jake2.game.GameAI; +import jake2.game.GameBase; +import jake2.game.Info; +import jake2.game.PlayerClient; +import jake2.game.edict_t; +import jake2.game.entity_state_t; +import jake2.game.usercmd_t; +import jake2.qcommon.Cbuf; +import jake2.qcommon.Com; +import jake2.qcommon.Cvar; +import jake2.qcommon.FS; +import jake2.qcommon.MSG; +import jake2.qcommon.SZ; 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= GameBase.g_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 - PlayerClient.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 - || (name.startsWith("players/") && 0 == allow_download_players.value) // now models - || (name.startsWith("models/") && 0 == allow_download_models.value) // now sounds - || (name.startsWith("sound/") - && 0 == allow_download_sounds.value) // now maps (note special case for maps, must not be in pak) - || (name.startsWith("maps/") && 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 - || (name.startsWith("maps/") && 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); - - int i= 0; - for (; i < ucmds.length; i++) - { - u= ucmds[i]; - if (0 == strcmp(Cmd.Argv(0), u.name)) - { - u.r.run(); - break; - } - } - - if (i == ucmds.length && sv.state == ss_game) - PlayerClient.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; - } - - PlayerClient.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= - Com.BlockSequenceCRCByte( - net_message.data, checksumIndex + 1, - net_message.readcount - checksumIndex - 1, - cl.netchan.incoming_sequence); - - if ((calculatedChecksum &0xff) != 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; - } - } - } -} +public class SV_USER { + + static edict_t sv_player; + + 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_USER.SV_New_f(); + } + }); + + static ucmd_t ucmds[] = { + // auto issued + new ucmd_t("new", new Runnable() { + public void run() { + SV_USER.SV_New_f(); + } + }), new ucmd_t("configstrings", new Runnable() { + public void run() { + SV_USER.SV_Configstrings_f(); + } + }), new ucmd_t("baselines", new Runnable() { + public void run() { + SV_USER.SV_Baselines_f(); + } + }), new ucmd_t("begin", new Runnable() { + public void run() { + SV_USER.SV_Begin_f(); + } + }), new ucmd_t("nextserver", new Runnable() { + public void run() { + SV_USER.SV_Nextserver_f(); + } + }), new ucmd_t("disconnect", new Runnable() { + public void run() { + SV_USER.SV_Disconnect_f(); + } + }), + + // issued by hand at client consoles + new ucmd_t("info", new Runnable() { + public void run() { + SV_USER.SV_ShowServerinfo_f(); + } + }), new ucmd_t("download", new Runnable() { + public void run() { + SV_USER.SV_BeginDownload_f(); + } + }), new ucmd_t("nextdl", new Runnable() { + public void run() { + SV_USER.SV_NextDownload_f(); + } + }) }; + + public static final int MAX_STRINGCMDS = 8; + + /* + * ============================================================ + * + * USER STRINGCMD EXECUTION + * + * sv_client and sv_player will be valid. + * ============================================================ + */ + + /* + * ================== SV_BeginDemoServer ================== + */ + public static void SV_BeginDemoserver() { + String name; + + name = "demos/" + SV_INIT.sv.name; + try { + SV_INIT.sv.demofile = FS.FOpenFile(name); + } catch (IOException e) { + Com.Error(Defines.ERR_DROP, "Couldn't open " + name + "\n"); + } + if (SV_INIT.sv.demofile == null) + Com.Error(Defines.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_MAIN.sv_client.name + "\n"); + + if (SV_MAIN.sv_client.state != Defines.cs_connected) { + Com.Printf("New not valid -- already spawned\n"); + return; + } + + // demo servers just dump the file message + if (SV_INIT.sv.state == Defines.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_MAIN.sv_client.netchan.message, + Defines.svc_serverdata); + MSG.WriteInt(SV_MAIN.sv_client.netchan.message, + Defines.PROTOCOL_VERSION); + MSG + .WriteLong(SV_MAIN.sv_client.netchan.message, + SV_INIT.svs.spawncount); + MSG.WriteByte(SV_MAIN.sv_client.netchan.message, + SV_INIT.sv.attractloop ? 1 : 0); + MSG.WriteString(SV_MAIN.sv_client.netchan.message, gamedir); + + if (SV_INIT.sv.state == Defines.ss_cinematic + || SV_INIT.sv.state == Defines.ss_pic) + playernum = -1; + else + //playernum = sv_client - svs.clients; + playernum = SV_MAIN.sv_client.serverindex; + + MSG.WriteShort(SV_MAIN.sv_client.netchan.message, playernum); + + // send full levelname + MSG.WriteString(SV_MAIN.sv_client.netchan.message, + SV_INIT.sv.configstrings[Defines.CS_NAME]); + + // + // game server + // + if (SV_INIT.sv.state == Defines.ss_game) { + // set up the entity for the client + ent = GameBase.g_edicts[playernum + 1]; + ent.s.number = playernum + 1; + SV_MAIN.sv_client.edict = ent; + SV_MAIN.sv_client.lastcmd = new usercmd_t(); + + // begin fetching configstrings + MSG.WriteByte(SV_MAIN.sv_client.netchan.message, + Defines.svc_stufftext); + MSG.WriteString(SV_MAIN.sv_client.netchan.message, + "cmd configstrings " + SV_INIT.svs.spawncount + " 0\n"); + } + + } + + /* + * ================== SV_Configstrings_f ================== + */ + public static void SV_Configstrings_f() { + int start; + + Com.DPrintf("Configstrings() from " + SV_MAIN.sv_client.name + "\n"); + + if (SV_MAIN.sv_client.state != Defines.cs_connected) { + Com.Printf("configstrings not valid -- already spawned\n"); + return; + } + + // handle the case of a level changing while a client was connecting + if (Lib.atoi(Cmd.Argv(1)) != SV_INIT.svs.spawncount) { + Com.Printf("SV_Configstrings_f from different level\n"); + SV_New_f(); + return; + } + + start = Lib.atoi(Cmd.Argv(2)); + + // write a packet full of data + + while (SV_MAIN.sv_client.netchan.message.cursize < Defines.MAX_MSGLEN / 2 + && start < Defines.MAX_CONFIGSTRINGS) { + if (SV_INIT.sv.configstrings[start] != null + && SV_INIT.sv.configstrings[start].length() != 0) { + MSG.WriteByte(SV_MAIN.sv_client.netchan.message, + Defines.svc_configstring); + MSG.WriteShort(SV_MAIN.sv_client.netchan.message, start); + MSG.WriteString(SV_MAIN.sv_client.netchan.message, + SV_INIT.sv.configstrings[start]); + } + start++; + } + + // send next command + + if (start == Defines.MAX_CONFIGSTRINGS) { + MSG.WriteByte(SV_MAIN.sv_client.netchan.message, + Defines.svc_stufftext); + MSG.WriteString(SV_MAIN.sv_client.netchan.message, "cmd baselines " + + SV_INIT.svs.spawncount + " 0\n"); + } else { + MSG.WriteByte(SV_MAIN.sv_client.netchan.message, + Defines.svc_stufftext); + MSG.WriteString(SV_MAIN.sv_client.netchan.message, + "cmd configstrings " + SV_INIT.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_MAIN.sv_client.name + "\n"); + + if (SV_MAIN.sv_client.state != Defines.cs_connected) { + Com.Printf("baselines not valid -- already spawned\n"); + return; + } + + // handle the case of a level changing while a client was connecting + if (Lib.atoi(Cmd.Argv(1)) != SV_INIT.svs.spawncount) { + Com.Printf("SV_Baselines_f from different level\n"); + SV_New_f(); + return; + } + + start = Lib.atoi(Cmd.Argv(2)); + + //memset (&nullstate, 0, sizeof(nullstate)); + nullstate = new entity_state_t(null); + + // write a packet full of data + + while (SV_MAIN.sv_client.netchan.message.cursize < Defines.MAX_MSGLEN / 2 + && start < Defines.MAX_EDICTS) { + base = SV_INIT.sv.baselines[start]; + if (base.modelindex != 0 || base.sound != 0 || base.effects != 0) { + MSG.WriteByte(SV_MAIN.sv_client.netchan.message, + Defines.svc_spawnbaseline); + MSG.WriteDeltaEntity(nullstate, base, + SV_MAIN.sv_client.netchan.message, true, true); + } + start++; + } + + // send next command + + if (start == Defines.MAX_EDICTS) { + MSG.WriteByte(SV_MAIN.sv_client.netchan.message, + Defines.svc_stufftext); + MSG.WriteString(SV_MAIN.sv_client.netchan.message, "precache " + + SV_INIT.svs.spawncount + "\n"); + } else { + MSG.WriteByte(SV_MAIN.sv_client.netchan.message, + Defines.svc_stufftext); + MSG.WriteString(SV_MAIN.sv_client.netchan.message, "cmd baselines " + + SV_INIT.svs.spawncount + " " + start + "\n"); + } + } + + /* + * ================== SV_Begin_f ================== + */ + public static void SV_Begin_f() { + Com.DPrintf("Begin() from " + SV_MAIN.sv_client.name + "\n"); + + // handle the case of a level changing while a client was connecting + if (Lib.atoi(Cmd.Argv(1)) != SV_INIT.svs.spawncount) { + Com.Printf("SV_Begin_f from different level\n"); + SV_New_f(); + return; + } + + SV_MAIN.sv_client.state = Defines.cs_spawned; + + // call the game begin function + PlayerClient.ClientBegin(SV_USER.sv_player); + + Cbuf.InsertFromDefer(); + } + + //============================================================================= + + /* + * ================== SV_NextDownload_f ================== + */ + public static void SV_NextDownload_f() { + int r; + int percent; + int size; + + if (SV_MAIN.sv_client.download == null) + return; + + r = SV_MAIN.sv_client.downloadsize - SV_MAIN.sv_client.downloadcount; + if (r > 1024) + r = 1024; + + MSG.WriteByte(SV_MAIN.sv_client.netchan.message, Defines.svc_download); + MSG.WriteShort(SV_MAIN.sv_client.netchan.message, r); + + SV_MAIN.sv_client.downloadcount += r; + size = SV_MAIN.sv_client.downloadsize; + if (size == 0) + size = 1; + percent = SV_MAIN.sv_client.downloadcount * 100 / size; + MSG.WriteByte(SV_MAIN.sv_client.netchan.message, percent); + SZ.Write(SV_MAIN.sv_client.netchan.message, SV_MAIN.sv_client.download, + SV_MAIN.sv_client.downloadcount - r, r); + + if (SV_MAIN.sv_client.downloadcount != SV_MAIN.sv_client.downloadsize) + return; + + FS.FreeFile(SV_MAIN.sv_client.download); + SV_MAIN.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 = Lib.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 + || SV_MAIN.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 + || (name.startsWith("players/") && 0 == SV_MAIN.allow_download_players.value) // now + // models + || (name.startsWith("models/") && 0 == SV_MAIN.allow_download_models.value) // now + // sounds + || (name.startsWith("sound/") && 0 == SV_MAIN.allow_download_sounds.value) + // now maps (note special case for maps, must not be in pak) + || (name.startsWith("maps/") && 0 == SV_MAIN.allow_download_maps.value) // MUST + // be + // in a + // subdirectory + || name.indexOf('/') == -1) { // don't allow anything with .. + // path + MSG.WriteByte(SV_MAIN.sv_client.netchan.message, + Defines.svc_download); + MSG.WriteShort(SV_MAIN.sv_client.netchan.message, -1); + MSG.WriteByte(SV_MAIN.sv_client.netchan.message, 0); + return; + } + + if (SV_MAIN.sv_client.download != null) + FS.FreeFile(SV_MAIN.sv_client.download); + + SV_MAIN.sv_client.download = FS.LoadFile(name); + SV_MAIN.sv_client.downloadsize = SV_MAIN.sv_client.download.length; + SV_MAIN.sv_client.downloadcount = offset; + + if (offset > SV_MAIN.sv_client.downloadsize) + SV_MAIN.sv_client.downloadcount = SV_MAIN.sv_client.downloadsize; + + if (SV_MAIN.sv_client.download == null // special check for maps, if it + // came from a pak file, don't + // allow + // download ZOID + || (name.startsWith("maps/") && FS.file_from_pak != 0)) { + Com.DPrintf("Couldn't download " + name + " to " + + SV_MAIN.sv_client.name + "\n"); + if (SV_MAIN.sv_client.download != null) { + FS.FreeFile(SV_MAIN.sv_client.download); + SV_MAIN.sv_client.download = null; + } + + MSG.WriteByte(SV_MAIN.sv_client.netchan.message, + Defines.svc_download); + MSG.WriteShort(SV_MAIN.sv_client.netchan.message, -1); + MSG.WriteByte(SV_MAIN.sv_client.netchan.message, 0); + return; + } + + SV_NextDownload_f(); + Com.DPrintf("Downloading " + name + " to " + SV_MAIN.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_MAIN.SV_DropClient(SV_MAIN.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_INIT.sv.state == Defines.ss_game + || (SV_INIT.sv.state == Defines.ss_pic && 0 == Cvar + .VariableValue("coop"))) + return; // can't nextserver while playing a normal game + + SV_INIT.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)) != SV_INIT.svs.spawncount) { + Com.DPrintf("Nextserver() from wrong level, from " + + SV_MAIN.sv_client.name + "\n"); + return; // leftover from last server + } + + Com.DPrintf("Nextserver() from " + SV_MAIN.sv_client.name + "\n"); + + SV_Nextserver(); + } + + /* + * ================== SV_ExecuteUserCommand ================== + */ + public static void SV_ExecuteUserCommand(String s) { + SV_USER.ucmd_t u = null; + + Cmd.TokenizeString(s.toCharArray(), true); + SV_USER.sv_player = SV_MAIN.sv_client.edict; + + // SV_BeginRedirect (RD_CLIENT); + + int i = 0; + for (; i < SV_USER.ucmds.length; i++) { + u = SV_USER.ucmds[i]; + if (0 == Lib.strcmp(Cmd.Argv(0), u.name)) { + u.r.run(); + break; + } + } + + if (i == SV_USER.ucmds.length && SV_INIT.sv.state == Defines.ss_game) + GameAI.ClientCommand(SV_USER.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_MAIN.sv_enforcetime.value != 0) { + Com.DPrintf("commandMsec underflow from " + cl.name + "\n"); + return; + } + + PlayerClient.ClientThink(cl.edict, cmd); + } + + /* + * =================== 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_MAIN.sv_client = cl; + SV_USER.sv_player = SV_MAIN.sv_client.edict; + + // only allow one move command + move_issued = false; + stringCmdCount = 0; + + while (true) { + if (Globals.net_message.readcount > Globals.net_message.cursize) { + Com.Printf("SV_ReadClientMessage: bad read:\n"); + Com.Printf(Lib.hexDump(Globals.net_message.data, 32, false)); + SV_MAIN.SV_DropClient(cl); + return; + } + + c = MSG.ReadByte(Globals.net_message); + if (c == -1) + break; + + switch (c) { + default: + Com.Printf("SV_ReadClientMessage: unknown command char\n"); + SV_MAIN.SV_DropClient(cl); + return; + + case Defines.clc_nop: + break; + + case Defines.clc_userinfo: + cl.userinfo = MSG.ReadString(Globals.net_message); + SV_MAIN.SV_UserinfoChanged(cl); + break; + + case Defines.clc_move: + if (move_issued) + return; // someone is trying to cheat... + + move_issued = true; + checksumIndex = Globals.net_message.readcount; + checksum = MSG.ReadByte(Globals.net_message); + lastframe = MSG.ReadLong(Globals.net_message); + + if (lastframe != cl.lastframe) { + cl.lastframe = lastframe; + if (cl.lastframe > 0) { + cl.frame_latency[cl.lastframe + & (Defines.LATENCY_COUNTS - 1)] = SV_INIT.svs.realtime + - cl.frames[cl.lastframe & Defines.UPDATE_MASK].senttime; + } + } + + //memset (nullcmd, 0, sizeof(nullcmd)); + nullcmd = new usercmd_t(); + MSG.ReadDeltaUsercmd(Globals.net_message, nullcmd, oldest); + MSG.ReadDeltaUsercmd(Globals.net_message, oldest, oldcmd); + MSG.ReadDeltaUsercmd(Globals.net_message, oldcmd, newcmd); + + if (cl.state != Defines.cs_spawned) { + cl.lastframe = -1; + break; + } + + // if the checksum fails, ignore the rest of the packet + + calculatedChecksum = Com.BlockSequenceCRCByte( + Globals.net_message.data, checksumIndex + 1, + Globals.net_message.readcount - checksumIndex - 1, + cl.netchan.incoming_sequence); + + if ((calculatedChecksum & 0xff) != checksum) { + Com.DPrintf("Failed command checksum for " + cl.name + " (" + + calculatedChecksum + " != " + checksum + ")/" + + cl.netchan.incoming_sequence + "\n"); + return; + } + + if (0 == SV_MAIN.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 Defines.clc_stringcmd: + s = MSG.ReadString(Globals.net_message); + + // malicious users may try using too many string commands + if (++stringCmdCount < SV_USER.MAX_STRINGCMDS) + SV_ExecuteUserCommand(s); + + if (cl.state == Defines.cs_zombie) + return; // disconnect command + break; + } + } + } +}
\ No newline at end of file diff --git a/src/jake2/server/SV_WORLD.java b/src/jake2/server/SV_WORLD.java index 46caf89..1d5fbb9 100644 --- a/src/jake2/server/SV_WORLD.java +++ b/src/jake2/server/SV_WORLD.java @@ -1,682 +1,528 @@ /* -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. - -*/ - + * 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.6 2004-08-29 21:39:25 hzi Exp $ - +// $Id: SV_WORLD.java,v 1.7 2004-09-22 19:22:12 salomo Exp $ package jake2.server; -import jake2.game.*; +import jake2.Defines; +import jake2.Globals; +import jake2.game.GameBase; +import jake2.game.cmodel_t; +import jake2.game.edict_t; +import jake2.game.link_t; +import jake2.game.trace_t; import jake2.qcommon.CM; import jake2.qcommon.Com; - -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 - //=============================================================================== - // - - 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 }; - - 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.p("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; - private static int leafs[]= new int[MAX_TOTAL_ENT_LEAFS]; - private static int clusters[]= new int[MAX_TOTAL_ENT_LEAFS]; - - public static void SV_LinkEdict(edict_t ent) - { - areanode_t node; - - 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 == GameBase.g_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 - - int iw[] = {topnode}; - - num_leafs= CM.CM_BoxLeafnums(ent.absmin, ent.absmax, leafs, MAX_TOTAL_ENT_LEAFS, iw); - - topnode= iw[0]; - - // 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_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]); - - return area_count; - } - - //=========================================================================== - - private static edict_t touch[]= new edict_t[MAX_EDICTS]; - /* - ============= - SV_PointContents - ============= - */ - - public static int SV_PointContents(float[] p) - { - edict_t 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 - - ==================== - */ - private static edict_t touchlist[]= new edict_t[MAX_EDICTS]; - public static void SV_ClipMoveToEntities(moveclip_t clip) - { - int i, num; - - 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.set(trace); - } - 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= GameBase.g_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; - } -} +import jake2.util.Math3D; + +public class SV_WORLD { + // world.c -- world query functions + // + // + //=============================================================================== + // + //ENTITY AREA CHECKING + // + //FIXME: this use of "area" is different from the bsp file use + //=============================================================================== + public static areanode_t sv_areanodes[] = new areanode_t[Defines.AREA_NODES]; + static { + SV_WORLD.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; + + public static final int MAX_TOTAL_ENT_LEAFS = 128; + + static int leafs[] = new int[MAX_TOTAL_ENT_LEAFS]; + + static int clusters[] = new int[MAX_TOTAL_ENT_LEAFS]; + + //=========================================================================== + static edict_t touch[] = new edict_t[Defines.MAX_EDICTS]; + + //=========================================================================== + static edict_t touchlist[] = new edict_t[Defines.MAX_EDICTS]; + + public static void initNodes() { + for (int n = 0; n < Defines.AREA_NODES; n++) + SV_WORLD.sv_areanodes[n] = new areanode_t(); + } + + // 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 }; + anode = SV_WORLD.sv_areanodes[SV_WORLD.sv_numareanodes]; + // just for debugging (rst) + Math3D.VectorCopy(mins, anode.mins_rst); + Math3D.VectorCopy(maxs, anode.maxs_rst); + SV_WORLD.sv_numareanodes++; + ClearLink(anode.trigger_edicts); + ClearLink(anode.solid_edicts); + if (depth == Defines.AREA_DEPTH) { + anode.axis = -1; + anode.children[0] = anode.children[1] = null; + return anode; + } + Math3D.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]); + Math3D.VectorCopy(mins, mins1); + Math3D.VectorCopy(mins, mins2); + Math3D.VectorCopy(maxs, maxs1); + Math3D.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_WORLD.sv_numareanodes = 0; + SV_CreateAreaNode(0, SV_INIT.sv.models[1].mins, + SV_INIT.sv.models[1].maxs); + /* + * Com.p("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; + } + + public static void SV_LinkEdict(edict_t ent) { + areanode_t node; + 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 == GameBase.g_edicts[0]) + return; // don't add the world + if (!ent.inuse) + return; + // set the size + Math3D.VectorSubtract(ent.maxs, ent.mins, ent.size); + // encode the size into the entity_state for client prediction + if (ent.solid == Defines.SOLID_BBOX + && 0 == (ent.svflags & Defines.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 == Defines.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 == Defines.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 + Math3D.VectorAdd(ent.s.origin, ent.mins, ent.absmin); + Math3D.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 + int iw[] = { topnode }; + num_leafs = CM.CM_BoxLeafnums(ent.absmin, ent.absmax, SV_WORLD.leafs, + SV_WORLD.MAX_TOTAL_ENT_LEAFS, iw); + topnode = iw[0]; + // set areas + for (int i = 0; i < num_leafs; i++) { + SV_WORLD.clusters[i] = CM.CM_LeafCluster(SV_WORLD.leafs[i]); + area = CM.CM_LeafArea(SV_WORLD.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_INIT.sv.state == Defines.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 >= SV_WORLD.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 (SV_WORLD.clusters[i] == -1) + continue; // not a visible leaf + for (j = 0; j < i; j++) + if (SV_WORLD.clusters[j] == SV_WORLD.clusters[i]) + break; + if (j == i) { + if (ent.num_clusters == Defines.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++] = SV_WORLD.clusters[i]; + } + } + } + // if first time, make sure old_origin is valid + if (0 == ent.linkcount) { + Math3D.VectorCopy(ent.s.origin, ent.s.old_origin); + } + ent.linkcount++; + if (ent.solid == Defines.SOLID_NOT) + return; + // find the first node that the ent's box crosses + node = SV_WORLD.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 == Defines.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 (SV_WORLD.area_type == Defines.AREA_SOLID) + start = node.solid_edicts; + else + start = node.trigger_edicts; + for (l = start.next; l != start; l = next) { + next = l.next; + check = (edict_t) l.o; + if (check.solid == Defines.SOLID_NOT) + continue; // deactivated + if (check.absmin[0] > SV_WORLD.area_maxs[0] + || check.absmin[1] > SV_WORLD.area_maxs[1] + || check.absmin[2] > SV_WORLD.area_maxs[2] + || check.absmax[0] < SV_WORLD.area_mins[0] + || check.absmax[1] < SV_WORLD.area_mins[1] + || check.absmax[2] < SV_WORLD.area_mins[2]) + continue; // not touching + if (SV_WORLD.area_count == SV_WORLD.area_maxcount) { + Com.Printf("SV_AreaEdicts: MAXCOUNT\n"); + return; + } + SV_WORLD.area_list[SV_WORLD.area_count] = check; + SV_WORLD.area_count++; + } + if (node.axis == -1) + return; // terminal node + // recurse down both sides + if (SV_WORLD.area_maxs[node.axis] > node.dist) + SV_AreaEdicts_r(node.children[0]); + if (SV_WORLD.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) { + SV_WORLD.area_mins = mins; + SV_WORLD.area_maxs = maxs; + SV_WORLD.area_list = list; + SV_WORLD.area_count = 0; + SV_WORLD.area_maxcount = maxcount; + SV_WORLD.area_type = areatype; + SV_AreaEdicts_r(SV_WORLD.sv_areanodes[0]); + return SV_WORLD.area_count; + } + + /* + * ============= SV_PointContents ============= + */ + public static int SV_PointContents(float[] p) { + edict_t hit; + int i, num; + int contents, c2; + int headnode; + float angles[]; + // get base contents from world + contents = CM.PointContents(p, SV_INIT.sv.models[1].headnode); + // or in contents from all the other entities + num = SV_AreaEdicts(p, p, SV_WORLD.touch, Defines.MAX_EDICTS, + Defines.AREA_SOLID); + for (i = 0; i < num; i++) { + hit = SV_WORLD.touch[i]; + // might intersect, so do an exact clip + headnode = SV_HullForEntity(hit); + angles = hit.s.angles; + if (hit.solid != Defines.SOLID_BSP) + angles = Globals.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 == Defines.SOLID_BSP) { + // explicit hulls in the BSP model + model = SV_INIT.sv.models[ent.s.modelindex]; + if (null == model) + Com.Error(Defines.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); + } + + public static void SV_ClipMoveToEntities(moveclip_t clip) { + int i, num; + edict_t touch; + trace_t trace; + int headnode; + float angles[]; + num = SV_AreaEdicts(clip.boxmins, clip.boxmaxs, SV_WORLD.touchlist, + Defines.MAX_EDICTS, Defines.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 = SV_WORLD.touchlist[i]; + if (touch.solid == Defines.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 & Defines.CONTENTS_DEADMONSTER) + && 0 != (touch.svflags & Defines.SVF_DEADMONSTER)) + continue; + // might intersect, so do an exact clip + headnode = SV_HullForEntity(touch); + angles = touch.s.angles; + if (touch.solid != Defines.SOLID_BSP) + angles = Globals.vec3_origin; // boxes don't rotate + if ((touch.svflags & Defines.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.set(trace); + } 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 = Globals.vec3_origin; + if (maxs == null) + maxs = Globals.vec3_origin; + + // clip to world + clip.trace = CM.BoxTrace(start, end, mins, maxs, 0, contentmask); + clip.trace.ent = GameBase.g_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; + Math3D.VectorCopy(mins, clip.mins2); + Math3D.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; + } +}
\ No newline at end of file diff --git a/src/jake2/server/server_static_t.java b/src/jake2/server/server_static_t.java index c9f94a2..fffc9e9 100644 --- a/src/jake2/server/server_static_t.java +++ b/src/jake2/server/server_static_t.java @@ -1,65 +1,71 @@ /* -Copyright (C) 1997-2001 Id Software, Inc. + * 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. + * + */ -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. +// Created on 14.01.2004 by RST. +// $Id: server_static_t.java,v 1.2 2004-09-22 19:22:12 salomo Exp $ +package jake2.server; -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. +import jake2.Defines; +import jake2.game.entity_state_t; +import jake2.qcommon.sizebuf_t; -See the GNU General Public License for more details. +import java.io.RandomAccessFile; -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. +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 -// Created on 14.01.2004 by RST. -// $Id: server_static_t.java,v 1.1 2004-07-07 19:59:51 hzi Exp $ + int realtime; // always increasing, no clamping, etc -package jake2.server; + String mapcmd = ""; // ie: *intro.cin+base -import jake2.*; -import jake2.client.*; -import jake2.game.*; -import jake2.qcommon.*; -import jake2.render.*; -import jake2.server.*; + int spawncount; // incremented each server start -import java.io.*; + // used to check late spawns -public class server_static_t { - public server_static_t() { - for (int n = 0; n < Defines.MAX_CHALLENGES; n++) { - challenges[n] = new challenge_t(); - } - } + client_t clients[]; // [maxclients->value]; - boolean initialized; // sv_init has completed - int realtime; // always increasing, no clamping, etc + int num_client_entities; // maxclients->value*UPDATE_BACKUP*MAX_PACKET_ENTITIES - //char mapcmd[MAX_TOKEN_CHARS]; // ie: *intro.cin+base - String mapcmd = ""; // ie: *intro.cin+base + int next_client_entities; // next client_entity to use - int spawncount; // incremented each server start - // used to check late spawns + entity_state_t client_entities[]; // [num_client_entities] - client_t clients[]; // [maxclients->value]; + int last_heartbeat; - 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] + challenge_t challenges[] = new challenge_t[Defines.MAX_CHALLENGES]; // to + // prevent + // invalid + // IPs + // from + // connecting - int last_heartbeat; + // serverrecord values + RandomAccessFile demofile; - challenge_t challenges[] = new challenge_t[Defines.MAX_CHALLENGES]; // to prevent invalid IPs from connecting + sizebuf_t demo_multicast = new sizebuf_t(); - // serverrecord values - RandomAccessFile demofile; - sizebuf_t demo_multicast = new sizebuf_t(); - byte demo_multicast_buf[] = new byte[Defines.MAX_MSGLEN]; -} + byte demo_multicast_buf[] = new byte[Defines.MAX_MSGLEN]; +}
\ No newline at end of file diff --git a/src/jake2/server/server_t.java b/src/jake2/server/server_t.java index 8e260a2..d67e384 100644 --- a/src/jake2/server/server_t.java +++ b/src/jake2/server/server_t.java @@ -1,76 +1,72 @@ /* -Copyright (C) 1997-2001 Id Software, Inc. + * 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. + * + */ -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. +// Created on 14.01.2004 by RST. +// $Id: server_t.java,v 1.2 2004-09-22 19:22:12 salomo Exp $ +package jake2.server; -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. +import jake2.Defines; +import jake2.game.cmodel_t; +import jake2.game.entity_state_t; +import jake2.qcommon.sizebuf_t; -See the GNU General Public License for more details. +import java.io.RandomAccessFile; -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. +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(); -// Created on 14.01.2004 by RST. -// $Id: server_t.java,v 1.1 2004-07-07 19:59:51 hzi Exp $ + for (int n = 0; n < Defines.MAX_EDICTS; n++) + baselines[n] = new entity_state_t(null); + } -package jake2.server; + int state; // precache commands are only valid during load -import java.io.File; -import java.io.RandomAccessFile; + boolean attractloop; // running cinematics and demos for the local system + // only -import jake2.*; -import jake2.client.*; -import jake2.game.*; -import jake2.qcommon.*; -import jake2.render.*; + boolean loadgame; // client begins should reuse existing entity -public class server_t { + int time; // always sv.framenum * 100 msec + + int framenum; + + String name = ""; // map name, or cinematic name + + cmodel_t models[]; + + 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; - 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 -} + boolean timedemo; // don't time sync +}
\ No newline at end of file |