+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);
+}
+
+void CL_ParsePointParticles1(void)
+{
+ int effectindex;
+ vec3_t origin;
+ effectindex = (unsigned short)MSG_ReadShort();
+ MSG_ReadVector(origin, cls.protocol);
+ CL_ParticleEffect(effectindex, 1, origin, origin, vec3_origin, vec3_origin, 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;
+ // hide ping reports in demos
+ if (cls.demoplayback)
+ cl.parsingtextexpectingpingforscores = 1;
+ for(cl.parsingtextplayerindex = 0; cl.parsingtextplayerindex < cl.maxclients && !cl.scores[cl.parsingtextplayerindex].name[0]; cl.parsingtextplayerindex++)
+ ;
+ if (cl.parsingtextplayerindex >= cl.maxclients) // should never happen, since the client itself should be in cl.scores
+ {
+ Con_Printf("ping reply but empty scoreboard?!?\n");
+ cl.parsingtextmode = CL_PARSETEXTMODE_NONE;
+ cl.parsingtextexpectingpingforscores = 0;
+ }
+ cl.parsingtextexpectingpingforscores = cl.parsingtextexpectingpingforscores ? 2 : 0;
+ return !cl.parsingtextexpectingpingforscores;
+ }
+ if (!strncmp(text, "host: ", 9))
+ {
+ // cl.parsingtextexpectingpingforscores = false; // really?
+ cl.parsingtextmode = CL_PARSETEXTMODE_STATUS;
+ cl.parsingtextplayerindex = 0;
+ return true;
+ }
+ if (cl.parsingtextmode == CL_PARSETEXTMODE_PING)
+ {
+ // if anything goes wrong, we'll assume this is not a ping report
+ qboolean expected = cl.parsingtextexpectingpingforscores;
+ cl.parsingtextexpectingpingforscores = 0;
+ cl.parsingtextmode = CL_PARSETEXTMODE_NONE;
+ t = text;
+ while (*t == ' ')
+ t++;
+ if ((*t >= '0' && *t <= '9') || *t == '-')
+ {
+ int ping = atoi(t);
+ while ((*t >= '0' && *t <= '9') || *t == '-')
+ t++;
+ if (*t == ' ')
+ {
+ int charindex = 0;
+ t++;
+ if(cl.parsingtextplayerindex < cl.maxclients)
+ {
+ for (charindex = 0;cl.scores[cl.parsingtextplayerindex].name[charindex] == t[charindex];charindex++)
+ ;
+ // 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++)
+ ;
+ //if (cl.parsingtextplayerindex < cl.maxclients) // we could still get unconnecteds!
+ {
+ // we parsed a valid ping entry, so expect another to follow
+ cl.parsingtextmode = CL_PARSETEXTMODE_PING;
+ cl.parsingtextexpectingpingforscores = expected;
+ }
+ return !expected;
+ }
+ }
+ if (!strncmp(t, "unconnected\n", 12))
+ {
+ // just ignore
+ cl.parsingtextmode = CL_PARSETEXTMODE_PING;
+ cl.parsingtextexpectingpingforscores = expected;
+ return !expected;
+ }
+ else
+ Con_DPrintf("player names '%s' and '%s' didn't match\n", cl.scores[cl.parsingtextplayerindex].name, t);
+ }
+ }
+ }
+ if (cl.parsingtextmode == CL_PARSETEXTMODE_STATUS)
+ {
+ if (!strncmp(text, "players: ", 9))
+ {
+ cl.parsingtextmode = CL_PARSETEXTMODE_STATUS_PLAYERID;
+ cl.parsingtextplayerindex = 0;
+ return true;
+ }
+ else if (!strstr(text, ": "))
+ {
+ cl.parsingtextmode = CL_PARSETEXTMODE_NONE; // status report ended
+ return true;
+ }
+ }
+ if (cl.parsingtextmode == CL_PARSETEXTMODE_STATUS_PLAYERID)
+ {
+ // if anything goes wrong, we'll assume this is not a status report
+ cl.parsingtextmode = CL_PARSETEXTMODE_NONE;
+ if (text[0] == '#' && text[1] >= '0' && text[1] <= '9')
+ {
+ t = text + 1;
+ cl.parsingtextplayerindex = atoi(t) - 1;
+ while (*t >= '0' && *t <= '9')
+ t++;
+ if (*t == ' ')
+ {
+ 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)
+ {
+ // if anything goes wrong, we'll assume this is not a status report
+ cl.parsingtextmode = CL_PARSETEXTMODE_NONE;
+ if (text[0] == ' ')
+ {
+ 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
+ // 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])
+ {
+ // 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;
+ }
+ }
+ return true;
+}
+
+extern cvar_t slowmo;
+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)
+ cl.time = cl.mtime[1] = newtime;
+ 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 (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_timescale; // fall into the past by 2ms
+ else
+ cl.time += 0.001 * cl.movevars_timescale; // creep forward 1ms
+ }
+ else if (cl_nettimesyncboundmode.integer == 5)
+ {
+ 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
+ cl.time = bound(cl.time - 0.002 * cl.movevars_timescale, cl.mtime[1], cl.time + 0.001 * cl.movevars_timescale);
+ }
+ else if (cl_nettimesyncboundmode.integer == 6)
+ {
+ cl.time = bound(cl.mtime[1], cl.time, cl.mtime[0]);
+ cl.time = bound(cl.time - 0.002 * cl.movevars_timescale, cl.mtime[1], cl.time + 0.001 * cl.movevars_timescale);
+ }
+ }
+ // 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]);
+}
+