static void CL_DemoSeekMs(double ms, int exactServerTime) // server time in milliseconds { double wantedTime; if (!clc.demoplaying) { Com_FuncDPrinf("not playing demo can't seek\n"); return; } wantedTime = ms; if (exactServerTime > 0) { wantedTime = exactServerTime; } DEMODEBUG("seek want %f\n", wantedTime); if (wantedTime > (double)cl.serverTime + di.Overf) { CL_DemoFastForward(wantedTime); } else { CL_RewindDemo(wantedTime); } }
static void CL_RewindDemo(double wantedTime) { int i; rewindBackups_t *rb; if (!IS_LEGACY_MOD) { Com_FuncPrinf("Rewind is only supported on legacy mod, sorry\n"); return; } if (wantedTime < (double)di.firstServerTime) { wantedTime = di.firstServerTime; } if (!rewindBackups[0].valid || di.snapCount == 0) { CL_DemoFastForward(wantedTime); return; } rb = NULL; for (i = di.snapCount - 1; i >= 0; i--) { rb = &rewindBackups[i]; // go back a second before wanted time in order to have snapshot backups available for screen matching if ((double)rb->cl.snap.serverTime < wantedTime - 1000.0) { break; } } if (rb == NULL || i < 0) { if (rb == NULL) { Com_FuncDPrinf("FIXME rewind couldn't find valid snap rb:%p i:%d rb serverTime %d wanted %f\n", rb, i, rewindBackups[0].cl.snap.serverTime, wantedTime); } rb = &rewindBackups[0]; i = 0; } DEMODEBUG("seeking to index %d %d cl.serverTime:%d cl.snap.serverTime:%d, new clc.lastExecutedServercommand %d clc.serverCommandSequence %d\n", i, rb->seekPoint, cl.serverTime, cl.snap.serverTime, rb->clc.lastExecutedServerCommand, rb->clc.serverCommandSequence); FS_Seek(clc.demofile, rb->seekPoint, FS_SEEK_SET); // TODO: take a look at these hacks di.numSnaps = rb->numSnaps; di.snapCount = i + 1; memcpy(&cl, &rb->cl, sizeof(clientActive_t)); memcpy(&clc, &rb->clc, sizeof(clientConnection_t)); memcpy(&cls, &rb->cls, sizeof(clientStatic_t)); di.Overf = 0; // TODO: this is a hack to set the state to something valid cls.state = CA_ACTIVE; CL_DemoFastForward(wantedTime); }
void CL_SeekNext_f(void) { snapshot_t snapshot; qboolean r; int i; if (cl.snap.serverTime == di.lastServerTime) { Com_FuncDPrinf("at last snap\n"); return; } // takes into account offline demos with snaps with same server time if (cl.serverTime < cl.snap.serverTime) { CL_DemoSeekMs(0, cl.snap.serverTime); return; } i = 1; while (1) { r = CL_PeekSnapshot(cl.snap.messageNum + i, &snapshot); if (!r) { Com_FuncDPrinf("couldn't get next snapshot\n"); return; } if (snapshot.serverTime > cl.serverTime) { break; } i++; } CL_DemoSeekMs(0, snapshot.serverTime); }
/* ================== CL_NextDemo Called when a demo or cinematic finishes If the "nextdemo" cvar is set, that command will be issued ================== */ void CL_NextDemo(void) { char v[MAX_STRING_CHARS]; Q_strncpyz(v, Cvar_VariableString("nextdemo"), sizeof(v)); v[MAX_STRING_CHARS - 1] = 0; Com_FuncDPrinf("CL_NextDemo: %s\n", v); if (!v[0]) { return; } Cvar_Set("nextdemo", ""); Cbuf_AddText(v); Cbuf_AddText("\n"); Cbuf_Execute(); }
void CL_SeekPrev_f(void) { clSnapshot_t *clSnap; int i = 0; if (cl.snap.serverTime == di.firstServerTime) { Com_FuncDPrinf("at first snap\n"); return; } // takes into account offline demos with snapshots having same server time while (qtrue) { clSnap = &cl.snapshots[(cl.snap.messageNum - i) & PACKET_MASK]; if (clSnap->serverTime < cl.serverTime) { break; } i++; } CL_DemoSeekMs(0, clSnap->serverTime); }
static void CL_DemoFastForward(double wantedTime) { int loopCount; if (cls.state < CA_CONNECTED) { return; } if (wantedTime >= di.lastServerTime) { return; } DEMODEBUG("fast_forward %f\n", wantedTime); if (wantedTime >= (double)cl.serverTime && wantedTime < (double)cl.snap.serverTime) { cl.serverTime = floor(wantedTime); cls.realtime = floor(wantedTime); di.Overf = wantedTime - floor(wantedTime); cl.serverTimeDelta = 0; return; } if (wantedTime < (double)cl.snap.serverTime) { Com_FuncPrinf("This should not happen %f < %d\n", wantedTime, cl.snap.serverTime); return; } if (wantedTime > (double)di.lastServerTime) { Com_FuncPrinf("would seek past end of demo (%f > %d)\n", wantedTime, di.lastServerTime); return; } DEMODEBUG("fastfowarding from %f to %f\n", (double)cl.serverTime + di.Overf, wantedTime); loopCount = 0; while ((double)cl.snap.serverTime <= wantedTime) { DEMODEBUG("Servertime: %d wanted time %lf\n", cl.snap.serverTime, wantedTime); CL_ReadDemoMessage(); while (clc.lastExecutedServerCommand < clc.serverCommandSequence) { if (clc.lastExecutedServerCommand + 1 <= clc.serverCommandSequence - MAX_RELIABLE_COMMANDS) { if (cl.snap.serverTime <= di.firstServerTime) { clc.lastExecutedServerCommand = clc.serverCommandSequence - 1; Com_FuncPrinf("setting clc.lastExecutedServerCommand %d (%d)\n", clc.lastExecutedServerCommand, loopCount); } else { Com_FuncDPrinf("FIXME %i (%i) + 1 <= (%i) - MAX_RELIABLE_COMMANDS (%i)\n", clc.lastExecutedServerCommand, clc.serverCommandSequence, cl.snap.serverTime, di.firstServerTime); break; } } CL_GetServerCommand(clc.lastExecutedServerCommand + 1); } loopCount++; } DEMODEBUG("read %d demo messages, cl.snap.serverTime %d, wantedTime %f\n", loopCount, cl.snap.serverTime, wantedTime); cl.serverTime = floor(wantedTime); cls.realtime = floor(wantedTime); di.Overf = wantedTime - floor(wantedTime); cl.serverTimeDelta = 0; // TODO: fix this //VM_Call(cgvm, CG_TIME_CHANGE, cl.serverTime, (int)(Overf * SUBTIME_RESOLUTION)); di.seeking = qtrue; di.firstNonDeltaMessageNumWritten = -1; }
// Do very shallow parse of the demo (could be extended) just to get times and snapshot count static void CL_ParseDemo(void) { int tstart = 0; int demofile = 0; // Reset our demo data memset(&di, 0, sizeof(di)); // Parse start di.gameStartTime = -1; di.gameEndTime = -1; FS_Seek(clc.demofile, 0, FS_SEEK_SET); tstart = Sys_Milliseconds(); while (qtrue) { int r; msg_t buf; msg_t *msg; byte bufData[MAX_MSGLEN]; int s; int cmd; di.demoPos = FS_FTell(clc.demofile); // get the sequence number r = FS_Read(&s, 4, clc.demofile); if (r != 4) { CL_DemoCompleted(); return; } clc.serverMessageSequence = LittleLong(s); // init the message MSG_Init(&buf, bufData, sizeof(bufData)); // get the length r = FS_Read(&buf.cursize, 4, clc.demofile); if (r != 4) { break; } buf.cursize = LittleLong(buf.cursize); if (buf.cursize == -1) { break; } if (buf.cursize > buf.maxsize) { Com_FuncDPrinf("demoMsglen > MAX_MSGLEN"); break; } r = FS_Read(buf.data, buf.cursize, clc.demofile); if (r != buf.cursize) { Com_FuncDPrinf("Demo file was truncated.\n"); break; } clc.lastPacketTime = cls.realtime; buf.readcount = 0; // parse msg = &buf; MSG_Bitstream(msg); // get the reliable sequence acknowledge number clc.reliableAcknowledge = MSG_ReadLong(msg); if (clc.reliableAcknowledge < clc.reliableSequence - MAX_RELIABLE_COMMANDS) { clc.reliableAcknowledge = clc.reliableSequence; } // parse the message while (qtrue) { if (msg->readcount > msg->cursize) { Com_FuncDrop("read past end of server message"); break; } cmd = MSG_ReadByte(msg); if (cmd == svc_EOF) { break; } // other commands switch (cmd) { default: Com_FuncDrop("Illegible server message %d", cmd); break; case svc_nop: break; case svc_serverCommand: MSG_ReadLong(msg); MSG_ReadString(msg); break; case svc_gamestate: clc.serverCommandSequence = MSG_ReadLong(msg); cl.gameState.dataCount = 1; while (qtrue) { int cmd2 = MSG_ReadByte(msg); if (cmd2 == svc_EOF) { break; } if (cmd2 == svc_configstring) { MSG_ReadShort(msg); MSG_ReadBigString(msg); } else if (cmd2 == svc_baseline) { entityState_t s1, s2; memset(&s1, 0, sizeof(s1)); memset(&s2, 0, sizeof(s2)); MSG_ReadBits(msg, GENTITYNUM_BITS); MSG_ReadDeltaEntity(msg, &s1, &s2, 0); } else { Com_FuncDrop("bad command byte"); } } MSG_ReadLong(msg); MSG_ReadLong(msg); break; case svc_snapshot: CL_ParseDemoSnapShotSimple(msg); break; case svc_download: MSG_ReadShort(msg); break; } } if (!cl.snap.valid) { Com_FuncDPrinf("!cl.snap.valid\n"); continue; } if (cl.snap.serverTime < cl.oldFrameServerTime) { // ignore snapshots that don't have entities if (cl.snap.snapFlags & SNAPFLAG_NOT_ACTIVE) { continue; } cls.state = CA_ACTIVE; // set the timedelta so we are exactly on this first frame cl.serverTimeDelta = cl.snap.serverTime - cls.realtime; cl.oldServerTime = cl.snap.serverTime; clc.timeDemoBaseTime = cl.snap.serverTime; } cl.oldFrameServerTime = cl.snap.serverTime; if (cl.newSnapshots) { CL_AdjustTimeDelta(); } di.lastServerTime = cl.snap.serverTime; if (di.firstServerTime == 0) { di.firstServerTime = cl.snap.serverTime; Com_FuncPrinf("firstServerTime %d\n", di.firstServerTime); } di.snapsInDemo++; } Com_FuncDPrinf("Snaps in demo: %i\n", di.snapsInDemo); Com_FuncDPrinf("last serverTime %d total %f minutes\n", cl.snap.serverTime, (cl.snap.serverTime - di.firstServerTime) / 1000.0 / 60.0); Com_FuncDPrinf("parse time %f seconds\n", (float)(Sys_Milliseconds() - tstart) / 1000.0); FS_Seek(clc.demofile, 0, FS_SEEK_SET); clc.demoplaying = qfalse; demofile = clc.demofile; CL_ClearState(); Com_Memset(&clc, 0, sizeof(clc)); clc.demofile = demofile; cls.state = CA_DISCONNECTED; cl_connectedToPureServer = qfalse; dpi.firstTime = di.firstServerTime; dpi.lastTime = di.lastServerTime; }
static void CL_ParseDemoSnapShotSimple(msg_t *msg) { int len; clSnapshot_t *old; clSnapshot_t newSnap; int deltaNum; int oldMessageNum; int i, packetNum; memset(&newSnap, 0, sizeof(newSnap)); newSnap.serverCommandNum = clc.serverCommandSequence; newSnap.serverTime = MSG_ReadLong(msg); newSnap.messageNum = clc.serverMessageSequence; deltaNum = MSG_ReadByte(msg); if (!deltaNum) { newSnap.deltaNum = -1; } else { newSnap.deltaNum = newSnap.messageNum - deltaNum; } newSnap.snapFlags = MSG_ReadByte(msg); if (newSnap.deltaNum <= 0) { newSnap.valid = qtrue; // uncompressed frame old = NULL; } else { old = &cl.snapshots[newSnap.deltaNum & PACKET_MASK]; if (!old->valid) { // should never happen Com_FuncPrinf("Delta from invalid frame (not supposed to happen!).\n"); } else if (old->messageNum != newSnap.deltaNum) { // The frame that the server did the delta from // is too old, so we can't reconstruct it properly. Com_FuncDPrinf("Delta frame too old.\n"); } else if (cl.parseEntitiesNum - old->parseEntitiesNum > MAX_PARSE_ENTITIES - 128) { Com_FuncDPrinf("Delta parseEntitiesNum too old.\n"); } else { newSnap.valid = qtrue; // valid delta parse } } // read areamask len = MSG_ReadByte(msg); if (len > sizeof(newSnap.areamask)) { Com_FuncDrop("Invalid size %d for areamask.", len); return; } MSG_ReadData(msg, &newSnap.areamask, len); if (old) { MSG_ReadDeltaPlayerstate(msg, &old->ps, &newSnap.ps); } else { MSG_ReadDeltaPlayerstate(msg, NULL, &newSnap.ps); } // read packet entities CL_ParsePacketEntities(msg, old, &newSnap); // if not valid, dump the entire thing now that it has // been properly read if (!newSnap.valid) { return; } // clear the valid flags of any snapshots between the last // received and this one, so if there was a dropped packet // it won't look like something valid to delta from next // time we wrap around in the buffer oldMessageNum = cl.snap.messageNum + 1; if (newSnap.messageNum - oldMessageNum >= PACKET_BACKUP) { oldMessageNum = newSnap.messageNum - (PACKET_BACKUP - 1); } for (; oldMessageNum < newSnap.messageNum; oldMessageNum++) { cl.snapshots[oldMessageNum & PACKET_MASK].valid = qfalse; } // copy to the current good spot cl.snap = newSnap; cl.snap.ping = 999; // calculate ping time for (i = 0; i < PACKET_BACKUP; i++) { packetNum = (clc.netchan.outgoingSequence - 1 - i) & PACKET_MASK; if (cl.snap.ps.commandTime >= cl.outPackets[packetNum].p_serverTime) { cl.snap.ping = cls.realtime - cl.outPackets[packetNum].p_realtime; break; } } // save the frame off in the backup array for later delta comparisons cl.snapshots[cl.snap.messageNum & PACKET_MASK] = cl.snap; cl.newSnapshots = qtrue; }
qboolean CL_PeekSnapshot(int snapshotNumber, snapshot_t *snapshot) { clSnapshot_t *clSnap; clSnapshot_t csn; int i, count; int origPosition; int cmd; //char *s; char buffer[16]; qboolean success = qfalse; int r; msg_t buf; byte bufData[MAX_MSGLEN]; int j; int lastPacketTimeOrig; int parseEntitiesNumOrig; int currentSnapNum; //int serverMessageSequence; clSnap = &csn; if (!clc.demoplaying) { return qfalse; } if (snapshotNumber <= cl.snap.messageNum) { success = CL_GetSnapshot(snapshotNumber, snapshot); if (!success) { Com_FuncDPrinf("snapshot number outside of backup buffer\n"); return qfalse; } return qtrue; } if (snapshotNumber > cl.snap.messageNum + 1) { Com_FuncDPrinf("FIXME CL_PeekSnapshot %d > cl.snap.messageNum + 1 (%d)\n", snapshotNumber, cl.snap.messageNum); //return qfalse; } parseEntitiesNumOrig = cl.parseEntitiesNum; lastPacketTimeOrig = clc.lastPacketTime; // CL_ReadDemoMessage() origPosition = FS_FTell(clc.demofile); currentSnapNum = cl.snap.messageNum; for (j = 0; j < snapshotNumber - currentSnapNum; j++) { // get the sequence number memset(buffer, 0, sizeof(buffer)); r = FS_Read(&buffer, 4, clc.demofile); if (r != 4) { Com_FuncDPrinf("couldn't read sequence number\n"); FS_Seek(clc.demofile, origPosition, FS_SEEK_SET); clc.lastPacketTime = lastPacketTimeOrig; cl.parseEntitiesNum = parseEntitiesNumOrig; return qfalse; } //serverMessageSequence = LittleLong(*((int *)buffer)); // init the message memset(&buf, 0, sizeof(msg_t)); MSG_Init(&buf, bufData, sizeof(bufData)); // get the length r = FS_Read(&buf.cursize, 4, clc.demofile); if (r != 4) { Com_FuncDPrinf("couldn't get length\n"); FS_Seek(clc.demofile, origPosition, FS_SEEK_SET); clc.lastPacketTime = lastPacketTimeOrig; cl.parseEntitiesNum = parseEntitiesNumOrig; return qfalse; } buf.cursize = LittleLong(buf.cursize); if (buf.cursize == -1) { Com_FuncDPrinf("buf.cursize == -1\n"); FS_Seek(clc.demofile, origPosition, FS_SEEK_SET); clc.lastPacketTime = lastPacketTimeOrig; cl.parseEntitiesNum = parseEntitiesNumOrig; return qfalse; } if (buf.cursize > buf.maxsize) { Com_FuncDrop("demoMsglen > MAX_MSGLEN"); } r = FS_Read(buf.data, buf.cursize, clc.demofile); if (r != buf.cursize) { Com_FuncDPrinf("Demo file was truncated.\n"); FS_Seek(clc.demofile, origPosition, FS_SEEK_SET); clc.lastPacketTime = lastPacketTimeOrig; cl.parseEntitiesNum = parseEntitiesNumOrig; return qfalse; } clc.lastPacketTime = cls.realtime; buf.readcount = 0; MSG_Bitstream(&buf); // get the reliable sequence acknowledge number MSG_ReadLong(&buf); // parse the message while (qtrue) { if (buf.readcount > buf.cursize) { Com_FuncDrop("read past end of server message"); break; } cmd = MSG_ReadByte(&buf); if (cmd == svc_EOF) { break; } success = qfalse; switch (cmd) { default: Com_FuncDrop("Illegible server message"); break; case svc_nop: break; case svc_serverCommand: MSG_ReadLong(&buf); // seq //s = MSG_ReadString(&buf); MSG_ReadString(&buf); break; case svc_gamestate: Com_FuncDPrinf("FIXME gamestate\n"); goto alldone; break; case svc_snapshot: // TODO: changed this check if it works CL_ParseSnapshot(&buf); if (cl.snap.valid) { success = qtrue; } break; case svc_download: Com_FuncDPrinf("FIXME download\n"); goto alldone; break; } } alldone: if (!success) { Com_FuncDPrinf("failed\n"); FS_Seek(clc.demofile, origPosition, FS_SEEK_SET); clc.lastPacketTime = lastPacketTimeOrig; cl.parseEntitiesNum = parseEntitiesNumOrig; return success; } // FIXME other ents not supported yet // if the entities in the frame have fallen out of their // circular buffer, we can't return it if (cl.parseEntitiesNum - clSnap->parseEntitiesNum >= MAX_PARSE_ENTITIES) { Com_FuncDPrinf("cl.parseEntitiesNum - clSnap->parseEntitiesNum >= MAX_PARSE_ENTITIES"); FS_Seek(clc.demofile, origPosition, FS_SEEK_SET); clc.lastPacketTime = lastPacketTimeOrig; cl.parseEntitiesNum = parseEntitiesNumOrig; return qtrue; // FIXME if you fix other ents } // write the snapshot snapshot->snapFlags = clSnap->snapFlags; snapshot->serverCommandSequence = clSnap->serverCommandNum; snapshot->ping = clSnap->ping; snapshot->serverTime = clSnap->serverTime; Com_Memcpy(snapshot->areamask, clSnap->areamask, sizeof(snapshot->areamask)); snapshot->ps = clSnap->ps; count = clSnap->numEntities; if (count > MAX_ENTITIES_IN_SNAPSHOT) { Com_FuncDPrinf("truncated %i entities to %i\n", count, MAX_ENTITIES_IN_SNAPSHOT); count = MAX_ENTITIES_IN_SNAPSHOT; } snapshot->numEntities = count; for (i = 0; i < count; i++) { snapshot->entities[i] = cl.parseEntities[(clSnap->parseEntitiesNum + i) & (MAX_PARSE_ENTITIES - 1)]; } } FS_Seek(clc.demofile, origPosition, FS_SEEK_SET); clc.lastPacketTime = lastPacketTimeOrig; cl.parseEntitiesNum = parseEntitiesNumOrig; // TODO: configstring changes and server commands!!! return qtrue; }