/*
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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

*/
// cl_parse.c  -- parse a message received from the server

#include "cdaudio.h"
#include "client.h"
#include "cmd.h"
#include "console.h"
#include "model.h"
#include "pmove.h"
#include "quakedef.h"
#include "sbar.h"
#include "screen.h"
#include "sys.h"

#ifdef GLQUAKE
#include "glquake.h"
#else
#include "d_iface.h"
#endif

static const 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 clients console buffer
				// the string should be \n terminated
    "svc_setangle",		// [vec3] set the view angle to this absolute
				// value

    "svc_serverdata",		// [long] version ...
    "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",

    "svc_cdtrack",
    "svc_sellscreen",

    "svc_smallkick",
    "svc_bigkick",

    "svc_updateping",
    "svc_updateentertime",

    "svc_updatestatlong",
    "svc_muzzleflash",
    "svc_updateuserinfo",
    "svc_download",
    "svc_playerinfo",
    "svc_nails",
    "svc_choke",
    "svc_modellist",
    "svc_soundlist",
    "svc_packetentities",
    "svc_deltapacketentities",
    "svc_maxspeed",
    "svc_entgravity",

    "svc_setinfo",
    "svc_serverinfo",
    "svc_updatepl",
    "NEW PROTOCOL",
    "NEW PROTOCOL",
    "NEW PROTOCOL",
    "NEW PROTOCOL",
    "NEW PROTOCOL",
    "NEW PROTOCOL",
    "NEW PROTOCOL",
    "NEW PROTOCOL",
    "NEW PROTOCOL",
    "NEW PROTOCOL",
    "NEW PROTOCOL",
    "NEW PROTOCOL",
    "NEW PROTOCOL"
};

static int oldparsecountmod;

int parsecountmod;
double parsecounttime;

int cl_spikeindex, cl_playerindex, cl_flagindex;

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

int packet_latency[NET_TIMINGS];

int
CL_CalcNet(void)
{
    int seq_no, i;
    frame_t *frame;
    int packets_lost;

    seq_no = cls.netchan.outgoing_sequence - UPDATE_BACKUP + 1;
    for (; seq_no <= cls.netchan.outgoing_sequence; seq_no++) {
	frame = &cl.frames[seq_no & UPDATE_MASK];

	if (frame->receivedtime == -1) {
            /* Not yet received, potentially dropped... */
            if (seq_no > cls.netchan.incoming_sequence) {
                packet_latency[seq_no & NET_TIMINGSMASK] = (realtime - frame->senttime) * 20;
            } else {
                packet_latency[seq_no & NET_TIMINGSMASK] = 9999;  // dropped
            }
        } else if (frame->receivedtime == -2) {
	    packet_latency[seq_no & NET_TIMINGSMASK] = 10000; // choked
        } else if (frame->invalid) {
	    packet_latency[seq_no & NET_TIMINGSMASK] = 9998;  // invalid delta
        } else {
	    packet_latency[seq_no & NET_TIMINGSMASK] = (frame->receivedtime - frame->senttime) * 20;
        }
    }

    packets_lost = 0;
    for (i = 0; i < NET_TIMINGS; i++) {
	if (packet_latency[i] == 9999)
	    packets_lost++;
    }

    return packets_lost * 100 / NET_TIMINGS;
}

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

/*
===============
CL_CheckOrDownloadFile

Returns true if the file exists, otherwise it attempts
to start a download from the server.
===============
*/
qboolean
CL_CheckOrDownloadFile(const char *filename)
{
    FILE *f;
    int err;

    if (strstr(filename, "..")) {
	Con_Printf("Refusing to download a path with ..\n");
	return true;
    }

    COM_FOpenFile(filename, &f);
    if (f) {			// it exists, no need to download
	fclose(f);
	return true;
    }
    /* can't download when recording */
    if (cls.demorecording) {
	Con_Printf("Unable to download %s in record mode.\n",
		   cls.downloadname);
	return true;
    }
    /* can't download when playback */
    if (cls.demoplayback)
	return true;

    qsnprintf(cls.downloadname, sizeof(cls.downloadname), "%s", filename);
    Con_Printf("Downloading %s...\n", cls.downloadname);

    /*
     * download to a temp name, and only rename to the real name when
     * done, so if interrupted a runt file wont be left
     */
    err = COM_DefaultExtension(cls.downloadname, ".tmp", cls.downloadtempname,
			       sizeof(cls.downloadtempname));
    if (err) {
	Con_Printf("Refusing download, pathname too long\n");
	return true;
    }

    MSG_WriteByte(&cls.netchan.message, clc_stringcmd);
    MSG_WriteStringf(&cls.netchan.message, "download %s", cls.downloadname);

    cls.downloadnumber++;

    return false;
}

