/*
Copyright (C) 1996-1997 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.

*/
// cl_main.c  -- client main loop

#include "quakedef.h"
#include "winquake.h"
#ifdef _WIN32
#include "winsock.h"
#else
#ifdef AMIGA
#define INADDR_LOOPBACK 0x7F000001
#define htonl(x) x
#else
#include <netinet/in.h>
#endif
#endif


// we need to declare some mouse variables here, because the menu system
// references them even when on a unix system.

qboolean  noclip_anglehack;   // remnant from old quake


cvar_t  rcon_password = {"rcon_password", "", false};

cvar_t  rcon_address = {"rcon_address", ""};

cvar_t  cl_timeout = {"cl_timeout", "60"};

cvar_t  cl_shownet = {"cl_shownet","0"};  // can be 0, 1, or 2

cvar_t  cl_sbar   = {"cl_sbar", "0", true};
cvar_t  cl_hudswap  = {"cl_hudswap", "0", true};
cvar_t  cl_maxfps = {"cl_maxfps", "0", true};

cvar_t  lookspring = {"lookspring","0", true};
cvar_t  lookstrafe = {"lookstrafe","0", true};
cvar_t  sensitivity = {"sensitivity","3", true};

cvar_t  m_pitch = {"m_pitch","0.022", true};
cvar_t  m_yaw = {"m_yaw","0.022"};
cvar_t  m_forward = {"m_forward","1"};
cvar_t  m_side = {"m_side","0.8"};

cvar_t  entlatency = {"entlatency", "20"};
cvar_t  cl_predict_players = {"cl_predict_players", "1"};
cvar_t  cl_predict_players2 = {"cl_predict_players2", "1"};
cvar_t  cl_solid_players = {"cl_solid_players", "1"};

cvar_t  localid = {"localid", ""};

static qboolean allowremotecmd = true;

//
// info mirrors
//
cvar_t  password = {"password", "", false, true};
cvar_t  spectator = {"spectator", "", false, true};
cvar_t  name = {"name","unnamed", true, true};
cvar_t  team = {"team","", true, true};
cvar_t  skin = {"skin","", true, true};
cvar_t  topcolor = {"topcolor","0", true, true};
cvar_t  bottomcolor = {"bottomcolor","0", true, true};
cvar_t  rate = {"rate","2500", true, true};
cvar_t  noaim = {"noaim","0", true, true};
cvar_t  msg = {"msg","1", true, true};

extern cvar_t cl_hightrack;


client_static_t cls;
client_state_t  cl;

entity_state_t  cl_baselines[MAX_EDICTS];
efrag_t     cl_efrags[MAX_EFRAGS];
entity_t    cl_static_entities[MAX_STATIC_ENTITIES];
lightstyle_t  cl_lightstyle[MAX_LIGHTSTYLES];
dlight_t    cl_dlights[MAX_DLIGHTS];

// refresh list
// this is double buffered so the last frame
// can be scanned for oldorigins of trailing objects
int       cl_numvisedicts, cl_oldnumvisedicts;
entity_t    *cl_visedicts, *cl_oldvisedicts;
entity_t    cl_visedicts_list[2][MAX_VISEDICTS];

double      connect_time = -1;    // for connection retransmits

quakeparms_t host_parms;

qboolean  host_initialized;   // true if into command execution
qboolean  nomaster;

double    host_frametime;
double    realtime;       // without any filtering or bounding
double    oldrealtime;      // last frame run
int     host_framecount;

int     host_hunklevel;

byte    *host_basepal;
byte    *host_colormap;

netadr_t  master_adr;       // address of the master server

cvar_t  host_speeds = {"host_speeds","0"};      // set for running times
cvar_t  show_fps = {"show_fps","0"};      // set for running times
cvar_t  developer = {"developer","0"};

int     fps_count;

jmp_buf   host_abort;

void Master_Connect_f (void);

float server_version = 0; // version of server we connected to

char emodel_name[] = 
  { 'e' ^ 0xff, 'm' ^ 0xff, 'o' ^ 0xff, 'd' ^ 0xff, 'e' ^ 0xff, 'l' ^ 0xff, 0 };
char pmodel_name[] = 
  { 'p' ^ 0xff, 'm' ^ 0xff, 'o' ^ 0xff, 'd' ^ 0xff, 'e' ^ 0xff, 'l' ^ 0xff, 0 };
char prespawn_name[] = 
  { 'p'^0xff, 'r'^0xff, 'e'^0xff, 's'^0xff, 'p'^0xff, 'a'^0xff, 'w'^0xff, 'n'^0xff,
    ' '^0xff, '%'^0xff, 'i'^0xff, ' '^0xff, '0'^0xff, ' '^0xff, '%'^0xff, 'i'^0xff, 0 };
char modellist_name[] = 
  { 'm'^0xff, 'o'^0xff, 'd'^0xff, 'e'^0xff, 'l'^0xff, 'l'^0xff, 'i'^0xff, 's'^0xff, 't'^0xff, 
    ' '^0xff, '%'^0xff, 'i'^0xff, ' '^0xff, '%'^0xff, 'i'^0xff, 0 };
