From 1e7f9d00c3bc87b0c60d6d42a294efac811a82ea Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Tue, 19 Jun 2018 16:22:30 +0300 Subject: [PATCH] Apply 4150 update --- common/render_api.h | 2 +- engine/client/cl_debug.c | 242 +++++++ engine/client/cl_demo.c | 263 ++++++-- engine/client/cl_frame.c | 13 +- engine/client/cl_game.c | 2 +- engine/client/cl_main.c | 49 +- engine/client/cl_parse.c | 272 +------- 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_backend.c | 31 +- engine/client/gl_image.c | 2 +- engine/client/gl_local.h | 2 + engine/client/gl_refrag.c | 6 +- engine/client/gl_rmain.c | 4 +- engine/client/gl_rmisc.c | 5 +- engine/client/gl_rsurf.c | 25 +- engine/client/gl_studio.c | 4 +- engine/client/gl_vidnt.c | 36 +- engine/client/s_main.c | 20 +- engine/common/build.c | 2 +- engine/common/common.h | 1 + engine/common/filesystem.c | 13 +- engine/common/input.c | 42 +- engine/common/mod_bmodel.c | 8 +- engine/common/net_buffer.c | 4 +- engine/common/net_encode.c | 30 +- engine/common/net_encode.h | 16 +- engine/common/protocol.h | 61 ++ engine/common/soundlib/snd_mp3.c | 8 +- engine/server/server.h | 25 +- engine/server/sv_cmds.c | 3 +- engine/server/sv_frame.c | 44 ++ engine/server/sv_game.c | 112 ++-- engine/server/sv_init.c | 17 +- engine/server/sv_main.c | 4 +- engine/server/sv_save.c | 67 +- 40 files changed, 1976 insertions(+), 511 deletions(-) create mode 100644 engine/client/cl_debug.c create mode 100644 engine/client/cl_qparse.c diff --git a/common/render_api.h b/common/render_api.h index 1deb14a7..246d7512 100644 --- a/common/render_api.h +++ b/common/render_api.h @@ -40,7 +40,7 @@ GNU General Public License for more details. #define PARM_TEX_MIPCOUNT 15 // count of mipmaps (0 - autogenerated, 1 - disabled of mipmapping) #define PARM_BSP2_SUPPORTED 16 // tell custom renderer what engine is support BSP2 in this build #define PARM_SKY_SPHERE 17 // sky is quake sphere ? -//reserved +#define PARAM_GAMEPAUSED 18 // game is paused #define PARM_MAP_HAS_DELUXE 19 // map has deluxedata #define PARM_MAX_ENTITIES 20 #define PARM_WIDESCREEN 21 diff --git a/engine/client/cl_debug.c b/engine/client/cl_debug.c new file mode 100644 index 00000000..4f740a3c --- /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 47cf1b4b..505db0c8 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 )) + { + CL_PlayDemoQuake( filename1 ); + return; + } + else if( !FS_FileExists( filename2, true )) { - MsgDev( D_ERROR, "couldn't open %s\n", filename ); + 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 576405d4..216b70c5 100644 --- a/engine/client/cl_frame.c +++ b/engine/client/cl_frame.c @@ -603,13 +603,13 @@ void CL_FlushEntityPacket( sizebuf_t *msg ) // read it all, but ignore it while( 1 ) { - newnum = MSG_ReadUBitLong( msg, MAX_VISIBLE_PACKET_BITS ); + newnum = MSG_ReadUBitLong( msg, MAX_ENTITY_BITS ); if( newnum == LAST_EDICT ) break; // done if( MSG_CheckOverflow( msg )) Host_Error( "CL_FlushEntityPacket: overflow\n" ); - MSG_ReadDeltaEntity( msg, &from, &to, newnum, CL_IsPlayerIndex( newnum ), cl.mtime[0] ); + MSG_ReadDeltaEntity( msg, &from, &to, newnum, CL_IsPlayerIndex( newnum ) ? DELTA_PLAYER : DELTA_ENTITY, cl.mtime[0] ); } } @@ -626,17 +626,18 @@ void CL_DeltaEntity( sizebuf_t *msg, frame_t *frame, int newnum, entity_state_t entity_state_t *state; qboolean newent = (old) ? false : true; int pack = frame->num_entities; - qboolean player = CL_IsPlayerIndex( newnum ); + int delta_type = DELTA_ENTITY; qboolean alive = true; // alloc next slot to store update state = &cls.packet_entities[cls.next_client_entities % cls.num_client_entities]; + if( CL_IsPlayerIndex( newnum )) delta_type = DELTA_PLAYER; if(( newnum < 0 ) || ( newnum >= clgame.maxEntities )) { MsgDev( D_ERROR, "CL_DeltaEntity: invalid newnum: %d\n", newnum ); if( has_update ) - MSG_ReadDeltaEntity( msg, old, state, newnum, player, cl.mtime[0] ); + MSG_ReadDeltaEntity( msg, old, state, newnum, delta_type, cl.mtime[0] ); return; } @@ -645,7 +646,7 @@ void CL_DeltaEntity( sizebuf_t *msg, frame_t *frame, int newnum, entity_state_t if( newent ) old = &ent->baseline; if( has_update ) - alive = MSG_ReadDeltaEntity( msg, old, state, newnum, player, cl.mtime[0] ); + alive = MSG_ReadDeltaEntity( msg, old, state, newnum, delta_type, cl.mtime[0] ); else memcpy( state, old, sizeof( entity_state_t )); if( !alive ) @@ -1074,7 +1075,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_game.c b/engine/client/cl_game.c index 1f701166..d9bbab68 100644 --- a/engine/client/cl_game.c +++ b/engine/client/cl_game.c @@ -1867,7 +1867,7 @@ int pfnDrawConsoleString( int x, int y, char *string ) int drawLen; if( !COM_CheckString( string )) - return 0; // silent ignore + return 0; // silent ignore Con_SetFont( con_fontsize->value ); clgame.ds.adjust_size = true; diff --git a/engine/client/cl_main.c b/engine/client/cl_main.c index 21901591..307eff59 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 b43a0183..9e54a943 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]; @@ -502,46 +313,27 @@ static client entity */ void CL_ParseStaticEntity( sizebuf_t *msg ) { - entity_state_t state; + int i, newnum; + entity_state_t from, to; cl_entity_t *ent; - int i; - - memset( &state, 0, sizeof( state )); - - state.modelindex = MSG_ReadShort( msg ); - state.sequence = MSG_ReadWord( msg ); - state.frame = MSG_ReadWord( msg ) * (1.0f / 128.0f); - state.colormap = MSG_ReadWord( msg ); - state.skin = MSG_ReadByte( msg ); - state.body = MSG_ReadByte( msg ); - state.scale = MSG_ReadCoord( msg ); - MSG_ReadVec3Coord( msg, state.origin ); - MSG_ReadVec3Angles( msg, state.angles ); - state.rendermode = MSG_ReadByte( msg ); - if( state.rendermode != kRenderNormal ) - { - state.renderamt = MSG_ReadByte( msg ); - state.rendercolor.r = MSG_ReadByte( msg ); - state.rendercolor.g = MSG_ReadByte( msg ); - state.rendercolor.b = MSG_ReadByte( msg ); - state.renderfx = MSG_ReadByte( msg ); - } + memset( &from, 0, sizeof( from )); + newnum = MSG_ReadUBitLong( msg, MAX_ENTITY_BITS ); + MSG_ReadDeltaEntity( msg, &from, &to, 0, DELTA_STATIC, cl.mtime[0] ); i = clgame.numStatics; if( i >= MAX_STATIC_ENTITIES ) { - MsgDev( D_ERROR, "CL_ParseStaticEntity: static entities limit exceeded!\n" ); + Con_Printf( S_ERROR, "MAX_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; + // all states are same + ent->baseline = ent->curstate = ent->prevstate = to; + ent->index = 0; // static entities doesn't has the numbers // statics may be respawned in game e.g. for demo recording if( cls.state == ca_connected || cls.state == ca_validate ) @@ -550,14 +342,14 @@ void CL_ParseStaticEntity( sizebuf_t *msg ) // setup the new static entity VectorCopy( ent->curstate.origin, ent->origin ); VectorCopy( ent->curstate.angles, ent->angles ); - ent->model = CL_ModelHandle( state.modelindex ); + ent->model = CL_ModelHandle( to.modelindex ); ent->curstate.framerate = 1.0f; CL_ResetLatchedVars( ent, true ); 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 +1973,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 +1993,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 ) { @@ -2251,6 +2028,8 @@ void CL_ParseServerMessage( sizebuf_t *msg, qboolean normal_message ) cmd = MSG_ReadServerCmd( msg ); +// Msg( "%s\n", CL_MsgInfo( cmd )); + // record command for debugging spew on parse problem CL_Parse_RecordCommand( cmd, bufStart ); @@ -2278,7 +2057,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 +2068,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 +2078,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 +2257,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 +2266,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 8afa0df2..5d706939 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..45c9959f --- /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 4f067930..d3a2dac5 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 1398660c..d2ad1459 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 55ef8975..d2861158 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 446401da..df9f30a4 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_backend.c b/engine/client/gl_backend.c index 25c8b241..71a94888 100644 --- a/engine/client/gl_backend.c +++ b/engine/client/gl_backend.c @@ -43,6 +43,25 @@ qboolean R_SpeedsMessage( char *out, size_t size ) return true; } +/* +============== +R_Speeds_Printf + +helper to print into r_speeds message +============== +*/ +void R_Speeds_Printf( const char *msg, ... ) +{ + va_list argptr; + char text[2048]; + + va_start( argptr, msg ); + Q_vsprintf( text, msg, argptr ); + va_end( argptr ); + + Q_strncat( r_speeds_msg, text, sizeof( r_speeds_msg )); +} + /* ============== GL_BackendStartFrame @@ -60,9 +79,17 @@ GL_BackendEndFrame */ void GL_BackendEndFrame( void ) { + mleaf_t *curleaf; + if( r_speeds->value <= 0 || !RI.drawWorld ) return; + if( !RI.viewleaf ) + curleaf = cl.worldmodel->leafs; + else curleaf = RI.viewleaf; + + R_Speeds_Printf( "Renderer: ^1Engine^7\n\n" ); + switch( (int)r_speeds->value ) { case 1: @@ -70,8 +97,8 @@ void GL_BackendEndFrame( void ) r_stats.c_world_polys, r_stats.c_alias_polys, r_stats.c_studio_polys, r_stats.c_sprite_polys ); break; case 2: - Q_snprintf( r_speeds_msg, sizeof( r_speeds_msg ), "visible leafs:\n%3i leafs\ncurrent leaf %3i", - r_stats.c_world_leafs, Mod_PointInLeaf( RI.pvsorigin, cl.worldmodel->nodes ) - cl.worldmodel->leafs ); + R_Speeds_Printf( "visible leafs:\n%3i leafs\ncurrent leaf %3i\n", r_stats.c_world_leafs, curleaf - cl.worldmodel->leafs ); + R_Speeds_Printf( "ReciusiveWorldNode: %3lf secs\nDrawTextureChains %lf\n", r_stats.t_world_node, r_stats.t_world_draw ); break; case 3: Q_snprintf( r_speeds_msg, sizeof( r_speeds_msg ), "%3i alias models drawn\n%3i studio models drawn\n%3i sprites drawn", diff --git a/engine/client/gl_image.c b/engine/client/gl_image.c index 33858d3a..699cca8c 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_local.h b/engine/client/gl_local.h index 75e69504..a92b740a 100644 --- a/engine/client/gl_local.h +++ b/engine/client/gl_local.h @@ -229,6 +229,8 @@ typedef struct uint c_particle_count; uint c_client_ents; // entities that moved to client + double t_world_node; + double t_world_draw; } ref_speeds_t; extern ref_speeds_t r_stats; diff --git a/engine/client/gl_refrag.c b/engine/client/gl_refrag.c index 66071ed1..86e4d835 100644 --- a/engine/client/gl_refrag.c +++ b/engine/client/gl_refrag.c @@ -81,7 +81,6 @@ R_SplitEntityOnNode static void R_SplitEntityOnNode( mnode_t *node ) { efrag_t *ef; - mplane_t *splitplane; mleaf_t *leaf; int sides; @@ -100,7 +99,7 @@ static void R_SplitEntityOnNode( mnode_t *node ) ef = clgame.free_efrags; if( !ef ) { - MsgDev( D_ERROR, "too many efrags!\n" ); + Con_Printf( S_ERROR "too many efrags!\n" ); return; // no free fragments... } @@ -120,8 +119,7 @@ static void R_SplitEntityOnNode( mnode_t *node ) } // NODE_MIXED - splitplane = node->plane; - sides = BOX_ON_PLANE_SIDE( r_emins, r_emaxs, splitplane ); + sides = BOX_ON_PLANE_SIDE( r_emins, r_emaxs, node->plane ); if( sides == 3 ) { diff --git a/engine/client/gl_rmain.c b/engine/client/gl_rmain.c index eb3d0dd6..fa9b4380 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; @@ -1207,6 +1207,8 @@ static int GL_RenderGetParm( int parm, int arg ) return tr.lightmapTextures[arg]; case PARM_SKY_SPHERE: return FBitSet( world.flags, FWORLD_SKYSPHERE ) && !FBitSet( world.flags, FWORLD_CUSTOM_SKYBOX ); + case PARAM_GAMEPAUSED: + return cl.paused; case PARM_WIDESCREEN: return glState.wideScreen; case PARM_FULLSCREEN: diff --git a/engine/client/gl_rmisc.c b/engine/client/gl_rmisc.c index 9868867f..96ef53c6 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 5437f64c..9feef4cc 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; @@ -1591,14 +1591,14 @@ void R_RecursiveWorldNode( mnode_t *node, uint clipflags ) mleaf_t *pleaf; int c, side; float dot; - +loc0: if( node->contents == CONTENTS_SOLID ) return; // hit a solid leaf if( node->visframe != tr.visframecount ) return; - if( clipflags && !r_nocull->value ) + if( clipflags && !CVAR_TO_BOOL( r_nocull )) { for( i = 0; i < 6; i++ ) { @@ -1667,7 +1667,8 @@ void R_RecursiveWorldNode( mnode_t *node, uint clipflags ) } // recurse down the back side - R_RecursiveWorldNode( node->children[!side], clipflags ); + node = node->children[!side]; + goto loc0; } /* @@ -1864,6 +1865,8 @@ R_DrawWorld */ void R_DrawWorld( void ) { + double start, end; + // paranoia issues: when gl_renderer is "0" we need have something valid for currententity // to prevent crashing until HeadShield drawing. RI.currententity = clgame.entities; @@ -1884,10 +1887,15 @@ void R_DrawWorld( void ) R_ClearSkyBox (); + start = Sys_DoubleTime(); if( RI.drawOrtho ) R_DrawWorldTopView( cl.worldmodel->nodes, RI.frustum.clipFlags ); else R_RecursiveWorldNode( cl.worldmodel->nodes, RI.frustum.clipFlags ); + end = Sys_DoubleTime(); + r_stats.t_world_node = end - start; + + start = Sys_DoubleTime(); R_DrawTextureChains(); if( !CL_IsDevOverviewMode( )) @@ -1902,6 +1910,9 @@ void R_DrawWorld( void ) R_DrawSkyBox(); } + end = Sys_DoubleTime(); + + r_stats.t_world_draw = end - start; tr.num_draw_decals = 0; skychain = NULL; diff --git a/engine/client/gl_studio.c b/engine/client/gl_studio.c index 4a5862d1..58216213 100644 --- a/engine/client/gl_studio.c +++ b/engine/client/gl_studio.c @@ -844,8 +844,8 @@ void R_StudioCalcBoneAdj( float dadt, float *adj, const byte *pcontroller1, cons { if( abs( pcontroller1[i] - pcontroller2[i] ) > 128 ) { - int a = (pcontroller1[j] + 128) % 256; - int b = (pcontroller2[j] + 128) % 256; + int a = (pcontroller1[i] + 128) % 256; + int b = (pcontroller2[i] + 128) % 256; value = (( a * dadt ) + ( b * ( 1.0f - dadt )) - 128) * (360.0f / 256.0f) + pbonecontroller[j].start; } else diff --git a/engine/client/gl_vidnt.c b/engine/client/gl_vidnt.c index 41c3a9e3..55e7693c 100644 --- a/engine/client/gl_vidnt.c +++ b/engine/client/gl_vidnt.c @@ -26,6 +26,8 @@ GNU General Public License for more details. #define WINDOW_STYLE (WS_OVERLAPPED|WS_BORDER|WS_SYSMENU|WS_CAPTION|WS_VISIBLE) #define WINDOW_EX_STYLE (0) #define WINDOW_NAME "Xash3D Window" // Half-Life +#define FCONTEXT_CORE_PROFILE BIT( 0 ) +#define FCONTEXT_DEBUG_ARB BIT( 1 ) convar_t *gl_extensions; convar_t *gl_texture_anisotropy; @@ -82,7 +84,7 @@ glwstate_t glw_state; static HWND hWndFake; static HDC hDCFake; static HGLRC hGLRCFake; -static qboolean debug_context; +static int context_flags; typedef enum { @@ -531,8 +533,10 @@ static void GL_SetDefaultState( void ) GL_SetDefaultTexState (); if( Sys_CheckParm( "-gldebug" )) - debug_context = true; - else debug_context = false; + SetBits( context_flags, FCONTEXT_DEBUG_ARB ); + + if( Sys_CheckParm( "-glcore" )) + SetBits( context_flags, FCONTEXT_CORE_PROFILE ); // init draw stack tr.draw_list = &tr.draw_stack[0]; @@ -572,7 +576,9 @@ GL_CreateContext */ qboolean GL_CreateContext( void ) { - HGLRC hBaseRC; + HGLRC hBaseRC; + int profile_mask; + int arb_flags; glw_state.extended = false; @@ -582,19 +588,27 @@ qboolean GL_CreateContext( void ) if(!( pwglMakeCurrent( glw_state.hDC, glw_state.hGLRC ))) return GL_DeleteContext(); - if( !debug_context ) // debug bit kill the perfomance + if( !context_flags ) // debug bit kill the perfomance return true; pwglCreateContextAttribsARB = GL_GetProcAddress( "wglCreateContextAttribsARB" ); - if( debug_context && pwglCreateContextAttribsARB != NULL ) + if( FBitSet( context_flags, FCONTEXT_CORE_PROFILE )) + profile_mask = WGL_CONTEXT_CORE_PROFILE_BIT_ARB; + else profile_mask = WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB; + + if( FBitSet( context_flags, FCONTEXT_DEBUG_ARB )) + arb_flags = WGL_CONTEXT_DEBUG_BIT_ARB; + else arb_flags = 0; + + if( pwglCreateContextAttribsARB != NULL ) { int attribs[] = { WGL_CONTEXT_MAJOR_VERSION_ARB, 2, WGL_CONTEXT_MINOR_VERSION_ARB, 0, - WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_DEBUG_BIT_ARB, -// WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB, + WGL_CONTEXT_FLAGS_ARB, arb_flags, + WGL_CONTEXT_PROFILE_MASK_ARB, profile_mask, 0 }; @@ -1441,7 +1455,7 @@ qboolean R_Init_OpenGL( void ) if( !opengl_dll.link ) return false; - if( debug_context || CVAR_TO_BOOL( gl_wgl_msaa_samples )) + if( context_flags || CVAR_TO_BOOL( gl_wgl_msaa_samples )) GL_CheckExtension( "OpenGL Internal ProcAddress", wglproc_funcs, NULL, GL_WGL_PROCADDRESS ); return VID_SetMode(); @@ -1583,7 +1597,7 @@ void GL_InitCommands( void ) r_dynamic = Cvar_Get( "r_dynamic", "1", FCVAR_ARCHIVE, "allow dynamic lighting (dlights, lightstyles)" ); r_traceglow = Cvar_Get( "r_traceglow", "1", FCVAR_ARCHIVE, "cull flares behind models" ); r_lightmap = Cvar_Get( "r_lightmap", "0", FCVAR_CHEAT, "lightmap debugging tool" ); - r_drawentities = Cvar_Get( "r_drawentities", "1", FCVAR_CHEAT, "render entities" ); + r_drawentities = Cvar_Get( "r_drawentities", "1", FCVAR_CHEAT|FCVAR_ARCHIVE, "render entities" ); r_decals = Cvar_Get( "r_decals", "4096", FCVAR_ARCHIVE, "sets the maximum number of decals" ); window_xpos = Cvar_Get( "_window_xpos", "130", FCVAR_RENDERINFO, "window position by horizontal" ); window_ypos = Cvar_Get( "_window_ypos", "48", FCVAR_RENDERINFO, "window position by vertical" ); @@ -1680,7 +1694,7 @@ void GL_InitExtensions( void ) else glConfig.hardware_type = GLHW_GENERIC; // initalize until base opengl functions loaded (old-context) - if( !debug_context && !CVAR_TO_BOOL( gl_wgl_msaa_samples )) + if( !context_flags && !CVAR_TO_BOOL( gl_wgl_msaa_samples )) GL_CheckExtension( "OpenGL Internal ProcAddress", wglproc_funcs, NULL, GL_WGL_PROCADDRESS ); // windows-specific extensions diff --git a/engine/client/s_main.c b/engine/client/s_main.c index 3e37cdfc..49dc6bfd 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/build.c b/engine/common/build.c index 04ffdb2e..a978fd07 100644 --- a/engine/common/build.c +++ b/engine/common/build.c @@ -48,6 +48,6 @@ int Q_buildnum( void ) return b; #else - return 4143; + return 4150; #endif } diff --git a/engine/common/common.h b/engine/common/common.h index ae187ff6..49082dc4 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 bc84b86a..2cc940f8 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 c4a246cd..7bddbf4c 100644 --- a/engine/common/input.c +++ b/engine/common/input.c @@ -226,6 +226,33 @@ void IN_ToggleClientMouse( int newstate, int oldstate ) } } +/* +=========== +IN_RecalcCenter + +Recalc the center of screen +=========== +*/ +void IN_RecalcCenter( qboolean setpos ) +{ + int width, height; + + if( host.status != HOST_FRAME ) + return; + + 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; + if( setpos ) SetCursorPos( host.window_center_x, host.window_center_y ); +} + /* =========== IN_ActivateMouse @@ -235,7 +262,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 +307,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( true ); SetCapture( host.hWnd ); ClipCursor( &window_rect ); @@ -498,6 +513,7 @@ LONG IN_WndProc( HWND hWnd, UINT uMsg, UINT wParam, LONG lParam ) S_Activate( fActivate, host.hWnd ); IN_ActivateMouse( fActivate ); Key_ClearStates(); + IN_RecalcCenter( false ); if( host.status == HOST_FRAME ) { diff --git a/engine/common/mod_bmodel.c b/engine/common/mod_bmodel.c index 3bf58bc7..016bfb1e 100644 --- a/engine/common/mod_bmodel.c +++ b/engine/common/mod_bmodel.c @@ -1472,6 +1472,12 @@ static void Mod_LoadSubmodels( dbspmodel_t *bmod ) { for( j = 0; j < 3; j++ ) { + // reset empty bounds to prevent error + if( in->mins[j] == 999999.0f ) + in->mins[j] = 0.0f; + if( in->maxs[j] == -999999.0f) + in->maxs[j] = 0.0f; + // spread the mins / maxs by a unit out->mins[j] = in->mins[j] - 1.0f; out->maxs[j] = in->maxs[j] + 1.0f; @@ -2188,7 +2194,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/net_buffer.c b/engine/common/net_buffer.c index e8cfbc48..9488a2c3 100644 --- a/engine/common/net_buffer.c +++ b/engine/common/net_buffer.c @@ -284,7 +284,7 @@ void MSG_WriteCoord( sizebuf_t *sb, float val ) { // g-cont. we loose precision here but keep old size of coord variable! if( FBitSet( host.features, ENGINE_WRITE_LARGE_COORD )) - MSG_WriteShort( sb, (int)( val * 2.0f )); + MSG_WriteShort( sb, Q_rint( val )); else MSG_WriteShort( sb, (int)( val * 8.0f )); } @@ -598,7 +598,7 @@ float MSG_ReadCoord( sizebuf_t *sb ) { // g-cont. we loose precision here but keep old size of coord variable! if( FBitSet( host.features, ENGINE_WRITE_LARGE_COORD )) - return (float)(MSG_ReadShort( sb ) * ( 1.0f / 2.0f )); + return (float)(MSG_ReadShort( sb )); return (float)(MSG_ReadShort( sb ) * ( 1.0f / 8.0f )); } diff --git a/engine/common/net_encode.c b/engine/common/net_encode.c index 73c8da19..d1d3550d 100644 --- a/engine/common/net_encode.c +++ b/engine/common/net_encode.c @@ -804,7 +804,7 @@ void Delta_Init( void ) Delta_AddField( "movevars_t", "stepsize", DT_FLOAT|DT_SIGNED, 16, 16.0f, 1.0f ); Delta_AddField( "movevars_t", "maxvelocity", DT_FLOAT|DT_SIGNED, 16, 8.0f, 1.0f ); - if( host.features & ENGINE_WRITE_LARGE_COORD ) + if( FBitSet( host.features, ENGINE_WRITE_LARGE_COORD )) Delta_AddField( "movevars_t", "zmax", DT_FLOAT|DT_SIGNED, 18, 1.0f, 1.0f ); else Delta_AddField( "movevars_t", "zmax", DT_FLOAT|DT_SIGNED, 16, 1.0f, 1.0f ); @@ -1703,7 +1703,7 @@ If force is not set, then nothing at all will be generated if the entity is identical, under the assumption that the in-order delta code will catch it. ================== */ -void MSG_WriteDeltaEntity( entity_state_t *from, entity_state_t *to, sizebuf_t *msg, qboolean force, qboolean player, float timebase, int baseline ) +void MSG_WriteDeltaEntity( entity_state_t *from, entity_state_t *to, sizebuf_t *msg, qboolean force, int delta_type, float timebase, int baseline ) { delta_info_t *dt = NULL; delta_t *pField; @@ -1757,7 +1757,7 @@ void MSG_WriteDeltaEntity( entity_state_t *from, entity_state_t *to, sizebuf_t * { dt = Delta_FindStruct( "custom_entity_state_t" ); } - else if( player ) + else if( delta_type == DELTA_PLAYER ) { dt = Delta_FindStruct( "entity_state_player_t" ); } @@ -1771,8 +1771,17 @@ void MSG_WriteDeltaEntity( entity_state_t *from, entity_state_t *to, sizebuf_t * pField = dt->pFields; Assert( pField != NULL ); - // activate fields and call custom encode func - Delta_CustomEncode( dt, from, to ); + if( delta_type == DELTA_STATIC ) + { + // static entities won't to be custom encoded + for( i = 0; i < dt->numFields; i++ ) + dt->pFields[i].bInactive = false; + } + else + { + // activate fields and call custom encode func + Delta_CustomEncode( dt, from, to ); + } // process fields for( i = 0; i < dt->numFields; i++, pField++ ) @@ -1796,7 +1805,7 @@ If the delta removes the entity, entity_state_t->number will be set to MAX_EDICT Can go from either a baseline or a previous packet_entity ================== */ -qboolean MSG_ReadDeltaEntity( sizebuf_t *msg, entity_state_t *from, entity_state_t *to, int number, qboolean player, float timebase ) +qboolean MSG_ReadDeltaEntity( sizebuf_t *msg, entity_state_t *from, entity_state_t *to, int number, int delta_type, float timebase ) { delta_info_t *dt = NULL; delta_t *pField; @@ -1834,7 +1843,12 @@ qboolean MSG_ReadDeltaEntity( sizebuf_t *msg, entity_state_t *from, entity_state if( baseline_offset != 0 ) { - if( baseline_offset > 0 ) + if( delta_type == DELTA_STATIC ) + { + int backup = Q_max( 0, clgame.numStatics - abs( baseline_offset )); + from = &clgame.static_entities[backup].baseline; + } + else if( baseline_offset > 0 ) { int backup = cls.next_client_entities - baseline_offset; from = &cls.packet_entities[backup % cls.num_client_entities]; @@ -1858,7 +1872,7 @@ qboolean MSG_ReadDeltaEntity( sizebuf_t *msg, entity_state_t *from, entity_state { dt = Delta_FindStruct( "custom_entity_state_t" ); } - else if( player ) + else if( delta_type == DELTA_PLAYER ) { dt = Delta_FindStruct( "entity_state_player_t" ); } diff --git a/engine/common/net_encode.h b/engine/common/net_encode.h index aa7bf02f..0a8bc599 100644 --- a/engine/common/net_encode.h +++ b/engine/common/net_encode.h @@ -40,8 +40,16 @@ GNU General Public License for more details. enum { CUSTOM_NONE = 0, - CUSTOM_SERVER_ENCODE, // keyword "gamedll" - CUSTOM_CLIENT_ENCODE, // keyword "client" + CUSTOM_SERVER_ENCODE, // known as "gamedll" + CUSTOM_CLIENT_ENCODE, // known as "client" +}; + +// don't change order! +enum +{ + DELTA_ENTITY = 0, + DELTA_PLAYER, + DELTA_STATIC, }; // struct info (filled by engine) @@ -114,8 +122,8 @@ void MSG_WriteClientData( sizebuf_t *msg, struct clientdata_s *from, struct clie void MSG_ReadClientData( sizebuf_t *msg, struct clientdata_s *from, struct clientdata_s *to, float timebase ); void MSG_WriteWeaponData( sizebuf_t *msg, struct weapon_data_s *from, struct weapon_data_s *to, float timebase, int index ); void MSG_ReadWeaponData( sizebuf_t *msg, struct weapon_data_s *from, struct weapon_data_s *to, float timebase ); -void MSG_WriteDeltaEntity( struct entity_state_s *from, struct entity_state_s *to, sizebuf_t *msg, qboolean force, qboolean pl, float tbase, int bl ); -qboolean MSG_ReadDeltaEntity( sizebuf_t *msg, struct entity_state_s *from, struct entity_state_s *to, int num, qboolean player, float timebase ); +void MSG_WriteDeltaEntity( struct entity_state_s *from, struct entity_state_s *to, sizebuf_t *msg, qboolean force, int type, float tbase, int ofs ); +qboolean MSG_ReadDeltaEntity( sizebuf_t *msg, struct entity_state_s *from, struct entity_state_s *to, int num, int type, float timebase ); int Delta_TestBaseline( struct entity_state_s *from, struct entity_state_s *to, qboolean player, float timebase ); #endif//NET_ENCODE_H \ No newline at end of file diff --git a/engine/common/protocol.h b/engine/common/protocol.h index 7c01e303..41fe15ef 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/common/soundlib/snd_mp3.c b/engine/common/soundlib/snd_mp3.c index b6c27a5b..66d2780f 100644 --- a/engine/common/soundlib/snd_mp3.c +++ b/engine/common/soundlib/snd_mp3.c @@ -58,7 +58,7 @@ qboolean Sound_LoadMPG( const char *name, const byte *buffer, size_t filesize ) size_t pos = 0; size_t bytesWrite = 0; char out[OUTBUF_SIZE]; - size_t outsize; + size_t outsize, padsize; int ret; wavinfo_t sc; @@ -91,7 +91,8 @@ qboolean Sound_LoadMPG( const char *name, const byte *buffer, size_t filesize ) sound.width = 2; // always 16-bit PCM sound.loopstart = -1; sound.size = ( sound.channels * sound.rate * sound.width ) * ( sc.playtime / 1000 ); // in bytes - pos += FRAME_SIZE; // evaluate pos + padsize = sound.size % FRAME_SIZE; + pos += FRAME_SIZE; // evaluate pos if( !sound.size ) { @@ -101,8 +102,9 @@ qboolean Sound_LoadMPG( const char *name, const byte *buffer, size_t filesize ) return false; } + // add sentinel make sure we not overrun + sound.wav = (byte *)Mem_Calloc( host.soundpool, sound.size + padsize ); sound.type = WF_PCMDATA; - sound.wav = (byte *)Mem_Malloc( host.soundpool, sound.size ); // decompress mpg into pcm wav format while( bytesWrite < sound.size ) diff --git a/engine/server/server.h b/engine/server/server.h index 61df4704..735021f1 100644 --- a/engine/server/server.h +++ b/engine/server/server.h @@ -113,24 +113,6 @@ typedef struct file_t *file; } server_log_t; -// like as entity_state_t in Quake -typedef struct -{ - char model[MAX_QPATH]; // name of static-entity model for right precache - vec3_t origin; - vec3_t angles; - short sequence; - short frame; - short colormap; - byte skin; // can't set contents! only real skin! - byte body; - float scale; - byte rendermode; - byte renderamt; - color24 rendercolor; - byte renderfx; -} sv_static_entity_t; - typedef struct server_s { sv_state_t state; // precache commands are only valid during load @@ -159,8 +141,6 @@ typedef struct server_s char event_precache[MAX_EVENTS][MAX_QPATH]; byte model_precache_flags[MAX_MODELS]; model_t *models[MAX_MODELS]; - - sv_static_entity_t static_entities[MAX_STATIC_ENTITIES]; int num_static_entities; // run local lightstyles to let SV_LightPoint grab the actual information @@ -386,6 +366,7 @@ typedef struct int next_client_entities; // next client_entity to use entity_state_t *packet_entities; // [num_client_entities] entity_state_t *baselines; // [GI->max_edicts] + entity_state_t *static_entities; // [MAX_STATIC_ENTITIES]; double last_heartbeat; challenge_t challenges[MAX_CHALLENGES]; // to prevent invalid IPs from connecting @@ -576,6 +557,7 @@ void SV_RequestMissingResources( void ); // sv_frame.c // void SV_InactivateClients( void ); +int SV_FindBestBaselineForStatic( int index, entity_state_t **baseline, entity_state_t *to ); void SV_WriteFrameToClient( sv_client_t *client, sizebuf_t *msg ); void SV_BuildClientFrame( sv_client_t *client ); void SV_SendMessagesToAll( void ); @@ -612,8 +594,8 @@ const char *SV_GetString( string_t iString ); sv_client_t *SV_ClientFromEdict( const edict_t *pEdict, qboolean spawned_only ); int SV_MapIsValid( const char *filename, const char *spawn_entity, const char *landmark_name ); void SV_StartSound( edict_t *ent, int chan, const char *sample, float vol, float attn, int flags, int pitch ); -void SV_CreateStaticEntity( struct sizebuf_s *msg, sv_static_entity_t *ent ); edict_t *SV_FindGlobalEntity( string_t classname, string_t globalname ); +qboolean SV_CreateStaticEntity( struct sizebuf_s *msg, int index ); void SV_SendUserReg( sizebuf_t *msg, sv_user_message_t *user ); edict_t* pfnPEntityOfEntIndex( int iEntIndex ); int pfnIndexOfEdict( const edict_t *pEdict ); @@ -622,6 +604,7 @@ void SV_UpdateBaseVelocity( edict_t *ent ); byte *pfnSetFatPVS( const float *org ); byte *pfnSetFatPAS( const float *org ); int pfnPrecacheModel( const char *s ); +int pfnModelIndex( const char *m ); void pfnRemoveEntity( edict_t* e ); void SV_RestartAmbientSounds( void ); void SV_RestartDecals( void ); diff --git a/engine/server/sv_cmds.c b/engine/server/sv_cmds.c index 5498c279..692fe5f1 100644 --- a/engine/server/sv_cmds.c +++ b/engine/server/sv_cmds.c @@ -800,6 +800,7 @@ void SV_InitHostCommands( void ) Cmd_AddCommand( "map_background", SV_MapBackground_f, "set background map" ); Cmd_AddCommand( "load", SV_Load_f, "load a saved game file" ); Cmd_AddCommand( "loadquick", SV_QuickLoad_f, "load a quick-saved game file" ); + Cmd_AddCommand( "reload", SV_Reload_f, "continue from latest save or restart level" ); } } @@ -818,7 +819,6 @@ void SV_InitOperatorCommands( void ) Cmd_AddCommand( "clientinfo", SV_ClientInfo_f, "print user infostring (player num required)" ); Cmd_AddCommand( "playersonly", SV_PlayersOnly_f, "freezes time, except for players" ); Cmd_AddCommand( "restart", SV_Restart_f, "restarting current level" ); - Cmd_AddCommand( "reload", SV_Reload_f, "continue from latest save or restart level" ); Cmd_AddCommand( "entpatch", SV_EntPatch_f, "write entity patch to allow external editing" ); Cmd_AddCommand( "edict_usage", SV_EdictUsage_f, "show info about edicts usage" ); Cmd_AddCommand( "entity_info", SV_EntityInfo_f, "show more info about edicts" ); @@ -852,7 +852,6 @@ void SV_KillOperatorCommands( void ) Cmd_RemoveCommand( "clientinfo" ); Cmd_RemoveCommand( "playersonly" ); Cmd_RemoveCommand( "restart" ); - Cmd_RemoveCommand( "reload" ); Cmd_RemoveCommand( "entpatch" ); Cmd_RemoveCommand( "edict_usage" ); Cmd_RemoveCommand( "entity_info" ); diff --git a/engine/server/sv_frame.c b/engine/server/sv_frame.c index 339b106b..e87e5ab0 100644 --- a/engine/server/sv_frame.c +++ b/engine/server/sv_frame.c @@ -172,6 +172,13 @@ Encode a client frame onto the network channel ============================================================================= */ +/* +============= +SV_FindBestBaseline + +trying to deltas with previous entities +============= +*/ int SV_FindBestBaseline( sv_client_t *cl, int index, entity_state_t **baseline, entity_state_t *to, client_frame_t *frame, qboolean player ) { int bestBitCount; @@ -205,6 +212,43 @@ int SV_FindBestBaseline( sv_client_t *cl, int index, entity_state_t **baseline, return index - bestfound; } +/* +============= +SV_FindBestBaselineForStatic + +trying to deltas with previous static entities +============= +*/ +int SV_FindBestBaselineForStatic( int index, entity_state_t **baseline, entity_state_t *to ) +{ + int bestBitCount; + int i, bitCount; + int bestfound, j; + + bestBitCount = j = Delta_TestBaseline( *baseline, to, false, sv.time ); + bestfound = index; + + // lookup backward for previous 64 states and try to interpret current delta as baseline + for( i = index - 1; bestBitCount > 0 && i >= 0 && ( index - i ) < ( MAX_CUSTOM_BASELINES - 1 ); i-- ) + { + // don't worry about underflow in circular buffer + entity_state_t *test = &svs.static_entities[i]; + + bitCount = Delta_TestBaseline( test, to, false, sv.time ); + + if( bitCount < bestBitCount ) + { + bestBitCount = bitCount; + bestfound = i; + } + } + + // using delta from previous entity as baseline for current + if( index != bestfound ) + *baseline = &svs.static_entities[bestfound]; + return index - bestfound; +} + /* ============= SV_EmitPacketEntities diff --git a/engine/server/sv_game.c b/engine/server/sv_game.c index a49a752b..c15374ad 100644 --- a/engine/server/sv_game.c +++ b/engine/server/sv_game.c @@ -497,39 +497,47 @@ SV_CreateStaticEntity NOTE: static entities only accepted when game is loading ======================= */ -void SV_CreateStaticEntity( sizebuf_t *msg, sv_static_entity_t *ent ) +qboolean SV_CreateStaticEntity( sizebuf_t *msg, int index ) { - int index; + entity_state_t nullstate, *baseline; + entity_state_t *state; + int offset; + + if( index >= ( MAX_STATIC_ENTITIES - 1 )) + { + if( !sv.static_ents_overflow ) + { + Con_Printf( S_WARN "MAX_STATIC_ENTITIES limit exceeded (%d)\n", MAX_STATIC_ENTITIES ); + sv.static_ents_overflow = true; + } + + sv.ignored_static_ents++; // continue overflowed entities + return false; + } // this can happens if serialized map contain too many static entities... - if( MSG_GetNumBytesLeft( msg ) < 35 ) + if( MSG_GetNumBytesLeft( msg ) < 50 ) { sv.ignored_static_ents++; - return; + return false; } - index = SV_ModelIndex( ent->model ); + state = &svs.static_entities[index]; // allocate a new one + memset( &nullstate, 0, sizeof( nullstate )); + baseline = &nullstate; + + // restore modelindex from modelname (already precached) + state->modelindex = pfnModelIndex( STRING( state->messagenum )); + state->entityType = ENTITY_NORMAL; // select delta-encode + state->number = 0; + + // trying to compress with previous delta's + offset = SV_FindBestBaselineForStatic( index, &baseline, state ); MSG_BeginServerCmd( msg, svc_spawnstatic ); - MSG_WriteShort( msg, index ); - MSG_WriteWord( msg, ent->sequence ); - MSG_WriteWord( msg, ent->frame ); - MSG_WriteWord( msg, ent->colormap ); - MSG_WriteByte( msg, ent->skin ); - MSG_WriteByte( msg, ent->body ); - MSG_WriteCoord( msg, ent->scale ); - MSG_WriteVec3Coord( msg, ent->origin ); - MSG_WriteVec3Angles( msg, ent->angles ); - MSG_WriteByte( msg, ent->rendermode ); + MSG_WriteDeltaEntity( baseline, state, msg, true, DELTA_STATIC, sv.time, offset ); - if( ent->rendermode != kRenderNormal ) - { - MSG_WriteByte( msg, ent->renderamt ); - MSG_WriteByte( msg, ent->rendercolor.r ); - MSG_WriteByte( msg, ent->rendercolor.g ); - MSG_WriteByte( msg, ent->rendercolor.b ); - MSG_WriteByte( msg, ent->renderfx ); - } + return true; } /* @@ -541,18 +549,14 @@ Write all the static ents into demo */ void SV_RestartStaticEnts( void ) { - sv_static_entity_t *clent; - int i; + int i; // remove all the static entities on the client R_ClearStaticEntities(); // resend them again for( i = 0; i < sv.num_static_entities; i++ ) - { - clent = &sv.static_entities[i]; - SV_CreateStaticEntity( &sv.reliable_datagram, clent ); - } + SV_CreateStaticEntity( &sv.reliable_datagram, i ); } /* @@ -1835,42 +1839,18 @@ move entity to client */ static void pfnMakeStatic( edict_t *ent ) { - sv_static_entity_t *clent; + entity_state_t *state; if( !SV_IsValidEdict( ent )) return; - if( sv.num_static_entities >= MAX_STATIC_ENTITIES ) - { - if( !sv.static_ents_overflow ) - { - Con_Printf( S_WARN "MAX_STATIC_ENTITIES limit exceeded (%d)\n", MAX_STATIC_ENTITIES ); - sv.static_ents_overflow = true; - } - sv.ignored_static_ents++; // continue overflowed entities - return; - } - - clent = &sv.static_entities[sv.num_static_entities++]; - - Q_strncpy( clent->model, STRING( ent->v.model ), sizeof( clent->model )); - VectorCopy( ent->v.origin, clent->origin ); - VectorCopy( ent->v.angles, clent->angles ); - - clent->sequence = ent->v.sequence; - clent->frame = ent->v.frame * 128; - clent->colormap = ent->v.colormap; - clent->skin = ent->v.skin; - clent->body = ent->v.body; - clent->scale = ent->v.scale; - clent->rendermode = ent->v.rendermode; - clent->renderamt = ent->v.renderamt; - clent->rendercolor.r = ent->v.rendercolor[0]; - clent->rendercolor.g = ent->v.rendercolor[1]; - clent->rendercolor.b = ent->v.rendercolor[2]; - clent->renderfx = ent->v.renderfx; + // fill the entity state + state = &svs.static_entities[sv.num_static_entities]; // allocate a new one + svgame.dllFuncs.pfnCreateBaseline( false, NUM_FOR_EDICT( ent ), state, ent, 0, vec3_origin, vec3_origin ); + state->messagenum = ent->v.model; // member modelname - SV_CreateStaticEntity( &sv.signon, clent ); + if( SV_CreateStaticEntity( &sv.signon, sv.num_static_entities )) + sv.num_static_entities++; // remove at end of the frame SetBits( ent->v.flags, FL_KILLME ); @@ -1986,31 +1966,31 @@ int SV_BuildSoundMsg( sizebuf_t *msg, edict_t *ent, int chan, const char *sample if( vol < 0 || vol > 255 ) { - Con_Printf( S_ERROR "SV_StartSound: volume = %i\n", vol ); + Con_Reportf( S_ERROR "SV_StartSound: volume = %i\n", vol ); vol = bound( 0, vol, 255 ); } if( attn < 0.0f || attn > 4.0f ) { - Con_Printf( S_ERROR "SV_StartSound: attenuation %g must be in range 0-4\n", attn ); + Con_Reportf( S_ERROR "SV_StartSound: attenuation %g must be in range 0-4\n", attn ); attn = bound( 0.0f, attn, 4.0f ); } if( chan < 0 || chan > 7 ) { - Con_Printf( S_ERROR "SV_StartSound: channel must be in range 0-7\n" ); + Con_Reportf( S_ERROR "SV_StartSound: channel must be in range 0-7\n" ); chan = bound( 0, chan, 7 ); } if( pitch < 0 || pitch > 255 ) { - Con_Printf( S_ERROR "SV_StartSound: pitch = %i\n", pitch ); + Con_Reportf( S_ERROR "SV_StartSound: pitch = %i\n", pitch ); pitch = bound( 0, pitch, 255 ); } if( !COM_CheckString( sample )) { - Con_Printf( S_ERROR "SV_StartSound: passed NULL sample\n" ); + Con_Reportf( S_ERROR "SV_StartSound: passed NULL sample\n" ); return 0; } @@ -4744,6 +4724,7 @@ void SV_UnloadProgs( void ) Cvar_FullSet( "sv_background", "0", FCVAR_READ_ONLY ); // free entity baselines + Z_Free( svs.static_entities ); Z_Free( svs.baselines ); svs.baselines = NULL; @@ -4872,6 +4853,7 @@ qboolean SV_LoadProgs( const char *name ) svgame.globals->maxEntities = GI->max_edicts; svgame.globals->maxClients = svs.maxclients; svgame.edicts = Mem_Calloc( svgame.mempool, sizeof( edict_t ) * GI->max_edicts ); + svs.static_entities = Z_Calloc( sizeof( entity_state_t ) * MAX_STATIC_ENTITIES ); svs.baselines = Z_Calloc( sizeof( entity_state_t ) * GI->max_edicts ); svgame.numEntities = svs.maxclients + 1; // clients + world diff --git a/engine/server/sv_init.c b/engine/server/sv_init.c index a76bc358..812960a9 100644 --- a/engine/server/sv_init.c +++ b/engine/server/sv_init.c @@ -378,7 +378,7 @@ void SV_CreateBaseline( void ) { entity_state_t nullstate, *base; int playermodel; - qboolean player; + int delta_type; int entnum; if( FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE )) @@ -396,13 +396,13 @@ void SV_CreateBaseline( void ) if( entnum != 0 && entnum <= svs.maxclients ) { - player = true; + delta_type = DELTA_PLAYER; } else { if( !pEdict->v.modelindex ) continue; // invisible - player = false; + delta_type = DELTA_ENTITY; } // take current state as baseline @@ -415,7 +415,7 @@ void SV_CreateBaseline( void ) base->entityType = ENTITY_BEAM; else base->entityType = ENTITY_NORMAL; - svgame.dllFuncs.pfnCreateBaseline( player, entnum, base, pEdict, playermodel, host.player_mins[0], host.player_maxs[0] ); + svgame.dllFuncs.pfnCreateBaseline( delta_type, entnum, base, pEdict, playermodel, host.player_mins[0], host.player_maxs[0] ); sv.last_valid_baseline = entnum; } @@ -434,19 +434,19 @@ void SV_CreateBaseline( void ) if( entnum != 0 && entnum <= svs.maxclients ) { - player = true; + delta_type = DELTA_PLAYER; } else { if( !pEdict->v.modelindex ) continue; // invisible - player = false; + delta_type = DELTA_ENTITY; } // take current state as baseline base = &svs.baselines[entnum]; - MSG_WriteDeltaEntity( &nullstate, base, &sv.signon, true, player, 1.0f, 0 ); + MSG_WriteDeltaEntity( &nullstate, base, &sv.signon, true, delta_type, 1.0f, 0 ); } MSG_WriteUBitLong( &sv.signon, LAST_EDICT, MAX_ENTITY_BITS ); // end of baselines @@ -455,7 +455,7 @@ void SV_CreateBaseline( void ) for( entnum = 0; entnum < sv.num_instanced; entnum++ ) { base = &sv.instanced[entnum].baseline; - MSG_WriteDeltaEntity( &nullstate, base, &sv.signon, true, false, 1.0f, 0 ); + MSG_WriteDeltaEntity( &nullstate, base, &sv.signon, true, DELTA_ENTITY, 1.0f, 0 ); } } @@ -783,6 +783,7 @@ qboolean SV_SpawnServer( const char *mapname, const char *startspot, qboolean ba MSG_Init( &sv.spec_datagram, "Spectator Datagram", sv.spectator_buf, sizeof( sv.spectator_buf )); // clearing all the baselines + memset( svs.static_entities, 0, sizeof( entity_state_t ) * MAX_STATIC_ENTITIES ); memset( svs.baselines, 0, sizeof( entity_state_t ) * GI->max_edicts ); // make cvars consistant diff --git a/engine/server/sv_main.c b/engine/server/sv_main.c index e749e2ad..f70ba8c2 100644 --- a/engine/server/sv_main.c +++ b/engine/server/sv_main.c @@ -158,7 +158,7 @@ void SV_UpdateMovevars( qboolean initialize ) if( sv_zmax.value < 256.0f ) Cvar_SetValue( "sv_zmax", 256.0f ); // clamp it right - if( host.features & ENGINE_WRITE_LARGE_COORD ) + if( FBitSet( host.features, ENGINE_WRITE_LARGE_COORD )) { if( sv_zmax.value > 131070.0f ) Cvar_SetValue( "sv_zmax", 131070.0f ); @@ -599,7 +599,7 @@ void Host_ServerFrame( void ) // if server is not active, do nothing if( !svs.initialized ) return; - if( sv.simulating || sv.state != ss_active ) + if( sv_fps.value != 0.0f && ( sv.simulating || sv.state != ss_active )) sv.time_residual += host.frametime; if( sv_fps.value == 0.0f ) diff --git a/engine/server/sv_save.c b/engine/server/sv_save.c index 14b1f076..daf4b66d 100644 --- a/engine/server/sv_save.c +++ b/engine/server/sv_save.c @@ -30,7 +30,7 @@ half-life implementation of saverestore system #define SAVEFILE_HEADER (('V'<<24)+('L'<<16)+('A'<<8)+'V') // little-endian "VALV" #define SAVEGAME_HEADER (('V'<<24)+('A'<<16)+('S'<<8)+'J') // little-endian "JSAV" #define SAVEGAME_VERSION 0x0071 // Version 0.71 GoldSrc compatible -#define CLIENT_SAVEGAME_VERSION 0x0065 // Version 0.65 +#define CLIENT_SAVEGAME_VERSION 0x0067 // Version 0.67 #define SAVE_HEAPSIZE 0x400000 // reserve 4Mb for now #define SAVE_HASHSTRINGS 0xFFF // 4095 unique strings @@ -160,19 +160,40 @@ static TYPEDESCRIPTION gDecalEntry[] = static TYPEDESCRIPTION gStaticEntry[] = { - DEFINE_ARRAY( sv_static_entity_t, model, FIELD_CHARACTER, 64 ), - DEFINE_FIELD( sv_static_entity_t, origin, FIELD_VECTOR ), - DEFINE_FIELD( sv_static_entity_t, angles, FIELD_VECTOR ), - DEFINE_FIELD( sv_static_entity_t, sequence, FIELD_SHORT ), - DEFINE_FIELD( sv_static_entity_t, frame, FIELD_SHORT ), - DEFINE_FIELD( sv_static_entity_t, colormap, FIELD_SHORT ), - DEFINE_FIELD( sv_static_entity_t, skin, FIELD_CHARACTER ), - DEFINE_FIELD( sv_static_entity_t, body, FIELD_CHARACTER ), - DEFINE_FIELD( sv_static_entity_t, scale, FIELD_FLOAT ), - DEFINE_FIELD( sv_static_entity_t, rendermode, FIELD_CHARACTER ), - DEFINE_FIELD( sv_static_entity_t, renderamt, FIELD_CHARACTER ), - DEFINE_ARRAY( sv_static_entity_t, rendercolor, FIELD_CHARACTER, sizeof( color24 )), - DEFINE_FIELD( sv_static_entity_t, renderfx, FIELD_CHARACTER ), + DEFINE_FIELD( entity_state_t, messagenum, FIELD_MODELNAME ), // HACKHACK: store model into messagenum + DEFINE_FIELD( entity_state_t, origin, FIELD_VECTOR ), + DEFINE_FIELD( entity_state_t, angles, FIELD_VECTOR ), + DEFINE_FIELD( entity_state_t, sequence, FIELD_INTEGER ), + DEFINE_FIELD( entity_state_t, frame, FIELD_FLOAT ), + DEFINE_FIELD( entity_state_t, colormap, FIELD_INTEGER ), + DEFINE_FIELD( entity_state_t, skin, FIELD_SHORT ), + DEFINE_FIELD( entity_state_t, body, FIELD_INTEGER ), + DEFINE_FIELD( entity_state_t, scale, FIELD_FLOAT ), + DEFINE_FIELD( entity_state_t, effects, FIELD_INTEGER ), + DEFINE_FIELD( entity_state_t, framerate, FIELD_FLOAT ), + DEFINE_FIELD( entity_state_t, mins, FIELD_VECTOR ), + DEFINE_FIELD( entity_state_t, maxs, FIELD_VECTOR ), + DEFINE_FIELD( entity_state_t, rendermode, FIELD_INTEGER ), + DEFINE_FIELD( entity_state_t, renderamt, FIELD_FLOAT ), + DEFINE_ARRAY( entity_state_t, rendercolor, FIELD_CHARACTER, sizeof( color24 )), + DEFINE_FIELD( entity_state_t, renderfx, FIELD_INTEGER ), + DEFINE_FIELD( entity_state_t, controller, FIELD_INTEGER ), + DEFINE_FIELD( entity_state_t, blending, FIELD_INTEGER ), + DEFINE_FIELD( entity_state_t, solid, FIELD_SHORT ), + DEFINE_FIELD( entity_state_t, animtime, FIELD_TIME ), + DEFINE_FIELD( entity_state_t, movetype, FIELD_INTEGER ), + DEFINE_FIELD( entity_state_t, vuser1, FIELD_VECTOR ), + DEFINE_FIELD( entity_state_t, vuser2, FIELD_VECTOR ), + DEFINE_FIELD( entity_state_t, vuser3, FIELD_VECTOR ), + DEFINE_FIELD( entity_state_t, vuser4, FIELD_VECTOR ), + DEFINE_FIELD( entity_state_t, iuser1, FIELD_INTEGER ), + DEFINE_FIELD( entity_state_t, iuser2, FIELD_INTEGER ), + DEFINE_FIELD( entity_state_t, iuser3, FIELD_INTEGER ), + DEFINE_FIELD( entity_state_t, iuser4, FIELD_INTEGER ), + DEFINE_FIELD( entity_state_t, fuser1, FIELD_FLOAT ), + DEFINE_FIELD( entity_state_t, fuser2, FIELD_FLOAT ), + DEFINE_FIELD( entity_state_t, fuser3, FIELD_FLOAT ), + DEFINE_FIELD( entity_state_t, fuser4, FIELD_FLOAT ), }; static TYPEDESCRIPTION gSoundEntry[] = @@ -1145,7 +1166,7 @@ static void SaveClientState( SAVERESTOREDATA *pSaveData, const char *level, int // write client entities for( i = 0; i < header.entityCount; i++ ) - svgame.dllFuncs.pfnSaveWriteFields( pSaveData, "STATICENTITY", &sv.static_entities[i], gStaticEntry, ARRAYSIZE( gStaticEntry )); + svgame.dllFuncs.pfnSaveWriteFields( pSaveData, "STATICENTITY", &svs.static_entities[i], gStaticEntry, ARRAYSIZE( gStaticEntry )); // write sounds for( i = 0; i < header.soundCount; i++ ) @@ -1188,7 +1209,6 @@ static void LoadClientState( SAVERESTOREDATA *pSaveData, const char *level, qboo int i, size, id, version; sv_client_t *cl = svs.clients; char name[MAX_QPATH]; - sv_static_entity_t staticEntry; soundlist_t soundEntry; decallist_t decalEntry; SAVE_CLIENT header; @@ -1248,22 +1268,19 @@ static void LoadClientState( SAVERESTOREDATA *pSaveData, const char *level, qboo // clear old entities if( !adjacent ) { - memset( sv.static_entities, 0, sizeof( sv.static_entities )); + memset( svs.static_entities, 0, sizeof( entity_state_t ) * MAX_STATIC_ENTITIES ); sv.num_static_entities = 0; } // restore client entities for( i = 0; i < header.entityCount; i++ ) { - svgame.dllFuncs.pfnSaveReadFields( pSaveData, "STATICENTITY", &staticEntry, gStaticEntry, ARRAYSIZE( gStaticEntry )); + id = sv.num_static_entities; + svgame.dllFuncs.pfnSaveReadFields( pSaveData, "STATICENTITY", &svs.static_entities[id], gStaticEntry, ARRAYSIZE( gStaticEntry )); if( adjacent ) continue; // static entities won't loading from adjacent levels - if( i >= MAX_STATIC_ENTITIES ) - continue; // silently overflowed - - SV_CreateStaticEntity( &sv.signon, &staticEntry ); - sv.static_entities[i] = staticEntry; - sv.num_static_entities++; + if( SV_CreateStaticEntity( &sv.signon, id )) + sv.num_static_entities++; } // restore sounds @@ -1558,7 +1575,7 @@ static int LoadGameState( char const *level, qboolean changelevel ) if( pent != NULL ) { - if( svgame.dllFuncs.pfnRestore( pent, pSaveData, false ) < 0 ) + if( svgame.dllFuncs.pfnRestore( pent, pSaveData, 0 ) < 0 ) { SetBits( pent->v.flags, FL_KILLME ); pTable->pent = NULL;