/*
=================
Model_NextDownload
=================
*/
static void
Model_NextDownload(void)
{
    char *s;
    int i;

    if (cls.downloadnumber == 0) {
	Con_Printf("Checking models...\n");
	cls.downloadnumber = 1;
    }

    cls.downloadtype = dl_model;
    for (; cl.model_name[cls.downloadnumber][0]; cls.downloadnumber++) {
	s = cl.model_name[cls.downloadnumber];
	if (s[0] == '*')
	    continue;		// inline brush model
	if (!CL_CheckOrDownloadFile(s))
	    return;		// started a download
    }

    for (i = 1; i < MAX_MODELS; i++) {
	if (!cl.model_name[i][0])
	    break;

	cl.model_precache[i] = Mod_ForName(cl.model_name[i], false);

	if (!cl.model_precache[i]) {
	    Con_Printf("\nThe required model file '%s' could not be found or downloaded.\n\n", cl.model_name[i]);
	    Con_Printf("You may need to download or purchase a %s client "
		       "pack in order to play on this server.\n\n", com_gamedirfile);
	    CL_Disconnect();
	    return;
	}
    }

    // all done
    cl.worldmodel = BrushModel(cl.model_precache[1]);
    R_NewMap();
    Hunk_Check();		// make sure nothing is hurt

    // done with modellist, request first of static signon messages
    MSG_WriteByte(&cls.netchan.message, clc_stringcmd);
    MSG_WriteStringf(&cls.netchan.message, "prespawn %i 0 %i", cl.servercount, cl.worldmodel->checksum2);
}

/*
=================
Sound_NextDownload
=================
*/
static void
Sound_NextDownload(void)
{
    char *s;
    int i;

    if (cls.downloadnumber == 0) {
	Con_Printf("Checking sounds...\n");
	cls.downloadnumber = 1;
    }

    cls.downloadtype = dl_sound;
    for (; cl.sound_name[cls.downloadnumber][0]; cls.downloadnumber++) {
	s = cl.sound_name[cls.downloadnumber];
	if (!CL_CheckOrDownloadFile(va("sound/%s", s)))
	    return;		// started a download
    }

    for (i = 1; i < MAX_SOUNDS; i++) {
	if (!cl.sound_name[i][0])
	    break;
	cl.sound_precache[i] = S_PrecacheSound(cl.sound_name[i]);
    }

    // done with sounds, request models now
    memset(cl.model_precache, 0, sizeof(cl.model_precache));
    cl_playerindex = -1;
    cl_spikeindex = -1;
    cl_flagindex = -1;
    MSG_WriteByte(&cls.netchan.message, clc_stringcmd);
    MSG_WriteStringf(&cls.netchan.message, "modellist %i 0", cl.servercount);
}


/*
======================
CL_RequestNextDownload
======================
*/
static void
CL_RequestNextDownload(void)
{
    switch (cls.downloadtype) {
    case dl_single:
	break;
    case dl_skin:
	Skin_NextDownload();
	break;
    case dl_model:
	Model_NextDownload();
	break;
    case dl_sound:
	Sound_NextDownload();
	break;
    case dl_none:
    default:
	Con_DPrintf("Unknown download type.\n");
    }
}

