/* * 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.16 2006-01-20 22:44:07 salomo Exp $ package jake2.server; import jake2.Defines; import jake2.Globals; import jake2.game.*; import jake2.qcommon.*; import jake2.sys.NET; import jake2.sys.Timer; import jake2.util.Lib; import java.io.IOException; public class SV_MAIN { /** Addess of group servers.*/ public static netadr_t master_adr[] = new netadr_t[Defines.MAX_MASTERS]; 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 /** * 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; /** * 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 * * ==============================================================================*/ /** * 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; } /** * 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"); } /** * 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.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); } /** * 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; 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); // force the IP key/value pair so the game can filter based on ip userinfo = Info.Info_SetValueForKey(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.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.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); } /** * Initializes player structures after successfull connection. */ 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 SV_MAIN.sv_client = SV_INIT.svs.clients[i]; int edictnum = i + 1; edict_t ent = GameBase.g_edicts[edictnum]; SV_INIT.svs.clients[i].edict = ent; // save challenge for checksumming SV_INIT.svs.clients[i].challenge = challenge; // 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"); } /** * Checks if the rcon password is corect. */ 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; } /** * A client issued an rcon command. Shift down the remaining args Redirect * all printfs fromt hte server to the client. */ public static void SVC_RemoteCommand() { int i; String remaining; i = Rcon_Validate(); String msg = Lib.CtoJava(Globals.net_message.data, 4, 1024); 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, StringBuffer buffer) { SV_SEND.SV_FlushRedirect(target, Lib.stringToBytes(buffer.toString())); } }); 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(); } /** * 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. It is used also by rcon commands. */ 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); //for debugging purposes //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)); } } /** * 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; } } /** * 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 } } /** * Reads packets from the network or loopback. */ 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.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; } } /** * 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 = Timer.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 = Timer.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.Sleep(SV_INIT.sv.time - SV_INIT.svs.realtime); return; } // update ping based on the last known frame from all clients SV_CalcPings(); // give the clients some timeslices 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 SV_ENTS.SV_RecordDemoMessage(); // send a heartbeat to the master if needed 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"); } } /** * 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 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); } } /** * 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); Cvar.Get("cheats", "0", 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); SV_MAIN.allow_download = Cvar.Get("allow_download", "1", 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); } /** * 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_INIT.svs.clients.length; 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_INIT.svs.clients.length; 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); } } /** * 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(); } }