/*
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.

*/
// sv_user.c -- server code for moving users

#include "qwsvdef.h"

edict_t *sv_player;

usercmd_t cmd;

cvar_t  cl_rollspeed = {"cl_rollspeed", "200"};
cvar_t  cl_rollangle = {"cl_rollangle", "2.0"};
cvar_t  sv_spectalk = {"sv_spectalk", "1"};

cvar_t  sv_mapcheck = {"sv_mapcheck", "1"};

extern  vec3_t  player_mins;

extern int fp_messages, fp_persecond, fp_secondsdead;
extern char fp_msg[];
extern cvar_t pausable;

/*
============================================================

USER STRINGCMD EXECUTION

host_client and sv_player will be valid.
============================================================
*/

/*
================
SV_New_f

Sends the first message from the server to a connected client.
This will be sent on the initial connection and upon each server load.
================
*/
void SV_New_f (void)
{
  char    *gamedir;
  int     playernum;

  if (host_client->state == cs_spawned)
    return;

  host_client->state = cs_connected;
  host_client->connection_started = realtime;

  // send the info about the new client to all connected clients
//  SV_FullClientUpdate (host_client, &sv.reliable_datagram);
//  host_client->sendinfo = true;

  gamedir = Info_ValueForKey (svs.info, "*gamedir");
  if (!gamedir[0])
    gamedir = "qw";

//NOTE:  This doesn't go through ClientReliableWrite since it's before the user
//spawns.  These functions are written to not overflow
  if (host_client->num_backbuf) {
    Con_Printf("WARNING %s: [SV_New] Back buffered (%d0, clearing", host_client->name, host_client->netchan.message.cursize); 
    host_client->num_backbuf = 0;
    SZ_Clear(&host_client->netchan.message);
  }

  // send the serverdata
  MSG_WriteByte (&host_client->netchan.message, svc_serverdata);
  MSG_WriteLong (&host_client->netchan.message, PROTOCOL_VERSION);
  MSG_WriteLong (&host_client->netchan.message, svs.spawncount);
  MSG_WriteString (&host_client->netchan.message, gamedir);

  playernum = NUM_FOR_EDICT(host_client->edict)-1;
  if (host_client->spectator)
    playernum |= 128;
  MSG_WriteByte (&host_client->netchan.message, playernum);

  // send full levelname
  MSG_WriteString (&host_client->netchan.message, PR_GetString(sv.edicts->v.message));

  // send the movevars
  MSG_WriteFloat(&host_client->netchan.message, movevars.gravity);
  MSG_WriteFloat(&host_client->netchan.message, movevars.stopspeed);
  MSG_WriteFloat(&host_client->netchan.message, movevars.maxspeed);
  MSG_WriteFloat(&host_client->netchan.message, movevars.spectatormaxspeed);
  MSG_WriteFloat(&host_client->netchan.message, movevars.accelerate);
  MSG_WriteFloat(&host_client->netchan.message, movevars.airaccelerate);
  MSG_WriteFloat(&host_client->netchan.message, movevars.wateraccelerate);
  MSG_WriteFloat(&host_client->netchan.message, movevars.friction);
  MSG_WriteFloat(&host_client->netchan.message, movevars.waterfriction);
  MSG_WriteFloat(&host_client->netchan.message, movevars.entgravity);

  // send music
  MSG_WriteByte (&host_client->netchan.message, svc_cdtrack);
  MSG_WriteByte (&host_client->netchan.message, sv.edicts->v.sounds);

  // send server info string
  MSG_WriteByte (&host_client->netchan.message, svc_stufftext);
  MSG_WriteString (&host_client->netchan.message, va("fullserverinfo \"%s\"\n", svs.info) );
}

/*
==================
SV_Soundlist_f
==================
*/
void SV_Soundlist_f (void)
{
  char    **s;
  int     n;

  if (host_client->state != cs_connected)
  {
    Con_Printf ("soundlist not valid -- allready spawned\n");
    return;
  }

  // handle the case of a level changing while a client was connecting
  if ( atoi(Cmd_Argv(1)) != svs.spawncount )
  {
    Con_Printf ("SV_Soundlist_f from different level\n");
    SV_New_f ();
    return;
  }

  n = atoi(Cmd_Argv(2));
  
//NOTE:  This doesn't go through ClientReliableWrite since it's before the user
//spawns.  These functions are written to not overflow
  if (host_client->num_backbuf) {
    Con_Printf("WARNING %s: [SV_Soundlist] Back buffered (%d0, clearing", host_client->name, host_client->netchan.message.cursize); 
    host_client->num_backbuf = 0;
    SZ_Clear(&host_client->netchan.message);
  }

  MSG_WriteByte (&host_client->netchan.message, svc_soundlist);
  MSG_WriteByte (&host_client->netchan.message, n);
  for (s = sv.sound_precache+1 + n ; 
    *s && host_client->netchan.message.cursize < (MAX_MSGLEN/2); 
    s++, n++)
    MSG_WriteString (&host_client->netchan.message, *s);

  MSG_WriteByte (&host_client->netchan.message, 0);

  // next msg
  if (*s)
    MSG_WriteByte (&host_client->netchan.message, n);
  else
    MSG_WriteByte (&host_client->netchan.message, 0);
}

/*
==================
SV_Modellist_f
==================
*/
void SV_Modellist_f (void)
{
  char    **s;
  int     n;

  if (host_client->state != cs_connected)
  {
    Con_Printf ("modellist not valid -- allready spawned\n");
    return;
  }
  
  // handle the case of a level changing while a client was connecting
  if ( atoi(Cmd_Argv(1)) != svs.spawncount )
  {
    Con_Printf ("SV_Modellist_f from different level\n");
    SV_New_f ();
    return;
  }

  n = atoi(Cmd_Argv(2));

//NOTE:  This doesn't go through ClientReliableWrite since it's before the user
//spawns.  These functions are written to not overflow
  if (host_client->num_backbuf) {
    Con_Printf("WARNING %s: [SV_Modellist] Back buffered (%d0, clearing", host_client->name, host_client->netchan.message.cursize); 
    host_client->num_backbuf = 0;
    SZ_Clear(&host_client->netchan.message);
  }

  MSG_WriteByte (&host_client->netchan.message, svc_modellist);
  MSG_WriteByte (&host_client->netchan.message, n);
  for (s = sv.model_precache+1+n ; 
    *s && host_client->netchan.message.cursize < (MAX_MSGLEN/2); 
    s++, n++)
    MSG_WriteString (&host_client->netchan.message, *s);
  MSG_WriteByte (&host_client->netchan.message, 0);

  // next msg
  if (*s)
    MSG_WriteByte (&host_client->netchan.message, n);
  else
    MSG_WriteByte (&host_client->netchan.message, 0);
}