/*
=====================
CL_ParseDownload

A download message has been received from the server
=====================
*/
static void
CL_ParseDownload(void)
{
    // read the data
    int size = MSG_ReadShort();
    int percent = MSG_ReadByte();

    if (cls.demoplayback) {
	if (size > 0)
	    msg_readcount += size;
	return;			// not in demo playback
    }

    if (size == -1) {
	Con_Printf("File not found.\n");
	if (cls.download) {
	    Con_Printf("cls.download shouldn't have been set\n");
	    fclose(cls.download);
	    cls.download = NULL;
	}
	CL_RequestNextDownload();
	return;
    }
    // open the file if not opened yet
    if (!cls.download) {
	cls.download = COM_FOpenFileCreate(cls.downloadtempname, "wb");
	if (!cls.download) {
	    msg_readcount += size;
	    Con_Printf("Failed to open %s\n", cls.downloadtempname);
	    CL_RequestNextDownload();
	    return;
	}
    }

    fwrite(net_message.data + msg_readcount, 1, size, cls.download);
    msg_readcount += size;

    if (percent != 100) {
// change display routines by zoid
	// request next block
#if 0
	Con_Printf(".");
	if (10 * (percent / 10) != cls.downloadpercent) {
	    cls.downloadpercent = 10 * (percent / 10);
	    Con_Printf("%i%%", cls.downloadpercent);
	}
#endif
	cls.downloadpercent = percent;

	MSG_WriteByte(&cls.netchan.message, clc_stringcmd);
	MSG_WriteString(&cls.netchan.message, "nextdl");
    } else {
#if 0
	Con_Printf("100%%\n");
#endif

	fclose(cls.download);

	// rename the temp file to it's final name
	if (strcmp(cls.downloadtempname, cls.downloadname)) {
	    int error = rename(va("%s/%s", com_gamedir, cls.downloadtempname),
                               va("%s/%s", com_gamedir, cls.downloadname));
	    if (error)
		Con_Printf("failed to rename.\n");
	}

	cls.download = NULL;
	cls.downloadpercent = 0;

	// get another file if needed

	CL_RequestNextDownload();
    }
}

static byte *upload_data;
static int upload_pos;
static int upload_size;

void
CL_NextUpload(void)
{
    byte buffer[1024];
    int r;
    int percent;
    int size;

    if (!upload_data)
	return;

    r = upload_size - upload_pos;
    if (r > 768)
	r = 768;
    memcpy(buffer, upload_data + upload_pos, r);
    MSG_WriteByte(&cls.netchan.message, clc_upload);
    MSG_WriteShort(&cls.netchan.message, r);

    upload_pos += r;
    size = upload_size;
    if (!size)
	size = 1;
    percent = upload_pos * 100 / size;
    MSG_WriteByte(&cls.netchan.message, percent);
    SZ_Write(&cls.netchan.message, buffer, r);

    Con_DPrintf("UPLOAD: %6d: %d written\n", upload_pos - r, r);

    if (upload_pos != upload_size)
	return;

    Con_Printf("Upload completed\n");

    free(upload_data);
    upload_data = 0;
    upload_pos = upload_size = 0;
}

void
CL_StartUpload(byte *data, int size)
{
    if (cls.state < ca_onserver)
	return;			// gotta be connected

    // override
    if (upload_data)
	free(upload_data);

    Con_DPrintf("Upload starting of %d...\n", size);

    upload_data = malloc(size);
    memcpy(upload_data, data, size);
    upload_size = size;
    upload_pos = 0;

    CL_NextUpload();
}

qboolean
CL_IsUploading(void)
{
    if (upload_data)
	return true;
    return false;
}

void
CL_StopUpload(void)
{
    if (upload_data)
	free(upload_data);
    upload_data = NULL;
}

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

  SERVER CONNECTING MESSAGES

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