char soundlist_name[] = 
  { 's'^0xff, 'o'^0xff, 'u'^0xff, 'n'^0xff, 'd'^0xff, 'l'^0xff, 'i'^0xff, 's'^0xff, 't'^0xff, 
    ' '^0xff, '%'^0xff, 'i'^0xff, ' '^0xff, '%'^0xff, 'i'^0xff, 0 };

/*
==================
CL_Quit_f
==================
*/
void CL_Quit_f (void)
{
  if (1 /* key_dest != key_console */ /* && cls.state != ca_dedicated */)
  {
    M_Menu_Quit_f ();
    return;
  }
  CL_Disconnect ();
  Sys_Quit ();
}

/*
=======================
CL_Version_f
======================
*/
void CL_Version_f (void)
{
  Con_Printf ("Version %4.2f\n", VERSION);
  Con_Printf ("Exe: "__TIME__" "__DATE__"\n");
#ifdef AMIGA
  Con_Printf ("QWcl%s V%4.2f\n",
#ifdef __PPC__
#ifdef WOS
              "WOS",
#else
              "PPC",
#endif
#else
              "68k",
#endif
              AMIGA_VERSION);
#endif
}


/*
=======================
CL_SendConnectPacket

called by CL_Connect_f and CL_CheckResend
======================
*/
void CL_SendConnectPacket (void)
{
  netadr_t  adr;
  char  data[2048];
  double t1, t2;
// JACK: Fixed bug where DNS lookups would cause two connects real fast
//       Now, adds lookup time to the connect time.
//     Should I add it to realtime instead?!?!

  if (cls.state != ca_disconnected)
    return;

  t1 = Sys_DoubleTime ();

  if (!NET_StringToAdr (cls.servername, &adr))
  {
    Con_Printf ("Bad server address\n");
    connect_time = -1;
    return;
  }

  if (!NET_IsClientLegal(&adr))
  {
    Con_Printf ("Illegal server address\n");
    connect_time = -1;
    return;
  }

  if (adr.port == 0)
    adr.port = BigShort (27500);
  t2 = Sys_DoubleTime ();

  connect_time = realtime+t2-t1;  // for retransmit requests

  cls.qport = Cvar_VariableValue("qport");

  Info_SetValueForStarKey (cls.userinfo, "*ip", NET_AdrToString(adr), MAX_INFO_STRING);

//  Con_Printf ("Connecting to %s...\n", cls.servername);
  sprintf (data, "%c%c%c%cconnect %i %i %i \"%s\"\n",
    255, 255, 255, 255, PROTOCOL_VERSION, cls.qport, cls.challenge, cls.userinfo);
  NET_SendPacket (strlen(data), data, adr);
}

/*
=================
CL_CheckForResend

Resend a connect message if the last one has timed out

=================
*/
void CL_CheckForResend (void)
{
  netadr_t  adr;
  char  data[2048];
  double t1, t2;

  if (connect_time == -1)
    return;
  if (cls.state != ca_disconnected)
    return;
  if (connect_time && realtime - connect_time < 5.0)
    return;

  t1 = Sys_DoubleTime ();
  if (!NET_StringToAdr (cls.servername, &adr))
  {
    Con_Printf ("Bad server address\n");
    connect_time = -1;
    return;
  }
  if (!NET_IsClientLegal(&adr))
  {
    Con_Printf ("Illegal server address\n");
    connect_time = -1;
    return;
  }

  if (adr.port == 0)
    adr.port = BigShort (27500);
  t2 = Sys_DoubleTime ();

  connect_time = realtime+t2-t1;  // for retransmit requests

  Con_Printf ("Connecting to %s...\n", cls.servername);
  sprintf (data, "%c%c%c%cgetchallenge\n", 255, 255, 255, 255);
  NET_SendPacket (strlen(data), data, adr);
}

void CL_BeginServerConnect(void)
{
  connect_time = 0;
  CL_CheckForResend();
}

/*
================
CL_Connect_f

================
*/
void CL_Connect_f (void)
{
  char  *server;

  if (Cmd_Argc() != 2)
  {
    Con_Printf ("usage: connect <server>\n");
    return; 
  }
  
  server = Cmd_Argv (1);

  CL_Disconnect ();

  strncpy (cls.servername, server, sizeof(cls.servername)-1);
  CL_BeginServerConnect();
}


/*
=====================
CL_Rcon_f

  Send the rest of the command line over as
  an unconnected command.
=====================
*/
void CL_Rcon_f (void)
{
  char  message[1024];
  int   i;
  netadr_t  to;

  if (!rcon_password.string)
  {
    Con_Printf ("You must set 'rcon_password' before\n"
          "issuing an rcon command.\n");
    return;
  }

  message[0] = 255;
  message[1] = 255;
  message[2] = 255;
  message[3] = 255;
  message[4] = 0;

  strcat (message, "rcon ");

  strcat (message, rcon_password.string);
  strcat (message, " ");

  for (i=1 ; i<Cmd_Argc() ; i++)
  {
    strcat (message, Cmd_Argv(i));
    strcat (message, " ");
  }

  if (cls.state >= ca_connected)
    to = cls.netchan.remote_address;
  else
  {
    if (!strlen(rcon_address.string))
    {
      Con_Printf ("You must either be connected,\n"
            "or set the 'rcon_address' cvar\n"
            "to issue rcon commands\n");

      return;
    }
    NET_StringToAdr (rcon_address.string, &to);
  }
  
  NET_SendPacket (strlen(message)+1, message
    , to);
}


