cvar_t cl_sound_r_exp3 = {0, "cl_sound_r_exp3", "weapons/r_exp3.wav", "sound to play during TE_EXPLOSION and related effects (empty cvar disables sound)"};
cvar_t cl_serverextension_download = {0, "cl_serverextension_download", "0", "indicates whether the server supports the download command"};
cvar_t cl_joinbeforedownloadsfinish = {0, "cl_joinbeforedownloadsfinish", "1", "if non-zero the game will begin after the map is loaded before other downloads finish"};
+cvar_t cl_nettimesyncmode = {0, "cl_nettimesyncmode", "2", "selects method of time synchronization in client with regard to server packets, values are: 0 = no sync, 1 = exact sync (reset timing each packet), 2 = loose sync (reset timing only if it is out of bounds), 3 = tight sync and bounding"};
static qboolean QW_CL_CheckOrDownloadFile(const char *filename);
static void QW_CL_RequestNextDownload(void);
// TODO: this would be a good place to do curl downloads
+ 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
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] != '*')
+ 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
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)
+ if (cl_serverextension_download.integer && cls.netcon && !sv.active)
{
Cmd_ForwardStringToServer(va("download %s", soundname));
// we'll try loading again when the download finishes
CL_BeginDownloads(false);
}
-extern void FS_Rescan_f(void);
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)
- if (!FS_FileExists(cls.qw_downloadname))
+ existingcrc = FS_CRCFile(cls.qw_downloadname, &existingsize);
+ if (existingsize)
{
- const char *extension;
-
+ 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_f();
+ FS_Rescan();
}
}
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
- FS_ChangeGameDir(str);
+ 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;
cl.loadsound_current = 1;
cl.downloadsound_current = 1;
cl.loadsound_total = numsounds;
+ cl.downloadcsqc = true;
cl.loadfinished = false;
}
if (!s->active)
return;
- if (s->modelindex >= MAX_MODELS && (65536-s->modelindex) >= MAX_MODELS)
+ if (s->modelindex >= MAX_MODELS)
Host_Error("CL_ValidateState: modelindex (%i) >= MAX_MODELS (%i)\n", s->modelindex, MAX_MODELS);
// colormap is client index + 1
VectorCopy(ent->state_current.origin, ent->persistent.neworigin);
VectorCopy(ent->state_current.angles, ent->persistent.newangles);
}
+ // trigger muzzleflash effect if necessary
+ if (ent->state_current.effects & EF_MUZZLEFLASH)
+ ent->persistent.muzzleflash = 1;
}
/*
ent->state_baseline.origin[i] = MSG_ReadCoord(cls.protocol);
ent->state_baseline.angles[i] = MSG_ReadAngle(cls.protocol);
}
- CL_ValidateState(&ent->state_baseline);
ent->state_previous = ent->state_current = ent->state_baseline;
}
// viewzoom interpolation
cl.mviewzoom[0] = (float) max(cl.stats[STAT_VIEWZOOM], 2) * (1.0f / 255.0f);
-
- // force a recalculation of the player prediction
- cl.movement_replay = true;
}
/*
color[2] = MSG_ReadCoord(cls.protocol) * (2.0f / 1.0f);
CL_ParticleExplosion(pos);
Matrix4x4_CreateTranslate(&tempmatrix, pos[0], pos[1], pos[2]);
- CL_AllocDlight(NULL, &tempmatrix, 350, color[0], color[1], color[2], 700, 0.5, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
+ CL_AllocLightFlash(NULL, &tempmatrix, 350, color[0], color[1], color[2], 700, 0.5, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
S_StartSound(-1, 0, cl.sfx_r_exp3, pos, 1, 1);
break;
color[1] = MSG_ReadByte() * (2.0f / 255.0f);
color[2] = MSG_ReadByte() * (2.0f / 255.0f);
Matrix4x4_CreateTranslate(&tempmatrix, pos[0], pos[1], pos[2]);
- CL_AllocDlight(NULL, &tempmatrix, 350, color[0], color[1], color[2], 700, 0.5, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
+ CL_AllocLightFlash(NULL, &tempmatrix, 350, color[0], color[1], color[2], 700, 0.5, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
S_StartSound(-1, 0, cl.sfx_r_exp3, pos, 1, 1);
break;
color[1] = MSG_ReadByte() * (2.0f / 255.0f);
color[2] = MSG_ReadByte() * (2.0f / 255.0f);
Matrix4x4_CreateTranslate(&tempmatrix, pos[0], pos[1], pos[2]);
- CL_AllocDlight(NULL, &tempmatrix, radius, color[0], color[1], color[2], radius / velspeed, velspeed, 0, -1, true, 1, 0.25, 1, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
+ CL_AllocLightFlash(NULL, &tempmatrix, radius, color[0], color[1], color[2], radius / velspeed, velspeed, 0, -1, true, 1, 0.25, 1, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
break;
case TE_FLAMEJET:
color[1] = tempcolor[1] * (2.0f / 255.0f);
color[2] = tempcolor[2] * (2.0f / 255.0f);
Matrix4x4_CreateTranslate(&tempmatrix, pos[0], pos[1], pos[2]);
- CL_AllocDlight(NULL, &tempmatrix, 350, color[0], color[1], color[2], 700, 0.5, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
+ CL_AllocLightFlash(NULL, &tempmatrix, 350, color[0], color[1], color[2], 700, 0.5, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
S_StartSound(-1, 0, cl.sfx_r_exp3, pos, 1, 1);
break;
}
}
+void CL_ParseTrailParticles(void)
+{
+ int entityindex;
+ int effectindex;
+ vec3_t start, end;
+ entityindex = (unsigned short)MSG_ReadShort();
+ if (entityindex >= MAX_EDICTS)
+ entityindex = 0;
+ if (entityindex >= cl.max_entities)
+ CL_ExpandEntities(entityindex);
+ effectindex = (unsigned short)MSG_ReadShort();
+ MSG_ReadVector(start, cls.protocol);
+ MSG_ReadVector(end, cls.protocol);
+ CL_ParticleEffect(effectindex, VectorDistance(start, end), start, end, vec3_origin, vec3_origin, entityindex > 0 ? cl.entities + entityindex : NULL, 0);
+}
+
+void CL_ParsePointParticles(void)
+{
+ int effectindex, count;
+ vec3_t origin, velocity;
+ effectindex = (unsigned short)MSG_ReadShort();
+ MSG_ReadVector(origin, cls.protocol);
+ MSG_ReadVector(velocity, cls.protocol);
+ count = (unsigned short)MSG_ReadShort();
+ CL_ParticleEffect(effectindex, count, origin, origin, velocity, velocity, NULL, 0);
+}
+
// look for anything interesting like player IP addresses or ping reports
qboolean CL_ExaminePrintString(const char *text)
{
{
for (charindex = 0;cl.scores[cl.parsingtextplayerindex].name[charindex] == t[charindex];charindex++)
;
- if (cl.scores[cl.parsingtextplayerindex].name[charindex] == 0 && t[charindex] == '\n')
+ // note: the matching algorithm stops at the end of the player name because some servers append text such as " READY" after the player name in the scoreboard but not in the ping report
+ //if (cl.scores[cl.parsingtextplayerindex].name[charindex] == 0 && t[charindex] == '\n')
+ if (t[charindex] == '\n')
{
cl.scores[cl.parsingtextplayerindex].qw_ping = bound(0, ping, 9999);
for (cl.parsingtextplayerindex++;cl.parsingtextplayerindex < cl.maxclients && !cl.scores[cl.parsingtextplayerindex].name[0];cl.parsingtextplayerindex++)
return true;
}
+static void CL_NetworkTimeReceived(double newtime)
+{
+ if (cl_nolerp.integer || cls.timedemo || (cl.islocalgame && !sv_fixedframeratesingleplayer.integer))
+ cl.mtime[1] = cl.mtime[0] = newtime;
+ else
+ {
+ cl.mtime[1] = max(cl.mtime[0], newtime - 0.1);
+ cl.mtime[0] = newtime;
+ }
+ if (cl_nettimesyncmode.integer == 3)
+ cl.time = cl.mtime[1];
+ if (cl_nettimesyncmode.integer == 2)
+ {
+ if (cl.time < cl.mtime[1] || cl.time > cl.mtime[0])
+ cl.time = cl.mtime[1];
+ }
+ else if (cl_nettimesyncmode.integer == 1)
+ cl.time = cl.mtime[1];
+ // this packet probably contains a player entity update, so we will need
+ // to update the prediction
+ cl.movement_needupdate = true;
+ // this may get updated later in parsing by svc_clientdata
+ cl.onground = false;
+ // if true the cl.viewangles are interpolated from cl.mviewangles[]
+ // during this frame
+ // (makes spectating players much smoother and prevents mouse movement from turning)
+ cl.fixangle[1] = cl.fixangle[0];
+ cl.fixangle[0] = false;
+ if (!cls.demoplayback)
+ VectorCopy(cl.mviewangles[0], cl.mviewangles[1]);
+}
+
#define SHOWNET(x) if(cl_shownet.integer==2)Con_Printf("%3i:%s\n", msg_readcount-1, x);
//[515]: csqc
if (cls.protocol == PROTOCOL_QUAKEWORLD)
{
- cl.mtime[1] = cl.mtime[0];
- cl.mtime[0] = realtime; // qw has no clock
- cl.movement_needupdate = true;
- cl.onground = false; // since there's no clientdata parsing, clear the onground flag here
- // if true the cl.viewangles are interpolated from cl.mviewangles[]
- // during this frame
- // (makes spectating players much smoother and prevents mouse movement from turning)
- cl.fixangle[1] = cl.fixangle[0];
- cl.fixangle[0] = false;
- if (!cls.demoplayback)
- VectorCopy(cl.mviewangles[0], cl.mviewangles[1]);
-
- // force a recalculation of the player prediction
- cl.movement_replay = true;
+ CL_NetworkTimeReceived(realtime); // qw has no clock
// slightly kill qw player entities each frame
for (i = 1;i < cl.maxclients;i++)
break;
case svc_time:
- cl.mtime[1] = cl.mtime[0];
- cl.mtime[0] = MSG_ReadFloat ();
- cl.movement_needupdate = true;
- // if true the cl.viewangles are interpolated from cl.mviewangles[]
- // during this frame
- // (makes spectating players much smoother and prevents mouse movement from turning)
- cl.fixangle[1] = cl.fixangle[0];
- cl.fixangle[0] = false;
- if (!cls.demoplayback)
- VectorCopy(cl.mviewangles[0], cl.mviewangles[1]);
+ CL_NetworkTimeReceived(MSG_ReadFloat());
break;
case svc_clientdata:
case svc_downloaddata:
CL_ParseDownload();
break;
+ case svc_trailparticles:
+ CL_ParseTrailParticles();
+ break;
+ case svc_pointparticles:
+ CL_ParsePointParticles();
+ break;
}
}
}
// server extension cvars set by commands issued from the server during connect
Cvar_RegisterVariable(&cl_serverextension_download);
+ Cvar_RegisterVariable(&cl_nettimesyncmode);
+
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)");
Cmd_AddCommand("skins", QW_CL_Skins_f, "downloads missing qw skins from server");