/*
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_parse.c  -- parse a message received from the server

#include "quakedef.h"

char *svc_strings[] =
{
  "svc_bad",
  "svc_nop",
  "svc_disconnect",
  "svc_updatestat",
  "svc_version",    // [long] server version
  "svc_setview",    // [short] entity number
  "svc_sound",      // <see code>
  "svc_time",     // [float] server time
  "svc_print",      // [string] null terminated string
  "svc_stufftext",    // [string] stuffed into client's console buffer
            // the string should be \n terminated
  "svc_setangle",   // [vec3] set the view angle to this absolute value
  
  "svc_serverinfo",   // [long] version
            // [string] signon string
            // [string]..[0]model cache [string]...[0]sounds cache
            // [string]..[0]item cache
  "svc_lightstyle",   // [byte] [string]
  "svc_updatename",   // [byte] [string]
  "svc_updatefrags",  // [byte] [short]
  "svc_clientdata",   // <shortbits + data>
  "svc_stopsound",    // <see code>
  "svc_updatecolors", // [byte] [byte]
  "svc_particle",   // [vec3] <variable>
  "svc_damage",     // [byte] impact [byte] blood [vec3] from
  
  "svc_spawnstatic",
  "OBSOLETE svc_spawnbinary",
  "svc_spawnbaseline",
  
  "svc_temp_entity",    // <variable>
  "svc_setpause",
  "svc_signonnum",
  "svc_centerprint",
  "svc_killedmonster",
  "svc_foundsecret",
  "svc_spawnstaticsound",
  "svc_intermission",
  "svc_finale",     // [string] music [string] text
  "svc_cdtrack",      // [byte] track [byte] looptrack
  "svc_sellscreen",
  "svc_cutscene"
};

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

/*
===============
CL_EntityNum

This error checks and tracks the total number of entities
===============
*/
entity_t  *CL_EntityNum (int num)
{
  if (num >= cl.num_entities)
  {
    if (num >= MAX_EDICTS)
      Host_Error ("CL_EntityNum: %i is an invalid number",num);
    while (cl.num_entities<=num)
    {
      cl_entities[cl.num_entities].colormap = vid.colormap;
      cl.num_entities++;
    }
  }
    
  return &cl_entities[num];
}


/*
==================
CL_ParseStartSoundPacket
==================
*/
void CL_ParseStartSoundPacket(void)
{
    vec3_t  pos;
    int   channel, ent;
    int   sound_num;
    int   volume;
    int   field_mask;
    float   attenuation;  
  int   i;
             
    field_mask = MSG_ReadByte(); 

    if (field_mask & SND_VOLUME)
    volume = MSG_ReadByte ();
  else
    volume = DEFAULT_SOUND_PACKET_VOLUME;
  
    if (field_mask & SND_ATTENUATION)
    attenuation = MSG_ReadByte () / 64.0;
  else
    attenuation = DEFAULT_SOUND_PACKET_ATTENUATION;
  
  channel = MSG_ReadShort ();
  sound_num = MSG_ReadByte ();

  ent = channel >> 3;
  channel &= 7;

  if (ent > MAX_EDICTS)
    Host_Error ("CL_ParseStartSoundPacket: ent = %i", ent);
  
  for (i=0 ; i<3 ; i++)
    pos[i] = MSG_ReadCoord ();
 
    S_StartSound (ent, channel, cl.sound_precache[sound_num], pos, volume/255.0, attenuation);
}       

/*
==================
CL_KeepaliveMessage

When the client is taking a long time to load stuff, send keepalive messages
so the server doesn't disconnect.
==================
*/
void CL_KeepaliveMessage (void)
{
  float time;
  static float lastmsg;
  int   ret;
  sizebuf_t old;
  byte    olddata[8192];
  
  if (sv.active)
    return;   // no need if server is local
  if (cls.demoplayback)
    return;

// read messages from server, should just be nops
  old = net_message;
  memcpy (olddata, net_message.data, net_message.cursize);
  
  do
  {
    ret = CL_GetMessage ();
    switch (ret)
    {
    default:
      Host_Error ("CL_KeepaliveMessage: CL_GetMessage failed");   
    case 0:
      break;  // nothing waiting
    case 1:
      Host_Error ("CL_KeepaliveMessage: received a message");
      break;
    case 2:
      if (MSG_ReadByte() != svc_nop)
        Host_Error ("CL_KeepaliveMessage: datagram wasn't a nop");
      break;
    }
  } while (ret);

  net_message = old;
  memcpy (net_message.data, olddata, net_message.cursize);

// check time
  time = Sys_FloatTime ();
  if (time - lastmsg < 5)
    return;
  lastmsg = time;

// write out a nop
  Con_Printf ("--> client to server keepalive\n");

  MSG_WriteByte (&cls.message, clc_nop);
  NET_SendMessage (cls.netcon, &cls.message);
  SZ_Clear (&cls.message);
}