/*
=====================
CL_ClearState

=====================
*/
void CL_ClearState (void)
{
  int     i;

  S_StopAllSounds (true);

  Con_DPrintf ("Clearing memory\n");
  D_FlushCaches ();
  Mod_ClearAll ();
  if (host_hunklevel) // FIXME: check this...
    Hunk_FreeToLowMark (host_hunklevel);

  CL_ClearTEnts ();

// wipe the entire cl structure
  memset (&cl, 0, sizeof(cl));

  SZ_Clear (&cls.netchan.message);

// clear other arrays 
  memset (cl_efrags, 0, sizeof(cl_efrags));
  memset (cl_dlights, 0, sizeof(cl_dlights));
  memset (cl_lightstyle, 0, sizeof(cl_lightstyle));

//
// allocate the efrags and chain together into a free list
//
  cl.free_efrags = cl_efrags;
  for (i=0 ; i<MAX_EFRAGS-1 ; i++)
    cl.free_efrags[i].entnext = &cl.free_efrags[i+1];
  cl.free_efrags[i].entnext = NULL;
}

/*
=====================
CL_Disconnect

Sends a disconnect message to the server
This is also called on Host_Error, so it shouldn't cause any errors
=====================
*/
void CL_Disconnect (void)
{
  byte  final[10];

  connect_time = -1;

#ifdef _WIN32
  SetWindowText (mainwindow, "QuakeWorld: disconnected");
#endif

// stop sounds (especially looping!)
  S_StopAllSounds (true);
  
// if running a local server, shut it down
  if (cls.demoplayback)
    CL_StopPlayback ();
  else if (cls.state != ca_disconnected)
  {
    if (cls.demorecording)
      CL_Stop_f ();

    final[0] = clc_stringcmd;
    strcpy (final+1, "drop");
    Netchan_Transmit (&cls.netchan, 6, final);
    Netchan_Transmit (&cls.netchan, 6, final);
    Netchan_Transmit (&cls.netchan, 6, final);

    cls.state = ca_disconnected;

    cls.demoplayback = cls.demorecording = cls.timedemo = false;
  }
  Cam_Reset();

  if (cls.download) {
    fclose(cls.download);
    cls.download = NULL;
  }

  CL_StopUpload();

}

void CL_Disconnect_f (void)
{
  CL_Disconnect ();
}

/*
====================
CL_User_f

user <name or userid>

Dump userdata / masterdata for a user
====================
*/
void CL_User_f (void)
{
  int   uid;
  int   i;

  if (Cmd_Argc() != 2)
  {
    Con_Printf ("Usage: user <username / userid>\n");
    return;
  }

  uid = atoi(Cmd_Argv(1));

  for (i=0 ; i<MAX_CLIENTS ; i++)
  {
    if (!cl.players[i].name[0])
      continue;
    if (cl.players[i].userid == uid
    || !strcmp(cl.players[i].name, Cmd_Argv(1)) )
    {
      Info_Print (cl.players[i].userinfo);
      return;
    }
  }
  Con_Printf ("User not in server.\n");
}

/*
====================
CL_Users_f

Dump userids for all current players
====================
*/
void CL_Users_f (void)
{
  int   i;
  int   c;

  c = 0;
  Con_Printf ("userid frags name\n");
  Con_Printf ("------ ----- ----\n");
  for (i=0 ; i<MAX_CLIENTS ; i++)
  {
    if (cl.players[i].name[0])
    {
      Con_Printf ("%6i %4i %s\n", cl.players[i].userid, cl.players[i].frags, cl.players[i].name);
      c++;
    }
  }

  Con_Printf ("%i total users\n", c);
}

void CL_Color_f (void)
{
  // just for quake compatability...
  int   top, bottom;
  char  num[16];

  if (Cmd_Argc() == 1)
  {
    Con_Printf ("\"color\" is \"%s %s\"\n",
      Info_ValueForKey (cls.userinfo, "topcolor"),
      Info_ValueForKey (cls.userinfo, "bottomcolor") );
    Con_Printf ("color <0-13> [0-13]\n");
    return;
  }

  if (Cmd_Argc() == 2)
    top = bottom = atoi(Cmd_Argv(1));
  else
  {
    top = atoi(Cmd_Argv(1));
    bottom = atoi(Cmd_Argv(2));
  }
  
  top &= 15;
  if (top > 13)
    top = 13;
  bottom &= 15;
  if (bottom > 13)
    bottom = 13;
  
  sprintf (num, "%i", top);
  Cvar_Set ("topcolor", num);
  sprintf (num, "%i", bottom);
  Cvar_Set ("bottomcolor", num);
}

/*
==================
CL_FullServerinfo_f

Sent by server when serverinfo changes
==================
*/
void CL_FullServerinfo_f (void)
{
  char *p;
  float v;

  if (Cmd_Argc() != 2)
  {
    Con_Printf ("usage: fullserverinfo <complete info string>\n");
    return;
  }

  strcpy (cl.serverinfo, Cmd_Argv(1));

  if ((p = Info_ValueForKey(cl.serverinfo, "*vesion")) && *p) {
    v = Q_atof(p);
    if (v) {
      if (!server_version)
        Con_Printf("Version %1.2f Server\n", v);
      server_version = v;
    }
  }
}

