diff options
Diffstat (limited to 'src/jake2/server/SV_MAIN.java')
-rw-r--r-- | src/jake2/server/SV_MAIN.java | 1030 |
1 files changed, 1030 insertions, 0 deletions
diff --git a/src/jake2/server/SV_MAIN.java b/src/jake2/server/SV_MAIN.java new file mode 100644 index 0000000..e3059ec --- /dev/null +++ b/src/jake2/server/SV_MAIN.java @@ -0,0 +1,1030 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +// Created on 13.01.2004 by RST. +// $Id: SV_MAIN.java,v 1.1 2004-07-07 19:59:49 hzi Exp $ + +package jake2.server; + +import java.io.DataOutputStream; +import java.io.IOException; + +import jake2.*; +import jake2.client.*; +import jake2.game.*; +import jake2.qcommon.*; +import jake2.render.*; +import jake2.sys.NET; +import jake2.sys.Sys; +import jake2.util.Lib; + +public class SV_MAIN extends SV_GAME { + + static netadr_t master_adr[] = new netadr_t[MAX_MASTERS]; // address of group servers + static { + for (int i = 0; i < MAX_MASTERS; i++) { + master_adr[i] = new netadr_t(); + } + } + public static client_t sv_client; // current client + + public static cvar_t sv_paused; + public static cvar_t sv_timedemo; + + public static cvar_t sv_enforcetime; + + public static cvar_t timeout; // seconds without any message + public static cvar_t zombietime; // seconds to sink messages after disconnect + + public static cvar_t rcon_password; // password for remote server commands + + public static cvar_t allow_download; + public static cvar_t allow_download_players; + public static cvar_t allow_download_models; + public static cvar_t allow_download_sounds; + public static cvar_t allow_download_maps; + + public static cvar_t sv_airaccelerate; + + public static cvar_t sv_noreload; // don't reload level state when reentering + + public static cvar_t maxclients; // FIXME: rename sv_maxclients + public static cvar_t sv_showclamp; + + public static cvar_t hostname; + public static cvar_t public_server; // should heartbeats be sent + + public static cvar_t sv_reconnect_limit; // minimum seconds between connect messages + + //============================================================================ + + /* + ===================== + SV_DropClient + + Called when the player is totally leaving the server, either willingly + or unwillingly. This is NOT called if the entire server is quiting + or crashing. + ===================== + */ + public static void SV_DropClient(client_t drop) { + // add the disconnect + MSG.WriteByte(drop.netchan.message, Defines.svc_disconnect); + + if (drop.state == Defines.cs_spawned) { + // call the prog function for removing a client + // this will remove the body, among other things + SV_GAME.ge.ClientDisconnect(drop.edict); + } + + if (drop.download != null) { + FS.FreeFile(drop.download); + drop.download = null; + } + + drop.state = Defines.cs_zombie; // become free in a few seconds + drop.name = ""; + } + + /* + ============================================================================== + + CONNECTIONLESS COMMANDS + + ============================================================================== + */ + + /* + =============== + SV_StatusString + + Builds the string that is sent as heartbeats and status replies + =============== + */ + public static String SV_StatusString() { + String player; + String status = ""; + int i; + client_t cl; + int statusLength; + int playerLength; + + status = Cvar.Serverinfo() + "\n"; + + for (i = 0; i < maxclients.value; i++) { + cl = svs.clients[i]; + if (cl.state == Defines.cs_connected || cl.state == Defines.cs_spawned) { + player = "" + cl.edict.client.ps.stats[Defines.STAT_FRAGS] + " " + cl.ping + "\"" + cl.name + "\"\n"; + + playerLength = player.length(); + statusLength = status.length(); + + if (statusLength + playerLength >= 1024) + break; // can't hold any more + + status += player; + } + } + + return status; + } + + /* + ================ + SVC_Status + + Responds with all the info that qplug or qspy can see + ================ + */ + public static void SVC_Status() { + Netchan.OutOfBandPrint(NS_SERVER, Netchan.net_from, "print\n" + SV_StatusString()); + } + + /* + ================ + SVC_Ack + + ================ + */ + public static void SVC_Ack() { + Com.Printf("Ping acknowledge from " + NET.AdrToString(Netchan.net_from) + "\n"); + } + + /* + ================ + SVC_Info + + Responds with short info for broadcast scans + The second parameter should be the current protocol version number. + ================ + */ + public static void SVC_Info() { + String string; + int i, count; + int version; + + if (maxclients.value == 1) + return; // ignore in single player + + version = atoi(Cmd.Argv(1)); + + if (version != PROTOCOL_VERSION) + string = hostname.string + ": wrong version\n"; + else { + count = 0; + for (i = 0; i < maxclients.value; i++) + if (svs.clients[i].state >= cs_connected) + count++; + + string = hostname.string + " " + sv.name + " " + count + "/" + (int) maxclients.value + "\n"; + } + + Netchan.OutOfBandPrint(NS_SERVER, Netchan.net_from, "info\n" + string); + } + + /* + ================ + SVC_Ping + + Just responds with an acknowledgement + ================ + */ + public static void SVC_Ping() { + Netchan.OutOfBandPrint(NS_SERVER, Netchan.net_from, "ack"); + } + + /* + ================= + SVC_GetChallenge + + Returns a challenge number that can be used + in a subsequent client_connect command. + We do this to prevent denial of service attacks that + flood the server with invalid connection IPs. With a + challenge, they must give a valid IP address. + ================= + */ + public static void SVC_GetChallenge() { + int i; + int oldest; + int oldestTime; + + oldest = 0; + oldestTime = 0x7fffffff; + + // see if we already have a challenge for this ip + for (i = 0; i < MAX_CHALLENGES; i++) { + if (NET.NET_CompareBaseAdr(Netchan.net_from, svs.challenges[i].adr)) + break; + if (svs.challenges[i].time < oldestTime) { + oldestTime = svs.challenges[i].time; + oldest = i; + } + } + + if (i == MAX_CHALLENGES) { + // overwrite the oldest + svs.challenges[oldest].challenge = rand() & 0x7fff; + svs.challenges[oldest].adr = Netchan.net_from; + svs.challenges[oldest].time = (int) Globals.curtime; + i = oldest; + } + + // send it back + Netchan.OutOfBandPrint(NS_SERVER, Netchan.net_from, "challenge " + svs.challenges[i].challenge); + } + + /* + ================== + SVC_DirectConnect + + A connection request that did not come from the master + ================== + */ + public static void SVC_DirectConnect() { + //char userinfo[MAX_INFO_STRING]; + String userinfo; + netadr_t adr; + int i; + client_t cl; + + edict_t ent; + int edictnum; + int version; + int qport; + + adr = Netchan.net_from; + + Com.DPrintf("SVC_DirectConnect ()\n"); + + version = atoi(Cmd.Argv(1)); + if (version != PROTOCOL_VERSION) { + Netchan.OutOfBandPrint(NS_SERVER, adr, "print\nServer is version " + VERSION + "\n"); + Com.DPrintf(" rejected connect from version " + version + "\n"); + return; + } + + qport = atoi(Cmd.Argv(2)); + int challenge = atoi(Cmd.Argv(3)); + userinfo = Cmd.Argv(4); + + //userinfo[sizeof(userinfo) - 1] = 0; + + // force the IP key/value pair so the game can filter based on ip + userinfo = Info.Info_SetValueForKey1(userinfo, "ip", NET.AdrToString(Netchan.net_from)); + + // attractloop servers are ONLY for local clients + if (sv.attractloop) { + if (!NET.IsLocalAddress(adr)) { + Com.Printf("Remote connect in attract loop. Ignored.\n"); + Netchan.OutOfBandPrint(NS_SERVER, adr, "print\nConnection refused.\n"); + return; + } + } + + // see if the challenge is valid + if (!NET.IsLocalAddress(adr)) { + for (i = 0; i < MAX_CHALLENGES; i++) { + if (NET.NET_CompareBaseAdr(Netchan.net_from, svs.challenges[i].adr)) { + if (challenge == svs.challenges[i].challenge) + break; // good + Netchan.OutOfBandPrint(NS_SERVER, adr, "print\nBad challenge.\n"); + return; + } + } + if (i == MAX_CHALLENGES) { + Netchan.OutOfBandPrint(NS_SERVER, adr, "print\nNo challenge for address.\n"); + return; + } + } + + //newcl = temp; + //memset (newcl, 0, sizeof(client_t)); + //newcl = new client_t(); + + // if there is already a slot for this ip, reuse it + for (i = 0; i < maxclients.value; i++) { + cl = svs.clients[i]; + + if (cl.state == cs_free) + continue; + if (NET.NET_CompareBaseAdr(adr, cl.netchan.remote_address) + && (cl.netchan.qport == qport || adr.port == cl.netchan.remote_address.port)) { + if (!NET.IsLocalAddress(adr) && (svs.realtime - cl.lastconnect) < ((int) sv_reconnect_limit.value * 1000)) { + Com.DPrintf(NET.AdrToString(adr) + ":reconnect rejected : too soon\n"); + return; + } + Com.Printf(NET.AdrToString(adr) + ":reconnect\n"); + + gotnewcl(i, challenge, userinfo, adr, qport); + return; + } + } + + // find a client slot + //newcl = null; + int index = -1; + for (i = 0; i < maxclients.value; i++) { + cl = svs.clients[i]; + if (cl.state == cs_free) { + index = i; + break; + } + } + if (index == -1) { + Netchan.OutOfBandPrint(NS_SERVER, adr, "print\nServer is full.\n"); + Com.DPrintf("Rejected a connection.\n"); + return; + } + gotnewcl(index, challenge, userinfo, adr, qport); + } + + public static void gotnewcl(int i, int challenge, String userinfo, netadr_t adr, int qport) { + // build a new connection + // accept the new client + // this is the only place a client_t is ever initialized + //*newcl = temp; + + sv_client = svs.clients[i]; + //edictnum = (newcl-svs.clients)+1; + int edictnum = i + 1; + edict_t ent = ge.edicts[edictnum]; + svs.clients[i].edict = ent; + svs.clients[i].challenge = challenge; // save challenge for checksumming + + // get the game a chance to reject this connection or modify the userinfo + if (!(ge.ClientConnect(ent, userinfo))) { + if (Info.Info_ValueForKey(userinfo, "rejmsg") != null) + Netchan.OutOfBandPrint( + NS_SERVER, + adr, + "print\n" + Info.Info_ValueForKey(userinfo, "rejmsg") + "\nConnection refused.\n"); + else + Netchan.OutOfBandPrint(NS_SERVER, adr, "print\nConnection refused.\n"); + Com.DPrintf("Game rejected a connection.\n"); + return; + } + + // parse some info from the info strings + svs.clients[i].userinfo = userinfo; + SV_UserinfoChanged(svs.clients[i]); + + // send the connect packet to the client + Netchan.OutOfBandPrint(NS_SERVER, adr, "client_connect"); + + Netchan.Setup(NS_SERVER, svs.clients[i].netchan, adr, qport); + + svs.clients[i].state = cs_connected; + + SZ.Init(svs.clients[i].datagram, svs.clients[i].datagram_buf, svs.clients[i].datagram_buf.length); + svs.clients[i].datagram.allowoverflow = true; + svs.clients[i].lastmessage = svs.realtime; // don't timeout + svs.clients[i].lastconnect = svs.realtime; + Com.DPrintf("new client added.\n"); + } + + public static int Rcon_Validate() { + if (0 == strlen(rcon_password.string)) + return 0; + + if (0 != strcmp(Cmd.Argv(1), rcon_password.string)) + return 0; + + return 1; + } + + /* + =============== + SVC_RemoteCommand + + A client issued an rcon command. + Shift down the remaining args + Redirect all printfs + =============== + */ + public static void SVC_RemoteCommand() { + int i; + //char remaining[1024]; + String remaining; + + i = Rcon_Validate(); + + String msg = new String(net_message.data, 4, -1); + + if (i == 0) + Com.Printf("Bad rcon from " + NET.AdrToString(Netchan.net_from) + ":\n" + msg + "\n"); + else + Com.Printf("Rcon from " + NET.AdrToString(Netchan.net_from) + ":\n" + msg + "\n"); + + Com.BeginRedirect(RD_PACKET, SV_SEND.sv_outputbuf, SV_OUTPUTBUF_LENGTH, new Com.RD_Flusher() { + public void rd_flush(int target, byte[] buffer) { + SV_SEND.SV_FlushRedirect(target, buffer); + } + }); + + if (0 == Rcon_Validate()) { + Com.Printf("Bad rcon_password.\n"); + } + else { + remaining = ""; + + for (i = 2; i < Cmd.Argc(); i++) { + strcat(remaining, Cmd.Argv(i)); + strcat(remaining, " "); + } + + Cmd.ExecuteString(remaining); + } + + Com.EndRedirect(); + } + + /* + ================= + SV_ConnectionlessPacket + + A connectionless packet has four leading 0xff + characters to distinguish it from a game channel. + Clients that are in the game can still send + connectionless packets. + ================= + */ + public static void SV_ConnectionlessPacket() { + String s; + String c; + + MSG.BeginReading(net_message); + MSG.ReadLong(net_message); // skip the -1 marker + + s = MSG.ReadStringLine(net_message); + + Cmd.TokenizeString(s.toCharArray(), false); + + c = Cmd.Argv(0); + Com.Printf("Packet " + NET.AdrToString(Netchan.net_from) + " : " + c + "\n"); + //Com.Printf(Lib.hexDump(net_message.data, 64, false) + "\n"); + + if (0 == strcmp(c, "ping")) + SVC_Ping(); + else if (0 == strcmp(c, "ack")) + SVC_Ack(); + else if (0 == strcmp(c, "status")) + SVC_Status(); + else if (0 == strcmp(c, "info")) + SVC_Info(); + else if (0 == strcmp(c, "getchallenge")) + SVC_GetChallenge(); + else if (0 == strcmp(c, "connect")) + SVC_DirectConnect(); + else if (0 == strcmp(c, "rcon")) + SVC_RemoteCommand(); + else + { + Com.Printf("bad connectionless packet from " + NET.AdrToString(Netchan.net_from) + "\n"); + Com.Printf("[" + s + "]\n"); + Com.Printf("" + Lib.hexDump(net_message.data, 128, false)); + } + } + + //============================================================================ + + /* + =================== + SV_CalcPings + + Updates the cl.ping variables + =================== + */ + public static void SV_CalcPings() { + int i, j; + client_t cl; + int total, count; + + for (i = 0; i < maxclients.value; i++) { + cl = svs.clients[i]; + if (cl.state != cs_spawned) + continue; + + total = 0; + count = 0; + for (j = 0; j < LATENCY_COUNTS; j++) { + if (cl.frame_latency[j] > 0) { + count++; + total += cl.frame_latency[j]; + } + } + if (0 == count) + cl.ping = 0; + else + cl.ping = total / count; + + // let the game dll know about the ping + cl.edict.client.ping = cl.ping; + } + } + + /* + =================== + SV_GiveMsec + + Every few frames, gives all clients an allotment of milliseconds + for their command moves. If they exceed it, assume cheating. + =================== + */ + public static void SV_GiveMsec() { + int i; + client_t cl; + + if ((sv.framenum & 15) != 0) + return; + + for (i = 0; i < maxclients.value; i++) { + cl = svs.clients[i]; + if (cl.state == cs_free) + continue; + + cl.commandMsec = 1800; // 1600 + some slop + } + } + + /* + ================= + SV_ReadPackets + ================= + */ + public static void SV_ReadPackets() { + int i; + client_t cl; + int qport =0; + + while (NET.GetPacket(NS_SERVER, Netchan.net_from, net_message)) { + + // check for connectionless packet (0xffffffff) first + if ((net_message.data[0] == -1) + && (net_message.data[1] == -1) + && (net_message.data[2] == -1) + && (net_message.data[3] == -1)) { + SV_ConnectionlessPacket(); + continue; + } + + // read the qport out of the message so we can fix up + // stupid address translating routers + MSG.BeginReading(net_message); + MSG.ReadLong(net_message); // sequence number + MSG.ReadLong(net_message); // sequence number + qport = MSG.ReadShort(net_message) & 0xffff; + + // check for packets from connected clients + for (i = 0; i < maxclients.value; i++) { + cl = svs.clients[i]; + if (cl.state == cs_free) + continue; + if (!NET.NET_CompareBaseAdr(Netchan.net_from, cl.netchan.remote_address)) + continue; + if (cl.netchan.qport != qport) + continue; + if (cl.netchan.remote_address.port != Netchan.net_from.port) { + Com.Printf("SV_ReadPackets: fixing up a translated port\n"); + cl.netchan.remote_address.port = Netchan.net_from.port; + } + + if (Netchan.Process(cl.netchan, net_message)) { // this is a valid, sequenced packet, so process it + if (cl.state != cs_zombie) { + cl.lastmessage = svs.realtime; // don't timeout + SV_USER.SV_ExecuteClientMessage(cl); + } + } + break; + } + + if (i != maxclients.value) + continue; + } + } + + /* + ================== + SV_CheckTimeouts + + If a packet has not been received from a client for timeout.value + seconds, drop the conneciton. Server frames are used instead of + realtime to avoid dropping the local client while debugging. + + When a client is normally dropped, the client_t goes into a zombie state + for a few seconds to make sure any final reliable message gets resent + if necessary + ================== + */ + public static void SV_CheckTimeouts() { + int i; + client_t cl; + int droppoint; + int zombiepoint; + + droppoint = (int) (svs.realtime - 1000 * timeout.value); + zombiepoint = (int) (svs.realtime - 1000 * zombietime.value); + + for (i = 0; i < maxclients.value; i++) { + cl = svs.clients[i]; + // message times may be wrong across a changelevel + if (cl.lastmessage > svs.realtime) + cl.lastmessage = svs.realtime; + + if (cl.state == cs_zombie && cl.lastmessage < zombiepoint) { + cl.state = cs_free; // can now be reused + continue; + } + if ((cl.state == cs_connected || cl.state == cs_spawned) && cl.lastmessage < droppoint) { + SV_SEND.SV_BroadcastPrintf(PRINT_HIGH, cl.name + " timed out\n"); + SV_DropClient(cl); + cl.state = cs_free; // don't bother with zombie state + } + } + } + + /* + ================ + SV_PrepWorldFrame + + This has to be done before the world logic, because + player processing happens outside RunWorldFrame + ================ + */ + public static void SV_PrepWorldFrame() { + edict_t ent; + int i; + + for (i = 0; i < ge.num_edicts; i++) { + ent = SV_GAME.ge.edicts[i]; + // events only last for a single message + ent.s.event = 0; + } + + } + + /* + ================= + SV_RunGameFrame + ================= + */ + public static void SV_RunGameFrame() { + if (host_speeds.value != 0) + time_before_game = Sys.Milliseconds(); + + // we always need to bump framenum, even if we + // don't run the world, otherwise the delta + // compression can get confused when a client + // has the "current" frame + sv.framenum++; + sv.time = sv.framenum * 100; + + // don't run if paused + if (0 == sv_paused.value || maxclients.value > 1) { + ge.RunFrame(); + + // never get more than one tic behind + if (sv.time < svs.realtime) { + //if (sv_showclamp.value != 0) + Com.Printf("sv highclamp\n"); + svs.realtime = sv.time; + } + } + + if (host_speeds.value != 0) + time_after_game = Sys.Milliseconds(); + + } + + /* + ================== + SV_Frame + + ================== + */ + public static void SV_Frame(long msec) { + Globals.time_before_game = Globals.time_after_game = 0; + + // if server is not active, do nothing + if (!svs.initialized) + return; + + svs.realtime += msec; + + // keep the random time dependent + Lib.rand(); + + // check timeouts + SV_CheckTimeouts(); + + // get packets from clients + SV_ReadPackets(); + + //if (Game.g_edicts[1] !=null) + // Com.p("player at:" + Lib.vtofsbeaty(Game.g_edicts[1].s.origin )); + + // move autonomous things around if enough time has passed + if (0== sv_timedemo.value && svs.realtime < sv.time) { + // never let the time get too far off + if (sv.time - svs.realtime > 100) { + //if (sv_showclamp.value != 0) + Com.Printf("sv lowclamp\n"); + svs.realtime = sv.time - 100; + } + NET.NET_Sleep(sv.time - svs.realtime); + return; + } + + // update ping based on the last known frame from all clients + //TODO: dont need yet + SV_CalcPings(); + + // give the clients some timeslices + //TODO: dont need yet + SV_GiveMsec(); + + // let everything in the world think and move + SV_RunGameFrame(); + + // send messages back to the clients that had packets read this frame + SV_SEND.SV_SendClientMessages(); + + // save the entire world state if recording a serverdemo + //TODO: dont need yet + //SV_WORLD.SV_RecordDemoMessage(); + + // send a heartbeat to the master if needed + //TODO: dont need yet + Master_Heartbeat(); + + // clear teleport flags, etc for next frame + SV_PrepWorldFrame(); + + } + + //============================================================================ + + /* + ================ + Master_Heartbeat + + Send a message to the master every few minutes to + let it know we are alive, and log information + ================ + */ + public static final int HEARTBEAT_SECONDS = 300; + public static void Master_Heartbeat() { + String string; + int i; + + // pgm post3.19 change, cvar pointer not validated before dereferencing + if (dedicated == null || 0 == dedicated.value) + return; // only dedicated servers send heartbeats + + // pgm post3.19 change, cvar pointer not validated before dereferencing + if (null == public_server || 0 == public_server.value) + return; // a private dedicated game + + // check for time wraparound + if (svs.last_heartbeat > svs.realtime) + svs.last_heartbeat = svs.realtime; + + if (svs.realtime - svs.last_heartbeat < HEARTBEAT_SECONDS * 1000) + return; // not time to send yet + + svs.last_heartbeat = svs.realtime; + + // send the same string that we would give for a status OOB command + string = SV_StatusString(); + + // send to group master + for (i = 0; i < MAX_MASTERS; i++) + if (master_adr[i].port != 0) { + Com.Printf("Sending heartbeat to " + NET.AdrToString(master_adr[i]) + "\n"); + Netchan.OutOfBandPrint(NS_SERVER, master_adr[i], "heartbeat\n" + string); + } + } + + /* + ================= + Master_Shutdown + + Informs all masters that this server is going down + ================= + */ + static void Master_Shutdown() { + int i; + + // pgm post3.19 change, cvar pointer not validated before dereferencing + if (null == dedicated || 0 == dedicated.value) + return; // only dedicated servers send heartbeats + + // pgm post3.19 change, cvar pointer not validated before dereferencing + if (null == public_server || 0 == public_server.value) + return; // a private dedicated game + + // send to group master + for (i = 0; i < MAX_MASTERS; i++) + if (master_adr[i].port != 0) { + if (i > 0) + Com.Printf("Sending heartbeat to " + NET.AdrToString(master_adr[i]) + "\n"); + Netchan.OutOfBandPrint(NS_SERVER, master_adr[i], "shutdown"); + } + } + + //============================================================================ + + /* + ================= + SV_UserinfoChanged + + Pull specific info from a newly changed userinfo string + into a more C freindly form. + ================= + */ + public static void SV_UserinfoChanged(client_t cl) { + String val; + int i; + + // call prog code to allow overrides + SV_GAME.ge.ClientUserinfoChanged(cl.edict, cl.userinfo); + + // name for C code + cl.name = Info.Info_ValueForKey(cl.userinfo, "name"); + + // mask off high bit + //TODO: masking for german umlaute + //for (i=0 ; i<sizeof(cl.name) ; i++) + // cl.name[i] &= 127; + + // rate command + val = Info.Info_ValueForKey(cl.userinfo, "rate"); + if (val.length() > 0) { + i = atoi(val); + cl.rate = i; + if (cl.rate < 100) + cl.rate = 100; + if (cl.rate > 15000) + cl.rate = 15000; + } + else + cl.rate = 5000; + + // msg command + val = Info.Info_ValueForKey(cl.userinfo, "msg"); + if (strlen(val) > 0) { + cl.messagelevel = atoi(val); + } + + } + + //============================================================================ + + /* + =============== + SV_Init + + Only called at quake2.exe startup, not for each game + =============== + */ + public static void SV_Init() { + SV_CCMDS.SV_InitOperatorCommands (); //ok. + + rcon_password = Cvar.Get("rcon_password", "", 0); + Cvar.Get("skill", "1", 0); + Cvar.Get("deathmatch", "0", CVAR_LATCH); + Cvar.Get("coop", "0", CVAR_LATCH); + Cvar.Get("dmflags", "" + DF_INSTANT_ITEMS, CVAR_SERVERINFO); + Cvar.Get("fraglimit", "0", CVAR_SERVERINFO); + Cvar.Get("timelimit", "0", CVAR_SERVERINFO); + Cvar.Get("cheats", "0", CVAR_SERVERINFO | CVAR_LATCH); + Cvar.Get("protocol", "" + PROTOCOL_VERSION, CVAR_SERVERINFO | CVAR_NOSET); + + SV_MAIN.maxclients = Cvar.Get("maxclients", "1", CVAR_SERVERINFO | CVAR_LATCH); + hostname = Cvar.Get("hostname", "noname", CVAR_SERVERINFO | CVAR_ARCHIVE); + timeout = Cvar.Get("timeout", "125", 0); + zombietime = Cvar.Get("zombietime", "2", 0); + sv_showclamp = Cvar.Get("showclamp", "0", 0); + sv_paused = Cvar.Get("paused", "0", 0); + sv_timedemo = Cvar.Get("timedemo", "0", 0); + sv_enforcetime = Cvar.Get("sv_enforcetime", "0", 0); + + // TODO: carsten, re-allow downloads per default + allow_download = Cvar.Get("allow_download", "0", CVAR_ARCHIVE); + allow_download_players = Cvar.Get("allow_download_players", "0", CVAR_ARCHIVE); + allow_download_models = Cvar.Get("allow_download_models", "1", CVAR_ARCHIVE); + allow_download_sounds = Cvar.Get("allow_download_sounds", "1", CVAR_ARCHIVE); + allow_download_maps = Cvar.Get("allow_download_maps", "1", CVAR_ARCHIVE); + + sv_noreload = Cvar.Get("sv_noreload", "0", 0); + sv_airaccelerate = Cvar.Get("sv_airaccelerate", "0", CVAR_LATCH); + public_server = Cvar.Get("public", "0", 0); + sv_reconnect_limit = Cvar.Get("sv_reconnect_limit", "3", CVAR_ARCHIVE); + + SZ.Init(net_message, net_message_buffer, net_message_buffer.length); + } + + /* + ================== + SV_FinalMessage + + Used by SV_Shutdown to send a final message to all + connected clients before the server goes down. The messages are sent immediately, + not just stuck on the outgoing message list, because the server is going + to totally exit after returning from this function. + ================== + */ + public static void SV_FinalMessage(String message, boolean reconnect) { + int i; + client_t cl; + + SZ.Clear(net_message); + MSG.WriteByte(net_message, svc_print); + MSG.WriteByte(net_message, PRINT_HIGH); + MSG.WriteString(net_message, message); + + if (reconnect) + MSG.WriteByte(net_message, svc_reconnect); + else + MSG.WriteByte(net_message, svc_disconnect); + + // send it twice + // stagger the packets to crutch operating system limited buffers + + for (i = 0; i < maxclients.value; i++) { + cl = svs.clients[i]; + if (cl.state >= cs_connected) + Netchan.Transmit(cl.netchan, net_message.cursize, net_message.data); + } + for (i = 0; i < maxclients.value; i++) { + cl = svs.clients[i]; + if (cl.state >= cs_connected) + Netchan.Transmit(cl.netchan, net_message.cursize, net_message.data); + } + } + + /* + ================ + SV_Shutdown + + Called when each game quits, + before Sys_Quit or Sys_Error + ================ + */ + public static void SV_Shutdown(String finalmsg, boolean reconnect) { + if (svs.clients != null) + SV_FinalMessage(finalmsg, reconnect); + + Master_Shutdown(); + + SV_GAME.SV_ShutdownGameProgs (); + + // free current level + if (sv.demofile != null) + try { + sv.demofile.close(); + } + catch (IOException e) { + e.printStackTrace(); + } + + //memset (&sv, 0, sizeof(sv)); + sv = new server_t(); + + + Com.SetServerState (sv.state); + + // free server static data + //if (svs.clients!=null) + // Z_Free (svs.clients); + //if (svs.client_entities) + // Z_Free (svs.client_entities); + + if (svs.demofile != null) + try { + svs.demofile.close(); + } + catch (IOException e1) { + e1.printStackTrace(); + } + //memset (&svs, 0, sizeof(svs)); + svs = new server_static_t(); + } +} |