/*
==================
CL_ParseServerData
==================
*/
static void
CL_ParseServerData(void)
{
    qboolean cflag = false;
    int protover;

    Con_DPrintf("Serverdata packet received.\n");
//
// wipe the client_state_t struct
//
    CL_ClearState();

// parse protocol version number
// allow 2.2 and 2.29 demos to play
    protover = MSG_ReadLong();
    if (protover != PROTOCOL_VERSION &&
	!(cls.demoplayback
	  && (protover == 26 || protover == 27 || protover == 28)))
	Host_EndGame("Server returned version %i, not %i\n"
		     "You probably need to upgrade.\n"
		     "Check http://disenchant.net/",
		     protover, PROTOCOL_VERSION);

    cl.servercount = MSG_ReadLong();

    // game directory
    const char *gamedir = MSG_ReadString();

    // TODO: handle this in Host_Gamedir
    if (strcasecmp(com_gamedirfile, gamedir)) {
	// save current config
	Host_WriteConfiguration();
	cflag = true;
    }

    Host_Gamedir(gamedir, GAME_TYPE_QW);

    //ZOID--run the autoexec.cfg in the gamedir
    //if it exists
    if (cflag) {
        FILE *f;
        COM_FOpenFile("config.cfg", &f);
        if (f) {
	    fclose(f);
	    Cbuf_AddText("cl_warncmd 0\n");
	    Cbuf_AddText("exec config.cfg\n");
	    Cbuf_AddText("exec frontend.cfg\n");
	    Cbuf_AddText("cl_warncmd 1\n");
	}
    }
    // parse player slot, high bit means spectator
    cl.playernum = MSG_ReadByte();
    if (cl.playernum & 128) {
	cl.spectator = true;
	cl.playernum &= ~128;
    }
    // get the full level name
    const char *levelname = MSG_ReadString();
    qsnprintf(cl.levelname, sizeof(cl.levelname), "%s", levelname);

    // get the movevars
    movevars.gravity = MSG_ReadFloat();
    movevars.stopspeed = MSG_ReadFloat();
    movevars.maxspeed = MSG_ReadFloat();
    movevars.spectatormaxspeed = MSG_ReadFloat();
    movevars.accelerate = MSG_ReadFloat();
    movevars.airaccelerate = MSG_ReadFloat();
    movevars.wateraccelerate = MSG_ReadFloat();
    movevars.friction = MSG_ReadFloat();
    movevars.waterfriction = MSG_ReadFloat();
    movevars.entgravity = MSG_ReadFloat();

    // 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, levelname);

    // ask for the sound list next
    memset(cl.sound_name, 0, sizeof(cl.sound_name));
    MSG_WriteByte(&cls.netchan.message, clc_stringcmd);
    MSG_WriteStringf(&cls.netchan.message, "soundlist %i 0", cl.servercount);

    // now waiting for downloads, etc
    cls.state = ca_onserver;
}

/*
==================
CL_ParseSoundlist
==================
*/
static void
CL_ParseSoundlist(void)
{
    const int NAMELEN = sizeof(cl.sound_name[0]);

    char *str, *name;
    int numsounds;
    int n;

// precache sounds
    numsounds = MSG_ReadByte();
    for (;;) {
	str = MSG_ReadString();
	if (!str[0])
	    break;
	numsounds++;
	if (numsounds == MAX_SOUNDS)
	    Host_EndGame("Server sent too many sound_precache");
	name = cl.sound_name[numsounds];
	qsnprintf(name, NAMELEN, "%s", str);
    }

    n = MSG_ReadByte();
    if (n) {
	MSG_WriteByte(&cls.netchan.message, clc_stringcmd);
	MSG_WriteStringf(&cls.netchan.message, "soundlist %i %i",
			 cl.servercount, n);
	return;
    }

    cls.downloadnumber = 0;
    cls.downloadtype = dl_sound;
    Sound_NextDownload();
}

/*
==================
CL_ParseModellist
==================
*/
static void
CL_ParseModellist(void)
{
    const int NAMELEN = sizeof(cl.model_name[0]);
    int nummodels;
    char *str, *name;
    int n;

// precache models and note certain default indexes
    nummodels = MSG_ReadByte();

    for (;;) {
	str = MSG_ReadString();
	if (!str[0])
	    break;
	nummodels++;
	if (nummodels == MAX_MODELS)
	    Host_EndGame("Server sent too many model_precache");
	name = cl.model_name[nummodels];
	qsnprintf(name, NAMELEN, "%s", str);

	if (!strcmp(name, "progs/spike.mdl"))
	    cl_spikeindex = nummodels;
	if (!strcmp(name, "progs/player.mdl"))
	    cl_playerindex = nummodels;
	if (!strcmp(name, "progs/flag.mdl"))
	    cl_flagindex = nummodels;
    }

    n = MSG_ReadByte();

    if (n) {
	MSG_WriteByte(&cls.netchan.message, clc_stringcmd);
	MSG_WriteStringf(&cls.netchan.message, "modellist %i %i",
			 cl.servercount, n);
	return;
    }

    cls.downloadnumber = 0;
    cls.downloadtype = dl_model;
    Model_NextDownload();
}