/*
==================
CL_FullInfo_f

Allow clients to change userinfo
==================
Casey was here :)
*/
void CL_FullInfo_f (void)
{
  char  key[512];
  char  value[512];
  char  *o;
  char  *s;

  if (Cmd_Argc() != 2)
  {
    Con_Printf ("fullinfo <complete info string>\n");
    return;
  }

  s = Cmd_Argv(1);
  if (*s == '\\')
    s++;
  while (*s)
  {
    o = key;
    while (*s && *s != '\\')
      *o++ = *s++;
    *o = 0;

    if (!*s)
    {
      Con_Printf ("MISSING VALUE\n");
      return;
    }

    o = value;
    s++;
    while (*s && *s != '\\')
      *o++ = *s++;
    *o = 0;

    if (*s)
      s++;

    if (!stricmp(key, pmodel_name) || !stricmp(key, emodel_name))
      continue;

    Info_SetValueForKey (cls.userinfo, key, value, MAX_INFO_STRING);
  }
}

/*
==================
CL_SetInfo_f

Allow clients to change userinfo
==================
*/
void CL_SetInfo_f (void)
{
  if (Cmd_Argc() == 1)
  {
    Info_Print (cls.userinfo);
    return;
  }
  if (Cmd_Argc() != 3)
  {
    Con_Printf ("usage: setinfo [ <key> <value> ]\n");
    return;
  }
  if (!stricmp(Cmd_Argv(1), pmodel_name) || !strcmp(Cmd_Argv(1), emodel_name))
    return;

  Info_SetValueForKey (cls.userinfo, Cmd_Argv(1), Cmd_Argv(2), MAX_INFO_STRING);
  if (cls.state >= ca_connected)
    Cmd_ForwardToServer ();
}

/*
====================
CL_Packet_f

packet <destination> <contents>

Contents allows \n escape character
====================
*/
void CL_Packet_f (void)
{
  char  send[2048];
  int   i, l;
  char  *in, *out;
  netadr_t  adr;

  if (Cmd_Argc() != 3)
  {
    Con_Printf ("packet <destination> <contents>\n");
    return;
  }

  if (!NET_StringToAdr (Cmd_Argv(1), &adr))
  {
    Con_Printf ("Bad address\n");
    return;
  }

  in = Cmd_Argv(2);
  out = send+4;
  send[0] = send[1] = send[2] = send[3] = 0xff;

  l = strlen (in);
  for (i=0 ; i<l ; i++)
  {
    if (in[i] == '\\' && in[i+1] == 'n')
    {
      *out++ = '\n';
      i++;
    }
    else
      *out++ = in[i];
  }
  *out = 0;

  NET_SendPacket (out-send, send, adr);
}


/*
=====================
CL_NextDemo

Called to play the next demo in the demo loop
=====================
*/
void CL_NextDemo (void)
{
  char  str[1024];

  if (cls.demonum == -1)
    return;   // don't play demos

  if (!cls.demos[cls.demonum][0] || cls.demonum == MAX_DEMOS)
  {
    cls.demonum = 0;
    if (!cls.demos[cls.demonum][0])
    {
//      Con_Printf ("No demos listed with startdemos\n");
      cls.demonum = -1;
      return;
    }
  }

  sprintf (str,"playdemo %s\n", cls.demos[cls.demonum]);
  Cbuf_InsertText (str);
  cls.demonum++;
}


/*
=================
CL_Changing_f

Just sent as a hint to the client that they should
drop to full console
=================
*/
void CL_Changing_f (void)
{
  if (cls.download)  // don't change when downloading
    return;

  S_StopAllSounds (true);
  cl.intermission = 0;
  cls.state = ca_connected; // not active anymore, but not disconnected
  Con_Printf ("\nChanging map...\n");
}


/*
=================
CL_Reconnect_f

The server is changing levels
=================
*/
void CL_Reconnect_f (void)
{
  if (cls.download)  // don't change when downloading
    return;

  S_StopAllSounds (true);

  if (cls.state == ca_connected) {
    Con_Printf ("reconnecting...\n");
    MSG_WriteChar (&cls.netchan.message, clc_stringcmd);
    MSG_WriteString (&cls.netchan.message, "new");
    return;
  }

  if (!*cls.servername) {
    Con_Printf("No server to reconnect to...\n");
    return;
  }

  CL_Disconnect();
  CL_BeginServerConnect();
}

