From 844e5842b714c4566c2820e7297fefaf76877465 Mon Sep 17 00:00:00 2001 From: g-cont Date: Thu, 14 Jun 2018 00:00:00 +0300 Subject: [PATCH] 14 Jun 2018 --- engine/client/cl_debug.c | 242 +++++++++ engine/client/cl_demo.c | 263 ++++++++-- engine/client/cl_frame.c | 2 +- engine/client/cl_main.c | 49 +- engine/client/cl_parse.c | 231 +-------- engine/client/cl_pmove.c | 4 + engine/client/cl_qparse.c | 1007 ++++++++++++++++++++++++++++++++++++ engine/client/cl_scrn.c | 1 + engine/client/cl_tent.c | 4 +- engine/client/cl_video.c | 3 + engine/client/client.h | 33 +- engine/client/gl_image.c | 2 +- engine/client/gl_rmain.c | 2 +- engine/client/gl_rmisc.c | 5 +- engine/client/gl_rsurf.c | 8 +- engine/client/s_main.c | 20 +- engine/common/common.h | 1 + engine/common/filesystem.c | 13 +- engine/common/input.c | 38 +- engine/common/mod_bmodel.c | 2 +- engine/common/protocol.h | 61 +++ engine/engine.dsp | 8 + 22 files changed, 1687 insertions(+), 312 deletions(-) create mode 100644 engine/client/cl_debug.c create mode 100644 engine/client/cl_qparse.c diff --git a/engine/client/cl_debug.c b/engine/client/cl_debug.c new file mode 100644 index 00000000..3af2e00f --- /dev/null +++ b/engine/client/cl_debug.c @@ -0,0 +1,242 @@ +/* +cl_debug.c - server message debugging +Copyright (C) 2018 Uncle Mike + +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 3 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. +*/ + +#include "common.h" +#include "client.h" +#include "net_encode.h" +#include "particledef.h" +#include "gl_local.h" +#include "cl_tent.h" +#include "shake.h" +#include "hltv.h" +#include "input.h" + +#define MSG_COUNT 32 // last 32 messages parsed +#define MSG_MASK (MSG_COUNT - 1) + +const char *svc_strings[svc_lastmsg+1] = +{ + "svc_bad", + "svc_nop", + "svc_disconnect", + "svc_event", + "svc_changing", + "svc_setview", + "svc_sound", + "svc_time", + "svc_print", + "svc_stufftext", + "svc_setangle", + "svc_serverdata", + "svc_lightstyle", + "svc_updateuserinfo", + "svc_deltatable", + "svc_clientdata", + "svc_resource", + "svc_pings", + "svc_particle", + "svc_restoresound", + "svc_spawnstatic", + "svc_event_reliable", + "svc_spawnbaseline", + "svc_temp_entity", + "svc_setpause", + "svc_signonnum", + "svc_centerprint", + "svc_unused27", + "svc_unused28", + "svc_unused29", + "svc_intermission", + "svc_finale", + "svc_cdtrack", + "svc_restore", + "svc_cutscene", + "svc_weaponanim", + "svc_bspdecal", + "svc_roomtype", + "svc_addangle", + "svc_usermessage", + "svc_packetentities", + "svc_deltapacketentities", + "svc_choke", + "svc_resourcelist", + "svc_deltamovevars", + "svc_resourcerequest", + "svc_customization", + "svc_crosshairangle", + "svc_soundfade", + "svc_filetxferfailed", + "svc_hltv", + "svc_director", + "svc_voiceinit", + "svc_voicedata", + "svc_unused54", + "svc_unused55", + "svc_resourcelocation", + "svc_querycvarvalue", + "svc_querycvarvalue2", +}; + +typedef struct +{ + int command; + int starting_offset; + int frame_number; +} oldcmd_t; + +typedef struct +{ + oldcmd_t oldcmd[MSG_COUNT]; + int currentcmd; + qboolean parsing; +} msg_debug_t; + +static msg_debug_t cls_message_debug; + +const char *CL_MsgInfo( int cmd ) +{ + static string sz; + + Q_strcpy( sz, "???" ); + + if( cmd >= 0 && cmd <= svc_lastmsg ) + { + // get engine message name + Q_strncpy( sz, svc_strings[cmd], sizeof( sz )); + } + else if( cmd > svc_lastmsg && cmd <= ( svc_lastmsg + MAX_USER_MESSAGES )) + { + int i; + + for( i = 0; i < MAX_USER_MESSAGES; i++ ) + { + if( clgame.msg[i].number == cmd ) + { + Q_strncpy( sz, clgame.msg[i].name, sizeof( sz )); + break; + } + } + } + return sz; +} + +/* +===================== +CL_Parse_Debug + +enable message debugging +===================== +*/ +void CL_Parse_Debug( qboolean enable ) +{ + cls_message_debug.parsing = enable; +} + +/* +===================== +CL_Parse_RecordCommand + +record new message params into debug buffer +===================== +*/ +void CL_Parse_RecordCommand( int cmd, int startoffset ) +{ + int slot; + + if( cmd == svc_nop ) return; + + slot = ( cls_message_debug.currentcmd++ & MSG_MASK ); + cls_message_debug.oldcmd[slot].command = cmd; + cls_message_debug.oldcmd[slot].starting_offset = startoffset; + cls_message_debug.oldcmd[slot].frame_number = host.framecount; +} + +/* +===================== +CL_ResetFrame +===================== +*/ +void CL_ResetFrame( frame_t *frame ) +{ + memset( &frame->graphdata, 0, sizeof( netbandwidthgraph_t )); + frame->receivedtime = host.realtime; + frame->valid = true; + frame->choked = false; + frame->latency = 0.0; + frame->time = cl.mtime[0]; +} + +/* +===================== +CL_WriteErrorMessage + +write net_message into buffer.dat for debugging +===================== +*/ +static void CL_WriteErrorMessage( int current_count, sizebuf_t *msg ) +{ + const char *buffer_file = "buffer.dat"; + file_t *fp; + + fp = FS_Open( buffer_file, "wb", false ); + if( !fp ) return; + + FS_Write( fp, &cls.starting_count, sizeof( int )); + FS_Write( fp, ¤t_count, sizeof( int )); + FS_Write( fp, MSG_GetData( msg ), MSG_GetMaxBytes( msg )); + FS_Close( fp ); + + Con_Printf( "Wrote erroneous message to %s\n", buffer_file ); +} + +/* +===================== +CL_WriteMessageHistory + +list last 32 messages for debugging net troubleshooting +===================== +*/ +void CL_WriteMessageHistory( void ) +{ + oldcmd_t *old, *failcommand; + sizebuf_t *msg = &net_message; + int i, thecmd; + + if( !cls.initialized || cls.state == ca_disconnected ) + return; + + if( !cls_message_debug.parsing ) + return; + + Con_Printf( "Last %i messages parsed.\n", MSG_COUNT ); + + // finish here + thecmd = cls_message_debug.currentcmd - 1; + thecmd -= ( MSG_COUNT - 1 ); // back up to here + + for( i = 0; i < MSG_COUNT - 1; i++ ) + { + thecmd &= MSG_MASK; + old = &cls_message_debug.oldcmd[thecmd]; + Con_Printf( "%i %04i %s\n", old->frame_number, old->starting_offset, CL_MsgInfo( old->command )); + thecmd++; + } + + failcommand = &cls_message_debug.oldcmd[thecmd]; + Con_Printf( "BAD: %3i:%s\n", MSG_GetNumBytesRead( msg ) - 1, CL_MsgInfo( failcommand->command )); + if( host_developer.value >= DEV_EXTENDED ) + CL_WriteErrorMessage( MSG_GetNumBytesRead( msg ) - 1, msg ); + cls_message_debug.parsing = false; +} \ No newline at end of file diff --git a/engine/client/cl_demo.c b/engine/client/cl_demo.c index 5efaab19..f764d812 100644 --- a/engine/client/cl_demo.c +++ b/engine/client/cl_demo.c @@ -626,6 +626,74 @@ void CL_ReadDemoSequence( qboolean discard ) cls.netchan.last_reliable_sequence = last_reliable_sequence; } +/* +================= +CL_DemoStartPlayback +================= +*/ +void CL_DemoStartPlayback( int mode ) +{ + if( cls.changedemo ) + { + S_StopAllSounds( true ); + SCR_BeginLoadingPlaque( false ); + + CL_ClearState (); + CL_InitEdicts (); // re-arrange edicts + } + else + { + // NOTE: at this point demo is still valid + CL_Disconnect(); + Host_ShutdownServer(); + + Con_FastClose(); + UI_SetActiveMenu( false ); + } + + cls.demoplayback = mode; + cls.state = ca_connected; + cl.background = (cls.demonum != -1) ? true : false; + cls.spectator = false; + cls.signon = 0; + + demo.starttime = CL_GetDemoPlaybackClock(); // for determining whether to read another message + + Netchan_Setup( NS_CLIENT, &cls.netchan, net_from, Cvar_VariableInteger( "net_qport" ), NULL, CL_GetFragmentSize ); + + memset( demo.cmds, 0, sizeof( demo.cmds )); + demo.angle_position = 1; + demo.framecount = 0; + cls.lastoutgoingcommand = -1; + cls.nextcmdtime = host.realtime; + cl.last_command_ack = -1; +} + +/* +================= +CL_PlayDemoQuake +================= +*/ +void CL_PlayDemoQuake( const char *demoname ) +{ + int c, neg = false; + + cls.demofile = FS_Open( demoname, "rb", true ); + Q_strncpy( cls.demoname, demoname, sizeof( cls.demoname )); + Q_strncpy( gameui.globals->demoname, demoname, sizeof( gameui.globals->demoname )); + demo.header.host_fps = host_maxfps->value; + cls.forcetrack = 0; + + while(( c = FS_Getc( cls.demofile )) != '\n' ) + { + if( c == '-' ) neg = true; + else cls.forcetrack = cls.forcetrack * 10 + (c - '0'); + } + + if( neg ) cls.forcetrack = -cls.forcetrack; + CL_DemoStartPlayback( DEMO_QUAKE1 ); +} + /* ================= CL_DemoAborted @@ -735,6 +803,94 @@ qboolean CL_ReadRawNetworkData( byte *buffer, size_t *length ) return true; } +/* +================= +CL_DemoReadMessageQuake + +reads demo data and write it to client +================= +*/ +qboolean CL_DemoReadMessageQuake( byte *buffer, size_t *length ) +{ + int msglen = 0; + demoangle_t *a; + + *length = 0; // assume we fail + + // decide if it is time to grab the next message + if( cls.signon == SIGNONS ) // allways grab until fully connected + { + if( cls.timedemo ) + { + if( host.framecount == cls.td_lastframe ) + return false; // 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 = host.realtime; + } + else if( cl.time <= cl.mtime[0] ) + { + // don't need another message yet + return false; + } + } + + // get the next message + FS_Read( cls.demofile, &msglen, sizeof( int )); + FS_Read( cls.demofile, &cl.viewangles[0], sizeof( float )); + FS_Read( cls.demofile, &cl.viewangles[1], sizeof( float )); + FS_Read( cls.demofile, &cl.viewangles[2], sizeof( float )); + cls.netchan.incoming_sequence++; + + // make sure what interp info contain angles from different frames + // or lerping will stop working + if( demo.lasttime != demo.timestamp ) + { + // select entry into circular buffer + demo.angle_position = (demo.angle_position + 1) & ANGLE_MASK; + a = &demo.cmds[demo.angle_position]; + + // record update + a->starttime = demo.timestamp; + VectorCopy( cl.viewangles, a->viewangles ); + demo.lasttime = demo.timestamp; + } + + if( msglen < 0 ) + { + MsgDev( D_ERROR, "Demo message length < 0\n" ); + CL_DemoCompleted(); + return false; + } + + if( msglen > MAX_INIT_MSG ) + { + MsgDev( D_ERROR, "Demo message %i > %i\n", msglen, MAX_INIT_MSG ); + CL_DemoCompleted(); + return false; + } + + if( msglen > 0 ) + { + if( FS_Read( cls.demofile, buffer, msglen ) != msglen ) + { + MsgDev( D_ERROR, "Error reading demo message data\n" ); + CL_DemoCompleted(); + return false; + } + } + + *length = msglen; + + if( cls.state != ca_active ) + Cbuf_Execute(); + return true; +} + /* ================= CL_DemoReadMessage @@ -765,6 +921,9 @@ qboolean CL_DemoReadMessage( byte *buffer, size_t *length ) return false; // paused } + if( cls.demoplayback == DEMO_QUAKE1 ) + return CL_DemoReadMessageQuake( buffer, length ); + do { qboolean bSkipMessage = false; @@ -933,7 +1092,8 @@ void CL_DemoInterpolateAngles( void ) QuaternionSlerp( q2, q1, frac, q ); QuaternionAngle( q, cl.viewangles ); } - else VectorCopy( cl.cmd->viewangles, cl.viewangles ); + else if( cls.demoplayback != DEMO_QUAKE1 ) + VectorCopy( cl.cmd->viewangles, cl.viewangles ); } /* @@ -976,7 +1136,8 @@ void CL_StopPlayback( void ) cls.demofile = NULL; cls.olddemonum = Q_max( -1, cls.demonum - 1 ); - Mem_Free( demo.directory.entries ); + if( demo.directory.entries != NULL ) + Mem_Free( demo.directory.entries ); cls.td_lastframe = host.framecount; demo.directory.numentries = 0; demo.directory.entries = NULL; @@ -1110,6 +1271,35 @@ qboolean CL_NextDemo( void ) return true; } +/* +================== +CL_CheckStartupDemos + +queue demos loop after movie playing +================== +*/ +void CL_CheckStartupDemos( void ) +{ + if( !cls.demos_pending ) + return; // no demos in loop + + if( cls.movienum != -1 ) + return; // wait until movies finished + + if( GameState->nextstate != STATE_RUNFRAME || cls.demoplayback ) + { + // commandline override + cls.demos_pending = false; + cls.demonum = -1; + return; + } + + // run demos loop in background mode + Cvar_SetValue( "v_dark", 1.0f ); + cls.demonum = 0; + CL_NextDemo (); +} + /* ================== CL_DemoGetName @@ -1230,8 +1420,9 @@ playdemo */ void CL_PlayDemo_f( void ) { - string filename; - string demoname; + char filename1[MAX_QPATH]; + char filename2[MAX_QPATH]; + char demoname[MAX_QPATH]; int i; if( Cmd_Argc() != 2 ) @@ -1251,17 +1442,24 @@ void CL_PlayDemo_f( void ) return; } - Q_strncpy( demoname, Cmd_Argv( 1 ), sizeof( demoname ) - 1 ); - Q_snprintf( filename, sizeof( filename ), "demos/%s.dem", demoname ); + Q_strncpy( demoname, Cmd_Argv( 1 ), sizeof( demoname )); + COM_StripExtension( demoname ); + Q_snprintf( filename1, sizeof( filename1 ), "%s.dem", demoname ); + Q_snprintf( filename2, sizeof( filename2 ), "demos/%s.dem", demoname ); - if( !FS_FileExists( filename, true )) + if( FS_FileExists( filename1, true )) { - MsgDev( D_ERROR, "couldn't open %s\n", filename ); + CL_PlayDemoQuake( filename1 ); + return; + } + else if( !FS_FileExists( filename2, true )) + { + MsgDev( D_ERROR, "couldn't open %s\n", filename2 ); CL_DemoAborted(); return; } - cls.demofile = FS_Open( filename, "rb", true ); + cls.demofile = FS_Open( filename2, "rb", true ); Q_strncpy( cls.demoname, demoname, sizeof( cls.demoname )); Q_strncpy( gameui.globals->demoname, demoname, sizeof( gameui.globals->demoname )); @@ -1270,7 +1468,7 @@ void CL_PlayDemo_f( void ) if( demo.header.id != IDEMOHEADER ) { - MsgDev( D_ERROR, "%s is not a demo file\n", filename ); + MsgDev( D_ERROR, "%s is not a demo file\n", demoname ); CL_DemoAborted(); return; } @@ -1297,24 +1495,6 @@ void CL_PlayDemo_f( void ) return; } - if( cls.changedemo ) - { - S_StopAllSounds( true ); - SCR_BeginLoadingPlaque( false ); - - CL_ClearState (); - CL_InitEdicts (); // re-arrange edicts - } - else - { - // NOTE: at this point demo is still valid - CL_Disconnect(); - Host_ShutdownServer(); - - Con_FastClose(); - UI_SetActiveMenu( false ); - } - // allocate demo entries demo.directory.entries = Mem_Malloc( cls.mempool, sizeof( demoentry_t ) * demo.directory.numentries ); @@ -1328,22 +1508,7 @@ void CL_PlayDemo_f( void ) FS_Seek( cls.demofile, demo.entry->offset, SEEK_SET ); - cls.demoplayback = true; - cls.state = ca_connected; - cl.background = (cls.demonum != -1) ? true : false; - cls.spectator = false; - cls.signon = 0; - - demo.starttime = CL_GetDemoPlaybackClock(); // for determining whether to read another message - - Netchan_Setup( NS_CLIENT, &cls.netchan, net_from, Cvar_VariableInteger( "net_qport" ), NULL, CL_GetFragmentSize ); - - memset( demo.cmds, 0, sizeof( demo.cmds )); - demo.angle_position = 1; - demo.framecount = 0; - cls.lastoutgoingcommand = -1; - cls.nextcmdtime = host.realtime; - cl.last_command_ack = -1; + CL_DemoStartPlayback( DEMO_XASH3D ); // g-cont. is this need? Q_strncpy( cls.servername, demoname, sizeof( cls.servername )); @@ -1402,15 +1567,7 @@ void CL_StartDemos_f( void ) for( i = 1; i < c + 1; i++ ) Q_strncpy( cls.demos[i-1], Cmd_Argv( i ), sizeof( cls.demos[0] )); - - if( !SV_Active() && !cls.demoplayback ) - { - // run demos loop in background mode - Cvar_SetValue( "v_dark", 1.0f ); - cls.demonum = 0; - CL_NextDemo (); - } - else cls.demonum = -1; + cls.demos_pending = true; } /* diff --git a/engine/client/cl_frame.c b/engine/client/cl_frame.c index e2dd4109..80b65502 100644 --- a/engine/client/cl_frame.c +++ b/engine/client/cl_frame.c @@ -1074,7 +1074,7 @@ void CL_LinkPacketEntities( frame_t *frame ) if( ent->curstate.rendermode == kRenderNormal ) { // auto 'solid' faces - if( FBitSet( ent->model->flags, MODEL_TRANSPARENT ) && FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE )) + if( FBitSet( ent->model->flags, MODEL_TRANSPARENT ) && CL_IsQuakeCompatible( )) { ent->curstate.rendermode = kRenderTransAlpha; ent->curstate.renderamt = 255; diff --git a/engine/client/cl_main.c b/engine/client/cl_main.c index 5c601122..4abc65b6 100644 --- a/engine/client/cl_main.c +++ b/engine/client/cl_main.c @@ -146,6 +146,19 @@ qboolean CL_IsBackgroundMap( void ) return ( cl.background && !cls.demoplayback ); } +qboolean CL_IsQuakeCompatible( void ) +{ + // feature set + if( FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE )) + return true; + + // quake demo playing + if( cls.demoplayback == DEMO_QUAKE1 ) + return true; + + return false; +} + char *CL_Userinfo( void ) { return cls.userinfo; @@ -242,14 +255,31 @@ static float CL_LerpPoint( void ) if( f == 0.0f || cls.timedemo ) { cl.time = cl.mtime[0]; - - // g-cont. probably this is redundant - if( cls.demoplayback ) - cl.oldtime = cl.mtime[0] - cl_clientframetime(); - return 1.0f; } + if( f > 0.1f ) + { + // dropped packet, or start of demo + cl.mtime[1] = cl.mtime[0] - 0.1f; + f = 0.1f; + } +#if 1 + frac = (cl.time - cl.mtime[1]) / f; + + if( frac < 0.0f ) + { + if( frac < -0.01 ) + cl.time = cl.mtime[1]; + frac = 0.0f; + } + else if( frac > 1.0f ) + { + if( frac > 1.01 ) + cl.time = cl.mtime[0]; + frac = 1.0f; + } +#else if( cl_interp->value > 0.001f ) { // manual lerp value (goldsrc mode) @@ -260,7 +290,7 @@ static float CL_LerpPoint( void ) // automatic lerp (classic mode) frac = ( cl.time - cl.mtime[1] ) / f; } - +#endif return frac; } @@ -2001,7 +2031,10 @@ void CL_ReadNetMessage( void ) if( !cls.demoplayback && !Netchan_Process( &cls.netchan, &net_message )) continue; // wasn't accepted for some reason - CL_ParseServerMessage( &net_message, true ); + // run special handler for quake demos + if( cls.demoplayback == DEMO_QUAKE1 ) + CL_ParseQuakeMessage( &net_message, true ); + else CL_ParseServerMessage( &net_message, true ); cl.send_reply = true; } @@ -2044,7 +2077,7 @@ void CL_ReadPackets( void ) // decide the simulation time cl.oldtime = cl.time; - if( !cls.demoplayback && !cl.paused ) + if( cls.demoplayback != DEMO_XASH3D && !cl.paused ) cl.time += host.frametime; // demo time diff --git a/engine/client/cl_parse.c b/engine/client/cl_parse.c index 0660823b..a5029f17 100644 --- a/engine/client/cl_parse.c +++ b/engine/client/cl_parse.c @@ -23,200 +23,8 @@ GNU General Public License for more details. #include "hltv.h" #include "input.h" -#define MSG_COUNT 32 // last 32 messages parsed -#define MSG_MASK (MSG_COUNT - 1) - int CL_UPDATE_BACKUP = SINGLEPLAYER_BACKUP; -const char *svc_strings[svc_lastmsg+1] = -{ - "svc_bad", - "svc_nop", - "svc_disconnect", - "svc_event", - "svc_changing", - "svc_setview", - "svc_sound", - "svc_time", - "svc_print", - "svc_stufftext", - "svc_setangle", - "svc_serverdata", - "svc_lightstyle", - "svc_updateuserinfo", - "svc_deltatable", - "svc_clientdata", - "svc_resource", - "svc_pings", - "svc_particle", - "svc_restoresound", - "svc_spawnstatic", - "svc_event_reliable", - "svc_spawnbaseline", - "svc_temp_entity", - "svc_setpause", - "svc_signonnum", - "svc_centerprint", - "svc_unused27", - "svc_unused28", - "svc_unused29", - "svc_intermission", - "svc_finale", - "svc_cdtrack", - "svc_restore", - "svc_cutscene", - "svc_weaponanim", - "svc_bspdecal", - "svc_roomtype", - "svc_addangle", - "svc_usermessage", - "svc_packetentities", - "svc_deltapacketentities", - "svc_choke", - "svc_resourcelist", - "svc_deltamovevars", - "svc_resourcerequest", - "svc_customization", - "svc_crosshairangle", - "svc_soundfade", - "svc_filetxferfailed", - "svc_hltv", - "svc_director", - "svc_voiceinit", - "svc_voicedata", - "svc_unused54", - "svc_unused55", - "svc_resourcelocation", - "svc_querycvarvalue", - "svc_querycvarvalue2", -}; - -typedef struct -{ - int command; - int starting_offset; - int frame_number; -} oldcmd_t; - -typedef struct -{ - oldcmd_t oldcmd[MSG_COUNT]; - int currentcmd; - qboolean parsing; -} msg_debug_t; - -static msg_debug_t cls_message_debug; -static int starting_count; - -const char *CL_MsgInfo( int cmd ) -{ - static string sz; - - Q_strcpy( sz, "???" ); - - if( cmd >= 0 && cmd <= svc_lastmsg ) - { - // get engine message name - Q_strncpy( sz, svc_strings[cmd], sizeof( sz )); - } - else if( cmd > svc_lastmsg && cmd <= ( svc_lastmsg + MAX_USER_MESSAGES )) - { - int i; - - for( i = 0; i < MAX_USER_MESSAGES; i++ ) - { - if( clgame.msg[i].number == cmd ) - { - Q_strncpy( sz, clgame.msg[i].name, sizeof( sz )); - break; - } - } - } - return sz; -} - -/* -===================== -CL_Parse_RecordCommand - -record new message params into debug buffer -===================== -*/ -void CL_Parse_RecordCommand( int cmd, int startoffset ) -{ - int slot; - - if( cmd == svc_nop ) return; - - slot = ( cls_message_debug.currentcmd++ & MSG_MASK ); - cls_message_debug.oldcmd[slot].command = cmd; - cls_message_debug.oldcmd[slot].starting_offset = startoffset; - cls_message_debug.oldcmd[slot].frame_number = host.framecount; -} - -/* -===================== -CL_WriteErrorMessage - -write net_message into buffer.dat for debugging -===================== -*/ -void CL_WriteErrorMessage( int current_count, sizebuf_t *msg ) -{ - const char *buffer_file = "buffer.dat"; - file_t *fp; - - fp = FS_Open( buffer_file, "wb", false ); - if( !fp ) return; - - FS_Write( fp, &starting_count, sizeof( int )); - FS_Write( fp, ¤t_count, sizeof( int )); - FS_Write( fp, MSG_GetData( msg ), MSG_GetMaxBytes( msg )); - FS_Close( fp ); - - Con_Printf( "Wrote erroneous message to %s\n", buffer_file ); -} - -/* -===================== -CL_WriteMessageHistory - -list last 32 messages for debugging net troubleshooting -===================== -*/ -void CL_WriteMessageHistory( void ) -{ - oldcmd_t *old, *failcommand; - sizebuf_t *msg = &net_message; - int i, thecmd; - - if( !cls.initialized || cls.state == ca_disconnected ) - return; - - if( !cls_message_debug.parsing ) - return; - - Con_Printf( "Last %i messages parsed.\n", MSG_COUNT ); - - // finish here - thecmd = cls_message_debug.currentcmd - 1; - thecmd -= ( MSG_COUNT - 1 ); // back up to here - - for( i = 0; i < MSG_COUNT - 1; i++ ) - { - thecmd &= MSG_MASK; - old = &cls_message_debug.oldcmd[thecmd]; - Con_Printf( "%i %04i %s\n", old->frame_number, old->starting_offset, CL_MsgInfo( old->command )); - thecmd++; - } - - failcommand = &cls_message_debug.oldcmd[thecmd]; - Con_Printf( "BAD: %3i:%s\n", MSG_GetNumBytesRead( msg ) - 1, CL_MsgInfo( failcommand->command )); - if( host_developer.value >= DEV_EXTENDED ) - CL_WriteErrorMessage( MSG_GetNumBytesRead( msg ) - 1, msg ); - cls_message_debug.parsing = false; -} - /* =============== CL_UserMsgStub @@ -387,6 +195,9 @@ void CL_ParseServerTime( sizebuf_t *msg ) cl.mtime[1] = cl.mtime[0]; cl.mtime[0] = MSG_ReadFloat( msg ); + if( cls.demoplayback == DEMO_QUAKE1 ) + return; // don't mess the time + if( cl.maxclients == 1 ) cl.time = cl.mtime[0]; @@ -557,7 +368,7 @@ void CL_ParseStaticEntity( sizebuf_t *msg ) if( ent->curstate.rendermode == kRenderNormal && ent->model != NULL ) { // auto 'solid' faces - if( FBitSet( ent->model->flags, MODEL_TRANSPARENT ) && FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE )) + if( FBitSet( ent->model->flags, MODEL_TRANSPARENT ) && CL_IsQuakeCompatible( )) { ent->curstate.rendermode = kRenderTransAlpha; ent->curstate.renderamt = 255; @@ -2181,21 +1992,6 @@ void CL_ParseUserMessage( sizebuf_t *msg, int svc_num ) } } -/* -===================== -CL_ResetFrame -===================== -*/ -void CL_ResetFrame( frame_t *frame ) -{ - memset( &frame->graphdata, 0, sizeof( netbandwidthgraph_t )); - frame->receivedtime = host.realtime; - frame->valid = true; - frame->choked = false; - frame->latency = 0.0; - frame->time = cl.mtime[0]; -} - /* ===================================================================== @@ -2216,8 +2012,8 @@ void CL_ParseServerMessage( sizebuf_t *msg, qboolean normal_message ) int cmd, param1, param2; int old_background; - cls_message_debug.parsing = true; // begin parsing - starting_count = MSG_GetNumBytesRead( msg ); // updates each frame + cls.starting_count = MSG_GetNumBytesRead( msg ); // updates each frame + CL_Parse_Debug( true ); // begin parsing if( normal_message ) { @@ -2278,7 +2074,7 @@ void CL_ParseServerMessage( sizebuf_t *msg, qboolean normal_message ) cls.changelevel = true; S_StopAllSounds( true ); - MsgDev( D_INFO, "Server changing, reconnecting\n" ); + Con_Printf( "Server changing, reconnecting\n" ); if( cls.demoplayback ) { @@ -2289,7 +2085,7 @@ void CL_ParseServerMessage( sizebuf_t *msg, qboolean normal_message ) CL_ClearState (); CL_InitEdicts (); // re-arrange edicts } - else MsgDev( D_INFO, "Server disconnected, reconnecting\n" ); + else Con_Printf( "Server disconnected, reconnecting\n" ); if( cls.demoplayback ) { @@ -2299,7 +2095,8 @@ void CL_ParseServerMessage( sizebuf_t *msg, qboolean normal_message ) else { // g-cont. local client skip the challenge - if( SV_Active()) cls.state = ca_disconnected; + if( SV_Active( )) + cls.state = ca_disconnected; else cls.state = ca_connecting; cl.background = old_background; cls.connect_time = MAX_HEARTBEAT; @@ -2477,8 +2274,8 @@ void CL_ParseServerMessage( sizebuf_t *msg, qboolean normal_message ) } } - cl.frames[cl.parsecountmod].graphdata.msgbytes += MSG_GetNumBytesRead( msg ) - starting_count; - cls_message_debug.parsing = false; // done + cl.frames[cl.parsecountmod].graphdata.msgbytes += MSG_GetNumBytesRead( msg ) - cls.starting_count; + CL_Parse_Debug( false ); // done // we don't know if it is ok to save a demo message until // after we have parsed the frame @@ -2486,11 +2283,11 @@ void CL_ParseServerMessage( sizebuf_t *msg, qboolean normal_message ) { if( cls.demorecording && !cls.demowaiting ) { - CL_WriteDemoMessage( false, starting_count, msg ); + CL_WriteDemoMessage( false, cls.starting_count, msg ); } else if( cls.state != ca_active ) { - CL_WriteDemoMessage( true, starting_count, msg ); + CL_WriteDemoMessage( true, cls.starting_count, msg ); } } } \ No newline at end of file diff --git a/engine/client/cl_pmove.c b/engine/client/cl_pmove.c index 3259e0ed..c3928d4b 100644 --- a/engine/client/cl_pmove.c +++ b/engine/client/cl_pmove.c @@ -115,6 +115,10 @@ qboolean CL_IsPredicted( void ) { if( cl_nopred->value || cl.intermission ) return false; + + // never predict the quake demos + if( cls.demoplayback == DEMO_QUAKE1 ) + return false; return true; } diff --git a/engine/client/cl_qparse.c b/engine/client/cl_qparse.c new file mode 100644 index 00000000..682a4001 --- /dev/null +++ b/engine/client/cl_qparse.c @@ -0,0 +1,1007 @@ +/* +cl_qparse.c - parse a message received from the Quake demo +Copyright (C) 2018 Uncle Mike + +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 3 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. +*/ + +#include "common.h" +#include "client.h" +#include "net_encode.h" +#include "particledef.h" +#include "gl_local.h" +#include "cl_tent.h" +#include "shake.h" +#include "hltv.h" +#include "input.h" + +#define STAT_HEALTH 0 +#define STAT_FRAGS 1 +#define STAT_WEAPON 2 +#define STAT_AMMO 3 +#define STAT_ARMOR 4 +#define STAT_WEAPONFRAME 5 +#define STAT_SHELLS 6 +#define STAT_NAILS 7 +#define STAT_ROCKETS 8 +#define STAT_CELLS 9 +#define STAT_ACTIVEWEAPON 10 +#define STAT_TOTALSECRETS 11 +#define STAT_TOTALMONSTERS 12 +#define STAT_SECRETS 13 // bumped on client side by svc_foundsecret +#define STAT_MONSTERS 14 // bumped by svc_killedmonster +#define MAX_STATS 32 + +static char msg_buf[8192]; +static sizebuf_t msg_demo; + +/* +================== +CL_DispatchQuakeMessage + +================== +*/ +static void CL_DispatchQuakeMessage( const char *name ) +{ + CL_DispatchUserMessage( name, msg_demo.iCurBit >> 3, msg_demo.pData ); + MSG_Clear( &msg_demo ); // don't forget to clear buffer +} + +/* +================== +CL_ParseQuakeStats + +redirect to qwrap->client +================== +*/ +static void CL_ParseQuakeStats( sizebuf_t *msg ) +{ + MSG_WriteByte( &msg_demo, MSG_ReadByte( msg )); // stat num + MSG_WriteLong( &msg_demo, MSG_ReadLong( msg )); // stat value + CL_DispatchQuakeMessage( "Stats" ); +} + +/* +================== +CL_ParseQuakeStats + +redirect to qwrap->client +================== +*/ +static int CL_UpdateQuakeStats( sizebuf_t *msg, int statnum, qboolean has_update ) +{ + int value = 0; + + MSG_WriteByte( &msg_demo, statnum ); // stat num + + if( has_update ) + { + if( statnum == STAT_HEALTH ) + value = MSG_ReadShort( msg ); + else value = MSG_ReadByte( msg ); + } + + MSG_WriteLong( &msg_demo, value ); + CL_DispatchQuakeMessage( "Stats" ); + + return value; +} + +/* +================== +CL_ParseQuakeSound + +================== +*/ +static void CL_ParseQuakeSound( sizebuf_t *msg ) +{ + int channel, sound; + int flags, entnum; + float volume, attn; + sound_t handle; + vec3_t pos; + + flags = MSG_ReadByte( msg ); + + if( FBitSet( flags, SND_VOLUME )) + volume = (float)MSG_ReadByte( msg ) / 255.0f; + else volume = VOL_NORM; + + if( FBitSet( flags, SND_ATTENUATION )) + attn = (float)MSG_ReadByte( msg ) / 64.0f; + else attn = ATTN_NONE; + + channel = MSG_ReadWord( msg ); + sound = MSG_ReadByte( msg ); // Quake1 have max 255 precached sounds. erm + + // positioned in space + MSG_ReadVec3Coord( msg, pos ); + + entnum = channel >> 3; // entity reletive + channel &= 7; + + // see precached sound + handle = cl.sound_index[sound]; + + if( !cl.audio_prepped ) + { + Con_Printf( S_WARN "CL_StartSoundPacket: ignore sound message: too early\n" ); + return; // too early + } + + S_StartSound( pos, entnum, channel, handle, volume, attn, PITCH_NORM, flags ); +} + +/* +================== +CL_ParseQuakeServerInfo + +================== +*/ +static void CL_ParseQuakeServerInfo( sizebuf_t *msg ) +{ + resource_t *pResource; + const char *pResName; + int gametype; + int i; + + Con_Reportf( "Serverdata packet received.\n" ); + cls.timestart = Sys_DoubleTime(); + + cls.demowaiting = false; // server is changed + + // wipe the client_t struct + if( !cls.changelevel && !cls.changedemo ) + CL_ClearState (); + cl.background = (cls.demonum != -1) ? true : false; + cls.state = ca_connected; + + // parse protocol version number + i = MSG_ReadLong( msg ); + + if( i != PROTOCOL_VERSION_QUAKE ) + Host_Error( "Server use invalid protocol (%i should be %i)\n", i, PROTOCOL_VERSION_QUAKE ); + + cl.maxclients = MSG_ReadByte( msg ); + gametype = MSG_ReadByte( msg ); // FIXME: tell the client about gametype + clgame.maxEntities = GI->max_edicts; + clgame.maxEntities = bound( 600, clgame.maxEntities, MAX_EDICTS ); + clgame.maxModels = MAX_MODELS; + Q_strncpy( clgame.maptitle, MSG_ReadString( msg ), MAX_STRING ); + + // Re-init hud video, especially if we changed game directories + clgame.dllFuncs.pfnVidInit(); + + if( Con_FixedFont( )) + { + // seperate the printfs so the server message can have a color + Con_Print( "\n\35\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\37\n" ); + Con_Print( va( "%c%s\n\n", 2, clgame.maptitle )); + } + + // multiplayer game? + if( cl.maxclients > 1 ) + { + // allow console in multiplayer games + host.allow_console = true; + + // loading user settings + CSCR_LoadDefaultCVars( "user.scr" ); + + if( r_decals->value > mp_decals.value ) + Cvar_SetValue( "r_decals", mp_decals.value ); + } + else Cvar_Reset( "r_decals" ); + + // re-init mouse + if( cl.background ) + host.mouse_visible = false; + + if( cl.background ) // tell the game parts about background state + Cvar_FullSet( "cl_background", "1", FCVAR_READ_ONLY ); + else Cvar_FullSet( "cl_background", "0", FCVAR_READ_ONLY ); + + S_StopBackgroundTrack (); + + if( !cls.changedemo ) + UI_SetActiveMenu( cl.background ); + else if( !cls.demoplayback ) + Key_SetKeyDest( key_menu ); + + // don't reset cursor in background mode + if( cl.background ) + IN_MouseRestorePos(); + + // will be changed later + cl.viewentity = cl.playernum + 1; + gameui.globals->maxClients = cl.maxclients; + Q_strncpy( gameui.globals->maptitle, clgame.maptitle, sizeof( gameui.globals->maptitle )); + + if( !cls.changelevel && !cls.changedemo ) + CL_InitEdicts (); // re-arrange edicts + + // Quake just have a large packet of initialization data + for( i = 1; i < MAX_MODELS; i++ ) + { + pResName = MSG_ReadString( msg ); + + if( !COM_CheckString( pResName )) + break; // end of list + + pResource = Mem_Calloc( cls.mempool, sizeof( resource_t )); + pResource->type = t_model; + + Q_strncpy( pResource->szFileName, pResName, sizeof( pResource->szFileName )); + if( i == 1 ) Q_strncpy( clgame.mapname, pResName, sizeof( clgame.mapname )); + pResource->nDownloadSize = -1; + pResource->nIndex = i; + + CL_AddToResourceList( pResource, &cl.resourcesneeded ); + } + + for( i = 1; i < MAX_SOUNDS; i++ ) + { + pResName = MSG_ReadString( msg ); + + if( !COM_CheckString( pResName )) + break; // end of list + + pResource = Mem_Calloc( cls.mempool, sizeof( resource_t )); + pResource->type = t_sound; + + Q_strncpy( pResource->szFileName, pResName, sizeof( pResource->szFileName )); + pResource->nDownloadSize = -1; + pResource->nIndex = i; + + CL_AddToResourceList( pResource, &cl.resourcesneeded ); + } + + // get splash name + if( cls.demoplayback && ( cls.demonum != -1 )) + Cvar_Set( "cl_levelshot_name", va( "levelshots/%s_%s", cls.demoname, glState.wideScreen ? "16x9" : "4x3" )); + else Cvar_Set( "cl_levelshot_name", va( "levelshots/%s_%s", clgame.mapname, glState.wideScreen ? "16x9" : "4x3" )); + Cvar_SetValue( "scr_loading", 0.0f ); // reset progress bar + + if(( cl_allow_levelshots->value && !cls.changelevel ) || cl.background ) + { + if( !FS_FileExists( va( "%s.bmp", cl_levelshot_name->string ), true )) + Cvar_Set( "cl_levelshot_name", "*black" ); // render a black screen + cls.scrshot_request = scrshot_plaque; // request levelshot even if exist (check filetime) + } + + memset( &clgame.movevars, 0, sizeof( clgame.movevars )); + memset( &clgame.oldmovevars, 0, sizeof( clgame.oldmovevars )); + memset( &clgame.centerPrint, 0, sizeof( clgame.centerPrint )); + cl.video_prepped = false; + cl.audio_prepped = false; + + // now we can start to precache + CL_BatchResourceRequest( true ); + + clgame.movevars.wateralpha = 1.0f; + clgame.entities->curstate.scale = 0.0f; + clgame.movevars.waveHeight = 0.0f; + clgame.movevars.zmax = 14172.0f; // 8192 * 1.74 + clgame.movevars.gravity = 800.0f; // quake doesn't write gravity in demos + + memcpy( &clgame.oldmovevars, &clgame.movevars, sizeof( movevars_t )); +} + +/* +================== +CL_ParseQuakeClientData + +================== +*/ +static void CL_ParseQuakeClientData( sizebuf_t *msg ) +{ + int i, bits = MSG_ReadWord( msg ); + frame_t *frame; + + // this is the frame update that this message corresponds to + i = cls.netchan.incoming_sequence; + + cl.parsecount = i; // ack'd incoming messages. + cl.parsecountmod = cl.parsecount & CL_UPDATE_MASK; // index into window. + frame = &cl.frames[cl.parsecountmod]; // frame at index. + frame->time = cl.mtime[0]; // mark network received time + frame->receivedtime = host.realtime; // time now that we are parsing. + memset( &frame->graphdata, 0, sizeof( netbandwidthgraph_t )); + memset( frame->flags, 0, sizeof( frame->flags )); + frame->first_entity = cls.next_client_entities; + frame->num_entities = 0; + frame->valid = true; // assume valid + + if( FBitSet( bits, SU_VIEWHEIGHT )) + frame->clientdata.view_ofs[2] = MSG_ReadChar( msg ); + else frame->clientdata.view_ofs[2] = 22.0f; + + if( FBitSet( bits, SU_IDEALPITCH )) + cl.local.idealpitch = MSG_ReadChar( msg ); + else cl.local.idealpitch = 0; + + for( i = 0; i < 3; i++ ) + { + if( FBitSet( bits, SU_PUNCH1 << i )) + frame->clientdata.punchangle[i] = (float)MSG_ReadChar( msg ); + else frame->clientdata.punchangle[i] = 0.0f; + + if( FBitSet( bits, ( SU_VELOCITY1 << i ))) + frame->clientdata.velocity[i] = MSG_ReadChar( msg ) * 16.0f; + else frame->clientdata.velocity[i] = 0; + } + + if( FBitSet( bits, SU_ONGROUND )) + SetBits( frame->clientdata.flags, FL_ONGROUND ); + if( FBitSet( bits, SU_INWATER )) + SetBits( frame->clientdata.flags, FL_INWATER ); + + // [always sent] + MSG_WriteLong( &msg_demo, MSG_ReadLong( msg )); + CL_DispatchQuakeMessage( "Items" ); + + if( FBitSet( bits, SU_WEAPONFRAME )) + CL_UpdateQuakeStats( msg, STAT_WEAPONFRAME, true ); + else CL_UpdateQuakeStats( msg, STAT_WEAPONFRAME, false ); + + if( FBitSet( bits, SU_ARMOR )) + CL_UpdateQuakeStats( msg, STAT_ARMOR, true ); + else CL_UpdateQuakeStats( msg, STAT_ARMOR, false ); + + if( FBitSet( bits, SU_WEAPON )) + frame->clientdata.viewmodel = CL_UpdateQuakeStats( msg, STAT_WEAPON, true ); + else frame->clientdata.viewmodel = CL_UpdateQuakeStats( msg, STAT_WEAPON, false ); + + cl.local.health = CL_UpdateQuakeStats( msg, STAT_HEALTH, true ); + CL_UpdateQuakeStats( msg, STAT_AMMO, true ); + CL_UpdateQuakeStats( msg, STAT_SHELLS, true ); + CL_UpdateQuakeStats( msg, STAT_NAILS, true ); + CL_UpdateQuakeStats( msg, STAT_ROCKETS, true ); + CL_UpdateQuakeStats( msg, STAT_CELLS, true ); + CL_UpdateQuakeStats( msg, STAT_ACTIVEWEAPON, true ); +} + +/* +================== +CL_ParseQuakeEntityData + +Parse an entity update message from the server +If an entities model or origin changes from frame to frame, it must be +relinked. Other attributes can change without relinking. +================== +*/ +void CL_ParseQuakeEntityData( sizebuf_t *msg, int bits ) +{ + int i, newnum, pack; + qboolean forcelink; + entity_state_t *state; + frame_t *frame; + cl_entity_t *ent; + + // first update is the final signon stage where we actually receive an entity (i.e., the world at least) + if( cls.signon == ( SIGNONS - 1 )) + { + // we are done with signon sequence. + cls.signon = SIGNONS; + + // Clear loading plaque. + CL_SignonReply (); + } + + // alloc next slot to store update + state = &cls.packet_entities[cls.next_client_entities % cls.num_client_entities]; + cl.validsequence = cls.netchan.incoming_sequence; + frame = &cl.frames[cl.parsecountmod]; + pack = frame->num_entities; + + if( FBitSet( bits, U_MOREBITS )) + { + i = MSG_ReadByte( msg ); + SetBits( bits, i << 8 ); + } + + if( FBitSet( bits, U_LONGENTITY )) + newnum = MSG_ReadWord( msg ); + else newnum = MSG_ReadByte( msg ); + + memset( state, 0, sizeof( *state )); + SetBits( state->entityType, ENTITY_NORMAL ); + state->number = newnum; + + // mark all the players + ent = CL_EDICT_NUM( newnum ); + ent->index = newnum; // enumerate entity index + ent->player = CL_IsPlayerIndex( newnum ); + + if( ent->curstate.msg_time != cl.mtime[1] ) + forcelink = true; // no previous frame to lerp from + else forcelink = false; + + if( FBitSet( bits, U_MODEL )) + state->modelindex = MSG_ReadByte( msg ); + else state->modelindex = ent->baseline.modelindex; + + if( FBitSet( bits, U_FRAME )) + state->frame = MSG_ReadByte( msg ); + else state->frame = ent->baseline.frame; + + if( FBitSet( bits, U_COLORMAP )) + state->colormap = MSG_ReadByte( msg ); + else state->colormap = ent->baseline.colormap; + + if( FBitSet( bits, U_SKIN )) + state->skin = MSG_ReadByte( msg ); + else state->skin = ent->baseline.skin; + + if( FBitSet( bits, U_EFFECTS )) + state->effects = MSG_ReadByte( msg ); + else state->effects = ent->baseline.effects; + + if( FBitSet( bits, U_ORIGIN1 )) + state->origin[0] = MSG_ReadCoord( msg ); + else state->origin[0] = ent->baseline.origin[0]; + + if( FBitSet( bits, U_ANGLE1 )) + state->angles[0] = MSG_ReadAngle( msg ); + else state->angles[0] = ent->baseline.angles[0]; + + if( FBitSet( bits, U_ORIGIN2 )) + state->origin[1] = MSG_ReadCoord( msg ); + else state->origin[1] = ent->baseline.origin[1]; + + if( FBitSet( bits, U_ANGLE2 )) + state->angles[1] = MSG_ReadAngle( msg ); + else state->angles[1] = ent->baseline.angles[1]; + + if( FBitSet( bits, U_ORIGIN3 )) + state->origin[2] = MSG_ReadCoord( msg ); + else state->origin[2] = ent->baseline.origin[2]; + + if( FBitSet( bits, U_ANGLE3 )) + state->angles[2] = MSG_ReadAngle( msg ); + else state->angles[2] = ent->baseline.angles[2]; + + if( FBitSet( bits, U_TRANS )) + { + int temp = MSG_ReadFloat( msg ); + float alpha = MSG_ReadFloat( msg ); + + if( alpha == 0.0f ) alpha = 1.0f; + + if( alpha < 1.0f ) + { + state->rendermode = kRenderTransTexture; + state->renderamt = (int)(alpha * 255.0f); + } + + if( temp == 2 && MSG_ReadFloat( msg )) + SetBits( state->effects, EF_FULLBRIGHT ); + } + + if( FBitSet( bits, U_NOLERP )) + forcelink = true; + + if( FBitSet( state->effects, 16 )) + SetBits( state->effects, EF_NODRAW ); + + if(( newnum - 1 ) == cl.playernum ) + VectorCopy( state->origin, frame->clientdata.origin ); + + if( forcelink ) + { + // interpolation must be reset + SETVISBIT( frame->flags, pack ); + + // release beams from previous entity + CL_KillDeadBeams( ent ); + } + + // add entity to packet + cls.next_client_entities++; + frame->num_entities++; +} + +/* +================== +CL_ParseQuakeParticles + +================== +*/ +void CL_ParseQuakeParticle( sizebuf_t *msg ) +{ + int count, color; + vec3_t org, dir; + + MSG_ReadVec3Coord( msg, org ); + dir[0] = MSG_ReadChar( msg ) * 0.0625f; + dir[1] = MSG_ReadChar( msg ) * 0.0625f; + dir[2] = MSG_ReadChar( msg ) * 0.0625f; + count = MSG_ReadByte( msg ); + color = MSG_ReadByte( msg ); + if( count == 255 ) count = 1024; + + R_RunParticleEffect( org, dir, color, count ); +} + +/* +=================== +CL_ParseQuakeStaticSound + +=================== +*/ +void CL_ParseQuakeStaticSound( sizebuf_t *msg ) +{ + int sound_num; + float vol, attn; + vec3_t org; + + MSG_ReadVec3Coord( msg, org ); + sound_num = MSG_ReadByte( msg ); + vol = (float)MSG_ReadByte( msg ) / 255.0f; + attn = (float)MSG_ReadByte( msg ) / 64.0f; + + S_StartSound( org, 0, CHAN_STATIC, cl.sound_index[sound_num], vol, attn, PITCH_NORM, 0 ); +} + +/* +================== +CL_ParseQuakeDamage + +redirect to qwrap->client +================== +*/ +static void CL_ParseQuakeDamage( sizebuf_t *msg ) +{ + MSG_WriteByte( &msg_demo, MSG_ReadByte( msg )); // armor + MSG_WriteByte( &msg_demo, MSG_ReadByte( msg )); // blood + MSG_WriteCoord( &msg_demo, MSG_ReadCoord( msg )); // direction + MSG_WriteCoord( &msg_demo, MSG_ReadCoord( msg )); // direction + MSG_WriteCoord( &msg_demo, MSG_ReadCoord( msg )); // direction + CL_DispatchQuakeMessage( "Damage" ); +} + +/* +=================== +CL_ParseQuakeStaticEntity + +=================== +*/ +static void CL_ParseQuakeStaticEntity( sizebuf_t *msg ) +{ + entity_state_t state; + cl_entity_t *ent; + int i; + + memset( &state, 0, sizeof( state )); + + state.modelindex = MSG_ReadByte( msg ); + state.frame = MSG_ReadByte( msg ); + state.colormap = MSG_ReadByte( msg ); + state.skin = MSG_ReadByte( msg ); + state.origin[0] = MSG_ReadCoord( msg ); + state.angles[0] = MSG_ReadAngle( msg ); + state.origin[1] = MSG_ReadCoord( msg ); + state.angles[1] = MSG_ReadAngle( msg ); + state.origin[2] = MSG_ReadCoord( msg ); + state.angles[2] = MSG_ReadAngle( msg ); + + i = clgame.numStatics; + if( i >= MAX_STATIC_ENTITIES ) + { + Con_Printf( S_ERROR, "CL_ParseStaticEntity: static entities limit exceeded!\n" ); + return; + } + + ent = &clgame.static_entities[i]; + clgame.numStatics++; + + ent->index = 0; // ??? + ent->baseline = state; + ent->curstate = state; + ent->prevstate = state; + + // statics may be respawned in game e.g. for demo recording + if( cls.state == ca_connected || cls.state == ca_validate ) + ent->trivial_accept = INVALID_HANDLE; + + // setup the new static entity + VectorCopy( ent->curstate.origin, ent->origin ); + VectorCopy( ent->curstate.angles, ent->angles ); + ent->model = CL_ModelHandle( state.modelindex ); + ent->curstate.framerate = 1.0f; + CL_ResetLatchedVars( ent, true ); + + if( ent->model != NULL ) + { + // auto 'solid' faces + if( FBitSet( ent->model->flags, MODEL_TRANSPARENT ) && CL_IsQuakeCompatible( )) + { + ent->curstate.rendermode = kRenderTransAlpha; + ent->curstate.renderamt = 255; + } + } + + R_AddEfrags( ent ); // add link +} + +/* +=================== +CL_ParseQuakeBaseline + +=================== +*/ +static void CL_ParseQuakeBaseline( sizebuf_t *msg ) +{ + entity_state_t state; + cl_entity_t *ent; + int newnum; + + memset( &state, 0, sizeof( state )); + newnum = MSG_ReadWord( msg ); // entnum + + if( newnum >= clgame.maxEntities ) + Host_Error( "CL_AllocEdict: no free edicts\n" ); + + ent = CL_EDICT_NUM( newnum ); + memset( &ent->prevstate, 0, sizeof( ent->prevstate )); + ent->index = newnum; + + // parse baseline + state.modelindex = MSG_ReadByte( msg ); + state.frame = MSG_ReadByte( msg ); + state.colormap = MSG_ReadByte( msg ); + state.skin = MSG_ReadByte( msg ); + state.origin[0] = MSG_ReadCoord( msg ); + state.angles[0] = MSG_ReadAngle( msg ); + state.origin[1] = MSG_ReadCoord( msg ); + state.angles[1] = MSG_ReadAngle( msg ); + state.origin[2] = MSG_ReadCoord( msg ); + state.angles[2] = MSG_ReadAngle( msg ); + ent->player = CL_IsPlayerIndex( newnum ); + + memcpy( &ent->baseline, &state, sizeof( entity_state_t )); + memcpy( &ent->prevstate, &state, sizeof( entity_state_t )); +} + +/* +=================== +CL_ParseQuakeTempEntity + +=================== +*/ +static void CL_ParseQuakeTempEntity( sizebuf_t *msg ) +{ + int type = MSG_ReadByte( msg ); + + MSG_WriteByte( &msg_demo, type ); + + // TE_LIGHTNING1, TE_LIGHTNING2, TE_LIGHTNING3, TE_BEAM, TE_LIGHTNING4 + if( type == 5 || type == 6 || type == 9 || type == 13 || type == 17 ) + MSG_WriteWord( &msg_demo, MSG_ReadWord( msg )); + + // all temp ents have position at beginning + MSG_WriteCoord( &msg_demo, MSG_ReadCoord( msg )); + MSG_WriteCoord( &msg_demo, MSG_ReadCoord( msg )); + MSG_WriteCoord( &msg_demo, MSG_ReadCoord( msg )); + + // TE_LIGHTNING1, TE_LIGHTNING2, TE_LIGHTNING3, TE_BEAM, TE_EXPLOSION3, TE_LIGHTNING4 + if( type == 5 || type == 6 || type == 9 || type == 13 || type == 16 || type == 17 ) + { + // write endpos for beams + MSG_WriteCoord( &msg_demo, MSG_ReadCoord( msg )); + MSG_WriteCoord( &msg_demo, MSG_ReadCoord( msg )); + MSG_WriteCoord( &msg_demo, MSG_ReadCoord( msg )); + } + + // TE_EXPLOSION2 + if( type == 12 ) + { + MSG_WriteByte( &msg_demo, MSG_ReadByte( msg )); + MSG_WriteByte( &msg_demo, MSG_ReadByte( msg )); + } + + if( type == 17 ) + MSG_WriteString( &msg_demo, MSG_ReadString( msg )); + + // TE_SMOKE (nehahra) + if( type == 18 ) + MSG_WriteByte( &msg_demo, MSG_ReadByte( msg )); + + CL_DispatchQuakeMessage( "TempEntity" ); +} + +/* +=================== +CL_ParseQuakeSignon + +very important message +=================== +*/ +static void CL_ParseQuakeSignon( sizebuf_t *msg ) +{ + int i = MSG_ReadByte( msg ); + + if( i == 3 ) cls.signon = SIGNONS - 1; + Msg( "CL_Signon: %d\n", i ); +} + +/* +================== +CL_ParseNehahraShowLMP + +redirect to qwrap->client +================== +*/ +static void CL_ParseNehahraShowLMP( sizebuf_t *msg ) +{ + MSG_WriteString( &msg_demo, MSG_ReadString( msg )); + MSG_WriteString( &msg_demo, MSG_ReadString( msg )); + MSG_WriteByte( &msg_demo, MSG_ReadByte( msg )); + MSG_WriteByte( &msg_demo, MSG_ReadByte( msg )); + CL_DispatchQuakeMessage( "Stats" ); +} + +/* +================== +CL_ParseNehahraHideLMP + +redirect to qwrap->client +================== +*/ +static void CL_ParseNehahraHideLMP( sizebuf_t *msg ) +{ + MSG_WriteString( &msg_demo, MSG_ReadString( msg )); + CL_DispatchQuakeMessage( "Stats" ); +} + +/* +================== +CL_ParseQuakeMessage + +================== +*/ +void CL_ParseQuakeMessage( sizebuf_t *msg, qboolean normal_message ) +{ + int cmd, param1, param2; + size_t bufStart; + const char *str; + + cls.starting_count = MSG_GetNumBytesRead( msg ); // updates each frame + CL_Parse_Debug( true ); // begin parsing + + // init excise buffer + MSG_Init( &msg_demo, "UserMsg", msg_buf, sizeof( msg_buf )); + + if( normal_message ) + { + // assume no entity/player update this packet + if( cls.state == ca_active ) + { + cl.frames[cls.netchan.incoming_sequence & CL_UPDATE_MASK].valid = false; + cl.frames[cls.netchan.incoming_sequence & CL_UPDATE_MASK].choked = false; + } + else + { + CL_ResetFrame( &cl.frames[cls.netchan.incoming_sequence & CL_UPDATE_MASK] ); + } + } + + // parse the message + while( 1 ) + { + if( MSG_CheckOverflow( msg )) + { + Host_Error( "CL_ParseServerMessage: overflow!\n" ); + return; + } + + // mark start position + bufStart = MSG_GetNumBytesRead( msg ); + + // end of message (align bits) + if( MSG_GetNumBitsLeft( msg ) < 8 ) + break; + + cmd = MSG_ReadServerCmd( msg ); + + // if the high bit of the command byte is set, it is a fast update + if( FBitSet( cmd, 128 )) + { + CL_ParseQuakeEntityData( msg, cmd & 127 ); + continue; + } + + Msg( "%s\n", CL_MsgInfo( cmd )); + + // record command for debugging spew on parse problem + CL_Parse_RecordCommand( cmd, bufStart ); + + // other commands + switch( cmd ) + { + case svc_nop: + // this does nothing + break; + case svc_disconnect: + CL_DemoCompleted (); + break; + case svc_updatestat: + CL_ParseQuakeStats( msg ); + break; + case svc_version: + param1 = MSG_ReadLong( msg ); + if( param1 != PROTOCOL_VERSION_QUAKE ) + Host_Error( "Server is protocol %i instead of %i\n", param1, PROTOCOL_VERSION_QUAKE ); + break; + case svc_setview: + CL_ParseViewEntity( msg ); + break; + case svc_sound: + CL_ParseQuakeSound( msg ); + cl.frames[cl.parsecountmod].graphdata.sound += MSG_GetNumBytesRead( msg ) - bufStart; + break; + case svc_time: + CL_ParseServerTime( msg ); + break; + case svc_print: + Con_Printf( "%s", MSG_ReadString( msg )); + break; + case svc_stufftext: + str = MSG_ReadString( msg ); + Msg( "%s\n", str ); + Cbuf_AddText( str ); + break; + case svc_setangle: + cl.viewangles[0] = MSG_ReadAngle( msg ); + cl.viewangles[1] = MSG_ReadAngle( msg ); + cl.viewangles[2] = MSG_ReadAngle( msg ); + break; + case svc_serverdata: + Cbuf_Execute(); // make sure any stuffed commands are done + CL_ParseQuakeServerInfo( msg ); + break; + case svc_lightstyle: + param1 = MSG_ReadByte( msg ); + str = MSG_ReadString( msg ); + CL_SetLightstyle( param1, str, cl.mtime[0] ); + break; + case svc_updatename: + param1 = MSG_ReadByte( msg ); + Q_strncpy( cl.players[param1].name, MSG_ReadString( msg ), sizeof( cl.players[0].name )); + Q_strncpy( cl.players[param1].model, "player", sizeof( cl.players[0].name )); + break; + case svc_updatefrags: + param1 = MSG_ReadByte( msg ); + param2 = MSG_ReadShort( msg ); + // FIXME: tell the client about scores + break; + case svc_clientdata: + CL_ParseQuakeClientData( msg ); + cl.frames[cl.parsecountmod].graphdata.client += MSG_GetNumBytesRead( msg ) - bufStart; + break; + case svc_stopsound: + param1 = MSG_ReadWord( msg ); + S_StopSound( param1 >> 3, param1 & 7, NULL ); + cl.frames[cl.parsecountmod].graphdata.sound += MSG_GetNumBytesRead( msg ) - bufStart; + break; + case svc_updatecolors: + param1 = MSG_ReadByte( msg ); + param2 = MSG_ReadByte( msg ); + cl.players[param1].topcolor = param2 & 0xF0; + cl.players[param1].bottomcolor = (param2 & 15) << 4; + break; + case svc_particle: + CL_ParseQuakeParticle( msg ); + break; + case svc_damage: + CL_ParseQuakeDamage( msg ); + break; + case svc_spawnstatic: + CL_ParseQuakeStaticEntity( msg ); + break; + case svc_spawnbinary: + // never used in Quake + break; + case svc_spawnbaseline: + CL_ParseQuakeBaseline( msg ); + break; + case svc_temp_entity: + CL_ParseQuakeTempEntity( msg ); + cl.frames[cl.parsecountmod].graphdata.tentities += MSG_GetNumBytesRead( msg ) - bufStart; + break; + case svc_setpause: + cl.paused = MSG_ReadByte( msg ); + break; + case svc_signonnum: + CL_ParseQuakeSignon( msg ); + break; + case svc_centerprint: + str = MSG_ReadString( msg ); + CL_DispatchUserMessage( "HudText", Q_strlen( str ), (void *)str ); + break; + case svc_killedmonster: + CL_DispatchQuakeMessage( "KillMonster" ); // just an event + break; + case svc_foundsecret: + CL_DispatchQuakeMessage( "FoundSecret" ); // just an event + break; + case svc_spawnstaticsound: + CL_ParseQuakeStaticSound( msg ); + break; + case svc_intermission: + cl.intermission = 1; + break; + case svc_finale: + CL_ParseFinaleCutscene( msg, 2 ); + break; + case svc_cdtrack: + param1 = MSG_ReadByte( msg ); + param1 = bound( 0, param1, MAX_CDTRACKS ); // tracknum + param2 = MSG_ReadByte( msg ); + param2 = bound( 0, param2, MAX_CDTRACKS ); // loopnum + Msg( "main track %d, loop track %d\n", param1, param2 ); + // FIXME: allow cls.forcetrack from demo + S_StartBackgroundTrack( clgame.cdtracks[param1], clgame.cdtracks[param2], 0, false ); + break; + case svc_sellscreen: + Cmd_ExecuteString( "help" ); + break; + case svc_cutscene: + CL_ParseFinaleCutscene( msg, 3 ); + break; + case svc_hidelmp: + CL_ParseNehahraHideLMP( msg ); + break; + case svc_showlmp: + CL_ParseNehahraShowLMP( msg ); + break; + case svc_skybox: + Q_strncpy( clgame.movevars.skyName, MSG_ReadString( msg ), sizeof( clgame.movevars.skyName )); + break; + case svc_skyboxsize: + MSG_ReadCoord( msg ); // obsolete + break; + case svc_fog: + if( MSG_ReadByte( msg )) + { + float fog_settings[4]; + int packed_fog[4]; + + fog_settings[3] = MSG_ReadFloat( msg ); // density + fog_settings[0] = MSG_ReadByte( msg ); // red + fog_settings[1] = MSG_ReadByte( msg ); // green + fog_settings[2] = MSG_ReadByte( msg ); // blue + packed_fog[0] = fog_settings[0] * 255; + packed_fog[1] = fog_settings[1] * 255; + packed_fog[2] = fog_settings[2] * 255; + packed_fog[3] = fog_settings[3] * 255; + clgame.movevars.fog_settings = (packed_fog[1]<<24)|(packed_fog[2]<<16)|(packed_fog[3]<<8)|packed_fog[0]; + } + else + { + clgame.movevars.fog_settings = 0; + } + break; + default: + Host_Error( "CL_ParseServerMessage: Illegible server message\n" ); + break; + } + } + + cl.frames[cl.parsecountmod].graphdata.msgbytes += MSG_GetNumBytesRead( msg ) - cls.starting_count; + CL_Parse_Debug( false ); // done + + // now process packet. + CL_ProcessPacket( &cl.frames[cl.parsecountmod] ); + + // add new entities into physic lists + CL_SetSolidEntities(); +} \ No newline at end of file diff --git a/engine/client/cl_scrn.c b/engine/client/cl_scrn.c index a6e0c66b..12a0c379 100644 --- a/engine/client/cl_scrn.c +++ b/engine/client/cl_scrn.c @@ -721,6 +721,7 @@ void SCR_Init( void ) // register our commands Cmd_AddCommand( "timerefresh", SCR_TimeRefresh_f, "turn quickly and print rendering statistcs" ); Cmd_AddCommand( "skyname", CL_SetSky_f, "set new skybox by basename" ); + Cmd_AddCommand( "loadsky", CL_SetSky_f, "set new skybox by basename" ); Cmd_AddCommand( "viewpos", SCR_Viewpos_f, "prints current player origin" ); Cmd_AddCommand( "sizeup", SCR_SizeUp_f, "screen size up to 10 points" ); Cmd_AddCommand( "sizedown", SCR_SizeDown_f, "screen size down to 10 points" ); diff --git a/engine/client/cl_tent.c b/engine/client/cl_tent.c index d7289412..236cafe3 100644 --- a/engine/client/cl_tent.c +++ b/engine/client/cl_tent.c @@ -151,7 +151,7 @@ void CL_AddClientResources( void ) int i; // don't request resources from localhost or in quake-compatibility mode - if( cl.maxclients <= 1 || FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE )) + if( cl.maxclients <= 1 || CL_IsQuakeCompatible( )) return; // check sprites first @@ -2808,7 +2808,7 @@ void CL_AddEntityEffects( cl_entity_t *ent ) if( FBitSet( ent->curstate.effects, EF_DIMLIGHT )) { - if( ent->player && !FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE )) + if( ent->player && !CL_IsQuakeCompatible( )) { CL_UpdateFlashlight( ent ); } diff --git a/engine/client/cl_video.c b/engine/client/cl_video.c index 810896e2..ad637240 100644 --- a/engine/client/cl_video.c +++ b/engine/client/cl_video.c @@ -48,6 +48,7 @@ qboolean SCR_NextMovie( void ) { S_StopAllSounds( true ); SCR_StopCinematic(); + CL_CheckStartupDemos(); return false; // don't play movies } @@ -56,6 +57,7 @@ qboolean SCR_NextMovie( void ) S_StopAllSounds( true ); SCR_StopCinematic(); cls.movienum = -1; + CL_CheckStartupDemos(); return false; } @@ -90,6 +92,7 @@ void SCR_CheckStartupVids( void ) { // don't run movies where we in developer-mode cls.movienum = -1; + CL_CheckStartupDemos(); return; } diff --git a/engine/client/client.h b/engine/client/client.h index 4d5f534a..cbfca646 100644 --- a/engine/client/client.h +++ b/engine/client/client.h @@ -53,6 +53,13 @@ GNU General Public License for more details. typedef int sound_t; +typedef enum +{ + DEMO_INACTIVE = 0, + DEMO_XASH3D, + DEMO_QUAKE1 +} demo_mode; + //============================================================================= typedef struct netbandwithgraph_s { @@ -593,6 +600,7 @@ typedef struct float packet_loss; double packet_loss_recalc_time; + int starting_count; // message num readed bits float nextcmdtime; // when can we send the next command packet? int lastoutgoingcommand; // sequence number of last outgoing command @@ -601,6 +609,7 @@ typedef struct int td_lastframe; // to meter out one message a frame int td_startframe; // host_framecount at start double td_starttime; // realtime at second frame of timedemo + int forcetrack; // -1 = use normal cd track // game images int pauseIcon; // draw 'paused' when game in-pause @@ -630,7 +639,8 @@ typedef struct // demo loop control int demonum; // -1 = don't play demos int olddemonum; // restore playing - string demos[MAX_DEMOS]; // when not playing + char demos[MAX_DEMOS][MAX_QPATH]; // when not playing + qboolean demos_pending; // movie playlist int movienum; @@ -738,6 +748,15 @@ void CL_RemoveFromResourceList( resource_t *pResource ); void CL_MoveToOnHandList( resource_t *pResource ); void CL_ClearResourceLists( void ); +// +// cl_debug.c +// +void CL_Parse_Debug( qboolean enable ); +void CL_Parse_RecordCommand( int cmd, int startoffset ); +void CL_ResetFrame( frame_t *frame ); +void CL_WriteMessageHistory( void ); +const char *CL_MsgInfo( int cmd ); + // // cl_main.c // @@ -765,8 +784,10 @@ void CL_WriteDemoMessage( qboolean startup, int start, sizebuf_t *msg ); void CL_WriteDemoUserMessage( const byte *buffer, size_t size ); qboolean CL_DemoReadMessage( byte *buffer, size_t *length ); void CL_DemoInterpolateAngles( void ); +void CL_CheckStartupDemos( void ); void CL_WriteDemoJumpTime( void ); void CL_CloseDemoHeader( void ); +void CL_DemoCompleted( void ); void CL_StopPlayback( void ); void CL_StopRecord( void ); void CL_PlayDemo_f( void ); @@ -776,7 +797,6 @@ void CL_Demos_f( void ); void CL_DeleteDemo_f( void ); void CL_Record_f( void ); void CL_Stop_f( void ); -void CL_FreeDemo( void ); // // cl_events.c @@ -847,6 +867,8 @@ void CL_StartResourceDownloading( const char *pszMessage, qboolean bCustom ); qboolean CL_DispatchUserMessage( const char *pszName, int iSize, void *pbuf ); qboolean CL_RequestMissingResources( void ); void CL_RegisterResources ( sizebuf_t *msg ); +void CL_ParseViewEntity( sizebuf_t *msg ); +void CL_ParseServerTime( sizebuf_t *msg ); // // cl_scrn.c @@ -908,6 +930,11 @@ void CL_PushPMStates( void ); void CL_PopPMStates( void ); void CL_SetUpPlayerPrediction( int dopred, int bIncludeLocalClient ); +// +// cl_qparse.c +// +void CL_ParseQuakeMessage( sizebuf_t *msg, qboolean normal_message ); + // // cl_studio.c // @@ -922,7 +949,7 @@ void CL_ResetLatchedVars( cl_entity_t *ent, qboolean full_reset ); qboolean CL_GetEntitySpatialization( struct channel_s *ch ); qboolean CL_GetMovieSpatialization( struct rawchan_s *ch ); void CL_ComputePlayerOrigin( cl_entity_t *clent ); -void CL_UpdateEntityFields( cl_entity_t *ent ); +void CL_ProcessPacket( frame_t *frame ); void CL_MoveThirdpersonCamera( void ); qboolean CL_IsPlayerIndex( int idx ); void CL_SetIdealPitch( void ); diff --git a/engine/client/gl_image.c b/engine/client/gl_image.c index 1e4eea71..4725def3 100644 --- a/engine/client/gl_image.c +++ b/engine/client/gl_image.c @@ -883,7 +883,7 @@ byte *GL_ApplyFilter( const byte *source, int width, int height ) byte *out = (byte *)source; int i; - if( FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE ) || glConfig.max_multisamples > 1 ) + if( CL_IsQuakeCompatible() || glConfig.max_multisamples > 1 ) return in; for( i = 0; source && i < width * height; i++, in += 4 ) diff --git a/engine/client/gl_rmain.c b/engine/client/gl_rmain.c index e7f0b146..79cde8a7 100644 --- a/engine/client/gl_rmain.c +++ b/engine/client/gl_rmain.c @@ -658,7 +658,7 @@ static void R_CheckFog( void ) int i, cnt, count; // quake global fog - if( clgame.movevars.fog_settings != 0 && FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE )) + if( clgame.movevars.fog_settings != 0 && CL_IsQuakeCompatible( )) { // quake-style global fog RI.fogColor[0] = ((clgame.movevars.fog_settings & 0xFF000000) >> 24) / 255.0f; diff --git a/engine/client/gl_rmisc.c b/engine/client/gl_rmisc.c index 0fd75266..99388948 100644 --- a/engine/client/gl_rmisc.c +++ b/engine/client/gl_rmisc.c @@ -465,9 +465,12 @@ void R_NewMap( void ) if( v_dark->value ) { screenfade_t *sf = &clgame.fade; + float fadetime = 5.0f; client_textmessage_t *title; title = CL_TextMessageGet( "GAMETITLE" ); + if( CL_IsQuakeCompatible( )) + fadetime = 1.0f; if( title ) { @@ -475,7 +478,7 @@ void R_NewMap( void ) sf->fadeEnd = title->holdtime + title->fadeout; sf->fadeReset = title->fadeout; } - else sf->fadeEnd = sf->fadeReset = 5.0f; + else sf->fadeEnd = sf->fadeReset = fadetime; sf->fadeFlags = FFADE_IN; sf->fader = sf->fadeg = sf->fadeb = 0; diff --git a/engine/client/gl_rsurf.c b/engine/client/gl_rsurf.c index 90c92eae..c3e30257 100644 --- a/engine/client/gl_rsurf.c +++ b/engine/client/gl_rsurf.c @@ -1232,7 +1232,7 @@ void R_DrawTextureChains( void ) if(( s->flags & SURF_DRAWTURB ) && clgame.movevars.wateralpha < 1.0f ) continue; // draw translucent water later - if( FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE ) && FBitSet( s->flags, SURF_TRANSPARENT )) + if( CL_IsQuakeCompatible() && FBitSet( s->flags, SURF_TRANSPARENT )) { draw_alpha_surfaces = true; continue; // draw transparent surfaces later @@ -1412,7 +1412,7 @@ void R_SetRenderMode( cl_entity_t *e ) case kRenderTransAlpha: pglEnable( GL_ALPHA_TEST ); pglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); - if( FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE )) + if( CL_IsQuakeCompatible( )) { pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); pglColor4f( 1.0f, 1.0f, 1.0f, tr.blend ); @@ -1482,7 +1482,7 @@ void R_DrawBrushModel( cl_entity_t *e ) if( rotated ) R_RotateForEntity( e ); else R_TranslateForEntity( e ); - if( FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE ) && FBitSet( clmodel->flags, MODEL_TRANSPARENT )) + if( CL_IsQuakeCompatible() && FBitSet( clmodel->flags, MODEL_TRANSPARENT )) e->curstate.rendermode = kRenderTransAlpha; e->visframe = tr.realframecount; // visible @@ -1515,7 +1515,7 @@ void R_DrawBrushModel( cl_entity_t *e ) for( i = 0; i < clmodel->nummodelsurfaces; i++, psurf++ ) { - if( FBitSet( psurf->flags, SURF_DRAWTURB ) && !FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE )) + if( FBitSet( psurf->flags, SURF_DRAWTURB ) && !CL_IsQuakeCompatible( )) { if( psurf->plane->type != PLANE_Z && !FBitSet( e->curstate.effects, EF_WATERSIDES )) continue; diff --git a/engine/client/s_main.c b/engine/client/s_main.c index bf4491bb..08e9b766 100644 --- a/engine/client/s_main.c +++ b/engine/client/s_main.c @@ -1298,7 +1298,7 @@ int S_GetCurrentDynamicSounds( soundlist_t *pout, int size ) looped = ( channels[i].use_loop && channels[i].sfx->cache->loopStart != -1 ); - if( channels[i].entchannel == CHAN_STATIC && looped && !FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE )) + if( channels[i].entchannel == CHAN_STATIC && looped && !CL_IsQuakeCompatible()) continue; // never serialize static looped sounds. It will be restoring in game code if( channels[i].isSentence && channels[i].name[0] ) @@ -2014,6 +2014,23 @@ void S_Play_f( void ) S_StartLocalSound( Cmd_Argv( 1 ), VOL_NORM, false ); } +void S_Play2_f( void ) +{ + int i = 1; + + if( Cmd_Argc() == 1 ) + { + Con_Printf( S_USAGE "play \n" ); + return; + } + + while( i < Cmd_Argc( )) + { + S_StartLocalSound( Cmd_Argv( i ), VOL_NORM, true ); + i++; + } +} + void S_PlayVol_f( void ) { if( Cmd_Argc() == 1 ) @@ -2180,6 +2197,7 @@ qboolean S_Init( void ) s_phs = Cvar_Get( "s_phs", "0", FCVAR_ARCHIVE, "cull sounds by PHS" ); Cmd_AddCommand( "play", S_Play_f, "playing a specified sound file" ); + Cmd_AddCommand( "play2", S_Play2_f, "playing a group of specified sound files" ); // nehahra stuff Cmd_AddCommand( "playvol", S_PlayVol_f, "playing a specified sound file with specified volume" ); Cmd_AddCommand( "stopsound", S_StopSound_f, "stop all sounds" ); Cmd_AddCommand( "music", S_Music_f, "starting a background track" ); diff --git a/engine/common/common.h b/engine/common/common.h index 94657e30..1eb5cd20 100644 --- a/engine/common/common.h +++ b/engine/common/common.h @@ -935,6 +935,7 @@ qboolean CL_IsTimeDemo( void ); qboolean CL_IsPlaybackDemo( void ); qboolean CL_IsBackgroundDemo( void ); qboolean CL_IsBackgroundMap( void ); +qboolean CL_IsQuakeCompatible( void ); qboolean SV_Initialized( void ); qboolean CL_LoadProgs( const char *name ); qboolean SV_GetSaveComment( const char *savename, char *comment ); diff --git a/engine/common/filesystem.c b/engine/common/filesystem.c index 949145b5..70afe13b 100644 --- a/engine/common/filesystem.c +++ b/engine/common/filesystem.c @@ -2333,18 +2333,18 @@ dll_user_t *FS_FindLibrary( const char *dllname, qboolean directpath ) COM_DefaultExtension( dllpath, ".dll" ); // apply ext if forget search = FS_FindFile( dllpath, &index, false ); - if( !search ) + if( !search && !directpath ) { fs_ext_path = false; - if( directpath ) return NULL; // direct paths fails here // trying check also 'bin' folder for indirect paths Q_strncpy( dllpath, dllname, sizeof( dllpath )); search = FS_FindFile( dllpath, &index, false ); - if( !search ) return NULL; // unable to find + if( !search ) return NULL; // unable to find } - // all done, create dll_user_t struct + // NOTE: for libraries we not fail even if search is NULL + // let the OS find library himself hInst = Mem_Calloc( host.mempool, sizeof( dll_user_t )); // save dllname for debug purposes @@ -2355,15 +2355,16 @@ dll_user_t *FS_FindLibrary( const char *dllname, qboolean directpath ) hInst->encrypted = FS_CheckForCrypt( dllpath ); - if( index < 0 && !hInst->encrypted ) + if( index < 0 && !hInst->encrypted && search ) { Q_snprintf( hInst->fullPath, sizeof( hInst->fullPath ), "%s%s", search->filename, dllpath ); hInst->custom_loader = false; // we can loading from disk and use normal debugging } else { + // NOTE: if search is NULL let the OS found library himself Q_strncpy( hInst->fullPath, dllpath, sizeof( hInst->fullPath )); - hInst->custom_loader = true; // loading from pack or wad - for release, debug don't working + hInst->custom_loader = (search) ? true : false; } fs_ext_path = false; // always reset direct paths diff --git a/engine/common/input.c b/engine/common/input.c index dac03cbb..c3ee5954 100644 --- a/engine/common/input.c +++ b/engine/common/input.c @@ -226,6 +226,29 @@ void IN_ToggleClientMouse( int newstate, int oldstate ) } } +/* +=========== +IN_RecalcCenter + +Recalc the center of screen +=========== +*/ +void IN_RecalcCenter( void ) +{ + int width = GetSystemMetrics( SM_CXSCREEN ); + int height = GetSystemMetrics( SM_CYSCREEN ); + + GetWindowRect( host.hWnd, &window_rect ); + if( window_rect.left < 0 ) window_rect.left = 0; + if( window_rect.top < 0 ) window_rect.top = 0; + if( window_rect.right >= width ) window_rect.right = width - 1; + if( window_rect.bottom >= height - 1 ) window_rect.bottom = height - 1; + + host.window_center_x = (window_rect.right + window_rect.left) / 2; + host.window_center_y = (window_rect.top + window_rect.bottom) / 2; + SetCursorPos( host.window_center_x, host.window_center_y ); +} + /* =========== IN_ActivateMouse @@ -235,7 +258,6 @@ Called when the window gains focus or changes in some way */ void IN_ActivateMouse( qboolean force ) { - int width, height; static int oldstate; if( !in_mouseinitialized ) @@ -281,18 +303,7 @@ void IN_ActivateMouse( qboolean force ) clgame.dllFuncs.IN_ActivateMouse(); } - width = GetSystemMetrics( SM_CXSCREEN ); - height = GetSystemMetrics( SM_CYSCREEN ); - - GetWindowRect( host.hWnd, &window_rect ); - if( window_rect.left < 0 ) window_rect.left = 0; - if( window_rect.top < 0 ) window_rect.top = 0; - if( window_rect.right >= width ) window_rect.right = width - 1; - if( window_rect.bottom >= height - 1 ) window_rect.bottom = height - 1; - - host.window_center_x = (window_rect.right + window_rect.left) / 2; - host.window_center_y = (window_rect.top + window_rect.bottom) / 2; - SetCursorPos( host.window_center_x, host.window_center_y ); + IN_RecalcCenter(); SetCapture( host.hWnd ); ClipCursor( &window_rect ); @@ -498,6 +509,7 @@ LONG IN_WndProc( HWND hWnd, UINT uMsg, UINT wParam, LONG lParam ) S_Activate( fActivate, host.hWnd ); IN_ActivateMouse( fActivate ); Key_ClearStates(); + IN_RecalcCenter(); if( host.status == HOST_FRAME ) { diff --git a/engine/common/mod_bmodel.c b/engine/common/mod_bmodel.c index d37fc217..c7c957b5 100644 --- a/engine/common/mod_bmodel.c +++ b/engine/common/mod_bmodel.c @@ -2188,7 +2188,7 @@ static void Mod_LoadSurfaces( dbspmodel_t *bmod ) if(( tex->name[0] == '*' && Q_stricmp( tex->name, "*default" )) || tex->name[0] == '!' ) SetBits( out->flags, SURF_DRAWTURB|SURF_DRAWTILED ); - if( !FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE )) + if( !CL_IsQuakeCompatible( )) { if( !Q_strncmp( tex->name, "water", 5 ) || !Q_strnicmp( tex->name, "laser", 5 )) SetBits( out->flags, SURF_DRAWTURB|SURF_DRAWTILED ); diff --git a/engine/common/protocol.h b/engine/common/protocol.h index 67deaea9..5e8adc99 100644 --- a/engine/common/protocol.h +++ b/engine/common/protocol.h @@ -179,6 +179,67 @@ GNU General Public License for more details. #define FRAGMENT_MAX_SIZE 64000 // maximal fragment size #define FRAGMENT_LOCAL_SIZE FRAGMENT_MAX_SIZE // local connection +// Quake1 Protocol +#define PROTOCOL_VERSION_QUAKE 15 + +// listed only unmatched ops +#define svc_updatestat 3 // [byte] [long] (svc_event) +#define svc_version 4 // [long] server version (svc_changing) +#define svc_updatename 13 // [byte] [string] (svc_updateuserinfo) +#define svc_updatefrags 14 // [byte] [short] (svc_deltatable) +#define svc_stopsound 16 // (svc_resource) +#define svc_updatecolors 17 // [byte] [byte] (svc_pings) +#define svc_damage 19 // (svc_restoresound) +#define svc_spawnbinary 21 // (svc_event_reliable) +#define svc_killedmonster 27 +#define svc_foundsecret 28 +#define svc_spawnstaticsound 29 // [coord3] [byte] samp [byte] vol [byte] aten +#define svc_sellscreen 33 // (svc_restore) +// Nehahra added +#define svc_showlmp 35 // [string] slotname [string] lmpfilename [coord] x [coord] y +#define svc_hidelmp 36 // [string] slotname +#define svc_skybox 37 // [string] skyname +#define svc_skyboxsize 50 // [coord] size (default is 4096) +#define svc_fog 51 // [byte] enable + // [float] density [byte] red [byte] green [byte] blue + +// if the high bit of the servercmd is set, the low bits are fast update flags: +#define U_MOREBITS (1<<0) +#define U_ORIGIN1 (1<<1) +#define U_ORIGIN2 (1<<2) +#define U_ORIGIN3 (1<<3) +#define U_ANGLE2 (1<<4) +#define U_NOLERP (1<<5) // don't interpolate movement +#define U_FRAME (1<<6) +#define U_SIGNAL (1<<7) // just differentiates from other updates + +// svc_update can pass all of the fast update bits, plus more +#define U_ANGLE1 (1<<8) +#define U_ANGLE3 (1<<9) +#define U_MODEL (1<<10) +#define U_COLORMAP (1<<11) +#define U_SKIN (1<<12) +#define U_EFFECTS (1<<13) +#define U_LONGENTITY (1<<14) +#define U_TRANS (1<<15) // nehahra + +// clientdata flags +#define SU_VIEWHEIGHT (1<<0) +#define SU_IDEALPITCH (1<<1) +#define SU_PUNCH1 (1<<2) +#define SU_PUNCH2 (1<<3) +#define SU_PUNCH3 (1<<4) +#define SU_VELOCITY1 (1<<5) +#define SU_VELOCITY2 (1<<6) +#define SU_VELOCITY3 (1<<7) +//define SU_AIMENT (1<<8) AVAILABLE BIT +#define SU_ITEMS (1<<9) +#define SU_ONGROUND (1<<10) // no data follows, the bit is it +#define SU_INWATER (1<<11) // no data follows, the bit is it +#define SU_WEAPONFRAME (1<<12) +#define SU_ARMOR (1<<13) +#define SU_WEAPON (1<<14) + extern const char *svc_strings[svc_lastmsg+1]; extern const char *clc_strings[clc_lastmsg+1]; diff --git a/engine/engine.dsp b/engine/engine.dsp index 579edb9e..ba2bd192 100644 --- a/engine/engine.dsp +++ b/engine/engine.dsp @@ -164,6 +164,10 @@ SOURCE=.\client\cl_custom.c # End Source File # Begin Source File +SOURCE=.\client\cl_debug.c +# End Source File +# Begin Source File + SOURCE=.\client\cl_demo.c # End Source File # Begin Source File @@ -200,6 +204,10 @@ SOURCE=.\client\cl_pmove.c # End Source File # Begin Source File +SOURCE=.\client\cl_qparse.c +# End Source File +# Begin Source File + SOURCE=.\client\cl_remap.c # End Source File # Begin Source File