/*
==================
CL_ParseBaseline
==================
*/
static void
CL_ParseBaseline(entity_state_t *es)
{
    int i;

    es->modelindex = MSG_ReadByte();
    es->frame = MSG_ReadByte();
    es->colormap = MSG_ReadByte();
    es->skinnum = MSG_ReadByte();
    for (i = 0; i < 3; i++) {
	es->origin[i] = MSG_ReadCoord();
	es->angles[i] = MSG_ReadAngle();
    }
}



/*
=====================
CL_ParseStatic

Static entities are non-interactive world objects
like torches
=====================
*/
static void
CL_ParseStatic(void)
{
    entity_t *ent;
    int i;
    entity_state_t es;

    CL_ParseBaseline(&es);

    i = cl.num_statics;
    if (i >= MAX_STATIC_ENTITIES)
	Host_EndGame("Too many static entities");
    ent = &cl_static_entities[i];
    cl.num_statics++;

// copy it to the current state
    ent->model = cl.model_precache[es.modelindex];
    ent->frame = es.frame;
    ent->colormap = vid.colormap;
    ent->skinnum = es.skinnum;
    ent->lerp.flags |= LERP_RESETANIM;

    VectorCopy(es.origin, ent->origin);
    VectorCopy(es.angles, ent->angles);

    R_AddEfrags(ent);
}

/*
===================
CL_ParseStaticSound
===================
*/
static 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);
}



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

ACTION MESSAGES

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

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

    channel = MSG_ReadShort();

    if (channel & SND_VOLUME)
	volume = MSG_ReadByte();
    else
	volume = DEFAULT_SOUND_PACKET_VOLUME;

    if (channel & SND_ATTENUATION)
	attenuation = MSG_ReadByte() / 64.0;
    else
	attenuation = DEFAULT_SOUND_PACKET_ATTENUATION;

    sound_num = MSG_ReadByte();

    for (i = 0; i < 3; i++)
	pos[i] = MSG_ReadCoord();

    ent = (channel >> 3) & 1023;
    channel &= 7;

    if (ent > MAX_EDICTS)
	Host_EndGame("%s: ent = %i", __func__, ent);

    S_StartSound(ent, channel, cl.sound_precache[sound_num], pos, volume / 255.0, attenuation);
}


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

Server information pertaining to this client only, sent every frame
==================
*/
static void
CL_ParseClientdata(void)
{
    int i;
    float latency;
    frame_t *frame;

// calculate simulated time of message
    oldparsecountmod = parsecountmod;

    i = cls.netchan.incoming_acknowledged;
    cl.parsecount = i;
    i &= UPDATE_MASK;
    parsecountmod = i;
    frame = &cl.frames[i];
    parsecounttime = cl.frames[i].senttime;

    frame->receivedtime = realtime;

// calculate latency
    latency = frame->receivedtime - frame->senttime;

    if (latency < 0 || latency > 1.0) {
//              Con_Printf ("Odd latency: %5.2f\n", latency);
    } else {
	// drift the average latency towards the observed latency
	if (latency < cls.latency)
	    cls.latency = latency;
	else
	    cls.latency += 0.001;	// drift up, so correction are needed
    }
}

