diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 417a4254..03c88acb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -43,7 +43,7 @@ jobs: rule: install - btype: Debug - rule: debug install + rule: debug defaults: run: @@ -136,7 +136,7 @@ jobs: - btype: Debug if: ${{ github.event_name != 'release' }} - rule: debug install + rule: debug - arch: x86 use_sdl: USE_SDL=0 @@ -175,12 +175,12 @@ jobs: retention-days: 5 macos-x86: - name: ${{ matrix.btype }} macOS x86_64 + name: ${{ matrix.btype }} macOS ${{ matrix.arch }} runs-on: macos-latest strategy: fail-fast: false matrix: - arch: [x86_64] + arch: [x86_64, aarch64] cc: [clang] btype: [Release, Debug] include: @@ -188,12 +188,12 @@ jobs: rule: install - btype: Debug - rule: debug install + rule: debug steps: - name: Install tools - run: brew install coreutils pkg-config sdl2 + run: brew install coreutils sdl2 # pkg-config - uses: actions/checkout@v4 with: @@ -230,7 +230,7 @@ jobs: 7z a -r cmod-macos-x86_64.zip ./macos-x86_64/* - name: Create latest build - uses: marvinpinto/action-automatic-releases@latest + uses: czietz/action-automatic-releases@latest with: repo_token: ${{ secrets.GITHUB_TOKEN }} automatic_release_tag: "latest" diff --git a/Makefile b/Makefile index 6aae06d2..7d1eb323 100644 --- a/Makefile +++ b/Makefile @@ -274,8 +274,12 @@ ifeq ($(USE_SYSTEM_VORBIS),1) endif # extract version info +ifneq ($(COMPILE_PLATFORM),darwin) VERSION=$(shell grep ".\+define[ \t]\+Q3_VERSION[ \t]\+\+" $(CMDIR)/q_shared.h | \ sed -e 's/.*".* \([^ ]*\)"/\1/') +else +VERSION=1.32e +endif # common qvm definition ifeq ($(ARCH),x86_64) diff --git a/code/client/cl_curl.c b/code/client/cl_curl.c index d8ef7eba..57c6e20c 100644 --- a/code/client/cl_curl.c +++ b/code/client/cl_curl.c @@ -388,9 +388,7 @@ void CL_cURL_BeginDownload( const char *localName, const char *remoteURL ) !clc.cURLDisconnected) { CL_AddReliableCommand("disconnect", qtrue); - CL_WritePacket(); - CL_WritePacket(); - CL_WritePacket(); + CL_WritePacket( 2 ); clc.cURLDisconnected = qtrue; } } @@ -767,8 +765,8 @@ static int Com_DL_CallbackProgress( void *data, double dltotal, double dlnow, do Com_Printf( "%s: aborted\n", dl->Name ); return -1; } - Cvar_Set( "cl_downloadSize", va( "%i", dl->Size ) ); - Cvar_Set( "cl_downloadCount", va( "%i", dl->Count ) ); + Cvar_SetIntegerValue( "cl_downloadSize", dl->Size ); + Cvar_SetIntegerValue( "cl_downloadCount", dl->Count ); } if ( dl->Size ) { @@ -1062,7 +1060,7 @@ qboolean Com_DL_Begin( download_t *dl, const char *localName, const char *remote Cvar_Set( "cl_downloadName", dl->Name ); Cvar_Set( "cl_downloadSize", "0" ); Cvar_Set( "cl_downloadCount", "0" ); - Cvar_Set( "cl_downloadTime", va( "%i", cls.realtime ) ); + Cvar_SetIntegerValue( "cl_downloadTime", cls.realtime ); } return qtrue; diff --git a/code/client/cl_input.c b/code/client/cl_input.c index 23022ab7..9ca16729 100644 --- a/code/client/cl_input.c +++ b/code/client/cl_input.c @@ -752,7 +752,7 @@ During normal gameplay, a client packet will contain something like: =================== */ -void CL_WritePacket( void ) { +void CL_WritePacket( int repeat ) { msg_t buf; byte data[ MAX_MSGLEN_BUF ]; int i, j, n; @@ -826,11 +826,10 @@ void CL_WritePacket( void ) { } // begin a client move command - if ( cl_nodelta->integer || !cl.snap.valid || clc.demowaiting - || clc.serverMessageSequence != cl.snap.messageNum ) { - MSG_WriteByte (&buf, clc_moveNoDelta); + if ( cl_nodelta->integer || !cl.snap.valid || clc.demowaiting || clc.serverMessageSequence != cl.snap.messageNum ) { + MSG_WriteByte( &buf, clc_moveNoDelta ); } else { - MSG_WriteByte (&buf, clc_move); + MSG_WriteByte( &buf, clc_move ); } // write the command count @@ -871,7 +870,24 @@ void CL_WritePacket( void ) { Com_Printf( "%i ", buf.cursize ); } - CL_Netchan_Transmit( &clc.netchan, &buf ); +#ifdef ELITEFORCE + if( !clc.compat ) +#endif + MSG_WriteByte( &buf, clc_EOF ); + + if ( buf.overflowed ) { + if ( cls.state >= CA_CONNECTED && cls.state != CA_CINEMATIC ) { + cls.state = CA_CONNECTING; // to avoid recursive error + } + Com_Error( ERR_DROP, "%s: message overflowed", __func__ ); + } + + if ( repeat == 0 || clc.netchan.remoteAddress.type == NA_LOOPBACK ) { + CL_Netchan_Transmit( &clc.netchan, &buf ); + } else { + CL_Netchan_Enqueue( &clc.netchan, &buf, repeat + 1 ); + NET_FlushPacketQueue( 0 ); + } } @@ -904,7 +920,7 @@ void CL_SendCmd( void ) { return; } - CL_WritePacket(); + CL_WritePacket( 0 ); } diff --git a/code/client/cl_main.c b/code/client/cl_main.c index 1f79f6a0..60507874 100644 --- a/code/client/cl_main.c +++ b/code/client/cl_main.c @@ -1198,7 +1198,7 @@ void CL_MapLoading( void ) { Com_Memset( cls.updateInfoString, 0, sizeof( cls.updateInfoString ) ); Com_Memset( clc.serverMessage, 0, sizeof( clc.serverMessage ) ); Com_Memset( &cl.gameState, 0, sizeof( cl.gameState ) ); - clc.lastPacketSentTime = -9999; + clc.lastPacketSentTime = cls.realtime - 9999; // send packet immediately cls.framecount++; SCR_UpdateScreen(); } else { @@ -1400,9 +1400,7 @@ qboolean CL_Disconnect( qboolean showMainMenu ) { // send it a few times in case one is dropped if ( cls.state >= CA_CONNECTED && cls.state != CA_CINEMATIC && !clc.demoplaying ) { CL_AddReliableCommand( "disconnect", qtrue ); - CL_WritePacket(); - CL_WritePacket(); - CL_WritePacket(); + CL_WritePacket( 2 ); } CL_ClearState(); @@ -2228,9 +2226,7 @@ static void CL_DownloadsComplete( void ) { // set pure checksums CL_SendPureChecksums(); - CL_WritePacket(); - CL_WritePacket(); - CL_WritePacket(); + CL_WritePacket( 2 ); } @@ -2386,7 +2382,7 @@ void CL_NextDownload( void ) Com_Error(ERR_DROP, "Incorrect checksum for file: %s", clc.downloadName); } - *clc.downloadTempName = *clc.downloadName = 0; + *clc.downloadTempName = *clc.downloadName = '\0'; Cvar_Set("cl_downloadName", ""); // We are looking to start a download here @@ -3088,7 +3084,7 @@ static qboolean CL_ConnectionlessPacket( const netadr_t *from, msg_t *msg ) { clc.netchan.compat = clc.compat; #endif cls.state = CA_CONNECTED; - clc.lastPacketSentTime = -9999; // send first packet immediately + clc.lastPacketSentTime = cls.realtime - 9999; // send first packet immediately return qtrue; } diff --git a/code/client/cl_net_chan.c b/code/client/cl_net_chan.c index a7e75b3f..68f49944 100644 --- a/code/client/cl_net_chan.c +++ b/code/client/cl_net_chan.c @@ -155,17 +155,6 @@ CL_Netchan_Transmit ================ */ void CL_Netchan_Transmit( netchan_t *chan, msg_t* msg ) { -#ifdef ELITEFORCE - if( !chan->compat ) -#endif - MSG_WriteByte( msg, clc_EOF ); - - if ( msg->overflowed ) { - if ( cls.state >= CA_CONNECTED && cls.state != CA_CINEMATIC ) { - cls.state = CA_CONNECTING; // to avoid recursive error - } - Com_Error( ERR_DROP, "%s: message overflowed", __func__ ); - } #ifndef ELITEFORCE if ( chan->compat ) @@ -182,6 +171,33 @@ void CL_Netchan_Transmit( netchan_t *chan, msg_t* msg ) { } +/* +=============== +CL_Netchan_Enqueue +================ +*/ +void CL_Netchan_Enqueue( netchan_t *chan, msg_t* msg, int times ) { + int i; + + // make sure we send all pending fragments to get correct chan->outgoingSequence + while ( CL_Netchan_TransmitNextFragment( chan ) ) { + ; + } + +#ifndef ELITEFORCE + if ( chan->compat ) { + CL_Netchan_Encode( msg ); + } +#endif + + for ( i = 0; i < times; i++ ) { + Netchan_Enqueue( chan, msg->cursize, msg->data ); + } + + chan->outgoingSequence++; +} + + /* ================= CL_Netchan_Process diff --git a/code/client/cl_parse.c b/code/client/cl_parse.c index e37aad74..bf1862fc 100644 --- a/code/client/cl_parse.c +++ b/code/client/cl_parse.c @@ -831,7 +831,7 @@ static void CL_ParseDownload( msg_t *msg ) { // So UI gets access to it Cvar_SetIntegerValue( "cl_downloadCount", clc.downloadCount ); - if (!size) { // A zero length block means EOF + if ( size == 0 ) { // A zero length block means EOF if ( clc.download != FS_INVALID_HANDLE ) { FS_FCloseFile( clc.download ); clc.download = FS_INVALID_HANDLE; @@ -849,8 +849,7 @@ static void CL_ParseDownload( msg_t *msg ) { // loading right away. If we take a while to load, the server is happily trying // to send us that last block over and over. // Write it twice to help make sure we acknowledge the download - CL_WritePacket(); - CL_WritePacket(); + CL_WritePacket( 1 ); // get another file if needed CL_NextDownload(); diff --git a/code/client/client.h b/code/client/client.h index 41eff7e8..87863749 100644 --- a/code/client/client.h +++ b/code/client/client.h @@ -477,7 +477,7 @@ qboolean CL_GetModeInfo( int *width, int *height, float *windowAspect, int mode, void CL_InitInput( void ); void CL_ClearInput( void ); void CL_SendCmd( void ); -void CL_WritePacket( void ); +void CL_WritePacket( int repeat ); // // cl_keys.c @@ -588,6 +588,7 @@ void Key_SetCatcher( int catcher ); // cl_net_chan.c // void CL_Netchan_Transmit( netchan_t *chan, msg_t *msg ); +void CL_Netchan_Enqueue( netchan_t *chan, msg_t *msg, int times ); qboolean CL_Netchan_Process( netchan_t *chan, msg_t *msg ); // diff --git a/code/eliteforce/server/stef_sv_record_spectator.c b/code/eliteforce/server/stef_sv_record_spectator.c index 3cf99a65..37461d1a 100644 --- a/code/eliteforce/server/stef_sv_record_spectator.c +++ b/code/eliteforce/server/stef_sv_record_spectator.c @@ -616,8 +616,8 @@ static void Record_Spectator_ProcessMessage( spectator_t *spectator, msg_t *msg cl->reliableAcknowledge = cl->reliableSequence; } - if ( serverId < sv.restartedServerId || serverId > sv.serverId ) { - // Pre map change serverID, or invalid high serverID + if ( serverId != sv.serverId ) { + // Invalid serverID if ( cl->messageAcknowledge > cl->gamestateMessageNum ) { // No previous gamestate waiting to be acknowledged - send new one Record_SendSpectatorGamestate( spectator ); diff --git a/code/eliteforce/stef_config.h b/code/eliteforce/stef_config.h index 8d831888..63636179 100644 --- a/code/eliteforce/stef_config.h +++ b/code/eliteforce/stef_config.h @@ -142,23 +142,10 @@ // coordinated with game module fixes in order to work consistently. #define STEF_SHIELD_EFFECT_FIX -// [BUGFIX] Disable force-spawning loading players into game during map restart. -// Fixes freezing after UDP download due to CS_ACTIVE check in SV_DoneDownload_f. -#define STEF_MAP_RESTART_NO_LOADING_SPAWN - -// [BUGFIX] Set players to CS_CONNECTED during UDP downloads, preventing server commands -// being added in SV_AddServerCommand which can accumulate without being sent properly. -#define STEF_DOWNLOAD_CONNECTION_STATE_FIX - // [BUGFIX] Fix for "Delta parseEntitiesNum too old" errors in certain cases. // (40+ sv_fps value + 1.20 client + high ping/bad connection) #define STEF_SNAPSHOT_DELTA_BUFFER_FIX -// [BUGFIX] Use alternative to changing serverid during map restarts. -// This avoids the need for a systeminfo update during map restarts and potentially fixes -// some intermittent buggy behavior seen in the EF 1.20 client. -#define STEF_MAP_RESTART_STATIC_SERVERID - // [BUGFIX] Prevent gamestate overflows by dropping entity baselines. // Fixes errors on certain maps under certain conditions. #define STEF_GAMESTATE_OVERFLOW_FIX diff --git a/code/qcommon/cmd.c b/code/qcommon/cmd.c index 2683e411..2455dff6 100644 --- a/code/qcommon/cmd.c +++ b/code/qcommon/cmd.c @@ -260,7 +260,6 @@ void Cbuf_Execute( void ) if ( cmd_wait > 0 ) { // delay command buffer execution - cmd_wait--; return; } @@ -357,9 +356,11 @@ void Cbuf_Execute( void ) Cbuf_Wait ============ */ -qboolean Cbuf_Wait( void ) +void Cbuf_Wait( void ) { - return (cmd_wait > 0) ? qtrue : qfalse; + if ( cmd_wait > 0 ) { + --cmd_wait; + } } diff --git a/code/qcommon/common.c b/code/qcommon/common.c index a12109d4..46fc0176 100644 --- a/code/qcommon/common.c +++ b/code/qcommon/common.c @@ -357,7 +357,7 @@ void NORETURN FORMAT_PRINTF(2, 3) QDECL Com_Error( errorParm_t code, const char com_errorEntered = qtrue; - Cvar_Set( "com_errorCode", va( "%i", code ) ); + Cvar_SetIntegerValue( "com_errorCode", code ); // when we are running automated scripts, make sure we // know if anything failed @@ -2899,11 +2899,13 @@ Returns last event time */ int Com_EventLoop( void ) { sysEvent_t ev; - netadr_t evFrom; + +#ifndef DEDICATED byte bufData[ MAX_MSGLEN_BUF ]; msg_t buf; MSG_Init( &buf, bufData, MAX_MSGLEN ); +#endif // !DEDICATED while ( 1 ) { ev = Com_GetEvent(); @@ -2912,21 +2914,20 @@ int Com_EventLoop( void ) { if ( ev.evType == SE_NONE ) { // manually send packet events for the loopback channel #ifndef DEDICATED + netadr_t evFrom; while ( NET_GetLoopPacket( NS_CLIENT, &evFrom, &buf ) ) { CL_PacketEvent( &evFrom, &buf ); } -#endif while ( NET_GetLoopPacket( NS_SERVER, &evFrom, &buf ) ) { // if the server just shut down, flush the events if ( com_sv_running->integer ) { Com_RunAndTimeServerPacket( &evFrom, &buf ); } } - +#endif // !DEDICATED return ev.evTime; } - switch ( ev.evType ) { #ifndef DEDICATED case SE_KEY: @@ -2941,7 +2942,7 @@ int Com_EventLoop( void ) { case SE_JOYSTICK_AXIS: CL_JoystickEvent( ev.evValue, ev.evValue2, ev.evTime ); break; -#endif +#endif // !DEDICATED case SE_CONSOLE: Cbuf_AddText( (char *)ev.evPtr ); Cbuf_AddText( "\n" ); @@ -4631,9 +4632,7 @@ void Com_Frame( qboolean noDelay ) { } Com_EventLoop(); - if ( !Cbuf_Wait() ) { - Cbuf_Execute(); - } + Cbuf_Execute(); // // client side @@ -4650,7 +4649,9 @@ void Com_Frame( qboolean noDelay ) { } #endif - NET_FlushPacketQueue(); + NET_FlushPacketQueue( 0 ); + + Cbuf_Wait(); // // report timing information diff --git a/code/qcommon/net_chan.c b/code/qcommon/net_chan.c index 3a2d044a..6fd94307 100644 --- a/code/qcommon/net_chan.c +++ b/code/qcommon/net_chan.c @@ -267,6 +267,60 @@ void Netchan_TransmitNextFragment( netchan_t *chan ) { } +/* +================= +EnqueueFragments +================= +*/ +static void Netchan_EnqueueFragments( const netchan_t *chan, const int length, const byte *data ) { + msg_t send; + byte send_buf[MAX_PACKETLEN + 8]; + int fragmentLength; + int unsentFragmentStart = 0; + + for ( ;; ) { + // write the packet header + MSG_InitOOB( &send, send_buf, sizeof( send_buf ) - 8 ); + + MSG_WriteLong( &send, chan->outgoingSequence | FRAGMENT_BIT ); + + // send the qport if we are a client + if ( chan->sock == NS_CLIENT ) { + MSG_WriteShort( &send, qport->integer ); + } + + if ( !chan->compat ) { + MSG_WriteLong( &send, NETCHAN_GENCHECKSUM( chan->challenge, chan->outgoingSequence ) ); + } + + // copy the reliable message to the packet first + fragmentLength = FRAGMENT_SIZE; + if ( unsentFragmentStart + fragmentLength > length ) { + fragmentLength = length - unsentFragmentStart; + } + + MSG_WriteShort( &send, unsentFragmentStart ); + MSG_WriteShort( &send, fragmentLength ); + MSG_WriteData( &send, data + unsentFragmentStart, fragmentLength ); + + // enqueue the datagram + NET_QueuePacket( chan->sock, send.cursize, send.data, &chan->remoteAddress, 0 /*offset*/ ); + + // TODO: add showpackets debug info + + unsentFragmentStart += fragmentLength; + + // this exit condition is a little tricky, because a packet + // that is exactly the fragment length still needs to send + // a second packet of zero length so that the other side + // can tell there aren't more to follow + if ( unsentFragmentStart == length && fragmentLength != FRAGMENT_SIZE ) { + break; + } + } +} + + /* =============== Netchan_Transmit @@ -280,8 +334,9 @@ void Netchan_Transmit( netchan_t *chan, int length, const byte *data ) { byte send_buf[MAX_PACKETLEN+8]; if ( length > MAX_MSGLEN ) { - Com_Error( ERR_DROP, "Netchan_Transmit: length = %i", length ); + Com_Error( ERR_DROP, "%s: length = %i", __func__, length ); } + chan->unsentFragmentStart = 0; // fragment large reliable messages @@ -301,7 +356,7 @@ void Netchan_Transmit( netchan_t *chan, int length, const byte *data ) { MSG_WriteLong( &send, chan->outgoingSequence ); // send the qport if we are a client - if(chan->sock == NS_CLIENT) + if ( chan->sock == NS_CLIENT ) MSG_WriteShort( &send, qport->integer ); if ( !chan->compat ) @@ -336,6 +391,49 @@ void Netchan_Transmit( netchan_t *chan, int length, const byte *data ) { } +/* +=============== +Netchan_Enqueue + +Enqueue a message to a queue#1, fragmenting if necessary +A 0 length will still generate a packet. +================ +*/ +void Netchan_Enqueue( netchan_t *chan, int length, const byte *data ) { + byte send_buf[MAX_PACKETLEN + 8]; + msg_t send; + + if ( length > MAX_MSGLEN ) { + Com_Error( ERR_DROP, "%s: length = %i", __func__, length ); + } + + // fragment large reliable messages + if ( length >= FRAGMENT_SIZE ) { + Netchan_EnqueueFragments( chan, length, data ); + return; + } + + // write the packet header + MSG_InitOOB( &send, send_buf, sizeof( send_buf ) - 8 ); + + MSG_WriteLong( &send, chan->outgoingSequence ); + + // send the qport if we are a client + if ( chan->sock == NS_CLIENT ) + MSG_WriteShort( &send, qport->integer ); + + if ( !chan->compat ) + MSG_WriteLong( &send, NETCHAN_GENCHECKSUM( chan->challenge, chan->outgoingSequence ) ); + + MSG_WriteData( &send, data, length ); + + // enqueue the datagram + NET_QueuePacket( chan->sock, send.cursize, send.data, &chan->remoteAddress, 0 /*offset*/ ); + + // TODO: add showpackets debug info +} + + /* ================= Netchan_Process @@ -376,8 +474,8 @@ qboolean Netchan_Process( netchan_t *chan, msg_t *msg ) { if ( chan->sock == NS_SERVER ) { /*qport=*/ MSG_ReadShort( msg ); } - if ( !chan->compat ) - { + + if ( !chan->compat ) { int checksum = MSG_ReadLong( msg ); // UDP spoofing protection @@ -437,7 +535,7 @@ qboolean Netchan_Process( netchan_t *chan, msg_t *msg ) { // - // if this is the final framgent of a reliable message, + // if this is the final fragment of a reliable message, // bump incoming_reliable_sequence // if ( fragmented ) { @@ -529,10 +627,11 @@ LOOPBACK BUFFERS FOR LOCAL PLAYER ============================================================================= */ +#ifndef DEDICATED // there needs to be enough loopback messages to hold a complete // gamestate of maximum size -#define MAX_LOOPBACK 16 +#define MAX_LOOPBACK 32 typedef struct { byte data[MAX_PACKETLEN]; @@ -585,62 +684,117 @@ static void NET_SendLoopPacket( netsrc_t sock, int length, const void *data ) loop->msgs[i].datalen = length; } +#endif // !DEDICATED + //============================================================================= typedef struct packetQueue_s { - struct packetQueue_s *next; - int length; - byte *data; - netadr_t to; - int release; + struct packetQueue_s *next; + struct packetQueue_s *prev; + int length; + byte *data; + netadr_t to; + netsrc_t sock; + int release; } packetQueue_t; static packetQueue_t *packetQueue = NULL; -static void NET_QueuePacket( int length, const void *data, const netadr_t *to, int offset ) +static packetQueue_t *list_remove( packetQueue_t *head, packetQueue_t *item ) { + if ( item->next != item ) { + item->next->prev = item->prev; + item->prev->next = item->next; + } else { + item->next = item->prev = NULL; + } + return item == head ? item->next : head; +} + + +static packetQueue_t *list_insert( packetQueue_t *head, packetQueue_t *item ) +{ + if ( head ) { + packetQueue_t *prev = head->prev; + packetQueue_t *next = head; + prev->next = item; + next->prev = item; + item->prev = prev; + item->next = next; + return head; + } else { + item->prev = item->next = item; + return item; + } +} + + +static packetQueue_t *list_process( packetQueue_t *head, const int time_diff ) +{ + packetQueue_t *item = head; + int do_break = 0; + int now; + do { + if ( head == NULL ) { + break; + } + if ( head->prev == item ) { + do_break = 1; + } + now = Sys_Milliseconds(); + if ( now - item->release >= time_diff ) { + packetQueue_t *next = item->next; +#ifndef DEDICATED + if ( item->to.type == NA_LOOPBACK ) + NET_SendLoopPacket( item->sock, item->length, item->data ); + else +#endif + Sys_SendPacket( item->length, item->data, &item->to ); + head = list_remove( head, item ); + Z_Free( item ); + item = next; + } else { + item = item->next; + } + } while ( do_break == 0 ); + + return head; +} + + +void NET_QueuePacket( netsrc_t sock, int length, const void *data, const netadr_t *to, int offset ) { - packetQueue_t *new, *next = packetQueue; + packetQueue_t *new; + + if ( to->type == NA_BOT ) { + return; + } + if ( to->type == NA_BAD ) { + return; + } + if ( com_timescale->value == 0.0f ) { + return; + } - if(offset > 999) + if ( offset > 999 ) { offset = 999; + } - new = S_Malloc(sizeof(packetQueue_t)); - new->data = S_Malloc(length); + new = S_Malloc(sizeof(*new) + length); + new->data = (byte *)( new + 1 ); Com_Memcpy(new->data, data, length); new->length = length; new->to = *to; - new->release = Sys_Milliseconds() + (int)((float)offset / com_timescale->value); + new->sock = sock; + new->release = Sys_Milliseconds() + (int)( (float)offset / com_timescale->value ); new->next = NULL; - if(!packetQueue) { - packetQueue = new; - return; - } - while(next) { - if(!next->next) { - next->next = new; - return; - } - next = next->next; - } + packetQueue = list_insert( packetQueue, new ); } -void NET_FlushPacketQueue( void ) +void NET_FlushPacketQueue( int time_diff ) { - packetQueue_t *last; - int now; - - while ( packetQueue ) { - now = Sys_Milliseconds(); - if ( packetQueue->release - now >= 0 ) - break; - Sys_SendPacket( packetQueue->length, packetQueue->data, &packetQueue->to ); - last = packetQueue; - packetQueue = packetQueue->next; - Z_Free( last->data ); - Z_Free( last ); - } + packetQueue = list_process( packetQueue, time_diff ); } @@ -651,10 +805,6 @@ void NET_SendPacket( netsrc_t sock, int length, const void *data, const netadr_t Com_Printf ("send packet %4i\n", length); } - if ( to->type == NA_LOOPBACK ) { - NET_SendLoopPacket( sock, length, data ); - return; - } if ( to->type == NA_BOT ) { return; } @@ -663,12 +813,17 @@ void NET_SendPacket( netsrc_t sock, int length, const void *data, const netadr_t } #ifndef DEDICATED if ( sock == NS_CLIENT && cl_packetdelay->integer > 0 ) { - NET_QueuePacket( length, data, to, cl_packetdelay->integer ); + NET_QueuePacket( sock, length, data, to, cl_packetdelay->integer ); } else #endif if ( sock == NS_SERVER && sv_packetdelay->integer > 0 ) { - NET_QueuePacket( length, data, to, sv_packetdelay->integer ); + NET_QueuePacket( sock, length, data, to, sv_packetdelay->integer ); } +#ifndef DEDICATED + else if ( to->type == NA_LOOPBACK ) { + NET_SendLoopPacket( sock, length, data ); + } +#endif else { Sys_SendPacket( length, data, to ); } diff --git a/code/qcommon/net_ip.c b/code/qcommon/net_ip.c index 226fc0ad..a29a3ca0 100644 --- a/code/qcommon/net_ip.c +++ b/code/qcommon/net_ip.c @@ -1899,7 +1899,10 @@ qboolean NET_Sleep( int timeout ) Sleep( timeout / 1000 ); return qtrue; #else - usleep( timeout ); + struct timespec req; + req.tv_sec = timeout / 1000000; + req.tv_nsec = ( timeout % 1000000 ) * 1000; + nanosleep( &req, NULL ); return qtrue; #endif } diff --git a/code/qcommon/q_shared.c b/code/qcommon/q_shared.c index a7c69bcc..ee1975fb 100644 --- a/code/qcommon/q_shared.c +++ b/code/qcommon/q_shared.c @@ -1309,24 +1309,56 @@ void Q_strncpyz( char *dest, const char *src, int destsize ) /* ============= Q_strncpy + +allows src and dest to be overlapped for QVM compatibility purposes ============= */ -char *Q_strncpy( char *dest, const char *src, int destsize ) +char *Q_strncpy( char *dest, char *src, int destsize ) { - char *start = dest; + char *s = src, *start = dest; + int src_len; - while ( destsize > 0 && (*dest++ = *src++) != '\0' ) { - --destsize; + while ( *s != '\0' ) + ++s; + src_len = (int)(s - src); + + if ( src_len > destsize ) { + src_len = destsize; + } + destsize -= src_len; + + if ( dest > src && dest < src + src_len ) { + int i; +#ifdef _DEBUG + Com_Printf( S_COLOR_YELLOW "Q_strncpy: overlapped (dest > src) buffers\n" ); +#endif + for ( i = src_len - 1; i >= 0; --i ) { + dest[i] = src[i]; // back overlapping + } + dest += src_len; + } else { +#ifdef _DEBUG + if ( src >= dest && src < dest + src_len ) { + Com_Printf( S_COLOR_YELLOW "Q_strncpy: overlapped (src >= dst) buffers\n" ); +#ifdef _MSC_VER + // __debugbreak(); +#endif + } +#endif + while ( src_len > 0 ) { + *dest++ = *src++; + --src_len; + } } - while ( --destsize > 0 ) { + while ( destsize > 0 ) { *dest++ = '\0'; + --destsize; } return start; } - /* ============= Q_stricmpn diff --git a/code/qcommon/q_shared.h b/code/qcommon/q_shared.h index 2ef12468..b2ad2afe 100644 --- a/code/qcommon/q_shared.h +++ b/code/qcommon/q_shared.h @@ -860,7 +860,7 @@ void Q_strcat( char *dest, int size, const char *src ); int Q_replace( const char *str1, const char *str2, char *src, int max_len ); char *Q_stradd( char *dst, const char *src ); -char *Q_strncpy( char *dest, const char *src, int destsize ); +char *Q_strncpy( char *dest, char *src, int destsize ); // strlen that discounts Quake color sequences int Q_PrintStrlen( const char *string ); diff --git a/code/qcommon/qcommon.h b/code/qcommon/qcommon.h index 8864dccb..0f50dc43 100644 --- a/code/qcommon/qcommon.h +++ b/code/qcommon/qcommon.h @@ -212,7 +212,8 @@ typedef struct { void NET_Init( void ); void NET_Shutdown( void ); -void NET_FlushPacketQueue(void); +void NET_FlushPacketQueue( int time_diff ); +void NET_QueuePacket( netsrc_t sock, int length, const void *data, const netadr_t *to, int offset ); void NET_SendPacket( netsrc_t sock, int length, const void *data, const netadr_t *to ); void QDECL NET_OutOfBandPrint( netsrc_t net_socket, const netadr_t *adr, const char *format, ...) __attribute__ ((format (printf, 3, 4))); void NET_OutOfBandCompress( netsrc_t sock, const netadr_t *adr, const byte *data, int len ); @@ -224,7 +225,9 @@ qboolean NET_IsLocalAddress( const netadr_t *adr ); const char *NET_AdrToString( const netadr_t *a ); const char *NET_AdrToStringwPort( const netadr_t *a ); int NET_StringToAdr( const char *s, netadr_t *a, netadrtype_t family ); +#ifndef DEDICATED qboolean NET_GetLoopPacket( netsrc_t sock, netadr_t *net_from, msg_t *net_message ); +#endif #ifdef USE_IPV6 void NET_JoinMulticast6( void ); void NET_LeaveMulticast6( void ); @@ -291,6 +294,7 @@ void Netchan_Setup( netsrc_t sock, netchan_t *chan, const netadr_t *adr, int por void Netchan_Transmit( netchan_t *chan, int length, const byte *data ); void Netchan_TransmitNextFragment( netchan_t *chan ); +void Netchan_Enqueue( netchan_t *chan, int length, const byte *data ); qboolean Netchan_Process( netchan_t *chan, msg_t *msg ); @@ -509,7 +513,7 @@ void Cbuf_Execute( void ); // Normally called once per frame, but may be explicitly invoked. // Do not call inside a command function, or current args will be destroyed. -qboolean Cbuf_Wait( void ); +void Cbuf_Wait( void ); // Checks if wait command timeout remaining //=========================================================================== diff --git a/code/renderervk/vk.c b/code/renderervk/vk.c index 31ca3b0f..96bde580 100644 --- a/code/renderervk/vk.c +++ b/code/renderervk/vk.c @@ -1588,7 +1588,8 @@ static qboolean vk_create_device( VkPhysicalDevice physical_device, int device_i vk.wideLines = qtrue; } - if ( device_features.fragmentStoresAndAtomics ) { + if ( device_features.fragmentStoresAndAtomics && device_features.vertexPipelineStoresAndAtomics ) { + features.vertexPipelineStoresAndAtomics = VK_TRUE; features.fragmentStoresAndAtomics = VK_TRUE; vk.fragmentStores = qtrue; } @@ -2859,6 +2860,7 @@ static void vk_alloc_persistent_pipelines( void ) } // flare visibility test dot + if ( vk.fragmentStores ) { Com_Memset( &def, 0, sizeof( def ) ); //def.state_bits = GLS_DEFAULT; diff --git a/code/sdl/sdl_glimp.c b/code/sdl/sdl_glimp.c index e9324a8d..3647c9e2 100644 --- a/code/sdl/sdl_glimp.c +++ b/code/sdl/sdl_glimp.c @@ -272,7 +272,11 @@ static int GLW_SetMode( int mode, const char *modeFS, qboolean fullscreen, qbool if ( fullscreen ) { +#ifdef MACOS_X + flags |= SDL_WINDOW_FULLSCREEN_DESKTOP; +#else flags |= SDL_WINDOW_FULLSCREEN; +#endif } else if ( r_noborder->integer ) { diff --git a/code/server/server.h b/code/server/server.h index 1a90f4c9..332f438b 100644 --- a/code/server/server.h +++ b/code/server/server.h @@ -66,12 +66,11 @@ typedef struct snapshotFrame_s { typedef struct { serverState_t state; qboolean restarting; // if true, send configstring changes during SS_LOADING + int pure; // fixed at level spawn + int maxclients; // fixed at level spawn int serverId; // changes each server start - int restartedServerId; // serverId before a map_restart + int restartedServerId; // changes each map restart int checksumFeed; // the feed key that we use to compute the pure checksum strings - // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=475 - // the serverId associated with the current checksumFeed (always <= serverId) - int checksumFeedServerId; int snapshotCounter; // incremented for each snapshot built int timeResidual; // <= 1000 / sv_frame->value char *configstrings[MAX_CONFIGSTRINGS]; @@ -150,6 +149,12 @@ struct leakyBucket_s { leakyBucket_t *prev, *next; }; +typedef enum { + GSA_INIT = 0, // gamestate never sent with current sv.serverId + GSA_SENT_ONCE, // gamestate sent once, client can reply with any (messageAcknowledge - gamestateMessageNum) >= 0 and correct serverId + GSA_SENT_MANY, // gamestate sent many times, client must reply with exact gamestateMessageNum == gamestateMessageNum and correct serverId + GSA_ACKED // gamestate acknowledged, no retansmissions needed +} gameStateAck_t; typedef struct client_s { clientState_t state; @@ -169,6 +174,10 @@ typedef struct client_s { sharedEntity_t *gentity; // SV_GentityNum(clientnum) char name[MAX_NAME_LENGTH]; // extracted from userinfo, high bits masked + gameStateAck_t gamestateAck; + qboolean downloading; // set at "download", reset at gamestate retransmission + // int serverId; // last acknowledged serverId + // downloading char downloadName[MAX_QPATH]; // if not empty string, we are downloading fileHandle_t download; // file being downloaded @@ -226,9 +235,6 @@ typedef struct client_s { int altSwapWeapons; int altSwapSuspend; #endif -#ifdef STEF_MAP_RESTART_STATIC_SERVERID - int mapRestartNetchanSequence; -#endif #ifdef STEF_GAMESTATE_OVERFLOW_FIX int maxEntityBaseline; #endif @@ -351,7 +357,6 @@ void SV_UpdateConfigstrings( client_t *client ); void SV_SetUserinfo( int index, const char *val ); void SV_GetUserinfo( int index, char *buffer, int bufferSize ); -void SV_ChangeMaxClients( void ); void SV_SpawnServer( const char *mapname, qboolean killBots ); @@ -363,11 +368,12 @@ void SV_GetChallenge( const netadr_t *from ); void SV_InitChallenger( void ); void SV_DirectConnect( const netadr_t *from ); +void SV_PrintClientStateChange( const client_t *cl, clientState_t newState ); void SV_ExecuteClientMessage( client_t *cl, msg_t *msg ); void SV_UserinfoChanged( client_t *cl, qboolean updateUserinfo, qboolean runFilter ); -void SV_ClientEnterWorld( client_t *client, usercmd_t *cmd ); +void SV_ClientEnterWorld( client_t *client ); void SV_FreeClient( client_t *client ); void SV_DropClient( client_t *drop, const char *reason ); diff --git a/code/server/sv_bot.c b/code/server/sv_bot.c index b21f2fbe..c2840aa2 100644 --- a/code/server/sv_bot.c +++ b/code/server/sv_bot.c @@ -49,13 +49,13 @@ int SV_BotAllocateClient( void ) { client_t *cl; // find a client slot - for ( i = 0, cl = svs.clients; i < sv_maxclients->integer; i++, cl++ ) { + for ( i = 0, cl = svs.clients; i < sv.maxclients; i++, cl++ ) { if ( cl->state == CS_FREE ) { break; } } - if ( i == sv_maxclients->integer ) { + if ( i == sv.maxclients ) { return -1; } @@ -87,7 +87,7 @@ SV_BotFreeClient void SV_BotFreeClient( int clientNum ) { client_t *cl; - if ( (unsigned) clientNum >= sv_maxclients->integer ) { + if ( (unsigned) clientNum >= sv.maxclients ) { Com_Error( ERR_DROP, "SV_BotFreeClient: bad clientNum: %i", clientNum ); } @@ -452,7 +452,7 @@ SV_BotClientCommand ================== */ static void BotClientCommand( int client, const char *command ) { - if ( (unsigned) client < sv_maxclients->integer ) { + if ( (unsigned) client < sv.maxclients ) { SV_ExecuteClientCommand( &svs.clients[client], command ); } } @@ -604,7 +604,7 @@ SV_BotGetConsoleMessage */ int SV_BotGetConsoleMessage( int client, char *buf, int size ) { - if ( (unsigned) client < sv_maxclients->integer ) { + if ( (unsigned) client < sv.maxclients ) { client_t* cl; int index; @@ -659,7 +659,7 @@ SV_BotGetSnapshotEntity ================== */ int SV_BotGetSnapshotEntity( int client, int sequence ) { - if ( (unsigned) client < sv_maxclients->integer ) { + if ( (unsigned) client < sv.maxclients ) { const client_t* cl = &svs.clients[client]; const clientSnapshot_t* frame = &cl->frames[cl->netchan.outgoingSequence & PACKET_MASK]; if ( (unsigned) sequence >= frame->num_entities ) { diff --git a/code/server/sv_ccmds.c b/code/server/sv_ccmds.c index d3f27be0..8bb3bf79 100644 --- a/code/server/sv_ccmds.c +++ b/code/server/sv_ccmds.c @@ -65,17 +65,17 @@ client_t *SV_GetPlayerByHandle( void ) { int plid = atoi(s); // Check for numeric playerid match - if(plid >= 0 && plid < sv_maxclients->integer) + if(plid >= 0 && plid < sv.maxclients) { cl = &svs.clients[plid]; - if(cl->state) + if (cl->state >= CS_CONNECTED) return cl; } } // check for a name match - for ( i=0, cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++ ) { + for ( i = 0, cl = svs.clients; i < sv.maxclients; i++, cl++ ) { if ( cl->state < CS_CONNECTED ) { continue; } @@ -128,7 +128,7 @@ static client_t *SV_GetPlayerByNum( void ) { } } idnum = atoi( s ); - if ( idnum < 0 || idnum >= sv_maxclients->integer ) { + if ( idnum < 0 || idnum >= sv.maxclients ) { Com_Printf( "Bad client slot: %i\n", idnum ); return NULL; } @@ -264,7 +264,7 @@ static void SV_MapRestart_f( void ) { int delay; // make sure we aren't restarting twice in the same frame - if ( com_frameTime == sv.serverId ) { + if ( com_frameTime == sv.restartedServerId ) { return; } @@ -314,27 +314,14 @@ static void SV_MapRestart_f( void ) { // map_restart has happened svs.snapFlagServerBit ^= SNAPFLAG_SERVERCOUNT; -#ifdef STEF_MAP_RESTART_STATIC_SERVERID - // Save the netchan sequence so we can drop incoming client movement commands from before the - // map restart was received by the client. I'm not sure this is really necessary, but it should - // be technically more consistent with the original behavior. - for ( i = 0; i < sv_maxclients->integer; ++i ) { - if ( svs.clients[i].state == CS_ACTIVE ) { - svs.clients[i].mapRestartNetchanSequence = svs.clients[i].netchan.outgoingSequence; - } - } -#else - // generate a new serverid - // TTimo - don't update restartedserverId there, otherwise we won't deal correctly with multiple map_restart - sv.serverId = com_frameTime; - Cvar_Set( "sv_serverid", va("%i", sv.serverId ) ); -#endif + // generate a new restartedServerid + sv.restartedServerId = com_frameTime; // if a map_restart occurs while a client is changing maps, we need // to give them the correct time so that when they finish loading // they don't violate the backwards time check in cl_cgame.c - for (i=0 ; iinteger ; i++) { - if (svs.clients[i].state == CS_PRIMED) { + for ( i = 0; i < sv.maxclients; i++ ) { + if ( svs.clients[i].state == CS_PRIMED ) { svs.clients[i].oldServerTime = sv.restartTime; } } @@ -361,7 +348,7 @@ static void SV_MapRestart_f( void ) { sv.restarting = qfalse; // connect and begin all the clients - for ( i = 0 ; i < sv_maxclients->integer ; i++ ) { + for ( i = 0; i < sv.maxclients; i++ ) { client = &svs.clients[i]; // send the new gamestate to all connected clients @@ -388,22 +375,25 @@ static void SV_MapRestart_f( void ) { continue; } - if ( client->state == CS_ACTIVE ) - SV_ClientEnterWorld( client, &client->lastUsercmd ); -#ifndef STEF_MAP_RESTART_NO_LOADING_SPAWN - else { - // If we don't reset client->lastUsercmd and are restarting during map load, - // the client will hang because we'll use the last Usercmd from the previous map, - // which is wrong obviously. - SV_ClientEnterWorld( client, NULL ); + if ( client->state == CS_ACTIVE ) { + SV_ClientEnterWorld( client ); } -#endif } // run another frame to allow things to look at all the players sv.time += 100; VM_Call( gvm, 1, GAME_RUN_FRAME, sv.time ); svs.time += 100; + + for ( i = 0; i < sv.maxclients; i++ ) { + client = &svs.clients[i]; + if ( client->state >= CS_PRIMED ) { + // accept usercmds starting from current server time only + // to emulate original behavior which dropped pre-restart commands via serverid check + Com_Memset( &client->lastUsercmd, 0x0, sizeof( client->lastUsercmd ) ); + client->lastUsercmd.serverTime = sv.time - 1; + } + } #ifdef STEF_LUA_SERVER Stef_Lua_SimpleEventCall( SV_LUA_EVENT_POST_MAP_RESTART ); #endif @@ -434,24 +424,24 @@ static void SV_Kick_f( void ) { cl = SV_GetPlayerByHandle(); if ( !cl ) { - if ( !Q_stricmp(Cmd_Argv(1), "all") ) { - for ( i=0, cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++ ) { - if ( !cl->state ) { + if ( !Q_stricmp( Cmd_Argv( 1 ), "all" ) ) { + for ( i = 0, cl = svs.clients; i < sv.maxclients; i++, cl++ ) { + if ( cl->state < CS_CONNECTED ) { continue; } - if( cl->netchan.remoteAddress.type == NA_LOOPBACK ) { + if ( cl->netchan.remoteAddress.type == NA_LOOPBACK ) { continue; } SV_DropClient( cl, "was kicked" ); cl->lastPacketTime = svs.time; // in case there is a funny zombie } } - else if ( !Q_stricmp(Cmd_Argv(1), "allbots") ) { - for ( i=0, cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++ ) { - if ( !cl->state ) { + else if ( !Q_stricmp( Cmd_Argv( 1 ), "allbots" ) ) { + for ( i = 0, cl = svs.clients; i < sv.maxclients; i++, cl++ ) { + if ( cl->state < CS_CONNECTED ) { continue; } - if( cl->netchan.remoteAddress.type != NA_BOT ) { + if ( cl->netchan.remoteAddress.type != NA_BOT ) { continue; } SV_DropClient( cl, "was kicked" ); @@ -460,8 +450,8 @@ static void SV_Kick_f( void ) { } return; } - if( cl->netchan.remoteAddress.type == NA_LOOPBACK ) { - Com_Printf("Cannot kick host player\n"); + if ( cl->netchan.remoteAddress.type == NA_LOOPBACK ) { + Com_Printf( "Cannot kick host player\n" ); return; } @@ -486,7 +476,7 @@ static void SV_KickBots_f( void ) { return; } - for( i = 0, cl = svs.clients; i < sv_maxclients->integer; i++, cl++ ) { + for( i = 0, cl = svs.clients; i < sv.maxclients; i++, cl++ ) { if ( cl->state < CS_CONNECTED ) { continue; } @@ -516,7 +506,7 @@ static void SV_KickAll_f( void ) { return; } - for( i = 0, cl = svs.clients; i < sv_maxclients->integer; i++, cl++ ) { + for( i = 0, cl = svs.clients; i < sv.maxclients; i++, cl++ ) { if ( cl->state < CS_CONNECTED ) { continue; } @@ -1255,7 +1245,7 @@ static void SV_Status_f( void ) { Com_Memset( al, 0, sizeof( al ) ); // first pass: save and determine max.lengths of name/address fields - for ( i = 0, cl = svs.clients ; i < sv_maxclients->integer ; i++, cl++ ) + for ( i = 0, cl = svs.clients; i < sv.maxclients; i++, cl++ ) { if ( cl->state == CS_FREE ) continue; @@ -1299,7 +1289,7 @@ static void SV_Status_f( void ) { Com_Printf( " -----\n" ); #endif - for ( i = 0, cl = svs.clients ; i < sv_maxclients->integer ; i++, cl++ ) + for ( i = 0, cl = svs.clients; i < sv.maxclients; i++, cl++ ) { if ( cl->state == CS_FREE ) continue; @@ -1560,7 +1550,7 @@ SV_CompleteMapName */ static void SV_CompleteMapName( const char *args, int argNum ) { if ( argNum == 2 ) { - if ( sv_pure->integer ) { + if ( sv.pure != 0 ) { Field_CompleteFilename( "maps", "bsp", qtrue, FS_MATCH_PK3s | FS_MATCH_STICK ); } else { Field_CompleteFilename( "maps", "bsp", qtrue, FS_MATCH_ANY | FS_MATCH_STICK ); diff --git a/code/server/sv_client.c b/code/server/sv_client.c index 76a7be7e..e0cb85e7 100644 --- a/code/server/sv_client.c +++ b/code/server/sv_client.c @@ -380,7 +380,7 @@ static int seqs[ MAX_CLIENTS ]; static void SV_SaveSequences( void ) { int i; - for ( i = 0; i < sv_maxclients->integer; i++ ) { + for ( i = 0; i < sv.maxclients; i++ ) { seqs[i] = svs.clients[i].reliableSequence; } } @@ -390,7 +390,7 @@ static void SV_InjectLocation( const char *tld, const char *country ) { const char *cmd; char *str; int i, n; - for ( i = 0; i < sv_maxclients->integer; i++ ) { + for ( i = 0; i < sv.maxclients; i++ ) { if ( seqs[i] != svs.clients[i].reliableSequence ) { for ( n = seqs[i]; n != svs.clients[i].reliableSequence + 1; n++ ) { cmd = svs.clients[i].reliableCommands[n & (MAX_RELIABLE_COMMANDS-1)]; @@ -424,6 +424,39 @@ static const char *SV_FindCountry( const char *tld ) { } +static const char *SV_GetStateName( clientState_t state ) { + switch ( state ) { + case CS_FREE: return "CS_FREE"; + case CS_ZOMBIE: return "CS_ZOMBIE"; + case CS_CONNECTED: return "CS_CONNECTED"; + case CS_PRIMED: return "CS_PRIMED"; + case CS_ACTIVE: return "CS_ACTIVE"; + default: return "CS_UNKNOWN"; + } +} + + +void SV_PrintClientStateChange( const client_t *cl, clientState_t newState ) { + + if ( cl->state == newState ) { + return; + } + +#ifndef _DEBUG + if ( com_developer->integer == 0 ) { + return; + } +#endif // !_DEBUG + + if ( cl->name[0] != '\0' ) { + Com_Printf( "Going from %s to %s for %s\n", SV_GetStateName( cl->state ), SV_GetStateName( newState ), cl->name ); + } else { + Com_Printf( "Going from %s to %s for client %d\n", SV_GetStateName( cl->state ), SV_GetStateName( newState ), (int)(cl - svs.clients) ); + } + +} + + /* ================== SV_DirectConnect @@ -470,7 +503,7 @@ void SV_DirectConnect( const netadr_t *from ) { } // check for concurrent connections - for ( i = 0, n = 0; i < sv_maxclients->integer; i++ ) { + for ( i = 0, n = 0; i < sv.maxclients; i++ ) { const netadr_t *addr = &svs.clients[ i ].netchan.remoteAddress; if ( addr->type != NA_BOT && NET_CompareBaseAdr( addr, from ) ) { if ( svs.clients[ i ].state >= CS_CONNECTED && !svs.clients[ i ].justConnected ) { @@ -627,7 +660,7 @@ void SV_DirectConnect( const netadr_t *from ) { // quick reject newcl = NULL; - for ( i = 0, cl = svs.clients ; i < sv_maxclients->integer ; i++, cl++ ) { + for ( i = 0, cl = svs.clients; i < sv.maxclients; i++, cl++ ) { if ( NET_CompareAdr( from, &cl->netchan.remoteAddress ) ) { int elapsed = svs.time - cl->lastConnectTime; if ( elapsed < ( sv_reconnectlimit->integer * 1000 ) && elapsed >= 0 ) { @@ -648,7 +681,7 @@ void SV_DirectConnect( const netadr_t *from ) { } // if there is already a slot for this ip, reuse it - for ( i = 0, cl = svs.clients ; i < sv_maxclients->integer ; i++, cl++ ) { + for ( i = 0, cl = svs.clients; i < sv.maxclients; i++, cl++ ) { if ( cl->state == CS_FREE ) { continue; } @@ -657,12 +690,15 @@ void SV_DirectConnect( const netadr_t *from ) { Com_Printf( "%s:reconnect\n", NET_AdrToString( from ) ); newcl = cl; - // this doesn't work because it nukes the players userinfo + if ( newcl->state >= CS_CONNECTED ) { + // call QVM disconnect function before calling connect again + // fixes issues such as disappearing CTF flags in unpatched mods + VM_Call( gvm, 1, GAME_CLIENT_DISCONNECT, newcl - svs.clients ); + + // don't leak memory or file handles due to e.g. downloads in progress + SV_FreeClient( newcl ); + } -// // disconnect the client from the game first so any flags the -// // player might have are dropped -// VM_Call( gvm, GAME_CLIENT_DISCONNECT, 1, newcl - svs.clients ); - // goto gotnewcl; } } @@ -694,7 +730,7 @@ void SV_DirectConnect( const netadr_t *from ) { // select least used free slot n = 0; newcl = NULL; - for ( i = startIndex; i < sv_maxclients->integer ; i++ ) { + for ( i = startIndex; i < sv.maxclients; i++ ) { cl = &svs.clients[i]; if ( cl->state == CS_FREE && ( newcl == NULL || svs.time - cl->lastDisconnectTime > n ) ) { n = svs.time - cl->lastDisconnectTime; @@ -705,16 +741,16 @@ void SV_DirectConnect( const netadr_t *from ) { if ( !newcl ) { if ( NET_IsLocalAddress( from ) ) { count = 0; - for ( i = startIndex; i < sv_maxclients->integer ; i++ ) { + for ( i = startIndex; i < sv.maxclients; i++ ) { cl = &svs.clients[i]; if (cl->netchan.remoteAddress.type == NA_BOT) { count++; } } // if they're all bots - if (count >= sv_maxclients->integer - startIndex) { - SV_DropClient(&svs.clients[sv_maxclients->integer - 1], "only bots on server"); - newcl = &svs.clients[sv_maxclients->integer - 1]; + if (count >= sv.maxclients - startIndex) { + SV_DropClient(&svs.clients[sv.maxclients - 1], "only bots on server"); + newcl = &svs.clients[sv.maxclients - 1]; } else { Com_Error( ERR_DROP, "server is full on local connect" ); @@ -790,7 +826,7 @@ void SV_DirectConnect( const netadr_t *from ) { NET_OutOfBandPrint( NS_SERVER, from, "connectResponse %d", challenge ); } - Com_DPrintf( "Going from CS_FREE to CS_CONNECTED for %s\n", newcl->name ); + SV_PrintClientStateChange( newcl, CS_CONNECTED ); newcl->state = CS_CONNECTED; newcl->lastSnapshotTime = svs.time - 9999; // generate a snapshot immediately @@ -813,12 +849,12 @@ void SV_DirectConnect( const netadr_t *from ) { // if this was the first client on the server, or the last client // the server can hold, send a heartbeat to the master. count = 0; - for (i=0,cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) { + for ( i = 0, cl = svs.clients ; i < sv.maxclients; i++, cl++) { if ( svs.clients[i].state >= CS_CONNECTED ) { count++; } } - if ( count == 1 || count == sv_maxclients->integer ) { + if ( count == 1 || count == sv.maxclients ) { SV_Heartbeat_f(); } } @@ -848,7 +884,7 @@ or crashing -- SV_FinalMessage() will handle that ===================== */ void SV_DropClient( client_t *drop, const char *reason ) { - char name[ MAX_NAME_LENGTH ]; + char name[ sizeof( drop->name ) ]; qboolean isBot; int i; @@ -897,7 +933,8 @@ void SV_DropClient( client_t *drop, const char *reason ) { // bots shouldn't go zombie, as there's no real net connection. drop->state = CS_FREE; } else { - Com_DPrintf( "Going to CS_ZOMBIE for %s\n", name ); + Q_strncpyz( drop->name, name, sizeof( name ) ); + SV_PrintClientStateChange( drop, CS_ZOMBIE ); drop->state = CS_ZOMBIE; // become free in a few seconds } @@ -912,12 +949,12 @@ void SV_DropClient( client_t *drop, const char *reason ) { // if this was the last client on the server, send a heartbeat // to the master so it is known the server is empty // send a heartbeat now so the master will get up to date info - for ( i = 0 ; i < sv_maxclients->integer ; i++ ) { + for ( i = 0; i < sv.maxclients; i++ ) { if ( svs.clients[i].state >= CS_CONNECTED ) { break; } } - if ( i == sv_maxclients->integer ) { + if ( i == sv.maxclients ) { SV_Heartbeat_f(); } } @@ -1022,14 +1059,16 @@ static void SV_SendClientGameState( client_t *client ) { const svEntity_t *svEnt; msg_t msg; byte msgBuffer[ MAX_MSGLEN_BUF ]; + qboolean csUpdated; Com_DPrintf( "SV_SendClientGameState() for %s\n", client->name ); - if ( client->state != CS_PRIMED ) { - Com_DPrintf( "Going from CS_CONNECTED to CS_PRIMED for %s\n", client->name ); - } + SV_PrintClientStateChange( client, CS_PRIMED ); + client->state = CS_PRIMED; + client->downloading = qfalse; + #ifndef NEW_FILESYSTEM client->pureAuthentic = qfalse; client->gotCP = qfalse; @@ -1043,6 +1082,10 @@ static void SV_SendClientGameState( client_t *client ) { // gamestate message was not just sent, forcing a retransmit client->gamestateMessageNum = client->netchan.outgoingSequence; + // accept usercmds starting from current server time only + Com_Memset( &client->lastUsercmd, 0x0, sizeof( client->lastUsercmd ) ); + client->lastUsercmd.serverTime = sv.time - 1; + #ifdef ELITEFORCE if( client->compat ) MSG_InitOOB(&msg, msgBuffer, MAX_MSGLEN ); @@ -1072,14 +1115,39 @@ static void SV_SendClientGameState( client_t *client ) { MSG_WriteLong( &msg, client->reliableSequence ); // write the configstrings + csUpdated = qfalse; #ifdef STEF_LUA_SUPPORT if ( !SV_Lua_GamestateConfigstrings( client - svs.clients, &msg ) ) #endif for ( start = 0 ; start < MAX_CONFIGSTRINGS ; start++ ) { - if (sv.configstrings[start][0]) { + if ( *sv.configstrings[ start ] != '\0' ) { MSG_WriteByte( &msg, svc_configstring ); MSG_WriteShort( &msg, start ); - MSG_WriteBigString( &msg, sv.configstrings[start] ); + if ( start == CS_SYSTEMINFO && sv.pure != sv_pure->integer ) { + // make sure we send latched sv.pure, not forced cvar value + char systemInfo[BIG_INFO_STRING]; + Q_strncpyz( systemInfo, sv.configstrings[ start ], sizeof( systemInfo ) ); + Info_SetValueForKey_s( systemInfo, sizeof( systemInfo ), "sv_pure", va( "%i", sv.pure ) ); + MSG_WriteBigString( &msg, systemInfo ); + } else { + MSG_WriteBigString( &msg, sv.configstrings[start] ); + } + } + if ( client->csUpdated[start] ) { + csUpdated = qtrue; + } + client->csUpdated[start] = qfalse; + } + + if ( client->gamestateAck == GSA_INIT ) { + // inital submission, accept any messageAcknowledge with matching serverId + client->gamestateAck = GSA_SENT_ONCE; + } else { + if ( client->gamestateAck == GSA_SENT_ONCE && !csUpdated ) { + // if no configstrings being updated since last submission then assume that we're (re)sending identical gamestate + } else { + // expect exact messageAcknowledge + client->gamestateAck = GSA_SENT_MANY; } } @@ -1145,16 +1213,29 @@ static void SV_SendClientGameState( client_t *client ) { SV_ClientEnterWorld ================== */ -void SV_ClientEnterWorld( client_t *client, usercmd_t *cmd ) { - int clientNum; +void SV_ClientEnterWorld( client_t *client ) { sharedEntity_t *ent; + qboolean isBot; + int clientNum; + + isBot = client->netchan.remoteAddress.type == NA_BOT; + + if ( !isBot ) { + SV_PrintClientStateChange( client, CS_ACTIVE ); + } else { + // client->serverId = sv.serverId; + } - Com_DPrintf( "Going from CS_PRIMED to CS_ACTIVE for %s\n", client->name ); client->state = CS_ACTIVE; + client->gamestateAck = GSA_ACKED; + + client->oldServerTime = 0; // resend all configstrings using the cs commands since these are // no longer sent when the client is CS_PRIMED - SV_UpdateConfigstrings( client ); + if ( !isBot ) { + SV_UpdateConfigstrings( client ); + } // set up the entity for the client clientNum = client - svs.clients; @@ -1165,13 +1246,8 @@ void SV_ClientEnterWorld( client_t *client, usercmd_t *cmd ) { client->deltaMessage = client->netchan.outgoingSequence - (PACKET_BACKUP + 1); // force delta reset client->lastSnapshotTime = svs.time - 9999; // generate a snapshot immediately - if(cmd) - memcpy(&client->lastUsercmd, cmd, sizeof(client->lastUsercmd)); - else - memset(&client->lastUsercmd, '\0', sizeof(client->lastUsercmd)); - // call the game begin function - VM_Call( gvm, 1, GAME_CLIENT_BEGIN, client - svs.clients ); + VM_Call( gvm, 1, GAME_CLIENT_BEGIN, clientNum ); } @@ -1238,7 +1314,7 @@ static void SV_DoneDownload_f( client_t *cl ) { if ( cl->state == CS_ACTIVE ) return; - Com_DPrintf( "clientDownload: %s Done\n", cl->name); + Com_DPrintf( "clientDownload: %s Done\n", cl->name ); // resend the game state to update any clients that entered during the download SV_SendClientGameState( cl ); @@ -1291,6 +1367,16 @@ static void SV_BeginDownload_f( client_t *cl ) { // cl->downloadName is non-zero now, SV_WriteDownloadToClient will see this and open // the file itself Q_strncpyz( cl->downloadName, Cmd_Argv(1), sizeof(cl->downloadName) ); + + SV_PrintClientStateChange( cl, CS_CONNECTED ); + cl->state = CS_CONNECTED; + cl->gentity = NULL; + + cl->downloading = qtrue; + + if ( cl->gamestateAck == GSA_ACKED ) { + cl->gamestateAck = GSA_SENT_ONCE; + } } @@ -1469,7 +1555,7 @@ static int SV_WriteDownloadToClient( client_t *cl ) (sv_allowDownload->integer & DLF_NO_UDP) ) { Com_Printf("clientDownload: %d : \"%s\" download disabled\n", (int) (cl - svs.clients), cl->downloadName); - if (sv_pure->integer) { + if ( sv.pure != 0 ) { Com_sprintf(errorMessage, sizeof(errorMessage), "Could not download \"%s\" because autodownloading is disabled on the server.\n\n" "You will need to get this file elsewhere before you " "can connect to this pure server.\n", cl->downloadName); @@ -1517,13 +1603,9 @@ static int SV_WriteDownloadToClient( client_t *cl ) Com_Printf( "clientDownload: %d : beginning \"%s\"\n", (int) (cl - svs.clients), cl->downloadName ); - // Init cl->downloadCurrentBlock = cl->downloadClientBlock = cl->downloadXmitBlock = 0; cl->downloadCount = 0; cl->downloadEOF = qfalse; -#ifdef STEF_DOWNLOAD_CONNECTION_STATE_FIX - cl->state = CS_CONNECTED; -#endif } // Perform any reads that we need to @@ -1659,7 +1741,7 @@ int SV_SendQueuedMessages( void ) int i, retval = -1, nextFragT; client_t *cl; - for( i = 0; i < sv_maxclients->integer; i++ ) + for( i = 0; i < sv.maxclients; i++ ) { cl = &svs.clients[i]; @@ -1691,7 +1773,7 @@ int SV_SendDownloadMessages( void ) int i, numDLs = 0; client_t *cl; - for( i = 0; i < sv_maxclients->integer; i++ ) + for( i = 0; i < sv.maxclients; i++ ) { cl = &svs.clients[ i ]; if ( cl->state >= CS_CONNECTED && *cl->downloadName ) @@ -1760,7 +1842,7 @@ static void SV_VerifyPaks_f( client_t *cl ) { // certain pk3 files, namely we want the client to have loaded the // ui and cgame that we think should be loaded based on the pure setting // - if ( sv_pure->integer != 0 ) { + if ( sv.pure != 0 ) { nChkSum1 = nChkSum2 = 0; @@ -1786,11 +1868,8 @@ static void SV_VerifyPaks_f( client_t *cl ) { } else { - // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=475 - // we may get incoming cp sequences from a previous checksumFeed, which we need to ignore - // since serverId is a frame count, it always goes up - if ( atoi( pArg ) - sv.checksumFeedServerId < 0 ) - { + // we may get incoming cp sequences from a previous serverId, which we need to ignore + if ( atoi( pArg ) != sv.serverId /* || !cl->gamestateAcked */ ) { Com_DPrintf( "ignoring outdated cp command from client %s\n", cl->name ); return; } @@ -2087,7 +2166,7 @@ void SV_PrintLocations_f( client_t *client ) { max_ctrylength = 7; // strlen( "country" ) // first pass: save and determine max.lengths of name/address fields - for ( i = 0, cl = svs.clients ; i < sv_maxclients->integer ; i++, cl++ ) + for ( i = 0, cl = svs.clients; i < sv.maxclients; i++, cl++ ) { if ( cl->state == CS_FREE ) continue; @@ -2111,7 +2190,7 @@ void SV_PrintLocations_f( client_t *client ) { Com_sprintf( line, sizeof( line ), "-- %s -- %s\n", filln, fillc ); s = Q_stradd( s, line ); - for ( i = 0, cl = svs.clients ; i < sv_maxclients->integer ; i++, cl++ ) + for ( i = 0, cl = svs.clients; i < sv.maxclients; i++, cl++ ) { if ( cl->state == CS_FREE ) continue; @@ -2186,6 +2265,7 @@ Also called by bot code qboolean SV_ExecuteClientCommand( client_t *cl, const char *s ) { const ucmd_t *ucmd; qboolean bFloodProtect; + qboolean isBot; Cmd_TokenizeString( s ); @@ -2197,7 +2277,8 @@ qboolean SV_ExecuteClientCommand( client_t *cl, const char *s ) { // We don't do this when the client hasn't been active yet since it's // normal to spam a lot of commands when downloading - bFloodProtect = cl->netchan.remoteAddress.type != NA_BOT && cl->state >= CS_ACTIVE; + isBot = cl->netchan.remoteAddress.type == NA_BOT ? qtrue: qfalse; + bFloodProtect = !isBot && cl->state >= CS_ACTIVE; #ifdef STEF_LUA_SERVER if ( sv_lua_running_client_command ) { @@ -2228,6 +2309,11 @@ qboolean SV_ExecuteClientCommand( client_t *cl, const char *s ) { } } + // if ( !isBot && ( !cl->gamestateAcked || sv.serverId != cl->serverId ) ) { + // Com_Printf( "%s: ignoring pre map_restart / outdated client command '%s'\n", cl->name, s ); + // return qtrue; + // } + #ifdef STEF_SERVER_ALT_SWAP_SUPPORT if ( sv_altSwapSupport->integer && !ucmd->name && !Q_stricmp( Cmd_Argv( 0 ), "setAltSwap" ) ) { cl->altSwapWeapons = atoi( Cmd_Argv( 1 ) ); @@ -2423,30 +2509,22 @@ static void SV_UserMove( client_t *cl, msg_t *msg, qboolean delta ) { // this gamestate, put the client into the world if ( cl->state == CS_PRIMED ) { #ifndef NEW_FILESYSTEM - if ( sv_pure->integer != 0 && !cl->gotCP ) { + if ( sv.pure != 0 && !cl->gotCP ) { // we didn't get a cp yet, don't assume anything and just send the gamestate all over again - if ( !SVC_RateLimit( &cl->gamestate_rate, 4, 1000 ) ) { + if ( !SVC_RateLimit( &cl->gamestate_rate, 2, 1000 ) ) { Com_DPrintf( "%s: didn't get cp command, resending gamestate\n", cl->name ); SV_SendClientGameState( cl ); } return; } #endif - SV_ClientEnterWorld( cl, &cmds[0] ); + SV_ClientEnterWorld( cl ); // the moves can be processed normally } #ifndef NEW_FILESYSTEM // a bad cp command was sent, drop the client - if ( sv_pure->integer != 0 && !cl->pureAuthentic ) { -#ifndef DEDICATED - if ( !cl->gotCP && cl->state == CS_ACTIVE && cl->netchan.remoteAddress.type == NA_LOOPBACK ) { - // fix host player being dropped with ZTM' FlexibleHud at level end in TA SP - // FIXME: exact reason how cl->pureAuthentic being reset is unclear, should be investigated later - Com_DPrintf( "%s: didn't get cp command, resending gamestate\n", cl->name ); - SV_SendClientGameState( cl ); - } else -#endif + if ( sv.pure != 0 && !cl->pureAuthentic ) { SV_DropClient( cl, "Cannot validate pure client!" ); return; } @@ -2547,6 +2625,8 @@ void SV_ExecuteClientMessage( client_t *cl, msg_t *msg ) { cl->justConnected = qfalse; + // cl->serverId = serverId; + // if this is a usercmd from a previous gamestate, // ignore it or retransmit the current gamestate // @@ -2555,41 +2635,32 @@ void SV_ExecuteClientMessage( client_t *cl, msg_t *msg ) { // the gamestate changes. After the download is finished, we'll // notice and send it a new game state // - // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=536 - // don't drop as long as previous command was a nextdl, after a dl is done, downloadName is set back to "" - // but we still need to read the next message to move to next download or send gamestate - // I don't like this hack though, it must have been working fine at some point, suspecting the fix is somewhere else - if ( serverId != sv.serverId && !*cl->downloadName && !strstr(cl->lastClientCommandString, "nextdl") ) { - // TTimo - use a comparison here to catch multiple map_restart - if ( serverId - sv.restartedServerId >= 0 && serverId - sv.serverId < 0 ) { - // they just haven't caught the \map_restart yet - Com_DPrintf( "%s: ignoring pre map_restart / outdated client message\n", cl->name ); + if ( cl->state == CS_CONNECTED ) { + if ( !cl->downloading ) { + // send initial gamestate, client may not acknowledge it in next command but start downloading after SV_ClientCommand() + if ( !SVC_RateLimit( &cl->gamestate_rate, 2, 1000 ) ) { + SV_SendClientGameState( cl ); + } return; } - // if we can tell that the client has dropped the last gamestate we sent them, resend it - if ( cl->state != CS_ACTIVE && cl->messageAcknowledge - cl->gamestateMessageNum > 0 ) { - if ( !SVC_RateLimit( &cl->gamestate_rate, 4, 1000 ) ) { - if ( cl->gentity ) - Com_DPrintf( "%s: dropped gamestate, resending\n", cl->name ); -#ifdef STEF_LOGGING_DEFS - Logging_Printf( LP_INFO, "SERVERSTATE", - "Sending gamestate for client %i due to primary trigger: state(%i) msg_serverId(%i) sv_serverId(%i) " - "restarted_serverId(%i) messageAcknowledge(%i) gamestateMessageNum(%i)\n", - (int)( cl - svs.clients ), cl->state, serverId, sv.serverId, sv.restartedServerId, cl->messageAcknowledge, - cl->gamestateMessageNum ); -#endif - SV_SendClientGameState( cl ); + } else if ( cl->gamestateAck != GSA_ACKED ) { + // early check for gamestate acknowledge + if ( serverId == sv.serverId ) { + const int delta = cl->messageAcknowledge - cl->gamestateMessageNum; + if ( delta == 0 || ( delta > 0 && cl->gamestateAck == GSA_SENT_ONCE ) ) { + cl->gamestateAck = GSA_ACKED; + // this client has acknowledged the new gamestate so it's + // safe to start sending it the real time again + Com_DPrintf( "%s acknowledged gamestate with delta %i\n", cl->name, delta ); + cl->oldServerTime = 0; } } - return; - } - - // this client has acknowledged the new gamestate so it's - // safe to start sending it the real time again - if( cl->oldServerTime && serverId == sv.serverId ){ - Com_DPrintf( "%s acknowledged gamestate\n", cl->name ); - cl->oldServerTime = 0; } + // else if ( cl->state == CS_PRIMED ) { + // in case of download intention client replies with (messageAcknowledge - gamestateMessageNum) >= 0 and (serverId == sv.serverId), sv.serverId can drift away later + // in case of lost gamestate client replies with (messageAcknowledge - gamestateMessageNum) > 0 and (serverId == sv.serverId) + // in case of disconnect/etc. client replies with any serverId + //} // read optional clientCommand strings do { @@ -2610,12 +2681,16 @@ void SV_ExecuteClientMessage( client_t *cl, msg_t *msg ) { } } while ( 1 ); -#ifdef STEF_MAP_RESTART_STATIC_SERVERID - if ( cl->mapRestartNetchanSequence && cl->messageAcknowledge < cl->mapRestartNetchanSequence ) { - // Skip movement commands from before the map restart + if ( cl->gamestateAck != GSA_ACKED ) { + // late check for gamestate resend + if ( cl->state == CS_PRIMED && cl->messageAcknowledge - cl->gamestateMessageNum > 0 ) { + Com_DPrintf( "%s: dropped gamestate, resending\n", cl->name ); + if ( !SVC_RateLimit( &cl->gamestate_rate, 2, 1000 ) ) { + SV_SendClientGameState( cl ); + } + } return; } -#endif // read the usercmd_t if ( c == clc_move ) { diff --git a/code/server/sv_game.c b/code/server/sv_game.c index bcc38707..ad448c5e 100644 --- a/code/server/sv_game.c +++ b/code/server/sv_game.c @@ -83,10 +83,10 @@ static void SV_GameSendServerCommand( int clientNum, const char *text ) { if ( clientNum == -1 ) { SV_SendServerCommand( NULL, "%s", text ); } else { - if ( clientNum < 0 || clientNum >= sv_maxclients->integer ) { + if ( clientNum < 0 || clientNum >= sv.maxclients ) { return; } - SV_SendServerCommand( svs.clients + clientNum, "%s", text ); + SV_SendServerCommand( svs.clients + clientNum, "%s", text ); } } @@ -99,10 +99,10 @@ Disconnects the client with a message =============== */ static void SV_GameDropClient( int clientNum, const char *reason ) { - if ( clientNum < 0 || clientNum >= sv_maxclients->integer ) { + if ( clientNum < 0 || clientNum >= sv.maxclients ) { return; } - SV_DropClient( svs.clients + clientNum, reason ); + SV_DropClient( svs.clients + clientNum, reason ); } @@ -292,7 +292,7 @@ SV_GetUsercmd =============== */ static void SV_GetUsercmd( int clientNum, usercmd_t *cmd ) { - if ( (unsigned) clientNum < sv_maxclients->integer ) { + if ( (unsigned) clientNum < sv.maxclients ) { *cmd = svs.clients[ clientNum ].lastUsercmd; } else { Com_Error( ERR_DROP, "%s(): bad clientNum: %i", __func__, clientNum ); @@ -537,7 +537,7 @@ static intptr_t SV_GameSystemCalls( intptr_t *args ) { return 0; case G_GET_ENTITY_TOKEN: { - const char *s = COM_Parse( &sv.entityParsePoint ); + char *s = (char*)COM_Parse( &sv.entityParsePoint ); VM_CHECKBOUNDS( gvm, args[1], args[2] ); //Q_strncpyz( VMA(1), s, args[2] ); // we can't use our optimized Q_strncpyz() function @@ -616,7 +616,7 @@ static intptr_t SV_GameSystemCalls( intptr_t *args ) { case BOTLIB_USER_COMMAND: { unsigned clientNum = args[1]; - if ( clientNum < sv_maxclients->integer ) + if ( clientNum < sv.maxclients ) { SV_ClientThink( &svs.clients[ clientNum ], VMA(2) ); } @@ -1122,7 +1122,7 @@ static void SV_InitGameVM( qboolean restart ) { // a previous level // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=522 // now done before GAME_INIT call - for ( i = 0 ; i < sv_maxclients->integer ; i++ ) { + for ( i = 0; i < sv.maxclients; i++ ) { svs.clients[i].gentity = NULL; } diff --git a/code/server/sv_init.c b/code/server/sv_init.c index 43713d95..1df15cfd 100644 --- a/code/server/sv_init.c +++ b/code/server/sv_init.c @@ -148,10 +148,12 @@ void SV_SetConfigstring (int index, const char *val) { if ( sv.state == SS_GAME || sv.restarting ) { // send the data to all relevant clients - for (i = 0, client = svs.clients; i < sv_maxclients->integer ; i++, client++) { + for (i = 0, client = svs.clients; i < sv.maxclients; i++, client++) { if ( client->state < CS_ACTIVE ) { - if ( client->state == CS_PRIMED ) - client->csUpdated[ index ] = qtrue; + if ( client->state == CS_PRIMED || client->state == CS_CONNECTED ) { + // track CS_CONNECTED clients as well to optimize gamestate acknowledge after downloading/retransmission + client->csUpdated[index] = qtrue; + } continue; } // do not always send server info to all clients @@ -196,8 +198,8 @@ SV_SetUserinfo =============== */ void SV_SetUserinfo( int index, const char *val ) { - if ( index < 0 || index >= sv_maxclients->integer ) { - Com_Error (ERR_DROP, "SV_SetUserinfo: bad index %i", index); + if ( index < 0 || index >= sv.maxclients ) { + Com_Error( ERR_DROP, "%s: bad index %i", __func__, index ); } if ( !val ) { @@ -218,10 +220,10 @@ SV_GetUserinfo */ void SV_GetUserinfo( int index, char *buffer, int bufferSize ) { if ( bufferSize < 1 ) { - Com_Error( ERR_DROP, "SV_GetUserinfo: bufferSize == %i", bufferSize ); + Com_Error( ERR_DROP, "%s: bufferSize == %i", __func__, bufferSize ); } - if ( index < 0 || index >= sv_maxclients->integer ) { - Com_Error (ERR_DROP, "SV_GetUserinfo: bad index %i", index); + if ( index < 0 || index >= sv.maxclients ) { + Com_Error( ERR_DROP, "%s: bad index %i", __func__, index ); } Q_strncpyz( buffer, svs.clients[ index ].userinfo, bufferSize ); #ifdef STEF_BOT_PASSWORD_FIX @@ -275,15 +277,19 @@ static void SV_CreateBaseline( void ) { SV_BoundMaxClients =============== */ -static void SV_BoundMaxClients( int minimum ) { +static int SV_BoundMaxClients( int minimum ) { // get the current maxclients value Cvar_Get( "sv_maxclients", "8", 0 ); - sv_maxclients->modified = qfalse; - if ( sv_maxclients->integer < minimum ) { - Cvar_Set( "sv_maxclients", va("%i", minimum) ); + Cvar_SetIntegerValue( "sv_maxclients", minimum ); + sv_maxclients->modified = qfalse; + return minimum; } + + sv_maxclients->modified = qfalse; + + return sv_maxclients->integer; } @@ -299,6 +305,20 @@ static void SV_SetSnapshotParams( void ) } +/* +=============== +SV_AllocClients +=============== +*/ +static void SV_AllocClients( int count ) +{ + svs.clients = Z_TagMalloc( count * sizeof( client_t ), TAG_CLIENTS ); + Com_Memset( svs.clients, 0x0, count * sizeof( client_t ) ); + sv.maxclients = count; + SV_SetSnapshotParams(); +} + + /* =============== SV_Startup @@ -313,11 +333,9 @@ static void SV_Startup( void ) { if ( svs.initialized ) { Com_Error( ERR_FATAL, "SV_Startup: svs.initialized" ); } - SV_BoundMaxClients( 1 ); - svs.clients = Z_TagMalloc( sv_maxclients->integer * sizeof( client_t ), TAG_CLIENTS ); - Com_Memset( svs.clients, 0, sv_maxclients->integer * sizeof( client_t ) ); - SV_SetSnapshotParams(); + SV_AllocClients( sv_maxclients->integer ); + svs.initialized = qtrue; // Don't respect sv_killserver unless a server is actually running @@ -339,37 +357,37 @@ static void SV_Startup( void ) { SV_ChangeMaxClients ================== */ -void SV_ChangeMaxClients( void ) { - int oldMaxClients; - int i; - client_t *oldClients; +static void SV_ChangeMaxClients( void ) { + client_t *oldClients; + int maxclients; int count; + int i; // get the highest client number in use count = 0; - for ( i = 0 ; i < sv_maxclients->integer ; i++ ) { + for ( i = 0; i < sv.maxclients; i++ ) { if ( svs.clients[i].state >= CS_CONNECTED ) { - if (i > count) + if ( i > count ) { count = i; + } } } count++; - oldMaxClients = sv_maxclients->integer; // never go below the highest client number in use - SV_BoundMaxClients( count ); + maxclients = SV_BoundMaxClients( count ); + // if still the same - if ( sv_maxclients->integer == oldMaxClients ) { + if ( maxclients == sv.maxclients ) { return; } oldClients = Hunk_AllocateTempMemory( count * sizeof(client_t) ); // copy the clients to hunk memory - for ( i = 0 ; i < count ; i++ ) { + for ( i = 0; i < count; i++ ) { if ( svs.clients[i].state >= CS_CONNECTED ) { oldClients[i] = svs.clients[i]; - } - else { + } else { Com_Memset(&oldClients[i], 0, sizeof(client_t)); } } @@ -378,11 +396,10 @@ void SV_ChangeMaxClients( void ) { Z_Free( svs.clients ); // allocate new clients - svs.clients = Z_TagMalloc( sv_maxclients->integer * sizeof(client_t), TAG_CLIENTS ); - Com_Memset( svs.clients, 0, sv_maxclients->integer * sizeof(client_t) ); + SV_AllocClients( maxclients ); // copy the clients over - for ( i = 0 ; i < count ; i++ ) { + for ( i = 0; i < count; i++ ) { if ( oldClients[i].state >= CS_CONNECTED ) { svs.clients[i] = oldClients[i]; } @@ -390,8 +407,6 @@ void SV_ChangeMaxClients( void ) { // free the old clients on the hunk Hunk_FreeTempMemory( oldClients ); - - SV_SetSnapshotParams(); } @@ -407,7 +422,7 @@ static void SV_ClearServer( void ) { Stef_Lua_SimpleEventCall( SV_LUA_EVENT_CLEAR_SERVER ); #endif - for ( i = 0 ; i < MAX_CONFIGSTRINGS ; i++ ) { + for ( i = 0; i < MAX_CONFIGSTRINGS; i++ ) { if ( sv.configstrings[i] ) { Z_Free( sv.configstrings[i] ); } @@ -529,17 +544,17 @@ void SV_SpawnServer( const char *mapname, qboolean killBots ) { // try to reset level time if server is empty if ( !sv_levelTimeReset->integer && !sv.restartTime ) { - for ( i = 0; i < sv_maxclients->integer; i++ ) { + for ( i = 0; i < sv.maxclients; i++ ) { if ( svs.clients[i].state >= CS_CONNECTED ) { break; } } - if ( i == sv_maxclients->integer ) { + if ( i == sv.maxclients ) { sv.time = 0; } } - for ( i = 0; i < sv_maxclients->integer; i++ ) { + for ( i = 0; i < sv.maxclients; i++ ) { // save when the server started for each client already connected if ( svs.clients[i].state >= CS_CONNECTED && sv_levelTimeReset->integer ) { svs.clients[i].oldServerTime = sv.time; @@ -548,9 +563,12 @@ void SV_SpawnServer( const char *mapname, qboolean killBots ) { } } + // preserve maxclients + i = sv.maxclients; // wipe the entire per-level structure SV_ClearServer(); - for ( i = 0 ; i < MAX_CONFIGSTRINGS ; i++ ) { + sv.maxclients = i; + for ( i = 0; i < MAX_CONFIGSTRINGS; i++ ) { sv.configstrings[i] = CopyString(""); } @@ -560,7 +578,10 @@ void SV_SpawnServer( const char *mapname, qboolean killBots ) { #endif // get latched value - Cvar_Get( "sv_pure", "1", CVAR_SYSTEMINFO | CVAR_LATCH ); + sv_pure = Cvar_Get( "sv_pure", "1", CVAR_SYSTEMINFO | CVAR_LATCH ); + + // VMs can change latched cvars instantly which could cause side-effects in SV_UserMove() + sv.pure = sv_pure->integer; // get a new checksum feed and restart the file system srand( Com_Milliseconds() ); @@ -588,13 +609,12 @@ void SV_SpawnServer( const char *mapname, qboolean killBots ) { // set serverinfo visible name Cvar_Set( "mapname", mapname ); - Cvar_Set( "sv_mapChecksum", va( "%i",checksum ) ); + Cvar_SetIntegerValue( "sv_mapChecksum", checksum ); // serverid should be different each time sv.serverId = com_frameTime; - sv.restartedServerId = sv.serverId; // I suppose the init here is just to be safe - sv.checksumFeedServerId = sv.serverId; - Cvar_Set( "sv_serverid", va( "%i", sv.serverId ) ); + sv.restartedServerId = sv.serverId; + Cvar_SetIntegerValue( "sv_serverid", sv.serverId ); // clear physics interaction links SV_ClearWorld(); @@ -616,8 +636,7 @@ void SV_SpawnServer( const char *mapname, qboolean killBots ) { sv_pure->modified = qfalse; // run a few frames to allow everything to settle - for ( i = 0; i < 3; i++ ) - { + for ( i = 0; i < 3; i++ ) { sv.time += 100; VM_Call( gvm, 1, GAME_RUN_FRAME, sv.time ); SV_BotFrame( sv.time ); @@ -626,7 +645,7 @@ void SV_SpawnServer( const char *mapname, qboolean killBots ) { // create a baseline for more efficient communications SV_CreateBaseline(); - for ( i = 0; i < sv_maxclients->integer; i++ ) { + for ( i = 0; i < sv.maxclients; i++ ) { // send the new gamestate to all connected clients if ( svs.clients[i].state >= CS_CONNECTED ) { const char *denied; @@ -649,25 +668,14 @@ void SV_SpawnServer( const char *mapname, qboolean killBots ) { // was connected before the level change SV_DropClient( &svs.clients[i], denied ); } else { - if( !isBot ) { + if ( !isBot ) { + svs.clients[i].gamestateAck = GSA_INIT; // resend gamestate, accept first correct serverId // when we get the next packet from a connected client, // the new gamestate will be sent svs.clients[i].state = CS_CONNECTED; - } - else { - client_t *client; - sharedEntity_t *ent; - - client = &svs.clients[i]; - client->state = CS_ACTIVE; - ent = SV_GentityNum( i ); - ent->s.number = i; - client->gentity = ent; - - client->deltaMessage = client->netchan.outgoingSequence - ( PACKET_BACKUP + 1 ); // force delta reset - client->lastSnapshotTime = svs.time - 9999; // generate a snapshot immediately - - VM_Call( gvm, 1, GAME_CLIENT_BEGIN, i ); + svs.clients[i].gentity = NULL; + } else { + SV_ClientEnterWorld( &svs.clients[i] ); } } } @@ -707,7 +715,7 @@ void SV_SpawnServer( const char *mapname, qboolean killBots ) { Cvar_Set( "sv_paks", "" ); Cvar_Set( "sv_pakNames", "" ); // not used on client-side - if ( sv_pure->integer ) { + if ( sv.pure != 0 ) { int freespace, pakslen, infolen; qboolean overflowed = qfalse; qboolean infoTruncated = qfalse; @@ -987,7 +995,7 @@ static void SV_FinalMessage( const char *message ) { // send it twice, ignoring rate for ( j = 0 ; j < 2 ; j++ ) { - for (i=0, cl = svs.clients ; i < sv_maxclients->integer ; i++, cl++) { + for ( i = 0, cl = svs.clients; i < sv.maxclients; i++, cl++) { if (cl->state >= CS_CONNECTED ) { // don't send a disconnect to a local client if ( cl->netchan.remoteAddress.type != NA_LOOPBACK ) { @@ -1006,6 +1014,8 @@ static void SV_FinalMessage( const char *message ) { } } } + + NET_FlushPacketQueue( 99999 ); } @@ -1046,7 +1056,7 @@ void SV_Shutdown( const char *finalmsg ) { if ( svs.clients ) { int index; - for ( index = 0; index < sv_maxclients->integer; index++ ) + for ( index = 0; index < sv.maxclients; index++ ) SV_FreeClient( &svs.clients[ index ] ); Z_Free( svs.clients ); diff --git a/code/server/sv_main.c b/code/server/sv_main.c index 85e5b5bb..841080fd 100644 --- a/code/server/sv_main.c +++ b/code/server/sv_main.c @@ -211,7 +211,7 @@ void QDECL SV_SendServerCommand( client_t *cl, const char *fmt, ... ) { } // send the data to all relevant clients - for ( j = 0, client = svs.clients; j < sv_maxclients->integer ; j++, client++ ) { + for ( j = 0, client = svs.clients; j < sv.maxclients; j++, client++ ) { if ( len <= 1022 || client->longstr ) { SV_AddServerCommand( client, message ); } @@ -726,7 +726,7 @@ static void SVC_Status( const netadr_t *from ) { status[0] = '\0'; statusLength = strlen( infostring ) + 16; // strlen( "statusResponse\n\n" ) - for ( i = 0 ; i < sv_maxclients->integer ; i++ ) { + for ( i = 0; i < sv.maxclients; i++ ) { cl = &svs.clients[i]; if ( cl->state >= CS_CONNECTED ) { @@ -797,7 +797,7 @@ static void SVC_Info( const netadr_t *from ) { // don't count privateclients count = humans = 0; - for ( i = sv_privateClients->integer ; i < sv_maxclients->integer ; i++ ) { + for ( i = sv_privateClients->integer; i < sv.maxclients; i++ ) { if ( svs.clients[i].state >= CS_CONNECTED ) { count++; if (svs.clients[i].netchan.remoteAddress.type != NA_BOT) { @@ -816,14 +816,13 @@ static void SVC_Info( const netadr_t *from ) { Info_SetValueForKey( infostring, "hostname", sv_hostname->string ); Info_SetValueForKey( infostring, "mapname", sv_mapname->string ); Info_SetValueForKey( infostring, "clients", va("%i", count) ); - Info_SetValueForKey(infostring, "g_humanplayers", va("%i", humans)); - Info_SetValueForKey( infostring, "sv_maxclients", - va("%i", sv_maxclients->integer - sv_privateClients->integer ) ); - Info_SetValueForKey( infostring, "gametype", va("%i", sv_gametype->integer ) ); - Info_SetValueForKey( infostring, "pure", va("%i", sv_pure->integer ) ); - Info_SetValueForKey(infostring, "g_needpass", va("%d", Cvar_VariableIntegerValue("g_needpass"))); + Info_SetValueForKey( infostring, "g_humanplayers", va( "%i", humans ) ); + Info_SetValueForKey( infostring, "sv_maxclients", va( "%i", sv.maxclients - sv_privateClients->integer ) ); + Info_SetValueForKey( infostring, "gametype", va( "%i", sv_gametype->integer ) ); + Info_SetValueForKey( infostring, "pure", va( "%i", sv.pure ) ); + Info_SetValueForKey( infostring, "g_needpass", va( "%d", Cvar_VariableIntegerValue( "g_needpass" ) ) ); gamedir = Cvar_VariableString( "fs_game" ); - if( *gamedir ) { + if ( *gamedir != '\0' ) { Info_SetValueForKey( infostring, "game", gamedir ); } @@ -1041,8 +1040,8 @@ void SV_PacketEvent( const netadr_t *from, msg_t *msg ) { qport = MSG_ReadShort( msg ) & 0xffff; // find which client the message is from - for (i=0, cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) { - if (cl->state == CS_FREE) { + for ( i = 0, cl = svs.clients; i < sv.maxclients; i++, cl++ ) { + if ( cl->state == CS_FREE ) { continue; } if ( !NET_CompareBaseAdr( from, &cl->netchan.remoteAddress ) ) { @@ -1050,7 +1049,7 @@ void SV_PacketEvent( const netadr_t *from, msg_t *msg ) { } // it is possible to have multiple clients from a single IP // address, so they are differentiated by the qport variable - if (cl->netchan.qport != qport) { + if ( cl->netchan.qport != qport ) { continue; } @@ -1059,18 +1058,18 @@ void SV_PacketEvent( const netadr_t *from, msg_t *msg ) { #endif // make sure it is a valid, in sequence packet - if (SV_Netchan_Process(cl, msg)) { + if ( SV_Netchan_Process( cl, msg ) ) { // the IP port can't be used to differentiate clients, because // some address translating routers periodically change UDP // port assignments - if (cl->netchan.remoteAddress.port != from->port) { + if ( cl->netchan.remoteAddress.port != from->port ) { Com_Printf( "SV_PacketEvent: fixing up a translated port\n" ); cl->netchan.remoteAddress.port = from->port; } // zombie clients still need to do the Netchan_Process // to make sure they don't need to retransmit the final // reliable message, but they don't do any other processing - if (cl->state != CS_ZOMBIE) { + if ( cl->state != CS_ZOMBIE ) { cl->lastPacketTime = svs.time; // don't timeout SV_ExecuteClientMessage( cl, msg ); } @@ -1097,7 +1096,7 @@ static void SV_CalcPings( void ) { int delta; playerState_t *ps; - for (i=0 ; i < sv_maxclients->integer ; i++) { + for ( i = 0; i < sv.maxclients; i++ ) { cl = &svs.clients[i]; if ( cl->state != CS_ACTIVE ) { cl->ping = 999; @@ -1186,7 +1185,7 @@ static void SV_CheckTimeouts( void ) { droppoint = svs.time - 1000 * sv_timeout->integer; zombiepoint = svs.time - 1000 * sv_zombietime->integer; - for ( i = 0, cl = svs.clients ; i < sv_maxclients->integer ; i++, cl++ ) { + for ( i = 0, cl = svs.clients ; i < sv.maxclients; i++, cl++ ) { if ( cl->state == CS_FREE ) { continue; } @@ -1197,7 +1196,7 @@ static void SV_CheckTimeouts( void ) { if ( cl->state == CS_ZOMBIE && cl->lastPacketTime - zombiepoint < 0 ) { // using the client id cause the cl->name is empty at this point - Com_DPrintf( "Going from CS_ZOMBIE to CS_FREE for client %d\n", i ); + SV_PrintClientStateChange( cl, CS_FREE ); cl->state = CS_FREE; // can now be reused continue; } @@ -1243,7 +1242,7 @@ static qboolean SV_CheckPaused( void ) { // only pause if there is just a single client connected count = 0; - for (i=0,cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) { + for ( i = 0, cl = svs.clients ; i < sv.maxclients; i++, cl++ ) { if ( cl->state >= CS_CONNECTED && cl->netchan.remoteAddress.type != NA_BOT ) { count++; } @@ -1313,7 +1312,7 @@ void SV_TrackCvarChanges( void ) if ( sv.state == SS_DEAD || !svs.clients ) return; - for ( i = 0, cl = svs.clients; i < sv_maxclients->integer; i++, cl++ ) { + for ( i = 0, cl = svs.clients; i < sv.maxclients; i++, cl++ ) { if ( cl->state >= CS_CONNECTED ) { SV_UserinfoChanged( cl, qfalse, qfalse ); // do not update userinfo, do not run filter } @@ -1333,7 +1332,7 @@ static void SV_Restart( const char *reason ) { if ( svs.clients ) { // check if we can reset map time without full server shutdown - for ( i = 0; i < sv_maxclients->integer; i++ ) { + for ( i = 0; i < sv.maxclients; i++ ) { if ( svs.clients[i].state >= CS_CONNECTED ) { sv_shutdown = qtrue; break; @@ -1421,13 +1420,13 @@ void SV_Frame( int msec ) { // try to do silent restart earlier if possible if ( sv.time > (12*3600*1000) && ( sv_levelTimeReset->integer == 0 || sv.time > 0x40000000 ) ) { if ( svs.clients ) { - for ( i = 0; i < sv_maxclients->integer; i++ ) { + for ( i = 0; i < sv.maxclients; i++ ) { // FIXME: deal with bots (reconnect?) if ( svs.clients[i].state != CS_FREE && svs.clients[i].netchan.remoteAddress.type != NA_BOT ) { break; } } - if ( i == sv_maxclients->integer ) { + if ( i == sv.maxclients ) { SV_Restart( "Restarting server" ); return; } diff --git a/code/server/sv_snapshot.c b/code/server/sv_snapshot.c index 7021067d..06227a63 100644 --- a/code/server/sv_snapshot.c +++ b/code/server/sv_snapshot.c @@ -791,16 +791,22 @@ void SV_SendClientMessages( void ) svs.msgTime = Sys_Milliseconds(); // send a message to each connected client - for( i = 0; i < sv_maxclients->integer; i++ ) + for ( i = 0; i < sv.maxclients; i++ ) { c = &svs.clients[ i ]; - + if ( c->state == CS_FREE ) continue; // not connected - if ( *c->downloadName ) + //if ( *c->downloadName ) + // continue; // Client is downloading, don't send snapshots + + if ( c->state == CS_CONNECTED ) continue; // Client is downloading, don't send snapshots + //if ( !c->gamestateAcked ) + // continue; // waiting usercmd/downloading + // 1. Local clients get snapshots every server frame // 2. Remote clients get snapshots depending from rate and requested number of updates @@ -812,7 +818,7 @@ void SV_SendClientMessages( void ) c->rateDelayed = qtrue; continue; // Drop this snapshot if the packet queue is still full or delta compression will break } - + if ( SV_RateMsec( c ) > 0 ) { // Not enough time since last packet passed through the line diff --git a/code/unix/linux_qgl.c b/code/unix/linux_qgl.c index 913d89ed..05fd390c 100644 --- a/code/unix/linux_qgl.c +++ b/code/unix/linux_qgl.c @@ -74,7 +74,10 @@ void QGL_Shutdown( qboolean unloadDLL ) // huh?), and it defaults to 0. For me, 500 seems to work. //if( r_GLlibCoolDownMsec->integer ) // usleep( r_GLlibCoolDownMsec->integer * 1000 ); - usleep( 250 * 1000 ); + struct timespec req; + req.tv_sec = 0; + req.tv_nsec = 250 * 1000000; + nanosleep( &req, NULL ); dlclose( glw_state.OpenGLLib ); diff --git a/code/unix/linux_snd.c b/code/unix/linux_snd.c index 4f26f0a5..2e2eb1de 100644 --- a/code/unix/linux_snd.c +++ b/code/unix/linux_snd.c @@ -729,10 +729,14 @@ static int xrun_recovery( snd_pcm_t *handle, int err ) else if ( err == -ESTRPIPE ) { int tries = 0; + struct timespec req; + req.tv_sec = period_time / 1000000; + req.tv_nsec = ( period_time % 1000000 ) * 1000; + /* wait until the suspend flag is released */ while ( ( err = _snd_pcm_resume( handle ) ) == -EAGAIN ) { - usleep( period_time ); + nanosleep( &req, NULL ); if ( tries++ < 16 ) { break; diff --git a/code/unix/unix_main.c b/code/unix/unix_main.c index f21c224e..ffe716f5 100644 --- a/code/unix/unix_main.c +++ b/code/unix/unix_main.c @@ -637,7 +637,10 @@ void Sys_Sleep( int msec ) { return; } #if 1 - usleep( msec * 1000 ); + struct timespec req; + req.tv_sec = msec / 1000; + req.tv_nsec = ( msec % 1000 ) * 1000000; + nanosleep( &req, NULL ); #else if ( com_dedicated->integer && stdin_active ) { FD_ZERO( &fdset );