added a new demo player with fast seeking support

added cl_demoPlayer and cl_escapeAbortsDemo
This commit is contained in:
myT 2022-11-04 05:00:39 +01:00
parent 9f90a6ee8b
commit f401f742ee
24 changed files with 1530 additions and 54 deletions

View file

@ -4,6 +4,13 @@ See the end of this file for known issues.
DD Mmm 20 - 1.53 DD Mmm 20 - 1.53
add: cl_demoPlayer <0|1> (default: 1) selects the demo playback system to use
cl_demoPlayer 0 = always uses the original demo player
cl_demoPlayer 1 = uses the new demo player with time rewind support when the mod supports it
add: cl_escapeAbortsDemo <0|1> (default: 1) decides whether the escape key can abort demo playback
reminder: you can always use /disconnect to stop demo playback
add: r_depthClamp <0|1> (default: 0) disables vertex clipping against the near and far clip planes add: r_depthClamp <0|1> (default: 0) disables vertex clipping against the near and far clip planes
enabling this feature will raise the maximum allowed FoV value of CPMA 1.53 enabling this feature will raise the maximum allowed FoV value of CPMA 1.53
it turns the view frustum into a pyramid and prevents any vertex between the near clip plane it turns the view frustum into a pyramid and prevents any vertex between the near clip plane
@ -43,8 +50,8 @@ add: r_alphaToCoverageMipBoost <0.0 to 0.5> (default: 0.125) boosts the alpha va
with A2C enabled, it prevents alpha-tested surfaces from fading (too much) in the distance with A2C enabled, it prevents alpha-tested surfaces from fading (too much) in the distance
chg: r_gpuMipGen 0 now respects r_mipGenFilter but not r_mipGenGamma (gamma 2 is used) chg: r_gpuMipGen 0 now respects r_mipGenFilter but not r_mipGenGamma (gamma 2 is used)
the new code will slow down map loads in general but produces higher quality results the new code will slow down map loads in general but produces higher quality results
r_mipGenFilter BL is a special case without gamma correction that's much faster than before r_mipGenFilter BL is a special case without gamma correction that's much faster than before
chg: image load/save errors now print warnings instead of triggering drop errors chg: image load/save errors now print warnings instead of triggering drop errors

View file

@ -648,7 +648,7 @@ static qbool CloseAVI( closeMode_t closeMode )
Z_Free( afd.eBuffer ); Z_Free( afd.eBuffer );
FS_FCloseFile( afd.f ); FS_FCloseFile( afd.f );
Com_Printf( "Wrote %d:%d frames to %s\n", afd.numVideoFrames, afd.numAudioFrames, afd.fileName ); Com_Printf( "Wrote %d:%d V:A frames to %s\n", afd.numVideoFrames, afd.numAudioFrames, afd.fileName );
if ( closeMode == CM_SEQUENCE_COMPLETE ) if ( closeMode == CM_SEQUENCE_COMPLETE )
S_StopAllSounds(); S_StopAllSounds();

View file

