diff --git a/qw/include/cl_cam.h b/qw/include/cl_cam.h index d162bbd9d..2d9c6f1ae 100644 --- a/qw/include/cl_cam.h +++ b/qw/include/cl_cam.h @@ -41,8 +41,11 @@ extern int autocam; extern int spec_track; // player# of who we are tracking +extern int ideal_track; extern struct cvar_s *chase_active; +void Cam_Lock (int playernum); +int Cam_TrackNum (void); qboolean Cam_DrawViewModel(void); qboolean Cam_DrawPlayer(int playernum); void Cam_Track(usercmd_t *cmd); diff --git a/qw/include/cl_ents.h b/qw/include/cl_ents.h index ec1196f90..8fbfcbff7 100644 --- a/qw/include/cl_ents.h +++ b/qw/include/cl_ents.h @@ -32,10 +32,11 @@ #include "QF/qtypes.h" void CL_SetSolidPlayers (int playernum); +void CL_ClearPredict (void); void CL_SetUpPlayerPrediction(qboolean dopred); void CL_EmitEntities (void); void CL_ClearProjectiles (void); -void CL_ParseProjectiles (void); +void CL_ParseProjectiles (qboolean nail2); void CL_ParsePacketEntities (qboolean delta); void CL_SetSolidEntities (void); void CL_ParsePlayerinfo (void); diff --git a/qw/include/cl_parse.h b/qw/include/cl_parse.h index c4be728ea..5ab36598d 100644 --- a/qw/include/cl_parse.h +++ b/qw/include/cl_parse.h @@ -50,6 +50,7 @@ extern int cl_gib3index; int CL_CalcNet (void); void CL_ParseServerMessage (void); +void CL_ParseClientdata (void); void CL_NewTranslation (int slot, struct skin_s *skin); qboolean CL_CheckOrDownloadFile (const char *filename); qboolean CL_IsUploading(void); diff --git a/qw/include/client.h b/qw/include/client.h index 756f3e0df..3e60cc4bf 100644 --- a/qw/include/client.h +++ b/qw/include/client.h @@ -102,6 +102,8 @@ typedef struct player_info_s byte translations[4*VID_GRADES*256]; // space for colormap32 int translationcolor[256]; struct skin_s *skin; + int stats[MAX_CL_STATS]; // health, etc + int prevcount; } player_info_t; @@ -178,6 +180,12 @@ typedef struct // entering a map (and clearing client_state_t) qboolean demorecording; qboolean demoplayback; + qboolean demoplayback2; + qboolean findtrack; + int lastto; + int lasttype; + int prevtime; + double basetime; qboolean timedemo; QFile *demofile; float td_lastframe; // to meter out one message a frame diff --git a/qw/include/protocol.h b/qw/include/protocol.h index 93bf46f88..da4c44d2c 100644 --- a/qw/include/protocol.h +++ b/qw/include/protocol.h @@ -118,6 +118,7 @@ #define svc_setinfo 51 // setinfo on a client #define svc_serverinfo 52 // serverinfo #define svc_updatepl 53 // [byte] [byte] +#define svc_nails2 54 // FIXME: from qwex. for interpolation, stores edict num // client to server =========================================================== @@ -130,6 +131,17 @@ #define clc_tmove 6 // teleport request, spectator only #define clc_upload 7 // teleport request, spectator only +// demo recording + +#define dem_cmd 0 +#define dem_read 1 +#define dem_set 2 +#define dem_multiple 3 +#define dem_single 4 +#define dem_stats 5 +#define dem_all 6 + + // ============================================== // playerinfo flags from server diff --git a/qw/source/cl_cam.c b/qw/source/cl_cam.c index 4f16ed674..b99f6ee84 100644 --- a/qw/source/cl_cam.c +++ b/qw/source/cl_cam.c @@ -93,6 +93,8 @@ qboolean cam_forceview; vec3_t cam_viewangles; int spec_track = 0; // player# of who we are tracking +int ideal_track = 0; +float last_lock = 0; int autocam = CAM_NONE; @@ -158,6 +160,14 @@ Cam_DrawPlayer (int playernum) return false; } +int +Cam_TrackNum (void) +{ + if (!autocam) + return -1; + return spec_track; +} + void Cam_Unlock (void) { @@ -174,11 +184,16 @@ void Cam_Lock (int playernum) { char st[40]; - +printf ("Cam_Lock: %d\n", playernum); snprintf (st, sizeof (st), "ptrack %i", playernum); + if (cls.demoplayback2) { + memcpy(cl.stats, cl.players[playernum].stats, sizeof (cl.stats)); + } + MSG_WriteByte (&cls.netchan.message, clc_stringcmd); MSG_WriteString (&cls.netchan.message, st); spec_track = playernum; + last_lock = realtime; cam_forceview = true; locked = false; Sbar_Changed (); @@ -355,8 +370,10 @@ Cam_CheckHighTarget (void) } } if (j >= 0) { - if (!locked || cl.players[j].frags > cl.players[spec_track].frags) + if (!locked || cl.players[j].frags > cl.players[spec_track].frags) { Cam_Lock (j); + ideal_track = spec_track; + } } else Cam_Unlock (); } @@ -394,6 +411,23 @@ Cam_Track (usercmd_t *cmd) } frame = &cl.frames[cls.netchan.incoming_sequence & UPDATE_MASK]; + if (autocam && cls.demoplayback2 && 0) { + if (ideal_track != spec_track && realtime - last_lock > 1 + && frame->playerstate[ideal_track].messagenum == cl.parsecount) + Cam_Lock (ideal_track); + + if (frame->playerstate[spec_track].messagenum != cl.parsecount) { + int i; + + for (i = 0; i < MAX_CLIENTS; i++) { + if (frame->playerstate[i].messagenum == cl.parsecount) + break; + } + if (i < MAX_CLIENTS) + Cam_Lock (i); + } + } + player = frame->playerstate + spec_track; self = frame->playerstate + cl.playernum; @@ -594,6 +628,8 @@ Cam_FinishMove (usercmd_t *cmd) s = &cl.players[i]; if (s->name[0] && !s->spectator) { Cam_Lock (i); + Con_Printf("tracking %s\n", s->name); + ideal_track = i; return; } i = (i + 1) % MAX_CLIENTS; @@ -603,6 +639,7 @@ Cam_FinishMove (usercmd_t *cmd) s = &cl.players[i]; if (s->name[0] && !s->spectator) { Cam_Lock (i); + ideal_track = i; return; } Con_Printf ("No target found ...\n"); @@ -614,6 +651,7 @@ Cam_Reset (void) { autocam = CAM_NONE; spec_track = 0; + ideal_track = 0; } void diff --git a/qw/source/cl_demo.c b/qw/source/cl_demo.c index 6f627d8ad..7526b3e9e 100644 --- a/qw/source/cl_demo.c +++ b/qw/source/cl_demo.c @@ -51,6 +51,8 @@ static const char rcsid[] = #include "QF/sys.h" #include "QF/va.h" +#include "cl_cam.h" +#include "cl_ents.h" #include "cl_main.h" #include "client.h" #include "compat.h" @@ -66,6 +68,7 @@ typedef struct { int cl_timeframes_isactive; int cl_timeframes_index; int demotime_cached; +float nextdemotime; char demoname[1024]; double *cl_timeframes_array; #define CL_TIMEFRAMES_ARRAYBLOCK 4096 @@ -108,16 +111,13 @@ CL_StopPlayback (void) cls.demofile = NULL; CL_SetState (ca_disconnected); cls.demoplayback = 0; + cls.demoplayback2 = 0; demotime_cached = 0; if (cls.timedemo) CL_FinishTimeDemo (); } -#define dem_cmd 0 -#define dem_read 1 -#define dem_set 2 - /* CL_WriteDemoCmd @@ -184,22 +184,51 @@ CL_WriteDemoMessage (sizebuf_t *msg) Qflush (cls.demofile); } +#if 0 +static const char *dem_names[] = { + "dem_cmd", + "dem_read", + "dem_set", + "dem_multiple", + "dem_single", + "dem_stats", + "dem_all", + "dem_invalid", +}; +#endif + qboolean CL_GetDemoMessage (void) { - byte c; + byte c, newtime; float demotime, f; static float cached_demotime; - int r, i, j; + static byte cached_newtime; + int r, i, j, tracknum; usercmd_t *pcmd; + if (!cls.demoplayback2) + nextdemotime = realtime; + if (realtime + 1.0 < nextdemotime) + realtime = nextdemotime - 1.0; + +nextdemomessage: // read the time from the packet + newtime = 0; if (demotime_cached) { demotime = cached_demotime; + newtime = cached_newtime; demotime_cached = 0; } else { - Qread (cls.demofile, &demotime, sizeof (demotime)); - demotime = LittleFloat (demotime); + if (cls.demoplayback2) { + Qread (cls.demofile, &newtime, sizeof (newtime)); + demotime = cls.basetime + (cls.prevtime + newtime) * 0.001; + } else { + Qread (cls.demofile, &demotime, sizeof (demotime)); + demotime = LittleFloat (demotime); + if (!nextdemotime) + realtime = nextdemotime = demotime; + } } // decide if it is time to grab the next message @@ -211,6 +240,7 @@ CL_GetDemoMessage (void) // rewind back to time demotime_cached = 1; cached_demotime = demotime; + cached_newtime = newtime; return 0; // already read this frame's message } if (!cls.td_starttime && cls.state == ca_active) { @@ -220,29 +250,44 @@ CL_GetDemoMessage (void) realtime = demotime; // warp } else if (!cl.paused && cls.state >= ca_onserver) { // always grab until fully connected - if (realtime + 1.0 < demotime) { + if (!cls.demoplayback2 && realtime + 1.0 < demotime) { // too far back realtime = demotime - 1.0; // rewind back to time demotime_cached = 1; cached_demotime = demotime; + cached_newtime = newtime; return 0; } else if (realtime < demotime) { // rewind back to time demotime_cached = 1; cached_demotime = demotime; + cached_newtime = newtime; return 0; // don't need another message yet } } else realtime = demotime; // we're warping + if (realtime - nextdemotime > 0.0001) { + if (nextdemotime != demotime) { + if (cls.demoplayback2) { + cls.netchan.incoming_sequence++; + cls.netchan.incoming_acknowledged++; + cls.netchan.frame_latency = 0; + } + } + } + nextdemotime = demotime; + + cls.prevtime += newtime; + if (cls.state < ca_demostart) Host_Error ("CL_GetDemoMessage: cls.state != ca_active"); // get the msg type Qread (cls.demofile, &c, sizeof (c)); - switch (c) { + switch (c & 7) { case dem_cmd: // user sent input net_message->message->cursize = -1; @@ -269,6 +314,7 @@ CL_GetDemoMessage (void) break; case dem_read: +readit: // get the next message Qread (cls.demofile, &net_message->message->cursize, 4); net_message->message->cursize = LittleLong @@ -282,6 +328,20 @@ CL_GetDemoMessage (void) CL_StopPlayback (); return 0; } + + if (cls.demoplayback2) { + tracknum = Cam_TrackNum (); + + if (cls.lasttype == dem_multiple) { + if (tracknum == -1) + goto nextdemomessage; + if (!(cls.lastto & (1 << tracknum))) + goto nextdemomessage; + } else if (cls.lasttype == dem_single) { + if (tracknum == -1 || cls.lastto != spec_track) + goto nextdemomessage; + } + } break; case dem_set: @@ -289,8 +349,38 @@ CL_GetDemoMessage (void) cls.netchan.outgoing_sequence = LittleLong (i); Qread (cls.demofile, &i, 4); cls.netchan.incoming_sequence = LittleLong (i); + if (cls.demoplayback2) { + cls.netchan.incoming_acknowledged = + cls.netchan.incoming_sequence; + goto nextdemomessage; + } break; + case dem_multiple: + r = Qread (cls.demofile, &i, 4); + if (r != 4) { + CL_StopPlayback (); + return 0; + } + cls.lastto = LittleLong (i); + cls.lasttype = dem_multiple; + goto readit; + + case dem_single: + cls.lastto = c >> 3; + cls.lasttype = dem_single; + goto readit; + + case dem_stats: + cls.lastto = c >> 3; + cls.lasttype = dem_stats; + goto readit; + + case dem_all: + cls.lastto = 0; + cls.lasttype = dem_all; + goto readit; + default: Con_Printf ("Corrupted demo.\n"); CL_StopPlayback (); @@ -789,10 +879,23 @@ CL_StartDemo (void) } cls.demoplayback = true; + if (strequal (COM_FileExtension (name), ".mvd")) { + cls.demoplayback2 = true; + Con_Printf ("mvd\n"); + } else { + Con_Printf ("qwd\n"); + } CL_SetState (ca_demostart); Netchan_Setup (&cls.netchan, net_from, 0); realtime = 0; + cls.findtrack = true; + cls.lasttype = 0; + cls.lastto = 0; + cls.prevtime = 0; + cls.basetime = 0; demotime_cached = 0; + nextdemotime = 0; + CL_ClearPredict (); } /* diff --git a/qw/source/cl_ents.c b/qw/source/cl_ents.c index 7421e962f..f151eb0ef 100644 --- a/qw/source/cl_ents.c +++ b/qw/source/cl_ents.c @@ -301,7 +301,8 @@ CL_ParsePacketEntities (qboolean delta) from = MSG_ReadByte (net_message); oldpacket = cl.frames[newpacket].delta_sequence; - + if (cls.demoplayback2) + from = oldpacket = (cls.netchan.incoming_sequence - 1); if ((from & UPDATE_MASK) != (oldpacket & UPDATE_MASK)) Con_DPrintf ("WARNING: from mismatch\n"); } else @@ -564,10 +565,10 @@ CL_ClearProjectiles (void) Nails are passed as efficient temporary entities */ void -CL_ParseProjectiles (void) +CL_ParseProjectiles (qboolean nail2) { byte bits[6]; - int i, c, d, j; + int i, c, d, j, num; entity_t *pr; c = MSG_ReadByte (net_message); @@ -578,6 +579,11 @@ CL_ParseProjectiles (void) d = c; for (i = 0; i < d; i++) { + if (nail2) + num = MSG_ReadByte (net_message); + else + num = 0; + for (j = 0; j < 6; j++) bits[j] = MSG_ReadByte (net_message); @@ -595,8 +601,11 @@ CL_ParseProjectiles (void) if (d < c) { c = (c - d) * 6; - for (i = 0; i < c; i++) + for (i = 0; i < c; i++) { + if (nail2) + MSG_ReadByte (net_message); MSG_ReadByte (net_message); + } } } @@ -618,19 +627,98 @@ CL_LinkProjectiles (void) } } +#define DF_ORIGIN 1 +#define DF_ANGLES (1<<3) +#define DF_EFFECTS (1<<6) +#define DF_SKINNUM (1<<7) +#define DF_DEAD (1<<8) +#define DF_GIB (1<<9) +#define DF_WEAPONFRAME (1<<10) +#define DF_MODEL (1<<11) + +int +TranslateFlags (int src) +{ + int dst = 0; + + if (src & DF_EFFECTS) + dst |= PF_EFFECTS; + if (src & DF_SKINNUM) + dst |= PF_SKINNUM; + if (src & DF_DEAD) + dst |= PF_DEAD; + if (src & DF_GIB) + dst |= PF_GIB; + if (src & DF_WEAPONFRAME) + dst |= PF_WEAPONFRAME; + if (src & DF_MODEL) + dst |= PF_MODEL; + + return dst; +} + void CL_ParsePlayerinfo (void) { - int flags, msec, num, i; - player_state_t *state; + int flags, msec, num, i; + player_info_t *info; + player_state_t *state, *prevstate; + static player_state_t dummy; num = MSG_ReadByte (net_message); if (num > MAX_CLIENTS) Host_Error ("CL_ParsePlayerinfo: bad num"); + info = &cl.players[num]; state = &cl.frames[parsecountmod].playerstate[num]; state->number = num; + + if (cls.demoplayback2) { + if (info->prevcount > cl.parsecount || !cl.parsecount) { + prevstate = &dummy; + } else { + if (cl.parsecount - info->prevcount >= UPDATE_BACKUP-1) + prevstate = &dummy; + else + prevstate = &cl.frames[info->prevcount + & UPDATE_MASK].playerstate[num]; + } + info->prevcount = cl.parsecount; + + if (cls.findtrack && info->stats[STAT_HEALTH] != 0) { + autocam = CAM_TRACK; + Cam_Lock (num); + ideal_track = num; + cls.findtrack = false; + } + + memcpy(state, prevstate, sizeof(player_state_t)); + + flags = MSG_ReadShort (net_message); + state->flags = TranslateFlags(flags); + state->messagenum = cl.parsecount; + state->command.msec = 0; + state->frame = MSG_ReadByte (net_message); + state->state_time = parsecounttime; + for (i=0; i <3; i++) + if (flags & (DF_ORIGIN << i)) + state->origin[i] = MSG_ReadCoord (net_message); + for (i=0; i <3; i++) + if (flags & (DF_ANGLES << i)) + state->command.angles[i] = MSG_ReadAngle16 (net_message); + if (flags & DF_MODEL) + state->modelindex = MSG_ReadByte (net_message); + if (flags & DF_SKINNUM) + state->skinnum = MSG_ReadByte (net_message); + if (flags & DF_EFFECTS) + state->effects = MSG_ReadByte (net_message); + if (flags & DF_WEAPONFRAME) + state->weaponframe = MSG_ReadByte (net_message); + VectorCopy (state->command.angles, state->viewangles); + return; + } + flags = state->flags = MSG_ReadShort (net_message); state->messagenum = cl.parsecount; @@ -830,7 +918,7 @@ CL_LinkPlayers (void) // only predict half the move to minimize overruns msec = 500 * (playertime - state->state_time); - if (msec <= 0 || (!cl_predict_players->int_val)) { + if (msec <= 0 || (!cl_predict_players->int_val) || cls.demoplayback2) { VectorCopy (state->origin, ent->origin); } else { // predict players movement state->command.msec = msec = min (msec, 255); @@ -927,6 +1015,13 @@ CL_SetSolidEntities (void) } } +void +CL_ClearPredict (void) +{ + memset (predicted_players, 0, sizeof (predicted_players)); + //fixangle = 0; +} + /* Calculate the new position of players, without other player clipping diff --git a/qw/source/cl_input.c b/qw/source/cl_input.c index 3f7774d17..4c9355343 100644 --- a/qw/source/cl_input.c +++ b/qw/source/cl_input.c @@ -670,7 +670,7 @@ CL_SendCmd (void) sizebuf_t buf; usercmd_t *cmd, *oldcmd; - if (cls.demoplayback) + if (cls.demoplayback && !cls.demoplayback2) return; // sendcmds come from the demo // save this command off for prediction @@ -684,6 +684,11 @@ CL_SendCmd (void) build_cmd (cmd); + if (cls.demoplayback2) { + cls.netchan.outgoing_sequence++; + return; + } + // send this and the previous cmds in the message, so // if the last packet was dropped, it can be recovered buf.maxsize = 128; diff --git a/qw/source/cl_main.c b/qw/source/cl_main.c index 6fc0d20a5..a7f4b0d7b 100644 --- a/qw/source/cl_main.c +++ b/qw/source/cl_main.c @@ -471,6 +471,7 @@ CL_Disconnect (void) CL_SetState (ca_disconnected); cls.demoplayback = cls.demorecording = cls.timedemo = false; + cls.demoplayback2 = false; CL_RemoveQFInfoKeys (); } @@ -1049,7 +1050,7 @@ CL_ReadPackets (void) SL_CheckPing (NET_AdrToString (net_from)); continue; } - if (net_message->message->cursize < 8) { + if (net_message->message->cursize < 8 && !cls.demoplayback2) { Con_Printf ("%s: Runt packet\n", NET_AdrToString (net_from)); continue; } @@ -1061,14 +1062,18 @@ CL_ReadPackets (void) NET_AdrToString (net_from)); continue; } - if (!Netchan_Process (&cls.netchan)) - continue; // wasn't accepted for some reason + if (!cls.demoplayback2) { + if (!Netchan_Process (&cls.netchan)) + continue; // wasn't accepted for some reason + } else { + MSG_BeginReading (net_message); + } if (cls.state != ca_disconnected) CL_ParseServerMessage (); } // check timeout - if (cls.state >= ca_connected + if (!cls.demoplayback && cls.state >= ca_connected && realtime - cls.netchan.last_received > cl_timeout->value) { Con_Printf ("\nServer connection timed out.\n"); CL_Disconnect (); @@ -1543,6 +1548,23 @@ Host_Frame (float time) // fetch results from server CL_ReadPackets (); + if (cls.demoplayback2) { + player_state_t *self, *oldself; + + self = &cl.frames[cl.parsecount + & UPDATE_MASK].playerstate[cl.playernum]; + oldself = &cl.frames[(cls.netchan.outgoing_sequence - 1) + & UPDATE_MASK].playerstate[cl.playernum]; + self->messagenum = cl.parsecount; + VectorCopy (oldself->origin, self->origin); + VectorCopy (oldself->velocity, self->velocity); + VectorCopy (oldself->viewangles, self->viewangles); + + CL_ParseClientdata (); + + cls.netchan.outgoing_sequence = cl.parsecount + 1; + } + // send intentions now // resend a connection request if necessary if (cls.state == ca_disconnected) { diff --git a/qw/source/cl_parse.c b/qw/source/cl_parse.c index 0b4e50683..d30ed2b66 100644 --- a/qw/source/cl_parse.c +++ b/qw/source/cl_parse.c @@ -60,6 +60,7 @@ static const char rcsid[] = #include "QF/gib_thread.h" #include "bothdefs.h" +#include "cl_cam.h" #include "cl_ents.h" #include "cl_input.h" #include "cl_main.h" @@ -138,7 +139,7 @@ char *svc_strings[] = { "svc_setinfo", "svc_serverinfo", "svc_updatepl", - "NEW PROTOCOL", + "svc_nails2", // FIXME from qwex "NEW PROTOCOL", "NEW PROTOCOL", "NEW PROTOCOL", @@ -658,11 +659,18 @@ CL_ParseServerData (void) snprintf (fn, sizeof (fn), "cmd_warncmd %d\n", cmd_warncmd_val); Cbuf_AddText (cl_cbuf, fn); } - // parse player slot, high bit means spectator - cl.playernum = MSG_ReadByte (net_message); - if (cl.playernum & 128) { + + if (cls.demoplayback2) { + realtime = cls.basetime = MSG_ReadFloat (net_message); + cl.playernum = 31; cl.spectator = true; - cl.playernum &= ~128; + } else { + // parse player slot, high bit means spectator + cl.playernum = MSG_ReadByte (net_message); + if (cl.playernum & 128) { + cl.spectator = true; + cl.playernum &= ~128; + } } // FIXME: evil hack so NQ and QW can share sound code @@ -915,10 +923,13 @@ CL_ParseClientdata (void) oldparsecountmod = parsecountmod; i = cls.netchan.incoming_acknowledged; + cl.parsecount = i; i &= UPDATE_MASK; parsecountmod = i; frame = &cl.frames[i]; + if (cls.demoplayback2) + frame->senttime = realtime - host_frametime;//realtime; parsecounttime = cl.frames[i].senttime; frame->receivedtime = realtime; @@ -1064,6 +1075,12 @@ CL_SetStat (int stat, int value) if (stat < 0 || stat >= MAX_CL_STATS) Host_Error ("CL_SetStat: %i is invalid", stat); + if (cls.demoplayback2) { + cl.players[cls.lastto].stats[stat] = value; + if (Cam_TrackNum () != cls.lastto) + return; + } + Sbar_Changed (); switch (stat) { @@ -1211,7 +1228,8 @@ CL_ParseServerMessage (void) // other commands switch (cmd) { default: - Host_Error ("CL_ParseServerMessage: Illegible server message"); + Host_Error ("CL_ParseServerMessage: Illegible server " + "message: %d\n", cmd); break; case svc_nop: @@ -1287,7 +1305,19 @@ CL_ParseServerMessage (void) break; case svc_setangle: - MSG_ReadAngleV (net_message, cl.viewangles); + if (!cls.demoplayback2) { + MSG_ReadAngleV (net_message, cl.viewangles); + } else { + j = MSG_ReadByte(net_message); + //fixangle |= 1 << j; + if (j != Cam_TrackNum()) { + MSG_ReadAngle (net_message); + MSG_ReadAngle (net_message); + MSG_ReadAngle (net_message); + } else { + MSG_ReadAngleV (net_message, cl.viewangles); + } + } // FIXME cl.viewangles[PITCH] = cl.viewangles[ROLL] = 0; break; @@ -1453,7 +1483,11 @@ CL_ParseServerMessage (void) break; case svc_nails: - CL_ParseProjectiles (); + CL_ParseProjectiles (false); + break; + + case svc_nails2: // FIXME from qwex + CL_ParseProjectiles (true); break; case svc_chokecount: // some preceding packets were choked