/*
=================
CL_ConnectionlessPacket

Responses to broadcasts, etc
=================
*/
void CL_ConnectionlessPacket (void)
{
  char  *s;
  int   c;

    MSG_BeginReading ();
    MSG_ReadLong ();        // skip the -1

  c = MSG_ReadByte ();
  if (!cls.demoplayback)
    Con_Printf ("%s: ", NET_AdrToString (net_from));
//  Con_DPrintf ("%s", net_message.data + 5);
  if (c == S2C_CONNECTION)
  {
    Con_Printf ("connection\n");
    if (cls.state >= ca_connected)
    {
      if (!cls.demoplayback)
        Con_Printf ("Dup connect received.  Ignored.\n");
      return;
    }
    Netchan_Setup (&cls.netchan, net_from, cls.qport);
    MSG_WriteChar (&cls.netchan.message, clc_stringcmd);
    MSG_WriteString (&cls.netchan.message, "new");  
    cls.state = ca_connected;
    Con_Printf ("Connected.\n");
    allowremotecmd = false; // localid required now for remote cmds
    return;
  }
  // remote command from gui front end
  if (c == A2C_CLIENT_COMMAND)
  {
    char  cmdtext[2048];

    Con_Printf ("client command\n");

    if ((*(unsigned *)net_from.ip != *(unsigned *)net_local_adr.ip
      && *(unsigned *)net_from.ip != htonl(INADDR_LOOPBACK)) )
    {
      Con_Printf ("Command packet from remote host.  Ignored.\n");
      return;
    }
#ifdef _WIN32
    ShowWindow (mainwindow, SW_RESTORE);
    SetForegroundWindow (mainwindow);
#endif
    s = MSG_ReadString ();

    strncpy(cmdtext, s, sizeof(cmdtext) - 1);
    cmdtext[sizeof(cmdtext) - 1] = 0;

    s = MSG_ReadString ();

    while (*s && isspace(*s))
      s++;
    while (*s && isspace(s[strlen(s) - 1]))
      s[strlen(s) - 1] = 0;

    if (!allowremotecmd && (!*localid.string || strcmp(localid.string, s))) {
      if (!*localid.string) {
        Con_Printf("===========================\n");
        Con_Printf("Command packet received from local host, but no "
          "localid has been set.  You may need to upgrade your server "
          "browser.\n");
        Con_Printf("===========================\n");
        return;
      }
      Con_Printf("===========================\n");
      Con_Printf("Invalid localid on command packet received from local host. "
        "\n|%s| != |%s|\n"
        "You may need to reload your server browser and QuakeWorld.\n",
        s, localid.string);
      Con_Printf("===========================\n");
      Cvar_Set("localid", "");
      return;
    }

    Cbuf_AddText (cmdtext);
    allowremotecmd = false;
    return;
  }
  // print command from somewhere
  if (c == A2C_PRINT)
  {
    Con_Printf ("print\n");

    s = MSG_ReadString ();
    Con_Print (s);
    return;
  }

  // ping from somewhere
  if (c == A2A_PING)
  {
    char  data[6];

    Con_Printf ("ping\n");

    data[0] = 0xff;
    data[1] = 0xff;
    data[2] = 0xff;
    data[3] = 0xff;
    data[4] = A2A_ACK;
    data[5] = 0;
    
    NET_SendPacket (6, &data, net_from);
    return;
  }

  if (c == S2C_CHALLENGE) {
    Con_Printf ("challenge\n");

    s = MSG_ReadString ();
    cls.challenge = atoi(s);
    CL_SendConnectPacket ();
    return;
  }

#if 0
  if (c == svc_disconnect) {
    Con_Printf ("disconnect\n");

    Host_EndGame ("Server disconnected");
    return;
  }
#endif

  Con_Printf ("unknown:  %c\n", c);
}


/*
=================
CL_ReadPackets
=================
*/
void CL_ReadPackets (void)
{
//  while (NET_GetPacket ())
  while (CL_GetMessage())
  {
    //
    // remote command packet
    //
    if (*(int *)net_message.data == -1)
    {
      CL_ConnectionlessPacket ();
      continue;
    }

    if (net_message.cursize < 8)
    {
      Con_Printf ("%s: Runt packet\n",NET_AdrToString(net_from));
      continue;
    }

    //
    // packet from server
    //
    if (!cls.demoplayback && 
      !NET_CompareAdr (net_from, cls.netchan.remote_address))
    {
      Con_DPrintf ("%s:sequenced packet without connection\n"
        ,NET_AdrToString(net_from));
      continue;
    }
    if (!Netchan_Process(&cls.netchan))
      continue;   // wasn't accepted for some reason
    CL_ParseServerMessage ();

//    if (cls.demoplayback && cls.state >= ca_active && !CL_DemoBehind())
//      return;
  }

  //
  // check timeout
  //
  if (cls.state >= ca_connected
   && realtime - cls.netchan.last_received > cl_timeout.value)
  {
    Con_Printf ("\nServer connection timed out.\n");
    CL_Disconnect ();
    return;
  }
  
}

//=============================================================================

/*
=====================
CL_Download_f
=====================
*/
void CL_Download_f (void)
{
  char *p, *q;

  if (cls.state == ca_disconnected)
  {
    Con_Printf ("Must be connected.\n");
    return;
  }

  if (Cmd_Argc() != 2)
  {
    Con_Printf ("Usage: download <datafile>\n");
    return;
  }

  sprintf (cls.downloadname, "%s/%s", com_gamedir, Cmd_Argv(1));

  p = cls.downloadname;
  for (;;) {
    if ((q = strchr(p, '/')) != NULL) {
      *q = 0;
      Sys_mkdir(cls.downloadname);
      *q = '/';
      p = q + 1;
    } else
      break;
  }

  strcpy(cls.downloadtempname, cls.downloadname);
  cls.download = fopen (cls.downloadname, "wb");
  cls.downloadtype = dl_single;

  MSG_WriteByte (&cls.netchan.message, clc_stringcmd);
  SZ_Print (&cls.netchan.message, va("download %s\n",Cmd_Argv(1)));
}

