diff options
Diffstat (limited to 'src/jake2/client/CL.java')
-rw-r--r-- | src/jake2/client/CL.java | 1625 |
1 files changed, 1625 insertions, 0 deletions
diff --git a/src/jake2/client/CL.java b/src/jake2/client/CL.java new file mode 100644 index 0000000..58ce048 --- /dev/null +++ b/src/jake2/client/CL.java @@ -0,0 +1,1625 @@ +/* + * CL.java + * Copyright (C) 2004 + * + * $Id: CL.java,v 1.1 2004-07-07 19:58:33 hzi Exp $ + */ +/* +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. + +*/ +package jake2.client; + +import jake2.Defines; +import jake2.Globals; +import jake2.game.*; +import jake2.qcommon.*; +import jake2.server.SV_MAIN; +import jake2.sys.*; +import jake2.util.Vargs; + +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; + +/** + * CL + */ +public final class CL extends CL_pred { + + //// cl_main.c -- client main loop + + /* + ==================== + CL_WriteDemoMessage + + Dumps the current net message, prefixed by the length + ==================== + */ + static void WriteDemoMessage() { + int swlen; + + // the first eight bytes are just packet sequencing stuff + swlen = net_message.cursize - 8; + + try { + cls.demofile.writeInt(swlen); + //fwrite (&swlen, 4, 1, cls.demofile); + cls.demofile.write(net_message.data, 8, swlen); + //fwrite (net_message.data+8, len, 1, cls.demofile); + } catch (IOException e) {} + + } + + /* + ==================== + CL_Stop_f + + stop recording a demo + ==================== + */ + static xcommand_t Stop_f = new xcommand_t() { + public void execute() { + try { + + int len; + + if (!cls.demorecording) { + Com.Printf("Not recording a demo.\n"); + return; + } + + // finish up + len = -1; + cls.demofile.writeInt(len); + cls.demofile.close(); + cls.demofile = null; + cls.demorecording = false; + Com.Printf("Stopped demo.\n"); + + } + catch (IOException e) { + } + } + }; + + /* + ==================== + CL_Record_f + + record <demoname> + + Begins recording a demo from the current position + ==================== + */ + private static entity_state_t nullstate = new entity_state_t(null); + static xcommand_t Record_f = new xcommand_t() { + public void execute() { + try { + String name; + byte buf_data[] = new byte[MAX_MSGLEN]; + sizebuf_t buf = new sizebuf_t(); + int i; + int len; + entity_state_t ent; + + if (Cmd.Argc() != 2) { + Com.Printf("record <demoname>\n"); + return; + } + + if (cls.demorecording) { + Com.Printf("Already recording.\n"); + return; + } + + if (cls.state != ca_active) { + Com.Printf("You must be in a level to record.\n"); + return; + } + + // + // open the demo file + // + name = FS.Gamedir() + "/demos/" + Cmd.Argv(1) + ".dm2"; + + Com.Printf("recording to " + name + ".\n"); + FS.CreatePath(name); + cls.demofile = new RandomAccessFile(name, "rw"); + if (cls.demofile == null) { + Com.Printf("ERROR: couldn't open.\n"); + return; + } + cls.demorecording = true; + + // don't start saving messages until a non-delta compressed message is received + cls.demowaiting = true; + + // + // write out messages to hold the startup information + // + SZ.Init(buf, buf_data, MAX_MSGLEN); + + // send the serverdata + MSG.WriteByte(buf, svc_serverdata); + MSG.WriteInt(buf, PROTOCOL_VERSION); + MSG.WriteInt(buf, 0x10000 + cl.servercount); + MSG.WriteByte(buf, 1); // demos are always attract loops + MSG.WriteString(buf, cl.gamedir); + MSG.WriteShort(buf, cl.playernum); + + MSG.WriteString(buf, cl.configstrings[CS_NAME]); + + // configstrings + for (i = 0; i < MAX_CONFIGSTRINGS; i++) { + if (cl.configstrings[i].length()>0) { + if (buf.cursize + cl.configstrings[i].length() + 32 > buf.maxsize) { // write it out + //len = LittleLong(buf.cursize); + //fwrite(& len, 4, 1, cls.demofile); + cls.demofile.writeInt(buf.cursize); + //fwrite(buf.data, buf.cursize, 1, cls.demofile); + cls.demofile.write(buf.data, 0, buf.cursize); + buf.cursize = 0; + } + + MSG.WriteByte(buf, svc_configstring); + MSG.WriteShort(buf, i); + MSG.WriteString(buf, cl.configstrings[i]); + } + + } + + // baselines + //memset( nullstate, 0, sizeof(nullstate)); + for (i = 0; i < MAX_EDICTS; i++) { + ent = cl_entities[i].baseline; + if (ent.modelindex == 0) + continue; + + if (buf.cursize + 64 > buf.maxsize) { // write it out + //len = LittleLong(buf.cursize); + //fwrite(& len, 4, 1, cls.demofile); + cls.demofile.writeInt(buf.cursize); + //fwrite(buf.data, buf.cursize, 1, cls.demofile); + cls.demofile.write(buf.data, 0, buf.cursize); + buf.cursize = 0; + } + + MSG.WriteByte(buf, svc_spawnbaseline); + MSG.WriteDeltaEntity(nullstate, cl_entities[i].baseline, buf, true, true); + } + + MSG.WriteByte(buf, svc_stufftext); + MSG.WriteString(buf, "precache\n"); + + // write it to the demo file + + //len = LittleLong(buf.cursize); + //fwrite(& len, 4, 1, cls.demofile); + cls.demofile.writeInt(buf.cursize); + //fwrite(buf.data, buf.cursize, 1, cls.demofile); + cls.demofile.write(buf.data, 0, buf.cursize); + // the rest of the demo file will be individual frames + + } + catch (IOException e) { + } + } + }; + + /* + ================== + CL_ForwardToServer_f + ================== + */ + static xcommand_t ForwardToServer_f = new xcommand_t() { + public void execute() { + if (cls.state != ca_connected && cls.state != ca_active) { + Com.Printf("Can't \"" + Cmd.Argv(0) + "\", not connected\n"); + return; + } + + // don't forward the first argument + if (Cmd.Argc() > 1) { + MSG.WriteByte(cls.netchan.message, clc_stringcmd); + SZ.Print(cls.netchan.message, Cmd.Args()); + } + } + }; + + /* + ================== + CL_Pause_f + ================== + */ + static xcommand_t Pause_f = new xcommand_t() { + public void execute() { + // never pause in multiplayer + + if (Cvar.VariableValue("maxclients") > 1 || Com.ServerState() == 0) { + Cvar.SetValue("paused", 0); + return; + } + + Cvar.SetValue("paused", cl_paused.value); + } + }; + + /* + ================== + CL_Quit_f + ================== + */ + static xcommand_t Quit_f = new xcommand_t() { + public void execute() { + Disconnect(); + Com.Quit(); + } + }; + + /* + ======================= + CL_SendConnectPacket + + We have gotten a challenge from the server, so try and + connect. + ====================== + */ + static void SendConnectPacket() { + netadr_t adr = new netadr_t(); + int port; + + if (!NET.StringToAdr(cls.servername, adr)) { + Com.Printf("Bad server address\n"); + cls.connect_time = 0; + return; + } + if (adr.port == 0) + adr.port = PORT_SERVER; + // adr.port = BigShort(PORT_SERVER); + + port = (int) Cvar.VariableValue("qport"); + userinfo_modified = false; + + Netchan.OutOfBandPrint( + NS_CLIENT, + adr, + "connect " + PROTOCOL_VERSION + " " + port + " " + cls.challenge + " \"" + Cvar.Userinfo() + "\"\n"); + } + + /* + ================= + CL_CheckForResend + + Resend a connect message if the last one has timed out + ================= + */ + static void CheckForResend() { + netadr_t adr = new netadr_t(); + + // if the local server is running and we aren't + // then connect + if (cls.state == ca_disconnected && Com.ServerState() != 0) { + cls.state = ca_connecting; + cls.servername = "localhost"; + // we don't need a challenge on the localhost + CL.SendConnectPacket(); + return; + } + + // resend if we haven't gotten a reply yet + if (cls.state != ca_connecting) + return; + + if (cls.realtime - cls.connect_time < 3000) + return; + + if (!NET.StringToAdr(cls.servername, adr)) { + Com.Printf("Bad server address\n"); + cls.state = ca_disconnected; + return; + } + if (adr.port == 0) + // adr.port = BigShort(PORT_SERVER); + adr.port = PORT_SERVER; + + cls.connect_time = cls.realtime; // for retransmit requests + + Com.Printf("Connecting to " + cls.servername + "...\n"); + + Netchan.OutOfBandPrint(NS_CLIENT, adr, "getchallenge\n"); + } + + /* + ================ + CL_Connect_f + + ================ + */ + static xcommand_t Connect_f = new xcommand_t() { + public void execute() { + String server; + + if (Cmd.Argc() != 2) { + Com.Printf("usage: connect <server>\n"); + return; + } + + if (Com.ServerState() != 0) { + // if running a local server, kill it and reissue + SV_MAIN.SV_Shutdown("Server quit\n", false); + } else { + CL.Disconnect(); + } + + server = Cmd.Argv(1); + + NET.Config(true); // allow remote + + CL.Disconnect(); + + cls.state = ca_connecting; + //strncpy (cls.servername, server, sizeof(cls.servername)-1); + cls.servername = server; + cls.connect_time = -99999; + // CL_CheckForResend() will fire immediately + } + }; + + /* + ===================== + CL_Rcon_f + + Send the rest of the command line over as + an unconnected command. + ===================== + */ + static xcommand_t Rcon_f = new xcommand_t() { + public void execute() { + StringBuffer message = new StringBuffer(1024); + int i; + netadr_t to = new netadr_t(); + + if (rcon_client_password.string == null) { + Com.Printf("You must set 'rcon_password' before\nissuing an rcon command.\n"); + return; + } + + message.append((char)255); + message.append((char)255); + message.append((char)255); + message.append((char)255); + + NET.Config(true); // allow remote + + //strcat (message, "rcon "); + message.append("rcon "); + + //strcat (message, rcon_client_password.string); + message.append(rcon_client_password.string); + //strcat (message, " "); + message.append(" "); + + for (i = 1; i < Cmd.Argc(); i++) { + //strcat (message, Cmd.Argv(i)); + message.append(Cmd.Argv(i)); + //strcat (message, " "); + message.append(" "); + } + + if (cls.state >= ca_connected) + to = cls.netchan.remote_address; + else { + if (strlen(rcon_address.string) == 0) { + Com.Printf( + "You must either be connected,\nor set the 'rcon_address' cvar\nto issue rcon commands\n"); + + return; + } + NET.StringToAdr(rcon_address.string, to); + if (to.port == 0) + //to.port = BigShort (PORT_SERVER); + to.port = PORT_SERVER; + } + message.append('\0'); + String b = message.toString(); + NET.SendPacket(NS_CLIENT, b.length(), b.getBytes(), to); + } + }; + + /* + ===================== + CL_ClearState + + ===================== + */ + + static void ClearState() { + S.StopAllSounds(); + CL.ClearEffects(); + CL.ClearTEnts(); + + // wipe the entire cl structure + + cl = new client_state_t(); + for (int i = 0; i < cl_entities.length; i++) { + cl_entities[i] = new centity_t(); + } + + SZ.Clear(cls.netchan.message); + } + + /* + ===================== + CL_Disconnect + + Goes from a connected state to full screen console state + Sends a disconnect message to the server + This is also called on Com_Error, so it shouldn't cause any errors + ===================== + */ + + static void Disconnect() { + + String fin; + + if (cls.state == ca_disconnected) + return; + + if (cl_timedemo != null && cl_timedemo.value != 0.0f) { + int time; + + time = (int) (Sys.Milliseconds() - cl.timedemo_start); + if (time > 0) + Com.Printf( + "%i frames, %3.1f seconds: %3.1f fps\n", + new Vargs(3).add(cl.timedemo_frames).add(time / 1000.0).add(cl.timedemo_frames * 1000.0 / time)); + } + + VectorClear(cl.refdef.blend); + //re.CinematicSetPalette(null); + + Menu.ForceMenuOff(); + + cls.connect_time = 0; + + // SCR.StopCinematic(); + + if (cls.demorecording) + CL.Stop_f.execute(); + + // send a disconnect message to the server + fin = (char) clc_stringcmd + "disconnect"; + Netchan.Transmit(cls.netchan, fin.length(), fin.getBytes()); + Netchan.Transmit(cls.netchan, fin.length(), fin.getBytes()); + Netchan.Transmit(cls.netchan, fin.length(), fin.getBytes()); + + CL.ClearState(); + + // stop download + if (cls.download != null) { + fclose(cls.download); + cls.download = null; + // fclose(cls.download); + // cls.download = NULL; + } + + cls.state = ca_disconnected; + } + + static xcommand_t Disconnect_f = new xcommand_t() { + public void execute() { + Com.Error(ERR_DROP, "Disconnected from server"); + } + }; + + /* + ================= + CL_Changing_f + + Just sent as a hint to the client that they should + drop to full console + ================= + */ + static xcommand_t Changing_f = new xcommand_t() { + public void execute() { + //ZOID + //if we are downloading, we don't change! + // This so we don't suddenly stop downloading a map + + if (cls.download != null) + return; + + SCR.BeginLoadingPlaque(); + cls.state = ca_connected; // not active anymore, but not disconnected + Com.Printf("\nChanging map...\n"); + } + }; + + /* + ================= + CL_Reconnect_f + + The server is changing levels + ================= + */ + static xcommand_t Reconnect_f = new xcommand_t() { + public void execute() { + //ZOID + //if we are downloading, we don't change! This so we don't suddenly stop downloading a map + if (cls.download != null) + return; + + S.StopAllSounds(); + if (cls.state == ca_connected) { + Com.Printf("reconnecting...\n"); + cls.state = ca_connected; + MSG.WriteChar(cls.netchan.message, clc_stringcmd); + MSG.WriteString(cls.netchan.message, "new"); + return; + } + + if (cls.servername != null) { + if (cls.state >= ca_connected) { + CL.Disconnect(); + cls.connect_time = cls.realtime - 1500; + } else + cls.connect_time = -99999; // fire immediately + + cls.state = ca_connecting; + Com.Printf("reconnecting...\n"); + } + } + }; + + /* + ================= + CL_ParseStatusMessage + + Handle a reply from a ping + ================= + */ + static void ParseStatusMessage() { + String s; + + s = MSG.ReadString(net_message); + + Com.Printf(s + "\n"); + Menu.AddToServerList(net_from, s); + } + + /* + ================= + CL_PingServers_f + ================= + */ + static xcommand_t PingServers_f = new xcommand_t() { + public void execute() { + int i; + netadr_t adr = new netadr_t(); + //char name[32]; + String name; + String adrstring; + cvar_t noudp; + cvar_t noipx; + + NET.Config(true); // allow remote + + // send a broadcast packet + Com.Printf("pinging broadcast...\n"); + + noudp = Cvar.Get("noudp", "0", CVAR_NOSET); + if (noudp.value == 0.0f) { + adr.type = NA_BROADCAST; + adr.port = PORT_SERVER; + //adr.port = BigShort(PORT_SERVER); + Netchan.OutOfBandPrint(NS_CLIENT, adr, "info " + PROTOCOL_VERSION); + } + + // we use no IPX + noipx = Cvar.Get("noipx", "1", CVAR_NOSET); + if (noipx.value == 0.0f) { + adr.type = NA_BROADCAST_IPX; + //adr.port = BigShort(PORT_SERVER); + adr.port = PORT_SERVER; + Netchan.OutOfBandPrint(NS_CLIENT, adr, "info " + PROTOCOL_VERSION); + } + + // send a packet to each address book entry + for (i = 0; i < 16; i++) { + //Com_sprintf (name, sizeof(name), "adr%i", i); + name = "adr" + i; + adrstring = Cvar.VariableString(name); + if (adrstring == null || adrstring.length() == 0) + continue; + + Com.Printf("pinging " + adrstring + "...\n"); + if (!NET.StringToAdr(adrstring, adr)) { + Com.Printf("Bad address: " + adrstring + "\n"); + continue; + } + if (adr.port == 0) + //adr.port = BigShort(PORT_SERVER); + adr.port = PORT_SERVER; + Netchan.OutOfBandPrint(NS_CLIENT, adr, "info " + PROTOCOL_VERSION); + } + } + }; + + + /* + ================= + CL_Skins_f + + Load or download any custom player skins and models + ================= + */ + static xcommand_t Skins_f = new xcommand_t() { + public void execute() { + int i; + + for (i = 0; i < MAX_CLIENTS; i++) { + if (cl.configstrings[CS_PLAYERSKINS + i] == null) + continue; + Com.Printf("client " + i + ": " + cl.configstrings[CS_PLAYERSKINS + i] + "\n"); + SCR.UpdateScreen(); + Sys.SendKeyEvents(); // pump message loop + CL.ParseClientinfo(i); + } + } + }; + + /* + ================= + CL_ConnectionlessPacket + + Responses to broadcasts, etc + ================= + */ + static void ConnectionlessPacket() { + String s; + String c; + + MSG.BeginReading(net_message); + MSG.ReadLong(net_message); // skip the -1 + + s = MSG.ReadStringLine(net_message); + + Cmd.TokenizeString(s.toCharArray(), false); + + c = Cmd.Argv(0); + + Com.Printf(NET.AdrToString(net_from) + ": " + c + " \n"); + + // server connection + if (c.equals("client_connect")) { + if (cls.state == ca_connected) { + Com.Printf("Dup connect received. Ignored.\n"); + return; + } + Netchan.Setup(NS_CLIENT, cls.netchan, net_from, cls.quakePort); + MSG.WriteChar(cls.netchan.message, clc_stringcmd); + MSG.WriteString(cls.netchan.message, "new"); + cls.state = ca_connected; + return; + } + + // server responding to a status broadcast + if (c.equals("info")) { + CL.ParseStatusMessage(); + return; + } + + // remote command from gui front end + if (c.equals ("cmd")) { + if (!NET.IsLocalAddress(net_from)) { + Com.Printf("Command packet from remote host. Ignored.\n"); + return; + } + s = MSG.ReadString(net_message); + Cbuf.AddText(s); + Cbuf.AddText("\n"); + return; + } + // print command from somewhere + if (c.equals("print")) { + s = MSG.ReadString(net_message); + Com.Printf(s); + return; + } + + // ping from somewhere + if (c.equals("ping")) { + Netchan.OutOfBandPrint(NS_CLIENT, net_from, "ack"); + return; + } + + // challenge from the server we are connecting to + if (c.equals("challenge")) { + cls.challenge = Integer.parseInt(Cmd.Argv(1)); + CL.SendConnectPacket(); + return; + } + + // echo request from server + if (c.equals("echo")) { + Netchan.OutOfBandPrint(NS_CLIENT, net_from, Cmd.Argv(1)); + return; + } + + Com.Printf("Unknown command.\n"); + } + + /* + ================= + CL_DumpPackets + + A vain attempt to help bad TCP stacks that cause problems + when they overflow + ================= + */ + static void DumpPackets() { + while (NET.GetPacket(NS_CLIENT, net_from, net_message)) { + Com.Printf("dumping a packet\n"); + } + } + + /* + ================= + CL_ReadPackets + ================= + */ + static void ReadPackets() { + while (NET.GetPacket(NS_CLIENT, net_from, net_message)) { + + // + // remote command packet + // + if (net_message.data[0] == -1 && net_message.data[1] == -1 + && net_message.data[2] == -1 && net_message.data[3] == -1) { + // if (*(int *)net_message.data == -1) + CL.ConnectionlessPacket(); + continue; + } + + if (cls.state == ca_disconnected || cls.state == ca_connecting) + continue; // dump it if not connected + + if (net_message.cursize < 8) { + Com.Printf(NET.AdrToString(net_from) + ": Runt packet\n"); + continue; + } + + // + // packet from server + // + if (!NET.CompareAdr(net_from, cls.netchan.remote_address)) { + Com.DPrintf(NET.AdrToString(net_from) + ":sequenced packet without connection\n"); + continue; + } + if (!Netchan.Process(cls.netchan, net_message)) + continue; // wasn't accepted for some reason + ParseServerMessage(); + } + + // + // check timeout + // + if (cls.state >= ca_connected && cls.realtime - cls.netchan.last_received > cl_timeout.value * 1000) { + if (++cl.timeoutcount > 5) // timeoutcount saves debugger + { + Com.Printf("\nServer connection timed out.\n"); + CL.Disconnect(); + return; + } + } + else + cl.timeoutcount = 0; + } + + // ============================================================================= + + /* + ============== + CL_FixUpGender_f + ============== + */ + static void FixUpGender() { + + String sk; + + if (gender_auto.value != 0.0f) { + + if (gender.modified) { + // was set directly, don't override the user + gender.modified = false; + return; + } + + sk = skin.string; + if (sk.startsWith("male") || sk.startsWith("cyborg")) + Cvar.Set("gender", "male"); + else if (sk.startsWith("female") || sk.startsWith("crackhor")) + Cvar.Set("gender", "female"); + else + Cvar.Set("gender", "none"); + gender.modified = false; + } + } + + /* + ============== + CL_Userinfo_f + ============== + */ + static xcommand_t Userinfo_f = new xcommand_t() { + public void execute() { + Com.Printf("User info settings:\n"); + Info.Print(Cvar.Userinfo()); + } + }; + + /* + ================= + CL_Snd_Restart_f + + Restart the sound subsystem so it can pick up + new parameters and flush all sounds + ================= + */ + static xcommand_t Snd_Restart_f = new xcommand_t() { + public void execute() { + S.Shutdown(); + S.Init(); + CL.RegisterSounds(); + } + }; + + static int precache_check; // for autodownload of precache items + static int precache_spawncount; + static int precache_tex; + static int precache_model_skin; + + static byte precache_model[]; // used for skin checking in alias models + + public static final int PLAYER_MULT = 5; + + // ENV_CNT is map load, ENV_CNT+1 is first env map + public static final int ENV_CNT = (CS_PLAYERSKINS + MAX_CLIENTS * PLAYER_MULT); + public static final int TEXTURE_CNT = (ENV_CNT + 13); + + static String env_suf[] = { "rt", "bk", "lf", "ft", "up", "dn" }; + + public static void RequestNextDownload() { + int map_checksum = 0; // for detecting cheater maps + //char fn[MAX_OSPATH]; + String fn; + + qfiles.dmdl_t pheader; + + if (cls.state != ca_connected) + return; + + if (SV_MAIN.allow_download.value == 0 && precache_check < ENV_CNT) + precache_check = ENV_CNT; + + // ZOID + if (precache_check == CS_MODELS) { // confirm map + precache_check = CS_MODELS + 2; // 0 isn't used + if (SV_MAIN.allow_download_maps.value != 0) + if (!CheckOrDownloadFile(cl.configstrings[CS_MODELS + 1])) + return; // started a download + } + if (precache_check >= CS_MODELS && precache_check < CS_MODELS + MAX_MODELS) { + if (SV_MAIN.allow_download_models.value != 0) { + while (precache_check < CS_MODELS + MAX_MODELS && cl.configstrings[precache_check].length() > 0) { + if (cl.configstrings[precache_check].charAt(0) == '*' || cl.configstrings[precache_check].charAt(0) == '#') { + precache_check++; + continue; + } + if (precache_model_skin == 0) { + if (!CheckOrDownloadFile(cl.configstrings[precache_check])) { + precache_model_skin = 1; + return; // started a download + } + precache_model_skin = 1; + } + + // checking for skins in the model + if (precache_model == null) { + + precache_model = FS.LoadFile(cl.configstrings[precache_check]); + if (precache_model == null) { + precache_model_skin = 0; + precache_check++; + continue; // couldn't load it + } + ByteBuffer bb = ByteBuffer.wrap(precache_model); + int header = Globals.endian.LittleLong(bb.getInt()); + + if (header != qfiles.IDALIASHEADER) { + // not an alias model + FS.FreeFile(precache_model); + precache_model = null; + precache_model_skin = 0; + precache_check++; + continue; + } + pheader = new qfiles.dmdl_t(ByteBuffer.wrap(precache_model)); + if (Globals.endian.LittleLong(pheader.version) != ALIAS_VERSION) { + precache_check++; + precache_model_skin = 0; + continue; // couldn't load it + } + } + + pheader = new qfiles.dmdl_t(ByteBuffer.wrap(precache_model)); + + int num_skins = Globals.endian.LittleLong(pheader.num_skins); + + while (precache_model_skin - 1 < num_skins) { + Com.Printf("critical code section because of endian mess!"); + + String name = + new String( + precache_model, + Globals.endian.LittleLong(pheader.ofs_skins) + (precache_model_skin - 1) * MAX_SKINNAME, + MAX_SKINNAME * num_skins); + + if (!CheckOrDownloadFile(name)) { + precache_model_skin++; + return; // started a download + } + precache_model_skin++; + } + if (precache_model != null) { + FS.FreeFile(precache_model); + precache_model = null; + } + precache_model_skin = 0; + precache_check++; + } + } + precache_check = CS_SOUNDS; + } + if (precache_check >= CS_SOUNDS && precache_check < CS_SOUNDS + MAX_SOUNDS) { + if (SV_MAIN.allow_download_sounds.value != 0) { + if (precache_check == CS_SOUNDS) + precache_check++; // zero is blank + while (precache_check < CS_SOUNDS + MAX_SOUNDS && cl.configstrings[precache_check].length() > 0) { + if (cl.configstrings[precache_check].charAt(0) == '*') { + precache_check++; + continue; + } + fn = "sound/" + cl.configstrings[precache_check++]; + if (!CheckOrDownloadFile(fn)) + return; // started a download + } + } + precache_check = CS_IMAGES; + } + if (precache_check >= CS_IMAGES && precache_check < CS_IMAGES + MAX_IMAGES) { + if (precache_check == CS_IMAGES) + precache_check++; // zero is blank + + while (precache_check < CS_IMAGES + MAX_IMAGES && cl.configstrings[precache_check].length() > 0) { + fn = "pics/" + cl.configstrings[precache_check++] + ".pcx"; + if (!CheckOrDownloadFile(fn)) + return; // started a download + } + precache_check = CS_PLAYERSKINS; + } + // skins are special, since a player has three things to download: + // model, weapon model and skin + // so precache_check is now *3 + if (precache_check >= CS_PLAYERSKINS && precache_check < CS_PLAYERSKINS + MAX_CLIENTS * PLAYER_MULT) { + if (SV_MAIN.allow_download_players.value != 0) { + while (precache_check < CS_PLAYERSKINS + MAX_CLIENTS * PLAYER_MULT) { + + int i, n; + //char model[MAX_QPATH], skin[MAX_QPATH], * p; + String model, skin, p; + + i = (precache_check - CS_PLAYERSKINS) / PLAYER_MULT; + n = (precache_check - CS_PLAYERSKINS) % PLAYER_MULT; + + if (cl.configstrings[CS_PLAYERSKINS + i].length() == 0) { + precache_check = CS_PLAYERSKINS + (i + 1) * PLAYER_MULT; + continue; + } + + int pos = cl.configstrings[CS_PLAYERSKINS + i].indexOf('\\'); + if (pos != -1) + pos++; + else + pos = 0; + + model = cl.configstrings[CS_PLAYERSKINS + i].substring(pos); + + pos = model.indexOf('/'); + + if (pos == -1) + pos = model.indexOf('\\'); + + if (pos != -1) { + skin = model.substring(pos + 1); + } + else + skin = ""; + + switch (n) { + case 0 : // model + fn = "players/" + model + "/tris.md2"; + if (!CheckOrDownloadFile(fn)) { + precache_check = CS_PLAYERSKINS + i * PLAYER_MULT + 1; + return; // started a download + } + n++; + /*FALL THROUGH*/ + + case 1 : // weapon model + fn = "players/" + model + "/weapon.md2"; + if (!CheckOrDownloadFile(fn)) { + precache_check = CS_PLAYERSKINS + i * PLAYER_MULT + 2; + return; // started a download + } + n++; + /*FALL THROUGH*/ + + case 2 : // weapon skin + fn = "players/" + model + "/weapon.pcx"; + if (!CheckOrDownloadFile(fn)) { + precache_check = CS_PLAYERSKINS + i * PLAYER_MULT + 3; + return; // started a download + } + n++; + /*FALL THROUGH*/ + + case 3 : // skin + fn = "players/" + model + "/" + skin + ".pcx"; + if (!CheckOrDownloadFile(fn)) { + precache_check = CS_PLAYERSKINS + i * PLAYER_MULT + 4; + return; // started a download + } + n++; + /*FALL THROUGH*/ + + case 4 : // skin_i + fn = "players/" + model + "/" + skin + "_i.pcx"; + if (!CheckOrDownloadFile(fn)) { + precache_check = CS_PLAYERSKINS + i * PLAYER_MULT + 5; + return; // started a download + } + // move on to next model + precache_check = CS_PLAYERSKINS + (i + 1) * PLAYER_MULT; + } + } + } + // precache phase completed + precache_check = ENV_CNT; + } + + if (precache_check == ENV_CNT) { + precache_check = ENV_CNT + 1; + + CM.intwrap iw = new CM.intwrap(map_checksum); + + CM.CM_LoadMap(cl.configstrings[CS_MODELS + 1], true, iw); + map_checksum = iw.i; +// TODO MD4 check abgeklemmt +// if ((map_checksum ^ atoi(cl.configstrings[CS_MAPCHECKSUM])) != 0) { +// Com.Error( +// ERR_DROP, +// "Local map version differs from server: " + map_checksum + " != '" + cl.configstrings[CS_MAPCHECKSUM] + "'\n"); +// return; +// } + } + + if (precache_check > ENV_CNT && precache_check < TEXTURE_CNT) { + if (SV_MAIN.allow_download.value != 0 && SV_MAIN.allow_download_maps.value != 0) { + while (precache_check < TEXTURE_CNT) { + int n = precache_check++ -ENV_CNT - 1; + + if ((n & 1) != 0) + fn = "env/" + cl.configstrings[CS_SKY] + env_suf[n / 2] + ".pcx"; + else + fn = "env/" + cl.configstrings[CS_SKY] + env_suf[n / 2] + ".tga"; + if (!CheckOrDownloadFile(fn)) + return; // started a download + } + } + precache_check = TEXTURE_CNT; + } + + if (precache_check == TEXTURE_CNT) { + precache_check = TEXTURE_CNT + 1; + precache_tex = 0; + } + + // confirm existance of textures, download any that don't exist + if (precache_check == TEXTURE_CNT + 1) { + // from qcommon/cmodel.c + // extern int numtexinfo; + // extern mapsurface_t map_surfaces[]; + + if (SV_MAIN.allow_download.value != 0 && SV_MAIN.allow_download_maps.value != 0) { + while (precache_tex < CM.numtexinfo) { + //char fn[MAX_OSPATH]; + + fn = "textures/" + CM.map_surfaces[precache_tex++].rname + ".wal"; + if (!CheckOrDownloadFile(fn)) + return; // started a download + } + } + precache_check = TEXTURE_CNT + 999; + } + + // ZOID + CL.RegisterSounds(); + PrepRefresh(); + + MSG.WriteByte(cls.netchan.message, clc_stringcmd); + MSG.WriteString(cls.netchan.message, "begin " + precache_spawncount + "\n"); + } + + /* + ================= + CL_Precache_f + + The server will send this command right + before allowing the client into the server + ================= + */ + static xcommand_t Precache_f = new xcommand_t() { + public void execute() { + /* Yet another hack to let old demos work + the old precache sequence */ + + if (Cmd.Argc() < 2) { + + CM.intwrap iw = new CM.intwrap(0); // for detecting cheater maps + + CM.CM_LoadMap(cl.configstrings[CS_MODELS + 1], true, iw); + int mapchecksum = iw.i ; + CL.RegisterSounds(); + CL.PrepRefresh(); + return; + } + + precache_check = CS_MODELS; + precache_spawncount = atoi(Cmd.Argv(1)); + precache_model = null; + precache_model_skin = 0; + + RequestNextDownload(); + } + }; + + /* + ================= + CL_InitLocal + ================= + */ + public static void InitLocal() { + cls.state = Defines.ca_disconnected; + cls.realtime = Sys.Milliseconds(); + + InitInput(); + + adr0 = Cvar.Get("adr0", "", CVAR_ARCHIVE); + adr1 = Cvar.Get("adr0", "", CVAR_ARCHIVE); + adr2 = Cvar.Get("adr0", "", CVAR_ARCHIVE); + adr3 = Cvar.Get("adr0", "", CVAR_ARCHIVE); + adr4 = Cvar.Get("adr0", "", CVAR_ARCHIVE); + adr5 = Cvar.Get("adr0", "", CVAR_ARCHIVE); + adr6 = Cvar.Get("adr0", "", CVAR_ARCHIVE); + adr7 = Cvar.Get("adr0", "", CVAR_ARCHIVE); + adr8 = Cvar.Get("adr0", "", CVAR_ARCHIVE); + + // + // register our variables + // + cl_stereo_separation = Cvar.Get("cl_stereo_separation", "0.4", CVAR_ARCHIVE); + cl_stereo = Cvar.Get("cl_stereo", "0", 0); + + cl_add_blend = Cvar.Get("cl_blend", "1", 0); + cl_add_lights = Cvar.Get("cl_lights", "1", 0); + cl_add_particles = Cvar.Get("cl_particles", "1", 0); + cl_add_entities = Cvar.Get("cl_entities", "1", 0); + cl_gun = Cvar.Get("cl_gun", "1", 0); + cl_footsteps = Cvar.Get("cl_footsteps", "1", 0); + cl_noskins = Cvar.Get("cl_noskins", "0", 0); + cl_autoskins = Cvar.Get("cl_autoskins", "0", 0); + cl_predict = Cvar.Get("cl_predict", "1", 0); + + cl_maxfps = Cvar.Get("cl_maxfps", "90", 0); + + cl_upspeed = Cvar.Get("cl_upspeed", "200", 0); + cl_forwardspeed = Cvar.Get("cl_forwardspeed", "200", 0); + cl_sidespeed = Cvar.Get("cl_sidespeed", "200", 0); + cl_yawspeed = Cvar.Get("cl_yawspeed", "140", 0); + cl_pitchspeed = Cvar.Get("cl_pitchspeed", "150", 0); + cl_anglespeedkey = Cvar.Get("cl_anglespeedkey", "1.5", 0); + + cl_run = Cvar.Get("cl_run", "0", CVAR_ARCHIVE); + freelook = Cvar.Get("freelook", "0", CVAR_ARCHIVE); + lookspring = Cvar.Get("lookspring", "0", CVAR_ARCHIVE); + lookstrafe = Cvar.Get("lookstrafe", "0", CVAR_ARCHIVE); + sensitivity = Cvar.Get("sensitivity", "3", CVAR_ARCHIVE); + + m_pitch = Cvar.Get("m_pitch", "0.022", CVAR_ARCHIVE); + m_yaw = Cvar.Get("m_yaw", "0.022", 0); + m_forward = Cvar.Get("m_forward", "1", 0); + m_side = Cvar.Get("m_side", "1", 0); + + cl_shownet = Cvar.Get("cl_shownet", "0", 0); + cl_showmiss = Cvar.Get("cl_showmiss", "0", 0); + cl_showclamp = Cvar.Get("showclamp", "0", 0); + cl_timeout = Cvar.Get("cl_timeout", "120", 0); + cl_paused = Cvar.Get("paused", "0", 0); + cl_timedemo = Cvar.Get("timedemo", "0", 0); + + rcon_client_password = Cvar.Get("rcon_password", "", 0); + rcon_address = Cvar.Get("rcon_address", "", 0); + + cl_lightlevel = Cvar.Get("r_lightlevel", "0", 0); + + // + // userinfo + // + info_password = Cvar.Get("password", "", CVAR_USERINFO); + info_spectator = Cvar.Get("spectator", "0", CVAR_USERINFO); + name = Cvar.Get("name", "unnamed", CVAR_USERINFO | CVAR_ARCHIVE); + skin = Cvar.Get("skin", "male/grunt", CVAR_USERINFO | CVAR_ARCHIVE); + rate = Cvar.Get("rate", "25000", CVAR_USERINFO | CVAR_ARCHIVE); // FIXME + msg = Cvar.Get("msg", "1", CVAR_USERINFO | CVAR_ARCHIVE); + hand = Cvar.Get("hand", "0", CVAR_USERINFO | CVAR_ARCHIVE); + fov = Cvar.Get("fov", "90", CVAR_USERINFO | CVAR_ARCHIVE); + gender = Cvar.Get("gender", "male", CVAR_USERINFO | CVAR_ARCHIVE); + gender_auto = Cvar.Get("gender_auto", "1", CVAR_ARCHIVE); + gender.modified = false; // clear this so we know when user sets it manually + + cl_vwep = Cvar.Get("cl_vwep", "1", CVAR_ARCHIVE); + + // + // register our commands + // + Cmd.AddCommand("cmd", ForwardToServer_f); + Cmd.AddCommand("pause", Pause_f); + Cmd.AddCommand("pingservers", PingServers_f); + Cmd.AddCommand("skins", Skins_f); + + Cmd.AddCommand("userinfo", Userinfo_f); + Cmd.AddCommand("snd_restart", Snd_Restart_f); + + Cmd.AddCommand("changing", Changing_f); + Cmd.AddCommand("disconnect", Disconnect_f); + Cmd.AddCommand("record", Record_f); + Cmd.AddCommand("stop", Stop_f); + + Cmd.AddCommand("quit", Quit_f); + + Cmd.AddCommand("connect", Connect_f); + Cmd.AddCommand("reconnect", Reconnect_f); + + Cmd.AddCommand("rcon", Rcon_f); + + Cmd.AddCommand("precache", Precache_f); + + Cmd.AddCommand("download", Download_f); + + // + // forward to server commands + // + // the only thing this does is allow command completion + // to work -- all unknown commands are automatically + // forwarded to the server + Cmd.AddCommand("wave", null); + Cmd.AddCommand("inven", null); + Cmd.AddCommand("kill", null); + Cmd.AddCommand("use", null); + Cmd.AddCommand("drop", null); + Cmd.AddCommand("say", null); + Cmd.AddCommand("say_team", null); + Cmd.AddCommand("info", null); + Cmd.AddCommand("prog", null); + Cmd.AddCommand("give", null); + Cmd.AddCommand("god", null); + Cmd.AddCommand("notarget", null); + Cmd.AddCommand("noclip", null); + Cmd.AddCommand("invuse", null); + Cmd.AddCommand("invprev", null); + Cmd.AddCommand("invnext", null); + Cmd.AddCommand("invdrop", null); + Cmd.AddCommand("weapnext", null); + Cmd.AddCommand("weapprev", null); + + } + + /* + =============== + CL_WriteConfiguration + + Writes key bindings and archived cvars to config.cfg + =============== + */ + static void WriteConfiguration() { + RandomAccessFile f; + String path; + + if (cls.state == ca_uninitialized) + return; + + path = FS.Gamedir() + "/config.cfg"; + f = fopen(path, "rw"); + try + { + f.seek(0); + f.setLength(0); + } + catch (IOException e1) + {} + if (f == null) { + Com.Printf("Couldn't write config.cfg.\n"); + return; + } + try { + f.writeBytes("// generated by quake, do not modify\n"); + } + catch (IOException e) {} + //fprintf (f, "// generated by quake, do not modify\n"); + Key.WriteBindings(f); + fclose(f); + Cvar.WriteVariables(path); + } + + /* + ================== + CL_FixCvarCheats + + ================== + */ + public static class cheatvar_t { + String name; + String value; + cvar_t var; + } + + public static String cheatvarsinfo[][] = { { "timescale", "1" }, { + "timedemo", "0" }, { + "r_drawworld", "1" }, { + "cl_testlights", "0" }, { + "r_fullbright", "0" }, { + "r_drawflat", "0" }, { + "paused", "0" }, { + "fixedtime", "0" }, { + "sw_draworder", "0" }, { + "gl_lightmap", "0" }, { + "gl_saturatelighting", "0" }, { + null, null } + }; + public static cheatvar_t cheatvars[]; + + static { + cheatvars = new cheatvar_t[cheatvarsinfo.length]; + for (int n = 0; n < cheatvarsinfo.length; n++) { + cheatvars[n] = new cheatvar_t(); + cheatvars[n].name = cheatvarsinfo[n][0]; + cheatvars[n].value = cheatvarsinfo[n][1]; + } + } + + static int numcheatvars; + + public static void FixCvarCheats() { + int i; + cheatvar_t var; + + if (0 == strcmp(cl.configstrings[CS_MAXCLIENTS], "1") || 0 == cl.configstrings[CS_MAXCLIENTS].length()) + return; // single player can cheat + + // find all the cvars if we haven't done it yet + if (0 == numcheatvars) { + while (cheatvars[numcheatvars].name != null) { + cheatvars[numcheatvars].var = Cvar.Get(cheatvars[numcheatvars].name, cheatvars[numcheatvars].value, 0); + numcheatvars++; + } + } + + // make sure they are all set to the proper values + for (i = 0; i < numcheatvars; i++) { + var = cheatvars[i]; + if (0 != strcmp(var.var.string, var.value)) { + Cvar.Set(var.name, var.value); + } + } + } + + // ============================================================================ + + /* + ================== + CL_SendCommand + + ================== + */ + public static void SendCommand() { + // get new key events + Sys.SendKeyEvents(); + + // allow mice or other external controllers to add commands + IN.Commands(); + + // process console commands + Cbuf.Execute(); + + // fix any cheating cvars + FixCvarCheats(); + + // send intentions now + SendCmd(); + + // resend a connection request if necessary + CheckForResend(); + } + + /* + ================== + CL_Frame + + ================== + */ + private static int extratime; + private static int lasttimecalled; + + public static void Frame(int msec) { + + extratime += msec; + + if (cl_timedemo.value == 0.0f) { + if (cls.state == ca_connected && extratime < 100) { + return; // don't flood packets out while connecting + } + if (extratime < 1000 / cl_maxfps.value) { + return; // framerate is too high + } + } + + // let the mouse activate or deactivate + IN.Frame(); + + // decide the simulation time + cls.frametime = extratime / 1000.0f; + cl.time += extratime; + cls.realtime = curtime; + + extratime = 0; + + if (cls.frametime > (1.0f / 5)) + cls.frametime = (1.0f / 5); + + // if in the debugger last frame, don't timeout + if (msec > 5000) + cls.netchan.last_received = Sys.Milliseconds(); + + // fetch results from server + CL.ReadPackets(); + + // send a new command message to the server + SendCommand(); + + // predict all unacknowledged movements + CL.PredictMovement(); + + // allow rendering DLL change + VID.CheckChanges(); + if (!cl.refresh_prepped && cls.state == ca_active) { + CL.PrepRefresh(); + // TODO force GC after level loading + System.gc(); + System.gc(); + } + + SCR.UpdateScreen(); + + // update audio + S.Update(cl.refdef.vieworg, cl.v_forward, cl.v_right, cl.v_up); + + // advance local effects for next frame + CL.RunDLights(); + CL.RunLightStyles(); + + SCR.RunConsole(); + + cls.framecount++; + } + + // ============================================================================ + + /* + =============== + CL_Shutdown + + FIXME: this is a callback from Sys_Quit and Com_Error. It would be better + to run quit through here before the final handoff to the sys code. + =============== + */ + static boolean isdown = false; + public static void Shutdown() { + + if (isdown) { + System.out.print("recursive shutdown\n"); + return; + } + isdown = true; + + WriteConfiguration(); + + S.Shutdown(); + IN.Shutdown(); + VID.Shutdown(); + } + + /** + * initialize client subsystem + */ + public static void Init() { + if (Globals.dedicated.value != 0.0f) + return; // nothing running on the client + + // all archived variables will now be loaded + + Console.Init(); //ok + + S.Init(); //empty + VID.Init(); + + V.Init(); + + Globals.net_message.data = Globals.net_message_buffer; + Globals.net_message.maxsize = Globals.net_message_buffer.length; + + Menu.Init(); + + SCR.Init(); + //Globals.cls.disable_screen = 1.0f; // don't draw yet + + CL.InitLocal(); + IN.Init(); + + FS.ExecAutoexec(); + Cbuf.Execute(); + } + + /** + * Called after an ERR_DROP was thrown. + */ + public static void Drop() { + if (Globals.cls.state == Defines.ca_uninitialized) + return; + if (Globals.cls.state == Defines.ca_disconnected) + return; + + CL.Disconnect(); + + // drop loading plaque unless this is the initial game start + if (Globals.cls.disable_servercount != -1) + SCR.EndLoadingPlaque(); // get rid of loading plaque + } + +} |