X-Git-Url: http://de.git.xonotic.org/?p=xonotic%2Fdarkplaces.git;a=blobdiff_plain;f=cl_parse.c;h=a0d340b276b2634cc03816ec48ef15dca9673d49;hp=5f41466ea59789df7e1fe5781f73862df16ae57f;hb=810077aff6c99517dbd4e112d1f5f184745d1a33;hpb=3ad7fd58a384148f78dfd1a541e81a69e90dab8f diff --git a/cl_parse.c b/cl_parse.c index 5f41466e..a0d340b2 100644 --- a/cl_parse.c +++ b/cl_parse.c @@ -24,6 +24,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "cl_collision.h" #include "csprogs.h" #include "libcurl.h" +#include "utf8lib.h" char *svc_strings[128] = { @@ -94,6 +95,9 @@ char *svc_strings[128] = "svc_entities", // 57 // [int] deltaframe [int] thisframe [float vector] eye [variable length] entitydata "svc_csqcentities", // 58 // [short] entnum [variable length] entitydata ... [short] 0x0000 "svc_spawnstaticsound2", // 59 // [coord3] [short] samp [byte] vol [byte] aten + "svc_trailparticles", // 60 // [short] entnum [short] effectnum [vector] start [vector] end + "svc_pointparticles", // 61 // [short] effectnum [vector] start [vector] velocity [short] count + "svc_pointparticles1", // 62 // [short] effectnum [vector] start, same as svc_pointparticles except velocity is zero and count is 1 }; char *qw_svc_strings[128] = @@ -156,14 +160,21 @@ char *qw_svc_strings[128] = //============================================================================= +cvar_t cl_worldmessage = {CVAR_READONLY, "cl_worldmessage", "", "title of current level"}; +cvar_t cl_worldname = {CVAR_READONLY, "cl_worldname", "", "name of current worldmodel"}; +cvar_t cl_worldnamenoextension = {CVAR_READONLY, "cl_worldnamenoextension", "", "name of current worldmodel without extension"}; +cvar_t cl_worldbasename = {CVAR_READONLY, "cl_worldbasename", "", "name of current worldmodel without maps/ prefix or extension"}; + cvar_t demo_nehahra = {0, "demo_nehahra", "0", "reads all quake demos as nehahra movie protocol"}; cvar_t developer_networkentities = {0, "developer_networkentities", "0", "prints received entities, value is 0-4 (higher for more info)"}; +cvar_t cl_gameplayfix_soundsmovewithentities = {0, "cl_gameplayfix_soundsmovewithentities", "1", "causes sounds made by lifts, players, projectiles, and any other entities, to move with the entity, so for example a rocket noise follows the rocket rather than staying at the starting position"}; cvar_t cl_sound_wizardhit = {0, "cl_sound_wizardhit", "wizard/hit.wav", "sound to play during TE_WIZSPIKE (empty cvar disables sound)"}; cvar_t cl_sound_hknighthit = {0, "cl_sound_hknighthit", "hknight/hit.wav", "sound to play during TE_KNIGHTSPIKE (empty cvar disables sound)"}; cvar_t cl_sound_tink1 = {0, "cl_sound_tink1", "weapons/tink1.wav", "sound to play with 80% chance during TE_SPIKE/TE_SUPERSPIKE (empty cvar disables sound)"}; cvar_t cl_sound_ric1 = {0, "cl_sound_ric1", "weapons/ric1.wav", "sound to play with 5% chance during TE_SPIKE/TE_SUPERSPIKE (empty cvar disables sound)"}; cvar_t cl_sound_ric2 = {0, "cl_sound_ric2", "weapons/ric2.wav", "sound to play with 5% chance during TE_SPIKE/TE_SUPERSPIKE (empty cvar disables sound)"}; cvar_t cl_sound_ric3 = {0, "cl_sound_ric3", "weapons/ric3.wav", "sound to play with 10% chance during TE_SPIKE/TE_SUPERSPIKE (empty cvar disables sound)"}; +cvar_t cl_readpicture_force = {0, "cl_readpicture_force", "0", "when enabled, the low quality pictures read by ReadPicture() are preferred over the high quality pictures on the file system"}; #define RIC_GUNSHOT 1 #define RIC_GUNSHOTQUAD 2 @@ -182,6 +193,8 @@ static void QW_CL_NextUpload(void); void QW_CL_StartUpload(unsigned char *data, int size); //static qboolean QW_CL_IsUploading(void); static void QW_CL_StopUpload(void); +void CL_VM_UpdateIntermissionState(int intermission); +qboolean CL_VM_Event_Sound(int sound_num, float volume, int channel, float attenuation, int ent, vec3_t pos); /* ================== @@ -242,7 +255,7 @@ void CL_ParseStartSoundPacket(int largesoundindex) channel &= 7; } - if (largesoundindex || field_mask & SND_LARGESOUND) + if (largesoundindex || (field_mask & SND_LARGESOUND) || cls.protocol == PROTOCOL_NEHAHRABJP2 || cls.protocol == PROTOCOL_NEHAHRABJP3) sound_num = (unsigned short) MSG_ReadShort (); else sound_num = MSG_ReadByte (); @@ -265,7 +278,8 @@ void CL_ParseStartSoundPacket(int largesoundindex) if (ent >= cl.max_entities) CL_ExpandEntities(ent); - S_StartSound (ent, channel, cl.sound_precache[sound_num], pos, volume/255.0f, attenuation); + if( !CL_VM_Event_Sound(sound_num, volume / 255.0f, channel, attenuation, ent, pos) ) + S_StartSound (ent, channel, cl.sound_precache[sound_num], pos, volume/255.0f, attenuation); } /* @@ -282,12 +296,25 @@ void CL_KeepaliveMessage (qboolean readmessages) { float time; static double nextmsg = -1; + static double nextupdate = -1; +#if 0 + static double lasttime = -1; +#endif int oldreadcount; qboolean oldbadread; sizebuf_t old; + if(cls.state != ca_dedicated) + { + if((time = Sys_DoubleTime()) >= nextupdate) + { + SCR_UpdateLoadingScreenIfShown(); + nextupdate = time + 2; + } + } + // no need if server is local and definitely not if this is a demo - if (!cls.netcon || cls.protocol == PROTOCOL_QUAKEWORLD) + if (!cls.netcon || cls.protocol == PROTOCOL_QUAKEWORLD || cls.signon >= SIGNONS) return; if (readmessages) @@ -306,6 +333,14 @@ void CL_KeepaliveMessage (qboolean readmessages) memcpy(net_message.data, olddata, net_message.cursize); } +#if 0 + if((time = Sys_DoubleTime()) >= lasttime + 1) + { + Con_Printf("long delta: %f\n", time - lasttime); + } + lasttime = Sys_DoubleTime(); +#endif + if (cls.netcon && (time = Sys_DoubleTime()) >= nextmsg) { sizebuf_t msg; @@ -332,13 +367,13 @@ void CL_ParseEntityLump(char *entdata) data = entdata; if (!data) return; - if (!COM_ParseToken_Simple(&data, false)) + if (!COM_ParseToken_Simple(&data, false, false)) return; // error if (com_token[0] != '{') return; // error while (1) { - if (!COM_ParseToken_Simple(&data, false)) + if (!COM_ParseToken_Simple(&data, false, false)) return; // error if (com_token[0] == '}') break; // end of worldspawn @@ -348,7 +383,7 @@ void CL_ParseEntityLump(char *entdata) strlcpy (key, com_token, sizeof (key)); while (key[strlen(key)-1] == ' ') // remove trailing spaces key[strlen(key)-1] = 0; - if (!COM_ParseToken_Simple(&data, false)) + if (!COM_ParseToken_Simple(&data, false, false)) return; // error strlcpy (value, com_token, sizeof (value)); if (!strcmp("sky", key)) @@ -358,7 +393,18 @@ void CL_ParseEntityLump(char *entdata) else if (!strcmp("qlsky", key)) // non-standard, introduced by QuakeLives (EEK) R_SetSkyBox(value); else if (!strcmp("fog", key)) - sscanf(value, "%f %f %f %f", &r_refdef.fog_density, &r_refdef.fog_red, &r_refdef.fog_green, &r_refdef.fog_blue); + { + FOG_clear(); // so missing values get good defaults + r_refdef.fog_start = 0; + r_refdef.fog_alpha = 1; + r_refdef.fog_end = 16384; + r_refdef.fog_height = 1<<30; + r_refdef.fog_fadedepth = 128; +#if _MSC_VER >= 1400 +#define sscanf sscanf_s +#endif + sscanf(value, "%f %f %f %f %f %f %f %f %f", &r_refdef.fog_density, &r_refdef.fog_red, &r_refdef.fog_green, &r_refdef.fog_blue, &r_refdef.fog_alpha, &r_refdef.fog_start, &r_refdef.fog_end, &r_refdef.fog_height, &r_refdef.fog_fadedepth); + } else if (!strcmp("fog_density", key)) r_refdef.fog_density = atof(value); else if (!strcmp("fog_red", key)) @@ -367,36 +413,73 @@ void CL_ParseEntityLump(char *entdata) r_refdef.fog_green = atof(value); else if (!strcmp("fog_blue", key)) r_refdef.fog_blue = atof(value); + else if (!strcmp("fog_alpha", key)) + r_refdef.fog_alpha = atof(value); + else if (!strcmp("fog_start", key)) + r_refdef.fog_start = atof(value); + else if (!strcmp("fog_end", key)) + r_refdef.fog_end = atof(value); + else if (!strcmp("fog_height", key)) + r_refdef.fog_height = atof(value); + else if (!strcmp("fog_fadedepth", key)) + r_refdef.fog_fadedepth = atof(value); + else if (!strcmp("fog_heighttexture", key)) + { + FOG_clear(); // so missing values get good defaults +#if _MSC_VER >= 1400 + sscanf_s(value, "%f %f %f %f %f %f %f %f %f %s", &r_refdef.fog_density, &r_refdef.fog_red, &r_refdef.fog_green, &r_refdef.fog_blue, &r_refdef.fog_alpha, &r_refdef.fog_start, &r_refdef.fog_end, &r_refdef.fog_height, &r_refdef.fog_fadedepth, r_refdef.fog_height_texturename, (unsigned int)sizeof(r_refdef.fog_height_texturename)); +#else + sscanf(value, "%f %f %f %f %f %f %f %f %f %63s", &r_refdef.fog_density, &r_refdef.fog_red, &r_refdef.fog_green, &r_refdef.fog_blue, &r_refdef.fog_alpha, &r_refdef.fog_start, &r_refdef.fog_end, &r_refdef.fog_height, &r_refdef.fog_fadedepth, r_refdef.fog_height_texturename); +#endif + r_refdef.fog_height_texturename[63] = 0; + } } } extern void CL_Locs_Reload_f(void); extern void CL_VM_Init (void); +static const vec3_t defaultmins = {-4096, -4096, -4096}; +static const vec3_t defaultmaxs = {4096, 4096, 4096}; static void CL_SetupWorldModel(void) { // update the world model - cl.entities[0].render.model = cl.worldmodel = cl.model_precache[1]; + cl.entities[0].render.model = cl.worldmodel = CL_GetModelByIndex(1); CL_UpdateRenderEntity(&cl.entities[0].render); + // make sure the cl.worldname and related cvars are set up now that we know the world model name // set up csqc world for collision culling if (cl.worldmodel) { - VectorCopy(cl.worldmodel->normalmins, cl.world.areagrid_mins); - VectorCopy(cl.worldmodel->normalmaxs, cl.world.areagrid_maxs); + strlcpy(cl.worldname, cl.worldmodel->name, sizeof(cl.worldname)); + FS_StripExtension(cl.worldname, cl.worldnamenoextension, sizeof(cl.worldnamenoextension)); + strlcpy(cl.worldbasename, !strncmp(cl.worldnamenoextension, "maps/", 5) ? cl.worldnamenoextension + 4 : cl.worldnamenoextension, sizeof(cl.worldbasename)); + Cvar_SetQuick(&cl_worldmessage, cl.worldmessage); + Cvar_SetQuick(&cl_worldname, cl.worldname); + Cvar_SetQuick(&cl_worldnamenoextension, cl.worldnamenoextension); + Cvar_SetQuick(&cl_worldbasename, cl.worldbasename); + World_SetSize(&cl.world, cl.worldname, cl.worldmodel->normalmins, cl.worldmodel->normalmaxs); } else { - VectorSet(cl.world.areagrid_mins, -4096, -4096, -4096); - VectorSet(cl.world.areagrid_maxs, 4096, 4096, 4096); + Cvar_SetQuick(&cl_worldmessage, cl.worldmessage); + Cvar_SetQuick(&cl_worldnamenoextension, ""); + Cvar_SetQuick(&cl_worldbasename, ""); + World_SetSize(&cl.world, "", defaultmins, defaultmaxs); } - World_Clear(&cl.world); + World_Start(&cl.world); // load or reload .loc file for team chat messages CL_Locs_Reload_f(); + // make sure we send enough keepalives + CL_KeepaliveMessage(false); + // reset particles and other per-level things R_Modules_NewMap(); + // make sure we send enough keepalives + CL_KeepaliveMessage(false); + // load the team chat beep if possible cl.foundtalk2wav = FS_FileExists("sound/misc/talk2.wav"); @@ -407,6 +490,7 @@ static void CL_SetupWorldModel(void) if (cl.loadcsqc) { cl.loadcsqc = false; + CL_VM_Init(); } } @@ -416,7 +500,7 @@ static qboolean QW_CL_CheckOrDownloadFile(const char *filename) qfile_t *file; // see if the file already exists - file = FS_Open(filename, "rb", true, false); + file = FS_OpenVirtualFile(filename, true); if (file) { FS_Close(file); @@ -520,32 +604,31 @@ static void QW_CL_RequestNextDownload(void) cls.qw_downloadtype = dl_none; - // parse the Q3 shader files - Mod_LoadQ3Shaders(); - // touch all of the precached models that are still loaded so we can free // anything that isn't needed if (!sv.active) Mod_ClearUsed(); for (i = 1;i < MAX_MODELS && cl.model_name[i][0];i++) - Mod_FindName(cl.model_name[i]); + Mod_FindName(cl.model_name[i], cl.model_name[i][0] == '*' ? cl.model_name[1] : NULL); // 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(); + cl.model_bolt = Mod_ForName("progs/bolt.mdl", false, false, NULL); + cl.model_bolt2 = Mod_ForName("progs/bolt2.mdl", false, false, NULL); + cl.model_bolt3 = Mod_ForName("progs/bolt3.mdl", false, false, NULL); + cl.model_beam = Mod_ForName("progs/beam.mdl", false, false, NULL); + + // we purge the models and sounds later in CL_SignonReply + //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); + cl.model_precache[1] = Mod_ForName(cl.model_name[1], false, false, NULL); 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) + if ((cl.model_precache[i] = Mod_ForName(cl.model_name[i], false, false, cl.model_name[i][0] == '*' ? cl.model_name[1] : NULL))->Draw == NULL) Con_Printf("Model %s could not be found or downloaded\n", cl.model_name[i]); // check memory integrity @@ -588,9 +671,8 @@ static void QW_CL_RequestNextDownload(void) 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); + // clear sound usage flags for purging of unused sounds + S_ClearUsed(); // precache any sounds used by the client cl.sfx_wizhit = S_PrecacheSound(cl_sound_wizardhit.string, false, true); @@ -601,12 +683,12 @@ static void QW_CL_RequestNextDownload(void) 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 + // sounds used by the game 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); - } + cl.sound_precache[i] = S_PrecacheSound(cl.sound_name[i], true, true); + + // we purge the models and sounds later in CL_SignonReply + //S_PurgeUnused(); // check memory integrity Mem_CheckSentinelsGlobal(); @@ -945,6 +1027,11 @@ static void CL_UpdateItemsAndWeapon(void) cl.activeweapon = cl.stats[STAT_ACTIVEWEAPON]; } +#define LOADPROGRESSWEIGHT_SOUND 1.0 +#define LOADPROGRESSWEIGHT_MODEL 4.0 +#define LOADPROGRESSWEIGHT_WORLDMODEL 30.0 +#define LOADPROGRESSWEIGHT_WORLDMODEL_INIT 2.0 + void CL_BeginDownloads(qboolean aborteddownload) { // quakeworld works differently @@ -960,6 +1047,8 @@ void CL_BeginDownloads(qboolean aborteddownload) // if we got here... // curl is done, so let's start with the business + if(!cl.loadbegun) + SCR_PushLoadingScreen(false, "Loading precaches", 1); cl.loadbegun = true; // if already downloading something from the previous level, don't stop it @@ -979,7 +1068,11 @@ void CL_BeginDownloads(qboolean aborteddownload) && (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)); + Con_Printf("Downloading new CSQC code to dlcache/%s.%i.%i\n", csqc_progname.string, csqc_progsize.integer, csqc_progcrc.integer); + if(cl_serverextension_download.integer == 2 && FS_HasZlib()) + Cmd_ForwardStringToServer(va("download %s deflate", csqc_progname.string)); + else + Cmd_ForwardStringToServer(va("download %s", csqc_progname.string)); return; } } @@ -987,22 +1080,73 @@ void CL_BeginDownloads(qboolean aborteddownload) if (cl.loadmodel_current < cl.loadmodel_total) { // loading models - - // parse the Q3 shader files - if (cl.loadmodel_current < 2) - Mod_LoadQ3Shaders(); - + if(cl.loadmodel_current == 1) + { + // worldmodel counts as 16 models (15 + world model setup), for better progress bar + SCR_PushLoadingScreen(false, "Loading precached models", + ( + (cl.loadmodel_total - 1) * LOADPROGRESSWEIGHT_MODEL + + LOADPROGRESSWEIGHT_WORLDMODEL + + LOADPROGRESSWEIGHT_WORLDMODEL_INIT + ) / ( + (cl.loadmodel_total - 1) * LOADPROGRESSWEIGHT_MODEL + + LOADPROGRESSWEIGHT_WORLDMODEL + + LOADPROGRESSWEIGHT_WORLDMODEL_INIT + + cl.loadsound_total * LOADPROGRESSWEIGHT_SOUND + ) + ); + SCR_BeginLoadingPlaque(); + } for (;cl.loadmodel_current < cl.loadmodel_total;cl.loadmodel_current++) { + SCR_PushLoadingScreen(false, cl.model_name[cl.loadmodel_current], + ( + (cl.loadmodel_current == 1) ? LOADPROGRESSWEIGHT_WORLDMODEL : LOADPROGRESSWEIGHT_MODEL + ) / ( + (cl.loadmodel_total - 1) * LOADPROGRESSWEIGHT_MODEL + + LOADPROGRESSWEIGHT_WORLDMODEL + + LOADPROGRESSWEIGHT_WORLDMODEL_INIT + ) + ); if (cl.model_precache[cl.loadmodel_current] && cl.model_precache[cl.loadmodel_current]->Draw) + { + SCR_PopLoadingScreen(false); + if(cl.loadmodel_current == 1) + { + SCR_PushLoadingScreen(false, cl.model_name[cl.loadmodel_current], 1.0 / cl.loadmodel_total); + SCR_PopLoadingScreen(false); + } 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); + } + CL_KeepaliveMessage(true); + + // if running a local game, calling Mod_ForName is a completely wasted effort... + if (sv.active) + cl.model_precache[cl.loadmodel_current] = sv.models[cl.loadmodel_current]; + else + { + if(cl.loadmodel_current == 1) + { + // they'll be soon loaded, but make sure we apply freshly downloaded shaders from a curled pk3 + Mod_FreeQ3Shaders(); + } + cl.model_precache[cl.loadmodel_current] = Mod_ForName(cl.model_name[cl.loadmodel_current], false, false, cl.model_name[cl.loadmodel_current][0] == '*' ? cl.model_name[1] : NULL); + } + SCR_PopLoadingScreen(false); 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 + SCR_PushLoadingScreen(true, "world model setup", + ( + LOADPROGRESSWEIGHT_WORLDMODEL_INIT + ) / ( + (cl.loadmodel_total - 1) * LOADPROGRESSWEIGHT_MODEL + + LOADPROGRESSWEIGHT_WORLDMODEL + + LOADPROGRESSWEIGHT_WORLDMODEL_INIT + ) + ); CL_SetupWorldModel(); + SCR_PopLoadingScreen(true); if (!cl.loadfinished && cl_joinbeforedownloadsfinish.integer) { cl.loadfinished = true; @@ -1012,27 +1156,50 @@ void CL_BeginDownloads(qboolean aborteddownload) } } } - + SCR_PopLoadingScreen(false); // finished loading models } if (cl.loadsound_current < cl.loadsound_total) { // loading sounds - + if(cl.loadsound_current == 1) + SCR_PushLoadingScreen(false, "Loading precached sounds", + ( + cl.loadsound_total * LOADPROGRESSWEIGHT_SOUND + ) / ( + (cl.loadmodel_total - 1) * LOADPROGRESSWEIGHT_MODEL + + LOADPROGRESSWEIGHT_WORLDMODEL + + LOADPROGRESSWEIGHT_WORLDMODEL_INIT + + cl.loadsound_total * LOADPROGRESSWEIGHT_SOUND + ) + ); for (;cl.loadsound_current < cl.loadsound_total;cl.loadsound_current++) { + SCR_PushLoadingScreen(false, cl.sound_name[cl.loadsound_current], 1.0 / cl.loadsound_total); if (cl.sound_precache[cl.loadsound_current] && S_IsSoundPrecached(cl.sound_precache[cl.loadsound_current])) + { + SCR_PopLoadingScreen(false); 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); + } + CL_KeepaliveMessage(true); + cl.sound_precache[cl.loadsound_current] = S_PrecacheSound(cl.sound_name[cl.loadsound_current], false, true); + SCR_PopLoadingScreen(false); } - + SCR_PopLoadingScreen(false); // finished loading sounds } + if(gamemode == GAME_NEXUIZ) + Cvar_SetValueQuick(&cl_serverextension_download, false); + // in Nexuiz, the built in download protocol is kinda broken (misses lots + // of dependencies) anyway, and can mess around with the game directory; + // until this is fixed, only support pk3 downloads via curl, and turn off + // individual file downloads other than for CSQC + // on the other end of the download protocol, GAME_NEXUIZ enforces writing + // to dlcache only + // idea: support download of pk3 files using this protocol later + // note: the reason these loops skip already-loaded things is that it // enables this command to be issued during the game if desired @@ -1044,9 +1211,11 @@ void CL_BeginDownloads(qboolean aborteddownload) { if (aborteddownload) { + if (cl.downloadmodel_current == 1) { // the worldmodel failed, but we need to set up anyway + Mod_FreeQ3Shaders(); CL_SetupWorldModel(); if (!cl.loadfinished && cl_joinbeforedownloadsfinish.integer) { @@ -1061,9 +1230,8 @@ void CL_BeginDownloads(qboolean aborteddownload) } 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])) + CL_KeepaliveMessage(true); + if (cl.model_name[cl.downloadmodel_current][0] != '*' && strcmp(cl.model_name[cl.downloadmodel_current], "null") && !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]); @@ -1077,10 +1245,19 @@ void CL_BeginDownloads(qboolean aborteddownload) 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) + { + // they'll be soon loaded, but make sure we apply freshly downloaded shaders from a curled pk3 + Mod_FreeQ3Shaders(); + } + + cl.model_precache[cl.downloadmodel_current] = Mod_ForName(cl.model_name[cl.downloadmodel_current], false, false, cl.model_name[cl.downloadmodel_current][0] == '*' ? cl.model_name[1] : NULL); if (cl.downloadmodel_current == 1) { // we now have the worldmodel so we can set up the game world + // or maybe we do not have it (cl_serverextension_download 0) + // then we need to continue loading ANYWAY! CL_SetupWorldModel(); if (!cl.loadfinished && cl_joinbeforedownloadsfinish.integer) { @@ -1109,8 +1286,7 @@ void CL_BeginDownloads(qboolean aborteddownload) } if (cl.sound_precache[cl.downloadsound_current] && S_IsSoundPrecached(cl.sound_precache[cl.downloadsound_current])) continue; - if (cls.signon < SIGNONS) - CL_KeepaliveMessage(true); + 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])) { @@ -1122,13 +1298,14 @@ void CL_BeginDownloads(qboolean aborteddownload) 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); + cl.sound_precache[cl.downloadsound_current] = S_PrecacheSound(cl.sound_name[cl.downloadsound_current], false, true); } // finished loading sounds } + SCR_PopLoadingScreen(false); + if (!cl.loadfinished) { cl.loadfinished = true; @@ -1147,51 +1324,86 @@ 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"); + Con_Printf("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) + if (cls.qw_downloadmemory && cls.qw_downloadmemorycursize == size && CRC_Block(cls.qw_downloadmemory, cls.qw_downloadmemorycursize) == 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(cls.qw_download_deflate) { - if ((int)existingsize != size || existingcrc != crc) + unsigned char *out; + size_t inflated_size; + out = FS_Inflate(cls.qw_downloadmemory, cls.qw_downloadmemorycursize, &inflated_size, tempmempool); + Mem_Free(cls.qw_downloadmemory); + if(out) { - // 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); - } + Con_Printf("Inflated download: new size: %u (%g%%)\n", (unsigned)inflated_size, 100.0 - 100.0*(cls.qw_downloadmemorycursize / (float)inflated_size)); + cls.qw_downloadmemory = out; + cls.qw_downloadmemorycursize = inflated_size; } + else + { + cls.qw_downloadmemory = NULL; + cls.qw_downloadmemorycursize = 0; + Con_Printf("Cannot inflate download, possibly corrupt or zlib not present\n"); + } + } + + if(!cls.qw_downloadmemory) + { + Con_Printf("Download \"%s\" is corrupt (see above!)\n", cls.qw_downloadname); } 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(); + crc = CRC_Block(cls.qw_downloadmemory, cls.qw_downloadmemorycursize); + size = cls.qw_downloadmemorycursize; + // 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 || gamemode == GAME_NEXUIZ || !strcmp(cls.qw_downloadname, csqc_progname.string)) + // let csprogs ALWAYS go to dlcache, to prevent "viral csprogs"; also, never put files outside dlcache for Nexuiz + { + 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(); + } } } + else if (cls.qw_downloadmemory && size) + { + Con_Printf("Download \"%s\" is corrupt (%i bytes, %i CRC, should be %i bytes, %i CRC), discarding\n", cls.qw_downloadname, size, crc, (int)cls.qw_downloadmemorycursize, (int)CRC_Block(cls.qw_downloadmemory, cls.qw_downloadmemorycursize)); + CL_BeginDownloads(true); + } if (cls.qw_downloadmemory) Mem_Free(cls.qw_downloadmemory); @@ -1205,7 +1417,7 @@ void CL_StopDownload(int size, int crc) void CL_ParseDownload(void) { int i, start, size; - unsigned char data[65536]; + static unsigned char data[NET_MAXMESSAGE]; start = MSG_ReadLong(); size = (unsigned short)MSG_ReadShort(); @@ -1260,14 +1472,23 @@ void CL_DownloadBegin_f(void) // 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_downloadmemory = (unsigned char *) Mem_Alloc(cls.permanentmempool, cls.qw_downloadmemorymaxsize); cls.qw_downloadnumber++; + cls.qw_download_deflate = false; + if(Cmd_Argc() >= 4) + { + if(!strcmp(Cmd_Argv(3), "deflate")) + cls.qw_download_deflate = true; + // check further encodings here + } + Cmd_ForwardStringToServer("sv_startdownload"); } void CL_StopDownload_f(void) { + Curl_CancelAll(); if (cls.qw_downloadname[0]) { Con_Printf("Download of %s aborted\n", cls.qw_downloadname); @@ -1369,7 +1590,16 @@ static void CL_SignonReply (void) break; case 4: + // after the level has been loaded, we shouldn't need the shaders, and + // if they are needed again they will be automatically loaded... + // we also don't need the unused models or sounds from the last level + Mod_FreeQ3Shaders(); + Mod_PurgeUnused(); + S_PurgeUnused(); + Con_ClearNotify(); + if (COM_CheckParm("-profilegameonly")) + Sys_AllowProfiling(true); break; } } @@ -1390,7 +1620,12 @@ void CL_ParseServerInfo (void) // if server is active, we already began a loading plaque if (!sv.active) + { SCR_BeginLoadingPlaque(); + S_StopAllSounds(); + // free q3 shaders so that any newly downloaded shaders will be active + Mod_FreeQ3Shaders(); + } // check memory integrity Mem_CheckSentinelsGlobal(); @@ -1440,12 +1675,12 @@ void CL_ParseServerInfo (void) 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.realplayerentity = 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)); + strlcpy (cl.worldmessage, str, sizeof(cl.worldmessage)); // get the movevars that are defined in the qw protocol cl.movevars_gravity = MSG_ReadFloat(); @@ -1463,14 +1698,14 @@ void CL_ParseServerInfo (void) cl.movevars_wallfriction = 0; cl.movevars_timescale = 1; cl.movevars_jumpvelocity = 270; - cl.movevars_edgefriction = 2; + cl.movevars_edgefriction = 1; cl.movevars_maxairspeed = 30; cl.movevars_stepheight = 18; cl.movevars_airaccel_qw = 1; cl.movevars_airaccel_sideways_friction = 0; // 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); + Con_Printf("\n\n<===================================>\n\n\2%s\n", str); // check memory integrity Mem_CheckSentinelsGlobal(); @@ -1490,6 +1725,17 @@ void CL_ParseServerInfo (void) // 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) + // this also means cl_autodemo does not work on QW protocol... + + strlcpy(cl.worldname, "", sizeof(cl.worldname)); + strlcpy(cl.worldnamenoextension, "", sizeof(cl.worldnamenoextension)); + strlcpy(cl.worldbasename, "qw", sizeof(cl.worldbasename)); + Cvar_SetQuick(&cl_worldname, cl.worldname); + Cvar_SetQuick(&cl_worldnamenoextension, cl.worldnamenoextension); + Cvar_SetQuick(&cl_worldbasename, cl.worldbasename); + + // check memory integrity + Mem_CheckSentinelsGlobal(); } else { @@ -1504,14 +1750,18 @@ void CL_ParseServerInfo (void) // parse gametype cl.gametype = MSG_ReadByte (); + // the original id singleplayer demos are bugged and contain + // GAME_DEATHMATCH even for singleplayer + if (cl.maxclients == 1 && cls.protocol == PROTOCOL_QUAKE) + cl.gametype = GAME_COOP; // parse signon message str = MSG_ReadString (); - strlcpy (cl.levelname, str, sizeof(cl.levelname)); + strlcpy (cl.worldmessage, str, sizeof(cl.worldmessage)); // 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); + Con_Printf("\n<===================================>\n\n\2%s\n", str); // check memory integrity Mem_CheckSentinelsGlobal(); @@ -1541,22 +1791,33 @@ void CL_ParseServerInfo (void) strlcpy (cl.sound_name[numsounds], str, sizeof (cl.sound_name[numsounds])); } + // set the base name for level-specific things... this gets updated again by CL_SetupWorldModel later + strlcpy(cl.worldname, cl.model_name[1], sizeof(cl.worldname)); + FS_StripExtension(cl.worldname, cl.worldnamenoextension, sizeof(cl.worldnamenoextension)); + strlcpy(cl.worldbasename, FS_FileWithoutPath(cl.worldnamenoextension), sizeof(cl.worldbasename)); + Cvar_SetQuick(&cl_worldmessage, cl.worldmessage); + Cvar_SetQuick(&cl_worldname, cl.worldname); + Cvar_SetQuick(&cl_worldnamenoextension, cl.worldnamenoextension); + Cvar_SetQuick(&cl_worldbasename, cl.worldbasename); + // touch all of the precached models that are still loaded so we can free // anything that isn't needed if (!sv.active) Mod_ClearUsed(); for (i = 1;i < nummodels;i++) - Mod_FindName(cl.model_name[i]); + Mod_FindName(cl.model_name[i], cl.model_name[i][0] == '*' ? cl.model_name[1] : NULL); // 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(); + cl.model_bolt = Mod_ForName("progs/bolt.mdl", false, false, NULL); + cl.model_bolt2 = Mod_ForName("progs/bolt2.mdl", false, false, NULL); + cl.model_bolt3 = Mod_ForName("progs/bolt3.mdl", false, false, NULL); + cl.model_beam = Mod_ForName("progs/beam.mdl", false, false, NULL); + + // we purge the models and sounds later in CL_SignonReply + //Mod_PurgeUnused(); + //S_PurgeUnused(); - // do the same for sounds - // FIXME: S_ServerSounds does not know about cl.sfx_ sounds - S_ServerSounds (cl.sound_name, numsounds); + // clear sound usage flags for purging of unused sounds + S_ClearUsed(); // precache any sounds used by the client cl.sfx_wizhit = S_PrecacheSound(cl_sound_wizardhit.string, false, true); @@ -1567,6 +1828,10 @@ void CL_ParseServerInfo (void) 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 used by the game + for (i = 1;i < MAX_SOUNDS && cl.sound_name[i][0];i++) + cl.sound_precache[i] = S_PrecacheSound(cl.sound_name[i], true, true); + // now we try to load everything that is new cl.loadmodel_current = 1; cl.downloadmodel_current = 1; @@ -1577,46 +1842,53 @@ void CL_ParseServerInfo (void) cl.downloadcsqc = true; cl.loadbegun = false; cl.loadfinished = false; - } - - // check memory integrity - Mem_CheckSentinelsGlobal(); + cl.loadcsqc = true; -// 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]; + // check memory integrity + Mem_CheckSentinelsGlobal(); - if (cls.demorecording) + // 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) { - // finish the previous level's demo file - CL_Stop_f(); - } + char demofile[MAX_OSPATH]; - // 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); + if (cls.demorecording) + { + // finish the previous level's demo file + CL_Stop_f(); + } - Con_Printf ("Auto-recording to %s.\n", demofile); + // start a new demo file + dpsnprintf (demofile, sizeof(demofile), "%s_%s.dem", Sys_TimeString (cl_autodemo_nameformat.string), cl.worldbasename); - 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; + Con_Printf ("Auto-recording to %s.\n", demofile); + + // Reset bit 0 for every new demo + Cvar_SetValueQuick(&cl_autodemo_delete, + (cl_autodemo_delete.integer & ~0x1) + | + ((cl_autodemo_delete.integer & 0x2) ? 0x1 : 0) + ); + + cls.demofile = FS_OpenRealFile(demofile, "wb", false); + if (cls.demofile) + { + cls.forcetrack = -1; + FS_Printf (cls.demofile, "%i\n", cls.forcetrack); + cls.demorecording = true; + strlcpy(cls.demoname, demofile, sizeof(cls.demoname)); + cls.demo_lastcsprogssize = -1; + cls.demo_lastcsprogscrc = -1; + } + else + Con_Print ("ERROR: couldn't open.\n"); } - else - Con_Print ("ERROR: couldn't open.\n"); } } void CL_ValidateState(entity_state_t *s) { - model_t *model; + dp_model_t *model; if (!s->active) return; @@ -1624,30 +1896,25 @@ void CL_ValidateState(entity_state_t *s) if (s->modelindex >= MAX_MODELS) Host_Error("CL_ValidateState: modelindex (%i) >= MAX_MODELS (%i)\n", s->modelindex, MAX_MODELS); + // these warnings are only warnings, no corrections are made to the state + // because states are often copied for decoding, which otherwise would + // propogate some of the corrections accidentally + // (this used to happen, sometimes affecting skin and frame) + // colormap is client index + 1 - if ((!s->flags & RENDER_COLORMAPPED) && s->colormap > cl.maxclients) - { + 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]; + model = CL_GetModelByIndex(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) @@ -1660,14 +1927,49 @@ void CL_MoveLerpEntityStates(entity_t *ent) 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; + ent->render.framegroupblend[0].frame = ent->render.framegroupblend[1].frame = ent->state_current.frame; + ent->render.framegroupblend[0].start = ent->render.framegroupblend[1].start = cl.time; + ent->render.framegroupblend[0].lerp = 1;ent->render.framegroupblend[1].lerp = 0; + ent->render.shadertime = cl.time; // reset various persistent stuff ent->persistent.muzzleflash = 0; ent->persistent.trail_allowed = false; } - else if (DotProduct(odelta, odelta) > 1000*1000 || (cl.fixangle[0] && !cl.fixangle[1])) + else if ((ent->state_previous.effects & EF_TELEPORT_BIT) != (ent->state_current.effects & EF_TELEPORT_BIT)) + { + // don't interpolate the move + 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); + ent->persistent.trail_allowed = false; + + // if(ent->state_current.frame != ent->state_previous.frame) + // do this even if we did change the frame + // teleport bit is only used if an animation restart, or a jump, is necessary + // so it should be always harmless to do this + { + ent->render.framegroupblend[0].frame = ent->render.framegroupblend[1].frame = ent->state_current.frame; + ent->render.framegroupblend[0].start = ent->render.framegroupblend[1].start = cl.time; + ent->render.framegroupblend[0].lerp = 1;ent->render.framegroupblend[1].lerp = 0; + } + + // note that this case must do everything the following case does too + } + else if ((ent->state_previous.effects & EF_RESTARTANIM_BIT) != (ent->state_current.effects & EF_RESTARTANIM_BIT)) + { + ent->render.framegroupblend[1] = ent->render.framegroupblend[0]; + ent->render.framegroupblend[1].lerp = 1; + ent->render.framegroupblend[0].frame = ent->state_current.frame; + ent->render.framegroupblend[0].start = cl.time; + ent->render.framegroupblend[0].lerp = 0; + } + else if (DotProduct(odelta, odelta) > 1000*1000 + || (cl.fixangle[0] && !cl.fixangle[1]) + || (ent->state_previous.tagindex != ent->state_current.tagindex) + || (ent->state_previous.tagentity != ent->state_current.tagentity)) { // don't interpolate the move // (the fixangle[] check detects teleports, but not constant fixangles @@ -1729,6 +2031,11 @@ void CL_ParseBaseline (entity_t *ent, int large) ent->state_baseline.modelindex = (unsigned short) MSG_ReadShort (); ent->state_baseline.frame = (unsigned short) MSG_ReadShort (); } + else if (cls.protocol == PROTOCOL_NEHAHRABJP || cls.protocol == PROTOCOL_NEHAHRABJP2 || cls.protocol == PROTOCOL_NEHAHRABJP3) + { + ent->state_baseline.modelindex = (unsigned short) MSG_ReadShort (); + ent->state_baseline.frame = MSG_ReadByte (); + } else { ent->state_baseline.modelindex = MSG_ReadByte (); @@ -1761,7 +2068,7 @@ void CL_ParseClientdata (void) VectorCopy (cl.mvelocity[0], cl.mvelocity[1]); cl.mviewzoom[1] = cl.mviewzoom[0]; - if (cls.protocol == PROTOCOL_QUAKE || cls.protocol == PROTOCOL_QUAKEDP || cls.protocol == PROTOCOL_NEHAHRAMOVIE || cls.protocol == PROTOCOL_DARKPLACES1 || cls.protocol == PROTOCOL_DARKPLACES2 || cls.protocol == PROTOCOL_DARKPLACES3 || cls.protocol == PROTOCOL_DARKPLACES4 || cls.protocol == PROTOCOL_DARKPLACES5) + if (cls.protocol == PROTOCOL_QUAKE || cls.protocol == PROTOCOL_QUAKEDP || cls.protocol == PROTOCOL_NEHAHRAMOVIE || cls.protocol == PROTOCOL_NEHAHRABJP || cls.protocol == PROTOCOL_NEHAHRABJP2 || cls.protocol == PROTOCOL_NEHAHRABJP3 || cls.protocol == PROTOCOL_DARKPLACES1 || cls.protocol == PROTOCOL_DARKPLACES2 || cls.protocol == PROTOCOL_DARKPLACES3 || cls.protocol == PROTOCOL_DARKPLACES4 || cls.protocol == PROTOCOL_DARKPLACES5) { cl.stats[STAT_VIEWHEIGHT] = DEFAULT_VIEWHEIGHT; cl.stats[STAT_ITEMS] = 0; @@ -1795,7 +2102,7 @@ void CL_ParseClientdata (void) { if (bits & (SU_PUNCH1<render.model = cl.model_precache[ent->state_baseline.modelindex]; - ent->render.frame = ent->render.frame1 = ent->render.frame2 = ent->state_baseline.frame; - ent->render.framelerp = 0; + ent->render.model = CL_GetModelByIndex(ent->state_baseline.modelindex); + ent->render.framegroupblend[0].frame = ent->state_baseline.frame; + ent->render.framegroupblend[0].lerp = 1; // make torchs play out of sync - ent->render.frame1time = ent->render.frame2time = lhrandom(-10, -1); - ent->render.colormap = -1; // no special coloring + ent->render.framegroupblend[0].start = lhrandom(-10, -1); ent->render.skinnum = ent->state_baseline.skin; ent->render.effects = ent->state_baseline.effects; ent->render.alpha = 1; @@ -1902,6 +2211,7 @@ void CL_ParseStatic (int large) //VectorCopy (ent->state_baseline.angles, ent->render.angles); Matrix4x4_CreateFromQuakeEntity(&ent->render.matrix, ent->state_baseline.origin[0], ent->state_baseline.origin[1], ent->state_baseline.origin[2], ent->state_baseline.angles[0], ent->state_baseline.angles[1], ent->state_baseline.angles[2], 1); + ent->render.allowdecals = true; CL_UpdateRenderEntity(&ent->render); } @@ -1916,7 +2226,7 @@ void CL_ParseStaticSound (int large) int sound_num, vol, atten; MSG_ReadVector(org, cls.protocol); - if (large) + if (large || cls.protocol == PROTOCOL_NEHAHRABJP2) sound_num = (unsigned short) MSG_ReadShort (); else sound_num = MSG_ReadByte (); @@ -1954,7 +2264,7 @@ void CL_ParseEffect2 (void) CL_Effect(org, modelindex, startframe, framecount, framerate); } -void CL_NewBeam (int ent, vec3_t start, vec3_t end, model_t *m, int lightning) +void CL_NewBeam (int ent, vec3_t start, vec3_t end, dp_model_t *m, int lightning) { int i; beam_t *b = NULL; @@ -1993,7 +2303,7 @@ void CL_NewBeam (int ent, vec3_t start, vec3_t end, model_t *m, int lightning) Con_Print("beam list overflow!\n"); } -void CL_ParseBeam (model_t *m, int lightning) +void CL_ParseBeam (dp_model_t *m, int lightning) { int ent; vec3_t start, end; @@ -2476,7 +2786,7 @@ void CL_ParseTempEntity(void) // LordHavoc: for compatibility with the Nehahra movie... case TE_LIGHTNING4NEH: - CL_ParseBeam(Mod_ForName(MSG_ReadString(), true, false, false), false); + CL_ParseBeam(Mod_ForName(MSG_ReadString(), true, false, NULL), false); break; case TE_LAVASPLASH: @@ -2496,7 +2806,7 @@ void CL_ParseTempEntity(void) colorStart = MSG_ReadByte(); colorLength = MSG_ReadByte(); CL_ParticleExplosion2(pos, colorStart, colorLength); - tempcolor = (unsigned char *)&palette_complete[(rand()%colorLength) + colorStart]; + tempcolor = palette_rgb[(rand()%colorLength) + colorStart]; color[0] = tempcolor[0] * (2.0f / 255.0f); color[1] = tempcolor[1] * (2.0f / 255.0f); color[2] = tempcolor[2] * (2.0f / 255.0f); @@ -2597,8 +2907,8 @@ static void CL_IPLog_Add(const char *address, const char *name, qboolean checkex return; if (!cl_iplog_loaded) CL_IPLog_Load(); - if (developer.integer >= 100) - Con_Printf("CL_IPLog_Add(\"%s\", \"%s\", %i, %i);\n", address, name, checkexisting, addtofile); + if (developer_extra.integer) + Con_DPrintf("CL_IPLog_Add(\"%s\", \"%s\", %i, %i);\n", address, name, checkexisting, addtofile); // see if it already exists if (checkexisting) { @@ -2606,8 +2916,8 @@ static void CL_IPLog_Add(const char *address, const char *name, qboolean checkex { if (!strcmp(cl_iplog_items[i].address, address) && !strcmp(cl_iplog_items[i].name, name)) { - if (developer.integer >= 100) - Con_Printf("... found existing \"%s\" \"%s\"\n", cl_iplog_items[i].address, cl_iplog_items[i].name); + if (developer_extra.integer) + Con_DPrintf("... found existing \"%s\" \"%s\"\n", cl_iplog_items[i].address, cl_iplog_items[i].name); return; } } @@ -2617,7 +2927,7 @@ static void CL_IPLog_Add(const char *address, const char *name, qboolean checkex { cl_iplog_item_t *olditems = cl_iplog_items; cl_iplog_maxitems = max(1024, cl_iplog_maxitems + 256); - cl_iplog_items = Mem_Alloc(cls.permanentmempool, cl_iplog_maxitems * sizeof(cl_iplog_item_t)); + cl_iplog_items = (cl_iplog_item_t *) Mem_Alloc(cls.permanentmempool, cl_iplog_maxitems * sizeof(cl_iplog_item_t)); if (olditems) { if (cl_iplog_numitems) @@ -2625,8 +2935,8 @@ static void CL_IPLog_Add(const char *address, const char *name, qboolean checkex Mem_Free(olditems); } } - cl_iplog_items[cl_iplog_numitems].address = Mem_Alloc(cls.permanentmempool, strlen(address) + 1); - cl_iplog_items[cl_iplog_numitems].name = Mem_Alloc(cls.permanentmempool, strlen(name) + 1); + cl_iplog_items[cl_iplog_numitems].address = (char *) Mem_Alloc(cls.permanentmempool, strlen(address) + 1); + cl_iplog_items[cl_iplog_numitems].name = (char *) Mem_Alloc(cls.permanentmempool, strlen(name) + 1); strlcpy(cl_iplog_items[cl_iplog_numitems].address, address, strlen(address) + 1); // TODO: maybe it would be better to strip weird characters from name when // copying it here rather than using a straight strcpy? @@ -2638,8 +2948,8 @@ static void CL_IPLog_Add(const char *address, const char *name, qboolean checkex // TODO: this ought to open the one in the userpath version of the base // gamedir, not the current gamedir Log_Printf(cl_iplog_name.string, "%s %s\n", address, name); - if (developer.integer >= 100) - Con_Printf("CL_IPLog_Add: appending this line to %s: %s %s\n", cl_iplog_name.string, address, name); + if (developer_extra.integer) + Con_DPrintf("CL_IPLog_Add: appending this line to %s: %s %s\n", cl_iplog_name.string, address, name); } } @@ -2671,7 +2981,7 @@ static void CL_IPLog_Load(void) text++; if (line[0] == '/' && line[1] == '/') continue; // skip comments if anyone happens to add them - for (i = 0;i < len && line[i] > ' ';i++) + for (i = 0;i < len && !ISWHITESPACE(line[i]);i++) address[i] = line[i]; address[i] = 0; // skip exactly one space character @@ -2759,7 +3069,7 @@ qboolean CL_ExaminePrintString(const char *text) if (cl.parsingtextmode == CL_PARSETEXTMODE_PING) { // if anything goes wrong, we'll assume this is not a ping report - qboolean expected = cl.parsingtextexpectingpingforscores; + qboolean expected = cl.parsingtextexpectingpingforscores != 0; cl.parsingtextexpectingpingforscores = 0; cl.parsingtextmode = CL_PARSETEXTMODE_NONE; t = text; @@ -2871,22 +3181,31 @@ qboolean CL_ExaminePrintString(const char *text) } extern cvar_t slowmo; +extern void CSQC_UpdateNetworkTimes(double newtime, double oldtime); static void CL_NetworkTimeReceived(double newtime) { double timehigh; cl.mtime[1] = cl.mtime[0]; cl.mtime[0] = newtime; - if (cls.timedemo || (cl.islocalgame && !sv_fixedframeratesingleplayer.integer) || cl.mtime[1] == cl.mtime[0] || cls.signon < SIGNONS) + if (cl_nolerp.integer || cls.timedemo || (cl.islocalgame && !sv_fixedframeratesingleplayer.integer) || cl.mtime[1] == cl.mtime[0] || cls.signon < SIGNONS) cl.time = cl.mtime[1] = newtime; - else if (cls.protocol != PROTOCOL_QUAKEWORLD && !cls.demoplayback) + else if (cls.demoplayback) + { + // when time falls behind during demo playback it means the cl.mtime[1] was altered + // due to a large time gap, so treat it as an instant change in time + // (this can also happen during heavy packet loss in the demo) + if (cl.time < newtime - 0.1) + cl.mtime[1] = cl.time = newtime; + } + else if (cls.protocol != PROTOCOL_QUAKEWORLD) { cl.mtime[1] = max(cl.mtime[1], cl.mtime[0] - 0.1); - if (developer.integer >= 100 && vid_activewindow) + if (developer_extra.integer && vid_activewindow) { if (cl.time < cl.mtime[1] - (cl.mtime[0] - cl.mtime[1])) - Con_Printf("--- cl.time < cl.mtime[1] (%f < %f ... %f)\n", cl.time, cl.mtime[1], cl.mtime[0]); + Con_DPrintf("--- cl.time < cl.mtime[1] (%f < %f ... %f)\n", cl.time, cl.mtime[1], cl.mtime[0]); else if (cl.time > cl.mtime[0] + (cl.mtime[0] - cl.mtime[1])) - Con_Printf("--- cl.time > cl.mtime[0] (%f > %f ... %f)\n", cl.time, cl.mtime[1], cl.mtime[0]); + Con_DPrintf("--- cl.time > cl.mtime[0] (%f > %f ... %f)\n", cl.time, cl.mtime[1], cl.mtime[0]); } cl.time += (cl.mtime[1] - cl.time) * bound(0, cl_nettimesyncfactor.value, 1); timehigh = cl.mtime[1] + (cl.mtime[0] - cl.mtime[1]) * cl_nettimesyncboundtolerance.value; @@ -2930,7 +3249,7 @@ static void CL_NetworkTimeReceived(double newtime) } // this packet probably contains a player entity update, so we will need // to update the prediction - cl.movement_needupdate = true; + cl.movement_replay = true; // this may get updated later in parsing by svc_clientdata cl.onground = false; // if true the cl.viewangles are interpolated from cl.mviewangles[] @@ -2940,9 +3259,14 @@ static void CL_NetworkTimeReceived(double newtime) cl.fixangle[0] = false; if (!cls.demoplayback) VectorCopy(cl.mviewangles[0], cl.mviewangles[1]); + // update the csqc's server timestamps, critical for proper sync + CSQC_UpdateNetworkTimes(cl.mtime[0], cl.mtime[1]); + + if (cl.mtime[0] > cl.mtime[1]) + World_Physics_Frame(&cl.world, cl.mtime[0] - cl.mtime[1], cl.movevars_gravity); } -#define SHOWNET(x) if(cl_shownet.integer==2)Con_Printf("%3i:%s\n", msg_readcount-1, x); +#define SHOWNET(x) if(cl_shownet.integer==2)Con_Printf("%3i:%s(%i)\n", msg_readcount-1, x, cmd); //[515]: csqc qboolean CL_VM_Parse_TempEntity (void); @@ -2966,6 +3290,8 @@ void CL_ParseServerMessage(void) unsigned char cmdlog[32]; char *cmdlogname[32], *temp; int cmdindex, cmdcount = 0; + qboolean qwplayerupdatereceived; + qboolean strip_pqc; // LordHavoc: moved demo message writing from before the packet parse to // after the packet parse so that CL_Stop_f can be called by cl_autodemo @@ -2975,8 +3301,7 @@ void CL_ParseServerMessage(void) cl.last_received_message = realtime; - if (cls.netcon && cls.signon < SIGNONS) - CL_KeepaliveMessage(false); + CL_KeepaliveMessage(false); // // if recording demos, copy the message out @@ -2997,10 +3322,6 @@ void CL_ParseServerMessage(void) { CL_NetworkTimeReceived(realtime); // qw has no clock - // slightly kill qw player entities each frame - for (i = 1;i < cl.maxclients;i++) - cl.entities_active[i] = false; - // kill all qw nails cl.qw_num_nails = 0; @@ -3009,6 +3330,8 @@ void CL_ParseServerMessage(void) cls.servermovesequence = cls.netcon->qw.incoming_sequence; + qwplayerupdatereceived = false; + while (1) { if (msg_badread) @@ -3271,6 +3594,14 @@ void CL_ParseServerMessage(void) break; case qw_svc_playerinfo: + // slightly kill qw player entities now that we know there is + // an update of player entities this frame... + if (!qwplayerupdatereceived) + { + qwplayerupdatereceived = true; + for (i = 1;i < cl.maxclients;i++) + cl.entities_active[i] = false; + } EntityStateQW_ReadPlayerUpdate(); break; @@ -3297,14 +3628,20 @@ void CL_ParseServerMessage(void) EntityFrameQW_CL_ReadFrame(false); // first update is the final signon stage if (cls.signon == SIGNONS - 1) + { cls.signon = SIGNONS; + CL_SignonReply (); + } break; case qw_svc_deltapacketentities: EntityFrameQW_CL_ReadFrame(true); // first update is the final signon stage if (cls.signon == SIGNONS - 1) + { cls.signon = SIGNONS; + CL_SignonReply (); + } break; case qw_svc_maxspeed: @@ -3318,7 +3655,7 @@ void CL_ParseServerMessage(void) break; case qw_svc_setpause: - cl.paused = MSG_ReadByte (); + cl.paused = MSG_ReadByte () != 0; if (cl.paused) CDAudio_Pause (); else @@ -3328,24 +3665,13 @@ void CL_ParseServerMessage(void) } } - // fully kill the still slightly dead qw player entities each frame, - // but only if a player update was received - for (i = 1;i <= cl.maxclients;i++) - if (cl.entities_active[i]) - break; - if (i <= cl.maxclients) + if (qwplayerupdatereceived) { - // kill all non-updated entities this frame + // fully kill any player entities that were not updated this frame for (i = 1;i <= cl.maxclients;i++) if (!cl.entities_active[i]) cl.entities[i].state_current.active = false; } - else - { - // no update this frame, restore the cl.entities_active for good measure - for (i = 1;i <= cl.maxclients;i++) - cl.entities_active[i] = cl.entities[i].state_current.active; - } } else { @@ -3358,6 +3684,7 @@ void CL_ParseServerMessage(void) if (cmd == -1) { +// R_TimeReport("END OF MESSAGE"); SHOWNET("END OF MESSAGE"); break; // end of message } @@ -3462,7 +3789,42 @@ void CL_ParseServerMessage(void) break; case svc_stufftext: - CL_VM_Parse_StuffCmd(MSG_ReadString ()); //[515]: csqc + temp = MSG_ReadString(); + /* if(utf8_enable.integer) + { + strip_pqc = true; + // we can safely strip and even + // interpret these in utf8 mode + } + else */ switch(cls.protocol) + { + case PROTOCOL_QUAKE: + case PROTOCOL_QUAKEDP: + // maybe add other protocols if + // so desired, but not DP7 + strip_pqc = true; + break; + case PROTOCOL_DARKPLACES7: + default: + // ProQuake does not support + // these protocols + strip_pqc = false; + break; + } + if(strip_pqc) + { + // skip over ProQuake messages, + // TODO actually interpret them + // (they are sbar team score + // updates), see proquake cl_parse.c + if(*temp == 0x01) + { + ++temp; + while(*temp >= 0x01 && *temp <= 0x1F) + ++temp; + } + } + CL_VM_Parse_StuffCmd(temp); //[515]: csqc break; case svc_damage: @@ -3493,7 +3855,10 @@ void CL_ParseServerMessage(void) if (cl.viewentity >= cl.max_entities) CL_ExpandEntities(cl.viewentity); // LordHavoc: assume first setview recieved is the real player entity - if (!cl.playerentity) + if (!cl.realplayerentity) + cl.realplayerentity = cl.viewentity; + // update cl.playerentity to this one if it is a valid player + if (cl.viewentity >= 1 && cl.viewentity <= cl.maxclients) cl.playerentity = cl.viewentity; break; @@ -3527,7 +3892,7 @@ void CL_ParseServerMessage(void) { if (i >= 1 && i < MAX_MODELS) { - model_t *model = Mod_ForName(s, false, false, i == 1); + dp_model_t *model = Mod_ForName(s, false, false, s[0] == '*' ? cl.model_name[1] : NULL); if (!model) Con_DPrintf("svc_precache: Mod_ForName(\"%s\") failed\n", s); cl.model_precache[i] = model; @@ -3540,7 +3905,7 @@ void CL_ParseServerMessage(void) i -= 32768; if (i >= 1 && i < MAX_SOUNDS) { - sfx_t *sfx = S_PrecacheSound (s, true, false); + sfx_t *sfx = S_PrecacheSound (s, true, true); if (!sfx && snd_initialized.integer) Con_DPrintf("svc_precache: S_PrecacheSound(\"%s\") failed\n", s); cl.sound_precache[i] = sfx; @@ -3617,7 +3982,7 @@ void CL_ParseServerMessage(void) break; case svc_setpause: - cl.paused = MSG_ReadByte (); + cl.paused = MSG_ReadByte () != 0; if (cl.paused) CDAudio_Pause (); else @@ -3678,12 +4043,14 @@ void CL_ParseServerMessage(void) if(!cl.intermission) cl.completed_time = cl.time; cl.intermission = 1; + CL_VM_UpdateIntermissionState(cl.intermission); break; case svc_finale: if(!cl.intermission) cl.completed_time = cl.time; cl.intermission = 2; + CL_VM_UpdateIntermissionState(cl.intermission); SCR_CenterPrint(MSG_ReadString ()); break; @@ -3691,6 +4058,7 @@ void CL_ParseServerMessage(void) if(!cl.intermission) cl.completed_time = cl.time; cl.intermission = 3; + CL_VM_UpdateIntermissionState(cl.intermission); SCR_CenterPrint(MSG_ReadString ()); break; @@ -3761,22 +4129,29 @@ void CL_ParseServerMessage(void) CL_ParsePointParticles1(); break; } +// R_TimeReport(svc_strings[cmd]); } } if (cls.signon == SIGNONS) CL_UpdateItemsAndWeapon(); +// R_TimeReport("UpdateItems"); EntityFrameQuake_ISeeDeadEntities(); +// R_TimeReport("ISeeDeadEntities"); CL_UpdateMoveVars(); +// R_TimeReport("UpdateMoveVars"); parsingerror = false; // LordHavoc: this was at the start of the function before cl_autodemo was // implemented if (cls.demorecording) + { CL_WriteDemoMessage (&net_message); +// R_TimeReport("WriteDemo"); + } } void CL_Parse_DumpPacket(void) @@ -3796,11 +4171,17 @@ void CL_Parse_ErrorCleanUp(void) void CL_Parse_Init(void) { + Cvar_RegisterVariable(&cl_worldmessage); + Cvar_RegisterVariable(&cl_worldname); + Cvar_RegisterVariable(&cl_worldnamenoextension); + Cvar_RegisterVariable(&cl_worldbasename); + // LordHavoc: added demo_nehahra cvar Cvar_RegisterVariable (&demo_nehahra); if (gamemode == GAME_NEHAHRA) Cvar_SetValue("demo_nehahra", 1); Cvar_RegisterVariable(&developer_networkentities); + Cvar_RegisterVariable(&cl_gameplayfix_soundsmovewithentities); Cvar_RegisterVariable(&cl_sound_wizardhit); Cvar_RegisterVariable(&cl_sound_hknighthit); @@ -3820,6 +4201,7 @@ void CL_Parse_Init(void) Cvar_RegisterVariable(&cl_nettimesyncboundmode); Cvar_RegisterVariable(&cl_nettimesyncboundtolerance); Cvar_RegisterVariable(&cl_iplog_name); + Cvar_RegisterVariable(&cl_readpicture_force); Cmd_AddCommand("nextul", QW_CL_NextUpload, "sends next fragment of current upload buffer (screenshot for example)"); Cmd_AddCommand("stopul", QW_CL_StopUpload, "aborts current upload (screenshot for example)");