X-Git-Url: https://git.xonotic.org/?a=blobdiff_plain;f=cl_parse.c;h=3c93a37bacfc360b9f7fb941d9cdf99acc755f29;hb=df96a719a2e77a0fc8175c003bf261b0016cefae;hp=4c1bc80dd3cb39201478b5e552cb03345471e3d7;hpb=5d105e92ce94949fcd5659172907ddd571496457;p=xonotic%2Fdarkplaces.git diff --git a/cl_parse.c b/cl_parse.c index 4c1bc80d..3c93a37b 100644 --- a/cl_parse.c +++ b/cl_parse.c @@ -23,6 +23,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "cdaudio.h" #include "cl_collision.h" #include "csprogs.h" +#include "libcurl.h" char *svc_strings[128] = { @@ -163,9 +164,17 @@ cvar_t cl_sound_tink1 = {0, "cl_sound_tink1", "weapons/tink1.wav", "sound to pla 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)"}; + +#define RIC_GUNSHOT 1 +#define RIC_GUNSHOTQUAD 2 +cvar_t cl_sound_ric_gunshot = {0, "cl_sound_ric_gunshot", "0", "specifies if and when the related cl_sound_ric and cl_sound_tink sounds apply to TE_GUNSHOT/TE_GUNSHOTQUAD, 0 = no sound, 1 = TE_GUNSHOT, 2 = TE_GUNSHOTQUAD, 3 = TE_GUNSHOT and TE_GUNSHOTQUAD"}; 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_joinbeforedownloadsfinish = {CVAR_SAVE, "cl_joinbeforedownloadsfinish", "1", "if non-zero the game will begin after the map is loaded before other downloads finish"}; +cvar_t cl_nettimesyncfactor = {CVAR_SAVE, "cl_nettimesyncfactor", "0", "rate at which client time adapts to match server time, 1 = instantly, 0.125 = slowly, 0 = not at all (bounding still applies)"}; +cvar_t cl_nettimesyncboundmode = {CVAR_SAVE, "cl_nettimesyncboundmode", "4", "method of restricting client time to valid values, 0 = no correction, 1 = tight bounding (jerky with packet loss), 2 = loose bounding (corrects it if out of bounds), 3 = leniant bounding (ignores temporary errors due to varying framerate), 4 = slow adjustment method from Quake3"}; +cvar_t cl_nettimesyncboundtolerance = {CVAR_SAVE, "cl_nettimesyncboundtolerance", "0.25", "how much error is tolerated by bounding check, as a fraction of frametime, 0.25 = up to 25% margin of error tolerated, 1 = use only new time, 0 = use only old time (same effect as setting cl_nettimesyncfactor to 1)"}; +cvar_t cl_iplog_name = {CVAR_SAVE, "cl_iplog_name", "darkplaces_iplog.txt", "name of iplog file containing player addresses for iplog_list command and automatic ip logging when parsing status command"}; static qboolean QW_CL_CheckOrDownloadFile(const char *filename); static void QW_CL_RequestNextDownload(void); @@ -306,7 +315,7 @@ void CL_KeepaliveMessage (qboolean readmessages) msg.data = buf; msg.maxsize = sizeof(buf); MSG_WriteChar(&msg, clc_nop); - NetConn_SendUnreliableMessage(cls.netcon, &msg, cls.protocol); + NetConn_SendUnreliableMessage(cls.netcon, &msg, cls.protocol, 10000); } } @@ -320,13 +329,13 @@ void CL_ParseEntityLump(char *entdata) data = entdata; if (!data) return; - if (!COM_ParseTokenConsole(&data)) + if (!COM_ParseToken_Simple(&data, false)) return; // error if (com_token[0] != '{') return; // error while (1) { - if (!COM_ParseTokenConsole(&data)) + if (!COM_ParseToken_Simple(&data, false)) return; // error if (com_token[0] == '}') break; // end of worldspawn @@ -336,7 +345,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_ParseTokenConsole(&data)) + if (!COM_ParseToken_Simple(&data, false)) return; // error strlcpy (value, com_token, sizeof (value)); if (!strcmp("sky", key)) @@ -401,6 +410,7 @@ static qboolean QW_CL_CheckOrDownloadFile(const char *filename) return false; } +extern void CL_Locs_Reload_f(void); static void QW_CL_ProcessUserInfo(int slot); static void QW_CL_RequestNextDownload(void) { @@ -469,7 +479,8 @@ static void QW_CL_RequestNextDownload(void) // touch all of the precached models that are still loaded so we can free // anything that isn't needed - Mod_ClearUsed(); + if (!sv.active) + Mod_ClearUsed(); for (i = 1;i < MAX_MODELS && cl.model_name[i][0];i++) Mod_FindName(cl.model_name[i]); // precache any models used by the client (this also marks them used) @@ -499,9 +510,14 @@ static void QW_CL_RequestNextDownload(void) cl.entities[0].render.model = cl.worldmodel = cl.model_precache[1]; CL_UpdateRenderEntity(&cl.entities[0].render); + CL_Locs_Reload_f(); R_Modules_NewMap(); - // TODO: add pmodel/emodel player.mdl/eyes.mdl CRCs to userinfo + cl.foundtalk2wav = FS_FileExists("sound/misc/talk2.wav"); + + // add pmodel/emodel CRCs to userinfo + CL_SetInfo("pmodel", va("%i", FS_CRCFile("progs/player.mdl", NULL)), true, true, true, true); + CL_SetInfo("emodel", va("%i", FS_CRCFile("progs/eyes.mdl", NULL)), true, true, true, true); // done checking sounds and models, send a prespawn command now MSG_WriteByte(&cls.netcon->message, qw_clc_stringcmd); @@ -895,7 +911,20 @@ void CL_BeginDownloads(qboolean aborteddownload) if (cls.protocol == PROTOCOL_QUAKEWORLD) return; - // TODO: this would be a good place to do curl downloads + // this would be a good place to do curl downloads + if(Curl_Have_forthismap()) + { + 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) { @@ -928,15 +957,17 @@ void CL_BeginDownloads(qboolean aborteddownload) // 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 3 like normal + // now issue the spawn to move on to signon 2 like normal if (cls.netcon) - Cmd_ForwardStringToServer("spawn"); + Cmd_ForwardStringToServer("prespawn"); } } } @@ -977,15 +1008,17 @@ void CL_BeginDownloads(qboolean aborteddownload) // 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 3 like normal + // now issue the spawn to move on to signon 2 like normal if (cls.netcon) - Cmd_ForwardStringToServer("spawn"); + Cmd_ForwardStringToServer("prespawn"); } } aborteddownload = false; @@ -1015,15 +1048,17 @@ void CL_BeginDownloads(qboolean aborteddownload) // 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 3 like normal + // now issue the spawn to move on to signon 2 like normal if (cls.netcon) - Cmd_ForwardStringToServer("spawn"); + Cmd_ForwardStringToServer("prespawn"); } } } @@ -1072,15 +1107,20 @@ void CL_BeginDownloads(qboolean aborteddownload) // check memory integrity Mem_CheckSentinelsGlobal(); - // now issue the spawn to move on to signon 3 like normal + // now issue the spawn to move on to signon 2 like normal if (cls.netcon) - Cmd_ForwardStringToServer("spawn"); + Cmd_ForwardStringToServer("prespawn"); } } void CL_BeginDownloads_f(void) { - CL_BeginDownloads(false); + // 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) @@ -1218,6 +1258,34 @@ void CL_DownloadFinished_f(void) 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 @@ -1234,8 +1302,17 @@ static void CL_SignonReply (void) case 1: if (cls.netcon) { - MSG_WriteByte (&cls.netcon->message, clc_stringcmd); - MSG_WriteString (&cls.netcon->message, "prespawn"); + // 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); @@ -1244,38 +1321,13 @@ static void CL_SignonReply (void) case 2: if (cls.netcon) { - 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)); - - 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)); - } - - MSG_WriteByte (&cls.netcon->message, clc_stringcmd); - MSG_WriteString (&cls.netcon->message, va("rate %i", cl_rate.integer)); + // 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"); - - // execute cl_begindownloads next frame after this message is sent - // (so that the server can see the player name while downloading) - Cbuf_AddText("\ncl_begindownloads\n"); + MSG_WriteByte (&cls.netcon->message, clc_stringcmd); + MSG_WriteString (&cls.netcon->message, "spawn"); } break; @@ -1367,16 +1419,24 @@ void CL_ParseServerInfo (void) 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(); + cl.movevars_gravity = MSG_ReadFloat(); + cl.movevars_stopspeed = MSG_ReadFloat(); + cl.movevars_maxspeed = MSG_ReadFloat(); + cl.movevars_spectatormaxspeed = MSG_ReadFloat(); + cl.movevars_accelerate = MSG_ReadFloat(); + cl.movevars_airaccelerate = MSG_ReadFloat(); + cl.movevars_wateraccelerate = MSG_ReadFloat(); + cl.movevars_friction = MSG_ReadFloat(); + cl.movevars_waterfriction = MSG_ReadFloat(); + cl.movevars_entgravity = MSG_ReadFloat(); + // other movevars not in the protocol... + cl.movevars_slowmo = 1; + cl.movevars_jumpvelocity = 270; + cl.movevars_edgefriction = 2; + cl.movevars_maxairspeed = 30; + cl.movevars_stepheight = 18; + cl.movevars_airaccel_qw = 1.0; + cl.movevars_airaccel_sideways_friction = 0.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); @@ -1390,6 +1450,7 @@ void CL_ParseServerInfo (void) MSG_WriteString(&cls.netcon->message, va("soundlist %i %i", cl.qw_servercount, 0)); } + cl.loadbegun = false; cl.loadfinished = false; cls.state = ca_connected; @@ -1451,7 +1512,8 @@ void CL_ParseServerInfo (void) // touch all of the precached models that are still loaded so we can free // anything that isn't needed - Mod_ClearUsed(); + if (!sv.active) + 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) @@ -1482,11 +1544,43 @@ void CL_ParseServerInfo (void) 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) @@ -1540,9 +1634,9 @@ void CL_MoveLerpEntityStates(entity_t *ent) ent->render.framelerp = 1; // reset various persistent stuff ent->persistent.muzzleflash = 0; - VectorCopy(ent->state_current.origin, ent->persistent.trail_origin); + ent->persistent.trail_allowed = false; } - else if (cls.timedemo || cl_nolerp.integer || DotProduct(odelta, odelta) > 1000*1000 || (cl.fixangle[0] && !cl.fixangle[1])) + else if (DotProduct(odelta, odelta) > 1000*1000 || (cl.fixangle[0] && !cl.fixangle[1])) { // don't interpolate the move // (the fixangle[] check detects teleports, but not constant fixangles @@ -1553,6 +1647,7 @@ void CL_MoveLerpEntityStates(entity_t *ent) 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; } else if (ent->state_current.flags & RENDER_STEP) { @@ -1753,6 +1848,14 @@ void CL_ParseStatic (int large) ent = &cl.static_entities[cl.num_static_entities++]; CL_ParseBaseline (ent, large); + if (ent->state_baseline.modelindex == 0) + { + Con_DPrintf("svc_parsestatic: static entity without model at %f %f %f\n", ent->state_baseline.origin[0], ent->state_baseline.origin[1], ent->state_baseline.origin[2]); + cl.num_static_entities--; + // This is definitely a cheesy way to conserve resources... + return; + } + // copy it to the current state ent->render.model = cl.model_precache[ent->state_baseline.modelindex]; ent->render.frame = ent->render.frame1 = ent->render.frame2 = ent->state_baseline.frame; @@ -1769,10 +1872,6 @@ void CL_ParseStatic (int large) 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); CL_UpdateRenderEntity(&ent->render); - - // This is definitely a cheesy way to conserve resources... - //if (ent->render.model == NULL) - // cl.num_static_entities--; } /* @@ -2002,6 +2101,21 @@ void CL_ParseTempEntity(void) VectorSet(pos2, pos[0] + radius, pos[1] + radius, pos[2] + radius); VectorSet(pos, pos[0] - radius, pos[1] - radius, pos[2] - radius); CL_ParticleEffect(EFFECT_TE_GUNSHOT, radius, pos, pos2, vec3_origin, vec3_origin, NULL, 0); + if(cl_sound_ric_gunshot.integer & RIC_GUNSHOT) + { + if (rand() % 5) + S_StartSound(-1, 0, cl.sfx_tink1, pos, 1, 1); + else + { + rnd = rand() & 3; + if (rnd == 1) + S_StartSound(-1, 0, cl.sfx_ric1, pos, 1, 1); + else if (rnd == 2) + S_StartSound(-1, 0, cl.sfx_ric2, pos, 1, 1); + else + S_StartSound(-1, 0, cl.sfx_ric3, pos, 1, 1); + } + } break; case QW_TE_BLOOD: @@ -2193,6 +2307,21 @@ void CL_ParseTempEntity(void) MSG_ReadVector(pos, cls.protocol); CL_FindNonSolidLocation(pos, pos, 4); CL_ParticleEffect(EFFECT_TE_GUNSHOT, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0); + if(cl_sound_ric_gunshot.integer & RIC_GUNSHOT) + { + if (rand() % 5) + S_StartSound(-1, 0, cl.sfx_tink1, pos, 1, 1); + else + { + rnd = rand() & 3; + if (rnd == 1) + S_StartSound(-1, 0, cl.sfx_ric1, pos, 1, 1); + else if (rnd == 2) + S_StartSound(-1, 0, cl.sfx_ric2, pos, 1, 1); + else + S_StartSound(-1, 0, cl.sfx_ric3, pos, 1, 1); + } + } break; case TE_GUNSHOTQUAD: @@ -2200,6 +2329,21 @@ void CL_ParseTempEntity(void) MSG_ReadVector(pos, cls.protocol); CL_FindNonSolidLocation(pos, pos, 4); CL_ParticleEffect(EFFECT_TE_GUNSHOTQUAD, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0); + if(cl_sound_ric_gunshot.integer & RIC_GUNSHOTQUAD) + { + if (rand() % 5) + S_StartSound(-1, 0, cl.sfx_tink1, pos, 1, 1); + else + { + rnd = rand() & 3; + if (rnd == 1) + S_StartSound(-1, 0, cl.sfx_ric1, pos, 1, 1); + else if (rnd == 2) + S_StartSound(-1, 0, cl.sfx_ric2, pos, 1, 1); + else + S_StartSound(-1, 0, cl.sfx_ric3, pos, 1, 1); + } + } break; case TE_EXPLOSION: @@ -2227,7 +2371,7 @@ void CL_ParseTempEntity(void) 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; @@ -2240,7 +2384,7 @@ void CL_ParseTempEntity(void) 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; @@ -2267,7 +2411,7 @@ void CL_ParseTempEntity(void) 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: @@ -2326,7 +2470,7 @@ void CL_ParseTempEntity(void) 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; @@ -2366,10 +2510,188 @@ void CL_ParseTempEntity(void) } } +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); +} + +typedef struct cl_iplog_item_s +{ + char *address; + char *name; +} +cl_iplog_item_t; + +static qboolean cl_iplog_loaded = false; +static int cl_iplog_numitems = 0; +static int cl_iplog_maxitems = 0; +static cl_iplog_item_t *cl_iplog_items; + +static void CL_IPLog_Load(void); +static void CL_IPLog_Add(const char *address, const char *name, qboolean checkexisting, qboolean addtofile) +{ + int i; + if (!address || !address[0] || !name || !name[0]) + 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); + // see if it already exists + if (checkexisting) + { + for (i = 0;i < cl_iplog_numitems;i++) + { + 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); + return; + } + } + } + // it does not already exist in the iplog, so add it + if (cl_iplog_maxitems <= cl_iplog_numitems || !cl_iplog_items) + { + 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)); + if (olditems) + { + if (cl_iplog_numitems) + memcpy(cl_iplog_items, olditems, cl_iplog_numitems * sizeof(cl_iplog_item_t)); + 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); + 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? + strlcpy(cl_iplog_items[cl_iplog_numitems].name, name, strlen(name) + 1); + cl_iplog_numitems++; + if (addtofile) + { + // add it to the iplog.txt file + // 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); + } +} + +static void CL_IPLog_Load(void) +{ + int i, len, linenumber; + char *text, *textend; + unsigned char *filedata; + fs_offset_t filesize; + char line[MAX_INPUTLINE]; + char address[MAX_INPUTLINE]; + cl_iplog_loaded = true; + // TODO: this ought to open the one in the userpath version of the base + // gamedir, not the current gamedir + filedata = FS_LoadFile(cl_iplog_name.string, tempmempool, true, &filesize); + if (!filedata) + return; + text = (char *)filedata; + textend = text + filesize; + for (linenumber = 1;text < textend;linenumber++) + { + for (len = 0;text < textend && *text != '\r' && *text != '\n';text++) + if (len < (int)sizeof(line) - 1) + line[len++] = *text; + line[len] = 0; + if (text < textend && *text == '\r' && text[1] == '\n') + text++; + if (text < textend && *text == '\n') + text++; + if (line[0] == '/' && line[1] == '/') + continue; // skip comments if anyone happens to add them + for (i = 0;i < len && line[i] > ' ';i++) + address[i] = line[i]; + address[i] = 0; + // skip exactly one space character + i++; + // address contains the address with termination, + // line + i contains the name with termination + if (address[0] && line[i]) + CL_IPLog_Add(address, line + i, false, false); + else + Con_Printf("%s:%i: could not parse address and name:\n%s\n", cl_iplog_name.string, linenumber, line); + } +} + +static void CL_IPLog_List_f(void) +{ + int i, j; + const char *addressprefix; + if (Cmd_Argc() > 2) + { + Con_Printf("usage: %s 123.456.789.\n", Cmd_Argv(0)); + return; + } + addressprefix = ""; + if (Cmd_Argc() >= 2) + addressprefix = Cmd_Argv(1); + if (!cl_iplog_loaded) + CL_IPLog_Load(); + if (addressprefix && addressprefix[0]) + Con_Printf("Listing iplog addresses beginning with %s\n", addressprefix); + else + Con_Printf("Listing all iplog entries\n"); + Con_Printf("address name\n"); + for (i = 0;i < cl_iplog_numitems;i++) + { + if (addressprefix && addressprefix[0]) + { + for (j = 0;addressprefix[j];j++) + if (addressprefix[j] != cl_iplog_items[i].address[j]) + break; + // if this address does not begin with the addressprefix string + // simply omit it from the output + if (addressprefix[j]) + continue; + } + // if name is less than 15 characters, left justify it and pad + // if name is more than 15 characters, print all of it, not worrying + // about the fact it will misalign the columns + if (strlen(cl_iplog_items[i].address) < 15) + Con_Printf("%-15s %s\n", cl_iplog_items[i].address, cl_iplog_items[i].name); + else + Con_Printf("%5s %s\n", cl_iplog_items[i].address, cl_iplog_items[i].name); + } +} + // look for anything interesting like player IP addresses or ping reports qboolean CL_ExaminePrintString(const char *text) { + int len; const char *t; + char temp[MAX_INPUTLINE]; if (!strcmp(text, "Client ping times:\n")) { cl.parsingtextmode = CL_PARSETEXTMODE_PING; @@ -2462,7 +2784,7 @@ qboolean CL_ExaminePrintString(const char *text) if (text[0] == '#' && text[1] >= '0' && text[1] <= '9') { t = text + 1; - cl.parsingtextplayerindex = atoi(t); + cl.parsingtextplayerindex = atoi(t) - 1; while (*t >= '0' && *t <= '9') t++; if (*t == ' ') @@ -2470,6 +2792,7 @@ qboolean CL_ExaminePrintString(const char *text) cl.parsingtextmode = CL_PARSETEXTMODE_STATUS_PLAYERIP; return true; } + // the player name follows here, along with frags and time } } if (cl.parsingtextmode == CL_PARSETEXTMODE_STATUS_PLAYERIP) @@ -2481,12 +2804,21 @@ qboolean CL_ExaminePrintString(const char *text) t = text; while (*t == ' ') t++; + for (len = 0;*t && *t != '\n';t++) + if (len < (int)sizeof(temp) - 1) + temp[len++] = *t; + temp[len] = 0; // botclient is perfectly valid, but we don't care about bots - if (strcmp(t, "botclient\n")) + // also don't try to look up the name of an invalid player index + if (strcmp(temp, "botclient") + && cl.parsingtextplayerindex >= 0 + && cl.parsingtextplayerindex < cl.maxclients + && cl.scores[cl.parsingtextplayerindex].name[0]) { - lhnetaddress_t address; - LHNETADDRESS_FromString(&address, t, 0); - // TODO: log the IP address and player name? + // log the player name and IP address string + // (this operates entirely on strings to avoid issues with the + // nature of a network address) + CL_IPLog_Add(temp, cl.scores[cl.parsingtextplayerindex].name, true, true); } cl.parsingtextmode = CL_PARSETEXTMODE_STATUS_PLAYERID; return true; @@ -2495,6 +2827,63 @@ qboolean CL_ExaminePrintString(const char *text) return true; } +extern cvar_t slowmo; +static void CL_NetworkTimeReceived(double newtime) +{ + double timehigh; + cl.mtime[1] = max(cl.mtime[0], newtime - 0.1); + cl.mtime[0] = newtime; + if (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) + { + if (developer.integer >= 100 && 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]); + 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]); + } + 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; + if (cl_nettimesyncboundmode.integer == 1) + cl.time = bound(cl.mtime[1], cl.time, cl.mtime[0]); + else if (cl_nettimesyncboundmode.integer == 2) + { + if (cl.time < cl.mtime[1] || cl.time > timehigh) + cl.time = cl.mtime[1]; + } + else if (cl_nettimesyncboundmode.integer == 3) + { + if ((cl.time < cl.mtime[1] && cl.oldtime < cl.mtime[1]) || (cl.time > timehigh && cl.oldtime > timehigh)) + cl.time = cl.mtime[1]; + } + else if (cl_nettimesyncboundmode.integer == 4) + { + if (fabs(cl.time - cl.mtime[1]) > 0.5) + cl.time = cl.mtime[1]; // reset + else if (fabs(cl.time - cl.mtime[1]) > 0.1) + cl.time += 0.5 * (cl.mtime[1] - cl.time); // fast + else if (cl.time > cl.mtime[1]) + cl.time -= 0.002 * cl.movevars_slowmo; // fall into the past by 2ms + else + cl.time += 0.001 * cl.movevars_slowmo; // creep forward 1ms + } + } + // 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 @@ -2511,6 +2900,7 @@ CL_ParseServerMessage ===================== */ int parsingerror = false; +extern void CL_UpdateMoveVars(void); void CL_ParseServerMessage(void) { int cmd; @@ -2520,8 +2910,11 @@ void CL_ParseServerMessage(void) char *cmdlogname[32], *temp; int cmdindex, cmdcount = 0; - if (cls.demorecording) - CL_WriteDemoMessage (); + // 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 + // code in CL_ParseServerinfo + //if (cls.demorecording) + // CL_WriteDemoMessage (&net_message); cl.last_received_message = realtime; @@ -2545,17 +2938,7 @@ void CL_ParseServerMessage(void) if (cls.protocol == PROTOCOL_QUAKEWORLD) { - cl.mtime[1] = cl.mtime[0]; - cl.mtime[0] = realtime; // qw has no clock - cl.timenonlerp = bound(cl.mtime[1], cl.timenonlerp, cl.mtime[0]); - 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]); + CL_NetworkTimeReceived(realtime); // qw has no clock // slightly kill qw player entities each frame for (i = 1;i < cl.maxclients;i++) @@ -2567,6 +2950,8 @@ void CL_ParseServerMessage(void) // fade weapon view kick cl.qw_weaponkick = min(cl.qw_weaponkick + 10 * bound(0, cl.time - cl.oldtime, 0.1), 0); + cls.servermovesequence = cls.netcon->qw.incoming_sequence; + while (1) { if (msg_badread) @@ -2865,11 +3250,11 @@ void CL_ParseServerMessage(void) break; case qw_svc_maxspeed: - cl.qw_movevars_maxspeed = MSG_ReadFloat(); + cl.movevars_maxspeed = MSG_ReadFloat(); break; case qw_svc_entgravity: - cl.qw_movevars_entgravity = MSG_ReadFloat(); + cl.movevars_entgravity = MSG_ReadFloat(); break; case qw_svc_setpause: @@ -2883,10 +3268,24 @@ void CL_ParseServerMessage(void) } } - // fully kill the still slightly dead qw player entities each frame - for (i = 1;i < cl.maxclients;i++) - if (!cl.entities_active[i]) - cl.entities[i].state_current.active = false; + // 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) + { + // kill all non-updated entities 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 { @@ -2966,17 +3365,7 @@ void CL_ParseServerMessage(void) break; case svc_time: - cl.mtime[1] = cl.mtime[0]; - cl.mtime[0] = MSG_ReadFloat (); - cl.timenonlerp = bound(cl.mtime[1], cl.timenonlerp, cl.mtime[0]); - 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: @@ -3081,7 +3470,7 @@ void CL_ParseServerMessage(void) { model_t *model = Mod_ForName(s, false, false, i == 1); if (!model) - Con_Printf("svc_precache: Mod_ForName(\"%s\") failed\n", s); + Con_DPrintf("svc_precache: Mod_ForName(\"%s\") failed\n", s); cl.model_precache[i] = model; } else @@ -3094,7 +3483,7 @@ void CL_ParseServerMessage(void) { sfx_t *sfx = S_PrecacheSound (s, true, false); if (!sfx && snd_initialized.integer) - Con_Printf("svc_precache: S_PrecacheSound(\"%s\") failed\n", s); + Con_DPrintf("svc_precache: S_PrecacheSound(\"%s\") failed\n", s); cl.sound_precache[i] = sfx; } else @@ -3300,6 +3689,12 @@ void CL_ParseServerMessage(void) case svc_downloaddata: CL_ParseDownload(); break; + case svc_trailparticles: + CL_ParseTrailParticles(); + break; + case svc_pointparticles: + CL_ParsePointParticles(); + break; } } } @@ -3309,7 +3704,14 @@ void CL_ParseServerMessage(void) EntityFrameQuake_ISeeDeadEntities(); + CL_UpdateMoveVars(); + parsingerror = false; + + // LordHavoc: this was at the start of the function before cl_autodemo was + // implemented + if (cls.demorecording) + CL_WriteDemoMessage (&net_message); } void CL_Parse_DumpPacket(void) @@ -3341,6 +3743,7 @@ void CL_Parse_Init(void) Cvar_RegisterVariable(&cl_sound_ric1); Cvar_RegisterVariable(&cl_sound_ric2); Cvar_RegisterVariable(&cl_sound_ric3); + Cvar_RegisterVariable(&cl_sound_ric_gunshot); Cvar_RegisterVariable(&cl_sound_r_exp3); Cvar_RegisterVariable(&cl_joinbeforedownloadsfinish); @@ -3348,6 +3751,11 @@ void CL_Parse_Init(void) // server extension cvars set by commands issued from the server during connect Cvar_RegisterVariable(&cl_serverextension_download); + Cvar_RegisterVariable(&cl_nettimesyncfactor); + Cvar_RegisterVariable(&cl_nettimesyncboundmode); + Cvar_RegisterVariable(&cl_nettimesyncboundtolerance); + Cvar_RegisterVariable(&cl_iplog_name); + 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"); @@ -3356,6 +3764,7 @@ void CL_Parse_Init(void) Cmd_AddCommand("cl_downloadbegin", CL_DownloadBegin_f, "(networking) informs client of download file information, client replies with sv_startsoundload to begin the transfer"); Cmd_AddCommand("stopdownload", CL_StopDownload_f, "terminates a download"); Cmd_AddCommand("cl_downloadfinished", CL_DownloadFinished_f, "signals that a download has finished and provides the client with file size and crc to check its integrity"); + Cmd_AddCommand("iplog_list", CL_IPLog_List_f, "lists names of players whose IP address begins with the supplied text (example: iplog_list 123.456.789)"); } void CL_Parse_Shutdown(void)