#include "quakedef.h" #ifndef CLIENTONLY #ifdef SERVER_DEMO_PLAYBACK void NPP_MVDWriteByte(qbyte data, client_t *to, int broadcast); void SV_New_f (void); /* float svdemotime; FILE *svdemofile; void SV_WriteDemoMessage (sizebuf_t *msg) { short len; float time; if (!svs.demorecording) return; time = LittleFloat(sv.time); fwrite(&time, 1, sizeof(time), svdemofile); len = LittleShort((short)msg->cursize); fwrite(&len, 1, sizeof(len), svdemofile); fwrite(msg->data, 1, msg->cursize, svdemofile); } qboolean SV_GetDemoMessage (void) { short len; float time; if (sv.time < svdemotime) { sv.msgfromdemo = false; return NET_GetPacket(NS_SERVER); } sv.msgfromdemo = true; fread(&len, 1, sizeof(len), svdemofile); net_message.cursize = LittleShort(len); fread(net_message.data, 1, net_message.cursize, svdemofile); sv.time = svdemotime; if (!fread(&time, 1, sizeof(time), svdemofile)) { svs.demoplayback = false; fclose(svdemofile); } svdemotime = LittleFloat(time); net_from.ip[0] = 0; net_from.ip[1] = 0; net_from.ip[2] = 0; net_from.ip[3] = 0; return true; } qboolean SV_GetPacket (void) { if (svs.demoplayback) return SV_GetDemoMessage (); if (!NET_GetPacket (NS_SERVER)) return false; SV_WriteDemoMessage (&net_message); return true; } void SV_RecordDemo_f (void) { client_t *c; int clnum; int i; char *name; char *mapname; name = Cmd_Argv(1); mapname = Cmd_Argv(2); svdemofile = fopen(name, "wb"); if (!svdemofile) { Con_Printf("Failed to open output file\n"); return; } fwrite(mapname, 1, sizeof(char)*(strlen(mapname)+1), svdemofile); for (clnum = 0; clnum < svs.allocated_client_slots; clnum++) //clear the server so the clients reconnect and send nice fresh messages. { c = &svs.clients[clnum]; if (c->state < cs_connected) continue; ClientReliableWrite_Begin (c, svc_stufftext, 2+strlen("reconnect\n")); ClientReliableWrite_String (c, "disconnect;wait;reconnect\n"); c->drop = true; } SV_SendMessagesToAll (); svs.demorecording = true; i = predictablerandgetseed(); fwrite(&i, 1, sizeof(i), svdemofile); SV_SpawnServer(mapname, NULL, false, false); } void SV_LoadClientDemo (void); void SV_PlayDemo_f(void) { client_t *c; int clnum; int i; char *name; float time; char mapname[64]; name = Cmd_Argv(1); if (svdemofile) fclose(svdemofile); svs.demoplayback=false; svs.demorecording=false; COM_FOpenFile(name, &svdemofile); if (!svdemofile) { Con_Printf("Failed to open input file\n"); return; } #ifndef SERVERONLY CL_Disconnect(); #endif i = 0; do { fread(mapname+i, 1, sizeof(char), svdemofile); i++; } while (mapname[i-1]); svs.demoplayback = true; for (clnum = 0; clnum < svs.allocated_client_slots; clnum++) //clear the server so new clients don't conflict. { c = &svs.clients[clnum]; if (c->state < cs_connected) continue; ClientReliableWrite_Begin (c, svc_stufftext, 2+strlen("reconnect\n")); ClientReliableWrite_String (c, "reconnect\n"); c->drop = true; } SV_SendMessagesToAll (); fread(&i, 1, sizeof(i), svdemofile); predictablesrand(i); fread(&time, 1, sizeof(time), svdemofile); svdemotime = LittleFloat(time); SV_SpawnServer(mapname, NULL, false, false); } */ qboolean SV_GetPacket (void) { return NET_GetPacket (NS_SERVER); } #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 #define svd sv //char empty[512]; qboolean SV_ReadMVD (void); #ifdef SERVERONLY float nextdemotime = 0; float olddemotime = 0; #else extern float nextdemotime; extern float olddemotime; #endif void SV_LoadClientDemo_f (void) { int i; char demoname[MAX_OSPATH]; client_t *ohc; if (Cmd_Argc() < 2) { Con_Printf("%s : play a server side multi-view demo\n", Cmd_Argv(0)); return; } if (svd.demofile) { Con_Printf ("Ending old demo\n"); VFS_CLOSE(svd.demofile); svd.demofile = NULL; SV_ReadMVD(); } Q_strncpyz(demoname, Cmd_Argv(1), sizeof(demoname)); if (!sv.state) Cmd_ExecuteString("map start\n", Cmd_ExecLevel); //go for the start map if (!sv.state) { Con_Printf("Could not activate server\n"); return; } svd.demofile = FS_OpenVFS(demoname, "rb", FS_GAME); if (!svd.demofile) //try with a different path svd.demofile = FS_OpenVFS(va("demos/%s", demoname), "rb", FS_GAME); com_filesize = VFS_GETLEN(svd.demofile); if (!svd.demofile) { Con_Printf("Failed to open %s\n", demoname); return; } if (com_filesize <= 0) { Con_Printf("Failed to open %s\n", demoname); VFS_CLOSE(svd.demofile); svd.demofile = NULL; return; } if (sv.demostate) { sv.demostatevalid = false; memset(sv.demostate, 0, sizeof(entity_state_t)*MAX_EDICTS); } /* for (i = 0; i < svs.allocated_client_slots; i++) { host_client = &svs.clients[i]; if (host_client->state == cs_spawned) host_client->state = cs_connected; } */ // SV_BroadcastCommand ("changing\n"); #ifndef SERVERONLY CL_Disconnect(); #endif svd.mvdplayback = true; Con_Printf("Playing from %s\n", demoname); for (i = 0; i < MAX_SIGNON_BUFFERS; i++) sv.demosignon_buffer_size[i] = 0; sv.demosignon.maxsize = sizeof(sv.demosignon_buffers[0]); sv.demosignon.data = sv.demosignon_buffers[0]; sv.demosignon.cursize = 0; sv.num_demosignon_buffers = 1; sv.democausesreconnect = false; *sv.demname = '\0'; svd.lasttype = dem_read; svd.realtime = realtime; nextdemotime = realtime-0.1; //cause read of the first 0.1 secs to get all spawn info. olddemotime = realtime; while (SV_ReadMVD()) { sv.datagram.cursize = 0; sv.reliable_datagram.cursize = 0; } //if we did need reconnect, continue needing it cos I can't be bothered to play with multiple buffers etc. // if (memcmp(sv.demmodel_precache, sv.model_precache, sizeof(sv.model_precache)) || memcmp(sv.demsound_precache, sv.sound_precache, sizeof(sv.sound_precache))) sv.democausesreconnect = true; if (sv.democausesreconnect) { svs.spawncount++; SV_BroadcastCommand ("changing\n"); //but this arrives BEFORE the serverdata ohc = host_client; for (i=0, host_client = svs.clients ; istate != cs_spawned) continue; host_client->state = cs_connected; host_client->istobeloaded = true; //don't harm the ent SV_New_f (); } host_client = ohc; } return; } qboolean SV_RunDemo (void) { int r, i; float demotime; qbyte c; // usercmd_t *pcmd; // usercmd_t emptycmd; qbyte newtime; readnext: // read the time from the packet if (svd.mvdplayback) { VFS_READ(svd.demofile, &newtime, sizeof(newtime)); nextdemotime = olddemotime + newtime * (1/1000.0f); demotime = nextdemotime; if (nextdemotime > svd.realtime) { VFS_SEEK(svd.demofile, VFS_TELL(svd.demofile) - sizeof(newtime)); return false; } else if (nextdemotime + 0.1 < svd.realtime) demotime = svd.realtime; //we froze too long.. ? } else { VFS_READ(svd.demofile, &demotime, sizeof(demotime)); demotime = LittleFloat(demotime); if (!nextdemotime) svd.realtime = nextdemotime = demotime; } // decide if it is time to grab the next message if (!sv.paused) { // always grab until fully connected if (!svd.mvdplayback) { if (svd.realtime + 1.0 < demotime) { // too far back svd.realtime = demotime - 1.0; // rewind back to time VFS_SEEK(svd.demofile, VFS_TELL(svd.demofile) - sizeof(demotime)); return false; } else if (nextdemotime < demotime) { // rewind back to time VFS_SEEK(svd.demofile, VFS_TELL(svd.demofile) - sizeof(demotime)); return false; // don't need another message yet } } } else { svd.realtime = demotime; // we're warping } olddemotime = demotime; // get the msg type if ((r = VFS_READ(svd.demofile, &c, sizeof(c))) != sizeof(c)) { Con_Printf ("Unexpected end of demo\n"); VFS_CLOSE(svd.demofile); svd.demofile = NULL; return false; // SV_Error ("Unexpected end of demo"); } switch (c & 7) { case dem_cmd : Con_Printf ("dem_cmd not supported\n"); VFS_CLOSE(svd.demofile); svd.demofile = NULL; return false; // user sent input // i = svd.netchan.outgoing_sequence & UPDATE_MASK; // pcmd = &cl.frames[i].cmd; // if ((r = fread (&emptycmd, sizeof(emptycmd), 1, svd.demofile)) != 1) // SV_Error ("Corrupted demo"); /* // qbyte order stuff for (j = 0; j < 3; j++) pcmd->angles[j] = LittleFloat(pcmd->angles[j]); pcmd->forwardmove = LittleShort(pcmd->forwardmove); pcmd->sidemove = LittleShort(pcmd->sidemove); pcmd->upmove = LittleShort(pcmd->upmove); cl.frames[i].senttime = demotime; cl.frames[i].receivedtime = -1; // we haven't gotten a reply yet svd.netchan.outgoing_sequence++; */ // fread (&emptycmd, 12, 1, svd.demofile); /* for (j = 0; j < 3; j++) cl.viewangles[i] = LittleFloat (cl.viewangles[i]); if (cl.spectator) Cam_TryLock (); */ goto readnext; case dem_read: readit: // get the next message VFS_READ (svd.demofile, &net_message.cursize, 4); net_message.cursize = LittleLong (net_message.cursize); if (!svd.mvdplayback && net_message.cursize > MAX_QWMSGLEN + 8) SV_Error ("Demo message > MAX_MSGLEN + 8"); else if (svd.mvdplayback && net_message.cursize > net_message.maxsize) SV_Error ("Demo message > MAX_UDP_PACKET"); if ((r = VFS_READ(svd.demofile, net_message.data, net_message.cursize)) != net_message.cursize) SV_Error ("Corrupted demo"); /* if (svd.mvdplayback) { tracknum = Cam_TrackNum(); if (svd.lasttype == dem_multiple) { if (tracknum == -1) goto readnext; if (!(svd.lastto & (1 << (tracknum)))) goto readnext; } else if (svd.lasttype == dem_single) { if (tracknum == -1 || svd.lastto != spec_track) goto readnext; } } */ return true; case dem_set: VFS_READ(svd.demofile, &i, 4); VFS_READ(svd.demofile, &i, 4); goto readnext; case dem_multiple: if ((r = VFS_READ(svd.demofile, &i, 4)) != 1) SV_Error ("Corrupted demo"); svd.lastto = LittleLong(i); svd.lasttype = dem_multiple; goto readit; case dem_single: svd.lastto = c >> 3; svd.lasttype = dem_single; goto readit; case dem_stats: svd.lastto = c >> 3; svd.lasttype = dem_stats; goto readit; case dem_all: svd.lastto = 0; svd.lasttype = dem_all; goto readit; default : SV_Error ("Corrupted demo"); return false; } } qboolean SV_ReadMVD (void) { int i, c; client_t *cl; int oldsc = svs.spawncount; if (!svd.demofile) { if (sv.demostate) BZ_Free(sv.demostate); sv.demostate=NULL; sv.demostatevalid = false; if (sv.democausesreconnect) { sv.democausesreconnect = false; svs.spawncount++; for (i=0, host_client = svs.clients ; istate != cs_spawned) continue; host_client->state = cs_connected; host_client->istobeloaded = true; //don't harm the ent SV_New_f (); } } nextdemotime = realtime; return false; } svd.realtime = realtime; if (!SV_RunDemo()) { if (!svd.demofile) { //demo ended. for (i=0, cl = svs.clients ; isendinfo = true; } } return false; } if (!svd.mvdplayback) //broadcast all. { for (c = 0; c < net_message.cursize; c++) NPP_MVDWriteByte(net_message.data[c], NULL, true); NPP_MVDForceFlush(); } else { switch(svd.lasttype) { default: Con_Printf("Bad sv.lasttype %i\n", sv.lasttype); break; case dem_set: //Unknown stuff. (Got to work out what this is for) case dem_read: //baseline stuff case dem_stats: //contains info read by server case dem_all: //broadcast things (like userinfo) case dem_multiple: //treat these as broadcast (tempents should be treated correctly) for (c = 0; c < net_message.cursize; c++) NPP_MVDWriteByte(net_message.data[c], NULL, true); NPP_MVDForceFlush(); break; // case dem_read: //baseline stuff case dem_single: for (i=0, cl = svs.clients ; istate) continue; // if (!(1 >> 3 & svd.lastto)) if (!cl->spec_track) continue; if (!(cl->spec_track >> 3 & svd.lastto)) continue; for (c = 0; c < net_message.cursize; c++) NPP_MVDWriteByte(net_message.data[c], cl, false); NPP_MVDForceFlush(); } break; } } if (oldsc != svs.spawncount) { VFS_CLOSE(svd.demofile); svd.demofile = NULL; for (i=0, host_client = svs.clients ; istate != cs_spawned) continue; host_client->state = cs_connected; host_client->istobeloaded = true; //don't harm the ent SV_New_f (); } return true; } return true; } void SV_Demo_Init(void) { Cmd_AddCommand("playmvd", SV_LoadClientDemo_f); Cmd_AddCommand("mvdplay", SV_LoadClientDemo_f); Cmd_AddCommand("svplay", SV_PlayDemo_f); Cmd_AddCommand("svrecord", SV_RecordDemo_f); } #endif //SERVER_DEMO_PLAYBACK #endif //CLIENTONLY