/*
==================
SV_PreSpawn_f
==================
*/
void SV_PreSpawn_f (void)
{
  unsigned  buf;
  unsigned  check;

  if (host_client->state != cs_connected)
  {
    Con_Printf ("prespawn not valid -- allready spawned\n");
    return;
  }
  
  // handle the case of a level changing while a client was connecting
  if ( atoi(Cmd_Argv(1)) != svs.spawncount )
  {
    Con_Printf ("SV_PreSpawn_f from different level\n");
    SV_New_f ();
    return;
  }
  
  buf = atoi(Cmd_Argv(2));
  if (buf >= sv.num_signon_buffers)
    buf = 0;

  if (!buf) {
    // should be three numbers following containing checksums
    check = atoi(Cmd_Argv(3));

//    Con_DPrintf("Client check = %d\n", check);

    if (sv_mapcheck.value && check != sv.worldmodel->checksum &&
      check != sv.worldmodel->checksum2) {
      SV_ClientPrintf (host_client, PRINT_HIGH, 
        "Map model file does not match (%s), %i != %i/%i.\n"
        "You may need a new version of the map, or the proper install files.\n",
        sv.modelname, check, sv.worldmodel->checksum, sv.worldmodel->checksum2);
      SV_DropClient (host_client); 
      return;
    }
    host_client->checksum = check;
  }

//NOTE:  This doesn't go through ClientReliableWrite since it's before the user
//spawns.  These functions are written to not overflow
  if (host_client->num_backbuf) {
    Con_Printf("WARNING %s: [SV_PreSpawn] Back buffered (%d0, clearing", host_client->name, host_client->netchan.message.cursize); 
    host_client->num_backbuf = 0;
    SZ_Clear(&host_client->netchan.message);
  }

  SZ_Write (&host_client->netchan.message, 
    sv.signon_buffers[buf],
    sv.signon_buffer_size[buf]);

  buf++;
  if (buf == sv.num_signon_buffers)
  { // all done prespawning
    MSG_WriteByte (&host_client->netchan.message, svc_stufftext);
    MSG_WriteString (&host_client->netchan.message, va("cmd spawn %i 0\n",svs.spawncount) );
  }
  else
  { // need to prespawn more
    MSG_WriteByte (&host_client->netchan.message, svc_stufftext);
    MSG_WriteString (&host_client->netchan.message, 
      va("cmd prespawn %i %i\n", svs.spawncount, buf) );
  }
}

/*
==================
SV_Spawn_f
==================
*/
void SV_Spawn_f (void)
{
  int   i;
  client_t  *client;
  edict_t *ent;
  eval_t *val;
  int n;

  if (host_client->state != cs_connected)
  {
    Con_Printf ("Spawn not valid -- allready spawned\n");
    return;
  }

// handle the case of a level changing while a client was connecting
  if ( atoi(Cmd_Argv(1)) != svs.spawncount )
  {
    Con_Printf ("SV_Spawn_f from different level\n");
    SV_New_f ();
    return;
  }

  n = atoi(Cmd_Argv(2));

  // make sure n is valid
  if ( n < 0 || n > MAX_CLIENTS )
  {
    Con_Printf ("SV_Spawn_f invalid client start\n");
    SV_New_f ();
    return;
  }


  
// send all current names, colors, and frag counts
  // FIXME: is this a good thing?
  SZ_Clear (&host_client->netchan.message);

// send current status of all other players

  // normally this could overflow, but no need to check due to backbuf
  for (i=n, client = svs.clients + n ; i<MAX_CLIENTS ; i++, client++)
    SV_FullClientUpdateToClient (client, host_client);
  
// send all current light styles
  for (i=0 ; i<MAX_LIGHTSTYLES ; i++)
  {
    ClientReliableWrite_Begin (host_client, svc_lightstyle, 
      3 + (sv.lightstyles[i] ? strlen(sv.lightstyles[i]) : 1));
    ClientReliableWrite_Byte (host_client, (char)i);
    ClientReliableWrite_String (host_client, sv.lightstyles[i]);
  }

  // set up the edict
  ent = host_client->edict;

  memset (&ent->v, 0, progs->entityfields * 4);
  ent->v.colormap = NUM_FOR_EDICT(ent);
  ent->v.team = 0;  // FIXME
  ent->v.netname = PR_SetString(host_client->name);

  host_client->entgravity = 1.0;
  val = GetEdictFieldValue(ent, "gravity");
  if (val)
    val->_float = 1.0;
  host_client->maxspeed = sv_maxspeed.value;
  val = GetEdictFieldValue(ent, "maxspeed");
  if (val)
    val->_float = sv_maxspeed.value;

//
// force stats to be updated
//
  memset (host_client->stats, 0, sizeof(host_client->stats));

  ClientReliableWrite_Begin (host_client, svc_updatestatlong, 6);
  ClientReliableWrite_Byte (host_client, STAT_TOTALSECRETS);
  ClientReliableWrite_Long (host_client, pr_global_struct->total_secrets);

  ClientReliableWrite_Begin (host_client, svc_updatestatlong, 6);
  ClientReliableWrite_Byte (host_client, STAT_TOTALMONSTERS);
  ClientReliableWrite_Long (host_client, pr_global_struct->total_monsters);

  ClientReliableWrite_Begin (host_client, svc_updatestatlong, 6);
  ClientReliableWrite_Byte (host_client, STAT_SECRETS);
  ClientReliableWrite_Long (host_client, pr_global_struct->found_secrets);

  ClientReliableWrite_Begin (host_client, svc_updatestatlong, 6);
  ClientReliableWrite_Byte (host_client, STAT_MONSTERS);
  ClientReliableWrite_Long (host_client, pr_global_struct->killed_monsters);

  // get the client to check and download skins
  // when that is completed, a begin command will be issued
  ClientReliableWrite_Begin (host_client, svc_stufftext, 8);
  ClientReliableWrite_String (host_client, "skins\n" );

}

