/* * 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.9 2004-10-17 21:31:32 cawe Exp $ package jake2.server; import jake2.Defines; import jake2.Globals; 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 { 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.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.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); } 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.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.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 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(); } }