#ifdef _WINDOWS
#include <windows.h>
/*
=================
CL_Minimize_f
=================
*/
void CL_Windows_f (void) {
//  if (modestate == MS_WINDOWED)
//    ShowWindow(mainwindow, SW_MINIMIZE);
//  else
    SendMessage(mainwindow, WM_SYSKEYUP, VK_TAB, 1 | (0x0F << 16) | (1<<29));
}
#endif

/*
=================
CL_Init
=================
*/
void CL_Init (void)
{
  extern  cvar_t    baseskin;
  extern  cvar_t    noskins;
  char st[80];

  cls.state = ca_disconnected;

  Info_SetValueForKey (cls.userinfo, "name", "unnamed", MAX_INFO_STRING);
  Info_SetValueForKey (cls.userinfo, "topcolor", "0", MAX_INFO_STRING);
  Info_SetValueForKey (cls.userinfo, "bottomcolor", "0", MAX_INFO_STRING);
  Info_SetValueForKey (cls.userinfo, "rate", "2500", MAX_INFO_STRING);
  Info_SetValueForKey (cls.userinfo, "msg", "1", MAX_INFO_STRING);
  sprintf (st, "%4.2f-%04d", VERSION, build_number());
  Info_SetValueForStarKey (cls.userinfo, "*ver", st, MAX_INFO_STRING);

  CL_InitInput ();
  CL_InitTEnts ();
  CL_InitPrediction ();
  CL_InitCam ();
  Pmove_Init ();
  
//
// register our commands
//
  Cvar_RegisterVariable (&show_fps);
  Cvar_RegisterVariable (&host_speeds);
  Cvar_RegisterVariable (&developer);

  Cvar_RegisterVariable (&cl_warncmd);
  Cvar_RegisterVariable (&cl_upspeed);
  Cvar_RegisterVariable (&cl_forwardspeed);
  Cvar_RegisterVariable (&cl_backspeed);
  Cvar_RegisterVariable (&cl_sidespeed);
  Cvar_RegisterVariable (&cl_movespeedkey);
  Cvar_RegisterVariable (&cl_yawspeed);
  Cvar_RegisterVariable (&cl_pitchspeed);
  Cvar_RegisterVariable (&cl_anglespeedkey);
  Cvar_RegisterVariable (&cl_shownet);
  Cvar_RegisterVariable (&cl_sbar);
  Cvar_RegisterVariable (&cl_hudswap);
  Cvar_RegisterVariable (&cl_maxfps);
  Cvar_RegisterVariable (&cl_timeout);
  Cvar_RegisterVariable (&lookspring);
  Cvar_RegisterVariable (&lookstrafe);
  Cvar_RegisterVariable (&sensitivity);

  Cvar_RegisterVariable (&m_pitch);
  Cvar_RegisterVariable (&m_yaw);
  Cvar_RegisterVariable (&m_forward);
  Cvar_RegisterVariable (&m_side);

  Cvar_RegisterVariable (&rcon_password);
  Cvar_RegisterVariable (&rcon_address);

  Cvar_RegisterVariable (&entlatency);
  Cvar_RegisterVariable (&cl_predict_players2);
  Cvar_RegisterVariable (&cl_predict_players);
  Cvar_RegisterVariable (&cl_solid_players);

  Cvar_RegisterVariable (&localid);

  Cvar_RegisterVariable (&baseskin);
  Cvar_RegisterVariable (&noskins);

  //
  // info mirrors
  //
  Cvar_RegisterVariable (&name);
  Cvar_RegisterVariable (&password);
  Cvar_RegisterVariable (&spectator);
  Cvar_RegisterVariable (&skin);
  Cvar_RegisterVariable (&team);
  Cvar_RegisterVariable (&topcolor);
  Cvar_RegisterVariable (&bottomcolor);
  Cvar_RegisterVariable (&rate);
  Cvar_RegisterVariable (&msg);
  Cvar_RegisterVariable (&noaim);


  Cmd_AddCommand ("version", CL_Version_f);

  Cmd_AddCommand ("changing", CL_Changing_f);
  Cmd_AddCommand ("disconnect", CL_Disconnect_f);
  Cmd_AddCommand ("record", CL_Record_f);
  Cmd_AddCommand ("rerecord", CL_ReRecord_f);
  Cmd_AddCommand ("stop", CL_Stop_f);
  Cmd_AddCommand ("playdemo", CL_PlayDemo_f);
  Cmd_AddCommand ("timedemo", CL_TimeDemo_f);

  Cmd_AddCommand ("skins", Skin_Skins_f);
  Cmd_AddCommand ("allskins", Skin_AllSkins_f);

  Cmd_AddCommand ("quit", CL_Quit_f);

  Cmd_AddCommand ("connect", CL_Connect_f);
  Cmd_AddCommand ("reconnect", CL_Reconnect_f);

  Cmd_AddCommand ("rcon", CL_Rcon_f);
  Cmd_AddCommand ("packet", CL_Packet_f);
  Cmd_AddCommand ("user", CL_User_f);
  Cmd_AddCommand ("users", CL_Users_f);

  Cmd_AddCommand ("setinfo", CL_SetInfo_f);
  Cmd_AddCommand ("fullinfo", CL_FullInfo_f);
  Cmd_AddCommand ("fullserverinfo", CL_FullServerinfo_f);

  Cmd_AddCommand ("color", CL_Color_f);
  Cmd_AddCommand ("download", CL_Download_f);

  Cmd_AddCommand ("nextul", CL_NextUpload);
  Cmd_AddCommand ("stopul", CL_StopUpload);

//
// forward to server commands
//
  Cmd_AddCommand ("kill", NULL);
  Cmd_AddCommand ("pause", NULL);
  Cmd_AddCommand ("say", NULL);
  Cmd_AddCommand ("say_team", NULL);
  Cmd_AddCommand ("serverinfo", NULL);

//
//  Windows commands
//
#ifdef _WINDOWS
  Cmd_AddCommand ("windows", CL_Windows_f);
#endif
}