/*
==================
SV_SpawnSpectator
==================
*/
void SV_SpawnSpectator (void)
{
  int   i;
  edict_t *e;

  VectorCopy (vec3_origin, sv_player->v.origin);
  VectorCopy (vec3_origin, sv_player->v.view_ofs);
  sv_player->v.view_ofs[2] = 22;

  // search for an info_playerstart to spawn the spectator at
  for (i=MAX_CLIENTS-1 ; i<sv.num_edicts ; i++)
  {
    e = EDICT_NUM(i);
    if (!strcmp(PR_GetString(e->v.classname), "info_player_start"))
    {
      VectorCopy (e->v.origin, sv_player->v.origin);
      return;
    }
  }

}

/*
==================
SV_Begin_f
==================
*/
void SV_Begin_f (void)
{
  unsigned pmodel = 0, emodel = 0;
  int   i;

  if (host_client->state == cs_spawned)
    return; // don't begin again

  host_client->state = cs_spawned;
  
  // handle the case of a level changing while a client was connecting
  if ( atoi(Cmd_Argv(1)) != svs.spawncount )
  {
    Con_Printf ("SV_Begin_f from different level\n");
    SV_New_f ();
    return;
  }

  if (host_client->spectator)
  {
    SV_SpawnSpectator ();

    if (SpectatorConnect) {
      // copy spawn parms out of the client_t
      for (i=0 ; i< NUM_SPAWN_PARMS ; i++)
        (&pr_global_struct->parm1)[i] = host_client->spawn_parms[i];
  
      // call the spawn function
      pr_global_struct->time = sv.time;
      pr_global_struct->self = EDICT_TO_PROG(sv_player);
      PR_ExecuteProgram (SpectatorConnect);
    }
  }
  else
  {
    // copy spawn parms out of the client_t
    for (i=0 ; i< NUM_SPAWN_PARMS ; i++)
      (&pr_global_struct->parm1)[i] = host_client->spawn_parms[i];

    // call the spawn function
    pr_global_struct->time = sv.time;
    pr_global_struct->self = EDICT_TO_PROG(sv_player);
    PR_ExecuteProgram (pr_global_struct->ClientConnect);

    // actually spawn the player
    pr_global_struct->time = sv.time;
    pr_global_struct->self = EDICT_TO_PROG(sv_player);
    PR_ExecuteProgram (pr_global_struct->PutClientInServer);  
  }

  // clear the net statistics, because connecting gives a bogus picture
  host_client->netchan.frame_latency = 0;
  host_client->netchan.frame_rate = 0;
  host_client->netchan.drop_count = 0;
  host_client->netchan.good_count = 0;

  //check he's not cheating

  pmodel = atoi(Info_ValueForKey (host_client->userinfo, "pmodel"));
  emodel = atoi(Info_ValueForKey (host_client->userinfo, "emodel"));

  if (pmodel != sv.model_player_checksum ||
    emodel != sv.eyes_player_checksum)
    SV_BroadcastPrintf (PRINT_HIGH, "%s WARNING: non standard player/eyes model detected\n", host_client->name);

  // if we are paused, tell the client
  if (sv.paused) {
    ClientReliableWrite_Begin (host_client, svc_setpause, 2);
    ClientReliableWrite_Byte (host_client, sv.paused);
    SV_ClientPrintf(host_client, PRINT_HIGH, "Server is paused.\n");
  }

#if 0
//
// send a fixangle over the reliable channel to make sure it gets there
// Never send a roll angle, because savegames can catch the server
// in a state where it is expecting the client to correct the angle
// and it won't happen if the game was just loaded, so you wind up
// with a permanent head tilt
  ent = EDICT_NUM( 1 + (host_client - svs.clients) );
  MSG_WriteByte (&host_client->netchan.message, svc_setangle);
  for (i=0 ; i < 2 ; i++)
    MSG_WriteAngle (&host_client->netchan.message, ent->v.angles[i] );
  MSG_WriteAngle (&host_client->netchan.message, 0 );
#endif
}

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

/*
==================
SV_NextDownload_f
==================
*/
void SV_NextDownload_f (void)
{
  byte  buffer[1024];
  int   r;
  int   percent;
  int   size;

  if (!host_client->download)
    return;

  r = host_client->downloadsize - host_client->downloadcount;
  if (r > 768)
    r = 768;
  r = fread (buffer, 1, r, host_client->download);
  ClientReliableWrite_Begin (host_client, svc_download, 6+r);
  ClientReliableWrite_Short (host_client, r);

  host_client->downloadcount += r;
  size = host_client->downloadsize;
  if (!size)
    size = 1;
  percent = host_client->downloadcount*100/size;
  ClientReliableWrite_Byte (host_client, percent);
  ClientReliableWrite_SZ (host_client, buffer, r);

  if (host_client->downloadcount != host_client->downloadsize)
    return;

  fclose (host_client->download);
  host_client->download = NULL;

}

void OutofBandPrintf(netadr_t where, char *fmt, ...)
{
  va_list   argptr;
  char  send[1024];
  
  send[0] = 0xff;
  send[1] = 0xff;
  send[2] = 0xff;
  send[3] = 0xff;
  send[4] = A2C_PRINT;
  va_start (argptr, fmt);
  vsprintf (send+5, fmt, argptr);
  va_end (argptr);

  NET_SendPacket (strlen(send)+1, send, where);
}