/*
==================
CL_ParseServerInfo
==================
*/
void CL_ParseServerInfo (void)
{
  char  *str;
  int   i;
  int   nummodels, numsounds;
  char  model_precache[MAX_MODELS][MAX_QPATH];
  char  sound_precache[MAX_SOUNDS][MAX_QPATH];
  
  Con_DPrintf ("Serverinfo packet received.\n");
//
// wipe the client_state_t struct
//
  CL_ClearState ();

// parse protocol version number
  i = MSG_ReadLong ();
  if (i != PROTOCOL_VERSION)
  {
    Con_Printf ("Server returned version %i, not %i", i, PROTOCOL_VERSION);
    return;
  }

// parse maxclients
  cl.maxclients = MSG_ReadByte ();
  if (cl.maxclients < 1 || cl.maxclients > MAX_SCOREBOARD)
  {
    Con_Printf("Bad maxclients (%u) from server\n", cl.maxclients);
    return;
  }
  cl.scores = Hunk_AllocName (cl.maxclients*sizeof(*cl.scores), "scores");

// parse gametype
  cl.gametype = MSG_ReadByte ();

// parse signon message
  str = MSG_ReadString ();
  strncpy (cl.levelname, str, sizeof(cl.levelname)-1);

// seperate the printfs so the server message can have a color
  Con_Printf("\n\n\35\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\37\n\n");
  Con_Printf ("%c%s\n", 2, str);

//
// first we go through and touch all of the precache data that still
// happens to be in the cache, so precaching something else doesn't
// needlessly purge it
//

// precache models
  memset (cl.model_precache, 0, sizeof(cl.model_precache));
  for (nummodels=1 ; ; nummodels++)
  {
    str = MSG_ReadString ();
    if (!str[0])
      break;
    if (nummodels==MAX_MODELS)
    {
      Con_Printf ("Server sent too many model precaches\n");
      return;
    }
    strcpy (model_precache[nummodels], str);
    Mod_TouchModel (str);
  }

// precache sounds
  memset (cl.sound_precache, 0, sizeof(cl.sound_precache));
  for (numsounds=1 ; ; numsounds++)
  {
    str = MSG_ReadString ();
    if (!str[0])
      break;
    if (numsounds==MAX_SOUNDS)
    {
      Con_Printf ("Server sent too many sound precaches\n");
      return;
    }
    strcpy (sound_precache[numsounds], str);
    S_TouchSound (str);
  }

//
// now we try to load everything else until a cache allocation fails
//

  for (i=1 ; i<nummodels ; i++)
  {
    cl.model_precache[i] = Mod_ForName (model_precache[i], false);
    if (cl.model_precache[i] == NULL)
    {
      Con_Printf("Model %s not found\n", model_precache[i]);
      return;
    }
    CL_KeepaliveMessage ();
  }

  S_BeginPrecaching ();
  for (i=1 ; i<numsounds ; i++)
  {
    cl.sound_precache[i] = S_PrecacheSound (sound_precache[i]);
    CL_KeepaliveMessage ();
  }
  S_EndPrecaching ();


// local state
  cl_entities[0].model = cl.worldmodel = cl.model_precache[1];
  
  R_NewMap ();

  Hunk_Check ();    // make sure nothing is hurt
  
  noclip_anglehack = false;   // noclip is turned off at start  
}


/*
==================
CL_ParseUpdate

Parse an entity update message from the server
If an entities model or origin changes from frame to frame, it must be
relinked.  Other attributes can change without relinking.
==================
*/
int bitcounts[16];