/*
================
Host_EndGame

Call this to drop to a console without exiting the qwcl
================
*/
void Host_EndGame (char *message, ...)
{
  va_list   argptr;
  char    string[1024];
  
  va_start (argptr,message);
  vsprintf (string,message,argptr);
  va_end (argptr);
  Con_Printf ("\n===========================\n");
  Con_Printf ("Host_EndGame: %s\n",string);
  Con_Printf ("===========================\n\n");
  
  CL_Disconnect ();

  longjmp (host_abort, 1);
}

/*
================
Host_Error

This shuts down the client and exits qwcl
================
*/
void Host_Error (char *error, ...)
{
  va_list   argptr;
  char    string[1024];
  static  qboolean inerror = false;
  
  if (inerror)
    Sys_Error ("Host_Error: recursively entered");
  inerror = true;
  
  va_start (argptr,error);
  vsprintf (string,error,argptr);
  va_end (argptr);
  Con_Printf ("Host_Error: %s\n",string);
  
  CL_Disconnect ();
  cls.demonum = -1;

  inerror = false;

// FIXME
  Sys_Error ("Host_Error: %s\n",string);
}


/*
===============
Host_WriteConfiguration

Writes key bindings and archived cvars to config.cfg
===============
*/
void Host_WriteConfiguration (void)
{
  FILE  *f;

  if (host_initialized)
  {
    f = fopen (va("%s/config.cfg",com_gamedir), "w");
    if (!f)
    {
      Con_Printf ("Couldn't write config.cfg.\n");
      return;
    }
    
    Key_WriteBindings (f);
    Cvar_WriteVariables (f);

    fclose (f);
  }
}


//============================================================================

#if 0
/*
==================
Host_SimulationTime

This determines if enough time has passed to run a simulation frame
==================
*/
qboolean Host_SimulationTime(float time)
{
  float fps;

  if (oldrealtime > realtime)
    oldrealtime = 0;

  if (cl_maxfps.value)
    fps = max(30.0, min(cl_maxfps.value, 72.0));
  else
    fps = max(30.0, min(rate.value/80.0, 72.0));

  if (!cls.timedemo && (realtime + time) - oldrealtime < 1.0/fps)
    return false;     // framerate is too high
  return true;
}
#endif


/*
==================
Host_Frame

Runs all active servers
==================
*/
int   nopacketcount;
void Host_Frame (float time)
{
  static double   time1 = 0;
  static double   time2 = 0;
  static double   time3 = 0;
  int     pass1, pass2, pass3;
  float fps;
  if (setjmp (host_abort) )
    return;     // something bad happened, or the server disconnected

  // decide the simulation time
  realtime += time;
  if (oldrealtime > realtime)
    oldrealtime = 0;

  if (cl_maxfps.value)
    fps = max(30.0, min(cl_maxfps.value, 72.0));
  else
    fps = max(30.0, min(rate.value/80.0, 72.0));

  if (!cls.timedemo && realtime - oldrealtime < 1.0/fps)
    return;     // framerate is too high

  host_frametime = realtime - oldrealtime;
  oldrealtime = realtime;
  if (host_frametime > 0.2)
    host_frametime = 0.2;
    
  // get new key events
  Sys_SendKeyEvents ();

  // allow mice or other external controllers to add commands
  IN_Commands ();

  // process console commands
  Cbuf_Execute ();

  // fetch results from server
  CL_ReadPackets ();

  // send intentions now
  // resend a connection request if necessary
  if (cls.state == ca_disconnected) {
    CL_CheckForResend ();
  } else
    CL_SendCmd ();

  // Set up prediction for other players
  CL_SetUpPlayerPrediction(false);

  // do client side motion prediction
  CL_PredictMove ();

  // Set up prediction for other players
  CL_SetUpPlayerPrediction(true);

  // build a refresh entity list
  CL_EmitEntities ();

  // update video
  if (host_speeds.value)
    time1 = Sys_DoubleTime ();

  SCR_UpdateScreen ();

  if (host_speeds.value)
    time2 = Sys_DoubleTime ();
    
  // update audio
  if (cls.state == ca_active)
  {
    S_Update (r_origin, vpn, vright, vup);
    CL_DecayLights ();
  }
  else
    S_Update (vec3_origin, vec3_origin, vec3_origin, vec3_origin);
  
  CDAudio_Update();

  if (host_speeds.value)
  {
    pass1 = (time1 - time3)*1000;
    time3 = Sys_DoubleTime ();
    pass2 = (time2 - time1)*1000;
    pass3 = (time3 - time2)*1000;
    Con_Printf ("%3i tot %3i server %3i gfx %3i snd\n",
          pass1+pass2+pass3, pass1, pass2, pass3);
  }

  host_framecount++;
  fps_count++;
}