/*
==================
SV_NextUpload
==================
*/
void SV_NextUpload (void)
{
  byte  buffer[1024];
  int   r;
  int   percent;
  int   size;
  client_t *client;

  if (!*host_client->uploadfn) {
    SV_ClientPrintf(host_client, PRINT_HIGH, "Upload denied\n");
    ClientReliableWrite_Begin (host_client, svc_stufftext, 8);
    ClientReliableWrite_String (host_client, "stopul");

    // suck out rest of packet
    size = MSG_ReadShort ();  MSG_ReadByte ();
    msg_readcount += size;
    return;
  }

  size = MSG_ReadShort ();
  percent = MSG_ReadByte ();

  if (!host_client->upload)
  {
    host_client->upload = fopen(host_client->uploadfn, "wb");
    if (!host_client->upload) {
      Sys_Printf("Can't create %s\n", host_client->uploadfn);
      ClientReliableWrite_Begin (host_client, svc_stufftext, 8);
      ClientReliableWrite_String (host_client, "stopul");
      *host_client->uploadfn = 0;
      return;
    }
    Sys_Printf("Receiving %s from %d...\n", host_client->uploadfn, host_client->userid);
    if (host_client->remote_snap)
      OutofBandPrintf(host_client->snap_from, "Server receiving %s from %d...\n", host_client->uploadfn, host_client->userid);
  }

  fwrite (net_message.data + msg_readcount, 1, size, host_client->upload);
  msg_readcount += size;

Con_DPrintf ("UPLOAD: %d received\n", size);

  if (percent != 100) {
    ClientReliableWrite_Begin (host_client, svc_stufftext, 8);
    ClientReliableWrite_String (host_client, "nextul\n");
  } else {
    fclose (host_client->upload);
    host_client->upload = NULL;

    Sys_Printf("%s upload completed.\n", host_client->uploadfn);

    if (host_client->remote_snap) {
      char *p;

      if ((p = strchr(host_client->uploadfn, '/')) != NULL)
        p++;
      else
        p = host_client->uploadfn;
      OutofBandPrintf(host_client->snap_from, "%s upload completed.\nTo download, enter:\ndownload %s\n", 
        host_client->uploadfn, p);
    }
  }

}

/*
==================
SV_BeginDownload_f
==================
*/
void SV_BeginDownload_f(void)
{
  char  *name;
  extern  cvar_t  allow_download;
  extern  cvar_t  allow_download_skins;
  extern  cvar_t  allow_download_models;
  extern  cvar_t  allow_download_sounds;
  extern  cvar_t  allow_download_maps;
  extern  int   file_from_pak; // ZOID did file come from pak?

  name = Cmd_Argv(1);
// hacked by zoid to allow more conrol over download
    // first off, no .. or global allow check
  if (strstr (name, "..") || !allow_download.value
    // leading dot is no good
    || *name == '.' 
    // leading slash bad as well, must be in subdir
    || *name == '/'
    // next up, skin check
    || (strncmp(name, "skins/", 6) == 0 && !allow_download_skins.value)
    // now models
    || (strncmp(name, "progs/", 6) == 0 && !allow_download_models.value)
    // now sounds
    || (strncmp(name, "sound/", 6) == 0 && !allow_download_sounds.value)
    // now maps (note special case for maps, must not be in pak)
    || (strncmp(name, "maps/", 6) == 0 && !allow_download_maps.value)
    // MUST be in a subdirectory  
    || !strstr (name, "/") )  
  { // don't allow anything with .. path
    ClientReliableWrite_Begin (host_client, svc_download, 4);
    ClientReliableWrite_Short (host_client, -1);
    ClientReliableWrite_Byte (host_client, 0);
    return;
  }

  if (host_client->download) {
    fclose (host_client->download);
    host_client->download = NULL;
  }

  // lowercase name (needed for casesen file systems)
  {
    char *p;

    for (p = name; *p; p++)
      *p = (char)tolower(*p);
  }


  host_client->downloadsize = COM_FOpenFile (name, &host_client->download);
  host_client->downloadcount = 0;

  if (!host_client->download
    // special check for maps, if it came from a pak file, don't allow
    // download  ZOID
    || (strncmp(name, "maps/", 5) == 0 && file_from_pak))
  {
    if (host_client->download) {
      fclose(host_client->download);
      host_client->download = NULL;
    }

    Sys_Printf ("Couldn't download %s to %s\n", name, host_client->name);
    ClientReliableWrite_Begin (host_client, svc_download, 4);
    ClientReliableWrite_Short (host_client, -1);
    ClientReliableWrite_Byte (host_client, 0);
    return;
  }

  SV_NextDownload_f ();
  Sys_Printf ("Downloading %s to %s\n", name, host_client->name);
}

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

/*
==================
SV_Say
==================
*/
void SV_Say (qboolean team)
{
  client_t *client;
  int   j, tmp;
  char  *p;
  char  text[2048];
  char  t1[32], *t2;

  if (Cmd_Argc () < 2)
    return;

  if (team)
  {
    strncpy (t1, Info_ValueForKey (host_client->userinfo, "team"), 31);
    t1[31] = 0;
  }

  if (host_client->spectator && (!sv_spectalk.value || team))
    sprintf (text, "[SPEC] %s: ", host_client->name);
  else if (team)
    sprintf (text, "(%s): ", host_client->name);
  else {
    sprintf (text, "%s: ", host_client->name);
  }

  if (fp_messages) {
    if (!sv.paused && realtime<host_client->lockedtill) {
      SV_ClientPrintf(host_client, PRINT_CHAT,
        "You can't talk for %d more seconds\n", 
          (int) (host_client->lockedtill - realtime));
      return;
    }
    tmp = host_client->whensaidhead - fp_messages + 1;
    if (tmp < 0)
      tmp = 10+tmp;
    if (!sv.paused &&
      host_client->whensaid[tmp] && (realtime-host_client->whensaid[tmp] < fp_persecond)) {
      host_client->lockedtill = realtime + fp_secondsdead;
      if (fp_msg[0])
        SV_ClientPrintf(host_client, PRINT_CHAT,
          "FloodProt: %s\n", fp_msg);
      else
        SV_ClientPrintf(host_client, PRINT_CHAT,
          "FloodProt: You can't talk for %d seconds.\n", fp_secondsdead);
      return;
    }
    host_client->whensaidhead++;
    if (host_client->whensaidhead > 9)
      host_client->whensaidhead = 0;
    host_client->whensaid[host_client->whensaidhead] = realtime;
  }

  p = Cmd_Args();

  if (*p == '"')
  {
    p++;
    p[Q_strlen(p)-1] = 0;
  }

  Q_strcat(text, p);
  Q_strcat(text, "\n");

  Sys_Printf ("%s", text);

  for (j = 0, client = svs.clients; j < MAX_CLIENTS; j++, client++)
  {
    if (client->state != cs_spawned)
      continue;
    if (host_client->spectator && !sv_spectalk.value)
      if (!client->spectator)
        continue;

    if (team)
    {
      // the spectator team
      if (host_client->spectator) {
        if (!client->spectator)
          continue;
      } else {
        t2 = Info_ValueForKey (client->userinfo, "team");
        if (strcmp(t1, t2) || client->spectator)
          continue; // on different teams
      }
    }
    SV_ClientPrintf(client, PRINT_CHAT, "%s", text);
  }
}