void CL_ParseUpdate (int bits)
{
  int     i;
  model_t   *model;
  int     modnum;
  qboolean  forcelink;
  entity_t  *ent;
  int     num;
  int     skin;

  if (cls.signon == SIGNONS - 1)
  { // first update is the final signon stage
    cls.signon = SIGNONS;
    CL_SignonReply ();
  }

  if (bits & U_MOREBITS)
  {
    i = MSG_ReadByte ();
    bits |= (i<<8);
  }

  if (bits & U_LONGENTITY)  
    num = MSG_ReadShort ();
  else
    num = MSG_ReadByte ();

  ent = CL_EntityNum (num);

for (i=0 ; i<16 ; i++)
if (bits&(1<<i))
  bitcounts[i]++;

  if (ent->msgtime != cl.mtime[1])
    forcelink = true; // no previous frame to lerp from
  else
    forcelink = false;

  ent->msgtime = cl.mtime[0];
  
  if (bits & U_MODEL)
  {
    modnum = MSG_ReadByte ();
    if (modnum >= MAX_MODELS)
      Host_Error ("CL_ParseModel: bad modnum");
  }
  else
    modnum = ent->baseline.modelindex;
    
  model = cl.model_precache[modnum];
  if (model != ent->model)
  {
    ent->model = model;
  // automatic animation (torches, etc) can be either all together
  // or randomized
    if (model)
    {
      if (model->synctype == ST_RAND)
        ent->syncbase = (float)(rand()&0x7fff) / 0x7fff;
      else
        ent->syncbase = 0.0;
    }
    else
      forcelink = true; // hack to make null model players work
#ifdef GLQUAKE
    if (num > 0 && num <= cl.maxclients)
      R_TranslatePlayerSkin (num - 1);
#endif
  }
  
  if (bits & U_FRAME)
    ent->frame = MSG_ReadByte ();
  else
    ent->frame = ent->baseline.frame;

  if (bits & U_COLORMAP)
    i = MSG_ReadByte();
  else
    i = ent->baseline.colormap;
  if (!i)
    ent->colormap = vid.colormap;
  else
  {
    if (i > cl.maxclients)
      Sys_Error ("i >= cl.maxclients");
    ent->colormap = cl.scores[i-1].translations;
  }

#ifdef GLQUAKE
  if (bits & U_SKIN)
    skin = MSG_ReadByte();
  else
    skin = ent->baseline.skin;
  if (skin != ent->skinnum) {
    ent->skinnum = skin;
    if (num > 0 && num <= cl.maxclients)
      R_TranslatePlayerSkin (num - 1);
  }

#else

  if (bits & U_SKIN)
    ent->skinnum = MSG_ReadByte();
  else
    ent->skinnum = ent->baseline.skin;
#endif

  if (bits & U_EFFECTS)
    ent->effects = MSG_ReadByte();
  else
    ent->effects = ent->baseline.effects;

// shift the known values for interpolation
  VectorCopy (ent->msg_origins[0], ent->msg_origins[1]);
  VectorCopy (ent->msg_angles[0], ent->msg_angles[1]);

  if (bits & U_ORIGIN1)
    ent->msg_origins[0][0] = MSG_ReadCoord ();
  else
    ent->msg_origins[0][0] = ent->baseline.origin[0];
  if (bits & U_ANGLE1)
    ent->msg_angles[0][0] = MSG_ReadAngle();
  else
    ent->msg_angles[0][0] = ent->baseline.angles[0];

  if (bits & U_ORIGIN2)
    ent->msg_origins[0][1] = MSG_ReadCoord ();
  else
    ent->msg_origins[0][1] = ent->baseline.origin[1];
  if (bits & U_ANGLE2)
    ent->msg_angles[0][1] = MSG_ReadAngle();
  else
    ent->msg_angles[0][1] = ent->baseline.angles[1];

  if (bits & U_ORIGIN3)
    ent->msg_origins[0][2] = MSG_ReadCoord ();
  else
    ent->msg_origins[0][2] = ent->baseline.origin[2];
  if (bits & U_ANGLE3)
    ent->msg_angles[0][2] = MSG_ReadAngle();
  else
    ent->msg_angles[0][2] = ent->baseline.angles[2];

  if ( bits & U_NOLERP )
    ent->forcelink = true;

  if ( forcelink )
  { // didn't have an update last message
    VectorCopy (ent->msg_origins[0], ent->msg_origins[1]);
    VectorCopy (ent->msg_origins[0], ent->origin);
    VectorCopy (ent->msg_angles[0], ent->msg_angles[1]);
    VectorCopy (ent->msg_angles[0], ent->angles);
    ent->forcelink = true;
  }
}