static void simple_crypt(char *buf, int len)
{
  while (len--)
    *buf++ ^= 0xff;
}

void Host_FixupModelNames(void)
{
  simple_crypt(emodel_name, sizeof(emodel_name) - 1);
  simple_crypt(pmodel_name, sizeof(pmodel_name) - 1);
  simple_crypt(prespawn_name,  sizeof(prespawn_name)  - 1);
  simple_crypt(modellist_name, sizeof(modellist_name) - 1);
  simple_crypt(soundlist_name, sizeof(soundlist_name) - 1);
}

//============================================================================

/*
====================
Host_Init
====================
*/
void Host_Init (quakeparms_t *parms)
{
  COM_InitArgv (parms->argc, parms->argv);
  COM_AddParm ("-game");
  COM_AddParm ("qw");

  Sys_mkdir("qw");

  if (COM_CheckParm ("-minmemory"))
    parms->memsize = MINIMUM_MEMORY;

  host_parms = *parms;

  if (parms->memsize < MINIMUM_MEMORY)
    Sys_Error ("Only %4.1f megs of memory reported, can't execute game", parms->memsize / (float)0x100000);

  Memory_Init (parms->membase, parms->memsize);
  Cbuf_Init ();
  Cmd_Init ();
  V_Init ();

  COM_Init ();

  Host_FixupModelNames();
  
  NET_Init (PORT_CLIENT);
  Netchan_Init ();

  W_LoadWadFile ("gfx.wad");
  Key_Init ();
  Con_Init ();  
  M_Init ();  
  Mod_Init ();
  
#ifdef AMIGA
  Con_Printf ("QWcl%s V%4.2f\n",
#ifdef __PPC__
#ifdef WOS
              "WOS",
#else
              "PPC",
#endif
#else
              "68k",
#endif
              AMIGA_VERSION);
  Con_Printf ("Amiga port 2000 by Frank Wille and Steffen Haeuser\n");
#endif
//  Con_Printf ("Exe: "__TIME__" "__DATE__"\n");
  Con_Printf ("%4.1f megs RAM used.\n",parms->memsize/ (1024*1024.0));
  
  R_InitTextures ();
 
  host_basepal = (byte *)COM_LoadHunkFile ("gfx/palette.lmp");
  if (!host_basepal)
    Sys_Error ("Couldn't load gfx/palette.lmp");
  host_colormap = (byte *)COM_LoadHunkFile ("gfx/colormap.lmp");
  if (!host_colormap)
    Sys_Error ("Couldn't load gfx/colormap.lmp");
#ifdef __linux__
  IN_Init ();
  CDAudio_Init ();
  VID_Init (host_basepal);
  Draw_Init ();
  SCR_Init ();
  R_Init ();

//  S_Init ();    // S_Init is now done as part of VID. Sigh.
  
  cls.state = ca_disconnected;
  Sbar_Init ();
  CL_Init ();
#else
  VID_Init (host_basepal);
  Draw_Init ();
  SCR_Init ();
  R_Init ();
//  S_Init ();    // S_Init is now done as part of VID. Sigh.
#ifdef GLQUAKE
  S_Init();
#endif

  cls.state = ca_disconnected;
  CDAudio_Init ();
  Sbar_Init ();
  CL_Init ();
  IN_Init ();
#endif

  Cbuf_InsertText ("exec quake.rc\n");
  Cbuf_AddText ("echo Type connect <internet address> or use GameSpy to connect to a game.\n");
  Cbuf_AddText ("cl_warncmd 1\n");

  Hunk_AllocName (0, "-HOST_HUNKLEVEL-");
  host_hunklevel = Hunk_LowMark ();

  host_initialized = true;

  Con_Printf ("\nClient Version %4.2f (Build %04d)\n\n", VERSION, build_number());

  Con_Printf (" QuakeWorld Initialized \n");  
}


/*
===============
Host_Shutdown

FIXME: this is a callback from Sys_Quit and Sys_Error.  It would be better
to run quit through here before the final handoff to the sys code.
===============
*/
void Host_Shutdown(void)
{
  static qboolean isdown = false;
  
  if (isdown)
  {
    printf ("recursive shutdown\n");
    return;
  }
  isdown = true;

  Host_WriteConfiguration (); 
    
  CDAudio_Shutdown ();
  NET_Shutdown ();
  S_Shutdown();
  IN_Shutdown ();
  if (host_basepal)
    VID_Shutdown();
}

