/* * Copyright (C) 1997-2001 Id Software, Inc. * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation; either version 2 of the License, or (at your option) any later * version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. * * See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with * this program; if not, write to the Free Software Foundation, Inc., 59 Temple * Place - Suite 330, Boston, MA 02111-1307, USA. * */ // Created on 17.01.2004 by RST. // $Id: SV_USER.java,v 1.10 2005-12-17 20:32:01 salomo Exp $ package jake2.server; import jake2.Defines; import jake2.Globals; import jake2.game.Cmd; import jake2.game.GameAI; import jake2.game.GameBase; import jake2.game.Info; import jake2.game.PlayerClient; import jake2.game.edict_t; import jake2.game.entity_state_t; import jake2.game.usercmd_t; import jake2.qcommon.Cbuf; import jake2.qcommon.Com; import jake2.qcommon.Cvar; import jake2.qcommon.FS; import jake2.qcommon.MSG; import jake2.qcommon.SZ; import jake2.util.Lib; import java.io.IOException; public class SV_USER { static edict_t sv_player; public static class ucmd_t { public ucmd_t(String n, Runnable r) { name = n; this.r = r; } String name; Runnable r; } static ucmd_t u1 = new ucmd_t("new", new Runnable() { public void run() { SV_USER.SV_New_f(); } }); static ucmd_t ucmds[] = { // auto issued new ucmd_t("new", new Runnable() { public void run() { SV_USER.SV_New_f(); } }), new ucmd_t("configstrings", new Runnable() { public void run() { SV_USER.SV_Configstrings_f(); } }), new ucmd_t("baselines", new Runnable() { public void run() { SV_USER.SV_Baselines_f(); } }), new ucmd_t("begin", new Runnable() { public void run() { SV_USER.SV_Begin_f(); } }), new ucmd_t("nextserver", new Runnable() { public void run() { SV_USER.SV_Nextserver_f(); } }), new ucmd_t("disconnect", new Runnable() { public void run() { SV_USER.SV_Disconnect_f(); } }), // issued by hand at client consoles new ucmd_t("info", new Runnable() { public void run() { SV_USER.SV_ShowServerinfo_f(); } }), new ucmd_t("download", new Runnable() { public void run() { SV_USER.SV_BeginDownload_f(); } }), new ucmd_t("nextdl", new Runnable() { public void run() { SV_USER.SV_NextDownload_f(); } }) }; public static final int MAX_STRINGCMDS = 8; /* * ============================================================ * * USER STRINGCMD EXECUTION * * sv_client and sv_player will be valid. * ============================================================ */ /* * ================== SV_BeginDemoServer ================== */ public static void SV_BeginDemoserver() { String name; name = "demos/" + SV_INIT.sv.name; try { SV_INIT.sv.demofile = FS.FOpenFile(name); } catch (IOException e) { Com.Error(Defines.ERR_DROP, "Couldn't open " + name + "\n"); } if (SV_INIT.sv.demofile == null) Com.Error(Defines.ERR_DROP, "Couldn't open " + name + "\n"); } /* * ================ SV_New_f * * Sends the first message from the server to a connected client. This will * be sent on the initial connection and upon each server load. * ================ */ public static void SV_New_f() { String gamedir; int playernum; edict_t ent; Com.DPrintf("New() from " + SV_MAIN.sv_client.name + "\n"); if (SV_MAIN.sv_client.state != Defines.cs_connected) { Com.Printf("New not valid -- already spawned\n"); return; } // demo servers just dump the file message if (SV_INIT.sv.state == Defines.ss_demo) { SV_BeginDemoserver(); return; } // // serverdata needs to go over for all types of servers // to make sure the protocol is right, and to set the gamedir // gamedir = Cvar.VariableString("gamedir"); // send the serverdata MSG.WriteByte(SV_MAIN.sv_client.netchan.message, Defines.svc_serverdata); MSG.WriteInt(SV_MAIN.sv_client.netchan.message, Defines.PROTOCOL_VERSION); MSG.WriteLong(SV_MAIN.sv_client.netchan.message, SV_INIT.svs.spawncount); MSG.WriteByte(SV_MAIN.sv_client.netchan.message, SV_INIT.sv.attractloop ? 1 : 0); MSG.WriteString(SV_MAIN.sv_client.netchan.message, gamedir); if (SV_INIT.sv.state == Defines.ss_cinematic || SV_INIT.sv.state == Defines.ss_pic) playernum = -1; else //playernum = sv_client - svs.clients; playernum = SV_MAIN.sv_client.serverindex; MSG.WriteShort(SV_MAIN.sv_client.netchan.message, playernum); // send full levelname MSG.WriteString(SV_MAIN.sv_client.netchan.message, SV_INIT.sv.configstrings[Defines.CS_NAME]); // // game server // if (SV_INIT.sv.state == Defines.ss_game) { // set up the entity for the client ent = GameBase.g_edicts[playernum + 1]; ent.s.number = playernum + 1; SV_MAIN.sv_client.edict = ent; SV_MAIN.sv_client.lastcmd = new usercmd_t(); // begin fetching configstrings MSG.WriteByte(SV_MAIN.sv_client.netchan.message, Defines.svc_stufftext); MSG.WriteString(SV_MAIN.sv_client.netchan.message, "cmd configstrings " + SV_INIT.svs.spawncount + " 0\n"); } } /* * ================== SV_Configstrings_f ================== */ public static void SV_Configstrings_f() { int start; Com.DPrintf("Configstrings() from " + SV_MAIN.sv_client.name + "\n"); if (SV_MAIN.sv_client.state != Defines.cs_connected) { Com.Printf("configstrings not valid -- already spawned\n"); return; } // handle the case of a level changing while a client was connecting if (Lib.atoi(Cmd.Argv(1)) != SV_INIT.svs.spawncount) { Com.Printf("SV_Configstrings_f from different level\n"); SV_New_f(); return; } start = Lib.atoi(Cmd.Argv(2)); // write a packet full of data while (SV_MAIN.sv_client.netchan.message.cursize < Defines.MAX_MSGLEN / 2 && start < Defines.MAX_CONFIGSTRINGS) { if (SV_INIT.sv.configstrings[start] != null && SV_INIT.sv.configstrings[start].length() != 0) { MSG.WriteByte(SV_MAIN.sv_client.netchan.message, Defines.svc_configstring); MSG.WriteShort(SV_MAIN.sv_client.netchan.message, start); MSG.WriteString(SV_MAIN.sv_client.netchan.message, SV_INIT.sv.configstrings[start]); } start++; } // send next command if (start == Defines.MAX_CONFIGSTRINGS) { MSG.WriteByte(SV_MAIN.sv_client.netchan.message, Defines.svc_stufftext); MSG.WriteString(SV_MAIN.sv_client.netchan.message, "cmd baselines " + SV_INIT.svs.spawncount + " 0\n"); } else { MSG.WriteByte(SV_MAIN.sv_client.netchan.message, Defines.svc_stufftext); MSG.WriteString(SV_MAIN.sv_client.netchan.message, "cmd configstrings " + SV_INIT.svs.spawncount + " " + start + "\n"); } } /* * ================== SV_Baselines_f ================== */ public static void SV_Baselines_f() { int start; entity_state_t nullstate; entity_state_t base; Com.DPrintf("Baselines() from " + SV_MAIN.sv_client.name + "\n"); if (SV_MAIN.sv_client.state != Defines.cs_connected) { Com.Printf("baselines not valid -- already spawned\n"); return; } // handle the case of a level changing while a client was connecting if (Lib.atoi(Cmd.Argv(1)) != SV_INIT.svs.spawncount) { Com.Printf("SV_Baselines_f from different level\n"); SV_New_f(); return; } start = Lib.atoi(Cmd.Argv(2)); //memset (&nullstate, 0, sizeof(nullstate)); nullstate = new entity_state_t(null); // write a packet full of data while (SV_MAIN.sv_client.netchan.message.cursize < Defines.MAX_MSGLEN / 2 && start < Defines.MAX_EDICTS) { base = SV_INIT.sv.baselines[start]; if (base.modelindex != 0 || base.sound != 0 || base.effects != 0) { MSG.WriteByte(SV_MAIN.sv_client.netchan.message, Defines.svc_spawnbaseline); MSG.WriteDeltaEntity(nullstate, base, SV_MAIN.sv_client.netchan.message, true, true); } start++; } // send next command if (start == Defines.MAX_EDICTS) { MSG.WriteByte(SV_MAIN.sv_client.netchan.message, Defines.svc_stufftext); MSG.WriteString(SV_MAIN.sv_client.netchan.message, "precache " + SV_INIT.svs.spawncount + "\n"); } else { MSG.WriteByte(SV_MAIN.sv_client.netchan.message, Defines.svc_stufftext); MSG.WriteString(SV_MAIN.sv_client.netchan.message, "cmd baselines " + SV_INIT.svs.spawncount + " " + start + "\n"); } } /* * ================== SV_Begin_f ================== */ public static void SV_Begin_f() { Com.DPrintf("Begin() from " + SV_MAIN.sv_client.name + "\n"); // handle the case of a level changing while a client was connecting if (Lib.atoi(Cmd.Argv(1)) != SV_INIT.svs.spawncount) { Com.Printf("SV_Begin_f from different level\n"); SV_New_f(); return; } SV_MAIN.sv_client.state = Defines.cs_spawned; // call the game begin function PlayerClient.ClientBegin(SV_USER.sv_player); Cbuf.InsertFromDefer(); } //============================================================================= /* * ================== SV_NextDownload_f ================== */ public static void SV_NextDownload_f() { int r; int percent; int size; if (SV_MAIN.sv_client.download == null) return; r = SV_MAIN.sv_client.downloadsize - SV_MAIN.sv_client.downloadcount; if (r > 1024) r = 1024; MSG.WriteByte(SV_MAIN.sv_client.netchan.message, Defines.svc_download); MSG.WriteShort(SV_MAIN.sv_client.netchan.message, r); SV_MAIN.sv_client.downloadcount += r; size = SV_MAIN.sv_client.downloadsize; if (size == 0) size = 1; percent = SV_MAIN.sv_client.downloadcount * 100 / size; MSG.WriteByte(SV_MAIN.sv_client.netchan.message, percent); SZ.Write(SV_MAIN.sv_client.netchan.message, SV_MAIN.sv_client.download, SV_MAIN.sv_client.downloadcount - r, r); if (SV_MAIN.sv_client.downloadcount != SV_MAIN.sv_client.downloadsize) return; FS.FreeFile(SV_MAIN.sv_client.download); SV_MAIN.sv_client.download = null; } /* * ================== SV_BeginDownload_f ================== */ public static void SV_BeginDownload_f() { String name; int offset = 0; name = Cmd.Argv(1); if (Cmd.Argc() > 2) offset = Lib.atoi(Cmd.Argv(2)); // downloaded offset // hacked by zoid to allow more conrol over download // first off, no .. or global allow check if (name.indexOf("..") != -1 || SV_MAIN.allow_download.value == 0 // leading dot is no good || name.charAt(0) == '.' // leading slash bad as well, must be // in subdir || name.charAt(0) == '/' // next up, skin check || (name.startsWith("players/") && 0 == SV_MAIN.allow_download_players.value) // now // models || (name.startsWith("models/") && 0 == SV_MAIN.allow_download_models.value) // now // sounds || (name.startsWith("sound/") && 0 == SV_MAIN.allow_download_sounds.value) // now maps (note special case for maps, must not be in pak) || (name.startsWith("maps/") && 0 == SV_MAIN.allow_download_maps.value) // MUST // be // in a // subdirectory || name.indexOf('/') == -1) { // don't allow anything with .. // path MSG.WriteByte(SV_MAIN.sv_client.netchan.message, Defines.svc_download); MSG.WriteShort(SV_MAIN.sv_client.netchan.message, -1); MSG.WriteByte(SV_MAIN.sv_client.netchan.message, 0); return; } if (SV_MAIN.sv_client.download != null) FS.FreeFile(SV_MAIN.sv_client.download); SV_MAIN.sv_client.download = FS.LoadFile(name); // rst: this handles loading errors, no message yet visible if (SV_MAIN.sv_client.download == null) { return; } SV_MAIN.sv_client.downloadsize = SV_MAIN.sv_client.download.length; SV_MAIN.sv_client.downloadcount = offset; if (offset > SV_MAIN.sv_client.downloadsize) SV_MAIN.sv_client.downloadcount = SV_MAIN.sv_client.downloadsize; if (SV_MAIN.sv_client.download == null // special check for maps, if it // came from a pak file, don't // allow // download ZOID || (name.startsWith("maps/") && FS.file_from_pak != 0)) { Com.DPrintf("Couldn't download " + name + " to " + SV_MAIN.sv_client.name + "\n"); if (SV_MAIN.sv_client.download != null) { FS.FreeFile(SV_MAIN.sv_client.download); SV_MAIN.sv_client.download = null; } MSG.WriteByte(SV_MAIN.sv_client.netchan.message, Defines.svc_download); MSG.WriteShort(SV_MAIN.sv_client.netchan.message, -1); MSG.WriteByte(SV_MAIN.sv_client.netchan.message, 0); return; } SV_NextDownload_f(); Com.DPrintf("Downloading " + name + " to " + SV_MAIN.sv_client.name + "\n"); } //============================================================================ /* * ================= SV_Disconnect_f * * The client is going to disconnect, so remove the connection immediately * ================= */ public static void SV_Disconnect_f() { // SV_EndRedirect (); SV_MAIN.SV_DropClient(SV_MAIN.sv_client); } /* * ================== SV_ShowServerinfo_f * * Dumps the serverinfo info string ================== */ public static void SV_ShowServerinfo_f() { Info.Print(Cvar.Serverinfo()); } public static void SV_Nextserver() { String v; //ZOID, ss_pic can be nextserver'd in coop mode if (SV_INIT.sv.state == Defines.ss_game || (SV_INIT.sv.state == Defines.ss_pic && 0 == Cvar.VariableValue("coop"))) return; // can't nextserver while playing a normal game SV_INIT.svs.spawncount++; // make sure another doesn't sneak in v = Cvar.VariableString("nextserver"); //if (!v[0]) if (v.length() == 0) Cbuf.AddText("killserver\n"); else { Cbuf.AddText(v); Cbuf.AddText("\n"); } Cvar.Set("nextserver", ""); } /* * ================== SV_Nextserver_f * * A cinematic has completed or been aborted by a client, so move to the * next server, ================== */ public static void SV_Nextserver_f() { if (Lib.atoi(Cmd.Argv(1)) != SV_INIT.svs.spawncount) { Com.DPrintf("Nextserver() from wrong level, from " + SV_MAIN.sv_client.name + "\n"); return; // leftover from last server } Com.DPrintf("Nextserver() from " + SV_MAIN.sv_client.name + "\n"); SV_Nextserver(); } /* * ================== SV_ExecuteUserCommand ================== */ public static void SV_ExecuteUserCommand(String s) { Com.dprintln("SV_ExecuteUserCommand:" + s ); SV_USER.ucmd_t u = null; Cmd.TokenizeString(s.toCharArray(), true); SV_USER.sv_player = SV_MAIN.sv_client.edict; // SV_BeginRedirect (RD_CLIENT); int i = 0; for (; i < SV_USER.ucmds.length; i++) { u = SV_USER.ucmds[i]; if (Cmd.Argv(0).equals(u.name)) { u.r.run(); break; } } if (i == SV_USER.ucmds.length && SV_INIT.sv.state == Defines.ss_game) Cmd.ClientCommand(SV_USER.sv_player); // SV_EndRedirect (); } /* * =========================================================================== * * USER CMD EXECUTION * * =========================================================================== */ public static void SV_ClientThink(client_t cl, usercmd_t cmd) { cl.commandMsec -= cmd.msec & 0xFF; if (cl.commandMsec < 0 && SV_MAIN.sv_enforcetime.value != 0) { Com.DPrintf("commandMsec underflow from " + cl.name + "\n"); return; } PlayerClient.ClientThink(cl.edict, cmd); } /* * =================== SV_ExecuteClientMessage * * The current net_message is parsed for the given client * =================== */ public static void SV_ExecuteClientMessage(client_t cl) { int c; String s; usercmd_t nullcmd = new usercmd_t(); usercmd_t oldest = new usercmd_t(), oldcmd = new usercmd_t(), newcmd = new usercmd_t(); int net_drop; int stringCmdCount; int checksum, calculatedChecksum; int checksumIndex; boolean move_issued; int lastframe; SV_MAIN.sv_client = cl; SV_USER.sv_player = SV_MAIN.sv_client.edict; // only allow one move command move_issued = false; stringCmdCount = 0; while (true) { if (Globals.net_message.readcount > Globals.net_message.cursize) { Com.Printf("SV_ReadClientMessage: bad read:\n"); Com.Printf(Lib.hexDump(Globals.net_message.data, 32, false)); SV_MAIN.SV_DropClient(cl); return; } c = MSG.ReadByte(Globals.net_message); if (c == -1) break; switch (c) { default: Com.Printf("SV_ReadClientMessage: unknown command char\n"); SV_MAIN.SV_DropClient(cl); return; case Defines.clc_nop: break; case Defines.clc_userinfo: cl.userinfo = MSG.ReadString(Globals.net_message); SV_MAIN.SV_UserinfoChanged(cl); break; case Defines.clc_move: if (move_issued) return; // someone is trying to cheat... move_issued = true; checksumIndex = Globals.net_message.readcount; checksum = MSG.ReadByte(Globals.net_message); lastframe = MSG.ReadLong(Globals.net_message); if (lastframe != cl.lastframe) { cl.lastframe = lastframe; if (cl.lastframe > 0) { cl.frame_latency[cl.lastframe & (Defines.LATENCY_COUNTS - 1)] = SV_INIT.svs.realtime - cl.frames[cl.lastframe & Defines.UPDATE_MASK].senttime; } } //memset (nullcmd, 0, sizeof(nullcmd)); nullcmd = new usercmd_t(); MSG.ReadDeltaUsercmd(Globals.net_message, nullcmd, oldest); MSG.ReadDeltaUsercmd(Globals.net_message, oldest, oldcmd); MSG.ReadDeltaUsercmd(Globals.net_message, oldcmd, newcmd); if (cl.state != Defines.cs_spawned) { cl.lastframe = -1; break; } // if the checksum fails, ignore the rest of the packet calculatedChecksum = Com.BlockSequenceCRCByte( Globals.net_message.data, checksumIndex + 1, Globals.net_message.readcount - checksumIndex - 1, cl.netchan.incoming_sequence); if ((calculatedChecksum & 0xff) != checksum) { Com.DPrintf("Failed command checksum for " + cl.name + " (" + calculatedChecksum + " != " + checksum + ")/" + cl.netchan.incoming_sequence + "\n"); return; } if (0 == SV_MAIN.sv_paused.value) { net_drop = cl.netchan.dropped; if (net_drop < 20) { //if (net_drop > 2) // Com.Printf ("drop %i\n", net_drop); while (net_drop > 2) { SV_ClientThink(cl, cl.lastcmd); net_drop--; } if (net_drop > 1) SV_ClientThink(cl, oldest); if (net_drop > 0) SV_ClientThink(cl, oldcmd); } SV_ClientThink(cl, newcmd); } // copy. cl.lastcmd.set(newcmd); break; case Defines.clc_stringcmd: s = MSG.ReadString(Globals.net_message); // malicious users may try using too many string commands if (++stringCmdCount < SV_USER.MAX_STRINGCMDS) SV_ExecuteUserCommand(s); if (cl.state == Defines.cs_zombie) return; // disconnect command break; } } } }