/*
==================
SV_Say_f
==================
*/
void SV_Say_f(void)
{
  SV_Say (false);
}
/*
==================
SV_Say_Team_f
==================
*/
void SV_Say_Team_f(void)
{
  SV_Say (true);
}



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

/*
=================
SV_Pings_f

The client is showing the scoreboard, so send new ping times for all
clients
=================
*/
void SV_Pings_f (void)
{
  client_t *client;
  int   j;

  for (j = 0, client = svs.clients; j < MAX_CLIENTS; j++, client++)
  {
    if (client->state != cs_spawned)
      continue;

    ClientReliableWrite_Begin (host_client, svc_updateping, 4);
    ClientReliableWrite_Byte (host_client, j);
    ClientReliableWrite_Short (host_client, SV_CalcPing(client));
    ClientReliableWrite_Begin (host_client, svc_updatepl, 4);
    ClientReliableWrite_Byte (host_client, j);
    ClientReliableWrite_Byte (host_client, client->lossage);
  }
}



/*
==================
SV_Kill_f
==================
*/
void SV_Kill_f (void)
{
  if (sv_player->v.health <= 0)
  {
    SV_ClientPrintf (host_client, PRINT_HIGH, "Can't suicide -- allready dead!\n");
    return;
  }
  
  pr_global_struct->time = sv.time;
  pr_global_struct->self = EDICT_TO_PROG(sv_player);
  PR_ExecuteProgram (pr_global_struct->ClientKill);
}

/*
==================
SV_TogglePause
==================
*/
void SV_TogglePause (const char *msg)
{
  int i;
  client_t *cl;

  sv.paused ^= 1;

  if (msg)
    SV_BroadcastPrintf (PRINT_HIGH, "%s", msg);

  // send notification to all clients
  for (i=0, cl = svs.clients ; i<MAX_CLIENTS ; i++, cl++)
  {
    if (!cl->state)
      continue;
    ClientReliableWrite_Begin (cl, svc_setpause, 2);
    ClientReliableWrite_Byte (cl, sv.paused);
  }
}


/*
==================
SV_Pause_f
==================
*/
void SV_Pause_f (void)
{
  int i;
  client_t *cl;
  char st[sizeof(host_client->name) + 32];

  if (!pausable.value) {
    SV_ClientPrintf (host_client, PRINT_HIGH, "Pause not allowed.\n");
    return;
  }

  if (host_client->spectator) {
    SV_ClientPrintf (host_client, PRINT_HIGH, "Spectators can not pause.\n");
    return;
  }

  if (sv.paused)
    sprintf (st, "%s paused the game\n", host_client->name);
  else
    sprintf (st, "%s unpaused the game\n", host_client->name);

  SV_TogglePause(st);
}


/*
=================
SV_Drop_f

The client is going to disconnect, so remove the connection immediately
=================
*/
void SV_Drop_f (void)
{
  SV_EndRedirect ();
  if (!host_client->spectator)
    SV_BroadcastPrintf (PRINT_HIGH, "%s dropped\n", host_client->name);
  SV_DropClient (host_client);  
}

/*
=================
SV_PTrack_f

Change the bandwidth estimate for a client
=================
*/
void SV_PTrack_f (void)
{
  int   i;
  edict_t *ent, *tent;
  
  if (!host_client->spectator)
    return;

  if (Cmd_Argc() != 2)
  {
    // turn off tracking
    host_client->spec_track = 0;
    ent = EDICT_NUM(host_client - svs.clients + 1);
    tent = EDICT_NUM(0);
    ent->v.goalentity = EDICT_TO_PROG(tent);
    return;
  }
  
  i = atoi(Cmd_Argv(1));
  if (i < 0 || i >= MAX_CLIENTS || svs.clients[i].state != cs_spawned ||
    svs.clients[i].spectator) {
    SV_ClientPrintf (host_client, PRINT_HIGH, "Invalid client to track\n");
    host_client->spec_track = 0;
    ent = EDICT_NUM(host_client - svs.clients + 1);
    tent = EDICT_NUM(0);
    ent->v.goalentity = EDICT_TO_PROG(tent);
    return;
  }
  host_client->spec_track = i + 1; // now tracking

  ent = EDICT_NUM(host_client - svs.clients + 1);
  tent = EDICT_NUM(i + 1);
  ent->v.goalentity = EDICT_TO_PROG(tent);
}


/*
=================
SV_Rate_f

Change the bandwidth estimate for a client
=================
*/
void SV_Rate_f (void)
{
  int   rate;
  
  if (Cmd_Argc() != 2)
  {
    SV_ClientPrintf (host_client, PRINT_HIGH, "Current rate is %i\n",
      (int)(1.0/host_client->netchan.rate + 0.5));
    return;
  }
  
  rate = atoi(Cmd_Argv(1));
  if (rate < 500)
    rate = 500;
  if (rate > 10000)
    rate = 10000;

  SV_ClientPrintf (host_client, PRINT_HIGH, "Net rate set to %i\n", rate);
  host_client->netchan.rate = 1.0/rate;
}


/*
=================
SV_Msg_f

Change the message level for a client
=================
*/
void SV_Msg_f (void)
{ 
  if (Cmd_Argc() != 2)
  {
    SV_ClientPrintf (host_client, PRINT_HIGH, "Current msg level is %i\n",
      host_client->messagelevel);
    return;
  }
  
  host_client->messagelevel = atoi(Cmd_Argv(1));

  SV_ClientPrintf (host_client, PRINT_HIGH, "Msg level set to %i\n", host_client->messagelevel);
}

