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/SV_MAIN.java | |
parent | bcb4ac6eefb425d5b0a90009da506361d5739e75 (diff) |
major refactoring in game, server and client package
Diffstat (limited to 'src/jake2/server/SV_MAIN.java')
-rw-r--r-- | src/jake2/server/SV_MAIN.java | 2034 |
1 files changed, 1032 insertions, 1002 deletions
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 |