@ -94,6 +94,11 @@ static int CL_GetCurrentCmdNumber()
static void CL_GetCurrentSnapshotNumber( int* snapshotNumber, int* serverTime ) static void CL_GetCurrentSnapshotNumber( int* snapshotNumber, int* serverTime )
{ {
if ( clc.newDemoPlayer ) {
CL_NDP_GetCurrentSnapshotNumber( snapshotNumber, serverTime );
return;
}
*snapshotNumber = cl.snap.messageNum; *snapshotNumber = cl.snap.messageNum;
*serverTime = cl.snap.serverTime; *serverTime = cl.snap.serverTime;
} }
@ -101,6 +106,10 @@ static void CL_GetCurrentSnapshotNumber( int* snapshotNumber, int* serverTime )
static qbool CL_GetSnapshot( int snapshotNumber, snapshot_t* snapshot ) static qbool CL_GetSnapshot( int snapshotNumber, snapshot_t* snapshot )
{ {
if ( clc.newDemoPlayer ) {
return CL_NDP_GetSnapshot( snapshotNumber, snapshot );
}
int i, count; int i, count;
if ( snapshotNumber > cl.snap.messageNum ) { if ( snapshotNumber > cl.snap.messageNum ) {
@ -154,7 +163,7 @@ static void CL_SetUserCmdValue( int userCmdValue, float sensitivityScale )
} }
static void CL_ConfigstringModified() void CL_ConfigstringModified()
{ {
int index = atoi( Cmd_Argv(1) ); int index = atoi( Cmd_Argv(1) );
if ( index < 0 || index >= MAX_CONFIGSTRINGS ) { if ( index < 0 || index >= MAX_CONFIGSTRINGS ) {
@ -209,6 +218,10 @@ static void CL_ConfigstringModified()
static qbool CL_GetServerCommand( int serverCommandNumber ) static qbool CL_GetServerCommand( int serverCommandNumber )
{ {
if ( clc.newDemoPlayer ) {
return CL_NDP_GetServerCommand( serverCommandNumber );
}
static char bigConfigString[BIG_INFO_STRING]; static char bigConfigString[BIG_INFO_STRING];
// if we have irretrievably lost a reliable command, drop the connection // if we have irretrievably lost a reliable command, drop the connection
@ -306,6 +319,7 @@ static void CL_CM_LoadMap( const char* mapname )
void CL_ShutdownCGame() void CL_ShutdownCGame()
{ {
Com_Memset( cls.cgvmCalls, 0, sizeof(cls.cgvmCalls) );
cls.keyCatchers &= ~KEYCATCH_CGAME; cls.keyCatchers &= ~KEYCATCH_CGAME;
cls.cgameStarted = qfalse; cls.cgameStarted = qfalse;
cls.cgameForwardInput = 0; cls.cgameForwardInput = 0;
@ -336,6 +350,12 @@ static qbool CL_CG_GetValue( char* value, int valueSize, const char* key )
{ "trap_MatchAlertEvent", CG_EXT_MATCHALERTEVENT }, { "trap_MatchAlertEvent", CG_EXT_MATCHALERTEVENT },
{ "trap_Error2", CG_EXT_ERROR2 }, { "trap_Error2", CG_EXT_ERROR2 },
{ "trap_IsRecordingDemo", CG_EXT_ISRECORDINGDEMO }, { "trap_IsRecordingDemo", CG_EXT_ISRECORDINGDEMO },
{ "trap_CNQ3_NDP_Enable", CG_EXT_NDP_ENABLE },
{ "trap_CNQ3_NDP_Seek", CG_EXT_NDP_SEEK },
{ "trap_CNQ3_NDP_ReadUntil", CG_EXT_NDP_READUNTIL },
{ "trap_CNQ3_NDP_StartVideo", CG_EXT_NDP_STARTVIDEO },
{ "trap_CNQ3_NDP_StopVideo", CG_EXT_NDP_STOPVIDEO },
{ "trap_CNQ3_R_RenderScene", CG_EXT_R_RENDERSCENE },
// commands // commands
{ "screenshotnc", 1 }, { "screenshotnc", 1 },
{ "screenshotncJPEG", 1 }, { "screenshotncJPEG", 1 },
@ -514,7 +534,7 @@ static intptr_t CL_CgameSystemCalls( intptr_t *args )
re.AddLightToScene( VMA(1), VMF(2), VMF(3), VMF(4), VMF(5) ); re.AddLightToScene( VMA(1), VMF(2), VMF(3), VMF(4), VMF(5) );
return 0; return 0;
case CG_R_RENDERSCENE: case CG_R_RENDERSCENE:
re.RenderScene( VMA(1) ); re.RenderScene( VMA(1), 0 );
return 0; return 0;
case CG_R_SETCOLOR: case CG_R_SETCOLOR:
re.SetColor( VMA(1) ); re.SetColor( VMA(1) );
@ -656,6 +676,38 @@ static intptr_t CL_CgameSystemCalls( intptr_t *args )
case CG_EXT_ISRECORDINGDEMO: case CG_EXT_ISRECORDINGDEMO:
return clc.demorecording; return clc.demorecording;
case CG_EXT_NDP_ENABLE:
if( clc.demoplaying && cl_demoPlayer->integer ) {
cls.cgameNewDemoPlayer = qtrue;
cls.cgvmCalls[CGVM_NDP_ANALYZE_COMMAND] = args[1];
cls.cgvmCalls[CGVM_NDP_GENERATE_COMMANDS] = args[2];
cls.cgvmCalls[CGVM_NDP_IS_CS_NEEDED] = args[3];
cls.cgvmCalls[CGVM_NDP_ANALYZE_SNAPSHOT] = args[4];
cls.cgvmCalls[CGVM_NDP_END_ANALYSIS] = args[5];
return qtrue;
} else {
return qfalse;
}
case CG_EXT_NDP_SEEK:
return CL_NDP_Seek( args[1] );
case CG_EXT_NDP_READUNTIL:
CL_NDP_ReadUntil( args[1] );
return 0;
case CG_EXT_NDP_STARTVIDEO:
Cvar_Set( cl_aviFrameRate->name, va( "%d", (int)args[2] ) );
return CL_OpenAVIForWriting( va( "videos/%s", (const char*)VMA(1) ), qfalse );
case CG_EXT_NDP_STOPVIDEO:
CL_CloseAVI();
return 0;
case CG_EXT_R_RENDERSCENE:
re.RenderScene( VMA(1), args[2] );
return 0;
default: default:
Com_Error( ERR_DROP, "Bad cgame system trap: %i", args[0] ); Com_Error( ERR_DROP, "Bad cgame system trap: %i", args[0] );
} }
@ -691,6 +743,7 @@ void CL_InitCGame()
int t = Sys_Milliseconds(); int t = Sys_Milliseconds();
cls.cgameForwardInput = 0; cls.cgameForwardInput = 0;
cls.cgameNewDemoPlayer = qfalse;
// put away the console // put away the console
Con_Close(); Con_Close();
@ -846,6 +899,11 @@ static void CL_FirstSnapshot()
void CL_SetCGameTime() void CL_SetCGameTime()
{ {
if ( clc.newDemoPlayer ) {
CL_NDP_SetCGameTime();
return;
}
// getting a valid frame message ends the connection process // getting a valid frame message ends the connection process
if ( cls.state != CA_ACTIVE ) { if ( cls.state != CA_ACTIVE ) {
if ( cls.state != CA_PRIMED ) { if ( cls.state != CA_PRIMED ) {
@ -946,3 +1004,45 @@ void CL_SetCGameTime()
} }
void CL_CGNDP_AnalyzeCommand( int serverTime )
{
Q_assert(cls.cgameNewDemoPlayer);
VM_Call(cgvm, cls.cgvmCalls[CGVM_NDP_ANALYZE_COMMAND], serverTime);
}
void CL_CGNDP_GenerateCommands( const char** commands, int* numCommandBytes )
{
Q_assert(cls.cgameNewDemoPlayer);
Q_assert(commands);
Q_assert(numCommandBytes);
VM_Call(cgvm, cls.cgvmCalls[CGVM_NDP_GENERATE_COMMANDS]);
*numCommandBytes = *(int*)interopBufferIn;
*commands = (const char*)interopBufferIn + 4;
}
qbool CL_CGNDP_IsConfigStringNeeded( int csIndex )
{
Q_assert(cls.cgameNewDemoPlayer);
Q_assert(csIndex >= 0 && csIndex < MAX_CONFIGSTRINGS);
return (qbool)VM_Call(cgvm, cls.cgvmCalls[CGVM_NDP_IS_CS_NEEDED], csIndex);
}
void CL_CGNDP_AnalyzeSnapshot( int progress )
{
Q_assert(cls.cgameNewDemoPlayer);
Q_assert(progress >= 0 && progress < 100);
VM_Call(cgvm, cls.cgvmCalls[CGVM_NDP_ANALYZE_SNAPSHOT], progress);
}
void CL_CGNDP_EndAnalysis( const char* filePath, int firstServerTime, int lastServerTime, qbool videoRestart )
{
Q_assert(cls.cgameNewDemoPlayer);
Q_assert(lastServerTime > firstServerTime);
Q_strncpyz((char*)interopBufferOut, filePath, interopBufferOutSize);
VM_Call(cgvm, cls.cgvmCalls[CGVM_NDP_END_ANALYSIS], firstServerTime, lastServerTime, videoRestart);
}

1232
code/client/cl_demo.cpp Normal file

File diff suppressed because it is too large Load diff

View file

@ -1118,7 +1118,7 @@ void CL_KeyEvent( int key, qbool down, unsigned time )
if ( cls.state == CA_ACTIVE && !clc.demoplaying ) { if ( cls.state == CA_ACTIVE && !clc.demoplaying ) {
VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_INGAME ); VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_INGAME );
} }
else { else if ( !clc.demoplaying || cl_escapeAbortsDemo->integer ) {
CL_Disconnect_f(); CL_Disconnect_f();
S_StopAllSounds(); S_StopAllSounds();
VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_MAIN ); VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_MAIN );

View file

@ -46,6 +46,9 @@ cvar_t *cl_allowDownload;
cvar_t *cl_inGameVideo; cvar_t *cl_inGameVideo;
cvar_t *cl_matchAlerts; cvar_t *cl_matchAlerts;
cvar_t *cl_demoPlayer;
cvar_t *cl_escapeAbortsDemo;
cvar_t *net_proxy; cvar_t *net_proxy;
cvar_t *r_khr_debug; cvar_t *r_khr_debug;
@ -89,7 +92,9 @@ not have future usercmd_t executed before it is executed
====================== ======================
*/ */
void CL_AddReliableCommand( const char *cmd ) { void CL_AddReliableCommand( const char *cmd ) {
int index; if ( clc.demoplaying ) {
return;
}
// if we would be losing an old command that hasn't been acknowledged, // if we would be losing an old command that hasn't been acknowledged,
// we must drop the connection // we must drop the connection
@ -97,7 +102,7 @@ void CL_AddReliableCommand( const char *cmd ) {
Com_Error( ERR_DROP, "Client command overflow" ); Com_Error( ERR_DROP, "Client command overflow" );
} }
clc.reliableSequence++; clc.reliableSequence++;
index = clc.reliableSequence & ( MAX_RELIABLE_COMMANDS - 1 ); const int index = clc.reliableSequence & ( MAX_RELIABLE_COMMANDS - 1 );
Q_strncpyz( clc.reliableCommands[ index ], cmd, sizeof( clc.reliableCommands[ index ] ) ); Q_strncpyz( clc.reliableCommands[ index ], cmd, sizeof( clc.reliableCommands[ index ] ) );
} }
@ -364,7 +369,7 @@ static void CL_WalkDemoExt( const char* path, fileHandle_t* fh )
} }
void CL_PlayDemo_f() static void CL_PlayDemo( qbool videoRestart )
{ {
if (Cmd_Argc() != 2) { if (Cmd_Argc() != 2) {
Com_Printf( "demo <demoname>\n" ); Com_Printf( "demo <demoname>\n" );
@ -395,6 +400,14 @@ void CL_PlayDemo_f()
clc.demoplaying = qtrue; clc.demoplaying = qtrue;
Q_strncpyz( cls.servername, shortPath, sizeof( cls.servername ) ); Q_strncpyz( cls.servername, shortPath, sizeof( cls.servername ) );
if ( cl_demoPlayer->integer ) {
while ( CL_MapDownload_Active() ) {
Sys_Sleep( 50 );
}
CL_NDP_PlayDemo( videoRestart );
return;
}
// read demo messages until connected // read demo messages until connected
while ( cls.state >= CA_CONNECTED && cls.state < CA_PRIMED && !CL_MapDownload_Active() ) { while ( cls.state >= CA_CONNECTED && cls.state < CA_PRIMED && !CL_MapDownload_Active() ) {
CL_ReadDemoMessage(); CL_ReadDemoMessage();
@ -730,6 +743,7 @@ void CL_Disconnect( qbool showMainMenu ) {
FS_FCloseFile( clc.demofile ); FS_FCloseFile( clc.demofile );
clc.demofile = 0; clc.demofile = 0;
} }
clc.demoplaying = qfalse;
if ( uivm && showMainMenu ) { if ( uivm && showMainMenu ) {
VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_NONE ); VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_NONE );
@ -1874,8 +1888,14 @@ static void CL_Vid_Restart_f()
// startup all the client stuff // startup all the client stuff
CL_StartHunkUsers(); CL_StartHunkUsers();
// we don't really technically need to run everything again,
// but trying to optimize parts out is very likely to lead to nasty bugs
if ( clc.demoplaying && clc.newDemoPlayer ) {
Cmd_TokenizeString( va("demo %s", clc.demoName) );
CL_PlayDemo( qtrue );
}
// start the cgame if connected // start the cgame if connected
if ( cls.state > CA_CONNECTED && cls.state != CA_CINEMATIC ) { else if ( cls.state > CA_CONNECTED && cls.state != CA_CINEMATIC ) {
cls.cgameStarted = qtrue; cls.cgameStarted = qtrue;
CL_InitCGame(); CL_InitCGame();
// send pure checksums // send pure checksums
@ -1903,30 +1923,42 @@ static void CL_Video_f()
{ {
char s[ MAX_OSPATH ]; char s[ MAX_OSPATH ];
if( !clc.demoplaying ) if( !clc.demoplaying || clc.newDemoPlayer )
{ {
Com_Printf( "ERROR: ^7/video is only enabled during demo playback\n" ); Com_Printf( "^3ERROR: ^7/%s is only enabled in the old demo player\n", Cmd_Argv( 0 ) );
return; return;
} }
if( Cmd_Argc( ) == 2 ) if( Cmd_Argc( ) == 2 )
{ {
Com_sprintf( s, MAX_OSPATH, "videos/%s", Cmd_Argv( 1 ) ); Q_strncpyz( s, Cmd_Argv( 1 ), sizeof( s ) );
} }
else else
{ {
qtime_t t; qtime_t t;
Com_RealTime( &t ); Com_RealTime( &t );
Com_sprintf( s, sizeof(s), "videos/%d_%02d_%02d-%02d_%02d_%02d", Com_sprintf( s, sizeof( s ), "%d_%02d_%02d-%02d_%02d_%02d",
1900+t.tm_year, 1+t.tm_mon, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec ); 1900+t.tm_year, 1+t.tm_mon, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec );
} }
CL_OpenAVIForWriting( s, qfalse ); CL_OpenAVIForWriting( va( "videos/%s", s ), qfalse );
} }
static void CL_StopVideo_f() static void CL_StopVideo_f()
{ {
if( !clc.demoplaying || clc.newDemoPlayer )
{
Com_Printf( "^3ERROR: ^7/%s is only enabled in the old demo player\n", Cmd_Argv( 0 ) );
return;
}
if( !CL_VideoRecording() )
{
Com_Printf( "No video is being recorded\n" );
return;
}
CL_CloseAVI(); CL_CloseAVI();
} }
@ -2083,6 +2115,12 @@ static void CL_CancelDownload_f()
} }
static void CL_PlayDemo_f()
{
CL_PlayDemo( qfalse );
}
static const cvarTableItem_t cl_cvars[] = static const cvarTableItem_t cl_cvars[] =
{ {
{ &cl_timeout, "cl_timeout", "200", 0, CVART_INTEGER, "30", "300", "connection time-out, in seconds" }, { &cl_timeout, "cl_timeout", "200", 0, CVART_INTEGER, "30", "300", "connection time-out, in seconds" },
@ -2107,7 +2145,9 @@ static const cvarTableItem_t cl_cvars[] =
{ NULL, "password", "", CVAR_USERINFO, CVART_STRING, NULL, NULL, "used by /" S_COLOR_CMD "connect" }, { NULL, "password", "", CVAR_USERINFO, CVART_STRING, NULL, NULL, "used by /" S_COLOR_CMD "connect" },
{ &cl_matchAlerts, "cl_matchAlerts", "7", CVAR_ARCHIVE, CVART_BITMASK, "0", XSTRING(MAF_MAX), help_cl_matchAlerts }, { &cl_matchAlerts, "cl_matchAlerts", "7", CVAR_ARCHIVE, CVART_BITMASK, "0", XSTRING(MAF_MAX), help_cl_matchAlerts },
{ &net_proxy, "net_proxy", "", CVAR_TEMP, CVART_STRING, NULL, NULL, help_net_proxy }, { &net_proxy, "net_proxy", "", CVAR_TEMP, CVART_STRING, NULL, NULL, help_net_proxy },
{ &r_khr_debug, "r_khr_debug", "2", CVAR_ARCHIVE, CVART_INTEGER, "0", "2", help_r_khr_debug } { &r_khr_debug, "r_khr_debug", "2", CVAR_ARCHIVE, CVART_INTEGER, "0", "2", help_r_khr_debug },
{ &cl_demoPlayer, "cl_demoPlayer", "1", CVAR_ARCHIVE, CVART_BOOL, NULL, NULL, help_cl_demoPlayer },
{ &cl_escapeAbortsDemo, "cl_escapeAbortsDemo", "1", CVAR_ARCHIVE, CVART_BOOL, NULL, NULL, "pressing escape aborts demo playback" },
}; };

View file

@ -894,7 +894,7 @@ static intptr_t CL_UISystemCalls( intptr_t* args )
return 0; return 0;
case UI_R_RENDERSCENE: case UI_R_RENDERSCENE:
re.RenderScene( VMA(1) ); re.RenderScene( VMA(1), 0 );
return 0; return 0;
case UI_R_SETCOLOR: case UI_R_SETCOLOR:
@ -1144,7 +1144,7 @@ static intptr_t CL_UISystemCalls( intptr_t* args )
return 0; return 0;
case UI_EXT_ENABLEERRORCALLBACK: case UI_EXT_ENABLEERRORCALLBACK:
cls.uiErrorCallbackVMCall = (int)args[1]; cls.uivmCalls[UIVM_ERROR_CALLBACK] = (int)args[1];
return 0; return 0;
default: default:
@ -1157,7 +1157,7 @@ static intptr_t CL_UISystemCalls( intptr_t* args )
void CL_ShutdownUI() void CL_ShutdownUI()
{ {
cls.uiErrorCallbackVMCall = 0; Com_Memset( cls.uivmCalls, 0, sizeof(cls.uivmCalls) );
cls.keyCatchers &= ~KEYCATCH_UI; cls.keyCatchers &= ~KEYCATCH_UI;
cls.uiStarted = qfalse; cls.uiStarted = qfalse;
if ( !uivm ) if ( !uivm )
@ -1187,18 +1187,17 @@ void CL_InitUI()
} }
// init for this gamestate // init for this gamestate
cls.uiErrorCallbackVMCall = 0;
VM_Call( uivm, UI_INIT, (cls.state >= CA_AUTHORIZING && cls.state < CA_ACTIVE) ); VM_Call( uivm, UI_INIT, (cls.state >= CA_AUTHORIZING && cls.state < CA_ACTIVE) );
} }
void CL_ForwardUIError( int level, int module, const char* error ) void CL_ForwardUIError( int level, int module, const char* error )
{ {
if ( uivm == NULL || cls.uiErrorCallbackVMCall == 0 ) if ( uivm == NULL || cls.uivmCalls[UIVM_ERROR_CALLBACK] == 0 )
return; return;
Q_strncpyz( (char*)interopBufferOut, error, interopBufferOutSize ); Q_strncpyz( (char*)interopBufferOut, error, interopBufferOutSize );
VM_Call( uivm, cls.uiErrorCallbackVMCall, level, module ); VM_Call( uivm, cls.uivmCalls[UIVM_ERROR_CALLBACK], level, module );
} }

View file

@ -173,7 +173,7 @@ typedef struct {
int serverMessageSequence; int serverMessageSequence;
// reliable messages received from server // reliable messages received from server
int serverCommandSequence; int serverCommandSequence; // the number of the latest available command
int lastExecutedServerCommand; // last server command grabbed or executed with CL_GetServerCommand int lastExecutedServerCommand; // last server command grabbed or executed with CL_GetServerCommand
char serverCommands[MAX_RELIABLE_COMMANDS][MAX_STRING_CHARS]; char serverCommands[MAX_RELIABLE_COMMANDS][MAX_STRING_CHARS];
qbool serverCommandsBad[MAX_RELIABLE_COMMANDS]; // non-zero means the command shouldn't be fed to cgame qbool serverCommandsBad[MAX_RELIABLE_COMMANDS]; // non-zero means the command shouldn't be fed to cgame
@ -196,6 +196,7 @@ typedef struct {
qbool demoplaying; qbool demoplaying;
qbool demowaiting; // don't record until a non-delta message is received qbool demowaiting; // don't record until a non-delta message is received
qbool firstDemoFrameSkipped; qbool firstDemoFrameSkipped;
qbool newDemoPlayer; // running the new player with rewind support
fileHandle_t demofile; fileHandle_t demofile;
int timeDemoFrames; // counter of rendered frames int timeDemoFrames; // counter of rendered frames
@ -245,6 +246,22 @@ typedef struct {
unsigned short port; unsigned short port;
} serverAddress_t; } serverAddress_t;
// CGame VM calls that are extensions
enum {
CGVM_NDP_END_ANALYSIS,
CGVM_NDP_ANALYZE_SNAPSHOT,
CGVM_NDP_ANALYZE_COMMAND,
CGVM_NDP_GENERATE_COMMANDS, // generate synchronization commands
CGVM_NDP_IS_CS_NEEDED, // does this config string need to be re-submitted?
CGVM_COUNT
};
// UI VM calls that are extensions
enum {
UIVM_ERROR_CALLBACK, // forward errors to UI?
UIVM_COUNT
};
typedef struct { typedef struct {
connstate_t state; // connection status connstate_t state; // connection status
int keyCatchers; // bit flags int keyCatchers; // bit flags
@ -261,12 +278,17 @@ typedef struct {
qbool uiStarted; qbool uiStarted;
qbool cgameStarted; qbool cgameStarted;
// extensions VM calls indices
// 0 when not available
int cgvmCalls[CGVM_COUNT];
int uivmCalls[UIVM_COUNT];
// extension: new demo player supported by the mod
qbool cgameNewDemoPlayer;
// extension: forward input to cgame regardless of keycatcher state? // extension: forward input to cgame regardless of keycatcher state?
int cgameForwardInput; // 1=mouse, 2=keys (note: we don't forward the escape key) int cgameForwardInput; // 1=mouse, 2=keys (note: we don't forward the escape key)
// extension: forward errors to ui?
int uiErrorCallbackVMCall; // 0 when not available
int framecount; int framecount;
int frametime; // msec since last frame int frametime; // msec since last frame
@ -354,6 +376,8 @@ extern cvar_t *cl_allowDownload; // 0=off, 1=CNQ3, -1=id
extern cvar_t *cl_inGameVideo; extern cvar_t *cl_inGameVideo;
extern cvar_t *cl_matchAlerts; // bit mask, see the MAF_* constants extern cvar_t *cl_matchAlerts; // bit mask, see the MAF_* constants
extern cvar_t *cl_demoPlayer;
extern cvar_t *cl_escapeAbortsDemo;
extern cvar_t *r_khr_debug; extern cvar_t *r_khr_debug;
@ -496,6 +520,12 @@ void CL_InitCGame();
void CL_ShutdownCGame(); void CL_ShutdownCGame();
void CL_CGameRendering( stereoFrame_t stereo ); void CL_CGameRendering( stereoFrame_t stereo );
void CL_SetCGameTime(); void CL_SetCGameTime();
void CL_ConfigstringModified();
void CL_CGNDP_EndAnalysis( const char* filePath, int firstServerTime, int lastServerTime, qbool videoRestart );
void CL_CGNDP_AnalyzeSnapshot( int progress );
void CL_CGNDP_AnalyzeCommand( int serverTime );
void CL_CGNDP_GenerateCommands( const char** commands, int* numCommandBytes );
qbool CL_CGNDP_IsConfigStringNeeded( int csIndex );
// //
// cl_ui.c // cl_ui.c
@ -546,6 +576,18 @@ void CL_MapDownload_CrashCleanUp();
qbool CL_GL_WantDebug(); // do we want a debug context from the platform layer? qbool CL_GL_WantDebug(); // do we want a debug context from the platform layer?
void CL_GL_Init(); // enables debug output if needed void CL_GL_Init(); // enables debug output if needed
//
// cl_demo.cpp
//
void CL_NDP_PlayDemo( qbool videoRestart );
void CL_NDP_SetCGameTime();
void CL_NDP_GetCurrentSnapshotNumber( int* snapshotNumber, int* serverTime );
qbool CL_NDP_GetSnapshot( int snapshotNumber, snapshot_t* snapshot );
qbool CL_NDP_GetServerCommand( int serverCommandNumber );
int CL_NDP_Seek( int serverTime );
void CL_NDP_ReadUntil( int serverTime );
void CL_NDP_HandleError();
// //
// OS-specific // OS-specific
// //

View file

@ -146,3 +146,8 @@ S_COLOR_VAL " 2 " S_COLOR_HELP "= On in debug builds only"
"Example: init lines starting with 'init'\n" \ "Example: init lines starting with 'init'\n" \
"Example: *init lines containing 'init'\n" \ "Example: *init lines containing 'init'\n" \
"Example: >*net lines starting with '>' AND containing 'net'" "Example: >*net lines starting with '>' AND containing 'net'"
#define help_cl_demoPlayer \
"1 enables demo rewind\n" \
S_COLOR_VAL " 0 " S_COLOR_HELP "= Uses the original demo player\n" \
S_COLOR_VAL " 1 " S_COLOR_HELP "= Uses the new demo player when the mod supports it"

View file

@ -233,6 +233,13 @@ static sfxHandle_t S_Base_RegisterSound( const char* name )
return 0; return 0;
} }
if ( name[0] == '\0' ) {
if ( !cls.cgameNewDemoPlayer ) {
Com_Printf( "Sound name is empty\n" );
}
return 0;
}
if ( strlen( name ) >= MAX_QPATH ) { if ( strlen( name ) >= MAX_QPATH ) {
Com_Printf( "Sound name exceeds MAX_QPATH\n" ); Com_Printf( "Sound name exceeds MAX_QPATH\n" );
return 0; return 0;

View file

@ -196,7 +196,13 @@ typedef enum {
CG_EXT_CMD_SETHELP, CG_EXT_CMD_SETHELP,
CG_EXT_MATCHALERTEVENT, CG_EXT_MATCHALERTEVENT,
CG_EXT_ERROR2, CG_EXT_ERROR2,
CG_EXT_ISRECORDINGDEMO CG_EXT_ISRECORDINGDEMO,
CG_EXT_NDP_ENABLE,
CG_EXT_NDP_SEEK,
CG_EXT_NDP_READUNTIL,
CG_EXT_NDP_STARTVIDEO,
CG_EXT_NDP_STOPVIDEO,
CG_EXT_R_RENDERSCENE
} cgameImport_t; } cgameImport_t;
@ -427,6 +433,7 @@ typedef enum {
CG_MOUSE_EVENT, CG_MOUSE_EVENT,
// void (*CG_MouseEvent)( int dx, int dy ); // void (*CG_MouseEvent)( int dx, int dy );
CG_EVENT_HANDLING CG_EVENT_HANDLING
// void (*CG_EventHandling)(int type); // void (*CG_EventHandling)(int type);
} cgameExport_t; } cgameExport_t;

View file

@ -250,6 +250,14 @@ void QDECL Com_ErrorExt( int code, int module, qbool realError, const char *fmt,
static int lastErrorTime; static int lastErrorTime;
static int errorCount; static int errorCount;
if ( code == ERR_DROP_NDP ) {
#if !defined(DEDICATED)
void CL_NDP_HandleError();
CL_NDP_HandleError();
#endif
code = ERR_DROP;
}
// make sure we can get at our local stuff // make sure we can get at our local stuff
FS_PureServerSetLoadedPaks( "" ); FS_PureServerSetLoadedPaks( "" );
@ -3593,3 +3601,19 @@ static const char* Com_GetCompilerInfo()
return "Unknown compiler"; return "Unknown compiler";
#endif #endif
} }
const char* Com_FormatBytes( int numBytes )
{
const char* units[] = { "bytes", "KB", "MB", "GB" };
const float dividers[] = { 1.0f, float(1 << 10), float(1 << 20), float(1 << 30) };
int unit = 0;
for ( int vi = numBytes; vi >= 1024; vi >>= 10 ) {
unit++;
}
const float vf = (float)numBytes / dividers[unit];
return va( "%.3f %s", vf, units[unit] );
}

View file

@ -1294,6 +1294,16 @@ int FS_Seek( fileHandle_t f, long offset, int origin )
} }
qbool FS_IsZipFile( fileHandle_t f )
{
if ( f < 0 || f >= MAX_FILE_HANDLES ) {
Com_Error( ERR_DROP, "FS_IsZipFile: out of range" );
}
return fsh[f].zipFile;
}
/* /*
====================================================================================== ======================================================================================

View file

@ -180,7 +180,7 @@ int MSG_ReadBits( msg_t *msg, int bits ) {
msg->readcount += 4; msg->readcount += 4;
msg->bit += 32; msg->bit += 32;
} else { } else {
Com_Error(ERR_DROP, "can't read %d bits\n", bits); Com_Error(ERR_DROP_NDP, "can't read %d bits\n", bits);
} }
} else { } else {
nbits = 0; nbits = 0;
@ -741,7 +741,7 @@ void MSG_ReadDeltaEntity( msg_t* msg, const entityState_t* from, entityState_t*
int startBit, endBit; int startBit, endBit;
if ( number < 0 || number >= MAX_GENTITIES ) { if ( number < 0 || number >= MAX_GENTITIES ) {
Com_Error( ERR_DROP, "Bad delta entity number: %i", number ); Com_Error( ERR_DROP_NDP, "Bad delta entity number: %i", number );
} }
if ( msg->bit == 0 ) { if ( msg->bit == 0 ) {
@ -771,7 +771,7 @@ void MSG_ReadDeltaEntity( msg_t* msg, const entityState_t* from, entityState_t*
lc = MSG_ReadByte(msg); lc = MSG_ReadByte(msg);
if ( lc < 0 || lc > ARRAY_LEN(entityStateFields) ) { if ( lc < 0 || lc > ARRAY_LEN(entityStateFields) ) {
Com_Error( ERR_DROP, "invalid entityState_t field count %d (max: %d)\n", lc, ARRAY_LEN(entityStateFields) ); Com_Error( ERR_DROP_NDP, "invalid entityState_t field count %d (max: %d)\n", lc, ARRAY_LEN(entityStateFields) );
} }
// shownet 2/3 will interleave with other printed info, -1 will // shownet 2/3 will interleave with other printed info, -1 will
@ -1095,7 +1095,7 @@ void MSG_ReadDeltaPlayerstate( msg_t* msg, const playerState_t* from, playerStat
lc = MSG_ReadByte(msg); lc = MSG_ReadByte(msg);
if ( lc < 0 || lc > ARRAY_LEN(playerStateFields) ) { if ( lc < 0 || lc > ARRAY_LEN(playerStateFields) ) {
Com_Error( ERR_DROP, "invalid playerState_t field count %d (max: %d)\n", lc, ARRAY_LEN(playerStateFields) ); Com_Error( ERR_DROP_NDP, "invalid playerState_t field count %d (max: %d)\n", lc, ARRAY_LEN(playerStateFields) );
} }
const netField_t* field; const netField_t* field;

View file

@ -672,9 +672,9 @@ FIXME: make this buffer size safe someday
*/ */
const char* QDECL va( const char* format, ... ) const char* QDECL va( const char* format, ... )
{ {
static char string[2][32000]; // in case va is called by nested functions static char string[4][32000]; // in case va is called by nested functions
static int index = 0; static int index = 0;
char* buf = string[index++ & 1]; char* buf = string[index++ & 3];
va_list argptr; va_list argptr;
va_start( argptr, format ); va_start( argptr, format );

View file

@ -215,6 +215,7 @@ typedef int clipHandle_t;
typedef enum { typedef enum {
ERR_FATAL, // exit the entire game with a popup window ERR_FATAL, // exit the entire game with a popup window
ERR_DROP, // print to console and disconnect from game ERR_DROP, // print to console and disconnect from game
ERR_DROP_NDP, // same but the NDP can run its own handler when active
ERR_SERVERDISCONNECT, // don't kill server ERR_SERVERDISCONNECT, // don't kill server
ERR_DISCONNECT, // client disconnected from the server ERR_DISCONNECT, // client disconnected from the server
ERR_NEED_CD // pop up the need-cd dialog ERR_NEED_CD // pop up the need-cd dialog

View file

@ -710,6 +710,9 @@ int FS_FOpenFileByMode( const char *qpath, fileHandle_t *f, fsMode_t mode );
int FS_Seek( fileHandle_t f, long offset, int origin ); int FS_Seek( fileHandle_t f, long offset, int origin );
// seek on a file (doesn't work for zip files!!!!!!!!) // seek on a file (doesn't work for zip files!!!!!!!!)
qbool FS_IsZipFile( fileHandle_t f );
// tells us whether we opened a zip file
qbool FS_FilenameCompare( const char *s1, const char *s2 ); qbool FS_FilenameCompare( const char *s1, const char *s2 );
const char *FS_LoadedPakChecksums( void ); const char *FS_LoadedPakChecksums( void );
@ -851,6 +854,7 @@ int Com_Filter( const char* filter, const char* name );
int Com_FilterPath( const char* filter, const char* name ); int Com_FilterPath( const char* filter, const char* name );
int Com_RealTime(qtime_t *qtime); int Com_RealTime(qtime_t *qtime);
qbool Com_SafeMode(); qbool Com_SafeMode();
const char *Com_FormatBytes( int numBytes );
void Com_StartupVariable( const char *match ); void Com_StartupVariable( const char *match );
// checks for and removes command line "+set var arg" constructs // checks for and removes command line "+set var arg" constructs

View file

@ -623,24 +623,11 @@ struct srfTriangles_t {
// trRefdef_t holds everything that comes in refdef_t, // trRefdef_t holds everything that comes in refdef_t,
// as well as the locally generated scene information // as well as the locally generated scene information
typedef struct { struct trRefdef_t : public refdef_t {
int x, y, width, height;
float fov_x, fov_y;
vec3_t vieworg;
vec3_t viewaxis[3]; // transformation matrix
int time; // time in milliseconds for shader effects and other time dependent rendering issues
int rdflags; // RDF_NOWORLDMODEL, etc
// 1 bits will prevent the associated area from rendering at all
byte areamask[MAX_MAP_AREA_BYTES];
qbool areamaskModified; // qtrue if areamask changed since last scene qbool areamaskModified; // qtrue if areamask changed since last scene
int microSeconds; // [0;999] micro-seconds to add to the timestamp
double floatTime; // tr.refdef.time / 1000.0 double floatTime; // tr.refdef.time / 1000.0
// text messages for deform text shaders
char text[MAX_RENDER_STRINGS][MAX_RENDER_STRING_LENGTH];
int num_entities; int num_entities;
trRefEntity_t *entities; trRefEntity_t *entities;
@ -655,8 +642,7 @@ typedef struct {
int numLitSurfs; int numLitSurfs;
litSurf_t* litSurfs; litSurf_t* litSurfs;
};
} trRefdef_t;
/* /*
@ -1399,7 +1385,7 @@ void RE_ClearScene();
void RE_AddRefEntityToScene( const refEntity_t *ent, qbool intShaderTime ); void RE_AddRefEntityToScene( const refEntity_t *ent, qbool intShaderTime );
void RE_AddPolyToScene( qhandle_t hShader , int numVerts, const polyVert_t *verts, int num ); void RE_AddPolyToScene( qhandle_t hShader , int numVerts, const polyVert_t *verts, int num );
void RE_AddLightToScene( const vec3_t org, float radius, float r, float g, float b ); void RE_AddLightToScene( const vec3_t org, float radius, float r, float g, float b );
void RE_RenderScene( const refdef_t *fd ); void RE_RenderScene( const refdef_t *fd, int us );
/* /*

View file

@ -131,7 +131,7 @@ typedef struct {
void (*AddPolyToScene)( qhandle_t hShader, int numVerts, const polyVert_t *verts, int num ); void (*AddPolyToScene)( qhandle_t hShader, int numVerts, const polyVert_t *verts, int num );
qbool (*LightForPoint)( const vec3_t point, vec3_t ambientLight, vec3_t directedLight, vec3_t lightDir ); qbool (*LightForPoint)( const vec3_t point, vec3_t ambientLight, vec3_t directedLight, vec3_t lightDir );
void (*AddLightToScene)( const vec3_t org, float radius, float r, float g, float b ); void (*AddLightToScene)( const vec3_t org, float radius, float r, float g, float b );
void (*RenderScene)( const refdef_t *fd ); void (*RenderScene)( const refdef_t *fd, int us );
void (*SetColor)( const float* rgba ); // NULL = 1,1,1,1 void (*SetColor)( const float* rgba ); // NULL = 1,1,1,1
void (*DrawStretchPic)( float x, float y, float w, float h, void (*DrawStretchPic)( float x, float y, float w, float h,

View file

@ -197,7 +197,7 @@ void RE_AddLightToScene( const vec3_t org, float radius, float r, float g, float
// draw a 3D view into a part of the window, then return to 2D drawing // draw a 3D view into a part of the window, then return to 2D drawing
// rendering a scene may require multiple views to be rendered to handle mirrors // rendering a scene may require multiple views to be rendered to handle mirrors
void RE_RenderScene( const refdef_t* fd ) void RE_RenderScene( const refdef_t* fd, int us )
{ {
if ( !tr.registered ) { if ( !tr.registered ) {
return; return;
@ -228,6 +228,7 @@ void RE_RenderScene( const refdef_t* fd )
VectorCopy( fd->viewaxis[2], tr.refdef.viewaxis[2] ); VectorCopy( fd->viewaxis[2], tr.refdef.viewaxis[2] );
tr.refdef.time = fd->time; tr.refdef.time = fd->time;
tr.refdef.microSeconds = us;
tr.refdef.rdflags = fd->rdflags; tr.refdef.rdflags = fd->rdflags;
// copy the areamask data over and note if it has changed, which // copy the areamask data over and note if it has changed, which
@ -248,7 +249,9 @@ void RE_RenderScene( const refdef_t* fd )
// derived info // derived info
tr.refdef.floatTime = (double)tr.refdef.time / 1000.0; tr.refdef.floatTime =
(double)tr.refdef.time / 1000.0 +
(double)tr.refdef.microSeconds / 1000000.0;
tr.refdef.numDrawSurfs = r_firstSceneDrawSurf; tr.refdef.numDrawSurfs = r_firstSceneDrawSurf;
tr.refdef.drawSurfs = backEndData->drawSurfs; tr.refdef.drawSurfs = backEndData->drawSurfs;

View file

@ -78,6 +78,7 @@ OBJECTS := \
$(OBJDIR)/cl_cgame.o \ $(OBJDIR)/cl_cgame.o \
$(OBJDIR)/cl_cin.o \ $(OBJDIR)/cl_cin.o \
$(OBJDIR)/cl_console.o \ $(OBJDIR)/cl_console.o \
$(OBJDIR)/cl_demo.o \
$(OBJDIR)/cl_download.o \ $(OBJDIR)/cl_download.o \
$(OBJDIR)/cl_gl.o \ $(OBJDIR)/cl_gl.o \
$(OBJDIR)/cl_input.o \ $(OBJDIR)/cl_input.o \
@ -205,6 +206,9 @@ $(OBJDIR)/cl_cin.o: ../../code/client/cl_cin.cpp
$(OBJDIR)/cl_console.o: ../../code/client/cl_console.cpp $(OBJDIR)/cl_console.o: ../../code/client/cl_console.cpp
@echo $(notdir $<) @echo $(notdir $<)
$(SILENT) $(CXX) $(ALL_CXXFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<" $(SILENT) $(CXX) $(ALL_CXXFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<"
$(OBJDIR)/cl_demo.o: ../../code/client/cl_demo.cpp
@echo $(notdir $<)
$(SILENT) $(CXX) $(ALL_CXXFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<"
$(OBJDIR)/cl_download.o: ../../code/client/cl_download.cpp $(OBJDIR)/cl_download.o: ../../code/client/cl_download.cpp
@echo $(notdir $<) @echo $(notdir $<)
$(SILENT) $(CXX) $(ALL_CXXFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<" $(SILENT) $(CXX) $(ALL_CXXFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<"

View file

@ -369,6 +369,7 @@ local function ApplyExeProjectSettings(exeName, server)
"client/cl_cgame.cpp", "client/cl_cgame.cpp",
"client/cl_cin.cpp", "client/cl_cin.cpp",
"client/cl_console.cpp", "client/cl_console.cpp",
"client/cl_demo.cpp",
"client/cl_download.cpp", "client/cl_download.cpp",
"client/cl_gl.cpp", "client/cl_gl.cpp",
"client/cl_input.cpp", "client/cl_input.cpp",

View file

@ -314,6 +314,7 @@ copy "..\..\.bin\release_x32\cnq3-x86.pdb" "$(QUAKE3DIR)"</Command>
<ClCompile Include="..\..\code\client\cl_cgame.cpp" /> <ClCompile Include="..\..\code\client\cl_cgame.cpp" />
<ClCompile Include="..\..\code\client\cl_cin.cpp" /> <ClCompile Include="..\..\code\client\cl_cin.cpp" />
<ClCompile Include="..\..\code\client\cl_console.cpp" /> <ClCompile Include="..\..\code\client\cl_console.cpp" />
<ClCompile Include="..\..\code\client\cl_demo.cpp" />
<ClCompile Include="..\..\code\client\cl_download.cpp" /> <ClCompile Include="..\..\code\client\cl_download.cpp" />
<ClCompile Include="..\..\code\client\cl_gl.cpp" /> <ClCompile Include="..\..\code\client\cl_gl.cpp" />
<ClCompile Include="..\..\code\client\cl_input.cpp" /> <ClCompile Include="..\..\code\client\cl_input.cpp" />

View file

@ -248,6 +248,9 @@
<ClCompile Include="..\..\code\client\cl_console.cpp"> <ClCompile Include="..\..\code\client\cl_console.cpp">
<Filter>client</Filter> <Filter>client</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="..\..\code\client\cl_demo.cpp">
<Filter>client</Filter>
</ClCompile>
<ClCompile Include="..\..\code\client\cl_download.cpp"> <ClCompile Include="..\..\code\client\cl_download.cpp">
<Filter>client</Filter> <Filter>client</Filter>
</ClCompile> </ClCompile>