/*
==================
SV_SetInfo_f

Allow clients to change userinfo
==================
*/
void SV_SetInfo_f (void)
{
  int i;
  char oldval[MAX_INFO_STRING];


  if (Cmd_Argc() == 1)
  {
    Con_Printf ("User info settings:\n");
    Info_Print (host_client->userinfo);
    return;
  }

  if (Cmd_Argc() != 3)
  {
    Con_Printf ("usage: setinfo [ <key> <value> ]\n");
    return;
  }

  if (Cmd_Argv(1)[0] == '*')
    return;   // don't set priveledged values

  strcpy(oldval, Info_ValueForKey(host_client->userinfo, Cmd_Argv(1)));

  Info_SetValueForKey (host_client->userinfo, Cmd_Argv(1), Cmd_Argv(2), MAX_INFO_STRING);
// name is extracted below in ExtractFromUserInfo
//  strncpy (host_client->name, Info_ValueForKey (host_client->userinfo, "name")
//    , sizeof(host_client->name)-1); 
//  SV_FullClientUpdate (host_client, &sv.reliable_datagram);
//  host_client->sendinfo = true;

  if (!strcmp(Info_ValueForKey(host_client->userinfo, Cmd_Argv(1)), oldval))
    return; // key hasn't changed

  // process any changed values
  SV_ExtractFromUserinfo (host_client);

  i = host_client - svs.clients;
  MSG_WriteByte (&sv.reliable_datagram, svc_setinfo);
  MSG_WriteByte (&sv.reliable_datagram, i);
  MSG_WriteString (&sv.reliable_datagram, Cmd_Argv(1));
  MSG_WriteString (&sv.reliable_datagram, Info_ValueForKey(host_client->userinfo, Cmd_Argv(1)));
}

/*
==================
SV_ShowServerinfo_f

Dumps the serverinfo info string
==================
*/
void SV_ShowServerinfo_f (void)
{
  Info_Print (svs.info);
}

void SV_NoSnap_f(void)
{
  if (*host_client->uploadfn) {
    *host_client->uploadfn = 0;
    SV_BroadcastPrintf (PRINT_HIGH, "%s refused remote screenshot\n", host_client->name);
  }
}

typedef struct
{
  char  *name;
  void  (*func) (void);
} ucmd_t;

ucmd_t ucmds[] =
{
  {"new", SV_New_f},
  {"modellist", SV_Modellist_f},
  {"soundlist", SV_Soundlist_f},
  {"prespawn", SV_PreSpawn_f},
  {"spawn", SV_Spawn_f},
  {"begin", SV_Begin_f},

  {"drop", SV_Drop_f},
  {"pings", SV_Pings_f},

// issued by hand at client consoles  
  {"rate", SV_Rate_f},
  {"kill", SV_Kill_f},
  {"pause", SV_Pause_f},
  {"msg", SV_Msg_f},

  {"say", SV_Say_f},
  {"say_team", SV_Say_Team_f},

  {"setinfo", SV_SetInfo_f},

  {"serverinfo", SV_ShowServerinfo_f},

  {"download", SV_BeginDownload_f},
  {"nextdl", SV_NextDownload_f},

  {"ptrack", SV_PTrack_f}, //ZOID - used with autocam

  {"snap", SV_NoSnap_f},
  
  {NULL, NULL}
};

/*
==================
SV_ExecuteUserCommand
==================
*/
void SV_ExecuteUserCommand (char *s)
{
  ucmd_t  *u;
  
  Cmd_TokenizeString (s);
  sv_player = host_client->edict;

  SV_BeginRedirect (RD_CLIENT);

  for (u=ucmds ; u->name ; u++)
    if (!strcmp (Cmd_Argv(0), u->name) )
    {
      u->func ();
      break;
    }

  if (!u->name)
    Con_Printf ("Bad user command: %s\n", Cmd_Argv(0));

  SV_EndRedirect ();
}

/*
===========================================================================

USER CMD EXECUTION

===========================================================================
*/

/*
===============
V_CalcRoll

Used by view and sv_user
===============
*/
float V_CalcRoll (vec3_t angles, vec3_t velocity)
{
  vec3_t  forward, right, up;
  float sign;
  float side;
  float value;
  
  AngleVectors (angles, forward, right, up);
  side = DotProduct (velocity, right);
  sign = side < 0 ? -1 : 1;
  side = fabs(side);
  
  value = cl_rollangle.value;

  if (side < cl_rollspeed.value)
    side = side * value / cl_rollspeed.value;
  else
    side = value;
  
  return side*sign;
  
}




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

vec3_t  pmove_mins, pmove_maxs;

/*
====================
AddLinksToPmove

====================
*/
void AddLinksToPmove ( areanode_t *node )
{
  link_t    *l, *next;
  edict_t   *check;
  int     pl;
  int     i;
  physent_t *pe;

  pl = EDICT_TO_PROG(sv_player);

  // touch linked edicts
  for (l = node->solid_edicts.next ; l != &node->solid_edicts ; l = next)
  {
    next = l->next;
    check = EDICT_FROM_AREA(l);

    if (check->v.owner == pl)
      continue;   // player's own missile
    if (check->v.solid == SOLID_BSP 
      || check->v.solid == SOLID_BBOX 
      || check->v.solid == SOLID_SLIDEBOX)
    {
      if (check == sv_player)
        continue;

      for (i=0 ; i<3 ; i++)
        if (check->v.absmin[i] > pmove_maxs[i]
        || check->v.absmax[i] < pmove_mins[i])
          break;
      if (i != 3)
        continue;
      if (pmove.numphysent == MAX_PHYSENTS)
        return;
      pe = &pmove.physents[pmove.numphysent];
      pmove.numphysent++;

      VectorCopy (check->v.origin, pe->origin);
      pe->info = NUM_FOR_EDICT(check);
      if (check->v.solid == SOLID_BSP)
        pe->model = sv.models[(int)(check->v.modelindex)];
      else
      {
        pe->model = NULL;
        VectorCopy (check->v.mins, pe->mins);
        VectorCopy (check->v.maxs, pe->maxs);
      }
    }
  }
  
// recurse down both sides
  if (node->axis == -1)
    return;

  if ( pmove_maxs[node->axis] > node->dist )
    AddLinksToPmove ( node->children[0] );
  if ( pmove_mins[node->axis] < node->dist )
    AddLinksToPmove ( node->children[1] );
}