/*
=====================
CL_NewTranslation
=====================
*/
static void
CL_NewTranslation(int slot)
{
#ifdef GLQUAKE
    if (slot > MAX_CLIENTS)
	Sys_Error("%s: slot > MAX_CLIENTS", __func__);

    R_TranslatePlayerSkin(slot);
#else
    int i, j;
    int top, bottom;
    byte *dest, *source;
    player_info_t *player;
    const char *skin_key;
    char skin[MAX_QPATH];

    if (slot > MAX_CLIENTS)
	Sys_Error("%s: slot > MAX_CLIENTS", __func__);

    player = &cl.players[slot];

    skin_key = Info_ValueForKey(player->userinfo, "skin");
    COM_StripExtension(skin_key, skin, sizeof(skin));

    if (player->skin && !strcasecmp(skin, player->skin->name))
	player->skin = NULL;

    if (player->_topcolor != player->topcolor ||
	player->_bottomcolor != player->bottomcolor || !player->skin) {
	player->_topcolor = player->topcolor;
	player->_bottomcolor = player->bottomcolor;

	dest = player->translations;
	source = vid.colormap;
	memcpy(dest, vid.colormap, sizeof(player->translations));
	top = player->topcolor;
	if (top > 13 || top < 0)
	    top = 13;
	top *= 16;
	bottom = player->bottomcolor;
	if (bottom > 13 || bottom < 0)
	    bottom = 13;
	bottom *= 16;

	for (i = 0; i < VID_GRADES; i++, dest += 256, source += 256) {
	    /* the artists made some backwards ranges.  sigh. */
	    if (top < 128)
		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];
	}
    }
#endif
}

/*
==============
CL_ProcessUserinfo
==============
*/
static void
CL_ProcessUserInfo(int slot, player_info_t * player)
{
    char *name;

    name = Info_ValueForKey(player->userinfo, "name");
    qsnprintf(player->name, sizeof(player->name), "%s", name);

    player->topcolor = atoi(Info_ValueForKey(player->userinfo, "topcolor"));
    player->bottomcolor =
	atoi(Info_ValueForKey(player->userinfo, "bottomcolor"));
    if (Info_ValueForKey(player->userinfo, "*spectator")[0])
	player->spectator = true;
    else
	player->spectator = false;

    if (cls.state == ca_active)
	Skin_Find(player);

    Sbar_Changed();
    CL_NewTranslation(slot);
}

/*
==============
CL_UpdateUserinfo
==============
*/
static void
CL_UpdateUserinfo(void)
{
    player_info_t *player;
    char *info;
    int slot;

    slot = MSG_ReadByte();
    if (slot >= MAX_CLIENTS)
	Host_EndGame("%s: svc_updateuserinfo > MAX_SCOREBOARD", __func__);

    player = &cl.players[slot];
    player->userid = MSG_ReadLong();

    info = MSG_ReadString();
    qsnprintf(player->userinfo, sizeof(player->userinfo), "%s", info);
    CL_ProcessUserInfo(slot, player);
}

/*
==============
CL_SetInfo
==============
*/
static void
CL_SetInfo(void)
{
    char key[MAX_MSGLEN];
    char value[MAX_MSGLEN];
    player_info_t *player;
    int slot;

    slot = MSG_ReadByte();
    if (slot >= MAX_CLIENTS)
	Host_EndGame("%s: svc_setinfo > MAX_SCOREBOARD", __func__);

    player = &cl.players[slot];
    qsnprintf(key, sizeof(key), "%s", MSG_ReadString());
    qsnprintf(value, sizeof(value), "%s", MSG_ReadString());

    Con_DPrintf("SETINFO %s: %s=%s\n", player->name, key, value);

    Info_SetValueForKey(player->userinfo, key, value, MAX_INFO_STRING);

    CL_ProcessUserInfo(slot, player);
}

/*
==============
CL_ServerInfo
==============
*/
static void
CL_ServerInfo(void)
{
    char key[MAX_MSGLEN];
    char value[MAX_MSGLEN];

    qsnprintf(key, sizeof(key), "%s", MSG_ReadString());
    qsnprintf(value, sizeof(value), "%s", MSG_ReadString());

    Con_DPrintf("SERVERINFO: %s=%s\n", key, value);

    Info_SetValueForKey(cl.serverinfo, key, value, MAX_SERVERINFO_STRING);
}

/*
=====================
CL_SetStat
=====================
*/
static void
CL_SetStat(int stat, int value)
{
    int j;

    if (stat < 0 || stat >= MAX_CL_STATS)
	Sys_Error("%s: %i is invalid", __func__, stat);

    Sbar_Changed();

    if (stat == STAT_ITEMS) {	// set flash times
	Sbar_Changed();
	for (j = 0; j < 32; j++)
	    if ((value & (1 << j)) && !(cl.stats[stat] & (1 << j)))
		cl.item_gettime[j] = cl.time;
    }

    cl.stats[stat] = value;
}

