/*
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.
*/
/* Modifications
Copyright 2003-2004 Bytonic Software
Copyright 2010 Google Inc.
*/
package com.googlecode.gwtquake.shared.client;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteOrder;
import com.googlecode.gwtquake.shared.common.*;
import com.googlecode.gwtquake.shared.game.*;
import com.googlecode.gwtquake.shared.server.ServerMain;
import com.googlecode.gwtquake.shared.sound.Sound;
import com.googlecode.gwtquake.shared.sys.*;
import com.googlecode.gwtquake.shared.util.*;
/**
* CL
*/
public final class Client {
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 =
(Constants.CS_PLAYERSKINS + Constants.MAX_CLIENTS * Client.PLAYER_MULT);
public static final int TEXTURE_CNT = (ENV_CNT + 13);
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 class CheatVar {
String name;
String value;
ConsoleVariable 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 cheatvars[];
static int numcheatvars;
static {
cheatvars = new CheatVar[cheatvarsinfo.length];
for (int n = 0; n < cheatvarsinfo.length; n++) {
cheatvars[n] = new CheatVar();
cheatvars[n].name = cheatvarsinfo[n][0];
cheatvars[n].value = cheatvarsinfo[n][1];
}
}
/**
* Stop_f
*
* Stop recording a demo.
*/
static ExecutableCommand stopCommand = new ExecutableCommand() {
public void execute() {
try {
int len;
if (!Globals.cls.demorecording) {
Com.Printf("Not recording a demo.\n");
return;
}
// finish up
len = -1;
Globals.cls.demofile.writeInt(EndianHandler.swapInt(len));
Globals.cls.demofile.close();
Globals.cls.demofile = null;
Globals.cls.demorecording = false;
Com.Printf("Stopped demo.\n");
} catch (IOException e) {
}
}
};
static EntityState nullstate = new EntityState(null);
/**
* Record_f
*
* record <demoname>
* Begins recording a demo from the current position.
*/
static ExecutableCommand recordCommand = new ExecutableCommand() {
public void execute() {
try {
String name;
int i;
EntityState ent;
if (Commands.Argc() != 2) {
Com.Printf("record <demoname>\n");
return;
}
if (Globals.cls.demorecording) {
Com.Printf("Already recording.\n");
return;
}
if (Globals.cls.state != Constants.ca_active) {
Com.Printf("You must be in a level to record.\n");
return;
}
//
// open the demo file
//
name = QuakeFileSystem.Gamedir() + "/demos/" + Commands.Argv(1) + ".dm2";
Com.Printf("recording to " + name + ".\n");
QuakeFileSystem.CreatePath(name);
Globals.cls.demofile = new RandomAccessFile(name, "rw");
if (Globals.cls.demofile == null) {
Com.Printf("ERROR: couldn't open.\n");
return;
}
Globals.cls.demorecording = true;
// don't start saving messages until a non-delta compressed
// message is received
Globals.cls.demowaiting = true;
//
// write out messages to hold the startup information
//
Buffer buf = Buffer.allocate(Constants.MAX_MSGLEN);
buf.order(ByteOrder.LITTLE_ENDIAN);
// send the serverdata
Buffers.writeByte(buf, Constants.svc_serverdata);
buf.putInt(Constants.PROTOCOL_VERSION);
buf.putInt(0x10000 + Globals.cl.servercount);
Buffers.writeByte(buf, 1); // demos are always attract loops
Buffers.WriteString(buf, Globals.cl.gamedir);
buf.WriteShort(Globals.cl.playernum);
Buffers.WriteString(buf, Globals.cl.configstrings[Constants.CS_NAME]);
// configstrings
for (i = 0; i < Constants.MAX_CONFIGSTRINGS; i++) {
if (Globals.cl.configstrings[i].length() > 0) {
if (buf.cursize + Globals.cl.configstrings[i].length()
+ 32 > buf.maxsize) {
// write it out
Globals.cls.demofile.writeInt(EndianHandler.swapInt(buf.cursize));
Globals.cls.demofile
.write(buf.data, 0, buf.cursize);
buf.cursize = 0;
}
Buffers.writeByte(buf, Constants.svc_configstring);
buf.WriteShort(i);
Buffers.WriteString(buf, Globals.cl.configstrings[i]);
}
}
// baselines
nullstate.clear();
for (i = 0; i < Constants.MAX_EDICTS; i++) {
ent = Globals.cl_entities[i].baseline;
if (ent.modelindex == 0)
continue;
if (buf.cursize + 64 > buf.maxsize) { // write it out
Globals.cls.demofile.writeInt(EndianHandler.swapInt(buf.cursize));
Globals.cls.demofile.write(buf.data, 0, buf.cursize);
buf.cursize = 0;
}
Buffers.writeByte(buf, Constants.svc_spawnbaseline);
Delta.WriteDeltaEntity(nullstate,
Globals.cl_entities[i].baseline, buf, true, true);
}
Buffers.writeByte(buf, Constants.svc_stufftext);
Buffers.WriteString(buf, "precache\n");
// write it to the demo file
Globals.cls.demofile.writeInt(EndianHandler.swapInt(buf.cursize));
Globals.cls.demofile.write(buf.data, 0, buf.cursize);
// the rest of the demo file will be individual frames
} catch (IOException e) {
}
}
};
/**
* ForwardToServer_f
*/
static ExecutableCommand forwardToServerCommand = new ExecutableCommand() {
public void execute() {
if (Globals.cls.state != Constants.ca_connected
&& Globals.cls.state != Constants.ca_active) {
Com.Printf("Can't \"" + Commands.Argv(0) + "\", not connected\n");
return;
}
// don't forward the first argument
if (Commands.Argc() > 1) {
Buffers.writeByte(Globals.cls.netchan.message,
Constants.clc_stringcmd);
Buffers.Print(Globals.cls.netchan.message, Commands.Args());
}
}
};
/**
* Pause_f
*/
static ExecutableCommand pauseCommand = new ExecutableCommand() {
public void execute() {
// never pause in multiplayer
if (ConsoleVariables.VariableValue("maxclients") > 1
|| Globals.server_state == 0) {
ConsoleVariables.SetValue("paused", 0);
return;
}
ConsoleVariables.SetValue("paused", Globals.cl_paused.value);
}
};
/**
* Quit_f
*/
static ExecutableCommand quitCommand = new ExecutableCommand() {
public void execute() {
disconnect();
Com.Quit();
}
};
/**
* Connect_f
*/
static ExecutableCommand connectCommand = new ExecutableCommand() {
public void execute() {
String server;
if (Commands.Argc() != 2) {
Com.Printf("usage: connect <server>\n");
return;
}
if (Globals.server_state != 0) {
// if running a local server, kill it and reissue
ServerMain.SV_Shutdown("Server quit\n", false);
} else {
disconnect();
}
server = Commands.Argv(1);
NET.Config(true); // allow remote
disconnect();
Globals.cls.state = Constants.ca_connecting;
//strncpy (cls.servername, server, sizeof(cls.servername)-1);
Globals.cls.servername = server;
Globals.cls.connect_time = -99999;
// CL_CheckForResend() will fire immediately
}
};
/**
* Rcon_f
*
* Send the rest of the command line over as an unconnected command.
*/
static ExecutableCommand rconCommand = new ExecutableCommand() {
public void execute() {
if (Globals.rcon_client_password.string.length() == 0) {
Com.Printf("You must set 'rcon_password' before\nissuing an rcon command.\n");
return;
}
StringBuffer message = new StringBuffer(1024);
// connection less packet
message.append('\u00ff');
message.append('\u00ff');
message.append('\u00ff');
message.append('\u00ff');
// allow remote
NET.Config(true);
message.append("rcon ");
message.append(Globals.rcon_client_password.string);
message.append(" ");
for (int i = 1; i < Commands.Argc(); i++) {
message.append(Commands.Argv(i));
message.append(" ");
}
NetworkAddress to = new NetworkAddress();
if (Globals.cls.state >= Constants.ca_connected)
to = Globals.cls.netchan.remote_address;
else {
if (Globals.rcon_address.string.length() == 0) {
Com.Printf("You must either be connected,\nor set the 'rcon_address' cvar\nto issue rcon commands\n");
return;
}
NET.StringToAdr(Globals.rcon_address.string, to);
if (to.port == 0) to.port = Constants.PORT_SERVER;
}
message.append('\0');
String b = message.toString();
NET.SendPacket(Constants.NS_CLIENT, b.length(), Lib.stringToBytes(b), to);
}
};
static ExecutableCommand disconnectCommand = new ExecutableCommand() {
public void execute() {
Com.Error(Constants.ERR_DROP, "Disconnected from server");
}
};
/**
* Changing_f
*
* Just sent as a hint to the client that they should drop to full console.
*/
static ExecutableCommand changingCommand = new ExecutableCommand() {
public void execute() {
//ZOID
//if we are downloading, we don't change!
// This so we don't suddenly stop downloading a map
if (Globals.cls.download != null)
return;
Screen.BeginLoadingPlaque();
Globals.cls.state = Constants.ca_connected; // not active anymore, but
// not disconnected
Com.Printf("\nChanging map...\n");
}
};
/**
* Reconnect_f
*
* The server is changing levels.
*/
static ExecutableCommand reconnectCommand = new ExecutableCommand() {
public void execute() {
//ZOID
//if we are downloading, we don't change! This so we don't suddenly
// stop downloading a map
if (Globals.cls.download != null)
return;
Sound.StopAllSounds();
if (Globals.cls.state == Constants.ca_connected) {
Com.Printf("reconnecting...\n");
Globals.cls.state = Constants.ca_connected;
Buffers.writeByte(Globals.cls.netchan.message, Constants.clc_stringcmd);
Buffers.WriteString(Globals.cls.netchan.message, "new");
return;
}
if (Globals.cls.servername != null) {
if (Globals.cls.state >= Constants.ca_connected) {
disconnect();
Globals.cls.connect_time = Globals.cls.realtime - 1500;
} else
Globals.cls.connect_time = -99999; // fire immediately
Globals.cls.state = Constants.ca_connecting;
Com.Printf("reconnecting...\n");
}
}
};
/**
* PingServers_f
*/
static ExecutableCommand pingServersCommand = new ExecutableCommand() {
public void execute() {
int i;
NetworkAddress adr = new NetworkAddress();
//char name[32];
String name;
String adrstring;
ConsoleVariable noudp;
ConsoleVariable noipx;
NET.Config(true); // allow remote
// send a broadcast packet
Com.Printf("pinging broadcast...\n");
noudp = ConsoleVariables.Get("noudp", "0", Constants.CVAR_NOSET);
if (noudp.value == 0.0f) {
adr.type = Constants.NA_BROADCAST;
adr.port = Constants.PORT_SERVER;
//adr.port = BigShort(PORT_SERVER);
NetworkChannel.OutOfBandPrint(Constants.NS_CLIENT, adr, "info "
+ Constants.PROTOCOL_VERSION);
}
// we use no IPX
noipx = ConsoleVariables.Get("noipx", "1", Constants.CVAR_NOSET);
if (noipx.value == 0.0f) {
adr.type = Constants.NA_BROADCAST_IPX;
//adr.port = BigShort(PORT_SERVER);
adr.port = Constants.PORT_SERVER;
NetworkChannel.OutOfBandPrint(Constants.NS_CLIENT, adr, "info "
+ Constants.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 = ConsoleVariables.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 = Constants.PORT_SERVER;
NetworkChannel.OutOfBandPrint(Constants.NS_CLIENT, adr, "info "
+ Constants.PROTOCOL_VERSION);
}
}
};
/**
* Skins_f
*
* Load or download any custom player skins and models.
*/
static ExecutableCommand skinsCommand = new ExecutableCommand() {
public void execute() {
int i;
for (i = 0; i < Constants.MAX_CLIENTS; i++) {
if (Globals.cl.configstrings[Constants.CS_PLAYERSKINS + i] == null)
continue;
Com.Printf("client " + i + ": "
+ Globals.cl.configstrings[Constants.CS_PLAYERSKINS + i]
+ "\n");
Screen.UpdateScreen();
Sys.SendKeyEvents(); // pump message loop
ClientParser.ParseClientinfo(i);
}
}
};
/**
* Userinfo_f
*/
static ExecutableCommand userinfoCommand = new ExecutableCommand() {
public void execute() {
Com.Printf("User info settings:\n");
Info.Print(ConsoleVariables.Userinfo());
}
};
/**
* Snd_Restart_f
*
* Restart the sound subsystem so it can pick up new parameters and flush
* all sounds.
*/
static ExecutableCommand sndRestartCommand = new ExecutableCommand() {
public void execute() {
Sound.Shutdown();
Sound.Init();
ClientParser.RegisterSounds();
}
};
static String env_suf[] = { "rt", "bk", "lf", "ft", "up", "dn" };
/**
* The server will send this command right before allowing the client into
* the server.
*/
static ExecutableCommand precacheCommand = new ExecutableCommand() {
public void execute() {
// Yet another hack to let old demos work the old precache sequence.
if (Commands.Argc() < 2) {
int iw[] = { 0 }; // for detecting cheater maps
CM.CM_LoadMap(Globals.cl.configstrings[Constants.CS_MODELS + 1],
true, iw);
ClientParser.RegisterSounds();
ClientView.PrepRefresh();
return;
}
Client.precache_check = Constants.CS_MODELS;
Client.precache_spawncount = Lib.atoi(Commands.Argv(1));
Client.precache_model = null;
Client.precache_model_skin = 0;
requestNextDownload();
}
};
private static int extratime;
// ============================================================================
/**
* 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;
/**
* 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 = Globals.net_message.cursize - 8;
try {
Globals.cls.demofile.writeInt(EndianHandler.swapInt(swlen));
Globals.cls.demofile.write(Globals.net_message.data, 8, swlen);
} catch (IOException e) {
}
}
/**
* SendConnectPacket
*
* We have gotten a challenge from the server, so try and connect.
*/
static void sendConnectPacket() {
NetworkAddress adr = new NetworkAddress();
int port;
if (!NET.StringToAdr(Globals.cls.servername, adr)) {
Com.Printf("Bad server address\n");
Globals.cls.connect_time = 0;
return;
}
if (adr.port == 0)
adr.port = Constants.PORT_SERVER;
// adr.port = BigShort(PORT_SERVER);
port = (int) ConsoleVariables.VariableValue("qport");
Globals.userinfo_modified = false;
NetworkChannel.OutOfBandPrint(Constants.NS_CLIENT, adr, "connect "
+ Constants.PROTOCOL_VERSION + " " + port + " "
+ Globals.cls.challenge + " \"" + ConsoleVariables.Userinfo() + "\"\n");
}
/**
* CheckForResend
*
* Resend a connect message if the last one has timed out.
*/
static void checkForResend() {
NetworkAddress adr = new NetworkAddress();
// if the local server is running and we aren't
// then connect
if (Globals.cls.state == Constants.ca_disconnected
&& Globals.server_state != 0) {
Globals.cls.state = Constants.ca_connecting;
Globals.cls.servername = "localhost";
// we don't need a challenge on the localhost
sendConnectPacket();
return;
}
// resend if we haven't gotten a reply yet
if (Globals.cls.state != Constants.ca_connecting)
return;
if (Globals.cls.realtime - Globals.cls.connect_time < 3000)
return;
if (!NET.StringToAdr(Globals.cls.servername, adr)) {
Com.Printf("Bad server address\n");
Globals.cls.state = Constants.ca_disconnected;
return;
}
if (adr.port == 0)
adr.port = Constants.PORT_SERVER;
// for retransmit requests
Globals.cls.connect_time = Globals.cls.realtime;
Com.Printf("Connecting to " + Globals.cls.servername + "...\n");
NetworkChannel.OutOfBandPrint(Constants.NS_CLIENT, adr, "getchallenge\n");
}
/**
* ClearState
*
*/
static void clearState() {
Sound.StopAllSounds();
ClientEffects.ClearEffects();
ClientTent.ClearTEnts();
// wipe the entire cl structure
Globals.cl = new ClientState();
for (int i = 0; i < Globals.cl_entities.length; i++) {
Globals.cl_entities[i] = new ClientEntity();
}
Globals.cls.netchan.message.clear();
}
/**
* 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 (Globals.cls.state == Constants.ca_disconnected)
return;
if (Globals.cl_timedemo != null && Globals.cl_timedemo.value != 0.0f) {
int time;
time = (int) (Timer.Milliseconds() - Globals.cl.timedemo_start);
if (time > 0)
Com.Printf("%i frames, %3.1f seconds: %3.1f fps\n",
new Vargs(3).add(Globals.cl.timedemo_frames).add(
time / 1000.0).add(
Globals.cl.timedemo_frames * 1000.0 / time));
}
Math3D.VectorClear(Globals.cl.refdef.blend);
Globals.re.CinematicSetPalette(null);
Menu.ForceMenuOff();
Globals.cls.connect_time = 0;
Screen.StopCinematic();
if (Globals.cls.demorecording)
stopCommand.execute();
// send a disconnect message to the server
fin = (char) Constants.clc_stringcmd + "disconnect";
NetworkChannel.Transmit(Globals.cls.netchan, fin.length(), Lib.stringToBytes(fin));
NetworkChannel.Transmit(Globals.cls.netchan, fin.length(), Lib.stringToBytes(fin));
NetworkChannel.Transmit(Globals.cls.netchan, fin.length(), Lib.stringToBytes(fin));
clearState();
// stop download
if (Globals.cls.download != null) {
Lib.fclose(Globals.cls.download);
Globals.cls.download = null;
}
Globals.cls.state = Constants.ca_disconnected;
}
/**
* ParseStatusMessage
*
* Handle a reply from a ping.
*/
static void parseStatusMessage() {
String s;
s = Buffers.getString(Globals.net_message);
Com.Printf(s + "\n");
Menu.AddToServerList(Globals.net_from, s);
}
/**
* ConnectionlessPacket
*
* Responses to broadcasts, etc
*/
static void connectionlessPacket() {
String s;
String c;
Globals.net_message.reset();
Globals.net_message.getInt(); // skip the -1
s = Buffers.getLine(Globals.net_message);
Commands.TokenizeString(s.toCharArray(), false);
c = Commands.Argv(0);
Com.Println(Globals.net_from.toString() + ": " + c);
// server connection
if (c.equals("client_connect")) {
if (Globals.cls.state == Constants.ca_connected) {
Com.Printf("Dup connect received. Ignored.\n");
return;
}
NetworkChannel.Setup(Globals.cls.netchan, Constants.NS_CLIENT,
Globals.net_from, Globals.cls.quakePort);
Buffers.writeByte(Globals.cls.netchan.message, Constants.clc_stringcmd);
Buffers.WriteString(Globals.cls.netchan.message, "new");
Globals.cls.state = Constants.ca_connected;
return;
}
// server responding to a status broadcast
if (c.equals("info")) {
parseStatusMessage();
return;
}
// remote command from gui front end
if (c.equals("cmd")) {
if (!NET.IsLocalAddress(Globals.net_from)) {
Com.Printf("Command packet from remote host. Ignored.\n");
return;
}
s = Buffers.getString(Globals.net_message);
CommandBuffer.AddText(s);
CommandBuffer.AddText("\n");
return;
}
// print command from somewhere
if (c.equals("print")) {
s = Buffers.getString(Globals.net_message);
if (s.length() > 0)
Com.Printf(s);
return;
}
// ping from somewhere
if (c.equals("ping")) {
NetworkChannel.OutOfBandPrint(Constants.NS_CLIENT, Globals.net_from, "ack");
return;
}
// challenge from the server we are connecting to
if (c.equals("challenge")) {
Globals.cls.challenge = Lib.atoi(Commands.Argv(1));
sendConnectPacket();
return;
}
// echo request from server
if (c.equals("echo")) {
NetworkChannel.OutOfBandPrint(Constants.NS_CLIENT, Globals.net_from, Commands
.Argv(1));
return;
}
Com.Printf("Unknown command.\n");
}
/**
* ReadPackets
*/
static void readPackets() {
while (NET.GetPacket(Constants.NS_CLIENT, Globals.net_from,
Globals.net_message)) {
//
// remote command packet
//
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) {
// if (*(int *)net_message.data == -1)
connectionlessPacket();
continue;
}
if (Globals.cls.state == Constants.ca_disconnected
|| Globals.cls.state == Constants.ca_connecting)
continue; // dump it if not connected
if (Globals.net_message.cursize < 8) {
Com.Printf(NET.AdrToString(Globals.net_from)
+ ": Runt packet\n");
continue;
}
//
// packet from server
//
if (!NET.CompareAdr(Globals.net_from,
Globals.cls.netchan.remote_address)) {
Com.DPrintf(NET.AdrToString(Globals.net_from)
+ ":sequenced packet without connection\n");
continue;
}
if (!NetworkChannel.Process(Globals.cls.netchan, Globals.net_message))
continue; // wasn't accepted for some reason
ClientParser.ParseServerMessage();
}
//
// check timeout
//
if (Globals.cls.state >= Constants.ca_connected
&& Globals.cls.realtime - Globals.cls.netchan.last_received > Globals.cl_timeout.value * 1000) {
if (++Globals.cl.timeoutcount > 5) // timeoutcount saves debugger
{
Com.Printf("\nServer connection timed out.\n");
disconnect();
return;
}
} else
Globals.cl.timeoutcount = 0;
}
// =============================================================================
/**
* FixUpGender_f
*/
static void fixUpGender() {
String sk;
if (Globals.gender_auto.value != 0.0f) {
if (Globals.gender.modified) {
// was set directly, don't override the user
Globals.gender.modified = false;
return;
}
sk = Globals.skin.string;
if (sk.startsWith("male") || sk.startsWith("cyborg"))
ConsoleVariables.Set("gender", "male");
else if (sk.startsWith("female") || sk.startsWith("crackhor"))
ConsoleVariables.Set("gender", "female");
else
ConsoleVariables.Set("gender", "none");
Globals.gender.modified = false;
}
}
public static void requestNextDownload() {
int map_checksum = 0; // for detecting cheater maps
//char fn[MAX_OSPATH];
String fn;
QuakeFiles.dmdl_t pheader;
if (Globals.cls.state != Constants.ca_connected)
return;
if (ServerMain.allow_download.value == 0 && Client.precache_check < ENV_CNT)
Client.precache_check = ENV_CNT;
// ZOID
if (Client.precache_check == Constants.CS_MODELS) { // confirm map
Client.precache_check = Constants.CS_MODELS + 2; // 0 isn't used
// if (SV_MAIN.allow_download_maps.value != 0)
// if (!CL_parse
// .CheckOrDownloadFile(Globals.cl.configstrings[Defines.CS_MODELS + 1]))
// return; // started a download
}
if (Client.precache_check >= Constants.CS_MODELS
&& Client.precache_check < Constants.CS_MODELS + Constants.MAX_MODELS) {
// if (SV_MAIN.allow_download_models.value != 0) {
// while (CL.precache_check < Defines.CS_MODELS
// + Defines.MAX_MODELS
// && Globals.cl.configstrings[CL.precache_check].length() > 0) {
// if (Globals.cl.configstrings[CL.precache_check].charAt(0) == '*'
// || Globals.cl.configstrings[CL.precache_check]
// .charAt(0) == '#') {
// CL.precache_check++;
// continue;
// }
// if (CL.precache_model_skin == 0) {
// if (!CL_parse
// .CheckOrDownloadFile(Globals.cl.configstrings[CL.precache_check])) {
// CL.precache_model_skin = 1;
// return; // started a download
// }
// CL.precache_model_skin = 1;
// }
//
// // checking for skins in the model
// if (CL.precache_model == null) {
//
// CL.precache_model = FS
// .LoadFile(Globals.cl.configstrings[CL.precache_check]);
// if (CL.precache_model == null) {
// CL.precache_model_skin = 0;
// CL.precache_check++;
// continue; // couldn't load it
// }
// ByteBuffer bb = ByteBuffer.wrap(CL.precache_model);
// bb.order(ByteOrder.LITTLE_ENDIAN);
//
// int header = bb.getInt();
//
// if (header != qfiles.IDALIASHEADER) {
// // not an alias model
// FS.FreeFile(CL.precache_model);
// CL.precache_model = null;
// CL.precache_model_skin = 0;
// CL.precache_check++;
// continue;
// }
// pheader = new qfiles.dmdl_t(ByteBuffer.wrap(
// CL.precache_model).order(
// ByteOrder.LITTLE_ENDIAN));
// if (pheader.version != Defines.ALIAS_VERSION) {
// CL.precache_check++;
// CL.precache_model_skin = 0;
// continue; // couldn't load it
// }
// }
//
// pheader = new qfiles.dmdl_t(ByteBuffer.wrap(
// CL.precache_model).order(ByteOrder.LITTLE_ENDIAN));
//
// int num_skins = pheader.num_skins;
//
// while (CL.precache_model_skin - 1 < num_skins) {
// //Com.Printf("critical code section because of endian
// // mess!\n");
//
// String name = Lib.CtoJava(CL.precache_model,
// pheader.ofs_skins
// + (CL.precache_model_skin - 1)
// * Defines.MAX_SKINNAME,
// Defines.MAX_SKINNAME * num_skins);
//
// if (!CL_parse.CheckOrDownloadFile(name)) {
// CL.precache_model_skin++;
// return; // started a download
// }
// CL.precache_model_skin++;
// }
// if (CL.precache_model != null) {
// FS.FreeFile(CL.precache_model);
// CL.precache_model = null;
// }
// CL.precache_model_skin = 0;
// CL.precache_check++;
// }
// }
Client.precache_check = Constants.CS_SOUNDS;
}
if (Client.precache_check >= Constants.CS_SOUNDS
&& Client.precache_check < Constants.CS_SOUNDS + Constants.MAX_SOUNDS) {
// if (SV_MAIN.allow_download_sounds.value != 0) {
// if (CL.precache_check == Defines.CS_SOUNDS)
// CL.precache_check++; // zero is blank
// while (CL.precache_check < Defines.CS_SOUNDS
// + Defines.MAX_SOUNDS
// && Globals.cl.configstrings[CL.precache_check].length() > 0) {
// if (Globals.cl.configstrings[CL.precache_check].charAt(0) == '*') {
// CL.precache_check++;
// continue;
// }
// fn = "sound/"
// + Globals.cl.configstrings[CL.precache_check++];
// if (!CL_parse.CheckOrDownloadFile(fn))
// return; // started a download
// }
// }
Client.precache_check = Constants.CS_IMAGES;
}
if (Client.precache_check >= Constants.CS_IMAGES
&& Client.precache_check < Constants.CS_IMAGES + Constants.MAX_IMAGES) {
if (Client.precache_check == Constants.CS_IMAGES)
Client.precache_check++; // zero is blank
//
// while (CL.precache_check < Defines.CS_IMAGES + Defines.MAX_IMAGES
// && Globals.cl.configstrings[CL.precache_check].length() > 0) {
// fn = "pics/" + Globals.cl.configstrings[CL.precache_check++]
// + ".pcx";
// if (!CL_parse.CheckOrDownloadFile(fn))
// return; // started a download
// }
Client.precache_check = Constants.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 (Client.precache_check >= Constants.CS_PLAYERSKINS
&& Client.precache_check < Constants.CS_PLAYERSKINS
+ Constants.MAX_CLIENTS * Client.PLAYER_MULT) {
// if (SV_MAIN.allow_download_players.value != 0) {
// while (CL.precache_check < Defines.CS_PLAYERSKINS
// + Defines.MAX_CLIENTS * CL.PLAYER_MULT) {
//
// int i, n;
// //char model[MAX_QPATH], skin[MAX_QPATH], * p;
// String model, skin;
//
// i = (CL.precache_check - Defines.CS_PLAYERSKINS)
// / CL.PLAYER_MULT;
// n = (CL.precache_check - Defines.CS_PLAYERSKINS)
// % CL.PLAYER_MULT;
//
// if (Globals.cl.configstrings[Defines.CS_PLAYERSKINS + i]
// .length() == 0) {
// CL.precache_check = Defines.CS_PLAYERSKINS + (i + 1)
// * CL.PLAYER_MULT;
// continue;
// }
//
// int pos = Globals.cl.configstrings[Defines.CS_PLAYERSKINS + i].indexOf('\\');
//
// if (pos != -1)
// pos++;
// else
// pos = 0;
//
// int pos2 = Globals.cl.configstrings[Defines.CS_PLAYERSKINS + i].indexOf('\\', pos);
//
// if (pos2 == -1)
// pos2 = Globals.cl.configstrings[Defines.CS_PLAYERSKINS + i].indexOf('/', pos);
//
//
// model = Globals.cl.configstrings[Defines.CS_PLAYERSKINS + i]
// .substring(pos, pos2);
//
// skin = Globals.cl.configstrings[Defines.CS_PLAYERSKINS + i].substring(pos2 + 1);
//
// switch (n) {
// case 0: // model
// fn = "players/" + model + "/tris.md2";
// if (!CL_parse.CheckOrDownloadFile(fn)) {
// CL.precache_check = Defines.CS_PLAYERSKINS + i
// * CL.PLAYER_MULT + 1;
// return; // started a download
// }
// n++;
// /* FALL THROUGH */
//
// case 1: // weapon model
// fn = "players/" + model + "/weapon.md2";
// if (!CL_parse.CheckOrDownloadFile(fn)) {
// CL.precache_check = Defines.CS_PLAYERSKINS + i
// * CL.PLAYER_MULT + 2;
// return; // started a download
// }
// n++;
// /* FALL THROUGH */
//
// case 2: // weapon skin
// fn = "players/" + model + "/weapon.pcx";
// if (!CL_parse.CheckOrDownloadFile(fn)) {
// CL.precache_check = Defines.CS_PLAYERSKINS + i
// * CL.PLAYER_MULT + 3;
// return; // started a download
// }
// n++;
// /* FALL THROUGH */
//
// case 3: // skin
// fn = "players/" + model + "/" + skin + ".pcx";
// if (!CL_parse.CheckOrDownloadFile(fn)) {
// CL.precache_check = Defines.CS_PLAYERSKINS + i
// * CL.PLAYER_MULT + 4;
// return; // started a download
// }
// n++;
// /* FALL THROUGH */
//
// case 4: // skin_i
// fn = "players/" + model + "/" + skin + "_i.pcx";
// if (!CL_parse.CheckOrDownloadFile(fn)) {
// CL.precache_check = Defines.CS_PLAYERSKINS + i
// * CL.PLAYER_MULT + 5;
// return; // started a download
// }
// // move on to next model
// CL.precache_check = Defines.CS_PLAYERSKINS + (i + 1)
// * CL.PLAYER_MULT;
// }
// }
// }
// precache phase completed
Client.precache_check = ENV_CNT;
}
if (Client.precache_check == ENV_CNT) {
Client.precache_check = ENV_CNT + 1;
int iw[] = { map_checksum };
CM.CM_LoadMap(Globals.cl.configstrings[Constants.CS_MODELS + 1],
true, iw);
map_checksum = iw[0];
// if ((map_checksum ^ Lib
// .atoi(Globals.cl.configstrings[Defines.CS_MAPCHECKSUM])) != 0) {
// Com
// .Error(
// Defines.ERR_DROP,
// "Local map version differs from server: "
// + map_checksum
// + " != '"
// + Globals.cl.configstrings[Defines.CS_MAPCHECKSUM]
// + "'\n");
// return;
// }
}
//
// if (CL.precache_check > ENV_CNT && CL.precache_check < TEXTURE_CNT) {
// if (SV_MAIN.allow_download.value != 0
// && SV_MAIN.allow_download_maps.value != 0) {
// while (CL.precache_check < TEXTURE_CNT) {
// int n = CL.precache_check++ - ENV_CNT - 1;
//
// if ((n & 1) != 0)
// fn = "env/" + Globals.cl.configstrings[Defines.CS_SKY]
// + env_suf[n / 2] + ".pcx";
// else
// fn = "env/" + Globals.cl.configstrings[Defines.CS_SKY]
// + env_suf[n / 2] + ".tga";
// if (!CL_parse.CheckOrDownloadFile(fn))
// return; // started a download
// }
// }
// CL.precache_check = TEXTURE_CNT;
// }
//
if (Client.precache_check == TEXTURE_CNT) {
Client.precache_check = TEXTURE_CNT + 1;
Client.precache_tex = 0;
}
//
// confirm existance of textures, download any that don't exist
if (Client.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 (CL.precache_tex < CM.numtexinfo) {
// //char fn[MAX_OSPATH];
//
// fn = "textures/" + CM.map_surfaces[CL.precache_tex++].rname
// + ".wal";
// if (!CL_parse.CheckOrDownloadFile(fn))
// return; // started a download
// }
// }
Client.precache_check = TEXTURE_CNT + 999;
}
// ZOID
ClientParser.RegisterSounds();
ClientView.PrepRefresh();
Buffers.writeByte(Globals.cls.netchan.message, Constants.clc_stringcmd);
Buffers.WriteString(Globals.cls.netchan.message, "begin "
+ Client.precache_spawncount + "\n");
}
/**
* InitLocal
*/
public static void initLocal() {
Globals.cls.state = Constants.ca_disconnected;
Globals.cls.realtime = Timer.Milliseconds();
ClientInput.InitInput();
ConsoleVariables.Get("adr0", "", Constants.CVAR_ARCHIVE);
ConsoleVariables.Get("adr1", "", Constants.CVAR_ARCHIVE);
ConsoleVariables.Get("adr2", "", Constants.CVAR_ARCHIVE);
ConsoleVariables.Get("adr3", "", Constants.CVAR_ARCHIVE);
ConsoleVariables.Get("adr4", "", Constants.CVAR_ARCHIVE);
ConsoleVariables.Get("adr5", "", Constants.CVAR_ARCHIVE);
ConsoleVariables.Get("adr6", "", Constants.CVAR_ARCHIVE);
ConsoleVariables.Get("adr7", "", Constants.CVAR_ARCHIVE);
ConsoleVariables.Get("adr8", "", Constants.CVAR_ARCHIVE);
//
// register our variables
//
Globals.cl_stereo_separation = ConsoleVariables.Get("cl_stereo_separation", "0.4",
Constants.CVAR_ARCHIVE);
Globals.cl_stereo = ConsoleVariables.Get("cl_stereo", "0", 0);
Globals.cl_add_blend = ConsoleVariables.Get("cl_blend", "1", 0);
Globals.cl_add_lights = ConsoleVariables.Get("cl_lights", "1", 0);
Globals.cl_add_particles = ConsoleVariables.Get("cl_particles", "1", 0);
Globals.cl_add_entities = ConsoleVariables.Get("cl_entities", "1", 0);
Globals.cl_gun = ConsoleVariables.Get("cl_gun", "1", 0);
Globals.cl_footsteps = ConsoleVariables.Get("cl_footsteps", "1", 0);
Globals.cl_noskins = ConsoleVariables.Get("cl_noskins", "0", 0);
Globals.cl_autoskins = ConsoleVariables.Get("cl_autoskins", "0", 0);
Globals.cl_predict = ConsoleVariables.Get("cl_predict", "1", 0);
Globals.cl_maxfps = ConsoleVariables.Get("cl_maxfps", "90", 0);
Globals.cl_upspeed = ConsoleVariables.Get("cl_upspeed", "200", 0);
Globals.cl_forwardspeed = ConsoleVariables.Get("cl_forwardspeed", "200", 0);
Globals.cl_sidespeed = ConsoleVariables.Get("cl_sidespeed", "200", 0);
Globals.cl_yawspeed = ConsoleVariables.Get("cl_yawspeed", "140", 0);
Globals.cl_pitchspeed = ConsoleVariables.Get("cl_pitchspeed", "150", 0);
Globals.cl_anglespeedkey = ConsoleVariables.Get("cl_anglespeedkey", "1.5", 0);
Globals.cl_run = ConsoleVariables.Get("cl_run", "0", Constants.CVAR_ARCHIVE);
Globals.lookspring = ConsoleVariables.Get("lookspring", "0", Constants.CVAR_ARCHIVE);
Globals.lookstrafe = ConsoleVariables.Get("lookstrafe", "0", Constants.CVAR_ARCHIVE);
Globals.sensitivity = ConsoleVariables
.Get("sensitivity", "3", Constants.CVAR_ARCHIVE);
Globals.m_pitch = ConsoleVariables.Get("m_pitch", "0.022", Constants.CVAR_ARCHIVE);
Globals.m_yaw = ConsoleVariables.Get("m_yaw", "0.022", 0);
Globals.m_forward = ConsoleVariables.Get("m_forward", "1", 0);
Globals.m_side = ConsoleVariables.Get("m_side", "1", 0);
Globals.cl_shownet = ConsoleVariables.Get("cl_shownet", "0", 0);
Globals.cl_showmiss = ConsoleVariables.Get("cl_showmiss", "0", 0);
Globals.cl_showclamp = ConsoleVariables.Get("showclamp", "0", 0);
Globals.cl_timeout = ConsoleVariables.Get("cl_timeout", "120", 0);
Globals.cl_paused = ConsoleVariables.Get("paused", "0", 0);
Globals.cl_timedemo = ConsoleVariables.Get("timedemo", "0", 0);
Globals.rcon_client_password = ConsoleVariables.Get("rcon_password", "", 0);
Globals.rcon_address = ConsoleVariables.Get("rcon_address", "", 0);
Globals.cl_lightlevel = ConsoleVariables.Get("r_lightlevel", "0", 0);
//
// userinfo
//
Globals.info_password = ConsoleVariables.Get("password", "", Constants.CVAR_USERINFO);
Globals.info_spectator = ConsoleVariables.Get("spectator", "0",
Constants.CVAR_USERINFO);
Globals.name = ConsoleVariables.Get("name", "unnamed", Constants.CVAR_USERINFO
| Constants.CVAR_ARCHIVE);
Globals.skin = ConsoleVariables.Get("skin", "male/grunt", Constants.CVAR_USERINFO
| Constants.CVAR_ARCHIVE);
Globals.rate = ConsoleVariables.Get("rate", "25000", Constants.CVAR_USERINFO
| Constants.CVAR_ARCHIVE); // FIXME
Globals.msg = ConsoleVariables.Get("msg", "1", Constants.CVAR_USERINFO
| Constants.CVAR_ARCHIVE);
Globals.hand = ConsoleVariables.Get("hand", "0", Constants.CVAR_USERINFO
| Constants.CVAR_ARCHIVE);
Globals.fov = ConsoleVariables.Get("fov", "90", Constants.CVAR_USERINFO
| Constants.CVAR_ARCHIVE);
Globals.gender = ConsoleVariables.Get("gender", "male", Constants.CVAR_USERINFO
| Constants.CVAR_ARCHIVE);
Globals.gender_auto = ConsoleVariables
.Get("gender_auto", "1", Constants.CVAR_ARCHIVE);
Globals.gender.modified = false; // clear this so we know when user sets
// it manually
Globals.cl_vwep = ConsoleVariables.Get("cl_vwep", "1", Constants.CVAR_ARCHIVE);
//
// register our commands
//
Commands.addCommand("cmd", forwardToServerCommand);
Commands.addCommand("pause", pauseCommand);
Commands.addCommand("pingservers", pingServersCommand);
Commands.addCommand("skins", skinsCommand);
Commands.addCommand("userinfo", userinfoCommand);
Commands.addCommand("snd_restart", sndRestartCommand);
Commands.addCommand("changing", changingCommand);
Commands.addCommand("disconnect", disconnectCommand);
Commands.addCommand("record", recordCommand);
Commands.addCommand("stop", stopCommand);
Commands.addCommand("quit", quitCommand);
Commands.addCommand("connect", connectCommand);
Commands.addCommand("reconnect", reconnectCommand);
Commands.addCommand("rcon", rconCommand);
Commands.addCommand("precache", precacheCommand);
Commands.addCommand("download", ClientParser.downloadCommand);
//
// forward to server commands
//
// the only thing this does is allow command completion
// to work -- all unknown commands are automatically
// forwarded to the server
Commands.addCommand("wave", null);
Commands.addCommand("inven", null);
Commands.addCommand("kill", null);
Commands.addCommand("use", null);
Commands.addCommand("drop", null);
Commands.addCommand("say", null);
Commands.addCommand("say_team", null);
Commands.addCommand("info", null);
Commands.addCommand("prog", null);
Commands.addCommand("give", null);
Commands.addCommand("god", null);
Commands.addCommand("notarget", null);
Commands.addCommand("noclip", null);
Commands.addCommand("invuse", null);
Commands.addCommand("invprev", null);
Commands.addCommand("invnext", null);
Commands.addCommand("invdrop", null);
Commands.addCommand("weapnext", null);
Commands.addCommand("weapprev", null);
}
/**
* WriteConfiguration
*
* Writes key bindings and archived cvars to config.cfg.
*/
public static void writeConfiguration() {
RandomAccessFile f;
String path;
// if (Globals.cls.state == Defines.ca_uninitialized)
// return;
path = QuakeFileSystem.Gamedir() + "/config.cfg";
f = Lib.fopen(path, "rw");
if (f == null) {
Com.Printf("Couldn't write config.cfg.\n");
return;
}
try {
f.seek(0);
f.setLength(0);
} catch (IOException e1) {
}
try {
f.writeBytes("// generated by quake, do not modify\n");
} catch (IOException e) {
}
Key.WriteBindings(f);
Lib.fclose(f);
ConsoleVariables.WriteVariables(path);
}
/**
* FixCvarCheats
*/
public static void fixCvarCheats() {
int i;
Client.CheatVar var;
if ("1".equals(Globals.cl.configstrings[Constants.CS_MAXCLIENTS])
|| 0 == Globals.cl.configstrings[Constants.CS_MAXCLIENTS]
.length())
return; // single player can cheat
// find all the cvars if we haven't done it yet
if (0 == Client.numcheatvars) {
while (Client.cheatvars[Client.numcheatvars].name != null) {
Client.cheatvars[Client.numcheatvars].var = ConsoleVariables.Get(
Client.cheatvars[Client.numcheatvars].name,
Client.cheatvars[Client.numcheatvars].value, 0);
Client.numcheatvars++;
}
}
// make sure they are all set to the proper values
for (i = 0; i < Client.numcheatvars; i++) {
var = Client.cheatvars[i];
if (!var.var.string.equals(var.value)) {
ConsoleVariables.Set(var.name, var.value);
}
}
}
// =============================================================
/**
* 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
CommandBuffer.Execute();
// fix any cheating cvars
fixCvarCheats();
// send intentions now
ClientInput.SendCmd();
// resend a connection request if necessary
checkForResend();
}
// private static int lasttimecalled;
/**
* Frame
*/
public static void frame(int msec) {
if (Globals.dedicated.value != 0)
return;
extratime += msec;
if (Globals.cl_timedemo.value == 0.0f) {
if (Globals.cls.state == Constants.ca_connected && extratime < 100) {
return; // don't flood packets out while connecting
}
if (extratime < 1000 / Globals.cl_maxfps.value) {
return; // framerate is too high
}
}
// let the mouse activate or deactivate
IN.Frame();
// decide the simulation time
Globals.cls.frametime = extratime / 1000.0f;
Globals.cl.time += extratime;
Globals.cls.realtime = Globals.curtime;
extratime = 0;
if (Globals.cls.frametime > (1.0f / 5))
Globals.cls.frametime = (1.0f / 5);
// if in the debugger last frame, don't timeout
if (msec > 5000)
Globals.cls.netchan.last_received = Timer.Milliseconds();
// fetch results from server
readPackets();
// send a new command message to the server
sendCommand();
// predict all unacknowledged movements
ClientPrediction.PredictMovement();
// allow rendering DLL change
Window.CheckChanges();
if (!Globals.cl.refresh_prepped
&& Globals.cls.state == Constants.ca_active) {
ClientView.PrepRefresh();
// HACK: PrepRefresh() can kick off model loads, and UpdateScreen()
// will try to use them imeediately. So we jump out early here if
// there are any outstanding file requests.
if (ResourceLoader.Pump()) {
return;
}
}
Screen.UpdateScreen();
// update audio
Sound.Update(Globals.cl.refdef.vieworg, Globals.cl.v_forward,
Globals.cl.v_right, Globals.cl.v_up);
// advance local effects for next frame
ClientEffects.RunDLights();
ClientEffects.RunLightStyles();
Screen.RunCinematic();
Screen.RunConsole();
Globals.cls.framecount++;
if (Globals.cls.state != Constants.ca_active
|| Globals.cls.key_dest != Constants.key_game) {
Compatibility.sleep(20);
}
}
/**
* Shutdown
*/
public static void shutdown() {
if (isdown) {
System.out.print("recursive shutdown\n");
return;
}
isdown = true;
writeConfiguration();
Sound.Shutdown();
IN.Shutdown();
Window.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
Sound.Init(); //empty
Window.Init();
Video.Init();
Globals.net_message.data = Globals.net_message_buffer;
Globals.net_message.maxsize = Globals.net_message_buffer.length;
Menu.Init();
Screen.Init();
//Globals.cls.disable_screen = 1.0f; // don't draw yet
initLocal();
IN.Init();
// FS.ExecAutoexec();
CommandBuffer.Execute();
}
/**
* Called after an ERR_DROP was thrown.
*/
public static void drop() {
if (Globals.cls.state == Constants.ca_uninitialized)
return;
if (Globals.cls.state == Constants.ca_disconnected)
return;
disconnect();
// drop loading plaque unless this is the initial game start
if (Globals.cls.disable_servercount != -1)
Screen.EndLoadingPlaque(); // get rid of loading plaque
}
}