+ Curl_Register_predownload(); // come back later
+ return;
+ }
+
+ // if we got here...
+ // curl is done, so let's start with the business
+ cl.loadbegun = true;
+
+ // if already downloading something from the previous level, don't stop it
+ if (cls.qw_downloadname[0])
+ return;
+
+ if (cl.downloadcsqc)
+ {
+ size_t progsize;
+ cl.downloadcsqc = false;
+ if (cls.netcon
+ && !sv.active
+ && csqc_progname.string
+ && csqc_progname.string[0]
+ && csqc_progcrc.integer >= 0
+ && cl_serverextension_download.integer
+ && (FS_CRCFile(csqc_progname.string, &progsize) != csqc_progcrc.integer || ((int)progsize != csqc_progsize.integer && csqc_progsize.integer != -1))
+ && !FS_FileExists(va("dlcache/%s.%i.%i", csqc_progname.string, csqc_progsize.integer, csqc_progcrc.integer)))
+ Cmd_ForwardStringToServer(va("download %s", csqc_progname.string));
+ }
+
+ if (cl.loadmodel_current < cl.loadmodel_total)
+ {
+ // loading models
+
+ for (;cl.loadmodel_current < cl.loadmodel_total;cl.loadmodel_current++)
+ {
+ if (cl.model_precache[cl.loadmodel_current] && cl.model_precache[cl.loadmodel_current]->Draw)
+ continue;
+ if (cls.signon < SIGNONS)
+ CL_KeepaliveMessage(true);
+ cl.model_precache[cl.loadmodel_current] = Mod_ForName(cl.model_name[cl.loadmodel_current], false, false, cl.loadmodel_current == 1);
+ if (cl.model_precache[cl.loadmodel_current] && cl.model_precache[cl.loadmodel_current]->Draw && cl.loadmodel_current == 1)
+ {
+ // we now have the worldmodel so we can set up the game world
+ cl.entities[0].render.model = cl.worldmodel = cl.model_precache[1];
+ CL_UpdateRenderEntity(&cl.entities[0].render);
+ CL_Locs_Reload_f();
+ R_Modules_NewMap();
+ cl.foundtalk2wav = FS_FileExists("sound/misc/talk2.wav");
+ // check memory integrity
+ Mem_CheckSentinelsGlobal();
+ if (!cl.loadfinished && cl_joinbeforedownloadsfinish.integer)
+ {
+ cl.loadfinished = true;
+ // now issue the spawn to move on to signon 2 like normal
+ if (cls.netcon)
+ Cmd_ForwardStringToServer("prespawn");
+ }
+ }
+ }
+
+ // finished loading models
+ }
+
+ if (cl.loadsound_current < cl.loadsound_total)
+ {
+ // loading sounds
+
+ for (;cl.loadsound_current < cl.loadsound_total;cl.loadsound_current++)
+ {
+ if (cl.sound_precache[cl.loadsound_current] && S_IsSoundPrecached(cl.sound_precache[cl.loadsound_current]))
+ continue;
+ if (cls.signon < SIGNONS)
+ CL_KeepaliveMessage(true);
+ // Don't lock the sfx here, S_ServerSounds already did that
+ cl.sound_precache[cl.loadsound_current] = S_PrecacheSound(cl.sound_name[cl.loadsound_current], false, false);
+ }
+
+ // finished loading sounds
+ }
+
+ // note: the reason these loops skip already-loaded things is that it
+ // enables this command to be issued during the game if desired
+
+ if (cl.downloadmodel_current < cl.loadmodel_total)
+ {
+ // loading models
+
+ for (;cl.downloadmodel_current < cl.loadmodel_total;cl.downloadmodel_current++)
+ {
+ if (aborteddownload)
+ {
+ if (cl.downloadmodel_current == 1)
+ {
+ // the worldmodel failed, but we need to set up anyway
+ cl.entities[0].render.model = cl.worldmodel = cl.model_precache[1];
+ CL_UpdateRenderEntity(&cl.entities[0].render);
+ CL_Locs_Reload_f();
+ R_Modules_NewMap();
+ cl.foundtalk2wav = FS_FileExists("sound/misc/talk2.wav");
+ // check memory integrity
+ Mem_CheckSentinelsGlobal();
+ if (!cl.loadfinished && cl_joinbeforedownloadsfinish.integer)
+ {
+ cl.loadfinished = true;
+ // now issue the spawn to move on to signon 2 like normal
+ if (cls.netcon)
+ Cmd_ForwardStringToServer("prespawn");
+ }
+ }
+ aborteddownload = false;
+ continue;
+ }
+ if (cl.model_precache[cl.downloadmodel_current] && cl.model_precache[cl.downloadmodel_current]->Draw)
+ continue;
+ if (cls.signon < SIGNONS)
+ CL_KeepaliveMessage(true);
+ if (!FS_FileExists(cl.model_name[cl.downloadmodel_current]))
+ {
+ if (cl.downloadmodel_current == 1)
+ Con_Printf("Map %s not found\n", cl.model_name[cl.downloadmodel_current]);
+ else
+ Con_Printf("Model %s not found\n", cl.model_name[cl.downloadmodel_current]);
+ // regarding the * check: don't try to download submodels
+ if (cl_serverextension_download.integer && cls.netcon && cl.model_name[cl.downloadmodel_current][0] != '*' && !sv.active)
+ {
+ Cmd_ForwardStringToServer(va("download %s", cl.model_name[cl.downloadmodel_current]));
+ // we'll try loading again when the download finishes
+ return;
+ }
+ }
+ cl.model_precache[cl.downloadmodel_current] = Mod_ForName(cl.model_name[cl.downloadmodel_current], false, false, cl.downloadmodel_current == 1);
+ if (cl.downloadmodel_current == 1)
+ {
+ // we now have the worldmodel so we can set up the game world
+ cl.entities[0].render.model = cl.worldmodel = cl.model_precache[1];
+ CL_UpdateRenderEntity(&cl.entities[0].render);
+ CL_Locs_Reload_f();
+ R_Modules_NewMap();
+ cl.foundtalk2wav = FS_FileExists("sound/misc/talk2.wav");
+ // check memory integrity
+ Mem_CheckSentinelsGlobal();
+ if (!cl.loadfinished && cl_joinbeforedownloadsfinish.integer)
+ {
+ cl.loadfinished = true;
+ // now issue the spawn to move on to signon 2 like normal
+ if (cls.netcon)
+ Cmd_ForwardStringToServer("prespawn");
+ }
+ }
+ }
+
+ // finished loading models
+ }
+
+ if (cl.downloadsound_current < cl.loadsound_total)
+ {
+ // loading sounds
+
+ for (;cl.downloadsound_current < cl.loadsound_total;cl.downloadsound_current++)
+ {
+ char soundname[MAX_QPATH];
+ if (aborteddownload)
+ {
+ aborteddownload = false;
+ continue;
+ }
+ if (cl.sound_precache[cl.downloadsound_current] && S_IsSoundPrecached(cl.sound_precache[cl.downloadsound_current]))
+ continue;
+ if (cls.signon < SIGNONS)
+ CL_KeepaliveMessage(true);
+ dpsnprintf(soundname, sizeof(soundname), "sound/%s", cl.sound_name[cl.downloadsound_current]);
+ if (!FS_FileExists(soundname) && !FS_FileExists(cl.sound_name[cl.downloadsound_current]))
+ {
+ Con_Printf("Sound %s not found\n", soundname);
+ if (cl_serverextension_download.integer && cls.netcon && !sv.active)
+ {
+ Cmd_ForwardStringToServer(va("download %s", soundname));
+ // we'll try loading again when the download finishes
+ return;
+ }
+ }
+ // Don't lock the sfx here, S_ServerSounds already did that
+ cl.sound_precache[cl.downloadsound_current] = S_PrecacheSound(cl.sound_name[cl.downloadsound_current], false, false);
+ }
+
+ // finished loading sounds
+ }
+
+ if (!cl.loadfinished)
+ {
+ cl.loadfinished = true;
+
+ // check memory integrity
+ Mem_CheckSentinelsGlobal();
+
+ // now issue the spawn to move on to signon 2 like normal
+ if (cls.netcon)
+ Cmd_ForwardStringToServer("prespawn");
+ }
+}
+
+void CL_BeginDownloads_f(void)
+{
+ // prevent cl_begindownloads from being issued multiple times in one match
+ // to prevent accidentally cancelled downloads
+ if(cl.loadbegun)
+ Con_DPrintf("cl_begindownloads is only valid once per match\n");
+ else
+ CL_BeginDownloads(false);
+}
+
+void CL_StopDownload(int size, int crc)
+{
+ if (cls.qw_downloadmemory && cls.qw_downloadmemorycursize == size && CRC_Block(cls.qw_downloadmemory, size) == crc)
+ {
+ int existingcrc;
+ size_t existingsize;
+ const char *extension;
+
+ // finished file
+ // save to disk only if we don't already have it
+ // (this is mainly for playing back demos)
+ existingcrc = FS_CRCFile(cls.qw_downloadname, &existingsize);
+ if (existingsize)
+ {
+ if ((int)existingsize != size || existingcrc != crc)
+ {
+ // we have a mismatching file, pick another name for it
+ char name[MAX_QPATH*2];
+ dpsnprintf(name, sizeof(name), "dlcache/%s.%i.%i", cls.qw_downloadname, size, crc);
+ if (!FS_FileExists(name))
+ {
+ Con_Printf("Downloaded \"%s\" (%i bytes, %i CRC)\n", name, size, crc);
+ FS_WriteFile(name, cls.qw_downloadmemory, cls.qw_downloadmemorycursize);
+ }
+ }
+ }
+ else
+ {
+ // we either don't have it or have a mismatching file...
+ // so it's time to accept the file
+ // but if we already have a mismatching file we need to rename
+ // this new one, and if we already have this file in renamed form,
+ // we do nothing
+ Con_Printf("Downloaded \"%s\" (%i bytes, %i CRC)\n", cls.qw_downloadname, size, crc);
+ FS_WriteFile(cls.qw_downloadname, cls.qw_downloadmemory, cls.qw_downloadmemorycursize);
+ extension = FS_FileExtension(cls.qw_downloadname);
+ if (!strcasecmp(extension, "pak") || !strcasecmp(extension, "pk3"))
+ FS_Rescan();
+ }
+ }
+
+ if (cls.qw_downloadmemory)
+ Mem_Free(cls.qw_downloadmemory);
+ cls.qw_downloadmemory = NULL;
+ cls.qw_downloadname[0] = 0;
+ cls.qw_downloadmemorymaxsize = 0;
+ cls.qw_downloadmemorycursize = 0;
+ cls.qw_downloadpercent = 0;
+}
+
+void CL_ParseDownload(void)
+{
+ int i, start, size;
+ unsigned char data[65536];
+ start = MSG_ReadLong();
+ size = (unsigned short)MSG_ReadShort();
+
+ // record the start/size information to ack in the next input packet
+ for (i = 0;i < CL_MAX_DOWNLOADACKS;i++)
+ {
+ if (!cls.dp_downloadack[i].start && !cls.dp_downloadack[i].size)
+ {
+ cls.dp_downloadack[i].start = start;
+ cls.dp_downloadack[i].size = size;
+ break;
+ }
+ }
+
+ MSG_ReadBytes(size, data);
+
+ if (!cls.qw_downloadname[0])
+ {
+ if (size > 0)
+ Con_Printf("CL_ParseDownload: received %i bytes with no download active\n", size);
+ return;
+ }
+
+ if (start + size > cls.qw_downloadmemorymaxsize)
+ Host_Error("corrupt download message\n");
+
+ // only advance cursize if the data is at the expected position
+ // (gaps are unacceptable)
+ memcpy(cls.qw_downloadmemory + start, data, size);
+ cls.qw_downloadmemorycursize = start + size;
+ cls.qw_downloadpercent = (int)floor((start+size) * 100.0 / cls.qw_downloadmemorymaxsize);
+ cls.qw_downloadpercent = bound(0, cls.qw_downloadpercent, 100);
+ cls.qw_downloadspeedcount += size;
+}
+
+void CL_DownloadBegin_f(void)
+{
+ int size = atoi(Cmd_Argv(1));
+
+ if (size < 0 || size > 1<<30 || FS_CheckNastyPath(Cmd_Argv(2), false))
+ {
+ Con_Printf("cl_downloadbegin: received bogus information\n");
+ CL_StopDownload(0, 0);
+ return;
+ }
+
+ if (cls.qw_downloadname[0])
+ Con_Printf("Download of %s aborted\n", cls.qw_downloadname);
+
+ CL_StopDownload(0, 0);
+
+ // we're really beginning a download now, so initialize stuff
+ strlcpy(cls.qw_downloadname, Cmd_Argv(2), sizeof(cls.qw_downloadname));
+ cls.qw_downloadmemorymaxsize = size;
+ cls.qw_downloadmemory = Mem_Alloc(cls.permanentmempool, cls.qw_downloadmemorymaxsize);
+ cls.qw_downloadnumber++;
+
+ Cmd_ForwardStringToServer("sv_startdownload");
+}
+
+void CL_StopDownload_f(void)
+{
+ if (cls.qw_downloadname[0])
+ {
+ Con_Printf("Download of %s aborted\n", cls.qw_downloadname);
+ CL_StopDownload(0, 0);
+ }
+ CL_BeginDownloads(true);
+}
+
+void CL_DownloadFinished_f(void)
+{
+ if (Cmd_Argc() < 3)
+ {
+ Con_Printf("Malformed cl_downloadfinished command\n");
+ return;
+ }
+ CL_StopDownload(atoi(Cmd_Argv(1)), atoi(Cmd_Argv(2)));
+ CL_BeginDownloads(false);
+}
+
+static void CL_SendPlayerInfo(void)
+{
+ MSG_WriteByte (&cls.netcon->message, clc_stringcmd);
+ MSG_WriteString (&cls.netcon->message, va("name \"%s\"", cl_name.string));
+
+ MSG_WriteByte (&cls.netcon->message, clc_stringcmd);
+ MSG_WriteString (&cls.netcon->message, va("color %i %i", cl_color.integer >> 4, cl_color.integer & 15));
+
+ MSG_WriteByte (&cls.netcon->message, clc_stringcmd);
+ MSG_WriteString (&cls.netcon->message, va("rate %i", cl_rate.integer));
+
+ if (cl_pmodel.integer)
+ {
+ MSG_WriteByte (&cls.netcon->message, clc_stringcmd);
+ MSG_WriteString (&cls.netcon->message, va("pmodel %i", cl_pmodel.integer));
+ }
+ if (*cl_playermodel.string)
+ {
+ MSG_WriteByte (&cls.netcon->message, clc_stringcmd);
+ MSG_WriteString (&cls.netcon->message, va("playermodel %s", cl_playermodel.string));
+ }
+ if (*cl_playerskin.string)
+ {
+ MSG_WriteByte (&cls.netcon->message, clc_stringcmd);
+ MSG_WriteString (&cls.netcon->message, va("playerskin %s", cl_playerskin.string));
+ }
+}
+
+/*
+=====================
+CL_SignonReply
+
+An svc_signonnum has been received, perform a client side setup
+=====================
+*/
+static void CL_SignonReply (void)
+{
+ Con_DPrintf("CL_SignonReply: %i\n", cls.signon);
+
+ switch (cls.signon)
+ {
+ case 1:
+ if (cls.netcon)
+ {
+ // send player info before we begin downloads
+ // (so that the server can see the player name while downloading)
+ CL_SendPlayerInfo();
+
+ // execute cl_begindownloads next frame
+ // (after any commands added by svc_stufftext have been executed)
+ // when done with downloads the "prespawn" will be sent
+ Cbuf_AddText("\ncl_begindownloads\n");
+
+ //MSG_WriteByte (&cls.netcon->message, clc_stringcmd);
+ //MSG_WriteString (&cls.netcon->message, "prespawn");
+ }
+ else // playing a demo... make sure loading occurs as soon as possible
+ CL_BeginDownloads(false);
+ break;
+
+ case 2:
+ if (cls.netcon)
+ {
+ // LordHavoc: quake sent the player info here but due to downloads
+ // it is sent earlier instead
+ // CL_SendPlayerInfo();
+
+ // LordHavoc: changed to begin a loading stage and issue this when done
+ MSG_WriteByte (&cls.netcon->message, clc_stringcmd);
+ MSG_WriteString (&cls.netcon->message, "spawn");
+ }
+ break;
+
+ case 3:
+ if (cls.netcon)
+ {
+ MSG_WriteByte (&cls.netcon->message, clc_stringcmd);
+ MSG_WriteString (&cls.netcon->message, "begin");
+ }
+ break;
+
+ case 4:
+ Con_ClearNotify();
+ break;
+ }
+}
+
+/*
+==================
+CL_ParseServerInfo
+==================
+*/
+void CL_ParseServerInfo (void)
+{
+ char *str;
+ int i;
+ protocolversion_t protocol;
+ int nummodels, numsounds;
+
+ Con_DPrint("Serverinfo packet received.\n");
+
+ // if server is active, we already began a loading plaque
+ if (!sv.active)
+ SCR_BeginLoadingPlaque();
+
+ // check memory integrity
+ Mem_CheckSentinelsGlobal();
+
+ // clear cl_serverextension cvars
+ Cvar_SetValueQuick(&cl_serverextension_download, 0);
+
+//
+// wipe the client_state_t struct
+//
+ CL_ClearState ();
+
+// parse protocol version number
+ i = MSG_ReadLong ();
+ protocol = Protocol_EnumForNumber(i);
+ if (protocol == PROTOCOL_UNKNOWN)
+ {
+ Host_Error("CL_ParseServerInfo: Server is unrecognized protocol number (%i)", i);
+ return;
+ }
+ // hack for unmarked Nehahra movie demos which had a custom protocol
+ if (protocol == PROTOCOL_QUAKEDP && cls.demoplayback && demo_nehahra.integer)
+ protocol = PROTOCOL_NEHAHRAMOVIE;
+ cls.protocol = protocol;
+ Con_DPrintf("Server protocol is %s\n", Protocol_NameForEnum(cls.protocol));
+
+ cl.num_entities = 1;
+
+ if (protocol == PROTOCOL_QUAKEWORLD)
+ {
+ char gamedir[1][MAX_QPATH];
+
+ cl.qw_servercount = MSG_ReadLong();
+
+ str = MSG_ReadString();
+ Con_Printf("server gamedir is %s\n", str);
+ strlcpy(gamedir[0], str, sizeof(gamedir[0]));
+
+ // change gamedir if needed
+ if (!FS_ChangeGameDirs(1, gamedir, true, false))
+ Host_Error("CL_ParseServerInfo: unable to switch to server specified gamedir");
+
+ cl.gametype = GAME_DEATHMATCH;
+ cl.maxclients = 32;
+
+ // parse player number
+ i = MSG_ReadByte();
+ // cl.qw_spectator is an unneeded flag, cl.scores[cl.playerentity].qw_spectator works better (it can be updated by the server during the game)
+ //cl.qw_spectator = (i & 128) != 0;
+ cl.playerentity = cl.viewentity = (i & 127) + 1;
+ cl.scores = (scoreboard_t *)Mem_Alloc(cls.levelmempool, cl.maxclients*sizeof(*cl.scores));
+
+ // get the full level name
+ str = MSG_ReadString ();
+ strlcpy (cl.levelname, str, sizeof(cl.levelname));
+
+ // get the movevars
+ cl.qw_movevars_gravity = MSG_ReadFloat();
+ cl.qw_movevars_stopspeed = MSG_ReadFloat();
+ cl.qw_movevars_maxspeed = MSG_ReadFloat();
+ cl.qw_movevars_spectatormaxspeed = MSG_ReadFloat();
+ cl.qw_movevars_accelerate = MSG_ReadFloat();
+ cl.qw_movevars_airaccelerate = MSG_ReadFloat();
+ cl.qw_movevars_wateraccelerate = MSG_ReadFloat();
+ cl.qw_movevars_friction = MSG_ReadFloat();
+ cl.qw_movevars_waterfriction = MSG_ReadFloat();
+ cl.qw_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\2%s\n", str);
+
+ // check memory integrity
+ Mem_CheckSentinelsGlobal();
+
+ if (cls.netcon)
+ {
+ MSG_WriteByte(&cls.netcon->message, qw_clc_stringcmd);
+ MSG_WriteString(&cls.netcon->message, va("soundlist %i %i", cl.qw_servercount, 0));
+ }
+
+ cl.loadbegun = false;
+ cl.loadfinished = false;
+
+ cls.state = ca_connected;
+ cls.signon = 1;
+
+ // note: on QW protocol we can't set up the gameworld until after
+ // downloads finish...
+ // (we don't even know the name of the map yet)
+ }
+ else
+ {
+ // parse maxclients
+ cl.maxclients = MSG_ReadByte ();
+ if (cl.maxclients < 1 || cl.maxclients > MAX_SCOREBOARD)
+ {
+ Host_Error("Bad maxclients (%u) from server", cl.maxclients);
+ return;
+ }
+ cl.scores = (scoreboard_t *)Mem_Alloc(cls.levelmempool, cl.maxclients*sizeof(*cl.scores));
+
+ // parse gametype
+ cl.gametype = MSG_ReadByte ();
+
+ // parse signon message
+ str = MSG_ReadString ();
+ strlcpy (cl.levelname, str, sizeof(cl.levelname));
+
+ // seperate the printfs so the server message can have a color
+ if (cls.protocol != PROTOCOL_NEHAHRAMOVIE) // no messages when playing the Nehahra movie
+ 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\2%s\n", str);
+
+ // check memory integrity
+ Mem_CheckSentinelsGlobal();
+
+ // parse model precache list
+ for (nummodels=1 ; ; nummodels++)
+ {
+ str = MSG_ReadString();
+ if (!str[0])
+ break;
+ 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)", (int)strlen(str), MAX_QPATH - 1);
+ strlcpy (cl.model_name[nummodels], str, sizeof (cl.model_name[nummodels]));
+ }
+ // parse sound precache list
+ for (numsounds=1 ; ; numsounds++)
+ {
+ str = MSG_ReadString();
+ if (!str[0])
+ break;
+ 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)", (int)strlen(str), MAX_QPATH - 1);
+ strlcpy (cl.sound_name[numsounds], str, sizeof (cl.sound_name[numsounds]));
+ }
+
+ // 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 < nummodels;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();
+
+ // do the same for sounds
+ // FIXME: S_ServerSounds does not know about cl.sfx_ sounds
+ S_ServerSounds (cl.sound_name, numsounds);
+
+ // 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);
+
+ // now we try to load everything that is new
+ cl.loadmodel_current = 1;
+ cl.downloadmodel_current = 1;
+ cl.loadmodel_total = nummodels;
+ cl.loadsound_current = 1;
+ cl.downloadsound_current = 1;
+ cl.loadsound_total = numsounds;
+ cl.downloadcsqc = true;
+ cl.loadbegun = false;
+ cl.loadfinished = false;
+ }
+
+ // check memory integrity
+ Mem_CheckSentinelsGlobal();
+
+// if cl_autodemo is set, automatically start recording a demo if one isn't being recorded already
+ if (cl_autodemo.integer && cls.netcon && cls.protocol != PROTOCOL_QUAKEWORLD)
+ {
+ char demofile[MAX_OSPATH];
+ char levelname[MAX_QPATH];
+
+ if (cls.demorecording)
+ {
+ // finish the previous level's demo file
+ CL_Stop_f();
+ }
+
+ // start a new demo file
+ strlcpy(levelname, FS_FileWithoutPath(cl.model_name[1]), sizeof(levelname));
+ if (strrchr(levelname, '.'))
+ *(strrchr(levelname, '.')) = 0;
+ dpsnprintf (demofile, sizeof(demofile), "%s_%s.dem", Sys_TimeString (cl_autodemo_nameformat.string), levelname);
+
+ Con_Printf ("Auto-recording to %s.\n", demofile);
+
+ cls.demofile = FS_Open (demofile, "wb", false, false);
+ if (cls.demofile)
+ {
+ cls.forcetrack = -1;
+ FS_Printf (cls.demofile, "%i\n", cls.forcetrack);
+ cls.demorecording = true;
+ }
+ else
+ Con_Print ("ERROR: couldn't open.\n");
+ }
+}
+
+void CL_ValidateState(entity_state_t *s)
+{
+ model_t *model;
+
+ if (!s->active)
+ return;
+
+ if (s->modelindex >= MAX_MODELS)
+ Host_Error("CL_ValidateState: modelindex (%i) >= MAX_MODELS (%i)\n", s->modelindex, MAX_MODELS);
+
+ // colormap is client index + 1
+ if ((!s->flags & RENDER_COLORMAPPED) && s->colormap > cl.maxclients)
+ {
+ Con_DPrintf("CL_ValidateState: colormap (%i) > cl.maxclients (%i)\n", s->colormap, cl.maxclients);
+ s->colormap = 0;
+ }
+
+ model = cl.model_precache[s->modelindex];
+ if (model && model->type && s->frame >= model->numframes)
+ {
+ Con_DPrintf("CL_ValidateState: no such frame %i in \"%s\" (which has %i frames)\n", s->frame, model->name, model->numframes);
+ s->frame = 0;
+ }
+ if (model && model->type && s->skin > 0 && s->skin >= model->numskins && !(s->lightpflags & PFLAGS_FULLDYNAMIC))
+ {
+ Con_DPrintf("CL_ValidateState: no such skin %i in \"%s\" (which has %i skins)\n", s->skin, model->name, model->numskins);
+ s->skin = 0;
+ }
+}
+
+void CL_MoveLerpEntityStates(entity_t *ent)
+{
+ float odelta[3], adelta[3];
+ CL_ValidateState(&ent->state_current);
+ VectorSubtract(ent->state_current.origin, ent->persistent.neworigin, odelta);
+ VectorSubtract(ent->state_current.angles, ent->persistent.newangles, adelta);
+ if (!ent->state_previous.active || ent->state_previous.modelindex != ent->state_current.modelindex)
+ {
+ // reset all persistent stuff if this is a new entity
+ ent->persistent.lerpdeltatime = 0;
+ ent->persistent.lerpstarttime = cl.mtime[1];
+ VectorCopy(ent->state_current.origin, ent->persistent.oldorigin);
+ VectorCopy(ent->state_current.angles, ent->persistent.oldangles);
+ VectorCopy(ent->state_current.origin, ent->persistent.neworigin);
+ VectorCopy(ent->state_current.angles, ent->persistent.newangles);
+ // reset animation interpolation as well
+ ent->render.frame = ent->render.frame1 = ent->render.frame2 = ent->state_current.frame;
+ ent->render.frame1time = ent->render.frame2time = cl.time;
+ ent->render.framelerp = 1;
+ // reset various persistent stuff
+ ent->persistent.muzzleflash = 0;