/*
==============
CL_MuzzleFlash
==============
*/
static void
CL_MuzzleFlash(void)
{
    vec3_t fv, rv, uv;
    dlight_t *dl;
    int i;
    player_state_t *pl;

    i = MSG_ReadShort();

    if ((unsigned)(i - 1) >= MAX_CLIENTS)
	return;

    pl = &cl.frames[parsecountmod].playerstate[i - 1];

    dl = CL_AllocDlight(i);
    VectorCopy(pl->origin, dl->origin);
    AngleVectors(pl->viewangles, fv, rv, uv);

    VectorMA(dl->origin, 18, fv, dl->origin);
    dl->radius = 200 + (rand() & 31);
    dl->minlight = 32;
    dl->die = cl.time + 0.1;
    dl->color = dl_colors[DLIGHT_FLASH];

    /*
     * The view model usually needs 2 animation frames for the muzzle
     * flash.  It seems the player models need three.
     */
    if (r_lerpmodels.value != 2) {
        if ((i - 1) == cl.playernum) {
            cl.viewent.lerp.flags |= LERP_RESETANIM | LERP_RESETANIM2;
        } else  {
	    cl_player_entities[i - 1].lerp.flags |= LERP_RESETANIM | LERP_RESETANIM2 | LERP_RESETANIM3;
        }
    }
}


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

