mirror of
https://github.com/Shpoike/Quakespasm.git
synced 2024-11-14 00:50:38 +00:00
0b30541ab9
This fixes the missing music in hipdemo1 caused by a leading space character before the cdtrack number, which ended up being parsed incorrectly. https://github.com/andrei-drexler/ironwail/issues/60 Note: original issue with fscanf (mentioned in the code comments) was caused by the inclusion of the newline in the format string. We work around it by reading the newline character separately.
483 lines
10 KiB
C
483 lines
10 KiB
C
/*
|
|
Copyright (C) 1996-2001 Id Software, Inc.
|
|
Copyright (C) 2010-2014 QuakeSpasm developers
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License
|
|
as published by the Free Software Foundation; either version 2
|
|
of the License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
|
|
See the GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
|
|
*/
|
|
|
|
#include "quakedef.h"
|
|
|
|
static void CL_FinishTimeDemo (void);
|
|
|
|
/*
|
|
==============================================================================
|
|
|
|
DEMO CODE
|
|
|
|
When a demo is playing back, all NET_SendMessages are skipped, and
|
|
NET_GetMessages are read from the demo file.
|
|
|
|
Whenever cl.time gets past the last received message, another message is
|
|
read from the demo file.
|
|
==============================================================================
|
|
*/
|
|
|
|
// from ProQuake: space to fill out the demo header for record at any time
|
|
static byte demo_head[3][MAX_MSGLEN];
|
|
static int demo_head_size[2];
|
|
|
|
/*
|
|
==============
|
|
CL_StopPlayback
|
|
|
|
Called when a demo file runs out, or the user starts a game
|
|
==============
|
|
*/
|
|
void CL_StopPlayback (void)
|
|
{
|
|
if (!cls.demoplayback)
|
|
return;
|
|
|
|
fclose (cls.demofile);
|
|
cls.demoplayback = false;
|
|
cls.demopaused = false;
|
|
cls.demofile = NULL;
|
|
cls.state = ca_disconnected;
|
|
|
|
if (cls.timedemo)
|
|
CL_FinishTimeDemo ();
|
|
}
|
|
|
|
/*
|
|
====================
|
|
CL_WriteDemoMessage
|
|
|
|
Dumps the current net message, prefixed by the length and view angles
|
|
====================
|
|
*/
|
|
static void CL_WriteDemoMessage (void)
|
|
{
|
|
int len;
|
|
int i;
|
|
float f;
|
|
|
|
len = LittleLong (net_message.cursize);
|
|
fwrite (&len, 4, 1, cls.demofile);
|
|
for (i = 0; i < 3; i++)
|
|
{
|
|
f = LittleFloat (cl.viewangles[i]);
|
|
fwrite (&f, 4, 1, cls.demofile);
|
|
}
|
|
fwrite (net_message.data, net_message.cursize, 1, cls.demofile);
|
|
fflush (cls.demofile);
|
|
}
|
|
|
|
static int CL_GetDemoMessage (void)
|
|
{
|
|
int r, i;
|
|
float f;
|
|
|
|
if (cls.demopaused)
|
|
return 0;
|
|
|
|
// decide if it is time to grab the next message
|
|
if (cls.signon == SIGNONS) // always grab until fully connected
|
|
{
|
|
if (cls.timedemo)
|
|
{
|
|
if (host_framecount == cls.td_lastframe)
|
|
return 0; // already read this frame's message
|
|
cls.td_lastframe = host_framecount;
|
|
// if this is the second frame, grab the real td_starttime
|
|
// so the bogus time on the first frame doesn't count
|
|
if (host_framecount == cls.td_startframe + 1)
|
|
cls.td_starttime = realtime;
|
|
}
|
|
else if (/* cl.time > 0 && */ cl.time <= cl.mtime[0])
|
|
{
|
|
return 0; // don't need another message yet
|
|
}
|
|
}
|
|
|
|
// get the next message
|
|
fread (&net_message.cursize, 4, 1, cls.demofile);
|
|
VectorCopy (cl.mviewangles[0], cl.mviewangles[1]);
|
|
for (i = 0 ; i < 3 ; i++)
|
|
{
|
|
r = fread (&f, 4, 1, cls.demofile);
|
|
cl.mviewangles[0][i] = LittleFloat (f);
|
|
}
|
|
|
|
net_message.cursize = LittleLong (net_message.cursize);
|
|
if (net_message.cursize > MAX_MSGLEN)
|
|
Sys_Error ("Demo message > MAX_MSGLEN");
|
|
r = fread (net_message.data, net_message.cursize, 1, cls.demofile);
|
|
if (r != 1)
|
|
{
|
|
CL_StopPlayback ();
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
====================
|
|
CL_GetMessage
|
|
|
|
Handles recording and playback of demos, on top of NET_ code
|
|
====================
|
|
*/
|
|
int CL_GetMessage (void)
|
|
{
|
|
int r;
|
|
|
|
if (cls.demoplayback)
|
|
return CL_GetDemoMessage ();
|
|
|
|
while (1)
|
|
{
|
|
r = NET_GetMessage (cls.netcon);
|
|
|
|
if (r != 1 && r != 2)
|
|
return r;
|
|
|
|
// discard nop keepalive message
|
|
if (net_message.cursize == 1 && net_message.data[0] == svc_nop)
|
|
Con_Printf ("<-- server to client keepalive\n");
|
|
else
|
|
break;
|
|
}
|
|
|
|
if (cls.demorecording)
|
|
CL_WriteDemoMessage ();
|
|
|
|
if (cls.signon < 2)
|
|
{
|
|
// record messages before full connection, so that a
|
|
// demo record can happen after connection is done
|
|
memcpy(demo_head[cls.signon], net_message.data, net_message.cursize);
|
|
demo_head_size[cls.signon] = net_message.cursize;
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
|
|
/*
|
|
====================
|
|
CL_Stop_f
|
|
|
|
stop recording a demo
|
|
====================
|
|
*/
|
|
void CL_Stop_f (void)
|
|
{
|
|
if (cmd_source != src_command)
|
|
return;
|
|
|
|
if (!cls.demorecording)
|
|
{
|
|
Con_Printf ("Not recording a demo.\n");
|
|
return;
|
|
}
|
|
|
|
// write a disconnect message to the demo file
|
|
SZ_Clear (&net_message);
|
|
MSG_WriteByte (&net_message, svc_disconnect);
|
|
CL_WriteDemoMessage ();
|
|
|
|
// finish up
|
|
fclose (cls.demofile);
|
|
cls.demofile = NULL;
|
|
cls.demorecording = false;
|
|
Con_Printf ("Completed demo\n");
|
|
|
|
// ericw -- update demo tab-completion list
|
|
DemoList_Rebuild ();
|
|
}
|
|
|
|
/*
|
|
====================
|
|
CL_Record_f
|
|
|
|
record <demoname> <map> [cd track]
|
|
====================
|
|
*/
|
|
void CL_Record_f (void)
|
|
{
|
|
int c;
|
|
char name[MAX_OSPATH];
|
|
int track;
|
|
|
|
if (cmd_source != src_command)
|
|
return;
|
|
|
|
if (cls.demoplayback)
|
|
{
|
|
Con_Printf ("Can't record during demo playback\n");
|
|
return;
|
|
}
|
|
|
|
if (cls.demorecording)
|
|
CL_Stop_f();
|
|
|
|
c = Cmd_Argc();
|
|
if (c != 2 && c != 3 && c != 4)
|
|
{
|
|
Con_Printf ("record <demoname> [<map> [cd track]]\n");
|
|
return;
|
|
}
|
|
|
|
if (strstr(Cmd_Argv(1), ".."))
|
|
{
|
|
Con_Printf ("Relative pathnames are not allowed.\n");
|
|
return;
|
|
}
|
|
|
|
if (c == 2 && cls.state == ca_connected)
|
|
{
|
|
#if 0
|
|
Con_Printf("Can not record - already connected to server\nClient demo recording must be started before connecting\n");
|
|
return;
|
|
#endif
|
|
if (cls.signon < 2)
|
|
{
|
|
Con_Printf("Can't record - try again when connected\n");
|
|
return;
|
|
}
|
|
}
|
|
|
|
// write the forced cd track number, or -1
|
|
if (c == 4)
|
|
{
|
|
track = atoi(Cmd_Argv(3));
|
|
Con_Printf ("Forcing CD track to %i\n", cls.forcetrack);
|
|
}
|
|
else
|
|
{
|
|
track = -1;
|
|
}
|
|
|
|
q_snprintf (name, sizeof(name), "%s/%s", com_gamedir, Cmd_Argv(1));
|
|
|
|
// start the map up
|
|
if (c > 2)
|
|
{
|
|
Cmd_ExecuteString ( va("map %s", Cmd_Argv(2)), src_command);
|
|
if (cls.state != ca_connected)
|
|
return;
|
|
}
|
|
|
|
// open the demo file
|
|
COM_AddExtension (name, ".dem", sizeof(name));
|
|
|
|
Con_Printf ("recording to %s.\n", name);
|
|
cls.demofile = fopen (name, "wb");
|
|
if (!cls.demofile)
|
|
{
|
|
Con_Printf ("ERROR: couldn't create %s\n", name);
|
|
return;
|
|
}
|
|
|
|
cls.forcetrack = track;
|
|
fprintf (cls.demofile, "%i\n", cls.forcetrack);
|
|
|
|
cls.demorecording = true;
|
|
|
|
// from ProQuake: initialize the demo file if we're already connected
|
|
if (c == 2 && cls.state == ca_connected)
|
|
{
|
|
byte *data = net_message.data;
|
|
int cursize = net_message.cursize;
|
|
int i;
|
|
|
|
for (i = 0; i < 2; i++)
|
|
{
|
|
net_message.data = demo_head[i];
|
|
net_message.cursize = demo_head_size[i];
|
|
CL_WriteDemoMessage();
|
|
}
|
|
|
|
net_message.data = demo_head[2];
|
|
SZ_Clear (&net_message);
|
|
|
|
// current names, colors, and frag counts
|
|
for (i = 0; i < cl.maxclients; i++)
|
|
{
|
|
MSG_WriteByte (&net_message, svc_updatename);
|
|
MSG_WriteByte (&net_message, i);
|
|
MSG_WriteString (&net_message, cl.scores[i].name);
|
|
MSG_WriteByte (&net_message, svc_updatefrags);
|
|
MSG_WriteByte (&net_message, i);
|
|
MSG_WriteShort (&net_message, cl.scores[i].frags);
|
|
MSG_WriteByte (&net_message, svc_updatecolors);
|
|
MSG_WriteByte (&net_message, i);
|
|
MSG_WriteByte (&net_message, cl.scores[i].colors);
|
|
}
|
|
|
|
// send all current light styles
|
|
for (i = 0; i < MAX_LIGHTSTYLES; i++)
|
|
{
|
|
MSG_WriteByte (&net_message, svc_lightstyle);
|
|
MSG_WriteByte (&net_message, i);
|
|
MSG_WriteString (&net_message, cl_lightstyle[i].map);
|
|
}
|
|
|
|
// what about the CD track or SVC fog... future consideration.
|
|
MSG_WriteByte (&net_message, svc_updatestat);
|
|
MSG_WriteByte (&net_message, STAT_TOTALSECRETS);
|
|
MSG_WriteLong (&net_message, cl.stats[STAT_TOTALSECRETS]);
|
|
|
|
MSG_WriteByte (&net_message, svc_updatestat);
|
|
MSG_WriteByte (&net_message, STAT_TOTALMONSTERS);
|
|
MSG_WriteLong (&net_message, cl.stats[STAT_TOTALMONSTERS]);
|
|
|
|
MSG_WriteByte (&net_message, svc_updatestat);
|
|
MSG_WriteByte (&net_message, STAT_SECRETS);
|
|
MSG_WriteLong (&net_message, cl.stats[STAT_SECRETS]);
|
|
|
|
MSG_WriteByte (&net_message, svc_updatestat);
|
|
MSG_WriteByte (&net_message, STAT_MONSTERS);
|
|
MSG_WriteLong (&net_message, cl.stats[STAT_MONSTERS]);
|
|
|
|
// view entity
|
|
MSG_WriteByte (&net_message, svc_setview);
|
|
MSG_WriteShort (&net_message, cl.viewentity);
|
|
|
|
// signon
|
|
MSG_WriteByte (&net_message, svc_signonnum);
|
|
MSG_WriteByte (&net_message, 3);
|
|
|
|
CL_WriteDemoMessage();
|
|
|
|
// restore net_message
|
|
net_message.data = data;
|
|
net_message.cursize = cursize;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
====================
|
|
CL_PlayDemo_f
|
|
|
|
play [demoname]
|
|
====================
|
|
*/
|
|
void CL_PlayDemo_f (void)
|
|
{
|
|
char name[MAX_OSPATH];
|
|
|
|
if (cmd_source != src_command)
|
|
return;
|
|
|
|
if (Cmd_Argc() != 2)
|
|
{
|
|
Con_Printf ("playdemo <demoname> : plays a demo\n");
|
|
return;
|
|
}
|
|
|
|
// disconnect from server
|
|
CL_Disconnect ();
|
|
|
|
// open the demo file
|
|
q_strlcpy (name, Cmd_Argv(1), sizeof(name));
|
|
COM_AddExtension (name, ".dem", sizeof(name));
|
|
|
|
Con_Printf ("Playing demo from %s.\n", name);
|
|
|
|
COM_FOpenFile (name, &cls.demofile, NULL);
|
|
if (!cls.demofile)
|
|
{
|
|
Con_Printf ("ERROR: couldn't open %s\n", name);
|
|
cls.demonum = -1; // stop demo loop
|
|
return;
|
|
}
|
|
|
|
// ZOID, fscanf is evil
|
|
// O.S.: if a space character e.g. 0x20 (' ') follows '\n',
|
|
// fscanf skips that byte too and screws up further reads.
|
|
// fscanf (cls.demofile, "%i\n", &cls.forcetrack);
|
|
if (fscanf (cls.demofile, "%i", &cls.forcetrack) != 1 || fgetc (cls.demofile) != '\n')
|
|
{
|
|
fclose (cls.demofile);
|
|
cls.demofile = NULL;
|
|
cls.demonum = -1; // stop demo loop
|
|
Con_Printf ("ERROR: demo \"%s\" is invalid\n", name);
|
|
return;
|
|
}
|
|
|
|
cls.demoplayback = true;
|
|
cls.demopaused = false;
|
|
cls.state = ca_connected;
|
|
|
|
// get rid of the menu and/or console
|
|
key_dest = key_game;
|
|
}
|
|
|
|
/*
|
|
====================
|
|
CL_FinishTimeDemo
|
|
|
|
====================
|
|
*/
|
|
static void CL_FinishTimeDemo (void)
|
|
{
|
|
int frames;
|
|
float time;
|
|
|
|
cls.timedemo = false;
|
|
|
|
// the first frame didn't count
|
|
frames = (host_framecount - cls.td_startframe) - 1;
|
|
time = realtime - cls.td_starttime;
|
|
if (!time)
|
|
time = 1;
|
|
Con_Printf ("%i frames %5.1f seconds %5.1f fps\n", frames, time, frames/time);
|
|
}
|
|
|
|
/*
|
|
====================
|
|
CL_TimeDemo_f
|
|
|
|
timedemo [demoname]
|
|
====================
|
|
*/
|
|
void CL_TimeDemo_f (void)
|
|
{
|
|
if (cmd_source != src_command)
|
|
return;
|
|
|
|
if (Cmd_Argc() != 2)
|
|
{
|
|
Con_Printf ("timedemo <demoname> : gets demo speeds\n");
|
|
return;
|
|
}
|
|
|
|
CL_PlayDemo_f ();
|
|
if (!cls.demofile)
|
|
return;
|
|
|
|
// cls.td_starttime will be grabbed at the second frame of the demo, so
|
|
// all the loading time doesn't get counted
|
|
|
|
cls.timedemo = true;
|
|
cls.td_startframe = host_framecount;
|
|
cls.td_lastframe = -1; // get a new message this frame
|
|
}
|
|
|