/*
==================
CL_ParseBaseline
==================
*/
void CL_ParseBaseline (entity_t *ent)
{
  int     i;
  
  ent->baseline.modelindex = MSG_ReadByte ();
  ent->baseline.frame = MSG_ReadByte ();
  ent->baseline.colormap = MSG_ReadByte();
  ent->baseline.skin = MSG_ReadByte();
  for (i=0 ; i<3 ; i++)
  {
    ent->baseline.origin[i] = MSG_ReadCoord ();
    ent->baseline.angles[i] = MSG_ReadAngle ();
  }
}


/*
==================
CL_ParseClientdata

Server information pertaining to this client only
==================
*/
void CL_ParseClientdata (int bits)
{
  int   i, j;
  
  if (bits & SU_VIEWHEIGHT)
    cl.viewheight = MSG_ReadChar ();
  else
    cl.viewheight = DEFAULT_VIEWHEIGHT;

  if (bits & SU_IDEALPITCH)
    cl.idealpitch = MSG_ReadChar ();
  else
    cl.idealpitch = 0;
  
  VectorCopy (cl.mvelocity[0], cl.mvelocity[1]);
  for (i=0 ; i<3 ; i++)
  {
    if (bits & (SU_PUNCH1<<i) )
      cl.punchangle[i] = MSG_ReadChar();
    else
      cl.punchangle[i] = 0;
    if (bits & (SU_VELOCITY1<<i) )
      cl.mvelocity[0][i] = MSG_ReadChar()*16;
    else
      cl.mvelocity[0][i] = 0;
  }

// [always sent]  if (bits & SU_ITEMS)
    i = MSG_ReadLong ();

  if (cl.items != i)
  { // set flash times
    Sbar_Changed ();
    for (j=0 ; j<32 ; j++)
      if ( (i & (1<<j)) && !(cl.items & (1<<j)))
        cl.item_gettime[j] = cl.time;
    cl.items = i;
  }
    
  cl.onground = (bits & SU_ONGROUND) != 0;
  cl.inwater = (bits & SU_INWATER) != 0;

  if (bits & SU_WEAPONFRAME)
    cl.stats[STAT_WEAPONFRAME] = MSG_ReadByte ();
  else
    cl.stats[STAT_WEAPONFRAME] = 0;

  if (bits & SU_ARMOR)
    i = MSG_ReadByte ();
  else
    i = 0;
  if (cl.stats[STAT_ARMOR] != i)
  {
    cl.stats[STAT_ARMOR] = i;
    Sbar_Changed ();
  }

  if (bits & SU_WEAPON)
    i = MSG_ReadByte ();
  else
    i = 0;
  if (cl.stats[STAT_WEAPON] != i)
  {
    cl.stats[STAT_WEAPON] = i;
    Sbar_Changed ();
  }
  
  i = MSG_ReadShort ();
  if (cl.stats[STAT_HEALTH] != i)
  {
    cl.stats[STAT_HEALTH] = i;
    Sbar_Changed ();
  }

  i = MSG_ReadByte ();
  if (cl.stats[STAT_AMMO] != i)
  {
    cl.stats[STAT_AMMO] = i;
    Sbar_Changed ();
  }

  for (i=0 ; i<4 ; i++)
  {
    j = MSG_ReadByte ();
    if (cl.stats[STAT_SHELLS+i] != j)
    {
      cl.stats[STAT_SHELLS+i] = j;
      Sbar_Changed ();
    }
  }

  i = MSG_ReadByte ();

  if (standard_quake)
  {
    if (cl.stats[STAT_ACTIVEWEAPON] != i)
    {
      cl.stats[STAT_ACTIVEWEAPON] = i;
      Sbar_Changed ();
    }
  }
  else
  {
    if (cl.stats[STAT_ACTIVEWEAPON] != (1<<i))
    {
      cl.stats[STAT_ACTIVEWEAPON] = (1<<i);
      Sbar_Changed ();
    }
  }
}

/*
=====================
CL_NewTranslation
=====================
*/
void CL_NewTranslation (int slot)
{
  int   i, j;
  int   top, bottom;
  byte  *dest, *source;
  
  if (slot > cl.maxclients)
    Sys_Error ("CL_NewTranslation: slot > cl.maxclients");
  dest = cl.scores[slot].translations;
  source = vid.colormap;
  memcpy (dest, vid.colormap, sizeof(cl.scores[slot].translations));
  top = cl.scores[slot].colors & 0xf0;
  bottom = (cl.scores[slot].colors &15)<<4;
#ifdef GLQUAKE
  R_TranslatePlayerSkin (slot);
#endif

  for (i=0 ; i<VID_GRADES ; i++, dest += 256, source+=256)
  {
    if (top < 128)  // the artists made some backwards ranges.  sigh.
      memcpy (dest + TOP_RANGE, source + top, 16);
    else
      for (j=0 ; j<16 ; j++)
        dest[TOP_RANGE+j] = source[top+15-j];
        
    if (bottom < 128)
      memcpy (dest + BOTTOM_RANGE, source + bottom, 16);
    else
      for (j=0 ; j<16 ; j++)
        dest[BOTTOM_RANGE+j] = source[bottom+15-j];   
  }
}