/*
=====================
CL_ParseServerMessage
=====================
*/
void
CL_ParseServerMessage(void)
{
    int cmd;
    char *s;
    int i, j;

    cl.last_servermessage = realtime;
    CL_ClearProjectiles();

//
// 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_ParseClientdata();

//
// parse the message
//
    while (1) {
	if (msg_badread)
	    Host_EndGame("%s: Bad server message", __func__);

	cmd = MSG_ReadByte();

	if (cmd == -1) {
	    msg_readcount++;	// so the EOM showner has the right value
	    SHOWNET("END OF MESSAGE");
	    break;
	}

	SHOWNET(svc_strings[cmd]);

	// other commands
	switch (cmd) {
	case svc_nop:
//                      Con_Printf ("svc_nop\n");
	    break;

	case svc_disconnect:
	    if (cls.state == ca_connected)
		Host_EndGame("Server disconnected\n"
			     "Server version may not be compatible");
	    else
		Host_EndGame("Server disconnected");
	    break;

	case svc_print:
	    i = MSG_ReadByte();
	    if (i == PRINT_CHAT) {
		S_LocalSound("misc/talk.wav");
		con_ormask = 128;
	    }
	    Con_Printf("%s", MSG_ReadString());
	    con_ormask = 0;
	    break;

	case svc_centerprint:
	    SCR_CenterPrint(MSG_ReadString());
	    break;

	case svc_stufftext:
	    Cbuf_AddText("%s", MSG_ReadString());
	    break;

	case svc_damage:
	    V_ParseDamage();
	    break;

	case svc_serverdata:
	    Cbuf_Execute();	// make sure any stuffed commands are done
	    CL_ParseServerData();
	    vid.recalc_refdef = true;	// leave full screen intermission
	    break;

	case svc_setangle:
	    for (i = 0; i < 3; i++)
		cl.viewangles[i] = MSG_ReadAngle();
//                      cl.viewangles[PITCH] = cl.viewangles[ROLL] = 0;
	    break;

	case svc_lightstyle:
	    i = MSG_ReadByte();
	    if (i >= MAX_LIGHTSTYLES)
		Sys_Error("svc_lightstyle > MAX_LIGHTSTYLES");
	    s = MSG_ReadString();
	    qsnprintf(cl_lightstyle[i].map, MAX_STYLESTRING, "%s", s);
	    cl_lightstyle[i].length = 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_updatefrags:
	    Sbar_Changed();
	    i = MSG_ReadByte();
	    if (i >= MAX_CLIENTS)
		Host_EndGame("%s: svc_updatefrags > MAX_SCOREBOARD", __func__);
	    cl.players[i].frags = MSG_ReadShort();
	    break;

	case svc_updateping:
	    i = MSG_ReadByte();
	    if (i >= MAX_CLIENTS)
		Host_EndGame("%s: svc_updateping > MAX_SCOREBOARD", __func__);
	    cl.players[i].ping = MSG_ReadShort();
	    break;

	case svc_updatepl:
	    i = MSG_ReadByte();
	    if (i >= MAX_CLIENTS)
		Host_EndGame("%s: svc_updatepl > MAX_SCOREBOARD", __func__);
	    cl.players[i].pl = MSG_ReadByte();
	    break;

	case svc_updateentertime:
	    // time is sent over as seconds ago
	    i = MSG_ReadByte();
	    if (i >= MAX_CLIENTS)
		Host_EndGame("%s: svc_updateentertime > MAX_SCOREBOARD",
			     __func__);
	    cl.players[i].entertime = realtime - MSG_ReadFloat();
	    break;

	case svc_spawnbaseline:
	    i = MSG_ReadShort();
	    CL_ParseBaseline(&cl_baselines[i]);
	    break;
	case svc_spawnstatic:
	    CL_ParseStatic();
	    break;
	case svc_temp_entity:
	    CL_ParseTEnt();
	    break;

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

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

	case svc_updatestat:
	    i = MSG_ReadByte();
	    j = MSG_ReadByte();
	    CL_SetStat(i, j);
	    break;
	case svc_updatestatlong:
	    i = MSG_ReadByte();
	    j = MSG_ReadLong();
	    CL_SetStat(i, j);
	    break;

	case svc_spawnstaticsound:
	    CL_ParseStaticSound();
	    break;

	case svc_cdtrack:
	    cl.cdtrack = MSG_ReadByte();
	    BGM_PlayCDTrack((byte)cl.cdtrack, true);
	    break;

	case svc_intermission:
	    cl.intermission = 1;
	    cl.completed_time = realtime;
	    vid.recalc_refdef = true;	// go to full screen
	    for (i = 0; i < 3; i++)
		cl.simorg[i] = MSG_ReadCoord();
	    for (i = 0; i < 3; i++)
		cl.simangles[i] = MSG_ReadAngle();
	    VectorCopy(vec3_origin, cl.simvel);
	    break;

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

	case svc_sellscreen:
	    Cmd_ExecuteString("help");
	    break;

	case svc_smallkick:
	    cl.punchangle = -2;
	    break;
	case svc_bigkick:
	    cl.punchangle = -4;
	    break;

	case svc_muzzleflash:
	    CL_MuzzleFlash();
	    break;

	case svc_updateuserinfo:
	    CL_UpdateUserinfo();
	    break;

	case svc_setinfo:
	    CL_SetInfo();
	    break;

	case svc_serverinfo:
	    CL_ServerInfo();
	    break;

	case svc_download:
	    CL_ParseDownload();
	    break;

	case svc_playerinfo:
	    CL_ParsePlayerinfo();
	    break;

	case svc_nails:
	    CL_ParseProjectiles();
	    break;

	case svc_chokecount:	// some preceding packets were choked
	    i = MSG_ReadByte();
	    for (j = 0; j < i; j++)
		cl.frames[(cls.netchan.incoming_acknowledged - 1 - j) & UPDATE_MASK].receivedtime = -2;
	    break;

	case svc_modellist:
	    CL_ParseModellist();
	    break;

	case svc_soundlist:
	    CL_ParseSoundlist();
	    break;

	case svc_packetentities:
	    CL_ParsePacketEntities(false);
	    break;

	case svc_deltapacketentities:
	    CL_ParsePacketEntities(true);
	    break;

	case svc_maxspeed:
	    movevars.maxspeed = MSG_ReadFloat();
	    break;

	case svc_entgravity:
	    movevars.entgravity = MSG_ReadFloat();
	    break;

	case svc_setpause:
	    cl.paused = MSG_ReadByte();
	    if (cl.paused)
		CDAudio_Pause();
	    else
		CDAudio_Resume();
	    break;

	default:
	    Host_EndGame("%s: Illegible server message", __func__);
	}
    }
}
