+ r_refdef.fog_blue = atof(value);
+ }
+}
+
+static qboolean QW_CL_CheckOrDownloadFile(const char *filename)
+{
+ qfile_t *file;
+
+ // see if the file already exists
+ file = FS_Open(filename, "rb", true, false);
+ if (file)
+ {
+ FS_Close(file);
+ return true;
+ }
+
+ // download messages in a demo would be bad
+ if (cls.demorecording)
+ {
+ Con_Printf("Unable to download \"%s\" when recording.\n", filename);
+ return true;
+ }
+
+ // don't try to download when playing a demo
+ if (!cls.netcon)
+ return true;
+
+ strlcpy(cls.qw_downloadname, filename, sizeof(cls.qw_downloadname));
+ Con_Printf("Downloading %s\n", filename);
+
+ if (!cls.qw_downloadmemory)
+ {
+ cls.qw_downloadmemory = NULL;
+ cls.qw_downloadmemorycursize = 0;
+ cls.qw_downloadmemorymaxsize = 1024*1024; // start out with a 1MB buffer
+ }
+
+ MSG_WriteByte(&cls.netcon->message, qw_clc_stringcmd);
+ MSG_WriteString(&cls.netcon->message, va("download %s", filename));
+
+ cls.qw_downloadnumber++;
+ cls.qw_downloadpercent = 0;
+ cls.qw_downloadmemorycursize = 0;
+
+ return false;
+}
+
+static void QW_CL_ProcessUserInfo(int slot);
+static void QW_CL_RequestNextDownload(void)
+{
+ int i;
+
+ // clear name of file that just finished
+ cls.qw_downloadname[0] = 0;
+
+ switch (cls.qw_downloadtype)
+ {
+ case dl_single:
+ break;
+ case dl_skin:
+ if (cls.qw_downloadnumber == 0)
+ Con_Printf("Checking skins...\n");
+ for (;cls.qw_downloadnumber < cl.maxclients;cls.qw_downloadnumber++)
+ {
+ if (!cl.scores[cls.qw_downloadnumber].name[0])
+ continue;
+ // check if we need to download the file, and return if so
+ if (!QW_CL_CheckOrDownloadFile(va("skins/%s.pcx", cl.scores[cls.qw_downloadnumber].qw_skin)))
+ return;
+ }
+
+ cls.qw_downloadtype = dl_none;
+
+ // load any newly downloaded skins
+ for (i = 0;i < cl.maxclients;i++)
+ QW_CL_ProcessUserInfo(i);
+
+ // if we're still in signon stages, request the next one
+ if (cls.signon != SIGNONS)
+ {
+ cls.signon = SIGNONS-1;
+ // we'll go to SIGNONS when the first entity update is received
+ MSG_WriteByte(&cls.netcon->message, qw_clc_stringcmd);
+ MSG_WriteString(&cls.netcon->message, va("begin %i", cl.qw_servercount));
+ }
+ break;
+ case dl_model:
+ if (cls.qw_downloadnumber == 0)
+ {
+ Con_Printf("Checking models...\n");
+ cls.qw_downloadnumber = 1;
+ }
+
+ for (;cls.qw_downloadnumber < MAX_MODELS && cl.model_name[cls.qw_downloadnumber][0];cls.qw_downloadnumber++)
+ {
+ // skip submodels
+ if (cl.model_name[cls.qw_downloadnumber][0] == '*')
+ continue;
+ if (!strcmp(cl.model_name[cls.qw_downloadnumber], "progs/spike.mdl"))
+ cl.qw_modelindex_spike = cls.qw_downloadnumber;
+ if (!strcmp(cl.model_name[cls.qw_downloadnumber], "progs/player.mdl"))
+ cl.qw_modelindex_player = cls.qw_downloadnumber;
+ if (!strcmp(cl.model_name[cls.qw_downloadnumber], "progs/flag.mdl"))
+ cl.qw_modelindex_flag = cls.qw_downloadnumber;
+ if (!strcmp(cl.model_name[cls.qw_downloadnumber], "progs/s_explod.spr"))
+ cl.qw_modelindex_s_explod = cls.qw_downloadnumber;
+ // check if we need to download the file, and return if so
+ if (!QW_CL_CheckOrDownloadFile(cl.model_name[cls.qw_downloadnumber]))
+ return;
+ }
+
+ cls.qw_downloadtype = dl_none;
+
+ // touch all of the precached models that are still loaded so we can free
+ // anything that isn't needed
+ Mod_ClearUsed();
+ for (i = 1;i < MAX_MODELS && cl.model_name[i][0];i++)
+ Mod_FindName(cl.model_name[i]);
+ // precache any models used by the client (this also marks them used)
+ cl.model_bolt = Mod_ForName("progs/bolt.mdl", false, false, false);
+ cl.model_bolt2 = Mod_ForName("progs/bolt2.mdl", false, false, false);
+ cl.model_bolt3 = Mod_ForName("progs/bolt3.mdl", false, false, false);
+ cl.model_beam = Mod_ForName("progs/beam.mdl", false, false, false);
+ Mod_PurgeUnused();
+
+ // now we try to load everything that is new
+
+ // world model
+ cl.model_precache[1] = Mod_ForName(cl.model_name[1], false, false, true);
+ if (cl.model_precache[1]->Draw == NULL)
+ Con_Printf("Map %s could not be found or downloaded\n", cl.model_name[1]);
+
+ // normal models
+ for (i = 2;i < MAX_MODELS && cl.model_name[i][0];i++)
+ if ((cl.model_precache[i] = Mod_ForName(cl.model_name[i], false, false, false))->Draw == NULL)
+ Con_Printf("Model %s could not be found or downloaded\n", cl.model_name[i]);
+
+ // check memory integrity
+ Mem_CheckSentinelsGlobal();
+
+ // now that we have a world model, set up the world entity, renderer
+ // modules and csqc
+ cl.entities[0].render.model = cl.worldmodel = cl.model_precache[1];
+ CL_BoundingBoxForEntity(&cl.entities[0].render);
+
+ R_Modules_NewMap();
+
+ // TODO: add pmodel/emodel player.mdl/eyes.mdl CRCs to userinfo
+
+ // done checking sounds and models, send a prespawn command now
+ MSG_WriteByte(&cls.netcon->message, qw_clc_stringcmd);
+ MSG_WriteString(&cls.netcon->message, va("prespawn %i 0 %i", cl.qw_servercount, cl.model_precache[1]->brush.qw_md4sum2));
+
+ if (cls.qw_downloadmemory)
+ {
+ Mem_Free(cls.qw_downloadmemory);
+ cls.qw_downloadmemory = NULL;
+ }
+ break;
+ case dl_sound:
+ if (cls.qw_downloadnumber == 0)
+ {
+ Con_Printf("Checking sounds...\n");
+ cls.qw_downloadnumber = 1;
+ }
+
+ for (;cl.sound_name[cls.qw_downloadnumber][0];cls.qw_downloadnumber++)
+ {
+ // check if we need to download the file, and return if so
+ if (!QW_CL_CheckOrDownloadFile(va("sound/%s", cl.sound_name[cls.qw_downloadnumber])))
+ return;
+ }
+
+ cls.qw_downloadtype = dl_none;
+
+ // load new sounds and unload old ones
+ // FIXME: S_ServerSounds does not know about cl.sfx_ sounds
+ S_ServerSounds(cl.sound_name, cls.qw_downloadnumber);
+
+ // precache any sounds used by the client
+ cl.sfx_wizhit = S_PrecacheSound(cl_sound_wizardhit.string, false, true);
+ cl.sfx_knighthit = S_PrecacheSound(cl_sound_hknighthit.string, false, true);
+ cl.sfx_tink1 = S_PrecacheSound(cl_sound_tink1.string, false, true);
+ cl.sfx_ric1 = S_PrecacheSound(cl_sound_ric1.string, false, true);
+ cl.sfx_ric2 = S_PrecacheSound(cl_sound_ric2.string, false, true);
+ cl.sfx_ric3 = S_PrecacheSound(cl_sound_ric3.string, false, true);
+ cl.sfx_r_exp3 = S_PrecacheSound(cl_sound_r_exp3.string, false, true);
+
+ // sounds
+ for (i = 1;i < MAX_SOUNDS && cl.sound_name[i][0];i++)
+ {
+ // Don't lock the sfx here, S_ServerSounds already did that
+ cl.sound_precache[i] = S_PrecacheSound(cl.sound_name[i], true, false);
+ }
+
+ // check memory integrity
+ Mem_CheckSentinelsGlobal();
+
+ // done with sound downloads, next we check models
+ MSG_WriteByte(&cls.netcon->message, qw_clc_stringcmd);
+ MSG_WriteString(&cls.netcon->message, va("modellist %i %i", cl.qw_servercount, 0));
+ break;
+ case dl_none:
+ default:
+ Con_Printf("Unknown download type.\n");
+ }
+}
+
+static void QW_CL_ParseDownload(void)
+{
+ int size = (signed short)MSG_ReadShort();
+ int percent = MSG_ReadByte();
+
+ //Con_Printf("download %i %i%% (%i/%i)\n", size, percent, cls.qw_downloadmemorycursize, cls.qw_downloadmemorymaxsize);
+
+ // skip the download fragment if playing a demo
+ if (!cls.netcon)
+ {
+ if (size > 0)
+ msg_readcount += size;
+ return;
+ }
+
+ if (size == -1)
+ {
+ Con_Printf("File not found.\n");
+ QW_CL_RequestNextDownload();
+ return;
+ }
+
+ if (msg_readcount + (unsigned short)size > net_message.cursize)
+ Host_Error("corrupt download message\n");
+
+ // make sure the buffer is big enough to include this new fragment
+ if (!cls.qw_downloadmemory || cls.qw_downloadmemorymaxsize < cls.qw_downloadmemorycursize + size)
+ {
+ unsigned char *old;
+ while (cls.qw_downloadmemorymaxsize < cls.qw_downloadmemorycursize + size)
+ cls.qw_downloadmemorymaxsize *= 2;
+ old = cls.qw_downloadmemory;
+ cls.qw_downloadmemory = (unsigned char *)Mem_Alloc(cls.permanentmempool, cls.qw_downloadmemorymaxsize);
+ if (old)
+ {
+ memcpy(cls.qw_downloadmemory, old, cls.qw_downloadmemorycursize);
+ Mem_Free(old);
+ }
+ }
+
+ // read the fragment out of the packet
+ MSG_ReadBytes(size, cls.qw_downloadmemory + cls.qw_downloadmemorycursize);
+ cls.qw_downloadmemorycursize += size;
+
+ cls.qw_downloadpercent = percent;
+
+ if (percent != 100)
+ {
+ // request next fragment
+ MSG_WriteByte(&cls.netcon->message, qw_clc_stringcmd);
+ MSG_WriteString(&cls.netcon->message, "nextdl");
+ }
+ else
+ {
+ // finished file
+ Con_Printf("Downloaded \"%s\"\n", cls.qw_downloadname);
+
+ FS_WriteFile(cls.qw_downloadname, cls.qw_downloadmemory, cls.qw_downloadmemorycursize);
+
+ cls.qw_downloadpercent = 0;
+
+ // start downloading the next file (or join the game)
+ QW_CL_RequestNextDownload();
+ }
+}
+
+static void QW_CL_ParseModelList(void)
+{
+ int n;
+ int nummodels = MSG_ReadByte();
+ char *str;
+
+ // parse model precache list
+ for (;;)
+ {
+ str = MSG_ReadString();
+ if (!str[0])
+ break;
+ nummodels++;
+ if (nummodels==MAX_MODELS)
+ Host_Error("Server sent too many model precaches");
+ if (strlen(str) >= MAX_QPATH)
+ Host_Error("Server sent a precache name of %i characters (max %i)", strlen(str), MAX_QPATH - 1);
+ strlcpy(cl.model_name[nummodels], str, sizeof (cl.model_name[nummodels]));
+ }
+
+ n = MSG_ReadByte();
+ if (n)
+ {
+ MSG_WriteByte(&cls.netcon->message, qw_clc_stringcmd);
+ MSG_WriteString(&cls.netcon->message, va("modellist %i %i", cl.qw_servercount, n));
+ return;
+ }
+
+ cls.signon = 2;
+ cls.qw_downloadnumber = 0;
+ cls.qw_downloadtype = dl_model;
+ QW_CL_RequestNextDownload();
+}
+
+static void QW_CL_ParseSoundList(void)
+{
+ int n;
+ int numsounds = MSG_ReadByte();
+ char *str;
+
+ // parse sound precache list
+ for (;;)
+ {
+ str = MSG_ReadString();
+ if (!str[0])
+ break;
+ numsounds++;
+ if (numsounds==MAX_SOUNDS)
+ Host_Error("Server sent too many sound precaches");
+ if (strlen(str) >= MAX_QPATH)
+ Host_Error("Server sent a precache name of %i characters (max %i)", strlen(str), MAX_QPATH - 1);
+ strlcpy(cl.sound_name[numsounds], str, sizeof (cl.sound_name[numsounds]));
+ }
+
+ n = MSG_ReadByte();
+
+ if (n)
+ {
+ MSG_WriteByte(&cls.netcon->message, qw_clc_stringcmd);
+ MSG_WriteString(&cls.netcon->message, va("soundlist %i %i", cl.qw_servercount, n));
+ return;
+ }
+
+ cls.signon = 2;
+ cls.qw_downloadnumber = 0;
+ cls.qw_downloadtype = dl_sound;
+ QW_CL_RequestNextDownload();
+}
+
+static void QW_CL_Skins_f(void)
+{
+ cls.qw_downloadnumber = 0;
+ cls.qw_downloadtype = dl_skin;
+ QW_CL_RequestNextDownload();
+}
+
+static void QW_CL_Changing_f(void)
+{
+ if (cls.qw_downloadmemory) // don't change when downloading
+ return;
+
+ S_StopAllSounds();
+ cl.intermission = 0;
+ cls.signon = 1; // not active anymore, but not disconnected
+ Con_Printf("\nChanging map...\n");
+}
+
+void QW_CL_NextUpload(void)
+{
+ int r, percent, size;
+
+ if (!cls.qw_uploaddata)
+ return;
+
+ r = cls.qw_uploadsize - cls.qw_uploadpos;
+ if (r > 768)
+ r = 768;
+ size = min(1, cls.qw_uploadsize);
+ percent = (cls.qw_uploadpos+r)*100/size;
+
+ MSG_WriteByte(&cls.netcon->message, qw_clc_upload);
+ MSG_WriteShort(&cls.netcon->message, r);
+ MSG_WriteByte(&cls.netcon->message, percent);
+ SZ_Write(&cls.netcon->message, cls.qw_uploaddata + cls.qw_uploadpos, r);
+
+ Con_DPrintf("UPLOAD: %6d: %d written\n", cls.qw_uploadpos, r);
+
+ cls.qw_uploadpos += r;
+
+ if (cls.qw_uploadpos < cls.qw_uploadsize)
+ return;
+
+ Con_Printf("Upload completed\n");
+
+ QW_CL_StopUpload();
+}
+
+void QW_CL_StartUpload(unsigned char *data, int size)
+{
+ // do nothing in demos or if not connected
+ if (!cls.netcon)
+ return;
+
+ // abort existing upload if in progress
+ QW_CL_StopUpload();
+
+ Con_DPrintf("Starting upload of %d bytes...\n", size);
+
+ cls.qw_uploaddata = (unsigned char *)Mem_Alloc(cls.permanentmempool, size);
+ memcpy(cls.qw_uploaddata, data, size);
+ cls.qw_uploadsize = size;
+ cls.qw_uploadpos = 0;
+
+ QW_CL_NextUpload();
+}
+
+#if 0
+qboolean QW_CL_IsUploading(void)
+{
+ return cls.qw_uploaddata != NULL;
+}
+#endif
+
+void QW_CL_StopUpload(void)
+{
+ if (cls.qw_uploaddata)
+ Mem_Free(cls.qw_uploaddata);
+ cls.qw_uploaddata = NULL;
+ cls.qw_uploadsize = 0;
+ cls.qw_uploadpos = 0;
+}
+
+static void QW_CL_ProcessUserInfo(int slot)
+{
+ int topcolor, bottomcolor;
+ char temp[2048];
+ InfoString_GetValue(cl.scores[slot].qw_userinfo, "name", cl.scores[slot].name, sizeof(cl.scores[slot].name));
+ InfoString_GetValue(cl.scores[slot].qw_userinfo, "topcolor", temp, sizeof(temp));topcolor = atoi(temp);
+ InfoString_GetValue(cl.scores[slot].qw_userinfo, "bottomcolor", temp, sizeof(temp));bottomcolor = atoi(temp);
+ cl.scores[slot].colors = topcolor * 16 + bottomcolor;
+ InfoString_GetValue(cl.scores[slot].qw_userinfo, "*spectator", temp, sizeof(temp));
+ cl.scores[slot].qw_spectator = temp[0] != 0;
+ InfoString_GetValue(cl.scores[slot].qw_userinfo, "team", cl.scores[slot].qw_team, sizeof(cl.scores[slot].qw_team));
+ InfoString_GetValue(cl.scores[slot].qw_userinfo, "skin", cl.scores[slot].qw_skin, sizeof(cl.scores[slot].qw_skin));
+ if (!cl.scores[slot].qw_skin[0])
+ strlcpy(cl.scores[slot].qw_skin, "base", sizeof(cl.scores[slot].qw_skin));
+ // TODO: skin cache
+}
+
+static void QW_CL_UpdateUserInfo(void)
+{
+ int slot;
+ slot = MSG_ReadByte();
+ if (slot >= cl.maxclients)
+ {
+ Con_Printf("svc_updateuserinfo >= cl.maxclients\n");
+ MSG_ReadLong();
+ MSG_ReadString();
+ return;
+ }
+ cl.scores[slot].qw_userid = MSG_ReadLong();
+ strlcpy(cl.scores[slot].qw_userinfo, MSG_ReadString(), sizeof(cl.scores[slot].qw_userinfo));
+
+ QW_CL_ProcessUserInfo(slot);
+}
+
+static void QW_CL_SetInfo(void)
+{
+ int slot;
+ char key[2048];
+ char value[2048];
+ slot = MSG_ReadByte();
+ strlcpy(key, MSG_ReadString(), sizeof(key));
+ strlcpy(value, MSG_ReadString(), sizeof(value));
+ if (slot >= cl.maxclients)
+ {
+ Con_Printf("svc_setinfo >= cl.maxclients\n");
+ return;
+ }
+ InfoString_SetValue(cl.scores[slot].qw_userinfo, sizeof(cl.scores[slot].qw_userinfo), key, value);
+
+ QW_CL_ProcessUserInfo(slot);
+}
+
+static void QW_CL_ServerInfo(void)
+{
+ char key[2048];
+ char value[2048];
+ char temp[32];
+ strlcpy(key, MSG_ReadString(), sizeof(key));
+ strlcpy(value, MSG_ReadString(), sizeof(value));
+ Con_DPrintf("SERVERINFO: %s=%s\n", key, value);
+ InfoString_SetValue(cl.qw_serverinfo, sizeof(cl.qw_serverinfo), key, value);
+ InfoString_GetValue(cl.qw_serverinfo, "teamplay", temp, sizeof(temp));
+ cl.qw_teamplay = atoi(temp);
+}
+
+static void QW_CL_ParseNails(void)
+{
+ int i, j;
+ int numnails = MSG_ReadByte();
+ vec_t *v;
+ unsigned char bits[6];
+ for (i = 0;i < numnails;i++)
+ {
+ for (j = 0;j < 6;j++)
+ bits[j] = MSG_ReadByte();
+ if (cl.qw_num_nails > 255)
+ continue;
+ v = cl.qw_nails[cl.qw_num_nails++];
+ v[0] = ( ( bits[0] + ((bits[1]&15)<<8) ) <<1) - 4096;
+ v[1] = ( ( (bits[1]>>4) + (bits[2]<<4) ) <<1) - 4096;
+ v[2] = ( ( bits[3] + ((bits[4]&15)<<8) ) <<1) - 4096;
+ v[3] = -360*(bits[4]>>4)/16;
+ v[4] = 360*bits[5]/256;
+ v[5] = 0;