/*
=====================
CL_ParseStatic
=====================
*/
void CL_ParseStatic (void)
{
  entity_t *ent;
  int   i;
    
  i = cl.num_statics;
  if (i >= MAX_STATIC_ENTITIES)
    Host_Error ("Too many static entities");
  ent = &cl_static_entities[i];
  cl.num_statics++;
  CL_ParseBaseline (ent);

// copy it to the current state
  ent->model = cl.model_precache[ent->baseline.modelindex];
  ent->frame = ent->baseline.frame;
  ent->colormap = vid.colormap;
  ent->skinnum = ent->baseline.skin;
  ent->effects = ent->baseline.effects;

  VectorCopy (ent->baseline.origin, ent->origin);
  VectorCopy (ent->baseline.angles, ent->angles); 
  R_AddEfrags (ent);
}

/*
===================
CL_ParseStaticSound
===================
*/
void CL_ParseStaticSound (void)
{
  vec3_t    org;
  int     sound_num, vol, atten;
  int     i;
  
  for (i=0 ; i<3 ; i++)
    org[i] = MSG_ReadCoord ();
  sound_num = MSG_ReadByte ();
  vol = MSG_ReadByte ();
  atten = MSG_ReadByte ();
  
  S_StaticSound (cl.sound_precache[sound_num], org, vol, atten);
}


#define SHOWNET(x) if(cl_shownet.value==2)Con_Printf ("%3i:%s\n", msg_readcount-1, x);

