summaryrefslogtreecommitdiff
path: root/src/client
diff options
context:
space:
mode:
Diffstat (limited to 'src/client')
-rw-r--r--src/client/CMakeLists.txt209
-rw-r--r--src/client/cl_avi.cpp664
-rw-r--r--src/client/cl_cgame.cpp1172
-rw-r--r--src/client/cl_cin.cpp1937
-rw-r--r--src/client/cl_console.cpp877
-rw-r--r--src/client/cl_curl.cpp364
-rw-r--r--src/client/cl_curl.h101
-rw-r--r--src/client/cl_input.cpp1194
-rw-r--r--src/client/cl_keys.cpp1665
-rw-r--r--src/client/cl_main.cpp5083
-rw-r--r--src/client/cl_net_chan.cpp190
-rw-r--r--src/client/cl_parse.cpp961
-rw-r--r--src/client/cl_rest.cpp117
-rw-r--r--src/client/cl_rest.h18
-rw-r--r--src/client/cl_scrn.cpp588
-rw-r--r--src/client/cl_ui.cpp1269
-rw-r--r--src/client/cl_updates.cpp476
-rw-r--r--src/client/cl_updates.h8
-rw-r--r--src/client/client.h716
-rw-r--r--src/client/keycodes.h42
-rw-r--r--src/client/keys.h66
-rw-r--r--src/client/libmumblelink.cpp190
-rw-r--r--src/client/libmumblelink.h41
-rw-r--r--src/client/qal.cpp337
-rw-r--r--src/client/qal.h252
-rw-r--r--src/client/snd_adpcm.cpp329
-rw-r--r--src/client/snd_codec.cpp239
-rw-r--r--src/client/snd_codec.h109
-rw-r--r--src/client/snd_codec_ogg.cpp479
-rw-r--r--src/client/snd_codec_opus.cpp452
-rw-r--r--src/client/snd_codec_wav.cpp293
-rw-r--r--src/client/snd_dma.cpp1644
-rw-r--r--src/client/snd_local.h267
-rw-r--r--src/client/snd_main.cpp566
-rw-r--r--src/client/snd_mem.cpp297
-rw-r--r--src/client/snd_mix.cpp792
-rw-r--r--src/client/snd_openal.cpp2737
-rw-r--r--src/client/snd_public.h88
-rw-r--r--src/client/snd_wavelet.cpp252
39 files changed, 27077 insertions, 4 deletions
diff --git a/src/client/CMakeLists.txt b/src/client/CMakeLists.txt
new file mode 100644
index 0000000..c10fd9c
--- /dev/null
+++ b/src/client/CMakeLists.txt
@@ -0,0 +1,209 @@
+#
+## .o88b. db d888888b d88888b d8b db d888888b
+## d8P Y8 88 `88' 88' 888o 88 `~~88~~'
+## 8P 88 88 88ooooo 88V8o 88 88
+## 8b 88 88 88~~~~~ 88 V8o88 88
+## Y8b d8 88booo. .88. 88. 88 V888 88
+## `Y88P' Y88888P Y888888P Y88888P VP V8P YP
+#
+
+find_package(CURL)
+find_package(OpenGL)
+find_package(OpenAL)
+include(${CMAKE_SOURCE_DIR}/cmake/SDL2.cmake)
+
+add_definitions(
+ -DUSE_LOCAL_HEADERS
+ -DUSE_RESTCLIENT
+ -DUSE_CODEC_OPUS
+ -DUSE_OPENAL
+ -DUSE_OPENAL_DLOPEN
+ -DUSE_INTERNAL_JPEG
+ -DUSE_RENDERER_DLOPEN
+ -DNDEBUG
+ -DPRODUCT_VERSION="1.3.0 alpha"
+ ${SDL2_DEFINES}
+ )
+
+set(PARENT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/..)
+set(EXTERNAL_DIR ${CMAKE_SOURCE_DIR}/external)
+if(APPLE)
+set(APPLE_SOURCES ${PARENT_DIR}/sys/sys_osx.mm)
+endif(APPLE)
+
+add_executable(
+ tremulous
+ #
+ ${PARENT_DIR}/asm/snapvector.c
+ #
+ cl_avi.cpp
+ cl_cgame.cpp
+ cl_cin.cpp
+ cl_console.cpp
+ cl_curl.cpp
+ cl_input.cpp
+ cl_keys.cpp
+ cl_main.cpp
+ cl_net_chan.cpp
+ cl_parse.cpp
+ cl_rest.cpp
+ cl_scrn.cpp
+ cl_ui.cpp
+ cl_updates.cpp
+ libmumblelink.cpp
+ qal.cpp
+ snd_adpcm.cpp
+ snd_codec.cpp
+ snd_codec_ogg.cpp
+ snd_codec_opus.cpp
+ snd_codec_wav.cpp
+ snd_dma.cpp
+ snd_main.cpp
+ snd_mem.cpp
+ snd_mix.cpp
+ snd_openal.cpp
+ snd_wavelet.cpp
+ #
+ ${PARENT_DIR}/qcommon/cm_load.cpp
+ ${PARENT_DIR}/qcommon/cm_patch.cpp
+ ${PARENT_DIR}/qcommon/cm_polylib.cpp
+ ${PARENT_DIR}/qcommon/cm_test.cpp
+ ${PARENT_DIR}/qcommon/cm_trace.cpp
+ ${PARENT_DIR}/qcommon/cmd.cpp
+ ${PARENT_DIR}/qcommon/common.cpp
+ ${PARENT_DIR}/qcommon/crypto.cpp
+ ${PARENT_DIR}/qcommon/cvar.cpp
+ ${PARENT_DIR}/qcommon/cvar.h
+ ${PARENT_DIR}/qcommon/files.cpp
+ ${PARENT_DIR}/qcommon/files.h
+ ${PARENT_DIR}/qcommon/huffman.cpp
+ ${PARENT_DIR}/qcommon/huffman.h
+ ${PARENT_DIR}/qcommon/ioapi.cpp
+ ${PARENT_DIR}/qcommon/md4.cpp
+ ${PARENT_DIR}/qcommon/md5.cpp
+ ${PARENT_DIR}/qcommon/msg.cpp
+ ${PARENT_DIR}/qcommon/msg.h
+ ${PARENT_DIR}/qcommon/net_chan.cpp
+ ${PARENT_DIR}/qcommon/net_ip.cpp
+ ${PARENT_DIR}/qcommon/net.h
+ ${PARENT_DIR}/qcommon/parse.cpp
+ ${PARENT_DIR}/qcommon/puff.cpp
+ ${PARENT_DIR}/qcommon/q_shared.c
+ ${PARENT_DIR}/qcommon/q3_lauxlib.cpp
+ ${PARENT_DIR}/qcommon/q_math.c
+ ${PARENT_DIR}/qcommon/unzip.cpp
+ ${PARENT_DIR}/qcommon/vm.cpp
+ ${PARENT_DIR}/qcommon/vm_interpreted.cpp
+ ${PARENT_DIR}/qcommon/vm_x86.cpp
+ #
+ ${PARENT_DIR}/sdl/sdl_input.cpp
+ ${PARENT_DIR}/sdl/sdl_snd.cpp
+ #
+ ${PARENT_DIR}/server/sv_ccmds.cpp
+ ${PARENT_DIR}/server/sv_client.cpp
+ ${PARENT_DIR}/server/sv_game.cpp
+ ${PARENT_DIR}/server/sv_init.cpp
+ ${PARENT_DIR}/server/sv_main.cpp
+ ${PARENT_DIR}/server/sv_net_chan.cpp
+ ${PARENT_DIR}/server/sv_snapshot.cpp
+ ${PARENT_DIR}/server/sv_world.cpp
+ #
+ ${PARENT_DIR}/sys/con_log.cpp
+ ${PARENT_DIR}/sys/con_tty.cpp
+ ${PARENT_DIR}/sys/sys_main.cpp
+ ${PARENT_DIR}/sys/sys_unix.cpp
+ ${PARENT_DIR}/sys/sys_shared.h
+ ${APPLE_SOURCES}
+
+ ${EXTERNAL_DIR}/semver/src/lib/semantic_version_v1.cpp
+ ${EXTERNAL_DIR}/semver/src/lib/semantic_version_v2.cpp
+ )
+
+if(APPLE)
+ # FIXME Prefixed with "lua" to prevent cmake from doing "-l-framework Cocoa"
+ set(FRAMEWORKS "-framework Cocoa -framework Security -framework OpenAL -framework IOKit")
+else(APPLE)
+ if(UNIX)
+ set(SYSLIBS dl rt)
+ endif(UNIX)
+endif(APPLE)
+
+if(NOT USE_RENDERER_DLOPEN)
+ if(USE_OPENGL1)
+ set(RENDERER_LIBRARY renderergl1)
+ endif(USE_OPENGL1)
+ set(RENDERER_LIBRARY renderergl2)
+endif(NOT USE_RENDERER_DLOPEN)
+
+target_link_libraries(
+ tremulous
+ #
+ lua
+ nettle
+ zlib
+ ogg
+ opus
+ opusfile
+ #
+ script_api
+ restclient
+ #
+ ${FRAMEWORKS}
+ ${CURL_LIBRARIES}
+ ${SDL2_LIBRARIES}
+ ${OPENGL_LIBRARIES}
+ ${OPENAL_LIBRARY}
+ ${SYSLIBS}
+ )
+
+include_directories(
+ ${PARENT_DIR}
+ ${PARENT_DIR}/qcommon
+ ${EXTERNAL_DIR}/zlib
+ ${EXTERNAL_DIR}/restclient
+ ${EXTERNAL_DIR}/rapidjson
+ ${EXTERNAL_DIR}/SDL2/include
+ ${EXTERNAL_DIR}/jpeg-8c
+ ${EXTERNAL_DIR}/libogg-1.3.2/include
+ ${EXTERNAL_DIR}/lua-5.3.3/include
+ ${EXTERNAL_DIR}/sol
+ ${EXTERNAL_DIR}/nettle-3.3
+ ${EXTERNAL_DIR}/opus-1.1.4/include
+ ${EXTERNAL_DIR}/opusfile-0.8/include
+ ${EXTERNAL_DIR}/restclient
+ ${EXTERNAL_DIR}/semver/src/include
+ ${SDL2_INCLUDE_DIRS}
+ ${OPENAL_INCLUDE_DIR}
+ )
+
+# TODO: Turn this into a macro
+if (USE_INTERNAL_SDL2)
+add_custom_command(
+ TARGET tremulous
+ POST_BUILD COMMAND ${CMAKE_COMMAND}
+ ARGS -E copy ${CMAKE_SOURCE_DIR}/external/SDL2/libs/Darwin/libSDL2-2.0.0.dylib ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/libSDL2-2.0.0.dylib
+ )
+endif(USE_INTERNAL_SDL2)
+
+if (USE_RENDERER_DLOPEN)
+ add_custom_command(
+ TARGET tremulous
+ POST_BUILD COMMAND ${CMAKE_COMMAND}
+ ARGS -E copy ${CMAKE_BINARY_DIR}/src/renderergl1/librenderergl1${CMAKE_SHARED_LIBRARY_SUFFIX} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/renderer_opengl1${CMAKE_SHARED_LIBRARY_SUFFIX}
+ )
+
+ add_custom_command(
+ TARGET tremulous
+ POST_BUILD COMMAND ${CMAKE_COMMAND}
+ ARGS -E copy ${CMAKE_BINARY_DIR}/src/renderergl2/librenderergl2${CMAKE_SHARED_LIBRARY_SUFFIX} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/renderer_opengl2${CMAKE_SHARED_LIBRARY_SUFFIX}
+ )
+endif(USE_RENDERER_DLOPEN)
+
+add_dependencies(
+ tremulous
+ ui
+ game
+ cgame
+ renderergl1
+ renderergl2
+ )
diff --git a/src/client/cl_avi.cpp b/src/client/cl_avi.cpp
new file mode 100644
index 0000000..d533e60
--- /dev/null
+++ b/src/client/cl_avi.cpp
@@ -0,0 +1,664 @@
+/*
+===========================================================================
+Copyright (C) 2005-2009 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
+
+This file is part of Tremulous.
+
+Tremulous 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.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+
+#include "client.h"
+#include "snd_local.h"
+
+#define INDEX_FILE_EXTENSION ".index.dat"
+
+#define MAX_RIFF_CHUNKS 16
+
+typedef struct audioFormat_s
+{
+ int rate;
+ int format;
+ int channels;
+ int bits;
+
+ int sampleSize;
+ int totalBytes;
+} audioFormat_t;
+
+typedef struct aviFileData_s
+{
+ bool fileOpen;
+ fileHandle_t f;
+ char fileName[ MAX_QPATH ];
+ int fileSize;
+ int moviOffset;
+ int moviSize;
+
+ fileHandle_t idxF;
+ int numIndices;
+
+ int frameRate;
+ int framePeriod;
+ int width, height;
+ int numVideoFrames;
+ int maxRecordSize;
+ bool motionJpeg;
+
+ bool audio;
+ audioFormat_t a;
+ int numAudioFrames;
+
+ int chunkStack[ MAX_RIFF_CHUNKS ];
+ int chunkStackTop;
+
+ byte *cBuffer, *eBuffer;
+} aviFileData_t;
+
+static aviFileData_t afd;
+
+#define MAX_AVI_BUFFER 2048
+
+static byte buffer[ MAX_AVI_BUFFER ];
+static int bufIndex;
+
+/*
+===============
+SafeFS_Write
+===============
+*/
+static ID_INLINE void SafeFS_Write( const void *buffer, int len, fileHandle_t f )
+{
+ if( FS_Write( buffer, len, f ) < len )
+ Com_Error( ERR_DROP, "Failed to write avi file" );
+}
+
+/*
+===============
+WRITE_STRING
+===============
+*/
+static ID_INLINE void WRITE_STRING( const char *s )
+{
+ ::memcpy( &buffer[ bufIndex ], s, strlen( s ) );
+ bufIndex += strlen( s );
+}
+
+/*
+===============
+WRITE_4BYTES
+===============
+*/
+static ID_INLINE void WRITE_4BYTES( int x )
+{
+ buffer[ bufIndex + 0 ] = (byte)( ( x >> 0 ) & 0xFF );
+ buffer[ bufIndex + 1 ] = (byte)( ( x >> 8 ) & 0xFF );
+ buffer[ bufIndex + 2 ] = (byte)( ( x >> 16 ) & 0xFF );
+ buffer[ bufIndex + 3 ] = (byte)( ( x >> 24 ) & 0xFF );
+ bufIndex += 4;
+}
+
+/*
+===============
+WRITE_2BYTES
+===============
+*/
+static ID_INLINE void WRITE_2BYTES( int x )
+{
+ buffer[ bufIndex + 0 ] = (byte)( ( x >> 0 ) & 0xFF );
+ buffer[ bufIndex + 1 ] = (byte)( ( x >> 8 ) & 0xFF );
+ bufIndex += 2;
+}
+
+/*
+===============
+START_CHUNK
+===============
+*/
+static ID_INLINE void START_CHUNK( const char *s )
+{
+ if( afd.chunkStackTop == MAX_RIFF_CHUNKS )
+ {
+ Com_Error( ERR_DROP, "ERROR: Top of chunkstack breached" );
+ }
+
+ afd.chunkStack[ afd.chunkStackTop ] = bufIndex;
+ afd.chunkStackTop++;
+ WRITE_STRING( s );
+ WRITE_4BYTES( 0 );
+}
+
+/*
+===============
+END_CHUNK
+===============
+*/
+static ID_INLINE void END_CHUNK( void )
+{
+ int endIndex = bufIndex;
+
+ if( afd.chunkStackTop <= 0 )
+ {
+ Com_Error( ERR_DROP, "ERROR: Bottom of chunkstack breached" );
+ }
+
+ afd.chunkStackTop--;
+ bufIndex = afd.chunkStack[ afd.chunkStackTop ];
+ bufIndex += 4;
+ WRITE_4BYTES( endIndex - bufIndex - 4 );
+ bufIndex = endIndex;
+ bufIndex = PAD( bufIndex, 2 );
+}
+
+/*
+===============
+CL_WriteAVIHeader
+===============
+*/
+void CL_WriteAVIHeader( void )
+{
+ bufIndex = 0;
+ afd.chunkStackTop = 0;
+
+ START_CHUNK( "RIFF" );
+ {
+ WRITE_STRING( "AVI " );
+ {
+ START_CHUNK( "LIST" );
+ {
+ WRITE_STRING( "hdrl" );
+ WRITE_STRING( "avih" );
+ WRITE_4BYTES( 56 ); //"avih" "chunk" size
+ WRITE_4BYTES( afd.framePeriod ); //dwMicroSecPerFrame
+ WRITE_4BYTES( afd.maxRecordSize *
+ afd.frameRate ); //dwMaxBytesPerSec
+ WRITE_4BYTES( 0 ); //dwReserved1
+ WRITE_4BYTES( 0x110 ); //dwFlags bits HAS_INDEX and IS_INTERLEAVED
+ WRITE_4BYTES( afd.numVideoFrames ); //dwTotalFrames
+ WRITE_4BYTES( 0 ); //dwInitialFrame
+
+ if( afd.audio ) //dwStreams
+ WRITE_4BYTES( 2 );
+ else
+ WRITE_4BYTES( 1 );
+
+ WRITE_4BYTES( afd.maxRecordSize ); //dwSuggestedBufferSize
+ WRITE_4BYTES( afd.width ); //dwWidth
+ WRITE_4BYTES( afd.height ); //dwHeight
+ WRITE_4BYTES( 0 ); //dwReserved[ 0 ]
+ WRITE_4BYTES( 0 ); //dwReserved[ 1 ]
+ WRITE_4BYTES( 0 ); //dwReserved[ 2 ]
+ WRITE_4BYTES( 0 ); //dwReserved[ 3 ]
+
+ START_CHUNK( "LIST" );
+ {
+ WRITE_STRING( "strl" );
+ WRITE_STRING( "strh" );
+ WRITE_4BYTES( 56 ); //"strh" "chunk" size
+ WRITE_STRING( "vids" );
+
+ if( afd.motionJpeg )
+ WRITE_STRING( "MJPG" );
+ else
+ WRITE_4BYTES( 0 ); // BI_RGB
+
+ WRITE_4BYTES( 0 ); //dwFlags
+ WRITE_4BYTES( 0 ); //dwPriority
+ WRITE_4BYTES( 0 ); //dwInitialFrame
+
+ WRITE_4BYTES( 1 ); //dwTimescale
+ WRITE_4BYTES( afd.frameRate ); //dwDataRate
+ WRITE_4BYTES( 0 ); //dwStartTime
+ WRITE_4BYTES( afd.numVideoFrames ); //dwDataLength
+
+ WRITE_4BYTES( afd.maxRecordSize ); //dwSuggestedBufferSize
+ WRITE_4BYTES( -1 ); //dwQuality
+ WRITE_4BYTES( 0 ); //dwSampleSize
+ WRITE_2BYTES( 0 ); //rcFrame
+ WRITE_2BYTES( 0 ); //rcFrame
+ WRITE_2BYTES( afd.width ); //rcFrame
+ WRITE_2BYTES( afd.height ); //rcFrame
+
+ WRITE_STRING( "strf" );
+ WRITE_4BYTES( 40 ); //"strf" "chunk" size
+ WRITE_4BYTES( 40 ); //biSize
+ WRITE_4BYTES( afd.width ); //biWidth
+ WRITE_4BYTES( afd.height ); //biHeight
+ WRITE_2BYTES( 1 ); //biPlanes
+ WRITE_2BYTES( 24 ); //biBitCount
+
+ if( afd.motionJpeg ) //biCompression
+ {
+ WRITE_STRING( "MJPG" );
+ WRITE_4BYTES( afd.width *
+ afd.height ); //biSizeImage
+ }
+ else
+ {
+ WRITE_4BYTES( 0 ); // BI_RGB
+ WRITE_4BYTES( afd.width *
+ afd.height * 3 ); //biSizeImage
+ }
+
+ WRITE_4BYTES( 0 ); //biXPelsPetMeter
+ WRITE_4BYTES( 0 ); //biYPelsPetMeter
+ WRITE_4BYTES( 0 ); //biClrUsed
+ WRITE_4BYTES( 0 ); //biClrImportant
+ }
+ END_CHUNK( );
+
+ if( afd.audio )
+ {
+ START_CHUNK( "LIST" );
+ {
+ WRITE_STRING( "strl" );
+ WRITE_STRING( "strh" );
+ WRITE_4BYTES( 56 ); //"strh" "chunk" size
+ WRITE_STRING( "auds" );
+ WRITE_4BYTES( 0 ); //FCC
+ WRITE_4BYTES( 0 ); //dwFlags
+ WRITE_4BYTES( 0 ); //dwPriority
+ WRITE_4BYTES( 0 ); //dwInitialFrame
+
+ WRITE_4BYTES( afd.a.sampleSize ); //dwTimescale
+ WRITE_4BYTES( afd.a.sampleSize *
+ afd.a.rate ); //dwDataRate
+ WRITE_4BYTES( 0 ); //dwStartTime
+ WRITE_4BYTES( afd.a.totalBytes /
+ afd.a.sampleSize ); //dwDataLength
+
+ WRITE_4BYTES( 0 ); //dwSuggestedBufferSize
+ WRITE_4BYTES( -1 ); //dwQuality
+ WRITE_4BYTES( afd.a.sampleSize ); //dwSampleSize
+ WRITE_2BYTES( 0 ); //rcFrame
+ WRITE_2BYTES( 0 ); //rcFrame
+ WRITE_2BYTES( 0 ); //rcFrame
+ WRITE_2BYTES( 0 ); //rcFrame
+
+ WRITE_STRING( "strf" );
+ WRITE_4BYTES( 18 ); //"strf" "chunk" size
+ WRITE_2BYTES( afd.a.format ); //wFormatTag
+ WRITE_2BYTES( afd.a.channels ); //nChannels
+ WRITE_4BYTES( afd.a.rate ); //nSamplesPerSec
+ WRITE_4BYTES( afd.a.sampleSize *
+ afd.a.rate ); //nAvgBytesPerSec
+ WRITE_2BYTES( afd.a.sampleSize ); //nBlockAlign
+ WRITE_2BYTES( afd.a.bits ); //wBitsPerSample
+ WRITE_2BYTES( 0 ); //cbSize
+ }
+ END_CHUNK( );
+ }
+ }
+ END_CHUNK( );
+
+ afd.moviOffset = bufIndex;
+
+ START_CHUNK( "LIST" );
+ {
+ WRITE_STRING( "movi" );
+ }
+ }
+ }
+}
+
+/*
+===============
+CL_OpenAVIForWriting
+
+Creates an AVI file and gets it into a state where
+writing the actual data can begin
+===============
+*/
+bool CL_OpenAVIForWriting( const char *fileName )
+{
+ if( afd.fileOpen )
+ return false;
+
+ ::memset( &afd, 0, sizeof( aviFileData_t ) );
+
+ // Don't start if a framerate has not been chosen
+ if( cl_aviFrameRate->integer <= 0 )
+ {
+ Com_Printf( S_COLOR_RED "cl_aviFrameRate must be >= 1\n" );
+ return false;
+ }
+
+ if( ( afd.f = FS_FOpenFileWrite( fileName ) ) <= 0 )
+ return false;
+
+ if( ( afd.idxF = FS_FOpenFileWrite(
+ va( "%s" INDEX_FILE_EXTENSION, fileName ) ) ) <= 0 )
+ {
+ FS_FCloseFile( afd.f );
+ return false;
+ }
+
+ Q_strncpyz( afd.fileName, fileName, MAX_QPATH );
+
+ afd.frameRate = cl_aviFrameRate->integer;
+ afd.framePeriod = (int)( 1000000.0f / afd.frameRate );
+ afd.width = cls.glconfig.vidWidth;
+ afd.height = cls.glconfig.vidHeight;
+
+ if( cl_aviMotionJpeg->integer )
+ afd.motionJpeg = true;
+ else
+ afd.motionJpeg = false;
+
+ // Buffers only need to store RGB pixels.
+ // Allocate a bit more space for the capture buffer to account for possible
+ // padding at the end of pixel lines, and padding for alignment
+ #define MAX_PACK_LEN 16
+ afd.cBuffer = (byte*)Z_Malloc((afd.width * 3 + MAX_PACK_LEN - 1) * afd.height + MAX_PACK_LEN - 1);
+ // raw avi files have pixel lines start on 4-byte boundaries
+ afd.eBuffer = (byte*)Z_Malloc(PAD(afd.width * 3, AVI_LINE_PADDING) * afd.height);
+
+ afd.a.rate = dma.speed;
+ afd.a.format = WAV_FORMAT_PCM;
+ afd.a.channels = dma.channels;
+ afd.a.bits = dma.samplebits;
+ afd.a.sampleSize = ( afd.a.bits / 8 ) * afd.a.channels;
+
+ if( afd.a.rate % afd.frameRate )
+ {
+ int suggestRate = afd.frameRate;
+
+ while( ( afd.a.rate % suggestRate ) && suggestRate >= 1 )
+ suggestRate--;
+
+ Com_Printf( S_COLOR_YELLOW "WARNING: cl_aviFrameRate is not a divisor "
+ "of the audio rate, suggest %d\n", suggestRate );
+ }
+
+ if( !Cvar_VariableIntegerValue( "s_initsound" ) )
+ {
+ afd.audio = false;
+ }
+ else if( Q_stricmp( Cvar_VariableString( "s_backend" ), "OpenAL" ) )
+ {
+ if( afd.a.bits != 16 || afd.a.channels != 2 )
+ {
+ Com_Printf( S_COLOR_YELLOW "WARNING: Audio format of %d bit/%d channels not supported",
+ afd.a.bits, afd.a.channels );
+ afd.audio = false;
+ }
+ else
+ afd.audio = true;
+ }
+ else
+ {
+ afd.audio = false;
+ Com_Printf( S_COLOR_YELLOW "WARNING: Audio capture is not supported "
+ "with OpenAL. Set s_useOpenAL to 0 for audio capture\n" );
+ }
+
+ // This doesn't write a real header, but allocates the
+ // correct amount of space at the beginning of the file
+ CL_WriteAVIHeader( );
+
+ SafeFS_Write( buffer, bufIndex, afd.f );
+ afd.fileSize = bufIndex;
+
+ bufIndex = 0;
+ START_CHUNK( "idx1" );
+ SafeFS_Write( buffer, bufIndex, afd.idxF );
+
+ afd.moviSize = 4; // For the "movi"
+ afd.fileOpen = true;
+
+ return true;
+}
+
+/*
+===============
+CL_CheckFileSize
+===============
+*/
+static bool CL_CheckFileSize( int bytesToAdd )
+{
+ unsigned int newFileSize;
+
+ newFileSize =
+ afd.fileSize + // Current file size
+ bytesToAdd + // What we want to add
+ ( afd.numIndices * 16 ) + // The index
+ 4; // The index size
+
+ // I assume all the operating systems
+ // we target can handle a 2Gb file
+ if( newFileSize > INT_MAX )
+ {
+ // Close the current file...
+ CL_CloseAVI( );
+
+ // ...And open a new one
+ CL_OpenAVIForWriting( va( "%s_", afd.fileName ) );
+
+ return true;
+ }
+
+ return false;
+}
+
+/*
+===============
+CL_WriteAVIVideoFrame
+===============
+*/
+void CL_WriteAVIVideoFrame( const byte *imageBuffer, int size )
+{
+ int chunkOffset = afd.fileSize - afd.moviOffset - 8;
+ int chunkSize = 8 + size;
+ int paddingSize = PADLEN(size, 2);
+ byte padding[ 4 ] = { 0 };
+
+ if( !afd.fileOpen )
+ return;
+
+ // Chunk header + contents + padding
+ if( CL_CheckFileSize( 8 + size + 2 ) )
+ return;
+
+ bufIndex = 0;
+ WRITE_STRING( "00dc" );
+ WRITE_4BYTES( size );
+
+ SafeFS_Write( buffer, 8, afd.f );
+ SafeFS_Write( imageBuffer, size, afd.f );
+ SafeFS_Write( padding, paddingSize, afd.f );
+ afd.fileSize += ( chunkSize + paddingSize );
+
+ afd.numVideoFrames++;
+ afd.moviSize += ( chunkSize + paddingSize );
+
+ if( size > afd.maxRecordSize )
+ afd.maxRecordSize = size;
+
+ // Index
+ bufIndex = 0;
+ WRITE_STRING( "00dc" ); //dwIdentifier
+ WRITE_4BYTES( 0x00000010 ); //dwFlags (all frames are KeyFrames)
+ WRITE_4BYTES( chunkOffset ); //dwOffset
+ WRITE_4BYTES( size ); //dwLength
+ SafeFS_Write( buffer, 16, afd.idxF );
+
+ afd.numIndices++;
+}
+
+#define PCM_BUFFER_SIZE 44100
+
+/*
+===============
+CL_WriteAVIAudioFrame
+===============
+*/
+void CL_WriteAVIAudioFrame( const byte *pcmBuffer, int size )
+{
+ static byte pcmCaptureBuffer[ PCM_BUFFER_SIZE ] = { 0 };
+ static int bytesInBuffer = 0;
+
+ if( !afd.audio )
+ return;
+
+ if( !afd.fileOpen )
+ return;
+
+ // Chunk header + contents + padding
+ if( CL_CheckFileSize( 8 + bytesInBuffer + size + 2 ) )
+ return;
+
+ if( bytesInBuffer + size > PCM_BUFFER_SIZE )
+ {
+ Com_Printf( S_COLOR_YELLOW
+ "WARNING: Audio capture buffer overflow -- truncating\n" );
+ size = PCM_BUFFER_SIZE - bytesInBuffer;
+ }
+
+ ::memcpy( &pcmCaptureBuffer[ bytesInBuffer ], pcmBuffer, size );
+ bytesInBuffer += size;
+
+ // Only write if we have a frame's worth of audio
+ if( bytesInBuffer >= (int)ceil( (float)afd.a.rate / (float)afd.frameRate ) *
+ afd.a.sampleSize )
+ {
+ int chunkOffset = afd.fileSize - afd.moviOffset - 8;
+ int chunkSize = 8 + bytesInBuffer;
+ int paddingSize = PADLEN(bytesInBuffer, 2);
+ byte padding[ 4 ] = { 0 };
+
+ bufIndex = 0;
+ WRITE_STRING( "01wb" );
+ WRITE_4BYTES( bytesInBuffer );
+
+ SafeFS_Write( buffer, 8, afd.f );
+ SafeFS_Write( pcmCaptureBuffer, bytesInBuffer, afd.f );
+ SafeFS_Write( padding, paddingSize, afd.f );
+ afd.fileSize += ( chunkSize + paddingSize );
+
+ afd.numAudioFrames++;
+ afd.moviSize += ( chunkSize + paddingSize );
+ afd.a.totalBytes += bytesInBuffer;
+
+ // Index
+ bufIndex = 0;
+ WRITE_STRING( "01wb" ); //dwIdentifier
+ WRITE_4BYTES( 0 ); //dwFlags
+ WRITE_4BYTES( chunkOffset ); //dwOffset
+ WRITE_4BYTES( bytesInBuffer ); //dwLength
+ SafeFS_Write( buffer, 16, afd.idxF );
+
+ afd.numIndices++;
+
+ bytesInBuffer = 0;
+ }
+}
+
+/*
+===============
+CL_TakeVideoFrame
+===============
+*/
+void CL_TakeVideoFrame( void )
+{
+ // AVI file isn't open
+ if( !afd.fileOpen )
+ return;
+
+ re.TakeVideoFrame( afd.width, afd.height, afd.cBuffer, afd.eBuffer, afd.motionJpeg );
+}
+
+/*
+===============
+CL_CloseAVI
+
+Closes the AVI file and writes an index chunk
+===============
+*/
+bool CL_CloseAVI( void )
+{
+ int indexRemainder;
+ int indexSize = afd.numIndices * 16;
+ const char *idxFileName = va( "%s" INDEX_FILE_EXTENSION, afd.fileName );
+
+ // AVI file isn't open
+ if( !afd.fileOpen )
+ return false;
+
+ afd.fileOpen = false;
+
+ FS_Seek( afd.idxF, 4, FS_SEEK_SET );
+ bufIndex = 0;
+ WRITE_4BYTES( indexSize );
+ SafeFS_Write( buffer, bufIndex, afd.idxF );
+ FS_FCloseFile( afd.idxF );
+
+ // Write index
+
+ // Open the temp index file
+ if( ( indexSize = FS_FOpenFileRead( idxFileName, &afd.idxF, true ) ) <= 0 )
+ {
+ FS_FCloseFile( afd.f );
+ return false;
+ }
+
+ indexRemainder = indexSize;
+
+ // Append index to end of avi file
+ while( indexRemainder > MAX_AVI_BUFFER )
+ {
+ FS_Read( buffer, MAX_AVI_BUFFER, afd.idxF );
+ SafeFS_Write( buffer, MAX_AVI_BUFFER, afd.f );
+ afd.fileSize += MAX_AVI_BUFFER;
+ indexRemainder -= MAX_AVI_BUFFER;
+ }
+ FS_Read( buffer, indexRemainder, afd.idxF );
+ SafeFS_Write( buffer, indexRemainder, afd.f );
+ afd.fileSize += indexRemainder;
+ FS_FCloseFile( afd.idxF );
+
+ // Remove temp index file
+ FS_HomeRemove( idxFileName );
+
+ // Write the real header
+ FS_Seek( afd.f, 0, FS_SEEK_SET );
+ CL_WriteAVIHeader( );
+
+ bufIndex = 4;
+ WRITE_4BYTES( afd.fileSize - 8 ); // "RIFF" size
+
+ bufIndex = afd.moviOffset + 4; // Skip "LIST"
+ WRITE_4BYTES( afd.moviSize );
+
+ SafeFS_Write( buffer, bufIndex, afd.f );
+
+ Z_Free( afd.cBuffer );
+ Z_Free( afd.eBuffer );
+ FS_FCloseFile( afd.f );
+
+ Com_Printf( "Wrote %d:%d frames to %s\n", afd.numVideoFrames, afd.numAudioFrames, afd.fileName );
+
+ return true;
+}
+
+/*
+===============
+CL_VideoRecording
+===============
+*/
+bool CL_VideoRecording( void )
+{
+ return afd.fileOpen;
+}
diff --git a/src/client/cl_cgame.cpp b/src/client/cl_cgame.cpp
new file mode 100644
index 0000000..f16dc35
--- /dev/null
+++ b/src/client/cl_cgame.cpp
@@ -0,0 +1,1172 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
+
+This file is part of Tremulous.
+
+Tremulous 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.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+
+// cl_cgame.c -- client system interaction with client game
+
+#include "client.h"
+
+#ifdef USE_MUMBLE
+#include "libmumblelink.h"
+#endif
+#include "snd_public.h"
+
+/*
+====================
+CL_GetGameState
+====================
+*/
+static void CL_GetGameState( gameState_t *gs )
+{
+ *gs = cl.gameState;
+}
+
+/*
+====================
+CL_GetGlconfig
+====================
+*/
+static void CL_GetGlconfig( glconfig_t *glconfig )
+{
+ *glconfig = cls.glconfig;
+}
+
+
+/*
+====================
+CL_GetUserCmd
+====================
+*/
+static bool CL_GetUserCmd( int cmdNumber, usercmd_t *ucmd )
+{
+ // cmds[cmdNumber] is the last properly generated command
+
+ // can't return anything that we haven't created yet
+ if ( cmdNumber > cl.cmdNumber )
+ Com_Error( ERR_DROP, "CL_GetUserCmd: %i >= %i", cmdNumber, cl.cmdNumber );
+
+ // the usercmd has been overwritten in the wrapping
+ // buffer because it is too far out of date
+ if ( cmdNumber <= cl.cmdNumber - CMD_BACKUP )
+ return false;
+
+ *ucmd = cl.cmds[ cmdNumber & CMD_MASK ];
+
+ return true;
+}
+
+static int CL_GetCurrentCmdNumber( void )
+{
+ return cl.cmdNumber;
+}
+
+/*
+====================
+CL_GetCurrentSnapshotNumber
+====================
+*/
+static void CL_GetCurrentSnapshotNumber( int *snapshotNumber, int *serverTime )
+{
+ *snapshotNumber = cl.snap.messageNum;
+ *serverTime = cl.snap.serverTime;
+}
+
+/*
+====================
+CL_GetSnapshot
+====================
+*/
+bool CL_GetSnapshot( int snapshotNumber, snapshot_t *snapshot )
+{
+ clSnapshot_t *clSnap;
+
+ if ( snapshotNumber > cl.snap.messageNum ) {
+ Com_Error( ERR_DROP, "CL_GetSnapshot: snapshotNumber > cl.snapshot.messageNum" );
+ }
+
+ // if the frame has fallen out of the circular buffer, we can't return it
+ if ( cl.snap.messageNum - snapshotNumber >= PACKET_BACKUP )
+ return false;
+
+ // if the frame is not valid, we can't return it
+ clSnap = &cl.snapshots[snapshotNumber & PACKET_MASK];
+ if ( !clSnap->valid )
+ return false;
+
+ // if the entities in the frame have fallen out of their circular buffer,
+ // we can't return it
+ if ( cl.parseEntitiesNum - clSnap->parseEntitiesNum >= MAX_PARSE_ENTITIES )
+ return false;
+
+ // write the snapshot
+ snapshot->snapFlags = clSnap->snapFlags;
+ snapshot->ping = clSnap->ping;
+ snapshot->serverTime = clSnap->serverTime;
+ ::memcpy( snapshot->areamask, clSnap->areamask, sizeof( snapshot->areamask ) );
+
+ int count = clSnap->numEntities;
+ if ( count > MAX_ENTITIES_IN_SNAPSHOT )
+ {
+ Com_DPrintf( "CL_GetSnapshot: truncated %i entities to %i\n", count, MAX_ENTITIES_IN_SNAPSHOT );
+ count = MAX_ENTITIES_IN_SNAPSHOT;
+ }
+
+ if ( cls.cgInterface == 2 )
+ {
+ alternateSnapshot_t *altSnapshot = (alternateSnapshot_t *)snapshot;
+ altSnapshot->ps = clSnap->alternatePs;
+ altSnapshot->serverCommandSequence = clSnap->serverCommandNum;
+ altSnapshot->numEntities = count;
+
+ for ( int i = 0 ; i < count ; i++ )
+ {
+ entityState_t *es = &cl.parseEntities[ ( clSnap->parseEntitiesNum + i ) & (MAX_PARSE_ENTITIES-1) ];
+ ::memcpy( &altSnapshot->entities[i], es, (size_t)&((entityState_t *)0)->weaponAnim );
+ altSnapshot->entities[i].generic1 = es->generic1;
+ }
+ }
+ else
+ {
+ snapshot->ps = clSnap->ps;
+ snapshot->serverCommandSequence = clSnap->serverCommandNum;
+ snapshot->numEntities = count;
+ for ( int i = 0 ; i < count ; i++ )
+ {
+ snapshot->entities[i] =
+ cl.parseEntities[ ( clSnap->parseEntitiesNum + i ) & (MAX_PARSE_ENTITIES-1) ];
+ }
+ }
+
+ // FIXME: configstring changes and server commands!!!
+
+ return true;
+}
+
+/*
+=====================
+CL_SetUserCmdValue
+=====================
+*/
+void CL_SetUserCmdValue( int userCmdValue, float sensitivityScale ) {
+ cl.cgameUserCmdValue = userCmdValue;
+ cl.cgameSensitivity = sensitivityScale;
+}
+
+/*
+=====================
+CL_AddCgameCommand
+=====================
+*/
+void CL_AddCgameCommand( const char *cmdName ) {
+ Cmd_AddCommand( cmdName, NULL );
+}
+
+/*
+=====================
+CL_ConfigstringModified
+=====================
+*/
+void CL_ConfigstringModified( void )
+{
+ int idx = atoi( Cmd_Argv(1) );
+ if ( idx < 0 || idx >= MAX_CONFIGSTRINGS )
+ Com_Error( ERR_DROP, "CL_ConfigstringModified: bad index %i", idx );
+
+ // get everything after "cs <num>"
+ const char* s = Cmd_ArgsFrom(2);
+ const char* old = cl.gameState.stringData + cl.gameState.stringOffsets[ idx ];
+ if ( !strcmp(old, s) )
+ return;
+
+ // build the new gameState_t
+ gameState_t oldGs = cl.gameState;
+
+ ::memset( &cl.gameState, 0, sizeof( cl.gameState ) );
+
+ // leave the first 0 for uninitialized strings
+ cl.gameState.dataCount = 1;
+
+ const char* dup;
+ for ( int i = 0 ; i < MAX_CONFIGSTRINGS ; i++ )
+ {
+ if ( i == idx )
+ dup = s;
+ else
+ dup = oldGs.stringData + oldGs.stringOffsets[ i ];
+
+ if ( !dup[0] )
+ continue; // leave with the default empty string
+
+ int len = strlen(dup);
+ if ( len + 1 + cl.gameState.dataCount > MAX_GAMESTATE_CHARS )
+ Com_Error(ERR_DROP, "MAX_GAMESTATE_CHARS exceeded");
+
+ // append it to the gameState string buffer
+ cl.gameState.stringOffsets[ i ] = cl.gameState.dataCount;
+ ::memcpy( cl.gameState.stringData + cl.gameState.dataCount, dup, len + 1 );
+ cl.gameState.dataCount += len + 1;
+ }
+
+ if ( idx == CS_SYSTEMINFO )
+ {
+ // parse serverId and other cvars
+ CL_SystemInfoChanged();
+ }
+}
+
+
+/*
+===================
+CL_GetServerCommand
+
+Set up argc/argv for the given command
+===================
+*/
+static bool CL_GetServerCommand( int serverCommandNumber )
+{
+ const char *s;
+ const char *cmd;
+ static char bigConfigString[BIG_INFO_STRING];
+ int argc;
+
+ // if we have irretrievably lost a reliable command, drop the connection
+ if ( serverCommandNumber <= clc.serverCommandSequence - MAX_RELIABLE_COMMANDS )
+ {
+ // when a demo record was started after the client got a whole bunch of
+ // reliable commands then the client never got those first reliable commands
+ if ( clc.demoplaying )
+ return false;
+
+ Com_Error( ERR_DROP, "CL_GetServerCommand: a reliable command was cycled out" );
+ return false;
+ }
+
+ if ( serverCommandNumber > clc.serverCommandSequence )
+ {
+ Com_Error( ERR_DROP, "CL_GetServerCommand: requested a command not received" );
+ return false;
+ }
+
+ s = clc.serverCommands[ serverCommandNumber & ( MAX_RELIABLE_COMMANDS - 1 ) ];
+ clc.lastExecutedServerCommand = serverCommandNumber;
+
+ Com_DPrintf( "serverCommand: %i : %s\n", serverCommandNumber, s );
+
+rescan:
+ Cmd_TokenizeString( s );
+ cmd = Cmd_Argv(0);
+ argc = Cmd_Argc();
+
+ if ( !strcmp( cmd, "disconnect" ) )
+ {
+ // allow server to indicate why they were disconnected
+ if ( argc >= 2 )
+ Com_Error( ERR_SERVERDISCONNECT, "Server disconnected - %s", Cmd_Argv( 1 ) );
+ else
+ Com_Error( ERR_SERVERDISCONNECT, "Server disconnected" );
+ }
+
+ if ( !strcmp( cmd, "bcs0" ) )
+ {
+ Com_sprintf( bigConfigString, BIG_INFO_STRING, "cs %s \"%s", Cmd_Argv(1), Cmd_Argv(2) );
+ return false;
+ }
+
+ if ( !strcmp( cmd, "bcs1" ) )
+ {
+ s = Cmd_Argv(2);
+ if( strlen(bigConfigString) + strlen(s) >= BIG_INFO_STRING )
+ Com_Error( ERR_DROP, "bcs exceeded BIG_INFO_STRING" );
+
+ strcat( bigConfigString, s );
+ return false;
+ }
+
+ if ( !strcmp( cmd, "bcs2" ) )
+ {
+ s = Cmd_Argv(2);
+ if( strlen(bigConfigString) + strlen(s) + 1 >= BIG_INFO_STRING )
+ Com_Error( ERR_DROP, "bcs exceeded BIG_INFO_STRING" );
+
+ strcat( bigConfigString, s );
+ strcat( bigConfigString, "\"" );
+ s = bigConfigString;
+ goto rescan;
+ }
+
+ if ( !strcmp( cmd, "cs" ) )
+ {
+ CL_ConfigstringModified();
+ // reparse the string, because CL_ConfigstringModified may have done
+ // another Cmd_TokenizeString()
+ Cmd_TokenizeString( s );
+ return true;
+ }
+
+ if ( !strcmp( cmd, "map_restart" ) )
+ {
+ // clear notify lines and outgoing commands before passing
+ // the restart to the cgame
+ Con_ClearNotify();
+ // reparse the string, because Con_ClearNotify() may have done another
+ // Cmd_TokenizeString()
+ Cmd_TokenizeString( s );
+ ::memset( cl.cmds, 0, sizeof( cl.cmds ) );
+ return true;
+ }
+
+ // we may want to put a "connect to other server" command here
+
+ // cgame can now act on the command
+ return true;
+}
+
+
+/*
+====================
+CL_CM_LoadMap
+
+Just adds default parameters that cgame doesn't need to know about
+====================
+*/
+void CL_CM_LoadMap( const char *mapname ) {
+ int checksum;
+
+ CM_LoadMap( mapname, true, &checksum );
+}
+
+char * safe_strncpy(char *dest, const char *src, size_t n)
+{
+ char *ret = dest;
+ while (n > 0 && src[0])
+ {
+ *ret++ = *src++;
+ --n;
+ }
+ while (n > 0)
+ {
+ *ret++ = '\0';
+ --n;
+ }
+ return dest;
+}
+
+/*
+====================
+CL_ShutdonwCGame
+
+====================
+*/
+void CL_ShutdownCGame( void ) {
+ Key_SetCatcher( Key_GetCatcher( ) & ~KEYCATCH_CGAME );
+ cls.cgameStarted = false;
+ if ( !cls.cgame ) {
+ return;
+ }
+ VM_Call( cls.cgame, CG_SHUTDOWN );
+ VM_Free( cls.cgame );
+ cls.cgame = NULL;
+}
+
+static int FloatAsInt( float f ) {
+ floatint_t fi;
+ fi.f = f;
+ return fi.i;
+}
+
+static bool probingCG = false;
+
+/*
+====================
+CL_CgameSystemCalls
+
+The cgame module is making a system call
+====================
+*/
+intptr_t CL_CgameSystemCalls( intptr_t *args )
+{
+ if( cls.cgInterface == 2 && args[0] >= CG_R_SETCLIPREGION && args[0] < CG_MEMSET )
+ {
+ if( args[0] < CG_S_STOPBACKGROUNDTRACK - 1 )
+ args[0] += 1;
+
+ else if( args[0] < CG_S_STOPBACKGROUNDTRACK + 4 )
+ args[0] += CG_PARSE_ADD_GLOBAL_DEFINE - CG_S_STOPBACKGROUNDTRACK + 1;
+
+ else if( args[0] < CG_PARSE_ADD_GLOBAL_DEFINE + 4 )
+ args[0] -= 4;
+
+ else if( args[0] >= CG_PARSE_SOURCE_FILE_AND_LINE && args[0] <= CG_S_SOUNDDURATION )
+ args[0] = CG_PARSE_SOURCE_FILE_AND_LINE - 1337 - args[0] ;
+ }
+
+ switch( args[0] )
+ {
+ case CG_PRINT:
+ Com_Printf( "%s", (const char*)VMA(1) );
+ return 0;
+ case CG_ERROR:
+ if( probingCG )
+ {
+ cls.cgInterface = 2; // this is a 1.1.0 cgame
+ return 0;
+ }
+ Com_Error( ERR_DROP, "%s", (const char*)VMA(1) );
+ return 0;
+ case CG_MILLISECONDS:
+ return Sys_Milliseconds();
+ case CG_CVAR_REGISTER:
+ Cvar_Register( (vmCvar_t*)VMA(1), (const char*)VMA(2), (const char*)VMA(3), args[4] );
+ return 0;
+ case CG_CVAR_UPDATE:
+ Cvar_Update( (vmCvar_t*)VMA(1) );
+ return 0;
+ case CG_CVAR_SET:
+ Cvar_SetSafe( (const char*)VMA(1), (const char*)VMA(2) );
+ return 0;
+ case CG_CVAR_VARIABLESTRINGBUFFER:
+ Cvar_VariableStringBuffer( (const char*)VMA(1), (char*)VMA(2), args[3] );
+ return 0;
+ case CG_ARGC:
+ return Cmd_Argc();
+ case CG_ARGV:
+ Cmd_ArgvBuffer( args[1], (char*)VMA(2), args[3] );
+ return 0;
+ case CG_ARGS:
+ Cmd_ArgsBuffer( (char*)VMA(1), args[2] );
+ return 0;
+ case CG_LITERAL_ARGS:
+ Cmd_LiteralArgsBuffer( (char*)VMA(1), args[2] );
+ return 0;
+ case CG_FS_FOPENFILE:
+ return FS_FOpenFileByMode( (const char*)VMA(1), (fileHandle_t*)VMA(2), (FS_Mode)args[3] );
+ case CG_FS_READ:
+ FS_Read( VMA(1), args[2], args[3] );
+ return 0;
+ case CG_FS_WRITE:
+ FS_Write( VMA(1), args[2], args[3] );
+ return 0;
+ case CG_FS_FCLOSEFILE:
+ FS_FCloseFile( args[1] );
+ return 0;
+ case CG_FS_SEEK:
+ return FS_Seek( (fileHandle_t)args[1], args[2], (FS_Origin)args[3] );
+ case CG_FS_GETFILELIST:
+ return FS_GetFileList( (const char*)VMA(1), (const char*)VMA(2), (char*)VMA(3), args[4] );
+ case CG_SENDCONSOLECOMMAND:
+ Cbuf_AddText( (const char*)VMA(1) );
+ return 0;
+ case CG_ADDCOMMAND:
+ CL_AddCgameCommand( (const char*)VMA(1) );
+ return 0;
+ case CG_REMOVECOMMAND:
+ Cmd_RemoveCommandSafe( (const char*)VMA(1) );
+ return 0;
+ case CG_SENDCLIENTCOMMAND:
+ CL_AddReliableCommand((const char*)VMA(1), false);
+ return 0;
+ case CG_UPDATESCREEN:
+ // this is used during lengthy level loading, so pump message loop
+ // Com_EventLoop(); // FIXME: if a server restarts here, BAD THINGS HAPPEN!
+ // We can't call Com_EventLoop here, a restart will crash and this _does_ happen
+ // if there is a map change while we are downloading at pk3.
+ // ZOID
+ SCR_UpdateScreen();
+ return 0;
+ case CG_CM_LOADMAP:
+ CL_CM_LoadMap( (const char*)VMA(1) );
+ return 0;
+ case CG_CM_NUMINLINEMODELS:
+ return CM_NumInlineModels();
+ case CG_CM_INLINEMODEL:
+ return CM_InlineModel( args[1] );
+ case CG_CM_TEMPBOXMODEL:
+ return CM_TempBoxModel( (const float*)VMA(1), (const float*)VMA(2), false );
+ case CG_CM_TEMPCAPSULEMODEL:
+ return CM_TempBoxModel( (const float*)VMA(1), (const float*)VMA(2), true );
+ case CG_CM_POINTCONTENTS:
+ return CM_PointContents( (const float*)VMA(1), args[2] );
+ case CG_CM_TRANSFORMEDPOINTCONTENTS:
+ return CM_TransformedPointContents( (const float*)VMA(1), (clipHandle_t)args[2],
+ (const float*)VMA(3), (const float*)VMA(4) );
+ case CG_CM_BOXTRACE:
+ CM_BoxTrace( (trace_t*)VMA(1), (const float*)VMA(2), (const float*)VMA(3),
+ (float*)VMA(4), (float*)VMA(5), (clipHandle_t)args[6], args[7], TT_AABB );
+ return 0;
+ case CG_CM_CAPSULETRACE:
+ CM_BoxTrace( (trace_t*)VMA(1), (const float*)VMA(2), (const float*)VMA(3),
+ (float*)VMA(4), (float*)VMA(5), (clipHandle_t)args[6], args[7], TT_CAPSULE );
+ return 0;
+ case CG_CM_TRANSFORMEDBOXTRACE:
+ CM_TransformedBoxTrace( (trace_t*)VMA(1), (const float*)VMA(2), (const float*)VMA(3),
+ (float*)VMA(4), (float*)VMA(5), (clipHandle_t)args[6], args[7],
+ (const float*)VMA(8), (const float*)VMA(9), TT_AABB );
+ return 0;
+ case CG_CM_TRANSFORMEDCAPSULETRACE:
+ CM_TransformedBoxTrace( (trace_t*)VMA(1), (const float*)VMA(2), (const float*)VMA(3),
+ (float*)VMA(4), (float*)VMA(5), (clipHandle_t)args[6], args[7],
+ (const float*)VMA(8), (const float*)VMA(9), TT_CAPSULE );
+ return 0;
+ case CG_CM_BISPHERETRACE:
+ CM_BiSphereTrace( (trace_t*)VMA(1), (const float*)VMA(2), (const float*)VMA(3), VMF(4), VMF(5), (clipHandle_t)args[6], args[7] );
+ return 0;
+ case CG_CM_TRANSFORMEDBISPHERETRACE:
+ CM_TransformedBiSphereTrace( (trace_t*)VMA(1), (const float*)VMA(2), (const float*)VMA(3),
+ VMF(4), VMF(5), (clipHandle_t)args[6], args[7], (const float*)VMA(8) );
+ return 0;
+ case CG_CM_MARKFRAGMENTS:
+ {
+ float (&arg2)[3][3] = *reinterpret_cast<float (*)[3][3]>(VMA(2));
+ return re.MarkFragments( args[1], arg2, (const float*)VMA(3), args[4], (float*)VMA(5), args[6], (markFragment_t*)VMA(7) );
+ }
+ case CG_S_STARTSOUND:
+ S_StartSound( (float*)VMA(1), args[2], args[3], (sfxHandle_t)args[4] );
+ return 0;
+ case CG_S_STARTLOCALSOUND:
+ S_StartLocalSound( (sfxHandle_t)args[1], args[2] );
+ return 0;
+ case CG_S_CLEARLOOPINGSOUNDS:
+ S_ClearLoopingSounds( (bool)args[1] );
+ return 0;
+ case CG_S_ADDLOOPINGSOUND:
+ S_AddLoopingSound( args[1], (const float*)VMA(2), (const float*)VMA(3), (sfxHandle_t)args[4] );
+ return 0;
+ case CG_S_ADDREALLOOPINGSOUND:
+ S_AddRealLoopingSound( args[1], (const float*)VMA(2), (const float*)VMA(3), (sfxHandle_t)args[4] );
+ return 0;
+ case CG_S_STOPLOOPINGSOUND:
+ S_StopLoopingSound( args[1] );
+ return 0;
+ case CG_S_UPDATEENTITYPOSITION:
+ S_UpdateEntityPosition( args[1], (const float*)VMA(2) );
+ return 0;
+ case CG_S_RESPATIALIZE:
+ {
+ float (&arg3)[3][3] = *reinterpret_cast<float (*)[3][3]>(VMA(3));
+ S_Respatialize( args[1], (const float*)VMA(2), arg3, args[4] );
+ return 0;
+ }
+ case CG_S_REGISTERSOUND:
+ return S_RegisterSound( (const char*)VMA(1), (bool)args[2] );
+ case CG_S_SOUNDDURATION:
+ return S_SoundDuration( args[1] );
+ case CG_S_STARTBACKGROUNDTRACK:
+ S_StartBackgroundTrack( (const char*)VMA(1), (const char*)VMA(2) );
+ return 0;
+ case CG_R_LOADWORLDMAP:
+ re.LoadWorld( (const char*)VMA(1) );
+ return 0;
+ case CG_R_REGISTERMODEL:
+ return re.RegisterModel( (const char*)VMA(1) );
+ case CG_R_REGISTERSKIN:
+ return re.RegisterSkin( (const char*)VMA(1) );
+ case CG_R_REGISTERSHADER:
+ return re.RegisterShader( (const char*)VMA(1) );
+ case CG_R_REGISTERSHADERNOMIP:
+ return re.RegisterShaderNoMip( (const char*)VMA(1) );
+ case CG_R_REGISTERFONT:
+ re.RegisterFont( (const char*)VMA(1), args[2], (fontInfo_t*)VMA(3));
+ return 0;
+ case CG_R_CLEARSCENE:
+ re.ClearScene();
+ return 0;
+ case CG_R_ADDREFENTITYTOSCENE:
+ re.AddRefEntityToScene( (const refEntity_t*)VMA(1) );
+ return 0;
+ case CG_R_ADDPOLYTOSCENE:
+ re.AddPolyToScene( (qhandle_t)args[1], args[2], (const polyVert_t*)VMA(3), 1 );
+ return 0;
+ case CG_R_ADDPOLYSTOSCENE:
+ re.AddPolyToScene( (qhandle_t)args[1], args[2], (const polyVert_t*)VMA(3), args[4] );
+ return 0;
+ case CG_R_LIGHTFORPOINT:
+ return re.LightForPoint( (float*)VMA(1), (float*)VMA(2), (float*)VMA(3), (float*)VMA(4) );
+ case CG_R_ADDLIGHTTOSCENE:
+ re.AddLightToScene( (const float*)VMA(1), VMF(2), VMF(3), VMF(4), VMF(5) );
+ return 0;
+ case CG_R_ADDADDITIVELIGHTTOSCENE:
+ re.AddAdditiveLightToScene( (const float*)VMA(1), VMF(2), VMF(3), VMF(4), VMF(5) );
+ return 0;
+ case CG_R_RENDERSCENE:
+ re.RenderScene( (const refdef_t*)VMA(1) );
+ return 0;
+ case CG_R_SETCOLOR:
+ re.SetColor( (const float*)VMA(1) );
+ return 0;
+ case CG_R_SETCLIPREGION:
+ re.SetClipRegion( (const float*)VMA(1) );
+ return 0;
+ case CG_R_DRAWSTRETCHPIC:
+ re.DrawStretchPic( VMF(1), VMF(2), VMF(3), VMF(4), VMF(5), VMF(6), VMF(7), VMF(8), (qhandle_t)args[9] );
+ return 0;
+ case CG_R_MODELBOUNDS:
+ re.ModelBounds( (qhandle_t)args[1], (float*)VMA(2), (float*)VMA(3) );
+ return 0;
+ case CG_R_LERPTAG:
+ return re.LerpTag( (orientation_t*)VMA(1), args[2], args[3], args[4], VMF(5), (const char*)VMA(6) );
+ case CG_GETGLCONFIG:
+ CL_GetGlconfig( (glconfig_t*)VMA(1) );
+ return 0;
+ case CG_GETGAMESTATE:
+ CL_GetGameState( (gameState_t*)VMA(1) );
+ return 0;
+ case CG_GETCURRENTSNAPSHOTNUMBER:
+ CL_GetCurrentSnapshotNumber( (int*)VMA(1), (int*)VMA(2) );
+ return 0;
+ case CG_GETSNAPSHOT:
+ return CL_GetSnapshot( args[1], (snapshot_t*)VMA(2) );
+ case CG_GETSERVERCOMMAND:
+ return CL_GetServerCommand( args[1] );
+ case CG_GETCURRENTCMDNUMBER:
+ return CL_GetCurrentCmdNumber();
+ case CG_GETUSERCMD:
+ return CL_GetUserCmd( args[1], (usercmd_t*)VMA(2) );
+ case CG_SETUSERCMDVALUE:
+ CL_SetUserCmdValue( args[1], VMF(2) );
+ return 0;
+ case CG_MEMORY_REMAINING:
+ return Hunk_MemoryRemaining();
+ case CG_KEY_ISDOWN:
+ return Key_IsDown( args[1] );
+ case CG_KEY_GETCATCHER:
+ return Key_GetCatcher();
+ case CG_KEY_SETCATCHER:
+ // don't allow the cgame module to toggle the console
+ Key_SetCatcher( ( args[1] & ~KEYCATCH_CONSOLE ) | ( Key_GetCatcher() & KEYCATCH_CONSOLE ) );
+ return 0;
+ case CG_KEY_GETKEY:
+ return Key_GetKey( (const char*)VMA(1) );
+
+ case CG_GETDEMOSTATE:
+ return CL_DemoState( );
+ case CG_GETDEMOPOS:
+ return CL_DemoPos( );
+ case CG_GETDEMONAME:
+ CL_DemoName( (char*)VMA(1), args[2] );
+ return 0;
+
+ case CG_KEY_KEYNUMTOSTRINGBUF:
+ Key_KeynumToStringBuf( args[1], (char*)VMA(2), args[3] );
+ return 0;
+ case CG_KEY_GETBINDINGBUF:
+ Key_GetBindingBuf( args[1], (char*)VMA(2), args[3] );
+ return 0;
+ case CG_KEY_SETBINDING:
+ Key_SetBinding( args[1], (const char*)VMA(2) );
+ return 0;
+
+ case CG_PARSE_ADD_GLOBAL_DEFINE:
+ return Parse_AddGlobalDefine( (char*)VMA(1) );
+ case CG_PARSE_LOAD_SOURCE:
+ return Parse_LoadSourceHandle( (const char*)VMA(1) );
+ case CG_PARSE_FREE_SOURCE:
+ return Parse_FreeSourceHandle( args[1] );
+ case CG_PARSE_READ_TOKEN:
+ return Parse_ReadTokenHandle( args[1], (pc_token_t*)VMA(2) );
+ case CG_PARSE_SOURCE_FILE_AND_LINE:
+ return Parse_SourceFileAndLine( args[1], (char*)VMA(2), (int*)VMA(3) );
+
+ case CG_KEY_SETOVERSTRIKEMODE:
+ Key_SetOverstrikeMode( (bool)args[1] );
+ return 0;
+
+ case CG_KEY_GETOVERSTRIKEMODE:
+ return Key_GetOverstrikeMode( );
+
+ case CG_FIELD_COMPLETELIST:
+ Field_CompleteList( (char*)VMA(1) );
+ return 0;
+
+ case CG_MEMSET:
+ ::memset( VMA(1), args[2], args[3] );
+ return 0;
+ case CG_MEMCPY:
+ ::memcpy( VMA(1), VMA(2), args[3] );
+ return 0;
+ case CG_STRNCPY:
+ safe_strncpy( (char*)VMA(1), (const char*)VMA(2), args[3] );
+ return args[1];
+ case CG_SIN:
+ return FloatAsInt( sin( VMF(1) ) );
+ case CG_COS:
+ return FloatAsInt( cos( VMF(1) ) );
+ case CG_ATAN2:
+ return FloatAsInt( atan2( VMF(1), VMF(2) ) );
+ case CG_SQRT:
+ return FloatAsInt( sqrt( VMF(1) ) );
+ case CG_FLOOR:
+ return FloatAsInt( floor( VMF(1) ) );
+ case CG_CEIL:
+ return FloatAsInt( ceil( VMF(1) ) );
+ case CG_ACOS:
+ return FloatAsInt( Q_acos( VMF(1) ) );
+
+ case CG_S_STOPBACKGROUNDTRACK:
+ S_StopBackgroundTrack();
+ return 0;
+
+ case CG_REAL_TIME:
+ return Com_RealTime( (qtime_t*)VMA(1) );
+ case CG_SNAPVECTOR:
+ Q_SnapVector((float*)VMA(1));
+ return 0;
+
+ case CG_CIN_PLAYCINEMATIC:
+ return CIN_PlayCinematic((const char*)VMA(1), args[2], args[3], args[4], args[5], args[6]);
+
+ case CG_CIN_STOPCINEMATIC:
+ return CIN_StopCinematic(args[1]);
+
+ case CG_CIN_RUNCINEMATIC:
+ return CIN_RunCinematic(args[1]);
+
+ case CG_CIN_DRAWCINEMATIC:
+ CIN_DrawCinematic(args[1]);
+ return 0;
+
+ case CG_CIN_SETEXTENTS:
+ CIN_SetExtents(args[1], args[2], args[3], args[4], args[5]);
+ return 0;
+
+ case CG_R_REMAP_SHADER:
+ re.RemapShader( (const char*)VMA(1), (const char*)VMA(2), (const char*)VMA(3) );
+ return 0;
+
+ case CG_GET_ENTITY_TOKEN:
+ return re.GetEntityToken( (char*)VMA(1), args[2] );
+ case CG_R_INPVS:
+ return re.inPVS( (const float*)VMA(1), (const float*)VMA(2) );
+
+ default:
+ assert(0);
+ Com_Error( ERR_DROP, "Bad cgame system trap: %ld", (long int) args[0] );
+ }
+ return 0;
+}
+
+
+/*
+====================
+CL_InitCGame
+
+Should only be called by CL_StartHunkUsers
+====================
+*/
+void CL_InitCGame( void ) {
+ const char *info;
+ const char *mapname;
+ int t1, t2;
+ char backup[ MAX_STRING_CHARS ];
+ vmInterpret_t interpret;
+
+ t1 = Sys_Milliseconds();
+
+ // find the current mapname
+ info = cl.gameState.stringData + cl.gameState.stringOffsets[ CS_SERVERINFO ];
+ mapname = Info_ValueForKey( info, "mapname" );
+ Com_sprintf( cl.mapname, sizeof( cl.mapname ), "maps/%s.bsp", mapname );
+
+ // load the dll or bytecode
+ interpret = (vmInterpret_t)Cvar_VariableValue("vm_cgame");
+ if(cl_connectedToPureServer)
+ {
+ // if sv_pure is set we only allow qvms to be loaded
+ if(interpret != VMI_COMPILED && interpret != VMI_BYTECODE)
+ interpret = VMI_COMPILED;
+ }
+
+ cls.cgame = VM_Create( "cgame", CL_CgameSystemCalls, interpret );
+ if ( !cls.cgame ) {
+ Com_Error( ERR_DROP, "VM_Create on cgame failed" );
+ }
+ clc.state = CA_LOADING;
+
+ Cvar_VariableStringBuffer( "cl_voipSendTarget", backup, sizeof( backup ) );
+ Cvar_Set( "cl_voipSendTarget", "" );
+
+ // Probe 1.1 or gpp cgame
+ cls.cgInterface = 0;
+ probingCG = true;
+ VM_Call( cls.cgame, CG_VOIP_STRING );
+ probingCG = false;
+
+ Cvar_Set( "cl_voipSendTarget", backup );
+
+ if ( ( clc.netchan.alternateProtocol == 2 ) != ( cls.cgInterface == 2 ) ) {
+ Com_Error( ERR_DROP, "%s protocol %i, but a cgame module using the %s interface was found",
+ ( clc.demoplaying ? "Demo was recorded using" : "Server uses" ),
+ ( clc.netchan.alternateProtocol == 0 ? PROTOCOL_VERSION : clc.netchan.alternateProtocol == 1 ? 70 : 69 ),
+ ( cls.cgInterface == 2 ? "1.1" : "non-1.1" ) );
+ }
+
+ // init for this gamestate
+ // use the lastExecutedServerCommand instead of the serverCommandSequence
+ // otherwise server commands sent just before a gamestate are dropped
+ VM_Call( cls.cgame, CG_INIT, clc.serverMessageSequence, clc.lastExecutedServerCommand, clc.clientNum );
+
+ // reset any CVAR_CHEAT cvars registered by cgame
+ if ( !clc.demoplaying && !cl_connectedToCheatServer )
+ Cvar_SetCheatState();
+
+ // we will send a usercmd this frame, which
+ // will cause the server to send us the first snapshot
+ clc.state = CA_PRIMED;
+
+ t2 = Sys_Milliseconds();
+
+ Com_Printf( "CL_InitCGame: %5.2f seconds\n", (t2-t1)/1000.0 );
+
+ // have the renderer touch all its images, so they are present
+ // on the card even if the driver does deferred loading
+ re.EndRegistration();
+
+ // make sure everything is paged in
+ if (!Sys_LowPhysicalMemory()) {
+ Com_TouchMemory();
+
+ CL_ProtocolSpecificCommandsInit();
+ }
+
+ // clear anything that got printed
+ Con_ClearNotify ();
+}
+
+
+/*
+====================
+CL_GameCommand
+
+See if the current console command is claimed by the cgame
+====================
+*/
+bool CL_GameCommand( void )
+{
+ if ( !cls.cgame )
+ return false;
+
+ return (bool)VM_Call( cls.cgame, CG_CONSOLE_COMMAND );
+}
+
+/*
+====================
+CL_GameConsoleText
+====================
+*/
+void CL_GameConsoleText( void )
+{
+ if ( !cls.cgame )
+ return;
+
+ VM_Call( cls.cgame, CG_CONSOLE_TEXT );
+}
+
+/*
+=====================
+CL_CGameRendering
+=====================
+*/
+void CL_CGameRendering( stereoFrame_t stereo )
+{
+ VM_Call( cls.cgame, CG_DRAW_ACTIVE_FRAME, cl.serverTime, stereo, clc.demoplaying );
+ VM_Debug( 0 );
+}
+
+
+/*
+=================
+CL_AdjustTimeDelta
+
+Adjust the clients view of server time.
+
+We attempt to have cl.serverTime exactly equal the server's view
+of time plus the timeNudge, but with variable latencies over
+the internet it will often need to drift a bit to match conditions.
+
+Our ideal time would be to have the adjusted time approach, but not pass,
+the very latest snapshot.
+
+Adjustments are only made when a new snapshot arrives with a rational
+latency, which keeps the adjustment process framerate independent and
+prevents massive overadjustment during times of significant packet loss
+or bursted delayed packets.
+=================
+*/
+
+#define RESET_TIME 500
+
+void CL_AdjustTimeDelta( void ) {
+ int newDelta;
+ int deltaDelta;
+
+ cl.newSnapshots = false;
+
+ // the delta never drifts when replaying a demo
+ if ( clc.demoplaying ) {
+ return;
+ }
+
+ newDelta = cl.snap.serverTime - cls.realtime;
+ deltaDelta = abs( newDelta - cl.serverTimeDelta );
+
+ if ( deltaDelta > RESET_TIME ) {
+ cl.serverTimeDelta = newDelta;
+ cl.oldServerTime = cl.snap.serverTime; // FIXME: is this a problem for cgame?
+ cl.serverTime = cl.snap.serverTime;
+ if ( cl_showTimeDelta->integer ) {
+ Com_Printf( "<RESET> " );
+ }
+ } else if ( deltaDelta > 100 ) {
+ // fast adjust, cut the difference in half
+ if ( cl_showTimeDelta->integer ) {
+ Com_Printf( "<FAST> " );
+ }
+ cl.serverTimeDelta = ( cl.serverTimeDelta + newDelta ) >> 1;
+ } else {
+ // slow drift adjust, only move 1 or 2 msec
+
+ // if any of the frames between this and the previous snapshot
+ // had to be extrapolated, nudge our sense of time back a little
+ // the granularity of +1 / -2 is too high for timescale modified frametimes
+ if ( com_timescale->value == 0 || com_timescale->value == 1 ) {
+ if ( cl.extrapolatedSnapshot ) {
+ cl.extrapolatedSnapshot = false;
+ cl.serverTimeDelta -= 2;
+ } else {
+ // otherwise, move our sense of time forward to minimize total latency
+ cl.serverTimeDelta++;
+ }
+ }
+ }
+
+ if ( cl_showTimeDelta->integer ) {
+ Com_Printf( "%i ", cl.serverTimeDelta );
+ }
+}
+
+
+/*
+==================
+CL_FirstSnapshot
+==================
+*/
+void CL_FirstSnapshot( void ) {
+ // ignore snapshots that don't have entities
+ if ( cl.snap.snapFlags & SNAPFLAG_NOT_ACTIVE ) {
+ return;
+ }
+ clc.state = CA_ACTIVE;
+
+ // set the timedelta so we are exactly on this first frame
+ cl.serverTimeDelta = cl.snap.serverTime - cls.realtime;
+ cl.oldServerTime = cl.snap.serverTime;
+
+ clc.timeDemoBaseTime = cl.snap.serverTime;
+
+ // if this is the first frame of active play,
+ // execute the contents of activeAction now
+ // this is to allow scripting a timedemo to start right
+ // after loading
+ if ( cl_activeAction->string[0] ) {
+ Cbuf_AddText( cl_activeAction->string );
+ Cvar_Set( "activeAction", "" );
+ }
+
+#ifdef USE_MUMBLE
+ if ((cl_useMumble->integer) && !mumble_islinked()) {
+ int ret = mumble_link(CLIENT_WINDOW_TITLE);
+ Com_Printf("Mumble: Linking to Mumble application %s\n", ret==0?"ok":"failed");
+ }
+#endif
+
+#ifdef USE_VOIP
+ if (!clc.voipCodecInitialized) {
+ int i;
+ int error;
+
+ clc.opusEncoder = opus_encoder_create(48000, 1, OPUS_APPLICATION_VOIP, &error);
+
+ if ( error ) {
+ Com_DPrintf("VoIP: Error opus_encoder_create %d\n", error);
+ return;
+ }
+
+ for (i = 0; i < MAX_CLIENTS; i++) {
+ clc.opusDecoder[i] = opus_decoder_create(48000, 1, &error);
+ if ( error ) {
+ Com_DPrintf("VoIP: Error opus_decoder_create(%d) %d\n", i, error);
+ return;
+ }
+ clc.voipIgnore[i] = false;
+ clc.voipGain[i] = 1.0f;
+ }
+ clc.voipCodecInitialized = true;
+ clc.voipMuteAll = false;
+ Cmd_AddCommand ("voip", CL_Voip_f);
+ Cvar_Set("cl_voipSendTarget", "spatial");
+ ::memset(clc.voipTargets, ~0, sizeof(clc.voipTargets));
+ }
+#endif
+}
+
+/*
+==================
+CL_SetCGameTime
+==================
+*/
+void CL_SetCGameTime( void ) {
+ // getting a valid frame message ends the connection process
+ if ( clc.state != CA_ACTIVE ) {
+ if ( clc.state != CA_PRIMED ) {
+ return;
+ }
+ if ( clc.demoplaying ) {
+ // we shouldn't get the first snapshot on the same frame
+ // as the gamestate, because it causes a bad time skip
+ if ( !clc.firstDemoFrameSkipped ) {
+ clc.firstDemoFrameSkipped = true;
+ return;
+ }
+ CL_ReadDemoMessage();
+ }
+ if ( cl.newSnapshots ) {
+ cl.newSnapshots = false;
+ CL_FirstSnapshot();
+ }
+ if ( clc.state != CA_ACTIVE ) {
+ return;
+ }
+ }
+
+ // if we have gotten to this point, cl.snap is guaranteed to be valid
+ if ( !cl.snap.valid ) {
+ Com_Error( ERR_DROP, "CL_SetCGameTime: !cl.snap.valid" );
+ }
+
+ // allow pause in single player
+ if ( sv_paused->integer && CL_CheckPaused() && com_sv_running->integer ) {
+ // paused
+ return;
+ }
+
+ if ( cl.snap.serverTime < cl.oldFrameServerTime ) {
+ Com_Error( ERR_DROP, "cl.snap.serverTime < cl.oldFrameServerTime" );
+ }
+ cl.oldFrameServerTime = cl.snap.serverTime;
+
+
+ // get our current view of time
+
+ if ( clc.demoplaying && cl_freezeDemo->integer ) {
+ // cl_freezeDemo is used to lock a demo in place for single frame advances
+
+ } else {
+ // cl_timeNudge is a user adjustable cvar that allows more
+ // or less latency to be added in the interest of better
+ // smoothness or better responsiveness.
+ int tn;
+
+ tn = cl_timeNudge->integer;
+ if (tn<-30) {
+ tn = -30;
+ } else if (tn>30) {
+ tn = 30;
+ }
+
+ cl.serverTime = cls.realtime + cl.serverTimeDelta - tn;
+
+ // guarantee that time will never flow backwards, even if
+ // serverTimeDelta made an adjustment or cl_timeNudge was changed
+ if ( cl.serverTime < cl.oldServerTime ) {
+ cl.serverTime = cl.oldServerTime;
+ }
+ cl.oldServerTime = cl.serverTime;
+
+ // note if we are almost past the latest frame (without timeNudge),
+ // so we will try and adjust back a bit when the next snapshot arrives
+ if ( cls.realtime + cl.serverTimeDelta >= cl.snap.serverTime - 5 ) {
+ cl.extrapolatedSnapshot = true;
+ }
+ }
+
+ // if we have gotten new snapshots, drift serverTimeDelta
+ // don't do this every frame, or a period of packet loss would
+ // make a huge adjustment
+ if ( cl.newSnapshots ) {
+ CL_AdjustTimeDelta();
+ }
+
+ if ( !clc.demoplaying ) {
+ return;
+ }
+
+ // if we are playing a demo back, we can just keep reading
+ // messages from the demo file until the cgame definately
+ // has valid snapshots to interpolate between
+
+ // a timedemo will always use a deterministic set of time samples
+ // no matter what speed machine it is run on,
+ // while a normal demo may have different time samples
+ // each time it is played back
+ if ( cl_timedemo->integer ) {
+ int now = Sys_Milliseconds( );
+ int frameDuration;
+
+ if (!clc.timeDemoStart) {
+ clc.timeDemoStart = clc.timeDemoLastFrame = now;
+ clc.timeDemoMinDuration = INT_MAX;
+ clc.timeDemoMaxDuration = 0;
+ }
+
+ frameDuration = now - clc.timeDemoLastFrame;
+ clc.timeDemoLastFrame = now;
+
+ // Ignore the first measurement as it'll always be 0
+ if( clc.timeDemoFrames > 0 )
+ {
+ if( frameDuration > clc.timeDemoMaxDuration )
+ clc.timeDemoMaxDuration = frameDuration;
+
+ if( frameDuration < clc.timeDemoMinDuration )
+ clc.timeDemoMinDuration = frameDuration;
+
+ // 255 ms = about 4fps
+ if( frameDuration > UCHAR_MAX )
+ frameDuration = UCHAR_MAX;
+
+ clc.timeDemoDurations[ ( clc.timeDemoFrames - 1 ) %
+ MAX_TIMEDEMO_DURATIONS ] = frameDuration;
+ }
+
+ clc.timeDemoFrames++;
+ cl.serverTime = clc.timeDemoBaseTime + clc.timeDemoFrames * 50;
+ }
+
+ while ( cl.serverTime >= cl.snap.serverTime ) {
+ // feed another messag, which should change
+ // the contents of cl.snap
+ CL_ReadDemoMessage();
+ if ( clc.state != CA_ACTIVE ) {
+ return; // end of demo
+ }
+ }
+}
diff --git a/src/client/cl_cin.cpp b/src/client/cl_cin.cpp
new file mode 100644
index 0000000..6326e89
--- /dev/null
+++ b/src/client/cl_cin.cpp
@@ -0,0 +1,1937 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
+
+This file is part of Tremulous.
+
+Tremulous 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.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+
+/*****************************************************************************
+ * name: cl_cin.c
+ *
+ * desc: video and cinematic playback
+ *
+ * $Archive: /MissionPack/code/client/cl_cin.c $
+ *
+ * cl_glconfig.hwtype trtypes 3dfx/ragepro need 256x256
+ *
+ *****************************************************************************/
+
+#include "client.h"
+#include "snd_local.h"
+
+#define MAXSIZE 8
+#define MINSIZE 4
+
+#define DEFAULT_CIN_WIDTH 512
+#define DEFAULT_CIN_HEIGHT 512
+
+#define ROQ_QUAD 0x1000
+#define ROQ_QUAD_INFO 0x1001
+#define ROQ_CODEBOOK 0x1002
+#define ROQ_QUAD_VQ 0x1011
+#define ROQ_QUAD_JPEG 0x1012
+#define ROQ_QUAD_HANG 0x1013
+#define ROQ_PACKET 0x1030
+#define ZA_SOUND_MONO 0x1020
+#define ZA_SOUND_STEREO 0x1021
+
+#define MAX_VIDEO_HANDLES 16
+
+static void RoQ_init(void);
+
+/******************************************************************************
+*
+* Class: trFMV
+*
+* Description: RoQ/RnR manipulation routines
+* not entirely complete for first run
+*
+******************************************************************************/
+
+static long ROQ_YY_tab[256];
+static long ROQ_UB_tab[256];
+static long ROQ_UG_tab[256];
+static long ROQ_VG_tab[256];
+static long ROQ_VR_tab[256];
+static unsigned short vq2[256 * 16 * 4];
+static unsigned short vq4[256 * 64 * 4];
+static unsigned short vq8[256 * 256 * 4];
+
+struct cinematics_t
+{
+ byte linbuf[DEFAULT_CIN_WIDTH * DEFAULT_CIN_HEIGHT * 4 * 2];
+ byte file[65536];
+ short sqrTable[256];
+
+ int mcomp[256];
+ byte *qStatus[2][32768];
+
+ long oldXOff;
+ long oldYOff;
+ long oldysize;
+ long oldxsize;
+
+ int currentHandle;
+};
+
+struct cin_cache
+{
+ char fileName[MAX_OSPATH];
+
+ int CIN_WIDTH;
+ int CIN_HEIGHT;
+
+ int xpos;
+ int ypos;
+ int width;
+ int height;
+
+ bool looping;
+ bool holdAtEnd;
+ bool dirty;
+ bool alterGameState;
+ bool silent;
+ bool shader;
+
+ fileHandle_t iFile;
+ e_status status;
+
+ int startTime;
+ int lastTime;
+ long tfps;
+ long RoQPlayed;
+ long ROQSize;
+ unsigned int RoQFrameSize;
+ long onQuad;
+ long numQuads;
+ long samplesPerLine;
+ unsigned int roq_id;
+ long screenDelta;
+
+ void (*VQ0)(byte *status, void *qdata);
+ void (*VQ1)(byte *status, void *qdata);
+ void (*VQNormal)(byte *status, void *qdata);
+ void (*VQBuffer)(byte *status, void *qdata);
+
+ long samplesPerPixel; // defaults to 2
+ byte *gray;
+ unsigned int xsize, ysize, maxsize, minsize;
+
+ bool half;
+ bool smootheddouble;
+ bool inMemory;
+ long normalBuffer0;
+ long roq_flags;
+ long roqF0;
+ long roqF1;
+ long t[2];
+ long roqFPS;
+ int playonwalls;
+ byte *buf;
+ long drawX;
+ long drawY;
+};
+
+static cinematics_t cin;
+static cin_cache cinTable[MAX_VIDEO_HANDLES];
+static int currentHandle = -1;
+static int CL_handle = -1;
+
+extern int s_soundtime; // sample PAIRS
+
+void CIN_CloseAllVideos(void)
+{
+ int i;
+
+ for (i = 0; i < MAX_VIDEO_HANDLES; i++)
+ {
+ if (cinTable[i].fileName[0] != 0)
+ {
+ CIN_StopCinematic(i);
+ }
+ }
+}
+
+static int CIN_HandleForVideo(void)
+{
+ int i;
+
+ for (i = 0; i < MAX_VIDEO_HANDLES; i++)
+ {
+ if (cinTable[i].fileName[0] == 0)
+ {
+ return i;
+ }
+ }
+ Com_Error(ERR_DROP, "CIN_HandleForVideo: none free");
+ return -1;
+}
+
+//-----------------------------------------------------------------------------
+// RllSetupTable
+//
+// Allocates and initializes the square table.
+//
+// Parameters: None
+//
+// Returns: Nothing
+//-----------------------------------------------------------------------------
+static void RllSetupTable(void)
+{
+ int z;
+
+ for (z = 0; z < 128; z++)
+ {
+ cin.sqrTable[z] = (short)(z * z);
+ cin.sqrTable[z + 128] = (short)(-cin.sqrTable[z]);
+ }
+}
+
+#if 0
+//-----------------------------------------------------------------------------
+// RllDecodeMonoToMono
+//
+// Decode mono source data into a mono buffer.
+//
+// Parameters: from -> buffer holding encoded data
+// to -> buffer to hold decoded data
+// size = number of bytes of input (= # of shorts of output)
+// signedOutput = 0 for unsigned output, non-zero for signed output
+// flag = flags from asset header
+//
+// Returns: Number of samples placed in output buffer
+//-----------------------------------------------------------------------------
+long RllDecodeMonoToMono(unsigned char *from, short *to, unsigned int size, char signedOutput, unsigned short flag)
+{
+ unsigned int z;
+ int prev;
+
+ if (signedOutput)
+ prev = flag - 0x8000;
+ else
+ prev = flag;
+
+ for (z = 0; z < size; z++)
+ {
+ prev = to[z] = (short)(prev + cin.sqrTable[from[z]]);
+ }
+ return size; //*sizeof(short));
+}
+#endif
+
+//-----------------------------------------------------------------------------
+// RllDecodeMonoToStereo
+//
+// Decode mono source data into a stereo buffer. Output is 4 times the number
+// of bytes in the input.
+//
+// Parameters: from -> buffer holding encoded data
+// to -> buffer to hold decoded data
+// size = number of bytes of input (= 1/4 # of bytes of output)
+// signedOutput = 0 for unsigned output, non-zero for signed output
+// flag = flags from asset header
+//
+// Returns: Number of samples placed in output buffer
+//-----------------------------------------------------------------------------
+static long RllDecodeMonoToStereo(unsigned char *from, short *to, unsigned int size, char signedOutput, unsigned short flag)
+{
+ unsigned int z;
+ int prev;
+
+ if (signedOutput)
+ prev = flag - 0x8000;
+ else
+ prev = flag;
+
+ for (z = 0; z < size; z++)
+ {
+ prev = (short)(prev + cin.sqrTable[from[z]]);
+ to[z * 2 + 0] = to[z * 2 + 1] = (short)(prev);
+ }
+
+ return size; // * 2 * sizeof(short));
+}
+
+//-----------------------------------------------------------------------------
+// RllDecodeStereoToStereo
+//
+// Decode stereo source data into a stereo buffer.
+//
+// Parameters: from -> buffer holding encoded data
+// to -> buffer to hold decoded data
+// size = number of bytes of input (= 1/2 # of bytes of output)
+// signedOutput = 0 for unsigned output, non-zero for signed output
+// flag = flags from asset header
+//
+// Returns: Number of samples placed in output buffer
+//-----------------------------------------------------------------------------
+static long RllDecodeStereoToStereo(unsigned char *from, short *to, unsigned int size, char signedOutput, unsigned short flag)
+{
+ unsigned int z;
+ unsigned char *zz = from;
+ int prevL, prevR;
+
+ if (signedOutput)
+ {
+ prevL = (flag & 0xff00) - 0x8000;
+ prevR = ((flag & 0x00ff) << 8) - 0x8000;
+ }
+ else
+ {
+ prevL = flag & 0xff00;
+ prevR = (flag & 0x00ff) << 8;
+ }
+
+ for (z = 0; z < size; z += 2)
+ {
+ prevL = (short)(prevL + cin.sqrTable[*zz++]);
+ prevR = (short)(prevR + cin.sqrTable[*zz++]);
+ to[z + 0] = (short)(prevL);
+ to[z + 1] = (short)(prevR);
+ }
+
+ return (size >> 1); //*sizeof(short));
+}
+
+#if 0
+//-----------------------------------------------------------------------------
+// RllDecodeStereoToMono
+//
+// Decode stereo source data into a mono buffer.
+//
+// Parameters: from -> buffer holding encoded data
+// to -> buffer to hold decoded data
+// size = number of bytes of input (= # of bytes of output)
+// signedOutput = 0 for unsigned output, non-zero for signed output
+// flag = flags from asset header
+//
+// Returns: Number of samples placed in output buffer
+//-----------------------------------------------------------------------------
+long RllDecodeStereoToMono(unsigned char *from, short *to, unsigned int size, char signedOutput, unsigned short flag)
+{
+ unsigned int z;
+ int prevL, prevR;
+
+ if (signedOutput)
+ {
+ prevL = (flag & 0xff00) - 0x8000;
+ prevR = ((flag & 0x00ff) << 8) - 0x8000;
+ }
+ else
+ {
+ prevL = flag & 0xff00;
+ prevR = (flag & 0x00ff) << 8;
+ }
+
+ for (z = 0; z < size; z += 1)
+ {
+ prevL = prevL + cin.sqrTable[from[z * 2]];
+ prevR = prevR + cin.sqrTable[from[z * 2 + 1]];
+ to[z] = (short)((prevL + prevR) / 2);
+ }
+
+ return size;
+}
+#endif
+
+/******************************************************************************
+*
+* Function:
+*
+* Description:
+*
+******************************************************************************/
+
+static void move8_32(byte *src, byte *dst, int spl)
+{
+ int i;
+
+ for (i = 0; i < 8; ++i)
+ {
+ memcpy(dst, src, 32);
+ src += spl;
+ dst += spl;
+ }
+}
+
+/******************************************************************************
+*
+* Function:
+*
+* Description:
+*
+******************************************************************************/
+
+static void move4_32(byte *src, byte *dst, int spl)
+{
+ int i;
+
+ for (i = 0; i < 4; ++i)
+ {
+ memcpy(dst, src, 16);
+ src += spl;
+ dst += spl;
+ }
+}
+
+/******************************************************************************
+*
+* Function:
+*
+* Description:
+*
+******************************************************************************/
+
+static void blit8_32(byte *src, byte *dst, int spl)
+{
+ int i;
+
+ for (i = 0; i < 8; ++i)
+ {
+ memcpy(dst, src, 32);
+ src += 32;
+ dst += spl;
+ }
+}
+
+/******************************************************************************
+*
+* Function:
+*
+* Description:
+*
+******************************************************************************/
+static void blit4_32(byte *src, byte *dst, int spl)
+{
+ int i;
+
+ for (i = 0; i < 4; ++i)
+ {
+ memmove(dst, src, 16);
+ src += 16;
+ dst += spl;
+ }
+}
+
+/******************************************************************************
+*
+* Function:
+*
+* Description:
+*
+******************************************************************************/
+
+static void blit2_32(byte *src, byte *dst, int spl)
+{
+ memcpy(dst, src, 8);
+ memcpy(dst + spl, src + 8, 8);
+}
+
+/******************************************************************************
+*
+* Function:
+*
+* Description:
+*
+******************************************************************************/
+
+static void blitVQQuad32fs(byte **status, unsigned char *data)
+{
+ unsigned short newd, celdata, code;
+ unsigned int index, i;
+ int spl;
+
+ newd = 0;
+ celdata = 0;
+ index = 0;
+
+ spl = cinTable[currentHandle].samplesPerLine;
+
+ do
+ {
+ if (!newd)
+ {
+ newd = 7;
+ celdata = data[0] + data[1] * 256;
+ data += 2;
+ }
+ else
+ {
+ newd--;
+ }
+
+ code = (unsigned short)(celdata & 0xc000);
+ celdata <<= 2;
+
+ switch (code)
+ {
+ case 0x8000: // vq code
+ blit8_32((byte *)&vq8[(*data) * 128], status[index], spl);
+ data++;
+ index += 5;
+ break;
+ case 0xc000: // drop
+ index++; // skip 8x8
+ for (i = 0; i < 4; i++)
+ {
+ if (!newd)
+ {
+ newd = 7;
+ celdata = data[0] + data[1] * 256;
+ data += 2;
+ }
+ else
+ {
+ newd--;
+ }
+
+ code = (unsigned short)(celdata & 0xc000);
+ celdata <<= 2;
+
+ switch (code)
+ { // code in top two bits of code
+ case 0x8000: // 4x4 vq code
+ blit4_32((byte *)&vq4[(*data) * 32], status[index], spl);
+ data++;
+ break;
+ case 0xc000: // 2x2 vq code
+ blit2_32((byte *)&vq2[(*data) * 8], status[index], spl);
+ data++;
+ blit2_32((byte *)&vq2[(*data) * 8], status[index] + 8, spl);
+ data++;
+ blit2_32((byte *)&vq2[(*data) * 8], status[index] + spl * 2, spl);
+ data++;
+ blit2_32((byte *)&vq2[(*data) * 8], status[index] + spl * 2 + 8, spl);
+ data++;
+ break;
+ case 0x4000: // motion compensation
+ move4_32(status[index] + cin.mcomp[(*data)], status[index], spl);
+ data++;
+ break;
+ }
+ index++;
+ }
+ break;
+ case 0x4000: // motion compensation
+ move8_32(status[index] + cin.mcomp[(*data)], status[index], spl);
+ data++;
+ index += 5;
+ break;
+ case 0x0000:
+ index += 5;
+ break;
+ }
+ } while (status[index] != NULL);
+}
+
+/******************************************************************************
+*
+* Function:
+*
+* Description:
+*
+******************************************************************************/
+
+static void ROQ_GenYUVTables(void)
+{
+ float t_ub, t_vr, t_ug, t_vg;
+ long i;
+
+ t_ub = (1.77200f / 2.0f) * (float)(1 << 6) + 0.5f;
+ t_vr = (1.40200f / 2.0f) * (float)(1 << 6) + 0.5f;
+ t_ug = (0.34414f / 2.0f) * (float)(1 << 6) + 0.5f;
+ t_vg = (0.71414f / 2.0f) * (float)(1 << 6) + 0.5f;
+ for (i = 0; i < 256; i++)
+ {
+ float x = (float)(2 * i - 255);
+
+ ROQ_UB_tab[i] = (long)((t_ub * x) + (1 << 5));
+ ROQ_VR_tab[i] = (long)((t_vr * x) + (1 << 5));
+ ROQ_UG_tab[i] = (long)((-t_ug * x));
+ ROQ_VG_tab[i] = (long)((-t_vg * x) + (1 << 5));
+ ROQ_YY_tab[i] = (long)((i << 6) | (i >> 2));
+ }
+}
+
+#define VQ2TO4(a, b, c, d) \
+ { \
+ *c++ = a[0]; \
+ *d++ = a[0]; \
+ *d++ = a[0]; \
+ *c++ = a[1]; \
+ *d++ = a[1]; \
+ *d++ = a[1]; \
+ *c++ = b[0]; \
+ *d++ = b[0]; \
+ *d++ = b[0]; \
+ *c++ = b[1]; \
+ *d++ = b[1]; \
+ *d++ = b[1]; \
+ *d++ = a[0]; \
+ *d++ = a[0]; \
+ *d++ = a[1]; \
+ *d++ = a[1]; \
+ *d++ = b[0]; \
+ *d++ = b[0]; \
+ *d++ = b[1]; \
+ *d++ = b[1]; \
+ a += 2; \
+ b += 2; \
+ }
+
+#define VQ2TO2(a, b, c, d) \
+ { \
+ *c++ = *a; \
+ *d++ = *a; \
+ *d++ = *a; \
+ *c++ = *b; \
+ *d++ = *b; \
+ *d++ = *b; \
+ *d++ = *a; \
+ *d++ = *a; \
+ *d++ = *b; \
+ *d++ = *b; \
+ a++; \
+ b++; \
+ }
+
+/******************************************************************************
+*
+* Function:
+*
+* Description:
+*
+******************************************************************************/
+
+static unsigned short yuv_to_rgb(long y, long u, long v)
+{
+ long r, g, b, YY = (long)(ROQ_YY_tab[(y)]);
+
+ r = (YY + ROQ_VR_tab[v]) >> 9;
+ g = (YY + ROQ_UG_tab[u] + ROQ_VG_tab[v]) >> 8;
+ b = (YY + ROQ_UB_tab[u]) >> 9;
+
+ if (r < 0) r = 0;
+ if (g < 0) g = 0;
+ if (b < 0) b = 0;
+ if (r > 31) r = 31;
+ if (g > 63) g = 63;
+ if (b > 31) b = 31;
+
+ return (unsigned short)((r << 11) + (g << 5) + (b));
+}
+
+/******************************************************************************
+*
+* Function:
+*
+* Description:
+*
+******************************************************************************/
+static unsigned int yuv_to_rgb24(long y, long u, long v)
+{
+ long r, g, b, YY = (long)(ROQ_YY_tab[(y)]);
+
+ r = (YY + ROQ_VR_tab[v]) >> 6;
+ g = (YY + ROQ_UG_tab[u] + ROQ_VG_tab[v]) >> 6;
+ b = (YY + ROQ_UB_tab[u]) >> 6;
+
+ if (r < 0) r = 0;
+ if (g < 0) g = 0;
+ if (b < 0) b = 0;
+ if (r > 255) r = 255;
+ if (g > 255) g = 255;
+ if (b > 255) b = 255;
+
+ return LittleLong((r) | (g << 8) | (b << 16) | (255 << 24));
+}
+
+/******************************************************************************
+*
+* Function:
+*
+* Description:
+*
+******************************************************************************/
+
+static void decodeCodeBook(byte *input, unsigned short roq_flags)
+{
+ long i, j, two, four;
+ unsigned short *aptr, *bptr, *cptr, *dptr;
+ long y0, y1, y2, y3, cr, cb;
+ byte *bbptr, *baptr, *bcptr, *bdptr;
+ union {
+ unsigned int *i;
+ unsigned short *s;
+ } iaptr, ibptr, icptr, idptr;
+
+ if (!roq_flags)
+ {
+ two = four = 256;
+ }
+ else
+ {
+ two = roq_flags >> 8;
+ if (!two) two = 256;
+ four = roq_flags & 0xff;
+ }
+
+ four *= 2;
+
+ bptr = (unsigned short *)vq2;
+
+ if (!cinTable[currentHandle].half)
+ {
+ if (!cinTable[currentHandle].smootheddouble)
+ {
+ //
+ // normal height
+ //
+ if (cinTable[currentHandle].samplesPerPixel == 2)
+ {
+ for (i = 0; i < two; i++)
+ {
+ y0 = (long)*input++;
+ y1 = (long)*input++;
+ y2 = (long)*input++;
+ y3 = (long)*input++;
+ cr = (long)*input++;
+ cb = (long)*input++;
+ *bptr++ = yuv_to_rgb(y0, cr, cb);
+ *bptr++ = yuv_to_rgb(y1, cr, cb);
+ *bptr++ = yuv_to_rgb(y2, cr, cb);
+ *bptr++ = yuv_to_rgb(y3, cr, cb);
+ }
+
+ cptr = (unsigned short *)vq4;
+ dptr = (unsigned short *)vq8;
+
+ for (i = 0; i < four; i++)
+ {
+ aptr = (unsigned short *)vq2 + (*input++) * 4;
+ bptr = (unsigned short *)vq2 + (*input++) * 4;
+ for (j = 0; j < 2; j++) VQ2TO4(aptr, bptr, cptr, dptr);
+ }
+ }
+ else if (cinTable[currentHandle].samplesPerPixel == 4)
+ {
+ ibptr.s = bptr;
+ for (i = 0; i < two; i++)
+ {
+ y0 = (long)*input++;
+ y1 = (long)*input++;
+ y2 = (long)*input++;
+ y3 = (long)*input++;
+ cr = (long)*input++;
+ cb = (long)*input++;
+ *ibptr.i++ = yuv_to_rgb24(y0, cr, cb);
+ *ibptr.i++ = yuv_to_rgb24(y1, cr, cb);
+ *ibptr.i++ = yuv_to_rgb24(y2, cr, cb);
+ *ibptr.i++ = yuv_to_rgb24(y3, cr, cb);
+ }
+
+ icptr.s = vq4;
+ idptr.s = vq8;
+
+ for (i = 0; i < four; i++)
+ {
+ iaptr.s = vq2;
+ iaptr.i += (*input++) * 4;
+ ibptr.s = vq2;
+ ibptr.i += (*input++) * 4;
+ for (j = 0; j < 2; j++) VQ2TO4(iaptr.i, ibptr.i, icptr.i, idptr.i);
+ }
+ }
+ else if (cinTable[currentHandle].samplesPerPixel == 1)
+ {
+ bbptr = (byte *)bptr;
+ for (i = 0; i < two; i++)
+ {
+ *bbptr++ = cinTable[currentHandle].gray[*input++];
+ *bbptr++ = cinTable[currentHandle].gray[*input++];
+ *bbptr++ = cinTable[currentHandle].gray[*input++];
+ *bbptr++ = cinTable[currentHandle].gray[*input];
+ input += 3;
+ }
+
+ bcptr = (byte *)vq4;
+ bdptr = (byte *)vq8;
+
+ for (i = 0; i < four; i++)
+ {
+ baptr = (byte *)vq2 + (*input++) * 4;
+ bbptr = (byte *)vq2 + (*input++) * 4;
+ for (j = 0; j < 2; j++) VQ2TO4(baptr, bbptr, bcptr, bdptr);
+ }
+ }
+ }
+ else
+ {
+ //
+ // double height, smoothed
+ //
+ if (cinTable[currentHandle].samplesPerPixel == 2)
+ {
+ for (i = 0; i < two; i++)
+ {
+ y0 = (long)*input++;
+ y1 = (long)*input++;
+ y2 = (long)*input++;
+ y3 = (long)*input++;
+ cr = (long)*input++;
+ cb = (long)*input++;
+ *bptr++ = yuv_to_rgb(y0, cr, cb);
+ *bptr++ = yuv_to_rgb(y1, cr, cb);
+ *bptr++ = yuv_to_rgb(((y0 * 3) + y2) / 4, cr, cb);
+ *bptr++ = yuv_to_rgb(((y1 * 3) + y3) / 4, cr, cb);
+ *bptr++ = yuv_to_rgb((y0 + (y2 * 3)) / 4, cr, cb);
+ *bptr++ = yuv_to_rgb((y1 + (y3 * 3)) / 4, cr, cb);
+ *bptr++ = yuv_to_rgb(y2, cr, cb);
+ *bptr++ = yuv_to_rgb(y3, cr, cb);
+ }
+
+ cptr = (unsigned short *)vq4;
+ dptr = (unsigned short *)vq8;
+
+ for (i = 0; i < four; i++)
+ {
+ aptr = (unsigned short *)vq2 + (*input++) * 8;
+ bptr = (unsigned short *)vq2 + (*input++) * 8;
+ for (j = 0; j < 2; j++)
+ {
+ VQ2TO4(aptr, bptr, cptr, dptr);
+ VQ2TO4(aptr, bptr, cptr, dptr);
+ }
+ }
+ }
+ else if (cinTable[currentHandle].samplesPerPixel == 4)
+ {
+ ibptr.s = bptr;
+ for (i = 0; i < two; i++)
+ {
+ y0 = (long)*input++;
+ y1 = (long)*input++;
+ y2 = (long)*input++;
+ y3 = (long)*input++;
+ cr = (long)*input++;
+ cb = (long)*input++;
+ *ibptr.i++ = yuv_to_rgb24(y0, cr, cb);
+ *ibptr.i++ = yuv_to_rgb24(y1, cr, cb);
+ *ibptr.i++ = yuv_to_rgb24(((y0 * 3) + y2) / 4, cr, cb);
+ *ibptr.i++ = yuv_to_rgb24(((y1 * 3) + y3) / 4, cr, cb);
+ *ibptr.i++ = yuv_to_rgb24((y0 + (y2 * 3)) / 4, cr, cb);
+ *ibptr.i++ = yuv_to_rgb24((y1 + (y3 * 3)) / 4, cr, cb);
+ *ibptr.i++ = yuv_to_rgb24(y2, cr, cb);
+ *ibptr.i++ = yuv_to_rgb24(y3, cr, cb);
+ }
+
+ icptr.s = vq4;
+ idptr.s = vq8;
+
+ for (i = 0; i < four; i++)
+ {
+ iaptr.s = vq2;
+ iaptr.i += (*input++) * 8;
+ ibptr.s = vq2;
+ ibptr.i += (*input++) * 8;
+ for (j = 0; j < 2; j++)
+ {
+ VQ2TO4(iaptr.i, ibptr.i, icptr.i, idptr.i);
+ VQ2TO4(iaptr.i, ibptr.i, icptr.i, idptr.i);
+ }
+ }
+ }
+ else if (cinTable[currentHandle].samplesPerPixel == 1)
+ {
+ bbptr = (byte *)bptr;
+ for (i = 0; i < two; i++)
+ {
+ y0 = (long)*input++;
+ y1 = (long)*input++;
+ y2 = (long)*input++;
+ y3 = (long)*input;
+ input += 3;
+ *bbptr++ = cinTable[currentHandle].gray[y0];
+ *bbptr++ = cinTable[currentHandle].gray[y1];
+ *bbptr++ = cinTable[currentHandle].gray[((y0 * 3) + y2) / 4];
+ *bbptr++ = cinTable[currentHandle].gray[((y1 * 3) + y3) / 4];
+ *bbptr++ = cinTable[currentHandle].gray[(y0 + (y2 * 3)) / 4];
+ *bbptr++ = cinTable[currentHandle].gray[(y1 + (y3 * 3)) / 4];
+ *bbptr++ = cinTable[currentHandle].gray[y2];
+ *bbptr++ = cinTable[currentHandle].gray[y3];
+ }
+
+ bcptr = (byte *)vq4;
+ bdptr = (byte *)vq8;
+
+ for (i = 0; i < four; i++)
+ {
+ baptr = (byte *)vq2 + (*input++) * 8;
+ bbptr = (byte *)vq2 + (*input++) * 8;
+ for (j = 0; j < 2; j++)
+ {
+ VQ2TO4(baptr, bbptr, bcptr, bdptr);
+ VQ2TO4(baptr, bbptr, bcptr, bdptr);
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ //
+ // 1/4 screen
+ //
+ if (cinTable[currentHandle].samplesPerPixel == 2)
+ {
+ for (i = 0; i < two; i++)
+ {
+ y0 = (long)*input;
+ input += 2;
+ y2 = (long)*input;
+ input += 2;
+ cr = (long)*input++;
+ cb = (long)*input++;
+ *bptr++ = yuv_to_rgb(y0, cr, cb);
+ *bptr++ = yuv_to_rgb(y2, cr, cb);
+ }
+
+ cptr = (unsigned short *)vq4;
+ dptr = (unsigned short *)vq8;
+
+ for (i = 0; i < four; i++)
+ {
+ aptr = (unsigned short *)vq2 + (*input++) * 2;
+ bptr = (unsigned short *)vq2 + (*input++) * 2;
+ for (j = 0; j < 2; j++)
+ {
+ VQ2TO2(aptr, bptr, cptr, dptr);
+ }
+ }
+ }
+ else if (cinTable[currentHandle].samplesPerPixel == 1)
+ {
+ bbptr = (byte *)bptr;
+
+ for (i = 0; i < two; i++)
+ {
+ *bbptr++ = cinTable[currentHandle].gray[*input];
+ input += 2;
+ *bbptr++ = cinTable[currentHandle].gray[*input];
+ input += 4;
+ }
+
+ bcptr = (byte *)vq4;
+ bdptr = (byte *)vq8;
+
+ for (i = 0; i < four; i++)
+ {
+ baptr = (byte *)vq2 + (*input++) * 2;
+ bbptr = (byte *)vq2 + (*input++) * 2;
+ for (j = 0; j < 2; j++)
+ {
+ VQ2TO2(baptr, bbptr, bcptr, bdptr);
+ }
+ }
+ }
+ else if (cinTable[currentHandle].samplesPerPixel == 4)
+ {
+ ibptr.s = bptr;
+ for (i = 0; i < two; i++)
+ {
+ y0 = (long)*input;
+ input += 2;
+ y2 = (long)*input;
+ input += 2;
+ cr = (long)*input++;
+ cb = (long)*input++;
+ *ibptr.i++ = yuv_to_rgb24(y0, cr, cb);
+ *ibptr.i++ = yuv_to_rgb24(y2, cr, cb);
+ }
+
+ icptr.s = vq4;
+ idptr.s = vq8;
+
+ for (i = 0; i < four; i++)
+ {
+ iaptr.s = vq2;
+ iaptr.i += (*input++) * 2;
+ ibptr.s = vq2 + (*input++) * 2;
+ ibptr.i += (*input++) * 2;
+ for (j = 0; j < 2; j++)
+ {
+ VQ2TO2(iaptr.i, ibptr.i, icptr.i, idptr.i);
+ }
+ }
+ }
+ }
+}
+
+/******************************************************************************
+*
+* Function:
+*
+* Description:
+*
+******************************************************************************/
+
+static void recurseQuad(long startX, long startY, long quadSize, long xOff, long yOff)
+{
+ byte *scroff;
+ long bigx, bigy, lowx, lowy, useY;
+ long offset;
+
+ offset = cinTable[currentHandle].screenDelta;
+
+ lowx = lowy = 0;
+ bigx = cinTable[currentHandle].xsize;
+ bigy = cinTable[currentHandle].ysize;
+
+ if (bigx > cinTable[currentHandle].CIN_WIDTH) bigx = cinTable[currentHandle].CIN_WIDTH;
+ if (bigy > cinTable[currentHandle].CIN_HEIGHT) bigy = cinTable[currentHandle].CIN_HEIGHT;
+
+ if ((startX >= lowx) && (startX + quadSize) <= (bigx) && (startY + quadSize) <= (bigy) && (startY >= lowy) &&
+ quadSize <= MAXSIZE)
+ {
+ useY = startY;
+ scroff = cin.linbuf +
+ (useY + ((cinTable[currentHandle].CIN_HEIGHT - bigy) >> 1) + yOff) *
+ (cinTable[currentHandle].samplesPerLine) +
+ (((startX + xOff)) * cinTable[currentHandle].samplesPerPixel);
+
+ cin.qStatus[0][cinTable[currentHandle].onQuad] = scroff;
+ cin.qStatus[1][cinTable[currentHandle].onQuad++] = scroff + offset;
+ }
+
+ if (quadSize != MINSIZE)
+ {
+ quadSize >>= 1;
+ recurseQuad(startX, startY, quadSize, xOff, yOff);
+ recurseQuad(startX + quadSize, startY, quadSize, xOff, yOff);
+ recurseQuad(startX, startY + quadSize, quadSize, xOff, yOff);
+ recurseQuad(startX + quadSize, startY + quadSize, quadSize, xOff, yOff);
+ }
+}
+
+/******************************************************************************
+*
+* Function:
+*
+* Description:
+*
+******************************************************************************/
+
+static void setupQuad(long xOff, long yOff)
+{
+ long numQuadCels, i, x, y;
+ byte *temp;
+
+ if (xOff == cin.oldXOff && yOff == cin.oldYOff && cinTable[currentHandle].ysize == cin.oldysize &&
+ cinTable[currentHandle].xsize == cin.oldxsize)
+ {
+ return;
+ }
+
+ cin.oldXOff = xOff;
+ cin.oldYOff = yOff;
+ cin.oldysize = cinTable[currentHandle].ysize;
+ cin.oldxsize = cinTable[currentHandle].xsize;
+
+ numQuadCels = (cinTable[currentHandle].xsize * cinTable[currentHandle].ysize) / (16);
+ numQuadCels += numQuadCels / 4;
+ numQuadCels += 64; // for overflow
+
+ cinTable[currentHandle].onQuad = 0;
+
+ for (y = 0; y < (long)cinTable[currentHandle].ysize; y += 16)
+ for (x = 0; x < (long)cinTable[currentHandle].xsize; x += 16) recurseQuad(x, y, 16, xOff, yOff);
+
+ temp = NULL;
+
+ for (i = (numQuadCels - 64); i < numQuadCels; i++)
+ {
+ cin.qStatus[0][i] = temp; // eoq
+ cin.qStatus[1][i] = temp; // eoq
+ }
+}
+
+/******************************************************************************
+*
+* Function:
+*
+* Description:
+*
+******************************************************************************/
+
+static void readQuadInfo(byte *qData)
+{
+ if (currentHandle < 0) return;
+
+ cinTable[currentHandle].xsize = qData[0] + qData[1] * 256;
+ cinTable[currentHandle].ysize = qData[2] + qData[3] * 256;
+ cinTable[currentHandle].maxsize = qData[4] + qData[5] * 256;
+ cinTable[currentHandle].minsize = qData[6] + qData[7] * 256;
+
+ cinTable[currentHandle].CIN_HEIGHT = cinTable[currentHandle].ysize;
+ cinTable[currentHandle].CIN_WIDTH = cinTable[currentHandle].xsize;
+
+ cinTable[currentHandle].samplesPerLine =
+ cinTable[currentHandle].CIN_WIDTH * cinTable[currentHandle].samplesPerPixel;
+ cinTable[currentHandle].screenDelta = cinTable[currentHandle].CIN_HEIGHT * cinTable[currentHandle].samplesPerLine;
+
+ cinTable[currentHandle].half = false;
+ cinTable[currentHandle].smootheddouble = false;
+
+ cinTable[currentHandle].VQ0 = cinTable[currentHandle].VQNormal;
+ cinTable[currentHandle].VQ1 = cinTable[currentHandle].VQBuffer;
+
+ cinTable[currentHandle].t[0] = cinTable[currentHandle].screenDelta;
+ cinTable[currentHandle].t[1] = -cinTable[currentHandle].screenDelta;
+
+ cinTable[currentHandle].drawX = cinTable[currentHandle].CIN_WIDTH;
+ cinTable[currentHandle].drawY = cinTable[currentHandle].CIN_HEIGHT;
+
+ // rage pro is very slow at 512 wide textures, voodoo can't do it at all
+ if (cls.glconfig.hardwareType == GLHW_RAGEPRO || cls.glconfig.maxTextureSize <= 256)
+ {
+ if (cinTable[currentHandle].drawX > 256)
+ {
+ cinTable[currentHandle].drawX = 256;
+ }
+ if (cinTable[currentHandle].drawY > 256)
+ {
+ cinTable[currentHandle].drawY = 256;
+ }
+ if (cinTable[currentHandle].CIN_WIDTH != 256 || cinTable[currentHandle].CIN_HEIGHT != 256)
+ {
+ Com_Printf("HACK: approxmimating cinematic for Rage Pro or Voodoo\n");
+ }
+ }
+}
+
+/******************************************************************************
+*
+* Function:
+*
+* Description:
+*
+******************************************************************************/
+
+static void RoQPrepMcomp(long xoff, long yoff)
+{
+ long i, j, x, y, temp, temp2;
+
+ i = cinTable[currentHandle].samplesPerLine;
+ j = cinTable[currentHandle].samplesPerPixel;
+ if (cinTable[currentHandle].xsize == (cinTable[currentHandle].ysize * 4) && !cinTable[currentHandle].half)
+ {
+ j = j + j;
+ i = i + i;
+ }
+
+ for (y = 0; y < 16; y++)
+ {
+ temp2 = (y + yoff - 8) * i;
+ for (x = 0; x < 16; x++)
+ {
+ temp = (x + xoff - 8) * j;
+ cin.mcomp[(x * 16) + y] = cinTable[currentHandle].normalBuffer0 - (temp2 + temp);
+ }
+ }
+}
+
+/******************************************************************************
+*
+* Function:
+*
+* Description:
+*
+******************************************************************************/
+
+static void initRoQ(void)
+{
+ if (currentHandle < 0) return;
+
+ cinTable[currentHandle].VQNormal = (void (*)(byte *, void *))blitVQQuad32fs;
+ cinTable[currentHandle].VQBuffer = (void (*)(byte *, void *))blitVQQuad32fs;
+ cinTable[currentHandle].samplesPerPixel = 4;
+ ROQ_GenYUVTables();
+ RllSetupTable();
+}
+
+/******************************************************************************
+*
+* Function:
+*
+* Description:
+*
+******************************************************************************/
+/*
+static byte* RoQFetchInterlaced( byte *source ) {
+ int x, *src, *dst;
+
+ if (currentHandle < 0) return NULL;
+
+ src = (int *)source;
+ dst = (int *)cinTable[currentHandle].buf2;
+
+ for(x=0;x<256*256;x++) {
+ *dst = *src;
+ dst++; src += 2;
+ }
+ return cinTable[currentHandle].buf2;
+}
+*/
+static void RoQReset(void)
+{
+ if (currentHandle < 0) return;
+
+ FS_FCloseFile(cinTable[currentHandle].iFile);
+ FS_FOpenFileRead(cinTable[currentHandle].fileName, &cinTable[currentHandle].iFile, true);
+ // let the background thread start reading ahead
+ FS_Read(cin.file, 16, cinTable[currentHandle].iFile);
+ RoQ_init();
+ cinTable[currentHandle].status = FMV_LOOPED;
+}
+
+/******************************************************************************
+*
+* Function:
+*
+* Description:
+*
+******************************************************************************/
+
+static void RoQInterrupt(void)
+{
+ byte *framedata;
+ short sbuf[32768];
+ int ssize;
+
+ if (currentHandle < 0) return;
+
+ FS_Read(cin.file, cinTable[currentHandle].RoQFrameSize + 8, cinTable[currentHandle].iFile);
+ if (cinTable[currentHandle].RoQPlayed >= cinTable[currentHandle].ROQSize)
+ {
+ if (cinTable[currentHandle].holdAtEnd == false)
+ {
+ if (cinTable[currentHandle].looping)
+ {
+ RoQReset();
+ }
+ else
+ {
+ cinTable[currentHandle].status = FMV_EOF;
+ }
+ }
+ else
+ {
+ cinTable[currentHandle].status = FMV_IDLE;
+ }
+ return;
+ }
+
+ framedata = cin.file;
+//
+// new frame is ready
+//
+redump:
+ switch (cinTable[currentHandle].roq_id)
+ {
+ case ROQ_QUAD_VQ:
+ if ((cinTable[currentHandle].numQuads & 1))
+ {
+ cinTable[currentHandle].normalBuffer0 = cinTable[currentHandle].t[1];
+ RoQPrepMcomp(cinTable[currentHandle].roqF0, cinTable[currentHandle].roqF1);
+ cinTable[currentHandle].VQ1((byte *)cin.qStatus[1], framedata);
+ cinTable[currentHandle].buf = cin.linbuf + cinTable[currentHandle].screenDelta;
+ }
+ else
+ {
+ cinTable[currentHandle].normalBuffer0 = cinTable[currentHandle].t[0];
+ RoQPrepMcomp(cinTable[currentHandle].roqF0, cinTable[currentHandle].roqF1);
+ cinTable[currentHandle].VQ0((byte *)cin.qStatus[0], framedata);
+ cinTable[currentHandle].buf = cin.linbuf;
+ }
+ if (cinTable[currentHandle].numQuads == 0)
+ { // first frame
+ ::memcpy(cin.linbuf + cinTable[currentHandle].screenDelta, cin.linbuf,
+ cinTable[currentHandle].samplesPerLine * cinTable[currentHandle].ysize);
+ }
+ cinTable[currentHandle].numQuads++;
+ cinTable[currentHandle].dirty = true;
+ break;
+ case ROQ_CODEBOOK:
+ decodeCodeBook(framedata, (unsigned short)cinTable[currentHandle].roq_flags);
+ break;
+ case ZA_SOUND_MONO:
+ if (!cinTable[currentHandle].silent)
+ {
+ ssize = RllDecodeMonoToStereo(framedata, sbuf, cinTable[currentHandle].RoQFrameSize, 0,
+ (unsigned short)cinTable[currentHandle].roq_flags);
+ S_RawSamples(0, ssize, 22050, 2, 1, (byte *)sbuf, 1.0f, -1);
+ }
+ break;
+ case ZA_SOUND_STEREO:
+ if (!cinTable[currentHandle].silent)
+ {
+ if (cinTable[currentHandle].numQuads == -1)
+ {
+ S_Update();
+ s_rawend[0] = s_soundtime;
+ }
+ ssize = RllDecodeStereoToStereo(framedata, sbuf, cinTable[currentHandle].RoQFrameSize, 0,
+ (unsigned short)cinTable[currentHandle].roq_flags);
+ S_RawSamples(0, ssize, 22050, 2, 2, (byte *)sbuf, 1.0f, -1);
+ }
+ break;
+ case ROQ_QUAD_INFO:
+ if (cinTable[currentHandle].numQuads == -1)
+ {
+ readQuadInfo(framedata);
+ setupQuad(0, 0);
+ cinTable[currentHandle].startTime = cinTable[currentHandle].lastTime = CL_ScaledMilliseconds();
+ }
+ if (cinTable[currentHandle].numQuads != 1) cinTable[currentHandle].numQuads = 0;
+ break;
+ case ROQ_PACKET:
+ cinTable[currentHandle].inMemory = (bool)cinTable[currentHandle].roq_flags;
+ cinTable[currentHandle].RoQFrameSize = 0; // for header
+ break;
+ case ROQ_QUAD_HANG:
+ cinTable[currentHandle].RoQFrameSize = 0;
+ break;
+ case ROQ_QUAD_JPEG:
+ break;
+ default:
+ cinTable[currentHandle].status = FMV_EOF;
+ break;
+ }
+ //
+ // read in next frame data
+ //
+ if (cinTable[currentHandle].RoQPlayed >= cinTable[currentHandle].ROQSize)
+ {
+ if (cinTable[currentHandle].holdAtEnd == false)
+ {
+ if (cinTable[currentHandle].looping)
+ {
+ RoQReset();
+ }
+ else
+ {
+ cinTable[currentHandle].status = FMV_EOF;
+ }
+ }
+ else
+ {
+ cinTable[currentHandle].status = FMV_IDLE;
+ }
+ return;
+ }
+
+ framedata += cinTable[currentHandle].RoQFrameSize;
+ cinTable[currentHandle].roq_id = framedata[0] + framedata[1] * 256;
+ cinTable[currentHandle].RoQFrameSize = framedata[2] + framedata[3] * 256 + framedata[4] * 65536;
+ cinTable[currentHandle].roq_flags = framedata[6] + framedata[7] * 256;
+ cinTable[currentHandle].roqF0 = (signed char)framedata[7];
+ cinTable[currentHandle].roqF1 = (signed char)framedata[6];
+
+ if (cinTable[currentHandle].RoQFrameSize > 65536 || cinTable[currentHandle].roq_id == 0x1084)
+ {
+ Com_DPrintf("roq_size>65536||roq_id==0x1084\n");
+ cinTable[currentHandle].status = FMV_EOF;
+ if (cinTable[currentHandle].looping)
+ {
+ RoQReset();
+ }
+ return;
+ }
+ if (cinTable[currentHandle].inMemory && (cinTable[currentHandle].status != FMV_EOF))
+ {
+ cinTable[currentHandle].inMemory = false;
+ framedata += 8;
+ goto redump;
+ }
+ //
+ // one more frame hits the dust
+ //
+ // assert(cinTable[currentHandle].RoQFrameSize <= 65536);
+ // r = FS_Read( cin.file, cinTable[currentHandle].RoQFrameSize+8, cinTable[currentHandle].iFile );
+ cinTable[currentHandle].RoQPlayed += cinTable[currentHandle].RoQFrameSize + 8;
+}
+
+/******************************************************************************
+*
+* Function:
+*
+* Description:
+*
+******************************************************************************/
+
+static void RoQ_init(void)
+{
+ cinTable[currentHandle].startTime = cinTable[currentHandle].lastTime = CL_ScaledMilliseconds();
+
+ cinTable[currentHandle].RoQPlayed = 24;
+
+ /* get frame rate */
+ cinTable[currentHandle].roqFPS = cin.file[6] + cin.file[7] * 256;
+
+ if (!cinTable[currentHandle].roqFPS) cinTable[currentHandle].roqFPS = 30;
+
+ cinTable[currentHandle].numQuads = -1;
+
+ cinTable[currentHandle].roq_id = cin.file[8] + cin.file[9] * 256;
+ cinTable[currentHandle].RoQFrameSize = cin.file[10] + cin.file[11] * 256 + cin.file[12] * 65536;
+ cinTable[currentHandle].roq_flags = cin.file[14] + cin.file[15] * 256;
+
+ if (cinTable[currentHandle].RoQFrameSize > 65536 || !cinTable[currentHandle].RoQFrameSize)
+ {
+ return;
+ }
+}
+
+/******************************************************************************
+*
+* Function:
+*
+* Description:
+*
+******************************************************************************/
+
+static void RoQShutdown(void)
+{
+ const char *s;
+
+ if (!cinTable[currentHandle].buf)
+ {
+ return;
+ }
+
+ if (cinTable[currentHandle].status == FMV_IDLE)
+ {
+ return;
+ }
+ Com_DPrintf("finished cinematic\n");
+ cinTable[currentHandle].status = FMV_IDLE;
+
+ if (cinTable[currentHandle].iFile)
+ {
+ FS_FCloseFile(cinTable[currentHandle].iFile);
+ cinTable[currentHandle].iFile = 0;
+ }
+
+ if (cinTable[currentHandle].alterGameState)
+ {
+ clc.state = CA_DISCONNECTED;
+ // we can't just do a vstr nextmap, because
+ // if we are aborting the intro cinematic with
+ // a devmap command, nextmap would be valid by
+ // the time it was referenced
+ s = Cvar_VariableString("nextmap");
+ if (s[0])
+ {
+ Cbuf_ExecuteText(EXEC_APPEND, va("%s\n", s));
+ Cvar_Set("nextmap", "");
+ }
+ CL_handle = -1;
+
+ CL_ProtocolSpecificCommandsInit();
+ }
+ cinTable[currentHandle].fileName[0] = 0;
+ currentHandle = -1;
+}
+
+/*
+==================
+CIN_StopCinematic
+==================
+*/
+e_status CIN_StopCinematic(int handle)
+{
+ if (handle < 0 || handle >= MAX_VIDEO_HANDLES || cinTable[handle].status == FMV_EOF) return FMV_EOF;
+ currentHandle = handle;
+
+ Com_DPrintf("trFMV::stop(), closing %s\n", cinTable[currentHandle].fileName);
+
+ if (!cinTable[currentHandle].buf)
+ {
+ return FMV_EOF;
+ }
+
+ if (cinTable[currentHandle].alterGameState)
+ {
+ if (clc.state != CA_CINEMATIC)
+ {
+ return cinTable[currentHandle].status;
+ }
+ }
+ cinTable[currentHandle].status = FMV_EOF;
+ RoQShutdown();
+
+ return FMV_EOF;
+}
+
+/*
+==================
+CIN_RunCinematic
+
+Fetch and decompress the pending frame
+==================
+*/
+
+e_status CIN_RunCinematic(int handle)
+{
+ int start = 0;
+ int thisTime = 0;
+
+ if (handle < 0 || handle >= MAX_VIDEO_HANDLES || cinTable[handle].status == FMV_EOF) return FMV_EOF;
+
+ if (cin.currentHandle != handle)
+ {
+ currentHandle = handle;
+ cin.currentHandle = currentHandle;
+ cinTable[currentHandle].status = FMV_EOF;
+ RoQReset();
+ }
+
+ if (cinTable[handle].playonwalls < -1)
+ {
+ return cinTable[handle].status;
+ }
+
+ currentHandle = handle;
+
+ if (cinTable[currentHandle].alterGameState)
+ {
+ if (clc.state != CA_CINEMATIC)
+ {
+ return cinTable[currentHandle].status;
+ }
+ }
+
+ if (cinTable[currentHandle].status == FMV_IDLE)
+ {
+ return cinTable[currentHandle].status;
+ }
+
+ thisTime = CL_ScaledMilliseconds();
+ if (cinTable[currentHandle].shader && (abs(thisTime - cinTable[currentHandle].lastTime)) > 100)
+ {
+ cinTable[currentHandle].startTime += thisTime - cinTable[currentHandle].lastTime;
+ }
+ cinTable[currentHandle].tfps = (((CL_ScaledMilliseconds() - cinTable[currentHandle].startTime) * 3) / 100);
+
+ start = cinTable[currentHandle].startTime;
+ while ((cinTable[currentHandle].tfps != cinTable[currentHandle].numQuads) &&
+ (cinTable[currentHandle].status == FMV_PLAY))
+ {
+ RoQInterrupt();
+ if (start != cinTable[currentHandle].startTime)
+ {
+ cinTable[currentHandle].tfps = (((CL_ScaledMilliseconds() - cinTable[currentHandle].startTime) * 3) / 100);
+ start = cinTable[currentHandle].startTime;
+ }
+ }
+
+ cinTable[currentHandle].lastTime = thisTime;
+
+ if (cinTable[currentHandle].status == FMV_LOOPED)
+ {
+ cinTable[currentHandle].status = FMV_PLAY;
+ }
+
+ if (cinTable[currentHandle].status == FMV_EOF)
+ {
+ if (cinTable[currentHandle].looping)
+ {
+ RoQReset();
+ }
+ else
+ {
+ RoQShutdown();
+ return FMV_EOF;
+ }
+ }
+
+ return cinTable[currentHandle].status;
+}
+
+void CIN_SetExtents(int handle, int x, int y, int w, int h)
+{
+ if (handle < 0 || handle >= MAX_VIDEO_HANDLES || cinTable[handle].status == FMV_EOF) return;
+ cinTable[handle].xpos = x;
+ cinTable[handle].ypos = y;
+ cinTable[handle].width = w;
+ cinTable[handle].height = h;
+ cinTable[handle].dirty = true;
+}
+
+static void CIN_SetLooping(int handle, bool loop)
+{
+ if (handle < 0 || handle >= MAX_VIDEO_HANDLES || cinTable[handle].status == FMV_EOF) return;
+ cinTable[handle].looping = loop;
+}
+
+/*
+==================
+CIN_PlayCinematic
+==================
+*/
+int CIN_PlayCinematic(const char *arg, int x, int y, int w, int h, int systemBits)
+{
+ unsigned short RoQID;
+ char name[MAX_OSPATH];
+ int i;
+
+ if (strstr(arg, "/") == NULL && strstr(arg, "\\") == NULL)
+ {
+ Com_sprintf(name, sizeof(name), "video/%s", arg);
+ }
+ else
+ {
+ Com_sprintf(name, sizeof(name), "%s", arg);
+ }
+
+ if (!(systemBits & CIN_system))
+ {
+ for (i = 0; i < MAX_VIDEO_HANDLES; i++)
+ {
+ if (!strcmp(cinTable[i].fileName, name))
+ {
+ return i;
+ }
+ }
+ }
+
+ Com_DPrintf("CIN_PlayCinematic( %s )\n", arg);
+
+ ::memset(&cin, 0, sizeof(cinematics_t));
+ currentHandle = CIN_HandleForVideo();
+
+ cin.currentHandle = currentHandle;
+
+ strcpy(cinTable[currentHandle].fileName, name);
+
+ cinTable[currentHandle].ROQSize = 0;
+ cinTable[currentHandle].ROQSize =
+ FS_FOpenFileRead(cinTable[currentHandle].fileName, &cinTable[currentHandle].iFile, true);
+
+ if (cinTable[currentHandle].ROQSize <= 0)
+ {
+ Com_DPrintf("play(%s), ROQSize<=0\n", arg);
+ cinTable[currentHandle].fileName[0] = 0;
+ return -1;
+ }
+
+ CIN_SetExtents(currentHandle, x, y, w, h);
+ CIN_SetLooping(currentHandle, ((systemBits & CIN_loop) != 0));
+
+ cinTable[currentHandle].CIN_HEIGHT = DEFAULT_CIN_HEIGHT;
+ cinTable[currentHandle].CIN_WIDTH = DEFAULT_CIN_WIDTH;
+ cinTable[currentHandle].holdAtEnd = (systemBits & CIN_hold) != 0;
+ cinTable[currentHandle].alterGameState = (systemBits & CIN_system) != 0;
+ cinTable[currentHandle].playonwalls = 1;
+ cinTable[currentHandle].silent = (systemBits & CIN_silent) != 0;
+ cinTable[currentHandle].shader = (systemBits & CIN_shader) != 0;
+
+ if (cinTable[currentHandle].alterGameState)
+ {
+ // close the menu
+ if (cls.ui)
+ {
+ VM_Call(cls.ui, UI_SET_ACTIVE_MENU - cls.uiInterface == 2 ? 2 : 0, UIMENU_NONE);
+ }
+ }
+ else
+ {
+ cinTable[currentHandle].playonwalls = cl_inGameVideo->integer;
+ }
+
+ initRoQ();
+
+ FS_Read(cin.file, 16, cinTable[currentHandle].iFile);
+
+ RoQID = (unsigned short)(cin.file[0]) + (unsigned short)(cin.file[1]) * 256;
+ if (RoQID == 0x1084)
+ {
+ RoQ_init();
+ // FS_Read (cin.file, cinTable[currentHandle].RoQFrameSize+8, cinTable[currentHandle].iFile);
+
+ cinTable[currentHandle].status = FMV_PLAY;
+ Com_DPrintf("trFMV::play(), playing %s\n", arg);
+
+ if (cinTable[currentHandle].alterGameState)
+ {
+ clc.state = CA_CINEMATIC;
+ }
+
+ if (!cinTable[currentHandle].silent)
+ {
+ s_rawend[0] = s_soundtime;
+ }
+
+ return currentHandle;
+ }
+ Com_DPrintf("trFMV::play(), invalid RoQ ID\n");
+
+ RoQShutdown();
+ return -1;
+}
+
+/*
+==================
+CIN_ResampleCinematic
+
+Resample cinematic to 256x256 and store in buf2
+==================
+*/
+static void CIN_ResampleCinematic(int handle, int *buf2)
+{
+ int ix, iy, *buf3, xm, ym, ll;
+ byte *buf;
+
+ buf = cinTable[handle].buf;
+
+ xm = cinTable[handle].CIN_WIDTH / 256;
+ ym = cinTable[handle].CIN_HEIGHT / 256;
+ ll = 8;
+ if (cinTable[handle].CIN_WIDTH == 512)
+ {
+ ll = 9;
+ }
+
+ buf3 = (int *)buf;
+ if (xm == 2 && ym == 2)
+ {
+ byte *bc2, *bc3;
+ int ic, iiy;
+
+ bc2 = (byte *)buf2;
+ bc3 = (byte *)buf3;
+ for (iy = 0; iy < 256; iy++)
+ {
+ iiy = iy << 12;
+ for (ix = 0; ix < 2048; ix += 8)
+ {
+ for (ic = ix; ic < (ix + 4); ic++)
+ {
+ *bc2 = (bc3[iiy + ic] + bc3[iiy + 4 + ic] + bc3[iiy + 2048 + ic] + bc3[iiy + 2048 + 4 + ic]) >> 2;
+ bc2++;
+ }
+ }
+ }
+ }
+ else if (xm == 2 && ym == 1)
+ {
+ byte *bc2, *bc3;
+ int ic, iiy;
+
+ bc2 = (byte *)buf2;
+ bc3 = (byte *)buf3;
+ for (iy = 0; iy < 256; iy++)
+ {
+ iiy = iy << 11;
+ for (ix = 0; ix < 2048; ix += 8)
+ {
+ for (ic = ix; ic < (ix + 4); ic++)
+ {
+ *bc2 = (bc3[iiy + ic] + bc3[iiy + 4 + ic]) >> 1;
+ bc2++;
+ }
+ }
+ }
+ }
+ else
+ {
+ for (iy = 0; iy < 256; iy++)
+ {
+ for (ix = 0; ix < 256; ix++)
+ {
+ buf2[(iy << 8) + ix] = buf3[((iy * ym) << ll) + (ix * xm)];
+ }
+ }
+ }
+}
+
+/*
+==================
+CIN_DrawCinematic
+==================
+*/
+void CIN_DrawCinematic(int handle)
+{
+ float x, y, w, h;
+ byte *buf;
+
+ if (handle < 0 || handle >= MAX_VIDEO_HANDLES || cinTable[handle].status == FMV_EOF) return;
+
+ if (!cinTable[handle].buf)
+ {
+ return;
+ }
+
+ x = cinTable[handle].xpos;
+ y = cinTable[handle].ypos;
+ w = cinTable[handle].width;
+ h = cinTable[handle].height;
+ buf = cinTable[handle].buf;
+ SCR_AdjustFrom640(&x, &y, &w, &h);
+
+ if (cinTable[handle].dirty &&
+ (cinTable[handle].CIN_WIDTH != cinTable[handle].drawX
+ || cinTable[handle].CIN_HEIGHT != cinTable[handle].drawY))
+ {
+ int *buf2 = (int *)Hunk_AllocateTempMemory(256 * 256 * 4);
+
+ CIN_ResampleCinematic(handle, buf2);
+
+ re.DrawStretchRaw(x, y, w, h, 256, 256, (byte *)buf2, handle, true);
+ cinTable[handle].dirty = false;
+ Hunk_FreeTempMemory(buf2);
+ return;
+ }
+
+ re.DrawStretchRaw(x, y, w, h, cinTable[handle].drawX, cinTable[handle].drawY,
+ buf, handle, cinTable[handle].dirty);
+
+ cinTable[handle].dirty = false;
+}
+
+void CL_PlayCinematic_f(void)
+{
+ int bits = CIN_system;
+
+ Com_DPrintf("CL_PlayCinematic_f\n");
+ if (clc.state == CA_CINEMATIC)
+ {
+ SCR_StopCinematic();
+ }
+
+ const char *arg = Cmd_Argv(1);
+ const char *s = Cmd_Argv(2);
+
+ if ((s && s[0] == '1') || Q_stricmp(arg, "demoend.roq") == 0 || Q_stricmp(arg, "end.roq") == 0)
+ {
+ bits |= CIN_hold;
+ }
+ if (s && s[0] == '2')
+ {
+ bits |= CIN_loop;
+ }
+
+ S_StopAllSounds();
+
+ CL_handle = CIN_PlayCinematic(arg, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, bits);
+ if (CL_handle >= 0)
+ {
+ do
+ {
+ SCR_RunCinematic();
+ } while (cinTable[currentHandle].buf == NULL &&
+ cinTable[currentHandle].status == FMV_PLAY); // wait for first frame (load codebook and sound)
+ }
+}
+
+void SCR_DrawCinematic(void)
+{
+ if (CL_handle >= 0 && CL_handle < MAX_VIDEO_HANDLES)
+ {
+ CIN_DrawCinematic(CL_handle);
+ }
+}
+
+void SCR_RunCinematic(void)
+{
+ if (CL_handle >= 0 && CL_handle < MAX_VIDEO_HANDLES)
+ {
+ CIN_RunCinematic(CL_handle);
+ }
+}
+
+void SCR_StopCinematic(void)
+{
+ if (CL_handle >= 0 && CL_handle < MAX_VIDEO_HANDLES)
+ {
+ CIN_StopCinematic(CL_handle);
+ S_StopAllSounds();
+ CL_handle = -1;
+ }
+}
+
+void CIN_UploadCinematic(int handle)
+{
+ if (handle >= 0 && handle < MAX_VIDEO_HANDLES)
+ {
+ if (!cinTable[handle].buf)
+ {
+ return;
+ }
+ if (cinTable[handle].playonwalls <= 0 && cinTable[handle].dirty)
+ {
+ if (cinTable[handle].playonwalls == 0)
+ {
+ cinTable[handle].playonwalls = -1;
+ }
+ else
+ {
+ if (cinTable[handle].playonwalls == -1)
+ {
+ cinTable[handle].playonwalls = -2;
+ }
+ else
+ {
+ cinTable[handle].dirty = false;
+ }
+ }
+ }
+
+ // Resample the video if needed
+ if (cinTable[handle].dirty && (cinTable[handle].CIN_WIDTH != cinTable[handle].drawX ||
+ cinTable[handle].CIN_HEIGHT != cinTable[handle].drawY))
+ {
+ int *buf2 = (int *)Hunk_AllocateTempMemory(256 * 256 * 4);
+
+ CIN_ResampleCinematic(handle, buf2);
+
+ re.UploadCinematic(
+ cinTable[handle].CIN_WIDTH, cinTable[handle].CIN_HEIGHT, 256, 256, (byte *)buf2, handle, true);
+ cinTable[handle].dirty = false;
+ Hunk_FreeTempMemory(buf2);
+ }
+ else
+ {
+ // Upload video at normal resolution
+ re.UploadCinematic(cinTable[handle].CIN_WIDTH, cinTable[handle].CIN_HEIGHT, cinTable[handle].drawX,
+ cinTable[handle].drawY, cinTable[handle].buf, handle, cinTable[handle].dirty);
+ cinTable[handle].dirty = false;
+ }
+
+ if (cl_inGameVideo->integer == 0 && cinTable[handle].playonwalls == 1)
+ {
+ cinTable[handle].playonwalls--;
+ }
+ else if (cl_inGameVideo->integer != 0 && cinTable[handle].playonwalls != 1)
+ {
+ cinTable[handle].playonwalls = 1;
+ }
+ }
+}
diff --git a/src/client/cl_console.cpp b/src/client/cl_console.cpp
new file mode 100644
index 0000000..e3e928c
--- /dev/null
+++ b/src/client/cl_console.cpp
@@ -0,0 +1,877 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
+
+This file is part of Tremulous.
+
+Tremulous 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.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+// console.c
+
+#include "client.h"
+
+#include "qcommon/cdefs.h"
+
+int g_console_field_width = 78;
+
+
+#define NUM_CON_TIMES 4
+
+#define CON_TEXTSIZE 163840
+typedef struct {
+ bool initialized;
+
+ short text[CON_TEXTSIZE];
+ int current; // line where next message will be printed
+ int x; // offset in current line for next print
+ int display; // bottom of console displays this line
+
+ int linewidth; // characters across screen
+ int totallines; // total lines in console scrollback
+
+ float xadjust; // for wide aspect screens
+
+ float displayFrac; // aproaches finalFrac at scr_conspeed
+ float finalFrac; // 0.0 to 1.0 lines of console to display
+
+ int vislines; // in scanlines
+
+ vec4_t color;
+} console_t;
+
+console_t con;
+
+cvar_t *con_conspeed;
+cvar_t *con_height;
+cvar_t *con_useShader;
+cvar_t *con_colorRed;
+cvar_t *con_colorGreen;
+cvar_t *con_colorBlue;
+cvar_t *con_colorAlpha;
+cvar_t *con_versionStr;
+
+#define DEFAULT_CONSOLE_WIDTH 78
+
+
+/*
+================
+Con_ToggleConsole_f
+================
+*/
+void Con_ToggleConsole_f (void) {
+ // Can't toggle the console when it's the only thing available
+ if ( clc.state == CA_DISCONNECTED && Key_GetCatcher( ) == KEYCATCH_CONSOLE ) {
+ return;
+ }
+
+ Field_Clear( &g_consoleField );
+ g_consoleField.widthInChars = g_console_field_width;
+
+ Key_SetCatcher( Key_GetCatcher( ) ^ KEYCATCH_CONSOLE );
+}
+
+/*
+===================
+Con_ToggleMenu_f
+===================
+*/
+void Con_ToggleMenu_f( void ) {
+ CL_KeyEvent( K_ESCAPE, true, Sys_Milliseconds() );
+ CL_KeyEvent( K_ESCAPE, false, Sys_Milliseconds() );
+}
+
+/*
+===================
+Con_MessageMode_f
+===================
+-*/
+void Con_MessageMode_f (void) {
+ chat_playerNum = -1;
+ chat_team = false;
+ chat_admins = false;
+ chat_clans = false;
+ Field_Clear( &chatField );
+ chatField.widthInChars = 30;
+
+ Key_SetCatcher( Key_GetCatcher( ) ^ KEYCATCH_MESSAGE );
+}
+
+/*
+====================
+Con_MessageMode2_f
+====================
+*/
+void Con_MessageMode2_f (void) {
+ chat_playerNum = -1;
+ chat_team = true;
+ chat_admins = false;
+ chat_clans = false;
+ Field_Clear( &chatField );
+ chatField.widthInChars = 25;
+ Key_SetCatcher( Key_GetCatcher( ) ^ KEYCATCH_MESSAGE );
+}
+
+/*
+===================
+Con_MessageMode3_f
+===================
+*/
+void Con_MessageMode3_f (void) {
+ chat_playerNum = VM_Call( cls.cgame, CG_CROSSHAIR_PLAYER );
+ if ( chat_playerNum < 0 || chat_playerNum >= MAX_CLIENTS ) {
+ chat_playerNum = -1;
+ return;
+ }
+ chat_team = false;
+ chat_admins = false;
+ chat_clans = false;
+ Field_Clear( &chatField );
+ chatField.widthInChars = 30;
+ Key_SetCatcher( Key_GetCatcher( ) ^ KEYCATCH_MESSAGE );
+}
+
+/*
+=====================
+Con_MessageMode4_f
+=====================
+*/
+void Con_MessageMode4_f (void) {
+ chat_playerNum = VM_Call( cls.cgame, CG_LAST_ATTACKER );
+ if ( chat_playerNum < 0 || chat_playerNum >= MAX_CLIENTS ) {
+ chat_playerNum = -1;
+ return;
+ }
+ chat_team = false;
+ chat_admins = false;
+ chat_clans = false;
+ Field_Clear( &chatField );
+ chatField.widthInChars = 30;
+ Key_SetCatcher( Key_GetCatcher( ) ^ KEYCATCH_MESSAGE );
+}
+
+/*
+================
+Con_MessageMode5_f
+================
+*/
+void Con_MessageMode5_f (void) {
+ int i;
+ chat_playerNum = -1;
+ chat_team = false;
+ chat_admins = true;
+ chat_clans = false;
+ Field_Clear( &chatField );
+ chatField.widthInChars = 25;
+ Key_SetCatcher( Key_GetCatcher( ) ^ KEYCATCH_MESSAGE );
+}
+
+/*
+================
+Con_MessageMode6_f
+================
+*/
+void Con_MessageMode6_f (void) {
+ int i;
+ chat_playerNum = -1;
+ chat_team = false;
+ chat_admins = false;
+ chat_clans = true;
+ Field_Clear( &chatField );
+ chatField.widthInChars = 25;
+ Key_SetCatcher( Key_GetCatcher( ) ^ KEYCATCH_MESSAGE );
+}
+
+/*
+================
+Con_Clear_f
+================
+*/
+void Con_Clear_f (void) {
+ int i;
+
+ for ( i = 0 ; i < CON_TEXTSIZE ; i++ ) {
+ con.text[i] = (ColorIndex(COLOR_WHITE)<<8) | ' ';
+ }
+
+ Con_Bottom(); // go to end
+}
+
+
+/*
+================
+Con_Dump_f
+
+Save the console contents out to a file
+================
+*/
+void Con_Dump_f (void)
+{
+ int l, x, i;
+ short *line;
+ fileHandle_t f;
+ int bufferlen;
+ char *buffer;
+ char filename[MAX_QPATH];
+
+ if (Cmd_Argc() != 2)
+ {
+ Com_Printf ("usage: condump <filename>\n");
+ return;
+ }
+
+ Q_strncpyz( filename, Cmd_Argv( 1 ), sizeof( filename ) );
+ COM_DefaultExtension( filename, sizeof( filename ), ".txt" );
+
+ if (!COM_CompareExtension(filename, ".txt"))
+ {
+ Com_Printf("Con_Dump_f: Only the \".txt\" extension is supported by this command!\n");
+ return;
+ }
+
+ f = FS_FOpenFileWrite( filename );
+ if (!f)
+ {
+ Com_Printf("ERROR: couldn't open %s.\n", filename);
+ return;
+ }
+
+ Com_Printf("Dumped console text to %s.\n", filename );
+
+ // skip empty lines
+ for (l = con.current - con.totallines + 1 ; l <= con.current ; l++)
+ {
+ line = con.text + (l%con.totallines)*con.linewidth;
+ for (x=0 ; x<con.linewidth ; x++)
+ if ((line[x] & 0xff) != ' ')
+ break;
+ if (x != con.linewidth)
+ break;
+ }
+
+#ifdef _WIN32
+ bufferlen = con.linewidth + 3 * sizeof ( char );
+#else
+ bufferlen = con.linewidth + 2 * sizeof ( char );
+#endif
+
+ buffer = (char*)Hunk_AllocateTempMemory( bufferlen );
+
+ // write the remaining lines
+ buffer[bufferlen-1] = 0;
+ for ( ; l <= con.current ; l++)
+ {
+ line = con.text + (l%con.totallines)*con.linewidth;
+ for(i=0; i<con.linewidth; i++)
+ buffer[i] = line[i] & 0xff;
+ for (x=con.linewidth-1 ; x>=0 ; x--)
+ {
+ if (buffer[x] == ' ')
+ buffer[x] = 0;
+ else
+ break;
+ }
+#ifdef _WIN32
+ Q_strcat(buffer, bufferlen, "\r\n");
+#else
+ Q_strcat(buffer, bufferlen, "\n");
+#endif
+ FS_Write(buffer, strlen(buffer), f);
+ }
+
+ Hunk_FreeTempMemory( buffer );
+ FS_FCloseFile( f );
+}
+
+
+/*
+================
+Con_ClearNotify
+================
+*/
+void Con_ClearNotify( void ) {
+ Cmd_TokenizeString( NULL );
+ CL_GameConsoleText( );
+}
+
+
+
+/*
+================
+Con_CheckResize
+
+If the line width has changed, reformat the buffer.
+================
+*/
+void Con_CheckResize (void)
+{
+ int i, j, width, oldwidth, oldtotallines, numlines, numchars;
+ short tbuf[CON_TEXTSIZE];
+
+ width = (SCREEN_WIDTH / SMALLCHAR_WIDTH) - 2;
+
+ if (width == con.linewidth)
+ return;
+
+ if (width < 1) // video hasn't been initialized yet
+ {
+ width = DEFAULT_CONSOLE_WIDTH;
+ con.linewidth = width;
+ con.totallines = CON_TEXTSIZE / con.linewidth;
+ for(i=0; i<CON_TEXTSIZE; i++)
+
+ con.text[i] = (ColorIndex(COLOR_WHITE)<<8) | ' ';
+ }
+ else
+ {
+ oldwidth = con.linewidth;
+ con.linewidth = width;
+ oldtotallines = con.totallines;
+ con.totallines = CON_TEXTSIZE / con.linewidth;
+ numlines = oldtotallines;
+
+ if (con.totallines < numlines)
+ numlines = con.totallines;
+
+ numchars = oldwidth;
+
+ if (con.linewidth < numchars)
+ numchars = con.linewidth;
+
+ ::memcpy(tbuf, con.text, CON_TEXTSIZE * sizeof(short));
+ for(i=0; i<CON_TEXTSIZE; i++)
+
+ con.text[i] = (ColorIndex(COLOR_WHITE)<<8) | ' ';
+
+
+ for (i=0 ; i<numlines ; i++)
+ {
+ for (j=0 ; j<numchars ; j++)
+ {
+ con.text[(con.totallines - 1 - i) * con.linewidth + j] =
+ tbuf[((con.current - i + oldtotallines) %
+ oldtotallines) * oldwidth + j];
+ }
+ }
+ }
+
+ con.current = con.totallines - 1;
+ con.display = con.current;
+}
+
+/*
+==================
+Cmd_CompleteTxtName
+==================
+*/
+void Cmd_CompleteTxtName( char *args UNUSED, int argNum ) {
+ if( argNum == 2 ) {
+ Field_CompleteFilename( "", "txt", false, true );
+ }
+}
+
+/*
+================
+Con_MessageModesInit
+================
+*/
+void Con_MessageModesInit(void) {
+ if( clc.netchan.alternateProtocol == 2 )
+ {
+ // add the client side message modes for 1.1 servers
+ if( !Cmd_CommadExists( "messagemode" ) )
+ Cmd_AddCommand ("messagemode", Con_MessageMode_f);
+ if( !Cmd_CommadExists( "messagemode2" ) )
+ Cmd_AddCommand ("messagemode2", Con_MessageMode2_f);
+ if( !Cmd_CommadExists( "messagemode3" ) )
+ Cmd_AddCommand ("messagemode3", Con_MessageMode3_f);
+ if( !Cmd_CommadExists( "messagemode4" ) )
+ Cmd_AddCommand ("messagemode4", Con_MessageMode4_f);
+ if( !Cmd_CommadExists( "messagemode5" ) )
+ Cmd_AddCommand ("messagemode5", Con_MessageMode5_f);
+ if( !Cmd_CommadExists( "messagemode6" ) )
+ Cmd_AddCommand ("messagemode6", Con_MessageMode6_f);
+ } else
+ {
+ // remove the client side message modes for non-1.1 servers
+ Cmd_RemoveCommand("messagemode");
+ Cmd_RemoveCommand("messagemode2");
+ Cmd_RemoveCommand("messagemode3");
+ Cmd_RemoveCommand("messagemode4");
+ Cmd_RemoveCommand("messagemode5");
+ Cmd_RemoveCommand("messagemode6");
+ }
+}
+
+/*
+================
+Con_Init
+================
+*/
+void Con_Init (void) {
+ int i;
+
+ con_conspeed = Cvar_Get ("scr_conspeed", "3", 0);
+ con_useShader = Cvar_Get ("scr_useShader", "1", CVAR_ARCHIVE);
+ con_height = Cvar_Get ("scr_height", "50", CVAR_ARCHIVE);
+ con_colorRed = Cvar_Get ("scr_colorRed", "0", CVAR_ARCHIVE);
+ con_colorBlue = Cvar_Get ("scr_colorBlue", "0", CVAR_ARCHIVE);
+ con_colorGreen = Cvar_Get ("scr_colorGreen", "0", CVAR_ARCHIVE);
+ con_colorAlpha = Cvar_Get ("scr_colorAlpha", ".8", CVAR_ARCHIVE);
+ con_versionStr = Cvar_Get ("scr_versionString", Q3_VERSION, CVAR_ARCHIVE);
+
+ Field_Clear( &g_consoleField );
+ g_consoleField.widthInChars = g_console_field_width;
+ for ( i = 0 ; i < COMMAND_HISTORY ; i++ ) {
+ Field_Clear( &historyEditLines[i] );
+ historyEditLines[i].widthInChars = g_console_field_width;
+ }
+ CL_LoadConsoleHistory( );
+ if( clc.netchan.alternateProtocol == 2 )
+ {
+ Cmd_AddCommand ("messagemode", Con_MessageMode_f);
+ Cmd_AddCommand ("messagemode2", Con_MessageMode2_f);
+ Cmd_AddCommand ("messagemode3", Con_MessageMode3_f);
+ Cmd_AddCommand ("messagemode4", Con_MessageMode4_f);
+ Cmd_AddCommand ("messagemode5", Con_MessageMode5_f);
+ Cmd_AddCommand ("messagemode6", Con_MessageMode6_f);
+ }
+
+ Cmd_AddCommand ("toggleconsole", Con_ToggleConsole_f);
+ Cmd_AddCommand ("togglemenu", Con_ToggleMenu_f);
+ Cmd_AddCommand ("clear", Con_Clear_f);
+ Cmd_AddCommand ("condump", Con_Dump_f);
+ Cmd_SetCommandCompletionFunc( "condump", Cmd_CompleteTxtName );
+}
+
+/*
+================
+Con_Shutdown
+================
+*/
+void Con_Shutdown(void)
+{
+ Cmd_RemoveCommand("toggleconsole");
+ Cmd_RemoveCommand("togglemenu");
+ Cmd_RemoveCommand("clear");
+ Cmd_RemoveCommand("condump");
+}
+
+/*
+===============
+Con_Linefeed
+===============
+*/
+void Con_Linefeed(void)
+{
+ int i;
+
+ con.x = 0;
+
+ if (con.display == con.current)
+ con.display++;
+
+ con.current++;
+
+ for(i=0; i<con.linewidth; i++)
+ con.text[(con.current%con.totallines)*con.linewidth+i] = (ColorIndex(COLOR_WHITE)<<8) | ' ';
+}
+
+/*
+================
+CL_ConsolePrint
+
+Handles cursor positioning, line wrapping, etc
+All console printing must go through this in order to be logged to disk
+If no console is visible, the text will appear at the top of the game window
+================
+*/
+void CL_ConsolePrint( const char *txt )
+{
+ int y, l;
+ unsigned char c;
+ unsigned short color;
+ bool skipnotify = false; // NERVE - SMF
+
+ // TTimo - prefix for text that shows up in console but not in notify
+ // backported from RTCW
+ if ( !Q_strncmp( txt, "[skipnotify]", 12 ) ) {
+ skipnotify = true;
+ txt += 12;
+ }
+
+ // for some demos we don't want to ever show anything on the console
+ if ( cl_noprint && cl_noprint->integer )
+ return;
+
+ if (!con.initialized) {
+ con.color[0] =
+ con.color[1] =
+ con.color[2] =
+ con.color[3] = 1.0f;
+ con.linewidth = -1;
+ Con_CheckResize ();
+ con.initialized = true;
+ }
+
+ if( !skipnotify && !(Key_GetCatcher( ) & KEYCATCH_CONSOLE) )
+ {
+ Cmd_SaveCmdContext( );
+
+ // feed the text to cgame
+ Cmd_TokenizeString( txt );
+ CL_GameConsoleText( );
+
+ Cmd_RestoreCmdContext( );
+ }
+
+ color = ColorIndex(COLOR_WHITE);
+
+ while ( (c = *((unsigned char *)txt)) != 0 )
+ {
+ if ( Q_IsColorString( txt ) )
+ {
+ color = ColorIndex( *(txt+1) );
+ txt += 2;
+ continue;
+ }
+
+ // count word length
+ for (l=0 ; l< con.linewidth ; l++)
+ {
+ if ( txt[l] <= ' ')
+ break;
+ }
+
+ // word wrap
+ if (l != con.linewidth && (con.x + l >= con.linewidth) )
+ Con_Linefeed();
+
+ txt++;
+
+ switch (c)
+ {
+ case INDENT_MARKER:
+ break;
+ case '\n':
+ Con_Linefeed();
+ break;
+ case '\r':
+ con.x = 0;
+ break;
+ default: // display character and advance
+ y = con.current % con.totallines;
+ con.text[y*con.linewidth+con.x] = (color << 8) | c;
+ con.x++;
+ if(con.x >= con.linewidth)
+ Con_Linefeed();
+ break;
+ }
+ }
+}
+
+
+/*
+==============================================================================
+
+DRAWING
+
+==============================================================================
+*/
+
+
+/*
+================
+Con_DrawInput
+
+Draw the editline after a ] prompt
+================
+*/
+static void Con_DrawInput (void)
+{
+ int y;
+
+ if ( clc.state != CA_DISCONNECTED && !(Key_GetCatcher( ) & KEYCATCH_CONSOLE ) ) {
+ return;
+ }
+
+ y = con.vislines - ( SMALLCHAR_HEIGHT * 2 );
+
+ re.SetColor( con.color );
+
+ SCR_DrawSmallChar( con.xadjust + 1 * SMALLCHAR_WIDTH, y, ']' );
+
+ Field_Draw( &g_consoleField,
+ con.xadjust + 2 * SMALLCHAR_WIDTH,
+ y,
+ SCREEN_WIDTH - 3 * SMALLCHAR_WIDTH,
+ true, true );
+}
+
+/*
+================
+Con_DrawSolidConsole
+
+Draws the console with the solid background
+================
+*/
+static void Con_DrawSolidConsole( float frac )
+{
+ int i, x, y;
+ int rows;
+ short *text;
+ int row;
+ int lines;
+ int currentColor;
+ vec4_t color;
+
+ lines = cls.glconfig.vidHeight * frac;
+ if (lines <= 0)
+ return;
+
+ if (lines > cls.glconfig.vidHeight )
+ lines = cls.glconfig.vidHeight;
+
+ // on wide screens, we will center the text
+ con.xadjust = 0;
+ SCR_AdjustFrom640( &con.xadjust, NULL, NULL, NULL );
+
+ // draw the background
+ y = frac * SCREEN_HEIGHT;
+ if ( y < 1 )
+ {
+ y = 0;
+ }
+ else if (con_useShader->integer)
+ {
+ SCR_DrawPic(0, 0, SCREEN_WIDTH, y, cls.consoleShader);
+ }
+ else
+ {
+ color[0] = con_colorRed->value;
+ color[1] = con_colorGreen->value;
+ color[2] = con_colorBlue->value;
+ color[3] = con_colorAlpha->value;
+ SCR_FillRect(0, 0, SCREEN_WIDTH, y, color);
+ }
+
+ color[0] = 1;
+ color[1] = 0;
+ color[2] = 0;
+ color[3] = 1;
+ SCR_FillRect(0, y, SCREEN_WIDTH, 2, color);
+
+
+ // draw the version number
+
+ re.SetColor( g_color_table[ColorIndex(COLOR_RED)] );
+
+ i = strlen( Q3_VERSION );
+
+ for (x=0 ; x<i ; x++) {
+ SCR_DrawSmallChar( cls.glconfig.vidWidth - ( i - x + 1 ) * SMALLCHAR_WIDTH,
+ lines - SMALLCHAR_HEIGHT, Q3_VERSION[x] );
+ }
+
+ // draw the text
+ con.vislines = lines;
+ rows = (lines-SMALLCHAR_HEIGHT)/SMALLCHAR_HEIGHT; // rows of text to draw
+
+ y = lines - (SMALLCHAR_HEIGHT*3);
+
+ // draw from the bottom up
+ if (con.display != con.current)
+ {
+ // draw arrows to show the buffer is backscrolled
+ re.SetColor( g_color_table[ColorIndex(COLOR_RED)] );
+ for (x=0 ; x<con.linewidth ; x+=4)
+ SCR_DrawSmallChar( con.xadjust + (x+1)*SMALLCHAR_WIDTH, y, '^' );
+ y -= SMALLCHAR_HEIGHT;
+ rows--;
+ }
+
+ row = con.display;
+
+ if ( con.x == 0 ) {
+ row--;
+ }
+
+ currentColor = 7;
+ re.SetColor( g_color_table[currentColor] );
+
+ for (i=0 ; i<rows ; i++, y -= SMALLCHAR_HEIGHT, row--)
+ {
+ if (row < 0)
+ break;
+ if (con.current - row >= con.totallines) {
+ // past scrollback wrap point
+ continue;
+ }
+
+ text = con.text + (row % con.totallines)*con.linewidth;
+
+ for (x=0 ; x<con.linewidth ; x++) {
+ if ( ( text[x] & 0xff ) == ' ' ) {
+ continue;
+ }
+
+ if ( ColorIndexForNumber( text[x]>>8 ) != currentColor ) {
+ currentColor = ColorIndexForNumber( text[x]>>8 );
+ re.SetColor( g_color_table[currentColor] );
+ }
+ SCR_DrawSmallChar( con.xadjust + (x+1)*SMALLCHAR_WIDTH, y, text[x] & 0xff );
+ }
+ }
+
+ // draw the input prompt, user text, and cursor if desired
+ Con_DrawInput ();
+
+ re.SetColor( NULL );
+}
+
+
+
+/*
+==================
+Con_DrawConsole
+==================
+*/
+void Con_DrawConsole( void ) {
+ // check for console width changes from a vid mode change
+ Con_CheckResize ();
+
+ // if disconnected, render console full screen
+ if ( clc.state == CA_DISCONNECTED ) {
+ if ( !( Key_GetCatcher( ) & (KEYCATCH_UI | KEYCATCH_CGAME)) ) {
+ Con_DrawSolidConsole( 1.0 );
+ return;
+ }
+ }
+
+ // draw the chat line
+ if( clc.netchan.alternateProtocol == 2 &&
+ ( Key_GetCatcher( ) & KEYCATCH_MESSAGE ) )
+ {
+ int skip;
+
+ if( chatField.buffer[0] == '/' ||
+ chatField.buffer[0] == '\\' )
+ {
+ SCR_DrawBigString( 8, 232, "Command:", 1.0f, qfalse );
+ skip = 10;
+ }
+ else if( chat_team )
+ {
+ SCR_DrawBigString( 8, 232, "Team Say:", 1.0f, qfalse );
+ skip = 11;
+ }
+ else if( chat_admins )
+ {
+ SCR_DrawBigString( 8, 232, "Admin Say:", 1.0f, qfalse );
+ skip = 11;
+ }
+ else if( chat_clans )
+ {
+ SCR_DrawBigString( 8, 232, "Clan Say:", 1.0f, qfalse );
+ skip = 11;
+ }
+ else
+ {
+ SCR_DrawBigString( 8, 232, "Say:", 1.0f, qfalse );
+ skip = 5;
+ }
+
+ Field_BigDraw( &chatField, skip * BIGCHAR_WIDTH, 232,
+ SCREEN_WIDTH - ( skip + 1 ) * BIGCHAR_WIDTH, qtrue, qtrue );
+ }
+
+ if ( con.displayFrac ) {
+ Con_DrawSolidConsole( con.displayFrac );
+ }
+
+ if( Key_GetCatcher( ) & ( KEYCATCH_UI | KEYCATCH_CGAME ) )
+ return;
+}
+//================================================================
+
+/*
+==================
+Con_RunConsole
+
+Scroll it up or down
+==================
+*/
+void Con_RunConsole (void) {
+ // decide on the destination height of the console
+ if ( Key_GetCatcher( ) & KEYCATCH_CONSOLE )
+ con.finalFrac = MAX(0.10, 0.01 * con_height->integer);
+ else
+ con.finalFrac = 0; // none visible
+
+ // scroll towards the destination height
+ if (con.finalFrac < con.displayFrac)
+ {
+ con.displayFrac -= con_conspeed->value*cls.realFrametime*0.001;
+ if (con.finalFrac > con.displayFrac)
+ con.displayFrac = con.finalFrac;
+
+ }
+ else if (con.finalFrac > con.displayFrac)
+ {
+ con.displayFrac += con_conspeed->value*cls.realFrametime*0.001;
+ if (con.finalFrac < con.displayFrac)
+ con.displayFrac = con.finalFrac;
+ }
+
+}
+
+
+void Con_PageUp( void ) {
+ con.display -= 2;
+ if ( con.current - con.display >= con.totallines ) {
+ con.display = con.current - con.totallines + 1;
+ }
+}
+
+void Con_PageDown( void ) {
+ con.display += 2;
+ if (con.display > con.current) {
+ con.display = con.current;
+ }
+}
+
+void Con_Top( void ) {
+ con.display = con.totallines;
+ if ( con.current - con.display >= con.totallines ) {
+ con.display = con.current - con.totallines + 1;
+ }
+}
+
+void Con_Bottom( void ) {
+ con.display = con.current;
+}
+
+
+void Con_Close( void ) {
+ if ( !com_cl_running->integer ) {
+ return;
+ }
+ Field_Clear( &g_consoleField );
+ Key_SetCatcher( Key_GetCatcher( ) & ~KEYCATCH_CONSOLE );
+ con.finalFrac = 0; // none visible
+ con.displayFrac = 0;
+}
diff --git a/src/client/cl_curl.cpp b/src/client/cl_curl.cpp
new file mode 100644
index 0000000..0c81985
--- /dev/null
+++ b/src/client/cl_curl.cpp
@@ -0,0 +1,364 @@
+/*
+===========================================================================
+Copyright (C) 2006 Tony J. White (tjw@tjw.org)
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
+
+This file is part of Tremulous.
+
+Tremulous 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.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+
+#include "client.h"
+
+#ifdef USE_CURL_DLOPEN
+#include "sys/sys_loadlib.h"
+
+cvar_t *cl_cURLLib;
+
+char* (*qcurl_version)(void);
+
+CURL* (*qcurl_easy_init)(void);
+CURLcode (*qcurl_easy_setopt)(CURL *curl, CURLoption option, ...);
+CURLcode (*qcurl_easy_perform)(CURL *curl);
+void (*qcurl_easy_cleanup)(CURL *curl);
+CURLcode (*qcurl_easy_getinfo)(CURL *curl, CURLINFO info, ...);
+CURL* (*qcurl_easy_duphandle)(CURL *curl);
+void (*qcurl_easy_reset)(CURL *curl);
+const char *(*qcurl_easy_strerror)(CURLcode);
+
+CURLM* (*qcurl_multi_init)(void);
+CURLMcode (*qcurl_multi_add_handle)(CURLM*, CURL*curl_handle);
+CURLMcode (*qcurl_multi_remove_handle)(CURLM*, CURL*);
+CURLMcode (*qcurl_multi_fdset)(CURLM*, fd_set* read_set, fd_set* write_set, fd_set* exc_set, int*);
+CURLMcode (*qcurl_multi_perform)(CURLM*, int*);
+CURLMcode (*qcurl_multi_cleanup)(CURLM*);
+CURLMsg *(*qcurl_multi_info_read)(CURLM*, int*);
+const char *(*qcurl_multi_strerror)(CURLMcode);
+
+struct curl_slist* (*qcurl_slist_append)(struct curl_slist*, const char*);
+void (*qcurl_slist_free_all)(struct curl_slist*);
+
+CURLcode (*qcurl_global_init)(long);
+void (*qcurl_global_cleanup)(void);
+
+
+static void *cURLLib = NULL;
+
+/*
+=================
+GPA
+=================
+*/
+static void *GPA(const char *str)
+{
+ void* rv = Sys_LoadFunction(cURLLib, str);
+ if(!rv)
+ {
+ Com_Printf("Can't load symbol %s\n", str);
+ clc.cURLEnabled = false;
+ return NULL;
+ }
+ else
+ {
+ Com_DPrintf("Loaded symbol %s (0x%p)\n", str, rv);
+ return rv;
+ }
+}
+#endif /* USE_CURL_DLOPEN */
+
+/*
+=================
+CL_cURL_Init
+=================
+*/
+bool CL_cURL_Init()
+{
+#ifdef USE_CURL_DLOPEN
+ cl_cURLLib = Cvar_Get("cl_cURLLib", DEFAULT_CURL_LIB, CVAR_ARCHIVE | CVAR_PROTECTED);
+
+ if(cURLLib)
+ return true;
+
+ Com_Printf("Loading \"%s\"...", cl_cURLLib->string);
+ if( !(cURLLib = Sys_LoadDll(cl_cURLLib->string, true)) )
+ {
+#ifdef ALTERNATE_CURL_LIB
+ // On some linux distributions there is no libcurl.so.3, but only libcurl.so.4. That one works too.
+ if( !(cURLLib = Sys_LoadDll(ALTERNATE_CURL_LIB, true)) )
+#endif
+ return false;
+ }
+
+ clc.cURLEnabled = true;
+
+ qcurl_version = (decltype(qcurl_version)) GPA("curl_version");
+
+ qcurl_easy_init = (decltype(qcurl_easy_init)) GPA("curl_easy_init");
+ qcurl_easy_setopt = (decltype(qcurl_easy_setopt)) GPA("curl_easy_setopt");
+ qcurl_easy_perform = (decltype(qcurl_easy_perform)) GPA("curl_easy_perform");
+ qcurl_easy_cleanup = (decltype(qcurl_easy_cleanup)) GPA("curl_easy_cleanup");
+ qcurl_easy_getinfo = (decltype(qcurl_easy_getinfo)) GPA("curl_easy_getinfo");
+ qcurl_easy_duphandle = (decltype(qcurl_easy_duphandle)) GPA("curl_easy_duphandle");
+ qcurl_easy_reset = (decltype(qcurl_easy_reset)) GPA("curl_easy_reset");
+ qcurl_easy_strerror = (decltype(qcurl_easy_strerror)) GPA("curl_easy_strerror");
+
+ qcurl_multi_init = (decltype(qcurl_multi_init)) GPA("curl_multi_init");
+ qcurl_multi_add_handle = (decltype(qcurl_multi_add_handle)) GPA("curl_multi_add_handle");
+ qcurl_multi_remove_handle = (decltype(qcurl_multi_remove_handle)) GPA("curl_multi_remove_handle");
+ qcurl_multi_fdset = (decltype(qcurl_multi_fdset)) GPA("curl_multi_fdset");
+ qcurl_multi_perform = (decltype(qcurl_multi_perform)) GPA("curl_multi_perform");
+ qcurl_multi_cleanup = (decltype(qcurl_multi_cleanup)) GPA("curl_multi_cleanup");
+ qcurl_multi_info_read = (decltype(qcurl_multi_info_read)) GPA("curl_multi_info_read");
+ qcurl_multi_strerror = (decltype(qcurl_multi_strerror)) GPA("curl_multi_strerror");
+ qcurl_slist_append = (decltype(qcurl_slist_append)) GPA("curl_slist_append");
+ qcurl_slist_free_all = (decltype(qcurl_slist_free_all)) GPA("curl_slist_free_all");
+ qcurl_global_init = (decltype(qcurl_global_init)) GPA("curl_global_init");
+ qcurl_global_cleanup = (decltype(qcurl_global_cleanup)) GPA("curl_global_cleanup");
+
+ if(!clc.cURLEnabled)
+ {
+ CL_cURL_Shutdown();
+ Com_Printf("FAIL One or more symbols not found\n");
+ return false;
+ }
+ Com_Printf("OK\n");
+
+ return true;
+#else
+ clc.cURLEnabled = true;
+ return true;
+#endif /* USE_CURL_DLOPEN */
+}
+
+/*
+=================
+CL_cURL_Shutdown
+=================
+*/
+void CL_cURL_Shutdown( void )
+{
+ CL_cURL_Cleanup();
+#ifdef USE_CURL_DLOPEN
+ if(cURLLib)
+ {
+ Sys_UnloadLibrary(cURLLib);
+ cURLLib = NULL;
+ }
+ qcurl_easy_init = NULL;
+ qcurl_easy_setopt = NULL;
+ qcurl_easy_perform = NULL;
+ qcurl_easy_cleanup = NULL;
+ qcurl_easy_getinfo = NULL;
+ qcurl_easy_duphandle = NULL;
+ qcurl_easy_reset = NULL;
+
+ qcurl_multi_init = NULL;
+ qcurl_multi_add_handle = NULL;
+ qcurl_multi_remove_handle = NULL;
+ qcurl_multi_fdset = NULL;
+ qcurl_multi_perform = NULL;
+ qcurl_multi_cleanup = NULL;
+ qcurl_multi_info_read = NULL;
+ qcurl_multi_strerror = NULL;
+#endif /* USE_CURL_DLOPEN */
+}
+
+void CL_cURL_Cleanup(void)
+{
+ if(clc.downloadCURLM) {
+ CURLMcode result;
+
+ if(clc.downloadCURL) {
+ result = qcurl_multi_remove_handle(clc.downloadCURLM,
+ clc.downloadCURL);
+ if(result != CURLM_OK) {
+ Com_DPrintf("qcurl_multi_remove_handle failed: %s\n", qcurl_multi_strerror(result));
+ }
+ qcurl_easy_cleanup(clc.downloadCURL);
+ }
+ result = qcurl_multi_cleanup(clc.downloadCURLM);
+ if(result != CURLM_OK) {
+ Com_DPrintf("CL_cURL_Cleanup: qcurl_multi_cleanup failed: %s\n", qcurl_multi_strerror(result));
+ }
+ clc.downloadCURLM = NULL;
+ clc.downloadCURL = NULL;
+ }
+ else if(clc.downloadCURL) {
+ qcurl_easy_cleanup(clc.downloadCURL);
+ clc.downloadCURL = NULL;
+ }
+}
+
+static int CL_cURL_CallbackProgress( void *dummy, double dltotal, double dlnow,
+ double ultotal, double ulnow )
+{
+ clc.downloadSize = (int)dltotal;
+ Cvar_SetValue( "cl_downloadSize", clc.downloadSize );
+ clc.downloadCount = (int)dlnow;
+ Cvar_SetValue( "cl_downloadCount", clc.downloadCount );
+ return 0;
+}
+
+static size_t CL_cURL_CallbackWrite(void *buffer, size_t size, size_t nmemb,
+ void *stream)
+{
+ FS_Write( buffer, size*nmemb, ((fileHandle_t*)stream)[0] );
+ return size*nmemb;
+}
+
+CURLcode qcurl_easy_setopt_warn(CURL *curl, CURLoption option, ...)
+{
+ CURLcode result;
+
+ va_list argp;
+ va_start(argp, option);
+
+ if(option < CURLOPTTYPE_OBJECTPOINT) {
+ long longValue = va_arg(argp, long);
+ result = qcurl_easy_setopt(curl, option, longValue);
+ } else if(option < CURLOPTTYPE_OFF_T) {
+ void *pointerValue = va_arg(argp, void *);
+ result = qcurl_easy_setopt(curl, option, pointerValue);
+ } else {
+ curl_off_t offsetValue = va_arg(argp, curl_off_t);
+ result = qcurl_easy_setopt(curl, option, offsetValue);
+ }
+
+ if(result != CURLE_OK) {
+ Com_DPrintf("qcurl_easy_setopt failed: %s\n", qcurl_easy_strerror(result));
+ }
+ va_end(argp);
+
+ return result;
+}
+
+void CL_cURL_BeginDownload( const char *localName, const char *remoteURL )
+{
+ CURLMcode result;
+
+ clc.cURLUsed = true;
+ Com_Printf("URL: %s\n", remoteURL);
+ Com_DPrintf("***** CL_cURL_BeginDownload *****\n"
+ "Localname: %s\n"
+ "RemoteURL: %s\n"
+ "****************************\n", localName, remoteURL);
+ CL_cURL_Cleanup();
+ Q_strncpyz(clc.downloadURL, remoteURL, sizeof(clc.downloadURL));
+ Q_strncpyz(clc.downloadName, localName, sizeof(clc.downloadName));
+ Com_sprintf(clc.downloadTempName, sizeof(clc.downloadTempName),
+ "%s.tmp", localName);
+
+ // Set so UI gets access to it
+ Cvar_Set("cl_downloadName", localName);
+ Cvar_Set("cl_downloadSize", "0");
+ Cvar_Set("cl_downloadCount", "0");
+ Cvar_SetValue("cl_downloadTime", cls.realtime);
+
+ clc.downloadBlock = 0; // Starting new file
+ clc.downloadCount = 0;
+
+ clc.downloadCURL = qcurl_easy_init();
+ if(!clc.downloadCURL) {
+ Com_Error(ERR_DROP, "CL_cURL_BeginDownload: qcurl_easy_init() "
+ "failed");
+ return;
+ }
+ clc.download = FS_SV_FOpenFileWrite(clc.downloadTempName);
+ if(!clc.download) {
+ Com_Error(ERR_DROP, "CL_cURL_BeginDownload: failed to open "
+ "%s for writing", clc.downloadTempName);
+ return;
+ }
+
+ if(com_developer->integer)
+ qcurl_easy_setopt_warn(clc.downloadCURL, CURLOPT_VERBOSE, 1);
+ qcurl_easy_setopt_warn(clc.downloadCURL, CURLOPT_URL, clc.downloadURL);
+ qcurl_easy_setopt_warn(clc.downloadCURL, CURLOPT_TRANSFERTEXT, 0);
+ qcurl_easy_setopt_warn(clc.downloadCURL, CURLOPT_REFERER, va("Tremulous://%s", NET_AdrToString(clc.serverAddress)));
+ qcurl_easy_setopt_warn(clc.downloadCURL, CURLOPT_USERAGENT, va("%s %s", Q3_VERSION, qcurl_version()));
+ qcurl_easy_setopt_warn(clc.downloadCURL, CURLOPT_WRITEFUNCTION, CL_cURL_CallbackWrite);
+ qcurl_easy_setopt_warn(clc.downloadCURL, CURLOPT_WRITEDATA, &clc.download);
+ qcurl_easy_setopt_warn(clc.downloadCURL, CURLOPT_NOPROGRESS, 0);
+ qcurl_easy_setopt_warn(clc.downloadCURL, CURLOPT_PROGRESSFUNCTION, CL_cURL_CallbackProgress);
+ qcurl_easy_setopt_warn(clc.downloadCURL, CURLOPT_PROGRESSDATA, NULL);
+ qcurl_easy_setopt_warn(clc.downloadCURL, CURLOPT_FAILONERROR, 1);
+ qcurl_easy_setopt_warn(clc.downloadCURL, CURLOPT_FOLLOWLOCATION, 1);
+ qcurl_easy_setopt_warn(clc.downloadCURL, CURLOPT_MAXREDIRS, 5);
+ qcurl_easy_setopt_warn(clc.downloadCURL, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS | CURLPROTO_FTP | CURLPROTO_FTPS );
+ clc.downloadCURLM = qcurl_multi_init();
+ if(!clc.downloadCURLM)
+ {
+ qcurl_easy_cleanup(clc.downloadCURL);
+ clc.downloadCURL = NULL;
+ Com_Error(ERR_DROP, "CL_cURL_BeginDownload: qcurl_multi_init() "
+ "failed");
+ return;
+ }
+
+ result = qcurl_multi_add_handle(clc.downloadCURLM, clc.downloadCURL);
+ if(result != CURLM_OK)
+ {
+ qcurl_easy_cleanup(clc.downloadCURL);
+ clc.downloadCURL = NULL;
+ Com_Error(ERR_DROP,"CL_cURL_BeginDownload: qcurl_multi_add_handle() failed: %s", qcurl_multi_strerror(result));
+ return;
+ }
+
+ if(!(clc.sv_allowDownload & DLF_NO_DISCONNECT) && !clc.cURLDisconnected)
+ {
+ CL_AddReliableCommand("disconnect", true);
+ CL_WritePacket();
+ CL_WritePacket();
+ CL_WritePacket();
+ clc.cURLDisconnected = true;
+ }
+}
+
+void CL_cURL_PerformDownload(void)
+{
+ CURLMcode res;
+ CURLMsg *msg;
+ int c;
+ int i = 0;
+
+ res = qcurl_multi_perform(clc.downloadCURLM, &c);
+ while(res == CURLM_CALL_MULTI_PERFORM && i < 100) {
+ res = qcurl_multi_perform(clc.downloadCURLM, &c);
+ i++;
+ }
+ if(res == CURLM_CALL_MULTI_PERFORM)
+ return;
+ msg = qcurl_multi_info_read(clc.downloadCURLM, &c);
+ if(msg == NULL) {
+ return;
+ }
+ FS_FCloseFile(clc.download);
+ if(msg->msg == CURLMSG_DONE && msg->data.result == CURLE_OK) {
+ FS_SV_Rename(clc.downloadTempName, clc.downloadName, false);
+ clc.downloadRestart = true;
+ }
+ else {
+ long code;
+
+ qcurl_easy_getinfo(msg->easy_handle, CURLINFO_RESPONSE_CODE, &code);
+ Com_Error(ERR_DROP, "Download Error: %s Code: %ld URL: %s",
+ qcurl_easy_strerror(msg->data.result),
+ code, clc.downloadURL);
+ }
+
+ CL_NextDownload();
+}
diff --git a/src/client/cl_curl.h b/src/client/cl_curl.h
new file mode 100644
index 0000000..2e3b4e3
--- /dev/null
+++ b/src/client/cl_curl.h
@@ -0,0 +1,101 @@
+/*
+===========================================================================
+Copyright (C) 2006 Tony J. White (tjw@tjw.org)
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
+
+This file is part of Tremulous.
+
+Tremulous 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.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+
+#ifndef __QCURL_H__
+#define __QCURL_H__
+
+#ifdef USE_LOCAL_HEADERS
+#include "curl/curl.h"
+#else
+#include <curl/curl.h>
+#endif
+
+#include "qcommon/q_shared.h"
+#include "qcommon/qcommon.h"
+
+#ifdef USE_CURL_DLOPEN
+#ifdef _WIN32
+ #define DEFAULT_CURL_LIB "libcurl-4.dll"
+ #define ALTERNATE_CURL_LIB "libcurl-3.dll"
+#elif defined(__APPLE__)
+ #define DEFAULT_CURL_LIB "libcurl.dylib"
+#else
+ #define DEFAULT_CURL_LIB "libcurl.so.4"
+ #define ALTERNATE_CURL_LIB "libcurl.so.3"
+#endif
+
+extern cvar_t *cl_cURLLib;
+
+extern char* (*qcurl_version)(void);
+extern CURL* (*qcurl_easy_init)(void);
+extern CURLcode (*qcurl_easy_setopt)(CURL *curl, CURLoption option, ...);
+extern CURLcode (*qcurl_easy_perform)(CURL *curl);
+extern void (*qcurl_easy_cleanup)(CURL *curl);
+extern CURLcode (*qcurl_easy_getinfo)(CURL *curl, CURLINFO info, ...);
+extern void (*qcurl_easy_reset)(CURL *curl);
+extern const char* (*qcurl_easy_strerror)(CURLcode);
+extern CURLM* (*qcurl_multi_init)(void);
+extern CURLMcode (*qcurl_multi_add_handle)(CURLM *multi_handle, CURL *curl_handle);
+extern CURLMcode (*qcurl_multi_remove_handle)(CURLM *multi_handle, CURL *curl_handle);
+extern CURLMcode (*qcurl_multi_fdset)(CURLM *multi_handle, fd_set *read_fd_set, fd_set *write_fd_set, fd_set *exc_fd_set, int *max_fd);
+extern CURLMcode (*qcurl_multi_perform)(CURLM *multi_handle, int *running_handles);
+extern CURLMcode (*qcurl_multi_cleanup)(CURLM *multi_handle);
+extern CURLMsg* (*qcurl_multi_info_read)(CURLM *multi_handle, int *msgs_in_queue);
+extern const char* (*qcurl_multi_strerror)(CURLMcode);
+extern struct curl_slist* (*qcurl_slist_append)(struct curl_slist *, const char *);
+extern void (*qcurl_slist_free_all)(struct curl_slist *);
+extern CURLcode (*qcurl_global_init)(long flags);
+extern void (*qcurl_global_cleanup)(void);
+
+#else
+#define qcurl_version curl_version
+#define qcurl_easy_init curl_easy_init
+#define qcurl_easy_setopt curl_easy_setopt
+#define qcurl_easy_perform curl_easy_perform
+#define qcurl_easy_cleanup curl_easy_cleanup
+#define qcurl_easy_getinfo curl_easy_getinfo
+#define qcurl_easy_duphandle curl_easy_duphandle
+#define qcurl_easy_reset curl_easy_reset
+#define qcurl_easy_strerror curl_easy_strerror
+#define qcurl_multi_init curl_multi_init
+#define qcurl_multi_add_handle curl_multi_add_handle
+#define qcurl_multi_remove_handle curl_multi_remove_handle
+#define qcurl_multi_fdset curl_multi_fdset
+#define qcurl_multi_perform curl_multi_perform
+#define qcurl_multi_cleanup curl_multi_cleanup
+#define qcurl_multi_info_read curl_multi_info_read
+#define qcurl_multi_strerror curl_multi_strerror
+#define qcurl_slist_append curl_slist_append
+#define qcurl_slist_free_all curl_slist_free_all
+#define qcurl_global_init curl_global_init
+#define qcurl_global_cleanup curl_global_cleanup
+#endif
+
+bool CL_cURL_Init( void );
+void CL_cURL_Shutdown( void );
+void CL_cURL_BeginDownload( const char *localName, const char *remoteURL );
+void CL_cURL_PerformDownload( void );
+void CL_cURL_Cleanup( void );
+
+#endif // __QCURL_H__
diff --git a/src/client/cl_input.cpp b/src/client/cl_input.cpp
new file mode 100644
index 0000000..e134646
--- /dev/null
+++ b/src/client/cl_input.cpp
@@ -0,0 +1,1194 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
+
+This file is part of Tremulous.
+
+Tremulous 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.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+
+// cl.input.c -- builds an intended movement command to send to the server
+
+#include "client.h"
+
+unsigned frame_msec;
+int old_com_frameTime;
+
+/*
+===============================================================================
+
+KEY BUTTONS
+
+Continuous button event tracking is complicated by the fact that two different
+input sources (say, mouse button 1 and the control key) can both press the
+same button, but the button should only be released when both of the
+pressing key have been released.
+
+When a key event issues a button command (+forward, +attack, etc), it appends
+its key number as argv(1) so it can be matched up with the release.
+
+argv(2) will be set to the time the event happened, which allows exact
+control even at low framerates when the down and up events may both get qued
+at the same time.
+
+===============================================================================
+*/
+
+kbutton_t in_left, in_right, in_forward, in_back;
+kbutton_t in_lookup, in_lookdown, in_moveleft, in_moveright;
+kbutton_t in_strafe, in_speed;
+kbutton_t in_up, in_down;
+
+#ifdef USE_VOIP
+kbutton_t in_voiprecord;
+#endif
+
+kbutton_t in_buttons[16];
+
+bool in_mlooking;
+
+static void IN_CenterView(void)
+{
+ cl.viewangles[PITCH] = -SHORT2ANGLE( (clc.netchan.alternateProtocol == 2
+ ? cl.snap.alternatePs.delta_angles
+ : cl.snap.ps.delta_angles)[PITCH] );
+}
+
+static void IN_MLookDown(void)
+{
+ in_mlooking = true;
+}
+
+static void IN_MLookUp(void)
+{
+ in_mlooking = false;
+ if (!cl_freelook->integer)
+ {
+ IN_CenterView();
+ }
+}
+
+static void IN_KeyDown(kbutton_t *b)
+{
+ int k;
+
+ const char *c = Cmd_Argv(1);
+ if (c[0])
+ {
+ k = atoi(c);
+ }
+ else
+ {
+ k = -1; // typed manually at the console for continuous down
+ }
+
+ if (k == b->down[0] || k == b->down[1])
+ {
+ return; // repeating key
+ }
+
+ if (!b->down[0])
+ {
+ b->down[0] = k;
+ }
+ else if (!b->down[1])
+ {
+ b->down[1] = k;
+ }
+ else
+ {
+ Com_Printf("Three keys down for a button!\n");
+ return;
+ }
+
+ if (b->active)
+ {
+ return; // still down
+ }
+
+ // save timestamp for partial frame summing
+ c = Cmd_Argv(2);
+ b->downtime = atoi(c);
+
+ b->active = true;
+ b->wasPressed = true;
+}
+
+static void IN_KeyUp(kbutton_t *b)
+{
+ int k;
+ unsigned uptime;
+
+ const char *c = Cmd_Argv(1);
+ if (c[0])
+ {
+ k = atoi(c);
+ }
+ else
+ {
+ // typed manually at the console, assume for unsticking, so clear all
+ b->down[0] = b->down[1] = 0;
+ b->active = false;
+ return;
+ }
+
+ if (b->down[0] == k)
+ {
+ b->down[0] = 0;
+ }
+ else if (b->down[1] == k)
+ {
+ b->down[1] = 0;
+ }
+ else
+ {
+ return; // key up without coresponding down (menu pass through)
+ }
+ if (b->down[0] || b->down[1])
+ {
+ return; // some other key is still holding it down
+ }
+
+ b->active = false;
+
+ // save timestamp for partial frame summing
+ c = Cmd_Argv(2);
+ uptime = atoi(c);
+ if (uptime)
+ {
+ b->msec += uptime - b->downtime;
+ }
+ else
+ {
+ b->msec += frame_msec / 2;
+ }
+
+ b->active = false;
+}
+
+/*
+===============
+CL_KeyState
+
+Returns the fraction of the frame that the key was down
+===============
+*/
+static float CL_KeyState(kbutton_t *key)
+{
+ float val;
+ int msec;
+
+ msec = key->msec;
+ key->msec = 0;
+
+ if (key->active)
+ {
+ // still down
+ if (!key->downtime)
+ {
+ msec = com_frameTime;
+ }
+ else
+ {
+ msec += com_frameTime - key->downtime;
+ }
+ key->downtime = com_frameTime;
+ }
+
+#if 0
+ if (msec) {
+ Com_Printf ("%i ", msec);
+ }
+#endif
+
+ val = (float)msec / frame_msec;
+ if (val < 0)
+ {
+ val = 0;
+ }
+ if (val > 1)
+ {
+ val = 1;
+ }
+
+ return val;
+}
+
+static void IN_UpDown(void) { IN_KeyDown(&in_up); }
+static void IN_UpUp(void) { IN_KeyUp(&in_up); }
+static void IN_DownDown(void) { IN_KeyDown(&in_down); }
+static void IN_DownUp(void) { IN_KeyUp(&in_down); }
+static void IN_LeftDown(void) { IN_KeyDown(&in_left); }
+static void IN_LeftUp(void) { IN_KeyUp(&in_left); }
+static void IN_RightDown(void) { IN_KeyDown(&in_right); }
+static void IN_RightUp(void) { IN_KeyUp(&in_right); }
+static void IN_ForwardDown(void) { IN_KeyDown(&in_forward); }
+static void IN_ForwardUp(void) { IN_KeyUp(&in_forward); }
+static void IN_BackDown(void) { IN_KeyDown(&in_back); }
+static void IN_BackUp(void) { IN_KeyUp(&in_back); }
+static void IN_LookupDown(void) { IN_KeyDown(&in_lookup); }
+static void IN_LookupUp(void) { IN_KeyUp(&in_lookup); }
+static void IN_LookdownDown(void) { IN_KeyDown(&in_lookdown); }
+static void IN_LookdownUp(void) { IN_KeyUp(&in_lookdown); }
+static void IN_MoveleftDown(void) { IN_KeyDown(&in_moveleft); }
+static void IN_MoveleftUp(void) { IN_KeyUp(&in_moveleft); }
+static void IN_MoverightDown(void) { IN_KeyDown(&in_moveright); }
+static void IN_MoverightUp(void) { IN_KeyUp(&in_moveright); }
+static void IN_SpeedDown(void) { IN_KeyDown(&in_speed); }
+static void IN_SpeedUp(void) { IN_KeyUp(&in_speed); }
+static void IN_StrafeDown(void) { IN_KeyDown(&in_strafe); }
+static void IN_StrafeUp(void) { IN_KeyUp(&in_strafe); }
+
+#ifdef USE_VOIP
+static void IN_VoipRecordDown(void)
+{
+ IN_KeyDown(&in_voiprecord);
+ Cvar_Set("cl_voipSend", "1");
+}
+
+static void IN_VoipRecordUp(void)
+{
+ IN_KeyUp(&in_voiprecord);
+ Cvar_Set("cl_voipSend", "0");
+}
+#endif
+
+static void IN_Button0Down(void) { IN_KeyDown(&in_buttons[0]); }
+static void IN_Button0Up(void) { IN_KeyUp(&in_buttons[0]); }
+static void IN_Button1Down(void) { IN_KeyDown(&in_buttons[1]); }
+static void IN_Button1Up(void) { IN_KeyUp(&in_buttons[1]); }
+static void IN_Button2Down(void) { IN_KeyDown(&in_buttons[2]); }
+static void IN_Button2Up(void) { IN_KeyUp(&in_buttons[2]); }
+static void IN_Button3Down(void) { IN_KeyDown(&in_buttons[3]); }
+static void IN_Button3Up(void) { IN_KeyUp(&in_buttons[3]); }
+static void IN_Button4Down(void) { IN_KeyDown(&in_buttons[4]); }
+static void IN_Button4Up(void) { IN_KeyUp(&in_buttons[4]); }
+static void IN_Button5Down(void) { IN_KeyDown(&in_buttons[5]); }
+static void IN_Button5Up(void) { IN_KeyUp(&in_buttons[5]); }
+static void IN_Button6Down(void) { IN_KeyDown(&in_buttons[6]); }
+static void IN_Button6Up(void) { IN_KeyUp(&in_buttons[6]); }
+static void IN_Button7Down(void) { IN_KeyDown(&in_buttons[7]); }
+static void IN_Button7Up(void) { IN_KeyUp(&in_buttons[7]); }
+static void IN_Button8Down(void) { IN_KeyDown(&in_buttons[8]); }
+static void IN_Button8Up(void) { IN_KeyUp(&in_buttons[8]); }
+static void IN_Button9Down(void) { IN_KeyDown(&in_buttons[9]); }
+static void IN_Button9Up(void) { IN_KeyUp(&in_buttons[9]); }
+static void IN_Button10Down(void) { IN_KeyDown(&in_buttons[10]); }
+static void IN_Button10Up(void) { IN_KeyUp(&in_buttons[10]); }
+static void IN_Button11Down(void) { IN_KeyDown(&in_buttons[11]); }
+static void IN_Button11Up(void) { IN_KeyUp(&in_buttons[11]); }
+static void IN_Button12Down(void) { IN_KeyDown(&in_buttons[12]); }
+static void IN_Button12Up(void) { IN_KeyUp(&in_buttons[12]); }
+static void IN_Button13Down(void) { IN_KeyDown(&in_buttons[13]); }
+static void IN_Button13Up(void) { IN_KeyUp(&in_buttons[13]); }
+static void IN_Button14Down(void) { IN_KeyDown(&in_buttons[14]); }
+static void IN_Button14Up(void) { IN_KeyUp(&in_buttons[14]); }
+static void IN_Button15Down(void) { IN_KeyDown(&in_buttons[15]); }
+static void IN_Button15Up(void) { IN_KeyUp(&in_buttons[15]); }
+
+//==========================================================================
+
+cvar_t *cl_yawspeed;
+cvar_t *cl_pitchspeed;
+
+cvar_t *cl_run;
+
+cvar_t *cl_anglespeedkey;
+
+/*
+================
+CL_AdjustAngles
+
+Moves the local angle positions
+================
+*/
+static void CL_AdjustAngles(void)
+{
+ float speed;
+
+ if (in_speed.active)
+ {
+ speed = 0.001 * cls.frametime * cl_anglespeedkey->value;
+ }
+ else
+ {
+ speed = 0.001 * cls.frametime;
+ }
+
+ if (!in_strafe.active)
+ {
+ cl.viewangles[YAW] -= speed * cl_yawspeed->value * CL_KeyState(&in_right);
+ cl.viewangles[YAW] += speed * cl_yawspeed->value * CL_KeyState(&in_left);
+ }
+
+ cl.viewangles[PITCH] -= speed * cl_pitchspeed->value * CL_KeyState(&in_lookup);
+ cl.viewangles[PITCH] += speed * cl_pitchspeed->value * CL_KeyState(&in_lookdown);
+}
+
+/*
+================
+CL_KeyMove
+
+Sets the usercmd_t based on key states
+================
+*/
+static void CL_KeyMove(usercmd_t *cmd)
+{
+ int movespeed;
+ int forward, side, up;
+
+ //
+ // adjust for speed key / running
+ // the walking flag is to keep animations consistant
+ // even during acceleration and develeration
+ //
+ if (in_speed.active ^ cl_run->integer)
+ {
+ movespeed = 127;
+ cmd->buttons &= ~BUTTON_WALKING;
+ }
+ else
+ {
+ cmd->buttons |= BUTTON_WALKING;
+ movespeed = 64;
+ }
+
+ forward = 0;
+ side = 0;
+ up = 0;
+ if (in_strafe.active)
+ {
+ side += movespeed * CL_KeyState(&in_right);
+ side -= movespeed * CL_KeyState(&in_left);
+ }
+
+ side += movespeed * CL_KeyState(&in_moveright);
+ side -= movespeed * CL_KeyState(&in_moveleft);
+
+ up += movespeed * CL_KeyState(&in_up);
+ up -= movespeed * CL_KeyState(&in_down);
+
+ forward += movespeed * CL_KeyState(&in_forward);
+ forward -= movespeed * CL_KeyState(&in_back);
+
+ cmd->forwardmove = ClampChar(forward);
+ cmd->rightmove = ClampChar(side);
+ cmd->upmove = ClampChar(up);
+}
+
+/*
+=================
+CL_MouseEvent
+=================
+*/
+void CL_MouseEvent(int dx, int dy, int time)
+{
+ if (Key_GetCatcher() & KEYCATCH_UI)
+ {
+ VM_Call(cls.ui, UI_MOUSE_EVENT, dx, dy);
+ }
+ else if (Key_GetCatcher() & KEYCATCH_CGAME)
+ {
+ VM_Call(cls.cgame, CG_MOUSE_EVENT, dx, dy);
+ }
+ else
+ {
+ cl.mouseDx[cl.mouseIndex] += dx;
+ cl.mouseDy[cl.mouseIndex] += dy;
+ }
+}
+
+/*
+=================
+CL_JoystickEvent
+
+Joystick values stay set until changed
+=================
+*/
+void CL_JoystickEvent(int axis, int value, int time)
+{
+ if (axis < 0 || axis >= MAX_JOYSTICK_AXIS)
+ {
+ Com_Error(ERR_DROP, "CL_JoystickEvent: bad axis %i", axis);
+ }
+ cl.joystickAxis[axis] = value;
+}
+
+/*
+=================
+CL_JoystickMove
+=================
+*/
+static void CL_JoystickMove(usercmd_t *cmd)
+{
+ float anglespeed;
+
+ float yaw = j_yaw->value * cl.joystickAxis[j_yaw_axis->integer];
+ float right = j_side->value * cl.joystickAxis[j_side_axis->integer];
+ float forward = j_forward->value * cl.joystickAxis[j_forward_axis->integer];
+ float pitch = j_pitch->value * cl.joystickAxis[j_pitch_axis->integer];
+ float up = j_up->value * cl.joystickAxis[j_up_axis->integer];
+
+ if (!(in_speed.active ^ cl_run->integer))
+ {
+ cmd->buttons |= BUTTON_WALKING;
+ }
+
+ if (in_speed.active)
+ {
+ anglespeed = 0.001 * cls.frametime * cl_anglespeedkey->value;
+ }
+ else
+ {
+ anglespeed = 0.001 * cls.frametime;
+ }
+
+ if (!in_strafe.active)
+ {
+ cl.viewangles[YAW] += anglespeed * yaw;
+ cmd->rightmove = ClampChar(cmd->rightmove + (int)right);
+ }
+ else
+ {
+ cl.viewangles[YAW] += anglespeed * right;
+ cmd->rightmove = ClampChar(cmd->rightmove + (int)yaw);
+ }
+
+ if (in_mlooking)
+ {
+ cl.viewangles[PITCH] += anglespeed * forward;
+ cmd->forwardmove = ClampChar(cmd->forwardmove + (int)pitch);
+ }
+ else
+ {
+ cl.viewangles[PITCH] += anglespeed * pitch;
+ cmd->forwardmove = ClampChar(cmd->forwardmove + (int)forward);
+ }
+
+ cmd->upmove = ClampChar(cmd->upmove + (int)up);
+}
+
+/*
+=================
+CL_MouseMove
+=================
+*/
+
+static void CL_MouseMove(usercmd_t *cmd)
+{
+ float mx, my;
+
+ // allow mouse smoothing
+ if (m_filter->integer)
+ {
+ mx = (cl.mouseDx[0] + cl.mouseDx[1]) * 0.5f;
+ my = (cl.mouseDy[0] + cl.mouseDy[1]) * 0.5f;
+ }
+ else
+ {
+ mx = cl.mouseDx[cl.mouseIndex];
+ my = cl.mouseDy[cl.mouseIndex];
+ }
+
+ cl.mouseIndex ^= 1;
+ cl.mouseDx[cl.mouseIndex] = 0;
+ cl.mouseDy[cl.mouseIndex] = 0;
+
+ if (mx == 0.0f && my == 0.0f) return;
+
+ if (cl_mouseAccel->value != 0.0f)
+ {
+ if (cl_mouseAccelStyle->integer == 0)
+ {
+ float accelSensitivity;
+ float rate;
+
+ rate = sqrt(mx * mx + my * my) / (float)frame_msec;
+
+ accelSensitivity = cl_sensitivity->value + rate * cl_mouseAccel->value;
+ mx *= accelSensitivity;
+ my *= accelSensitivity;
+
+ if (cl_showMouseRate->integer) Com_Printf("rate: %f, accelSensitivity: %f\n", rate, accelSensitivity);
+ }
+ else
+ {
+ float rate[2];
+ float power[2];
+
+ // sensitivity remains pretty much unchanged at low speeds
+ // cl_mouseAccel is a power value to how the acceleration is shaped
+ // cl_mouseAccelOffset is the rate for which the acceleration will have doubled the non accelerated
+ // amplification
+ // NOTE: decouple the config cvars for independent acceleration setup along X and Y?
+
+ rate[0] = fabs(mx) / (float)frame_msec;
+ rate[1] = fabs(my) / (float)frame_msec;
+ power[0] = powf(rate[0] / cl_mouseAccelOffset->value, cl_mouseAccel->value);
+ power[1] = powf(rate[1] / cl_mouseAccelOffset->value, cl_mouseAccel->value);
+
+ mx = cl_sensitivity->value * (mx + ((mx < 0) ? -power[0] : power[0]) * cl_mouseAccelOffset->value);
+ my = cl_sensitivity->value * (my + ((my < 0) ? -power[1] : power[1]) * cl_mouseAccelOffset->value);
+
+ if (cl_showMouseRate->integer)
+ Com_Printf("ratex: %f, ratey: %f, powx: %f, powy: %f\n", rate[0], rate[1], power[0], power[1]);
+ }
+ }
+ else
+ {
+ mx *= cl_sensitivity->value;
+ my *= cl_sensitivity->value;
+ }
+
+ // ingame FOV
+ mx *= cl.cgameSensitivity;
+ my *= cl.cgameSensitivity;
+
+ // add mouse X/Y movement to cmd
+ if (in_strafe.active)
+ cmd->rightmove = ClampChar(cmd->rightmove + m_side->value * mx);
+ else
+ cl.viewangles[YAW] -= m_yaw->value * mx;
+
+ if ((in_mlooking || cl_freelook->integer) && !in_strafe.active)
+ cl.viewangles[PITCH] += m_pitch->value * my;
+ else
+ cmd->forwardmove = ClampChar(cmd->forwardmove - m_forward->value * my);
+}
+
+/*
+==============
+CL_CmdButtons
+==============
+*/
+static void CL_CmdButtons(usercmd_t *cmd)
+{
+ int i;
+
+ //
+ // figure button bits
+ // send a button bit even if the key was pressed and released in
+ // less than a frame
+ //
+ for (i = 0; i < 15; i++)
+ {
+ if (in_buttons[i].active || in_buttons[i].wasPressed)
+ {
+ cmd->buttons |= 1 << i;
+ }
+ in_buttons[i].wasPressed = false;
+ }
+
+ if (Key_GetCatcher())
+ {
+ cmd->buttons |= BUTTON_TALK;
+ }
+
+ // allow the game to know if any key at all is
+ // currently pressed, even if it isn't bound to anything
+ if (anykeydown && Key_GetCatcher() == 0)
+ {
+ cmd->buttons |= BUTTON_ANY;
+ }
+}
+
+/*
+==============
+CL_FinishMove
+==============
+*/
+static void CL_FinishMove(usercmd_t *cmd)
+{
+ int i;
+
+ // copy the state that the cgame is currently sending
+ cmd->weapon = cl.cgameUserCmdValue;
+
+ // send the current server time so the amount of movement
+ // can be determined without allowing cheating
+ cmd->serverTime = cl.serverTime;
+
+ for (i = 0; i < 3; i++)
+ {
+ cmd->angles[i] = ANGLE2SHORT(cl.viewangles[i]);
+ }
+}
+
+/*
+=================
+CL_CreateCmd
+=================
+*/
+static usercmd_t CL_CreateCmd(void)
+{
+ usercmd_t cmd;
+ vec3_t oldAngles;
+
+ VectorCopy(cl.viewangles, oldAngles);
+
+ // keyboard angle adjustment
+ CL_AdjustAngles();
+
+ ::memset(&cmd, 0, sizeof(cmd));
+
+ CL_CmdButtons(&cmd);
+
+ // get basic movement from keyboard
+ CL_KeyMove(&cmd);
+
+ // get basic movement from mouse
+ CL_MouseMove(&cmd);
+
+ // get basic movement from joystick
+ CL_JoystickMove(&cmd);
+
+ // check to make sure the angles haven't wrapped
+ if (cl.viewangles[PITCH] - oldAngles[PITCH] > 90)
+ {
+ cl.viewangles[PITCH] = oldAngles[PITCH] + 90;
+ }
+ else if (oldAngles[PITCH] - cl.viewangles[PITCH] > 90)
+ {
+ cl.viewangles[PITCH] = oldAngles[PITCH] - 90;
+ }
+
+ // store out the final values
+ CL_FinishMove(&cmd);
+
+ // draw debug graphs of turning for mouse testing
+ if (cl_debugMove->integer)
+ {
+ if (cl_debugMove->integer == 1)
+ {
+ SCR_DebugGraph(fabs(cl.viewangles[YAW] - oldAngles[YAW]));
+ }
+ if (cl_debugMove->integer == 2)
+ {
+ SCR_DebugGraph(fabs(cl.viewangles[PITCH] - oldAngles[PITCH]));
+ }
+ }
+
+ return cmd;
+}
+
+/*
+=================
+CL_CreateNewCommands
+
+Create a new usercmd_t structure for this frame
+=================
+*/
+static void CL_CreateNewCommands(void)
+{
+ int cmdNum;
+
+ // no need to create usercmds until we have a gamestate
+ if (clc.state < CA_PRIMED)
+ {
+ return;
+ }
+
+ frame_msec = com_frameTime - old_com_frameTime;
+
+ // if running over 1000fps, act as if each frame is 1ms
+ // prevents divisions by zero
+ if (frame_msec < 1)
+ {
+ frame_msec = 1;
+ }
+
+ // if running less than 5fps, truncate the extra time to prevent
+ // unexpected moves after a hitch
+ if (frame_msec > 200)
+ {
+ frame_msec = 200;
+ }
+ old_com_frameTime = com_frameTime;
+
+ // generate a command for this frame
+ cl.cmdNumber++;
+ cmdNum = cl.cmdNumber & CMD_MASK;
+ cl.cmds[cmdNum] = CL_CreateCmd();
+}
+
+/*
+=================
+CL_ReadyToSendPacket
+
+Returns false if we are over the maxpackets limit
+and should choke back the bandwidth a bit by not sending
+a packet this frame. All the commands will still get
+delivered in the next packet, but saving a header and
+getting more delta compression will reduce total bandwidth.
+=================
+*/
+static bool CL_ReadyToSendPacket(void)
+{
+ int oldPacketNum;
+ int delta;
+
+ // don't send anything if playing back a demo
+ if (clc.demoplaying || clc.state == CA_CINEMATIC)
+ {
+ return false;
+ }
+
+ // If we are downloading, we send no less than 50ms between packets
+ if (*clc.downloadTempName && cls.realtime - clc.lastPacketSentTime < 50)
+ {
+ return false;
+ }
+
+ // if we don't have a valid gamestate yet, only send
+ // one packet a second
+ if (clc.state != CA_ACTIVE && clc.state != CA_PRIMED && !*clc.downloadTempName &&
+ cls.realtime - clc.lastPacketSentTime < 1000)
+ {
+ return false;
+ }
+
+ // send every frame for loopbacks
+ if (clc.netchan.remoteAddress.type == NA_LOOPBACK)
+ {
+ return true;
+ }
+
+ // send every frame for LAN
+ if (cl_lanForcePackets->integer && Sys_IsLANAddress(clc.netchan.remoteAddress))
+ {
+ return true;
+ }
+
+ // check for exceeding cl_maxpackets
+ if (cl_maxpackets->integer < 15)
+ {
+ Cvar_Set("cl_maxpackets", "15");
+ }
+ else if (cl_maxpackets->integer > 125)
+ {
+ Cvar_Set("cl_maxpackets", "125");
+ }
+ oldPacketNum = (clc.netchan.outgoingSequence - 1) & PACKET_MASK;
+ delta = cls.realtime - cl.outPackets[oldPacketNum].p_realtime;
+ if (delta < 1000 / cl_maxpackets->integer)
+ {
+ // the accumulated commands will go out in the next packet
+ return false;
+ }
+
+ return true;
+}
+
+/*
+===================
+CL_WritePacket
+
+Create and send the command packet to the server
+Including both the reliable commands and the usercmds
+
+During normal gameplay, a client packet will contain something like:
+
+4 sequence number
+2 qport
+4 serverid
+4 acknowledged sequence number
+4 clc.serverCommandSequence
+<optional reliable commands>
+1 clc_move or clc_moveNoDelta
+1 command count
+<count * usercmds>
+
+===================
+*/
+void CL_WritePacket(void)
+{
+ msg_t buf;
+ byte data[MAX_MSGLEN];
+ int i, j;
+ usercmd_t *cmd, *oldcmd;
+ usercmd_t nullcmd;
+ int packetNum;
+ int oldPacketNum;
+ int count, key;
+
+ // don't send anything if playing back a demo
+ if (clc.demoplaying || clc.state == CA_CINEMATIC)
+ {
+ return;
+ }
+
+ ::memset(&nullcmd, 0, sizeof(nullcmd));
+ oldcmd = &nullcmd;
+
+ MSG_Init(&buf, data, sizeof(data));
+
+ MSG_Bitstream(&buf);
+ // write the current serverId so the server
+ // can tell if this is from the current gameState
+ MSG_WriteLong(&buf, cl.serverId);
+
+ // write the last message we received, which can
+ // be used for delta compression, and is also used
+ // to tell if we dropped a gamestate
+ MSG_WriteLong(&buf, clc.serverMessageSequence);
+
+ // write the last reliable message we received
+ MSG_WriteLong(&buf, clc.serverCommandSequence);
+
+ // write any unacknowledged clientCommands
+ for (i = clc.reliableAcknowledge + 1; i <= clc.reliableSequence; i++)
+ {
+ MSG_WriteByte(&buf, clc_clientCommand);
+ MSG_WriteLong(&buf, i);
+ MSG_WriteString(&buf, clc.reliableCommands[i & (MAX_RELIABLE_COMMANDS - 1)]);
+ }
+
+ // we want to send all the usercmds that were generated in the last
+ // few packet, so even if a couple packets are dropped in a row,
+ // all the cmds will make it to the server
+ if (cl_packetdup->integer < 0)
+ {
+ Cvar_Set("cl_packetdup", "0");
+ }
+ else if (cl_packetdup->integer > 5)
+ {
+ Cvar_Set("cl_packetdup", "5");
+ }
+ oldPacketNum = (clc.netchan.outgoingSequence - 1 - cl_packetdup->integer) & PACKET_MASK;
+ count = cl.cmdNumber - cl.outPackets[oldPacketNum].p_cmdNumber;
+ if (count > MAX_PACKET_USERCMDS)
+ {
+ count = MAX_PACKET_USERCMDS;
+ Com_Printf("MAX_PACKET_USERCMDS\n");
+ }
+
+#ifdef USE_VOIP
+ if (clc.voipOutgoingDataSize > 0)
+ {
+ if ((clc.voipFlags & VOIP_SPATIAL) || Com_IsVoipTarget(clc.voipTargets, sizeof(clc.voipTargets), -1))
+ {
+ if (clc.netchan.alternateProtocol != 0)
+ {
+ MSG_WriteByte(&buf, clc_EOF);
+ }
+ MSG_WriteByte(&buf, clc_voipSpeex);
+ if (clc.netchan.alternateProtocol != 0)
+ {
+ MSG_WriteByte(&buf, clc_voipSpeex + 1);
+ }
+ MSG_WriteByte(&buf, clc.voipOutgoingGeneration);
+ MSG_WriteLong(&buf, clc.voipOutgoingSequence);
+ MSG_WriteByte(&buf, clc.voipOutgoingDataFrames);
+ if (clc.netchan.alternateProtocol == 0)
+ {
+ MSG_WriteData(&buf, clc.voipTargets, sizeof(clc.voipTargets));
+ MSG_WriteByte(&buf, clc.voipFlags);
+ }
+ else
+ {
+ MSG_WriteLong(&buf, clc.voipTargets[0] | (clc.voipTargets[1] << 8) | (clc.voipTargets[2] << 16) |
+ ((clc.voipTargets[3] & 0x7F) << 24));
+ MSG_WriteLong(&buf, (clc.voipTargets[3] >> 7) | (clc.voipTargets[4] << 1) | (clc.voipTargets[5] << 9) |
+ (clc.voipTargets[6] << 17) | ((clc.voipTargets[7] & 0x3F) << 25));
+ MSG_WriteLong(&buf, clc.voipTargets[7] >> 6);
+ }
+ MSG_WriteShort(&buf, clc.voipOutgoingDataSize);
+ MSG_WriteData(&buf, clc.voipOutgoingData, clc.voipOutgoingDataSize);
+
+ // If we're recording a demo, we have to fake a server packet with
+ // this VoIP data so it gets to disk; the server doesn't send it
+ // back to us, and we might as well eliminate concerns about dropped
+ // and misordered packets here.
+ if (clc.demorecording && !clc.demowaiting)
+ {
+ const int voipSize = clc.voipOutgoingDataSize;
+ msg_t fakemsg;
+ byte fakedata[MAX_MSGLEN];
+ MSG_Init(&fakemsg, fakedata, sizeof(fakedata));
+ MSG_Bitstream(&fakemsg);
+ MSG_WriteLong(&fakemsg, clc.reliableAcknowledge);
+ MSG_WriteByte(&fakemsg, svc_voipOpus);
+ MSG_WriteShort(&fakemsg, clc.clientNum);
+ MSG_WriteByte(&fakemsg, clc.voipOutgoingGeneration);
+ MSG_WriteLong(&fakemsg, clc.voipOutgoingSequence);
+ MSG_WriteByte(&fakemsg, clc.voipOutgoingDataFrames);
+ MSG_WriteShort(&fakemsg, clc.voipOutgoingDataSize);
+ if (clc.netchan.alternateProtocol == 0)
+ {
+ MSG_WriteBits(&fakemsg, clc.voipFlags, VOIP_FLAGCNT);
+ }
+ MSG_WriteData(&fakemsg, clc.voipOutgoingData, voipSize);
+ MSG_WriteByte(&fakemsg, svc_EOF);
+ CL_WriteDemoMessage(&fakemsg, 0);
+ }
+
+ clc.voipOutgoingSequence += clc.voipOutgoingDataFrames;
+ clc.voipOutgoingDataSize = 0;
+ clc.voipOutgoingDataFrames = 0;
+ }
+ else
+ {
+ // We have data, but no targets. Silently discard all data
+ clc.voipOutgoingDataSize = 0;
+ clc.voipOutgoingDataFrames = 0;
+ }
+ }
+#endif
+
+ if (count >= 1)
+ {
+ if (cl_showSend->integer)
+ {
+ Com_Printf("(%i)", count);
+ }
+
+ // begin a client move command
+ 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);
+ }
+
+ // write the command count
+ MSG_WriteByte(&buf, count);
+
+ // use the checksum feed in the key
+ key = clc.checksumFeed;
+ // also use the message acknowledge
+ key ^= clc.serverMessageSequence;
+ // also use the last acknowledged server command in the key
+ key ^= MSG_HashKey(clc.netchan.alternateProtocol,
+ clc.serverCommands[clc.serverCommandSequence & (MAX_RELIABLE_COMMANDS - 1)], 32);
+
+ // write all the commands, including the predicted command
+ for (i = 0; i < count; i++)
+ {
+ j = (cl.cmdNumber - count + i + 1) & CMD_MASK;
+ cmd = &cl.cmds[j];
+ MSG_WriteDeltaUsercmdKey(&buf, key, oldcmd, cmd);
+ oldcmd = cmd;
+ }
+ }
+
+ //
+ // deliver the message
+ //
+ packetNum = clc.netchan.outgoingSequence & PACKET_MASK;
+ cl.outPackets[packetNum].p_realtime = cls.realtime;
+ cl.outPackets[packetNum].p_serverTime = oldcmd->serverTime;
+ cl.outPackets[packetNum].p_cmdNumber = cl.cmdNumber;
+ clc.lastPacketSentTime = cls.realtime;
+
+ if (cl_showSend->integer)
+ {
+ Com_Printf("%i ", buf.cursize);
+ }
+
+ CL_Netchan_Transmit(&clc.netchan, &buf);
+}
+
+/*
+=================
+CL_SendCmd
+
+Called every frame to builds and sends a command packet to the server.
+=================
+*/
+void CL_SendCmd(void)
+{
+ // don't send any message if not connected
+ if (clc.state < CA_CONNECTED)
+ {
+ return;
+ }
+
+ // don't send commands if paused
+ if (com_sv_running->integer && sv_paused->integer && cl_paused->integer)
+ {
+ return;
+ }
+
+ // we create commands even if a demo is playing,
+ CL_CreateNewCommands();
+
+ // don't send a packet if the last packet was sent too recently
+ if (!CL_ReadyToSendPacket())
+ {
+ if (cl_showSend->integer)
+ {
+ Com_Printf(". ");
+ }
+ return;
+ }
+
+ CL_WritePacket();
+}
+
+/*
+============
+CL_InitInput
+============
+*/
+void CL_InitInput(void)
+{
+ Cmd_AddCommand("centerview", IN_CenterView);
+
+ Cmd_AddCommand("+moveup", IN_UpDown);
+ Cmd_AddCommand("-moveup", IN_UpUp);
+ Cmd_AddCommand("+movedown", IN_DownDown);
+ Cmd_AddCommand("-movedown", IN_DownUp);
+ Cmd_AddCommand("+left", IN_LeftDown);
+ Cmd_AddCommand("-left", IN_LeftUp);
+ Cmd_AddCommand("+right", IN_RightDown);
+ Cmd_AddCommand("-right", IN_RightUp);
+ Cmd_AddCommand("+forward", IN_ForwardDown);
+ Cmd_AddCommand("-forward", IN_ForwardUp);
+ Cmd_AddCommand("+back", IN_BackDown);
+ Cmd_AddCommand("-back", IN_BackUp);
+ Cmd_AddCommand("+lookup", IN_LookupDown);
+ Cmd_AddCommand("-lookup", IN_LookupUp);
+ Cmd_AddCommand("+lookdown", IN_LookdownDown);
+ Cmd_AddCommand("-lookdown", IN_LookdownUp);
+ Cmd_AddCommand("+strafe", IN_StrafeDown);
+ Cmd_AddCommand("-strafe", IN_StrafeUp);
+ Cmd_AddCommand("+moveleft", IN_MoveleftDown);
+ Cmd_AddCommand("-moveleft", IN_MoveleftUp);
+ Cmd_AddCommand("+moveright", IN_MoverightDown);
+ Cmd_AddCommand("-moveright", IN_MoverightUp);
+ Cmd_AddCommand("+speed", IN_SpeedDown);
+ Cmd_AddCommand("-speed", IN_SpeedUp);
+ Cmd_AddCommand("+attack", IN_Button0Down);
+ Cmd_AddCommand("-attack", IN_Button0Up);
+ Cmd_AddCommand("+button0", IN_Button0Down);
+ Cmd_AddCommand("-button0", IN_Button0Up);
+ Cmd_AddCommand("+button1", IN_Button1Down);
+ Cmd_AddCommand("-button1", IN_Button1Up);
+ Cmd_AddCommand("+button2", IN_Button2Down);
+ Cmd_AddCommand("-button2", IN_Button2Up);
+ Cmd_AddCommand("+button3", IN_Button3Down);
+ Cmd_AddCommand("-button3", IN_Button3Up);
+ Cmd_AddCommand("+button4", IN_Button4Down);
+ Cmd_AddCommand("-button4", IN_Button4Up);
+ Cmd_AddCommand("+button5", IN_Button5Down);
+ Cmd_AddCommand("-button5", IN_Button5Up);
+ Cmd_AddCommand("+button6", IN_Button6Down);
+ Cmd_AddCommand("-button6", IN_Button6Up);
+ Cmd_AddCommand("+button7", IN_Button7Down);
+ Cmd_AddCommand("-button7", IN_Button7Up);
+ Cmd_AddCommand("+button8", IN_Button8Down);
+ Cmd_AddCommand("-button8", IN_Button8Up);
+ Cmd_AddCommand("+button9", IN_Button9Down);
+ Cmd_AddCommand("-button9", IN_Button9Up);
+ Cmd_AddCommand("+button10", IN_Button10Down);
+ Cmd_AddCommand("-button10", IN_Button10Up);
+ Cmd_AddCommand("+button11", IN_Button11Down);
+ Cmd_AddCommand("-button11", IN_Button11Up);
+ Cmd_AddCommand("+button12", IN_Button12Down);
+ Cmd_AddCommand("-button12", IN_Button12Up);
+ Cmd_AddCommand("+button13", IN_Button13Down);
+ Cmd_AddCommand("-button13", IN_Button13Up);
+ Cmd_AddCommand("+button14", IN_Button14Down);
+ Cmd_AddCommand("-button14", IN_Button14Up);
+ Cmd_AddCommand("+mlook", IN_MLookDown);
+ Cmd_AddCommand("-mlook", IN_MLookUp);
+
+#ifdef USE_VOIP
+ Cmd_AddCommand("+voiprecord", IN_VoipRecordDown);
+ Cmd_AddCommand("-voiprecord", IN_VoipRecordUp);
+#endif
+
+ cl_nodelta = Cvar_Get("cl_nodelta", "0", 0);
+ cl_debugMove = Cvar_Get("cl_debugMove", "0", 0);
+}
+
+/*
+============
+CL_ShutdownInput
+============
+*/
+void CL_ShutdownInput(void)
+{
+ Cmd_RemoveCommand("centerview");
+
+ Cmd_RemoveCommand("+moveup");
+ Cmd_RemoveCommand("-moveup");
+ Cmd_RemoveCommand("+movedown");
+ Cmd_RemoveCommand("-movedown");
+ Cmd_RemoveCommand("+left");
+ Cmd_RemoveCommand("-left");
+ Cmd_RemoveCommand("+right");
+ Cmd_RemoveCommand("-right");
+ Cmd_RemoveCommand("+forward");
+ Cmd_RemoveCommand("-forward");
+ Cmd_RemoveCommand("+back");
+ Cmd_RemoveCommand("-back");
+ Cmd_RemoveCommand("+lookup");
+ Cmd_RemoveCommand("-lookup");
+ Cmd_RemoveCommand("+lookdown");
+ Cmd_RemoveCommand("-lookdown");
+ Cmd_RemoveCommand("+strafe");
+ Cmd_RemoveCommand("-strafe");
+ Cmd_RemoveCommand("+moveleft");
+ Cmd_RemoveCommand("-moveleft");
+ Cmd_RemoveCommand("+moveright");
+ Cmd_RemoveCommand("-moveright");
+ Cmd_RemoveCommand("+speed");
+ Cmd_RemoveCommand("-speed");
+ Cmd_RemoveCommand("+attack");
+ Cmd_RemoveCommand("-attack");
+ Cmd_RemoveCommand("+button0");
+ Cmd_RemoveCommand("-button0");
+ Cmd_RemoveCommand("+button1");
+ Cmd_RemoveCommand("-button1");
+ Cmd_RemoveCommand("+button2");
+ Cmd_RemoveCommand("-button2");
+ Cmd_RemoveCommand("+button3");
+ Cmd_RemoveCommand("-button3");
+ Cmd_RemoveCommand("+button4");
+ Cmd_RemoveCommand("-button4");
+ Cmd_RemoveCommand("+button5");
+ Cmd_RemoveCommand("-button5");
+ Cmd_RemoveCommand("+button6");
+ Cmd_RemoveCommand("-button6");
+ Cmd_RemoveCommand("+button7");
+ Cmd_RemoveCommand("-button7");
+ Cmd_RemoveCommand("+button8");
+ Cmd_RemoveCommand("-button8");
+ Cmd_RemoveCommand("+button9");
+ Cmd_RemoveCommand("-button9");
+ Cmd_RemoveCommand("+button10");
+ Cmd_RemoveCommand("-button10");
+ Cmd_RemoveCommand("+button11");
+ Cmd_RemoveCommand("-button11");
+ Cmd_RemoveCommand("+button12");
+ Cmd_RemoveCommand("-button12");
+ Cmd_RemoveCommand("+button13");
+ Cmd_RemoveCommand("-button13");
+ Cmd_RemoveCommand("+button14");
+ Cmd_RemoveCommand("-button14");
+ Cmd_RemoveCommand("+mlook");
+ Cmd_RemoveCommand("-mlook");
+
+#ifdef USE_VOIP
+ Cmd_RemoveCommand("+voiprecord");
+ Cmd_RemoveCommand("-voiprecord");
+#endif
+}
diff --git a/src/client/cl_keys.cpp b/src/client/cl_keys.cpp
new file mode 100644
index 0000000..82f5666
--- /dev/null
+++ b/src/client/cl_keys.cpp
@@ -0,0 +1,1665 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
+
+This file is part of Tremulous.
+
+Tremulous 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.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+
+#include "client.h"
+
+/*
+
+key up events are sent even if in console mode
+
+*/
+
+field_t historyEditLines[COMMAND_HISTORY];
+
+int nextHistoryLine; // the last line in the history buffer, not masked
+int historyLine; // the line being displayed from history buffer will be <= nextHistoryLine
+
+field_t g_consoleField;
+
+field_t chatField;
+bool chat_team;
+bool chat_admins;
+bool chat_clans;
+int chat_playerNum;
+
+bool key_overstrikeMode;
+
+int anykeydown;
+qkey_t keys[MAX_KEYS];
+
+struct keyname_t {
+ const char* name;
+ int keynum;
+};
+
+// names not in this list can either be lowercase ascii, or '0xnn' hex sequences
+keyname_t keynames[] =
+{
+ {"TAB", K_TAB},
+ {"ENTER", K_ENTER},
+ {"ESCAPE", K_ESCAPE},
+ {"SPACE", K_SPACE},
+ {"BACKSPACE", K_BACKSPACE},
+ {"UPARROW", K_UPARROW},
+ {"DOWNARROW", K_DOWNARROW},
+ {"LEFTARROW", K_LEFTARROW},
+ {"RIGHTARROW", K_RIGHTARROW},
+
+ {"ALT", K_ALT},
+ {"CTRL", K_CTRL},
+ {"SHIFT", K_SHIFT},
+
+ {"COMMAND", K_COMMAND},
+
+ {"CAPSLOCK", K_CAPSLOCK},
+
+ {"F1", K_F1},
+ {"F2", K_F2},
+ {"F3", K_F3},
+ {"F4", K_F4},
+ {"F5", K_F5},
+ {"F6", K_F6},
+ {"F7", K_F7},
+ {"F8", K_F8},
+ {"F9", K_F9},
+ {"F10", K_F10},
+ {"F11", K_F11},
+ {"F12", K_F12},
+ {"F13", K_F13},
+ {"F14", K_F14},
+ {"F15", K_F15},
+
+ {"INS", K_INS},
+ {"DEL", K_DEL},
+ {"PGDN", K_PGDN},
+ {"PGUP", K_PGUP},
+ {"HOME", K_HOME},
+ {"END", K_END},
+
+ {"MOUSE1", K_MOUSE1},
+ {"MOUSE2", K_MOUSE2},
+ {"MOUSE3", K_MOUSE3},
+ {"MOUSE4", K_MOUSE4},
+ {"MOUSE5", K_MOUSE5},
+
+ {"MWHEELUP", K_MWHEELUP },
+ {"MWHEELDOWN", K_MWHEELDOWN },
+
+ {"JOY1", K_JOY1},
+ {"JOY2", K_JOY2},
+ {"JOY3", K_JOY3},
+ {"JOY4", K_JOY4},
+ {"JOY5", K_JOY5},
+ {"JOY6", K_JOY6},
+ {"JOY7", K_JOY7},
+ {"JOY8", K_JOY8},
+ {"JOY9", K_JOY9},
+ {"JOY10", K_JOY10},
+ {"JOY11", K_JOY11},
+ {"JOY12", K_JOY12},
+ {"JOY13", K_JOY13},
+ {"JOY14", K_JOY14},
+ {"JOY15", K_JOY15},
+ {"JOY16", K_JOY16},
+ {"JOY17", K_JOY17},
+ {"JOY18", K_JOY18},
+ {"JOY19", K_JOY19},
+ {"JOY20", K_JOY20},
+ {"JOY21", K_JOY21},
+ {"JOY22", K_JOY22},
+ {"JOY23", K_JOY23},
+ {"JOY24", K_JOY24},
+ {"JOY25", K_JOY25},
+ {"JOY26", K_JOY26},
+ {"JOY27", K_JOY27},
+ {"JOY28", K_JOY28},
+ {"JOY29", K_JOY29},
+ {"JOY30", K_JOY30},
+ {"JOY31", K_JOY31},
+ {"JOY32", K_JOY32},
+
+ {"AUX1", K_AUX1},
+ {"AUX2", K_AUX2},
+ {"AUX3", K_AUX3},
+ {"AUX4", K_AUX4},
+ {"AUX5", K_AUX5},
+ {"AUX6", K_AUX6},
+ {"AUX7", K_AUX7},
+ {"AUX8", K_AUX8},
+ {"AUX9", K_AUX9},
+ {"AUX10", K_AUX10},
+ {"AUX11", K_AUX11},
+ {"AUX12", K_AUX12},
+ {"AUX13", K_AUX13},
+ {"AUX14", K_AUX14},
+ {"AUX15", K_AUX15},
+ {"AUX16", K_AUX16},
+
+ {"KP_HOME", K_KP_HOME },
+ {"KP_UPARROW", K_KP_UPARROW },
+ {"KP_PGUP", K_KP_PGUP },
+ {"KP_LEFTARROW", K_KP_LEFTARROW },
+ {"KP_5", K_KP_5 },
+ {"KP_RIGHTARROW", K_KP_RIGHTARROW },
+ {"KP_END", K_KP_END },
+ {"KP_DOWNARROW", K_KP_DOWNARROW },
+ {"KP_PGDN", K_KP_PGDN },
+ {"KP_ENTER", K_KP_ENTER },
+ {"KP_INS", K_KP_INS },
+ {"KP_DEL", K_KP_DEL },
+ {"KP_SLASH", K_KP_SLASH },
+ {"KP_MINUS", K_KP_MINUS },
+ {"KP_PLUS", K_KP_PLUS },
+ {"KP_NUMLOCK", K_KP_NUMLOCK },
+ {"KP_STAR", K_KP_STAR },
+ {"KP_EQUALS", K_KP_EQUALS },
+
+ {"PAUSE", K_PAUSE},
+
+ {"SEMICOLON", ';'}, // because a raw semicolon seperates commands
+
+ {"WORLD_0", K_WORLD_0},
+ {"WORLD_1", K_WORLD_1},
+ {"WORLD_2", K_WORLD_2},
+ {"WORLD_3", K_WORLD_3},
+ {"WORLD_4", K_WORLD_4},
+ {"WORLD_5", K_WORLD_5},
+ {"WORLD_6", K_WORLD_6},
+ {"WORLD_7", K_WORLD_7},
+ {"WORLD_8", K_WORLD_8},
+ {"WORLD_9", K_WORLD_9},
+ {"WORLD_10", K_WORLD_10},
+ {"WORLD_11", K_WORLD_11},
+ {"WORLD_12", K_WORLD_12},
+ {"WORLD_13", K_WORLD_13},
+ {"WORLD_14", K_WORLD_14},
+ {"WORLD_15", K_WORLD_15},
+ {"WORLD_16", K_WORLD_16},
+ {"WORLD_17", K_WORLD_17},
+ {"WORLD_18", K_WORLD_18},
+ {"WORLD_19", K_WORLD_19},
+ {"WORLD_20", K_WORLD_20},
+ {"WORLD_21", K_WORLD_21},
+ {"WORLD_22", K_WORLD_22},
+ {"WORLD_23", K_WORLD_23},
+ {"WORLD_24", K_WORLD_24},
+ {"WORLD_25", K_WORLD_25},
+ {"WORLD_26", K_WORLD_26},
+ {"WORLD_27", K_WORLD_27},
+ {"WORLD_28", K_WORLD_28},
+ {"WORLD_29", K_WORLD_29},
+ {"WORLD_30", K_WORLD_30},
+ {"WORLD_31", K_WORLD_31},
+ {"WORLD_32", K_WORLD_32},
+ {"WORLD_33", K_WORLD_33},
+ {"WORLD_34", K_WORLD_34},
+ {"WORLD_35", K_WORLD_35},
+ {"WORLD_36", K_WORLD_36},
+ {"WORLD_37", K_WORLD_37},
+ {"WORLD_38", K_WORLD_38},
+ {"WORLD_39", K_WORLD_39},
+ {"WORLD_40", K_WORLD_40},
+ {"WORLD_41", K_WORLD_41},
+ {"WORLD_42", K_WORLD_42},
+ {"WORLD_43", K_WORLD_43},
+ {"WORLD_44", K_WORLD_44},
+ {"WORLD_45", K_WORLD_45},
+ {"WORLD_46", K_WORLD_46},
+ {"WORLD_47", K_WORLD_47},
+ {"WORLD_48", K_WORLD_48},
+ {"WORLD_49", K_WORLD_49},
+ {"WORLD_50", K_WORLD_50},
+ {"WORLD_51", K_WORLD_51},
+ {"WORLD_52", K_WORLD_52},
+ {"WORLD_53", K_WORLD_53},
+ {"WORLD_54", K_WORLD_54},
+ {"WORLD_55", K_WORLD_55},
+ {"WORLD_56", K_WORLD_56},
+ {"WORLD_57", K_WORLD_57},
+ {"WORLD_58", K_WORLD_58},
+ {"WORLD_59", K_WORLD_59},
+ {"WORLD_60", K_WORLD_60},
+ {"WORLD_61", K_WORLD_61},
+ {"WORLD_62", K_WORLD_62},
+ {"WORLD_63", K_WORLD_63},
+ {"WORLD_64", K_WORLD_64},
+ {"WORLD_65", K_WORLD_65},
+ {"WORLD_66", K_WORLD_66},
+ {"WORLD_67", K_WORLD_67},
+ {"WORLD_68", K_WORLD_68},
+ {"WORLD_69", K_WORLD_69},
+ {"WORLD_70", K_WORLD_70},
+ {"WORLD_71", K_WORLD_71},
+ {"WORLD_72", K_WORLD_72},
+ {"WORLD_73", K_WORLD_73},
+ {"WORLD_74", K_WORLD_74},
+ {"WORLD_75", K_WORLD_75},
+ {"WORLD_76", K_WORLD_76},
+ {"WORLD_77", K_WORLD_77},
+ {"WORLD_78", K_WORLD_78},
+ {"WORLD_79", K_WORLD_79},
+ {"WORLD_80", K_WORLD_80},
+ {"WORLD_81", K_WORLD_81},
+ {"WORLD_82", K_WORLD_82},
+ {"WORLD_83", K_WORLD_83},
+ {"WORLD_84", K_WORLD_84},
+ {"WORLD_85", K_WORLD_85},
+ {"WORLD_86", K_WORLD_86},
+ {"WORLD_87", K_WORLD_87},
+ {"WORLD_88", K_WORLD_88},
+ {"WORLD_89", K_WORLD_89},
+ {"WORLD_90", K_WORLD_90},
+ {"WORLD_91", K_WORLD_91},
+ {"WORLD_92", K_WORLD_92},
+ {"WORLD_93", K_WORLD_93},
+ {"WORLD_94", K_WORLD_94},
+ {"WORLD_95", K_WORLD_95},
+
+ {"WINDOWS", K_SUPER},
+ {"COMPOSE", K_COMPOSE},
+ {"MODE", K_MODE},
+ {"HELP", K_HELP},
+ {"PRINT", K_PRINT},
+ {"SYSREQ", K_SYSREQ},
+ {"SCROLLOCK", K_SCROLLOCK },
+ {"BREAK", K_BREAK},
+ {"MENU", K_MENU},
+ {"POWER", K_POWER},
+ {"EURO", K_EURO},
+ {"UNDO", K_UNDO},
+
+ {"PAD0_A", K_PAD0_A },
+ {"PAD0_B", K_PAD0_B },
+ {"PAD0_X", K_PAD0_X },
+ {"PAD0_Y", K_PAD0_Y },
+ {"PAD0_BACK", K_PAD0_BACK },
+ {"PAD0_GUIDE", K_PAD0_GUIDE },
+ {"PAD0_START", K_PAD0_START },
+ {"PAD0_LEFTSTICK_CLICK", K_PAD0_LEFTSTICK_CLICK },
+ {"PAD0_RIGHTSTICK_CLICK", K_PAD0_RIGHTSTICK_CLICK },
+ {"PAD0_LEFTSHOULDER", K_PAD0_LEFTSHOULDER },
+ {"PAD0_RIGHTSHOULDER", K_PAD0_RIGHTSHOULDER },
+ {"PAD0_DPAD_UP", K_PAD0_DPAD_UP },
+ {"PAD0_DPAD_DOWN", K_PAD0_DPAD_DOWN },
+ {"PAD0_DPAD_LEFT", K_PAD0_DPAD_LEFT },
+ {"PAD0_DPAD_RIGHT", K_PAD0_DPAD_RIGHT },
+
+ {"PAD0_LEFTSTICK_LEFT", K_PAD0_LEFTSTICK_LEFT },
+ {"PAD0_LEFTSTICK_RIGHT", K_PAD0_LEFTSTICK_RIGHT },
+ {"PAD0_LEFTSTICK_UP", K_PAD0_LEFTSTICK_UP },
+ {"PAD0_LEFTSTICK_DOWN", K_PAD0_LEFTSTICK_DOWN },
+ {"PAD0_RIGHTSTICK_LEFT", K_PAD0_RIGHTSTICK_LEFT },
+ {"PAD0_RIGHTSTICK_RIGHT", K_PAD0_RIGHTSTICK_RIGHT },
+ {"PAD0_RIGHTSTICK_UP", K_PAD0_RIGHTSTICK_UP },
+ {"PAD0_RIGHTSTICK_DOWN", K_PAD0_RIGHTSTICK_DOWN },
+ {"PAD0_LEFTTRIGGER", K_PAD0_LEFTTRIGGER },
+ {"PAD0_RIGHTTRIGGER", K_PAD0_RIGHTTRIGGER },
+
+ {NULL,0}
+};
+
+/*
+=============================================================================
+
+EDIT FIELDS
+
+=============================================================================
+*/
+
+
+/*
+===================
+Field_Draw
+
+Handles horizontal scrolling and cursor blinking
+x, y, and width are in pixels
+===================
+*/
+static void Field_VariableSizeDraw( field_t *edit, int x, int y, int width, int size,
+ bool showCursor, bool noColorEscape )
+{
+ int len;
+ int drawLen;
+ int prestep;
+ int cursorChar;
+ char str[MAX_STRING_CHARS];
+ int i;
+
+ drawLen = edit->widthInChars - 1; // - 1 so there is always a space for the cursor
+ len = strlen( edit->buffer );
+
+ // guarantee that cursor will be visible
+ if ( len <= drawLen ) {
+ prestep = 0;
+ } else {
+ if ( edit->scroll + drawLen > len ) {
+ edit->scroll = len - drawLen;
+ if ( edit->scroll < 0 ) {
+ edit->scroll = 0;
+ }
+ }
+ prestep = edit->scroll;
+ }
+
+ if ( prestep + drawLen > len ) {
+ drawLen = len - prestep;
+ }
+
+ // extract <drawLen> characters from the field at <prestep>
+ if ( drawLen >= MAX_STRING_CHARS ) {
+ Com_Error( ERR_DROP, "drawLen >= MAX_STRING_CHARS" );
+ }
+
+ ::memcpy( str, edit->buffer + prestep, drawLen );
+ str[ drawLen ] = 0;
+
+ // draw it
+ if ( size == SMALLCHAR_WIDTH ) {
+ float color[4];
+
+ color[0] = color[1] = color[2] = color[3] = 1.0;
+ SCR_DrawSmallStringExt( x, y, str, color, false, noColorEscape );
+ } else {
+ // draw big string with drop shadow
+ SCR_DrawBigString( x, y, str, 1.0, noColorEscape );
+ }
+
+ // draw the cursor
+ if ( showCursor ) {
+ if ( (int)( cls.realtime >> 8 ) & 1 ) {
+ return; // off blink
+ }
+
+ if ( key_overstrikeMode ) {
+ cursorChar = 11;
+ } else {
+ cursorChar = 10;
+ }
+
+ i = drawLen - strlen( str );
+
+ if ( size == SMALLCHAR_WIDTH ) {
+ SCR_DrawSmallChar( x + ( edit->cursor - prestep - i ) * size, y, cursorChar );
+ } else {
+ str[0] = cursorChar;
+ str[1] = 0;
+ SCR_DrawBigString( x + ( edit->cursor - prestep - i ) * size, y, str, 1.0, false );
+
+ }
+ }
+}
+
+void Field_Draw( field_t *edit, int x, int y, int width, bool showCursor, bool noColorEscape )
+{
+ Field_VariableSizeDraw( edit, x, y, width, SMALLCHAR_WIDTH, showCursor, noColorEscape );
+}
+
+void Field_BigDraw( field_t *edit, int x, int y, int width, bool showCursor, bool noColorEscape )
+{
+ Field_VariableSizeDraw( edit, x, y, width, BIGCHAR_WIDTH, showCursor, noColorEscape );
+}
+
+/*
+================
+Field_Paste
+================
+*/
+static void Field_Paste( field_t *edit )
+{
+ char *cbd;
+ int pasteLen, i;
+
+ cbd = Sys_GetClipboardData();
+
+ if ( !cbd ) {
+ return;
+ }
+
+ // send as if typed, so insert / overstrike works properly
+ pasteLen = strlen( cbd );
+ for ( i = 0 ; i < pasteLen ; i++ ) {
+ Field_CharEvent( edit, cbd[i] );
+ }
+
+ Z_Free( cbd );
+}
+
+/*
+=================
+Field_KeyDownEvent
+
+Performs the basic line editing functions for the console,
+in-game talk, and menu fields
+
+Key events are used for non-printable characters, others are gotten from char events.
+=================
+*/
+void Field_KeyDownEvent( field_t *edit, int key )
+{
+ int len;
+
+ // shift-insert is paste
+ if ( ( ( key == K_INS ) || ( key == K_KP_INS ) ) && keys[K_SHIFT].down )
+ {
+ Field_Paste( edit );
+ return;
+ }
+
+ key = tolower( key );
+ len = strlen( edit->buffer );
+
+ switch ( key )
+ {
+ case K_DEL:
+ if ( edit->cursor < len )
+ {
+ memmove( edit->buffer + edit->cursor,
+ edit->buffer + edit->cursor + 1,
+ len - edit->cursor );
+ }
+ break;
+
+ case K_RIGHTARROW:
+ if ( edit->cursor < len ) {
+ edit->cursor++;
+ }
+ break;
+
+ case K_LEFTARROW:
+ if ( edit->cursor > 0 ) {
+ edit->cursor--;
+ }
+ break;
+
+ case K_HOME:
+ edit->cursor = 0;
+ break;
+
+ case K_END:
+ edit->cursor = len;
+ break;
+
+ case K_INS:
+ key_overstrikeMode = !key_overstrikeMode;
+ break;
+
+ default:
+ break;
+ }
+
+ // Change scroll if cursor is no longer visible
+ if ( edit->cursor < edit->scroll )
+ {
+ edit->scroll = edit->cursor;
+ }
+ else if ( edit->cursor >= edit->scroll + edit->widthInChars && edit->cursor <= len )
+ {
+ edit->scroll = edit->cursor - edit->widthInChars + 1;
+ }
+}
+
+/*
+==================
+Field_CharEvent
+==================
+*/
+void Field_CharEvent( field_t *edit, int ch )
+{
+ int len;
+
+ if ( ch == 'v' - 'a' + 1 ) { // ctrl-v is paste
+ Field_Paste( edit );
+ return;
+ }
+
+ if ( ch == 'c' - 'a' + 1 ) { // ctrl-c clears the field
+ Field_Clear( edit );
+ return;
+ }
+
+ len = strlen( edit->buffer );
+
+ if ( ch == 'h' - 'a' + 1 ) { // ctrl-h is backspace
+ if ( edit->cursor > 0 ) {
+ memmove( edit->buffer + edit->cursor - 1,
+ edit->buffer + edit->cursor, len + 1 - edit->cursor );
+ edit->cursor--;
+ if ( edit->cursor < edit->scroll )
+ {
+ edit->scroll--;
+ }
+ }
+ return;
+ }
+
+ if ( ch == 'a' - 'a' + 1 ) { // ctrl-a is home
+ edit->cursor = 0;
+ edit->scroll = 0;
+ return;
+ }
+
+ if ( ch == 'e' - 'a' + 1 ) { // ctrl-e is end
+ edit->cursor = len;
+ edit->scroll = edit->cursor - edit->widthInChars;
+ return;
+ }
+
+ //
+ // ignore any other non printable chars
+ //
+ if ( ch < 32 ) {
+ return;
+ }
+
+ if ( key_overstrikeMode ) {
+ // - 2 to leave room for the leading slash and trailing \0
+ if ( edit->cursor == MAX_EDIT_LINE - 2 )
+ return;
+ edit->buffer[edit->cursor] = ch;
+ edit->cursor++;
+ } else { // insert mode
+ // - 2 to leave room for the leading slash and trailing \0
+ if ( len == MAX_EDIT_LINE - 2 ) {
+ return; // all full
+ }
+ memmove( edit->buffer + edit->cursor + 1,
+ edit->buffer + edit->cursor, len + 1 - edit->cursor );
+ edit->buffer[edit->cursor] = ch;
+ edit->cursor++;
+ }
+
+
+ if ( edit->cursor >= edit->widthInChars ) {
+ edit->scroll++;
+ }
+
+ if ( edit->cursor == len + 1) {
+ edit->buffer[edit->cursor] = 0;
+ }
+}
+
+/*
+=============================================================================
+
+CONSOLE LINE EDITING
+
+==============================================================================
+*/
+
+/*
+====================
+Console_Key
+
+Handles history and console scrollback
+====================
+*/
+static void Console_Key(int key)
+{
+ // ctrl-L clears screen
+ if ( key == 'l' && keys[K_CTRL].down )
+ {
+ Cbuf_AddText ("clear\n");
+ return;
+ }
+
+ // enter finishes the line
+ if ( key == K_ENTER || key == K_KP_ENTER )
+ {
+ // if not in the game explicitly prepend a slash if needed
+ if ( clc.state != CA_ACTIVE
+ && g_consoleField.buffer[0]
+ && g_consoleField.buffer[0] != '\\'
+ && g_consoleField.buffer[0] != '/' )
+ {
+ char temp[MAX_EDIT_LINE-1];
+
+ Q_strncpyz( temp, g_consoleField.buffer, sizeof( temp ) );
+ Com_sprintf( g_consoleField.buffer, sizeof( g_consoleField.buffer ), "\\%s", temp );
+ g_consoleField.cursor++;
+ }
+
+ Com_Printf ( "]%s\n", g_consoleField.buffer );
+
+ // leading slash is an explicit command
+ if ( g_consoleField.buffer[0] == '\\' || g_consoleField.buffer[0] == '/' )
+ {
+ Cbuf_AddText( g_consoleField.buffer+1 ); // valid command
+ Cbuf_AddText ("\n");
+ }
+ else
+ {
+ // other text will be chat messages
+ if ( !g_consoleField.buffer[0] )
+ {
+ return; // empty lines just scroll the console without adding to history
+ }
+ else
+ {
+ Cbuf_AddText ("cmd say ");
+ Cbuf_AddText( g_consoleField.buffer );
+ Cbuf_AddText ("\n");
+ }
+ }
+
+ // copy line to history buffer
+ historyEditLines[nextHistoryLine % COMMAND_HISTORY] = g_consoleField;
+ nextHistoryLine++;
+ historyLine = nextHistoryLine;
+
+ Field_Clear( &g_consoleField );
+
+ g_consoleField.widthInChars = g_console_field_width;
+
+ CL_SaveConsoleHistory( );
+
+ if ( clc.state == CA_DISCONNECTED ) {
+ SCR_UpdateScreen (); // force an update, because the command
+ } // may take some time
+ return;
+ }
+
+ // command completion
+
+ if (key == K_TAB) {
+ Field_AutoComplete(&g_consoleField);
+ return;
+ }
+
+ // command history (ctrl-p ctrl-n for unix style)
+
+ if ( (key == K_MWHEELUP && keys[K_SHIFT].down) || ( key == K_UPARROW ) || ( key == K_KP_UPARROW ) ||
+ ( ( tolower(key) == 'p' ) && keys[K_CTRL].down ) ) {
+ if ( nextHistoryLine - historyLine < COMMAND_HISTORY
+ && historyLine > 0 ) {
+ historyLine--;
+ }
+ g_consoleField = historyEditLines[ historyLine % COMMAND_HISTORY ];
+ return;
+ }
+
+ if ( (key == K_MWHEELDOWN && keys[K_SHIFT].down) || ( key == K_DOWNARROW ) || ( key == K_KP_DOWNARROW ) ||
+ ( ( tolower(key) == 'n' ) && keys[K_CTRL].down ) ) {
+ historyLine++;
+ if (historyLine >= nextHistoryLine) {
+ historyLine = nextHistoryLine;
+ Field_Clear( &g_consoleField );
+ g_consoleField.widthInChars = g_console_field_width;
+ return;
+ }
+ g_consoleField = historyEditLines[ historyLine % COMMAND_HISTORY ];
+ return;
+ }
+
+ // console scrolling
+ if ( key == K_PGUP ) {
+ Con_PageUp();
+ return;
+ }
+
+ if ( key == K_PGDN) {
+ Con_PageDown();
+ return;
+ }
+
+ if ( key == K_MWHEELUP) { //----(SA) added some mousewheel functionality to the console
+ Con_PageUp();
+ if(keys[K_CTRL].down) { // hold <ctrl> to accelerate scrolling
+ Con_PageUp();
+ Con_PageUp();
+ }
+ return;
+ }
+
+ if ( key == K_MWHEELDOWN) { //----(SA) added some mousewheel functionality to the console
+ Con_PageDown();
+ if(keys[K_CTRL].down) { // hold <ctrl> to accelerate scrolling
+ Con_PageDown();
+ Con_PageDown();
+ }
+ return;
+ }
+
+ // ctrl-home = top of console
+ if ( key == K_HOME && keys[K_CTRL].down ) {
+ Con_Top();
+ return;
+ }
+
+ // ctrl-end = bottom of console
+ if ( key == K_END && keys[K_CTRL].down ) {
+ Con_Bottom();
+ return;
+ }
+
+ // pass to the normal editline routine
+ Field_KeyDownEvent( &g_consoleField, key );
+}
+//============================================================================
+
+
+
+/*
+=================
+Message_Key
+
+In game talk message
+=================
+*/
+static void Message_Key( int key ) {
+
+ char buffer[MAX_STRING_CHARS];
+
+ if (key == K_ESCAPE) {
+ Key_SetCatcher( Key_GetCatcher( ) & ~KEYCATCH_MESSAGE );
+ Field_Clear( &chatField);
+ return;
+ }
+
+ if ( key == K_ENTER || key == K_KP_ENTER ) {
+ if ( chatField.buffer[0] && clc.state == CA_ACTIVE ){
+
+ if( chatField.buffer[0] == '/' ||
+ chatField.buffer[0] == '\\' )
+ {
+ Com_sprintf( buffer, sizeof( buffer ), "%s\n", &chatField.buffer[1] );
+ }
+
+ else if (chat_playerNum != -1 ) {
+ Com_sprintf( buffer, sizeof( buffer ),
+ "tell %i \"%s\"\n",
+ chat_playerNum,
+ chatField.buffer );
+ }
+ else if (chat_team) {
+ Com_sprintf( buffer, sizeof( buffer ),
+ "say_team \"%s\"\n",
+ chatField.buffer );
+ }
+ else if (chat_admins) {
+ Com_sprintf( buffer, sizeof( buffer ),
+ "a \"%s\"\n",
+ chatField.buffer );
+ }
+ else if (chat_clans) {
+ char clantagDecolored[ 32 ];
+
+ Q_strncpyz( clantagDecolored, cl_clantag->string,
+ sizeof(clantagDecolored) );
+ Q_CleanStr( clantagDecolored );
+
+ if( strlen(clantagDecolored) > 2 && strlen(clantagDecolored) < 11 ) {
+ Com_sprintf( buffer, sizeof( buffer ),
+ "m \"%s\" \"%s\"\n", clantagDecolored,
+ chatField.buffer );
+ } else {
+ //string isnt long enough
+ Com_Printf (
+ "^3Error:your cl_clantag has to be Between 3 and 10 chars long. current value is:^7 %s^7\n",
+ clantagDecolored );
+ return;
+ }
+ }
+ else {
+ Com_sprintf( buffer, sizeof( buffer ),
+ "say \"%s\"\n", chatField.buffer );
+ }
+
+ CL_AddReliableCommand( buffer, false );
+ }
+ Key_SetCatcher( Key_GetCatcher( ) & ~KEYCATCH_MESSAGE );
+ Field_Clear( &chatField );
+ return;
+ }
+
+ Field_KeyDownEvent( &chatField, key);
+}
+
+//============================================================================
+
+
+bool Key_GetOverstrikeMode( void )
+{
+ return key_overstrikeMode;
+}
+
+
+void Key_SetOverstrikeMode( bool state )
+{
+ key_overstrikeMode = state;
+}
+
+
+/*
+===================
+Key_IsDown
+===================
+*/
+bool Key_IsDown( int keynum )
+{
+ if ( keynum < 0 || keynum >= MAX_KEYS )
+ return false;
+
+ return keys[keynum].down;
+}
+
+/*
+===================
+Key_StringToKeynum
+
+Returns a key number to be used to index keys[] by looking at
+the given string. Single ascii characters return themselves, while
+the K_* names are matched up.
+
+0x11 will be interpreted as raw hex, which will allow new controlers
+
+to be configured even if they don't have defined names.
+===================
+*/
+int Key_StringToKeynum( const char *str ) {
+ keyname_t *kn;
+
+ if ( !str || !str[0] ) {
+ return -1;
+ }
+ if ( !str[1] ) {
+ return tolower( str[0] );
+ }
+
+ // check for hex code
+ if ( strlen( str ) == 4 ) {
+ int n = Com_HexStrToInt( str );
+
+ if ( n >= 0 ) {
+ return n;
+ }
+ }
+
+ // scan for a text match
+ for ( kn=keynames ; kn->name ; kn++ ) {
+ if ( !Q_stricmp( str,kn->name ) )
+ return kn->keynum;
+ }
+
+ return -1;
+}
+
+/*
+===================
+Key_KeynumToString
+
+Returns a string (either a single ascii char, a K_* name, or a 0x11 hex string) for the
+given keynum.
+===================
+*/
+const char *Key_KeynumToString( int keynum ) {
+ keyname_t *kn;
+ static char tinystr[5];
+ int i, j;
+
+ if ( keynum == -1 ) {
+ return "<KEY NOT FOUND>";
+ }
+
+ if ( keynum < 0 || keynum >= MAX_KEYS ) {
+ return "<OUT OF RANGE>";
+ }
+
+ // check for printable ascii (don't use quote)
+ if ( keynum > 32 && keynum < 127 && keynum != '"' && keynum != ';' ) {
+ tinystr[0] = keynum;
+ tinystr[1] = 0;
+ return tinystr;
+ }
+
+ // check for a key string
+ for ( kn=keynames ; kn->name ; kn++ ) {
+ if (keynum == kn->keynum) {
+ return kn->name;
+ }
+ }
+
+ // make a hex string
+ i = keynum >> 4;
+ j = keynum & 15;
+
+ tinystr[0] = '0';
+ tinystr[1] = 'x';
+ tinystr[2] = i > 9 ? i - 10 + 'a' : i + '0';
+ tinystr[3] = j > 9 ? j - 10 + 'a' : j + '0';
+ tinystr[4] = 0;
+
+ return tinystr;
+}
+
+
+/*
+===================
+Key_SetBinding
+===================
+*/
+void Key_SetBinding( int keynum, const char *binding ) {
+ if ( keynum < 0 || keynum >= MAX_KEYS ) {
+ return;
+ }
+
+ // free old bindings
+ if ( keys[ keynum ].binding ) {
+ Z_Free( keys[ keynum ].binding );
+ }
+
+ // allocate memory for new binding
+ keys[keynum].binding = CopyString( binding );
+
+ // consider this like modifying an archived cvar, so the
+ // file write will be triggered at the next oportunity
+ cvar_modifiedFlags |= CVAR_ARCHIVE;
+}
+
+
+/*
+===================
+Key_GetBinding
+===================
+*/
+const char *Key_GetBinding( int keynum ) {
+ if ( keynum < 0 || keynum >= MAX_KEYS ) {
+ return "";
+ }
+
+ return keys[ keynum ].binding;
+}
+
+/*
+===================
+Key_GetKey
+===================
+*/
+
+int Key_GetKey(const char *binding) {
+ int i;
+
+ if (binding) {
+ for (i=0 ; i < MAX_KEYS ; i++) {
+ if (keys[i].binding && Q_stricmp(binding, keys[i].binding) == 0) {
+ return i;
+ }
+ }
+ }
+ return -1;
+}
+
+/*
+===================
+Key_Unbind_f
+===================
+*/
+void Key_Unbind_f (void)
+{
+ if (Cmd_Argc() != 2)
+ {
+ Com_Printf ("unbind <key> : remove commands from a key\n");
+ return;
+ }
+
+ int b = Key_StringToKeynum(Cmd_Argv(1));
+ if ( b == -1 )
+ {
+ Com_Printf ("\"%s\" isn't a valid key\n", Cmd_Argv(1));
+ return;
+ }
+
+ Key_SetBinding (b, "");
+}
+
+/*
+===================
+Key_Unbindall_f
+===================
+*/
+void Key_Unbindall_f (void)
+{
+ int i;
+
+ for (i=0 ; i < MAX_KEYS; i++)
+ if (keys[i].binding)
+ Key_SetBinding (i, "");
+}
+
+
+/*
+===================
+Key_Bind_f
+===================
+*/
+void Key_Bind_f (void)
+{
+ int i, c, b;
+ char cmd[1024];
+
+ c = Cmd_Argc();
+
+ if (c < 2)
+ {
+ Com_Printf ("bind <key> [command] : attach a command to a key\n");
+ return;
+ }
+ b = Key_StringToKeynum (Cmd_Argv(1));
+ if (b==-1)
+ {
+ Com_Printf ("\"%s\" isn't a valid key\n", Cmd_Argv(1));
+ return;
+ }
+
+ if (c == 2)
+ {
+ if (keys[b].binding && keys[b].binding[0])
+ Com_Printf ("\"%s\" = \"%s\"\n", Key_KeynumToString(b), keys[b].binding );
+ else
+ Com_Printf ("\"%s\" is not bound\n", Key_KeynumToString(b) );
+ return;
+ }
+
+// copy the rest of the command line
+ cmd[0] = 0; // start out with a null string
+ for (i=2 ; i< c ; i++)
+ {
+ strcat (cmd, Cmd_Argv(i));
+ if (i != (c-1))
+ strcat (cmd, " ");
+ }
+
+ Key_SetBinding (b, cmd);
+}
+
+/*
+============
+Key_WriteBindings
+
+Writes lines containing "bind key value"
+============
+*/
+void Key_WriteBindings( fileHandle_t f ) {
+ int i;
+
+ FS_Printf (f, "unbindall\n" );
+
+ for (i=0 ; i<MAX_KEYS ; i++) {
+ if (keys[i].binding && keys[i].binding[0] ) {
+ FS_Printf (f, "bind %s \"%s\"\n", Key_KeynumToString(i), keys[i].binding);
+
+ }
+
+ }
+}
+
+
+/*
+============
+Key_Bindlist_f
+
+============
+*/
+void Key_Bindlist_f( void ) {
+ int i;
+
+ for ( i = 0 ; i < MAX_KEYS ; i++ ) {
+ if ( keys[i].binding && keys[i].binding[0] ) {
+ Com_Printf( "%s \"%s\"\n", Key_KeynumToString(i), keys[i].binding );
+ }
+ }
+}
+
+/*
+============
+Key_KeynameCompletion
+============
+*/
+void Key_KeynameCompletion( void(*callback)(const char *s) ) {
+ int i;
+
+ for( i = 0; keynames[ i ].name != NULL; i++ )
+ callback( keynames[ i ].name );
+}
+
+/*
+====================
+Key_CompleteUnbind
+====================
+*/
+static void Key_CompleteUnbind( char *args, int argNum )
+{
+ if( argNum == 2 )
+ {
+ // Skip "unbind "
+ char *p = Com_SkipTokens( args, 1, " " );
+
+ if( p > args )
+ Field_CompleteKeyname( );
+ }
+}
+
+/*
+====================
+Key_CompleteBind
+====================
+*/
+static void Key_CompleteBind( char *args, int argNum )
+{
+ char *p;
+
+ if( argNum == 2 )
+ {
+ // Skip "bind "
+ p = Com_SkipTokens( args, 1, " " );
+
+ if( p > args )
+ Field_CompleteKeyname( );
+ }
+ else if( argNum >= 3 )
+ {
+ // Skip "bind <key> "
+ p = Com_SkipTokens( args, 2, " " );
+
+ if( p > args )
+ Field_CompleteCommand( p, true, true );
+ }
+}
+
+/*
+===================
+CL_InitKeyCommands
+===================
+*/
+void CL_InitKeyCommands( void )
+{
+ // register our functions
+ Cmd_AddCommand ("bind",Key_Bind_f);
+ Cmd_SetCommandCompletionFunc( "bind", Key_CompleteBind );
+ Cmd_AddCommand ("unbind",Key_Unbind_f);
+ Cmd_SetCommandCompletionFunc( "unbind", Key_CompleteUnbind );
+ Cmd_AddCommand ("unbindall",Key_Unbindall_f);
+ Cmd_AddCommand ("bindlist",Key_Bindlist_f);
+}
+
+/*
+===================
+CL_BindUICommand
+
+Returns true if bind command should be executed while user interface is shown
+===================
+*/
+static bool CL_BindUICommand( const char *cmd )
+{
+ if ( Key_GetCatcher( ) & KEYCATCH_CONSOLE )
+ return false;
+
+ if ( !Q_stricmp( cmd, "toggleconsole" ) )
+ return true;
+ if ( !Q_stricmp( cmd, "togglemenu" ) )
+ return true;
+
+ return false;
+}
+
+/*
+===================
+CL_ParseBinding
+
+Execute the commands in the bind string
+===================
+*/
+static void CL_ParseBinding( int key, bool down, unsigned time )
+{
+ char buf[ MAX_STRING_CHARS ], *p = buf, *end;
+ bool allCommands, allowUpCmds;
+
+ if( clc.state == CA_DISCONNECTED && Key_GetCatcher( ) == 0 )
+ return;
+ if( !keys[key].binding || !keys[key].binding[0] )
+ return;
+ Q_strncpyz( buf, keys[key].binding, sizeof( buf ) );
+
+ // run all bind commands if console, ui, etc aren't reading keys
+ allCommands = ( Key_GetCatcher( ) == 0 );
+
+ // allow button up commands if in game even if key catcher is set
+ allowUpCmds = ( clc.state != CA_DISCONNECTED );
+
+ while( 1 )
+ {
+ while( isspace( *p ) )
+ p++;
+ end = strchr( p, ';' );
+ if( end )
+ *end = '\0';
+ if( *p == '+' )
+ {
+ // button commands add keynum and time as parameters
+ // so that multiple sources can be discriminated and
+ // subframe corrected
+ if ( allCommands || ( allowUpCmds && !down ) ) {
+ char cmd[1024];
+ Com_sprintf( cmd, sizeof( cmd ), "%c%s %d %d\n",
+ ( down ) ? '+' : '-', p + 1, key, time );
+ Cbuf_AddText( cmd );
+ }
+ }
+ else if( down )
+ {
+ // normal commands only execute on key press
+ if ( allCommands || CL_BindUICommand( p ) ) {
+ Cbuf_AddText( p );
+ Cbuf_AddText( "\n" );
+ }
+ }
+ if( !end )
+ break;
+ p = end + 1;
+ }
+}
+
+/*
+===================
+CL_KeyDownEvent
+
+Called by CL_KeyEvent to handle a keypress
+===================
+*/
+static void CL_KeyDownEvent( int key, unsigned time )
+{
+ keys[key].down = true;
+ keys[key].repeats++;
+ if( keys[key].repeats == 1 )
+ anykeydown++;
+
+ if( keys[K_ALT].down && key == K_ENTER )
+ {
+ Cvar_SetValue( "r_fullscreen",
+ !Cvar_VariableIntegerValue( "r_fullscreen" ) );
+ return;
+ }
+
+ // console key is hardcoded, so the user can never unbind it
+ if( key == K_CONSOLE || ( keys[K_SHIFT].down && key == K_ESCAPE ) )
+ {
+ Con_ToggleConsole_f ();
+ Key_ClearStates ();
+ return;
+ }
+
+
+ // keys can still be used for bound actions
+ if ( ( key < 128 || key == K_MOUSE1 ) &&
+ ( clc.demoplaying || clc.state == CA_CINEMATIC ) && Key_GetCatcher( ) == 0 ) {
+
+ if (Cvar_VariableValue ("com_cameraMode") == 0) {
+ if( clc.demoplaying && key != K_ESCAPE ) {
+ // avoid accidental stopping of demos from pressing a random key by opening the console
+ Con_ToggleConsole_f ();
+ Key_ClearStates ();
+ return;
+ }
+ Cvar_Set ("nextdemo","");
+ key = K_ESCAPE;
+ }
+ }
+
+ // escape is always handled special
+ if ( key == K_ESCAPE ) {
+ if ( clc.netchan.alternateProtocol == 2 &&
+ ( Key_GetCatcher( ) & KEYCATCH_MESSAGE ) ) {
+ // clear message mode
+ Message_Key( key );
+ return;
+ }
+
+ // escape always gets out of CGAME stuff
+ if (Key_GetCatcher( ) & KEYCATCH_CGAME) {
+ Key_SetCatcher( Key_GetCatcher( ) & ~KEYCATCH_CGAME );
+ VM_Call (cls.cgame, CG_EVENT_HANDLING, CGAME_EVENT_NONE);
+ return;
+ }
+
+ if ( !( Key_GetCatcher( ) & KEYCATCH_UI ) ) {
+ if ( clc.state == CA_ACTIVE && !clc.demoplaying ) {
+ VM_Call( cls.ui, UI_SET_ACTIVE_MENU - ( cls.uiInterface == 2 ? 2 : 0 ), UIMENU_INGAME );
+ }
+ else if ( clc.state != CA_DISCONNECTED ) {
+ CL_Disconnect_f();
+ S_StopAllSounds();
+ VM_Call( cls.ui, UI_SET_ACTIVE_MENU - ( cls.uiInterface == 2 ? 2 : 0 ), UIMENU_MAIN );
+ }
+ return;
+ }
+
+ VM_Call( cls.ui, UI_KEY_EVENT, key, true );
+ return;
+ }
+
+ // send the bound action
+ CL_ParseBinding( key, true, time );
+
+ // distribute the key down event to the apropriate handler
+ if ( Key_GetCatcher( ) & KEYCATCH_CONSOLE ) {
+ Console_Key( key );
+ } else if ( Key_GetCatcher( ) & KEYCATCH_UI ) {
+ if ( cls.ui ) {
+ VM_Call( cls.ui, UI_KEY_EVENT, key, true );
+ }
+ } else if ( Key_GetCatcher( ) & KEYCATCH_CGAME ) {
+ if ( cls.cgame ) {
+ VM_Call( cls.cgame, CG_KEY_EVENT, key, true );
+ }
+ } else if ( clc.netchan.alternateProtocol == 2 &&
+ ( Key_GetCatcher( ) & KEYCATCH_MESSAGE ) ) {
+ Message_Key( key );
+ } else if ( clc.state == CA_DISCONNECTED ) {
+ Console_Key( key );
+ }
+}
+
+/*
+===================
+CL_KeyUpEvent
+
+Called by CL_KeyEvent to handle a keyrelease
+===================
+*/
+static void CL_KeyUpEvent( int key, unsigned time )
+{
+ keys[key].repeats = 0;
+ keys[key].down = false;
+ anykeydown--;
+
+ if (anykeydown < 0) {
+ anykeydown = 0;
+ }
+
+ // don't process key-up events for the console key
+ if ( key == K_CONSOLE || ( key == K_ESCAPE && keys[K_SHIFT].down ) )
+ return;
+
+ //
+ // key up events only perform actions if the game key binding is
+ // a button command (leading + sign). These will be processed even in
+ // console mode and menu mode, to keep the character from continuing
+ // an action started before a mode switch.
+ //
+ CL_ParseBinding( key, false, time );
+
+ if ( Key_GetCatcher( ) & KEYCATCH_UI && cls.ui ) {
+ VM_Call( cls.ui, UI_KEY_EVENT, key, false );
+ } else if ( Key_GetCatcher( ) & KEYCATCH_CGAME && cls.cgame ) {
+ VM_Call( cls.cgame, CG_KEY_EVENT, key, false );
+ }
+}
+
+/*
+===================
+CL_KeyEvent
+
+Called by the system for both key up and key down events
+===================
+*/
+void CL_KeyEvent(int key, bool down, unsigned time)
+{
+ if( down )
+ CL_KeyDownEvent( key, time );
+ else
+ CL_KeyUpEvent( key, time );
+}
+
+/*
+===================
+CL_CharEvent
+
+Normal keyboard characters, already shifted / capslocked / etc
+===================
+*/
+void CL_CharEvent( int key )
+{
+ // delete is not a printable character and is otherwise handled by
+ // Field_KeyDownEvent
+ if ( key == 127 )
+ return;
+
+ // distribute the key down event to the apropriate handler
+ if ( Key_GetCatcher( ) & KEYCATCH_CONSOLE )
+ Field_CharEvent( &g_consoleField, key );
+
+ else if ( Key_GetCatcher( ) & KEYCATCH_UI )
+ VM_Call( cls.ui, UI_KEY_EVENT, key | K_CHAR_FLAG, true );
+
+ else if ( clc.netchan.alternateProtocol == 2 &&
+ ( Key_GetCatcher( ) & KEYCATCH_MESSAGE ) )
+ Field_CharEvent( &chatField, key );
+
+ else if ( clc.state == CA_DISCONNECTED )
+ Field_CharEvent( &g_consoleField, key );
+}
+
+
+/*
+===================
+Key_ClearStates
+===================
+*/
+void Key_ClearStates(void)
+{
+ anykeydown = 0;
+
+ for ( int i = 0 ; i < MAX_KEYS ; i++ )
+ {
+ if ( keys[i].down )
+ CL_KeyEvent( i, false, 0 );
+
+ keys[i].down = 0;
+ keys[i].repeats = 0;
+ }
+}
+
+/*
+====================
+Key_KeynumToStringBuf
+====================
+*/
+void Key_KeynumToStringBuf( int keynum, char *buf, int buflen )
+{
+ Q_strncpyz( buf, Key_KeynumToString( keynum ), buflen );
+}
+
+/*
+====================
+Key_GetBindingBuf
+====================
+*/
+void Key_GetBindingBuf( int keynum, char *buf, int buflen )
+{
+ const char* value = Key_GetBinding( keynum );
+ if ( value )
+ {
+ Q_strncpyz( buf, value, buflen );
+ }
+ else
+ {
+ *buf = 0;
+ }
+}
+
+static int keyCatchers = 0;
+
+/*
+====================
+Key_GetCatcher
+====================
+*/
+int Key_GetCatcher( void )
+{
+ return keyCatchers;
+}
+
+/*
+====================
+Key_SetCatcher
+====================
+*/
+void Key_SetCatcher( int catcher )
+{
+ // If the catcher state is changing, clear all key states
+ if( catcher != keyCatchers )
+ Key_ClearStates( );
+
+ keyCatchers = catcher;
+}
+
+// This must not exceed MAX_CMD_LINE
+#define MAX_CONSOLE_SAVE_BUFFER 1024
+#define CONSOLE_HISTORY_FILE "q3history"
+static char consoleSaveBuffer[ MAX_CONSOLE_SAVE_BUFFER ];
+static int consoleSaveBufferSize = 0;
+
+/*
+================
+CL_LoadConsoleHistory
+
+Load the console history from cl_consoleHistory
+================
+*/
+void CL_LoadConsoleHistory( void )
+{
+ char *token, *text_p;
+ int i, numChars, numLines = 0;
+ fileHandle_t f;
+
+ consoleSaveBufferSize = FS_FOpenFileRead( CONSOLE_HISTORY_FILE, &f, false );
+ if( !f )
+ {
+ Com_Printf( "Couldn't read %s.\n", CONSOLE_HISTORY_FILE );
+ return;
+ }
+
+ if( consoleSaveBufferSize <= MAX_CONSOLE_SAVE_BUFFER &&
+ FS_Read( consoleSaveBuffer, consoleSaveBufferSize, f ) == consoleSaveBufferSize )
+ {
+ text_p = consoleSaveBuffer;
+
+ for( i = COMMAND_HISTORY - 1; i >= 0; i-- )
+ {
+ if( !*( token = COM_Parse( &text_p ) ) )
+ break;
+
+ historyEditLines[ i ].cursor = atoi( token );
+
+ if( !*( token = COM_Parse( &text_p ) ) )
+ break;
+
+ historyEditLines[ i ].scroll = atoi( token );
+
+ if( !*( token = COM_Parse( &text_p ) ) )
+ break;
+
+ numChars = atoi( token );
+ text_p++;
+ if( numChars > ( strlen( consoleSaveBuffer ) - ( text_p - consoleSaveBuffer ) ) )
+ {
+ Com_DPrintf( S_COLOR_YELLOW "WARNING: probable corrupt history\n" );
+ break;
+ }
+ ::memcpy( historyEditLines[ i ].buffer, text_p, numChars );
+ historyEditLines[ i ].buffer[ numChars ] = '\0';
+ text_p += numChars;
+
+ numLines++;
+ }
+
+ memmove( &historyEditLines[ 0 ], &historyEditLines[ i + 1 ],
+ numLines * sizeof( field_t ) );
+ for( i = numLines; i < COMMAND_HISTORY; i++ )
+ Field_Clear( &historyEditLines[ i ] );
+
+ historyLine = nextHistoryLine = numLines;
+ }
+ else
+ Com_Printf( "Couldn't read %s.\n", CONSOLE_HISTORY_FILE );
+
+ FS_FCloseFile( f );
+}
+
+/*
+================
+CL_SaveConsoleHistory
+
+Save the console history into the cvar cl_consoleHistory
+so that it persists across invocations of q3
+================
+*/
+void CL_SaveConsoleHistory( void )
+{
+ int i;
+ int lineLength, saveBufferLength, additionalLength;
+ fileHandle_t f;
+
+ consoleSaveBuffer[ 0 ] = '\0';
+
+ i = ( nextHistoryLine - 1 ) % COMMAND_HISTORY;
+ do
+ {
+ if( historyEditLines[ i ].buffer[ 0 ] )
+ {
+ lineLength = strlen( historyEditLines[ i ].buffer );
+ saveBufferLength = strlen( consoleSaveBuffer );
+
+ //ICK
+ additionalLength = lineLength + strlen( "999 999 999 " );
+
+ if( saveBufferLength + additionalLength < MAX_CONSOLE_SAVE_BUFFER )
+ {
+ Q_strcat( consoleSaveBuffer, MAX_CONSOLE_SAVE_BUFFER,
+ va( "%d %d %d %s ",
+ historyEditLines[ i ].cursor,
+ historyEditLines[ i ].scroll,
+ lineLength,
+ historyEditLines[ i ].buffer ) );
+ }
+ else
+ break;
+ }
+ i = ( i - 1 + COMMAND_HISTORY ) % COMMAND_HISTORY;
+ }
+ while( i != ( nextHistoryLine - 1 ) % COMMAND_HISTORY );
+
+ consoleSaveBufferSize = strlen( consoleSaveBuffer );
+
+ f = FS_FOpenFileWrite( CONSOLE_HISTORY_FILE );
+ if( !f )
+ {
+ Com_Printf( "Couldn't write %s.\n", CONSOLE_HISTORY_FILE );
+ return;
+ }
+
+ if( FS_Write( consoleSaveBuffer, consoleSaveBufferSize, f ) < consoleSaveBufferSize )
+ Com_Printf( "Couldn't write %s.\n", CONSOLE_HISTORY_FILE );
+
+ FS_FCloseFile( f );
+}
diff --git a/src/client/cl_main.cpp b/src/client/cl_main.cpp
new file mode 100644
index 0000000..8b33acd
--- /dev/null
+++ b/src/client/cl_main.cpp
@@ -0,0 +1,5083 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
+
+This file is part of Tremulous.
+
+Tremulous 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.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+// cl_main.c -- client main loop
+
+#include "client.h"
+
+#ifndef _WIN32
+#include <sys/stat.h>
+#endif
+
+#include <climits>
+
+#include "sys/sys_loadlib.h"
+#include "sys/sys_local.h"
+
+#include "cl_updates.h"
+#ifdef USE_MUMBLE
+#include "libmumblelink.h"
+#endif
+
+#ifdef USE_MUMBLE
+cvar_t *cl_useMumble;
+cvar_t *cl_mumbleScale;
+#endif
+
+#ifdef USE_VOIP
+cvar_t *cl_voipUseVAD;
+cvar_t *cl_voipVADThreshold;
+cvar_t *cl_voipSend;
+cvar_t *cl_voipSendTarget;
+cvar_t *cl_voipGainDuringCapture;
+cvar_t *cl_voipCaptureMult;
+cvar_t *cl_voipShowMeter;
+cvar_t *cl_voipProtocol;
+cvar_t *cl_voip;
+#endif
+
+#ifdef USE_RENDERER_DLOPEN
+cvar_t *cl_renderer;
+#endif
+
+cvar_t *cl_nodelta;
+cvar_t *cl_debugMove;
+
+cvar_t *cl_noprint;
+cvar_t *cl_motd;
+
+cvar_t *rcon_client_password;
+cvar_t *rconAddress;
+
+cvar_t *cl_timeout;
+cvar_t *cl_maxpackets;
+cvar_t *cl_packetdup;
+cvar_t *cl_timeNudge;
+cvar_t *cl_showTimeDelta;
+cvar_t *cl_freezeDemo;
+
+cvar_t *cl_shownet;
+cvar_t *cl_showSend;
+cvar_t *cl_timedemo;
+cvar_t *cl_timedemoLog;
+cvar_t *cl_autoRecordDemo;
+cvar_t *cl_aviFrameRate;
+cvar_t *cl_aviMotionJpeg;
+cvar_t *cl_forceavidemo;
+
+cvar_t *cl_freelook;
+cvar_t *cl_sensitivity;
+
+cvar_t *cl_mouseAccel;
+cvar_t *cl_mouseAccelOffset;
+cvar_t *cl_mouseAccelStyle;
+cvar_t *cl_showMouseRate;
+
+cvar_t *m_pitch;
+cvar_t *m_yaw;
+cvar_t *m_forward;
+cvar_t *m_side;
+cvar_t *m_filter;
+
+cvar_t *j_pitch;
+cvar_t *j_yaw;
+cvar_t *j_forward;
+cvar_t *j_side;
+cvar_t *j_up;
+cvar_t *j_pitch_axis;
+cvar_t *j_yaw_axis;
+cvar_t *j_forward_axis;
+cvar_t *j_side_axis;
+cvar_t *j_up_axis;
+
+cvar_t *cl_activeAction;
+
+cvar_t *cl_motdString;
+
+cvar_t *cl_allowDownload;
+cvar_t *com_downloadPrompt;
+cvar_t *cl_conXOffset;
+cvar_t *cl_inGameVideo;
+
+cvar_t *cl_serverStatusResendTime;
+
+cvar_t *cl_lanForcePackets;
+
+cvar_t *cl_guidServerUniq;
+
+cvar_t *cl_clantag;
+
+cvar_t *cl_consoleKeys;
+
+cvar_t *cl_rate;
+
+cvar_t *cl_rsaAuth;
+
+clientActive_t cl;
+clientConnection_t clc;
+clientStatic_t cls;
+
+char cl_reconnectArgs[MAX_OSPATH];
+char cl_oldGame[MAX_QPATH];
+bool cl_oldGameSet;
+
+// Structure containing functions exported from refresh DLL
+refexport_t re;
+#ifdef USE_RENDERER_DLOPEN
+static void *rendererLib = NULL;
+#endif
+
+ping_t cl_pinglist[MAX_PINGREQUESTS];
+
+typedef struct serverStatus_s {
+ char string[BIG_INFO_STRING];
+ netadr_t address;
+ int time, startTime;
+ bool pending;
+ bool print;
+ bool retrieved;
+} serverStatus_t;
+
+serverStatus_t cl_serverStatusList[MAX_SERVERSTATUSREQUESTS];
+
+static void CL_InitRef(void);
+
+#if defined __USEA3D && defined __A3D_GEOM
+void hA3Dg_ExportRenderGeom(refexport_t *incoming_re);
+#endif
+
+static bool noGameRestart = false;
+
+static void CL_DownloadUpdate_f() { CL_DownloadRelease(); }
+
+static void CL_InstallUpdate_f()
+{
+ if (Cmd_Argc() > 1)
+ CL_ExecuteInstaller();
+ else
+ CL_ExecuteInstaller();
+}
+
+static void CL_CheckForUpdate_f() { CL_GetLatestRelease(); }
+
+static void CL_BrowseHomepath_f() { FS_BrowseHomepath(); }
+
+static void CL_BrowseDemos_f() { FS_OpenBaseGamePath( "demos/" ); }
+
+static void CL_BrowseScreenShots_f() { FS_OpenBaseGamePath( "screenshots/" ); }
+
+#ifdef USE_MUMBLE
+static void CL_UpdateMumble(void)
+{
+ vec3_t pos, forward, up;
+ float scale = cl_mumbleScale->value;
+ float tmp;
+
+ if (!cl_useMumble->integer) return;
+
+ // !!! FIXME: not sure if this is even close to correct.
+ if (clc.netchan.alternateProtocol == 2)
+ {
+ AngleVectors(cl.snap.alternatePs.viewangles, forward, NULL, up);
+
+ pos[0] = cl.snap.alternatePs.origin[0] * scale;
+ pos[1] = cl.snap.alternatePs.origin[2] * scale;
+ pos[2] = cl.snap.alternatePs.origin[1] * scale;
+ }
+ else
+ {
+ AngleVectors(cl.snap.ps.viewangles, forward, NULL, up);
+
+ pos[0] = cl.snap.ps.origin[0] * scale;
+ pos[1] = cl.snap.ps.origin[2] * scale;
+ pos[2] = cl.snap.ps.origin[1] * scale;
+ }
+
+ tmp = forward[1];
+ forward[1] = forward[2];
+ forward[2] = tmp;
+
+ tmp = up[1];
+ up[1] = up[2];
+ up[2] = tmp;
+
+ if (cl_useMumble->integer > 1)
+ {
+ fprintf(stderr, "%f %f %f, %f %f %f, %f %f %f\n",
+ pos[0], pos[1], pos[2],
+ forward[0], forward[1], forward[2],
+ up[0], up[1], up[2]);
+ }
+
+ mumble_update_coordinates(pos, forward, up);
+}
+#endif
+
+#ifdef USE_VOIP
+static void CL_UpdateVoipIgnore(const char *idstr, bool ignore)
+{
+ if ((*idstr >= '0') && (*idstr <= '9'))
+ {
+ const int id = atoi(idstr);
+ if ((id >= 0) && (id < MAX_CLIENTS))
+ {
+ clc.voipIgnore[id] = ignore;
+ CL_AddReliableCommand(va("voip %s %d", ignore ? "ignore" : "unignore", id), false);
+ Com_Printf("VoIP: %s ignoring player #%d\n", ignore ? "Now" : "No longer", id);
+ return;
+ }
+ }
+ Com_Printf("VoIP: invalid player ID#\n");
+}
+
+static void CL_UpdateVoipGain(const char *idstr, float gain)
+{
+ if ((*idstr >= '0') && (*idstr <= '9'))
+ {
+ const int id = atoi(idstr);
+ if (gain < 0.0f) gain = 0.0f;
+ if ((id >= 0) && (id < MAX_CLIENTS))
+ {
+ clc.voipGain[id] = gain;
+ Com_Printf("VoIP: player #%d gain now set to %f\n", id, gain);
+ }
+ }
+}
+
+void CL_Voip_f(void)
+{
+ const char *cmd = Cmd_Argv(1);
+ const char *reason = NULL;
+
+ if (clc.state != CA_ACTIVE)
+ reason = "Not connected to a server";
+ else if (!clc.voipCodecInitialized)
+ reason = "Voip codec not initialized";
+ else if (!clc.voipEnabled)
+ reason = "Server doesn't support VoIP";
+
+ if (reason != NULL)
+ {
+ Com_Printf("VoIP: command ignored: %s\n", reason);
+ return;
+ }
+
+ if (strcmp(cmd, "ignore") == 0)
+ {
+ CL_UpdateVoipIgnore(Cmd_Argv(2), true);
+ }
+ else if (strcmp(cmd, "unignore") == 0)
+ {
+ CL_UpdateVoipIgnore(Cmd_Argv(2), false);
+ }
+ else if (strcmp(cmd, "gain") == 0)
+ {
+ if (Cmd_Argc() > 3)
+ {
+ CL_UpdateVoipGain(Cmd_Argv(2), atof(Cmd_Argv(3)));
+ }
+ else if (Q_isanumber(Cmd_Argv(2)))
+ {
+ int id = atoi(Cmd_Argv(2));
+ if (id >= 0 && id < MAX_CLIENTS)
+ {
+ Com_Printf(
+ "VoIP: current gain for player #%d "
+ "is %f\n",
+ id, clc.voipGain[id]);
+ }
+ else
+ {
+ Com_Printf("VoIP: invalid player ID#\n");
+ }
+ }
+ else
+ {
+ Com_Printf("usage: voip gain <playerID#> [value]\n");
+ }
+ }
+ else if (strcmp(cmd, "muteall") == 0)
+ {
+ Com_Printf("VoIP: muting incoming voice\n");
+ CL_AddReliableCommand("voip muteall", false);
+ clc.voipMuteAll = true;
+ }
+ else if (strcmp(cmd, "unmuteall") == 0)
+ {
+ Com_Printf("VoIP: unmuting incoming voice\n");
+ CL_AddReliableCommand("voip unmuteall", false);
+ clc.voipMuteAll = false;
+ }
+ else
+ {
+ Com_Printf(
+ "usage: voip [un]ignore <playerID#>\n"
+ " voip [un]muteall\n"
+ " voip gain <playerID#> [value]\n");
+ }
+}
+
+static void CL_VoipNewGeneration(void)
+{
+ // don't have a zero generation so new clients won't match, and don't
+ // wrap to negative so MSG_ReadLong() doesn't "fail."
+ clc.voipOutgoingGeneration++;
+ if (clc.voipOutgoingGeneration <= 0) clc.voipOutgoingGeneration = 1;
+ clc.voipPower = 0.0f;
+ clc.voipOutgoingSequence = 0;
+
+ opus_encoder_ctl(clc.opusEncoder, OPUS_RESET_STATE);
+}
+
+/*
+===============
+CL_VoipParseTargets
+
+sets clc.voipTargets according to cl_voipSendTarget
+Generally we don't want who's listening to change during a transmission,
+so this is only called when the key is first pressed
+===============
+*/
+static void CL_VoipParseTargets(void)
+{
+ const char *target = cl_voipSendTarget->string;
+ char *end;
+ int val;
+
+ ::memset(clc.voipTargets, 0, sizeof(clc.voipTargets));
+ clc.voipFlags &= ~VOIP_SPATIAL;
+
+ while (target)
+ {
+ while (*target == ',' || *target == ' ') target++;
+
+ if (!*target) break;
+
+ if (isdigit(*target))
+ {
+ val = strtol(target, &end, 10);
+ target = end;
+ }
+ else
+ {
+ if (!Q_stricmpn(target, "all", 3))
+ {
+ ::memset(clc.voipTargets, ~0, sizeof(clc.voipTargets));
+ return;
+ }
+ if (!Q_stricmpn(target, "spatial", 7))
+ {
+ clc.voipFlags |= VOIP_SPATIAL;
+ target += 7;
+ continue;
+ }
+ else
+ {
+ if (!Q_stricmpn(target, "attacker", 8))
+ {
+ val = VM_Call(cls.cgame, CG_LAST_ATTACKER);
+ target += 8;
+ }
+ else if (!Q_stricmpn(target, "crosshair", 9))
+ {
+ val = VM_Call(cls.cgame, CG_CROSSHAIR_PLAYER);
+ target += 9;
+ }
+ else
+ {
+ while (*target && *target != ',' && *target != ' ') target++;
+
+ continue;
+ }
+
+ if (val < 0) continue;
+ }
+ }
+
+ if (val < 0 || val >= MAX_CLIENTS)
+ {
+ Com_Printf(S_COLOR_YELLOW
+ "WARNING: VoIP "
+ "target %d is not a valid client "
+ "number\n",
+ val);
+
+ continue;
+ }
+
+ clc.voipTargets[val / 8] |= 1 << (val % 8);
+ }
+}
+
+/*
+===============
+CL_CaptureVoip
+
+Record more audio from the hardware if required and encode it into Opus
+ data for later transmission.
+===============
+*/
+static void CL_CaptureVoip(void)
+{
+ const float audioMult = cl_voipCaptureMult->value;
+ const bool useVad = (cl_voipUseVAD->integer != 0);
+ bool initialFrame = false;
+ bool finalFrame = false;
+
+#if USE_MUMBLE
+ // if we're using Mumble, don't try to handle VoIP transmission ourselves.
+ if (cl_useMumble->integer) return;
+#endif
+
+ // If your data rate is too low, you'll get Connection Interrupted warnings
+ // when VoIP packets arrive, even if you have a broadband connection.
+ // This might work on rates lower than 25000, but for safety's sake, we'll
+ // just demand it. Who doesn't have at least a DSL line now, anyhow? If
+ // you don't, you don't need VoIP. :)
+ if (cl_voip->modified || cl_rate->modified)
+ {
+ if ((cl_voip->integer) && (cl_rate->integer < 25000))
+ {
+ Com_Printf(S_COLOR_YELLOW "Your network rate is too slow for VoIP.\n");
+ Com_Printf("Set 'Data Rate' to 'LAN/Cable/xDSL' in 'Setup/System/Network'.\n");
+ Com_Printf("Until then, VoIP is disabled.\n");
+ Cvar_Set("cl_voip", "0");
+ }
+ Cvar_Set("cl_voipProtocol", cl_voip->integer ? "opus" : "");
+ cl_voip->modified = false;
+ cl_rate->modified = false;
+ }
+
+ if (!clc.voipCodecInitialized) return; // just in case this gets called at a bad time.
+
+ if (clc.voipOutgoingDataSize > 0) return; // packet is pending transmission, don't record more yet.
+
+ if (cl_voipUseVAD->modified)
+ {
+ Cvar_Set("cl_voipSend", (useVad) ? "1" : "0");
+ cl_voipUseVAD->modified = false;
+ }
+
+ if ((useVad) && (!cl_voipSend->integer)) Cvar_Set("cl_voipSend", "1"); // lots of things reset this.
+
+ if (cl_voipSend->modified)
+ {
+ bool dontCapture = false;
+ if (clc.state != CA_ACTIVE)
+ dontCapture = true; // not connected to a server.
+ else if (!clc.voipEnabled)
+ dontCapture = true; // server doesn't support VoIP.
+ else if (clc.demoplaying)
+ dontCapture = true; // playing back a demo.
+ else if (cl_voip->integer == 0)
+ dontCapture = true; // client has VoIP support disabled.
+ else if (audioMult == 0.0f)
+ dontCapture = true; // basically silenced incoming audio.
+
+ cl_voipSend->modified = false;
+
+ if (dontCapture)
+ {
+ Cvar_Set("cl_voipSend", "0");
+ return;
+ }
+
+ if (cl_voipSend->integer)
+ {
+ initialFrame = true;
+ }
+ else
+ {
+ finalFrame = true;
+ }
+ }
+
+ // try to get more audio data from the sound card...
+
+ if (initialFrame)
+ {
+ S_MasterGain(Com_Clamp(0.0f, 1.0f, cl_voipGainDuringCapture->value));
+ S_StartCapture();
+ CL_VoipNewGeneration();
+ CL_VoipParseTargets();
+ }
+
+ if ((cl_voipSend->integer) || (finalFrame))
+ { // user wants to capture audio?
+ int samples = S_AvailableCaptureSamples();
+ const int packetSamples = (finalFrame) ? VOIP_MAX_FRAME_SAMPLES : VOIP_MAX_PACKET_SAMPLES;
+
+ // enough data buffered in audio hardware to process yet?
+ if (samples >= packetSamples)
+ {
+ // audio capture is always MONO16.
+ static int16_t sampbuffer[VOIP_MAX_PACKET_SAMPLES];
+ float voipPower = 0.0f;
+ int voipFrames;
+ int i, bytes;
+
+ if (samples > VOIP_MAX_PACKET_SAMPLES) samples = VOIP_MAX_PACKET_SAMPLES;
+
+ // !!! FIXME: maybe separate recording from encoding, so voipPower
+ // !!! FIXME: updates faster than 4Hz?
+
+ samples -= samples % VOIP_MAX_FRAME_SAMPLES;
+ if (samples != 120 && samples != 240 && samples != 480 && samples != 960 && samples != 1920 &&
+ samples != 2880)
+ {
+ Com_Printf("Voip: bad number of samples %d\n", samples);
+ return;
+ }
+ voipFrames = samples / VOIP_MAX_FRAME_SAMPLES;
+
+ S_Capture(samples, (byte *)sampbuffer); // grab from audio card.
+
+ // check the "power" of this packet...
+ for (i = 0; i < samples; i++)
+ {
+ const float flsamp = (float)sampbuffer[i];
+ const float s = fabs(flsamp);
+ voipPower += s * s;
+ sampbuffer[i] = (int16_t)((flsamp)*audioMult);
+ }
+
+ // encode raw audio samples into Opus data...
+ bytes = opus_encode(clc.opusEncoder, sampbuffer, samples, (unsigned char *)clc.voipOutgoingData,
+ sizeof(clc.voipOutgoingData));
+ if (bytes <= 0)
+ {
+ Com_DPrintf("VoIP: Error encoding %d samples\n", samples);
+ bytes = 0;
+ }
+
+ clc.voipPower = (voipPower / (32768.0f * 32768.0f * ((float)samples))) * 100.0f;
+
+ if ((useVad) && (clc.voipPower < cl_voipVADThreshold->value))
+ {
+ CL_VoipNewGeneration(); // no "talk" for at least 1/4 second.
+ }
+ else
+ {
+ clc.voipOutgoingDataSize = bytes;
+ clc.voipOutgoingDataFrames = voipFrames;
+
+ Com_DPrintf("VoIP: Send %d frames, %d bytes, %f power\n", voipFrames, bytes, clc.voipPower);
+
+#if 0
+ static FILE *encio = NULL;
+ if (encio == NULL) encio = fopen("voip-outgoing-encoded.bin", "wb");
+ if (encio != NULL) { fwrite(clc.voipOutgoingData, bytes, 1, encio); fflush(encio); }
+ static FILE *decio = NULL;
+ if (decio == NULL) decio = fopen("voip-outgoing-decoded.bin", "wb");
+ if (decio != NULL) { fwrite(sampbuffer, voipFrames * VOIP_MAX_FRAME_SAMPLES * 2, 1, decio); fflush(decio); }
+#endif
+ }
+ }
+ }
+
+ // User requested we stop recording, and we've now processed the last of
+ // any previously-buffered data. Pause the capture device, etc.
+ if (finalFrame)
+ {
+ S_StopCapture();
+ S_MasterGain(1.0f);
+ clc.voipPower = 0.0f; // force this value so it doesn't linger.
+ }
+}
+#endif
+
+/*
+=======================================================================
+
+CLIENT RELIABLE COMMAND COMMUNICATION
+
+=======================================================================
+*/
+
+/*
+======================
+CL_AddReliableCommand
+
+The given command will be transmitted to the server, and is gauranteed to
+not have future usercmd_t executed before it is executed
+======================
+*/
+void CL_AddReliableCommand(const char *cmd, bool isDisconnectCmd)
+{
+ int unacknowledged = clc.reliableSequence - clc.reliableAcknowledge;
+
+ // if we would be losing an old command that hasn't been acknowledged,
+ // we must drop the connection
+ // also leave one slot open for the disconnect command in this case.
+
+ if ((isDisconnectCmd && unacknowledged > MAX_RELIABLE_COMMANDS) ||
+ (!isDisconnectCmd && unacknowledged >= MAX_RELIABLE_COMMANDS))
+ {
+ if (com_errorEntered)
+ return;
+ else
+ Com_Error(ERR_DROP, "Client command overflow");
+ }
+
+ Q_strncpyz(
+ clc.reliableCommands[++clc.reliableSequence & (MAX_RELIABLE_COMMANDS - 1)], cmd, sizeof(*clc.reliableCommands));
+}
+
+/*
+=======================================================================
+
+CLIENT SIDE DEMO RECORDING
+
+=======================================================================
+*/
+
+/*
+====================
+CL_WriteDemoMessage
+
+Dumps the current net message, prefixed by the length
+====================
+*/
+void CL_WriteDemoMessage(msg_t *msg, int headerBytes)
+{
+ int len, swlen;
+
+ // write the packet sequence
+ len = clc.serverMessageSequence;
+ swlen = LittleLong(len);
+ FS_Write(&swlen, 4, clc.demofile);
+ // skip the packet sequencing information
+ len = msg->cursize - headerBytes;
+ swlen = LittleLong(len);
+ FS_Write(&swlen, 4, clc.demofile);
+ FS_Write(msg->data + headerBytes, len, clc.demofile);
+}
+
+/*
+====================
+CL_StopRecording_f
+
+stop recording a demo
+====================
+*/
+void CL_StopRecord_f(void)
+{
+ int len;
+
+ if (!clc.demorecording)
+ {
+ Com_Printf("Not recording a demo.\n");
+ return;
+ }
+
+ // finish up
+ len = -1;
+ FS_Write(&len, 4, clc.demofile);
+ FS_Write(&len, 4, clc.demofile);
+ FS_FCloseFile(clc.demofile);
+ clc.demofile = 0;
+ clc.demorecording = false;
+ clc.spDemoRecording = false;
+ Com_Printf("Stopped demo.\n");
+}
+
+/*
+==================
+CL_DemoFilename
+==================
+*/
+static void CL_DemoFilename(int number, char *fileName, int fileNameSize)
+{
+ int a, b, c, d;
+
+ if (number < 0 || number > 9999) number = 9999;
+
+ a = number / 1000;
+ number -= a * 1000;
+ b = number / 100;
+ number -= b * 100;
+ c = number / 10;
+ number -= c * 10;
+ d = number;
+
+ Com_sprintf(fileName, fileNameSize, "demo%i%i%i%i", a, b, c, d);
+}
+
+/*
+====================
+CL_Record_f
+
+record <demoname>
+
+Begins recording a demo from the current position
+====================
+*/
+static char demoName[MAX_QPATH]; // compiler bug workaround
+
+static void CL_Record_f(void)
+{
+ char name[MAX_OSPATH];
+ byte bufData[MAX_MSGLEN];
+ msg_t buf;
+ int i;
+ int len;
+ entityState_t *ent;
+ entityState_t nullstate;
+
+ if (Cmd_Argc() > 2)
+ {
+ Com_Printf("record <demoname>\n");
+ return;
+ }
+
+ if (clc.demorecording)
+ {
+ if (!clc.spDemoRecording)
+ {
+ Com_Printf("Already recording.\n");
+ }
+ return;
+ }
+
+ if (clc.state != CA_ACTIVE)
+ {
+ Com_Printf("You must be in a level to record.\n");
+ return;
+ }
+
+ // sync 0 doesn't prevent recording, so not forcing it off .. everyone does g_sync 1 ; record ; g_sync 0 ..
+ if (NET_IsLocalAddress(clc.serverAddress) && !Cvar_VariableValue("g_synchronousClients"))
+ {
+ Com_Printf(S_COLOR_YELLOW "WARNING: You should set 'g_synchronousClients 1' for smoother demo recording\n");
+ }
+
+ if (Cmd_Argc() == 2)
+ {
+ const char *s = Cmd_Argv(1);
+ Q_strncpyz(demoName, s, sizeof(demoName));
+ Com_sprintf(
+ name, sizeof(name),
+ "demos/%s.%s%d", demoName, DEMOEXT,
+ (clc.netchan.alternateProtocol == 0 ?
+ PROTOCOL_VERSION : clc.netchan.alternateProtocol == 1 ? 70 : 69));
+ }
+ else
+ {
+ int number;
+
+ // scan for a free demo name
+ for (number = 0; number <= 9999; number++)
+ {
+ CL_DemoFilename(number, demoName, sizeof(demoName));
+ Com_sprintf(name, sizeof(name), "demos/%s.%s%d", demoName, DEMOEXT, PROTOCOL_VERSION);
+
+ if (!FS_FileExists(name)) break; // file doesn't exist
+ }
+ }
+
+ // open the demo file
+
+ Com_Printf("recording to %s.\n", name);
+ clc.demofile = FS_FOpenFileWrite(name);
+ if (!clc.demofile)
+ {
+ Com_Printf("ERROR: couldn't open.\n");
+ return;
+ }
+ clc.demorecording = true;
+ if (Cvar_VariableValue("ui_recordSPDemo"))
+ {
+ clc.spDemoRecording = true;
+ }
+ else
+ {
+ clc.spDemoRecording = false;
+ }
+
+ Q_strncpyz(clc.demoName, demoName, sizeof(clc.demoName));
+
+ // don't start saving messages until a non-delta compressed message is received
+ clc.demowaiting = true;
+
+ // write out the gamestate message
+ MSG_Init(&buf, bufData, sizeof(bufData));
+ MSG_Bitstream(&buf);
+
+ // NOTE, MRE: all server->client messages now acknowledge
+ MSG_WriteLong(&buf, clc.reliableSequence);
+
+ MSG_WriteByte(&buf, svc_gamestate);
+ MSG_WriteLong(&buf, clc.serverCommandSequence);
+
+ // configstrings
+ for (i = 0; i < MAX_CONFIGSTRINGS; i++)
+ {
+ if (!cl.gameState.stringOffsets[i])
+ {
+ continue;
+ }
+ const char *s = cl.gameState.stringData + cl.gameState.stringOffsets[i];
+ MSG_WriteByte(&buf, svc_configstring);
+ MSG_WriteShort(&buf, i);
+ MSG_WriteBigString(&buf, s);
+ }
+
+ // baselines
+ ::memset(&nullstate, 0, sizeof(nullstate));
+ for (i = 0; i < MAX_GENTITIES; i++)
+ {
+ ent = &cl.entityBaselines[i];
+ if (!ent->number)
+ {
+ continue;
+ }
+ MSG_WriteByte(&buf, svc_baseline);
+ MSG_WriteDeltaEntity(clc.netchan.alternateProtocol, &buf, &nullstate, ent, true);
+ }
+
+ MSG_WriteByte(&buf, svc_EOF);
+
+ // finished writing the gamestate stuff
+
+ // write the client num
+ MSG_WriteLong(&buf, clc.clientNum);
+ // write the checksum feed
+ MSG_WriteLong(&buf, clc.checksumFeed);
+
+ // finished writing the client packet
+ MSG_WriteByte(&buf, svc_EOF);
+
+ // write it to the demo file
+ len = LittleLong(clc.serverMessageSequence - 1);
+ FS_Write(&len, 4, clc.demofile);
+
+ len = LittleLong(buf.cursize);
+ FS_Write(&len, 4, clc.demofile);
+ FS_Write(buf.data, buf.cursize, clc.demofile);
+
+ // the rest of the demo file will be copied from net messages
+}
+
+/*
+=======================================================================
+
+CLIENT SIDE DEMO PLAYBACK
+
+=======================================================================
+*/
+
+/*
+=================
+CL_DemoFrameDurationSDev
+=================
+*/
+static float CL_DemoFrameDurationSDev(void)
+{
+ int i;
+ int numFrames;
+ float mean = 0.0f;
+ float variance = 0.0f;
+
+ if ((clc.timeDemoFrames - 1) > MAX_TIMEDEMO_DURATIONS)
+ numFrames = MAX_TIMEDEMO_DURATIONS;
+ else
+ numFrames = clc.timeDemoFrames - 1;
+
+ for (i = 0; i < numFrames; i++) mean += clc.timeDemoDurations[i];
+ mean /= numFrames;
+
+ for (i = 0; i < numFrames; i++)
+ {
+ float x = clc.timeDemoDurations[i];
+
+ variance += ((x - mean) * (x - mean));
+ }
+ variance /= numFrames;
+
+ return sqrt(variance);
+}
+
+/*
+=================
+CL_DemoCompleted
+=================
+*/
+void CL_DemoCompleted(void)
+{
+ char buffer[MAX_STRING_CHARS];
+
+ if (cl_timedemo && cl_timedemo->integer)
+ {
+ int time;
+
+ time = Sys_Milliseconds() - clc.timeDemoStart;
+ if (time > 0)
+ {
+ // Millisecond times are frame durations:
+ // minimum/average/maximum/std deviation
+ Com_sprintf(buffer, sizeof(buffer), "%i frames %3.1f seconds %3.1f fps %d.0/%.1f/%d.0/%.1f ms\n",
+ clc.timeDemoFrames, time / 1000.0, clc.timeDemoFrames * 1000.0 / time, clc.timeDemoMinDuration,
+ time / (float)clc.timeDemoFrames, clc.timeDemoMaxDuration, CL_DemoFrameDurationSDev());
+ Com_Printf("%s", buffer);
+
+ // Write a log of all the frame durations
+ if (cl_timedemoLog && strlen(cl_timedemoLog->string) > 0)
+ {
+ int i;
+ int numFrames;
+ fileHandle_t f;
+
+ if ((clc.timeDemoFrames - 1) > MAX_TIMEDEMO_DURATIONS)
+ numFrames = MAX_TIMEDEMO_DURATIONS;
+ else
+ numFrames = clc.timeDemoFrames - 1;
+
+ f = FS_FOpenFileWrite(cl_timedemoLog->string);
+ if (f)
+ {
+ FS_Printf(f, "# %s", buffer);
+
+ for (i = 0; i < numFrames; i++) FS_Printf(f, "%d\n", clc.timeDemoDurations[i]);
+
+ FS_FCloseFile(f);
+ Com_Printf("%s written\n", cl_timedemoLog->string);
+ }
+ else
+ {
+ Com_Printf("Couldn't open %s for writing\n", cl_timedemoLog->string);
+ }
+ }
+ }
+ }
+
+ CL_Disconnect(true);
+ CL_NextDemo();
+}
+
+/*
+=================
+CL_ReadDemoMessage
+=================
+*/
+void CL_ReadDemoMessage(void)
+{
+ int r;
+ msg_t buf;
+ byte bufData[MAX_MSGLEN];
+ int s;
+
+ if (!clc.demofile)
+ {
+ CL_DemoCompleted();
+ return;
+ }
+
+ // get the sequence number
+ r = FS_Read(&s, 4, clc.demofile);
+ if (r != 4)
+ {
+ CL_DemoCompleted();
+ return;
+ }
+ clc.serverMessageSequence = LittleLong(s);
+
+ // init the message
+ MSG_Init(&buf, bufData, sizeof(bufData));
+
+ // get the length
+ r = FS_Read(&buf.cursize, 4, clc.demofile);
+ if (r != 4)
+ {
+ CL_DemoCompleted();
+ return;
+ }
+ buf.cursize = LittleLong(buf.cursize);
+ if (buf.cursize == -1)
+ {
+ CL_DemoCompleted();
+ return;
+ }
+ if (buf.cursize > buf.maxsize)
+ {
+ Com_Error(ERR_DROP, "CL_ReadDemoMessage: demoMsglen > MAX_MSGLEN");
+ }
+ r = FS_Read(buf.data, buf.cursize, clc.demofile);
+ if (r != buf.cursize)
+ {
+ Com_Printf("Demo file was truncated.\n");
+ CL_DemoCompleted();
+ return;
+ }
+
+ clc.lastPacketTime = cls.realtime;
+ buf.readcount = 0;
+ CL_ParseServerMessage(&buf);
+}
+
+/*
+====================
+CL_WalkDemoExt
+====================
+*/
+static int CL_WalkDemoExt(const char *arg, char *name, int *demofile)
+{
+ int i;
+ *demofile = 0;
+
+ Com_sprintf(name, MAX_OSPATH, "demos/%s.%s%d", arg, DEMOEXT, PROTOCOL_VERSION);
+ FS_FOpenFileRead(name, demofile, true);
+
+ if (*demofile)
+ {
+ Com_Printf("Demo file: %s\n", name);
+ return PROTOCOL_VERSION;
+ }
+
+ Com_Printf("Not found: %s\n", name);
+
+ for (i = 0; demo_protocols[i]; ++i)
+ {
+ if (demo_protocols[i] == PROTOCOL_VERSION) continue;
+
+ Com_sprintf(name, MAX_OSPATH, "demos/%s.%s%d", arg, DEMOEXT, demo_protocols[i]);
+ FS_FOpenFileRead(name, demofile, true);
+ if (*demofile)
+ {
+ Com_Printf("Demo file: %s\n", name);
+
+ return demo_protocols[i];
+ }
+ else
+ Com_Printf("Not found: %s\n", name);
+ }
+
+ return -1;
+}
+
+/*
+====================
+CL_CompleteDemoName
+====================
+*/
+static void CL_CompleteDemoName(char *args, int argNum)
+{
+ if (argNum == 2)
+ {
+ char demoExt[16];
+
+ Com_sprintf(demoExt, sizeof(demoExt), ".%s%d", DEMOEXT, PROTOCOL_VERSION);
+ Field_CompleteFilename("demos", demoExt, true, true);
+ }
+}
+
+/*
+====================
+CL_PlayDemo_f
+
+demo <demoname>
+
+====================
+*/
+void CL_PlayDemo_f(void)
+{
+ char name[MAX_OSPATH];
+ const char *ext_test;
+ int protocol, i;
+ char retry[MAX_OSPATH];
+
+ if (Cmd_Argc() != 2)
+ {
+ Com_Printf("demo <demoname>\n");
+ return;
+ }
+
+ // make sure a local server is killed
+ // 2 means don't force disconnect of local client
+ Cvar_Set("sv_killserver", "2");
+
+ // open the demo file
+ const char *arg = Cmd_Argv(1);
+
+ CL_Disconnect(true);
+
+ // check for an extension .DEMOEXT_?? (?? is protocol)
+ ext_test = strrchr(arg, '.');
+
+ if (ext_test && !Q_stricmpn(ext_test + 1, DEMOEXT, ARRAY_LEN(DEMOEXT) - 1))
+ {
+ protocol = atoi(ext_test + ARRAY_LEN(DEMOEXT));
+
+ for (i = 0; demo_protocols[i]; i++)
+ {
+ if (demo_protocols[i] == protocol) break;
+ }
+
+ if (demo_protocols[i] || protocol == PROTOCOL_VERSION)
+ {
+ Com_sprintf(name, sizeof(name), "demos/%s", arg);
+ FS_FOpenFileRead(name, &clc.demofile, true);
+ }
+ else
+ {
+ int len;
+
+ Com_Printf("Protocol %d not supported for demos\n", protocol);
+ len = ext_test - arg;
+
+ if (len >= ARRAY_LEN(retry)) len = ARRAY_LEN(retry) - 1;
+
+ Q_strncpyz(retry, arg, len + 1);
+ retry[len] = '\0';
+ protocol = CL_WalkDemoExt(retry, name, &clc.demofile);
+ }
+ }
+ else
+ protocol = CL_WalkDemoExt(arg, name, &clc.demofile);
+
+ if (!clc.demofile)
+ {
+ Com_Error(ERR_DROP, "couldn't open %s", name);
+ return;
+ }
+ Q_strncpyz(clc.demoName, arg, sizeof(clc.demoName));
+
+ clc.state = CA_CONNECTED;
+ clc.demoplaying = true;
+ Q_strncpyz(clc.servername, arg, sizeof(clc.servername));
+ clc.netchan.alternateProtocol = (protocol == 69 ? 2 : protocol == 70 ? 1 : 0);
+
+ // read demo messages until connected
+ while (clc.state >= CA_CONNECTED && clc.state < CA_PRIMED)
+ {
+ CL_ReadDemoMessage();
+ }
+ // don't get the first snapshot this frame, to prevent the long
+ // time from the gamestate load from messing causing a time skip
+ clc.firstDemoFrameSkipped = false;
+}
+
+/*
+====================
+CL_StartDemoLoop
+
+Closing the main menu will restart the demo loop
+====================
+*/
+static void CL_StartDemoLoop(void)
+{
+ // start the demo loop again
+ Cbuf_AddText("d1\n");
+ Key_SetCatcher(Key_GetCatcher() & KEYCATCH_CONSOLE);
+}
+
+/*
+==================
+CL_NextDemo
+
+Called when a demo or cinematic finishes
+If the "nextdemo" cvar is set, that command will be issued
+==================
+*/
+void CL_NextDemo(void)
+{
+ char v[MAX_STRING_CHARS];
+
+ Q_strncpyz(v, Cvar_VariableString("nextdemo"), sizeof(v));
+ v[MAX_STRING_CHARS - 1] = 0;
+ Com_DPrintf("CL_NextDemo: %s\n", v);
+ if (!v[0])
+ {
+ return;
+ }
+
+ Cvar_Set("nextdemo", "");
+ Cbuf_AddText(v);
+ Cbuf_AddText("\n");
+ Cbuf_Execute();
+}
+
+/*
+==================
+CL_DemoState
+
+Returns the current state of the demo system
+==================
+*/
+demoState_t CL_DemoState(void)
+{
+ if (clc.demoplaying)
+ {
+ return DS_PLAYBACK;
+ }
+ else if (clc.demorecording)
+ {
+ return DS_RECORDING;
+ }
+ else
+ {
+ return DS_NONE;
+ }
+}
+
+/*
+==================
+CL_DemoPos
+
+Returns the current position of the demo
+==================
+*/
+int CL_DemoPos(void)
+{
+ if (clc.demoplaying || clc.demorecording)
+ {
+ return FS_FTell(clc.demofile);
+ }
+ else
+ {
+ return 0;
+ }
+}
+
+/*
+==================
+CL_DemoName
+
+Returns the name of the demo
+==================
+*/
+void CL_DemoName(char *buffer, int size)
+{
+ if (clc.demoplaying || clc.demorecording)
+ {
+ Q_strncpyz(buffer, clc.demoName, size);
+ }
+ else if (size >= 1)
+ {
+ buffer[0] = '\0';
+ }
+}
+
+//======================================================================
+
+/*
+=====================
+CL_ClearState
+
+Called before parsing a gamestate
+=====================
+*/
+void CL_ClearState(void)
+{
+ // S_StopAllSounds();
+
+ ::memset(&cl, 0, sizeof(cl));
+}
+
+/*
+====================
+CL_UpdateGUID
+
+update cl_guid using QKEY_FILE and optional prefix
+====================
+*/
+static void CL_UpdateGUID(const char *prefix, int prefix_len)
+{
+ fileHandle_t f;
+ int len;
+
+ len = FS_SV_FOpenFileRead(QKEY_FILE, &f);
+ FS_FCloseFile(f);
+
+ if (len != QKEY_SIZE)
+ Cvar_Set("cl_guid", "");
+ else
+ Cvar_Set("cl_guid", Com_MD5File(QKEY_FILE, QKEY_SIZE, prefix, prefix_len));
+}
+
+static void CL_OldGame(void)
+{
+ if (cl_oldGameSet)
+ {
+ // change back to previous fs_game
+ cl_oldGameSet = false;
+ Cvar_Set2("fs_game", cl_oldGame, true);
+ FS_ConditionalRestart(clc.checksumFeed, false);
+ }
+}
+
+/*
+=====================
+CL_Disconnect
+
+Called when a connection, demo, or cinematic is being terminated.
+Goes from a connected state to either a menu state or a console state
+Sends a disconnect message to the server
+This is also called on Com_Error and Com_Quit, so it shouldn't cause any errors
+=====================
+*/
+void CL_Disconnect(bool showMainMenu)
+{
+ if (!com_cl_running || !com_cl_running->integer)
+ {
+ return;
+ }
+
+ // shutting down the client so enter full screen ui mode
+ Cvar_Set("r_uiFullScreen", "1");
+
+ if (clc.demorecording)
+ {
+ CL_StopRecord_f();
+ }
+
+ if (clc.download)
+ {
+ FS_FCloseFile(clc.download);
+ clc.download = 0;
+ }
+ *clc.downloadTempName = *clc.downloadName = 0;
+ Cvar_Set("cl_downloadName", "");
+
+#ifdef USE_MUMBLE
+ if (cl_useMumble->integer && mumble_islinked())
+ {
+ Com_Printf("Mumble: Unlinking from Mumble application\n");
+ mumble_unlink();
+ }
+#endif
+
+#ifdef USE_VOIP
+ if (cl_voipSend->integer)
+ {
+ int tmp = cl_voipUseVAD->integer;
+ cl_voipUseVAD->integer = 0; // disable this for a moment.
+ clc.voipOutgoingDataSize = 0; // dump any pending VoIP transmission.
+ Cvar_Set("cl_voipSend", "0");
+ CL_CaptureVoip(); // clean up any state...
+ cl_voipUseVAD->integer = tmp;
+ }
+
+ if (clc.voipCodecInitialized)
+ {
+ int i;
+ opus_encoder_destroy(clc.opusEncoder);
+ for (i = 0; i < MAX_CLIENTS; i++)
+ {
+ opus_decoder_destroy(clc.opusDecoder[i]);
+ }
+ }
+ Cmd_RemoveCommand("voip");
+#endif
+
+ if (clc.demofile)
+ {
+ FS_FCloseFile(clc.demofile);
+ clc.demofile = 0;
+ }
+
+ if (cls.ui && showMainMenu)
+ {
+ VM_Call(cls.ui, UI_SET_ACTIVE_MENU - (cls.uiInterface == 2 ? 2 : 0), UIMENU_NONE);
+ }
+
+ SCR_StopCinematic();
+ S_ClearSoundBuffer();
+
+ // send a disconnect message to the server
+ // send it a few times in case one is dropped
+ if (clc.state >= CA_CONNECTED)
+ {
+ CL_AddReliableCommand("disconnect", true);
+ CL_WritePacket();
+ CL_WritePacket();
+ CL_WritePacket();
+ }
+
+ // Remove pure paks
+ FS_PureServerSetLoadedPaks("", "");
+ FS_PureServerSetReferencedPaks("", "");
+
+ CL_ClearState();
+
+ // wipe the client connection
+ ::memset(&clc, 0, sizeof(clc));
+
+ clc.state = CA_DISCONNECTED;
+
+ CL_ProtocolSpecificCommandsInit();
+
+ // allow cheats locally
+ Cvar_Set("sv_cheats", "1");
+
+ // not connected to a pure server anymore
+ cl_connectedToPureServer = false;
+
+#ifdef USE_VOIP
+ // not connected to voip server anymore.
+ clc.voipEnabled = false;
+#endif
+
+ // Stop recording any video
+ if (CL_VideoRecording())
+ {
+ // Finish rendering current frame
+ SCR_UpdateScreen();
+ CL_CloseAVI();
+ }
+
+ CL_UpdateGUID(NULL, 0);
+
+ if (!noGameRestart)
+ CL_OldGame();
+ else
+ noGameRestart = false;
+}
+
+/*
+===================
+CL_ForwardCommandToServer
+
+adds the current command line as a clientCommand
+things like godmode, noclip, etc, are commands directed to the server,
+so when they are typed in at the console, they will need to be forwarded.
+===================
+*/
+void CL_ForwardCommandToServer(const char *string)
+{
+ const char *cmd = Cmd_Argv(0);
+
+ // ignore key up commands
+ if (cmd[0] == '-')
+ {
+ return;
+ }
+
+ if (clc.demoplaying || clc.state < CA_CONNECTED || cmd[0] == '+')
+ {
+ Com_Printf("Unknown command \"%s" S_COLOR_WHITE "\"\n", cmd);
+ return;
+ }
+
+ if (Cmd_Argc() > 1)
+ {
+ CL_AddReliableCommand(string, false);
+ }
+ else
+ {
+ CL_AddReliableCommand(cmd, false);
+ }
+}
+
+/*
+===================
+CL_RequestMotd
+
+===================
+*/
+static void CL_RequestMotd(void)
+{
+ char info[MAX_INFO_STRING];
+
+ if (!cl_motd->integer)
+ {
+ return;
+ }
+ Com_Printf("Resolving %s\n", MASTER_SERVER_NAME);
+
+ switch (NET_StringToAdr(MASTER_SERVER_NAME, &cls.updateServer, NA_UNSPEC))
+ {
+ case 0:
+ Com_Printf("Couldn't resolve master address\n");
+ return;
+
+ case 2:
+ cls.updateServer.port = BigShort(PORT_MASTER);
+ default:
+ break;
+ }
+
+ Com_Printf("%s resolved to %s\n", MASTER_SERVER_NAME, NET_AdrToStringwPort(cls.updateServer));
+
+ info[0] = 0;
+
+ Com_sprintf(cls.updateChallenge, sizeof(cls.updateChallenge), "%i", ((rand() << 16) ^ rand()) ^ Com_Milliseconds());
+
+ Info_SetValueForKey(info, "challenge", cls.updateChallenge);
+
+ NET_OutOfBandPrint(NS_CLIENT, cls.updateServer, "getmotd%s", info);
+}
+
+/*
+======================================================================
+
+CONSOLE COMMANDS
+
+======================================================================
+*/
+
+/*
+==================
+CL_ShowIP_f
+==================
+*/
+static void CL_ShowIP_f(void) { Sys_ShowIP(); }
+
+/*
+==================
+CL_ForwardToServer_f
+==================
+*/
+static void CL_ForwardToServer_f(void)
+{
+ if (clc.state != CA_ACTIVE || clc.demoplaying)
+ {
+ Com_Printf("Not connected to a server.\n");
+ return;
+ }
+
+ // don't forward the first argument
+ if (Cmd_Argc() > 1)
+ {
+ CL_AddReliableCommand(Cmd_Args(), false);
+ }
+}
+
+/*
+==================
+CL_Disconnect_f
+==================
+*/
+void CL_Disconnect_f(void)
+{
+ SCR_StopCinematic();
+ if (clc.state != CA_DISCONNECTED && clc.state != CA_CINEMATIC)
+ {
+ Com_Error(ERR_DISCONNECT, "Disconnected from server");
+ }
+}
+
+/*
+================
+CL_Reconnect_f
+
+================
+*/
+static void CL_Reconnect_f(void)
+{
+ if (!strlen(cl_reconnectArgs)) return;
+ Cbuf_AddText(va("connect %s\n", cl_reconnectArgs));
+}
+
+/*
+================
+CL_Connect_f
+
+================
+*/
+void CL_Connect_f(void)
+{
+ const char *server;
+ int alternateProtocol;
+ const char *serverString;
+ int argc = Cmd_Argc();
+ netadrtype_t family = NA_UNSPEC;
+
+ if (argc < 2 || argc > 4)
+ {
+ Com_Printf("usage: connect [-4|-6] server [-g|-1]\n");
+ return;
+ }
+
+ alternateProtocol = 0;
+ if (argc == 2)
+ {
+ }
+ else if (!strcmp(Cmd_Argv(argc - 1), "-g"))
+ {
+ alternateProtocol = 1;
+ --argc;
+ }
+ else if (!strcmp(Cmd_Argv(argc - 1), "-1"))
+ {
+ alternateProtocol = 2;
+ --argc;
+ }
+ else if (argc == 4)
+ {
+ --argc;
+ }
+
+ if (argc == 2)
+ server = Cmd_Argv(1);
+ else
+ {
+ if (!strcmp(Cmd_Argv(1), "-4"))
+ family = NA_IP;
+ else if (!strcmp(Cmd_Argv(1), "-6"))
+ family = NA_IP6;
+ else
+ Com_Printf("warning: only -4 or -6 as address type understood.\n");
+
+ server = Cmd_Argv(2);
+ }
+
+ // save arguments for reconnect
+ Q_strncpyz(cl_reconnectArgs, Cmd_Args(), sizeof(cl_reconnectArgs));
+
+ // clear any previous "server full" type messages
+ clc.serverMessage[0] = 0;
+
+ if (com_sv_running->integer && !strcmp(server, "localhost"))
+ {
+ // if running a local server, kill it
+ SV_Shutdown("Server quit");
+ }
+
+ // make sure a local server is killed
+ Cvar_Set("sv_killserver", "1");
+ SV_Frame(0);
+
+ noGameRestart = true;
+ CL_Disconnect(true);
+
+ Q_strncpyz(clc.servername, server, sizeof(clc.servername));
+
+ if (!NET_StringToAdr(clc.servername, &clc.serverAddress, family))
+ {
+ Com_Printf("Bad server address\n");
+ clc.state = CA_DISCONNECTED;
+ CL_ProtocolSpecificCommandsInit();
+ return;
+ }
+ if (clc.serverAddress.port == 0)
+ {
+ clc.serverAddress.port = BigShort(PORT_SERVER);
+ }
+ clc.serverAddress.alternateProtocol = alternateProtocol;
+
+ serverString = NET_AdrToStringwPort(clc.serverAddress);
+
+ Com_Printf("%s resolved to %s\n", clc.servername, serverString);
+
+ if (cl_guidServerUniq->integer)
+ CL_UpdateGUID(serverString, strlen(serverString));
+ else
+ CL_UpdateGUID(NULL, 0);
+
+ clc.challenge2[0] = '\0';
+ clc.sendSignature = false;
+
+ // if we aren't playing on a lan, we need to authenticate
+ // with the cd key
+ if (NET_IsLocalAddress(clc.serverAddress))
+ {
+ clc.state = CA_CHALLENGING;
+ clc.sendSignature = true;
+ }
+ else
+ {
+ clc.state = CA_CONNECTING;
+
+ // Set a client challenge number that ideally is mirrored back by the server.
+ clc.challenge = ((rand() << 16) ^ rand()) ^ Com_Milliseconds();
+ }
+
+ Key_SetCatcher(Key_GetCatcher() & KEYCATCH_CONSOLE);
+ clc.connectTime = -99999; // CL_CheckForResend() will fire immediately
+ clc.connectPacketCount = 0;
+}
+
+#define MAX_RCON_MESSAGE 1024
+
+/*
+==================
+CL_CompleteRcon
+==================
+*/
+static void CL_CompleteRcon(char *args, int argNum)
+{
+ if (argNum == 2)
+ {
+ // Skip "rcon "
+ char *p = Com_SkipTokens(args, 1, " ");
+
+ if (p > args) Field_CompleteCommand(p, true, true);
+ }
+}
+
+/*
+=====================
+CL_Rcon_f
+
+ Send the rest of the command line over as
+ an unconnected command.
+=====================
+*/
+static void CL_Rcon_f(void)
+{
+ char message[MAX_RCON_MESSAGE];
+ netadr_t to;
+
+ if (!rcon_client_password->string[0])
+ {
+ Com_Printf(
+ "You must set 'rconpassword' before\n"
+ "issuing an rcon command.\n");
+ return;
+ }
+
+ message[0] = -1;
+ message[1] = -1;
+ message[2] = -1;
+ message[3] = -1;
+ message[4] = 0;
+
+ Q_strcat(message, MAX_RCON_MESSAGE, "rcon ");
+
+ Q_strcat(message, MAX_RCON_MESSAGE, rcon_client_password->string);
+ Q_strcat(message, MAX_RCON_MESSAGE, " ");
+
+ // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=543
+ Q_strcat(message, MAX_RCON_MESSAGE, Cmd_Cmd() + 5);
+
+ if (clc.state >= CA_CONNECTED)
+ {
+ to = clc.netchan.remoteAddress;
+ }
+ else
+ {
+ if (!strlen(rconAddress->string))
+ {
+ Com_Printf(
+ "You must either be connected,\n"
+ "or set the 'rconAddress' cvar\n"
+ "to issue rcon commands\n");
+
+ return;
+ }
+ NET_StringToAdr(rconAddress->string, &to, NA_UNSPEC);
+ if (to.port == 0)
+ {
+ to.port = BigShort(PORT_SERVER);
+ }
+ }
+
+ NET_SendPacket(NS_CLIENT, strlen(message) + 1, message, to);
+}
+
+/*
+=================
+CL_SendPureChecksums
+=================
+*/
+static void CL_SendPureChecksums(void)
+{
+ char cMsg[MAX_INFO_VALUE];
+
+ // if we are pure we need to send back a command with our referenced pk3 checksums
+ Com_sprintf(cMsg, sizeof(cMsg), "cp %d %s", cl.serverId, FS_ReferencedPakPureChecksums());
+
+ CL_AddReliableCommand(cMsg, false);
+}
+
+/*
+=================
+CL_ResetPureClientAtServer
+=================
+*/
+static void CL_ResetPureClientAtServer(void) { CL_AddReliableCommand("vdr", false); }
+
+/*
+=================
+CL_Snd_Restart
+
+Restart the sound subsystem
+=================
+*/
+static void CL_Snd_Shutdown(void)
+{
+ S_Shutdown();
+ cls.soundStarted = false;
+}
+/*
+==================
+CL_PK3List_f
+==================
+*/
+static void CL_OpenedPK3List_f(void) { Com_Printf("Opened PK3 Names: %s\n", FS_LoadedPakNames(false)); }
+
+/*
+==================
+CL_PureList_f
+==================
+*/
+static void CL_ReferencedPK3List_f(void) { Com_Printf("Referenced PK3 Names: %s\n", FS_ReferencedPakNames(false)); }
+
+/*
+==================
+CL_Configstrings_f
+==================
+*/
+static void CL_Configstrings_f(void)
+{
+ int i;
+ int ofs;
+
+ if (clc.state != CA_ACTIVE)
+ {
+ Com_Printf("Not connected to a server.\n");
+ return;
+ }
+
+ for (i = 0; i < MAX_CONFIGSTRINGS; i++)
+ {
+ ofs = cl.gameState.stringOffsets[i];
+ if (!ofs)
+ {
+ continue;
+ }
+ Com_Printf("%4i: %s\n", i, cl.gameState.stringData + ofs);
+ }
+}
+
+/*
+==============
+CL_Clientinfo_f
+==============
+*/
+static void CL_Clientinfo_f(void)
+{
+ Com_Printf("--------- Client Information ---------\n");
+ Com_Printf("state: %i\n", clc.state);
+ Com_Printf("Server: %s\n", clc.servername);
+ Com_Printf("User info settings:\n");
+ Info_Print(Cvar_InfoString(CVAR_USERINFO));
+ Com_Printf("--------------------------------------\n");
+}
+
+//====================================================================
+
+/*
+=================
+CL_DownloadsComplete
+
+Called when all downloading has been completed
+=================
+*/
+static void CL_DownloadsComplete(void)
+{
+ Com_Printf("Downloads complete\n");
+
+ // if we downloaded with cURL
+ if (clc.cURLUsed)
+ {
+ clc.cURLUsed = false;
+ CL_cURL_Shutdown();
+ if (clc.cURLDisconnected)
+ {
+ if (clc.downloadRestart)
+ {
+ if (!clc.activeCURLNotGameRelated) FS_Restart(clc.checksumFeed);
+ clc.downloadRestart = false;
+ }
+ clc.cURLDisconnected = false;
+ if (!clc.activeCURLNotGameRelated) CL_Reconnect_f();
+ return;
+ }
+ }
+
+ // if we downloaded files we need to restart the file system
+ if (clc.downloadRestart)
+ {
+ clc.downloadRestart = false;
+
+ FS_Restart(clc.checksumFeed); // We possibly downloaded a pak, restart the file system to load it
+
+ // inform the server so we get new gamestate info
+ CL_AddReliableCommand("donedl", false);
+
+ // by sending the donedl command we request a new gamestate
+ // so we don't want to load stuff yet
+ return;
+ }
+ else
+ {
+ FS_ClearPakReferences(0);
+ }
+
+ // let the client game init and load data
+ clc.state = CA_LOADING;
+
+ // Pump the loop, this may change gamestate!
+ Com_EventLoop();
+
+ // if the gamestate was changed by calling Com_EventLoop
+ // then we loaded everything already and we don't want to do it again.
+ if (clc.state != CA_LOADING)
+ {
+ return;
+ }
+
+ // starting to load a map so we get out of full screen ui mode
+ Cvar_Set("r_uiFullScreen", "0");
+
+ // flush client memory and start loading stuff
+ // this will also (re)load the UI
+ // if this is a local client then only the client part of the hunk
+ // will be cleared, note that this is done after the hunk mark has been set
+ CL_FlushMemory();
+
+ // initialize the CGame
+ cls.cgameStarted = true;
+ CL_InitCGame();
+
+ // set pure checksums
+ CL_SendPureChecksums();
+
+ CL_WritePacket();
+ CL_WritePacket();
+ CL_WritePacket();
+}
+
+/*
+=================
+CL_BeginDownload
+
+Requests a file to download from the server. Stores it in the current
+game directory.
+=================
+*/
+static void CL_BeginDownload(const char *localName, const char *remoteName)
+{
+ Com_DPrintf(
+ "***** CL_BeginDownload *****\n"
+ "Localname: %s\n"
+ "Remotename: %s\n"
+ "****************************\n",
+ localName, remoteName);
+
+ Q_strncpyz(clc.downloadName, localName, sizeof(clc.downloadName));
+ Com_sprintf(clc.downloadTempName, sizeof(clc.downloadTempName), "%s.tmp", localName);
+
+ // Set so UI gets access to it
+ Cvar_Set("cl_downloadName", remoteName);
+ Cvar_Set("cl_downloadSize", "0");
+ Cvar_Set("cl_downloadCount", "0");
+ Cvar_SetValue("cl_downloadTime", cls.realtime);
+
+ clc.downloadBlock = 0; // Starting new file
+ clc.downloadCount = 0;
+
+ // Stop any errant looping sounds that may be playing
+ S_ClearLoopingSounds(true);
+
+ CL_AddReliableCommand(va("download %s", remoteName), false);
+}
+
+/*
+=================
+CL_NextDownload
+
+A download completed or failed
+=================
+*/
+void CL_NextDownload(void)
+{
+ char *s;
+ char *remoteName, *localName;
+ bool useCURL = false;
+ int prompt;
+
+ // A download has finished, check whether this matches a referenced checksum
+ if (*clc.downloadName && !clc.activeCURLNotGameRelated)
+ {
+ char *zippath = FS_BuildOSPath(Cvar_VariableString("fs_homepath"), clc.downloadName, "");
+ zippath[strlen(zippath) - 1] = '\0';
+
+ if (!FS_CompareZipChecksum(zippath)) Com_Error(ERR_DROP, "Incorrect checksum for file: %s", clc.downloadName);
+ }
+
+ *clc.downloadTempName = *clc.downloadName = 0;
+ Cvar_Set("cl_downloadName", "");
+
+ // We are looking to start a download here
+ if (*clc.downloadList)
+ {
+ // Prompt if we do not allow automatic downloads
+ prompt = com_downloadPrompt->integer;
+ if (!(prompt & DLP_TYPE_MASK) && !(cl_allowDownload->integer & DLF_ENABLE))
+ {
+ char files[MAX_INFO_STRING] = "";
+ char *name, *head;
+ const char *pure_msg;
+ const char *url_msg = "";
+ int i = 0, others = 0, swap = 0, max_list = 12;
+
+ // Set the download URL message
+ if ((clc.sv_allowDownload & DLF_ENABLE) && !(clc.sv_allowDownload & DLF_NO_REDIRECT))
+ {
+ url_msg = va("The server redirects to the following URL:\n%s", clc.sv_dlURL);
+ max_list -= 6;
+ }
+
+ // Make a pretty version of the download list
+ name = clc.downloadList;
+ if (*name == '@') name++;
+
+ do
+ {
+ // Copy remote name
+ head = name;
+ while (*head && *head != '@') head++;
+
+ swap = *head;
+ *head = 0;
+
+ if (i++ < max_list)
+ {
+ if (i > 1) Q_strcat(files, sizeof(files), ", ");
+ Q_strcat(files, sizeof(files), name);
+ }
+ else
+ {
+ others++;
+ }
+
+ *head = swap;
+ if (!swap) break;
+
+ // Skip local name
+ head++;
+ while (*head && *head != '@') head++;
+
+ name = head + 1;
+ } while (*head);
+
+ if (others)
+ {
+ Q_strcat(files, sizeof(files), va("(%d other file%s)\n", others, others > 1 ? "s" : ""));
+ }
+
+ // Set the pure message
+ if (cl_connectedToPureServer)
+ {
+ if (!(clc.sv_allowDownload & DLF_ENABLE) ||
+ ((clc.sv_allowDownload & DLF_NO_UDP) && (clc.sv_allowDownload & DLF_NO_REDIRECT)))
+ {
+ pure_msg =
+ "You are missing files required by the server. "
+ "The server does not allow downloading. "
+ "You must install these files manually:";
+ }
+ else
+ {
+ pure_msg =
+ "You are missing files required by the server. "
+ "You must download these files or disconnect:";
+ }
+ }
+ else
+ {
+ pure_msg =
+ "You are missing optional files provided by the "
+ "server. You may not need them to play but can "
+ "choose to download them anyway:";
+ }
+
+ Cvar_Set("com_downloadPromptText", va("%s\n\n%s\n%s", pure_msg, files, url_msg));
+ Cvar_Set("com_downloadPrompt", va("%d", DLP_SHOW));
+ return;
+ }
+
+ if (!(prompt & DLP_PROMPTED)) Cvar_Set("com_downloadPrompt", va("%d", prompt | DLP_PROMPTED));
+
+ prompt &= DLP_TYPE_MASK;
+
+ s = clc.downloadList;
+
+ // format is:
+ // @remotename@localname@remotename@localname, etc.
+
+ if (*s == '@') s++;
+ remoteName = s;
+
+ if ((s = strchr(s, '@')) == NULL)
+ {
+ CL_DownloadsComplete();
+ return;
+ }
+
+ *s++ = 0;
+ localName = s;
+ if ((s = strchr(s, '@')) != NULL)
+ *s++ = 0;
+ else
+ s = localName + strlen(localName); // point at the nul byte
+
+ if (((cl_allowDownload->integer & DLF_ENABLE) && !(cl_allowDownload->integer & DLF_NO_REDIRECT)) ||
+ prompt == DLP_CURL)
+ {
+ Com_Printf("Trying CURL download: %s; %s\n", localName, remoteName);
+ if (clc.sv_allowDownload & DLF_NO_REDIRECT)
+ {
+ Com_Printf(
+ "WARNING: server does not "
+ "allow download redirection "
+ "(sv_allowDownload is %d)\n",
+ clc.sv_allowDownload);
+ }
+ else if (!*clc.sv_dlURL)
+ {
+ Com_Printf(
+ "WARNING: server allows "
+ "download redirection, but does not "
+ "have sv_dlURL set\n");
+ }
+ else if (!CL_cURL_Init())
+ {
+ Com_Printf(
+ "WARNING: could not load "
+ "cURL library\n");
+ }
+ else
+ {
+ CL_cURL_BeginDownload(localName, va("%s/%s", clc.sv_dlURL, remoteName));
+ useCURL = true;
+ }
+ }
+ else if (!(clc.sv_allowDownload & DLF_NO_REDIRECT))
+ {
+ Com_Printf(
+ "WARNING: server allows download "
+ "redirection, but it disabled by client "
+ "configuration (cl_allowDownload is %d)\n",
+ cl_allowDownload->integer);
+ }
+ if (!useCURL)
+ {
+ Com_Printf("Trying UDP download: %s; %s\n", localName, remoteName);
+
+ if ((!(cl_allowDownload->integer & DLF_ENABLE) || (cl_allowDownload->integer & DLF_NO_UDP)) &&
+ prompt != DLP_UDP)
+ {
+ if (cl_connectedToPureServer)
+ {
+ Com_Error(ERR_DROP,
+ "Automatic downloads are "
+ "disabled on your client (cl_allowDownload is %d). "
+ "You can enable automatic downloads in the Options "
+ "menu.",
+ cl_allowDownload->integer);
+ return;
+ }
+
+ Com_Printf("WARNING: UDP downloads are disabled.\n");
+ CL_DownloadsComplete();
+ return;
+ }
+ else
+ {
+ CL_BeginDownload(localName, remoteName);
+ }
+ }
+ clc.downloadRestart = true;
+
+ // move over the rest
+ memmove(clc.downloadList, s, strlen(s) + 1);
+
+ return;
+ }
+
+ CL_DownloadsComplete();
+}
+
+/*
+=================
+CL_InitDownloads
+
+After receiving a valid game state, we valid the cgame and local zip files here
+and determine if we need to download them
+=================
+*/
+void CL_InitDownloads(void)
+{
+ if (FS_ComparePaks(clc.downloadList, sizeof(clc.downloadList), true))
+ {
+ Com_Printf("Need paks: %s\n", clc.downloadList);
+
+ Cvar_Set("com_downloadPrompt", "0");
+ if (*clc.downloadList)
+ {
+ // if autodownloading is not enabled on the server
+ clc.state = CA_CONNECTED;
+ *clc.downloadTempName = *clc.downloadName = 0;
+ Cvar_Set("cl_downloadName", "");
+ CL_NextDownload();
+ return;
+ }
+ }
+ CL_DownloadsComplete();
+}
+
+/*
+===============
+CL_UnloadRSAKeypair
+===============
+*/
+static void CL_UnloadRSAKeypair(void)
+{
+ rsa_public_key_clear(&cls.rsa.public_key);
+ rsa_private_key_clear(&cls.rsa.private_key);
+}
+
+/*
+===============
+CL_WriteRSAPublicKey
+===============
+*/
+static bool CL_WriteRSAPublicKey(void)
+{
+ struct nettle_buffer key_buffer;
+ fileHandle_t f;
+
+ f = FS_SV_FOpenFileWrite(RSA_PUBLIC_KEY_FILE);
+ if (!f) return false;
+
+ nettle_buffer_init(&key_buffer);
+ if (!rsa_keypair_to_sexp(&key_buffer, NULL, &cls.rsa.public_key, NULL))
+ {
+ FS_FCloseFile(f);
+ nettle_buffer_clear(&key_buffer);
+ return false;
+ }
+
+ FS_Write(key_buffer.contents, key_buffer.size, f);
+ FS_FCloseFile(f);
+
+ nettle_buffer_clear(&key_buffer);
+ return true;
+}
+
+/*
+===============
+CL_WriteRSAPrivateKey
+===============
+*/
+static bool CL_WriteRSAPrivateKey(void)
+{
+ struct nettle_buffer key_buffer;
+ fileHandle_t f;
+
+#ifndef _WIN32
+ int old_umask = umask(0377);
+#endif
+ f = FS_SV_FOpenFileWrite(RSA_PRIVATE_KEY_FILE);
+#ifndef _WIN32
+ umask(old_umask);
+#endif
+ if (!f) return false;
+
+ nettle_buffer_init(&key_buffer);
+ if (!rsa_keypair_to_sexp(&key_buffer, NULL, &cls.rsa.public_key, &cls.rsa.private_key))
+ {
+ FS_FCloseFile(f);
+ nettle_buffer_clear(&key_buffer);
+ return false;
+ }
+
+ FS_Write(key_buffer.contents, key_buffer.size, f);
+ FS_FCloseFile(f);
+
+ nettle_buffer_clear(&key_buffer);
+ return true;
+}
+
+/*
+===============
+CL_GenerateRSAKeypair
+
+public_key and private_key must already be inititalized before calling this
+function. This is done by CL_LoadRSAKeypair.
+===============
+*/
+static void CL_GenerateRSAKeypair(void)
+{
+ mpz_set_ui(cls.rsa.public_key.e, RSA_PUBLIC_EXPONENT);
+
+ int success = rsa_generate_keypair(
+ &cls.rsa.public_key, &cls.rsa.private_key, NULL, qnettle_random, NULL, NULL, RSA_KEY_LENGTH, 0);
+ if (success)
+ if (CL_WriteRSAPrivateKey())
+ if (CL_WriteRSAPublicKey())
+ {
+ Com_Printf("RSA keypair generated\n");
+ return;
+ }
+
+ // failure
+ CL_UnloadRSAKeypair();
+ Com_Printf("Error generating RSA keypair, setting cl_rsaAuth to 0\n");
+ Cvar_Set("cl_rsaAuth", "0");
+}
+
+/*
+===============
+CL_LoadRSAKeypair
+
+Attempt to load RSA keys from RSA_PRIVATE_KEY_FILE
+If this fails, generate a new keypair
+===============
+*/
+static void CL_LoadRSAKeypair(void)
+{
+ int len;
+ fileHandle_t f;
+ uint8_t *buf;
+
+ rsa_public_key_init(&cls.rsa.public_key);
+ rsa_private_key_init(&cls.rsa.private_key);
+
+ Com_DPrintf("Loading RSA private key from %s\n", RSA_PRIVATE_KEY_FILE);
+
+ len = FS_SV_FOpenFileRead(RSA_PRIVATE_KEY_FILE, &f);
+ if (!f)
+ {
+ Com_DPrintf("RSA private key not found, generating\n");
+ CL_GenerateRSAKeypair();
+ return;
+ }
+
+ if (len < 1)
+ {
+ Com_DPrintf("RSA private key empty, generating\n");
+ FS_FCloseFile(f);
+ CL_GenerateRSAKeypair();
+ return;
+ }
+
+ buf = (uint8_t *)Z_Malloc(len);
+ FS_Read(buf, len, f);
+ FS_FCloseFile(f);
+
+ if (!rsa_keypair_from_sexp(&cls.rsa.public_key, &cls.rsa.private_key, 0, len, buf))
+ {
+ memset(buf, 0, len);
+ Z_Free(buf);
+ CL_UnloadRSAKeypair();
+ Com_Error(ERR_FATAL, "Invalid RSA private key found.");
+ return;
+ }
+
+ memset(buf, 0, len);
+ Z_Free(buf);
+
+ len = FS_SV_FOpenFileRead(RSA_PUBLIC_KEY_FILE, &f);
+ if (!f || len < 1) CL_WriteRSAPublicKey();
+ if (f) FS_FCloseFile(f);
+
+ Com_DPrintf("RSA private key loaded\n");
+}
+
+/*
+=================
+CL_CheckForResend
+
+Resend a connect message if the last one has timed out
+=================
+*/
+static void CL_CheckForResend(void)
+{
+ int port;
+ char info[MAX_INFO_STRING];
+ char data[MAX_MSGLEN];
+
+ // don't send anything if playing back a demo
+ if (clc.demoplaying)
+ {
+ return;
+ }
+
+ // resend if we haven't gotten a reply yet
+ if (clc.state != CA_CONNECTING && clc.state != CA_CHALLENGING)
+ {
+ return;
+ }
+
+ if (cls.realtime - clc.connectTime < RETRANSMIT_TIMEOUT)
+ {
+ return;
+ }
+
+ clc.connectTime = cls.realtime; // for retransmit requests
+ clc.connectPacketCount++;
+
+ switch (clc.state)
+ {
+ case CA_CONNECTING:
+ // requesting a challenge
+
+ // The challenge request shall be followed by a client challenge so no malicious server can hijack this
+ // connection.
+ // Add the gamename so the server knows we're running the correct game or can reject the client
+ // with a meaningful message
+ if (clc.serverAddress.alternateProtocol == 2)
+ {
+ Com_sprintf(data, sizeof(data), "getchallenge");
+ }
+ else if (clc.serverAddress.alternateProtocol == 1)
+ {
+ Com_sprintf(data, sizeof(data), "getchallenge %d", clc.challenge);
+ }
+ else
+ Com_sprintf(data, sizeof(data), "getchallenge %d %s", clc.challenge, com_gamename->string);
+
+ NET_OutOfBandPrint(NS_CLIENT, clc.serverAddress, "%s", data);
+ break;
+
+ case CA_CHALLENGING:
+ // sending back the challenge
+ port = Cvar_VariableValue("net_qport");
+
+ Q_strncpyz(info, Cvar_InfoString(CVAR_USERINFO), sizeof(info));
+ Info_SetValueForKey(info, "protocol",
+ va("%i",
+ (clc.serverAddress.alternateProtocol == 0 ? PROTOCOL_VERSION
+ : clc.serverAddress.alternateProtocol == 1 ? 70 : 69)));
+ Info_SetValueForKey(info, "qport", va("%i", port));
+ Info_SetValueForKey(info, "challenge", va("%i", clc.challenge));
+
+ if (cl_rsaAuth->integer && clc.sendSignature)
+ {
+ char public_key[RSA_STRING_LENGTH];
+ char signature[RSA_STRING_LENGTH];
+ struct sha256_ctx sha256_hash;
+ mpz_t n;
+
+ Info_SetValueForKey(info, "challenge2", clc.challenge2);
+
+ sha256_init(&sha256_hash);
+ sha256_update(&sha256_hash, strlen(info), (uint8_t *)info);
+
+ mpz_init(n);
+ rsa_sha256_sign(&cls.rsa.private_key, &sha256_hash, n);
+ mpz_get_str(signature, 16, n);
+ mpz_clear(n);
+
+ mpz_get_str(public_key, 16, cls.rsa.public_key.n);
+
+ Com_sprintf(data, sizeof(data), "connect \"%s\" %s %s", info, public_key, signature);
+ }
+ else
+ {
+ Com_sprintf(data, sizeof(data), "connect \"%s\"", info);
+ }
+
+ NET_OutOfBandData(NS_CLIENT, clc.serverAddress, (byte *)data, strlen(data));
+ // the most current userinfo has been sent, so watch for any
+ // newer changes to userinfo variables
+ cvar_modifiedFlags &= ~CVAR_USERINFO;
+ break;
+
+ default:
+ Com_Error(ERR_FATAL, "CL_CheckForResend: bad clc.state");
+ }
+}
+
+/*
+=====================
+CL_MapLoading
+
+A local server is starting to load a map, so update the
+screen to let the user know about it, then dump all client
+memory on the hunk from cgame, ui, and renderer
+=====================
+*/
+void CL_MapLoading(void)
+{
+ if (com_dedicated->integer)
+ {
+ clc.state = CA_DISCONNECTED;
+ Key_SetCatcher(KEYCATCH_CONSOLE);
+ CL_ProtocolSpecificCommandsInit();
+ return;
+ }
+
+ if (!com_cl_running->integer)
+ {
+ return;
+ }
+
+ Key_SetCatcher(Key_GetCatcher() & KEYCATCH_CONSOLE);
+
+ // if we are already connected to the local host, stay connected
+ if (clc.state >= CA_CONNECTED && !Q_stricmp(clc.servername, "localhost"))
+ {
+ clc.state = CA_CONNECTED; // so the connect screen is drawn
+ ::memset(cls.updateInfoString, 0, sizeof(cls.updateInfoString));
+ ::memset(clc.serverMessage, 0, sizeof(clc.serverMessage));
+ ::memset(&cl.gameState, 0, sizeof(cl.gameState));
+ clc.lastPacketSentTime = -9999;
+ SCR_UpdateScreen();
+ }
+ else
+ {
+ CL_Disconnect(true);
+ Q_strncpyz(clc.servername, "localhost", sizeof(clc.servername));
+ clc.state = CA_CHALLENGING; // so the connect screen is drawn
+ Key_SetCatcher(Key_GetCatcher() & KEYCATCH_CONSOLE);
+ SCR_UpdateScreen();
+ clc.connectTime = -RETRANSMIT_TIMEOUT;
+ NET_StringToAdr(clc.servername, &clc.serverAddress, NA_UNSPEC);
+ // we don't need a challenge on the localhost
+
+ CL_CheckForResend();
+ }
+}
+
+/*
+===================
+CL_MotdPacket
+
+===================
+*/
+static void CL_MotdPacket(netadr_t from, const char *info)
+{
+ const char *v;
+
+ // if not from our server, ignore it
+ if (!NET_CompareAdr(from, cls.updateServer))
+ {
+ Com_DPrintf("MOTD packet from unexpected source\n");
+ return;
+ }
+
+ Com_DPrintf("MOTD packet: %s\n", info);
+ while (*info != '\\') info++;
+
+ // check challenge
+ v = Info_ValueForKey(info, "challenge");
+ if (strcmp(v, cls.updateChallenge))
+ {
+ Com_DPrintf(
+ "MOTD packet mismatched challenge: "
+ "'%s' != '%s'\n",
+ v, cls.updateChallenge);
+ return;
+ }
+
+ v = Info_ValueForKey(info, "motd");
+
+ Q_strncpyz(cls.updateInfoString, info, sizeof(cls.updateInfoString));
+ Cvar_Set("cl_motdString", v);
+}
+
+/*
+===================
+CL_InitServerInfo
+===================
+*/
+static void CL_InitServerInfo(serverInfo_t *server, netadr_t *address)
+{
+ server->adr = *address;
+ server->clients = 0;
+ server->hostName[0] = '\0';
+ server->mapName[0] = '\0';
+ server->label[0] = '\0';
+ server->maxClients = 0;
+ server->maxPing = 0;
+ server->minPing = 0;
+ server->ping = -1;
+ server->game[0] = '\0';
+ server->gameType = 0;
+ server->netType = 0;
+}
+
+/*
+===================
+CL_GSRSequenceInformation
+
+Parses this packet's index and the number of packets from a master server's
+response. Updates the packet count and returns the index. Advances the data
+pointer as appropriate (but only when parsing was successful)
+
+The sequencing information isn't terribly useful at present (we can skip
+duplicate packets, but we don't bother to make sure we've got all of them).
+===================
+*/
+static int CL_GSRSequenceInformation(int alternateProtocol, byte **data)
+{
+ char *p = (char *)*data, *e;
+ int ind, num;
+ // '\0'-delimited fields: this packet's index, total number of packets
+ if (*p++ != '\0') return -1;
+
+ ind = strtol(p, (char **)&e, 10);
+ if (*e++ != '\0') return -1;
+
+ num = strtol(e, (char **)&p, 10);
+ if (*p++ != '\0') return -1;
+
+ if (num <= 0 || ind <= 0 || ind > num) return -1; // nonsensical response
+
+ if (cls.numAlternateMasterPackets[alternateProtocol] > 0 && num != cls.numAlternateMasterPackets[alternateProtocol])
+ {
+ // Assume we sent two getservers and somehow they changed in
+ // between - only use the results that arrive later
+ Com_DPrintf("Master changed its mind about%s packet count!\n",
+ (alternateProtocol == 0 ? "" : alternateProtocol == 1 ? " alternate-1" : " alternate-2"));
+ cls.receivedAlternateMasterPackets[alternateProtocol] = 0;
+ // cls.numglobalservers = 0;
+ // cls.numGlobalServerAddresses = 0;
+ }
+ cls.numAlternateMasterPackets[alternateProtocol] = num;
+
+ // successfully parsed
+ *data = (byte *)p;
+ return ind;
+}
+
+/*
+===================
+CL_GSRFeaturedLabel
+
+Parses from the data an arbitrary text string labelling the servers in the
+following getserversresponse packet.
+The result is copied to *buf, and *data is advanced as appropriate
+===================
+*/
+static void CL_GSRFeaturedLabel(byte **data, char *buf, int size)
+{
+ char *l = buf;
+
+ // copy until '\0' which indicates field break
+ // or slash which indicates beginning of server list
+ while (**data && **data != '\\' && **data != '/')
+ {
+ if (l < &buf[size - 1])
+ *l = **data;
+ else if (l == &buf[size - 1])
+ Com_DPrintf(S_COLOR_YELLOW
+ "Warning: "
+ "CL_GSRFeaturedLabel: overflow\n");
+ l++, (*data)++;
+ }
+
+ if (l < &buf[size - 1])
+ *l = '\0';
+ else
+ buf[size - 1] = '\0';
+}
+
+#define MAX_SERVERSPERPACKET 256
+
+/*
+===================
+CL_ServersResponsePacket
+===================
+*/
+static void CL_ServersResponsePacket(const netadr_t *from, msg_t *msg, bool extended)
+{
+ int i, count, total;
+ netadr_t addresses[MAX_SERVERSPERPACKET];
+ int numservers;
+ byte *buffptr;
+ byte *buffend;
+ char label[MAX_FEATLABEL_CHARS] = "";
+
+ Com_DPrintf("CL_ServersResponsePacket from %s %s\n",
+ NET_AdrToStringwPort(*from),
+ extended ? " (extended)" : "");
+
+ if (cls.numglobalservers == -1)
+ {
+ // state to detect lack of servers or lack of response
+ cls.numglobalservers = 0;
+ cls.numGlobalServerAddresses = 0;
+ for (i = 0; i < 3; ++i)
+ {
+ cls.numAlternateMasterPackets[i] = 0;
+ cls.receivedAlternateMasterPackets[i] = 0;
+ }
+ }
+
+ // parse through server response string
+ numservers = 0;
+ buffptr = msg->data;
+ buffend = buffptr + msg->cursize;
+
+ // skip header
+ buffptr += 4;
+
+ // advance to initial token
+ // I considered using strchr for this but I don't feel like relying
+ // on its behaviour with '\0'
+ while (*buffptr && *buffptr != '\\' && *buffptr != '/')
+ {
+ buffptr++;
+
+ if (buffptr >= buffend) break;
+ }
+
+ if (*buffptr == '\0')
+ {
+ int ind = CL_GSRSequenceInformation(from->alternateProtocol, &buffptr);
+ if (ind >= 0)
+ {
+ // this denotes the start of new-syntax stuff
+ // have we already received this packet?
+ if (cls.receivedAlternateMasterPackets[from->alternateProtocol] & (1 << (ind - 1)))
+ {
+ Com_DPrintf(
+ "CL_ServersResponsePacket: "
+ "received packet %d again, ignoring\n",
+ ind);
+ return;
+ }
+ // TODO: detect dropped packets and make another
+ // request
+ Com_DPrintf(
+ "CL_ServersResponsePacket:%s packet "
+ "%d of %d\n",
+ (from->alternateProtocol == 0 ? "" : from->alternateProtocol == 1 ? " alternate-1" : " alternate-2"),
+ ind, cls.numAlternateMasterPackets[from->alternateProtocol]);
+ cls.receivedAlternateMasterPackets[from->alternateProtocol] |= (1 << (ind - 1));
+
+ CL_GSRFeaturedLabel(&buffptr, label, sizeof(label));
+ }
+ // now skip to the server list
+ for (; buffptr < buffend && *buffptr != '\\' && *buffptr != '/'; buffptr++)
+ ;
+ }
+
+ while (buffptr + 1 < buffend)
+ {
+ // IPv4 address
+ if (*buffptr == '\\')
+ {
+ buffptr++;
+
+ if (buffend - buffptr < sizeof(addresses[numservers].ip) + sizeof(addresses[numservers].port) + 1) break;
+
+ for (i = 0; i < sizeof(addresses[numservers].ip); i++) addresses[numservers].ip[i] = *buffptr++;
+
+ addresses[numservers].type = NA_IP;
+ }
+ // IPv6 address, if it's an extended response
+ else if (extended && *buffptr == '/')
+ {
+ buffptr++;
+
+ if (buffend - buffptr < sizeof(addresses[numservers].ip6) + sizeof(addresses[numservers].port) + 1) break;
+
+ for (i = 0; i < sizeof(addresses[numservers].ip6); i++) addresses[numservers].ip6[i] = *buffptr++;
+
+ addresses[numservers].type = NA_IP6;
+ addresses[numservers].scope_id = from->scope_id;
+ }
+ else
+ // syntax error!
+ break;
+
+ // parse out port
+ addresses[numservers].port = (*buffptr++) << 8;
+ addresses[numservers].port += *buffptr++;
+ addresses[numservers].port = BigShort(addresses[numservers].port);
+
+ // syntax check
+ if (*buffptr != '\\' && *buffptr != '/') break;
+
+ addresses[numservers].alternateProtocol = from->alternateProtocol;
+
+ numservers++;
+ if (numservers >= MAX_SERVERSPERPACKET) break;
+ }
+
+ count = cls.numglobalservers;
+
+ for (i = 0; i < numservers && count < MAX_GLOBAL_SERVERS; i++)
+ {
+ // build net address
+ serverInfo_t *server = &cls.globalServers[count];
+
+ CL_InitServerInfo(server, &addresses[i]);
+ Q_strncpyz(server->label, label, sizeof(server->label));
+ // advance to next slot
+ count++;
+ }
+
+ // if getting the global list
+ if (count >= MAX_GLOBAL_SERVERS && cls.numGlobalServerAddresses < MAX_GLOBAL_SERVERS)
+ {
+ // if we couldn't store the servers in the main list anymore
+ for (; i < numservers && cls.numGlobalServerAddresses < MAX_GLOBAL_SERVERS; i++)
+ {
+ // just store the addresses in an additional list
+ cls.globalServerAddresses[cls.numGlobalServerAddresses++] = addresses[i];
+ }
+ }
+
+ cls.numglobalservers = count;
+ total = count + cls.numGlobalServerAddresses;
+
+ Com_Printf("%d servers parsed (total %d)\n", numservers, total);
+}
+/*
+==================
+CL_CheckTimeout
+
+==================
+*/
+static void CL_CheckTimeout(void)
+{
+ //
+ // check timeout
+ //
+ if ((!CL_CheckPaused() || !sv_paused->integer) && clc.state >= CA_CONNECTED && clc.state != CA_CINEMATIC &&
+ cls.realtime - clc.lastPacketTime > cl_timeout->value * 1000)
+ {
+ if (++cl.timeoutcount > 5)
+ { // timeoutcount saves debugger
+ Com_Printf("\nServer connection timed out.\n");
+ CL_Disconnect(true);
+ return;
+ }
+ }
+ else
+ {
+ cl.timeoutcount = 0;
+ }
+}
+
+/*
+==================
+CL_CheckPaused
+Check whether client has been paused.
+==================
+*/
+bool CL_CheckPaused(void)
+{ // if cl_paused->modified is set, the cvar has only been changed in
+ // this frame. Keep paused in this frame to ensure the server doesn't
+ // lag behind.
+ if (cl_paused->integer || cl_paused->modified) return true;
+
+ return false;
+}
+
+//============================================================================
+
+/*
+==================
+CL_CheckUserinfo
+
+==================
+*/
+static void CL_CheckUserinfo(void)
+{
+ // don't add reliable commands when not yet connected
+ if (clc.state < CA_CONNECTED) return;
+
+ // don't overflow the reliable command buffer when paused
+ if (CL_CheckPaused()) return;
+
+ // send a reliable userinfo update if needed
+ if (cvar_modifiedFlags & CVAR_USERINFO)
+ {
+ cvar_modifiedFlags &= ~CVAR_USERINFO;
+ CL_AddReliableCommand(va("userinfo \"%s\"", Cvar_InfoString(CVAR_USERINFO)), false);
+ }
+}
+
+/*
+==================
+CL_Frame
+
+==================
+*/
+void CL_Frame(int msec)
+{
+ if (!com_cl_running->integer) return;
+
+ // We may have a download prompt ready
+ if ((com_downloadPrompt->integer & DLP_TYPE_MASK) && !(com_downloadPrompt->integer & DLP_PROMPTED))
+ {
+ Com_Printf("Download prompt returned %d\n", com_downloadPrompt->integer);
+ CL_NextDownload();
+ }
+ else if (com_downloadPrompt->integer & DLP_SHOW)
+ {
+ // If the UI VM does not support the download prompt, we need to catch
+ // the prompt here and replicate regular behavior.
+ // One frame will always run between requesting and showing the prompt.
+
+ if (com_downloadPrompt->integer & DLP_STALE)
+ {
+ Com_Printf("WARNING: UI VM does not support download prompt\n");
+ Cvar_Set("com_downloadPrompt", va("%d", DLP_IGNORE));
+ CL_NextDownload();
+ }
+ else
+ {
+ Cvar_Set("com_downloadPrompt", va("%d", com_downloadPrompt->integer | DLP_STALE));
+ }
+ }
+
+ if (clc.downloadCURLM)
+ {
+ CL_cURL_PerformDownload();
+ // we can't process frames normally when in disconnected download mode
+ // since the ui vm expects clc.state to be CA_CONNECTED
+ if (clc.cURLDisconnected)
+ {
+ cls.realFrametime = msec;
+ cls.frametime = msec;
+ cls.realtime += cls.frametime;
+
+ SCR_UpdateScreen();
+ S_Update();
+ Con_RunConsole();
+
+ cls.framecount++;
+ return;
+ }
+ }
+
+ if (clc.state == CA_DISCONNECTED && !(Key_GetCatcher() & KEYCATCH_UI) && !com_sv_running->integer && cls.ui)
+ {
+ // if disconnected, bring up the menu
+ S_StopAllSounds();
+ VM_Call(cls.ui, UI_SET_ACTIVE_MENU - (cls.uiInterface == 2 ? 2 : 0), UIMENU_MAIN);
+ }
+
+ // if recording an avi, lock to a fixed fps
+ if (CL_VideoRecording() && cl_aviFrameRate->integer && msec)
+ {
+ // save the current screen
+ if (clc.state == CA_ACTIVE || cl_forceavidemo->integer)
+ {
+ float fps = MIN(cl_aviFrameRate->value * com_timescale->value, 1000.0f);
+ float frameDuration = MAX(1000.0f / fps, 1.0f) + clc.aviVideoFrameRemainder;
+
+ CL_TakeVideoFrame();
+
+ msec = (int)frameDuration;
+ clc.aviVideoFrameRemainder = frameDuration - msec;
+ }
+ }
+
+ if (cl_autoRecordDemo->integer)
+ {
+ if (clc.state == CA_ACTIVE && !clc.demorecording && !clc.demoplaying)
+ {
+ // If not recording a demo, and we should be, start one
+ qtime_t now;
+ Com_RealTime(&now);
+
+ const char *nowString = va("%04d%02d%02d%02d%02d%02d", 1900 + now.tm_year, 1 + now.tm_mon, now.tm_mday,
+ now.tm_hour, now.tm_min, now.tm_sec);
+
+ char serverName[MAX_OSPATH];
+ Q_strncpyz(serverName, clc.servername, MAX_OSPATH);
+
+ // Replace the ":" in the address as it is not a valid
+ // file name character
+ char *p = strstr(serverName, ":");
+ if (p) *p = '.';
+
+ char mapName[MAX_QPATH];
+ Q_strncpyz(mapName, COM_SkipPath(cl.mapname), sizeof(cl.mapname));
+ COM_StripExtension(mapName, mapName, sizeof(mapName));
+ Cbuf_ExecuteText(EXEC_NOW, va("record \"%s-%s-%s\"\n", nowString, serverName, mapName));
+ }
+ else if (clc.state != CA_ACTIVE && clc.demorecording)
+ {
+ // Recording, but not CA_ACTIVE, so stop recording
+ CL_StopRecord_f();
+ }
+ }
+
+ // save the msec before checking pause
+ cls.realFrametime = msec;
+
+ // decide the simulation time
+ cls.frametime = msec;
+
+ cls.realtime += cls.frametime;
+
+ if (cl_timegraph->integer)
+ {
+ SCR_DebugGraph(cls.realFrametime * 0.25);
+ }
+
+ // see if we need to update any userinfo
+ CL_CheckUserinfo();
+
+ // if we haven't gotten a packet in a long time, drop the connection
+ CL_CheckTimeout();
+
+ // send intentions now
+ CL_SendCmd();
+
+ // resend a connection request if necessary
+ CL_CheckForResend();
+
+ // decide on the serverTime to render
+ CL_SetCGameTime();
+
+ // update the screen
+ SCR_UpdateScreen();
+
+ // update audio
+ S_Update();
+
+#ifdef USE_VOIP
+ CL_CaptureVoip();
+#endif
+
+#ifdef USE_MUMBLE
+ CL_UpdateMumble();
+#endif
+
+ // advance local effects for next frame
+ SCR_RunCinematic();
+
+ Con_RunConsole();
+
+ cls.framecount++;
+}
+
+//============================================================================
+
+/*
+================
+CL_RefPrintf
+
+DLL glue
+================
+*/
+static __attribute__((format(printf, 2, 3))) void QDECL CL_RefPrintf(int print_level, const char *fmt, ...)
+{
+ va_list argptr;
+ char msg[MAXPRINTMSG];
+
+ va_start(argptr, fmt);
+ Q_vsnprintf(msg, sizeof(msg), fmt, argptr);
+ va_end(argptr);
+
+ switch (print_level)
+ {
+ case PRINT_ALL:
+ Com_Printf("%s", msg);
+ break;
+
+ case PRINT_WARNING:
+ Com_Printf(S_COLOR_YELLOW "%s", msg);
+ break;
+
+ case PRINT_DEVELOPER:
+ Com_DPrintf(S_COLOR_RED "%s", msg);
+ break;
+ }
+}
+
+/*
+============
+CL_ShutdownRef
+============
+*/
+static void CL_ShutdownRef(void)
+{
+ if (re.Shutdown) re.Shutdown(true);
+
+ ::memset(&re, 0, sizeof(re));
+
+#ifdef USE_RENDERER_DLOPEN
+ if (rendererLib)
+ {
+ Sys_UnloadLibrary(rendererLib);
+ rendererLib = NULL;
+ }
+#endif
+}
+
+/*
+=====================
+CL_ShutdownAll
+=====================
+*/
+void CL_ShutdownAll(bool shutdownRef)
+{
+ if (CL_VideoRecording()) CL_CloseAVI();
+
+ if (clc.demorecording) CL_StopRecord_f();
+
+ CL_cURL_Shutdown();
+
+ // clear sounds
+ S_DisableSounds();
+ // shutdown CGame
+ CL_ShutdownCGame();
+ // shutdown UI
+ CL_ShutdownUI();
+
+ // shutdown the renderer
+ if (shutdownRef)
+ CL_ShutdownRef();
+ else if (re.Shutdown)
+ re.Shutdown(false); // don't destroy window or context
+
+ cls.uiStarted = false;
+ cls.cgameStarted = false;
+ cls.rendererStarted = false;
+ cls.soundRegistered = false;
+}
+
+/*
+=================
+CL_ClearMemory
+
+Called by Com_GameRestart
+=================
+*/
+static void CL_ClearMemory(bool shutdownRef)
+{
+ // shutdown all the client stuff
+ CL_ShutdownAll(shutdownRef);
+
+ // if not running a server clear the whole hunk
+ if (!com_sv_running->integer)
+ {
+ // clear the whole hunk
+ Hunk_Clear();
+ // clear collision map data
+ CM_ClearMap();
+ }
+ else
+ {
+ // clear all the client data on the hunk
+ Hunk_ClearToMark();
+ }
+}
+
+/*
+=================
+CL_FlushMemory
+
+Called by CL_MapLoading, CL_Connect_f, CL_PlayDemo_f, and CL_ParseGamestate the only
+ways a client gets into a game
+Also called by Com_Error
+=================
+*/
+void CL_FlushMemory(void)
+{
+ CL_ClearMemory(false);
+ CL_StartHunkUsers(false);
+}
+
+/*
+=================
+CL_Vid_Restart_f
+
+Restart the video subsystem
+
+we also have to reload the UI and CGame because the renderer
+doesn't know what graphics to reload
+=================
+*/
+static void CL_Vid_Restart_f(void)
+{
+ // Settings may have changed so stop recording now
+ if (CL_VideoRecording())
+ {
+ CL_CloseAVI();
+ }
+
+ if (clc.demorecording) CL_StopRecord_f();
+
+ // don't let them loop during the restart
+ S_StopAllSounds();
+
+ if (!FS_ConditionalRestart(clc.checksumFeed, true))
+ {
+ // if not running a server clear the whole hunk
+ if (com_sv_running->integer)
+ {
+ // clear all the client data on the hunk
+ Hunk_ClearToMark();
+ }
+ else
+ {
+ // clear the whole hunk
+ Hunk_Clear();
+ }
+
+ // shutdown the UI
+ CL_ShutdownUI();
+ // shutdown the CGame
+ CL_ShutdownCGame();
+ // shutdown the renderer and clear the renderer interface
+ CL_ShutdownRef();
+ // client is no longer pure untill new checksums are sent
+ CL_ResetPureClientAtServer();
+ // clear pak references
+ FS_ClearPakReferences(FS_UI_REF | FS_CGAME_REF);
+ // reinitialize the filesystem if the game directory or checksum has changed
+
+ cls.rendererStarted = false;
+ cls.uiStarted = false;
+ cls.cgameStarted = false;
+ cls.soundRegistered = false;
+
+ // unpause so the cgame definately gets a snapshot and renders a frame
+ Cvar_Set("cl_paused", "0");
+
+ // initialize the renderer interface
+ CL_InitRef();
+
+ // startup all the client stuff
+ CL_StartHunkUsers(false);
+
+ // start the cgame if connected
+ if (clc.state > CA_CONNECTED && clc.state != CA_CINEMATIC)
+ {
+ cls.cgameStarted = true;
+ CL_InitCGame();
+ // send pure checksums
+ CL_SendPureChecksums();
+ }
+ }
+}
+
+/*
+=================
+CL_Snd_Restart_f
+
+Restart the sound subsystem
+The cgame and game must also be forced to restart because
+handles will be invalid
+=================
+*/
+static void CL_Snd_Restart_f(void)
+{
+ CL_Snd_Shutdown();
+ // sound will be reinitialized by vid_restart
+ CL_Vid_Restart_f();
+}
+
+
+/*
+============
+CL_InitRenderer
+============
+*/
+static void CL_InitRenderer(void)
+{
+ // this sets up the renderer and calls R_Init
+ re.BeginRegistration(&cls.glconfig);
+
+ // load character sets
+ cls.charSetShader = re.RegisterShader("gfx/2d/bigchars");
+ cls.whiteShader = re.RegisterShader("white");
+ cls.consoleShader = re.RegisterShader("console");
+ g_console_field_width = cls.glconfig.vidWidth / SMALLCHAR_WIDTH - 2;
+ g_consoleField.widthInChars = g_console_field_width;
+}
+
+/*
+============================
+CL_StartHunkUsers
+
+After the server has cleared the hunk, these will need to be restarted
+This is the only place that any of these functions are called from
+============================
+*/
+void CL_StartHunkUsers(bool rendererOnly)
+{
+ if (!com_cl_running)
+ {
+ return;
+ }
+
+ if (!com_cl_running->integer)
+ {
+ return;
+ }
+
+ if (!cls.rendererStarted)
+ {
+ cls.rendererStarted = true;
+ CL_InitRenderer();
+ }
+
+ if (rendererOnly)
+ {
+ return;
+ }
+
+ if (!cls.soundStarted)
+ {
+ cls.soundStarted = true;
+ S_Init();
+ }
+
+ if (!cls.soundRegistered)
+ {
+ cls.soundRegistered = true;
+ S_BeginRegistration();
+ }
+
+ if (com_dedicated->integer)
+ {
+ return;
+ }
+
+ if (!cls.uiStarted)
+ {
+ cls.uiStarted = true;
+ CL_InitUI();
+ }
+}
+
+/*
+============
+CL_RefMalloc
+============
+*/
+static void *CL_RefMalloc(int size) { return Z_TagMalloc(size, TAG_RENDERER); }
+
+/*
+============
+CL_ScaledMilliseconds
+============
+*/
+int CL_ScaledMilliseconds(void) { return Sys_Milliseconds() * com_timescale->value; }
+
+//===========================================================================================
+
+static void CL_SetModel_f(void)
+{
+ char name[256];
+
+ const char *arg = Cmd_Argv(1);
+ if (arg[0])
+ {
+ Cvar_Set("model", arg);
+ Cvar_Set("headmodel", arg);
+ }
+ else
+ {
+ Cvar_VariableStringBuffer("model", name, sizeof(name));
+ Com_Printf("model is set to %s\n", name);
+ }
+}
+
+//===========================================================================================
+
+/*
+===============
+CL_Video_f
+
+video
+video [filename]
+===============
+*/
+static void CL_Video_f(void)
+{
+ char filename[MAX_OSPATH];
+
+ if (!clc.demoplaying)
+ {
+ Com_Printf("The video command can only be used when playing back demos\n");
+ return;
+ }
+
+ if (Cmd_Argc() == 2)
+ {
+ // explicit filename
+ Com_sprintf(filename, MAX_OSPATH, "videos/%s.avi", Cmd_Argv(1));
+ }
+ else
+ {
+ int i, last;
+
+ // scan for a free filename
+ for (i = 0; i <= 9999; i++)
+ {
+ int a, b, c, d;
+
+ last = i;
+
+ a = last / 1000;
+ last -= a * 1000;
+ b = last / 100;
+ last -= b * 100;
+ c = last / 10;
+ last -= c * 10;
+ d = last;
+
+ Com_sprintf(filename, MAX_OSPATH, "videos/video%d%d%d%d.avi", a, b, c, d);
+
+ if (!FS_FileExists(filename)) break; // file doesn't exist
+ }
+
+ if (i > 9999)
+ {
+ Com_Printf(S_COLOR_RED "ERROR: no free file names to create video\n");
+ return;
+ }
+ }
+
+ CL_OpenAVIForWriting(filename);
+}
+
+/*
+===============
+CL_StopVideo_f
+===============
+*/
+static void CL_StopVideo_f(void) { CL_CloseAVI(); }
+/*
+===============
+CL_GenerateQKey
+
+test to see if a valid QKEY_FILE exists. If one does not, try to generate
+it by filling it with 2048 bytes of random data.
+===============
+*/
+static void CL_GenerateQKey(void)
+{
+ int len = 0;
+ unsigned char buff[QKEY_SIZE];
+ fileHandle_t f;
+
+ len = FS_SV_FOpenFileRead(QKEY_FILE, &f);
+ FS_FCloseFile(f);
+ if (len == QKEY_SIZE)
+ {
+ Com_Printf("QKEY found.\n");
+ return;
+ }
+ else
+ {
+ if (len > 0)
+ {
+ Com_Printf("QKEY file size != %d, regenerating\n", QKEY_SIZE);
+ }
+
+ Com_Printf("QKEY building random string\n");
+ Com_RandomBytes(buff, sizeof(buff));
+
+ f = FS_SV_FOpenFileWrite(QKEY_FILE);
+ if (!f)
+ {
+ Com_Printf("QKEY could not open %s for write\n", QKEY_FILE);
+ return;
+ }
+ FS_Write(buff, sizeof(buff), f);
+ FS_FCloseFile(f);
+ Com_Printf("QKEY generated\n");
+ }
+}
+
+static void CL_SetServerInfo(serverInfo_t *server, const char *info, int ping)
+{
+ if (server)
+ {
+ if (info)
+ {
+ server->clients = atoi(Info_ValueForKey(info, "clients"));
+ Q_strncpyz(server->hostName, Info_ValueForKey(info, "hostname"), MAX_HOSTNAME_LENGTH);
+ Q_strncpyz(server->mapName, Info_ValueForKey(info, "mapname"), MAX_NAME_LENGTH);
+ server->maxClients = atoi(Info_ValueForKey(info, "sv_maxclients"));
+ server->gameType = atoi(Info_ValueForKey(info, "gametype"));
+ server->netType = atoi(Info_ValueForKey(info, "nettype"));
+ server->minPing = atoi(Info_ValueForKey(info, "minping"));
+ server->maxPing = atoi(Info_ValueForKey(info, "maxping"));
+ const char *game = Info_ValueForKey(info, "game");
+ Q_strncpyz(server->game, (game[0]) ? game : BASEGAME, MAX_NAME_LENGTH);
+ }
+ server->ping = ping;
+ }
+}
+
+static void CL_SetServerInfoByAddress(netadr_t from, const char *info, int ping)
+{
+ int i;
+
+ for (i = 0; i < MAX_OTHER_SERVERS; i++)
+ {
+ if (NET_CompareAdr(from, cls.localServers[i].adr))
+ {
+ CL_SetServerInfo(&cls.localServers[i], info, ping);
+ }
+ }
+
+ for (i = 0; i < MAX_GLOBAL_SERVERS; i++)
+ {
+ if (NET_CompareAdr(from, cls.globalServers[i].adr))
+ {
+ CL_SetServerInfo(&cls.globalServers[i], info, ping);
+ }
+ }
+
+ for (i = 0; i < MAX_OTHER_SERVERS; i++)
+ {
+ if (NET_CompareAdr(from, cls.favoriteServers[i].adr))
+ {
+ CL_SetServerInfo(&cls.favoriteServers[i], info, ping);
+ }
+ }
+}
+
+/*
+===================
+CL_ServerInfoPacket
+===================
+*/
+static void CL_ServerInfoPacket(netadr_t from, msg_t *msg)
+{
+ int i, type;
+ char info[MAX_INFO_STRING];
+ char *infoString;
+ int prot;
+ char *gamename;
+ bool gameMismatch;
+
+ infoString = MSG_ReadString(msg);
+
+ if (from.alternateProtocol == 0)
+ {
+ // if this isn't the correct gamename, ignore it
+ gamename = Info_ValueForKey(infoString, "gamename");
+
+ gameMismatch = !*gamename || strcmp(gamename, com_gamename->string) != 0;
+
+ if (gameMismatch)
+ {
+ Com_DPrintf("Game mismatch in info packet: %s\n", infoString);
+ return;
+ }
+ }
+
+ // if this isn't the correct protocol version, ignore it
+ prot = atoi(Info_ValueForKey(infoString, "protocol"));
+ if (prot != (from.alternateProtocol == 0 ? PROTOCOL_VERSION : from.alternateProtocol == 1 ? 70 : 69))
+ {
+ Com_DPrintf("Different protocol info packet: %s\n", infoString);
+ return;
+ }
+
+ // iterate servers waiting for ping response
+ for (i = 0; i < MAX_PINGREQUESTS; i++)
+ {
+ if (cl_pinglist[i].adr.port && !cl_pinglist[i].time && NET_CompareAdr(from, cl_pinglist[i].adr))
+ {
+ // calc ping time
+ cl_pinglist[i].time = Sys_Milliseconds() - cl_pinglist[i].start;
+ Com_DPrintf("ping time %dms from %s\n", cl_pinglist[i].time, NET_AdrToString(from));
+
+ // save of info
+ Q_strncpyz(cl_pinglist[i].info, infoString, sizeof(cl_pinglist[i].info));
+
+ // tack on the net type
+ // NOTE: make sure these types are in sync with the netnames strings in the UI
+ switch (from.type)
+ {
+ case NA_BROADCAST:
+ case NA_IP:
+ type = 1;
+ break;
+ case NA_IP6:
+ type = 2;
+ break;
+ default:
+ type = 0;
+ break;
+ }
+ Info_SetValueForKey(cl_pinglist[i].info, "nettype", va("%d", type));
+ CL_SetServerInfoByAddress(from, infoString, cl_pinglist[i].time);
+
+ return;
+ }
+ }
+
+ // if not just sent a local broadcast or pinging local servers
+ if (cls.pingUpdateSource != AS_LOCAL)
+ {
+ return;
+ }
+
+ for (i = 0; i < MAX_OTHER_SERVERS; i++)
+ {
+ // empty slot
+ if (cls.localServers[i].adr.port == 0)
+ {
+ break;
+ }
+
+ // avoid duplicate
+ if (NET_CompareAdr(from, cls.localServers[i].adr))
+ {
+ return;
+ }
+ }
+
+ if (i == MAX_OTHER_SERVERS)
+ {
+ Com_DPrintf("MAX_OTHER_SERVERS hit, dropping infoResponse\n");
+ return;
+ }
+
+ // add this to the list
+ cls.numlocalservers = i + 1;
+ CL_InitServerInfo(&cls.localServers[i], &from);
+
+ Q_strncpyz(info, MSG_ReadString(msg), MAX_INFO_STRING);
+ if (strlen(info))
+ {
+ if (info[strlen(info) - 1] != '\n')
+ {
+ Q_strcat(info, sizeof(info), "\n");
+ }
+ Com_Printf("%s: %s", NET_AdrToStringwPort(from), info);
+ }
+}
+
+/*
+===================
+CL_ServerStatusResponse
+===================
+*/
+static void CL_ServerStatusResponse(netadr_t from, msg_t *msg)
+{
+ char info[MAX_INFO_STRING];
+ int i, l, score, ping;
+ int len;
+ serverStatus_t *serverStatus;
+
+ serverStatus = NULL;
+ for (i = 0; i < MAX_SERVERSTATUSREQUESTS; i++)
+ {
+ if (NET_CompareAdr(from, cl_serverStatusList[i].address))
+ {
+ serverStatus = &cl_serverStatusList[i];
+ break;
+ }
+ }
+ // if we didn't request this server status
+ if (!serverStatus)
+ {
+ return;
+ }
+
+ const char *s = MSG_ReadStringLine(msg);
+
+ len = 0;
+ Com_sprintf(&serverStatus->string[len], sizeof(serverStatus->string) - len, "%s", s);
+
+ if (serverStatus->print)
+ {
+ Com_Printf("Server settings:\n");
+ // print cvars
+ while (*s)
+ {
+ for (i = 0; i < 2 && *s; i++)
+ {
+ if (*s == '\\') s++;
+ l = 0;
+ while (*s)
+ {
+ info[l++] = *s;
+ if (l >= MAX_INFO_STRING - 1) break;
+ s++;
+ if (*s == '\\')
+ {
+ break;
+ }
+ }
+ info[l] = '\0';
+ if (i)
+ {
+ Com_Printf("%s\n", info);
+ }
+ else
+ {
+ Com_Printf("%-24s", info);
+ }
+ }
+ }
+ }
+
+ len = strlen(serverStatus->string);
+ Com_sprintf(&serverStatus->string[len], sizeof(serverStatus->string) - len, "\\");
+
+ if (serverStatus->print)
+ {
+ Com_Printf("\nPlayers:\n");
+ Com_Printf("num: score: ping: name:\n");
+ }
+ for (i = 0, s = MSG_ReadStringLine(msg); *s; s = MSG_ReadStringLine(msg), i++)
+ {
+ len = strlen(serverStatus->string);
+ Com_sprintf(&serverStatus->string[len], sizeof(serverStatus->string) - len, "\\%s", s);
+
+ if (serverStatus->print)
+ {
+ score = ping = 0;
+ sscanf(s, "%d %d", &score, &ping);
+ s = strchr(s, ' ');
+ if (s) s = strchr(s + 1, ' ');
+ if (s)
+ s++;
+ else
+ s = "unknown";
+ Com_Printf("%-2d %-3d %-3d %s\n", i, score, ping, s);
+ }
+ }
+ len = strlen(serverStatus->string);
+ Com_sprintf(&serverStatus->string[len], sizeof(serverStatus->string) - len, "\\");
+
+ serverStatus->time = Com_Milliseconds();
+ serverStatus->address = from;
+ serverStatus->pending = false;
+ if (serverStatus->print)
+ {
+ serverStatus->retrieved = true;
+ }
+}
+
+/*
+=================
+CL_ConnectionlessPacket
+
+Responses to broadcasts, etc
+=================
+*/
+static void CL_ConnectionlessPacket(netadr_t from, msg_t *msg)
+{
+ int challenge = 0;
+
+ MSG_BeginReadingOOB(msg);
+ MSG_ReadLong(msg); // skip the -1
+
+ const char *s = MSG_ReadStringLine(msg);
+
+ Cmd_TokenizeString(s);
+
+ const char *c = Cmd_Argv(0);
+
+ Com_DPrintf("CL packet %s: %s\n", NET_AdrToStringwPort(from), c);
+
+ // challenge from the server we are connecting to
+ if (!Q_stricmp(c, "challengeResponse"))
+ {
+ int ver;
+
+ if (clc.state != CA_CONNECTING)
+ {
+ Com_DPrintf("Unwanted challenge response received. Ignored.\n");
+ return;
+ }
+
+ const char *strver = Cmd_Argv(3);
+ if (*strver)
+ {
+ ver = atoi(strver);
+
+ if (ver != PROTOCOL_VERSION)
+ {
+ Com_Printf(S_COLOR_YELLOW
+ "Warning: Server reports protocol version %d, we have %d. "
+ "Trying anyways.\n",
+ ver, PROTOCOL_VERSION);
+ }
+ }
+ if (clc.serverAddress.alternateProtocol == 0)
+ {
+ c = Cmd_Argv(2);
+ if (*c) challenge = atoi(c);
+
+ if (!*c || challenge != clc.challenge)
+ {
+ Com_Printf("Bad challenge for challengeResponse. Ignored.\n");
+ return;
+ }
+ }
+
+ // start sending challenge response instead of challenge request packets
+ clc.challenge = atoi(Cmd_Argv(1));
+ clc.state = CA_CHALLENGING;
+ clc.connectPacketCount = 0;
+ clc.connectTime = -99999;
+
+ if (cl_rsaAuth->integer)
+ {
+ s = Cmd_Argv(4);
+ if (*s)
+ {
+ Q_strncpyz(clc.challenge2, s, sizeof(clc.challenge2));
+ clc.sendSignature = true;
+ }
+ }
+
+ // take this address as the new server address. This allows
+ // a server proxy to hand off connections to multiple servers
+ clc.serverAddress = from;
+ Com_DPrintf("challengeResponse: %d\n", clc.challenge);
+ return;
+ }
+
+ // server connection
+ if (!Q_stricmp(c, "connectResponse"))
+ {
+ if (clc.state >= CA_CONNECTED)
+ {
+ Com_Printf("Dup connect received. Ignored.\n");
+ return;
+ }
+ if (clc.state != CA_CHALLENGING)
+ {
+ Com_Printf("connectResponse packet while not connecting. Ignored.\n");
+ return;
+ }
+ if (!NET_CompareAdr(from, clc.serverAddress))
+ {
+ Com_Printf("connectResponse from wrong address. Ignored.\n");
+ return;
+ }
+
+ if (clc.serverAddress.alternateProtocol == 0)
+ {
+ c = Cmd_Argv(1);
+
+ if (*c)
+ challenge = atoi(c);
+ else
+ {
+ Com_Printf("Bad connectResponse received. Ignored.\n");
+ return;
+ }
+
+ if (challenge != clc.challenge)
+ {
+ Com_Printf("ConnectResponse with bad challenge received. Ignored.\n");
+ return;
+ }
+ }
+
+ Netchan_Setup(clc.serverAddress.alternateProtocol, NS_CLIENT, &clc.netchan, from,
+ Cvar_VariableValue("net_qport"), clc.challenge);
+
+ clc.state = CA_CONNECTED;
+ clc.lastPacketSentTime = -9999; // send first packet immediately
+ return;
+ }
+
+ // server responding to an info broadcast
+ if (!Q_stricmp(c, "infoResponse"))
+ {
+ CL_ServerInfoPacket(from, msg);
+ return;
+ }
+
+ // server responding to a get playerlist
+ if (!Q_stricmp(c, "statusResponse"))
+ {
+ CL_ServerStatusResponse(from, msg);
+ return;
+ }
+
+ // echo request from server
+ if (!Q_stricmp(c, "echo"))
+ {
+ NET_OutOfBandPrint(NS_CLIENT, from, "%s", Cmd_Argv(1));
+ return;
+ }
+
+ // global MOTD from trem master
+ if (!Q_stricmp(c, "motd"))
+ {
+ CL_MotdPacket(from, s);
+ return;
+ }
+
+ // echo request from server
+ if (!Q_stricmp(c, "print"))
+ {
+ s = MSG_ReadString(msg);
+
+ Q_strncpyz(clc.serverMessage, s, sizeof(clc.serverMessage));
+
+ while (clc.serverMessage[strlen(clc.serverMessage) - 1] == '\n')
+ clc.serverMessage[strlen(clc.serverMessage) - 1] = '\0';
+
+ Com_Printf("%s", s);
+
+ return;
+ }
+
+ // list of servers sent back by a master server (classic)
+ if (!Q_strncmp(c, "getserversResponse", 18))
+ {
+ CL_ServersResponsePacket(&from, msg, false);
+
+ return;
+ }
+
+ // list of servers sent back by a master server (extended)
+ if (!Q_strncmp(c, "getserversExtResponse", 21))
+ {
+ CL_ServersResponsePacket(&from, msg, true);
+ return;
+ }
+
+ Com_DPrintf("Unknown connectionless packet command.\n");
+}
+
+/*
+=================
+CL_PacketEvent
+
+A packet has arrived from the main event loop
+=================
+*/
+void CL_PacketEvent(netadr_t from, msg_t *msg)
+{
+ int headerBytes;
+
+ clc.lastPacketTime = cls.realtime;
+
+ if (msg->cursize >= 4 && *(int *)msg->data == -1)
+ {
+ CL_ConnectionlessPacket(from, msg);
+ return;
+ }
+
+ if (clc.state < CA_CONNECTED)
+ {
+ return; // can't be a valid sequenced packet
+ }
+
+ if (msg->cursize < 4)
+ {
+ Com_Printf("%s: Runt packet\n", NET_AdrToStringwPort(from));
+ return;
+ }
+
+ //
+ // packet from server
+ //
+ if (!NET_CompareAdr(from, clc.netchan.remoteAddress))
+ {
+ Com_DPrintf("%s:sequenced packet without connection\n", NET_AdrToStringwPort(from));
+ // FIXME: send a client disconnect?
+ return;
+ }
+
+ if (!CL_Netchan_Process(&clc.netchan, msg))
+ {
+ return; // out of order, duplicated, etc
+ }
+
+ // the header is different lengths for reliable and unreliable messages
+ headerBytes = msg->readcount;
+
+ // track the last message received so it can be returned in
+ // client messages, allowing the server to detect a dropped
+ // gamestate
+ clc.serverMessageSequence = LittleLong(*(int *)msg->data);
+
+ clc.lastPacketTime = cls.realtime;
+ CL_ParseServerMessage(msg);
+
+ //
+ // we don't know if it is ok to save a demo message until
+ // after we have parsed the frame
+ //
+ if (clc.demorecording && !clc.demowaiting)
+ {
+ CL_WriteDemoMessage(msg, headerBytes);
+ }
+}
+
+/*
+===================
+CL_GetServerStatus
+===================
+*/
+static serverStatus_t *CL_GetServerStatus(netadr_t from)
+{
+ int i, oldest, oldestTime;
+
+ for (i = 0; i < MAX_SERVERSTATUSREQUESTS; i++)
+ {
+ if (NET_CompareAdr(from, cl_serverStatusList[i].address))
+ {
+ return &cl_serverStatusList[i];
+ }
+ }
+ for (i = 0; i < MAX_SERVERSTATUSREQUESTS; i++)
+ {
+ if (cl_serverStatusList[i].retrieved)
+ {
+ return &cl_serverStatusList[i];
+ }
+ }
+ oldest = -1;
+ oldestTime = 0;
+ for (i = 0; i < MAX_SERVERSTATUSREQUESTS; i++)
+ {
+ if (oldest == -1 || cl_serverStatusList[i].startTime < oldestTime)
+ {
+ oldest = i;
+ oldestTime = cl_serverStatusList[i].startTime;
+ }
+ }
+ return &cl_serverStatusList[oldest];
+}
+
+/*
+===================
+CL_ServerStatus
+===================
+*/
+bool CL_ServerStatus(char *serverAddress, char *serverStatusString, int maxLen)
+{
+ int i;
+ netadr_t to;
+ serverStatus_t *serverStatus;
+
+ // if no server address then reset all server status requests
+ if (!serverAddress)
+ {
+ for (i = 0; i < MAX_SERVERSTATUSREQUESTS; i++)
+ {
+ cl_serverStatusList[i].address.port = 0;
+ cl_serverStatusList[i].retrieved = true;
+ }
+ return false;
+ }
+ // get the address
+ if (!NET_StringToAdr(serverAddress, &to, NA_UNSPEC))
+ {
+ return false;
+ }
+ serverStatus = CL_GetServerStatus(to);
+ // if no server status string then reset the server status request for this address
+ if (!serverStatusString)
+ {
+ serverStatus->retrieved = true;
+ return false;
+ }
+
+ // if this server status request has the same address
+ if (NET_CompareAdr(to, serverStatus->address))
+ {
+ // if we received a response for this server status request
+ if (!serverStatus->pending)
+ {
+ Q_strncpyz(serverStatusString, serverStatus->string, maxLen);
+ serverStatus->retrieved = true;
+ serverStatus->startTime = 0;
+ return true;
+ }
+ // resend the request regularly
+ else if (serverStatus->startTime < Com_Milliseconds() - cl_serverStatusResendTime->integer)
+ {
+ serverStatus->print = false;
+ serverStatus->pending = true;
+ serverStatus->retrieved = false;
+ serverStatus->time = 0;
+ serverStatus->startTime = Com_Milliseconds();
+ NET_OutOfBandPrint(NS_CLIENT, to, "getstatus");
+ return false;
+ }
+ }
+ // if retrieved
+ else if (serverStatus->retrieved)
+ {
+ serverStatus->address = to;
+ serverStatus->print = false;
+ serverStatus->pending = true;
+ serverStatus->retrieved = false;
+ serverStatus->startTime = Com_Milliseconds();
+ serverStatus->time = 0;
+ NET_OutOfBandPrint(NS_CLIENT, to, "getstatus");
+ return false;
+ }
+ return false;
+}
+
+/*
+==================
+CL_LocalServers_f
+==================
+*/
+static void CL_LocalServers_f(void)
+{
+ const char *message;
+ int i, j;
+ netadr_t to;
+
+ Com_Printf("Scanning for servers on the local network...\n");
+
+ // reset the list, waiting for response
+ cls.numlocalservers = 0;
+ cls.pingUpdateSource = AS_LOCAL;
+
+ for (i = 0; i < MAX_OTHER_SERVERS; i++)
+ {
+ bool b = cls.localServers[i].visible;
+ ::memset(&cls.localServers[i], 0, sizeof(cls.localServers[i]));
+ cls.localServers[i].visible = b;
+ }
+ ::memset(&to, 0, sizeof(to));
+
+ // The 'xxx' in the message is a challenge that will be echoed back
+ // by the server. We don't care about that here, but master servers
+ // can use that to prevent spoofed server responses from invalid ip
+ message = "\377\377\377\377getinfo xxx";
+
+ // send each message twice in case one is dropped
+ for (i = 0; i < 2; i++)
+ {
+ // send a broadcast packet on each server port
+ // we support multiple server ports so a single machine
+ // can nicely run multiple servers
+ for (j = 0; j < NUM_SERVER_PORTS; j++)
+ {
+ to.port = BigShort((short)(PORT_SERVER + j));
+
+ to.type = NA_BROADCAST;
+ NET_SendPacket(NS_CLIENT, strlen(message), message, to);
+ to.type = NA_MULTICAST6;
+ NET_SendPacket(NS_CLIENT, strlen(message), message, to);
+ }
+ }
+}
+
+/*
+==================
+CL_GlobalServers_f
+==================
+*/
+static void CL_GlobalServers_f(void)
+{
+ int netAlternateProtocols, a;
+ int i;
+ char command[1024];
+ const char *masteraddress;
+
+ int masterNum;
+ int count = Cmd_Argc();
+ if ( count < 2 || (masterNum = atoi(Cmd_Argv(1))) < 0 || masterNum > MAX_MASTER_SERVERS )
+ {
+ Com_Printf("usage: globalservers <master# 0-%d> [keywords]\n", MAX_MASTER_SERVERS);
+ return;
+ }
+
+ netAlternateProtocols = Cvar_VariableIntegerValue("net_alternateProtocols");
+
+ for (a = 0; a < 3; ++a)
+ {
+ // indent
+ if (a == 0 && (netAlternateProtocols & NET_DISABLEPRIMPROTO)) continue;
+ if (a == 1 && !(netAlternateProtocols & NET_ENABLEALT1PROTO)) continue;
+ if (a == 2 && !(netAlternateProtocols & NET_ENABLEALT2PROTO)) continue;
+
+ // request from all master servers
+ if ( masterNum == 0 )
+ {
+ int numAddress = 0;
+
+ for ( int i = 1; i <= MAX_MASTER_SERVERS; i++ )
+ {
+ sprintf(command, "sv_master%d", i);
+ masteraddress = Cvar_VariableString(command);
+
+ if(!*masteraddress)
+ continue;
+
+ numAddress++;
+
+ Com_sprintf(command, sizeof(command), "globalservers %d %s %s\n", i, Cmd_Argv(2), Cmd_ArgsFrom(3));
+ Cbuf_AddText(command);
+ }
+
+ if ( !numAddress )
+ Com_Printf("CL_GlobalServers_f: Error: No master server addresses.\n");
+
+ return;
+ }
+
+ sprintf(command, "sv_%smaster%d", (a == 0 ? "" : a == 1 ? "alt1" : "alt2"), masterNum);
+ masteraddress = Cvar_VariableString(command);
+
+ if (!*masteraddress)
+ {
+ Com_Printf("CL_GlobalServers_f: Error: No%s master server address given.\n",
+ (a == 0 ? "" : a == 1 ? " alternate-1" : " alternate-2"));
+ continue;
+ }
+
+ // reset the list, waiting for response
+ // -1 is used to distinguish a "no response"
+ netadr_t to;
+ int i = NET_StringToAdr(masteraddress, &to, NA_UNSPEC);
+
+ if ( i == 0 )
+ {
+ Com_Printf("CL_GlobalServers_f: Error: could not resolve address of%s master %s\n",
+ (a == 0 ? "" : a == 1 ? " alternate-1" : " alternate-2"), masteraddress);
+ continue;
+ }
+ else if ( i == 2 )
+ {
+ to.port = BigShort(a == 0 ? PORT_MASTER : a == 1 ? ALT1PORT_MASTER : ALT2PORT_MASTER);
+ }
+ to.alternateProtocol = a;
+
+ Com_Printf("Requesting servers from%s master %s...\n",
+ a == 0 ? "" : a == 1 ? " alternate-1" : " alternate-2",
+ masteraddress);
+
+ cls.numglobalservers = -1;
+ cls.pingUpdateSource = AS_GLOBAL;
+
+ Com_sprintf(command, sizeof(command), "getserversExt %s %i%s",
+ com_gamename->string,
+ a == 0 ? PROTOCOL_VERSION : a == 1 ? 70 : 69,
+ Cvar_VariableIntegerValue("net_enabled") & NET_ENABLEV4 ? "" : " ipv6");
+
+ for (i = 3; i < count; i++)
+ {
+ Q_strcat(command, sizeof(command), " ");
+ Q_strcat(command, sizeof(command), Cmd_Argv(i));
+ }
+
+ NET_OutOfBandPrint(NS_SERVER, to, "%s", command);
+ // outdent
+ }
+ CL_RequestMotd();
+}
+
+/*
+==================
+CL_GetPing
+==================
+*/
+void CL_GetPing(int n, char *buf, int buflen, int *pingtime)
+{
+ const char *str;
+ int time;
+ int maxPing;
+
+ if (n < 0 || n >= MAX_PINGREQUESTS || !cl_pinglist[n].adr.port)
+ {
+ // empty or invalid slot
+ buf[0] = '\0';
+ *pingtime = 0;
+ return;
+ }
+
+ str = NET_AdrToStringwPort(cl_pinglist[n].adr);
+ Q_strncpyz(buf, str, buflen);
+
+ time = cl_pinglist[n].time;
+ if (!time)
+ {
+ // check for timeout
+ time = Sys_Milliseconds() - cl_pinglist[n].start;
+ maxPing = Cvar_VariableIntegerValue("cl_maxPing");
+ if (maxPing < 100)
+ {
+ maxPing = 100;
+ }
+ if (time < maxPing)
+ {
+ // not timed out yet
+ time = 0;
+ }
+ }
+
+ CL_SetServerInfoByAddress(cl_pinglist[n].adr, cl_pinglist[n].info, cl_pinglist[n].time);
+
+ *pingtime = time;
+}
+
+/*
+==================
+CL_GetPingInfo
+==================
+*/
+void CL_GetPingInfo(int n, char *buf, int buflen)
+{
+ if (n < 0 || n >= MAX_PINGREQUESTS || !cl_pinglist[n].adr.port)
+ {
+ // empty or invalid slot
+ if (buflen) buf[0] = '\0';
+ return;
+ }
+
+ Q_strncpyz(buf, cl_pinglist[n].info, buflen);
+}
+
+/*
+==================
+CL_ClearPing
+==================
+*/
+void CL_ClearPing(int n)
+{
+ if (n < 0 || n >= MAX_PINGREQUESTS) return;
+
+ cl_pinglist[n].adr.port = 0;
+}
+
+/*
+==================
+CL_GetPingQueueCount
+==================
+*/
+int CL_GetPingQueueCount(void)
+{
+ int i;
+ int count;
+ ping_t *pingptr;
+
+ count = 0;
+ pingptr = cl_pinglist;
+
+ for (i = 0; i < MAX_PINGREQUESTS; i++, pingptr++)
+ {
+ if (pingptr->adr.port)
+ {
+ count++;
+ }
+ }
+
+ return (count);
+}
+
+/*
+==================
+CL_GetFreePing
+==================
+*/
+static ping_t *CL_GetFreePing(void)
+{
+ ping_t *pingptr;
+ ping_t *best;
+ int oldest;
+ int i;
+ int time;
+
+ pingptr = cl_pinglist;
+ for (i = 0; i < MAX_PINGREQUESTS; i++, pingptr++)
+ {
+ // find free ping slot
+ if (pingptr->adr.port)
+ {
+ if (!pingptr->time)
+ {
+ if (Sys_Milliseconds() - pingptr->start < 500)
+ {
+ // still waiting for response
+ continue;
+ }
+ }
+ else if (pingptr->time < 500)
+ {
+ // results have not been queried
+ continue;
+ }
+ }
+
+ // clear it
+ pingptr->adr.port = 0;
+ return (pingptr);
+ }
+
+ // use oldest entry
+ pingptr = cl_pinglist;
+ best = cl_pinglist;
+ oldest = INT_MIN;
+ for (i = 0; i < MAX_PINGREQUESTS; i++, pingptr++)
+ {
+ // scan for oldest
+ time = Sys_Milliseconds() - pingptr->start;
+ if (time > oldest)
+ {
+ oldest = time;
+ best = pingptr;
+ }
+ }
+
+ return (best);
+}
+
+/*
+==================
+CL_Ping_f
+==================
+*/
+static void CL_Ping_f(void)
+{
+ netadr_t to;
+ ping_t *pingptr;
+ const char *server;
+ int argc;
+ netadrtype_t family = NA_UNSPEC;
+
+ argc = Cmd_Argc();
+
+ if (argc != 2 && argc != 3)
+ {
+ Com_Printf("usage: ping [-4|-6] server\n");
+ return;
+ }
+
+ if (argc == 2)
+ server = Cmd_Argv(1);
+ else
+ {
+ if (!strcmp(Cmd_Argv(1), "-4"))
+ family = NA_IP;
+ else if (!strcmp(Cmd_Argv(1), "-6"))
+ family = NA_IP6;
+ else
+ Com_Printf("warning: only -4 or -6 as address type understood.\n");
+
+ server = Cmd_Argv(2);
+ }
+
+ ::memset(&to, 0, sizeof(netadr_t));
+
+ if (!NET_StringToAdr(server, &to, family))
+ {
+ return;
+ }
+
+ pingptr = CL_GetFreePing();
+
+ memcpy(&pingptr->adr, &to, sizeof(netadr_t));
+ pingptr->start = Sys_Milliseconds();
+ pingptr->time = 0;
+
+ CL_SetServerInfoByAddress(pingptr->adr, NULL, 0);
+
+ NET_OutOfBandPrint(NS_CLIENT, to, "getinfo xxx");
+}
+
+/*
+==================
+CL_UpdateVisiblePings_f
+==================
+*/
+bool CL_UpdateVisiblePings_f(int source)
+{
+ int slots, i;
+ char buff[MAX_STRING_CHARS];
+ int pingTime;
+ int max;
+ bool status = false;
+
+ if (source < 0 || source > AS_FAVORITES)
+ {
+ return false;
+ }
+
+ cls.pingUpdateSource = source;
+
+ slots = CL_GetPingQueueCount();
+ if (slots < MAX_PINGREQUESTS)
+ {
+ serverInfo_t *server = NULL;
+
+ switch (source)
+ {
+ case AS_LOCAL:
+ server = &cls.localServers[0];
+ max = cls.numlocalservers;
+ break;
+ case AS_GLOBAL:
+ server = &cls.globalServers[0];
+ max = cls.numglobalservers;
+ break;
+ case AS_FAVORITES:
+ server = &cls.favoriteServers[0];
+ max = cls.numfavoriteservers;
+ break;
+ default:
+ return false;
+ }
+ for (i = 0; i < max; i++)
+ {
+ if (server[i].visible)
+ {
+ if (server[i].ping == -1)
+ {
+ int j;
+
+ if (slots >= MAX_PINGREQUESTS)
+ {
+ break;
+ }
+ for (j = 0; j < MAX_PINGREQUESTS; j++)
+ {
+ if (!cl_pinglist[j].adr.port)
+ {
+ continue;
+ }
+ if (NET_CompareAdr(cl_pinglist[j].adr, server[i].adr))
+ {
+ // already on the list
+ break;
+ }
+ }
+ if (j >= MAX_PINGREQUESTS)
+ {
+ status = true;
+ for (j = 0; j < MAX_PINGREQUESTS; j++)
+ {
+ if (!cl_pinglist[j].adr.port)
+ {
+ break;
+ }
+ }
+ memcpy(&cl_pinglist[j].adr, &server[i].adr, sizeof(netadr_t));
+ cl_pinglist[j].start = Sys_Milliseconds();
+ cl_pinglist[j].time = 0;
+ NET_OutOfBandPrint(NS_CLIENT, cl_pinglist[j].adr, "getinfo xxx");
+ slots++;
+ }
+ }
+ // if the server has a ping higher than cl_maxPing or
+ // the ping packet got lost
+ else if (server[i].ping == 0)
+ {
+ // if we are updating global servers
+ if (source == AS_GLOBAL)
+ {
+ //
+ if (cls.numGlobalServerAddresses > 0)
+ {
+ // overwrite this server with one from the additional global servers
+ cls.numGlobalServerAddresses--;
+ CL_InitServerInfo(&server[i], &cls.globalServerAddresses[cls.numGlobalServerAddresses]);
+ // NOTE: the server[i].visible flag stays untouched
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (slots)
+ {
+ status = true;
+ }
+ for (i = 0; i < MAX_PINGREQUESTS; i++)
+ {
+ if (!cl_pinglist[i].adr.port)
+ {
+ continue;
+ }
+ CL_GetPing(i, buff, MAX_STRING_CHARS, &pingTime);
+ if (pingTime != 0)
+ {
+ CL_ClearPing(i);
+ status = true;
+ }
+ }
+
+ return status;
+}
+
+/*
+==================
+CL_ServerStatus_f
+==================
+*/
+static void CL_ServerStatus_f(void)
+{
+ netadr_t to, *toptr = NULL;
+ const char *server;
+ serverStatus_t *serverStatus;
+ int argc;
+ netadrtype_t family = NA_UNSPEC;
+
+ argc = Cmd_Argc();
+
+ if (argc != 2 && argc != 3)
+ {
+ if (clc.state != CA_ACTIVE || clc.demoplaying)
+ {
+ Com_Printf("Not connected to a server.\n");
+ Com_Printf("usage: serverstatus [-4|-6] server\n");
+ return;
+ }
+
+ toptr = &clc.serverAddress;
+ }
+
+ if (!toptr)
+ {
+ ::memset(&to, 0, sizeof(netadr_t));
+
+ if (argc == 2)
+ server = Cmd_Argv(1);
+ else
+ {
+ if (!strcmp(Cmd_Argv(1), "-4"))
+ family = NA_IP;
+ else if (!strcmp(Cmd_Argv(1), "-6"))
+ family = NA_IP6;
+ else
+ Com_Printf("warning: only -4 or -6 as address type understood.\n");
+
+ server = Cmd_Argv(2);
+ }
+
+ toptr = &to;
+ if (!NET_StringToAdr(server, toptr, family)) return;
+ }
+
+ NET_OutOfBandPrint(NS_CLIENT, *toptr, "getstatus");
+
+ serverStatus = CL_GetServerStatus(*toptr);
+ serverStatus->address = *toptr;
+ serverStatus->print = true;
+ serverStatus->pending = true;
+}
+
+/*
+============
+CL_InitRef
+============
+*/
+static void CL_InitRef(void)
+{
+ refimport_t ri;
+ refexport_t *ret;
+#ifdef USE_RENDERER_DLOPEN
+ GetRefAPI_t GetRefAPI;
+ char dllName[MAX_OSPATH];
+#endif
+
+ Com_Printf("----- Initializing Renderer ----\n");
+
+#ifdef USE_RENDERER_DLOPEN
+ cl_renderer = Cvar_Get("cl_renderer", "opengl2", CVAR_ARCHIVE | CVAR_LATCH);
+
+ Com_sprintf(dllName, sizeof(dllName), "renderer_%s" DLL_EXT, cl_renderer->string);
+
+ if (!(rendererLib = Sys_LoadDll(dllName, false)) && strcmp(cl_renderer->string, cl_renderer->resetString))
+ {
+ Com_Printf("failed:\n\"%s\"\n", Sys_LibraryError());
+ Cvar_ForceReset("cl_renderer");
+
+ Com_sprintf(dllName, sizeof(dllName), "renderer_opengl1" DLL_EXT);
+ rendererLib = Sys_LoadDll(dllName, false);
+ }
+
+ if (!rendererLib)
+ {
+ Com_Printf("failed:\n\"%s\"\n", Sys_LibraryError());
+ Com_Error(ERR_FATAL, "Failed to load renderer");
+ }
+
+ GetRefAPI = (GetRefAPI_t)Sys_LoadFunction(rendererLib, "GetRefAPI");
+ if (!GetRefAPI)
+ {
+ Com_Error(ERR_FATAL, "Can't load symbol GetRefAPI: '%s'", Sys_LibraryError());
+ }
+#endif
+
+ ri.Cmd_AddCommand = Cmd_AddCommand;
+ ri.Cmd_RemoveCommand = Cmd_RemoveCommand;
+ ri.Cmd_Argc = Cmd_Argc;
+ ri.Cmd_Argv = Cmd_Argv;
+ ri.Cmd_ExecuteText = Cbuf_ExecuteText;
+ ri.Printf = CL_RefPrintf;
+ ri.Error = Com_Error;
+ ri.Milliseconds = CL_ScaledMilliseconds;
+ ri.Malloc = CL_RefMalloc;
+ ri.Free = Z_Free;
+#ifdef HUNK_DEBUG
+ ri.Hunk_AllocDebug = Hunk_AllocDebug;
+#else
+ ri.Hunk_Alloc = Hunk_Alloc;
+#endif
+ ri.Hunk_AllocateTempMemory = Hunk_AllocateTempMemory;
+ ri.Hunk_FreeTempMemory = Hunk_FreeTempMemory;
+
+ ri.CM_ClusterPVS = CM_ClusterPVS;
+ ri.CM_DrawDebugSurface = CM_DrawDebugSurface;
+
+ ri.FS_ReadFile = FS_ReadFile;
+ ri.FS_FreeFile = FS_FreeFile;
+ ri.FS_WriteFile = FS_WriteFile;
+ ri.FS_FreeFileList = FS_FreeFileList;
+ ri.FS_ListFiles = FS_ListFiles;
+ ri.FS_FileIsInPAK = FS_FileIsInPAK;
+ ri.FS_FileExists = FS_FileExists;
+ ri.Cvar_Get = Cvar_Get;
+ ri.Cvar_Set = Cvar_Set;
+ ri.Cvar_SetValue = Cvar_SetValue;
+ ri.Cvar_CheckRange = Cvar_CheckRange;
+ ri.Cvar_SetDescription = Cvar_SetDescription;
+ ri.Cvar_VariableIntegerValue = Cvar_VariableIntegerValue;
+
+ // cinematic stuff
+
+ ri.CIN_UploadCinematic = CIN_UploadCinematic;
+ ri.CIN_PlayCinematic = CIN_PlayCinematic;
+ ri.CIN_RunCinematic = CIN_RunCinematic;
+
+ ri.CL_WriteAVIVideoFrame = CL_WriteAVIVideoFrame;
+
+ ri.IN_Init = IN_Init;
+ ri.IN_Shutdown = IN_Shutdown;
+ ri.IN_Restart = IN_Restart;
+
+ ri.Sys_GLimpSafeInit = Sys_GLimpSafeInit;
+ ri.Sys_GLimpInit = Sys_GLimpInit;
+ ri.Sys_LowPhysicalMemory = Sys_LowPhysicalMemory;
+
+ ret = GetRefAPI(REF_API_VERSION, &ri);
+
+#if defined __USEA3D && defined __A3D_GEOM
+ hA3Dg_ExportRenderGeom(ret);
+#endif
+
+ Com_Printf("-------------------------------\n");
+
+ if (!ret)
+ {
+ Com_Error(ERR_FATAL, "Couldn't initialize refresh");
+ }
+
+ re = *ret;
+
+ // unpause so the cgame definately gets a snapshot and renders a frame
+ Cvar_Set("cl_paused", "0");
+}
+
+/*
+====================
+CL_ProtocolSpecificCommandsInit
+
+For adding/remove commands that depend on a/some
+specific protocols, whenever the protcol may change
+====================
+*/
+void CL_ProtocolSpecificCommandsInit(void)
+{
+ Con_MessageModesInit();
+}
+
+/*
+====================
+CL_Init
+====================
+*/
+void CL_Init(void)
+{
+ Com_Printf("----- Client Initialization -----\n");
+
+ Con_Init();
+
+ if (!com_fullyInitialized)
+ {
+ CL_ClearState();
+ clc.state = CA_DISCONNECTED; // no longer CA_UNINITIALIZED
+ cl_oldGameSet = false;
+ }
+
+ CL_InitInput();
+
+ //
+ // register our variables
+ //
+ cl_noprint = Cvar_Get("cl_noprint", "0", 0);
+ cl_motd = Cvar_Get("cl_motd", "1", 0);
+
+ cl_timeout = Cvar_Get("cl_timeout", "200", 0);
+
+ cl_timeNudge = Cvar_Get("cl_timeNudge", "0", CVAR_TEMP);
+ cl_shownet = Cvar_Get("cl_shownet", "0", CVAR_TEMP);
+ cl_showSend = Cvar_Get("cl_showSend", "0", CVAR_TEMP);
+ cl_showTimeDelta = Cvar_Get("cl_showTimeDelta", "0", CVAR_TEMP);
+ cl_freezeDemo = Cvar_Get("cl_freezeDemo", "0", CVAR_TEMP);
+ rcon_client_password = Cvar_Get("rconPassword", "", CVAR_TEMP);
+ cl_activeAction = Cvar_Get("activeAction", "", CVAR_TEMP);
+
+ cl_timedemo = Cvar_Get("timedemo", "0", 0);
+ cl_timedemoLog = Cvar_Get("cl_timedemoLog", "", CVAR_ARCHIVE);
+ cl_autoRecordDemo = Cvar_Get("cl_autoRecordDemo", "0", CVAR_ARCHIVE);
+ cl_aviFrameRate = Cvar_Get("cl_aviFrameRate", "25", CVAR_ARCHIVE);
+ cl_aviMotionJpeg = Cvar_Get("cl_aviMotionJpeg", "1", CVAR_ARCHIVE);
+ cl_forceavidemo = Cvar_Get("cl_forceavidemo", "0", 0);
+
+ rconAddress = Cvar_Get("rconAddress", "", 0);
+
+ cl_yawspeed = Cvar_Get("cl_yawspeed", "140", CVAR_ARCHIVE);
+ cl_pitchspeed = Cvar_Get("cl_pitchspeed", "140", CVAR_ARCHIVE);
+ cl_anglespeedkey = Cvar_Get("cl_anglespeedkey", "1.5", 0);
+
+ cl_maxpackets = Cvar_Get("cl_maxpackets", "30", CVAR_ARCHIVE);
+ cl_packetdup = Cvar_Get("cl_packetdup", "1", CVAR_ARCHIVE);
+
+ cl_run = Cvar_Get("cl_run", "1", CVAR_ARCHIVE);
+ cl_sensitivity = Cvar_Get("sensitivity", "5", CVAR_ARCHIVE);
+ cl_mouseAccel = Cvar_Get("cl_mouseAccel", "0", CVAR_ARCHIVE);
+ cl_freelook = Cvar_Get("cl_freelook", "1", CVAR_ARCHIVE);
+
+ // 0: legacy mouse acceleration
+ // 1: new implementation
+ cl_mouseAccelStyle = Cvar_Get("cl_mouseAccelStyle", "0", CVAR_ARCHIVE);
+ // offset for the power function (for style 1, ignored otherwise)
+ // this should be set to the max rate value
+ cl_mouseAccelOffset = Cvar_Get("cl_mouseAccelOffset", "5", CVAR_ARCHIVE);
+ Cvar_CheckRange(cl_mouseAccelOffset, 0.001f, 50000.0f, false);
+
+ cl_showMouseRate = Cvar_Get("cl_showmouserate", "0", 0);
+
+ cl_allowDownload = Cvar_Get("cl_allowDownload", "1", CVAR_ARCHIVE);
+
+ if (cl_allowDownload->integer != -1) cl_allowDownload->integer = DLF_ENABLE;
+
+ com_downloadPrompt = Cvar_Get("com_downloadPrompt", "0", CVAR_ROM);
+ Cvar_Get("com_downloadPromptText", "", CVAR_TEMP);
+
+ cl_conXOffset = Cvar_Get("cl_conXOffset", "0", 0);
+#ifdef __APPLE__
+ // In game video is REALLY slow in Mac OS X right now due to driver slowness
+ cl_inGameVideo = Cvar_Get("r_inGameVideo", "0", CVAR_ARCHIVE);
+#else
+ cl_inGameVideo = Cvar_Get("r_inGameVideo", "1", CVAR_ARCHIVE);
+#endif
+
+ cl_serverStatusResendTime = Cvar_Get("cl_serverStatusResendTime", "750", 0);
+
+ m_pitch = Cvar_Get("m_pitch", "0.022", CVAR_ARCHIVE);
+ m_yaw = Cvar_Get("m_yaw", "0.022", CVAR_ARCHIVE);
+ m_forward = Cvar_Get("m_forward", "0.25", CVAR_ARCHIVE);
+ m_side = Cvar_Get("m_side", "0.25", CVAR_ARCHIVE);
+#ifdef __APPLE__
+ // Input is jittery on OS X w/o this
+ m_filter = Cvar_Get("m_filter", "1", CVAR_ARCHIVE);
+#else
+ m_filter = Cvar_Get("m_filter", "0", CVAR_ARCHIVE);
+#endif
+
+ j_pitch = Cvar_Get("j_pitch", "0.022", CVAR_ARCHIVE);
+ j_yaw = Cvar_Get("j_yaw", "-0.022", CVAR_ARCHIVE);
+ j_forward = Cvar_Get("j_forward", "-0.25", CVAR_ARCHIVE);
+ j_side = Cvar_Get("j_side", "0.25", CVAR_ARCHIVE);
+ j_up = Cvar_Get("j_up", "0", CVAR_ARCHIVE);
+
+ j_pitch_axis = Cvar_Get("j_pitch_axis", "3", CVAR_ARCHIVE);
+ j_yaw_axis = Cvar_Get("j_yaw_axis", "2", CVAR_ARCHIVE);
+ j_forward_axis = Cvar_Get("j_forward_axis", "1", CVAR_ARCHIVE);
+ j_side_axis = Cvar_Get("j_side_axis", "0", CVAR_ARCHIVE);
+ j_up_axis = Cvar_Get("j_up_axis", "4", CVAR_ARCHIVE);
+
+ Cvar_CheckRange(j_pitch_axis, 0, MAX_JOYSTICK_AXIS - 1, true);
+ Cvar_CheckRange(j_yaw_axis, 0, MAX_JOYSTICK_AXIS - 1, true);
+ Cvar_CheckRange(j_forward_axis, 0, MAX_JOYSTICK_AXIS - 1, true);
+ Cvar_CheckRange(j_side_axis, 0, MAX_JOYSTICK_AXIS - 1, true);
+ Cvar_CheckRange(j_up_axis, 0, MAX_JOYSTICK_AXIS - 1, true);
+
+ cl_motdString = Cvar_Get("cl_motdString", "", CVAR_ROM);
+
+ Cvar_Get("cl_maxPing", "800", CVAR_ARCHIVE);
+
+ cl_lanForcePackets = Cvar_Get("cl_lanForcePackets", "1", CVAR_ARCHIVE);
+
+ cl_guidServerUniq = Cvar_Get("cl_guidServerUniq", "1", CVAR_ARCHIVE);
+
+ cl_rsaAuth = Cvar_Get("cl_rsaAuth", "0", CVAR_INIT | CVAR_PROTECTED);
+
+ // ~ and `, as keys and characters
+ cl_consoleKeys = Cvar_Get("cl_consoleKeys", "~ ` 0x7e 0x60", CVAR_ARCHIVE);
+
+ cl_clantag = Cvar_Get ("cl_clantag", "", CVAR_ARCHIVE);
+
+ // userinfo
+ Cvar_Get("name", "UnnamedPlayer", CVAR_USERINFO | CVAR_ARCHIVE);
+ cl_rate = Cvar_Get("rate", "25000", CVAR_USERINFO | CVAR_ARCHIVE);
+ Cvar_Get("snaps", "40", CVAR_USERINFO | CVAR_ARCHIVE);
+ Cvar_Get("color1", "4", CVAR_USERINFO | CVAR_ARCHIVE);
+ Cvar_Get("color2", "5", CVAR_USERINFO | CVAR_ARCHIVE);
+ Cvar_Get("handicap", "100", CVAR_USERINFO | CVAR_ARCHIVE);
+ Cvar_Get("sex", "male", CVAR_USERINFO | CVAR_ARCHIVE);
+
+ Cvar_Get("password", "", CVAR_USERINFO);
+
+#ifdef USE_MUMBLE
+ cl_useMumble = Cvar_Get("cl_useMumble", "0", CVAR_ARCHIVE | CVAR_LATCH);
+ cl_mumbleScale = Cvar_Get("cl_mumbleScale", "0.0254", CVAR_ARCHIVE);
+#endif
+
+#ifdef USE_VOIP
+ cl_voipSend = Cvar_Get("cl_voipSend", "0", 0);
+ cl_voipSendTarget = Cvar_Get("cl_voipSendTarget", "spatial", 0);
+ cl_voipGainDuringCapture = Cvar_Get("cl_voipGainDuringCapture", "0.2", CVAR_ARCHIVE);
+ cl_voipCaptureMult = Cvar_Get("cl_voipCaptureMult", "2.0", CVAR_ARCHIVE);
+ cl_voipUseVAD = Cvar_Get("cl_voipUseVAD", "0", CVAR_ARCHIVE);
+ cl_voipVADThreshold = Cvar_Get("cl_voipVADThreshold", "0.25", CVAR_ARCHIVE);
+ cl_voipShowMeter = Cvar_Get("cl_voipShowMeter", "1", CVAR_ARCHIVE);
+
+ cl_voip = Cvar_Get("cl_voip", "1", CVAR_ARCHIVE);
+ Cvar_CheckRange(cl_voip, 0, 1, true);
+ cl_voipProtocol = Cvar_Get("cl_voipProtocol", cl_voip->integer ? "opus" : "", CVAR_USERINFO | CVAR_ROM);
+#endif
+
+ // cgame might not be initialized before menu is used
+ Cvar_Get("cg_viewsize", "100", CVAR_ARCHIVE);
+ // Make sure cg_stereoSeparation is zero as that variable is deprecated and should not be used anymore.
+ Cvar_Get("cg_stereoSeparation", "0", CVAR_ROM);
+
+ //
+ // register our commands
+ //
+ Cmd_AddCommand("cmd", CL_ForwardToServer_f);
+ Cmd_AddCommand("configstrings", CL_Configstrings_f);
+ Cmd_AddCommand("clientinfo", CL_Clientinfo_f);
+ Cmd_AddCommand("snd_restart", CL_Snd_Restart_f);
+ Cmd_AddCommand("vid_restart", CL_Vid_Restart_f);
+ Cmd_AddCommand("disconnect", CL_Disconnect_f);
+ Cmd_AddCommand("record", CL_Record_f);
+ Cmd_AddCommand("demo", CL_PlayDemo_f);
+ Cmd_SetCommandCompletionFunc("demo", CL_CompleteDemoName);
+ Cmd_AddCommand("cinematic", CL_PlayCinematic_f);
+ Cmd_AddCommand("stoprecord", CL_StopRecord_f);
+ Cmd_AddCommand("connect", CL_Connect_f);
+ Cmd_AddCommand("reconnect", CL_Reconnect_f);
+ Cmd_AddCommand("localservers", CL_LocalServers_f);
+ Cmd_AddCommand("globalservers", CL_GlobalServers_f);
+ Cmd_AddCommand("rcon", CL_Rcon_f);
+ Cmd_SetCommandCompletionFunc("rcon", CL_CompleteRcon);
+ Cmd_AddCommand("ping", CL_Ping_f);
+ Cmd_AddCommand("serverstatus", CL_ServerStatus_f);
+ Cmd_AddCommand("showip", CL_ShowIP_f);
+ Cmd_AddCommand("fs_openedList", CL_OpenedPK3List_f);
+ Cmd_AddCommand("fs_referencedList", CL_ReferencedPK3List_f);
+ Cmd_AddCommand("model", CL_SetModel_f);
+ Cmd_AddCommand("video", CL_Video_f);
+ Cmd_AddCommand("stopvideo", CL_StopVideo_f);
+ Cmd_AddCommand("downloadUpdate", CL_DownloadUpdate_f);
+ Cmd_AddCommand("installUpdate", CL_InstallUpdate_f);
+ Cmd_AddCommand("checkForUpdate", CL_CheckForUpdate_f);
+ Cmd_AddCommand("browseHomepath", CL_BrowseHomepath_f);
+ Cmd_AddCommand("browseDemos", CL_BrowseDemos_f);
+ Cmd_AddCommand("browseScreenShots", CL_BrowseScreenShots_f);
+
+ CL_InitRef();
+
+ SCR_Init();
+
+ // Cbuf_Execute ();
+
+ Cvar_Set("cl_running", "1");
+
+ if (cl_rsaAuth->integer) CL_LoadRSAKeypair();
+
+ CL_GenerateQKey();
+ Cvar_Get("cl_guid", "", CVAR_USERINFO | CVAR_ROM);
+ if (clc.state == CA_DISCONNECTED) CL_UpdateGUID(NULL, 0);
+
+ Com_Printf("----- Client Initialization Complete -----\n");
+}
+
+/*
+===============
+CL_Shutdown
+
+===============
+*/
+void CL_Shutdown(const char *finalmsg, bool disconnect, bool quit)
+{
+ static bool recursive = false;
+ int realtime;
+
+ // check whether the client is running at all.
+ if (!(com_cl_running && com_cl_running->integer)) return;
+
+ Com_Printf("----- Client Shutdown (%s) -----\n", finalmsg);
+
+ if (recursive)
+ {
+ Com_Printf("WARNING: Recursive shutdown\n");
+ return;
+ }
+ recursive = true;
+
+ noGameRestart = quit;
+
+ if (disconnect) CL_Disconnect(true);
+
+ CL_ClearMemory(true);
+ CL_Snd_Shutdown();
+
+ Cmd_RemoveCommand("cmd");
+ Cmd_RemoveCommand("configstrings");
+ Cmd_RemoveCommand("clientinfo");
+ Cmd_RemoveCommand("snd_restart");
+ Cmd_RemoveCommand("vid_restart");
+ Cmd_RemoveCommand("disconnect");
+ Cmd_RemoveCommand("record");
+ Cmd_RemoveCommand("demo");
+ Cmd_RemoveCommand("cinematic");
+ Cmd_RemoveCommand("stoprecord");
+ Cmd_RemoveCommand("connect");
+ Cmd_RemoveCommand("reconnect");
+ Cmd_RemoveCommand("localservers");
+ Cmd_RemoveCommand("globalservers");
+ Cmd_RemoveCommand("rcon");
+ Cmd_RemoveCommand("ping");
+ Cmd_RemoveCommand("serverstatus");
+ Cmd_RemoveCommand("showip");
+ Cmd_RemoveCommand("fs_openedList");
+ Cmd_RemoveCommand("fs_referencedList");
+ Cmd_RemoveCommand("model");
+ Cmd_RemoveCommand("video");
+ Cmd_RemoveCommand("stopvideo");
+
+ CL_ShutdownInput();
+ Con_Shutdown();
+
+ Cvar_Set("cl_running", "0");
+
+ recursive = false;
+
+ if (cl_rsaAuth->integer) CL_UnloadRSAKeypair();
+
+ realtime = cls.realtime;
+ ::memset(&cls, 0, sizeof(cls));
+ cls.realtime = realtime;
+ Key_SetCatcher(0);
+
+ Com_Printf("-----------------------\n");
+}
diff --git a/src/client/cl_net_chan.cpp b/src/client/cl_net_chan.cpp
new file mode 100644
index 0000000..80ac25d
--- /dev/null
+++ b/src/client/cl_net_chan.cpp
@@ -0,0 +1,190 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
+
+This file is part of Tremulous.
+
+Tremulous 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.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+
+#include "client.h"
+
+#include "qcommon/q_shared.h"
+#include "qcommon/qcommon.h"
+
+/*
+==============
+CL_Netchan_Encode
+
+ // first 12 bytes of the data are always:
+ long serverId;
+ long messageAcknowledge;
+ long reliableAcknowledge;
+
+==============
+*/
+static void CL_Netchan_Encode(msg_t *msg)
+{
+ int serverId, messageAcknowledge, reliableAcknowledge;
+ int i, idx, srdc, sbit;
+ bool soob;
+ byte key, *string;
+
+ if (msg->cursize <= CL_ENCODE_START)
+ {
+ return;
+ }
+
+ srdc = msg->readcount;
+ sbit = msg->bit;
+ soob = msg->oob;
+
+ msg->bit = 0;
+ msg->readcount = 0;
+ msg->oob = false;
+
+ serverId = MSG_ReadLong(msg);
+ messageAcknowledge = MSG_ReadLong(msg);
+ reliableAcknowledge = MSG_ReadLong(msg);
+
+ msg->oob = soob;
+ msg->bit = sbit;
+ msg->readcount = srdc;
+
+ string = (byte *)clc.serverCommands[reliableAcknowledge & (MAX_RELIABLE_COMMANDS - 1)];
+ idx = 0;
+ //
+ key = clc.challenge ^ serverId ^ messageAcknowledge;
+ for (i = CL_ENCODE_START; i < msg->cursize; i++)
+ {
+ // modify the key with the last received now acknowledged server command
+ if (!string[idx]) idx = 0;
+ if (string[idx] > 127 || (string[idx] == '%' && clc.netchan.alternateProtocol == 2))
+ {
+ key ^= '.' << (i & 1);
+ }
+ else
+ {
+ key ^= string[idx] << (i & 1);
+ }
+ idx++;
+ // encode the data with this key
+ *(msg->data + i) = (*(msg->data + i)) ^ key;
+ }
+}
+
+/*
+==============
+CL_Netchan_Decode
+
+ // first four bytes of the data are always:
+ long reliableAcknowledge;
+
+==============
+*/
+static void CL_Netchan_Decode(msg_t *msg)
+{
+ long reliableAcknowledge, i, idx;
+ byte key, *string;
+ int srdc, sbit;
+ bool soob;
+
+ srdc = msg->readcount;
+ sbit = msg->bit;
+ soob = msg->oob;
+
+ msg->oob = false;
+
+ reliableAcknowledge = MSG_ReadLong(msg);
+
+ msg->oob = soob;
+ msg->bit = sbit;
+ msg->readcount = srdc;
+
+ string = (byte *)clc.reliableCommands[reliableAcknowledge & (MAX_RELIABLE_COMMANDS - 1)];
+ idx = 0;
+ // xor the client challenge with the netchan sequence number (need something that changes every message)
+ key = clc.challenge ^ LittleLong(*(unsigned *)msg->data);
+ for (i = msg->readcount + CL_DECODE_START; i < msg->cursize; i++)
+ {
+ // modify the key with the last sent and with this message acknowledged client command
+ if (!string[idx]) idx = 0;
+ if (string[idx] > 127 || (string[idx] == '%' && clc.netchan.alternateProtocol == 2))
+ {
+ key ^= '.' << (i & 1);
+ }
+ else
+ {
+ key ^= string[idx] << (i & 1);
+ }
+ idx++;
+ // decode the data with this key
+ *(msg->data + i) = *(msg->data + i) ^ key;
+ }
+}
+
+/*
+=================
+CL_Netchan_TransmitNextFragment
+=================
+*/
+static bool CL_Netchan_TransmitNextFragment(netchan_t *chan)
+{
+ if (chan->unsentFragments)
+ {
+ Netchan_TransmitNextFragment(chan);
+ return true;
+ }
+
+ return false;
+}
+
+/*
+===============
+CL_Netchan_Transmit
+================
+*/
+void CL_Netchan_Transmit(netchan_t *chan, msg_t *msg)
+{
+ MSG_WriteByte(msg, clc_EOF);
+
+ if (chan->alternateProtocol != 0) CL_Netchan_Encode(msg);
+ Netchan_Transmit(chan, msg->cursize, msg->data);
+
+ // Transmit all fragments without delay
+ while (CL_Netchan_TransmitNextFragment(chan))
+ {
+ Com_DPrintf("WARNING: #462 unsent fragments (not supposed to happen!)\n");
+ }
+}
+
+/*
+=================
+CL_Netchan_Process
+=================
+*/
+bool CL_Netchan_Process(netchan_t *chan, msg_t *msg)
+{
+ int ret;
+
+ ret = Netchan_Process(chan, msg);
+ if (!ret) return false;
+ if (chan->alternateProtocol != 0) CL_Netchan_Decode(msg);
+
+ return true;
+}
diff --git a/src/client/cl_parse.cpp b/src/client/cl_parse.cpp
new file mode 100644
index 0000000..111211a
--- /dev/null
+++ b/src/client/cl_parse.cpp
@@ -0,0 +1,961 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
+
+This file is part of Tremulous.
+
+Tremulous 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.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+// cl_parse.c -- parse a message received from the server
+
+#include "client.h"
+
+const char *svc_strings[256] = {
+ "svc_bad",
+ "svc_nop",
+ "svc_gamestate",
+ "svc_configstring",
+ "svc_baseline",
+ "svc_serverCommand",
+ "svc_download",
+ "svc_snapshot",
+ "svc_EOF",
+ "svc_voipSpeex",
+ "svc_voipOpus",
+};
+
+void SHOWNET( msg_t *msg, const char *s) {
+ if ( cl_shownet->integer >= 2) {
+ Com_Printf ("%3i:%s\n", msg->readcount-1, s);
+ }
+}
+
+
+/*
+=========================================================================
+
+MESSAGE PARSING
+
+=========================================================================
+*/
+
+/*
+==================
+CL_DeltaEntity
+
+Parses deltas from the given base and adds the resulting entity
+to the current frame
+==================
+*/
+static void CL_DeltaEntity(msg_t *msg, clSnapshot_t *frame, int newnum, entityState_t *old, bool unchanged)
+{
+ entityState_t *state;
+
+ // save the parsed entity state into the big circular buffer so it can be
+ // used as the source for a later delta
+ state = &cl.parseEntities[cl.parseEntitiesNum & (MAX_PARSE_ENTITIES-1)];
+
+ if ( unchanged )
+ {
+ *state = *old;
+ }
+ else
+ {
+ MSG_ReadDeltaEntity( clc.netchan.alternateProtocol, msg, old, state, newnum );
+ }
+
+ if ( state->number == (MAX_GENTITIES-1) )
+ {
+ return; // entity was delta removed
+ }
+
+ cl.parseEntitiesNum++;
+ frame->numEntities++;
+}
+
+/*
+==================
+CL_ParsePacketEntities
+
+==================
+*/
+static void CL_ParsePacketEntities( msg_t *msg, clSnapshot_t *oldframe, clSnapshot_t *newframe)
+{
+ int newnum;
+ entityState_t *oldstate;
+ int oldindex, oldnum;
+
+ newframe->parseEntitiesNum = cl.parseEntitiesNum;
+ newframe->numEntities = 0;
+
+ // delta from the entities present in oldframe
+ oldindex = 0;
+ oldstate = NULL;
+ if (!oldframe) {
+ oldnum = 99999;
+ } else {
+ if ( oldindex >= oldframe->numEntities ) {
+ oldnum = 99999;
+ } else {
+ oldstate = &cl.parseEntities[
+ (oldframe->parseEntitiesNum + oldindex) & (MAX_PARSE_ENTITIES-1)];
+ oldnum = oldstate->number;
+ }
+ }
+
+ while ( 1 ) {
+ // read the entity index number
+ newnum = MSG_ReadBits( msg, GENTITYNUM_BITS );
+
+ if ( newnum == (MAX_GENTITIES-1) ) {
+ break;
+ }
+
+ if ( msg->readcount > msg->cursize ) {
+ Com_Error (ERR_DROP,"CL_ParsePacketEntities: end of message");
+ }
+
+ while ( oldnum < newnum ) {
+ // one or more entities from the old packet are unchanged
+ if ( cl_shownet->integer == 3 ) {
+ Com_Printf ("%3i: unchanged: %i\n", msg->readcount, oldnum);
+ }
+ CL_DeltaEntity( msg, newframe, oldnum, oldstate, true );
+
+ oldindex++;
+
+ if ( oldindex >= oldframe->numEntities ) {
+ oldnum = 99999;
+ } else {
+ oldstate = &cl.parseEntities[
+ (oldframe->parseEntitiesNum + oldindex) & (MAX_PARSE_ENTITIES-1)];
+ oldnum = oldstate->number;
+ }
+ }
+ if (oldnum == newnum) {
+ // delta from previous state
+ if ( cl_shownet->integer == 3 ) {
+ Com_Printf ("%3i: delta: %i\n", msg->readcount, newnum);
+ }
+ CL_DeltaEntity( msg, newframe, newnum, oldstate, false );
+
+ oldindex++;
+
+ if ( oldindex >= oldframe->numEntities ) {
+ oldnum = 99999;
+ } else {
+ oldstate = &cl.parseEntities[
+ (oldframe->parseEntitiesNum + oldindex) & (MAX_PARSE_ENTITIES-1)];
+ oldnum = oldstate->number;
+ }
+ continue;
+ }
+
+ if ( oldnum > newnum ) {
+ // delta from baseline
+ if ( cl_shownet->integer == 3 ) {
+ Com_Printf ("%3i: baseline: %i\n", msg->readcount, newnum);
+ }
+ CL_DeltaEntity( msg, newframe, newnum, &cl.entityBaselines[newnum], false );
+ continue;
+ }
+
+ }
+
+ // any remaining entities in the old frame are copied over
+ while ( oldnum != 99999 ) {
+ // one or more entities from the old packet are unchanged
+ if ( cl_shownet->integer == 3 ) {
+ Com_Printf ("%3i: unchanged: %i\n", msg->readcount, oldnum);
+ }
+ CL_DeltaEntity( msg, newframe, oldnum, oldstate, true );
+
+ oldindex++;
+
+ if ( oldindex >= oldframe->numEntities ) {
+ oldnum = 99999;
+ } else {
+ oldstate = &cl.parseEntities[
+ (oldframe->parseEntitiesNum + oldindex) & (MAX_PARSE_ENTITIES-1)];
+ oldnum = oldstate->number;
+ }
+ }
+}
+
+
+/*
+================
+CL_ParseSnapshot
+
+If the snapshot is parsed properly, it will be copied to
+cl.snap and saved in cl.snapshots[]. If the snapshot is invalid
+for any reason, no changes to the state will be made at all.
+================
+*/
+void CL_ParseSnapshot( msg_t *msg ) {
+ int len;
+ clSnapshot_t *old;
+ clSnapshot_t newSnap;
+ int deltaNum;
+ int oldMessageNum;
+ int i, packetNum;
+
+ // get the reliable sequence acknowledge number
+ // NOTE: now sent with all server to client messages
+ //clc.reliableAcknowledge = MSG_ReadLong( msg );
+
+ // read in the new snapshot to a temporary buffer
+ // we will only copy to cl.snap if it is valid
+ ::memset(&newSnap, 0, sizeof(newSnap));
+
+ // we will have read any new server commands in this
+ // message before we got to svc_snapshot
+ newSnap.serverCommandNum = clc.serverCommandSequence;
+
+ newSnap.serverTime = MSG_ReadLong( msg );
+
+ // if we were just unpaused, we can only *now* really let the
+ // change come into effect or the client hangs.
+ cl_paused->modified = false;
+
+ newSnap.messageNum = clc.serverMessageSequence;
+
+ deltaNum = MSG_ReadByte( msg );
+ if ( !deltaNum ) {
+ newSnap.deltaNum = -1;
+ } else {
+ newSnap.deltaNum = newSnap.messageNum - deltaNum;
+ }
+ newSnap.snapFlags = MSG_ReadByte( msg );
+
+ // If the frame is delta compressed from data that we
+ // no longer have available, we must suck up the rest of
+ // the frame, but not use it, then ask for a non-compressed
+ // message
+ if ( newSnap.deltaNum <= 0 ) {
+ newSnap.valid = true; // uncompressed frame
+ old = NULL;
+ clc.demowaiting = false; // we can start recording now
+ } else {
+ old = &cl.snapshots[newSnap.deltaNum & PACKET_MASK];
+ if ( !old->valid ) {
+ // should never happen
+ Com_DPrintf ("Delta from invalid frame (not supposed to happen!).\n");
+ } else if ( old->messageNum != newSnap.deltaNum ) {
+ // The frame that the server did the delta from
+ // is too old, so we can't reconstruct it properly.
+ Com_DPrintf ("Delta frame too old.\n");
+ } else if ( cl.parseEntitiesNum - old->parseEntitiesNum > MAX_PARSE_ENTITIES - MAX_SNAPSHOT_ENTITIES ) {
+ Com_DPrintf ("Delta parseEntitiesNum too old.\n");
+ } else {
+ newSnap.valid = true; // valid delta parse
+ }
+ }
+
+ // read areamask
+ len = MSG_ReadByte( msg );
+
+ if(len > sizeof(newSnap.areamask))
+ {
+ Com_Error (ERR_DROP,"CL_ParseSnapshot: Invalid size %d for areamask", len);
+ return;
+ }
+
+ MSG_ReadData( msg, &newSnap.areamask, len);
+
+ // read playerinfo
+ SHOWNET( msg, "playerstate" );
+ if ( clc.netchan.alternateProtocol == 2 ) {
+ if ( old ) {
+ MSG_ReadDeltaAlternatePlayerstate( msg, &old->alternatePs, &newSnap.alternatePs );
+ } else {
+ MSG_ReadDeltaAlternatePlayerstate( msg, NULL, &newSnap.alternatePs );
+ }
+ } else {
+ if ( old ) {
+ MSG_ReadDeltaPlayerstate( msg, &old->ps, &newSnap.ps );
+ } else {
+ MSG_ReadDeltaPlayerstate( msg, NULL, &newSnap.ps );
+ }
+ }
+
+ // read packet entities
+ SHOWNET( msg, "packet entities" );
+ CL_ParsePacketEntities( msg, old, &newSnap );
+
+ // if not valid, dump the entire thing now that it has
+ // been properly read
+ if ( !newSnap.valid ) {
+ return;
+ }
+
+ // clear the valid flags of any snapshots between the last
+ // received and this one, so if there was a dropped packet
+ // it won't look like something valid to delta from next
+ // time we wrap around in the buffer
+ oldMessageNum = cl.snap.messageNum + 1;
+
+ if ( newSnap.messageNum - oldMessageNum >= PACKET_BACKUP ) {
+ oldMessageNum = newSnap.messageNum - ( PACKET_BACKUP - 1 );
+ }
+ for ( ; oldMessageNum < newSnap.messageNum ; oldMessageNum++ ) {
+ cl.snapshots[oldMessageNum & PACKET_MASK].valid = false;
+ }
+
+ // copy to the current good spot
+ cl.snap = newSnap;
+ cl.snap.ping = 999;
+ // calculate ping time
+ for ( i = 0 ; i < PACKET_BACKUP ; i++ ) {
+ packetNum = ( clc.netchan.outgoingSequence - 1 - i ) & PACKET_MASK;
+ if ( ( clc.netchan.alternateProtocol == 2 ? cl.snap.alternatePs.commandTime : cl.snap.ps.commandTime ) >= cl.outPackets[ packetNum ].p_serverTime ) {
+ cl.snap.ping = cls.realtime - cl.outPackets[ packetNum ].p_realtime;
+ break;
+ }
+ }
+ // save the frame off in the backup array for later delta comparisons
+ cl.snapshots[cl.snap.messageNum & PACKET_MASK] = cl.snap;
+
+ if (cl_shownet->integer == 3) {
+ Com_Printf( " snapshot:%i delta:%i ping:%i\n", cl.snap.messageNum,
+ cl.snap.deltaNum, cl.snap.ping );
+ }
+
+ cl.newSnapshots = true;
+}
+
+
+//=====================================================================
+
+bool cl_connectedToPureServer;
+bool cl_connectedToCheatServer;
+
+/*
+==================
+CL_SystemInfoChanged
+
+The systeminfo configstring has been changed, so parse
+new information out of it. This will happen at every
+gamestate, and possibly during gameplay.
+==================
+*/
+void CL_SystemInfoChanged( void ) {
+ char *systemInfo;
+ const char *s, *t;
+ char key[BIG_INFO_KEY];
+ char value[BIG_INFO_VALUE];
+ bool gameSet;
+
+ systemInfo = cl.gameState.stringData + cl.gameState.stringOffsets[ CS_SYSTEMINFO ];
+ // NOTE TTimo:
+ // when the serverId changes, any further messages we send to the server will use this new serverId
+ // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=475
+ // in some cases, outdated cp commands might get sent with this news serverId
+ cl.serverId = atoi( Info_ValueForKey( systemInfo, "sv_serverid" ) );
+
+#ifdef USE_VOIP
+ s = Info_ValueForKey( systemInfo, "sv_voipProtocol" );
+ clc.voipEnabled = !Q_stricmp(s, "opus");
+#endif
+
+ // only set the paks, fs_game, and sv_pure when playing a demo
+ if ( !clc.demoplaying ) {
+ s = Info_ValueForKey( systemInfo, "sv_cheats" );
+ cl_connectedToCheatServer = (bool)atoi( s );
+ if ( !cl_connectedToCheatServer ) {
+ Cvar_SetCheatState();
+ }
+ }
+
+ // check pure server string
+ s = Info_ValueForKey( systemInfo, "sv_paks" );
+ t = Info_ValueForKey( systemInfo, "sv_pakNames" );
+ FS_PureServerSetLoadedPaks( s, t );
+
+ s = Info_ValueForKey( systemInfo, "sv_referencedPaks" );
+ t = Info_ValueForKey( systemInfo, "sv_referencedPakNames" );
+ FS_PureServerSetReferencedPaks( s, t );
+
+ gameSet = false;
+ // scan through all the variables in the systeminfo and locally set cvars to match
+ s = systemInfo;
+ while ( s ) {
+ int cvar_flags;
+
+ Info_NextPair( &s, key, value );
+ if ( !key[0] ) {
+ break;
+ }
+
+ // ehw!
+ if (!Q_stricmp(key, "fs_game"))
+ {
+ if(FS_CheckDirTraversal(value))
+ {
+ Com_Printf(S_COLOR_YELLOW "WARNING: Server sent invalid fs_game value %s\n", value);
+ continue;
+ }
+
+ gameSet = true;
+ } else if ( clc.demoplaying && Q_stricmp(key, "sv_pure" ) ) {
+ continue;
+ }
+
+ if((cvar_flags = Cvar_Flags(key)) == CVAR_NONEXISTENT)
+ Cvar_Get(key, value, CVAR_SERVER_CREATED | CVAR_ROM);
+ else
+ {
+ // If this cvar may not be modified by a server discard the value.
+ if(!(cvar_flags & (CVAR_SYSTEMINFO | CVAR_SERVER_CREATED | CVAR_USER_CREATED)))
+ {
+ if(Q_stricmp(key, "g_synchronousClients") && Q_stricmp(key, "pmove_fixed") &&
+ Q_stricmp(key, "pmove_msec"))
+ {
+ Com_Printf(S_COLOR_YELLOW "WARNING: server is not allowed to set %s=%s\n", key, value);
+ continue;
+ }
+ }
+
+ Cvar_SetSafe(key, value);
+ }
+ }
+ // if game folder should not be set and it is set at the client side
+ if ( !gameSet && *Cvar_VariableString("fs_game") ) {
+ Cvar_Set( "fs_game", "" );
+ }
+ cl_connectedToPureServer = (bool)Cvar_VariableValue( "sv_pure" );
+}
+
+/*
+==================
+CL_ParseServerInfo
+==================
+*/
+static void CL_ParseServerInfo(void)
+{
+ const char *serverInfo;
+
+ serverInfo = cl.gameState.stringData
+ + cl.gameState.stringOffsets[ CS_SERVERINFO ];
+
+ clc.sv_allowDownload = atoi(Info_ValueForKey(serverInfo,
+ "sv_allowDownload"));
+ Q_strncpyz(clc.sv_dlURL,
+ Info_ValueForKey(serverInfo, "sv_dlURL"),
+ sizeof(clc.sv_dlURL));
+}
+
+/*
+==================
+CL_ParseGamestate
+==================
+*/
+void CL_ParseGamestate( msg_t *msg ) {
+ int i;
+ entityState_t *es;
+ int newnum;
+ entityState_t nullstate;
+ int cmd;
+ char *s;
+ char oldGame[MAX_QPATH];
+
+ clc.connectPacketCount = 0;
+
+ // wipe local client state
+ CL_ClearState();
+
+ // a gamestate always marks a server command sequence
+ clc.serverCommandSequence = MSG_ReadLong( msg );
+
+ // parse all the configstrings and baselines
+ cl.gameState.dataCount = 1; // leave a 0 at the beginning for uninitialized configstrings
+ while ( 1 ) {
+ cmd = MSG_ReadByte( msg );
+
+ if ( cmd == svc_EOF ) {
+ break;
+ }
+
+ if ( cmd == svc_configstring ) {
+ int len;
+
+ i = MSG_ReadShort( msg );
+ if ( i < 0 || i >= MAX_CONFIGSTRINGS ) {
+ Com_Error( ERR_DROP, "configstring > MAX_CONFIGSTRINGS" );
+ }
+ s = MSG_ReadBigString( msg );
+ len = strlen( s );
+
+ if ( len + 1 + cl.gameState.dataCount > MAX_GAMESTATE_CHARS ) {
+ Com_Error( ERR_DROP, "MAX_GAMESTATE_CHARS exceeded" );
+ }
+
+ // append it to the gameState string buffer
+ cl.gameState.stringOffsets[ i ] = cl.gameState.dataCount;
+ ::memcpy( cl.gameState.stringData + cl.gameState.dataCount, s, len + 1 );
+ cl.gameState.dataCount += len + 1;
+ } else if ( cmd == svc_baseline ) {
+ newnum = MSG_ReadBits( msg, GENTITYNUM_BITS );
+ if ( newnum < 0 || newnum >= MAX_GENTITIES ) {
+ Com_Error( ERR_DROP, "Baseline number out of range: %i", newnum );
+ }
+ ::memset(&nullstate, 0, sizeof(nullstate));
+ es = &cl.entityBaselines[ newnum ];
+ MSG_ReadDeltaEntity( clc.netchan.alternateProtocol, msg, &nullstate, es, newnum );
+ } else {
+ Com_Error( ERR_DROP, "CL_ParseGamestate: bad command byte" );
+ }
+ }
+
+ clc.clientNum = MSG_ReadLong(msg);
+ // read the checksum feed
+ clc.checksumFeed = MSG_ReadLong( msg );
+
+ // save old gamedir
+ Cvar_VariableStringBuffer("fs_game", oldGame, sizeof(oldGame));
+
+ // parse useful values out of CS_SERVERINFO
+ CL_ParseServerInfo();
+
+ // parse serverId and other cvars
+ CL_SystemInfoChanged();
+
+ // stop recording now so the demo won't have an unnecessary level load at the end.
+ if(cl_autoRecordDemo->integer && clc.demorecording)
+ CL_StopRecord_f();
+
+ // reinitialize the filesystem if the game directory has changed
+ if(!cl_oldGameSet && (Cvar_Flags("fs_game") & CVAR_MODIFIED))
+ {
+ cl_oldGameSet = true;
+ Q_strncpyz(cl_oldGame, oldGame, sizeof(cl_oldGame));
+ }
+
+ FS_ConditionalRestart(clc.checksumFeed, false);
+
+ // This used to call CL_StartHunkUsers, but now we enter the download state before loading the
+ // cgame
+ CL_InitDownloads();
+
+ // make sure the game starts
+ Cvar_Set( "cl_paused", "0" );
+}
+
+
+//=====================================================================
+
+/*
+=====================
+CL_ParseDownload
+
+A download message has been received from the server
+=====================
+*/
+void CL_ParseDownload ( msg_t *msg ) {
+ int size;
+ unsigned char data[MAX_MSGLEN];
+ uint16_t block;
+
+ if (!*clc.downloadTempName) {
+ Com_Printf("Server sending download, but no download was requested\n");
+ CL_AddReliableCommand("stopdl", false);
+ return;
+ }
+
+ // read the data
+ block = MSG_ReadShort ( msg );
+
+ if(!block && !clc.downloadBlock)
+ {
+ // block zero is special, contains file size
+ clc.downloadSize = MSG_ReadLong ( msg );
+
+ Cvar_SetValue( "cl_downloadSize", clc.downloadSize );
+
+ if (clc.downloadSize < 0)
+ {
+ Com_Error( ERR_DROP, "%s", MSG_ReadString( msg ) );
+ return;
+ }
+ }
+
+ size = MSG_ReadShort ( msg );
+ if (size < 0 || size > sizeof(data))
+ {
+ Com_Error(ERR_DROP, "CL_ParseDownload: Invalid size %d for download chunk", size);
+ return;
+ }
+
+ MSG_ReadData(msg, data, size);
+
+ if((clc.downloadBlock & 0xFFFF) != block)
+ {
+ Com_DPrintf( "CL_ParseDownload: Expected block %d, got %d\n", (clc.downloadBlock & 0xFFFF), block);
+ return;
+ }
+
+ // open the file if not opened yet
+ if (!clc.download)
+ {
+ clc.download = FS_SV_FOpenFileWrite( clc.downloadTempName );
+
+ if (!clc.download) {
+ Com_Printf( "Could not create %s\n", clc.downloadTempName );
+ CL_AddReliableCommand("stopdl", false);
+ CL_NextDownload();
+ return;
+ }
+ }
+
+ if (size)
+ FS_Write( data, size, clc.download );
+
+ CL_AddReliableCommand(va("nextdl %d", clc.downloadBlock), false);
+ clc.downloadBlock++;
+
+ clc.downloadCount += size;
+
+ // So UI gets access to it
+ Cvar_SetValue( "cl_downloadCount", clc.downloadCount );
+
+ if (!size) { // A zero length block means EOF
+ if (clc.download) {
+ FS_FCloseFile( clc.download );
+ clc.download = 0;
+
+ // rename the file
+ FS_SV_Rename( clc.downloadTempName, clc.downloadName, false );
+ }
+
+ // send intentions now
+ // We need this because without it, we would hold the last nextdl and then start
+ // 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();
+
+ // get another file if needed
+ CL_NextDownload ();
+ }
+}
+
+#ifdef USE_VOIP
+static bool CL_ShouldIgnoreVoipSender(int sender)
+{
+ if (!cl_voip->integer)
+ return true; // VoIP is disabled.
+ else if ((sender == clc.clientNum) && (!clc.demoplaying))
+ return true; // ignore own voice (unless playing back a demo).
+ else if (clc.voipMuteAll)
+ return true; // all channels are muted with extreme prejudice.
+ else if (clc.voipIgnore[sender])
+ return true; // just ignoring this guy.
+ else if (clc.voipGain[sender] == 0.0f)
+ return true; // too quiet to play.
+
+ return false;
+}
+
+/*
+=====================
+CL_PlayVoip
+
+Play raw data
+=====================
+*/
+
+static void CL_PlayVoip(int sender, int samplecnt, const byte *data, int flags)
+{
+ if(flags & VOIP_DIRECT)
+ {
+ S_RawSamples(sender + 1, samplecnt, 48000, 2, 1, data, clc.voipGain[sender], -1);
+ }
+
+ if(flags & VOIP_SPATIAL)
+ {
+ S_RawSamples(sender + MAX_CLIENTS + 1, samplecnt, 48000, 2, 1, data, 1.0f, sender);
+ }
+}
+
+/*
+=====================
+CL_ParseVoip
+
+A VoIP message has been received from the server
+=====================
+*/
+static void CL_ParseVoip( msg_t *msg, bool ignoreData )
+{
+ static short decoded[VOIP_MAX_PACKET_SAMPLES*4]; // !!! FIXME: don't hard code
+
+ const int sender = MSG_ReadShort(msg);
+ const int generation = MSG_ReadByte(msg);
+ const int sequence = MSG_ReadLong(msg);
+ const int frames = MSG_ReadByte(msg);
+ const int packetsize = MSG_ReadShort(msg);
+ int flags = VOIP_DIRECT;
+ if (clc.netchan.alternateProtocol == 0)
+ flags = MSG_ReadBits(msg, VOIP_FLAGCNT);
+ char encoded[4000];
+ int numSamples;
+ int seqdiff;
+ int written = 0;
+ int i;
+
+ Com_DPrintf("VoIP: %d-byte packet from client %d\n", packetsize, sender);
+
+ if (sender < 0)
+ return; // short/invalid packet, bail.
+ else if (generation < 0)
+ return; // short/invalid packet, bail.
+ else if (sequence < 0)
+ return; // short/invalid packet, bail.
+ else if (frames < 0)
+ return; // short/invalid packet, bail.
+ else if (packetsize < 0)
+ return; // short/invalid packet, bail.
+
+ if (packetsize > sizeof (encoded)) { // overlarge packet?
+ int bytesleft = packetsize;
+ while (bytesleft) {
+ int br = bytesleft;
+ if (br > sizeof (encoded))
+ br = sizeof (encoded);
+ MSG_ReadData(msg, encoded, br);
+ bytesleft -= br;
+ }
+ return; // overlarge packet, bail.
+ }
+
+ MSG_ReadData(msg, encoded, packetsize);
+
+ if (ignoreData) {
+ return; // just ignore legacy speex voip data
+ } else if (!clc.voipCodecInitialized) {
+ return; // can't handle VoIP without libopus!
+ } else if (sender >= MAX_CLIENTS) {
+ return; // bogus sender.
+ } else if (CL_ShouldIgnoreVoipSender(sender)) {
+ return; // Channel is muted, bail.
+ }
+
+ // !!! FIXME: make sure data is narrowband? Does decoder handle this?
+
+ Com_DPrintf("VoIP: packet accepted!\n");
+
+ seqdiff = sequence - clc.voipIncomingSequence[sender];
+
+ // This is a new "generation" ... a new recording started, reset the bits.
+ if (generation != clc.voipIncomingGeneration[sender]) {
+ Com_DPrintf("VoIP: new generation %d!\n", generation);
+ opus_decoder_ctl(clc.opusDecoder[sender], OPUS_RESET_STATE);
+ clc.voipIncomingGeneration[sender] = generation;
+ seqdiff = 0;
+ } else if (seqdiff < 0) { // we're ahead of the sequence?!
+ // This shouldn't happen unless the packet is corrupted or something.
+ Com_DPrintf("VoIP: misordered sequence! %d < %d!\n",
+ sequence, clc.voipIncomingSequence[sender]);
+ // reset the decoder just in case.
+ opus_decoder_ctl(clc.opusDecoder[sender], OPUS_RESET_STATE);
+ seqdiff = 0;
+ } else if (seqdiff * VOIP_MAX_PACKET_SAMPLES*2 >= sizeof (decoded)) { // dropped more than we can handle?
+ // just start over.
+ Com_DPrintf("VoIP: Dropped way too many (%d) frames from client #%d\n",
+ seqdiff, sender);
+ opus_decoder_ctl(clc.opusDecoder[sender], OPUS_RESET_STATE);
+ seqdiff = 0;
+ }
+
+ if (seqdiff != 0) {
+ Com_DPrintf("VoIP: Dropped %d frames from client #%d\n",
+ seqdiff, sender);
+ // tell opus that we're missing frames...
+ for (i = 0; i < seqdiff; i++) {
+ assert((written + VOIP_MAX_PACKET_SAMPLES) * 2 < sizeof (decoded));
+ numSamples = opus_decode(clc.opusDecoder[sender], NULL, 0, decoded + written, VOIP_MAX_PACKET_SAMPLES, 0);
+ if ( numSamples <= 0 ) {
+ Com_DPrintf("VoIP: Error decoding frame %d from client #%d\n", i, sender);
+ continue;
+ }
+ written += numSamples;
+ }
+ }
+
+ numSamples = opus_decode(clc.opusDecoder[sender],
+ (const unsigned char*)encoded,
+ packetsize,
+ decoded + written,
+ ARRAY_LEN(decoded) - written,
+ 0);
+
+ if ( numSamples <= 0 ) {
+ Com_DPrintf("VoIP: Error decoding voip data from client #%d\n", sender);
+ numSamples = 0;
+ }
+
+ #if 0
+ static FILE *encio = NULL;
+ if (encio == NULL) encio = fopen("voip-incoming-encoded.bin", "wb");
+ if (encio != NULL) { fwrite(encoded, len, 1, encio); fflush(encio); }
+ static FILE *decio = NULL;
+ if (decio == NULL) decio = fopen("voip-incoming-decoded.bin", "wb");
+ if (decio != NULL) { fwrite(decoded+written, clc.speexFrameSize*2, 1, decio); fflush(decio); }
+ #endif
+
+ written += numSamples;
+
+ Com_DPrintf("VoIP: playback %d bytes, %d samples, %d frames\n",
+ written * 2, written, frames);
+
+ if(written > 0)
+ CL_PlayVoip(sender, written, (const byte *) decoded, flags);
+
+ clc.voipIncomingSequence[sender] = sequence + frames;
+}
+#endif
+
+
+/*
+=====================
+CL_ParseCommandString
+
+Command strings are just saved off until cgame asks for them
+when it transitions a snapshot
+=====================
+*/
+void CL_ParseCommandString( msg_t *msg ) {
+ char *s;
+ int seq;
+ int index;
+
+ seq = MSG_ReadLong( msg );
+ s = MSG_ReadString( msg );
+
+ // see if we have already executed stored it off
+ if ( clc.serverCommandSequence >= seq ) {
+ return;
+ }
+ clc.serverCommandSequence = seq;
+
+ index = seq & (MAX_RELIABLE_COMMANDS-1);
+ Q_strncpyz( clc.serverCommands[ index ], s, sizeof( clc.serverCommands[ index ] ) );
+}
+
+
+/*
+=====================
+CL_ParseServerMessage
+=====================
+*/
+void CL_ParseServerMessage( msg_t *msg ) {
+ int cmd;
+
+ if ( cl_shownet->integer == 1 ) {
+ Com_Printf ("%i ",msg->cursize);
+ } else if ( cl_shownet->integer >= 2 ) {
+ Com_Printf ("------------------\n");
+ }
+
+ MSG_Bitstream(msg);
+
+ // get the reliable sequence acknowledge number
+ clc.reliableAcknowledge = MSG_ReadLong( msg );
+ //
+ if ( clc.reliableAcknowledge < clc.reliableSequence - MAX_RELIABLE_COMMANDS ) {
+ clc.reliableAcknowledge = clc.reliableSequence;
+ }
+
+ //
+ // parse the message
+ //
+ while ( 1 ) {
+ if ( msg->readcount > msg->cursize ) {
+ Com_Error (ERR_DROP,"CL_ParseServerMessage: read past end of server message");
+ break;
+ }
+
+ cmd = MSG_ReadByte( msg );
+
+ if ( clc.netchan.alternateProtocol != 0 )
+ {
+ // See if this is an extension command after the EOF, which means we
+ // got data that a legacy client should ignore.
+ if ( cmd == svc_EOF && MSG_LookaheadByte( msg ) == svc_voipSpeex ) {
+ SHOWNET( msg, "EXTENSION" );
+ MSG_ReadByte( msg ); // throw the svc_extension byte away.
+ cmd = MSG_ReadByte( msg ); // something legacy clients can't do!
+ // sometimes you get a svc_extension at end of stream...dangling
+ // bits in the huffman decoder giving a bogus value?
+ if ( cmd == -1 ) {
+ cmd = svc_EOF;
+ }
+ }
+
+ if ( cmd == svc_voipSpeex ) {
+ cmd = svc_voipSpeex + 1;
+ } else if ( cmd == svc_voipSpeex + 1 ) {
+ cmd = svc_voipSpeex;
+ }
+ }
+
+ if (cmd == svc_EOF) {
+ SHOWNET( msg, "END OF MESSAGE" );
+ break;
+ }
+
+ if ( cl_shownet->integer >= 2 ) {
+ if ( (cmd < 0) || (!svc_strings[cmd]) ) {
+ Com_Printf( "%3i:BAD CMD %i\n", msg->readcount-1, cmd );
+ } else {
+ SHOWNET( msg, svc_strings[cmd] );
+ }
+ }
+
+ // other commands
+ switch ( cmd ) {
+ default:
+ Com_Error (ERR_DROP,"CL_ParseServerMessage: Illegible server message");
+ break;
+ case svc_nop:
+ break;
+ case svc_serverCommand:
+ CL_ParseCommandString( msg );
+ break;
+ case svc_gamestate:
+ CL_ParseGamestate( msg );
+ break;
+ case svc_snapshot:
+ CL_ParseSnapshot( msg );
+ break;
+ case svc_download:
+ CL_ParseDownload( msg );
+ break;
+ case svc_voipSpeex:
+#ifdef USE_VOIP
+ CL_ParseVoip( msg, true );
+#endif
+ break;
+ case svc_voipOpus:
+#ifdef USE_VOIP
+ CL_ParseVoip( msg, !clc.voipEnabled );
+#endif
+ break;
+ }
+ }
+}
diff --git a/src/client/cl_rest.cpp b/src/client/cl_rest.cpp
new file mode 100644
index 0000000..c1ff2fc
--- /dev/null
+++ b/src/client/cl_rest.cpp
@@ -0,0 +1,117 @@
+//
+// Restclient wrapper abusively modified to be a one-off donwloader.
+// -wtfbbqhax
+//
+// TODO Verify sha256 of file if exists
+
+#include "cl_rest.h"
+
+#include <unistd.h>
+
+#include <cerrno>
+#include <cstring>
+#include <fstream>
+#include <iostream>
+#include <vector>
+
+#include "qcommon/files.h"
+#include "restclient/restclient.h"
+
+bool is_good(std::string filename, int permissions = (R_OK|W_OK))
+{
+ int ret = access(filename.c_str(), permissions);
+ if (ret)
+ std::cerr << filename << ": " << strerror(errno) << std::endl;
+
+ return !ret;
+}
+
+bool MakeDir(std::string destdir, std::string basegame)
+{
+ std::string destpath(destdir);
+
+ if ( basegame != "" )
+ {
+ destpath += '/';
+ destpath += basegame;
+ }
+
+ if ( *destpath.rbegin() != '/' )
+ destpath += '/'; // XXX FS_CreatePath requires a trailing slash.
+ // Maybe the assumption is that a file listing might be included?
+
+ FS_CreatePath(destpath.c_str());
+ return true;
+}
+
+#include "sys/dialog.h"
+static bool PromptDownloadPk3s(std::string basegame, const std::vector<std::string>& missing)
+{
+ std::string msg;
+
+ msg = "The following files must be downloaded to complete the installation.\n\n";
+ for ( auto f : missing )
+ msg += "\t" + basegame + "/" + f + "\n";
+
+ msg += "\n";
+ msg += "Yes to continue, No to quit the game.";
+
+ if( Sys_Dialog( DT_YES_NO, msg.c_str(), "You're almost ready!" ) == DR_YES )
+ return true;
+
+ return false;
+}
+
+bool GetTremulousPk3s(const char* destdir, const char* basegame)
+{
+ std::string baseuri = "https://github.com/wtfbbqhax/tremulous-data/raw/master/";
+ std::vector<std::string> files = {
+ "data-gpp1.pk3",
+ "data-1.1.0.pk3",
+ "map-arachnid2-1.1.0.pk3",
+ "map-atcs-1.1.0.pk3",
+ "map-karith-1.1.0.pk3",
+ "map-nexus6-1.1.0.pk3",
+ "map-niveus-1.1.0.pk3",
+ "map-transit-1.1.0.pk3",
+ "map-tremor-1.1.0.pk3",
+ "map-uncreation-1.1.0.pk3"
+ };
+
+ RestClient::init();
+
+ MakeDir(destdir, basegame);
+
+ if (!PromptDownloadPk3s(basegame, files))
+ return false;
+
+ for (auto f : files )
+ {
+ std::string destpath(destdir);
+ destpath += "/";
+ destpath += basegame;
+ destpath += "/";
+ destpath += f;
+
+ if ( is_good(destpath) )
+ {
+ return false;
+ }
+
+ std::cout << "Downloading " << baseuri << f << std::endl;
+ std::ofstream dl(destpath);
+ //dl.open(destpath);
+ if ( dl.fail() )
+ {
+ std::cerr << "Error " << strerror(errno) << "\n";
+ continue;
+ }
+
+ RestClient::Response resp = RestClient::get(baseuri + f);
+
+ dl << resp.body;
+ dl.close();
+ }
+
+ return true;
+}
diff --git a/src/client/cl_rest.h b/src/client/cl_rest.h
new file mode 100644
index 0000000..afde7d2
--- /dev/null
+++ b/src/client/cl_rest.h
@@ -0,0 +1,18 @@
+#ifndef _CL_REST_H_
+#define _CL_REST_H_
+
+bool GetPermissions(const char*execpath);
+
+// Download a fresh copy of the "required" base game files to the top-most fs_basegame.
+//
+// Returns true(1) on success
+// Return false(0) on error
+//
+bool GetTremulousPk3s(const char* destdir, const char* basegame);
+
+#include <string>
+
+//bool is_good(std::string filename, int permissions = (R_OK|W_OK));
+bool MakeDir(std::string destdir, std::string basegame);
+
+#endif
diff --git a/src/client/cl_scrn.cpp b/src/client/cl_scrn.cpp
new file mode 100644
index 0000000..7d0c7e2
--- /dev/null
+++ b/src/client/cl_scrn.cpp
@@ -0,0 +1,588 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
+
+This file is part of Tremulous.
+
+Tremulous 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.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+// cl_scrn.c -- master for refresh, status bar, console, chat, notify, etc
+
+#include "client.h"
+
+bool scr_initialized; // ready to draw
+
+cvar_t *cl_timegraph;
+cvar_t *cl_debuggraph;
+cvar_t *cl_graphheight;
+cvar_t *cl_graphscale;
+cvar_t *cl_graphshift;
+
+/*
+================
+SCR_DrawNamedPic
+
+Coordinates are 640*480 virtual values
+=================
+*/
+void SCR_DrawNamedPic(float x, float y, float width, float height, const char *picname)
+{
+ qhandle_t hShader;
+
+ assert(width != 0);
+
+ hShader = re.RegisterShader(picname);
+ SCR_AdjustFrom640(&x, &y, &width, &height);
+ re.DrawStretchPic(x, y, width, height, 0, 0, 1, 1, hShader);
+}
+
+/*
+================
+SCR_AdjustFrom640
+
+Adjusted for resolution and screen aspect ratio
+================
+*/
+void SCR_AdjustFrom640(float *x, float *y, float *w, float *h)
+{
+ float xscale;
+ float yscale;
+
+#if 0
+ // adjust for wide screens
+ if ( cls.glconfig.vidWidth * 480 > cls.glconfig.vidHeight * 640 ) {
+ *x += 0.5 * ( cls.glconfig.vidWidth - ( cls.glconfig.vidHeight * 640 / 480 ) );
+ }
+#endif
+
+ // scale for screen sizes
+ xscale = cls.glconfig.vidWidth / 640.0;
+ yscale = cls.glconfig.vidHeight / 480.0;
+ if (x)
+ {
+ *x *= xscale;
+ }
+ if (y)
+ {
+ *y *= yscale;
+ }
+ if (w)
+ {
+ *w *= xscale;
+ }
+ if (h)
+ {
+ *h *= yscale;
+ }
+}
+
+/*
+================
+SCR_FillRect
+
+Coordinates are 640*480 virtual values
+=================
+*/
+void SCR_FillRect(float x, float y, float width, float height, const float *color)
+{
+ re.SetColor(color);
+
+ SCR_AdjustFrom640(&x, &y, &width, &height);
+ re.DrawStretchPic(x, y, width, height, 0, 0, 0, 0, cls.whiteShader);
+
+ re.SetColor(NULL);
+}
+
+/*
+================
+SCR_DrawPic
+
+Coordinates are 640*480 virtual values
+=================
+*/
+void SCR_DrawPic(float x, float y, float width, float height, qhandle_t hShader)
+{
+ SCR_AdjustFrom640(&x, &y, &width, &height);
+ re.DrawStretchPic(x, y, width, height, 0, 0, 1, 1, hShader);
+}
+
+/*
+** SCR_DrawChar
+** chars are drawn at 640*480 virtual screen size
+*/
+static void SCR_DrawChar(int x, int y, float size, int ch)
+{
+ int row, col;
+ float frow, fcol;
+ float ax, ay, aw, ah;
+
+ ch &= 255;
+
+ if (ch == ' ')
+ {
+ return;
+ }
+
+ if (y < -size)
+ {
+ return;
+ }
+
+ ax = x;
+ ay = y;
+ aw = size;
+ ah = size;
+ SCR_AdjustFrom640(&ax, &ay, &aw, &ah);
+
+ row = ch >> 4;
+ col = ch & 15;
+
+ frow = row * 0.0625;
+ fcol = col * 0.0625;
+ size = 0.0625;
+
+ re.DrawStretchPic(ax, ay, aw, ah, fcol, frow, fcol + size, frow + size, cls.charSetShader);
+}
+
+/*
+** SCR_DrawSmallChar
+** small chars are drawn at native screen resolution
+*/
+void SCR_DrawSmallChar(int x, int y, int ch)
+{
+ int row, col;
+ float frow, fcol;
+ float size;
+
+ ch &= 255;
+
+ if (ch == ' ')
+ {
+ return;
+ }
+
+ if (y < -SMALLCHAR_HEIGHT)
+ {
+ return;
+ }
+
+ row = ch >> 4;
+ col = ch & 15;
+
+ frow = row * 0.0625;
+ fcol = col * 0.0625;
+ size = 0.0625;
+
+ re.DrawStretchPic(x, y, SMALLCHAR_WIDTH, SMALLCHAR_HEIGHT, fcol, frow, fcol + size, frow + size, cls.charSetShader);
+}
+
+/*
+==================
+SCR_DrawBigString[Color]
+
+Draws a multi-colored string with a drop shadow, optionally forcing
+to a fixed color.
+
+Coordinates are at 640 by 480 virtual resolution
+==================
+*/
+static void SCR_DrawStringExt(
+ int x, int y, float size, const char *string, float *setColor, bool forceColor, bool noColorEscape)
+{
+ vec4_t color;
+ const char *s;
+ int xx;
+
+ // draw the drop shadow
+ color[0] = color[1] = color[2] = 0;
+ color[3] = setColor[3];
+ re.SetColor(color);
+ s = string;
+ xx = x;
+ while (*s)
+ {
+ if (!noColorEscape && Q_IsColorString(s))
+ {
+ s += 2;
+ continue;
+ }
+ SCR_DrawChar(xx + 2, y + 2, size, *s);
+ xx += size;
+ s++;
+ }
+
+ // draw the colored text
+ s = string;
+ xx = x;
+ re.SetColor(setColor);
+ while (*s)
+ {
+ if (Q_IsColorString(s))
+ {
+ if (!forceColor)
+ {
+ ::memcpy(color, g_color_table[ColorIndex(*(s + 1))], sizeof(color));
+ color[3] = setColor[3];
+ re.SetColor(color);
+ }
+ if (!noColorEscape)
+ {
+ s += 2;
+ continue;
+ }
+ }
+ SCR_DrawChar(xx, y, size, *s);
+ xx += size;
+ s++;
+ }
+ re.SetColor(NULL);
+}
+
+void SCR_DrawBigString(int x, int y, const char *s, float alpha, bool noColorEscape)
+{
+ float color[4];
+
+ color[0] = color[1] = color[2] = 1.0;
+ color[3] = alpha;
+ SCR_DrawStringExt(x, y, BIGCHAR_WIDTH, s, color, false, noColorEscape);
+}
+
+void SCR_DrawBigStringColor(int x, int y, const char *s, vec4_t color, bool noColorEscape)
+{
+ SCR_DrawStringExt(x, y, BIGCHAR_WIDTH, s, color, true, noColorEscape);
+}
+
+/*
+==================
+SCR_DrawSmallString[Color]
+
+Draws a multi-colored string with a drop shadow, optionally forcing
+to a fixed color.
+==================
+*/
+void SCR_DrawSmallStringExt(int x, int y, const char *string, float *setColor, bool forceColor, bool noColorEscape)
+{
+ vec4_t color;
+ const char *s;
+ int xx;
+
+ // draw the colored text
+ s = string;
+ xx = x;
+ re.SetColor(setColor);
+ while (*s)
+ {
+ if (Q_IsColorString(s))
+ {
+ if (!forceColor)
+ {
+ ::memcpy(color, g_color_table[ColorIndex(*(s + 1))], sizeof(color));
+ color[3] = setColor[3];
+ re.SetColor(color);
+ }
+ if (!noColorEscape)
+ {
+ s += 2;
+ continue;
+ }
+ }
+ SCR_DrawSmallChar(xx, y, *s);
+ xx += SMALLCHAR_WIDTH;
+ s++;
+ }
+ re.SetColor(NULL);
+}
+
+/*
+** SCR_Strlen -- skips color escape codes
+*/
+static int SCR_Strlen(const char *str)
+{
+ const char *s = str;
+ int count = 0;
+
+ while (*s)
+ {
+ if (Q_IsColorString(s))
+ {
+ s += 2;
+ }
+ else
+ {
+ count++;
+ s++;
+ }
+ }
+
+ return count;
+}
+
+/*
+** SCR_GetBigStringWidth
+*/
+int SCR_GetBigStringWidth(const char *str) { return SCR_Strlen(str) * BIGCHAR_WIDTH; }
+//===============================================================================
+
+#ifdef USE_VOIP
+/*
+=================
+SCR_DrawVoipMeter
+
+FIXME: inherited from ioq3, move to cgame/ui
+=================
+*/
+void SCR_DrawVoipMeter(void)
+{
+ char buffer[16];
+ char string[256];
+ int limit, i;
+
+ if (!cl_voipShowMeter->integer)
+ return; // player doesn't want to show meter at all.
+ else if (!cl_voipSend->integer)
+ return; // not recording at the moment.
+ else if (clc.state != CA_ACTIVE)
+ return; // not connected to a server.
+ else if (!clc.voipEnabled)
+ return; // server doesn't support VoIP.
+ else if (clc.demoplaying)
+ return; // playing back a demo.
+ else if (!cl_voip->integer)
+ return; // client has VoIP support disabled.
+
+ limit = (int)(clc.voipPower * 10.0f);
+ if (limit > 10) limit = 10;
+
+ for (i = 0; i < limit; i++) buffer[i] = '*';
+ while (i < 10) buffer[i++] = ' ';
+ buffer[i] = '\0';
+
+ sprintf(string, "VoIP: [%s]", buffer);
+ SCR_DrawStringExt(320 - strlen(string) * 4, 10, 8, string, g_color_table[7], true, false);
+}
+#endif
+
+/*
+===============================================================================
+
+DEBUG GRAPH
+
+===============================================================================
+*/
+
+static int current;
+static float values[1024];
+
+/*
+==============
+SCR_DebugGraph
+==============
+*/
+void SCR_DebugGraph(float value)
+{
+ values[current] = value;
+ current = (current + 1) % ARRAY_LEN(values);
+}
+
+/*
+==============
+SCR_DrawDebugGraph
+==============
+*/
+void SCR_DrawDebugGraph(void)
+{
+ int a, x, y, w, i, h;
+ float v;
+
+ //
+ // draw the graph
+ //
+ w = cls.glconfig.vidWidth;
+ x = 0;
+ y = cls.glconfig.vidHeight;
+ re.SetColor(g_color_table[0]);
+ re.DrawStretchPic(x, y - cl_graphheight->integer, w, cl_graphheight->integer, 0, 0, 0, 0, cls.whiteShader);
+ re.SetColor(NULL);
+
+ for (a = 0; a < w; a++)
+ {
+ i = (ARRAY_LEN(values) + current - 1 - (a % ARRAY_LEN(values))) % ARRAY_LEN(values);
+ v = values[i];
+ v = v * cl_graphscale->integer + cl_graphshift->integer;
+
+ if (v < 0) v += cl_graphheight->integer * (1 + (int)(-v / cl_graphheight->integer));
+ h = (int)v % cl_graphheight->integer;
+ re.DrawStretchPic(x + w - 1 - a, y - h, 1, h, 0, 0, 0, 0, cls.whiteShader);
+ }
+}
+
+//=============================================================================
+
+/*
+==================
+SCR_Init
+==================
+*/
+void SCR_Init(void)
+{
+ cl_timegraph = Cvar_Get("timegraph", "0", CVAR_CHEAT);
+ cl_debuggraph = Cvar_Get("debuggraph", "0", CVAR_CHEAT);
+ cl_graphheight = Cvar_Get("graphheight", "32", CVAR_CHEAT);
+ cl_graphscale = Cvar_Get("graphscale", "1", CVAR_CHEAT);
+ cl_graphshift = Cvar_Get("graphshift", "0", CVAR_CHEAT);
+
+ scr_initialized = true;
+}
+
+//=======================================================
+
+/*
+==================
+SCR_DrawScreenField
+
+This will be called twice if rendering in stereo mode
+==================
+*/
+void SCR_DrawScreenField(stereoFrame_t stereoFrame)
+{
+ bool uiFullscreen;
+
+ re.BeginFrame(stereoFrame);
+
+ uiFullscreen = (cls.ui && VM_Call(cls.ui, UI_IS_FULLSCREEN - (cls.uiInterface == 2 ? 2 : 0)));
+
+ // wide aspect ratio screens need to have the sides cleared
+ // unless they are displaying game renderings
+ if (uiFullscreen || clc.state < CA_LOADING)
+ {
+ if (cls.glconfig.vidWidth * 480 > cls.glconfig.vidHeight * 640)
+ {
+ re.SetColor(g_color_table[0]);
+ re.DrawStretchPic(0, 0, cls.glconfig.vidWidth, cls.glconfig.vidHeight, 0, 0, 0, 0, cls.whiteShader);
+ re.SetColor(NULL);
+ }
+ }
+
+ // if the menu is going to cover the entire screen, we
+ // don't need to render anything under it
+ if (cls.ui && !uiFullscreen)
+ {
+ switch (clc.state)
+ {
+ default:
+ Com_Error(ERR_FATAL, "SCR_DrawScreenField: bad clc.state");
+ break;
+ case CA_CINEMATIC:
+ SCR_DrawCinematic();
+ break;
+ case CA_DISCONNECTED:
+ // force menu up
+ S_StopAllSounds();
+ VM_Call(cls.ui, UI_SET_ACTIVE_MENU - (cls.uiInterface == 2 ? 2 : 0), UIMENU_MAIN);
+ break;
+ case CA_CONNECTING:
+ case CA_CHALLENGING:
+ case CA_CONNECTED:
+ // connecting clients will only show the connection dialog
+ // refresh to update the time
+ VM_Call(cls.ui, UI_REFRESH - (cls.uiInterface == 2 ? 2 : 0), cls.realtime);
+ VM_Call(cls.ui, UI_DRAW_CONNECT_SCREEN - (cls.uiInterface == 2 ? 2 : 0), false);
+ break;
+ case CA_LOADING:
+ case CA_PRIMED:
+ // draw the game information screen and loading progress
+ CL_CGameRendering(stereoFrame);
+ break;
+ case CA_ACTIVE:
+ // always supply STEREO_CENTER as vieworg offset is now done by the engine.
+ CL_CGameRendering(stereoFrame);
+#ifdef USE_VOIP
+ SCR_DrawVoipMeter();
+#endif
+ break;
+ }
+ }
+
+ // the menu draws next
+ if (Key_GetCatcher() & KEYCATCH_UI && cls.ui)
+ {
+ VM_Call(cls.ui, UI_REFRESH - (cls.uiInterface == 2 ? 2 : 0), cls.realtime);
+ }
+
+ // console draws next
+ Con_DrawConsole();
+
+ // debug graph can be drawn on top of anything
+ if (cl_debuggraph->integer || cl_timegraph->integer || cl_debugMove->integer)
+ {
+ SCR_DrawDebugGraph();
+ }
+}
+
+/*
+==================
+SCR_UpdateScreen
+
+This is called every frame, and can also be called explicitly to flush
+text to the screen.
+==================
+*/
+void SCR_UpdateScreen(void)
+{
+ static int recursive;
+
+ if (!scr_initialized)
+ {
+ return; // not initialized yet
+ }
+
+ if (++recursive > 2)
+ {
+ Com_Error(ERR_FATAL, "SCR_UpdateScreen: recursively called");
+ }
+ recursive = 1;
+
+ // If there is no VM, there are also no rendering commands issued. Stop the renderer in
+ // that case.
+ if (cls.ui || com_dedicated->integer)
+ {
+ // XXX
+ int in_anaglyphMode = Cvar_VariableIntegerValue("r_anaglyphMode");
+ // if running in stereo, we need to draw the frame twice
+ if (cls.glconfig.stereoEnabled || in_anaglyphMode)
+ {
+ SCR_DrawScreenField(STEREO_LEFT);
+ SCR_DrawScreenField(STEREO_RIGHT);
+ }
+ else
+ {
+ SCR_DrawScreenField(STEREO_CENTER);
+ }
+
+ if (com_speeds->integer)
+ {
+ re.EndFrame(&time_frontend, &time_backend);
+ }
+ else
+ {
+ re.EndFrame(NULL, NULL);
+ }
+ }
+
+ recursive = 0;
+}
diff --git a/src/client/cl_ui.cpp b/src/client/cl_ui.cpp
new file mode 100644
index 0000000..4052fe6
--- /dev/null
+++ b/src/client/cl_ui.cpp
@@ -0,0 +1,1269 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
+
+This file is part of Tremulous.
+
+Tremulous 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.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+
+#include "client.h"
+
+#include "cl_updates.h"
+
+/*
+====================
+GetClientState
+====================
+*/
+static void GetClientState(uiClientState_t *state)
+{
+ state->connectPacketCount = clc.connectPacketCount;
+ state->connState = clc.state;
+ Q_strncpyz(state->servername, clc.servername, sizeof(state->servername));
+ Q_strncpyz(state->updateInfoString, cls.updateInfoString, sizeof(state->updateInfoString));
+ Q_strncpyz(state->messageString, clc.serverMessage, sizeof(state->messageString));
+ state->clientNum = (clc.netchan.alternateProtocol == 2 ? cl.snap.alternatePs.clientNum : cl.snap.ps.clientNum);
+}
+
+/*
+====================
+LAN_LoadCachedServers
+====================
+*/
+void LAN_LoadCachedServers(void)
+{
+ int size;
+ fileHandle_t fileIn;
+ cls.numglobalservers = cls.numfavoriteservers = 0;
+ cls.numGlobalServerAddresses = 0;
+ if (FS_SV_FOpenFileRead("servercache.dat", &fileIn))
+ {
+ FS_Read(&cls.numglobalservers, sizeof(int), fileIn);
+ FS_Read(&cls.numfavoriteservers, sizeof(int), fileIn);
+ FS_Read(&size, sizeof(int), fileIn);
+ if (size == sizeof(cls.globalServers) + sizeof(cls.favoriteServers))
+ {
+ FS_Read(&cls.globalServers, sizeof(cls.globalServers), fileIn);
+ FS_Read(&cls.favoriteServers, sizeof(cls.favoriteServers), fileIn);
+ }
+ else
+ {
+ cls.numglobalservers = cls.numfavoriteservers = 0;
+ cls.numGlobalServerAddresses = 0;
+ }
+ FS_FCloseFile(fileIn);
+ }
+}
+
+/*
+====================
+LAN_SaveServersToCache
+====================
+*/
+void LAN_SaveServersToCache(void)
+{
+ fileHandle_t fileOut = FS_SV_FOpenFileWrite("servercache.dat");
+ FS_Write(&cls.numglobalservers, sizeof(int), fileOut);
+ FS_Write(&cls.numfavoriteservers, sizeof(int), fileOut);
+
+ int size = sizeof(cls.globalServers) + sizeof(cls.favoriteServers);
+ FS_Write(&size, sizeof(int), fileOut);
+ FS_Write(&cls.globalServers, sizeof(cls.globalServers), fileOut);
+ FS_Write(&cls.favoriteServers, sizeof(cls.favoriteServers), fileOut);
+ FS_FCloseFile(fileOut);
+}
+
+/*
+====================
+GetNews
+====================
+*/
+static bool GetNews(bool begin)
+{
+ bool finished = false;
+ fileHandle_t fileIn;
+ int readSize;
+
+ if (begin)
+ { // if not already using curl, start the download
+ if (!clc.downloadCURLM)
+ {
+ if (!CL_cURL_Init())
+ {
+ Cvar_Set("cl_newsString", "^1Error: Could not load cURL library");
+ return true;
+ }
+ clc.activeCURLNotGameRelated = true;
+ CL_cURL_BeginDownload("news.dat", "http://grangerhub.com/wp-content/uploads/clientnews.txt");
+ return false;
+ }
+ }
+
+ if (!clc.downloadCURLM && FS_SV_FOpenFileRead("news.dat", &fileIn))
+ {
+ readSize = FS_Read(clc.newsString, sizeof(clc.newsString), fileIn);
+ FS_FCloseFile(fileIn);
+ clc.newsString[readSize] = '\0';
+ if (readSize > 0)
+ {
+ finished = true;
+ clc.cURLUsed = false;
+ CL_cURL_Shutdown();
+ clc.activeCURLNotGameRelated = false;
+ }
+ }
+ if (!finished) strcpy(clc.newsString, "Retrieving...");
+ Cvar_Set("cl_newsString", clc.newsString);
+ return finished;
+ Cvar_Set("cl_newsString", "^1You must compile your client with CURL support to use this feature");
+ return true;
+}
+
+/*
+====================
+LAN_ResetPings
+====================
+*/
+static void LAN_ResetPings(int source)
+{
+ int count, i;
+ serverInfo_t *servers = NULL;
+ count = 0;
+
+ switch (source)
+ {
+ case AS_LOCAL:
+ servers = &cls.localServers[0];
+ count = MAX_OTHER_SERVERS;
+ break;
+ case AS_MPLAYER:
+ case AS_GLOBAL:
+ servers = &cls.globalServers[0];
+ count = MAX_GLOBAL_SERVERS;
+ break;
+ case AS_FAVORITES:
+ servers = &cls.favoriteServers[0];
+ count = MAX_OTHER_SERVERS;
+ break;
+ }
+ if (servers)
+ {
+ for (i = 0; i < count; i++)
+ {
+ servers[i].ping = -1;
+ }
+ }
+}
+
+/*
+====================
+LAN_AddServer
+====================
+*/
+static int LAN_AddServer(int source, const char *name, const char *address)
+{
+ int max, *count, i;
+ netadr_t adr;
+ serverInfo_t *servers = NULL;
+ max = MAX_OTHER_SERVERS;
+ count = NULL;
+
+ switch (source)
+ {
+ case AS_LOCAL:
+ count = &cls.numlocalservers;
+ servers = &cls.localServers[0];
+ break;
+ case AS_MPLAYER:
+ case AS_GLOBAL:
+ max = MAX_GLOBAL_SERVERS;
+ count = &cls.numglobalservers;
+ servers = &cls.globalServers[0];
+ break;
+ case AS_FAVORITES:
+ count = &cls.numfavoriteservers;
+ servers = &cls.favoriteServers[0];
+ break;
+ }
+ if (servers && *count < max)
+ {
+ NET_StringToAdr(address, &adr, NA_UNSPEC);
+ for (i = 0; i < *count; i++)
+ {
+ if (NET_CompareAdr(servers[i].adr, adr))
+ {
+ break;
+ }
+ }
+ if (i >= *count)
+ {
+ servers[*count].adr = adr;
+ Q_strncpyz(servers[*count].hostName, name, sizeof(servers[*count].hostName));
+ servers[*count].visible = true;
+ (*count)++;
+ return 1;
+ }
+ return 0;
+ }
+ return -1;
+}
+
+/*
+====================
+LAN_RemoveServer
+====================
+*/
+static void LAN_RemoveServer(int source, const char *addr)
+{
+ int *count, i;
+ serverInfo_t *servers = NULL;
+ count = NULL;
+ switch (source)
+ {
+ case AS_LOCAL:
+ count = &cls.numlocalservers;
+ servers = &cls.localServers[0];
+ break;
+ case AS_MPLAYER:
+ case AS_GLOBAL:
+ count = &cls.numglobalservers;
+ servers = &cls.globalServers[0];
+ break;
+ case AS_FAVORITES:
+ count = &cls.numfavoriteservers;
+ servers = &cls.favoriteServers[0];
+ break;
+ }
+ if (servers)
+ {
+ netadr_t comp;
+ NET_StringToAdr(addr, &comp, NA_UNSPEC);
+ for (i = 0; i < *count; i++)
+ {
+ if (NET_CompareAdr(comp, servers[i].adr))
+ {
+ int j = i;
+ while (j < *count - 1)
+ {
+ ::memcpy(&servers[j], &servers[j + 1], sizeof(servers[j]));
+ j++;
+ }
+ (*count)--;
+ break;
+ }
+ }
+ }
+}
+
+/*
+====================
+LAN_GetServerCount
+====================
+*/
+static int LAN_GetServerCount(int source)
+{
+ switch (source)
+ {
+ case AS_LOCAL:
+ return cls.numlocalservers;
+ break;
+ case AS_MPLAYER:
+ case AS_GLOBAL:
+ return cls.numglobalservers;
+ break;
+ case AS_FAVORITES:
+ return cls.numfavoriteservers;
+ break;
+ }
+ return 0;
+}
+
+/*
+====================
+LAN_GetLocalServerAddressString
+====================
+*/
+static void LAN_GetServerAddressString(int source, int n, char *buf, int buflen)
+{
+ switch (source)
+ {
+ case AS_LOCAL:
+ if (n >= 0 && n < MAX_OTHER_SERVERS)
+ {
+ Q_strncpyz(buf, NET_AdrToStringwPort(cls.localServers[n].adr), buflen);
+ if (cls.localServers[n].adr.alternateProtocol != 0)
+ Q_strncpyz(buf + (int)strlen(buf), (cls.localServers[n].adr.alternateProtocol == 1 ? " -g" : " -1"),
+ buflen - (int)strlen(buf));
+ return;
+ }
+ break;
+ case AS_MPLAYER:
+ case AS_GLOBAL:
+ if (n >= 0 && n < MAX_GLOBAL_SERVERS)
+ {
+ Q_strncpyz(buf, NET_AdrToStringwPort(cls.globalServers[n].adr), buflen);
+ if (cls.globalServers[n].adr.alternateProtocol != 0)
+ Q_strncpyz(buf + (int)strlen(buf),
+ (cls.globalServers[n].adr.alternateProtocol == 1 ? " -g" : " -1"), buflen - (int)strlen(buf));
+ return;
+ }
+ break;
+ case AS_FAVORITES:
+ if (n >= 0 && n < MAX_OTHER_SERVERS)
+ {
+ Q_strncpyz(buf, NET_AdrToStringwPort(cls.favoriteServers[n].adr), buflen);
+ if (cls.favoriteServers[n].adr.alternateProtocol != 0)
+ Q_strncpyz(buf + (int)strlen(buf),
+ (cls.favoriteServers[n].adr.alternateProtocol == 1 ? " -g" : " -1"), buflen - (int)strlen(buf));
+ return;
+ }
+ break;
+ }
+ buf[0] = '\0';
+}
+
+/*
+====================
+LAN_GetServerInfo
+====================
+*/
+static void LAN_GetServerInfo(int source, int n, char *buf, int buflen)
+{
+ char info[MAX_STRING_CHARS];
+ serverInfo_t *server = NULL;
+ info[0] = '\0';
+ switch (source)
+ {
+ case AS_LOCAL:
+ if (n >= 0 && n < MAX_OTHER_SERVERS)
+ {
+ server = &cls.localServers[n];
+ }
+ break;
+ case AS_MPLAYER:
+ case AS_GLOBAL:
+ if (n >= 0 && n < MAX_GLOBAL_SERVERS)
+ {
+ server = &cls.globalServers[n];
+ }
+ break;
+ case AS_FAVORITES:
+ if (n >= 0 && n < MAX_OTHER_SERVERS)
+ {
+ server = &cls.favoriteServers[n];
+ }
+ break;
+ }
+ if (server && buf)
+ {
+ buf[0] = '\0';
+ if (server->adr.alternateProtocol != 0)
+ {
+ char hn[MAX_HOSTNAME_LENGTH];
+ Q_strncpyz(hn, server->hostName, sizeof(hn));
+ Q_strcat(
+ hn, sizeof(hn), (server->adr.alternateProtocol == 1 ? S_COLOR_WHITE " [GPP]" : S_COLOR_WHITE " [1.1]"));
+ Info_SetValueForKey(info, "hostname", hn);
+ }
+ else
+ {
+ Info_SetValueForKey(info, "hostname", server->hostName);
+ }
+ Info_SetValueForKey(info, "mapname", server->mapName);
+ Info_SetValueForKey(info, "label", server->label);
+ Info_SetValueForKey(info, "clients", va("%i", server->clients));
+ Info_SetValueForKey(info, "sv_maxclients", va("%i", server->maxClients));
+ Info_SetValueForKey(info, "ping", va("%i", server->ping));
+ Info_SetValueForKey(info, "minping", va("%i", server->minPing));
+ Info_SetValueForKey(info, "maxping", va("%i", server->maxPing));
+ Info_SetValueForKey(info, "game", server->game);
+ Info_SetValueForKey(info, "gametype", va("%i", server->gameType));
+ Info_SetValueForKey(info, "nettype", va("%i", server->netType));
+ Info_SetValueForKey(info, "addr", NET_AdrToStringwPort(server->adr));
+ Q_strncpyz(buf, info, buflen);
+ }
+ else
+ {
+ if (buf)
+ {
+ buf[0] = '\0';
+ }
+ }
+}
+
+/*
+====================
+LAN_GetServerPing
+====================
+*/
+static int LAN_GetServerPing(int source, int n)
+{
+ serverInfo_t *server = NULL;
+ switch (source)
+ {
+ case AS_LOCAL:
+ if (n >= 0 && n < MAX_OTHER_SERVERS)
+ {
+ server = &cls.localServers[n];
+ }
+ break;
+ case AS_MPLAYER:
+ case AS_GLOBAL:
+ if (n >= 0 && n < MAX_GLOBAL_SERVERS)
+ {
+ server = &cls.globalServers[n];
+ }
+ break;
+ case AS_FAVORITES:
+ if (n >= 0 && n < MAX_OTHER_SERVERS)
+ {
+ server = &cls.favoriteServers[n];
+ }
+ break;
+ }
+ if (server)
+ {
+ return server->ping;
+ }
+ return -1;
+}
+
+/*
+====================
+LAN_GetServerPtr
+====================
+*/
+static serverInfo_t *LAN_GetServerPtr(int source, int n)
+{
+ switch (source)
+ {
+ case AS_LOCAL:
+ if (n >= 0 && n < MAX_OTHER_SERVERS)
+ {
+ return &cls.localServers[n];
+ }
+ break;
+ case AS_MPLAYER:
+ case AS_GLOBAL:
+ if (n >= 0 && n < MAX_GLOBAL_SERVERS)
+ {
+ return &cls.globalServers[n];
+ }
+ break;
+ case AS_FAVORITES:
+ if (n >= 0 && n < MAX_OTHER_SERVERS)
+ {
+ return &cls.favoriteServers[n];
+ }
+ break;
+ }
+ return NULL;
+}
+
+#define FEATURED_MAXPING 200
+/*
+====================
+LAN_CompareServers
+====================
+*/
+static int LAN_CompareServers(int source, int sortKey, int sortDir, int s1, int s2)
+{
+ int res;
+ serverInfo_t *server1, *server2;
+
+ server1 = LAN_GetServerPtr(source, s1);
+ server2 = LAN_GetServerPtr(source, s2);
+ if (!server1 || !server2)
+ {
+ return 0;
+ }
+
+ // featured servers on top
+ if ((server1->label[0] && server1->ping <= FEATURED_MAXPING) ||
+ (server2->label[0] && server2->ping <= FEATURED_MAXPING))
+ {
+ res = Q_stricmpn(server1->label, server2->label, MAX_FEATLABEL_CHARS);
+ if (res) return -res;
+ }
+
+ res = 0;
+ switch (sortKey)
+ {
+ case SORT_HOST:
+ {
+ char hostName1[MAX_HOSTNAME_LENGTH];
+ char hostName2[MAX_HOSTNAME_LENGTH];
+ char *p;
+ int i;
+
+ for (p = server1->hostName, i = 0; *p != '\0'; p++)
+ {
+ if (Q_isalpha(*p)) hostName1[i++] = *p;
+ }
+ hostName1[i] = '\0';
+
+ for (p = server2->hostName, i = 0; *p != '\0'; p++)
+ {
+ if (Q_isalpha(*p)) hostName2[i++] = *p;
+ }
+ hostName2[i] = '\0';
+
+ res = Q_stricmp(hostName1, hostName2);
+ }
+ break;
+
+ case SORT_GAME:
+ res = Q_stricmp(server1->game, server2->game);
+ break;
+ case SORT_MAP:
+ res = Q_stricmp(server1->mapName, server2->mapName);
+ break;
+ case SORT_CLIENTS:
+ if (server1->clients < server2->clients)
+ {
+ res = -1;
+ }
+ else if (server1->clients > server2->clients)
+ {
+ res = 1;
+ }
+ else
+ {
+ res = 0;
+ }
+ break;
+ case SORT_PING:
+ if (server1->ping < server2->ping)
+ {
+ res = -1;
+ }
+ else if (server1->ping > server2->ping)
+ {
+ res = 1;
+ }
+ else
+ {
+ res = 0;
+ }
+ break;
+ }
+
+ if (sortDir)
+ {
+ if (res < 0) return 1;
+ if (res > 0) return -1;
+ return 0;
+ }
+ return res;
+}
+
+/*
+====================
+LAN_GetPingQueueCount
+====================
+*/
+static int LAN_GetPingQueueCount(void) { return (CL_GetPingQueueCount()); }
+/*
+====================
+LAN_ClearPing
+====================
+*/
+static void LAN_ClearPing(int n) { CL_ClearPing(n); }
+/*
+====================
+LAN_GetPing
+====================
+*/
+static void LAN_GetPing(int n, char *buf, int buflen, int *pingtime) { CL_GetPing(n, buf, buflen, pingtime); }
+/*
+====================
+LAN_GetPingInfo
+====================
+*/
+static void LAN_GetPingInfo(int n, char *buf, int buflen) { CL_GetPingInfo(n, buf, buflen); }
+/*
+====================
+LAN_MarkServerVisible
+====================
+*/
+static void LAN_MarkServerVisible(int source, int n, bool visible)
+{
+ if (n == -1)
+ {
+ int count = MAX_OTHER_SERVERS;
+ serverInfo_t *server = NULL;
+ switch (source)
+ {
+ case AS_LOCAL:
+ server = &cls.localServers[0];
+ break;
+ case AS_MPLAYER:
+ case AS_GLOBAL:
+ server = &cls.globalServers[0];
+ count = MAX_GLOBAL_SERVERS;
+ break;
+ case AS_FAVORITES:
+ server = &cls.favoriteServers[0];
+ break;
+ }
+ if (server)
+ {
+ for (n = 0; n < count; n++)
+ {
+ server[n].visible = visible;
+ }
+ }
+ }
+ else
+ {
+ switch (source)
+ {
+ case AS_LOCAL:
+ if (n >= 0 && n < MAX_OTHER_SERVERS)
+ {
+ cls.localServers[n].visible = visible;
+ }
+ break;
+ case AS_MPLAYER:
+ case AS_GLOBAL:
+ if (n >= 0 && n < MAX_GLOBAL_SERVERS)
+ {
+ cls.globalServers[n].visible = visible;
+ }
+ break;
+ case AS_FAVORITES:
+ if (n >= 0 && n < MAX_OTHER_SERVERS)
+ {
+ cls.favoriteServers[n].visible = visible;
+ }
+ break;
+ }
+ }
+}
+
+/*
+=======================
+LAN_ServerIsVisible
+=======================
+*/
+static bool LAN_ServerIsVisible(int source, int n)
+{
+ switch (source)
+ {
+ case AS_LOCAL:
+ if (n >= 0 && n < MAX_OTHER_SERVERS)
+ {
+ return cls.localServers[n].visible;
+ }
+ break;
+ case AS_MPLAYER:
+ case AS_GLOBAL:
+ if (n >= 0 && n < MAX_GLOBAL_SERVERS)
+ {
+ return cls.globalServers[n].visible;
+ }
+ break;
+ case AS_FAVORITES:
+ if (n >= 0 && n < MAX_OTHER_SERVERS)
+ {
+ return cls.favoriteServers[n].visible;
+ }
+ break;
+ }
+ return false;
+}
+
+/*
+=======================
+LAN_UpdateVisiblePings
+=======================
+*/
+static bool LAN_UpdateVisiblePings(int source) { return CL_UpdateVisiblePings_f(source); }
+/*
+====================
+LAN_GetServerStatus
+====================
+*/
+static bool LAN_GetServerStatus(char *serverAddress, char *serverStatus, int maxLen)
+{
+ return CL_ServerStatus(serverAddress, serverStatus, maxLen);
+}
+
+/*
+====================
+CL_GetGlConfig
+====================
+*/
+static void CL_GetGlconfig(glconfig_t *config) { *config = cls.glconfig; }
+/*
+====================
+GetConfigString
+====================
+*/
+static bool GetConfigString(int i, char *buf, int size)
+{
+ int offset;
+
+ if (i < 0 || i >= MAX_CONFIGSTRINGS) return false;
+
+ offset = cl.gameState.stringOffsets[i];
+ if (!offset)
+ {
+ if (size) buf[0] = '\0';
+ return false;
+ }
+
+ Q_strncpyz(buf, cl.gameState.stringData + offset, size);
+
+ return true;
+}
+
+/*
+====================
+FloatAsInt
+====================
+*/
+static int FloatAsInt(float f)
+{
+ floatint_t fi;
+ fi.f = f;
+ return fi.i;
+}
+
+static bool probingUI = false;
+
+/*
+====================
+CL_UISystemCalls
+
+The ui module is making a system call
+====================
+*/
+intptr_t CL_UISystemCalls(intptr_t *args)
+{
+ if (cls.uiInterface == 2)
+ {
+ if (args[0] >= UI_R_SETCLIPREGION && args[0] < UI_MEMSET)
+ {
+ if (args[0] < UI_S_STOPBACKGROUNDTRACK - 1)
+ {
+ args[0] += 1;
+ }
+ else if (args[0] < UI_S_STOPBACKGROUNDTRACK + 4)
+ {
+ args[0] += UI_PARSE_ADD_GLOBAL_DEFINE - UI_S_STOPBACKGROUNDTRACK + 1;
+ }
+ else if (args[0] >= UI_PARSE_ADD_GLOBAL_DEFINE + 4)
+ {
+ args[0] += UI_GETNEWS - UI_PARSE_ADD_GLOBAL_DEFINE - 5;
+ if (args[0] == UI_PARSE_SOURCE_FILE_AND_LINE || args[0] == UI_GETNEWS)
+ args[0] = UI_PARSE_SOURCE_FILE_AND_LINE - 1337 - args[0];
+ }
+ else
+ {
+ args[0] -= 4;
+ }
+ }
+
+ switch (args[0])
+ {
+ case UI_LAN_GETSERVERCOUNT:
+ case UI_LAN_GETSERVERADDRESSSTRING:
+ case UI_LAN_GETSERVERINFO:
+ case UI_LAN_MARKSERVERVISIBLE:
+ case UI_LAN_UPDATEVISIBLEPINGS:
+ case UI_LAN_RESETPINGS:
+ case UI_LAN_ADDSERVER:
+ case UI_LAN_REMOVESERVER:
+ case UI_LAN_GETSERVERPING:
+ case UI_LAN_SERVERISVISIBLE:
+ case UI_LAN_COMPARESERVERS:
+ if (args[1] == AS_GLOBAL)
+ {
+ args[1] = AS_LOCAL;
+ }
+ else if (args[1] == AS_LOCAL)
+ {
+ args[1] = AS_GLOBAL;
+ }
+ }
+ }
+
+ switch (args[0])
+ {
+ case UI_ERROR:
+ if (probingUI)
+ {
+ cls.uiInterface = 2;
+ return 0;
+ }
+ Com_Error(ERR_DROP, "%s", (const char *)VMA(1));
+ return 0;
+
+ case UI_PRINT:
+ Com_Printf("%s", (const char *)VMA(1));
+ return 0;
+
+ case UI_MILLISECONDS:
+ return Sys_Milliseconds();
+
+ case UI_CVAR_REGISTER:
+ Cvar_Register((vmCvar_t *)VMA(1), (const char *)VMA(2), (const char *)VMA(3), args[4]);
+ return 0;
+
+ case UI_CVAR_UPDATE:
+ Cvar_Update((vmCvar_t *)VMA(1));
+ return 0;
+
+ case UI_CVAR_SET:
+ Cvar_SetSafe((const char *)VMA(1), (const char *)VMA(2));
+ return 0;
+
+ case UI_CVAR_VARIABLEVALUE:
+ return FloatAsInt(Cvar_VariableValue((const char *)VMA(1)));
+
+ case UI_CVAR_VARIABLESTRINGBUFFER:
+ Cvar_VariableStringBuffer((const char *)VMA(1), (char *)VMA(2), args[3]);
+ return 0;
+
+ case UI_CVAR_SETVALUE:
+ Cvar_SetValueSafe((const char *)VMA(1), VMF(2));
+ return 0;
+
+ case UI_CVAR_RESET:
+ Cvar_Reset((const char *)VMA(1));
+ return 0;
+
+ case UI_CVAR_CREATE:
+ Cvar_Register(NULL, (const char *)VMA(1), (const char *)VMA(2), args[3]);
+ return 0;
+
+ case UI_CVAR_INFOSTRINGBUFFER:
+ Cvar_InfoStringBuffer(args[1], (char *)VMA(2), args[3]);
+ return 0;
+
+ case UI_ARGC:
+ return Cmd_Argc();
+
+ case UI_ARGV:
+ Cmd_ArgvBuffer(args[1], (char *)VMA(2), args[3]);
+ return 0;
+
+ case UI_CMD_EXECUTETEXT:
+ if (args[1] == EXEC_NOW)
+ {
+ if (!strncmp((const char *)VMA(2), "snd_restart", 11)
+ || !strncmp((const char *)VMA(2), "vid_restart", 11)
+ || !strncmp((const char *)VMA(2), "quit", 5))
+ {
+ Com_Printf(S_COLOR_YELLOW "turning EXEC_NOW '%.11s' into EXEC_INSERT\n", (const char *)VMA(2));
+ args[1] = EXEC_INSERT;
+ }
+ }
+
+ // TODO: Do this better
+ if (!strncmp((const char *)VMA(2), "checkForUpdate", 14))
+ {
+ CL_GetLatestRelease();
+ return 0;
+ }
+
+ Cbuf_ExecuteText(args[1], (const char *)VMA(2));
+ return 0;
+
+ case UI_FS_FOPENFILE:
+ return FS_FOpenFileByMode((const char *)VMA(1), (fileHandle_t *)VMA(2), (FS_Mode)args[3]);
+
+ case UI_FS_READ:
+ FS_Read(VMA(1), args[2], args[3]);
+ return 0;
+
+ case UI_FS_WRITE:
+ FS_Write(VMA(1), args[2], args[3]);
+ return 0;
+
+ case UI_FS_FCLOSEFILE:
+ FS_FCloseFile(args[1]);
+ return 0;
+
+ case UI_FS_GETFILELIST:
+ return FS_GetFileList((const char *)VMA(1), (const char *)VMA(2), (char *)VMA(3), args[4]);
+
+ case UI_FS_SEEK:
+ return FS_Seek((fileHandle_t)args[1], args[2], (FS_Origin)args[3]);
+
+ case UI_R_REGISTERMODEL:
+ return re.RegisterModel((const char *)VMA(1));
+
+ case UI_R_REGISTERSKIN:
+ return re.RegisterSkin((const char *)VMA(1));
+
+ case UI_R_REGISTERSHADERNOMIP:
+ return re.RegisterShaderNoMip((const char *)VMA(1));
+
+ case UI_R_CLEARSCENE:
+ re.ClearScene();
+ return 0;
+
+ case UI_R_ADDREFENTITYTOSCENE:
+ re.AddRefEntityToScene((const refEntity_t *)VMA(1));
+ return 0;
+
+ case UI_R_ADDPOLYTOSCENE:
+ re.AddPolyToScene(args[1], args[2], (const polyVert_t *)VMA(3), 1);
+ return 0;
+
+ case UI_R_ADDLIGHTTOSCENE:
+ re.AddLightToScene((const float *)VMA(1), VMF(2), VMF(3), VMF(4), VMF(5));
+ return 0;
+
+ case UI_R_RENDERSCENE:
+ re.RenderScene((const refdef_t *)VMA(1));
+ return 0;
+
+ case UI_R_SETCOLOR:
+ re.SetColor((const float *)VMA(1));
+ return 0;
+
+ case UI_R_SETCLIPREGION:
+ re.SetClipRegion((const float *)VMA(1));
+ return 0;
+
+ case UI_R_DRAWSTRETCHPIC:
+ re.DrawStretchPic(VMF(1), VMF(2), VMF(3), VMF(4), VMF(5), VMF(6), VMF(7), VMF(8), args[9]);
+ return 0;
+
+ case UI_R_MODELBOUNDS:
+ re.ModelBounds(args[1], (float *)VMA(2), (float *)VMA(3));
+ return 0;
+
+ case UI_UPDATESCREEN:
+ SCR_UpdateScreen();
+ return 0;
+
+ case UI_CM_LERPTAG:
+ re.LerpTag((orientation_t *)VMA(1), args[2], args[3], args[4], VMF(5), (const char *)VMA(6));
+ return 0;
+
+ case UI_S_REGISTERSOUND:
+ return S_RegisterSound((const char *)VMA(1), (bool)args[2]);
+
+ case UI_S_STARTLOCALSOUND:
+ S_StartLocalSound(args[1], args[2]);
+ return 0;
+
+ case UI_KEY_KEYNUMTOSTRINGBUF:
+ Key_KeynumToStringBuf(args[1], (char *)VMA(2), args[3]);
+ return 0;
+
+ case UI_KEY_GETBINDINGBUF:
+ Key_GetBindingBuf(args[1], (char *)VMA(2), args[3]);
+ return 0;
+
+ case UI_KEY_SETBINDING:
+ Key_SetBinding(args[1], (const char *)VMA(2));
+ return 0;
+
+ case UI_KEY_ISDOWN:
+ return Key_IsDown(args[1]);
+
+ case UI_KEY_GETOVERSTRIKEMODE:
+ return Key_GetOverstrikeMode();
+
+ case UI_KEY_SETOVERSTRIKEMODE:
+ Key_SetOverstrikeMode((bool)args[1]);
+ return 0;
+
+ case UI_KEY_CLEARSTATES:
+ Key_ClearStates();
+ return 0;
+
+ case UI_KEY_GETCATCHER:
+ return Key_GetCatcher();
+
+ case UI_KEY_SETCATCHER:
+ // don't allow the ui module to toggle the console
+ Key_SetCatcher((args[1] & ~KEYCATCH_CONSOLE) | (Key_GetCatcher() & KEYCATCH_CONSOLE));
+ return 0;
+
+ case UI_GETCLIPBOARDDATA:
+ ((char *)VMA(1))[0] = '\0';
+ return 0;
+
+ case UI_GETCLIENTSTATE:
+ GetClientState((uiClientState_t *)VMA(1));
+ return 0;
+
+ case UI_GETGLCONFIG:
+ CL_GetGlconfig((glconfig_t *)VMA(1));
+ return 0;
+
+ case UI_GETCONFIGSTRING:
+ return GetConfigString(args[1], (char *)VMA(2), args[3]);
+
+ case UI_LAN_LOADCACHEDSERVERS:
+ LAN_LoadCachedServers();
+ return 0;
+
+ case UI_LAN_SAVECACHEDSERVERS:
+ LAN_SaveServersToCache();
+ return 0;
+
+ case UI_LAN_ADDSERVER:
+ return LAN_AddServer(args[1], (const char *)VMA(2), (const char *)VMA(3));
+
+ case UI_LAN_REMOVESERVER:
+ LAN_RemoveServer(args[1], (const char *)VMA(2));
+ return 0;
+
+ case UI_LAN_GETPINGQUEUECOUNT:
+ return LAN_GetPingQueueCount();
+
+ case UI_LAN_CLEARPING:
+ LAN_ClearPing(args[1]);
+ return 0;
+
+ case UI_LAN_GETPING:
+ LAN_GetPing(args[1], (char *)VMA(2), args[3], (int *)VMA(4));
+ return 0;
+
+ case UI_LAN_GETPINGINFO:
+ LAN_GetPingInfo(args[1], (char *)VMA(2), args[3]);
+ return 0;
+
+ case UI_LAN_GETSERVERCOUNT:
+ return LAN_GetServerCount(args[1]);
+
+ case UI_LAN_GETSERVERADDRESSSTRING:
+ LAN_GetServerAddressString(args[1], args[2], (char *)VMA(3), args[4]);
+ return 0;
+
+ case UI_LAN_GETSERVERINFO:
+ LAN_GetServerInfo(args[1], args[2], (char *)VMA(3), args[4]);
+ return 0;
+
+ case UI_LAN_GETSERVERPING:
+ return LAN_GetServerPing(args[1], args[2]);
+
+ case UI_LAN_MARKSERVERVISIBLE:
+ LAN_MarkServerVisible(args[1], args[2], (bool)args[3]);
+ return 0;
+
+ case UI_LAN_SERVERISVISIBLE:
+ return LAN_ServerIsVisible(args[1], args[2]);
+
+ case UI_LAN_UPDATEVISIBLEPINGS:
+ return LAN_UpdateVisiblePings(args[1]);
+
+ case UI_LAN_RESETPINGS:
+ LAN_ResetPings(args[1]);
+ return 0;
+
+ case UI_LAN_SERVERSTATUS:
+ return LAN_GetServerStatus((char *)VMA(1), (char *)VMA(2), args[3]);
+
+ case UI_GETNEWS:
+ return GetNews((bool)args[1]);
+
+ case UI_LAN_COMPARESERVERS:
+ return LAN_CompareServers(args[1], args[2], args[3], args[4], args[5]);
+
+ case UI_MEMORY_REMAINING:
+ return Hunk_MemoryRemaining();
+
+ case UI_SET_PBCLSTATUS:
+ return 0;
+
+ case UI_R_REGISTERFONT:
+ re.RegisterFont((const char *)VMA(1), args[2], (fontInfo_t *)VMA(3));
+ return 0;
+
+ case UI_MEMSET:
+ ::memset(VMA(1), args[2], args[3]);
+ return 0;
+
+ case UI_MEMCPY:
+ ::memcpy(VMA(1), VMA(2), args[3]);
+ return 0;
+
+ case UI_STRNCPY:
+ strncpy((char *)VMA(1), (const char *)VMA(2), args[3]);
+ return args[1];
+
+ case UI_SIN:
+ return FloatAsInt(sin(VMF(1)));
+
+ case UI_COS:
+ return FloatAsInt(cos(VMF(1)));
+
+ case UI_ATAN2:
+ return FloatAsInt(atan2(VMF(1), VMF(2)));
+
+ case UI_SQRT:
+ return FloatAsInt(sqrt(VMF(1)));
+
+ case UI_FLOOR:
+ return FloatAsInt(floor(VMF(1)));
+
+ case UI_CEIL:
+ return FloatAsInt(ceil(VMF(1)));
+
+ case UI_PARSE_ADD_GLOBAL_DEFINE:
+ return Parse_AddGlobalDefine((char *)VMA(1));
+ case UI_PARSE_LOAD_SOURCE:
+ return Parse_LoadSourceHandle((char *)VMA(1));
+ case UI_PARSE_FREE_SOURCE:
+ return Parse_FreeSourceHandle(args[1]);
+ case UI_PARSE_READ_TOKEN:
+ return Parse_ReadTokenHandle(args[1], (pc_token_t *)VMA(2));
+ case UI_PARSE_SOURCE_FILE_AND_LINE:
+ return Parse_SourceFileAndLine(args[1], (char *)VMA(2), (int *)VMA(3));
+
+ case UI_S_STOPBACKGROUNDTRACK:
+ S_StopBackgroundTrack();
+ return 0;
+ case UI_S_STARTBACKGROUNDTRACK:
+ S_StartBackgroundTrack((const char *)VMA(1), (const char *)VMA(2));
+ return 0;
+
+ case UI_REAL_TIME:
+ return Com_RealTime((qtime_t *)VMA(1));
+
+ case UI_CIN_PLAYCINEMATIC:
+ return CIN_PlayCinematic((const char *)VMA(1), args[2], args[3], args[4], args[5], args[6]);
+
+ case UI_CIN_STOPCINEMATIC:
+ return CIN_StopCinematic(args[1]);
+
+ case UI_CIN_RUNCINEMATIC:
+ return CIN_RunCinematic(args[1]);
+
+ case UI_CIN_DRAWCINEMATIC:
+ CIN_DrawCinematic(args[1]);
+ return 0;
+
+ case UI_CIN_SETEXTENTS:
+ CIN_SetExtents(args[1], args[2], args[3], args[4], args[5]);
+ return 0;
+
+ case UI_R_REMAP_SHADER:
+ re.RemapShader((const char *)VMA(1), (const char *)VMA(2), (const char *)VMA(3));
+ return 0;
+
+ default:
+ Com_Error(ERR_DROP, "Bad UI system trap: %ld", (long int)args[0]);
+ }
+
+ return 0;
+}
+
+/*
+====================
+CL_ShutdownUI
+====================
+*/
+void CL_ShutdownUI(void)
+{
+ Key_SetCatcher(Key_GetCatcher() & ~KEYCATCH_UI);
+ cls.uiStarted = false;
+ if (!cls.ui)
+ {
+ return;
+ }
+ VM_Call(cls.ui, UI_SHUTDOWN);
+ VM_Free(cls.ui);
+ cls.ui = NULL;
+}
+
+/*
+====================
+CL_InitUI
+====================
+*/
+void CL_InitUI(void)
+{
+ // load the dll or bytecode
+ vmInterpret_t interpret = (vmInterpret_t)Cvar_VariableValue("vm_ui");
+ if (cl_connectedToPureServer)
+ {
+ // if sv_pure is set we only allow qvms to be loaded
+ if (interpret != VMI_COMPILED && interpret != VMI_BYTECODE) interpret = VMI_COMPILED;
+ }
+
+ cls.ui = VM_Create("ui", CL_UISystemCalls, interpret);
+ if (!cls.ui)
+ {
+ Com_Printf("Failed to find a valid UI vm. The following paths were searched:\n");
+ Cmd_ExecuteString("path /\n");
+ Com_Error(ERR_RECONNECT, "VM_Create on UI failed");
+ }
+
+ // sanity check
+ int v = VM_Call(cls.ui, UI_GETAPIVERSION);
+ if (v != UI_API_VERSION)
+ {
+ // Free cls.ui now, so UI_SHUTDOWN doesn't get called later.
+ VM_Free(cls.ui);
+ cls.ui = NULL;
+
+ cls.uiStarted = false;
+ Com_Error(ERR_DROP, "User Interface is version %d, expected %d", v, UI_API_VERSION);
+ }
+
+ // Probe UI interface
+ // Calls the GPP UI_CONSOLE_COMMAND (10), if GPP will return false 0. If a 1.1.0 qvm, will hit the error handler.
+ Cmd_TokenizeString("");
+ cls.uiInterface = 0;
+ probingUI = true;
+ if ( VM_Call(cls.ui, UI_CONSOLE_COMMAND, 0) < 0 )
+ cls.uiInterface = 2;
+
+ probingUI = false;
+
+ if (clc.state >= CA_CONNECTED && clc.state <= CA_ACTIVE &&
+ (clc.netchan.alternateProtocol == 2) != (cls.uiInterface == 2))
+ {
+ Com_Printf(S_COLOR_YELLOW "WARNING: %s protocol %i, but a ui module using the %s interface was found\n",
+ (clc.demoplaying ? "Demo was recorded using" : "Server uses"),
+ (clc.netchan.alternateProtocol == 0 ? PROTOCOL_VERSION : clc.netchan.alternateProtocol == 1 ? 70 : 69),
+ (cls.uiInterface == 2 ? "1.1" : "non-1.1"));
+ }
+
+ // init for this gamestate
+ VM_Call(cls.ui, UI_INIT, (clc.state >= CA_AUTHORIZING && clc.state < CA_ACTIVE));
+
+ // show where the ui folder was loaded from
+ Cmd_ExecuteString("which ui/\n");
+
+ clc.newsString[0] = '\0';
+}
+
+/*
+====================
+UI_GameCommand
+
+See if the current console command is claimed by the ui
+====================
+*/
+bool UI_GameCommand(void)
+{
+ if (!cls.ui) return false;
+
+ return (bool)VM_Call(cls.ui, UI_CONSOLE_COMMAND - (cls.uiInterface == 2 ? 2 : 0), cls.realtime);
+}
diff --git a/src/client/cl_updates.cpp b/src/client/cl_updates.cpp
new file mode 100644
index 0000000..0e6a237
--- /dev/null
+++ b/src/client/cl_updates.cpp
@@ -0,0 +1,476 @@
+#include "cl_updates.h"
+
+#include <libgen.h>
+#include <unistd.h>
+
+#include "nettle/rsa.h"
+#include "nettle/sha2.h"
+#include "rapidjson.h"
+#include "restclient/connection.h"
+#include "restclient/restclient.h"
+#include "semantic_version.h"
+
+#include <iostream>
+#include <fstream>
+#include <vector>
+#include <array>
+
+#include "qcommon/cvar.h"
+#include "qcommon/q_platform.h"
+#include "qcommon/q_shared.h"
+#include "qcommon/qcommon.h"
+#include "qcommon/unzip.h"
+#include "sys/sys_shared.h"
+
+#include "cl_rest.h"
+
+using namespace std;
+
+std::vector<std::string> release_package;
+static std::string granger_exe;
+static std::string granger_main_lua;
+static int nextCheckTime = 0;
+
+static const uint8_t release_key_pub[] = {
+ 0x28, 0x31, 0x30, 0x3a, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x2d, 0x6b,
+ 0x65, 0x79, 0x28, 0x39, 0x3a, 0x72, 0x73, 0x61, 0x2d, 0x70, 0x6b, 0x63,
+ 0x73, 0x31, 0x28, 0x31, 0x3a, 0x6e, 0x35, 0x31, 0x33, 0x3a, 0x00, 0xaa,
+ 0x4a, 0xe1, 0xdc, 0x08, 0xed, 0x90, 0x92, 0x66, 0x4a, 0xc2, 0x00, 0x43,
+ 0x3e, 0x89, 0x40, 0x4d, 0x11, 0xaa, 0x98, 0x2d, 0x55, 0x08, 0xec, 0x43,
+ 0x9f, 0x73, 0xfe, 0xc9, 0x67, 0xcb, 0xb0, 0x4a, 0x52, 0x89, 0x1d, 0x1a,
+ 0xc1, 0x86, 0x29, 0x26, 0x32, 0xb2, 0x34, 0xb6, 0xa5, 0x42, 0x16, 0x08,
+ 0x60, 0x8d, 0xf1, 0xe4, 0x45, 0xd4, 0x90, 0x2b, 0xfb, 0x98, 0xb2, 0x2e,
+ 0xa7, 0xa9, 0x79, 0xff, 0x5e, 0xaa, 0xb5, 0xd6, 0xf9, 0xf9, 0x2e, 0x67,
+ 0x5c, 0xb6, 0x6c, 0x36, 0x70, 0xf3, 0xea, 0x3f, 0xd8, 0xc8, 0xd0, 0xda,
+ 0xd8, 0xfb, 0x1b, 0x55, 0xe9, 0x69, 0x9d, 0x4a, 0xea, 0xf2, 0xb1, 0xdd,
+ 0x6b, 0xea, 0xb4, 0x99, 0xd7, 0x5e, 0xca, 0x5f, 0x34, 0xcf, 0xee, 0xbb,
+ 0xc6, 0x07, 0xa8, 0x3b, 0x59, 0xc3, 0xc1, 0x53, 0x15, 0x2b, 0xe4, 0x2f,
+ 0x17, 0x2d, 0x0c, 0x0f, 0x4e, 0xee, 0x2e, 0xef, 0x97, 0x9c, 0xff, 0x58,
+ 0xec, 0x6a, 0xf1, 0x12, 0x21, 0x5a, 0xc5, 0xbb, 0x6a, 0xef, 0xb0, 0xc8,
+ 0x4b, 0x7d, 0x33, 0xca, 0x53, 0x03, 0x31, 0x4a, 0xbd, 0x82, 0x01, 0x56,
+ 0x8f, 0x4e, 0x1d, 0x21, 0x7d, 0x2e, 0x8d, 0xaa, 0x0d, 0xcd, 0xe6, 0x19,
+ 0x09, 0x79, 0x76, 0x11, 0x16, 0xb5, 0xd2, 0xc1, 0x03, 0xdb, 0xaa, 0x11,
+ 0xd5, 0x89, 0x70, 0xc8, 0x71, 0xbf, 0x1a, 0xea, 0x9d, 0xd6, 0xaa, 0x77,
+ 0x97, 0xa4, 0xcc, 0x1d, 0xd6, 0x1c, 0x1a, 0x14, 0x6f, 0x35, 0x34, 0x6a,
+ 0x10, 0x32, 0x13, 0x33, 0x41, 0x27, 0xad, 0x1b, 0x59, 0x41, 0xff, 0xb0,
+ 0x1b, 0x64, 0x14, 0xea, 0x29, 0x44, 0x2b, 0x62, 0xdc, 0x63, 0xbc, 0xc7,
+ 0xf8, 0xac, 0xf7, 0x5c, 0x9d, 0x3c, 0xbd, 0x17, 0x84, 0xd9, 0x56, 0x09,
+ 0x44, 0x1b, 0xdd, 0x72, 0x16, 0xf6, 0x67, 0xc4, 0x49, 0xd3, 0x56, 0x74,
+ 0x42, 0x2d, 0x08, 0xe2, 0x0b, 0x0c, 0x40, 0x97, 0x62, 0x97, 0xb3, 0xd6,
+ 0x0c, 0x69, 0x04, 0x6a, 0xd7, 0x7b, 0xf0, 0xe1, 0x37, 0x0b, 0xe0, 0xee,
+ 0x6b, 0x2d, 0x79, 0xa0, 0x72, 0xa5, 0x75, 0x97, 0xb1, 0x6b, 0x01, 0xa2,
+ 0xb8, 0xe3, 0xc7, 0x3c, 0x10, 0x50, 0x59, 0xe4, 0xda, 0x9e, 0x8d, 0xe6,
+ 0x1b, 0xef, 0xa6, 0xae, 0xc4, 0xd7, 0xd8, 0x9b, 0x57, 0x4c, 0xa3, 0xd7,
+ 0x9f, 0x37, 0x17, 0x72, 0x7f, 0x60, 0xd6, 0x4c, 0x42, 0x14, 0x54, 0x66,
+ 0x88, 0xc6, 0xa2, 0x1e, 0xdf, 0x55, 0xfc, 0xef, 0xb9, 0x05, 0x63, 0x8a,
+ 0xc4, 0x5b, 0xe7, 0x45, 0x28, 0x0e, 0x55, 0x8e, 0xcc, 0x74, 0x25, 0xf2,
+ 0xea, 0x2a, 0x66, 0x51, 0xf2, 0x8f, 0x72, 0x1d, 0x97, 0x5a, 0xfb, 0x1c,
+ 0x95, 0x01, 0x93, 0x6b, 0x2b, 0xa5, 0x87, 0x5d, 0xd8, 0xea, 0xb2, 0x24,
+ 0x78, 0xb1, 0x58, 0x63, 0x47, 0x86, 0xed, 0xd9, 0xbc, 0xe9, 0xd2, 0xea,
+ 0xa8, 0x90, 0x5b, 0xe8, 0x82, 0x89, 0xa2, 0xe2, 0x52, 0x1f, 0x78, 0x00,
+ 0x93, 0x64, 0x54, 0xdb, 0x9b, 0x93, 0xc3, 0xee, 0xa2, 0x37, 0xab, 0x2e,
+ 0x7e, 0x8f, 0xec, 0xb1, 0x0b, 0x69, 0xad, 0x21, 0xda, 0xa9, 0xaf, 0xd2,
+ 0x22, 0x52, 0x2f, 0x1a, 0x6b, 0xb9, 0x21, 0x5e, 0xe9, 0x1a, 0xe1, 0x4c,
+ 0x33, 0x26, 0x46, 0xa1, 0xde, 0x52, 0xf3, 0x87, 0xf4, 0x8c, 0x99, 0xe4,
+ 0x5d, 0xe7, 0x5b, 0x76, 0x6c, 0xf5, 0xe1, 0xae, 0x5b, 0xaa, 0xbb, 0x3a,
+ 0xf4, 0x90, 0xa7, 0x5c, 0x5c, 0x72, 0xab, 0xb8, 0x71, 0xdc, 0x47, 0xb1,
+ 0x75, 0x0d, 0xc8, 0xcc, 0xfd, 0xce, 0x62, 0xbe, 0xe5, 0x9f, 0x85, 0x00,
+ 0x53, 0x35, 0x91, 0x44, 0x29, 0x5f, 0x64, 0x8d, 0x7c, 0x37, 0x32, 0x28,
+ 0x45, 0x9d, 0xa3, 0x49, 0xbb, 0xe3, 0xd3, 0x1e, 0x0a, 0xae, 0x53, 0xbd,
+ 0x66, 0x86, 0x8c, 0x4d, 0xa6, 0xd3, 0x85, 0x29, 0x28, 0x31, 0x3a, 0x65,
+ 0x33, 0x3a, 0x01, 0x00, 0x01, 0x29, 0x29, 0x29
+};
+static const size_t release_key_pub_len = 560;
+
+struct UpdateManager {
+
+ static void refresh();
+ static void download();
+ static void validate_signature(std::string path, std::string signature_path);
+ static void extract(std::string extract_path, std::string path);
+ static void execute();
+
+private:
+ static constexpr auto url = "https://api.github.com/repos/GrangerHub/tremulous/releases";
+ static constexpr auto package_name = RELEASE_PACKAGE_NAME;
+ static constexpr auto signature_name = RELEASE_SIGNATURE_NAME;
+ static constexpr auto granger_binary_name = GRANGER_EXE;
+};
+
+#define AU_ACT_NIL 0
+#define AU_ACT_GET 1
+#define AU_ACT_RUN 2
+
+void UpdateManager::refresh()
+{
+ auto currentTime = Sys_Milliseconds();
+
+ if ( nextCheckTime > currentTime )
+ return;
+
+ nextCheckTime = currentTime + 10000;
+
+ Cvar_SetValue("ui_autoupdate_action", 0);
+ Cvar_Set("cl_latestDownload", "");
+ Cvar_Set("cl_latestRelease", "");
+ Cvar_Set("cl_latestSignature", "");
+
+ RestClient::Response r = RestClient::get(url);
+ if ( r.code != 200 )
+ {
+ std::string msg { va("Server did not return OK status code: %d", r.code) };
+
+ Cvar_Set("cl_latestRelease",
+ S_COLOR_RED "ERROR:\n"
+ S_COLOR_WHITE "Server did not return OK status code");
+
+ return;
+ }
+
+ rapidjson::Document d;
+ d.Parse( r.body.c_str() );
+
+ rapidjson::Value &release = d[0];
+ std::string txt;
+
+ semver::v2::Version current { PRODUCT_VERSION + 1 };
+ semver::v2::Version latest { release["tag_name"].GetString() + 1 };
+
+ if ( current == latest )
+ {
+ txt += "You are up to date\n\n";
+ }
+ else if ( current < latest )
+ {
+ txt += "A new release is available!\n\n";
+ }
+ else if ( current > latest )
+ {
+ txt += "Wow! You are ahead of the release.\n\n";
+ }
+
+ txt += release["tag_name"].GetString();
+ if (release["prerelease"].IsTrue())
+ txt += S_COLOR_YELLOW " (Prerelease)";
+
+ txt += '\n';
+ txt += S_COLOR_CYAN "Released:" S_COLOR_WHITE;
+ txt += release["published_at"].GetString();
+
+ txt += '\n';
+ txt += S_COLOR_RED "Release Notes:\n" S_COLOR_WHITE;
+
+ if (!release["body"].IsNull())
+ {
+ txt += '\n';
+ txt += release["body"].GetString();
+ }
+
+ rapidjson::Value &assets = release["assets"];
+ for ( rapidjson::SizeType i = 0; i < assets.Size(); ++i )
+ {
+ auto &a = assets[i];
+ std::string name = a["name"].GetString();
+
+ if ( name == package_name )
+ {
+ std::string dl = a["browser_download_url"].GetString();
+ Cvar_Set("cl_latestDownload", dl.c_str());
+ Cvar_Set("cl_latestPackage", package_name);
+ Cvar_Set("cl_latestRelease", txt.c_str());
+ Cvar_SetValue("ui_autoupdate_action", AU_ACT_GET);
+ }
+ else if ( name == signature_name )
+ {
+ std::string dl = a["browser_download_url"].GetString();
+ Cvar_Set("cl_latestSignature", dl.c_str());
+ }
+ }
+}
+
+void UpdateManager::extract(std::string extract_path, std::string path)
+{
+ // Extract the release package
+ auto z = unzOpen(path.c_str());
+ assert( z != nullptr );
+
+ unz_global_info zi;
+ unzGetGlobalInfo(z, &zi);
+
+ // Iterate through all files in the package
+ unzGoToFirstFile(z);
+ for (int i = 0; i < zi.number_entry; ++i)
+ {
+ unz_file_info fi;
+ char filename[256] = ""; // 256 == MAX_ZPATH
+ int err;
+
+ err = unzGetCurrentFileInfo(z, &fi, filename, sizeof(filename),
+ nullptr, 0, nullptr, 0);
+ assert( err == UNZ_OK ); // != OK means corrupt archive
+
+ std::string fn = { filename };
+
+ // FIXME stop doing dumb string stuff
+ std::string fullpath = extract_path;
+ fullpath += PATH_SEP;
+ fullpath += fn;
+ release_package.emplace_back(fn);
+
+ // Bad assumption of a directory?
+ if ( !fi.compressed_size && !fi.uncompressed_size )
+ {
+ Com_DPrintf(S_COLOR_CYAN"ARCHIVED DIR: "
+ S_COLOR_WHITE"%s\n",
+ fn.c_str());
+
+ MakeDir(extract_path, fn);
+
+ unzGoToNextFile(z);
+ continue;
+ }
+
+ // Must be a file
+ Com_DPrintf(S_COLOR_CYAN"ARCHIVED FILE: "
+ S_COLOR_WHITE"%s (%lu/%lu)\n",
+ filename,
+ fi.compressed_size,
+ fi.uncompressed_size);
+
+ if ( fn.rfind(granger_binary_name) != std::string::npos )
+ granger_exe = fn;
+
+ else if ( fn.rfind("main.lua") != std::string::npos )
+ granger_main_lua = fn;
+
+ err = unzOpenCurrentFile(z);
+ assert( err == UNZ_OK );
+
+ // FIXME cleanup all this shit string stuff
+ std::string path = extract_path;
+ path += PATH_SEP;
+ path += fn;
+
+ Com_DPrintf(S_COLOR_YELLOW"Extracted FILE: "
+ S_COLOR_WHITE"%s\n",
+ path.c_str());
+
+ std::fstream dl;
+ dl.open(path, fstream::out|ios::binary);
+
+ // Extract the release package
+ size_t numwrote;
+ do {
+ // Extract 16k at a time
+ unsigned blocksiz = 16384;
+
+ if ( blocksiz > fi.uncompressed_size - numwrote )
+ blocksiz = fi.uncompressed_size - numwrote;
+
+ uint8_t block[blocksiz];
+ unzReadCurrentFile(z, static_cast<void*>(&block), blocksiz);
+
+ dl.write((const char*)block, blocksiz);
+ numwrote += blocksiz;
+
+ } while ( fi.uncompressed_size > numwrote );
+ dl.close();
+
+ unzCloseCurrentFile(z);
+ unzGoToNextFile(z);
+ }
+
+ unzClose(z);
+}
+
+void UpdateManager::download()
+{
+ cvar_t *cl_enableSignatureCheck = Cvar_Get("cl_enableSignatureCheck", "0", CVAR_ARCHIVE | CVAR_PROTECTED);
+
+ // Check for and download signature
+ std::string signature_url = Cvar_VariableString("cl_latestSignature");
+ if ( signature_url.empty() && cl_enableSignatureCheck->integer)
+ throw exception();
+
+ // Download the latest release
+ auto url = Cvar_VariableString("cl_latestDownload");
+ auto r = RestClient::get(url);
+ if ( r.code != 200 )
+ throw exception();
+
+ Com_DPrintf(S_COLOR_CYAN "URL: " S_COLOR_WHITE "%s\n", url);
+
+ // FIXME Cleanup this string bullshit
+ std::string extract_path, signature_path, path { Cvar_VariableString("fs_homepath") };
+ path += PATH_SEP;
+ extract_path = path;
+ signature_path = path + signature_name;
+ path += package_name;
+
+ MakeDir(extract_path, "extract");
+ extract_path += "extract";
+
+ Com_DPrintf(S_COLOR_CYAN"PATH: " S_COLOR_WHITE "%s\n",
+ path.c_str());
+
+ // Write the release package to disk
+ {
+ std::fstream dl;
+ dl.open( path, fstream::out|ios::binary );
+ dl.write( r.body.c_str(), r.body.length() );
+ dl.close();
+ }
+
+ if (cl_enableSignatureCheck->integer)
+ {
+ auto r = RestClient::get(signature_url);
+ if ( r.code != 200 )
+ throw exception();
+
+ std::fstream dl;
+ dl.open( signature_path, fstream::out|ios::binary );
+ dl.write( r.body.c_str(), r.body.length() );
+ dl.close();
+
+ // Validate the signature of the package if enabled
+ UpdateManager::validate_signature(path, signature_path);
+ }
+
+ // Extract the contents of the release package
+ UpdateManager::extract(extract_path, path);
+
+ // Delete the release package
+ unlink(path.c_str());
+
+ Cvar_SetValue("au_autoupdate_action", AU_ACT_RUN);
+}
+
+void UpdateManager::validate_signature(std::string path, std::string signature_path)
+{
+ // Load public key
+ rsa_public_key public_key;
+ rsa_public_key_init(&public_key);
+ rsa_keypair_from_sexp(&public_key, NULL, 0, release_key_pub_len, release_key_pub);
+
+ auto min = [](auto a, auto b) { return a < b ? a : b; };
+
+ // Read in signature
+ mpz_t signature;
+ {
+ std::ifstream f(signature_path, ios::binary);
+ f.seekg (0, f.end);
+ size_t length = f.tellg();
+ f.seekg (0, f.beg);
+
+ std::vector<char> buffer(512, 0);
+ f.read(buffer.data(), min(length, buffer.size()));
+ nettle_mpz_init_set_str_256_u(signature, f.gcount(), (uint8_t *)buffer.data());
+ }
+
+ // Hash file
+ sha256_ctx ctx;
+ sha256_init(&ctx);
+ {
+ std::ifstream f(path, ios::binary);
+ f.seekg (0, f.end);
+ size_t length = f.tellg();
+ f.seekg (0, f.beg);
+
+ std::vector<unsigned char> buffer(16384, 0);
+ while (f.read((char *)buffer.data(), min(length, buffer.size()))) {
+ auto nbytes = f.gcount();
+ sha256_update(&ctx, nbytes, buffer.data());
+ length -= nbytes;
+ if (length <= 0) {
+ break;
+ }
+ }
+ }
+
+ // Verify signature
+ if (!rsa_sha256_verify(&public_key, &ctx, signature)) {
+ rsa_public_key_clear(&public_key);
+ mpz_clear(signature);
+ Com_Error( ERR_DROP, "Update signature was not verified\n" );
+ return;
+ }
+
+ unlink(signature_path.c_str());
+ rsa_public_key_clear(&public_key);
+ mpz_clear(signature);
+}
+
+extern char** environ;
+
+class FailInstaller : public std::exception {
+ std::string msg;
+public:
+ FailInstaller(int e)
+ { msg = strerror(e); }
+
+ virtual const char* what() throw()
+ { return msg.c_str(); }
+};
+
+void UpdateManager::execute()
+{
+ granger_exe = "";
+ granger_main_lua = "";
+
+ if ( granger_exe == "" || granger_main_lua == "" )
+ {
+ for ( auto const& i : release_package )
+ {
+ Com_Printf(S_COLOR_RED " Unlink %s\n", i.c_str());
+ unlink(i.c_str());
+ }
+
+ Com_Error( ERR_DROP, "Missing Granger or GrangerScript\n" );
+ return;
+ }
+
+ std::array<const char*, 4> argv{};
+
+ char* tmp = strdup(granger_exe.c_str());
+ const char* dir = dirname(tmp);
+
+ argv[0] = granger_exe.c_str();
+ argv[1] = va(" -C %s", dir);
+ argv[2] = granger_main_lua.c_str();
+
+ // Dump the details
+ Com_Printf(S_COLOR_YELLOW"Launching "
+ S_COLOR_WHITE"%s %s %s\n",
+ argv[0], argv[1], argv[2]);
+
+#ifndef _WIN32
+ // Fork solely to try cleanup SDL2/GL states
+ auto pid = fork();
+ if (pid == -1)
+ throw FailInstaller(errno);
+
+ if (pid == 0)
+ {
+ execve(argv[0],
+ const_cast<char **>(argv.data()),
+ environ);
+
+ throw FailInstaller(errno);
+ }
+ else
+ {
+ Engine_Exit("");
+ }
+#else
+ // FIXME Dirty exit on Windows...
+ execve(argv[0],
+ const_cast<char **>(argv.data()),
+ environ);
+
+ throw FailInstaller(errno);
+#endif
+}
+
+void CL_GetLatestRelease() { UpdateManager::refresh(); }
+void CL_DownloadRelease() { UpdateManager::download(); }
+void CL_ExecuteInstaller() { UpdateManager::execute(); }
diff --git a/src/client/cl_updates.h b/src/client/cl_updates.h
new file mode 100644
index 0000000..f85c64a
--- /dev/null
+++ b/src/client/cl_updates.h
@@ -0,0 +1,8 @@
+#ifndef CL_UPDATES
+#define CL_UPDATES
+
+void CL_GetLatestRelease();
+void CL_DownloadRelease();
+void CL_ExecuteInstaller();
+
+#endif
diff --git a/src/client/client.h b/src/client/client.h
new file mode 100644
index 0000000..2474a3d
--- /dev/null
+++ b/src/client/client.h
@@ -0,0 +1,716 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
+
+This file is part of Tremulous.
+
+Tremulous 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.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+// client.h -- primary header for client
+
+#ifndef _CLIENT_H_
+#define _CLIENT_H_
+
+#ifdef USE_VOIP
+#include <opus.h>
+#endif
+
+#include "cgame/cg_public.h"
+#include "qcommon/alternatePlayerstate.h"
+#include "qcommon/cmd.h"
+#include "qcommon/crypto.h"
+#include "qcommon/cvar.h"
+#include "qcommon/files.h"
+#include "qcommon/huffman.h"
+#include "qcommon/msg.h"
+#include "qcommon/net.h"
+#include "qcommon/q_shared.h"
+#include "qcommon/qcommon.h"
+#include "qcommon/vm.h"
+#include "renderercommon/tr_public.h"
+#include "sys/sys_shared.h"
+#include "ui/ui_public.h"
+
+#include "cl_curl.h"
+#include "keys.h"
+#include "snd_public.h"
+
+struct alternateEntityState_t {
+ int number; // entity index
+ int eType; // entityType_t
+ int eFlags;
+
+ trajectory_t pos; // for calculating position
+ trajectory_t apos; // for calculating angles
+
+ int time;
+ int time2;
+
+ vec3_t origin;
+ vec3_t origin2;
+
+ vec3_t angles;
+ vec3_t angles2;
+
+ int otherEntityNum; // shotgun sources, etc
+ int otherEntityNum2;
+
+ int groundEntityNum; // ENTITYNUM_NONE = in air
+
+ int constantLight; // r + (g<<8) + (b<<16) + (intensity<<24)
+ int loopSound; // constantly loop this sound
+
+ int modelindex;
+ int modelindex2;
+ int clientNum; // 0 to (MAX_CLIENTS - 1), for players and corpses
+ int frame;
+
+ int solid; // for client side prediction, trap_linkentity sets this properly
+
+ int event; // impulse events -- muzzle flashes, footsteps, etc
+ int eventParm;
+
+ // for players
+ int misc; // bit flags
+ int weapon; // determines weapon and flash model, etc
+ int legsAnim; // mask off ANIM_TOGGLEBIT
+ int torsoAnim; // mask off ANIM_TOGGLEBIT
+
+ int generic1;
+};
+
+struct alternateSnapshot_t {
+ int snapFlags; // SNAPFLAG_RATE_DELAYED, etc
+ int ping;
+
+ int serverTime; // server time the message is valid for (in msec)
+
+ byte areamask[MAX_MAP_AREA_BYTES]; // portalarea visibility bits
+
+ alternatePlayerState_t ps; // complete information about the current player at this time
+
+ int numEntities; // all of the entities that need to be presented
+ alternateEntityState_t entities[MAX_ENTITIES_IN_SNAPSHOT]; // at the time of this snapshot
+
+ int numServerCommands; // text based server commands to execute when this
+ int serverCommandSequence; // snapshot becomes current
+};
+
+// file full of random crap that gets used to create cl_guid
+#define QKEY_FILE "qkey"
+#define QKEY_SIZE 2048
+
+#define RETRANSMIT_TIMEOUT 3000 // time between connection packet retransmits
+
+// snapshots are a view of the server at a given time
+struct clSnapshot_t {
+ bool valid; // cleared if delta parsing was invalid
+ int snapFlags; // rate delayed and dropped commands
+
+ int serverTime; // server time the message is valid for (in msec)
+
+ int messageNum; // copied from netchan->incoming_sequence
+ int deltaNum; // messageNum the delta is from
+ int ping; // time from when cmdNum-1 was sent to time packet was reeceived
+ byte areamask[MAX_MAP_AREA_BYTES]; // portalarea visibility bits
+
+ int cmdNum; // the next cmdNum the server is expecting
+ playerState_t ps; // complete information about the current player at this time
+ alternatePlayerState_t alternatePs; // complete information about the current player at this time
+
+ int numEntities; // all of the entities that need to be presented
+ int parseEntitiesNum; // at the time of this snapshot
+
+ int serverCommandNum; // execute all commands up to this before
+ // making the snapshot current
+};
+
+/*
+=============================================================================
+
+the clientActive_t structure is wiped completely at every
+new gamestate_t, potentially several times during an established connection
+
+=============================================================================
+*/
+
+struct outPacket_t {
+ int p_cmdNumber; // cl.cmdNumber when packet was sent
+ int p_serverTime; // usercmd->serverTime when packet was sent
+ int p_realtime; // cls.realtime when packet was sent
+};
+
+// the parseEntities array must be large enough to hold PACKET_BACKUP frames of
+// entities, so that when a delta compressed message arives from the server
+// it can be un-deltad from the original
+#define MAX_PARSE_ENTITIES (PACKET_BACKUP * MAX_SNAPSHOT_ENTITIES)
+
+extern int g_console_field_width;
+
+struct clientActive_t {
+ int timeoutcount; // it requres several frames in a timeout condition
+ // to disconnect, preventing debugging breaks from
+ // causing immediate disconnects on continue
+ clSnapshot_t snap; // latest received from server
+
+ int serverTime; // may be paused during play
+ int oldServerTime; // to prevent time from flowing bakcwards
+ int oldFrameServerTime; // to check tournament restarts
+ int serverTimeDelta; // cl.serverTime = cls.realtime + cl.serverTimeDelta
+ // this value changes as net lag varies
+ bool extrapolatedSnapshot; // set if any cgame frame has been forced to extrapolate
+ // cleared when CL_AdjustTimeDelta looks at it
+ bool newSnapshots; // set on parse of any valid packet
+
+ gameState_t gameState; // configstrings
+ char mapname[MAX_QPATH]; // extracted from CS_SERVERINFO
+
+ int parseEntitiesNum; // index (not anded off) into cl_parse_entities[]
+
+ int mouseDx[2], mouseDy[2]; // added to by mouse events
+ int mouseIndex;
+ int joystickAxis[MAX_JOYSTICK_AXIS]; // set by joystick events
+
+ // cgame communicates a few values to the client system
+ int cgameUserCmdValue; // current weapon to add to usercmd_t
+ float cgameSensitivity;
+
+ // cmds[cmdNumber] is the predicted command, [cmdNumber-1] is the last
+ // properly generated command
+ usercmd_t cmds[CMD_BACKUP]; // each mesage will send several old cmds
+ int cmdNumber; // incremented each frame, because multiple
+ // frames may need to be packed into a single packet
+
+ outPacket_t outPackets[PACKET_BACKUP]; // information about each packet we have sent out
+
+ // the client maintains its own idea of view angles, which are
+ // sent to the server each frame. It is cleared to 0 upon entering each level.
+ // the server sends a delta each frame which is added to the locally
+ // tracked view angles to account for standing on rotating objects,
+ // and teleport direction changes
+ vec3_t viewangles;
+
+ int serverId; // included in each client message so the server
+ // can tell if it is for a prior map_restart
+ // big stuff at end of structure so most offsets are 15 bits or less
+ clSnapshot_t snapshots[PACKET_BACKUP];
+
+ entityState_t entityBaselines[MAX_GENTITIES]; // for delta compression when not in previous frame
+
+ entityState_t parseEntities[MAX_PARSE_ENTITIES];
+};
+
+extern clientActive_t cl;
+
+/*
+=============================================================================
+
+the clientConnection_t structure is wiped when disconnecting from a server,
+either to go to a full screen console, play a demo, or connect to a different server
+
+A connection can be to either a server through the network layer or a
+demo through a file.
+
+=============================================================================
+*/
+
+#define MAX_TIMEDEMO_DURATIONS 4096
+
+struct clientConnection_t {
+ connstate_t state; // connection status
+
+ int clientNum;
+ int lastPacketSentTime; // for retransmits during connection
+ int lastPacketTime; // for timeouts
+
+ char servername[MAX_OSPATH]; // name of server from original connect (used by reconnect)
+ netadr_t serverAddress;
+ int connectTime; // for connection retransmits
+ int connectPacketCount; // for display on connection dialog
+ char serverMessage[MAX_STRING_TOKENS]; // for display on connection dialog
+
+ int challenge; // from the server to use for connecting
+ char challenge2[33];
+ bool sendSignature;
+ int checksumFeed; // from the server for checksum calculations
+
+ // these are our reliable messages that go to the server
+ int reliableSequence;
+ int reliableAcknowledge; // the last one the server has executed
+ char reliableCommands[MAX_RELIABLE_COMMANDS][MAX_STRING_CHARS];
+
+ // server message (unreliable) and command (reliable) sequence
+ // numbers are NOT cleared at level changes, but continue to
+ // increase as long as the connection is valid
+
+ // message sequence is used by both the network layer and the
+ // delta compression layer
+ int serverMessageSequence;
+
+ // reliable messages received from server
+ int serverCommandSequence;
+ int lastExecutedServerCommand; // last server command grabbed or executed with CL_GetServerCommand
+ char serverCommands[MAX_RELIABLE_COMMANDS][MAX_STRING_CHARS];
+
+ // file transfer from server
+ fileHandle_t download;
+ char downloadTempName[MAX_OSPATH];
+ char downloadName[MAX_OSPATH];
+
+ // XXX Refactor this -vjr
+ bool cURLEnabled;
+ bool cURLUsed;
+ bool cURLDisconnected;
+
+ char downloadURL[MAX_OSPATH];
+ CURL *downloadCURL;
+ CURLM *downloadCURLM;
+ bool activeCURLNotGameRelated;
+
+ int sv_allowDownload;
+ char sv_dlURL[MAX_CVAR_VALUE_STRING];
+ int downloadNumber;
+ int downloadBlock; // block we are waiting for
+ int downloadCount; // how many bytes we got
+ int downloadSize; // how many bytes we got
+ char downloadList[MAX_INFO_STRING]; // list of paks we need to download
+ bool downloadRestart; // if true, we need to do another FS_Restart because we downloaded a pak
+ char newsString[MAX_NEWS_STRING];
+
+ // demo information
+ char demoName[MAX_QPATH];
+ bool spDemoRecording;
+ bool demorecording;
+ bool demoplaying;
+ bool demowaiting; // don't record until a non-delta message is received
+ bool firstDemoFrameSkipped;
+ fileHandle_t demofile;
+
+ int timeDemoFrames; // counter of rendered frames
+ int timeDemoStart; // cls.realtime before first frame
+ int timeDemoBaseTime; // each frame will be at this time + frameNum * 50
+ int timeDemoLastFrame; // time the last frame was rendered
+ int timeDemoMinDuration; // minimum frame duration
+ int timeDemoMaxDuration; // maximum frame duration
+ unsigned char timeDemoDurations[MAX_TIMEDEMO_DURATIONS]; // log of frame durations
+
+ float aviVideoFrameRemainder;
+ float aviSoundFrameRemainder;
+
+#ifdef USE_VOIP
+ bool voipEnabled;
+ bool voipCodecInitialized;
+
+ // incoming data...
+ // !!! FIXME: convert from parallel arrays to array of a struct.
+ OpusDecoder *opusDecoder[MAX_CLIENTS];
+ byte voipIncomingGeneration[MAX_CLIENTS];
+ int voipIncomingSequence[MAX_CLIENTS];
+ float voipGain[MAX_CLIENTS];
+ bool voipIgnore[MAX_CLIENTS];
+ bool voipMuteAll;
+
+ // outgoing data...
+ // if voipTargets[i / 8] & (1 << (i % 8)),
+ // then we are sending to clientnum i.
+ uint8_t voipTargets[(MAX_CLIENTS + 7) / 8];
+ uint8_t voipFlags;
+ OpusEncoder *opusEncoder;
+ int voipOutgoingDataSize;
+ int voipOutgoingDataFrames;
+ int voipOutgoingSequence;
+ byte voipOutgoingGeneration;
+ byte voipOutgoingData[1024];
+ float voipPower;
+#endif
+
+ // big stuff at end of structure so most offsets are 15 bits or less
+ netchan_t netchan;
+};
+
+extern clientConnection_t clc;
+
+/*
+==================================================================
+
+the clientStatic_t structure is never wiped, and is used even when
+no client connection is active at all
+(except when CL_Shutdown is called)
+
+==================================================================
+*/
+
+struct ping_t {
+ netadr_t adr;
+ int start;
+ int time;
+ char info[MAX_INFO_STRING];
+};
+
+#define MAX_FEATLABEL_CHARS 32
+struct serverInfo_t {
+ netadr_t adr;
+ char hostName[MAX_HOSTNAME_LENGTH];
+ char mapName[MAX_NAME_LENGTH];
+ char game[MAX_NAME_LENGTH];
+ char label[MAX_FEATLABEL_CHARS]; // for featured servers, NULL otherwise
+ int netType;
+ int gameType;
+ int clients;
+ int maxClients;
+ int minPing;
+ int maxPing;
+ int ping;
+ bool visible;
+};
+
+struct clientStatic_t {
+ // when the server clears the hunk, all of these must be restarted
+ bool rendererStarted;
+ bool soundStarted;
+ bool soundRegistered;
+ bool uiStarted;
+ bool cgameStarted;
+
+ int framecount;
+ int frametime; // msec since last frame
+
+ int realtime; // ignores pause
+ int realFrametime; // ignoring pause, so console always works
+
+ // master server sequence information
+ int numAlternateMasterPackets[3];
+ unsigned int receivedAlternateMasterPackets[3]; // bitfield
+
+ int numlocalservers;
+ serverInfo_t localServers[MAX_OTHER_SERVERS];
+
+ int numglobalservers;
+ serverInfo_t globalServers[MAX_GLOBAL_SERVERS];
+ // additional global servers
+ int numGlobalServerAddresses;
+ netadr_t globalServerAddresses[MAX_GLOBAL_SERVERS];
+
+ int numfavoriteservers;
+ serverInfo_t favoriteServers[MAX_OTHER_SERVERS];
+
+ int pingUpdateSource; // source currently pinging or updating
+
+ // update server info
+ netadr_t updateServer;
+ char updateChallenge[MAX_TOKEN_CHARS];
+ char updateInfoString[MAX_INFO_STRING];
+
+ netadr_t authorizeServer;
+
+ // rendering info
+ glconfig_t glconfig;
+ qhandle_t charSetShader;
+ qhandle_t whiteShader;
+ qhandle_t consoleShader;
+
+ vm_t *cgame;
+ int cgInterface; // 0 == gpp, 2 == 1.1.0
+
+ vm_t *ui;
+ int uiInterface;
+
+ struct {
+ struct rsa_public_key public_key;
+ struct rsa_private_key private_key;
+ } rsa;
+};
+
+extern clientStatic_t cls;
+
+extern char cl_oldGame[MAX_QPATH];
+extern bool cl_oldGameSet;
+
+//=============================================================================
+
+extern refexport_t re; // interface to refresh .dll
+
+//
+// cvars
+//
+extern cvar_t *cl_nodelta;
+extern cvar_t *cl_debugMove;
+extern cvar_t *cl_noprint;
+extern cvar_t *cl_timegraph;
+extern cvar_t *cl_maxpackets;
+extern cvar_t *cl_packetdup;
+extern cvar_t *cl_shownet;
+extern cvar_t *cl_showSend;
+extern cvar_t *cl_timeNudge;
+extern cvar_t *cl_showTimeDelta;
+extern cvar_t *cl_freezeDemo;
+
+extern cvar_t *cl_yawspeed;
+extern cvar_t *cl_pitchspeed;
+extern cvar_t *cl_run;
+extern cvar_t *cl_anglespeedkey;
+
+extern cvar_t *cl_sensitivity;
+extern cvar_t *cl_freelook;
+
+extern cvar_t *cl_mouseAccel;
+extern cvar_t *cl_mouseAccelOffset;
+extern cvar_t *cl_mouseAccelStyle;
+extern cvar_t *cl_showMouseRate;
+
+extern cvar_t *m_pitch;
+extern cvar_t *m_yaw;
+extern cvar_t *m_forward;
+extern cvar_t *m_side;
+extern cvar_t *m_filter;
+
+extern cvar_t *j_pitch;
+extern cvar_t *j_yaw;
+extern cvar_t *j_forward;
+extern cvar_t *j_side;
+extern cvar_t *j_up;
+extern cvar_t *j_pitch_axis;
+extern cvar_t *j_yaw_axis;
+extern cvar_t *j_forward_axis;
+extern cvar_t *j_side_axis;
+extern cvar_t *j_up_axis;
+
+extern cvar_t *cl_timedemo;
+extern cvar_t *cl_aviFrameRate;
+extern cvar_t *cl_aviMotionJpeg;
+
+extern cvar_t *cl_activeAction;
+
+extern cvar_t *cl_allowDownload;
+extern cvar_t *cl_downloadMethod;
+extern cvar_t *cl_conXOffset;
+extern cvar_t *cl_inGameVideo;
+
+extern cvar_t *cl_lanForcePackets;
+extern cvar_t *cl_autoRecordDemo;
+
+extern cvar_t *cl_clantag;
+
+extern cvar_t *cl_consoleKeys;
+
+#ifdef USE_MUMBLE
+extern cvar_t *cl_useMumble;
+extern cvar_t *cl_mumbleScale;
+#endif
+
+#ifdef USE_VOIP
+// cl_voipSendTarget is a string: "all" to broadcast to everyone, "none" to
+// send to no one, or a comma-separated list of client numbers:
+// "0,7,2,23" ... an empty string is treated like "all".
+extern cvar_t *cl_voipUseVAD;
+extern cvar_t *cl_voipVADThreshold;
+extern cvar_t *cl_voipSend;
+extern cvar_t *cl_voipSendTarget;
+extern cvar_t *cl_voipGainDuringCapture;
+extern cvar_t *cl_voipCaptureMult;
+extern cvar_t *cl_voipShowMeter;
+extern cvar_t *cl_voip;
+
+// 20ms at 48k
+#define VOIP_MAX_FRAME_SAMPLES (20 * 48)
+
+// 3 frame is 60ms of audio, the max opus will encode at once
+#define VOIP_MAX_PACKET_FRAMES 3
+#define VOIP_MAX_PACKET_SAMPLES (VOIP_MAX_FRAME_SAMPLES * VOIP_MAX_PACKET_FRAMES)
+#endif
+
+extern cvar_t *cl_rsaAuth;
+
+//=================================================
+
+//
+// cl_main
+//
+
+void CL_Init(void);
+void CL_AddReliableCommand(const char *cmd, bool isDisconnectCmd);
+
+void CL_StartHunkUsers(bool rendererOnly);
+
+void CL_Disconnect_f(void);
+void CL_NextDemo(void);
+void CL_ReadDemoMessage(void);
+demoState_t CL_DemoState(void);
+int CL_DemoPos(void);
+void CL_DemoName(char *buffer, int size);
+void CL_StopRecord_f(void);
+
+void CL_InitDownloads(void);
+void CL_NextDownload(void);
+
+void CL_GetPing(int n, char *buf, int buflen, int *pingtime);
+void CL_GetPingInfo(int n, char *buf, int buflen);
+void CL_ClearPing(int n);
+int CL_GetPingQueueCount(void);
+
+bool CL_ServerStatus(char *serverAddress, char *serverStatusString, int maxLen);
+
+bool CL_CheckPaused(void);
+
+//
+// cl_input
+//
+typedef struct {
+ int down[2]; // key nums holding it down
+ unsigned downtime; // msec timestamp
+ unsigned msec; // msec down this frame if both a down and up happened
+ bool active; // current state
+ bool wasPressed; // set when down, not cleared when up
+} kbutton_t;
+
+void CL_InitInput(void);
+void CL_ShutdownInput(void);
+void CL_SendCmd(void);
+void CL_ClearState(void);
+void CL_ReadPackets(void);
+
+void CL_WritePacket(void);
+//void IN_CenterView(void);
+
+int Key_StringToKeynum(const char *str);
+const char *Key_KeynumToString(int keynum);
+
+//
+// cl_parse.c
+//
+extern bool cl_connectedToPureServer;
+extern bool cl_connectedToCheatServer;
+
+#ifdef USE_VOIP
+void CL_Voip_f(void);
+#endif
+
+void CL_SystemInfoChanged(void);
+void CL_ParseServerMessage(msg_t *msg);
+
+//====================================================================
+
+bool CL_UpdateVisiblePings_f(int source);
+
+//
+// console
+//
+void Con_DrawCharacter(int cx, int line, int num);
+
+void Con_CheckResize(void);
+void Con_MessageModesInit(void);
+void CL_ProtocolSpecificCommandsInit(void);
+void Con_Init(void);
+void Con_Shutdown(void);
+void Con_Clear_f(void);
+void Con_ToggleConsole_f(void);
+void Con_ClearNotify(void);
+void Con_RunConsole(void);
+void Con_DrawConsole(void);
+void Con_PageUp(void);
+void Con_PageDown(void);
+void Con_Top(void);
+void Con_Bottom(void);
+void Con_Close(void);
+
+void CL_LoadConsoleHistory(void);
+void CL_SaveConsoleHistory(void);
+
+//
+// cl_scrn.c
+//
+void SCR_Init(void);
+void SCR_UpdateScreen(void);
+
+void SCR_DebugGraph(float value);
+
+int SCR_GetBigStringWidth(const char *str); // returns in virtual 640x480 coordinates
+
+void SCR_AdjustFrom640(float *x, float *y, float *w, float *h);
+void SCR_FillRect(float x, float y, float width, float height, const float *color);
+void SCR_DrawPic(float x, float y, float width, float height, qhandle_t hShader);
+void SCR_DrawNamedPic(float x, float y, float width, float height, const char *picname);
+
+void SCR_DrawBigString(int x, int y, const char *s, float alpha,
+ bool noColorEscape); // draws a string with embedded color control characters with fade
+void SCR_DrawBigStringColor(
+ int x, int y, const char *s, vec4_t color, bool noColorEscape); // ignores embedded color control characters
+void SCR_DrawSmallStringExt(int x, int y, const char *string, float *setColor, bool forceColor, bool noColorEscape);
+void SCR_DrawSmallChar(int x, int y, int ch);
+
+//
+// cl_cin.c
+//
+
+void CL_PlayCinematic_f(void);
+void SCR_DrawCinematic(void);
+void SCR_RunCinematic(void);
+void SCR_StopCinematic(void);
+int CIN_PlayCinematic(const char *arg0, int xpos, int ypos, int width, int height, int bits);
+e_status CIN_StopCinematic(int handle);
+e_status CIN_RunCinematic(int handle);
+void CIN_DrawCinematic(int handle);
+void CIN_SetExtents(int handle, int x, int y, int w, int h);
+void CIN_UploadCinematic(int handle);
+void CIN_CloseAllVideos(void);
+
+//
+// cl_cgame.c
+//
+void CL_InitCGame(void);
+void CL_ShutdownCGame(void);
+bool CL_GameCommand(void);
+void CL_GameConsoleText(void);
+void CL_CGameRendering(stereoFrame_t stereo);
+void CL_SetCGameTime(void);
+void CL_FirstSnapshot(void);
+void CL_ShaderStateChanged(void);
+
+//
+// cl_ui.c
+//
+void CL_InitUI(void);
+void CL_ShutdownUI(void);
+int Key_GetCatcher(void);
+void Key_SetCatcher(int catcher);
+void LAN_LoadCachedServers(void);
+void LAN_SaveServersToCache(void);
+
+//
+// cl_net_chan.c
+//
+void CL_Netchan_Transmit(netchan_t *chan, msg_t *msg); // int length, const byte *data );
+bool CL_Netchan_Process(netchan_t *chan, msg_t *msg);
+
+//
+// cl_avi.c
+//
+bool CL_OpenAVIForWriting(const char *filename);
+void CL_TakeVideoFrame(void);
+void CL_WriteAVIVideoFrame(const byte *imageBuffer, int size);
+void CL_WriteAVIAudioFrame(const byte *pcmBuffer, int size);
+bool CL_CloseAVI(void);
+bool CL_VideoRecording(void);
+
+//
+// cl_main.c
+//
+void CL_WriteDemoMessage(msg_t *msg, int headerBytes);
+int CL_ScaledMilliseconds(void);
+
+#endif
diff --git a/src/client/keycodes.h b/src/client/keycodes.h
index ae6f189..3b2958d 100644
--- a/src/client/keycodes.h
+++ b/src/client/keycodes.h
@@ -1,13 +1,14 @@
/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
-Copyright (C) 2000-2006 Tim Angus
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
This file is part of Tremulous.
Tremulous is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
-published by the Free Software Foundation; either version 2 of the License,
+published by the Free Software Foundation; either version 3 of the License,
or (at your option) any later version.
Tremulous is distributed in the hope that it will be
@@ -16,8 +17,8 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
-along with Tremulous; if not, write to the Free Software
-Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+along with Tremulous; if not, see <https://www.gnu.org/licenses/>
+
===========================================================================
*/
//
@@ -262,6 +263,39 @@ typedef enum {
K_EURO,
K_UNDO,
+ // Gamepad controls
+ // Ordered to match SDL2 game controller buttons and axes
+ // Do not change this order without also changing IN_GamepadMove() in SDL_input.c
+ K_PAD0_A,
+ K_PAD0_B,
+ K_PAD0_X,
+ K_PAD0_Y,
+ K_PAD0_BACK,
+ K_PAD0_GUIDE,
+ K_PAD0_START,
+ K_PAD0_LEFTSTICK_CLICK,
+ K_PAD0_RIGHTSTICK_CLICK,
+ K_PAD0_LEFTSHOULDER,
+ K_PAD0_RIGHTSHOULDER,
+ K_PAD0_DPAD_UP,
+ K_PAD0_DPAD_DOWN,
+ K_PAD0_DPAD_LEFT,
+ K_PAD0_DPAD_RIGHT,
+
+ K_PAD0_LEFTSTICK_LEFT,
+ K_PAD0_LEFTSTICK_RIGHT,
+ K_PAD0_LEFTSTICK_UP,
+ K_PAD0_LEFTSTICK_DOWN,
+ K_PAD0_RIGHTSTICK_LEFT,
+ K_PAD0_RIGHTSTICK_RIGHT,
+ K_PAD0_RIGHTSTICK_UP,
+ K_PAD0_RIGHTSTICK_DOWN,
+ K_PAD0_LEFTTRIGGER,
+ K_PAD0_RIGHTTRIGGER,
+
+ // Pseudo-key that brings the console down
+ K_CONSOLE,
+
MAX_KEYS
} keyNum_t;
diff --git a/src/client/keys.h b/src/client/keys.h
new file mode 100644
index 0000000..5c8877f
--- /dev/null
+++ b/src/client/keys.h
@@ -0,0 +1,66 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
+
+This file is part of Tremulous.
+
+Tremulous 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.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+#ifndef _KEYS_H_
+#define _KEYS_H_
+
+#include "keycodes.h"
+
+typedef struct {
+ bool down;
+ int repeats; // if > 1, it is autorepeating
+ char *binding;
+} qkey_t;
+
+extern bool key_overstrikeMode;
+extern qkey_t keys[MAX_KEYS];
+
+// NOTE TTimo the declaration of field_t and Field_Clear is now in qcommon/qcommon.h
+void Field_KeyDownEvent(field_t *edit, int key);
+void Field_CharEvent(field_t *edit, int ch);
+void Field_Draw(field_t *edit, int x, int y, int width, bool showCursor, bool noColorEscape);
+void Field_BigDraw(field_t *edit, int x, int y, int width, bool showCursor, bool noColorEscape);
+
+#define COMMAND_HISTORY 32
+extern field_t historyEditLines[COMMAND_HISTORY];
+
+extern field_t g_consoleField;
+extern field_t chatField;
+extern int anykeydown;
+extern bool chat_team;
+extern bool chat_admins;
+extern bool chat_clans;
+extern int chat_playerNum;
+
+void Key_WriteBindings(fileHandle_t f);
+void Key_SetBinding(int keynum, const char *binding);
+const char *Key_GetBinding(int keynum);
+bool Key_IsDown(int keynum);
+bool Key_GetOverstrikeMode(void);
+void Key_SetOverstrikeMode(bool state);
+void Key_ClearStates(void);
+int Key_GetKey(const char *binding);
+void Key_KeynumToStringBuf(int keynum, char *buf, int buflen);
+void Key_GetBindingBuf(int keynum, char *buf, int buflen);
+
+#endif
diff --git a/src/client/libmumblelink.cpp b/src/client/libmumblelink.cpp
new file mode 100644
index 0000000..59d59d3
--- /dev/null
+++ b/src/client/libmumblelink.cpp
@@ -0,0 +1,190 @@
+/* libmumblelink.c -- mumble link interface
+
+ Copyright (C) 2008 Ludwig Nussel <ludwig.nussel@suse.de>
+ Copyright (C) 2000-2013 Darklegion Development
+ Copyright (C) 2015-2019 GrangerHub
+
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the authors be held liable for any damages
+ arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+ 3. This notice may not be removed or altered from any source distribution.
+
+*/
+
+#include "libmumblelink.h"
+
+#include <fcntl.h>
+#ifdef _WIN32
+#include <windows.h>
+#define uint32_t UINT32
+#else
+#include <unistd.h>
+#ifdef __sun
+#define _POSIX_C_SOURCE 199309L
+#endif
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#endif
+
+#include <cstdint>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+
+#ifndef MIN
+#define MIN(a, b) ((a)<(b)?(a):(b))
+#endif
+
+typedef struct
+{
+ uint32_t uiVersion;
+ uint32_t uiTick;
+ float fAvatarPosition[3];
+ float fAvatarFront[3];
+ float fAvatarTop[3];
+ wchar_t name[256];
+ /* new in mumble 1.2 */
+ float fCameraPosition[3];
+ float fCameraFront[3];
+ float fCameraTop[3];
+ wchar_t identity[256];
+ uint32_t context_len;
+ unsigned char context[256];
+ wchar_t description[2048];
+} LinkedMem;
+
+static LinkedMem *lm = NULL;
+
+#ifdef WIN32
+static HANDLE hMapObject = NULL;
+#else
+static int32_t GetTickCount(void)
+{
+ struct timeval tv;
+ gettimeofday(&tv,NULL);
+
+ return tv.tv_usec / 1000 + tv.tv_sec * 1000;
+}
+#endif
+
+int mumble_link(const char* name)
+{
+#ifdef WIN32
+ if(lm)
+ return 0;
+
+ hMapObject = OpenFileMappingW(FILE_MAP_ALL_ACCESS, FALSE, L"MumbleLink");
+ if (hMapObject == NULL)
+ return -1;
+
+ lm = (LinkedMem *) MapViewOfFile(hMapObject, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(LinkedMem));
+ if (lm == NULL) {
+ CloseHandle(hMapObject);
+ hMapObject = NULL;
+ return -1;
+ }
+#else
+ char file[256];
+ int shmfd;
+ if(lm)
+ return 0;
+
+ snprintf(file, sizeof (file), "/MumbleLink.%d", getuid());
+ shmfd = shm_open(file, O_RDWR, S_IRUSR | S_IWUSR);
+ if(shmfd < 0) {
+ return -1;
+ }
+
+ lm = (LinkedMem *) (mmap(NULL, sizeof(LinkedMem), PROT_READ | PROT_WRITE, MAP_SHARED, shmfd,0));
+ if (lm == (void *) (-1)) {
+ lm = NULL;
+ close(shmfd);
+ return -1;
+ }
+ close(shmfd);
+#endif
+ memset(lm, 0, sizeof(LinkedMem));
+ mbstowcs(lm->name, name, sizeof(lm->name) / sizeof(wchar_t));
+
+ return 0;
+}
+
+void mumble_update_coordinates(float fPosition[3], float fFront[3], float fTop[3])
+{
+ mumble_update_coordinates2(fPosition, fFront, fTop, fPosition, fFront, fTop);
+}
+
+void mumble_update_coordinates2(float fAvatarPosition[3], float fAvatarFront[3], float fAvatarTop[3],
+ float fCameraPosition[3], float fCameraFront[3], float fCameraTop[3])
+{
+ if (!lm)
+ return;
+
+ memcpy(lm->fAvatarPosition, fAvatarPosition, sizeof(lm->fAvatarPosition));
+ memcpy(lm->fAvatarFront, fAvatarFront, sizeof(lm->fAvatarFront));
+ memcpy(lm->fAvatarTop, fAvatarTop, sizeof(lm->fAvatarTop));
+ memcpy(lm->fCameraPosition, fCameraPosition, sizeof(lm->fCameraPosition));
+ memcpy(lm->fCameraFront, fCameraFront, sizeof(lm->fCameraFront));
+ memcpy(lm->fCameraTop, fCameraTop, sizeof(lm->fCameraTop));
+ lm->uiVersion = 2;
+ lm->uiTick = GetTickCount();
+}
+
+void mumble_set_identity(const char* identity)
+{
+ size_t len;
+ if (!lm)
+ return;
+ len = MIN(sizeof(lm->identity)/sizeof(wchar_t), strlen(identity)+1);
+ mbstowcs(lm->identity, identity, len);
+}
+
+void mumble_set_context(const unsigned char* context, size_t len)
+{
+ if (!lm)
+ return;
+ len = MIN(sizeof(lm->context), len);
+ lm->context_len = len;
+ memcpy(lm->context, context, len);
+}
+
+void mumble_set_description(const char* description)
+{
+ size_t len;
+ if (!lm)
+ return;
+ len = MIN(sizeof(lm->description)/sizeof(wchar_t), strlen(description)+1);
+ mbstowcs(lm->description, description, len);
+}
+
+void mumble_unlink()
+{
+ if(!lm)
+ return;
+#ifdef WIN32
+ UnmapViewOfFile(lm);
+ CloseHandle(hMapObject);
+ hMapObject = NULL;
+#else
+ munmap(lm, sizeof(LinkedMem));
+#endif
+ lm = NULL;
+}
+
+int mumble_islinked(void)
+{
+ return lm != NULL;
+}
diff --git a/src/client/libmumblelink.h b/src/client/libmumblelink.h
new file mode 100644
index 0000000..aa8e2ce
--- /dev/null
+++ b/src/client/libmumblelink.h
@@ -0,0 +1,41 @@
+/* libmumblelink.h -- mumble link interface
+
+ Copyright (C) 2008 Ludwig Nussel <ludwig.nussel@suse.de>
+ Copyright (C) 2000-2013 Darklegion Development
+ Copyright (C) 2015-2019 GrangerHub
+
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the authors be held liable for any damages
+ arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+ 3. This notice may not be removed or altered from any source distribution.
+
+*/
+#ifndef _LIBMUMBLELINK_H_
+#define _LIBMUMBLELINK_H_
+
+int mumble_link(const char* name);
+int mumble_islinked(void);
+void mumble_update_coordinates(float fPosition[3], float fFront[3], float fTop[3]);
+
+/* new for mumble 1.2: also set camera position */
+void mumble_update_coordinates2(float fAvatarPosition[3], float fAvatarFront[3], float fAvatarTop[3],
+ float fCameraPosition[3], float fCameraFront[3], float fCameraTop[3]);
+
+void mumble_set_description(const char* description);
+void mumble_set_context(const unsigned char* context, size_t len);
+void mumble_set_identity(const char* identity);
+
+void mumble_unlink(void);
+
+#endif // _LIBMUMBLELINK_H_
diff --git a/src/client/qal.cpp b/src/client/qal.cpp
new file mode 100644
index 0000000..466a45e
--- /dev/null
+++ b/src/client/qal.cpp
@@ -0,0 +1,337 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2005 Stuart Dalton (badcdev@gmail.com)
+Copyright (C) 2015-2019 GrangerHub
+
+This file is part of Tremulous.
+
+Tremulous 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.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+
+// Dynamically loads OpenAL
+
+#ifdef USE_OPENAL
+
+#include "qal.h"
+
+#ifdef USE_OPENAL_DLOPEN
+#include "sys/sys_loadlib.h"
+
+LPALENABLE qalEnable;
+LPALDISABLE qalDisable;
+LPALISENABLED qalIsEnabled;
+LPALGETSTRING qalGetString;
+LPALGETBOOLEANV qalGetBooleanv;
+LPALGETINTEGERV qalGetIntegerv;
+LPALGETFLOATV qalGetFloatv;
+LPALGETDOUBLEV qalGetDoublev;
+LPALGETBOOLEAN qalGetBoolean;
+LPALGETINTEGER qalGetInteger;
+LPALGETFLOAT qalGetFloat;
+LPALGETDOUBLE qalGetDouble;
+LPALGETERROR qalGetError;
+LPALISEXTENSIONPRESENT qalIsExtensionPresent;
+LPALGETPROCADDRESS qalGetProcAddress;
+LPALGETENUMVALUE qalGetEnumValue;
+LPALLISTENERF qalListenerf;
+LPALLISTENER3F qalListener3f;
+LPALLISTENERFV qalListenerfv;
+LPALLISTENERI qalListeneri;
+LPALGETLISTENERF qalGetListenerf;
+LPALGETLISTENER3F qalGetListener3f;
+LPALGETLISTENERFV qalGetListenerfv;
+LPALGETLISTENERI qalGetListeneri;
+LPALGENSOURCES qalGenSources;
+LPALDELETESOURCES qalDeleteSources;
+LPALISSOURCE qalIsSource;
+LPALSOURCEF qalSourcef;
+LPALSOURCE3F qalSource3f;
+LPALSOURCEFV qalSourcefv;
+LPALSOURCEI qalSourcei;
+LPALGETSOURCEF qalGetSourcef;
+LPALGETSOURCE3F qalGetSource3f;
+LPALGETSOURCEFV qalGetSourcefv;
+LPALGETSOURCEI qalGetSourcei;
+LPALSOURCEPLAYV qalSourcePlayv;
+LPALSOURCESTOPV qalSourceStopv;
+LPALSOURCEREWINDV qalSourceRewindv;
+LPALSOURCEPAUSEV qalSourcePausev;
+LPALSOURCEPLAY qalSourcePlay;
+LPALSOURCESTOP qalSourceStop;
+LPALSOURCEREWIND qalSourceRewind;
+LPALSOURCEPAUSE qalSourcePause;
+LPALSOURCEQUEUEBUFFERS qalSourceQueueBuffers;
+LPALSOURCEUNQUEUEBUFFERS qalSourceUnqueueBuffers;
+LPALGENBUFFERS qalGenBuffers;
+LPALDELETEBUFFERS qalDeleteBuffers;
+LPALISBUFFER qalIsBuffer;
+LPALBUFFERDATA qalBufferData;
+LPALGETBUFFERF qalGetBufferf;
+LPALGETBUFFERI qalGetBufferi;
+LPALDOPPLERFACTOR qalDopplerFactor;
+LPALSPEEDOFSOUND qalSpeedOfSound;
+LPALDISTANCEMODEL qalDistanceModel;
+
+LPALCCREATECONTEXT qalcCreateContext;
+LPALCMAKECONTEXTCURRENT qalcMakeContextCurrent;
+LPALCPROCESSCONTEXT qalcProcessContext;
+LPALCSUSPENDCONTEXT qalcSuspendContext;
+LPALCDESTROYCONTEXT qalcDestroyContext;
+LPALCGETCURRENTCONTEXT qalcGetCurrentContext;
+LPALCGETCONTEXTSDEVICE qalcGetContextsDevice;
+LPALCOPENDEVICE qalcOpenDevice;
+LPALCCLOSEDEVICE qalcCloseDevice;
+LPALCGETERROR qalcGetError;
+LPALCISEXTENSIONPRESENT qalcIsExtensionPresent;
+LPALCGETPROCADDRESS qalcGetProcAddress;
+LPALCGETENUMVALUE qalcGetEnumValue;
+LPALCGETSTRING qalcGetString;
+LPALCGETINTEGERV qalcGetIntegerv;
+LPALCCAPTUREOPENDEVICE qalcCaptureOpenDevice;
+LPALCCAPTURECLOSEDEVICE qalcCaptureCloseDevice;
+LPALCCAPTURESTART qalcCaptureStart;
+LPALCCAPTURESTOP qalcCaptureStop;
+LPALCCAPTURESAMPLES qalcCaptureSamples;
+
+static void *OpenALLib = NULL;
+
+static bool alinit_fail = false;
+
+/*
+=================
+GPA
+=================
+*/
+static void *GPA(const char *str)
+{
+ void *rv;
+
+ rv = Sys_LoadFunction(OpenALLib, str);
+ if(!rv)
+ {
+ Com_Printf( " Can't load symbol %s\n", str);
+ alinit_fail = true;
+ return NULL;
+ }
+ else
+ {
+ Com_DPrintf( " Loaded symbol %s (%p)\n", str, rv);
+ return rv;
+ }
+}
+
+/*
+=================
+QAL_Init
+=================
+*/
+bool QAL_Init(const char *libname)
+{
+ if(OpenALLib)
+ return true;
+
+ if(!(OpenALLib = Sys_LoadDll(libname, true)))
+ return false;
+
+ alinit_fail = false;
+
+ qalEnable = (LPALENABLE)GPA("alEnable");
+ qalDisable = (LPALDISABLE)GPA("alDisable");
+ qalIsEnabled = (LPALISENABLED)GPA("alIsEnabled");
+ qalGetString = (LPALGETSTRING)GPA("alGetString");
+ qalGetBooleanv = (LPALGETBOOLEANV)GPA("alGetBooleanv");
+ qalGetIntegerv = (LPALGETINTEGERV)GPA("alGetIntegerv");
+ qalGetFloatv = (LPALGETFLOATV)GPA("alGetFloatv");
+ qalGetDoublev = (LPALGETDOUBLEV)GPA("alGetDoublev");
+ qalGetBoolean = (LPALGETBOOLEAN)GPA("alGetBoolean");
+ qalGetInteger = (LPALGETINTEGER)GPA("alGetInteger");
+ qalGetFloat = (LPALGETFLOAT)GPA("alGetFloat");
+ qalGetDouble = (LPALGETDOUBLE)GPA("alGetDouble");
+ qalGetError = (LPALGETERROR)GPA("alGetError");
+ qalIsExtensionPresent = (LPALISEXTENSIONPRESENT)GPA("alIsExtensionPresent");
+ qalGetProcAddress = (LPALGETPROCADDRESS)GPA("alGetProcAddress");
+ qalGetEnumValue = (LPALGETENUMVALUE)GPA("alGetEnumValue");
+ qalListenerf = (LPALLISTENERF)GPA("alListenerf");
+ qalListener3f = (LPALLISTENER3F)GPA("alListener3f");
+ qalListenerfv = (LPALLISTENERFV)GPA("alListenerfv");
+ qalListeneri = (LPALLISTENERI)GPA("alListeneri");
+ qalGetListenerf = (LPALGETLISTENERF)GPA("alGetListenerf");
+ qalGetListener3f = (LPALGETLISTENER3F)GPA("alGetListener3f");
+ qalGetListenerfv = (LPALGETLISTENERFV)GPA("alGetListenerfv");
+ qalGetListeneri = (LPALGETLISTENERI)GPA("alGetListeneri");
+ qalGenSources = (LPALGENSOURCES)GPA("alGenSources");
+ qalDeleteSources = (LPALDELETESOURCES)GPA("alDeleteSources");
+ qalIsSource = (LPALISSOURCE)GPA("alIsSource");
+ qalSourcef = (LPALSOURCEF)GPA("alSourcef");
+ qalSource3f = (LPALSOURCE3F)GPA("alSource3f");
+ qalSourcefv = (LPALSOURCEFV)GPA("alSourcefv");
+ qalSourcei = (LPALSOURCEI)GPA("alSourcei");
+ qalGetSourcef = (LPALGETSOURCEF)GPA("alGetSourcef");
+ qalGetSource3f = (LPALGETSOURCE3F)GPA("alGetSource3f");
+ qalGetSourcefv = (LPALGETSOURCEFV)GPA("alGetSourcefv");
+ qalGetSourcei = (LPALGETSOURCEI)GPA("alGetSourcei");
+ qalSourcePlayv = (LPALSOURCEPLAYV)GPA("alSourcePlayv");
+ qalSourceStopv = (LPALSOURCESTOPV)GPA("alSourceStopv");
+ qalSourceRewindv = (LPALSOURCEREWINDV)GPA("alSourceRewindv");
+ qalSourcePausev = (LPALSOURCEPAUSEV)GPA("alSourcePausev");
+ qalSourcePlay = (LPALSOURCEPLAY)GPA("alSourcePlay");
+ qalSourceStop = (LPALSOURCESTOP)GPA("alSourceStop");
+ qalSourceRewind = (LPALSOURCEREWIND)GPA("alSourceRewind");
+ qalSourcePause = (LPALSOURCEPAUSE)GPA("alSourcePause");
+ qalSourceQueueBuffers = (LPALSOURCEQUEUEBUFFERS)GPA("alSourceQueueBuffers");
+ qalSourceUnqueueBuffers = (LPALSOURCEUNQUEUEBUFFERS)GPA("alSourceUnqueueBuffers");
+ qalGenBuffers = (LPALGENBUFFERS)GPA("alGenBuffers");
+ qalDeleteBuffers = (LPALDELETEBUFFERS)GPA("alDeleteBuffers");
+ qalIsBuffer = (LPALISBUFFER)GPA("alIsBuffer");
+ qalBufferData = (LPALBUFFERDATA)GPA("alBufferData");
+ qalGetBufferf = (LPALGETBUFFERF)GPA("alGetBufferf");
+ qalGetBufferi = (LPALGETBUFFERI)GPA("alGetBufferi");
+ qalDopplerFactor = (LPALDOPPLERFACTOR)GPA("alDopplerFactor");
+ qalSpeedOfSound = (LPALSPEEDOFSOUND)GPA("alSpeedOfSound");
+ qalDistanceModel = (LPALDISTANCEMODEL)GPA("alDistanceModel");
+
+ qalcCreateContext = (LPALCCREATECONTEXT)GPA("alcCreateContext");
+ qalcMakeContextCurrent = (LPALCMAKECONTEXTCURRENT)GPA("alcMakeContextCurrent");
+ qalcProcessContext = (LPALCPROCESSCONTEXT)GPA("alcProcessContext");
+ qalcSuspendContext = (LPALCSUSPENDCONTEXT)GPA("alcSuspendContext");
+ qalcDestroyContext = (LPALCDESTROYCONTEXT)GPA("alcDestroyContext");
+ qalcGetCurrentContext = (LPALCGETCURRENTCONTEXT)GPA("alcGetCurrentContext");
+ qalcGetContextsDevice = (LPALCGETCONTEXTSDEVICE)GPA("alcGetContextsDevice");
+ qalcOpenDevice = (LPALCOPENDEVICE)GPA("alcOpenDevice");
+ qalcCloseDevice = (LPALCCLOSEDEVICE)GPA("alcCloseDevice");
+ qalcGetError = (LPALCGETERROR)GPA("alcGetError");
+ qalcIsExtensionPresent = (LPALCISEXTENSIONPRESENT)GPA("alcIsExtensionPresent");
+ qalcGetProcAddress = (LPALCGETPROCADDRESS)GPA("alcGetProcAddress");
+ qalcGetEnumValue = (LPALCGETENUMVALUE)GPA("alcGetEnumValue");
+ qalcGetString = (LPALCGETSTRING)GPA("alcGetString");
+ qalcGetIntegerv = (LPALCGETINTEGERV)GPA("alcGetIntegerv");
+ qalcCaptureOpenDevice = (LPALCCAPTUREOPENDEVICE)GPA("alcCaptureOpenDevice");
+ qalcCaptureCloseDevice = (LPALCCAPTURECLOSEDEVICE)GPA("alcCaptureCloseDevice");
+ qalcCaptureStart = (LPALCCAPTURESTART)GPA("alcCaptureStart");
+ qalcCaptureStop = (LPALCCAPTURESTOP)GPA("alcCaptureStop");
+ qalcCaptureSamples = (LPALCCAPTURESAMPLES)GPA("alcCaptureSamples");
+
+ if(alinit_fail)
+ {
+ QAL_Shutdown();
+ Com_Printf( " One or more symbols not found\n");
+ return false;
+ }
+
+ return true;
+}
+
+/*
+=================
+QAL_Shutdown
+=================
+*/
+void QAL_Shutdown( void )
+{
+ if(OpenALLib)
+ {
+ Sys_UnloadLibrary(OpenALLib);
+ OpenALLib = NULL;
+ }
+
+ qalEnable = NULL;
+ qalDisable = NULL;
+ qalIsEnabled = NULL;
+ qalGetString = NULL;
+ qalGetBooleanv = NULL;
+ qalGetIntegerv = NULL;
+ qalGetFloatv = NULL;
+ qalGetDoublev = NULL;
+ qalGetBoolean = NULL;
+ qalGetInteger = NULL;
+ qalGetFloat = NULL;
+ qalGetDouble = NULL;
+ qalGetError = NULL;
+ qalIsExtensionPresent = NULL;
+ qalGetProcAddress = NULL;
+ qalGetEnumValue = NULL;
+ qalListenerf = NULL;
+ qalListener3f = NULL;
+ qalListenerfv = NULL;
+ qalListeneri = NULL;
+ qalGetListenerf = NULL;
+ qalGetListener3f = NULL;
+ qalGetListenerfv = NULL;
+ qalGetListeneri = NULL;
+ qalGenSources = NULL;
+ qalDeleteSources = NULL;
+ qalIsSource = NULL;
+ qalSourcef = NULL;
+ qalSource3f = NULL;
+ qalSourcefv = NULL;
+ qalSourcei = NULL;
+ qalGetSourcef = NULL;
+ qalGetSource3f = NULL;
+ qalGetSourcefv = NULL;
+ qalGetSourcei = NULL;
+ qalSourcePlayv = NULL;
+ qalSourceStopv = NULL;
+ qalSourceRewindv = NULL;
+ qalSourcePausev = NULL;
+ qalSourcePlay = NULL;
+ qalSourceStop = NULL;
+ qalSourceRewind = NULL;
+ qalSourcePause = NULL;
+ qalSourceQueueBuffers = NULL;
+ qalSourceUnqueueBuffers = NULL;
+ qalGenBuffers = NULL;
+ qalDeleteBuffers = NULL;
+ qalIsBuffer = NULL;
+ qalBufferData = NULL;
+ qalGetBufferf = NULL;
+ qalGetBufferi = NULL;
+ qalDopplerFactor = NULL;
+ qalSpeedOfSound = NULL;
+ qalDistanceModel = NULL;
+
+ qalcCreateContext = NULL;
+ qalcMakeContextCurrent = NULL;
+ qalcProcessContext = NULL;
+ qalcSuspendContext = NULL;
+ qalcDestroyContext = NULL;
+ qalcGetCurrentContext = NULL;
+ qalcGetContextsDevice = NULL;
+ qalcOpenDevice = NULL;
+ qalcCloseDevice = NULL;
+ qalcGetError = NULL;
+ qalcIsExtensionPresent = NULL;
+ qalcGetProcAddress = NULL;
+ qalcGetEnumValue = NULL;
+ qalcGetString = NULL;
+ qalcGetIntegerv = NULL;
+ qalcCaptureOpenDevice = NULL;
+ qalcCaptureCloseDevice = NULL;
+ qalcCaptureStart = NULL;
+ qalcCaptureStop = NULL;
+ qalcCaptureSamples = NULL;
+}
+#else
+bool QAL_Init(const char *libname)
+{
+ return true;
+}
+void QAL_Shutdown( void )
+{
+}
+#endif
+#endif
diff --git a/src/client/qal.h b/src/client/qal.h
new file mode 100644
index 0000000..41d6244
--- /dev/null
+++ b/src/client/qal.h
@@ -0,0 +1,252 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2005 Stuart Dalton (badcdev@gmail.com)
+Copyright (C) 2015-2019 GrangerHub
+
+This file is part of Tremulous.
+
+Tremulous 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.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+
+#ifndef __QAL_H__
+#define __QAL_H__
+
+#ifdef USE_LOCAL_HEADERS
+# include "AL/al.h"
+# include "AL/alc.h"
+#else
+# if defined(_MSC_VER) || defined(__APPLE__)
+// MSVC users must install the OpenAL SDK which doesn't use the AL/*.h scheme.
+// OSX framework also needs this
+# include <al.h>
+# include <alc.h>
+# else
+# include <AL/al.h>
+# include <AL/alc.h>
+# endif
+#endif
+
+#include "qcommon/q_shared.h"
+#include "qcommon/qcommon.h"
+
+#ifdef USE_OPENAL_DLOPEN
+#define AL_NO_PROTOTYPES
+#define ALC_NO_PROTOTYPES
+#endif
+
+/* Hack to enable compiling both on OpenAL SDK and OpenAL-soft. */
+#ifndef ALC_ENUMERATE_ALL_EXT
+# define ALC_ENUMERATE_ALL_EXT 1
+# define ALC_DEFAULT_ALL_DEVICES_SPECIFIER 0x1012
+# define ALC_ALL_DEVICES_SPECIFIER 0x1013
+#endif
+
+#ifdef USE_OPENAL_DLOPEN
+extern LPALENABLE qalEnable;
+extern LPALDISABLE qalDisable;
+extern LPALISENABLED qalIsEnabled;
+extern LPALGETSTRING qalGetString;
+extern LPALGETBOOLEANV qalGetBooleanv;
+extern LPALGETINTEGERV qalGetIntegerv;
+extern LPALGETFLOATV qalGetFloatv;
+extern LPALGETDOUBLEV qalGetDoublev;
+extern LPALGETBOOLEAN qalGetBoolean;
+extern LPALGETINTEGER qalGetInteger;
+extern LPALGETFLOAT qalGetFloat;
+extern LPALGETDOUBLE qalGetDouble;
+extern LPALGETERROR qalGetError;
+extern LPALISEXTENSIONPRESENT qalIsExtensionPresent;
+extern LPALGETPROCADDRESS qalGetProcAddress;
+extern LPALGETENUMVALUE qalGetEnumValue;
+extern LPALLISTENERF qalListenerf;
+extern LPALLISTENER3F qalListener3f;
+extern LPALLISTENERFV qalListenerfv;
+extern LPALLISTENERI qalListeneri;
+extern LPALLISTENER3I qalListener3i;
+extern LPALLISTENERIV qalListeneriv;
+extern LPALGETLISTENERF qalGetListenerf;
+extern LPALGETLISTENER3F qalGetListener3f;
+extern LPALGETLISTENERFV qalGetListenerfv;
+extern LPALGETLISTENERI qalGetListeneri;
+extern LPALGETLISTENER3I qalGetListener3i;
+extern LPALGETLISTENERIV qalGetListeneriv;
+extern LPALGENSOURCES qalGenSources;
+extern LPALDELETESOURCES qalDeleteSources;
+extern LPALISSOURCE qalIsSource;
+extern LPALSOURCEF qalSourcef;
+extern LPALSOURCE3F qalSource3f;
+extern LPALSOURCEFV qalSourcefv;
+extern LPALSOURCEI qalSourcei;
+extern LPALSOURCE3I qalSource3i;
+extern LPALSOURCEIV qalSourceiv;
+extern LPALGETSOURCEF qalGetSourcef;
+extern LPALGETSOURCE3F qalGetSource3f;
+extern LPALGETSOURCEFV qalGetSourcefv;
+extern LPALGETSOURCEI qalGetSourcei;
+extern LPALGETSOURCE3I qalGetSource3i;
+extern LPALGETSOURCEIV qalGetSourceiv;
+extern LPALSOURCEPLAYV qalSourcePlayv;
+extern LPALSOURCESTOPV qalSourceStopv;
+extern LPALSOURCEREWINDV qalSourceRewindv;
+extern LPALSOURCEPAUSEV qalSourcePausev;
+extern LPALSOURCEPLAY qalSourcePlay;
+extern LPALSOURCESTOP qalSourceStop;
+extern LPALSOURCEREWIND qalSourceRewind;
+extern LPALSOURCEPAUSE qalSourcePause;
+extern LPALSOURCEQUEUEBUFFERS qalSourceQueueBuffers;
+extern LPALSOURCEUNQUEUEBUFFERS qalSourceUnqueueBuffers;
+extern LPALGENBUFFERS qalGenBuffers;
+extern LPALDELETEBUFFERS qalDeleteBuffers;
+extern LPALISBUFFER qalIsBuffer;
+extern LPALBUFFERDATA qalBufferData;
+extern LPALBUFFERF qalBufferf;
+extern LPALBUFFER3F qalBuffer3f;
+extern LPALBUFFERFV qalBufferfv;
+extern LPALBUFFERF qalBufferi;
+extern LPALBUFFER3F qalBuffer3i;
+extern LPALBUFFERFV qalBufferiv;
+extern LPALGETBUFFERF qalGetBufferf;
+extern LPALGETBUFFER3F qalGetBuffer3f;
+extern LPALGETBUFFERFV qalGetBufferfv;
+extern LPALGETBUFFERI qalGetBufferi;
+extern LPALGETBUFFER3I qalGetBuffer3i;
+extern LPALGETBUFFERIV qalGetBufferiv;
+extern LPALDOPPLERFACTOR qalDopplerFactor;
+extern LPALSPEEDOFSOUND qalSpeedOfSound;
+extern LPALDISTANCEMODEL qalDistanceModel;
+
+extern LPALCCREATECONTEXT qalcCreateContext;
+extern LPALCMAKECONTEXTCURRENT qalcMakeContextCurrent;
+extern LPALCPROCESSCONTEXT qalcProcessContext;
+extern LPALCSUSPENDCONTEXT qalcSuspendContext;
+extern LPALCDESTROYCONTEXT qalcDestroyContext;
+extern LPALCGETCURRENTCONTEXT qalcGetCurrentContext;
+extern LPALCGETCONTEXTSDEVICE qalcGetContextsDevice;
+extern LPALCOPENDEVICE qalcOpenDevice;
+extern LPALCCLOSEDEVICE qalcCloseDevice;
+extern LPALCGETERROR qalcGetError;
+extern LPALCISEXTENSIONPRESENT qalcIsExtensionPresent;
+extern LPALCGETPROCADDRESS qalcGetProcAddress;
+extern LPALCGETENUMVALUE qalcGetEnumValue;
+extern LPALCGETSTRING qalcGetString;
+extern LPALCGETINTEGERV qalcGetIntegerv;
+extern LPALCCAPTUREOPENDEVICE qalcCaptureOpenDevice;
+extern LPALCCAPTURECLOSEDEVICE qalcCaptureCloseDevice;
+extern LPALCCAPTURESTART qalcCaptureStart;
+extern LPALCCAPTURESTOP qalcCaptureStop;
+extern LPALCCAPTURESAMPLES qalcCaptureSamples;
+#else
+#define qalEnable alEnable
+#define qalDisable alDisable
+#define qalIsEnabled alIsEnabled
+#define qalGetString alGetString
+#define qalGetBooleanv alGetBooleanv
+#define qalGetIntegerv alGetIntegerv
+#define qalGetFloatv alGetFloatv
+#define qalGetDoublev alGetDoublev
+#define qalGetBoolean alGetBoolean
+#define qalGetInteger alGetInteger
+#define qalGetFloat alGetFloat
+#define qalGetDouble alGetDouble
+#define qalGetError alGetError
+#define qalIsExtensionPresent alIsExtensionPresent
+#define qalGetProcAddress alGetProcAddress
+#define qalGetEnumValue alGetEnumValue
+#define qalListenerf alListenerf
+#define qalListener3f alListener3f
+#define qalListenerfv alListenerfv
+#define qalListeneri alListeneri
+#define qalListener3i alListener3i
+#define qalListeneriv alListeneriv
+#define qalGetListenerf alGetListenerf
+#define qalGetListener3f alGetListener3f
+#define qalGetListenerfv alGetListenerfv
+#define qalGetListeneri alGetListeneri
+#define qalGetListener3i alGetListener3i
+#define qalGetListeneriv alGetListeneriv
+#define qalGenSources alGenSources
+#define qalDeleteSources alDeleteSources
+#define qalIsSource alIsSource
+#define qalSourcef alSourcef
+#define qalSource3f alSource3f
+#define qalSourcefv alSourcefv
+#define qalSourcei alSourcei
+#define qalSource3i alSource3i
+#define qalSourceiv alSourceiv
+#define qalGetSourcef alGetSourcef
+#define qalGetSource3f alGetSource3f
+#define qalGetSourcefv alGetSourcefv
+#define qalGetSourcei alGetSourcei
+#define qalGetSource3i alGetSource3i
+#define qalGetSourceiv alGetSourceiv
+#define qalSourcePlayv alSourcePlayv
+#define qalSourceStopv alSourceStopv
+#define qalSourceRewindv alSourceRewindv
+#define qalSourcePausev alSourcePausev
+#define qalSourcePlay alSourcePlay
+#define qalSourceStop alSourceStop
+#define qalSourceRewind alSourceRewind
+#define qalSourcePause alSourcePause
+#define qalSourceQueueBuffers alSourceQueueBuffers
+#define qalSourceUnqueueBuffers alSourceUnqueueBuffers
+#define qalGenBuffers alGenBuffers
+#define qalDeleteBuffers alDeleteBuffers
+#define qalIsBuffer alIsBuffer
+#define qalBufferData alBufferData
+#define qalBufferf alBufferf
+#define qalBuffer3f alBuffer3f
+#define qalBufferfv alBufferfv
+#define qalBufferi alBufferi
+#define qalBuffer3i alBuffer3i
+#define qalBufferiv alBufferiv
+#define qalGetBufferf alGetBufferf
+#define qalGetBuffer3f alGetBuffer3f
+#define qalGetBufferfv alGetBufferfv
+#define qalGetBufferi alGetBufferi
+#define qalGetBuffer3i alGetBuffer3i
+#define qalGetBufferiv alGetBufferiv
+#define qalDopplerFactor alDopplerFactor
+#define qalSpeedOfSound alSpeedOfSound
+#define qalDistanceModel alDistanceModel
+
+#define qalcCreateContext alcCreateContext
+#define qalcMakeContextCurrent alcMakeContextCurrent
+#define qalcProcessContext alcProcessContext
+#define qalcSuspendContext alcSuspendContext
+#define qalcDestroyContext alcDestroyContext
+#define qalcGetCurrentContext alcGetCurrentContext
+#define qalcGetContextsDevice alcGetContextsDevice
+#define qalcOpenDevice alcOpenDevice
+#define qalcCloseDevice alcCloseDevice
+#define qalcGetError alcGetError
+#define qalcIsExtensionPresent alcIsExtensionPresent
+#define qalcGetProcAddress alcGetProcAddress
+#define qalcGetEnumValue alcGetEnumValue
+#define qalcGetString alcGetString
+#define qalcGetIntegerv alcGetIntegerv
+#define qalcCaptureOpenDevice alcCaptureOpenDevice
+#define qalcCaptureCloseDevice alcCaptureCloseDevice
+#define qalcCaptureStart alcCaptureStart
+#define qalcCaptureStop alcCaptureStop
+#define qalcCaptureSamples alcCaptureSamples
+#endif
+
+bool QAL_Init(const char *libname);
+void QAL_Shutdown( void );
+
+#endif // __QAL_H__
diff --git a/src/client/snd_adpcm.cpp b/src/client/snd_adpcm.cpp
new file mode 100644
index 0000000..d5d9930
--- /dev/null
+++ b/src/client/snd_adpcm.cpp
@@ -0,0 +1,329 @@
+/***********************************************************
+Copyright 1992 by Stichting Mathematisch Centrum, Amsterdam, The
+Netherlands.
+
+ All Rights Reserved
+
+Permission to use, copy, modify, and distribute this software and its
+documentation for any purpose and without fee is hereby granted,
+provided that the above copyright notice appear in all copies and that
+both that copyright notice and this permission notice appear in
+supporting documentation, and that the names of Stichting Mathematisch
+Centrum or CWI not be used in advertising or publicity pertaining to
+distribution of the software without specific, written prior permission.
+
+STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO
+THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE
+FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+******************************************************************/
+
+/*
+** Intel/DVI ADPCM coder/decoder.
+**
+** The algorithm for this coder was taken from the IMA Compatability Project
+** proceedings, Vol 2, Number 2; May 1992.
+**
+** Version 1.2, 18-Dec-92.
+*/
+
+#include "snd_local.h"
+
+/* Intel ADPCM step variation table */
+static int indexTable[16] = {
+ -1, -1, -1, -1, 2, 4, 6, 8,
+ -1, -1, -1, -1, 2, 4, 6, 8,
+};
+
+static int stepsizeTable[89] = {
+ 7, 8, 9, 10, 11, 12, 13, 14, 16, 17,
+ 19, 21, 23, 25, 28, 31, 34, 37, 41, 45,
+ 50, 55, 60, 66, 73, 80, 88, 97, 107, 118,
+ 130, 143, 157, 173, 190, 209, 230, 253, 279, 307,
+ 337, 371, 408, 449, 494, 544, 598, 658, 724, 796,
+ 876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066,
+ 2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358,
+ 5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899,
+ 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767
+};
+
+
+void S_AdpcmEncode( short indata[], char outdata[], int len, struct adpcm_state *state ) {
+ short *inp; /* Input buffer pointer */
+ signed char *outp; /* output buffer pointer */
+ int val; /* Current input sample value */
+ int sign; /* Current adpcm sign bit */
+ int delta; /* Current adpcm output value */
+ int diff; /* Difference between val and sample */
+ int step; /* Stepsize */
+ int valpred; /* Predicted output value */
+ int vpdiff; /* Current change to valpred */
+ int index; /* Current step change index */
+ int outputbuffer; /* place to keep previous 4-bit value */
+ int bufferstep; /* toggle between outputbuffer/output */
+
+ outp = (signed char *)outdata;
+ inp = indata;
+
+ valpred = state->sample;
+ index = state->index;
+ step = stepsizeTable[index];
+
+ outputbuffer = 0; // quiet a compiler warning
+ bufferstep = 1;
+
+ for ( ; len > 0 ; len-- ) {
+ val = *inp++;
+
+ /* Step 1 - compute difference with previous value */
+ diff = val - valpred;
+ sign = (diff < 0) ? 8 : 0;
+ if ( sign ) diff = (-diff);
+
+ /* Step 2 - Divide and clamp */
+ /* Note:
+ ** This code *approximately* computes:
+ ** delta = diff*4/step;
+ ** vpdiff = (delta+0.5)*step/4;
+ ** but in shift step bits are dropped. The net result of this is
+ ** that even if you have fast mul/div hardware you cannot put it to
+ ** good use since the fixup would be too expensive.
+ */
+ delta = 0;
+ vpdiff = (step >> 3);
+
+ if ( diff >= step ) {
+ delta = 4;
+ diff -= step;
+ vpdiff += step;
+ }
+ step >>= 1;
+ if ( diff >= step ) {
+ delta |= 2;
+ diff -= step;
+ vpdiff += step;
+ }
+ step >>= 1;
+ if ( diff >= step ) {
+ delta |= 1;
+ vpdiff += step;
+ }
+
+ /* Step 3 - Update previous value */
+ if ( sign )
+ valpred -= vpdiff;
+ else
+ valpred += vpdiff;
+
+ /* Step 4 - Clamp previous value to 16 bits */
+ if ( valpred > 32767 )
+ valpred = 32767;
+ else if ( valpred < -32768 )
+ valpred = -32768;
+
+ /* Step 5 - Assemble value, update index and step values */
+ delta |= sign;
+
+ index += indexTable[delta];
+ if ( index < 0 ) index = 0;
+ if ( index > 88 ) index = 88;
+ step = stepsizeTable[index];
+
+ /* Step 6 - Output value */
+ if ( bufferstep ) {
+ outputbuffer = (delta << 4) & 0xf0;
+ } else {
+ *outp++ = (delta & 0x0f) | outputbuffer;
+ }
+ bufferstep = !bufferstep;
+ }
+
+ /* Output last step, if needed */
+ if ( !bufferstep )
+ *outp++ = outputbuffer;
+
+ state->sample = valpred;
+ state->index = index;
+}
+
+
+/* static */ void S_AdpcmDecode( const char indata[], short *outdata, int len, struct adpcm_state *state ) {
+ signed char *inp; /* Input buffer pointer */
+ int outp; /* output buffer pointer */
+ int sign; /* Current adpcm sign bit */
+ int delta; /* Current adpcm output value */
+ int step; /* Stepsize */
+ int valpred; /* Predicted value */
+ int vpdiff; /* Current change to valpred */
+ int index; /* Current step change index */
+ int inputbuffer; /* place to keep next 4-bit value */
+ int bufferstep; /* toggle between inputbuffer/input */
+
+ outp = 0;
+ inp = (signed char *)indata;
+
+ valpred = state->sample;
+ index = state->index;
+ step = stepsizeTable[index];
+
+ bufferstep = 0;
+ inputbuffer = 0; // quiet a compiler warning
+ for ( ; len > 0 ; len-- ) {
+
+ /* Step 1 - get the delta value */
+ if ( bufferstep ) {
+ delta = inputbuffer & 0xf;
+ } else {
+ inputbuffer = *inp++;
+ delta = (inputbuffer >> 4) & 0xf;
+ }
+ bufferstep = !bufferstep;
+
+ /* Step 2 - Find new index value (for later) */
+ index += indexTable[delta];
+ if ( index < 0 ) index = 0;
+ if ( index > 88 ) index = 88;
+
+ /* Step 3 - Separate sign and magnitude */
+ sign = delta & 8;
+ delta = delta & 7;
+
+ /* Step 4 - Compute difference and new predicted value */
+ /*
+ ** Computes 'vpdiff = (delta+0.5)*step/4', but see comment
+ ** in adpcm_coder.
+ */
+ vpdiff = step >> 3;
+ if ( delta & 4 ) vpdiff += step;
+ if ( delta & 2 ) vpdiff += step>>1;
+ if ( delta & 1 ) vpdiff += step>>2;
+
+ if ( sign )
+ valpred -= vpdiff;
+ else
+ valpred += vpdiff;
+
+ /* Step 5 - clamp output value */
+ if ( valpred > 32767 )
+ valpred = 32767;
+ else if ( valpred < -32768 )
+ valpred = -32768;
+
+ /* Step 6 - Update step value */
+ step = stepsizeTable[index];
+
+ /* Step 7 - Output value */
+ outdata[outp] = valpred;
+ outp++;
+ }
+
+ state->sample = valpred;
+ state->index = index;
+}
+
+
+/*
+====================
+S_AdpcmMemoryNeeded
+
+Returns the amount of memory (in bytes) needed to store the samples in out internal adpcm format
+====================
+*/
+int S_AdpcmMemoryNeeded( const wavinfo_t *info ) {
+ float scale;
+ int scaledSampleCount;
+ int sampleMemory;
+ int blockCount;
+ int headerMemory;
+
+ // determine scale to convert from input sampling rate to desired sampling rate
+ scale = (float)info->rate / dma.speed;
+
+ // calc number of samples at playback sampling rate
+ scaledSampleCount = info->samples / scale;
+
+ // calc memory need to store those samples using ADPCM at 4 bits per sample
+ sampleMemory = scaledSampleCount / 2;
+
+ // calc number of sample blocks needed of PAINTBUFFER_SIZE
+ blockCount = scaledSampleCount / PAINTBUFFER_SIZE;
+ if( scaledSampleCount % PAINTBUFFER_SIZE ) {
+ blockCount++;
+ }
+
+ // calc memory needed to store the block headers
+ headerMemory = blockCount * sizeof(adpcm_state_t);
+
+ return sampleMemory + headerMemory;
+}
+
+
+/*
+====================
+S_AdpcmGetSamples
+====================
+*/
+void S_AdpcmGetSamples(sndBuffer *chunk, short *to) {
+ adpcm_state_t state;
+ byte *out;
+
+ // get the starting state from the block header
+ state.index = chunk->adpcm.index;
+ state.sample = chunk->adpcm.sample;
+
+ out = (byte *)chunk->sndChunk;
+ // get samples
+ S_AdpcmDecode((char *) out, to, SND_CHUNK_SIZE_BYTE*2, &state );
+}
+
+
+/*
+====================
+S_AdpcmEncodeSound
+====================
+*/
+void S_AdpcmEncodeSound( sfx_t *sfx, short *samples ) {
+ adpcm_state_t state;
+ int inOffset;
+ int count;
+ int n;
+ sndBuffer *newchunk, *chunk;
+ byte *out;
+
+ inOffset = 0;
+ count = sfx->soundLength;
+ state.index = 0;
+ state.sample = samples[0];
+
+ chunk = NULL;
+ while( count ) {
+ n = count;
+ if( n > SND_CHUNK_SIZE_BYTE*2 ) {
+ n = SND_CHUNK_SIZE_BYTE*2;
+ }
+
+ newchunk = SND_malloc();
+ if (sfx->soundData == NULL) {
+ sfx->soundData = newchunk;
+ } else if (chunk != NULL) {
+ chunk->next = newchunk;
+ }
+ chunk = newchunk;
+
+ // output the header
+ chunk->adpcm.index = state.index;
+ chunk->adpcm.sample = state.sample;
+
+ out = (byte *)chunk->sndChunk;
+
+ // encode the samples
+ S_AdpcmEncode( samples + inOffset, (char *) out, n, &state );
+
+ inOffset += n;
+ count -= n;
+ }
+}
diff --git a/src/client/snd_codec.cpp b/src/client/snd_codec.cpp
new file mode 100644
index 0000000..a794b90
--- /dev/null
+++ b/src/client/snd_codec.cpp
@@ -0,0 +1,239 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2005 Stuart Dalton (badcdev@gmail.com)
+Copyright (C) 2015-2019 GrangerHub
+
+This file is part of Tremulous.
+
+Tremulous 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.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+
+#include "client.h"
+#include "snd_codec.h"
+
+static snd_codec_t *codecs;
+
+/*
+=================
+S_CodecGetSound
+
+Opens/loads a sound, tries codec based on the sound's file extension
+then tries all supported codecs.
+=================
+*/
+static void *S_CodecGetSound(const char *filename, snd_info_t *info)
+{
+ snd_codec_t *codec;
+ snd_codec_t *orgCodec = NULL;
+ bool orgNameFailed = false;
+ char localName[ MAX_QPATH ];
+ const char *ext;
+ char altName[ MAX_QPATH ];
+ void *rtn = NULL;
+
+ Q_strncpyz(localName, filename, MAX_QPATH);
+
+ ext = COM_GetExtension(localName);
+
+ if( *ext )
+ {
+ // Look for the correct loader and use it
+ for( codec = codecs; codec; codec = codec->next )
+ {
+ if( !Q_stricmp( ext, codec->ext ) )
+ {
+ // Load
+ if( info )
+ rtn = codec->load(localName, info);
+ else
+ rtn = codec->open(localName);
+ break;
+ }
+ }
+
+ // A loader was found
+ if( codec )
+ {
+ if( !rtn )
+ {
+ // Loader failed, most likely because the file isn't there;
+ // try again without the extension
+ orgNameFailed = true;
+ orgCodec = codec;
+ COM_StripExtension( filename, localName, MAX_QPATH );
+ }
+ else
+ {
+ // Something loaded
+ return rtn;
+ }
+ }
+ }
+
+ // Try and find a suitable match using all
+ // the sound codecs supported
+ for( codec = codecs; codec; codec = codec->next )
+ {
+ if( codec == orgCodec )
+ continue;
+
+ Com_sprintf( altName, sizeof (altName), "%s.%s", localName, codec->ext );
+
+ // Load
+ if( info )
+ rtn = codec->load(altName, info);
+ else
+ rtn = codec->open(altName);
+
+ if( rtn )
+ {
+ if( orgNameFailed )
+ {
+ Com_DPrintf(S_COLOR_YELLOW "WARNING: %s not present, using %s instead\n",
+ filename, altName );
+ }
+
+ return rtn;
+ }
+ }
+
+ Com_Printf(S_COLOR_YELLOW "WARNING: Failed to %s sound %s!\n", info ? "load" : "open", filename);
+
+ return NULL;
+}
+
+/*
+=================
+S_CodecInit
+=================
+*/
+void S_CodecInit()
+{
+ codecs = NULL;
+
+#ifdef USE_CODEC_OPUS
+ S_CodecRegister(&opus_codec);
+#endif
+
+#ifdef USE_CODEC_VORBIS
+ S_CodecRegister(&ogg_codec);
+#endif
+
+// Register wav codec last so that it is always tried first when a file extension was not found
+ S_CodecRegister(&wav_codec);
+}
+
+/*
+=================
+S_CodecShutdown
+=================
+*/
+void S_CodecShutdown()
+{
+ codecs = NULL;
+}
+
+/*
+=================
+S_CodecRegister
+=================
+*/
+void S_CodecRegister(snd_codec_t *codec)
+{
+ codec->next = codecs;
+ codecs = codec;
+}
+
+/*
+=================
+S_CodecLoad
+=================
+*/
+void *S_CodecLoad(const char *filename, snd_info_t *info)
+{
+ return S_CodecGetSound(filename, info);
+}
+
+/*
+=================
+S_CodecOpenStream
+=================
+*/
+snd_stream_t *S_CodecOpenStream(const char *filename)
+{
+ return (snd_stream_t*)S_CodecGetSound(filename, NULL);
+}
+
+void S_CodecCloseStream(snd_stream_t *stream)
+{
+ stream->codec->close(stream);
+}
+
+int S_CodecReadStream(snd_stream_t *stream, int bytes, void *buffer)
+{
+ return stream->codec->read(stream, bytes, buffer);
+}
+
+//=======================================================================
+// Util functions (used by codecs)
+
+/*
+=================
+S_CodecUtilOpen
+=================
+*/
+snd_stream_t *S_CodecUtilOpen(const char *filename, snd_codec_t *codec)
+{
+ snd_stream_t *stream;
+ fileHandle_t hnd;
+ int length;
+
+ // Try to open the file
+ length = FS_FOpenFileRead(filename, &hnd, true);
+ if(!hnd)
+ {
+ Com_DPrintf("Can't read sound file %s\n", filename);
+ return NULL;
+ }
+
+ // Allocate a stream
+ stream = (snd_stream_t*)Z_Malloc(sizeof(snd_stream_t));
+ if(!stream)
+ {
+ FS_FCloseFile(hnd);
+ return NULL;
+ }
+
+ // Copy over, return
+ stream->codec = codec;
+ stream->file = hnd;
+ stream->length = length;
+ return stream;
+}
+
+/*
+=================
+S_CodecUtilClose
+=================
+*/
+void S_CodecUtilClose(snd_stream_t **stream)
+{
+ FS_FCloseFile((*stream)->file);
+ Z_Free(*stream);
+ *stream = NULL;
+}
diff --git a/src/client/snd_codec.h b/src/client/snd_codec.h
new file mode 100644
index 0000000..a5f4358
--- /dev/null
+++ b/src/client/snd_codec.h
@@ -0,0 +1,109 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2005 Stuart Dalton (badcdev@gmail.com)
+Copyright (C) 2015-2019 GrangerHub
+
+This file is part of Tremulous.
+
+Tremulous 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.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+
+#ifndef _SND_CODEC_H_
+#define _SND_CODEC_H_
+
+#include "qcommon/q_shared.h"
+#include "qcommon/qcommon.h"
+
+typedef struct snd_info_s
+{
+ int rate;
+ int width;
+ int channels;
+ int samples;
+ int size;
+ int dataofs;
+} snd_info_t;
+
+typedef struct snd_codec_s snd_codec_t;
+
+typedef struct snd_stream_s
+{
+ snd_codec_t *codec;
+ fileHandle_t file;
+ snd_info_t info;
+ int length;
+ int pos;
+ void *ptr;
+} snd_stream_t;
+
+// Codec functions
+typedef void *(*CODEC_LOAD)(const char *filename, snd_info_t *info);
+typedef snd_stream_t *(*CODEC_OPEN)(const char *filename);
+typedef int (*CODEC_READ)(snd_stream_t *stream, int bytes, void *buffer);
+typedef void (*CODEC_CLOSE)(snd_stream_t *stream);
+
+// Codec data structure
+struct snd_codec_s
+{
+ const char *ext;
+ CODEC_LOAD load;
+ CODEC_OPEN open;
+ CODEC_READ read;
+ CODEC_CLOSE close;
+ snd_codec_t *next;
+};
+
+// Codec management
+void S_CodecInit( void );
+void S_CodecShutdown( void );
+void S_CodecRegister(snd_codec_t *codec);
+void *S_CodecLoad(const char *filename, snd_info_t *info);
+snd_stream_t *S_CodecOpenStream(const char *filename);
+void S_CodecCloseStream(snd_stream_t *stream);
+int S_CodecReadStream(snd_stream_t *stream, int bytes, void *buffer);
+
+// Util functions (used by codecs)
+snd_stream_t *S_CodecUtilOpen(const char *filename, snd_codec_t *codec);
+void S_CodecUtilClose(snd_stream_t **stream);
+
+// WAV Codec
+extern snd_codec_t wav_codec;
+void *S_WAV_CodecLoad(const char *filename, snd_info_t *info);
+snd_stream_t *S_WAV_CodecOpenStream(const char *filename);
+void S_WAV_CodecCloseStream(snd_stream_t *stream);
+int S_WAV_CodecReadStream(snd_stream_t *stream, int bytes, void *buffer);
+
+// Ogg Vorbis codec
+#ifdef USE_CODEC_VORBIS
+extern snd_codec_t ogg_codec;
+void *S_OGG_CodecLoad(const char *filename, snd_info_t *info);
+snd_stream_t *S_OGG_CodecOpenStream(const char *filename);
+void S_OGG_CodecCloseStream(snd_stream_t *stream);
+int S_OGG_CodecReadStream(snd_stream_t *stream, int bytes, void *buffer);
+#endif // USE_CODEC_VORBIS
+
+// Ogg Opus codec
+#ifdef USE_CODEC_OPUS
+extern snd_codec_t opus_codec;
+void *S_OggOpus_CodecLoad(const char *filename, snd_info_t *info);
+snd_stream_t *S_OggOpus_CodecOpenStream(const char *filename);
+void S_OggOpus_CodecCloseStream(snd_stream_t *stream);
+int S_OggOpus_CodecReadStream(snd_stream_t *stream, int bytes, void *buffer);
+#endif // USE_CODEC_OPUS
+
+#endif // !_SND_CODEC_H_
diff --git a/src/client/snd_codec_ogg.cpp b/src/client/snd_codec_ogg.cpp
new file mode 100644
index 0000000..15fcd37
--- /dev/null
+++ b/src/client/snd_codec_ogg.cpp
@@ -0,0 +1,479 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2005 Stuart Dalton (badcdev@gmail.com)
+Copyright (C) 2005-2006 Joerg Dietrich <dietrich_joerg@gmx.de>
+Copyright (C) 2015-2019 GrangerHub
+
+This file is part of Tremulous.
+
+Tremulous 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.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+
+// OGG support is enabled by this define
+#ifdef USE_CODEC_VORBIS
+
+// includes for the Q3 sound system
+#include "client.h"
+
+// includes for the OGG codec
+#define OV_EXCLUDE_STATIC_CALLBACKS
+#include <vorbis/vorbisfile.h>
+
+#include <cerrno>
+
+#include "snd_codec.h"
+
+// The OGG codec can return the samples in a number of different formats,
+// we use the standard signed short format.
+#define OGG_SAMPLEWIDTH 2
+
+// Q3 OGG codec
+snd_codec_t ogg_codec =
+{
+ "ogg",
+ S_OGG_CodecLoad,
+ S_OGG_CodecOpenStream,
+ S_OGG_CodecReadStream,
+ S_OGG_CodecCloseStream,
+ NULL
+};
+
+// callbacks for vobisfile
+
+// fread() replacement
+size_t S_OGG_Callback_read(void *ptr, size_t size, size_t nmemb, void *datasource)
+{
+ snd_stream_t *stream;
+ int byteSize = 0;
+ int bytesRead = 0;
+ size_t nMembRead = 0;
+
+ // check if input is valid
+ if(!ptr)
+ {
+ errno = EFAULT;
+ return 0;
+ }
+
+ if(!(size && nmemb))
+ {
+ // It's not an error, caller just wants zero bytes!
+ errno = 0;
+ return 0;
+ }
+
+ if(!datasource)
+ {
+ errno = EBADF;
+ return 0;
+ }
+
+ // we use a snd_stream_t in the generic pointer to pass around
+ stream = (snd_stream_t *) datasource;
+
+ // FS_Read does not support multi-byte elements
+ byteSize = nmemb * size;
+
+ // read it with the Q3 function FS_Read()
+ bytesRead = FS_Read(ptr, byteSize, stream->file);
+
+ // update the file position
+ stream->pos += bytesRead;
+
+ // this function returns the number of elements read not the number of bytes
+ nMembRead = bytesRead / size;
+
+ // even if the last member is only read partially
+ // it is counted as a whole in the return value
+ if(bytesRead % size)
+ {
+ nMembRead++;
+ }
+
+ return nMembRead;
+}
+
+// fseek() replacement
+int S_OGG_Callback_seek(void *datasource, ogg_int64_t offset, int whence)
+{
+ snd_stream_t *stream;
+ int retVal = 0;
+
+ // check if input is valid
+ if(!datasource)
+ {
+ errno = EBADF;
+ return -1;
+ }
+
+ // snd_stream_t in the generic pointer
+ stream = (snd_stream_t *) datasource;
+
+ // we must map the whence to its Q3 counterpart
+ switch(whence)
+ {
+ case SEEK_SET :
+ {
+ // set the file position in the actual file with the Q3 function
+ retVal = FS_Seek(stream->file, (long) offset, FS_SEEK_SET);
+
+ // something has gone wrong, so we return here
+ if(retVal < 0)
+ {
+ return retVal;
+ }
+
+ // keep track of file position
+ stream->pos = (int) offset;
+ break;
+ }
+
+ case SEEK_CUR :
+ {
+ // set the file position in the actual file with the Q3 function
+ retVal = FS_Seek(stream->file, (long) offset, FS_SEEK_CUR);
+
+ // something has gone wrong, so we return here
+ if(retVal < 0)
+ {
+ return retVal;
+ }
+
+ // keep track of file position
+ stream->pos += (int) offset;
+ break;
+ }
+
+ case SEEK_END :
+ {
+ // set the file position in the actual file with the Q3 function
+ retVal = FS_Seek(stream->file, (long) offset, FS_SEEK_END);
+
+ // something has gone wrong, so we return here
+ if(retVal < 0)
+ {
+ return retVal;
+ }
+
+ // keep track of file position
+ stream->pos = stream->length + (int) offset;
+ break;
+ }
+
+ default :
+ {
+ // unknown whence, so we return an error
+ errno = EINVAL;
+ return -1;
+ }
+ }
+
+ // stream->pos shouldn't be smaller than zero or bigger than the filesize
+ stream->pos = (stream->pos < 0) ? 0 : stream->pos;
+ stream->pos = (stream->pos > stream->length) ? stream->length : stream->pos;
+
+ return 0;
+}
+
+// fclose() replacement
+int S_OGG_Callback_close(void *datasource)
+{
+ // we do nothing here and close all things manually in S_OGG_CodecCloseStream()
+ return 0;
+}
+
+// ftell() replacement
+long S_OGG_Callback_tell(void *datasource)
+{
+ snd_stream_t *stream;
+
+ // check if input is valid
+ if(!datasource)
+ {
+ errno = EBADF;
+ return -1;
+ }
+
+ // snd_stream_t in the generic pointer
+ stream = (snd_stream_t *) datasource;
+
+ return (long) FS_FTell(stream->file);
+}
+
+// the callback structure
+const ov_callbacks S_OGG_Callbacks =
+{
+ &S_OGG_Callback_read,
+ &S_OGG_Callback_seek,
+ &S_OGG_Callback_close,
+ &S_OGG_Callback_tell
+};
+
+/*
+=================
+S_OGG_CodecOpenStream
+=================
+*/
+snd_stream_t *S_OGG_CodecOpenStream(const char *filename)
+{
+ snd_stream_t *stream;
+
+ // OGG codec control structure
+ OggVorbis_File *vf;
+
+ // some variables used to get informations about the OGG
+ vorbis_info *OGGInfo;
+ ogg_int64_t numSamples;
+
+ // check if input is valid
+ if(!filename)
+ {
+ return NULL;
+ }
+
+ // Open the stream
+ stream = S_CodecUtilOpen(filename, &ogg_codec);
+ if(!stream)
+ {
+ return NULL;
+ }
+
+ // alloctate the OggVorbis_File
+ vf = (OggVorbis_File*)Z_Malloc(sizeof(OggVorbis_File));
+ if(!vf)
+ {
+ S_CodecUtilClose(&stream);
+
+ return NULL;
+ }
+
+ // open the codec with our callbacks and stream as the generic pointer
+ if(ov_open_callbacks(stream, vf, NULL, 0, S_OGG_Callbacks) != 0)
+ {
+ Z_Free(vf);
+
+ S_CodecUtilClose(&stream);
+
+ return NULL;
+ }
+
+ // the stream must be seekable
+ if(!ov_seekable(vf))
+ {
+ ov_clear(vf);
+
+ Z_Free(vf);
+
+ S_CodecUtilClose(&stream);
+
+ return NULL;
+ }
+
+ // we only support OGGs with one substream
+ if(ov_streams(vf) != 1)
+ {
+ ov_clear(vf);
+
+ Z_Free(vf);
+
+ S_CodecUtilClose(&stream);
+
+ return NULL;
+ }
+
+ // get the info about channels and rate
+ OGGInfo = ov_info(vf, 0);
+ if(!OGGInfo)
+ {
+ ov_clear(vf);
+
+ Z_Free(vf);
+
+ S_CodecUtilClose(&stream);
+
+ return NULL;
+ }
+
+ // get the number of sample-frames in the OGG
+ numSamples = ov_pcm_total(vf, 0);
+
+ // fill in the info-structure in the stream
+ stream->info.rate = OGGInfo->rate;
+ stream->info.width = OGG_SAMPLEWIDTH;
+ stream->info.channels = OGGInfo->channels;
+ stream->info.samples = numSamples;
+ stream->info.size = stream->info.samples * stream->info.channels * stream->info.width;
+ stream->info.dataofs = 0;
+
+ // We use stream->pos for the file pointer in the compressed ogg file
+ stream->pos = 0;
+
+ // We use the generic pointer in stream for the OGG codec control structure
+ stream->ptr = vf;
+
+ return stream;
+}
+
+/*
+=================
+S_OGG_CodecCloseStream
+=================
+*/
+void S_OGG_CodecCloseStream(snd_stream_t *stream)
+{
+ // check if input is valid
+ if(!stream)
+ {
+ return;
+ }
+
+ // let the OGG codec cleanup its stuff
+ ov_clear((OggVorbis_File *) stream->ptr);
+
+ // free the OGG codec control struct
+ Z_Free(stream->ptr);
+
+ // close the stream
+ S_CodecUtilClose(&stream);
+}
+
+/*
+=================
+S_OGG_CodecReadStream
+=================
+*/
+int S_OGG_CodecReadStream(snd_stream_t *stream, int bytes, void *buffer)
+{
+ // buffer handling
+ int bytesRead, bytesLeft, c;
+ char *bufPtr;
+
+ // Bitstream for the decoder
+ int BS = 0;
+
+ // big endian machines want their samples in big endian order
+ int IsBigEndian = 0;
+
+# ifdef Q3_BIG_ENDIAN
+ IsBigEndian = 1;
+# endif // Q3_BIG_ENDIAN
+
+ // check if input is valid
+ if(!(stream && buffer))
+ {
+ return 0;
+ }
+
+ if(bytes <= 0)
+ {
+ return 0;
+ }
+
+ bytesRead = 0;
+ bytesLeft = bytes;
+ bufPtr = (char*)buffer;
+
+ // cycle until we have the requested or all available bytes read
+ while(-1)
+ {
+ // read some bytes from the OGG codec
+ c = ov_read((OggVorbis_File *) stream->ptr, bufPtr, bytesLeft, IsBigEndian, OGG_SAMPLEWIDTH, 1, &BS);
+
+ // no more bytes are left
+ if(c <= 0)
+ {
+ break;
+ }
+
+ bytesRead += c;
+ bytesLeft -= c;
+ bufPtr += c;
+
+ // we have enough bytes
+ if(bytesLeft <= 0)
+ {
+ break;
+ }
+ }
+
+ return bytesRead;
+}
+
+/*
+=====================================================================
+S_OGG_CodecLoad
+
+We handle S_OGG_CodecLoad as a special case of the streaming functions
+where we read the whole stream at once.
+======================================================================
+*/
+void *S_OGG_CodecLoad(const char *filename, snd_info_t *info)
+{
+ snd_stream_t *stream;
+ byte *buffer;
+ int bytesRead;
+
+ // check if input is valid
+ if(!(filename && info))
+ {
+ return NULL;
+ }
+
+ // open the file as a stream
+ stream = S_OGG_CodecOpenStream(filename);
+ if(!stream)
+ {
+ return NULL;
+ }
+
+ // copy over the info
+ info->rate = stream->info.rate;
+ info->width = stream->info.width;
+ info->channels = stream->info.channels;
+ info->samples = stream->info.samples;
+ info->size = stream->info.size;
+ info->dataofs = stream->info.dataofs;
+
+ // allocate a buffer
+ // this buffer must be free-ed by the caller of this function
+ buffer = (byte*)Hunk_AllocateTempMemory(info->size);
+ if(!buffer)
+ {
+ S_OGG_CodecCloseStream(stream);
+
+ return NULL;
+ }
+
+ // fill the buffer
+ bytesRead = S_OGG_CodecReadStream(stream, info->size, buffer);
+
+ // we don't even have read a single byte
+ if(bytesRead <= 0)
+ {
+ Hunk_FreeTempMemory(buffer);
+ S_OGG_CodecCloseStream(stream);
+
+ return NULL;
+ }
+
+ S_OGG_CodecCloseStream(stream);
+
+ return buffer;
+}
+
+#endif // USE_CODEC_VORBIS
diff --git a/src/client/snd_codec_opus.cpp b/src/client/snd_codec_opus.cpp
new file mode 100644
index 0000000..a8ffba5
--- /dev/null
+++ b/src/client/snd_codec_opus.cpp
@@ -0,0 +1,452 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2005 Stuart Dalton (badcdev@gmail.com)
+Copyright (C) 2005-2006 Joerg Dietrich <dietrich_joerg@gmx.de>
+Copyright (C) 2015-2019 GrangerHub
+
+This file is part of Tremulous.
+
+Tremulous 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.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+
+// Ogg Opus support is enabled by this define
+#ifdef USE_CODEC_OPUS
+
+// includes for the Q3 sound system
+#include "client.h"
+
+// includes for the Ogg Opus codec
+#include <opusfile.h>
+
+#include <cerrno>
+
+#include "snd_codec.h"
+
+// samples are 16 bit
+#define OPUS_SAMPLEWIDTH 2
+
+// Q3 Ogg Opus codec
+snd_codec_t opus_codec =
+{
+ "opus",
+ S_OggOpus_CodecLoad,
+ S_OggOpus_CodecOpenStream,
+ S_OggOpus_CodecReadStream,
+ S_OggOpus_CodecCloseStream,
+ NULL
+};
+
+// callbacks for opusfile
+
+// fread() replacement
+int S_OggOpus_Callback_read(void *datasource, unsigned char *ptr, int size )
+{
+ snd_stream_t *stream;
+ int bytesRead = 0;
+
+ // check if input is valid
+ if(!ptr)
+ {
+ errno = EFAULT;
+ return -1;
+ }
+
+ if(!size)
+ {
+ // It's not an error, caller just wants zero bytes!
+ errno = 0;
+ return 0;
+ }
+
+ if (size < 0)
+ {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if(!datasource)
+ {
+ errno = EBADF;
+ return -1;
+ }
+
+ // we use a snd_stream_t in the generic pointer to pass around
+ stream = (snd_stream_t *) datasource;
+
+ // read it with the Q3 function FS_Read()
+ bytesRead = FS_Read(ptr, size, stream->file);
+
+ // update the file position
+ stream->pos += bytesRead;
+
+ return bytesRead;
+}
+
+// fseek() replacement
+int S_OggOpus_Callback_seek(void *datasource, opus_int64 offset, int whence)
+{
+ snd_stream_t *stream;
+ int retVal = 0;
+
+ // check if input is valid
+ if(!datasource)
+ {
+ errno = EBADF;
+ return -1;
+ }
+
+ // snd_stream_t in the generic pointer
+ stream = (snd_stream_t *) datasource;
+
+ // we must map the whence to its Q3 counterpart
+ switch(whence)
+ {
+ case SEEK_SET :
+ {
+ // set the file position in the actual file with the Q3 function
+ retVal = FS_Seek(stream->file, (long) offset, FS_SEEK_SET);
+
+ // something has gone wrong, so we return here
+ if(retVal < 0)
+ {
+ return retVal;
+ }
+
+ // keep track of file position
+ stream->pos = (int) offset;
+ break;
+ }
+
+ case SEEK_CUR :
+ {
+ // set the file position in the actual file with the Q3 function
+ retVal = FS_Seek(stream->file, (long) offset, FS_SEEK_CUR);
+
+ // something has gone wrong, so we return here
+ if(retVal < 0)
+ {
+ return retVal;
+ }
+
+ // keep track of file position
+ stream->pos += (int) offset;
+ break;
+ }
+
+ case SEEK_END :
+ {
+ // set the file position in the actual file with the Q3 function
+ retVal = FS_Seek(stream->file, (long) offset, FS_SEEK_END);
+
+ // something has gone wrong, so we return here
+ if(retVal < 0)
+ {
+ return retVal;
+ }
+
+ // keep track of file position
+ stream->pos = stream->length + (int) offset;
+ break;
+ }
+
+ default :
+ {
+ // unknown whence, so we return an error
+ errno = EINVAL;
+ return -1;
+ }
+ }
+
+ // stream->pos shouldn't be smaller than zero or bigger than the filesize
+ stream->pos = (stream->pos < 0) ? 0 : stream->pos;
+ stream->pos = (stream->pos > stream->length) ? stream->length : stream->pos;
+
+ return 0;
+}
+
+// fclose() replacement
+int S_OggOpus_Callback_close(void *datasource)
+{
+ // we do nothing here and close all things manually in S_OggOpus_CodecCloseStream()
+ return 0;
+}
+
+// ftell() replacement
+opus_int64 S_OggOpus_Callback_tell(void *datasource)
+{
+ snd_stream_t *stream;
+
+ // check if input is valid
+ if(!datasource)
+ {
+ errno = EBADF;
+ return -1;
+ }
+
+ // snd_stream_t in the generic pointer
+ stream = (snd_stream_t *) datasource;
+
+ return (opus_int64) FS_FTell(stream->file);
+}
+
+// the callback structure
+const OpusFileCallbacks S_OggOpus_Callbacks =
+{
+ &S_OggOpus_Callback_read,
+ &S_OggOpus_Callback_seek,
+ &S_OggOpus_Callback_tell,
+ &S_OggOpus_Callback_close
+};
+
+/*
+=================
+S_OggOpus_CodecOpenStream
+=================
+*/
+snd_stream_t *S_OggOpus_CodecOpenStream(const char *filename)
+{
+ snd_stream_t *stream;
+
+ // Opus codec control structure
+ OggOpusFile *of;
+
+ // some variables used to get informations about the file
+ const OpusHead *opusInfo;
+ ogg_int64_t numSamples;
+
+ // check if input is valid
+ if(!filename)
+ {
+ return NULL;
+ }
+
+ // Open the stream
+ stream = S_CodecUtilOpen(filename, &opus_codec);
+ if(!stream)
+ {
+ return NULL;
+ }
+
+ // open the codec with our callbacks and stream as the generic pointer
+ of = op_open_callbacks(stream, &S_OggOpus_Callbacks, NULL, 0, NULL );
+ if (!of)
+ {
+ S_CodecUtilClose(&stream);
+
+ return NULL;
+ }
+
+ // the stream must be seekable
+ if(!op_seekable(of))
+ {
+ op_free(of);
+
+ S_CodecUtilClose(&stream);
+
+ return NULL;
+ }
+
+ // get the info about channels and rate
+ opusInfo = op_head(of, -1);
+ if(!opusInfo)
+ {
+ op_free(of);
+
+ S_CodecUtilClose(&stream);
+
+ return NULL;
+ }
+
+ if(opusInfo->stream_count != 1)
+ {
+ op_free(of);
+
+ S_CodecUtilClose(&stream);
+
+ Com_Printf("Only Ogg Opus files with one stream are support\n");
+ return NULL;
+ }
+
+ if(opusInfo->channel_count != 1 && opusInfo->channel_count != 2)
+ {
+ op_free(of);
+
+ S_CodecUtilClose(&stream);
+
+ Com_Printf("Only mono and stereo Ogg Opus files are supported\n");
+ return NULL;
+ }
+
+ // get the number of sample-frames in the file
+ numSamples = op_pcm_total(of, -1);
+
+ // fill in the info-structure in the stream
+ stream->info.rate = 48000;
+ stream->info.width = OPUS_SAMPLEWIDTH;
+ stream->info.channels = opusInfo->channel_count;
+ stream->info.samples = numSamples;
+ stream->info.size = stream->info.samples * stream->info.channels * stream->info.width;
+ stream->info.dataofs = 0;
+
+ // We use stream->pos for the file pointer in the compressed ogg file
+ stream->pos = 0;
+
+ // We use the generic pointer in stream for the opus codec control structure
+ stream->ptr = of;
+
+ return stream;
+}
+
+/*
+=================
+S_OggOpus_CodecCloseStream
+=================
+*/
+void S_OggOpus_CodecCloseStream(snd_stream_t *stream)
+{
+ // check if input is valid
+ if(!stream)
+ {
+ return;
+ }
+
+ // let the opus codec cleanup its stuff
+ op_free((OggOpusFile *) stream->ptr);
+
+ // close the stream
+ S_CodecUtilClose(&stream);
+}
+
+/*
+=================
+S_OggOpus_CodecReadStream
+=================
+*/
+int S_OggOpus_CodecReadStream(snd_stream_t *stream, int bytes, void *buffer)
+{
+ // buffer handling
+ int samplesRead, samplesLeft, c;
+ opus_int16 *bufPtr;
+
+ // check if input is valid
+ if(!(stream && buffer))
+ {
+ return 0;
+ }
+
+ if(bytes <= 0)
+ {
+ return 0;
+ }
+
+ samplesRead = 0;
+ samplesLeft = bytes / stream->info.channels / stream->info.width;
+ bufPtr = (opus_int16*)buffer;
+
+ if(samplesLeft <= 0)
+ {
+ return 0;
+ }
+
+ // cycle until we have the requested or all available bytes read
+ while(-1)
+ {
+ // read some samples from the opus codec
+ c = op_read((OggOpusFile *) stream->ptr, bufPtr + samplesRead * stream->info.channels, samplesLeft * stream->info.channels, NULL);
+
+ // no more samples are left
+ if(c <= 0)
+ {
+ break;
+ }
+
+ samplesRead += c;
+ samplesLeft -= c;
+
+ // we have enough samples
+ if(samplesLeft <= 0)
+ {
+ break;
+ }
+ }
+
+ return samplesRead * stream->info.channels * stream->info.width;
+}
+
+/*
+=====================================================================
+S_OggOpus_CodecLoad
+
+We handle S_OggOpus_CodecLoad as a special case of the streaming functions
+where we read the whole stream at once.
+======================================================================
+*/
+void *S_OggOpus_CodecLoad(const char *filename, snd_info_t *info)
+{
+ snd_stream_t *stream;
+ byte *buffer;
+ int bytesRead;
+
+ // check if input is valid
+ if(!(filename && info))
+ {
+ return NULL;
+ }
+
+ // open the file as a stream
+ stream = S_OggOpus_CodecOpenStream(filename);
+ if(!stream)
+ {
+ return NULL;
+ }
+
+ // copy over the info
+ info->rate = stream->info.rate;
+ info->width = stream->info.width;
+ info->channels = stream->info.channels;
+ info->samples = stream->info.samples;
+ info->size = stream->info.size;
+ info->dataofs = stream->info.dataofs;
+
+ // allocate a buffer
+ // this buffer must be free-ed by the caller of this function
+ buffer = (byte*)Hunk_AllocateTempMemory(info->size);
+ if(!buffer)
+ {
+ S_OggOpus_CodecCloseStream(stream);
+
+ return NULL;
+ }
+
+ // fill the buffer
+ bytesRead = S_OggOpus_CodecReadStream(stream, info->size, buffer);
+
+ // we don't even have read a single byte
+ if(bytesRead <= 0)
+ {
+ Hunk_FreeTempMemory(buffer);
+ S_OggOpus_CodecCloseStream(stream);
+
+ return NULL;
+ }
+
+ S_OggOpus_CodecCloseStream(stream);
+
+ return buffer;
+}
+
+#endif // USE_CODEC_OPUS
diff --git a/src/client/snd_codec_wav.cpp b/src/client/snd_codec_wav.cpp
new file mode 100644
index 0000000..a4016e2
--- /dev/null
+++ b/src/client/snd_codec_wav.cpp
@@ -0,0 +1,293 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2005 Stuart Dalton (badcdev@gmail.com)
+Copyright (C) 2015-2019 GrangerHub
+
+This file is part of Tremulous.
+
+Tremulous 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.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+
+#include "client.h"
+#include "snd_codec.h"
+
+/*
+=================
+FGetLittleLong
+=================
+*/
+static int FGetLittleLong( fileHandle_t f ) {
+ int v;
+
+ FS_Read( &v, sizeof(v), f );
+
+ return LittleLong( v);
+}
+
+/*
+=================
+FGetLittleShort
+=================
+*/
+static short FGetLittleShort( fileHandle_t f ) {
+ short v;
+
+ FS_Read( &v, sizeof(v), f );
+
+ return LittleShort( v);
+}
+
+/*
+=================
+S_ReadChunkInfo
+=================
+*/
+static int S_ReadChunkInfo(fileHandle_t f, char *name)
+{
+ int len, r;
+
+ name[4] = 0;
+
+ r = FS_Read(name, 4, f);
+ if(r != 4)
+ return -1;
+
+ len = FGetLittleLong(f);
+ if( len < 0 ) {
+ Com_Printf( S_COLOR_YELLOW "WARNING: Negative chunk length\n" );
+ return -1;
+ }
+
+ return len;
+}
+
+/*
+=================
+S_FindRIFFChunk
+
+Returns the length of the data in the chunk, or -1 if not found
+=================
+*/
+static int S_FindRIFFChunk( fileHandle_t f, const char *chunk ) {
+ char name[5];
+ int len;
+
+ while( ( len = S_ReadChunkInfo(f, name) ) >= 0 )
+ {
+ // If this is the right chunk, return
+ if( !Q_strncmp( name, chunk, 4 ) )
+ return len;
+
+ len = PAD( len, 2 );
+
+ // Not the right chunk - skip it
+ FS_Seek( f, len, FS_SEEK_CUR );
+ }
+
+ return -1;
+}
+
+/*
+=================
+S_ByteSwapRawSamples
+=================
+*/
+static void S_ByteSwapRawSamples( int samples, int width, int s_channels, const byte *data ) {
+ int i;
+
+ if ( width != 2 ) {
+ return;
+ }
+ if ( LittleShort( 256 ) == 256 ) {
+ return;
+ }
+
+ if ( s_channels == 2 ) {
+ samples <<= 1;
+ }
+ for ( i = 0 ; i < samples ; i++ ) {
+ ((short *)data)[i] = LittleShort( ((short *)data)[i] );
+ }
+}
+
+/*
+=================
+S_ReadRIFFHeader
+=================
+*/
+static bool S_ReadRIFFHeader(fileHandle_t file, snd_info_t *info)
+{
+ char dump[16];
+ int bits;
+ int fmtlen = 0;
+
+ // skip the riff wav header
+ FS_Read(dump, 12, file);
+
+ // Scan for the format chunk
+ if((fmtlen = S_FindRIFFChunk(file, "fmt ")) < 0)
+ {
+ Com_Printf( S_COLOR_RED "ERROR: Couldn't find \"fmt\" chunk\n");
+ return false;
+ }
+
+ // Save the parameters
+ FGetLittleShort(file); // wav_format
+ info->channels = FGetLittleShort(file);
+ info->rate = FGetLittleLong(file);
+ FGetLittleLong(file);
+ FGetLittleShort(file);
+ bits = FGetLittleShort(file);
+
+ if( bits < 8 )
+ {
+ Com_Printf( S_COLOR_RED "ERROR: Less than 8 bit sound is not supported\n");
+ return false;
+ }
+
+ info->width = bits / 8;
+ info->dataofs = 0;
+
+ // Skip the rest of the format chunk if required
+ if(fmtlen > 16)
+ {
+ fmtlen -= 16;
+ FS_Seek( file, fmtlen, FS_SEEK_CUR );
+ }
+
+ // Scan for the data chunk
+ if( (info->size = S_FindRIFFChunk(file, "data")) < 0)
+ {
+ Com_Printf( S_COLOR_RED "ERROR: Couldn't find \"data\" chunk\n");
+ return false;
+ }
+ info->samples = (info->size / info->width) / info->channels;
+
+ return true;
+}
+
+// WAV codec
+snd_codec_t wav_codec =
+{
+ "wav",
+ S_WAV_CodecLoad,
+ S_WAV_CodecOpenStream,
+ S_WAV_CodecReadStream,
+ S_WAV_CodecCloseStream,
+ NULL
+};
+
+/*
+=================
+S_WAV_CodecLoad
+=================
+*/
+void *S_WAV_CodecLoad(const char *filename, snd_info_t *info)
+{
+ fileHandle_t file;
+ void *buffer;
+
+ // Try to open the file
+ FS_FOpenFileRead(filename, &file, true);
+ if(!file)
+ {
+ return NULL;
+ }
+
+ // Read the RIFF header
+ if(!S_ReadRIFFHeader(file, info))
+ {
+ FS_FCloseFile(file);
+ Com_Printf( S_COLOR_RED "ERROR: Incorrect/unsupported format in \"%s\"\n",
+ filename);
+ return NULL;
+ }
+
+ // Allocate some memory
+ buffer = Hunk_AllocateTempMemory(info->size);
+ if(!buffer)
+ {
+ FS_FCloseFile(file);
+ Com_Printf( S_COLOR_RED "ERROR: Out of memory reading \"%s\"\n",
+ filename);
+ return NULL;
+ }
+
+ // Read, byteswap
+ FS_Read(buffer, info->size, file);
+ S_ByteSwapRawSamples(info->samples, info->width, info->channels, (byte *)buffer);
+
+ // Close and return
+ FS_FCloseFile(file);
+ return buffer;
+}
+
+/*
+=================
+S_WAV_CodecOpenStream
+=================
+*/
+snd_stream_t *S_WAV_CodecOpenStream(const char *filename)
+{
+ snd_stream_t *rv;
+
+ // Open
+ rv = S_CodecUtilOpen(filename, &wav_codec);
+ if(!rv)
+ return NULL;
+
+ // Read the RIFF header
+ if(!S_ReadRIFFHeader(rv->file, &rv->info))
+ {
+ S_CodecUtilClose(&rv);
+ return NULL;
+ }
+
+ return rv;
+}
+
+/*
+=================
+S_WAV_CodecCloseStream
+=================
+*/
+void S_WAV_CodecCloseStream(snd_stream_t *stream)
+{
+ S_CodecUtilClose(&stream);
+}
+
+/*
+=================
+S_WAV_CodecReadStream
+=================
+*/
+int S_WAV_CodecReadStream(snd_stream_t *stream, int bytes, void *buffer)
+{
+ int remaining = stream->info.size - stream->pos;
+ int samples;
+
+ if(remaining <= 0)
+ return 0;
+ if(bytes > remaining)
+ bytes = remaining;
+ stream->pos += bytes;
+ samples = (bytes / stream->info.width) / stream->info.channels;
+ FS_Read(buffer, bytes, stream->file);
+ S_ByteSwapRawSamples(samples, stream->info.width, stream->info.channels, (byte*)buffer);
+ return bytes;
+}
diff --git a/src/client/snd_dma.cpp b/src/client/snd_dma.cpp
new file mode 100644
index 0000000..bef2b69
--- /dev/null
+++ b/src/client/snd_dma.cpp
@@ -0,0 +1,1644 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
+
+This file is part of Tremulous.
+
+Tremulous 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.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+
+/*****************************************************************************
+ * name: snd_dma.c
+ *
+ * desc: main control for any streaming sound output device
+ *
+ * $Archive: /MissionPack/code/client/snd_dma.c $
+ *
+ *****************************************************************************/
+
+#include "snd_local.h"
+#include "snd_codec.h"
+#include "client.h"
+
+void S_Update_( void );
+void S_Base_StopAllSounds(void);
+void S_Base_StopBackgroundTrack( void );
+
+snd_stream_t *s_backgroundStream = NULL;
+static char s_backgroundLoop[MAX_QPATH];
+//static char s_backgroundMusic[MAX_QPATH]; //TTimo: unused
+
+
+// =======================================================================
+// Internal sound data & structures
+// =======================================================================
+
+// only begin attenuating sound volumes when outside the FULLVOLUME range
+#define SOUND_FULLVOLUME 80
+
+#define SOUND_ATTENUATE 0.0008f
+
+channel_t s_channels[MAX_CHANNELS];
+channel_t loop_channels[MAX_CHANNELS];
+int numLoopChannels;
+
+static bool s_soundStarted;
+static bool s_soundMuted;
+
+dma_t dma;
+
+static int listener_number;
+static vec3_t listener_origin;
+static vec3_t listener_axis[3];
+
+int s_soundtime; // sample PAIRS
+int s_paintedtime; // sample PAIRS
+
+// MAX_SFX may be larger than MAX_SOUNDS because
+// of custom player sounds
+#define MAX_SFX 4096
+sfx_t s_knownSfx[MAX_SFX];
+int s_numSfx = 0;
+
+#define LOOP_HASH 128
+static sfx_t *sfxHash[LOOP_HASH];
+
+cvar_t *s_testsound;
+cvar_t *s_show;
+cvar_t *s_mixahead;
+cvar_t *s_mixPreStep;
+
+static loopSound_t loopSounds[MAX_GENTITIES];
+static channel_t *freelist = NULL;
+
+int s_rawend[MAX_RAW_STREAMS];
+portable_samplepair_t s_rawsamples[MAX_RAW_STREAMS][MAX_RAW_SAMPLES];
+
+
+// ====================================================================
+// User-setable variables
+// ====================================================================
+
+
+void S_Base_SoundInfo(void) {
+ Com_Printf("----- Sound Info -----\n" );
+ if (!s_soundStarted) {
+ Com_Printf ("sound system not started\n");
+ } else {
+ Com_Printf("%5d stereo\n", dma.channels - 1);
+ Com_Printf("%5d samples\n", dma.samples);
+ Com_Printf("%5d samplebits\n", dma.samplebits);
+ Com_Printf("%5d submission_chunk\n", dma.submission_chunk);
+ Com_Printf("%5d speed\n", dma.speed);
+ Com_Printf("%p dma buffer\n", dma.buffer);
+ if ( s_backgroundStream ) {
+ Com_Printf("Background file: %s\n", s_backgroundLoop );
+ } else {
+ Com_Printf("No background file.\n" );
+ }
+
+ }
+ Com_Printf("----------------------\n" );
+}
+
+
+#ifdef USE_VOIP
+static
+void S_Base_StartCapture( void )
+{
+ // !!! FIXME: write me.
+}
+
+static
+int S_Base_AvailableCaptureSamples( void )
+{
+ // !!! FIXME: write me.
+ return 0;
+}
+
+static
+void S_Base_Capture( int samples, byte *data )
+{
+ // !!! FIXME: write me.
+}
+
+static
+void S_Base_StopCapture( void )
+{
+ // !!! FIXME: write me.
+}
+
+static
+void S_Base_MasterGain( float val )
+{
+ // !!! FIXME: write me.
+}
+#endif
+
+
+
+/*
+=================
+S_Base_SoundList
+=================
+*/
+void S_Base_SoundList( void ) {
+ int i;
+ sfx_t *sfx;
+ int size, total;
+ char type[4][16];
+ char mem[2][16];
+
+ strcpy(type[0], "16bit");
+ strcpy(type[1], "adpcm");
+ strcpy(type[2], "daub4");
+ strcpy(type[3], "mulaw");
+ strcpy(mem[0], "paged out");
+ strcpy(mem[1], "resident ");
+ total = 0;
+ for (sfx=s_knownSfx, i=0 ; i<s_numSfx ; i++, sfx++) {
+ size = sfx->soundLength;
+ total += size;
+ Com_Printf("%6i[%s] : %s[%s]\n", size, type[sfx->soundCompressionMethod],
+ sfx->soundName, mem[sfx->inMemory] );
+ }
+ Com_Printf ("Total resident: %i\n", total);
+ S_DisplayFreeMemory();
+}
+
+
+
+void S_ChannelFree(channel_t *v) {
+ v->thesfx = NULL;
+ *(channel_t **)v = freelist;
+ freelist = (channel_t*)v;
+}
+
+channel_t* S_ChannelMalloc( void ) {
+ channel_t *v;
+ if (freelist == NULL) {
+ return NULL;
+ }
+ v = freelist;
+ freelist = *(channel_t **)freelist;
+ v->allocTime = Com_Milliseconds();
+ return v;
+}
+
+void S_ChannelSetup( void ) {
+ channel_t *p, *q;
+
+ // clear all the sounds so they don't
+ ::memset( s_channels, 0, sizeof( s_channels ) );
+
+ p = s_channels;;
+ q = p + MAX_CHANNELS;
+ while (--q > p) {
+ *(channel_t **)q = q-1;
+ }
+
+ *(channel_t **)q = NULL;
+ freelist = p + MAX_CHANNELS - 1;
+ Com_DPrintf("Channel memory manager started\n");
+}
+
+
+
+// =======================================================================
+// Load a sound
+// =======================================================================
+
+/*
+================
+return a hash value for the sfx name
+================
+*/
+static long S_HashSFXName(const char *name) {
+ int i;
+ long hash;
+ char letter;
+
+ hash = 0;
+ i = 0;
+ while (name[i] != '\0') {
+ letter = tolower(name[i]);
+ if (letter =='.') break; // don't include extension
+ if (letter =='\\') letter = '/'; // damn path names
+ hash+=(long)(letter)*(i+119);
+ i++;
+ }
+ hash &= (LOOP_HASH-1);
+ return hash;
+}
+
+/*
+==================
+S_FindName
+
+Will allocate a new sfx if it isn't found
+==================
+*/
+static sfx_t *S_FindName( const char *name ) {
+ int i;
+ int hash;
+
+ sfx_t *sfx;
+
+ if (!name) {
+ Com_Error(ERR_FATAL, "Sound name is NULL");
+ }
+
+ if (!name[0]) {
+ Com_Printf( S_COLOR_YELLOW "WARNING: Sound name is empty\n" );
+ return NULL;
+ }
+
+ if (strlen(name) >= MAX_QPATH) {
+ Com_Printf( S_COLOR_YELLOW "WARNING: Sound name is too long: %s\n", name );
+ return NULL;
+ }
+
+ if (name[0] == '*') {
+ Com_Printf( S_COLOR_YELLOW "WARNING: Tried to load player sound directly: %s\n", name );
+ return NULL;
+ }
+
+ hash = S_HashSFXName(name);
+
+ sfx = sfxHash[hash];
+ // see if already loaded
+ while (sfx) {
+ if (!Q_stricmp(sfx->soundName, name) ) {
+ return sfx;
+ }
+ sfx = sfx->next;
+ }
+
+ // find a free sfx
+ for (i=0 ; i < s_numSfx ; i++) {
+ if (!s_knownSfx[i].soundName[0]) {
+ break;
+ }
+ }
+
+ if (i == s_numSfx) {
+ if (s_numSfx == MAX_SFX) {
+ Com_Error (ERR_FATAL, "S_FindName: out of sfx_t");
+ }
+ s_numSfx++;
+ }
+
+ sfx = &s_knownSfx[i];
+ ::memset (sfx, 0, sizeof(*sfx));
+ strcpy (sfx->soundName, name);
+
+ sfx->next = sfxHash[hash];
+ sfxHash[hash] = sfx;
+
+ return sfx;
+}
+
+/*
+=================
+S_DefaultSound
+=================
+*/
+void S_DefaultSound( sfx_t *sfx ) {
+
+ int i;
+
+ sfx->soundLength = 512;
+ sfx->soundData = SND_malloc();
+ sfx->soundData->next = NULL;
+
+
+ for ( i = 0 ; i < sfx->soundLength ; i++ ) {
+ sfx->soundData->sndChunk[i] = i;
+ }
+}
+
+/*
+===================
+S_DisableSounds
+
+Disables sounds until the next S_BeginRegistration.
+This is called when the hunk is cleared and the sounds
+are no longer valid.
+===================
+*/
+void S_Base_DisableSounds( void ) {
+ S_Base_StopAllSounds();
+ s_soundMuted = true;
+}
+
+/*
+==================
+S_RegisterSound
+
+Creates a default buzz sound if the file can't be loaded
+==================
+*/
+sfxHandle_t S_Base_RegisterSound( const char *name, bool compressed ) {
+ sfx_t *sfx;
+
+ compressed = false;
+ if (!s_soundStarted) {
+ return 0;
+ }
+
+ sfx = S_FindName( name );
+ if ( !sfx ) {
+ return 0;
+ }
+
+ if ( sfx->soundData ) {
+ if ( sfx->defaultSound ) {
+ Com_Printf( S_COLOR_YELLOW "WARNING: could not find %s - using default\n", sfx->soundName );
+ return 0;
+ }
+ return sfx - s_knownSfx;
+ }
+
+ sfx->inMemory = false;
+ sfx->soundCompressed = compressed;
+
+ S_memoryLoad(sfx);
+
+ if ( sfx->defaultSound ) {
+ Com_Printf( S_COLOR_YELLOW "WARNING: could not find %s - using default\n", sfx->soundName );
+ return 0;
+ }
+
+ return sfx - s_knownSfx;
+}
+
+/*
+==================
+S_Base_SoundDuration
+==================
+*/
+static int S_Base_SoundDuration( sfxHandle_t handle ) {
+ if ( handle < 0 || handle >= s_numSfx ) {
+ Com_Printf( S_COLOR_YELLOW "S_Base_SoundDuration: handle %i out of range\n", handle );
+ return 0;
+ }
+ return s_knownSfx[ handle ].duration;
+}
+
+
+
+/*
+=====================
+S_BeginRegistration
+
+=====================
+*/
+void S_Base_BeginRegistration( void ) {
+ s_soundMuted = false; // we can play again
+
+ if (s_numSfx == 0) {
+ SND_setup();
+
+ ::memset(s_knownSfx, '\0', sizeof(s_knownSfx));
+ ::memset(sfxHash, '\0', sizeof(sfx_t *) * LOOP_HASH);
+
+ S_Base_RegisterSound("sound/feedback/hit.wav", false); // changed to a sound in baseq3
+ }
+}
+
+void S_memoryLoad(sfx_t *sfx) {
+ // load the sound file
+ if ( !S_LoadSound ( sfx ) ) {
+// Com_Printf( S_COLOR_YELLOW "WARNING: couldn't load sound: %s\n", sfx->soundName );
+ sfx->defaultSound = true;
+ }
+ sfx->inMemory = true;
+}
+
+//=============================================================================
+
+/*
+=================
+S_SpatializeOrigin
+
+Used for spatializing s_channels
+=================
+*/
+void S_SpatializeOrigin (vec3_t origin, int master_vol, int *left_vol, int *right_vol)
+{
+ vec_t dot;
+ vec_t dist;
+ vec_t lscale, rscale, scale;
+ vec3_t source_vec;
+ vec3_t vec;
+
+ const float dist_mult = SOUND_ATTENUATE;
+
+ // calculate stereo seperation and distance attenuation
+ VectorSubtract(origin, listener_origin, source_vec);
+
+ dist = VectorNormalize(source_vec);
+ dist -= SOUND_FULLVOLUME;
+ if (dist < 0)
+ dist = 0; // close enough to be at full volume
+ dist *= dist_mult; // different attenuation levels
+
+ VectorRotate( source_vec, listener_axis, vec );
+
+ dot = -vec[1];
+
+ if (dma.channels == 1)
+ { // no attenuation = no spatialization
+ rscale = 1.0;
+ lscale = 1.0;
+ }
+ else
+ {
+ rscale = 0.5 * (1.0 + dot);
+ lscale = 0.5 * (1.0 - dot);
+ if ( rscale < 0 ) {
+ rscale = 0;
+ }
+ if ( lscale < 0 ) {
+ lscale = 0;
+ }
+ }
+
+ // add in distance effect
+ scale = (1.0 - dist) * rscale;
+ *right_vol = (master_vol * scale);
+ if (*right_vol < 0)
+ *right_vol = 0;
+
+ scale = (1.0 - dist) * lscale;
+ *left_vol = (master_vol * scale);
+ if (*left_vol < 0)
+ *left_vol = 0;
+}
+
+// =======================================================================
+// Start a sound effect
+// =======================================================================
+
+/*
+=================
+S_Base_HearingThroughEntity
+
+Also see S_AL_HearingThroughEntity
+=================
+*/
+static bool S_Base_HearingThroughEntity( int entityNum, vec3_t origin )
+{
+ float distanceSq;
+ vec3_t sorigin;
+
+ if (origin)
+ VectorCopy(origin, sorigin);
+ else
+ VectorCopy(loopSounds[entityNum].origin, sorigin);
+
+ if( listener_number == entityNum )
+ {
+ // This is an outrageous hack to detect
+ // whether or not the player is rendering in third person or not. We can't
+ // ask the renderer because the renderer has no notion of entities and we
+ // can't ask cgame since that would involve changing the API and hence mod
+ // compatibility. I don't think there is any way around this, but I'll leave
+ // the FIXME just in case anyone has a bright idea.
+ distanceSq = DistanceSquared(
+ sorigin,
+ listener_origin );
+
+ if( distanceSq > THIRD_PERSON_THRESHOLD_SQ )
+ return false; //we're the player, but third person
+ else
+ return true; //we're the player
+ }
+ else
+ return false; //not the player
+}
+
+/*
+====================
+S_Base_StartSoundEx
+
+Validates the parms and ques the sound up
+if origin is NULL, the sound will be dynamically sourced from the entity
+Entchannel 0 will never override a playing sound
+====================
+*/
+static void S_Base_StartSoundEx( vec3_t origin, int entityNum, int entchannel, sfxHandle_t sfxHandle, bool localSound ) {
+ channel_t *ch;
+ sfx_t *sfx;
+ int i, oldest, chosen, time;
+ int inplay, allowed;
+ bool fullVolume;
+
+ if ( !s_soundStarted || s_soundMuted ) {
+ return;
+ }
+
+ if ( !origin && ( entityNum < 0 || entityNum >= MAX_GENTITIES ) ) {
+ Com_Error( ERR_DROP, "S_StartSound: bad entitynum %i", entityNum );
+ }
+
+ if ( sfxHandle < 0 || sfxHandle >= s_numSfx ) {
+ Com_Printf( S_COLOR_YELLOW "S_StartSound: handle %i out of range\n", sfxHandle );
+ return;
+ }
+
+ sfx = &s_knownSfx[ sfxHandle ];
+
+ if (sfx->inMemory == false) {
+ S_memoryLoad(sfx);
+ }
+
+ if ( s_show->integer == 1 ) {
+ Com_Printf( "%i : %s\n", s_paintedtime, sfx->soundName );
+ }
+
+ time = Com_Milliseconds();
+
+// Com_Printf("playing %s\n", sfx->soundName);
+ // pick a channel to play on
+
+ allowed = 4;
+ if (entityNum == listener_number) {
+ allowed = 8;
+ }
+
+ fullVolume = false;
+ if (localSound || S_Base_HearingThroughEntity(entityNum, origin)) {
+ fullVolume = true;
+ }
+
+ ch = s_channels;
+ inplay = 0;
+ for ( i = 0; i < MAX_CHANNELS ; i++, ch++ ) {
+ if (ch->entnum == entityNum && ch->thesfx == sfx) {
+ if (time - ch->allocTime < 50) {
+// if (Cvar_VariableValue( "cg_showmiss" )) {
+// Com_Printf("double sound start\n");
+// }
+ return;
+ }
+ inplay++;
+ }
+ }
+
+ if (inplay>allowed) {
+ return;
+ }
+
+ sfx->lastTimeUsed = time;
+
+ ch = S_ChannelMalloc(); // entityNum, entchannel);
+ if (!ch) {
+ ch = s_channels;
+
+ oldest = sfx->lastTimeUsed;
+ chosen = -1;
+ for ( i = 0 ; i < MAX_CHANNELS ; i++, ch++ ) {
+ if (ch->entnum != listener_number && ch->entnum == entityNum && ch->allocTime<oldest && ch->entchannel != CHAN_ANNOUNCER) {
+ oldest = ch->allocTime;
+ chosen = i;
+ }
+ }
+ if (chosen == -1) {
+ ch = s_channels;
+ for ( i = 0 ; i < MAX_CHANNELS ; i++, ch++ ) {
+ if (ch->entnum != listener_number && ch->allocTime<oldest && ch->entchannel != CHAN_ANNOUNCER) {
+ oldest = ch->allocTime;
+ chosen = i;
+ }
+ }
+ if (chosen == -1) {
+ ch = s_channels;
+ if (ch->entnum == listener_number) {
+ for ( i = 0 ; i < MAX_CHANNELS ; i++, ch++ ) {
+ if (ch->allocTime<oldest) {
+ oldest = ch->allocTime;
+ chosen = i;
+ }
+ }
+ }
+ if (chosen == -1) {
+ Com_DPrintf("dropping sound\n");
+ return;
+ }
+ }
+ }
+ ch = &s_channels[chosen];
+ ch->allocTime = sfx->lastTimeUsed;
+ }
+
+ if (origin) {
+ VectorCopy (origin, ch->origin);
+ ch->fixed_origin = true;
+ } else {
+ ch->fixed_origin = false;
+ }
+
+ ch->master_vol = 127;
+ ch->entnum = entityNum;
+ ch->thesfx = sfx;
+ ch->startSample = START_SAMPLE_IMMEDIATE;
+ ch->entchannel = entchannel;
+ ch->leftvol = ch->master_vol; // these will get calced at next spatialize
+ ch->rightvol = ch->master_vol; // unless the game isn't running
+ ch->doppler = false;
+ ch->fullVolume = fullVolume;
+}
+
+/*
+====================
+S_StartSound
+
+if origin is NULL, the sound will be dynamically sourced from the entity
+====================
+*/
+void S_Base_StartSound( vec3_t origin, int entityNum, int entchannel, sfxHandle_t sfxHandle ) {
+ S_Base_StartSoundEx( origin, entityNum, entchannel, sfxHandle, false );
+}
+
+/*
+==================
+S_StartLocalSound
+==================
+*/
+void S_Base_StartLocalSound( sfxHandle_t sfxHandle, int channelNum ) {
+ if ( !s_soundStarted || s_soundMuted ) {
+ return;
+ }
+
+ if ( sfxHandle < 0 || sfxHandle >= s_numSfx ) {
+ Com_Printf( S_COLOR_YELLOW "S_StartLocalSound: handle %i out of range\n", sfxHandle );
+ return;
+ }
+
+ S_Base_StartSoundEx( NULL, listener_number, channelNum, sfxHandle, true );
+}
+
+
+/*
+==================
+S_ClearSoundBuffer
+
+If we are about to perform file access, clear the buffer
+so sound doesn't stutter.
+==================
+*/
+void S_Base_ClearSoundBuffer( void ) {
+ int clear;
+
+ if (!s_soundStarted)
+ return;
+
+ // stop looping sounds
+ ::memset(loopSounds, 0, MAX_GENTITIES*sizeof(loopSound_t));
+ ::memset(loop_channels, 0, MAX_CHANNELS*sizeof(channel_t));
+ numLoopChannels = 0;
+
+ S_ChannelSetup();
+
+ ::memset(s_rawend, '\0', sizeof (s_rawend));
+
+ if (dma.samplebits == 8)
+ clear = 0x80;
+ else
+ clear = 0;
+
+ SNDDMA_BeginPainting ();
+ if (dma.buffer)
+ ::memset(dma.buffer, clear, dma.samples * dma.samplebits/8);
+ SNDDMA_Submit ();
+}
+
+/*
+==================
+S_StopAllSounds
+==================
+*/
+void S_Base_StopAllSounds(void) {
+ if ( !s_soundStarted ) {
+ return;
+ }
+
+ // stop the background music
+ S_Base_StopBackgroundTrack();
+
+ S_Base_ClearSoundBuffer ();
+}
+
+/*
+==============================================================
+
+continuous looping sounds are added each frame
+
+==============================================================
+*/
+
+void S_Base_StopLoopingSound(int entityNum) {
+ loopSounds[entityNum].active = false;
+// loopSounds[entityNum].sfx = 0;
+ loopSounds[entityNum].kill = false;
+}
+
+/*
+==================
+S_ClearLoopingSounds
+
+==================
+*/
+void S_Base_ClearLoopingSounds( bool killall )
+{
+ int i;
+ for ( i = 0 ; i < MAX_GENTITIES ; i++) {
+ if (killall || loopSounds[i].kill == true || (loopSounds[i].sfx && loopSounds[i].sfx->soundLength == 0)) {
+ S_Base_StopLoopingSound(i);
+ }
+ }
+ numLoopChannels = 0;
+}
+
+/*
+==================
+S_AddLoopingSound
+
+Called during entity generation for a frame
+Include velocity in case I get around to doing doppler...
+==================
+*/
+void S_Base_AddLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfxHandle ) {
+ sfx_t *sfx;
+
+ if ( !s_soundStarted || s_soundMuted ) {
+ return;
+ }
+
+ if ( sfxHandle < 0 || sfxHandle >= s_numSfx ) {
+ Com_Printf( S_COLOR_YELLOW "S_AddLoopingSound: handle %i out of range\n", sfxHandle );
+ return;
+ }
+
+ sfx = &s_knownSfx[ sfxHandle ];
+
+ if (sfx->inMemory == false) {
+ S_memoryLoad(sfx);
+ }
+
+ if ( !sfx->soundLength ) {
+ Com_Error( ERR_DROP, "%s has length 0", sfx->soundName );
+ }
+
+ VectorCopy( origin, loopSounds[entityNum].origin );
+ VectorCopy( velocity, loopSounds[entityNum].velocity );
+ loopSounds[entityNum].active = true;
+ loopSounds[entityNum].kill = true;
+ loopSounds[entityNum].doppler = false;
+ loopSounds[entityNum].oldDopplerScale = 1.0;
+ loopSounds[entityNum].dopplerScale = 1.0;
+ loopSounds[entityNum].sfx = sfx;
+
+ if (s_doppler->integer && VectorLengthSquared(velocity)>0.0) {
+ vec3_t out;
+ float lena, lenb;
+
+ loopSounds[entityNum].doppler = true;
+ lena = DistanceSquared(loopSounds[listener_number].origin, loopSounds[entityNum].origin);
+ VectorAdd(loopSounds[entityNum].origin, loopSounds[entityNum].velocity, out);
+ lenb = DistanceSquared(loopSounds[listener_number].origin, out);
+ if ((loopSounds[entityNum].framenum+1) != cls.framecount) {
+ loopSounds[entityNum].oldDopplerScale = 1.0;
+ } else {
+ loopSounds[entityNum].oldDopplerScale = loopSounds[entityNum].dopplerScale;
+ }
+ loopSounds[entityNum].dopplerScale = lenb/(lena*100);
+ if (loopSounds[entityNum].dopplerScale<=1.0) {
+ loopSounds[entityNum].doppler = false; // don't bother doing the math
+ } else if (loopSounds[entityNum].dopplerScale>MAX_DOPPLER_SCALE) {
+ loopSounds[entityNum].dopplerScale = MAX_DOPPLER_SCALE;
+ }
+ }
+
+ loopSounds[entityNum].framenum = cls.framecount;
+}
+
+/*
+==================
+S_AddLoopingSound
+
+Called during entity generation for a frame
+Include velocity in case I get around to doing doppler...
+==================
+*/
+void S_Base_AddRealLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfxHandle ) {
+ sfx_t *sfx;
+
+ if ( !s_soundStarted || s_soundMuted ) {
+ return;
+ }
+
+ if ( sfxHandle < 0 || sfxHandle >= s_numSfx ) {
+ Com_Printf( S_COLOR_YELLOW "S_AddRealLoopingSound: handle %i out of range\n", sfxHandle );
+ return;
+ }
+
+ sfx = &s_knownSfx[ sfxHandle ];
+
+ if (sfx->inMemory == false) {
+ S_memoryLoad(sfx);
+ }
+
+ if ( !sfx->soundLength ) {
+ Com_Error( ERR_DROP, "%s has length 0", sfx->soundName );
+ }
+ VectorCopy( origin, loopSounds[entityNum].origin );
+ VectorCopy( velocity, loopSounds[entityNum].velocity );
+ loopSounds[entityNum].sfx = sfx;
+ loopSounds[entityNum].active = true;
+ loopSounds[entityNum].kill = false;
+ loopSounds[entityNum].doppler = false;
+}
+
+
+
+/*
+==================
+S_AddLoopSounds
+
+Spatialize all of the looping sounds.
+All sounds are on the same cycle, so any duplicates can just
+sum up the channel multipliers.
+==================
+*/
+void S_AddLoopSounds (void) {
+ int i, j, time;
+ int left_total, right_total, left, right;
+ channel_t *ch;
+ loopSound_t *loop, *loop2;
+ static int loopFrame;
+
+
+ numLoopChannels = 0;
+
+ time = Com_Milliseconds();
+
+ loopFrame++;
+ for ( i = 0 ; i < MAX_GENTITIES ; i++) {
+ loop = &loopSounds[i];
+ if ( !loop->active || loop->mergeFrame == loopFrame ) {
+ continue; // already merged into an earlier sound
+ }
+
+ if (loop->kill) {
+ S_SpatializeOrigin( loop->origin, 127, &left_total, &right_total); // 3d
+ } else {
+ S_SpatializeOrigin( loop->origin, 90, &left_total, &right_total); // sphere
+ }
+
+ loop->sfx->lastTimeUsed = time;
+
+ for (j=(i+1); j< MAX_GENTITIES ; j++) {
+ loop2 = &loopSounds[j];
+ if ( !loop2->active || loop2->doppler || loop2->sfx != loop->sfx) {
+ continue;
+ }
+ loop2->mergeFrame = loopFrame;
+
+ if (loop2->kill) {
+ S_SpatializeOrigin( loop2->origin, 127, &left, &right); // 3d
+ } else {
+ S_SpatializeOrigin( loop2->origin, 90, &left, &right); // sphere
+ }
+
+ loop2->sfx->lastTimeUsed = time;
+ left_total += left;
+ right_total += right;
+ }
+ if (left_total == 0 && right_total == 0) {
+ continue; // not audible
+ }
+
+ // allocate a channel
+ ch = &loop_channels[numLoopChannels];
+
+ if (left_total > 255) {
+ left_total = 255;
+ }
+ if (right_total > 255) {
+ right_total = 255;
+ }
+
+ ch->master_vol = 127;
+ ch->leftvol = left_total;
+ ch->rightvol = right_total;
+ ch->thesfx = loop->sfx;
+ ch->doppler = loop->doppler;
+ ch->dopplerScale = loop->dopplerScale;
+ ch->oldDopplerScale = loop->oldDopplerScale;
+ ch->fullVolume = false;
+ numLoopChannels++;
+ if (numLoopChannels == MAX_CHANNELS) {
+ return;
+ }
+ }
+}
+
+//=============================================================================
+
+/*
+=================
+S_ByteSwapRawSamples
+
+If raw data has been loaded in little endien binary form, this must be done.
+If raw data was calculated, as with ADPCM, this should not be called.
+=================
+*/
+void S_ByteSwapRawSamples( int samples, int width, int s_channels, const byte *data ) {
+ int i;
+
+ if ( width != 2 ) {
+ return;
+ }
+ if ( LittleShort( 256 ) == 256 ) {
+ return;
+ }
+
+ if ( s_channels == 2 ) {
+ samples <<= 1;
+ }
+ for ( i = 0 ; i < samples ; i++ ) {
+ ((short *)data)[i] = LittleShort( ((short *)data)[i] );
+ }
+}
+
+/*
+============
+S_Base_RawSamples
+
+Music streaming
+============
+*/
+void S_Base_RawSamples( int stream, int samples, int rate, int width, int s_channels, const byte *data, float volume, int entityNum)
+{
+ int i;
+ int src, dst;
+ float scale;
+ int intVolumeLeft, intVolumeRight;
+ portable_samplepair_t *rawsamples;
+
+ if ( !s_soundStarted || s_soundMuted ) {
+ return;
+ }
+
+ if ( (stream < 0) || (stream >= MAX_RAW_STREAMS) ) {
+ return;
+ }
+
+ rawsamples = s_rawsamples[stream];
+
+ if ( s_muted->integer ) {
+ intVolumeLeft = intVolumeRight = 0;
+ } else {
+ int leftvol, rightvol;
+
+ if ( entityNum >= 0 && entityNum < MAX_GENTITIES ) {
+ // support spatialized raw streams, e.g. for VoIP
+ S_SpatializeOrigin( loopSounds[ entityNum ].origin, 256, &leftvol, &rightvol );
+ } else {
+ leftvol = rightvol = 256;
+ }
+
+ intVolumeLeft = leftvol * volume * s_volume->value;
+ intVolumeRight = rightvol * volume * s_volume->value;
+ }
+
+ if ( s_rawend[stream] < s_soundtime ) {
+ Com_DPrintf( "S_Base_RawSamples: resetting minimum: %i < %i\n", s_rawend[stream], s_soundtime );
+ s_rawend[stream] = s_soundtime;
+ }
+
+ scale = (float)rate / dma.speed;
+
+//Com_Printf ("%i < %i < %i\n", s_soundtime, s_paintedtime, s_rawend[stream]);
+ if (s_channels == 2 && width == 2)
+ {
+ if (scale == 1.0)
+ { // optimized case
+ for (i=0 ; i<samples ; i++)
+ {
+ dst = s_rawend[stream]&(MAX_RAW_SAMPLES-1);
+ s_rawend[stream]++;
+ rawsamples[dst].left = ((short *)data)[i*2] * intVolumeLeft;
+ rawsamples[dst].right = ((short *)data)[i*2+1] * intVolumeRight;
+ }
+ }
+ else
+ {
+ for (i=0 ; ; i++)
+ {
+ src = i*scale;
+ if (src >= samples)
+ break;
+ dst = s_rawend[stream]&(MAX_RAW_SAMPLES-1);
+ s_rawend[stream]++;
+ rawsamples[dst].left = ((short *)data)[src*2] * intVolumeLeft;
+ rawsamples[dst].right = ((short *)data)[src*2+1] * intVolumeRight;
+ }
+ }
+ }
+ else if (s_channels == 1 && width == 2)
+ {
+ for (i=0 ; ; i++)
+ {
+ src = i*scale;
+ if (src >= samples)
+ break;
+ dst = s_rawend[stream]&(MAX_RAW_SAMPLES-1);
+ s_rawend[stream]++;
+ rawsamples[dst].left = ((short *)data)[src] * intVolumeLeft;
+ rawsamples[dst].right = ((short *)data)[src] * intVolumeRight;
+ }
+ }
+ else if (s_channels == 2 && width == 1)
+ {
+ intVolumeLeft *= 256;
+ intVolumeRight *= 256;
+
+ for (i=0 ; ; i++)
+ {
+ src = i*scale;
+ if (src >= samples)
+ break;
+ dst = s_rawend[stream]&(MAX_RAW_SAMPLES-1);
+ s_rawend[stream]++;
+ rawsamples[dst].left = ((char *)data)[src*2] * intVolumeLeft;
+ rawsamples[dst].right = ((char *)data)[src*2+1] * intVolumeRight;
+ }
+ }
+ else if (s_channels == 1 && width == 1)
+ {
+ intVolumeLeft *= 256;
+ intVolumeRight *= 256;
+
+ for (i=0 ; ; i++)
+ {
+ src = i*scale;
+ if (src >= samples)
+ break;
+ dst = s_rawend[stream]&(MAX_RAW_SAMPLES-1);
+ s_rawend[stream]++;
+ rawsamples[dst].left = (((byte *)data)[src]-128) * intVolumeLeft;
+ rawsamples[dst].right = (((byte *)data)[src]-128) * intVolumeRight;
+ }
+ }
+
+ if ( s_rawend[stream] > s_soundtime + MAX_RAW_SAMPLES ) {
+ Com_DPrintf( "S_Base_RawSamples: overflowed %i > %i\n", s_rawend[stream], s_soundtime );
+ }
+}
+
+//=============================================================================
+
+/*
+=====================
+S_UpdateEntityPosition
+
+let the sound system know where an entity currently is
+======================
+*/
+void S_Base_UpdateEntityPosition( int entityNum, const vec3_t origin ) {
+ if ( entityNum < 0 || entityNum >= MAX_GENTITIES ) {
+ Com_Error( ERR_DROP, "S_UpdateEntityPosition: bad entitynum %i", entityNum );
+ }
+ VectorCopy( origin, loopSounds[entityNum].origin );
+}
+
+
+/*
+============
+S_Respatialize
+
+Change the volumes of all the playing sounds for changes in their positions
+============
+*/
+void S_Base_Respatialize( int entityNum, const vec3_t head, vec3_t axis[3], int inwater ) {
+ int i;
+ channel_t *ch;
+ vec3_t origin;
+
+ if ( !s_soundStarted || s_soundMuted ) {
+ return;
+ }
+
+ listener_number = entityNum;
+ VectorCopy(head, listener_origin);
+ VectorCopy(axis[0], listener_axis[0]);
+ VectorCopy(axis[1], listener_axis[1]);
+ VectorCopy(axis[2], listener_axis[2]);
+
+ // update spatialization for dynamic sounds
+ ch = s_channels;
+ for ( i = 0 ; i < MAX_CHANNELS ; i++, ch++ ) {
+ if ( !ch->thesfx ) {
+ continue;
+ }
+ // local and first person sounds will always be full volume
+ if (ch->fullVolume) {
+ ch->leftvol = ch->master_vol;
+ ch->rightvol = ch->master_vol;
+ } else {
+ if (ch->fixed_origin) {
+ VectorCopy( ch->origin, origin );
+ } else {
+ VectorCopy( loopSounds[ ch->entnum ].origin, origin );
+ }
+
+ S_SpatializeOrigin (origin, ch->master_vol, &ch->leftvol, &ch->rightvol);
+ }
+ }
+
+ // add loopsounds
+ S_AddLoopSounds ();
+}
+
+
+/*
+========================
+S_ScanChannelStarts
+
+Returns true if any new sounds were started since the last mix
+========================
+*/
+bool S_ScanChannelStarts( void ) {
+ channel_t *ch;
+ int i;
+ bool newSamples;
+
+ newSamples = false;
+ ch = s_channels;
+
+ for (i=0; i<MAX_CHANNELS ; i++, ch++) {
+ if ( !ch->thesfx ) {
+ continue;
+ }
+ // if this channel was just started this frame,
+ // set the sample count to it begins mixing
+ // into the very first sample
+ if ( ch->startSample == START_SAMPLE_IMMEDIATE ) {
+ ch->startSample = s_paintedtime;
+ newSamples = true;
+ continue;
+ }
+
+ // if it is completely finished by now, clear it
+ if ( ch->startSample + (ch->thesfx->soundLength) <= s_paintedtime ) {
+ S_ChannelFree(ch);
+ }
+ }
+
+ return newSamples;
+}
+
+/*
+============
+S_Update
+
+Called once each time through the main loop
+============
+*/
+void S_Base_Update( void ) {
+ int i;
+ int total;
+ channel_t *ch;
+
+ if ( !s_soundStarted || s_soundMuted ) {
+// Com_DPrintf ("not started or muted\n");
+ return;
+ }
+
+ //
+ // debugging output
+ //
+ if ( s_show->integer == 2 ) {
+ total = 0;
+ ch = s_channels;
+ for (i=0 ; i<MAX_CHANNELS; i++, ch++) {
+ if (ch->thesfx && (ch->leftvol || ch->rightvol) ) {
+ Com_Printf ("%d %d %s\n", ch->leftvol, ch->rightvol, ch->thesfx->soundName);
+ total++;
+ }
+ }
+
+ Com_Printf ("----(%i)---- painted: %i\n", total, s_paintedtime);
+ }
+
+ // add raw data from streamed samples
+ S_UpdateBackgroundTrack();
+
+ // mix some sound
+ S_Update_();
+}
+
+void S_GetSoundtime(void)
+{
+ int samplepos;
+ static int buffers;
+ static int oldsamplepos;
+ int fullsamples;
+
+ fullsamples = dma.samples / dma.channels;
+
+ if( CL_VideoRecording( ) )
+ {
+ float fps = MIN(cl_aviFrameRate->value, 1000.0f);
+ float frameDuration = MAX(dma.speed / fps, 1.0f) + clc.aviSoundFrameRemainder;
+
+ int msec = (int)frameDuration;
+ s_soundtime += msec;
+ clc.aviSoundFrameRemainder = frameDuration - msec;
+
+ return;
+ }
+
+ // it is possible to miscount buffers if it has wrapped twice between
+ // calls to S_Update. Oh well.
+ samplepos = SNDDMA_GetDMAPos();
+ if (samplepos < oldsamplepos)
+ {
+ buffers++; // buffer wrapped
+
+ if (s_paintedtime > 0x40000000)
+ { // time to chop things off to avoid 32 bit limits
+ buffers = 0;
+ s_paintedtime = fullsamples;
+ S_Base_StopAllSounds ();
+ }
+ }
+ oldsamplepos = samplepos;
+
+ s_soundtime = buffers*fullsamples + samplepos/dma.channels;
+
+#if 0
+// check to make sure that we haven't overshot
+ if (s_paintedtime < s_soundtime)
+ {
+ Com_DPrintf ("S_Update_ : overflow\n");
+ s_paintedtime = s_soundtime;
+ }
+#endif
+
+ if ( dma.submission_chunk < 256 ) {
+ s_paintedtime = s_soundtime + s_mixPreStep->value * dma.speed;
+ } else {
+ s_paintedtime = s_soundtime + dma.submission_chunk;
+ }
+}
+
+
+void S_Update_(void) {
+ unsigned endtime;
+ int samps;
+ static float lastTime = 0.0f;
+ float ma, op;
+ float thisTime, sane;
+ static int ot = -1;
+
+ if ( !s_soundStarted || s_soundMuted ) {
+ return;
+ }
+
+ thisTime = Com_Milliseconds();
+
+ // Updates s_soundtime
+ S_GetSoundtime();
+
+ if (s_soundtime == ot) {
+ return;
+ }
+ ot = s_soundtime;
+
+ // clear any sound effects that end before the current time,
+ // and start any new sounds
+ S_ScanChannelStarts();
+
+ sane = thisTime - lastTime;
+ if (sane<11) {
+ sane = 11; // 85hz
+ }
+
+ ma = s_mixahead->value * dma.speed;
+ op = s_mixPreStep->value + sane*dma.speed*0.01;
+
+ if (op < ma) {
+ ma = op;
+ }
+
+ // mix ahead of current position
+ endtime = s_soundtime + ma;
+
+ // mix to an even submission block size
+ endtime = (endtime + dma.submission_chunk-1)
+ & ~(dma.submission_chunk-1);
+
+ // never mix more than the complete buffer
+ samps = dma.samples >> (dma.channels-1);
+ if (endtime - s_soundtime > samps)
+ endtime = s_soundtime + samps;
+
+
+
+ SNDDMA_BeginPainting ();
+
+ S_PaintChannels (endtime);
+
+ SNDDMA_Submit ();
+
+ lastTime = thisTime;
+}
+
+
+
+/*
+===============================================================================
+
+background music functions
+
+===============================================================================
+*/
+
+/*
+======================
+S_StopBackgroundTrack
+======================
+*/
+void S_Base_StopBackgroundTrack( void ) {
+ if(!s_backgroundStream)
+ return;
+ S_CodecCloseStream(s_backgroundStream);
+ s_backgroundStream = NULL;
+ s_rawend[0] = 0;
+}
+
+/*
+======================
+S_OpenBackgroundStream
+======================
+*/
+static void S_OpenBackgroundStream( const char *filename ) {
+ // close the background track, but DON'T reset s_rawend
+ // if restarting the same back ground track
+ if(s_backgroundStream)
+ {
+ S_CodecCloseStream(s_backgroundStream);
+ s_backgroundStream = NULL;
+ }
+
+ // Open stream
+ s_backgroundStream = S_CodecOpenStream(filename);
+ if(!s_backgroundStream) {
+ Com_Printf( S_COLOR_YELLOW "WARNING: couldn't open music file %s\n", filename );
+ return;
+ }
+
+ if(s_backgroundStream->info.channels != 2 || s_backgroundStream->info.rate != 22050) {
+ Com_Printf(S_COLOR_YELLOW "WARNING: music file %s is not 22k stereo\n", filename );
+ }
+}
+
+/*
+======================
+S_StartBackgroundTrack
+======================
+*/
+void S_Base_StartBackgroundTrack( const char *intro, const char *loop ){
+ if ( !intro ) {
+ intro = "";
+ }
+ if ( !loop || !loop[0] ) {
+ loop = intro;
+ }
+ Com_DPrintf( "S_StartBackgroundTrack( %s, %s )\n", intro, loop );
+
+ if(!*intro)
+ {
+ S_Base_StopBackgroundTrack();
+ return;
+ }
+
+ Q_strncpyz( s_backgroundLoop, loop, sizeof( s_backgroundLoop ) );
+
+ S_OpenBackgroundStream( intro );
+}
+
+/*
+======================
+S_UpdateBackgroundTrack
+======================
+*/
+void S_UpdateBackgroundTrack( void ) {
+ int bufferSamples;
+ int fileSamples;
+ byte raw[30000]; // just enough to fit in a mac stack frame
+ int fileBytes;
+ int r;
+
+ if(!s_backgroundStream) {
+ return;
+ }
+
+ // don't bother playing anything if musicvolume is 0
+ if ( s_musicVolume->value <= 0 ) {
+ return;
+ }
+
+ // see how many samples should be copied into the raw buffer
+ if ( s_rawend[0] < s_soundtime ) {
+ s_rawend[0] = s_soundtime;
+ }
+
+ while ( s_rawend[0] < s_soundtime + MAX_RAW_SAMPLES ) {
+ bufferSamples = MAX_RAW_SAMPLES - (s_rawend[0] - s_soundtime);
+
+ // decide how much data needs to be read from the file
+ fileSamples = bufferSamples * s_backgroundStream->info.rate / dma.speed;
+
+ if (!fileSamples)
+ return;
+
+ // our max buffer size
+ fileBytes = fileSamples * (s_backgroundStream->info.width * s_backgroundStream->info.channels);
+ if ( fileBytes > sizeof(raw) ) {
+ fileBytes = sizeof(raw);
+ fileSamples = fileBytes / (s_backgroundStream->info.width * s_backgroundStream->info.channels);
+ }
+
+ // Read
+ r = S_CodecReadStream(s_backgroundStream, fileBytes, raw);
+ if(r < fileBytes)
+ {
+ fileSamples = r / (s_backgroundStream->info.width * s_backgroundStream->info.channels);
+ }
+
+ if(r > 0)
+ {
+ // add to raw buffer
+ S_Base_RawSamples(0, fileSamples, s_backgroundStream->info.rate,
+ s_backgroundStream->info.width, s_backgroundStream->info.channels, raw, s_musicVolume->value, -1);
+ }
+ else
+ {
+ // loop
+ if(s_backgroundLoop[0])
+ {
+ S_OpenBackgroundStream( s_backgroundLoop );
+ if(!s_backgroundStream)
+ return;
+ }
+ else
+ {
+ S_Base_StopBackgroundTrack();
+ return;
+ }
+ }
+
+ }
+}
+
+
+/*
+======================
+S_FreeOldestSound
+======================
+*/
+
+void S_FreeOldestSound( void ) {
+ int i, oldest, used;
+ sfx_t *sfx;
+ sndBuffer *buffer, *nbuffer;
+
+ oldest = Com_Milliseconds();
+ used = 0;
+
+ for (i=1 ; i < s_numSfx ; i++) {
+ sfx = &s_knownSfx[i];
+ if (sfx->inMemory && sfx->lastTimeUsed<oldest) {
+ used = i;
+ oldest = sfx->lastTimeUsed;
+ }
+ }
+
+ sfx = &s_knownSfx[used];
+
+ Com_DPrintf("S_FreeOldestSound: freeing sound %s\n", sfx->soundName);
+
+ buffer = sfx->soundData;
+ while(buffer != NULL) {
+ nbuffer = buffer->next;
+ SND_free(buffer);
+ buffer = nbuffer;
+ }
+ sfx->inMemory = false;
+ sfx->soundData = NULL;
+}
+
+// =======================================================================
+// Shutdown sound engine
+// =======================================================================
+
+void S_Base_Shutdown( void ) {
+ if ( !s_soundStarted ) {
+ return;
+ }
+
+ SNDDMA_Shutdown();
+ SND_shutdown();
+
+ s_soundStarted = false;
+ s_numSfx = 0;
+
+ Cmd_RemoveCommand("s_info");
+}
+
+/*
+================
+S_Init
+================
+*/
+bool S_Base_Init( soundInterface_t *si )
+{
+
+ if( !si ) {
+ return false;
+ }
+
+ s_mixahead = Cvar_Get ("s_mixahead", "0.2", CVAR_ARCHIVE);
+ s_mixPreStep = Cvar_Get ("s_mixPreStep", "0.05", CVAR_ARCHIVE);
+ s_show = Cvar_Get ("s_show", "0", CVAR_CHEAT);
+ s_testsound = Cvar_Get ("s_testsound", "0", CVAR_CHEAT);
+
+ bool r = SNDDMA_Init();
+ if ( r )
+ {
+ s_soundStarted = true;
+ s_soundMuted = true;
+// s_numSfx = 0;
+
+ ::memset(sfxHash, 0, sizeof(sfx_t *)*LOOP_HASH);
+
+ s_soundtime = 0;
+ s_paintedtime = 0;
+
+ S_Base_StopAllSounds( );
+ } else {
+ return false;
+ }
+
+ si->Shutdown = S_Base_Shutdown;
+ si->StartSound = S_Base_StartSound;
+ si->StartLocalSound = S_Base_StartLocalSound;
+ si->StartBackgroundTrack = S_Base_StartBackgroundTrack;
+ si->StopBackgroundTrack = S_Base_StopBackgroundTrack;
+ si->RawSamples = S_Base_RawSamples;
+ si->StopAllSounds = S_Base_StopAllSounds;
+ si->ClearLoopingSounds = S_Base_ClearLoopingSounds;
+ si->AddLoopingSound = S_Base_AddLoopingSound;
+ si->AddRealLoopingSound = S_Base_AddRealLoopingSound;
+ si->StopLoopingSound = S_Base_StopLoopingSound;
+ si->Respatialize = S_Base_Respatialize;
+ si->UpdateEntityPosition = S_Base_UpdateEntityPosition;
+ si->Update = S_Base_Update;
+ si->DisableSounds = S_Base_DisableSounds;
+ si->BeginRegistration = S_Base_BeginRegistration;
+ si->RegisterSound = S_Base_RegisterSound;
+ si->SoundDuration = S_Base_SoundDuration;
+ si->ClearSoundBuffer = S_Base_ClearSoundBuffer;
+ si->SoundInfo = S_Base_SoundInfo;
+ si->SoundList = S_Base_SoundList;
+
+#ifdef USE_VOIP
+ si->StartCapture = S_Base_StartCapture;
+ si->AvailableCaptureSamples = S_Base_AvailableCaptureSamples;
+ si->Capture = S_Base_Capture;
+ si->StopCapture = S_Base_StopCapture;
+ si->MasterGain = S_Base_MasterGain;
+#endif
+
+ return true;
+}
diff --git a/src/client/snd_local.h b/src/client/snd_local.h
new file mode 100644
index 0000000..38f26c4
--- /dev/null
+++ b/src/client/snd_local.h
@@ -0,0 +1,267 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
+
+This file is part of Tremulous.
+
+Tremulous 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.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+// snd_local.h -- private sound definations
+
+#ifndef _SND_LOCAL_H
+#define _SND_LOCAL_H
+
+#include "qcommon/q_shared.h"
+#include "qcommon/qcommon.h"
+
+#include "snd_public.h"
+
+typedef struct cvar_s cvar_t;
+
+#define PAINTBUFFER_SIZE 4096 // this is in samples
+
+#define SND_CHUNK_SIZE 1024 // samples
+#define SND_CHUNK_SIZE_FLOAT (SND_CHUNK_SIZE/2) // floats
+#define SND_CHUNK_SIZE_BYTE (SND_CHUNK_SIZE*2) // floats
+
+typedef struct {
+ int left; // the final values will be clamped to +/- 0x00ffff00 and shifted down
+ int right;
+} portable_samplepair_t;
+
+typedef struct adpcm_state {
+ short sample; /* Previous output value */
+ char index; /* Index into stepsize table */
+} adpcm_state_t;
+
+typedef struct sndBuffer_s {
+ short sndChunk[SND_CHUNK_SIZE];
+ struct sndBuffer_s *next;
+ int size;
+ adpcm_state_t adpcm;
+} sndBuffer;
+
+typedef struct sfx_s {
+ sndBuffer *soundData;
+ bool defaultSound; // couldn't be loaded, so use buzz
+ bool inMemory; // not in Memory
+ bool soundCompressed; // not in Memory
+ int soundCompressionMethod;
+ int soundLength;
+ int soundChannels;
+ char soundName[MAX_QPATH];
+ int lastTimeUsed;
+ int duration;
+ struct sfx_s *next;
+} sfx_t;
+
+typedef struct {
+ int channels;
+ int samples; // mono samples in buffer
+ int submission_chunk; // don't mix less than this #
+ int samplebits;
+ int speed;
+ byte *buffer;
+} dma_t;
+
+#define START_SAMPLE_IMMEDIATE 0x7fffffff
+
+#define MAX_DOPPLER_SCALE 50.0f //arbitrary
+
+#define THIRD_PERSON_THRESHOLD_SQ (48.0f*48.0f)
+
+typedef struct loopSound_s {
+ vec3_t origin;
+ vec3_t velocity;
+ sfx_t *sfx;
+ int mergeFrame;
+ bool active;
+ bool kill;
+ bool doppler;
+ float dopplerScale;
+ float oldDopplerScale;
+ int framenum;
+} loopSound_t;
+
+typedef struct
+{
+ sfx_t *thesfx; // sfx structure
+ int allocTime;
+ int startSample; // START_SAMPLE_IMMEDIATE = set immediately on next mix
+ int entnum; // to allow overriding a specific sound
+ int entchannel; // to allow overriding a specific sound
+ int leftvol; // 0-255 volume after spatialization
+ int rightvol; // 0-255 volume after spatialization
+ int master_vol; // 0-255 volume before spatialization
+ float dopplerScale;
+ float oldDopplerScale;
+ vec3_t origin; // only use if fixed_origin is set
+ bool fixed_origin; // use origin instead of fetching entnum's origin
+ bool doppler;
+ bool fullVolume;
+} channel_t;
+
+
+#define WAV_FORMAT_PCM 1
+
+
+typedef struct {
+ int format;
+ int rate;
+ int width;
+ int channels;
+ int samples;
+ int dataofs; // chunk starts this many bytes from file start
+} wavinfo_t;
+
+// Interface between Q3 sound "api" and the sound backend
+typedef struct
+{
+ void (*Shutdown)(void);
+ void (*StartSound)( vec3_t origin, int entnum, int entchannel, sfxHandle_t sfx );
+ void (*StartLocalSound)( sfxHandle_t sfx, int channelNum );
+ void (*StartBackgroundTrack)( const char *intro, const char *loop );
+ void (*StopBackgroundTrack)( void );
+ void (*RawSamples)(int stream, int samples, int rate, int width, int channels, const byte *data, float volume, int entityNum);
+ void (*StopAllSounds)( void );
+ void (*ClearLoopingSounds)( bool killall );
+ void (*AddLoopingSound)( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx );
+ void (*AddRealLoopingSound)( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx );
+ void (*StopLoopingSound)(int entityNum );
+ void (*Respatialize)( int entityNum, const vec3_t origin, vec3_t axis[3], int inwater );
+ void (*UpdateEntityPosition)( int entityNum, const vec3_t origin );
+ void (*Update)( void );
+ void (*DisableSounds)( void );
+ void (*BeginRegistration)( void );
+ sfxHandle_t (*RegisterSound)( const char *sample, bool compressed );
+ int (*SoundDuration)( sfxHandle_t handle );
+ void (*ClearSoundBuffer)( void );
+ void (*SoundInfo)( void );
+ void (*SoundList)( void );
+#ifdef USE_VOIP
+ void (*StartCapture)( void );
+ int (*AvailableCaptureSamples)( void );
+ void (*Capture)( int samples, byte *data );
+ void (*StopCapture)( void );
+ void (*MasterGain)( float gain );
+#endif
+} soundInterface_t;
+
+
+/*
+====================================================================
+
+ SYSTEM SPECIFIC FUNCTIONS
+
+====================================================================
+*/
+
+// initializes cycling through a DMA buffer and returns information on it
+bool SNDDMA_Init(void);
+
+// gets the current DMA position
+int SNDDMA_GetDMAPos(void);
+
+// shutdown the DMA xfer.
+void SNDDMA_Shutdown(void);
+
+void SNDDMA_BeginPainting (void);
+
+void SNDDMA_Submit(void);
+
+//====================================================================
+
+#define MAX_CHANNELS 96
+
+extern channel_t s_channels[MAX_CHANNELS];
+extern channel_t loop_channels[MAX_CHANNELS];
+extern int numLoopChannels;
+
+extern int s_paintedtime;
+extern vec3_t listener_forward;
+extern vec3_t listener_right;
+extern vec3_t listener_up;
+extern dma_t dma;
+
+#define MAX_RAW_SAMPLES 16384
+#define MAX_RAW_STREAMS (MAX_CLIENTS * 2 + 1)
+extern portable_samplepair_t s_rawsamples[MAX_RAW_STREAMS][MAX_RAW_SAMPLES];
+extern int s_rawend[MAX_RAW_STREAMS];
+
+extern cvar_t *s_volume;
+extern cvar_t *s_musicVolume;
+extern cvar_t *s_muted;
+extern cvar_t *s_doppler;
+
+extern cvar_t *s_testsound;
+
+bool S_LoadSound( sfx_t *sfx );
+
+void SND_free(sndBuffer *v);
+sndBuffer* SND_malloc( void );
+void SND_setup( void );
+void SND_shutdown(void);
+
+void S_PaintChannels(int endtime);
+
+void S_memoryLoad(sfx_t *sfx);
+
+// spatializes a channel
+void S_Spatialize(channel_t *ch);
+
+// adpcm functions
+int S_AdpcmMemoryNeeded( const wavinfo_t *info );
+void S_AdpcmEncodeSound( sfx_t *sfx, short *samples );
+void S_AdpcmGetSamples(sndBuffer *chunk, short *to);
+
+// wavelet function
+
+#define SENTINEL_MULAW_ZERO_RUN 127
+#define SENTINEL_MULAW_FOUR_BIT_RUN 126
+
+void S_FreeOldestSound( void );
+
+#define NXStream byte
+
+void encodeWavelet(sfx_t *sfx, short *packets);
+void decodeWavelet( sndBuffer *stream, short *packets);
+
+void encodeMuLaw( sfx_t *sfx, short *packets);
+extern short mulawToShort[256];
+
+extern short *sfxScratchBuffer;
+extern sfx_t *sfxScratchPointer;
+extern int sfxScratchIndex;
+
+bool S_Base_Init( soundInterface_t *si );
+
+// OpenAL stuff
+typedef enum
+{
+ SRCPRI_AMBIENT = 0, // Ambient sound effects
+ SRCPRI_ENTITY, // Entity sound effects
+ SRCPRI_ONESHOT, // One-shot sounds
+ SRCPRI_LOCAL, // Local sounds
+ SRCPRI_STREAM // Streams (music, cutscenes)
+} alSrcPriority_t;
+
+typedef int srcHandle_t;
+
+bool S_AL_Init( soundInterface_t *si );
+
+#endif
diff --git a/src/client/snd_main.cpp b/src/client/snd_main.cpp
new file mode 100644
index 0000000..a6873f1
--- /dev/null
+++ b/src/client/snd_main.cpp
@@ -0,0 +1,566 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2005 Stuart Dalton (badcdev@gmail.com)
+Copyright (C) 2015-2019 GrangerHub
+
+This file is part of Tremulous.
+
+Tremulous 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.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+
+#include "client.h"
+#include "snd_codec.h"
+#include "snd_local.h"
+#include "snd_public.h"
+
+cvar_t *s_volume;
+cvar_t *s_muted;
+cvar_t *s_musicVolume;
+cvar_t *s_doppler;
+cvar_t *s_backend;
+cvar_t *s_muteWhenMinimized;
+cvar_t *s_muteWhenUnfocused;
+
+static soundInterface_t si;
+
+/*
+=================
+S_ValidateInterface
+=================
+*/
+static bool S_ValidSoundInterface( soundInterface_t *si )
+{
+ if( !si->Shutdown ) return false;
+ if( !si->StartSound ) return false;
+ if( !si->StartLocalSound ) return false;
+ if( !si->StartBackgroundTrack ) return false;
+ if( !si->StopBackgroundTrack ) return false;
+ if( !si->RawSamples ) return false;
+ if( !si->StopAllSounds ) return false;
+ if( !si->ClearLoopingSounds ) return false;
+ if( !si->AddLoopingSound ) return false;
+ if( !si->AddRealLoopingSound ) return false;
+ if( !si->StopLoopingSound ) return false;
+ if( !si->Respatialize ) return false;
+ if( !si->UpdateEntityPosition ) return false;
+ if( !si->Update ) return false;
+ if( !si->DisableSounds ) return false;
+ if( !si->BeginRegistration ) return false;
+ if( !si->RegisterSound ) return false;
+ if( !si->SoundDuration ) return false;
+ if( !si->ClearSoundBuffer ) return false;
+ if( !si->SoundInfo ) return false;
+ if( !si->SoundList ) return false;
+
+#ifdef USE_VOIP
+ if( !si->StartCapture ) return false;
+ if( !si->AvailableCaptureSamples ) return false;
+ if( !si->Capture ) return false;
+ if( !si->StopCapture ) return false;
+ if( !si->MasterGain ) return false;
+#endif
+
+ return true;
+}
+
+/*
+=================
+S_StartSound
+=================
+*/
+void S_StartSound( vec3_t origin, int entnum, int entchannel, sfxHandle_t sfx )
+{
+ if( si.StartSound ) {
+ si.StartSound( origin, entnum, entchannel, sfx );
+ }
+}
+
+/*
+=================
+S_StartLocalSound
+=================
+*/
+void S_StartLocalSound( sfxHandle_t sfx, int channelNum )
+{
+ if( si.StartLocalSound ) {
+ si.StartLocalSound( sfx, channelNum );
+ }
+}
+
+/*
+=================
+S_StartBackgroundTrack
+=================
+*/
+void S_StartBackgroundTrack( const char *intro, const char *loop )
+{
+ if( si.StartBackgroundTrack ) {
+ si.StartBackgroundTrack( intro, loop );
+ }
+}
+
+/*
+=================
+S_StopBackgroundTrack
+=================
+*/
+void S_StopBackgroundTrack( void )
+{
+ if( si.StopBackgroundTrack ) {
+ si.StopBackgroundTrack( );
+ }
+}
+
+/*
+=================
+S_RawSamples
+=================
+*/
+void S_RawSamples (int stream, int samples, int rate, int width, int channels,
+ const byte *data, float volume, int entityNum)
+{
+ if(si.RawSamples)
+ si.RawSamples(stream, samples, rate, width, channels, data, volume, entityNum);
+}
+
+/*
+=================
+S_StopAllSounds
+=================
+*/
+void S_StopAllSounds( void )
+{
+ if( si.StopAllSounds ) {
+ si.StopAllSounds( );
+ }
+}
+
+/*
+=================
+S_ClearLoopingSounds
+=================
+*/
+void S_ClearLoopingSounds( bool killall )
+{
+ if( si.ClearLoopingSounds ) {
+ si.ClearLoopingSounds( killall );
+ }
+}
+
+/*
+=================
+S_AddLoopingSound
+=================
+*/
+void S_AddLoopingSound( int entityNum, const vec3_t origin,
+ const vec3_t velocity, sfxHandle_t sfx )
+{
+ if( si.AddLoopingSound ) {
+ si.AddLoopingSound( entityNum, origin, velocity, sfx );
+ }
+}
+
+/*
+=================
+S_AddRealLoopingSound
+=================
+*/
+void S_AddRealLoopingSound( int entityNum, const vec3_t origin,
+ const vec3_t velocity, sfxHandle_t sfx )
+{
+ if( si.AddRealLoopingSound ) {
+ si.AddRealLoopingSound( entityNum, origin, velocity, sfx );
+ }
+}
+
+/*
+=================
+S_StopLoopingSound
+=================
+*/
+void S_StopLoopingSound( int entityNum )
+{
+ if( si.StopLoopingSound ) {
+ si.StopLoopingSound( entityNum );
+ }
+}
+
+/*
+=================
+S_Respatialize
+=================
+*/
+void S_Respatialize( int entityNum, const vec3_t origin,
+ vec3_t axis[3], int inwater )
+{
+ if( si.Respatialize ) {
+ si.Respatialize( entityNum, origin, axis, inwater );
+ }
+}
+
+/*
+=================
+S_UpdateEntityPosition
+=================
+*/
+void S_UpdateEntityPosition( int entityNum, const vec3_t origin )
+{
+ if( si.UpdateEntityPosition ) {
+ si.UpdateEntityPosition( entityNum, origin );
+ }
+}
+
+/*
+=================
+S_Update
+=================
+*/
+void S_Update( void )
+{
+ if(s_muted->integer)
+ {
+ if(!(s_muteWhenMinimized->integer && com_minimized->integer) &&
+ !(s_muteWhenUnfocused->integer && com_unfocused->integer))
+ {
+ s_muted->integer = false;
+ s_muted->modified = true;
+ }
+ }
+ else
+ {
+ if((s_muteWhenMinimized->integer && com_minimized->integer) ||
+ (s_muteWhenUnfocused->integer && com_unfocused->integer))
+ {
+ s_muted->integer = true;
+ s_muted->modified = true;
+ }
+ }
+
+ if( si.Update ) {
+ si.Update( );
+ }
+}
+
+/*
+=================
+S_DisableSounds
+=================
+*/
+void S_DisableSounds( void )
+{
+ if( si.DisableSounds ) {
+ si.DisableSounds( );
+ }
+}
+
+/*
+=================
+S_BeginRegistration
+=================
+*/
+void S_BeginRegistration( void )
+{
+ if( si.BeginRegistration ) {
+ si.BeginRegistration( );
+ }
+}
+
+/*
+=================
+S_RegisterSound
+=================
+*/
+sfxHandle_t S_RegisterSound( const char *sample, bool compressed )
+{
+ if( si.RegisterSound ) {
+ return si.RegisterSound( sample, compressed );
+ } else {
+ return 0;
+ }
+}
+
+/*
+=================
+S_SoundDuration
+=================
+*/
+int S_SoundDuration( sfxHandle_t handle )
+{
+ if( si.SoundDuration )
+ return si.SoundDuration( handle );
+ else
+ return 0;
+}
+
+/*
+=================
+S_ClearSoundBuffer
+=================
+*/
+void S_ClearSoundBuffer( void )
+{
+ if( si.ClearSoundBuffer ) {
+ si.ClearSoundBuffer( );
+ }
+}
+
+/*
+=================
+S_SoundInfo
+=================
+*/
+void S_SoundInfo( void )
+{
+ if( si.SoundInfo ) {
+ si.SoundInfo( );
+ }
+}
+
+/*
+=================
+S_SoundList
+=================
+*/
+void S_SoundList( void )
+{
+ if( si.SoundList ) {
+ si.SoundList( );
+ }
+}
+
+
+#ifdef USE_VOIP
+/*
+=================
+S_StartCapture
+=================
+*/
+void S_StartCapture( void )
+{
+ if( si.StartCapture ) {
+ si.StartCapture( );
+ }
+}
+
+/*
+=================
+S_AvailableCaptureSamples
+=================
+*/
+int S_AvailableCaptureSamples( void )
+{
+ if( si.AvailableCaptureSamples ) {
+ return si.AvailableCaptureSamples( );
+ }
+ return 0;
+}
+
+/*
+=================
+S_Capture
+=================
+*/
+void S_Capture( int samples, byte *data )
+{
+ if( si.Capture ) {
+ si.Capture( samples, data );
+ }
+}
+
+/*
+=================
+S_StopCapture
+=================
+*/
+void S_StopCapture( void )
+{
+ if( si.StopCapture ) {
+ si.StopCapture( );
+ }
+}
+
+/*
+=================
+S_MasterGain
+=================
+*/
+void S_MasterGain( float gain )
+{
+ if( si.MasterGain ) {
+ si.MasterGain( gain );
+ }
+}
+#endif
+
+//=============================================================================
+
+/*
+=================
+S_Play_f
+=================
+*/
+void S_Play_f( void ) {
+ int i;
+ int c;
+ sfxHandle_t h;
+
+ if( !si.RegisterSound || !si.StartLocalSound ) {
+ return;
+ }
+
+ c = Cmd_Argc();
+
+ if( c < 2 ) {
+ Com_Printf ("Usage: play <sound filename> [sound filename] [sound filename] ...\n");
+ return;
+ }
+
+ for( i = 1; i < c; i++ ) {
+ h = si.RegisterSound( Cmd_Argv(i), false );
+
+ if( h ) {
+ si.StartLocalSound( h, CHAN_LOCAL_SOUND );
+ }
+ }
+}
+
+/*
+=================
+S_Music_f
+=================
+*/
+void S_Music_f( void ) {
+ int c;
+
+ if( !si.StartBackgroundTrack ) {
+ return;
+ }
+
+ c = Cmd_Argc();
+
+ if ( c == 2 ) {
+ si.StartBackgroundTrack( Cmd_Argv(1), NULL );
+ } else if ( c == 3 ) {
+ si.StartBackgroundTrack( Cmd_Argv(1), Cmd_Argv(2) );
+ } else {
+ Com_Printf ("Usage: music <musicfile> [loopfile]\n");
+ return;
+ }
+
+}
+
+/*
+=================
+S_Music_f
+=================
+*/
+void S_StopMusic_f( void )
+{
+ if(!si.StopBackgroundTrack)
+ return;
+
+ si.StopBackgroundTrack();
+}
+
+
+//=============================================================================
+
+/*
+=================
+S_Init
+=================
+*/
+void S_Init( void )
+{
+ cvar_t *cv;
+ bool started = false;
+
+ Com_Printf( "------ Initializing Sound ------\n" );
+
+ s_volume = Cvar_Get( "s_volume", "0.8", CVAR_ARCHIVE );
+ s_musicVolume = Cvar_Get( "s_musicvolume", "0.25", CVAR_ARCHIVE );
+ s_muted = Cvar_Get("s_muted", "0", CVAR_ROM);
+ s_doppler = Cvar_Get( "s_doppler", "1", CVAR_ARCHIVE );
+ s_backend = Cvar_Get( "s_backend", "", CVAR_ROM );
+ s_muteWhenMinimized = Cvar_Get( "s_muteWhenMinimized", "0", CVAR_ARCHIVE );
+ s_muteWhenUnfocused = Cvar_Get( "s_muteWhenUnfocused", "0", CVAR_ARCHIVE );
+
+ cv = Cvar_Get( "s_initsound", "1", 0 );
+ if( !cv->integer ) {
+ Com_Printf( "Sound disabled.\n" );
+ } else {
+
+ S_CodecInit( );
+
+ Cmd_AddCommand( "play", S_Play_f );
+ Cmd_AddCommand( "music", S_Music_f );
+ Cmd_AddCommand( "stopmusic", S_StopMusic_f );
+ Cmd_AddCommand( "s_list", S_SoundList );
+ Cmd_AddCommand( "s_stop", S_StopAllSounds );
+ Cmd_AddCommand( "s_info", S_SoundInfo );
+
+ cv = Cvar_Get( "s_useOpenAL", "0", CVAR_ARCHIVE );
+ if( cv->integer ) {
+ //OpenAL
+ started = S_AL_Init( &si );
+ Cvar_Set( "s_backend", "OpenAL" );
+ }
+
+ if( !started ) {
+ started = S_Base_Init( &si );
+ Cvar_Set( "s_backend", "base" );
+ }
+
+ if( started ) {
+ if( !S_ValidSoundInterface( &si ) ) {
+ Com_Error( ERR_FATAL, "Sound interface invalid" );
+ }
+
+ S_SoundInfo( );
+ Com_Printf( "Sound initialization successful.\n" );
+ } else {
+ Com_Printf( "Sound initialization failed.\n" );
+ }
+ }
+
+ Com_Printf( "--------------------------------\n");
+}
+
+/*
+=================
+S_Shutdown
+=================
+*/
+void S_Shutdown( void )
+{
+ if( si.Shutdown )
+ {
+ si.Shutdown( );
+ }
+
+ ::memset( &si, 0, sizeof( soundInterface_t ) );
+
+ Cmd_RemoveCommand( "play" );
+ Cmd_RemoveCommand( "music");
+ Cmd_RemoveCommand( "stopmusic");
+ Cmd_RemoveCommand( "s_list" );
+ Cmd_RemoveCommand( "s_stop" );
+ Cmd_RemoveCommand( "s_info" );
+
+ S_CodecShutdown( );
+}
diff --git a/src/client/snd_mem.cpp b/src/client/snd_mem.cpp
new file mode 100644
index 0000000..128a471
--- /dev/null
+++ b/src/client/snd_mem.cpp
@@ -0,0 +1,297 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
+
+This file is part of Tremulous.
+
+Tremulous 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.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+
+/*****************************************************************************
+ * name: snd_mem.c
+ *
+ * desc: sound caching
+ *****************************************************************************/
+
+#include "snd_codec.h"
+#include "snd_local.h"
+
+#include "qcommon/cvar.h"
+
+#define DEF_COMSOUNDMEGS "8"
+
+/*
+===============================================================================
+
+memory management
+
+===============================================================================
+*/
+
+static sndBuffer *buffer = NULL;
+static sndBuffer *freelist = NULL;
+static int inUse = 0;
+static int totalInUse = 0;
+
+short *sfxScratchBuffer = NULL;
+sfx_t *sfxScratchPointer = NULL;
+int sfxScratchIndex = 0;
+
+void SND_free(sndBuffer *v)
+{
+ *(sndBuffer **)v = freelist;
+ freelist = (sndBuffer *)v;
+ inUse += sizeof(sndBuffer);
+}
+
+sndBuffer *SND_malloc(void)
+{
+ sndBuffer *v;
+redo:
+ if (freelist == NULL)
+ {
+ S_FreeOldestSound();
+ goto redo;
+ }
+
+ inUse -= sizeof(sndBuffer);
+ totalInUse += sizeof(sndBuffer);
+
+ v = freelist;
+ freelist = *(sndBuffer **)freelist;
+ v->next = NULL;
+
+ return v;
+}
+
+void SND_setup(void)
+{
+ cvar_t* cv = Cvar_Get("com_soundMegs",
+ DEF_COMSOUNDMEGS, CVAR_LATCH | CVAR_ARCHIVE);
+ int scs = cv->integer * 1536;
+
+ buffer = (sndBuffer*)malloc(scs * sizeof(sndBuffer));
+
+ // allocate the stack based hunk allocator
+ sfxScratchBuffer = (short*)malloc(SND_CHUNK_SIZE * sizeof(short) * 4);
+ sfxScratchPointer = NULL;
+
+ inUse = scs * sizeof(sndBuffer);
+ sndBuffer* p = buffer;
+ sndBuffer* q = p + scs;
+ while (--q > p)
+ {
+ *(sndBuffer **)q = q - 1;
+ }
+
+ *(sndBuffer **)q = NULL;
+ freelist = p + scs - 1;
+
+ Com_Printf("Sound memory manager started\n");
+}
+
+void SND_shutdown(void)
+{
+ free(sfxScratchBuffer);
+ free(buffer);
+}
+
+/*
+================
+ResampleSfx
+
+resample / decimate to the current source rate
+================
+*/
+static int ResampleSfx(sfx_t *sfx, int channels, int inrate, int inwidth, int samples, byte *data, bool compressed)
+{
+ float stepscale = (float)inrate / dma.speed; // this is usually 0.5, 1, or 2
+ int outcount = samples / stepscale;
+ int fracstep = stepscale * 256 * channels;
+ sndBuffer* chunk = sfx->soundData;
+
+ int samplefrac = 0;
+ int srcsample = 0;
+
+ for (int i = 0; i < outcount; i++)
+ {
+ srcsample += samplefrac >> 8;
+ samplefrac &= 255;
+ samplefrac += fracstep;
+
+ for (int j = 0; j < channels; j++)
+ {
+ int sample;
+
+ if (inwidth == 2)
+ {
+ sample = ((short *)data)[srcsample + j];
+ }
+ else
+ {
+ sample = (int)((unsigned char)(data[srcsample + j]) - 128) << 8;
+ }
+
+ int part = (i * channels + j) & (SND_CHUNK_SIZE - 1);
+ if (part == 0)
+ {
+ sndBuffer *newchunk = SND_malloc();
+
+ if (chunk == NULL)
+ {
+ sfx->soundData = newchunk;
+ }
+ else
+ {
+ chunk->next = newchunk;
+ }
+ chunk = newchunk;
+ }
+
+ chunk->sndChunk[part] = sample;
+ }
+ }
+
+ return outcount;
+}
+
+/*
+================
+ResampleSfxRaw
+
+resample / decimate to the current source rate
+================
+*/
+static int ResampleSfxRaw(short *sfx, int channels, int inrate, int inwidth, int samples, byte *data)
+{
+ float stepscale = (float)inrate / dma.speed; // this is usually 0.5, 1, or 2
+ int outcount = samples / stepscale;
+ int fracstep = stepscale * 256 * channels;
+
+ int samplefrac = 0;
+ int srcsample = 0;
+
+ for (int i = 0; i < outcount; i++)
+ {
+ srcsample += samplefrac >> 8;
+ srcsample &= 255;
+ samplefrac += fracstep;
+
+ for (int j = 0; j < channels; j++)
+ {
+ int sample;
+ if (inwidth == 2)
+ {
+ sample = LittleShort(((short *)data)[srcsample + j]);
+ }
+ else
+ {
+ sample = (int)((unsigned char)(data[srcsample + j]) - 128) << 8;
+ }
+ sfx[i * channels + j] = sample;
+ }
+ }
+ return outcount;
+}
+
+//=============================================================================
+
+/*
+==============
+S_LoadSound
+
+The filename may be different than sfx->name in the case
+of a forced fallback of a player specific sound
+==============
+*/
+bool S_LoadSound(sfx_t *sfx)
+{
+ snd_info_t info;
+ byte *data = (byte*)S_CodecLoad(sfx->soundName, &info);
+ if (!data)
+ {
+ return false;
+ }
+
+ int size_per_sec = info.rate * info.channels * info.width;
+ if (size_per_sec > 0)
+ {
+ sfx->duration = (int)(1000.0f * ((double)info.size / size_per_sec));
+ }
+
+ if (info.width == 1)
+ {
+ Com_DPrintf(S_COLOR_YELLOW "WARNING: %s is a 8 bit audio file\n",
+ sfx->soundName);
+ }
+
+ if (info.rate != 22050)
+ {
+ Com_DPrintf(S_COLOR_YELLOW "WARNING: %s is not a 22kHz audio file\n",
+ sfx->soundName);
+ }
+
+ short* samples = (short*)Hunk_AllocateTempMemory(info.channels * info.samples * sizeof(short) * 2);
+
+ sfx->lastTimeUsed = Com_Milliseconds() + 1;
+
+ // each of these compression schemes works just fine
+ // but the 16bit quality is much nicer and with a local
+ // install assured we can rely upon the sound memory
+ // manager to do the right thing for us and page
+ // sound in as needed
+
+ if (info.channels == 1 && sfx->soundCompressed == true)
+ {
+ sfx->soundCompressionMethod = 1;
+ sfx->soundData = NULL;
+ sfx->soundLength = ResampleSfxRaw(
+ samples,
+ info.channels,
+ info.rate,
+ info.width,
+ info.samples,
+ data + info.dataofs);
+ S_AdpcmEncodeSound(sfx, samples);
+ }
+ else
+ {
+ sfx->soundCompressionMethod = 0;
+ sfx->soundData = NULL;
+ sfx->soundLength = ResampleSfx(
+ sfx,
+ info.channels,
+ info.rate,
+ info.width,
+ info.samples,
+ data + info.dataofs,
+ false);
+ }
+
+ sfx->soundChannels = info.channels;
+
+ Hunk_FreeTempMemory(samples);
+ Hunk_FreeTempMemory(data);
+
+ return true;
+}
+
+void S_DisplayFreeMemory(void)
+{
+ Com_Printf("%d bytes free sound buffer memory, %d total used\n", inUse, totalInUse);
+}
diff --git a/src/client/snd_mix.cpp b/src/client/snd_mix.cpp
new file mode 100644
index 0000000..4030bc6
--- /dev/null
+++ b/src/client/snd_mix.cpp
@@ -0,0 +1,792 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
+
+This file is part of Tremulous.
+
+Tremulous 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.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+// snd_mix.c -- portable code to mix sounds for snd_dma.c
+
+#include "snd_local.h"
+#if idppc_altivec && !defined(__APPLE__)
+#include <altivec.h>
+#endif
+#include "client.h"
+
+static portable_samplepair_t paintbuffer[PAINTBUFFER_SIZE];
+static int snd_vol;
+
+int* snd_p;
+int snd_linear_count;
+short* snd_out;
+
+#undef id386
+
+#if !id386 // if configured not to use asm
+
+void S_WriteLinearBlastStereo16 (void)
+{
+ int i;
+ int val;
+
+ for (i=0 ; i<snd_linear_count ; i+=2)
+ {
+ val = snd_p[i]>>8;
+ if (val > 0x7fff)
+ snd_out[i] = 0x7fff;
+ else if (val < -32768)
+ snd_out[i] = -32768;
+ else
+ snd_out[i] = val;
+
+ val = snd_p[i+1]>>8;
+ if (val > 0x7fff)
+ snd_out[i+1] = 0x7fff;
+ else if (val < -32768)
+ snd_out[i+1] = -32768;
+ else
+ snd_out[i+1] = val;
+ }
+}
+#elif defined(__GNUC__)
+// uses snd_mixa.s
+void S_WriteLinearBlastStereo16 (void);
+#else
+
+__declspec( naked ) void S_WriteLinearBlastStereo16 (void)
+{
+ __asm {
+
+ push edi
+ push ebx
+ mov ecx,ds:dword ptr[snd_linear_count]
+ mov ebx,ds:dword ptr[snd_p]
+ mov edi,ds:dword ptr[snd_out]
+LWLBLoopTop:
+ mov eax,ds:dword ptr[-8+ebx+ecx*4]
+ sar eax,8
+ cmp eax,07FFFh
+ jg LClampHigh
+ cmp eax,0FFFF8000h
+ jnl LClampDone
+ mov eax,0FFFF8000h
+ jmp LClampDone
+LClampHigh:
+ mov eax,07FFFh
+LClampDone:
+ mov edx,ds:dword ptr[-4+ebx+ecx*4]
+ sar edx,8
+ cmp edx,07FFFh
+ jg LClampHigh2
+ cmp edx,0FFFF8000h
+ jnl LClampDone2
+ mov edx,0FFFF8000h
+ jmp LClampDone2
+LClampHigh2:
+ mov edx,07FFFh
+LClampDone2:
+ shl edx,16
+ and eax,0FFFFh
+ or edx,eax
+ mov ds:dword ptr[-4+edi+ecx*2],edx
+ sub ecx,2
+ jnz LWLBLoopTop
+ pop ebx
+ pop edi
+ ret
+ }
+}
+
+#endif
+
+void S_TransferStereo16 (unsigned long *pbuf, int endtime)
+{
+ int lpos;
+ int ls_paintedtime;
+
+ snd_p = (int *) paintbuffer;
+ ls_paintedtime = s_paintedtime;
+
+ while (ls_paintedtime < endtime)
+ {
+ // handle recirculating buffer issues
+ lpos = ls_paintedtime & ((dma.samples>>1)-1);
+
+ snd_out = (short *) pbuf + (lpos<<1);
+
+ snd_linear_count = (dma.samples>>1) - lpos;
+ if (ls_paintedtime + snd_linear_count > endtime)
+ snd_linear_count = endtime - ls_paintedtime;
+
+ snd_linear_count <<= 1;
+
+ // write a linear blast of samples
+ S_WriteLinearBlastStereo16 ();
+
+ snd_p += snd_linear_count;
+ ls_paintedtime += (snd_linear_count>>1);
+
+ if( CL_VideoRecording( ) )
+ CL_WriteAVIAudioFrame( (byte *)snd_out, snd_linear_count << 1 );
+ }
+}
+
+/*
+===================
+S_TransferPaintBuffer
+
+===================
+*/
+void S_TransferPaintBuffer(int endtime)
+{
+ int out_idx;
+ int count;
+ int out_mask;
+ int *p;
+ int step;
+ int val;
+ unsigned long *pbuf;
+
+ pbuf = (unsigned long *)dma.buffer;
+
+
+ if ( s_testsound->integer ) {
+ int i;
+
+ // write a fixed sine wave
+ count = (endtime - s_paintedtime);
+ for (i=0 ; i<count ; i++)
+ paintbuffer[i].left = paintbuffer[i].right = sin((s_paintedtime+i)*0.1)*20000*256;
+ }
+
+
+ if (dma.samplebits == 16 && dma.channels == 2)
+ { // optimized case
+ S_TransferStereo16 (pbuf, endtime);
+ }
+ else
+ { // general case
+ p = (int *) paintbuffer;
+ count = (endtime - s_paintedtime) * dma.channels;
+ out_mask = dma.samples - 1;
+ out_idx = s_paintedtime * dma.channels & out_mask;
+ step = 3 - dma.channels;
+
+ if (dma.samplebits == 16)
+ {
+ short *out = (short *) pbuf;
+ while (count--)
+ {
+ val = *p >> 8;
+ p+= step;
+ if (val > 0x7fff)
+ val = 0x7fff;
+ else if (val < -32768)
+ val = -32768;
+ out[out_idx] = val;
+ out_idx = (out_idx + 1) & out_mask;
+ }
+ }
+ else if (dma.samplebits == 8)
+ {
+ unsigned char *out = (unsigned char *) pbuf;
+ while (count--)
+ {
+ val = *p >> 8;
+ p+= step;
+ if (val > 0x7fff)
+ val = 0x7fff;
+ else if (val < -32768)
+ val = -32768;
+ out[out_idx] = (val>>8) + 128;
+ out_idx = (out_idx + 1) & out_mask;
+ }
+ }
+ }
+}
+
+
+/*
+===============================================================================
+
+CHANNEL MIXING
+
+===============================================================================
+*/
+
+#if idppc_altivec
+static void S_PaintChannelFrom16_altivec( channel_t *ch, const sfx_t *sc, int count, int sampleOffset, int bufferOffset ) {
+ int data, aoff, boff;
+ int leftvol, rightvol;
+ int i, j;
+ portable_samplepair_t *samp;
+ sndBuffer *chunk;
+ short *samples;
+ float ooff, fdata[2], fdiv, fleftvol, frightvol;
+
+ if (sc->soundChannels <= 0) {
+ return;
+ }
+
+ samp = &paintbuffer[ bufferOffset ];
+
+ if (ch->doppler) {
+ sampleOffset = sampleOffset*ch->oldDopplerScale;
+ }
+
+ if ( sc->soundChannels == 2 ) {
+ sampleOffset *= sc->soundChannels;
+
+ if ( sampleOffset & 1 ) {
+ sampleOffset &= ~1;
+ }
+ }
+
+ chunk = sc->soundData;
+ while (sampleOffset>=SND_CHUNK_SIZE) {
+ chunk = chunk->next;
+ sampleOffset -= SND_CHUNK_SIZE;
+ if (!chunk) {
+ chunk = sc->soundData;
+ }
+ }
+
+ if (!ch->doppler || ch->dopplerScale==1.0f) {
+ vector signed short volume_vec;
+ vector unsigned int volume_shift;
+ int vectorCount, samplesLeft, chunkSamplesLeft;
+ leftvol = ch->leftvol*snd_vol;
+ rightvol = ch->rightvol*snd_vol;
+ samples = chunk->sndChunk;
+ ((short *)&volume_vec)[0] = leftvol;
+ ((short *)&volume_vec)[1] = leftvol;
+ ((short *)&volume_vec)[4] = leftvol;
+ ((short *)&volume_vec)[5] = leftvol;
+ ((short *)&volume_vec)[2] = rightvol;
+ ((short *)&volume_vec)[3] = rightvol;
+ ((short *)&volume_vec)[6] = rightvol;
+ ((short *)&volume_vec)[7] = rightvol;
+ volume_shift = vec_splat_u32(8);
+ i = 0;
+
+ while(i < count) {
+ /* Try to align destination to 16-byte boundary */
+ while(i < count && (((unsigned long)&samp[i] & 0x1f) || ((count-i) < 8) || ((SND_CHUNK_SIZE - sampleOffset) < 8))) {
+ data = samples[sampleOffset++];
+ samp[i].left += (data * leftvol)>>8;
+
+ if ( sc->soundChannels == 2 ) {
+ data = samples[sampleOffset++];
+ }
+ samp[i].right += (data * rightvol)>>8;
+
+ if (sampleOffset == SND_CHUNK_SIZE) {
+ chunk = chunk->next;
+ samples = chunk->sndChunk;
+ sampleOffset = 0;
+ }
+ i++;
+ }
+ /* Destination is now aligned. Process as many 8-sample
+ chunks as we can before we run out of room from the current
+ sound chunk. We do 8 per loop to avoid extra source data reads. */
+ samplesLeft = count - i;
+ chunkSamplesLeft = SND_CHUNK_SIZE - sampleOffset;
+ if(samplesLeft > chunkSamplesLeft)
+ samplesLeft = chunkSamplesLeft;
+
+ vectorCount = samplesLeft / 8;
+
+ if(vectorCount)
+ {
+ vector unsigned char tmp;
+ vector short s0, s1, sampleData0, sampleData1;
+ vector signed int merge0, merge1;
+ vector signed int d0, d1, d2, d3;
+ vector unsigned char samplePermute0 =
+ VECCONST_UINT8(0, 1, 4, 5, 0, 1, 4, 5, 2, 3, 6, 7, 2, 3, 6, 7);
+ vector unsigned char samplePermute1 =
+ VECCONST_UINT8(8, 9, 12, 13, 8, 9, 12, 13, 10, 11, 14, 15, 10, 11, 14, 15);
+ vector unsigned char loadPermute0, loadPermute1;
+
+ // Rather than permute the vectors after we load them to do the sample
+ // replication and rearrangement, we permute the alignment vector so
+ // we do everything in one step below and avoid data shuffling.
+ tmp = vec_lvsl(0,&samples[sampleOffset]);
+ loadPermute0 = vec_perm(tmp,tmp,samplePermute0);
+ loadPermute1 = vec_perm(tmp,tmp,samplePermute1);
+
+ s0 = *(vector short *)&samples[sampleOffset];
+ while(vectorCount)
+ {
+ /* Load up source (16-bit) sample data */
+ s1 = *(vector short *)&samples[sampleOffset+7];
+
+ /* Load up destination sample data */
+ d0 = *(vector signed int *)&samp[i];
+ d1 = *(vector signed int *)&samp[i+2];
+ d2 = *(vector signed int *)&samp[i+4];
+ d3 = *(vector signed int *)&samp[i+6];
+
+ sampleData0 = vec_perm(s0,s1,loadPermute0);
+ sampleData1 = vec_perm(s0,s1,loadPermute1);
+
+ merge0 = vec_mule(sampleData0,volume_vec);
+ merge0 = vec_sra(merge0,volume_shift); /* Shift down to proper range */
+
+ merge1 = vec_mulo(sampleData0,volume_vec);
+ merge1 = vec_sra(merge1,volume_shift);
+
+ d0 = vec_add(merge0,d0);
+ d1 = vec_add(merge1,d1);
+
+ merge0 = vec_mule(sampleData1,volume_vec);
+ merge0 = vec_sra(merge0,volume_shift); /* Shift down to proper range */
+
+ merge1 = vec_mulo(sampleData1,volume_vec);
+ merge1 = vec_sra(merge1,volume_shift);
+
+ d2 = vec_add(merge0,d2);
+ d3 = vec_add(merge1,d3);
+
+ /* Store destination sample data */
+ *(vector signed int *)&samp[i] = d0;
+ *(vector signed int *)&samp[i+2] = d1;
+ *(vector signed int *)&samp[i+4] = d2;
+ *(vector signed int *)&samp[i+6] = d3;
+
+ i += 8;
+ vectorCount--;
+ s0 = s1;
+ sampleOffset += 8;
+ }
+ if (sampleOffset == SND_CHUNK_SIZE) {
+ chunk = chunk->next;
+ samples = chunk->sndChunk;
+ sampleOffset = 0;
+ }
+ }
+ }
+ } else {
+ fleftvol = ch->leftvol*snd_vol;
+ frightvol = ch->rightvol*snd_vol;
+
+ ooff = sampleOffset;
+ samples = chunk->sndChunk;
+
+ for ( i=0 ; i<count ; i++ ) {
+
+ aoff = ooff;
+ ooff = ooff + ch->dopplerScale * sc->soundChannels;
+ boff = ooff;
+ fdata[0] = fdata[1] = 0;
+ for (j=aoff; j<boff; j += sc->soundChannels) {
+ if (j == SND_CHUNK_SIZE) {
+ chunk = chunk->next;
+ if (!chunk) {
+ chunk = sc->soundData;
+ }
+ samples = chunk->sndChunk;
+ ooff -= SND_CHUNK_SIZE;
+ }
+ if ( sc->soundChannels == 2 ) {
+ fdata[0] += samples[j&(SND_CHUNK_SIZE-1)];
+ fdata[1] += samples[(j+1)&(SND_CHUNK_SIZE-1)];
+ } else {
+ fdata[0] += samples[j&(SND_CHUNK_SIZE-1)];
+ fdata[1] += samples[j&(SND_CHUNK_SIZE-1)];
+ }
+ }
+ fdiv = 256 * (boff-aoff) / sc->soundChannels;
+ samp[i].left += (fdata[0] * fleftvol)/fdiv;
+ samp[i].right += (fdata[1] * frightvol)/fdiv;
+ }
+ }
+}
+#endif
+
+static void S_PaintChannelFrom16_scalar( channel_t *ch, const sfx_t *sc, int count, int sampleOffset, int bufferOffset ) {
+ int data, aoff, boff;
+ int leftvol, rightvol;
+ int i, j;
+ portable_samplepair_t *samp;
+ sndBuffer *chunk;
+ short *samples;
+ float ooff, fdata[2], fdiv, fleftvol, frightvol;
+
+ if (sc->soundChannels <= 0) {
+ return;
+ }
+
+ samp = &paintbuffer[ bufferOffset ];
+
+ if (ch->doppler) {
+ sampleOffset = sampleOffset*ch->oldDopplerScale;
+ }
+
+ if ( sc->soundChannels == 2 ) {
+ sampleOffset *= sc->soundChannels;
+
+ if ( sampleOffset & 1 ) {
+ sampleOffset &= ~1;
+ }
+ }
+
+ chunk = sc->soundData;
+ while (sampleOffset>=SND_CHUNK_SIZE) {
+ chunk = chunk->next;
+ sampleOffset -= SND_CHUNK_SIZE;
+ if (!chunk) {
+ chunk = sc->soundData;
+ }
+ }
+
+ if (!ch->doppler || ch->dopplerScale==1.0f) {
+ leftvol = ch->leftvol*snd_vol;
+ rightvol = ch->rightvol*snd_vol;
+ samples = chunk->sndChunk;
+ for ( i=0 ; i<count ; i++ ) {
+ data = samples[sampleOffset++];
+ samp[i].left += (data * leftvol)>>8;
+
+ if ( sc->soundChannels == 2 ) {
+ data = samples[sampleOffset++];
+ }
+ samp[i].right += (data * rightvol)>>8;
+
+ if (sampleOffset == SND_CHUNK_SIZE) {
+ chunk = chunk->next;
+ samples = chunk->sndChunk;
+ sampleOffset = 0;
+ }
+ }
+ } else {
+ fleftvol = ch->leftvol*snd_vol;
+ frightvol = ch->rightvol*snd_vol;
+
+ ooff = sampleOffset;
+ samples = chunk->sndChunk;
+
+
+
+
+ for ( i=0 ; i<count ; i++ ) {
+
+ aoff = ooff;
+ ooff = ooff + ch->dopplerScale * sc->soundChannels;
+ boff = ooff;
+ fdata[0] = fdata[1] = 0;
+ for (j=aoff; j<boff; j += sc->soundChannels) {
+ if (j == SND_CHUNK_SIZE) {
+ chunk = chunk->next;
+ if (!chunk) {
+ chunk = sc->soundData;
+ }
+ samples = chunk->sndChunk;
+ ooff -= SND_CHUNK_SIZE;
+ }
+ if ( sc->soundChannels == 2 ) {
+ fdata[0] += samples[j&(SND_CHUNK_SIZE-1)];
+ fdata[1] += samples[(j+1)&(SND_CHUNK_SIZE-1)];
+ } else {
+ fdata[0] += samples[j&(SND_CHUNK_SIZE-1)];
+ fdata[1] += samples[j&(SND_CHUNK_SIZE-1)];
+ }
+ }
+ fdiv = 256 * (boff-aoff) / sc->soundChannels;
+ samp[i].left += (fdata[0] * fleftvol)/fdiv;
+ samp[i].right += (fdata[1] * frightvol)/fdiv;
+ }
+ }
+}
+
+static void S_PaintChannelFrom16( channel_t *ch, const sfx_t *sc, int count, int sampleOffset, int bufferOffset ) {
+#if idppc_altivec
+ if (com_altivec->integer) {
+ // must be in a seperate function or G3 systems will crash.
+ S_PaintChannelFrom16_altivec( ch, sc, count, sampleOffset, bufferOffset );
+ return;
+ }
+#endif
+ S_PaintChannelFrom16_scalar( ch, sc, count, sampleOffset, bufferOffset );
+}
+
+void S_PaintChannelFromWavelet( channel_t *ch, sfx_t *sc, int count, int sampleOffset, int bufferOffset ) {
+ int data;
+ int leftvol, rightvol;
+ int i;
+ portable_samplepair_t *samp;
+ sndBuffer *chunk;
+ short *samples;
+
+ leftvol = ch->leftvol*snd_vol;
+ rightvol = ch->rightvol*snd_vol;
+
+ i = 0;
+ samp = &paintbuffer[ bufferOffset ];
+ chunk = sc->soundData;
+ while (sampleOffset>=(SND_CHUNK_SIZE_FLOAT*4)) {
+ chunk = chunk->next;
+ sampleOffset -= (SND_CHUNK_SIZE_FLOAT*4);
+ i++;
+ }
+
+ if (i!=sfxScratchIndex || sfxScratchPointer != sc) {
+ S_AdpcmGetSamples( chunk, sfxScratchBuffer );
+ sfxScratchIndex = i;
+ sfxScratchPointer = sc;
+ }
+
+ samples = sfxScratchBuffer;
+
+ for ( i=0 ; i<count ; i++ ) {
+ data = samples[sampleOffset++];
+ samp[i].left += (data * leftvol)>>8;
+ samp[i].right += (data * rightvol)>>8;
+
+ if (sampleOffset == SND_CHUNK_SIZE*2) {
+ chunk = chunk->next;
+ decodeWavelet(chunk, sfxScratchBuffer);
+ sfxScratchIndex++;
+ sampleOffset = 0;
+ }
+ }
+}
+
+void S_PaintChannelFromADPCM( channel_t *ch, sfx_t *sc, int count, int sampleOffset, int bufferOffset ) {
+ int data;
+ int leftvol, rightvol;
+ int i;
+ portable_samplepair_t *samp;
+ sndBuffer *chunk;
+ short *samples;
+
+ leftvol = ch->leftvol*snd_vol;
+ rightvol = ch->rightvol*snd_vol;
+
+ i = 0;
+ samp = &paintbuffer[ bufferOffset ];
+ chunk = sc->soundData;
+
+ if (ch->doppler) {
+ sampleOffset = sampleOffset*ch->oldDopplerScale;
+ }
+
+ while (sampleOffset>=(SND_CHUNK_SIZE*4)) {
+ chunk = chunk->next;
+ sampleOffset -= (SND_CHUNK_SIZE*4);
+ i++;
+ }
+
+ if (i!=sfxScratchIndex || sfxScratchPointer != sc) {
+ S_AdpcmGetSamples( chunk, sfxScratchBuffer );
+ sfxScratchIndex = i;
+ sfxScratchPointer = sc;
+ }
+
+ samples = sfxScratchBuffer;
+
+ for ( i=0 ; i<count ; i++ ) {
+ data = samples[sampleOffset++];
+ samp[i].left += (data * leftvol)>>8;
+ samp[i].right += (data * rightvol)>>8;
+
+ if (sampleOffset == SND_CHUNK_SIZE*4) {
+ chunk = chunk->next;
+ S_AdpcmGetSamples( chunk, sfxScratchBuffer);
+ sampleOffset = 0;
+ sfxScratchIndex++;
+ }
+ }
+}
+
+void S_PaintChannelFromMuLaw( channel_t *ch, sfx_t *sc, int count, int sampleOffset, int bufferOffset ) {
+ int data;
+ int leftvol, rightvol;
+ int i;
+ portable_samplepair_t *samp;
+ sndBuffer *chunk;
+ byte *samples;
+ float ooff;
+
+ leftvol = ch->leftvol*snd_vol;
+ rightvol = ch->rightvol*snd_vol;
+
+ samp = &paintbuffer[ bufferOffset ];
+ chunk = sc->soundData;
+ while (sampleOffset>=(SND_CHUNK_SIZE*2)) {
+ chunk = chunk->next;
+ sampleOffset -= (SND_CHUNK_SIZE*2);
+ if (!chunk) {
+ chunk = sc->soundData;
+ }
+ }
+
+ if (!ch->doppler) {
+ samples = (byte *)chunk->sndChunk + sampleOffset;
+ for ( i=0 ; i<count ; i++ ) {
+ data = mulawToShort[*samples];
+ samp[i].left += (data * leftvol)>>8;
+ samp[i].right += (data * rightvol)>>8;
+ samples++;
+ if (chunk != NULL && samples == (byte *)chunk->sndChunk+(SND_CHUNK_SIZE*2)) {
+ chunk = chunk->next;
+ samples = (byte *)chunk->sndChunk;
+ }
+ }
+ } else {
+ ooff = sampleOffset;
+ samples = (byte *)chunk->sndChunk;
+ for ( i=0 ; i<count ; i++ ) {
+ data = mulawToShort[samples[(int)(ooff)]];
+ ooff = ooff + ch->dopplerScale;
+ samp[i].left += (data * leftvol)>>8;
+ samp[i].right += (data * rightvol)>>8;
+ if (ooff >= SND_CHUNK_SIZE*2) {
+ chunk = chunk->next;
+ if (!chunk) {
+ chunk = sc->soundData;
+ }
+ samples = (byte *)chunk->sndChunk;
+ ooff = 0.0;
+ }
+ }
+ }
+}
+
+/*
+===================
+S_PaintChannels
+===================
+*/
+void S_PaintChannels( int endtime ) {
+ int i;
+ int end;
+ int stream;
+ channel_t *ch;
+ sfx_t *sc;
+ int ltime, count;
+ int sampleOffset;
+
+ if(s_muted->integer)
+ snd_vol = 0;
+ else
+ snd_vol = s_volume->value*255;
+
+//Com_Printf ("%i to %i\n", s_paintedtime, endtime);
+ while ( s_paintedtime < endtime ) {
+ // if paintbuffer is smaller than DMA buffer
+ // we may need to fill it multiple times
+ end = endtime;
+ if ( endtime - s_paintedtime > PAINTBUFFER_SIZE ) {
+ end = s_paintedtime + PAINTBUFFER_SIZE;
+ }
+
+ // clear the paint buffer and mix any raw samples...
+ ::memset(paintbuffer, 0, sizeof (paintbuffer));
+ for (stream = 0; stream < MAX_RAW_STREAMS; stream++) {
+ if ( s_rawend[stream] >= s_paintedtime ) {
+ // copy from the streaming sound source
+ const portable_samplepair_t *rawsamples = s_rawsamples[stream];
+ const int stop = (end < s_rawend[stream]) ? end : s_rawend[stream];
+ for ( i = s_paintedtime ; i < stop ; i++ ) {
+ const int s = i&(MAX_RAW_SAMPLES-1);
+ paintbuffer[i-s_paintedtime].left += rawsamples[s].left;
+ paintbuffer[i-s_paintedtime].right += rawsamples[s].right;
+ }
+ }
+ }
+
+ // paint in the channels.
+ ch = s_channels;
+ for ( i = 0; i < MAX_CHANNELS ; i++, ch++ ) {
+ if ( !ch->thesfx || (ch->leftvol<0.25 && ch->rightvol<0.25 )) {
+ continue;
+ }
+
+ ltime = s_paintedtime;
+ sc = ch->thesfx;
+
+ if (sc->soundData==NULL || sc->soundLength==0) {
+ continue;
+ }
+
+ sampleOffset = ltime - ch->startSample;
+ count = end - ltime;
+ if ( sampleOffset + count > sc->soundLength ) {
+ count = sc->soundLength - sampleOffset;
+ }
+
+ if ( count > 0 ) {
+ if( sc->soundCompressionMethod == 1) {
+ S_PaintChannelFromADPCM (ch, sc, count, sampleOffset, ltime - s_paintedtime);
+ } else if( sc->soundCompressionMethod == 2) {
+ S_PaintChannelFromWavelet (ch, sc, count, sampleOffset, ltime - s_paintedtime);
+ } else if( sc->soundCompressionMethod == 3) {
+ S_PaintChannelFromMuLaw (ch, sc, count, sampleOffset, ltime - s_paintedtime);
+ } else {
+ S_PaintChannelFrom16 (ch, sc, count, sampleOffset, ltime - s_paintedtime);
+ }
+ }
+ }
+
+ // paint in the looped channels.
+ ch = loop_channels;
+ for ( i = 0; i < numLoopChannels ; i++, ch++ ) {
+ if ( !ch->thesfx || (!ch->leftvol && !ch->rightvol )) {
+ continue;
+ }
+
+ ltime = s_paintedtime;
+ sc = ch->thesfx;
+
+ if (sc->soundData==NULL || sc->soundLength==0) {
+ continue;
+ }
+ // we might have to make two passes if it
+ // is a looping sound effect and the end of
+ // the sample is hit
+ do {
+ sampleOffset = (ltime % sc->soundLength);
+
+ count = end - ltime;
+ if ( sampleOffset + count > sc->soundLength ) {
+ count = sc->soundLength - sampleOffset;
+ }
+
+ if ( count > 0 ) {
+ if( sc->soundCompressionMethod == 1) {
+ S_PaintChannelFromADPCM (ch, sc, count, sampleOffset, ltime - s_paintedtime);
+ } else if( sc->soundCompressionMethod == 2) {
+ S_PaintChannelFromWavelet (ch, sc, count, sampleOffset, ltime - s_paintedtime);
+ } else if( sc->soundCompressionMethod == 3) {
+ S_PaintChannelFromMuLaw (ch, sc, count, sampleOffset, ltime - s_paintedtime);
+ } else {
+ S_PaintChannelFrom16 (ch, sc, count, sampleOffset, ltime - s_paintedtime);
+ }
+ ltime += count;
+ }
+ } while ( ltime < end);
+ }
+
+ // transfer out according to DMA format
+ S_TransferPaintBuffer( end );
+ s_paintedtime = end;
+ }
+}
diff --git a/src/client/snd_openal.cpp b/src/client/snd_openal.cpp
new file mode 100644
index 0000000..7ee53e2
--- /dev/null
+++ b/src/client/snd_openal.cpp
@@ -0,0 +1,2737 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2005 Stuart Dalton (badcdev@gmail.com)
+Copyright (C) 2015-2019 GrangerHub
+
+This file is part of Tremulous.
+
+Tremulous 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.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+
+#include "snd_local.h"
+#include "snd_codec.h"
+#include "client.h"
+
+#ifdef USE_OPENAL
+
+#include "qal.h"
+
+// Console variables specific to OpenAL
+cvar_t *s_alPrecache;
+cvar_t *s_alGain;
+cvar_t *s_alSources;
+cvar_t *s_alDopplerFactor;
+cvar_t *s_alDopplerSpeed;
+cvar_t *s_alMinDistance;
+cvar_t *s_alMaxDistance;
+cvar_t *s_alRolloff;
+cvar_t *s_alGraceDistance;
+cvar_t *s_alDriver;
+cvar_t *s_alDevice;
+cvar_t *s_alInputDevice;
+cvar_t *s_alAvailableDevices;
+cvar_t *s_alAvailableInputDevices;
+
+static bool enumeration_ext = false;
+static bool enumeration_all_ext = false;
+#ifdef USE_VOIP
+static bool capture_ext = false;
+#endif
+
+/*
+=================
+S_AL_Format
+=================
+*/
+static
+ALuint S_AL_Format(int width, int channels)
+{
+ ALuint format = AL_FORMAT_MONO16;
+
+ // Work out format
+ if(width == 1)
+ {
+ if(channels == 1)
+ format = AL_FORMAT_MONO8;
+ else if(channels == 2)
+ format = AL_FORMAT_STEREO8;
+ }
+ else if(width == 2)
+ {
+ if(channels == 1)
+ format = AL_FORMAT_MONO16;
+ else if(channels == 2)
+ format = AL_FORMAT_STEREO16;
+ }
+
+ return format;
+}
+
+/*
+=================
+S_AL_ErrorMsg
+=================
+*/
+static const char *S_AL_ErrorMsg(ALenum error)
+{
+ switch(error)
+ {
+ case AL_NO_ERROR:
+ return "No error";
+ case AL_INVALID_NAME:
+ return "Invalid name";
+ case AL_INVALID_ENUM:
+ return "Invalid enumerator";
+ case AL_INVALID_VALUE:
+ return "Invalid value";
+ case AL_INVALID_OPERATION:
+ return "Invalid operation";
+ case AL_OUT_OF_MEMORY:
+ return "Out of memory";
+ default:
+ return "Unknown error";
+ }
+}
+
+/*
+=================
+S_AL_ClearError
+=================
+*/
+static void S_AL_ClearError( bool quiet )
+{
+ int error = qalGetError();
+
+ if( quiet )
+ return;
+ if(error != AL_NO_ERROR)
+ {
+ Com_Printf(S_COLOR_YELLOW "WARNING: unhandled AL error: %s\n",
+ S_AL_ErrorMsg(error));
+ }
+}
+
+
+//===========================================================================
+
+
+typedef struct alSfx_s
+{
+ char filename[MAX_QPATH];
+ ALuint buffer; // OpenAL buffer
+ snd_info_t info; // information for this sound like rate, sample count..
+
+ bool isDefault; // Couldn't be loaded - use default FX
+ bool isDefaultChecked; // Sound has been check if it isDefault
+ bool inMemory; // Sound is stored in memory
+ bool isLocked; // Sound is locked (can not be unloaded)
+ int lastUsedTime; // Time last used
+
+ int duration; // Milliseconds
+
+ int loopCnt; // number of loops using this sfx
+ int loopActiveCnt; // number of playing loops using this sfx
+ int masterLoopSrc; // All other sources looping this buffer are synced to this master src
+} alSfx_t;
+
+static bool alBuffersInitialised = false;
+
+// Sound effect storage, data structures
+#define MAX_SFX 4096
+static alSfx_t knownSfx[MAX_SFX];
+static sfxHandle_t numSfx = 0;
+
+static sfxHandle_t default_sfx;
+
+/*
+=================
+S_AL_BufferFindFree
+
+Find a free handle
+=================
+*/
+static sfxHandle_t S_AL_BufferFindFree( void )
+{
+ int i;
+
+ for(i = 0; i < MAX_SFX; i++)
+ {
+ // Got one
+ if(knownSfx[i].filename[0] == '\0')
+ {
+ if(i >= numSfx)
+ numSfx = i + 1;
+ return i;
+ }
+ }
+
+ // Shit...
+ Com_Error(ERR_FATAL, "S_AL_BufferFindFree: No free sound handles");
+ return -1;
+}
+
+/*
+=================
+S_AL_BufferFind
+
+Find a sound effect if loaded, set up a handle otherwise
+=================
+*/
+static sfxHandle_t S_AL_BufferFind(const char *filename)
+{
+ // Look it up in the table
+ sfxHandle_t sfx = -1;
+ int i;
+
+ if ( !filename ) {
+ Com_Error( ERR_FATAL, "Sound name is NULL" );
+ }
+
+ if ( !filename[0] ) {
+ Com_Printf( S_COLOR_YELLOW "WARNING: Sound name is empty\n" );
+ return 0;
+ }
+
+ if ( strlen( filename ) >= MAX_QPATH ) {
+ Com_Printf( S_COLOR_YELLOW "WARNING: Sound name is too long: %s\n", filename );
+ return 0;
+ }
+
+ if ( filename[0] == '*' ) {
+ Com_Printf( S_COLOR_YELLOW "WARNING: Tried to load player sound directly: %s\n", filename );
+ return 0;
+ }
+
+ for(i = 0; i < numSfx; i++)
+ {
+ if(!Q_stricmp(knownSfx[i].filename, filename))
+ {
+ sfx = i;
+ break;
+ }
+ }
+
+ // Not found in table?
+ if(sfx == -1)
+ {
+ alSfx_t *ptr;
+
+ sfx = S_AL_BufferFindFree();
+
+ // Clear and copy the filename over
+ ptr = &knownSfx[sfx];
+ memset(ptr, 0, sizeof(*ptr));
+ ptr->masterLoopSrc = -1;
+ strcpy(ptr->filename, filename);
+ }
+
+ // Return the handle
+ return sfx;
+}
+
+/*
+=================
+S_AL_BufferUseDefault
+=================
+*/
+static void S_AL_BufferUseDefault(sfxHandle_t sfx)
+{
+ if(sfx == default_sfx)
+ Com_Error(ERR_FATAL, "Can't load default sound effect %s", knownSfx[sfx].filename);
+
+ Com_Printf( S_COLOR_YELLOW "WARNING: Using default sound for %s\n", knownSfx[sfx].filename);
+ knownSfx[sfx].isDefault = true;
+ knownSfx[sfx].buffer = knownSfx[default_sfx].buffer;
+}
+
+/*
+=================
+S_AL_BufferUnload
+=================
+*/
+static void S_AL_BufferUnload(sfxHandle_t sfx)
+{
+ if(knownSfx[sfx].filename[0] == '\0')
+ return;
+
+ if(!knownSfx[sfx].inMemory)
+ return;
+
+ // Delete it
+ S_AL_ClearError( false );
+ qalDeleteBuffers(1, &knownSfx[sfx].buffer);
+ if(qalGetError() != AL_NO_ERROR)
+ Com_Printf( S_COLOR_RED "ERROR: Can't delete sound buffer for %s\n",
+ knownSfx[sfx].filename);
+
+ knownSfx[sfx].inMemory = false;
+}
+
+/*
+=================
+S_AL_BufferEvict
+=================
+*/
+static bool S_AL_BufferEvict( void )
+{
+ int i, oldestBuffer = -1;
+ int oldestTime = Sys_Milliseconds( );
+
+ for( i = 0; i < numSfx; i++ )
+ {
+ if( !knownSfx[ i ].filename[ 0 ] )
+ continue;
+
+ if( !knownSfx[ i ].inMemory )
+ continue;
+
+ if( knownSfx[ i ].lastUsedTime < oldestTime )
+ {
+ oldestTime = knownSfx[ i ].lastUsedTime;
+ oldestBuffer = i;
+ }
+ }
+
+ if( oldestBuffer >= 0 )
+ {
+ S_AL_BufferUnload( oldestBuffer );
+ return true;
+ }
+ else
+ return false;
+}
+
+/*
+=================
+S_AL_GenBuffers
+=================
+*/
+static bool S_AL_GenBuffers(ALsizei numBuffers, ALuint *buffers, const char *name)
+{
+ S_AL_ClearError( false );
+ qalGenBuffers( numBuffers, buffers );
+ ALenum error = qalGetError();
+
+ // If we ran out of buffers, start evicting the least recently used sounds
+ while( error == AL_INVALID_VALUE )
+ {
+ if( !S_AL_BufferEvict( ) )
+ {
+ Com_Printf( S_COLOR_RED "ERROR: Out of audio buffers\n");
+ return false;
+ }
+
+ // Try again
+ S_AL_ClearError( false );
+ qalGenBuffers( numBuffers, buffers );
+ error = qalGetError();
+ }
+
+ if( error != AL_NO_ERROR )
+ {
+ Com_Printf( S_COLOR_RED "ERROR: Can't create a sound buffer for %s - %s\n",
+ name, S_AL_ErrorMsg(error));
+ return false;
+ }
+
+ return true;
+}
+
+/*
+=================
+S_AL_BufferLoad
+=================
+*/
+static void S_AL_BufferLoad(sfxHandle_t sfx, bool cache)
+{
+ ALenum error;
+ ALuint format;
+
+ void *data;
+ snd_info_t info;
+ alSfx_t *curSfx = &knownSfx[sfx];
+
+ // Nothing?
+ if(curSfx->filename[0] == '\0')
+ return;
+
+ // Already done?
+ if((curSfx->inMemory) || (curSfx->isDefault) || (!cache && curSfx->isDefaultChecked))
+ return;
+
+ // Try to load
+ data = S_CodecLoad(curSfx->filename, &info);
+ if(!data)
+ {
+ S_AL_BufferUseDefault(sfx);
+ return;
+ }
+
+ curSfx->isDefaultChecked = true;
+
+ if (!cache)
+ {
+ // Don't create AL cache
+ Hunk_FreeTempMemory(data);
+ return;
+ }
+
+ format = S_AL_Format(info.width, info.channels);
+
+ // Create a buffer
+ if (!S_AL_GenBuffers(1, &curSfx->buffer, curSfx->filename))
+ {
+ S_AL_BufferUseDefault(sfx);
+ Hunk_FreeTempMemory(data);
+ return;
+ }
+
+ // Fill the buffer
+ if( info.size == 0 )
+ {
+ // We have no data to buffer, so buffer silence
+ byte dummyData[ 2 ] = { 0 };
+
+ qalBufferData(curSfx->buffer, AL_FORMAT_MONO16, (void *)dummyData, 2, 22050);
+ }
+ else
+ qalBufferData(curSfx->buffer, format, data, info.size, info.rate);
+
+ error = qalGetError();
+
+ // If we ran out of memory, start evicting the least recently used sounds
+ while(error == AL_OUT_OF_MEMORY)
+ {
+ if( !S_AL_BufferEvict( ) )
+ {
+ qalDeleteBuffers(1, &curSfx->buffer);
+ S_AL_BufferUseDefault(sfx);
+ Hunk_FreeTempMemory(data);
+ Com_Printf( S_COLOR_RED "ERROR: Out of memory loading %s\n", curSfx->filename);
+ return;
+ }
+
+ // Try load it again
+ qalBufferData(curSfx->buffer, format, data, info.size, info.rate);
+ error = qalGetError();
+ }
+
+ // Some other error condition
+ if(error != AL_NO_ERROR)
+ {
+ qalDeleteBuffers(1, &curSfx->buffer);
+ S_AL_BufferUseDefault(sfx);
+ Hunk_FreeTempMemory(data);
+ Com_Printf( S_COLOR_RED "ERROR: Can't fill sound buffer for %s - %s\n",
+ curSfx->filename, S_AL_ErrorMsg(error));
+ return;
+ }
+
+ curSfx->info = info;
+
+ // Free the memory
+ Hunk_FreeTempMemory(data);
+
+ // Woo!
+ curSfx->inMemory = true;
+}
+
+/*
+=================
+S_AL_BufferUse
+=================
+*/
+static void S_AL_BufferUse(sfxHandle_t sfx)
+{
+ if(knownSfx[sfx].filename[0] == '\0')
+ return;
+
+ if((!knownSfx[sfx].inMemory) && (!knownSfx[sfx].isDefault))
+ S_AL_BufferLoad(sfx, true);
+ knownSfx[sfx].lastUsedTime = Sys_Milliseconds();
+}
+
+/*
+=================
+S_AL_BufferInit
+=================
+*/
+static bool S_AL_BufferInit( void )
+{
+ if(alBuffersInitialised)
+ return true;
+
+ // Clear the hash table, and SFX table
+ memset(knownSfx, 0, sizeof(knownSfx));
+ numSfx = 0;
+
+ // Load the default sound, and lock it
+ default_sfx = S_AL_BufferFind("sound/feedback/hit.wav");
+ S_AL_BufferUse(default_sfx);
+ knownSfx[default_sfx].isLocked = true;
+
+ // All done
+ alBuffersInitialised = true;
+ return true;
+}
+
+/*
+=================
+S_AL_BufferShutdown
+=================
+*/
+static void S_AL_BufferShutdown( void )
+{
+ if(!alBuffersInitialised)
+ return;
+
+ // Unlock the default sound effect
+ knownSfx[default_sfx].isLocked = false;
+
+ // Free all used effects
+ for(int i = 0; i < numSfx; i++)
+ S_AL_BufferUnload(i);
+
+ // Clear the tables
+ numSfx = 0;
+
+ // All undone
+ alBuffersInitialised = false;
+}
+
+/*
+=================
+S_AL_RegisterSound
+=================
+*/
+static sfxHandle_t S_AL_RegisterSound( const char *sample, bool compressed )
+{
+ sfxHandle_t sfx = S_AL_BufferFind(sample);
+
+ if((!knownSfx[sfx].inMemory) && (!knownSfx[sfx].isDefault))
+ S_AL_BufferLoad(sfx, s_alPrecache->integer ? true : false);
+ knownSfx[sfx].lastUsedTime = Com_Milliseconds();
+
+ if (knownSfx[sfx].isDefault) {
+ return 0;
+ }
+
+ return sfx;
+}
+
+/*
+=================
+S_AL_SoundDuration
+=================
+*/
+static int S_AL_SoundDuration( sfxHandle_t sfx )
+{
+ if (sfx < 0 || sfx >= numSfx)
+ {
+ Com_Printf(S_COLOR_RED "ERROR: S_AL_SoundDuration: handle %i out of range\n", sfx);
+ return 0;
+ }
+ return knownSfx[sfx].duration;
+}
+
+/*
+=================
+S_AL_BufferGet
+
+Return's a sfx's buffer
+=================
+*/
+static
+ALuint S_AL_BufferGet(sfxHandle_t sfx)
+{
+ return knownSfx[sfx].buffer;
+}
+
+
+//===========================================================================
+
+
+typedef struct src_s
+{
+ ALuint alSource; // OpenAL source object
+ sfxHandle_t sfx; // Sound effect in use
+
+ int lastUsedTime; // Last time used
+ alSrcPriority_t priority; // Priority
+ int entity; // Owning entity (-1 if none)
+ int channel; // Associated channel (-1 if none)
+
+ bool isActive; // Is this source currently in use?
+ bool isPlaying; // Is this source currently playing, or stopped?
+ bool isLocked; // This is locked (un-allocatable)
+ bool isLooping; // Is this a looping effect (attached to an entity)
+ bool isTracking; // Is this object tracking its owner
+ bool isStream; // Is this source a stream
+
+ float curGain; // gain employed if source is within maxdistance.
+ float scaleGain; // Last gain value for this source. 0 if muted.
+
+ float lastTimePos; // On stopped loops, the last position in the buffer
+ int lastSampleTime; // Time when this was stopped
+ vec3_t loopSpeakerPos; // Origin of the loop speaker
+
+ bool local; // Is this local (relative to the cam)
+} src_t;
+
+#ifdef __APPLE__
+ #define MAX_SRC 64
+#else
+ #define MAX_SRC 128
+#endif
+static src_t srcList[MAX_SRC];
+static int srcCount = 0;
+static int srcActiveCnt = 0;
+static bool alSourcesInitialised = false;
+static int lastListenerNumber = -1;
+static vec3_t lastListenerOrigin = { 0.0f, 0.0f, 0.0f };
+
+typedef struct sentity_s
+{
+ vec3_t origin;
+
+ bool srcAllocated; // If a src_t has been allocated to this entity
+ int srcIndex;
+
+ bool loopAddedThisFrame;
+ alSrcPriority_t loopPriority;
+ sfxHandle_t loopSfx;
+ bool startLoopingSound;
+} sentity_t;
+
+static sentity_t entityList[MAX_GENTITIES];
+
+/*
+=================
+S_AL_SanitiseVector
+=================
+*/
+#define S_AL_SanitiseVector(v) _S_AL_SanitiseVector(v,__LINE__)
+static void _S_AL_SanitiseVector( vec3_t v, int line )
+{
+ if( Q_isnan( v[ 0 ] ) || Q_isnan( v[ 1 ] ) || Q_isnan( v[ 2 ] ) )
+ {
+ Com_DPrintf( S_COLOR_YELLOW "WARNING: vector with one or more NaN components "
+ "being passed to OpenAL at %s:%d -- zeroing\n", __FILE__, line );
+ VectorClear( v );
+ }
+}
+
+/*
+=================
+S_AL_Gain
+Set gain to 0 if muted, otherwise set it to given value.
+=================
+*/
+
+static void S_AL_Gain(ALuint source, float gainval)
+{
+ if(s_muted->integer)
+ qalSourcef(source, AL_GAIN, 0.0f);
+ else
+ qalSourcef(source, AL_GAIN, gainval);
+}
+
+/*
+=================
+S_AL_ScaleGain
+Adapt the gain if necessary to get a quicker fadeout when the source is too far away.
+=================
+*/
+
+static void S_AL_ScaleGain(src_t *chksrc, vec3_t origin)
+{
+ float distance;
+
+ if(!chksrc->local)
+ distance = Distance(origin, lastListenerOrigin);
+
+ // If we exceed a certain distance, scale the gain linearly until the sound
+ // vanishes into nothingness.
+ if(!chksrc->local && (distance -= s_alMaxDistance->value) > 0)
+ {
+ float scaleFactor;
+
+ if(distance >= s_alGraceDistance->value)
+ scaleFactor = 0.0f;
+ else
+ scaleFactor = 1.0f - distance / s_alGraceDistance->value;
+
+ scaleFactor *= chksrc->curGain;
+
+ if(chksrc->scaleGain != scaleFactor)
+ {
+ chksrc->scaleGain = scaleFactor;
+ S_AL_Gain(chksrc->alSource, chksrc->scaleGain);
+ }
+ }
+ else if(chksrc->scaleGain != chksrc->curGain)
+ {
+ chksrc->scaleGain = chksrc->curGain;
+ S_AL_Gain(chksrc->alSource, chksrc->scaleGain);
+ }
+}
+
+/*
+=================
+S_AL_HearingThroughEntity
+
+Also see S_Base_HearingThroughEntity
+=================
+*/
+static bool S_AL_HearingThroughEntity( int entityNum )
+{
+
+ if( lastListenerNumber == entityNum )
+ {
+ // This is an outrageous hack to detect
+ // whether or not the player is rendering in third person or not. We can't
+ // ask the renderer because the renderer has no notion of entities and we
+ // can't ask cgame since that would involve changing the API and hence mod
+ // compatibility. I don't think there is any way around this, but I'll leave
+ // the FIXME just in case anyone has a bright idea.
+ float distanceSq = DistanceSquared( entityList[ entityNum ].origin, lastListenerOrigin );
+ if( distanceSq > THIRD_PERSON_THRESHOLD_SQ )
+ return false; //we're the player, but third person
+
+ //we're the player
+ return true;
+ }
+
+ //not the player
+ return false;
+}
+
+/*
+=================
+S_AL_SrcInit
+=================
+*/
+static bool S_AL_SrcInit( void )
+{
+ int i;
+ int limit;
+
+ // Clear the sources data structure
+ memset(srcList, 0, sizeof(srcList));
+ srcCount = 0;
+ srcActiveCnt = 0;
+
+ // Cap s_alSources to MAX_SRC
+ limit = s_alSources->integer;
+ if(limit > MAX_SRC)
+ limit = MAX_SRC;
+ else if(limit < 16)
+ limit = 16;
+
+ S_AL_ClearError( false );
+ // Allocate as many sources as possible
+ for(i = 0; i < limit; i++)
+ {
+ qalGenSources(1, &srcList[i].alSource);
+ if(qalGetError() != AL_NO_ERROR)
+ break;
+ srcCount++;
+ }
+
+ // All done. Print this for informational purposes
+ Com_Printf( "Allocated %d sources.\n", srcCount);
+ alSourcesInitialised = true;
+ return true;
+}
+
+/*
+=================
+S_AL_SrcShutdown
+=================
+*/
+static
+void S_AL_SrcShutdown( void )
+{
+ int i;
+ src_t *curSource;
+
+ if(!alSourcesInitialised)
+ return;
+
+ // Destroy all the sources
+ for(i = 0; i < srcCount; i++)
+ {
+ curSource = &srcList[i];
+
+ if(curSource->isLocked)
+ Com_DPrintf( S_COLOR_YELLOW "WARNING: Source %d is locked\n", i);
+
+ if(curSource->entity > 0)
+ entityList[curSource->entity].srcAllocated = false;
+
+ qalSourceStop(srcList[i].alSource);
+ qalDeleteSources(1, &srcList[i].alSource);
+ }
+
+ memset(srcList, 0, sizeof(srcList));
+
+ alSourcesInitialised = false;
+}
+
+/*
+=================
+S_AL_SrcSetup
+=================
+*/
+static void S_AL_SrcSetup(srcHandle_t src, sfxHandle_t sfx, alSrcPriority_t priority, int entity, int channel, bool local)
+{
+ src_t *curSource;
+
+ // Set up src struct
+ curSource = &srcList[src];
+
+ curSource->lastUsedTime = Sys_Milliseconds();
+ curSource->sfx = sfx;
+ curSource->priority = priority;
+ curSource->entity = entity;
+ curSource->channel = channel;
+ curSource->isPlaying = false;
+ curSource->isLocked = false;
+ curSource->isLooping = false;
+ curSource->isTracking = false;
+ curSource->isStream = false;
+ curSource->curGain = s_alGain->value * s_volume->value;
+ curSource->scaleGain = curSource->curGain;
+ curSource->local = local;
+
+ // Set up OpenAL source
+ if(sfx >= 0)
+ {
+ // Mark the SFX as used, and grab the raw AL buffer
+ S_AL_BufferUse(sfx);
+ qalSourcei(curSource->alSource, AL_BUFFER, S_AL_BufferGet(sfx));
+ }
+
+ qalSourcef(curSource->alSource, AL_PITCH, 1.0f);
+ S_AL_Gain(curSource->alSource, curSource->curGain);
+ qalSourcefv(curSource->alSource, AL_POSITION, vec3_origin);
+ qalSourcefv(curSource->alSource, AL_VELOCITY, vec3_origin);
+ qalSourcei(curSource->alSource, AL_LOOPING, AL_FALSE);
+ qalSourcef(curSource->alSource, AL_REFERENCE_DISTANCE, s_alMinDistance->value);
+
+ if(local)
+ {
+ qalSourcei(curSource->alSource, AL_SOURCE_RELATIVE, AL_TRUE);
+ qalSourcef(curSource->alSource, AL_ROLLOFF_FACTOR, 0.0f);
+ }
+ else
+ {
+ qalSourcei(curSource->alSource, AL_SOURCE_RELATIVE, AL_FALSE);
+ qalSourcef(curSource->alSource, AL_ROLLOFF_FACTOR, s_alRolloff->value);
+ }
+}
+
+/*
+=================
+S_AL_SaveLoopPos
+Remove given source as loop master if it is the master and hand off master status to another source in this case.
+=================
+*/
+
+static void S_AL_SaveLoopPos(src_t *dest, ALuint alSource)
+{
+ int error;
+
+ S_AL_ClearError(false);
+
+ qalGetSourcef(alSource, AL_SEC_OFFSET, &dest->lastTimePos);
+ if((error = qalGetError()) != AL_NO_ERROR)
+ {
+ // Old OpenAL implementations don't support AL_SEC_OFFSET
+
+ if(error != AL_INVALID_ENUM)
+ {
+ Com_Printf(S_COLOR_YELLOW "WARNING: Could not get time offset for alSource %d: %s\n",
+ alSource, S_AL_ErrorMsg(error));
+ }
+
+ dest->lastTimePos = -1;
+ }
+ else
+ dest->lastSampleTime = Sys_Milliseconds();
+}
+
+/*
+=================
+S_AL_NewLoopMaster
+Remove given source as loop master if it is the master and hand off master status to another source in this case.
+=================
+*/
+
+static void S_AL_NewLoopMaster(src_t *rmSource, bool iskilled)
+{
+ int index;
+ src_t *curSource = NULL;
+ alSfx_t *curSfx;
+
+ curSfx = &knownSfx[rmSource->sfx];
+
+ if(rmSource->isPlaying)
+ curSfx->loopActiveCnt--;
+ if(iskilled)
+ curSfx->loopCnt--;
+
+ if(curSfx->loopCnt)
+ {
+ if(rmSource->priority == SRCPRI_ENTITY)
+ {
+ if(!iskilled && rmSource->isPlaying)
+ {
+ // only sync ambient loops...
+ // It makes more sense to have sounds for weapons/projectiles unsynced
+ S_AL_SaveLoopPos(rmSource, rmSource->alSource);
+ }
+ }
+ else if(rmSource == &srcList[curSfx->masterLoopSrc])
+ {
+ int firstInactive = -1;
+
+ // Only if rmSource was the master and if there are still playing loops for
+ // this sound will we need to find a new master.
+
+ if(iskilled || curSfx->loopActiveCnt)
+ {
+ for(index = 0; index < srcCount; index++)
+ {
+ curSource = &srcList[index];
+
+ if(curSource->sfx == rmSource->sfx && curSource != rmSource &&
+ curSource->isActive && curSource->isLooping && curSource->priority == SRCPRI_AMBIENT)
+ {
+ if(curSource->isPlaying)
+ {
+ curSfx->masterLoopSrc = index;
+ break;
+ }
+ else if(firstInactive < 0)
+ firstInactive = index;
+ }
+ }
+ }
+
+ if(!curSfx->loopActiveCnt)
+ {
+ if(firstInactive < 0)
+ {
+ if(iskilled)
+ {
+ curSfx->masterLoopSrc = -1;
+ return;
+ }
+ else
+ curSource = rmSource;
+ }
+ else
+ curSource = &srcList[firstInactive];
+
+ if(rmSource->isPlaying)
+ {
+ // this was the last not stopped source, save last sample position + time
+ S_AL_SaveLoopPos(curSource, rmSource->alSource);
+ }
+ else
+ {
+ // second case: all loops using this sound have stopped due to listener being of of range,
+ // and now the inactive master gets deleted. Just move over the soundpos settings to the
+ // new master.
+ curSource->lastTimePos = rmSource->lastTimePos;
+ curSource->lastSampleTime = rmSource->lastSampleTime;
+ }
+ }
+ }
+ }
+ else
+ curSfx->masterLoopSrc = -1;
+}
+
+/*
+=================
+S_AL_SrcKill
+=================
+*/
+static void S_AL_SrcKill(srcHandle_t src)
+{
+ src_t *curSource = &srcList[src];
+
+ // I'm not touching it. Unlock it first.
+ if(curSource->isLocked)
+ return;
+
+ // Remove the entity association and loop master status
+ if(curSource->isLooping)
+ {
+ curSource->isLooping = false;
+
+ if(curSource->entity != -1)
+ {
+ sentity_t *curEnt = &entityList[curSource->entity];
+
+ curEnt->srcAllocated = false;
+ curEnt->srcIndex = -1;
+ curEnt->loopAddedThisFrame = false;
+ curEnt->startLoopingSound = false;
+ }
+
+ S_AL_NewLoopMaster(curSource, true);
+ }
+
+ // Stop it if it's playing
+ if(curSource->isPlaying)
+ {
+ qalSourceStop(curSource->alSource);
+ curSource->isPlaying = false;
+ }
+
+ // Detach any buffers
+ qalSourcei(curSource->alSource, AL_BUFFER, 0);
+
+ curSource->sfx = 0;
+ curSource->lastUsedTime = 0;
+ curSource->priority = SRCPRI_AMBIENT;
+ curSource->entity = -1;
+ curSource->channel = -1;
+ if(curSource->isActive)
+ {
+ curSource->isActive = false;
+ srcActiveCnt--;
+ }
+ curSource->isLocked = false;
+ curSource->isTracking = false;
+ curSource->local = false;
+}
+
+/*
+=================
+S_AL_SrcAlloc
+=================
+*/
+static
+srcHandle_t S_AL_SrcAlloc( alSrcPriority_t priority, int entnum, int channel )
+{
+ int i;
+ int empty = -1;
+ int weakest = -1;
+ int weakest_time = Sys_Milliseconds();
+ int weakest_pri = 999;
+ float weakest_gain = 1000.0;
+ bool weakest_isplaying = true;
+ int weakest_numloops = 0;
+ src_t *curSource;
+
+ for(i = 0; i < srcCount; i++)
+ {
+ curSource = &srcList[i];
+
+ // If it's locked, we aren't even going to look at it
+ if(curSource->isLocked)
+ continue;
+
+ // Is it empty or not?
+ if(!curSource->isActive)
+ {
+ empty = i;
+ break;
+ }
+
+ if(curSource->isPlaying)
+ {
+ if(weakest_isplaying && curSource->priority < priority &&
+ (curSource->priority < weakest_pri ||
+ (!curSource->isLooping && (curSource->scaleGain < weakest_gain || curSource->lastUsedTime < weakest_time))))
+ {
+ // If it has lower priority, is fainter or older, flag it as weak
+ // the last two values are only compared if it's not a looping sound, because we want to prevent two
+ // loops (loops are added EVERY frame) fighting for a slot
+ weakest_pri = curSource->priority;
+ weakest_time = curSource->lastUsedTime;
+ weakest_gain = curSource->scaleGain;
+ weakest = i;
+ }
+ }
+ else
+ {
+ weakest_isplaying = false;
+
+ if(weakest < 0 ||
+ knownSfx[curSource->sfx].loopCnt > weakest_numloops ||
+ curSource->priority < weakest_pri ||
+ curSource->lastUsedTime < weakest_time)
+ {
+ // Sources currently not playing of course have lowest priority
+ // also try to always keep at least one loop master for every loop sound
+ weakest_pri = curSource->priority;
+ weakest_time = curSource->lastUsedTime;
+ weakest_numloops = knownSfx[curSource->sfx].loopCnt;
+ weakest = i;
+ }
+ }
+
+ // The channel system is not actually adhered to by baseq3, and not
+ // implemented in snd_dma.c, so while the following is strictly correct, it
+ // causes incorrect behaviour versus defacto baseq3
+#if 0
+ // Is it an exact match, and not on channel 0?
+ if((curSource->entity == entnum) && (curSource->channel == channel) && (channel != 0))
+ {
+ S_AL_SrcKill(i);
+ return i;
+ }
+#endif
+ }
+
+ if(empty == -1)
+ empty = weakest;
+
+ if(empty >= 0)
+ {
+ S_AL_SrcKill(empty);
+ srcList[empty].isActive = true;
+ srcActiveCnt++;
+ }
+
+ return empty;
+}
+
+/*
+=================
+S_AL_SrcFind
+
+Finds an active source with matching entity and channel numbers
+Returns -1 if there isn't one
+=================
+*/
+#if 0
+static
+srcHandle_t S_AL_SrcFind(int entnum, int channel)
+{
+ int i;
+ for(i = 0; i < srcCount; i++)
+ {
+ if(!srcList[i].isActive)
+ continue;
+ if((srcList[i].entity == entnum) && (srcList[i].channel == channel))
+ return i;
+ }
+ return -1;
+}
+#endif
+
+/*
+=================
+S_AL_SrcLock
+
+Locked sources will not be automatically reallocated or managed
+=================
+*/
+static
+void S_AL_SrcLock(srcHandle_t src)
+{
+ srcList[src].isLocked = true;
+}
+
+/*
+=================
+S_AL_SrcUnlock
+
+Once unlocked, the source may be reallocated again
+=================
+*/
+static
+void S_AL_SrcUnlock(srcHandle_t src)
+{
+ srcList[src].isLocked = false;
+}
+
+/*
+=================
+S_AL_UpdateEntityPosition
+=================
+*/
+static
+void S_AL_UpdateEntityPosition( int entityNum, const vec3_t origin )
+{
+ vec3_t sanOrigin;
+
+ VectorCopy( origin, sanOrigin );
+ S_AL_SanitiseVector( sanOrigin );
+ if ( entityNum < 0 || entityNum >= MAX_GENTITIES )
+ Com_Error( ERR_DROP, "S_UpdateEntityPosition: bad entitynum %i", entityNum );
+ VectorCopy( sanOrigin, entityList[entityNum].origin );
+}
+
+/*
+=================
+S_AL_CheckInput
+Check whether input values from mods are out of range.
+Necessary for i.g. Western Quake3 mod which is buggy.
+=================
+*/
+static bool S_AL_CheckInput(int entityNum, sfxHandle_t sfx)
+{
+ if (entityNum < 0 || entityNum >= MAX_GENTITIES)
+ Com_Error(ERR_DROP, "ERROR: S_AL_CheckInput: bad entitynum %i", entityNum);
+
+ if (sfx < 0 || sfx >= numSfx)
+ {
+ Com_Printf(S_COLOR_RED "ERROR: S_AL_CheckInput: handle %i out of range\n", sfx);
+ return true;
+ }
+
+ return false;
+}
+
+/*
+=================
+S_AL_StartLocalSound
+
+Play a local (non-spatialized) sound effect
+=================
+*/
+static
+void S_AL_StartLocalSound(sfxHandle_t sfx, int channel)
+{
+ srcHandle_t src;
+
+ if(S_AL_CheckInput(0, sfx))
+ return;
+
+ // Try to grab a source
+ src = S_AL_SrcAlloc(SRCPRI_LOCAL, -1, channel);
+
+ if(src == -1)
+ return;
+
+ // Set up the effect
+ S_AL_SrcSetup(src, sfx, SRCPRI_LOCAL, -1, channel, true);
+
+ // Start it playing
+ srcList[src].isPlaying = true;
+ qalSourcePlay(srcList[src].alSource);
+}
+
+/*
+=================
+S_AL_StartSound
+
+Play a one-shot sound effect
+=================
+*/
+static void S_AL_StartSound( vec3_t origin, int entnum, int entchannel, sfxHandle_t sfx )
+{
+ vec3_t sorigin;
+ srcHandle_t src;
+ src_t *curSource;
+
+ if(origin)
+ {
+ if(S_AL_CheckInput(0, sfx))
+ return;
+
+ VectorCopy(origin, sorigin);
+ }
+ else
+ {
+ if(S_AL_CheckInput(entnum, sfx))
+ return;
+
+ if(S_AL_HearingThroughEntity(entnum))
+ {
+ S_AL_StartLocalSound(sfx, entchannel);
+ return;
+ }
+
+ VectorCopy(entityList[entnum].origin, sorigin);
+ }
+
+ S_AL_SanitiseVector(sorigin);
+
+ if((srcActiveCnt > 5 * srcCount / 3) &&
+ (DistanceSquared(sorigin, lastListenerOrigin) >=
+ (s_alMaxDistance->value + s_alGraceDistance->value) * (s_alMaxDistance->value + s_alGraceDistance->value)))
+ {
+ // We're getting tight on sources and source is not within hearing distance so don't add it
+ return;
+ }
+
+ // Try to grab a source
+ src = S_AL_SrcAlloc(SRCPRI_ONESHOT, entnum, entchannel);
+ if(src == -1)
+ return;
+
+ S_AL_SrcSetup(src, sfx, SRCPRI_ONESHOT, entnum, entchannel, false);
+
+ curSource = &srcList[src];
+
+ if(!origin)
+ curSource->isTracking = true;
+
+ qalSourcefv(curSource->alSource, AL_POSITION, sorigin );
+ S_AL_ScaleGain(curSource, sorigin);
+
+ // Start it playing
+ curSource->isPlaying = true;
+ qalSourcePlay(curSource->alSource);
+}
+
+/*
+=================
+S_AL_ClearLoopingSounds
+=================
+*/
+static void S_AL_ClearLoopingSounds( bool killall )
+{
+ int i;
+ for(i = 0; i < srcCount; i++)
+ {
+ if((srcList[i].isLooping) && (srcList[i].entity != -1))
+ entityList[srcList[i].entity].loopAddedThisFrame = false;
+ }
+}
+
+/*
+=================
+S_AL_SrcLoop
+=================
+*/
+static void S_AL_SrcLoop( alSrcPriority_t priority, sfxHandle_t sfx,
+ const vec3_t origin, const vec3_t velocity, int entityNum )
+{
+ int src;
+ sentity_t *sent = &entityList[ entityNum ];
+ src_t *curSource;
+ vec3_t sorigin, svelocity;
+
+ if(S_AL_CheckInput(entityNum, sfx))
+ return;
+
+ // Do we need to allocate a new source for this entity
+ if( !sent->srcAllocated )
+ {
+ // Try to get a channel
+ src = S_AL_SrcAlloc( priority, entityNum, -1 );
+ if( src == -1 )
+ {
+ Com_DPrintf( S_COLOR_YELLOW "WARNING: Failed to allocate source "
+ "for loop sfx %d on entity %d\n", sfx, entityNum );
+ return;
+ }
+
+ curSource = &srcList[src];
+
+ sent->startLoopingSound = true;
+
+ curSource->lastTimePos = -1.0;
+ curSource->lastSampleTime = Sys_Milliseconds();
+ }
+ else
+ {
+ src = sent->srcIndex;
+ curSource = &srcList[src];
+ }
+
+ sent->srcAllocated = true;
+ sent->srcIndex = src;
+
+ sent->loopPriority = priority;
+ sent->loopSfx = sfx;
+
+ // If this is not set then the looping sound is stopped.
+ sent->loopAddedThisFrame = true;
+
+ // UGH
+ // These lines should be called via S_AL_SrcSetup, but we
+ // can't call that yet as it buffers sfxes that may change
+ // with subsequent calls to S_AL_SrcLoop
+ curSource->entity = entityNum;
+ curSource->isLooping = true;
+
+ if( S_AL_HearingThroughEntity( entityNum ) )
+ {
+ curSource->local = true;
+
+ VectorClear(sorigin);
+
+ qalSourcefv(curSource->alSource, AL_POSITION, sorigin);
+ qalSourcefv(curSource->alSource, AL_VELOCITY, vec3_origin);
+ }
+ else
+ {
+ curSource->local = false;
+
+ if(origin)
+ VectorCopy(origin, sorigin);
+ else
+ VectorCopy(sent->origin, sorigin);
+
+ S_AL_SanitiseVector(sorigin);
+
+ VectorCopy(sorigin, curSource->loopSpeakerPos);
+
+ if(velocity)
+ {
+ VectorCopy(velocity, svelocity);
+ S_AL_SanitiseVector(svelocity);
+ }
+ else
+ VectorClear(svelocity);
+
+ qalSourcefv(curSource->alSource, AL_POSITION, (ALfloat *) sorigin);
+ qalSourcefv(curSource->alSource, AL_VELOCITY, (ALfloat *) svelocity);
+ }
+}
+
+/*
+=================
+S_AL_AddLoopingSound
+=================
+*/
+static void S_AL_AddLoopingSound(int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx)
+{
+ S_AL_SrcLoop(SRCPRI_ENTITY, sfx, origin, velocity, entityNum);
+}
+
+/*
+=================
+S_AL_AddRealLoopingSound
+=================
+*/
+static void S_AL_AddRealLoopingSound(int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx)
+{
+ S_AL_SrcLoop(SRCPRI_AMBIENT, sfx, origin, velocity, entityNum);
+}
+
+/*
+=================
+S_AL_StopLoopingSound
+=================
+*/
+static
+void S_AL_StopLoopingSound(int entityNum )
+{
+ if(entityList[entityNum].srcAllocated)
+ S_AL_SrcKill(entityList[entityNum].srcIndex);
+}
+
+/*
+=================
+S_AL_SrcUpdate
+
+Update state (move things around, manage sources, and so on)
+=================
+*/
+static
+void S_AL_SrcUpdate( void )
+{
+ int i;
+ int entityNum;
+ ALint state;
+ src_t *curSource;
+
+ for(i = 0; i < srcCount; i++)
+ {
+ entityNum = srcList[i].entity;
+ curSource = &srcList[i];
+
+ if(curSource->isLocked)
+ continue;
+
+ if(!curSource->isActive)
+ continue;
+
+ // Update source parameters
+ if((s_alGain->modified) || (s_volume->modified))
+ curSource->curGain = s_alGain->value * s_volume->value;
+ if((s_alRolloff->modified) && (!curSource->local))
+ qalSourcef(curSource->alSource, AL_ROLLOFF_FACTOR, s_alRolloff->value);
+ if(s_alMinDistance->modified)
+ qalSourcef(curSource->alSource, AL_REFERENCE_DISTANCE, s_alMinDistance->value);
+
+ if(curSource->isLooping)
+ {
+ sentity_t *sent = &entityList[ entityNum ];
+
+ // If a looping effect hasn't been touched this frame, pause or kill it
+ if(sent->loopAddedThisFrame)
+ {
+ alSfx_t *curSfx;
+
+ // The sound has changed without an intervening removal
+ if(curSource->isActive && !sent->startLoopingSound &&
+ curSource->sfx != sent->loopSfx)
+ {
+ S_AL_NewLoopMaster(curSource, true);
+
+ curSource->isPlaying = false;
+ qalSourceStop(curSource->alSource);
+ qalSourcei(curSource->alSource, AL_BUFFER, 0);
+ sent->startLoopingSound = true;
+ }
+
+ // The sound hasn't been started yet
+ if(sent->startLoopingSound)
+ {
+ S_AL_SrcSetup(i, sent->loopSfx, sent->loopPriority,
+ entityNum, -1, curSource->local);
+ curSource->isLooping = true;
+
+ knownSfx[curSource->sfx].loopCnt++;
+ sent->startLoopingSound = false;
+ }
+
+ curSfx = &knownSfx[curSource->sfx];
+
+ S_AL_ScaleGain(curSource, curSource->loopSpeakerPos);
+ if(!curSource->scaleGain)
+ {
+ if(curSource->isPlaying)
+ {
+ // Sound is mute, stop playback until we are in range again
+ S_AL_NewLoopMaster(curSource, false);
+ qalSourceStop(curSource->alSource);
+ curSource->isPlaying = false;
+ }
+ else if(!curSfx->loopActiveCnt && curSfx->masterLoopSrc < 0)
+ curSfx->masterLoopSrc = i;
+
+ continue;
+ }
+
+ if(!curSource->isPlaying)
+ {
+ qalSourcei(curSource->alSource, AL_LOOPING, AL_TRUE);
+ curSource->isPlaying = true;
+ qalSourcePlay(curSource->alSource);
+
+ if(curSource->priority == SRCPRI_AMBIENT)
+ {
+ // If there are other ambient looping sources with the same sound,
+ // make sure the sound of these sources are in sync.
+
+ if(curSfx->loopActiveCnt)
+ {
+ int offset, error;
+
+ // we already have a master loop playing, get buffer position.
+ S_AL_ClearError(false);
+ qalGetSourcei(srcList[curSfx->masterLoopSrc].alSource, AL_SAMPLE_OFFSET, &offset);
+ if((error = qalGetError()) != AL_NO_ERROR)
+ {
+ if(error != AL_INVALID_ENUM)
+ {
+ Com_Printf(S_COLOR_YELLOW "WARNING: Cannot get sample offset from source %d: "
+ "%s\n", i, S_AL_ErrorMsg(error));
+ }
+ }
+ else
+ qalSourcei(curSource->alSource, AL_SAMPLE_OFFSET, offset);
+ }
+ else if(curSfx->loopCnt && curSfx->masterLoopSrc >= 0)
+ {
+ float secofs;
+
+ src_t *master = &srcList[curSfx->masterLoopSrc];
+ // This loop sound used to be played, but all sources are stopped. Use last sample position/time
+ // to calculate offset so the player thinks the sources continued playing while they were inaudible.
+
+ if(master->lastTimePos >= 0)
+ {
+ secofs = master->lastTimePos + (Sys_Milliseconds() - master->lastSampleTime) / 1000.0f;
+ secofs = fmodf(secofs, (float) curSfx->info.samples / curSfx->info.rate);
+
+ qalSourcef(curSource->alSource, AL_SEC_OFFSET, secofs);
+ }
+
+ // I be the master now
+ curSfx->masterLoopSrc = i;
+ }
+ else
+ curSfx->masterLoopSrc = i;
+ }
+ else if(curSource->lastTimePos >= 0)
+ {
+ float secofs;
+
+ // For unsynced loops (SRCPRI_ENTITY) just carry on playing as if the sound was never stopped
+
+ secofs = curSource->lastTimePos + (Sys_Milliseconds() - curSource->lastSampleTime) / 1000.0f;
+ secofs = fmodf(secofs, (float) curSfx->info.samples / curSfx->info.rate);
+ qalSourcef(curSource->alSource, AL_SEC_OFFSET, secofs);
+ }
+
+ curSfx->loopActiveCnt++;
+ }
+
+ // Update locality
+ if(curSource->local)
+ {
+ qalSourcei(curSource->alSource, AL_SOURCE_RELATIVE, AL_TRUE);
+ qalSourcef(curSource->alSource, AL_ROLLOFF_FACTOR, 0.0f);
+ }
+ else
+ {
+ qalSourcei(curSource->alSource, AL_SOURCE_RELATIVE, AL_FALSE);
+ qalSourcef(curSource->alSource, AL_ROLLOFF_FACTOR, s_alRolloff->value);
+ }
+
+ }
+ else if(curSource->priority == SRCPRI_AMBIENT)
+ {
+ if(curSource->isPlaying)
+ {
+ S_AL_NewLoopMaster(curSource, false);
+ qalSourceStop(curSource->alSource);
+ curSource->isPlaying = false;
+ }
+ }
+ else
+ S_AL_SrcKill(i);
+
+ continue;
+ }
+
+ if(!curSource->isStream)
+ {
+ // Check if it's done, and flag it
+ qalGetSourcei(curSource->alSource, AL_SOURCE_STATE, &state);
+ if(state == AL_STOPPED)
+ {
+ curSource->isPlaying = false;
+ S_AL_SrcKill(i);
+ continue;
+ }
+ }
+
+ // Query relativity of source, don't move if it's true
+ qalGetSourcei(curSource->alSource, AL_SOURCE_RELATIVE, &state);
+
+ // See if it needs to be moved
+ if(curSource->isTracking && !state)
+ {
+ qalSourcefv(curSource->alSource, AL_POSITION, entityList[entityNum].origin);
+ S_AL_ScaleGain(curSource, entityList[entityNum].origin);
+ }
+ }
+}
+
+/*
+=================
+S_AL_SrcShutup
+=================
+*/
+static
+void S_AL_SrcShutup( void )
+{
+ int i;
+ for(i = 0; i < srcCount; i++)
+ S_AL_SrcKill(i);
+}
+
+/*
+=================
+S_AL_SrcGet
+=================
+*/
+static
+ALuint S_AL_SrcGet(srcHandle_t src)
+{
+ return srcList[src].alSource;
+}
+
+
+//===========================================================================
+
+// Q3A cinematics use up to 12 buffers at once
+#define MAX_STREAM_BUFFERS 20
+
+static srcHandle_t streamSourceHandles[MAX_RAW_STREAMS];
+static bool streamPlaying[MAX_RAW_STREAMS];
+static ALuint streamSources[MAX_RAW_STREAMS];
+static ALuint streamBuffers[MAX_RAW_STREAMS][MAX_STREAM_BUFFERS];
+static int streamNumBuffers[MAX_RAW_STREAMS];
+static int streamBufIndex[MAX_RAW_STREAMS];
+
+/*
+=================
+S_AL_AllocateStreamChannel
+=================
+*/
+static void S_AL_AllocateStreamChannel(int stream, int entityNum)
+{
+ srcHandle_t cursrc;
+ ALuint alsrc;
+
+ if ((stream < 0) || (stream >= MAX_RAW_STREAMS))
+ return;
+
+ if(entityNum >= 0)
+ {
+ // This is a stream that tracks an entity
+ // Allocate a streamSource at normal priority
+ cursrc = S_AL_SrcAlloc(SRCPRI_ENTITY, entityNum, 0);
+ if(cursrc < 0)
+ return;
+
+ S_AL_SrcSetup(cursrc, -1, SRCPRI_ENTITY, entityNum, 0, false);
+ alsrc = S_AL_SrcGet(cursrc);
+ srcList[cursrc].isTracking = true;
+ srcList[cursrc].isStream = true;
+ }
+ else
+ {
+ // Unspatialized stream source
+
+ // Allocate a streamSource at high priority
+ cursrc = S_AL_SrcAlloc(SRCPRI_STREAM, -2, 0);
+ if(cursrc < 0)
+ return;
+
+ alsrc = S_AL_SrcGet(cursrc);
+
+ // Lock the streamSource so nobody else can use it, and get the raw streamSource
+ S_AL_SrcLock(cursrc);
+
+ // make sure that after unmuting the S_AL_Gain in S_Update() does not turn
+ // volume up prematurely for this source
+ srcList[cursrc].scaleGain = 0.0f;
+
+ // Set some streamSource parameters
+ qalSourcei (alsrc, AL_BUFFER, 0 );
+ qalSourcei (alsrc, AL_LOOPING, AL_FALSE );
+ qalSource3f(alsrc, AL_POSITION, 0.0, 0.0, 0.0);
+ qalSource3f(alsrc, AL_VELOCITY, 0.0, 0.0, 0.0);
+ qalSource3f(alsrc, AL_DIRECTION, 0.0, 0.0, 0.0);
+ qalSourcef (alsrc, AL_ROLLOFF_FACTOR, 0.0 );
+ qalSourcei (alsrc, AL_SOURCE_RELATIVE, AL_TRUE );
+ }
+
+ streamSourceHandles[stream] = cursrc;
+ streamSources[stream] = alsrc;
+
+ streamNumBuffers[stream] = 0;
+ streamBufIndex[stream] = 0;
+}
+
+/*
+=================
+S_AL_FreeStreamChannel
+=================
+*/
+static void S_AL_FreeStreamChannel( int stream )
+{
+ if ((stream < 0) || (stream >= MAX_RAW_STREAMS))
+ return;
+
+ // Detach any buffers
+ qalSourcei(streamSources[stream], AL_BUFFER, 0);
+
+ // Delete the buffers
+ if (streamNumBuffers[stream] > 0) {
+ qalDeleteBuffers(streamNumBuffers[stream], streamBuffers[stream]);
+ streamNumBuffers[stream] = 0;
+ }
+
+ // Release the output streamSource
+ S_AL_SrcUnlock(streamSourceHandles[stream]);
+ S_AL_SrcKill(streamSourceHandles[stream]);
+ streamSources[stream] = 0;
+ streamSourceHandles[stream] = -1;
+}
+
+/*
+=================
+S_AL_RawSamples
+=================
+*/
+static
+void S_AL_RawSamples(int stream, int samples, int rate, int width, int channels, const byte *data, float volume, int entityNum)
+{
+ int numBuffers;
+ ALuint buffer;
+ ALuint format;
+
+ if ((stream < 0) || (stream >= MAX_RAW_STREAMS))
+ return;
+
+ format = S_AL_Format( width, channels );
+
+ // Create the streamSource if necessary
+ if(streamSourceHandles[stream] == -1)
+ {
+ S_AL_AllocateStreamChannel(stream, entityNum);
+
+ // Failed?
+ if(streamSourceHandles[stream] == -1)
+ {
+ Com_Printf( S_COLOR_RED "ERROR: Can't allocate streaming streamSource\n");
+ return;
+ }
+ }
+
+ qalGetSourcei(streamSources[stream], AL_BUFFERS_QUEUED, &numBuffers);
+
+ if (numBuffers == MAX_STREAM_BUFFERS)
+ {
+ Com_DPrintf(S_COLOR_RED"WARNING: Steam dropping raw samples, reached MAX_STREAM_BUFFERS\n");
+ return;
+ }
+
+ // Allocate a new AL buffer if needed
+ if (numBuffers == streamNumBuffers[stream])
+ {
+ ALuint oldBuffers[MAX_STREAM_BUFFERS];
+ int i;
+
+ if (!S_AL_GenBuffers(1, &buffer, "stream"))
+ return;
+
+ ::memcpy(oldBuffers, &streamBuffers[stream], sizeof (oldBuffers));
+
+ // Reorder buffer array in order of oldest to newest
+ for ( i = 0; i < streamNumBuffers[stream]; ++i )
+ streamBuffers[stream][i] = oldBuffers[(streamBufIndex[stream] + i) % streamNumBuffers[stream]];
+
+ // Add the new buffer to end
+ streamBuffers[stream][streamNumBuffers[stream]] = buffer;
+ streamBufIndex[stream] = streamNumBuffers[stream];
+ streamNumBuffers[stream]++;
+ }
+
+ // Select next buffer in loop
+ buffer = streamBuffers[stream][ streamBufIndex[stream] ];
+ streamBufIndex[stream] = (streamBufIndex[stream] + 1) % streamNumBuffers[stream];
+
+ // Fill buffer
+ qalBufferData(buffer, format, (ALvoid *)data, (samples * width * channels), rate);
+
+ // Shove the data onto the streamSource
+ qalSourceQueueBuffers(streamSources[stream], 1, &buffer);
+
+ if(entityNum < 0)
+ {
+ // Volume
+ S_AL_Gain (streamSources[stream], volume * s_volume->value * s_alGain->value);
+ }
+
+ // Start stream
+ if(!streamPlaying[stream])
+ {
+ qalSourcePlay( streamSources[stream] );
+ streamPlaying[stream] = true;
+ }
+}
+
+/*
+=================
+S_AL_StreamUpdate
+=================
+*/
+static
+void S_AL_StreamUpdate( int stream )
+{
+ int numBuffers;
+ ALint state;
+
+ if ((stream < 0) || (stream >= MAX_RAW_STREAMS))
+ return;
+
+ if(streamSourceHandles[stream] == -1)
+ return;
+
+ // Un-queue any buffers
+ qalGetSourcei( streamSources[stream], AL_BUFFERS_PROCESSED, &numBuffers );
+ while( numBuffers-- )
+ {
+ ALuint buffer;
+ qalSourceUnqueueBuffers(streamSources[stream], 1, &buffer);
+ }
+
+ // Start the streamSource playing if necessary
+ qalGetSourcei( streamSources[stream], AL_BUFFERS_QUEUED, &numBuffers );
+
+ qalGetSourcei(streamSources[stream], AL_SOURCE_STATE, &state);
+ if(state == AL_STOPPED)
+ {
+ streamPlaying[stream] = false;
+
+ // If there are no buffers queued up, release the streamSource
+ if( !numBuffers )
+ S_AL_FreeStreamChannel( stream );
+ }
+
+ if( !streamPlaying[stream] && numBuffers )
+ {
+ qalSourcePlay( streamSources[stream] );
+ streamPlaying[stream] = true;
+ }
+}
+
+/*
+=================
+S_AL_StreamDie
+=================
+*/
+static
+void S_AL_StreamDie( int stream )
+{
+ if ((stream < 0) || (stream >= MAX_RAW_STREAMS))
+ return;
+
+ if(streamSourceHandles[stream] == -1)
+ return;
+
+ streamPlaying[stream] = false;
+ qalSourceStop(streamSources[stream]);
+
+ S_AL_FreeStreamChannel(stream);
+}
+
+
+//===========================================================================
+
+
+#define NUM_MUSIC_BUFFERS 4
+#define MUSIC_BUFFER_SIZE 4096
+
+static bool musicPlaying = false;
+static srcHandle_t musicSourceHandle = -1;
+static ALuint musicSource;
+static ALuint musicBuffers[NUM_MUSIC_BUFFERS];
+
+static snd_stream_t *mus_stream;
+static snd_stream_t *intro_stream;
+static char s_backgroundLoop[MAX_QPATH];
+
+static byte decode_buffer[MUSIC_BUFFER_SIZE];
+
+/*
+=================
+S_AL_MusicSourceGet
+=================
+*/
+static void S_AL_MusicSourceGet( void )
+{
+ // Allocate a musicSource at high priority
+ musicSourceHandle = S_AL_SrcAlloc(SRCPRI_STREAM, -2, 0);
+ if(musicSourceHandle == -1)
+ return;
+
+ // Lock the musicSource so nobody else can use it, and get the raw musicSource
+ S_AL_SrcLock(musicSourceHandle);
+ musicSource = S_AL_SrcGet(musicSourceHandle);
+
+ // make sure that after unmuting the S_AL_Gain in S_Update() does not turn
+ // volume up prematurely for this source
+ srcList[musicSourceHandle].scaleGain = 0.0f;
+
+ // Set some musicSource parameters
+ qalSource3f(musicSource, AL_POSITION, 0.0, 0.0, 0.0);
+ qalSource3f(musicSource, AL_VELOCITY, 0.0, 0.0, 0.0);
+ qalSource3f(musicSource, AL_DIRECTION, 0.0, 0.0, 0.0);
+ qalSourcef (musicSource, AL_ROLLOFF_FACTOR, 0.0 );
+ qalSourcei (musicSource, AL_SOURCE_RELATIVE, AL_TRUE );
+}
+
+/*
+=================
+S_AL_MusicSourceFree
+=================
+*/
+static void S_AL_MusicSourceFree( void )
+{
+ // Release the output musicSource
+ S_AL_SrcUnlock(musicSourceHandle);
+ S_AL_SrcKill(musicSourceHandle);
+ musicSource = 0;
+ musicSourceHandle = -1;
+}
+
+/*
+=================
+S_AL_CloseMusicFiles
+=================
+*/
+static void S_AL_CloseMusicFiles(void)
+{
+ if(intro_stream)
+ {
+ S_CodecCloseStream(intro_stream);
+ intro_stream = NULL;
+ }
+
+ if(mus_stream)
+ {
+ S_CodecCloseStream(mus_stream);
+ mus_stream = NULL;
+ }
+}
+
+/*
+=================
+S_AL_StopBackgroundTrack
+=================
+*/
+static
+void S_AL_StopBackgroundTrack( void )
+{
+ if(!musicPlaying)
+ return;
+
+ // Stop playing
+ qalSourceStop(musicSource);
+
+ // Detach any buffers
+ qalSourcei(musicSource, AL_BUFFER, 0);
+
+ // Delete the buffers
+ qalDeleteBuffers(NUM_MUSIC_BUFFERS, musicBuffers);
+
+ // Free the musicSource
+ S_AL_MusicSourceFree();
+
+ // Unload the stream
+ S_AL_CloseMusicFiles();
+
+ musicPlaying = false;
+}
+
+/*
+=================
+S_AL_MusicProcess
+=================
+*/
+static
+void S_AL_MusicProcess(ALuint b)
+{
+ ALenum error;
+ int l;
+ ALuint format;
+ snd_stream_t *curstream;
+
+ S_AL_ClearError( false );
+
+ if(intro_stream)
+ curstream = intro_stream;
+ else
+ curstream = mus_stream;
+
+ if(!curstream)
+ return;
+
+ l = S_CodecReadStream(curstream, MUSIC_BUFFER_SIZE, decode_buffer);
+
+ // Run out data to read, start at the beginning again
+ if(l == 0)
+ {
+ S_CodecCloseStream(curstream);
+
+ // the intro stream just finished playing so we don't need to reopen
+ // the music stream.
+ if(intro_stream)
+ intro_stream = NULL;
+ else
+ mus_stream = S_CodecOpenStream(s_backgroundLoop);
+
+ curstream = mus_stream;
+
+ if(!curstream)
+ {
+ S_AL_StopBackgroundTrack();
+ return;
+ }
+
+ l = S_CodecReadStream(curstream, MUSIC_BUFFER_SIZE, decode_buffer);
+ }
+
+ format = S_AL_Format(curstream->info.width, curstream->info.channels);
+
+ if( l == 0 )
+ {
+ // We have no data to buffer, so buffer silence
+ byte dummyData[ 2 ] = { 0 };
+
+ qalBufferData( b, AL_FORMAT_MONO16, (void *)dummyData, 2, 22050 );
+ }
+ else
+ qalBufferData(b, format, decode_buffer, l, curstream->info.rate);
+
+ if( ( error = qalGetError( ) ) != AL_NO_ERROR )
+ {
+ S_AL_StopBackgroundTrack( );
+ Com_Printf( S_COLOR_RED "ERROR: while buffering data for music stream - %s\n",
+ S_AL_ErrorMsg( error ) );
+ return;
+ }
+}
+
+/*
+=================
+S_AL_StartBackgroundTrack
+=================
+*/
+static
+void S_AL_StartBackgroundTrack( const char *intro, const char *loop )
+{
+ int i;
+ bool issame;
+
+ // Stop any existing music that might be playing
+ S_AL_StopBackgroundTrack();
+
+ if((!intro || !*intro) && (!loop || !*loop))
+ return;
+
+ // Allocate a musicSource
+ S_AL_MusicSourceGet();
+ if(musicSourceHandle == -1)
+ return;
+
+ if (!loop || !*loop)
+ {
+ loop = intro;
+ issame = true;
+ }
+ else if(intro && *intro && !strcmp(intro, loop))
+ issame = true;
+ else
+ issame = false;
+
+ // Copy the loop over
+ Q_strncpyz( s_backgroundLoop, loop, sizeof( s_backgroundLoop ) );
+
+ if(!issame)
+ {
+ // Open the intro and don't mind whether it succeeds.
+ // The important part is the loop.
+ intro_stream = S_CodecOpenStream(intro);
+ }
+ else
+ intro_stream = NULL;
+
+ mus_stream = S_CodecOpenStream(s_backgroundLoop);
+ if(!mus_stream)
+ {
+ S_AL_CloseMusicFiles();
+ S_AL_MusicSourceFree();
+ return;
+ }
+
+ // Generate the musicBuffers
+ if (!S_AL_GenBuffers(NUM_MUSIC_BUFFERS, musicBuffers, "music"))
+ return;
+
+ // Queue the musicBuffers up
+ for(i = 0; i < NUM_MUSIC_BUFFERS; i++)
+ {
+ S_AL_MusicProcess(musicBuffers[i]);
+ }
+
+ qalSourceQueueBuffers(musicSource, NUM_MUSIC_BUFFERS, musicBuffers);
+
+ // Set the initial gain property
+ S_AL_Gain(musicSource, s_alGain->value * s_musicVolume->value);
+
+ // Start playing
+ qalSourcePlay(musicSource);
+
+ musicPlaying = true;
+}
+
+/*
+=================
+S_AL_MusicUpdate
+=================
+*/
+static
+void S_AL_MusicUpdate( void )
+{
+ int numBuffers;
+ ALint state;
+
+ if(!musicPlaying)
+ return;
+
+ qalGetSourcei( musicSource, AL_BUFFERS_PROCESSED, &numBuffers );
+ while( numBuffers-- )
+ {
+ ALuint b;
+ qalSourceUnqueueBuffers(musicSource, 1, &b);
+ S_AL_MusicProcess(b);
+ qalSourceQueueBuffers(musicSource, 1, &b);
+ }
+
+ // Hitches can cause OpenAL to be starved of buffers when streaming.
+ // If this happens, it will stop playback. This restarts the source if
+ // it is no longer playing, and if there are buffers available
+ qalGetSourcei( musicSource, AL_SOURCE_STATE, &state );
+ qalGetSourcei( musicSource, AL_BUFFERS_QUEUED, &numBuffers );
+ if( state == AL_STOPPED && numBuffers )
+ {
+ Com_DPrintf( S_COLOR_YELLOW "Restarted OpenAL music\n" );
+ qalSourcePlay(musicSource);
+ }
+
+ // Set the gain property
+ S_AL_Gain(musicSource, s_alGain->value * s_musicVolume->value);
+}
+
+
+//===========================================================================
+
+
+// Local state variables
+static ALCdevice *alDevice;
+static ALCcontext *alContext;
+
+#ifdef USE_VOIP
+static ALCdevice *alCaptureDevice;
+static cvar_t *s_alCapture;
+#endif
+
+#ifdef _WIN32
+#define ALDRIVER_DEFAULT "OpenAL32.dll"
+#elif defined(__APPLE__)
+#define ALDRIVER_DEFAULT "/System/Library/Frameworks/OpenAL.framework/OpenAL"
+#elif defined(__OpenBSD__)
+#define ALDRIVER_DEFAULT "libopenal.so"
+#else
+#define ALDRIVER_DEFAULT "libopenal.so.1"
+#endif
+
+/*
+=================
+S_AL_StopAllSounds
+=================
+*/
+static
+void S_AL_StopAllSounds( void )
+{
+ int i;
+ S_AL_SrcShutup();
+ S_AL_StopBackgroundTrack();
+ for (i = 0; i < MAX_RAW_STREAMS; i++)
+ S_AL_StreamDie(i);
+}
+
+/*
+=================
+S_AL_Respatialize
+=================
+*/
+static
+void S_AL_Respatialize( int entityNum, const vec3_t origin, vec3_t axis[3], int inwater )
+{
+ float orientation[6];
+ vec3_t sorigin;
+
+ VectorCopy( origin, sorigin );
+ S_AL_SanitiseVector( sorigin );
+
+ S_AL_SanitiseVector( axis[ 0 ] );
+ S_AL_SanitiseVector( axis[ 1 ] );
+ S_AL_SanitiseVector( axis[ 2 ] );
+
+ orientation[0] = axis[0][0]; orientation[1] = axis[0][1]; orientation[2] = axis[0][2];
+ orientation[3] = axis[2][0]; orientation[4] = axis[2][1]; orientation[5] = axis[2][2];
+
+ lastListenerNumber = entityNum;
+ VectorCopy( sorigin, lastListenerOrigin );
+
+ // Set OpenAL listener paramaters
+ qalListenerfv(AL_POSITION, (ALfloat *)sorigin);
+ qalListenerfv(AL_VELOCITY, vec3_origin);
+ qalListenerfv(AL_ORIENTATION, orientation);
+}
+
+/*
+=================
+S_AL_Update
+=================
+*/
+static
+void S_AL_Update( void )
+{
+ int i;
+
+ if(s_muted->modified)
+ {
+ // muted state changed. Let S_AL_Gain turn up all sources again.
+ for(i = 0; i < srcCount; i++)
+ {
+ if(srcList[i].isActive)
+ S_AL_Gain(srcList[i].alSource, srcList[i].scaleGain);
+ }
+
+ s_muted->modified = false;
+ }
+
+ // Update SFX channels
+ S_AL_SrcUpdate();
+
+ // Update streams
+ for (i = 0; i < MAX_RAW_STREAMS; i++)
+ S_AL_StreamUpdate(i);
+ S_AL_MusicUpdate();
+
+ // Doppler
+ if(s_doppler->modified)
+ {
+ s_alDopplerFactor->modified = true;
+ s_doppler->modified = false;
+ }
+
+ // Doppler parameters
+ if(s_alDopplerFactor->modified)
+ {
+ if(s_doppler->integer)
+ qalDopplerFactor(s_alDopplerFactor->value);
+ else
+ qalDopplerFactor(0.0f);
+ s_alDopplerFactor->modified = false;
+ }
+ if(s_alDopplerSpeed->modified)
+ {
+ qalSpeedOfSound(s_alDopplerSpeed->value);
+ s_alDopplerSpeed->modified = false;
+ }
+
+ // Clear the modified flags on the other cvars
+ s_alGain->modified = false;
+ s_volume->modified = false;
+ s_musicVolume->modified = false;
+ s_alMinDistance->modified = false;
+ s_alRolloff->modified = false;
+}
+
+/*
+=================
+S_AL_DisableSounds
+=================
+*/
+static
+void S_AL_DisableSounds( void )
+{
+ S_AL_StopAllSounds();
+}
+
+/*
+=================
+S_AL_BeginRegistration
+=================
+*/
+static
+void S_AL_BeginRegistration( void )
+{
+ if(!numSfx)
+ S_AL_BufferInit();
+}
+
+/*
+=================
+S_AL_ClearSoundBuffer
+=================
+*/
+static
+void S_AL_ClearSoundBuffer( void )
+{
+}
+
+/*
+=================
+S_AL_SoundList
+=================
+*/
+static
+void S_AL_SoundList( void )
+{
+}
+
+#ifdef USE_VOIP
+static
+void S_AL_StartCapture( void )
+{
+ if (alCaptureDevice != NULL)
+ qalcCaptureStart(alCaptureDevice);
+}
+
+static
+int S_AL_AvailableCaptureSamples( void )
+{
+ int retval = 0;
+ if (alCaptureDevice != NULL)
+ {
+ ALint samples = 0;
+ qalcGetIntegerv(alCaptureDevice, ALC_CAPTURE_SAMPLES, sizeof (samples), &samples);
+ retval = (int) samples;
+ }
+ return retval;
+}
+
+static
+void S_AL_Capture( int samples, byte *data )
+{
+ if (alCaptureDevice != NULL)
+ qalcCaptureSamples(alCaptureDevice, data, samples);
+}
+
+void S_AL_StopCapture( void )
+{
+ if (alCaptureDevice != NULL)
+ qalcCaptureStop(alCaptureDevice);
+}
+
+void S_AL_MasterGain( float gain )
+{
+ qalListenerf(AL_GAIN, gain);
+}
+#endif
+
+
+/*
+=================
+S_AL_SoundInfo
+=================
+*/
+static void S_AL_SoundInfo(void)
+{
+ Com_Printf( "OpenAL info:\n" );
+ Com_Printf( " Vendor: %s\n", qalGetString( AL_VENDOR ) );
+ Com_Printf( " Version: %s\n", qalGetString( AL_VERSION ) );
+ Com_Printf( " Renderer: %s\n", qalGetString( AL_RENDERER ) );
+ Com_Printf( " AL Extensions: %s\n", qalGetString( AL_EXTENSIONS ) );
+ Com_Printf( " ALC Extensions: %s\n", qalcGetString( alDevice, ALC_EXTENSIONS ) );
+
+ if(enumeration_all_ext)
+ Com_Printf(" Device: %s\n", qalcGetString(alDevice, ALC_ALL_DEVICES_SPECIFIER));
+ else if(enumeration_ext)
+ Com_Printf(" Device: %s\n", qalcGetString(alDevice, ALC_DEVICE_SPECIFIER));
+
+ if(enumeration_all_ext || enumeration_ext)
+ Com_Printf(" Available Devices:\n%s", s_alAvailableDevices->string);
+
+#ifdef USE_VOIP
+ if(capture_ext)
+ {
+ Com_Printf(" Input Device: %s\n", qalcGetString(alCaptureDevice, ALC_CAPTURE_DEVICE_SPECIFIER));
+ Com_Printf(" Available Input Devices:\n%s", s_alAvailableInputDevices->string);
+ }
+#endif
+}
+
+
+
+/*
+=================
+S_AL_Shutdown
+=================
+*/
+static
+void S_AL_Shutdown( void )
+{
+ // Shut down everything
+ int i;
+ for (i = 0; i < MAX_RAW_STREAMS; i++)
+ S_AL_StreamDie(i);
+ S_AL_StopBackgroundTrack( );
+ S_AL_SrcShutdown( );
+ S_AL_BufferShutdown( );
+
+ qalcDestroyContext(alContext);
+ qalcCloseDevice(alDevice);
+
+#ifdef USE_VOIP
+ if (alCaptureDevice != NULL) {
+ qalcCaptureStop(alCaptureDevice);
+ qalcCaptureCloseDevice(alCaptureDevice);
+ alCaptureDevice = NULL;
+ Com_Printf( "OpenAL capture device closed.\n" );
+ }
+#endif
+
+ for (i = 0; i < MAX_RAW_STREAMS; i++) {
+ streamSourceHandles[i] = -1;
+ streamPlaying[i] = false;
+ streamSources[i] = 0;
+ }
+
+ QAL_Shutdown();
+}
+
+#endif
+
+/*
+=================
+S_AL_Init
+=================
+*/
+bool S_AL_Init( soundInterface_t *si )
+{
+#ifdef USE_OPENAL
+ const char* device = NULL;
+ const char* inputdevice = NULL;
+ int i;
+
+ if( !si ) {
+ return false;
+ }
+
+ for (i = 0; i < MAX_RAW_STREAMS; i++) {
+ streamSourceHandles[i] = -1;
+ streamPlaying[i] = false;
+ streamSources[i] = 0;
+ streamNumBuffers[i] = 0;
+ streamBufIndex[i] = 0;
+ }
+
+ // New console variables
+ s_alPrecache = Cvar_Get( "s_alPrecache", "1", CVAR_ARCHIVE );
+ s_alGain = Cvar_Get( "s_alGain", "1.0", CVAR_ARCHIVE );
+ s_alSources = Cvar_Get( "s_alSources", "96", CVAR_ARCHIVE );
+ s_alDopplerFactor = Cvar_Get( "s_alDopplerFactor", "1.0", CVAR_ARCHIVE );
+ s_alDopplerSpeed = Cvar_Get( "s_alDopplerSpeed", "9000", CVAR_ARCHIVE );
+ s_alMinDistance = Cvar_Get( "s_alMinDistance", "120", CVAR_CHEAT );
+ s_alMaxDistance = Cvar_Get("s_alMaxDistance", "1024", CVAR_CHEAT);
+ s_alRolloff = Cvar_Get( "s_alRolloff", "2", CVAR_CHEAT);
+ s_alGraceDistance = Cvar_Get("s_alGraceDistance", "512", CVAR_CHEAT);
+
+
+ s_alInputDevice = Cvar_Get( "s_alInputDevice", "", CVAR_ARCHIVE | CVAR_LATCH );
+ s_alDevice = Cvar_Get("s_alDevice", "", CVAR_ARCHIVE | CVAR_LATCH);
+ s_alDriver = Cvar_Get( "s_alDriver", ALDRIVER_DEFAULT, CVAR_ARCHIVE | CVAR_LATCH | CVAR_PROTECTED);
+
+ // Load QAL
+ if( !QAL_Init( s_alDriver->string ) )
+ {
+ Com_Printf( "Failed to load library: \"%s\".\n", s_alDriver->string );
+ if( !Q_stricmp( s_alDriver->string, ALDRIVER_DEFAULT ) || !QAL_Init( ALDRIVER_DEFAULT ) ) {
+ return false;
+ }
+ }
+
+ device = s_alDevice->string;
+ if(device && !*device)
+ device = NULL;
+
+ inputdevice = s_alInputDevice->string;
+ if(inputdevice && !*inputdevice)
+ inputdevice = NULL;
+
+
+ // Device enumeration support
+ enumeration_all_ext = qalcIsExtensionPresent(NULL, "ALC_ENUMERATE_ALL_EXT");
+ enumeration_ext = qalcIsExtensionPresent(NULL, "ALC_ENUMERATION_EXT");
+
+ if(enumeration_ext || enumeration_all_ext)
+ {
+ char devicenames[16384] = "";
+ const char *devicelist;
+#ifdef _WIN32
+ const char *defaultdevice;
+#endif
+ int curlen;
+
+ // get all available devices + the default device name.
+ if(enumeration_all_ext)
+ {
+ devicelist = qalcGetString(NULL, ALC_ALL_DEVICES_SPECIFIER);
+#ifdef _WIN32
+ defaultdevice = qalcGetString(NULL, ALC_DEFAULT_ALL_DEVICES_SPECIFIER);
+#endif
+ }
+ else
+ {
+ // We don't have ALC_ENUMERATE_ALL_EXT but normal enumeration.
+ devicelist = qalcGetString(NULL, ALC_DEVICE_SPECIFIER);
+#ifdef _WIN32
+ defaultdevice = qalcGetString(NULL, ALC_DEFAULT_DEVICE_SPECIFIER);
+#endif
+ enumeration_ext = true;
+ }
+
+#ifdef _WIN32
+ // check whether the default device is generic hardware. If it is, change to
+ // Generic Software as that one works more reliably with various sound systems.
+ // If it's not, use OpenAL's default selection as we don't want to ignore
+ // native hardware acceleration.
+ if(!device && defaultdevice && !strcmp(defaultdevice, "Generic Hardware"))
+ device = "Generic Software";
+#endif
+
+ // dump a list of available devices to a cvar for the user to see.
+
+ if(devicelist)
+ {
+ while((curlen = strlen(devicelist)))
+ {
+ Q_strcat(devicenames, sizeof(devicenames), devicelist);
+ Q_strcat(devicenames, sizeof(devicenames), "\n");
+
+ devicelist += curlen + 1;
+ }
+ }
+
+ s_alAvailableDevices = Cvar_Get("s_alAvailableDevices", devicenames, CVAR_ROM | CVAR_NORESTART);
+ }
+
+ alDevice = qalcOpenDevice(device);
+ if( !alDevice && device )
+ {
+ Com_Printf( "Failed to open OpenAL device '%s', trying default.\n", device );
+ alDevice = qalcOpenDevice(NULL);
+ }
+
+ if( !alDevice )
+ {
+ QAL_Shutdown( );
+ Com_Printf( "Failed to open OpenAL device.\n" );
+ return false;
+ }
+
+ // Create OpenAL context
+ alContext = qalcCreateContext( alDevice, NULL );
+ if( !alContext )
+ {
+ QAL_Shutdown( );
+ qalcCloseDevice( alDevice );
+ Com_Printf( "Failed to create OpenAL context.\n" );
+ return false;
+ }
+ qalcMakeContextCurrent( alContext );
+
+ // Initialize sources, buffers, music
+ S_AL_BufferInit( );
+ S_AL_SrcInit( );
+
+ // Set up OpenAL parameters (doppler, etc)
+ qalDistanceModel(AL_INVERSE_DISTANCE_CLAMPED);
+ qalDopplerFactor( s_alDopplerFactor->value );
+ qalSpeedOfSound( s_alDopplerSpeed->value );
+
+#ifdef USE_VOIP
+ // !!! FIXME: some of these alcCaptureOpenDevice() values should be cvars.
+ // !!! FIXME: add support for capture device enumeration.
+ // !!! FIXME: add some better error reporting.
+ s_alCapture = Cvar_Get( "s_alCapture", "1", CVAR_ARCHIVE | CVAR_LATCH );
+ if (!s_alCapture->integer)
+ {
+ Com_Printf("OpenAL capture support disabled by user ('+set s_alCapture 1' to enable)\n");
+ }
+#if USE_MUMBLE
+ else if (cl_useMumble->integer)
+ {
+ Com_Printf("OpenAL capture support disabled for Mumble support\n");
+ }
+#endif
+ else
+ {
+#ifdef __APPLE__
+ // !!! FIXME: Apple has a 1.1-compliant OpenAL, which includes
+ // !!! FIXME: capture support, but they don't list it in the
+ // !!! FIXME: extension string. We need to check the version string,
+ // !!! FIXME: then the extension string, but that's too much trouble,
+ // !!! FIXME: so we'll just check the function pointer for now.
+ if (qalcCaptureOpenDevice == NULL)
+#else
+ if (!qalcIsExtensionPresent(NULL, "ALC_EXT_capture"))
+#endif
+ {
+ Com_Printf("No ALC_EXT_capture support, can't record audio.\n");
+ }
+ else
+ {
+ char inputdevicenames[16384] = "";
+ const char *inputdevicelist;
+ const char *defaultinputdevice;
+ int curlen;
+
+ capture_ext = true;
+
+ // get all available input devices + the default input device name.
+ inputdevicelist = qalcGetString(NULL, ALC_CAPTURE_DEVICE_SPECIFIER);
+ defaultinputdevice = qalcGetString(NULL, ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER);
+
+ // dump a list of available devices to a cvar for the user to see.
+ if (inputdevicelist)
+ {
+ while((curlen = strlen(inputdevicelist)))
+ {
+ Q_strcat(inputdevicenames, sizeof(inputdevicenames), inputdevicelist);
+ Q_strcat(inputdevicenames, sizeof(inputdevicenames), "\n");
+ inputdevicelist += curlen + 1;
+ }
+ }
+
+ s_alAvailableInputDevices = Cvar_Get("s_alAvailableInputDevices", inputdevicenames, CVAR_ROM | CVAR_NORESTART);
+
+ Com_Printf("OpenAL default capture device is '%s'\n", defaultinputdevice ? defaultinputdevice : "none");
+ alCaptureDevice = qalcCaptureOpenDevice(inputdevice, 48000, AL_FORMAT_MONO16, VOIP_MAX_PACKET_SAMPLES*4);
+ if( !alCaptureDevice && inputdevice )
+ {
+ Com_Printf( "Failed to open OpenAL Input device '%s', trying default.\n", inputdevice );
+ alCaptureDevice = qalcCaptureOpenDevice(NULL, 48000, AL_FORMAT_MONO16, VOIP_MAX_PACKET_SAMPLES*4);
+ }
+ Com_Printf( "OpenAL capture device %s.\n",
+ (alCaptureDevice == NULL) ? "failed to open" : "opened");
+ }
+ }
+#endif
+
+ si->Shutdown = S_AL_Shutdown;
+ si->StartSound = S_AL_StartSound;
+ si->StartLocalSound = S_AL_StartLocalSound;
+ si->StartBackgroundTrack = S_AL_StartBackgroundTrack;
+ si->StopBackgroundTrack = S_AL_StopBackgroundTrack;
+ si->RawSamples = S_AL_RawSamples;
+ si->StopAllSounds = S_AL_StopAllSounds;
+ si->ClearLoopingSounds = S_AL_ClearLoopingSounds;
+ si->AddLoopingSound = S_AL_AddLoopingSound;
+ si->AddRealLoopingSound = S_AL_AddRealLoopingSound;
+ si->StopLoopingSound = S_AL_StopLoopingSound;
+ si->Respatialize = S_AL_Respatialize;
+ si->UpdateEntityPosition = S_AL_UpdateEntityPosition;
+ si->Update = S_AL_Update;
+ si->DisableSounds = S_AL_DisableSounds;
+ si->BeginRegistration = S_AL_BeginRegistration;
+ si->RegisterSound = S_AL_RegisterSound;
+ si->SoundDuration = S_AL_SoundDuration;
+ si->ClearSoundBuffer = S_AL_ClearSoundBuffer;
+ si->SoundInfo = S_AL_SoundInfo;
+ si->SoundList = S_AL_SoundList;
+
+#ifdef USE_VOIP
+ si->StartCapture = S_AL_StartCapture;
+ si->AvailableCaptureSamples = S_AL_AvailableCaptureSamples;
+ si->Capture = S_AL_Capture;
+ si->StopCapture = S_AL_StopCapture;
+ si->MasterGain = S_AL_MasterGain;
+#endif
+
+ return true;
+#else
+ return false;
+#endif
+}
diff --git a/src/client/snd_public.h b/src/client/snd_public.h
new file mode 100644
index 0000000..b8e97b9
--- /dev/null
+++ b/src/client/snd_public.h
@@ -0,0 +1,88 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
+
+This file is part of Tremulous.
+
+Tremulous 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.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+
+#ifndef _SND_PUBLIC_H_
+#define _SND_PUBLIC_H_
+
+void S_Init( void );
+void S_Shutdown( void );
+
+// if origin is NULL, the sound will be dynamically sourced from the entity
+void S_StartSound( vec3_t origin, int entnum, int entchannel, sfxHandle_t sfx );
+void S_StartLocalSound( sfxHandle_t sfx, int channelNum );
+
+void S_StartBackgroundTrack( const char *intro, const char *loop );
+void S_StopBackgroundTrack( void );
+
+// cinematics and voice-over-network will send raw samples
+// 1.0 volume will be direct output of source samples
+void S_RawSamples(int stream, int samples, int rate, int width, int channels,
+ const byte *data, float volume, int entityNum);
+
+// stop all sounds and the background track
+void S_StopAllSounds( void );
+
+// all continuous looping sounds must be added before calling S_Update
+void S_ClearLoopingSounds( bool killall );
+void S_AddLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx );
+void S_AddRealLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx );
+void S_StopLoopingSound(int entityNum );
+
+// recompute the relative volumes for all running sounds
+// relative to the given entityNum / orientation
+void S_Respatialize( int entityNum, const vec3_t origin, vec3_t axis[3], int inwater );
+
+// let the sound system know where an entity currently is
+void S_UpdateEntityPosition( int entityNum, const vec3_t origin );
+
+void S_Update( void );
+
+void S_DisableSounds( void );
+
+void S_BeginRegistration( void );
+
+// RegisterSound will allways return a valid sample, even if it
+// has to create a placeholder. This prevents continuous filesystem
+// checks for missing files
+sfxHandle_t S_RegisterSound( const char *sample, bool compressed );
+
+int S_SoundDuration( sfxHandle_t handle );
+
+void S_DisplayFreeMemory(void);
+
+void S_ClearSoundBuffer( void );
+
+void SNDDMA_Activate( void );
+
+void S_UpdateBackgroundTrack( void );
+
+
+#ifdef USE_VOIP
+void S_StartCapture( void );
+int S_AvailableCaptureSamples( void );
+void S_Capture( int samples, byte *data );
+void S_StopCapture( void );
+void S_MasterGain( float gain );
+#endif
+#endif
diff --git a/src/client/snd_wavelet.cpp b/src/client/snd_wavelet.cpp
new file mode 100644
index 0000000..e06e3ef
--- /dev/null
+++ b/src/client/snd_wavelet.cpp
@@ -0,0 +1,252 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
+
+This file is part of Tremulous.
+
+Tremulous 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.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+
+#include "snd_local.h"
+
+#define C0 0.4829629131445341
+#define C1 0.8365163037378079
+#define C2 0.2241438680420134
+#define C3 -0.1294095225512604
+
+void daub4(float b[], unsigned long n, int isign)
+{
+ float wksp[4097] = { 0.0f };
+#define a(x) b[(x)-1] // numerical recipies so a[1] = b[0]
+
+
+ unsigned long nh,nh1,i,j;
+
+ if (n < 4) return;
+
+ nh1=(nh=n >> 1)+1;
+ if (isign >= 0) {
+ for (i=1,j=1;j<=n-3;j+=2,i++) {
+ wksp[i] = C0*a(j)+C1*a(j+1)+C2*a(j+2)+C3*a(j+3);
+ wksp[i+nh] = C3*a(j)-C2*a(j+1)+C1*a(j+2)-C0*a(j+3);
+ }
+ wksp[i ] = C0*a(n-1)+C1*a(n)+C2*a(1)+C3*a(2);
+ wksp[i+nh] = C3*a(n-1)-C2*a(n)+C1*a(1)-C0*a(2);
+ } else {
+ wksp[1] = C2*a(nh)+C1*a(n)+C0*a(1)+C3*a(nh1);
+ wksp[2] = C3*a(nh)-C0*a(n)+C1*a(1)-C2*a(nh1);
+ for (i=1,j=3;i<nh;i++) {
+ wksp[j++] = C2*a(i)+C1*a(i+nh)+C0*a(i+1)+C3*a(i+nh1);
+ wksp[j++] = C3*a(i)-C0*a(i+nh)+C1*a(i+1)-C2*a(i+nh1);
+ }
+ }
+ for (i=1;i<=n;i++) {
+ a(i) = wksp[i];
+ }
+}
+
+void wt1(float a[], unsigned long n, int isign)
+{
+ unsigned long nn;
+ int inverseStartLength = n/4;
+ if (n < inverseStartLength) return;
+ if (isign >= 0) {
+ for (nn=n;nn>=inverseStartLength;nn>>=1) daub4(a,nn,isign);
+ } else {
+ for (nn=inverseStartLength;nn<=n;nn<<=1) daub4(a,nn,isign);
+ }
+}
+
+/* The number of bits required by each value */
+static unsigned char numBits[] = {
+ 0,1,2,2,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,
+ 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
+ 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
+ 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
+ 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
+ 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
+ 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
+ 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
+};
+
+byte MuLawEncode(short s) {
+ unsigned long adjusted;
+ byte sign, exponent, mantissa;
+
+ sign = (s<0)?0:0x80;
+
+ if (s<0) s=-s;
+ adjusted = (long)s << (16-sizeof(short)*8);
+ adjusted += 128L + 4L;
+ if (adjusted > 32767) adjusted = 32767;
+ exponent = numBits[(adjusted>>7)&0xff] - 1;
+ mantissa = (adjusted>>(exponent+3))&0xf;
+ return ~(sign | (exponent<<4) | mantissa);
+}
+
+short MuLawDecode(byte uLaw) {
+ signed long adjusted;
+ byte exponent, mantissa;
+
+ uLaw = ~uLaw;
+ exponent = (uLaw>>4) & 0x7;
+ mantissa = (uLaw&0xf) + 16;
+ adjusted = (mantissa << (exponent +3)) - 128 - 4;
+
+ return (uLaw & 0x80)? adjusted : -adjusted;
+}
+
+short mulawToShort[256];
+static bool madeTable = false;
+
+static int NXStreamCount;
+
+void NXPutc(NXStream *stream, char out) {
+ stream[NXStreamCount++] = out;
+}
+
+
+void encodeWavelet( sfx_t *sfx, short *packets) {
+ float wksp[4097] = {0}, temp;
+ int i, samples, size;
+ sndBuffer *newchunk, *chunk;
+ byte *out;
+
+ if (!madeTable) {
+ for (i=0;i<256;i++) {
+ mulawToShort[i] = (float)MuLawDecode((byte)i);
+ }
+ madeTable = true;
+ }
+ chunk = NULL;
+
+ samples = sfx->soundLength;
+ while(samples>0) {
+ size = samples;
+ if (size>(SND_CHUNK_SIZE*2)) {
+ size = (SND_CHUNK_SIZE*2);
+ }
+
+ if (size<4) {
+ size = 4;
+ }
+
+ newchunk = SND_malloc();
+ if (sfx->soundData == NULL) {
+ sfx->soundData = newchunk;
+ } else if (chunk != NULL) {
+ chunk->next = newchunk;
+ }
+ chunk = newchunk;
+ for(i=0; i<size; i++) {
+ wksp[i] = *packets;
+ packets++;
+ }
+ wt1(wksp, size, 1);
+ out = (byte *)chunk->sndChunk;
+
+ for(i=0;i<size;i++) {
+ temp = wksp[i];
+ if (temp > 32767) temp = 32767; else if (temp<-32768) temp = -32768;
+ out[i] = MuLawEncode((short)temp);
+ }
+
+ chunk->size = size;
+ samples -= size;
+ }
+}
+
+void decodeWavelet(sndBuffer *chunk, short *to) {
+ float wksp[4097] = {0};
+ int i;
+ byte *out;
+
+ int size = chunk->size;
+
+ out = (byte *)chunk->sndChunk;
+ for(i=0;i<size;i++) {
+ wksp[i] = mulawToShort[out[i]];
+ }
+
+ wt1(wksp, size, -1);
+
+ if (!to) return;
+
+ for(i=0; i<size; i++) {
+ to[i] = wksp[i];
+ }
+}
+
+
+void encodeMuLaw( sfx_t *sfx, short *packets) {
+ int i, samples, size, grade, poop;
+ sndBuffer *newchunk, *chunk;
+ byte *out;
+
+ if (!madeTable) {
+ for (i=0;i<256;i++) {
+ mulawToShort[i] = (float)MuLawDecode((byte)i);
+ }
+ madeTable = true;
+ }
+
+ chunk = NULL;
+ samples = sfx->soundLength;
+ grade = 0;
+
+ while(samples>0) {
+ size = samples;
+ if (size>(SND_CHUNK_SIZE*2)) {
+ size = (SND_CHUNK_SIZE*2);
+ }
+
+ newchunk = SND_malloc();
+ if (sfx->soundData == NULL) {
+ sfx->soundData = newchunk;
+ } else if (chunk != NULL) {
+ chunk->next = newchunk;
+ }
+ chunk = newchunk;
+ out = (byte *)chunk->sndChunk;
+ for(i=0; i<size; i++) {
+ poop = packets[0]+grade;
+ if (poop>32767) {
+ poop = 32767;
+ } else if (poop<-32768) {
+ poop = -32768;
+ }
+ out[i] = MuLawEncode((short)poop);
+ grade = poop - mulawToShort[out[i]];
+ packets++;
+ }
+ chunk->size = size;
+ samples -= size;
+ }
+}
+
+void decodeMuLaw(sndBuffer *chunk, short *to) {
+ int i;
+ byte *out;
+
+ int size = chunk->size;
+
+ out = (byte *)chunk->sndChunk;
+ for(i=0;i<size;i++) {
+ to[i] = mulawToShort[out[i]];
+ }
+}