/*
================
AddAllEntsToPmove

For debugging
================
*/
void AddAllEntsToPmove (void)
{
  int     e;
  edict_t   *check;
  int     i;
  physent_t *pe;
  int     pl;

  pl = EDICT_TO_PROG(sv_player);
  check = NEXT_EDICT(sv.edicts);
  for (e=1 ; e<sv.num_edicts ; e++, check = NEXT_EDICT(check))
  {
    if (check->free)
      continue;
    if (check->v.owner == pl)
      continue;
    if (check->v.solid == SOLID_BSP 
      || check->v.solid == SOLID_BBOX 
      || check->v.solid == SOLID_SLIDEBOX)
    {
      if (check == sv_player)
        continue;

      for (i=0 ; i<3 ; i++)
        if (check->v.absmin[i] > pmove_maxs[i]
        || check->v.absmax[i] < pmove_mins[i])
          break;
      if (i != 3)
        continue;
      pe = &pmove.physents[pmove.numphysent];

      VectorCopy (check->v.origin, pe->origin);
      pmove.physents[pmove.numphysent].info = e;
      if (check->v.solid == SOLID_BSP)
        pe->model = sv.models[(int)(check->v.modelindex)];
      else
      {
        pe->model = NULL;
        VectorCopy (check->v.mins, pe->mins);
        VectorCopy (check->v.maxs, pe->maxs);
      }

      if (++pmove.numphysent == MAX_PHYSENTS)
        break;
    }
  }
}

/*
===========
SV_PreRunCmd
===========
Done before running a player command.  Clears the touch array
*/
byte playertouch[(MAX_EDICTS+7)/8];

void SV_PreRunCmd(void)
{
  memset(playertouch, 0, sizeof(playertouch));
}

/*
===========
SV_RunCmd
===========
*/
void SV_RunCmd (usercmd_t *ucmd)
{
  edict_t   *ent;
  int     i, n;
  int     oldmsec;

  cmd = *ucmd;

  // chop up very long commands
  if (cmd.msec > 50)
  {
    oldmsec = ucmd->msec;
    cmd.msec = oldmsec/2;
    SV_RunCmd (&cmd);
    cmd.msec = oldmsec/2;
    cmd.impulse = 0;
    SV_RunCmd (&cmd);
    return;
  }

  if (!sv_player->v.fixangle)
    VectorCopy (ucmd->angles, sv_player->v.v_angle);

  sv_player->v.button0 = ucmd->buttons & 1;
  sv_player->v.button2 = (ucmd->buttons & 2)>>1;
  if (ucmd->impulse)
    sv_player->v.impulse = ucmd->impulse;

//
// angles
// show 1/3 the pitch angle and all the roll angle  
  if (sv_player->v.health > 0)
  {
    if (!sv_player->v.fixangle)
    {
      sv_player->v.angles[PITCH] = -sv_player->v.v_angle[PITCH]/3;
      sv_player->v.angles[YAW] = sv_player->v.v_angle[YAW];
    }
    sv_player->v.angles[ROLL] = 
      V_CalcRoll (sv_player->v.angles, sv_player->v.velocity)*4;
  }

  host_frametime = ucmd->msec * 0.001;
  if (host_frametime > 0.1)
    host_frametime = 0.1;

  if (!host_client->spectator)
  {
    pr_global_struct->frametime = host_frametime;

    pr_global_struct->time = sv.time;
    pr_global_struct->self = EDICT_TO_PROG(sv_player);
    PR_ExecuteProgram (pr_global_struct->PlayerPreThink);

    SV_RunThink (sv_player);
  }

  for (i=0 ; i<3 ; i++)
    pmove.origin[i] = sv_player->v.origin[i] + (sv_player->v.mins[i] - player_mins[i]);
  VectorCopy (sv_player->v.velocity, pmove.velocity);
  VectorCopy (sv_player->v.v_angle, pmove.angles);

  pmove.spectator = host_client->spectator;
  pmove.waterjumptime = sv_player->v.teleport_time;
  pmove.numphysent = 1;
  pmove.physents[0].model = sv.worldmodel;
  pmove.cmd = *ucmd;
  pmove.dead = sv_player->v.health <= 0;
  pmove.oldbuttons = host_client->oldbuttons;

  movevars.entgravity = host_client->entgravity;
  movevars.maxspeed = host_client->maxspeed;

  for (i=0 ; i<3 ; i++)
  {
    pmove_mins[i] = pmove.origin[i] - 256;
    pmove_maxs[i] = pmove.origin[i] + 256;
  }
#if 1
  AddLinksToPmove ( sv_areanodes );
#else
  AddAllEntsToPmove ();
#endif

#if 0
{
  int before, after;

before = PM_TestPlayerPosition (pmove.origin);
  PlayerMove ();
after = PM_TestPlayerPosition (pmove.origin);

if (sv_player->v.health > 0 && before && !after )
  Con_Printf ("player %s got stuck in playermove!!!!\n", host_client->name);
}
#else
  PlayerMove ();
#endif

  host_client->oldbuttons = pmove.oldbuttons;
  sv_player->v.teleport_time = pmove.waterjumptime;
  sv_player->v.waterlevel = waterlevel;
  sv_player->v.watertype = watertype;
  if (onground != -1)
  {
    sv_player->v.flags = (int)sv_player->v.flags | FL_ONGROUND;
    sv_player->v.groundentity = EDICT_TO_PROG(EDICT_NUM(pmove.physents[onground].info));
  }
  else
    sv_player->v.flags = (int)sv_player->v.flags & ~FL_ONGROUND;
  for (i=0 ; i<3 ; i++)
    sv_player->v.origin[i] = pmove.origin[i] - (sv_player->v.mins[i] - player_mins[i]);

#if 0
  // truncate velocity the same way the net protocol will
  for (i=0 ; i<3 ; i++)
    sv_player->v.velocity[i] = (int)pmove.velocity[i];
#else
  VectorCopy (pmove.velocity, sv_player->v.velocity);
#endif

  VectorCopy (pmove.angles, sv_player->v.v_angle);

  if (!host_client->spectator)
  {
    // link into place and touch triggers
    SV_LinkEdict (sv_player, true);

    // touch other objects
    for (i=0 ; i<pmove.numtouch ; i++)
    {
      n = pmove.physents[pmove.touchindex[i]].info;
      ent = EDICT_NUM(n);
      if (!ent->v.touch || (playertouch[n/8]&(1<<(n%8))))
        continue;
      pr_global_struct->self = EDICT_TO_PROG(ent);
      pr_global_struct->other = EDICT_TO_PROG(sv_player);
      PR_ExecuteProgram (ent->v.touch);
      playertouch[n/8] |= 1 << (n%8);
    }
  }
}