/*
=====================
CL_ParseServerMessage
=====================
*/
void CL_ParseServerMessage (void)
{
  int     cmd;
  int     i;
  
//
// if recording demos, copy the message out
//
  if (cl_shownet.value == 1)
    Con_Printf ("%i ",net_message.cursize);
  else if (cl_shownet.value == 2)
    Con_Printf ("------------------\n");
  
  cl.onground = false;  // unless the server says otherwise 
//
// parse the message
//
  MSG_BeginReading ();
  
  while (1)
  {
    if (msg_badread)
      Host_Error ("CL_ParseServerMessage: Bad server message");

    cmd = MSG_ReadByte ();

    if (cmd == -1)
    {
      SHOWNET("END OF MESSAGE");
      return;   // end of message
    }

  // if the high bit of the command byte is set, it is a fast update
    if (cmd & 128)
    {
      SHOWNET("fast update");
      CL_ParseUpdate (cmd&127);
      continue;
    }

    SHOWNET(svc_strings[cmd]);
  
  // other commands
    switch (cmd)
    {
    default:
      Host_Error ("CL_ParseServerMessage: Illegible server message\n");
      break;
      
    case svc_nop:
//      Con_Printf ("svc_nop\n");
      break;
      
    case svc_time:
      cl.mtime[1] = cl.mtime[0];
      cl.mtime[0] = MSG_ReadFloat ();     
      break;
      
    case svc_clientdata:
      i = MSG_ReadShort ();
      CL_ParseClientdata (i);
      break;
    
    case svc_version:
      i = MSG_ReadLong ();
      if (i != PROTOCOL_VERSION)
        Host_Error ("CL_ParseServerMessage: Server is protocol %i instead of %i\n", i, PROTOCOL_VERSION);
      break;
      
    case svc_disconnect:
      Host_EndGame ("Server disconnected\n");

    case svc_print:
      Con_Printf ("%s", MSG_ReadString ());
      break;
      
    case svc_centerprint:
      SCR_CenterPrint (MSG_ReadString ());
      break;
      
    case svc_stufftext:
      Cbuf_AddText (MSG_ReadString ());
      break;
      
    case svc_damage:
      V_ParseDamage ();
      break;
      
    case svc_serverinfo:
      CL_ParseServerInfo ();
      vid.recalc_refdef = true; // leave intermission full screen
      break;
      
    case svc_setangle:
      for (i=0 ; i<3 ; i++)
        cl.viewangles[i] = MSG_ReadAngle ();
      break;
      
    case svc_setview:
      cl.viewentity = MSG_ReadShort ();
      break;
          
    case svc_lightstyle:
      i = MSG_ReadByte ();
      if (i >= MAX_LIGHTSTYLES)
        Sys_Error ("svc_lightstyle > MAX_LIGHTSTYLES");
      Q_strcpy (cl_lightstyle[i].map,  MSG_ReadString());
      cl_lightstyle[i].length = Q_strlen(cl_lightstyle[i].map);
      break;
      
    case svc_sound:
      CL_ParseStartSoundPacket();
      break;
      
    case svc_stopsound:
      i = MSG_ReadShort();
      S_StopSound(i>>3, i&7);
      break;
    
    case svc_updatename:
      Sbar_Changed ();
      i = MSG_ReadByte ();
      if (i >= cl.maxclients)
        Host_Error ("CL_ParseServerMessage: svc_updatename > MAX_SCOREBOARD");
      strcpy (cl.scores[i].name, MSG_ReadString ());
      break;
      
    case svc_updatefrags:
      Sbar_Changed ();
      i = MSG_ReadByte ();
      if (i >= cl.maxclients)
        Host_Error ("CL_ParseServerMessage: svc_updatefrags > MAX_SCOREBOARD");
      cl.scores[i].frags = MSG_ReadShort ();
      break;      

    case svc_updatecolors:
      Sbar_Changed ();
      i = MSG_ReadByte ();
      if (i >= cl.maxclients)
        Host_Error ("CL_ParseServerMessage: svc_updatecolors > MAX_SCOREBOARD");
      cl.scores[i].colors = MSG_ReadByte ();
      CL_NewTranslation (i);
      break;
      
    case svc_particle:
      R_ParseParticleEffect ();
      break;

    case svc_spawnbaseline:
      i = MSG_ReadShort ();
      // must use CL_EntityNum() to force cl.num_entities up
      CL_ParseBaseline (CL_EntityNum(i));
      break;
    case svc_spawnstatic:
      CL_ParseStatic ();
      break;      
    case svc_temp_entity:
      CL_ParseTEnt ();
      break;

    case svc_setpause:
      {
        cl.paused = MSG_ReadByte ();

        if (cl.paused)
        {
          CDAudio_Pause ();
#ifdef _WIN32
          VID_HandlePause (true);
#endif
        }
        else
        {
          CDAudio_Resume ();
#ifdef _WIN32
          VID_HandlePause (false);
#endif
        }
      }
      break;
      
    case svc_signonnum:
      i = MSG_ReadByte ();
      if (i <= cls.signon)
        Host_Error ("Received signon %i when at %i", i, cls.signon);
      cls.signon = i;
      CL_SignonReply ();
      break;

    case svc_killedmonster:
      cl.stats[STAT_MONSTERS]++;
      break;

    case svc_foundsecret:
      cl.stats[STAT_SECRETS]++;
      break;

    case svc_updatestat:
      i = MSG_ReadByte ();
      if (i < 0 || i >= MAX_CL_STATS)
        Sys_Error ("svc_updatestat: %i is invalid", i);
      cl.stats[i] = MSG_ReadLong ();;
      break;
      
    case svc_spawnstaticsound:
      CL_ParseStaticSound ();
      break;

    case svc_cdtrack:
      cl.cdtrack = MSG_ReadByte ();
      cl.looptrack = MSG_ReadByte ();
      if ( (cls.demoplayback || cls.demorecording) && (cls.forcetrack != -1) )
        CDAudio_Play ((byte)cls.forcetrack, true);
      else
        CDAudio_Play ((byte)cl.cdtrack, true);
      break;

    case svc_intermission:
      cl.intermission = 1;
      cl.completed_time = cl.time;
      vid.recalc_refdef = true; // go to full screen
      break;

    case svc_finale:
      cl.intermission = 2;
      cl.completed_time = cl.time;
      vid.recalc_refdef = true; // go to full screen
      SCR_CenterPrint (MSG_ReadString ());      
      break;

    case svc_cutscene:
      cl.intermission = 3;
      cl.completed_time = cl.time;
      vid.recalc_refdef = true; // go to full screen
      SCR_CenterPrint (MSG_ReadString ());      
      break;

    case svc_sellscreen:
      Cmd_ExecuteString ("help", src_command);
      break;
    }
  }
}