/*
===========
SV_PostRunCmd
===========
Done after running a player command.
*/
void SV_PostRunCmd(void)
{
  // run post-think

  if (!host_client->spectator) {
    pr_global_struct->time = sv.time;
    pr_global_struct->self = EDICT_TO_PROG(sv_player);
    PR_ExecuteProgram (pr_global_struct->PlayerPostThink);
    SV_RunNewmis ();
  } else if (SpectatorThink) {
    pr_global_struct->time = sv.time;
    pr_global_struct->self = EDICT_TO_PROG(sv_player);
    PR_ExecuteProgram (SpectatorThink);
  }
}


/*
===================
SV_ExecuteClientMessage

The current net_message is parsed for the given client
===================
*/
void SV_ExecuteClientMessage (client_t *cl)
{
  int   c;
  char  *s;
  usercmd_t oldest, oldcmd, newcmd;
  client_frame_t  *frame;
  vec3_t o;
  qboolean  move_issued = false; //only allow one move command
  int   checksumIndex;
  byte  checksum, calculatedChecksum;
  int   seq_hash;

  // calc ping time
  frame = &cl->frames[cl->netchan.incoming_acknowledged & UPDATE_MASK];
  frame->ping_time = realtime - frame->senttime;

  // make sure the reply sequence number matches the incoming
  // sequence number 
  if (cl->netchan.incoming_sequence >= cl->netchan.outgoing_sequence)
    cl->netchan.outgoing_sequence = cl->netchan.incoming_sequence;
  else
    cl->send_message = false; // don't reply, sequences have slipped    

  // save time for ping calculations
  cl->frames[cl->netchan.outgoing_sequence & UPDATE_MASK].senttime = realtime;
  cl->frames[cl->netchan.outgoing_sequence & UPDATE_MASK].ping_time = -1;

  host_client = cl;
  sv_player = host_client->edict;

//  seq_hash = (cl->netchan.incoming_sequence & 0xffff) ; // ^ QW_CHECK_HASH;
  seq_hash = cl->netchan.incoming_sequence;
  
  // mark time so clients will know how much to predict
  // other players
  cl->localtime = sv.time;
  cl->delta_sequence = -1;  // no delta unless requested
  while (1)
  {
    if (msg_badread)
    {
      Con_Printf ("SV_ReadClientMessage: badread\n");
      SV_DropClient (cl);
      return;
    } 

    c = MSG_ReadByte ();
    if (c == -1)
      break;
        
    switch (c)
    {
    default:
      Con_Printf ("SV_ReadClientMessage: unknown command char\n");
      SV_DropClient (cl);
      return;
            
    case clc_nop:
      break;

    case clc_delta:
      cl->delta_sequence = MSG_ReadByte ();
      break;

    case clc_move:
      if (move_issued)
        return;   // someone is trying to cheat...

      move_issued = true;

      checksumIndex = MSG_GetReadCount();
      checksum = (byte)MSG_ReadByte ();

      // read loss percentage
      cl->lossage = MSG_ReadByte();

      MSG_ReadDeltaUsercmd (&nullcmd, &oldest);
      MSG_ReadDeltaUsercmd (&oldest, &oldcmd);
      MSG_ReadDeltaUsercmd (&oldcmd, &newcmd);

      if ( cl->state != cs_spawned )
        break;

      // if the checksum fails, ignore the rest of the packet
      calculatedChecksum = COM_BlockSequenceCRCByte(
        net_message.data + checksumIndex + 1,
        MSG_GetReadCount() - checksumIndex - 1,
        seq_hash);

      if (calculatedChecksum != checksum)
      {
        Con_DPrintf ("Failed command checksum for %s(%d) (%d != %d)\n", 
          cl->name, cl->netchan.incoming_sequence, checksum, calculatedChecksum);
        return;
      }

      if (!sv.paused) {
        SV_PreRunCmd();

        if (net_drop < 20)
        {
          while (net_drop > 2)
          {
            SV_RunCmd (&cl->lastcmd);
            net_drop--;
          }
          if (net_drop > 1)
            SV_RunCmd (&oldest);
          if (net_drop > 0)
            SV_RunCmd (&oldcmd);
        }
        SV_RunCmd (&newcmd);

        SV_PostRunCmd();
      }

      cl->lastcmd = newcmd;
      cl->lastcmd.buttons = 0; // avoid multiple fires on lag
      break;


    case clc_stringcmd: 
      s = MSG_ReadString ();
      SV_ExecuteUserCommand (s);
      break;

    case clc_tmove:
      o[0] = MSG_ReadCoord();
      o[1] = MSG_ReadCoord();
      o[2] = MSG_ReadCoord();
      // only allowed by spectators
      if (host_client->spectator) {
        VectorCopy(o, sv_player->v.origin);
        SV_LinkEdict(sv_player, false);
      }
      break;

    case clc_upload:
      SV_NextUpload();
      break;

    }
  }
}

/*
==============
SV_UserInit
==============
*/
void SV_UserInit (void)
{
  Cvar_RegisterVariable (&cl_rollspeed);
  Cvar_RegisterVariable (&cl_rollangle);
  Cvar_RegisterVariable (&sv_spectalk);
  Cvar_RegisterVariable (&sv_mapcheck);
}


