aboutsummaryrefslogtreecommitdiff
path: root/code/client
diff options
context:
space:
mode:
Diffstat (limited to 'code/client')
-rw-r--r--code/client/cl_avi.c671
-rw-r--r--code/client/cl_cgame.c1105
-rw-r--r--code/client/cl_cin.c1668
-rw-r--r--code/client/cl_console.c795
-rw-r--r--code/client/cl_curl.c338
-rw-r--r--code/client/cl_curl.h102
-rw-r--r--code/client/cl_input.c1045
-rw-r--r--code/client/cl_keys.c1515
-rw-r--r--code/client/cl_main.c4229
-rw-r--r--code/client/cl_net_chan.c167
-rw-r--r--code/client/cl_parse.c913
-rw-r--r--code/client/cl_scrn.c597
-rw-r--r--code/client/cl_ui.c1154
-rw-r--r--code/client/client.h627
-rw-r--r--code/client/keycodes.h279
-rw-r--r--code/client/keys.h55
-rw-r--r--code/client/libmumblelink.c185
-rw-r--r--code/client/libmumblelink.h35
-rw-r--r--code/client/qal.c351
-rw-r--r--code/client/qal.h245
-rw-r--r--code/client/snd_adpcm.c330
-rw-r--r--code/client/snd_codec.c237
-rw-r--r--code/client/snd_codec.h98
-rw-r--r--code/client/snd_codec_ogg.c477
-rw-r--r--code/client/snd_codec_wav.c294
-rw-r--r--code/client/snd_dma.c1541
-rw-r--r--code/client/snd_local.h251
-rw-r--r--code/client/snd_main.c550
-rw-r--r--code/client/snd_mem.c265
-rw-r--r--code/client/snd_mix.c741
-rw-r--r--code/client/snd_openal.c2504
-rw-r--r--code/client/snd_public.h82
-rw-r--r--code/client/snd_wavelet.c253
33 files changed, 23699 insertions, 0 deletions
diff --git a/code/client/cl_avi.c b/code/client/cl_avi.c
new file mode 100644
index 0000000..b03adea
--- /dev/null
+++ b/code/client/cl_avi.c
@@ -0,0 +1,671 @@
+/*
+===========================================================================
+Copyright (C) 2005-2006 Tim Angus
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code 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,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+#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
+{
+ qboolean 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;
+ qboolean motionJpeg;
+
+ qboolean 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\n" );
+}
+
+/*
+===============
+WRITE_STRING
+===============
+*/
+static ID_INLINE void WRITE_STRING( const char *s )
+{
+ Com_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;
+}
+
+/*
+===============
+WRITE_1BYTES
+===============
+*/
+static ID_INLINE void WRITE_1BYTES( int x )
+{
+ buffer[ bufIndex ] = x;
+ bufIndex += 1;
+}
+
+/*
+===============
+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\n" );
+ }
+
+ 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\n" );
+ }
+
+ 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
+===============
+*/
+qboolean CL_OpenAVIForWriting( const char *fileName )
+{
+ if( afd.fileOpen )
+ return qfalse;
+
+ Com_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 qfalse;
+ }
+
+ if( ( afd.f = FS_FOpenFileWrite( fileName ) ) <= 0 )
+ return qfalse;
+
+ if( ( afd.idxF = FS_FOpenFileWrite(
+ va( "%s" INDEX_FILE_EXTENSION, fileName ) ) ) <= 0 )
+ {
+ FS_FCloseFile( afd.f );
+ return qfalse;
+ }
+
+ 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 = qtrue;
+ else
+ afd.motionJpeg = qfalse;
+
+ afd.cBuffer = Z_Malloc( afd.width * afd.height * 4 );
+ afd.eBuffer = Z_Malloc( afd.width * afd.height * 4 );
+
+ 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 = qfalse;
+ }
+ 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 = qfalse;
+ }
+ else
+ afd.audio = qtrue;
+ }
+ else
+ {
+ afd.audio = qfalse;
+ 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 = qtrue;
+
+ return qtrue;
+}
+
+/*
+===============
+CL_CheckFileSize
+===============
+*/
+static qboolean 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 qtrue;
+ }
+
+ return qfalse;
+}
+
+/*
+===============
+CL_WriteAVIVideoFrame
+===============
+*/
+void CL_WriteAVIVideoFrame( const byte *imageBuffer, int size )
+{
+ int chunkOffset = afd.fileSize - afd.moviOffset - 8;
+ int chunkSize = 8 + size;
+ int paddingSize = PAD( size, 2 ) - size;
+ 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;
+ }
+
+ Com_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 = PAD( bytesInBuffer, 2 ) - bytesInBuffer;
+ 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
+===============
+*/
+qboolean 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 qfalse;
+
+ afd.fileOpen = qfalse;
+
+ 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, qtrue ) ) <= 0 )
+ {
+ FS_FCloseFile( afd.f );
+ return qfalse;
+ }
+
+ 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 qtrue;
+}
+
+/*
+===============
+CL_VideoRecording
+===============
+*/
+qboolean CL_VideoRecording( void )
+{
+ return afd.fileOpen;
+}
diff --git a/code/client/cl_cgame.c b/code/client/cl_cgame.c
new file mode 100644
index 0000000..3c351a1
--- /dev/null
+++ b/code/client/cl_cgame.c
@@ -0,0 +1,1105 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code 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,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+// cl_cgame.c -- client system interaction with client game
+
+#include "client.h"
+
+#include "../botlib/botlib.h"
+
+#include "libmumblelink.h"
+
+extern botlib_export_t *botlib_export;
+
+extern qboolean loadCamera(const char *name);
+extern void startCamera(int time);
+extern qboolean getCameraInfo(int time, vec3_t *origin, vec3_t *angles);
+
+/*
+====================
+CL_GetGameState
+====================
+*/
+void CL_GetGameState( gameState_t *gs ) {
+ *gs = cl.gameState;
+}
+
+/*
+====================
+CL_GetGlconfig
+====================
+*/
+void CL_GetGlconfig( glconfig_t *glconfig ) {
+ *glconfig = cls.glconfig;
+}
+
+
+/*
+====================
+CL_GetUserCmd
+====================
+*/
+qboolean 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 qfalse;
+ }
+
+ *ucmd = cl.cmds[ cmdNumber & CMD_MASK ];
+
+ return qtrue;
+}
+
+int CL_GetCurrentCmdNumber( void ) {
+ return cl.cmdNumber;
+}
+
+
+/*
+====================
+CL_GetParseEntityState
+====================
+*/
+qboolean CL_GetParseEntityState( int parseEntityNumber, entityState_t *state ) {
+ // can't return anything that hasn't been parsed yet
+ if ( parseEntityNumber >= cl.parseEntitiesNum ) {
+ Com_Error( ERR_DROP, "CL_GetParseEntityState: %i >= %i",
+ parseEntityNumber, cl.parseEntitiesNum );
+ }
+
+ // can't return anything that has been overwritten in the circular buffer
+ if ( parseEntityNumber <= cl.parseEntitiesNum - MAX_PARSE_ENTITIES ) {
+ return qfalse;
+ }
+
+ *state = cl.parseEntities[ parseEntityNumber & ( MAX_PARSE_ENTITIES - 1 ) ];
+ return qtrue;
+}
+
+/*
+====================
+CL_GetCurrentSnapshotNumber
+====================
+*/
+void CL_GetCurrentSnapshotNumber( int *snapshotNumber, int *serverTime ) {
+ *snapshotNumber = cl.snap.messageNum;
+ *serverTime = cl.snap.serverTime;
+}
+
+/*
+====================
+CL_GetSnapshot
+====================
+*/
+qboolean CL_GetSnapshot( int snapshotNumber, snapshot_t *snapshot ) {
+ clSnapshot_t *clSnap;
+ int i, count;
+
+ 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 qfalse;
+ }
+
+ // if the frame is not valid, we can't return it
+ clSnap = &cl.snapshots[snapshotNumber & PACKET_MASK];
+ if ( !clSnap->valid ) {
+ return qfalse;
+ }
+
+ // 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 qfalse;
+ }
+
+ // write the snapshot
+ snapshot->snapFlags = clSnap->snapFlags;
+ snapshot->serverCommandSequence = clSnap->serverCommandNum;
+ snapshot->ping = clSnap->ping;
+ snapshot->serverTime = clSnap->serverTime;
+ Com_Memcpy( snapshot->areamask, clSnap->areamask, sizeof( snapshot->areamask ) );
+ snapshot->ps = clSnap->ps;
+ 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;
+ }
+ snapshot->numEntities = count;
+ for ( i = 0 ; i < count ; i++ ) {
+ snapshot->entities[i] =
+ cl.parseEntities[ ( clSnap->parseEntitiesNum + i ) & (MAX_PARSE_ENTITIES-1) ];
+ }
+
+ // FIXME: configstring changes and server commands!!!
+
+ return qtrue;
+}
+
+/*
+=====================
+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_CgameError
+=====================
+*/
+void CL_CgameError( const char *string ) {
+ Com_Error( ERR_DROP, "%s", string );
+}
+
+
+/*
+=====================
+CL_ConfigstringModified
+=====================
+*/
+void CL_ConfigstringModified( void ) {
+ char *old, *s;
+ int i, index;
+ char *dup;
+ gameState_t oldGs;
+ int len;
+
+ index = atoi( Cmd_Argv(1) );
+ if ( index < 0 || index >= MAX_CONFIGSTRINGS ) {
+ Com_Error( ERR_DROP, "configstring > MAX_CONFIGSTRINGS" );
+ }
+ // get everything after "cs <num>"
+ s = Cmd_ArgsFrom(2);
+
+ old = cl.gameState.stringData + cl.gameState.stringOffsets[ index ];
+ if ( !strcmp( old, s ) ) {
+ return; // unchanged
+ }
+
+ // build the new gameState_t
+ oldGs = cl.gameState;
+
+ Com_Memset( &cl.gameState, 0, sizeof( cl.gameState ) );
+
+ // leave the first 0 for uninitialized strings
+ cl.gameState.dataCount = 1;
+
+ for ( i = 0 ; i < MAX_CONFIGSTRINGS ; i++ ) {
+ if ( i == index ) {
+ dup = s;
+ } else {
+ dup = oldGs.stringData + oldGs.stringOffsets[ i ];
+ }
+ if ( !dup[0] ) {
+ continue; // leave with the default empty string
+ }
+
+ 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;
+ Com_Memcpy( cl.gameState.stringData + cl.gameState.dataCount, dup, len + 1 );
+ cl.gameState.dataCount += len + 1;
+ }
+
+ if ( index == CS_SYSTEMINFO ) {
+ // parse serverId and other cvars
+ CL_SystemInfoChanged();
+ }
+
+}
+
+
+/*
+===================
+CL_GetServerCommand
+
+Set up argc/argv for the given command
+===================
+*/
+qboolean CL_GetServerCommand( int serverCommandNumber ) {
+ char *s;
+ 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 qfalse;
+ Com_Error( ERR_DROP, "CL_GetServerCommand: a reliable command was cycled out" );
+ return qfalse;
+ }
+
+ if ( serverCommandNumber > clc.serverCommandSequence ) {
+ Com_Error( ERR_DROP, "CL_GetServerCommand: requested a command not received" );
+ return qfalse;
+ }
+
+ 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" ) ) {
+ // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=552
+ // 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\n" );
+ }
+
+ if ( !strcmp( cmd, "bcs0" ) ) {
+ Com_sprintf( bigConfigString, BIG_INFO_STRING, "cs %s \"%s", Cmd_Argv(1), Cmd_Argv(2) );
+ return qfalse;
+ }
+
+ 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 qfalse;
+ }
+
+ 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 qtrue;
+ }
+
+ 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 );
+ Com_Memset( cl.cmds, 0, sizeof( cl.cmds ) );
+ return qtrue;
+ }
+
+ // the clientLevelShot command is used during development
+ // to generate 128*128 screenshots from the intermission
+ // point of levels for the menu system to use
+ // we pass it along to the cgame to make apropriate adjustments,
+ // but we also clear the console and notify lines here
+ if ( !strcmp( cmd, "clientLevelShot" ) ) {
+ // don't do it if we aren't running the server locally,
+ // otherwise malicious remote servers could overwrite
+ // the existing thumbnails
+ if ( !com_sv_running->integer ) {
+ return qfalse;
+ }
+ // close the console
+ Con_Close();
+ // take a special screenshot next frame
+ Cbuf_AddText( "wait ; wait ; wait ; wait ; screenshot levelshot\n" );
+ return qtrue;
+ }
+
+ // we may want to put a "connect to other server" command here
+
+ // cgame can now act on the command
+ return qtrue;
+}
+
+
+/*
+====================
+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, qtrue, &checksum );
+}
+
+/*
+====================
+CL_ShutdonwCGame
+
+====================
+*/
+void CL_ShutdownCGame( void ) {
+ Key_SetCatcher( Key_GetCatcher( ) & ~KEYCATCH_CGAME );
+ cls.cgameStarted = qfalse;
+ if ( !cgvm ) {
+ return;
+ }
+ VM_Call( cgvm, CG_SHUTDOWN );
+ VM_Free( cgvm );
+ cgvm = NULL;
+}
+
+static int FloatAsInt( float f ) {
+ floatint_t fi;
+ fi.f = f;
+ return fi.i;
+}
+
+/*
+====================
+CL_CgameSystemCalls
+
+The cgame module is making a system call
+====================
+*/
+intptr_t CL_CgameSystemCalls( intptr_t *args ) {
+ switch( args[0] ) {
+ case CG_PRINT:
+ Com_Printf( "%s", (const char*)VMA(1) );
+ return 0;
+ case CG_ERROR:
+ Com_Error( ERR_DROP, "%s", (const char*)VMA(1) );
+ return 0;
+ case CG_MILLISECONDS:
+ return Sys_Milliseconds();
+ case CG_CVAR_REGISTER:
+ Cvar_Register( VMA(1), VMA(2), VMA(3), args[4] );
+ return 0;
+ case CG_CVAR_UPDATE:
+ Cvar_Update( VMA(1) );
+ return 0;
+ case CG_CVAR_SET:
+ Cvar_Set( VMA(1), VMA(2) );
+ return 0;
+ case CG_CVAR_VARIABLESTRINGBUFFER:
+ Cvar_VariableStringBuffer( VMA(1), VMA(2), args[3] );
+ return 0;
+ case CG_ARGC:
+ return Cmd_Argc();
+ case CG_ARGV:
+ Cmd_ArgvBuffer( args[1], VMA(2), args[3] );
+ return 0;
+ case CG_ARGS:
+ Cmd_ArgsBuffer( VMA(1), args[2] );
+ return 0;
+ case CG_FS_FOPENFILE:
+ return FS_FOpenFileByMode( VMA(1), VMA(2), args[3] );
+ case CG_FS_READ:
+ FS_Read2( 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( args[1], args[2], args[3] );
+ case CG_SENDCONSOLECOMMAND:
+ Cbuf_AddText( VMA(1) );
+ return 0;
+ case CG_ADDCOMMAND:
+ CL_AddCgameCommand( VMA(1) );
+ return 0;
+ case CG_REMOVECOMMAND:
+ Cmd_RemoveCommand( VMA(1) );
+ return 0;
+ case CG_SENDCLIENTCOMMAND:
+ CL_AddReliableCommand(VMA(1), qfalse);
+ 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( 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( VMA(1), VMA(2), /*int capsule*/ qfalse );
+ case CG_CM_TEMPCAPSULEMODEL:
+ return CM_TempBoxModel( VMA(1), VMA(2), /*int capsule*/ qtrue );
+ case CG_CM_POINTCONTENTS:
+ return CM_PointContents( VMA(1), args[2] );
+ case CG_CM_TRANSFORMEDPOINTCONTENTS:
+ return CM_TransformedPointContents( VMA(1), args[2], VMA(3), VMA(4) );
+ case CG_CM_BOXTRACE:
+ CM_BoxTrace( VMA(1), VMA(2), VMA(3), VMA(4), VMA(5), args[6], args[7], /*int capsule*/ qfalse );
+ return 0;
+ case CG_CM_CAPSULETRACE:
+ CM_BoxTrace( VMA(1), VMA(2), VMA(3), VMA(4), VMA(5), args[6], args[7], /*int capsule*/ qtrue );
+ return 0;
+ case CG_CM_TRANSFORMEDBOXTRACE:
+ CM_TransformedBoxTrace( VMA(1), VMA(2), VMA(3), VMA(4), VMA(5), args[6], args[7], VMA(8), VMA(9), /*int capsule*/ qfalse );
+ return 0;
+ case CG_CM_TRANSFORMEDCAPSULETRACE:
+ CM_TransformedBoxTrace( VMA(1), VMA(2), VMA(3), VMA(4), VMA(5), args[6], args[7], VMA(8), VMA(9), /*int capsule*/ qtrue );
+ return 0;
+ case CG_CM_MARKFRAGMENTS:
+ return re.MarkFragments( args[1], VMA(2), VMA(3), args[4], VMA(5), args[6], VMA(7) );
+ case CG_S_STARTSOUND:
+ S_StartSound( VMA(1), args[2], args[3], args[4] );
+ return 0;
+ case CG_S_STARTLOCALSOUND:
+ S_StartLocalSound( args[1], args[2] );
+ return 0;
+ case CG_S_CLEARLOOPINGSOUNDS:
+ S_ClearLoopingSounds(args[1]);
+ return 0;
+ case CG_S_ADDLOOPINGSOUND:
+ S_AddLoopingSound( args[1], VMA(2), VMA(3), args[4] );
+ return 0;
+ case CG_S_ADDREALLOOPINGSOUND:
+ S_AddRealLoopingSound( args[1], VMA(2), VMA(3), args[4] );
+ return 0;
+ case CG_S_STOPLOOPINGSOUND:
+ S_StopLoopingSound( args[1] );
+ return 0;
+ case CG_S_UPDATEENTITYPOSITION:
+ S_UpdateEntityPosition( args[1], VMA(2) );
+ return 0;
+ case CG_S_RESPATIALIZE:
+ S_Respatialize( args[1], VMA(2), VMA(3), args[4] );
+ return 0;
+ case CG_S_REGISTERSOUND:
+ return S_RegisterSound( VMA(1), args[2] );
+ case CG_S_STARTBACKGROUNDTRACK:
+ S_StartBackgroundTrack( VMA(1), VMA(2) );
+ return 0;
+ case CG_R_LOADWORLDMAP:
+ re.LoadWorld( VMA(1) );
+ return 0;
+ case CG_R_REGISTERMODEL:
+ return re.RegisterModel( VMA(1) );
+ case CG_R_REGISTERSKIN:
+ return re.RegisterSkin( VMA(1) );
+ case CG_R_REGISTERSHADER:
+ return re.RegisterShader( VMA(1) );
+ case CG_R_REGISTERSHADERNOMIP:
+ return re.RegisterShaderNoMip( VMA(1) );
+ case CG_R_REGISTERFONT:
+ re.RegisterFont( VMA(1), args[2], VMA(3));
+ case CG_R_CLEARSCENE:
+ re.ClearScene();
+ return 0;
+ case CG_R_ADDREFENTITYTOSCENE:
+ re.AddRefEntityToScene( VMA(1) );
+ return 0;
+ case CG_R_ADDPOLYTOSCENE:
+ re.AddPolyToScene( args[1], args[2], VMA(3), 1 );
+ return 0;
+ case CG_R_ADDPOLYSTOSCENE:
+ re.AddPolyToScene( args[1], args[2], VMA(3), args[4] );
+ return 0;
+ case CG_R_LIGHTFORPOINT:
+ return re.LightForPoint( VMA(1), VMA(2), VMA(3), VMA(4) );
+ case CG_R_ADDLIGHTTOSCENE:
+ re.AddLightToScene( VMA(1), VMF(2), VMF(3), VMF(4), VMF(5) );
+ return 0;
+ case CG_R_ADDADDITIVELIGHTTOSCENE:
+ re.AddAdditiveLightToScene( VMA(1), VMF(2), VMF(3), VMF(4), VMF(5) );
+ return 0;
+ case CG_R_RENDERSCENE:
+ re.RenderScene( VMA(1) );
+ return 0;
+ case CG_R_SETCOLOR:
+ re.SetColor( 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), args[9] );
+ return 0;
+ case CG_R_MODELBOUNDS:
+ re.ModelBounds( args[1], VMA(2), VMA(3) );
+ return 0;
+ case CG_R_LERPTAG:
+ return re.LerpTag( VMA(1), args[2], args[3], args[4], VMF(5), VMA(6) );
+ case CG_GETGLCONFIG:
+ CL_GetGlconfig( VMA(1) );
+ return 0;
+ case CG_GETGAMESTATE:
+ CL_GetGameState( VMA(1) );
+ return 0;
+ case CG_GETCURRENTSNAPSHOTNUMBER:
+ CL_GetCurrentSnapshotNumber( VMA(1), VMA(2) );
+ return 0;
+ case CG_GETSNAPSHOT:
+ return CL_GetSnapshot( args[1], VMA(2) );
+ case CG_GETSERVERCOMMAND:
+ return CL_GetServerCommand( args[1] );
+ case CG_GETCURRENTCMDNUMBER:
+ return CL_GetCurrentCmdNumber();
+ case CG_GETUSERCMD:
+ return CL_GetUserCmd( args[1], 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 close the console
+ Key_SetCatcher( args[1] | ( Key_GetCatcher( ) & KEYCATCH_CONSOLE ) );
+ return 0;
+ case CG_KEY_GETKEY:
+ return Key_GetKey( VMA(1) );
+
+
+
+ case CG_MEMSET:
+ Com_Memset( VMA(1), args[2], args[3] );
+ return 0;
+ case CG_MEMCPY:
+ Com_Memcpy( VMA(1), VMA(2), args[3] );
+ return 0;
+ case CG_STRNCPY:
+ strncpy( VMA(1), 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_PC_ADD_GLOBAL_DEFINE:
+ return botlib_export->PC_AddGlobalDefine( VMA(1) );
+ case CG_PC_LOAD_SOURCE:
+ return botlib_export->PC_LoadSourceHandle( VMA(1) );
+ case CG_PC_FREE_SOURCE:
+ return botlib_export->PC_FreeSourceHandle( args[1] );
+ case CG_PC_READ_TOKEN:
+ return botlib_export->PC_ReadTokenHandle( args[1], VMA(2) );
+ case CG_PC_SOURCE_FILE_AND_LINE:
+ return botlib_export->PC_SourceFileAndLine( args[1], VMA(2), VMA(3) );
+
+ case CG_S_STOPBACKGROUNDTRACK:
+ S_StopBackgroundTrack();
+ return 0;
+
+ case CG_REAL_TIME:
+ return Com_RealTime( VMA(1) );
+ case CG_SNAPVECTOR:
+ Sys_SnapVector( VMA(1) );
+ return 0;
+
+ case CG_CIN_PLAYCINEMATIC:
+ return CIN_PlayCinematic(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( VMA(1), VMA(2), VMA(3) );
+ return 0;
+
+/*
+ case CG_LOADCAMERA:
+ return loadCamera(VMA(1));
+
+ case CG_STARTCAMERA:
+ startCamera(args[1]);
+ return 0;
+
+ case CG_GETCAMERAINFO:
+ return getCameraInfo(args[1], VMA(2), VMA(3));
+*/
+ case CG_GET_ENTITY_TOKEN:
+ return re.GetEntityToken( VMA(1), args[2] );
+ case CG_R_INPVS:
+ return re.inPVS( VMA(1), 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;
+ vmInterpret_t interpret;
+
+ t1 = Sys_Milliseconds();
+
+ // put away the console
+ Con_Close();
+
+ // 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
+ if ( cl_connectedToPureServer != 0 ) {
+ // if sv_pure is set we only allow qvms to be loaded
+ interpret = VMI_COMPILED;
+ }
+ else {
+ interpret = Cvar_VariableValue( "vm_cgame" );
+ }
+ cgvm = VM_Create( "cgame", CL_CgameSystemCalls, interpret );
+ if ( !cgvm ) {
+ Com_Error( ERR_DROP, "VM_Create on cgame failed" );
+ }
+ cls.state = CA_LOADING;
+
+ // init for this gamestate
+ // use the lastExecutedServerCommand instead of the serverCommandSequence
+ // otherwise server commands sent just before a gamestate are dropped
+ VM_Call( cgvm, 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
+ cls.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();
+ }
+
+ // clear anything that got printed
+ Con_ClearNotify ();
+}
+
+
+/*
+====================
+CL_GameCommand
+
+See if the current console command is claimed by the cgame
+====================
+*/
+qboolean CL_GameCommand( void ) {
+ if ( !cgvm ) {
+ return qfalse;
+ }
+
+ return VM_Call( cgvm, CG_CONSOLE_COMMAND );
+}
+
+
+
+/*
+=====================
+CL_CGameRendering
+=====================
+*/
+void CL_CGameRendering( stereoFrame_t stereo ) {
+ VM_Call( cgvm, 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 resetTime;
+ int newDelta;
+ int deltaDelta;
+
+ cl.newSnapshots = qfalse;
+
+ // the delta never drifts when replaying a demo
+ if ( clc.demoplaying ) {
+ return;
+ }
+
+ // if the current time is WAY off, just correct to the current value
+ if ( com_sv_running->integer ) {
+ resetTime = 100;
+ } else {
+ resetTime = RESET_TIME;
+ }
+
+ 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 = qfalse;
+ 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;
+ }
+ cls.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.speexInitialized) {
+ int i;
+ speex_bits_init(&clc.speexEncoderBits);
+ speex_bits_reset(&clc.speexEncoderBits);
+
+ clc.speexEncoder = speex_encoder_init(&speex_nb_mode);
+
+ speex_encoder_ctl(clc.speexEncoder, SPEEX_GET_FRAME_SIZE,
+ &clc.speexFrameSize);
+ speex_encoder_ctl(clc.speexEncoder, SPEEX_GET_SAMPLING_RATE,
+ &clc.speexSampleRate);
+
+ clc.speexPreprocessor = speex_preprocess_state_init(clc.speexFrameSize,
+ clc.speexSampleRate);
+
+ i = 1;
+ speex_preprocess_ctl(clc.speexPreprocessor,
+ SPEEX_PREPROCESS_SET_DENOISE, &i);
+
+ i = 1;
+ speex_preprocess_ctl(clc.speexPreprocessor,
+ SPEEX_PREPROCESS_SET_AGC, &i);
+
+ for (i = 0; i < MAX_CLIENTS; i++) {
+ speex_bits_init(&clc.speexDecoderBits[i]);
+ speex_bits_reset(&clc.speexDecoderBits[i]);
+ clc.speexDecoder[i] = speex_decoder_init(&speex_nb_mode);
+ clc.voipIgnore[i] = qfalse;
+ clc.voipGain[i] = 1.0f;
+ }
+ clc.speexInitialized = qtrue;
+ clc.voipMuteAll = qfalse;
+ Cmd_AddCommand ("voip", CL_Voip_f);
+ Cvar_Set("cl_voipSendTarget", "all");
+ clc.voipTarget1 = clc.voipTarget2 = clc.voipTarget3 = 0x7FFFFFFF;
+ }
+#endif
+}
+
+/*
+==================
+CL_SetCGameTime
+==================
+*/
+void CL_SetCGameTime( void ) {
+ // getting a valid frame message ends the connection process
+ if ( cls.state != CA_ACTIVE ) {
+ if ( cls.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 = qtrue;
+ return;
+ }
+ CL_ReadDemoMessage();
+ }
+ if ( cl.newSnapshots ) {
+ cl.newSnapshots = qfalse;
+ CL_FirstSnapshot();
+ }
+ if ( cls.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 = qtrue;
+ }
+ }
+
+ // 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 ( cls.state != CA_ACTIVE ) {
+ return; // end of demo
+ }
+ }
+
+}
+
+
+
diff --git a/code/client/cl_cin.c b/code/client/cl_cin.c
new file mode 100644
index 0000000..1fc5520
--- /dev/null
+++ b/code/client/cl_cin.c
@@ -0,0 +1,1668 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code 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,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+/*****************************************************************************
+ * 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
+
+extern glconfig_t glConfig;
+
+
+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];
+
+
+typedef struct {
+ 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, oldYOff, oldysize, oldxsize;
+
+ int currentHandle;
+} cinematics_t;
+
+typedef struct {
+ char fileName[MAX_OSPATH];
+ int CIN_WIDTH, CIN_HEIGHT;
+ int xpos, ypos, width, height;
+ qboolean looping, holdAtEnd, dirty, alterGameState, silent, shader;
+ fileHandle_t iFile;
+ e_status status;
+ unsigned int startTime;
+ unsigned 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;
+
+ qboolean half, smootheddouble, inMemory;
+ long normalBuffer0;
+ long roq_flags;
+ long roqF0;
+ long roqF1;
+ long t[2];
+ long roqFPS;
+ int playonwalls;
+ byte* buf;
+ long drawX, drawY;
+} cin_cache;
+
+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;
+}
+
+
+extern int CL_ScaledMilliseconds(void);
+
+//-----------------------------------------------------------------------------
+// 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]);
+ }
+}
+
+
+
+//-----------------------------------------------------------------------------
+// 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));
+}
+
+
+//-----------------------------------------------------------------------------
+// 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
+//-----------------------------------------------------------------------------
+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
+//-----------------------------------------------------------------------------
+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));
+}
+
+
+//-----------------------------------------------------------------------------
+// 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;
+}
+
+/******************************************************************************
+*
+* 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].CIN_WIDTH*cinTable[currentHandle].CIN_HEIGHT) / (16);
+ numQuadCels += numQuadCels/4 + numQuadCels/16;
+ numQuadCels += 64; // for overflow
+
+ 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 = qfalse;
+ cinTable[currentHandle].smootheddouble = qfalse;
+
+ 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 ( glConfig.hardwareType == GLHW_RAGEPRO || 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, qtrue);
+ // 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==qfalse) {
+ 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
+ Com_Memcpy(cin.linbuf+cinTable[currentHandle].screenDelta, cin.linbuf, cinTable[currentHandle].samplesPerLine*cinTable[currentHandle].ysize);
+ }
+ cinTable[currentHandle].numQuads++;
+ cinTable[currentHandle].dirty = qtrue;
+ 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 );
+ }
+ 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 );
+ }
+ break;
+ case ROQ_QUAD_INFO:
+ if (cinTable[currentHandle].numQuads == -1) {
+ readQuadInfo( framedata );
+ setupQuad( 0, 0 );
+ // we need to use CL_ScaledMilliseconds because of the smp mode calls from the renderer
+ cinTable[currentHandle].startTime = cinTable[currentHandle].lastTime = CL_ScaledMilliseconds()*com_timescale->value;
+ }
+ if (cinTable[currentHandle].numQuads != 1) cinTable[currentHandle].numQuads = 0;
+ break;
+ case ROQ_PACKET:
+ cinTable[currentHandle].inMemory = 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==qfalse) {
+ 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--; 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 )
+{
+ // we need to use CL_ScaledMilliseconds because of the smp mode calls from the renderer
+ cinTable[currentHandle].startTime = cinTable[currentHandle].lastTime = CL_ScaledMilliseconds()*com_timescale->value;
+
+ 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) {
+ cls.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;
+ }
+ cinTable[currentHandle].fileName[0] = 0;
+ currentHandle = -1;
+}
+
+/*
+==================
+SCR_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 ( cls.state != CA_CINEMATIC ) {
+ return cinTable[currentHandle].status;
+ }
+ }
+ cinTable[currentHandle].status = FMV_EOF;
+ RoQShutdown();
+
+ return FMV_EOF;
+}
+
+/*
+==================
+SCR_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 ( cls.state != CA_CINEMATIC ) {
+ return cinTable[currentHandle].status;
+ }
+ }
+
+ if (cinTable[currentHandle].status == FMV_IDLE) {
+ return cinTable[currentHandle].status;
+ }
+
+ // we need to use CL_ScaledMilliseconds because of the smp mode calls from the renderer
+ thisTime = CL_ScaledMilliseconds()*com_timescale->value;
+ if (cinTable[currentHandle].shader && (abs(thisTime - cinTable[currentHandle].lastTime))>100) {
+ cinTable[currentHandle].startTime += thisTime - cinTable[currentHandle].lastTime;
+ }
+ // we need to use CL_ScaledMilliseconds because of the smp mode calls from the renderer
+ cinTable[currentHandle].tfps = ((((CL_ScaledMilliseconds()*com_timescale->value) - 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) {
+ // we need to use CL_ScaledMilliseconds because of the smp mode calls from the renderer
+ cinTable[currentHandle].tfps = ((((CL_ScaledMilliseconds()*com_timescale->value)
+ - 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 cinTable[currentHandle].status;
+}
+
+/*
+==================
+CL_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("SCR_PlayCinematic( %s )\n", arg);
+
+ Com_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, qtrue);
+
+ 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 ( uivm ) {
+ VM_Call( uivm, UI_SET_ACTIVE_MENU, 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) {
+ cls.state = CA_CINEMATIC;
+ }
+
+ Con_Close();
+
+ s_rawend[0] = s_soundtime;
+
+ return currentHandle;
+ }
+ Com_DPrintf("trFMV::play(), invalid RoQ ID\n");
+
+ RoQShutdown();
+ return -1;
+}
+
+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 = qtrue;
+}
+
+void CIN_SetLooping(int handle, qboolean loop) {
+ if (handle < 0 || handle>= MAX_VIDEO_HANDLES || cinTable[handle].status == FMV_EOF) return;
+ cinTable[handle].looping = loop;
+}
+
+/*
+==================
+SCR_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 ix, iy, *buf2, *buf3, xm, ym, ll;
+
+ 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;
+ buf2 = Hunk_AllocateTempMemory( 256*256*4 );
+ 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)];
+ }
+ }
+ }
+ re.DrawStretchRaw( x, y, w, h, 256, 256, (byte *)buf2, handle, qtrue);
+ cinTable[handle].dirty = qfalse;
+ Hunk_FreeTempMemory(buf2);
+ return;
+ }
+
+ re.DrawStretchRaw( x, y, w, h, cinTable[handle].drawX, cinTable[handle].drawY, buf, handle, cinTable[handle].dirty);
+ cinTable[handle].dirty = qfalse;
+}
+
+void CL_PlayCinematic_f(void) {
+ char *arg, *s;
+ qboolean holdatend;
+ int bits = CIN_system;
+
+ Com_DPrintf("CL_PlayCinematic_f\n");
+ if (cls.state == CA_CINEMATIC) {
+ SCR_StopCinematic();
+ }
+
+ arg = Cmd_Argv( 1 );
+ s = Cmd_Argv(2);
+
+ holdatend = qfalse;
+ 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 = qfalse;
+ }
+ }
+ }
+ re.UploadCinematic( 256, 256, 256, 256, cinTable[handle].buf, handle, cinTable[handle].dirty);
+ if (cl_inGameVideo->integer == 0 && cinTable[handle].playonwalls == 1) {
+ cinTable[handle].playonwalls--;
+ }
+ }
+}
+
diff --git a/code/client/cl_console.c b/code/client/cl_console.c
new file mode 100644
index 0000000..da575a3
--- /dev/null
+++ b/code/client/cl_console.c
@@ -0,0 +1,795 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code 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,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+// console.c
+
+#include "client.h"
+
+
+int g_console_field_width = 78;
+
+
+#define NUM_CON_TIMES 4
+
+#define CON_TEXTSIZE 32768
+typedef struct {
+ qboolean 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
+
+ int times[NUM_CON_TIMES]; // cls.realtime time the line was generated
+ // for transparent notify lines
+ vec4_t color;
+} console_t;
+
+extern console_t con;
+
+console_t con;
+
+cvar_t *con_conspeed;
+cvar_t *con_notifytime;
+
+#define DEFAULT_CONSOLE_WIDTH 78
+
+vec4_t console_color = {1.0, 1.0, 1.0, 1.0};
+
+
+/*
+================
+Con_ToggleConsole_f
+================
+*/
+void Con_ToggleConsole_f (void) {
+ // Can't toggle the console when it's the only thing available
+ if ( cls.state == CA_DISCONNECTED && Key_GetCatcher( ) == KEYCATCH_CONSOLE ) {
+ return;
+ }
+
+ Field_Clear( &g_consoleField );
+ g_consoleField.widthInChars = g_console_field_width;
+
+ Con_ClearNotify ();
+ Key_SetCatcher( Key_GetCatcher( ) ^ KEYCATCH_CONSOLE );
+}
+
+/*
+================
+Con_MessageMode_f
+================
+*/
+void Con_MessageMode_f (void) {
+ chat_playerNum = -1;
+ chat_team = qfalse;
+ 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 = qtrue;
+ Field_Clear( &chatField );
+ chatField.widthInChars = 25;
+ Key_SetCatcher( Key_GetCatcher( ) ^ KEYCATCH_MESSAGE );
+}
+
+/*
+================
+Con_MessageMode3_f
+================
+*/
+void Con_MessageMode3_f (void) {
+ chat_playerNum = VM_Call( cgvm, CG_CROSSHAIR_PLAYER );
+ if ( chat_playerNum < 0 || chat_playerNum >= MAX_CLIENTS ) {
+ chat_playerNum = -1;
+ return;
+ }
+ chat_team = qfalse;
+ Field_Clear( &chatField );
+ chatField.widthInChars = 30;
+ Key_SetCatcher( Key_GetCatcher( ) ^ KEYCATCH_MESSAGE );
+}
+
+/*
+================
+Con_MessageMode4_f
+================
+*/
+void Con_MessageMode4_f (void) {
+ chat_playerNum = VM_Call( cgvm, CG_LAST_ATTACKER );
+ if ( chat_playerNum < 0 || chat_playerNum >= MAX_CLIENTS ) {
+ chat_playerNum = -1;
+ return;
+ }
+ chat_team = qfalse;
+ Field_Clear( &chatField );
+ chatField.widthInChars = 30;
+ 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;
+ char buffer[1024];
+
+ if (Cmd_Argc() != 2)
+ {
+ Com_Printf ("usage: condump <filename>\n");
+ return;
+ }
+
+ Com_Printf ("Dumped console text to %s.\n", Cmd_Argv(1) );
+
+ f = FS_FOpenFileWrite( Cmd_Argv( 1 ) );
+ if (!f)
+ {
+ Com_Printf ("ERROR: couldn't open.\n");
+ return;
+ }
+
+ // 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;
+ }
+
+ // write the remaining lines
+ buffer[con.linewidth] = 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;
+ }
+ strcat( buffer, "\n" );
+ FS_Write(buffer, strlen(buffer), f);
+ }
+
+ FS_FCloseFile( f );
+}
+
+
+/*
+================
+Con_ClearNotify
+================
+*/
+void Con_ClearNotify( void ) {
+ int i;
+
+ for ( i = 0 ; i < NUM_CON_TIMES ; i++ ) {
+ con.times[i] = 0;
+ }
+}
+
+
+
+/*
+================
+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;
+
+ Com_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_ClearNotify ();
+ }
+
+ con.current = con.totallines - 1;
+ con.display = con.current;
+}
+
+/*
+==================
+Cmd_CompleteTxtName
+==================
+*/
+void Cmd_CompleteTxtName( char *args, int argNum ) {
+ if( argNum == 2 ) {
+ Field_CompleteFilename( "", "txt", qfalse );
+ }
+}
+
+
+/*
+================
+Con_Init
+================
+*/
+void Con_Init (void) {
+ int i;
+
+ con_notifytime = Cvar_Get ("con_notifytime", "3", 0);
+ con_conspeed = Cvar_Get ("scr_conspeed", "3", 0);
+
+ 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( );
+
+ Cmd_AddCommand ("toggleconsole", Con_ToggleConsole_f);
+ 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 ("clear", Con_Clear_f);
+ Cmd_AddCommand ("condump", Con_Dump_f);
+ Cmd_SetCommandCompletionFunc( "condump", Cmd_CompleteTxtName );
+}
+
+
+/*
+===============
+Con_Linefeed
+===============
+*/
+void Con_Linefeed (qboolean skipnotify)
+{
+ int i;
+
+ // mark time for transparent overlay
+ if (con.current >= 0)
+ {
+ if (skipnotify)
+ con.times[con.current % NUM_CON_TIMES] = 0;
+ else
+ con.times[con.current % NUM_CON_TIMES] = cls.realtime;
+ }
+
+ 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( char *txt ) {
+ int y;
+ int c, l;
+ int color;
+ qboolean skipnotify = qfalse; // NERVE - SMF
+ int prev; // 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 = qtrue;
+ 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 = qtrue;
+ }
+
+ color = ColorIndex(COLOR_WHITE);
+
+ while ( (c = *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(skipnotify);
+
+ }
+
+ txt++;
+
+ switch (c)
+ {
+ case '\n':
+ Con_Linefeed (skipnotify);
+ 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(skipnotify);
+ con.x = 0;
+ }
+ break;
+ }
+ }
+
+
+ // mark time for transparent overlay
+ if (con.current >= 0) {
+ // NERVE - SMF
+ if ( skipnotify ) {
+ prev = con.current % NUM_CON_TIMES - 1;
+ if ( prev < 0 )
+ prev = NUM_CON_TIMES - 1;
+ con.times[prev] = 0;
+ }
+ else
+ // -NERVE - SMF
+ con.times[con.current % NUM_CON_TIMES] = cls.realtime;
+ }
+}
+
+
+/*
+==============================================================================
+
+DRAWING
+
+==============================================================================
+*/
+
+
+/*
+================
+Con_DrawInput
+
+Draw the editline after a ] prompt
+================
+*/
+void Con_DrawInput (void) {
+ int y;
+
+ if ( cls.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, qtrue, qtrue );
+}
+
+
+/*
+================
+Con_DrawNotify
+
+Draws the last few lines of output transparently over the game top
+================
+*/
+void Con_DrawNotify (void)
+{
+ int x, v;
+ short *text;
+ int i;
+ int time;
+ int skip;
+ int currentColor;
+
+ currentColor = 7;
+ re.SetColor( g_color_table[currentColor] );
+
+ v = 0;
+ for (i= con.current-NUM_CON_TIMES+1 ; i<=con.current ; i++)
+ {
+ if (i < 0)
+ continue;
+ time = con.times[i % NUM_CON_TIMES];
+ if (time == 0)
+ continue;
+ time = cls.realtime - time;
+ if (time > con_notifytime->value*1000)
+ continue;
+ text = con.text + (i % con.totallines)*con.linewidth;
+
+ if (cl.snap.ps.pm_type != PM_INTERMISSION && Key_GetCatcher( ) & (KEYCATCH_UI | KEYCATCH_CGAME) ) {
+ continue;
+ }
+
+ for (x = 0 ; x < con.linewidth ; x++) {
+ if ( ( text[x] & 0xff ) == ' ' ) {
+ continue;
+ }
+ if ( ( (text[x]>>8)&7 ) != currentColor ) {
+ currentColor = (text[x]>>8)&7;
+ re.SetColor( g_color_table[currentColor] );
+ }
+ SCR_DrawSmallChar( cl_conXOffset->integer + con.xadjust + (x+1)*SMALLCHAR_WIDTH, v, text[x] & 0xff );
+ }
+
+ v += SMALLCHAR_HEIGHT;
+ }
+
+ re.SetColor( NULL );
+
+ if (Key_GetCatcher( ) & (KEYCATCH_UI | KEYCATCH_CGAME) ) {
+ return;
+ }
+
+ // draw the chat line
+ if ( Key_GetCatcher( ) & KEYCATCH_MESSAGE )
+ {
+ if (chat_team)
+ {
+ SCR_DrawBigString (8, v, "say_team:", 1.0f, qfalse );
+ skip = 10;
+ }
+ else
+ {
+ SCR_DrawBigString (8, v, "say:", 1.0f, qfalse );
+ skip = 5;
+ }
+
+ Field_BigDraw( &chatField, skip * BIGCHAR_WIDTH, v,
+ SCREEN_WIDTH - ( skip + 1 ) * BIGCHAR_WIDTH, qtrue, qtrue );
+
+ v += BIGCHAR_HEIGHT;
+ }
+
+}
+
+/*
+================
+Con_DrawSolidConsole
+
+Draws the console with the solid background
+================
+*/
+void Con_DrawSolidConsole( float frac ) {
+ int i, x, y;
+ int rows;
+ short *text;
+ int row;
+ int lines;
+// qhandle_t conShader;
+ 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 {
+ SCR_DrawPic( 0, 0, SCREEN_WIDTH, y, cls.consoleShader );
+ }
+
+ 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_WIDTH)/SMALLCHAR_WIDTH; // 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 ( ( (text[x]>>8)&7 ) != currentColor ) {
+ currentColor = (text[x]>>8)&7;
+ 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 ( cls.state == CA_DISCONNECTED ) {
+ if ( !( Key_GetCatcher( ) & (KEYCATCH_UI | KEYCATCH_CGAME)) ) {
+ Con_DrawSolidConsole( 1.0 );
+ return;
+ }
+ }
+
+ if ( con.displayFrac ) {
+ Con_DrawSolidConsole( con.displayFrac );
+ } else {
+ // draw notify lines
+ if ( cls.state == CA_ACTIVE ) {
+ Con_DrawNotify ();
+ }
+ }
+}
+
+//================================================================
+
+/*
+==================
+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 = 0.5; // half screen
+ 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 );
+ Con_ClearNotify ();
+ Key_SetCatcher( Key_GetCatcher( ) & ~KEYCATCH_CONSOLE );
+ con.finalFrac = 0; // none visible
+ con.displayFrac = 0;
+}
diff --git a/code/client/cl_curl.c b/code/client/cl_curl.c
new file mode 100644
index 0000000..170e97b
--- /dev/null
+++ b/code/client/cl_curl.c
@@ -0,0 +1,338 @@
+/*
+===========================================================================
+Copyright (C) 2006 Tony J. White (tjw@tjw.org)
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code 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,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+#ifdef USE_CURL
+#include "client.h"
+cvar_t *cl_cURLLib;
+
+#ifdef USE_CURL_DLOPEN
+#include "../sys/sys_loadlib.h"
+
+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 *multi_handle,
+ CURL *curl_handle);
+CURLMcode (*qcurl_multi_remove_handle)(CURLM *multi_handle,
+ CURL *curl_handle);
+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);
+CURLMcode (*qcurl_multi_perform)(CURLM *multi_handle,
+ int *running_handles);
+CURLMcode (*qcurl_multi_cleanup)(CURLM *multi_handle);
+CURLMsg *(*qcurl_multi_info_read)(CURLM *multi_handle,
+ int *msgs_in_queue);
+const char *(*qcurl_multi_strerror)(CURLMcode);
+
+static void *cURLLib = NULL;
+
+/*
+=================
+GPA
+=================
+*/
+static void *GPA(char *str)
+{
+ void *rv;
+
+ rv = Sys_LoadFunction(cURLLib, str);
+ if(!rv)
+ {
+ Com_Printf("Can't load symbol %s\n", str);
+ clc.cURLEnabled = qfalse;
+ return NULL;
+ }
+ else
+ {
+ Com_DPrintf("Loaded symbol %s (0x%p)\n", str, rv);
+ return rv;
+ }
+}
+#endif /* USE_CURL_DLOPEN */
+
+/*
+=================
+CL_cURL_Init
+=================
+*/
+qboolean CL_cURL_Init()
+{
+#ifdef USE_CURL_DLOPEN
+ if(cURLLib)
+ return qtrue;
+
+
+ Com_Printf("Loading \"%s\"...", cl_cURLLib->string);
+ if( (cURLLib = Sys_LoadLibrary(cl_cURLLib->string)) == 0 )
+ {
+#ifdef _WIN32
+ return qfalse;
+#else
+ char fn[1024];
+
+ Q_strncpyz( fn, Sys_Cwd( ), sizeof( fn ) );
+ strncat(fn, "/", sizeof(fn)-strlen(fn)-1);
+ strncat(fn, cl_cURLLib->string, sizeof(fn)-strlen(fn)-1);
+
+ if((cURLLib = Sys_LoadLibrary(fn)) == 0)
+ {
+#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_LoadLibrary(ALTERNATE_CURL_LIB)) == 0 )
+ {
+ return qfalse;
+ }
+#else
+ return qfalse;
+#endif
+ }
+#endif /* _WIN32 */
+ }
+
+ clc.cURLEnabled = qtrue;
+
+ qcurl_version = GPA("curl_version");
+
+ qcurl_easy_init = GPA("curl_easy_init");
+ qcurl_easy_setopt = GPA("curl_easy_setopt");
+ qcurl_easy_perform = GPA("curl_easy_perform");
+ qcurl_easy_cleanup = GPA("curl_easy_cleanup");
+ qcurl_easy_getinfo = GPA("curl_easy_getinfo");
+ qcurl_easy_duphandle = GPA("curl_easy_duphandle");
+ qcurl_easy_reset = GPA("curl_easy_reset");
+ qcurl_easy_strerror = GPA("curl_easy_strerror");
+
+ qcurl_multi_init = GPA("curl_multi_init");
+ qcurl_multi_add_handle = GPA("curl_multi_add_handle");
+ qcurl_multi_remove_handle = GPA("curl_multi_remove_handle");
+ qcurl_multi_fdset = GPA("curl_multi_fdset");
+ qcurl_multi_perform = GPA("curl_multi_perform");
+ qcurl_multi_cleanup = GPA("curl_multi_cleanup");
+ qcurl_multi_info_read = GPA("curl_multi_info_read");
+ qcurl_multi_strerror = GPA("curl_multi_strerror");
+
+ if(!clc.cURLEnabled)
+ {
+ CL_cURL_Shutdown();
+ Com_Printf("FAIL One or more symbols not found\n");
+ return qfalse;
+ }
+ Com_Printf("OK\n");
+
+ return qtrue;
+#else
+ clc.cURLEnabled = qtrue;
+ return qtrue;
+#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) {
+ if(clc.downloadCURL) {
+ qcurl_multi_remove_handle(clc.downloadCURLM,
+ clc.downloadCURL);
+ qcurl_easy_cleanup(clc.downloadCURL);
+ }
+ qcurl_multi_cleanup(clc.downloadCURLM);
+ 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;
+}
+
+void CL_cURL_BeginDownload( const char *localName, const char *remoteURL )
+{
+ clc.cURLUsed = qtrue;
+ 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\n");
+ return;
+ }
+ clc.download = FS_SV_FOpenFileWrite(clc.downloadTempName);
+ if(!clc.download) {
+ Com_Error(ERR_DROP, "CL_cURL_BeginDownload: failed to open "
+ "%s for writing\n", clc.downloadTempName);
+ return;
+ }
+ qcurl_easy_setopt(clc.downloadCURL, CURLOPT_WRITEDATA, clc.download);
+ if(com_developer->integer)
+ qcurl_easy_setopt(clc.downloadCURL, CURLOPT_VERBOSE, 1);
+ qcurl_easy_setopt(clc.downloadCURL, CURLOPT_URL, clc.downloadURL);
+ qcurl_easy_setopt(clc.downloadCURL, CURLOPT_TRANSFERTEXT, 0);
+ qcurl_easy_setopt(clc.downloadCURL, CURLOPT_REFERER, va("ioQ3://%s",
+ NET_AdrToString(clc.serverAddress)));
+ qcurl_easy_setopt(clc.downloadCURL, CURLOPT_USERAGENT, va("%s %s",
+ Q3_VERSION, qcurl_version()));
+ qcurl_easy_setopt(clc.downloadCURL, CURLOPT_WRITEFUNCTION,
+ CL_cURL_CallbackWrite);
+ qcurl_easy_setopt(clc.downloadCURL, CURLOPT_WRITEDATA, &clc.download);
+ qcurl_easy_setopt(clc.downloadCURL, CURLOPT_NOPROGRESS, 0);
+ qcurl_easy_setopt(clc.downloadCURL, CURLOPT_PROGRESSFUNCTION,
+ CL_cURL_CallbackProgress);
+ qcurl_easy_setopt(clc.downloadCURL, CURLOPT_PROGRESSDATA, NULL);
+ qcurl_easy_setopt(clc.downloadCURL, CURLOPT_FAILONERROR, 1);
+ qcurl_easy_setopt(clc.downloadCURL, CURLOPT_FOLLOWLOCATION, 1);
+ qcurl_easy_setopt(clc.downloadCURL, CURLOPT_MAXREDIRS, 5);
+ 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\n");
+ return;
+ }
+ qcurl_multi_add_handle(clc.downloadCURLM, clc.downloadCURL);
+
+ if(!(clc.sv_allowDownload & DLF_NO_DISCONNECT) &&
+ !clc.cURLDisconnected) {
+
+ CL_AddReliableCommand("disconnect", qtrue);
+ CL_WritePacket();
+ CL_WritePacket();
+ CL_WritePacket();
+ clc.cURLDisconnected = qtrue;
+ }
+}
+
+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);
+ clc.downloadRestart = qtrue;
+ }
+ 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();
+}
+#endif /* USE_CURL */
diff --git a/code/client/cl_curl.h b/code/client/cl_curl.h
new file mode 100644
index 0000000..c8d3006
--- /dev/null
+++ b/code/client/cl_curl.h
@@ -0,0 +1,102 @@
+/*
+===========================================================================
+Copyright (C) 2006 Tony J. White (tjw@tjw.org)
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code 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,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+
+#ifndef __QCURL_H__
+#define __QCURL_H__
+
+extern cvar_t *cl_cURLLib;
+
+#include "../qcommon/q_shared.h"
+#include "../qcommon/qcommon.h"
+
+#ifdef WIN32
+#define DEFAULT_CURL_LIB "libcurl-3.dll"
+#elif defined(MACOS_X)
+#define DEFAULT_CURL_LIB "libcurl.dylib"
+#else
+#define DEFAULT_CURL_LIB "libcurl.so.4"
+#define ALTERNATE_CURL_LIB "libcurl.so.3"
+#endif
+
+#ifdef USE_LOCAL_HEADERS
+ #include "../libcurl/curl/curl.h"
+#else
+ #include <curl/curl.h>
+#endif
+
+
+#ifdef USE_CURL_DLOPEN
+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);
+#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
+#endif
+
+qboolean 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/code/client/cl_input.c b/code/client/cl_input.c
new file mode 100644
index 0000000..a42030d
--- /dev/null
+++ b/code/client/cl_input.c
@@ -0,0 +1,1045 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code 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,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+// 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];
+
+
+qboolean in_mlooking;
+
+
+void IN_MLookDown( void ) {
+ in_mlooking = qtrue;
+}
+
+void IN_MLookUp( void ) {
+ in_mlooking = qfalse;
+ if ( !cl_freelook->integer ) {
+ IN_CenterView ();
+ }
+}
+
+void IN_KeyDown( kbutton_t *b ) {
+ int k;
+ char *c;
+
+ 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 = qtrue;
+ b->wasPressed = qtrue;
+}
+
+void IN_KeyUp( kbutton_t *b ) {
+ int k;
+ char *c;
+ unsigned uptime;
+
+ 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 = qfalse;
+ 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 = qfalse;
+
+ // 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 = qfalse;
+}
+
+
+
+/*
+===============
+CL_KeyState
+
+Returns the fraction of the frame that the key was down
+===============
+*/
+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;
+}
+
+
+
+void IN_UpDown(void) {IN_KeyDown(&in_up);}
+void IN_UpUp(void) {IN_KeyUp(&in_up);}
+void IN_DownDown(void) {IN_KeyDown(&in_down);}
+void IN_DownUp(void) {IN_KeyUp(&in_down);}
+void IN_LeftDown(void) {IN_KeyDown(&in_left);}
+void IN_LeftUp(void) {IN_KeyUp(&in_left);}
+void IN_RightDown(void) {IN_KeyDown(&in_right);}
+void IN_RightUp(void) {IN_KeyUp(&in_right);}
+void IN_ForwardDown(void) {IN_KeyDown(&in_forward);}
+void IN_ForwardUp(void) {IN_KeyUp(&in_forward);}
+void IN_BackDown(void) {IN_KeyDown(&in_back);}
+void IN_BackUp(void) {IN_KeyUp(&in_back);}
+void IN_LookupDown(void) {IN_KeyDown(&in_lookup);}
+void IN_LookupUp(void) {IN_KeyUp(&in_lookup);}
+void IN_LookdownDown(void) {IN_KeyDown(&in_lookdown);}
+void IN_LookdownUp(void) {IN_KeyUp(&in_lookdown);}
+void IN_MoveleftDown(void) {IN_KeyDown(&in_moveleft);}
+void IN_MoveleftUp(void) {IN_KeyUp(&in_moveleft);}
+void IN_MoverightDown(void) {IN_KeyDown(&in_moveright);}
+void IN_MoverightUp(void) {IN_KeyUp(&in_moveright);}
+
+void IN_SpeedDown(void) {IN_KeyDown(&in_speed);}
+void IN_SpeedUp(void) {IN_KeyUp(&in_speed);}
+void IN_StrafeDown(void) {IN_KeyDown(&in_strafe);}
+void IN_StrafeUp(void) {IN_KeyUp(&in_strafe);}
+
+#ifdef USE_VOIP
+void IN_VoipRecordDown(void)
+{
+ IN_KeyDown(&in_voiprecord);
+ Cvar_Set("cl_voipSend", "1");
+}
+
+void IN_VoipRecordUp(void)
+{
+ IN_KeyUp(&in_voiprecord);
+ Cvar_Set("cl_voipSend", "0");
+}
+#endif
+
+void IN_Button0Down(void) {IN_KeyDown(&in_buttons[0]);}
+void IN_Button0Up(void) {IN_KeyUp(&in_buttons[0]);}
+void IN_Button1Down(void) {IN_KeyDown(&in_buttons[1]);}
+void IN_Button1Up(void) {IN_KeyUp(&in_buttons[1]);}
+void IN_Button2Down(void) {IN_KeyDown(&in_buttons[2]);}
+void IN_Button2Up(void) {IN_KeyUp(&in_buttons[2]);}
+void IN_Button3Down(void) {IN_KeyDown(&in_buttons[3]);}
+void IN_Button3Up(void) {IN_KeyUp(&in_buttons[3]);}
+void IN_Button4Down(void) {IN_KeyDown(&in_buttons[4]);}
+void IN_Button4Up(void) {IN_KeyUp(&in_buttons[4]);}
+void IN_Button5Down(void) {IN_KeyDown(&in_buttons[5]);}
+void IN_Button5Up(void) {IN_KeyUp(&in_buttons[5]);}
+void IN_Button6Down(void) {IN_KeyDown(&in_buttons[6]);}
+void IN_Button6Up(void) {IN_KeyUp(&in_buttons[6]);}
+void IN_Button7Down(void) {IN_KeyDown(&in_buttons[7]);}
+void IN_Button7Up(void) {IN_KeyUp(&in_buttons[7]);}
+void IN_Button8Down(void) {IN_KeyDown(&in_buttons[8]);}
+void IN_Button8Up(void) {IN_KeyUp(&in_buttons[8]);}
+void IN_Button9Down(void) {IN_KeyDown(&in_buttons[9]);}
+void IN_Button9Up(void) {IN_KeyUp(&in_buttons[9]);}
+void IN_Button10Down(void) {IN_KeyDown(&in_buttons[10]);}
+void IN_Button10Up(void) {IN_KeyUp(&in_buttons[10]);}
+void IN_Button11Down(void) {IN_KeyDown(&in_buttons[11]);}
+void IN_Button11Up(void) {IN_KeyUp(&in_buttons[11]);}
+void IN_Button12Down(void) {IN_KeyDown(&in_buttons[12]);}
+void IN_Button12Up(void) {IN_KeyUp(&in_buttons[12]);}
+void IN_Button13Down(void) {IN_KeyDown(&in_buttons[13]);}
+void IN_Button13Up(void) {IN_KeyUp(&in_buttons[13]);}
+void IN_Button14Down(void) {IN_KeyDown(&in_buttons[14]);}
+void IN_Button14Up(void) {IN_KeyUp(&in_buttons[14]);}
+void IN_Button15Down(void) {IN_KeyDown(&in_buttons[15]);}
+void IN_Button15Up(void) {IN_KeyUp(&in_buttons[15]);}
+
+void IN_ButtonDown (void) {
+ IN_KeyDown(&in_buttons[1]);}
+void IN_ButtonUp (void) {
+ IN_KeyUp(&in_buttons[1]);}
+
+void IN_CenterView (void) {
+ cl.viewangles[PITCH] = -SHORT2ANGLE(cl.snap.ps.delta_angles[PITCH]);
+}
+
+
+//==========================================================================
+
+cvar_t *cl_upspeed;
+cvar_t *cl_forwardspeed;
+cvar_t *cl_sidespeed;
+
+cvar_t *cl_yawspeed;
+cvar_t *cl_pitchspeed;
+
+cvar_t *cl_run;
+
+cvar_t *cl_anglespeedkey;
+
+
+/*
+================
+CL_AdjustAngles
+
+Moves the local angle positions
+================
+*/
+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
+================
+*/
+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( uivm, UI_MOUSE_EVENT, dx, dy );
+ } else if (Key_GetCatcher( ) & KEYCATCH_CGAME) {
+ VM_Call (cgvm, 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
+=================
+*/
+void CL_JoystickMove( usercmd_t *cmd ) {
+ int movespeed;
+ float anglespeed;
+
+ if ( in_speed.active ^ cl_run->integer ) {
+ movespeed = 2;
+ } else {
+ movespeed = 1;
+ 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 * cl_yawspeed->value * cl.joystickAxis[AXIS_SIDE];
+ } else {
+ cmd->rightmove = ClampChar( cmd->rightmove + cl.joystickAxis[AXIS_SIDE] );
+ }
+
+ if ( in_mlooking ) {
+ cl.viewangles[PITCH] += anglespeed * cl_pitchspeed->value * cl.joystickAxis[AXIS_FORWARD];
+ } else {
+ cmd->forwardmove = ClampChar( cmd->forwardmove + cl.joystickAxis[AXIS_FORWARD] );
+ }
+
+ cmd->upmove = ClampChar( cmd->upmove + cl.joystickAxis[AXIS_UP] );
+}
+
+/*
+=================
+CL_MouseMove
+=================
+*/
+
+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
+==============
+*/
+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 = qfalse;
+ }
+
+ 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
+==============
+*/
+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
+=================
+*/
+usercmd_t CL_CreateCmd( void ) {
+ usercmd_t cmd;
+ vec3_t oldAngles;
+
+ VectorCopy( cl.viewangles, oldAngles );
+
+ // keyboard angle adjustment
+ CL_AdjustAngles ();
+
+ Com_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( abs(cl.viewangles[YAW] - oldAngles[YAW]), 0 );
+ }
+ if ( cl_debugMove->integer == 2 ) {
+ SCR_DebugGraph( abs(cl.viewangles[PITCH] - oldAngles[PITCH]), 0 );
+ }
+ }
+
+ return cmd;
+}
+
+
+/*
+=================
+CL_CreateNewCommands
+
+Create a new usercmd_t structure for this frame
+=================
+*/
+void CL_CreateNewCommands( void ) {
+ usercmd_t *cmd;
+ int cmdNum;
+
+ // no need to create usercmds until we have a gamestate
+ if ( cls.state < CA_PRIMED ) {
+ return;
+ }
+
+ frame_msec = com_frameTime - old_com_frameTime;
+
+ // 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 ();
+ cmd = &cl.cmds[cmdNum];
+}
+
+/*
+=================
+CL_ReadyToSendPacket
+
+Returns qfalse 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.
+=================
+*/
+qboolean CL_ReadyToSendPacket( void ) {
+ int oldPacketNum;
+ int delta;
+
+ // don't send anything if playing back a demo
+ if ( clc.demoplaying || cls.state == CA_CINEMATIC ) {
+ return qfalse;
+ }
+
+ // If we are downloading, we send no less than 50ms between packets
+ if ( *clc.downloadTempName &&
+ cls.realtime - clc.lastPacketSentTime < 50 ) {
+ return qfalse;
+ }
+
+ // if we don't have a valid gamestate yet, only send
+ // one packet a second
+ if ( cls.state != CA_ACTIVE &&
+ cls.state != CA_PRIMED &&
+ !*clc.downloadTempName &&
+ cls.realtime - clc.lastPacketSentTime < 1000 ) {
+ return qfalse;
+ }
+
+ // send every frame for loopbacks
+ if ( clc.netchan.remoteAddress.type == NA_LOOPBACK ) {
+ return qtrue;
+ }
+
+ // send every frame for LAN
+ if ( cl_lanForcePackets->integer && Sys_IsLANAddress( clc.netchan.remoteAddress ) ) {
+ return qtrue;
+ }
+
+ // 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 qfalse;
+ }
+
+ return qtrue;
+}
+
+/*
+===================
+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 || cls.state == CA_CINEMATIC ) {
+ return;
+ }
+
+ Com_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) { // only send if data.
+ // Move cl_voipSendTarget from a string to the bitmasks if needed.
+ if (cl_voipSendTarget->modified) {
+ char buffer[32];
+ const char *target = cl_voipSendTarget->string;
+
+ if (Q_stricmp(target, "attacker") == 0) {
+ int player = VM_Call( cgvm, CG_LAST_ATTACKER );
+ Com_sprintf(buffer, sizeof (buffer), "%d", player);
+ target = buffer;
+ } else if (Q_stricmp(target, "crosshair") == 0) {
+ int player = VM_Call( cgvm, CG_CROSSHAIR_PLAYER );
+ Com_sprintf(buffer, sizeof (buffer), "%d", player);
+ target = buffer;
+ }
+
+ if ((*target == '\0') || (Q_stricmp(target, "all") == 0)) {
+ const int all = 0x7FFFFFFF;
+ clc.voipTarget1 = clc.voipTarget2 = clc.voipTarget3 = all;
+ } else if (Q_stricmp(target, "none") == 0) {
+ clc.voipTarget1 = clc.voipTarget2 = clc.voipTarget3 = 0;
+ } else {
+ const char *ptr = target;
+ clc.voipTarget1 = clc.voipTarget2 = clc.voipTarget3 = 0;
+ do {
+ if ((*ptr == ',') || (*ptr == '\0')) {
+ const int val = atoi(target);
+ target = ptr + 1;
+ if ((val >= 0) && (val < 31)) {
+ clc.voipTarget1 |= (1 << (val-0));
+ } else if ((val >= 31) && (val < 62)) {
+ clc.voipTarget2 |= (1 << (val-31));
+ } else if ((val >= 62) && (val < 93)) {
+ clc.voipTarget3 |= (1 << (val-62));
+ }
+ }
+ } while (*(ptr++));
+ }
+ cl_voipSendTarget->modified = qfalse;
+ }
+
+ MSG_WriteByte (&buf, clc_EOF); // placate legacy servers.
+ MSG_WriteByte (&buf, clc_extension);
+ MSG_WriteByte (&buf, clc_voip);
+ MSG_WriteByte (&buf, clc.voipOutgoingGeneration);
+ MSG_WriteLong (&buf, clc.voipOutgoingSequence);
+ MSG_WriteByte (&buf, clc.voipOutgoingDataFrames);
+ MSG_WriteLong (&buf, clc.voipTarget1);
+ MSG_WriteLong (&buf, clc.voipTarget2);
+ MSG_WriteLong (&buf, clc.voipTarget3);
+ 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_EOF);
+ MSG_WriteByte (&fakemsg, svc_extension);
+ MSG_WriteByte (&fakemsg, svc_voip);
+ 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 );
+ 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
+#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.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);
+
+ // clients never really should have messages large enough
+ // to fragment, but in case they do, fire them all off
+ // at once
+ // TTimo: this causes a packet burst, which is bad karma for winsock
+ // added a WARNING message, we'll see if there are legit situations where this happens
+ while ( clc.netchan.unsentFragments ) {
+ Com_DPrintf( "WARNING: #462 unsent fragments (not supposed to happen!)\n" );
+ CL_Netchan_TransmitNextFragment( &clc.netchan );
+ }
+}
+
+/*
+=================
+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 ( cls.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);
+}
diff --git a/code/client/cl_keys.c b/code/client/cl_keys.c
new file mode 100644
index 0000000..0f72795
--- /dev/null
+++ b/code/client/cl_keys.c
@@ -0,0 +1,1515 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code 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,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+#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;
+qboolean chat_team;
+
+int chat_playerNum;
+
+
+qboolean key_overstrikeMode;
+
+int anykeydown;
+qkey_t keys[MAX_KEYS];
+
+
+typedef struct {
+ char *name;
+ int keynum;
+} keyname_t;
+
+
+// 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},
+
+ {NULL,0}
+};
+
+/*
+=============================================================================
+
+EDIT FIELDS
+
+=============================================================================
+*/
+
+
+/*
+===================
+Field_Draw
+
+Handles horizontal scrolling and cursor blinking
+x, y, and width are in pixels
+===================
+*/
+void Field_VariableSizeDraw( field_t *edit, int x, int y, int width, int size, qboolean showCursor,
+ qboolean 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" );
+ }
+
+ Com_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, qfalse, 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, qfalse );
+
+ }
+ }
+}
+
+void Field_Draw( field_t *edit, int x, int y, int width, qboolean showCursor, qboolean noColorEscape )
+{
+ Field_VariableSizeDraw( edit, x, y, width, SMALLCHAR_WIDTH, showCursor, noColorEscape );
+}
+
+void Field_BigDraw( field_t *edit, int x, int y, int width, qboolean showCursor, qboolean noColorEscape )
+{
+ Field_VariableSizeDraw( edit, x, y, width, BIGCHAR_WIDTH, showCursor, noColorEscape );
+}
+
+/*
+================
+Field_Paste
+================
+*/
+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 ) {
+ if ( edit->cursor == MAX_EDIT_LINE - 1 )
+ return;
+ edit->buffer[edit->cursor] = ch;
+ edit->cursor++;
+ } else { // insert mode
+ if ( len == MAX_EDIT_LINE - 1 ) {
+ 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
+====================
+*/
+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 ( cls.state != CA_ACTIVE && 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 ( cls.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
+================
+*/
+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] && cls.state == CA_ACTIVE ) {
+ 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
+ Com_sprintf( buffer, sizeof( buffer ), "say \"%s\"\n", chatField.buffer );
+
+
+
+ CL_AddReliableCommand(buffer, qfalse);
+ }
+ Key_SetCatcher( Key_GetCatcher( ) & ~KEYCATCH_MESSAGE );
+ Field_Clear( &chatField );
+ return;
+ }
+
+ Field_KeyDownEvent( &chatField, key );
+}
+
+//============================================================================
+
+
+qboolean Key_GetOverstrikeMode( void ) {
+ return key_overstrikeMode;
+}
+
+
+void Key_SetOverstrikeMode( qboolean state ) {
+ key_overstrikeMode = state;
+}
+
+
+/*
+===================
+Key_IsDown
+===================
+*/
+qboolean Key_IsDown( int keynum ) {
+ if ( keynum < 0 || keynum >= MAX_KEYS ) {
+ return qfalse;
+ }
+
+ 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( char *str ) {
+ keyname_t *kn;
+
+ if ( !str || !str[0] ) {
+ return -1;
+ }
+ if ( !str[1] ) {
+ return 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.
+===================
+*/
+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
+===================
+*/
+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)
+{
+ int b;
+
+ if (Cmd_Argc() != 2)
+ {
+ Com_Printf ("unbind <key> : remove commands from 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;
+ }
+
+ 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)
+ Com_Printf ("\"%s\" = \"%s\"\n", Cmd_Argv(1), keys[b].binding );
+ else
+ Com_Printf ("\"%s\" is not bound\n", Cmd_Argv(1) );
+ 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, qtrue, qtrue );
+ }
+}
+
+/*
+===================
+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_ParseBinding
+
+Execute the commands in the bind string
+===================
+*/
+void CL_ParseBinding( int key, qboolean down, unsigned time )
+{
+ char buf[ MAX_STRING_CHARS ], *p = buf, *end;
+
+ if( !keys[key].binding || !keys[key].binding[0] )
+ return;
+ Q_strncpyz( buf, keys[key].binding, sizeof( buf ) );
+
+ 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
+ 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
+ Cbuf_AddText( p );
+ Cbuf_AddText( "\n" );
+ }
+ if( !end )
+ break;
+ p = end + 1;
+ }
+}
+
+/*
+===================
+CL_KeyDownEvent
+
+Called by CL_KeyEvent to handle a keypress
+===================
+*/
+void CL_KeyDownEvent( int key, unsigned time )
+{
+ keys[key].down = qtrue;
+ 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 || cls.state == CA_CINEMATIC ) && Key_GetCatcher( ) == 0 ) {
+
+ if (Cvar_VariableValue ("com_cameraMode") == 0) {
+ Cvar_Set ("nextdemo","");
+ key = K_ESCAPE;
+ }
+ }
+
+ // escape is always handled special
+ if ( key == K_ESCAPE ) {
+ if ( 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 (cgvm, CG_EVENT_HANDLING, CGAME_EVENT_NONE);
+ return;
+ }
+
+ if ( !( Key_GetCatcher( ) & KEYCATCH_UI ) ) {
+ if ( cls.state == CA_ACTIVE && !clc.demoplaying ) {
+ VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_INGAME );
+ }
+ else if ( cls.state != CA_DISCONNECTED ) {
+ CL_Disconnect_f();
+ S_StopAllSounds();
+ VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_MAIN );
+ }
+ return;
+ }
+
+ VM_Call( uivm, UI_KEY_EVENT, key, qtrue );
+ return;
+ }
+
+ // distribute the key down event to the apropriate handler
+ if ( Key_GetCatcher( ) & KEYCATCH_CONSOLE ) {
+ Console_Key( key );
+ } else if ( Key_GetCatcher( ) & KEYCATCH_UI ) {
+ if ( uivm ) {
+ VM_Call( uivm, UI_KEY_EVENT, key, qtrue );
+ }
+ } else if ( Key_GetCatcher( ) & KEYCATCH_CGAME ) {
+ if ( cgvm ) {
+ VM_Call( cgvm, CG_KEY_EVENT, key, qtrue );
+ }
+ } else if ( Key_GetCatcher( ) & KEYCATCH_MESSAGE ) {
+ Message_Key( key );
+ } else if ( cls.state == CA_DISCONNECTED ) {
+ Console_Key( key );
+ } else {
+ // send the bound action
+ CL_ParseBinding( key, qtrue, time );
+ }
+ return;
+}
+
+/*
+===================
+CL_KeyUpEvent
+
+Called by CL_KeyEvent to handle a keyrelease
+===================
+*/
+void CL_KeyUpEvent( int key, unsigned time )
+{
+ keys[key].repeats = 0;
+ keys[key].down = qfalse;
+ 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.
+ //
+ if( cls.state != CA_DISCONNECTED )
+ CL_ParseBinding( key, qfalse, time );
+
+ if ( Key_GetCatcher( ) & KEYCATCH_UI && uivm ) {
+ VM_Call( uivm, UI_KEY_EVENT, key, qfalse );
+ } else if ( Key_GetCatcher( ) & KEYCATCH_CGAME && cgvm ) {
+ VM_Call( cgvm, CG_KEY_EVENT, key, qfalse );
+ }
+}
+
+/*
+===================
+CL_KeyEvent
+
+Called by the system for both key up and key down events
+===================
+*/
+void CL_KeyEvent (int key, qboolean 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( uivm, UI_KEY_EVENT, key | K_CHAR_FLAG, qtrue );
+ }
+ else if ( Key_GetCatcher( ) & KEYCATCH_MESSAGE )
+ {
+ Field_CharEvent( &chatField, key );
+ }
+ else if ( cls.state == CA_DISCONNECTED )
+ {
+ Field_CharEvent( &g_consoleField, key );
+ }
+}
+
+
+/*
+===================
+Key_ClearStates
+===================
+*/
+void Key_ClearStates (void)
+{
+ int i;
+
+ anykeydown = 0;
+
+ for ( i=0 ; i < MAX_KEYS ; i++ ) {
+ if ( keys[i].down ) {
+ CL_KeyEvent( i, qfalse, 0 );
+
+ }
+ keys[i].down = 0;
+ keys[i].repeats = 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, qfalse );
+ 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;
+ }
+ Com_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/code/client/cl_main.c b/code/client/cl_main.c
new file mode 100644
index 0000000..55bf41c
--- /dev/null
+++ b/code/client/cl_main.c
@@ -0,0 +1,4229 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code 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,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+// cl_main.c -- client main loop
+
+#include "client.h"
+#include <limits.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_voip;
+#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 *cl_activeAction;
+
+cvar_t *cl_motdString;
+
+cvar_t *cl_allowDownload;
+cvar_t *cl_conXOffset;
+cvar_t *cl_inGameVideo;
+
+cvar_t *cl_serverStatusResendTime;
+cvar_t *cl_trn;
+
+cvar_t *cl_lanForcePackets;
+
+cvar_t *cl_guidServerUniq;
+
+cvar_t *cl_consoleKeys;
+
+clientActive_t cl;
+clientConnection_t clc;
+clientStatic_t cls;
+vm_t *cgvm;
+
+// Structure containing functions exported from refresh DLL
+refexport_t re;
+
+ping_t cl_pinglist[MAX_PINGREQUESTS];
+
+typedef struct serverStatus_s
+{
+ char string[BIG_INFO_STRING];
+ netadr_t address;
+ int time, startTime;
+ qboolean pending;
+ qboolean print;
+ qboolean retrieved;
+} serverStatus_t;
+
+serverStatus_t cl_serverStatusList[MAX_SERVERSTATUSREQUESTS];
+int serverStatusCount;
+
+#if defined __USEA3D && defined __A3D_GEOM
+ void hA3Dg_ExportRenderGeom (refexport_t *incoming_re);
+#endif
+
+extern void SV_BotFrame( int time );
+void CL_CheckForResend( void );
+void CL_ShowIP_f(void);
+void CL_ServerStatus_f(void);
+void CL_ServerStatusResponse( netadr_t from, msg_t *msg );
+
+/*
+===============
+CL_CDDialog
+
+Called by Com_Error when a cd is needed
+===============
+*/
+void CL_CDDialog( void ) {
+ cls.cddialog = qtrue; // start it next frame
+}
+
+#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.
+ 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, qboolean 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), qfalse);
+ 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 (cls.state != CA_ACTIVE)
+ reason = "Not connected to a server";
+ else if (!clc.speexInitialized)
+ reason = "Speex not initialized";
+ else if (!cl_connectedToVoipServer)
+ reason = "Server doesn't support VoIP";
+ else if ( Cvar_VariableValue( "g_gametype" ) == GT_SINGLE_PLAYER || Cvar_VariableValue("ui_singlePlayerActive"))
+ reason = "running in single-player mode";
+
+ if (reason != NULL) {
+ Com_Printf("VoIP: command ignored: %s\n", reason);
+ return;
+ }
+
+ if (strcmp(cmd, "ignore") == 0) {
+ CL_UpdateVoipIgnore(Cmd_Argv(2), qtrue);
+ } else if (strcmp(cmd, "unignore") == 0) {
+ CL_UpdateVoipIgnore(Cmd_Argv(2), qfalse);
+ } 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", qfalse);
+ clc.voipMuteAll = qtrue;
+ } else if (strcmp(cmd, "unmuteall") == 0) {
+ Com_Printf("VoIP: unmuting incoming voice\n");
+ CL_AddReliableCommand("voip unmuteall", qfalse);
+ clc.voipMuteAll = qfalse;
+ } 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;
+}
+
+/*
+===============
+CL_CaptureVoip
+
+Record more audio from the hardware if required and encode it into Speex
+ data for later transmission.
+===============
+*/
+static
+void CL_CaptureVoip(void)
+{
+ const float audioMult = cl_voipCaptureMult->value;
+ const qboolean useVad = (cl_voipUseVAD->integer != 0);
+ qboolean initialFrame = qfalse;
+ qboolean finalFrame = qfalse;
+
+#if USE_MUMBLE
+ // if we're using Mumble, don't try to handle VoIP transmission ourselves.
+ if (cl_useMumble->integer)
+ return;
+#endif
+
+ if (!clc.speexInitialized)
+ 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 = qfalse;
+ }
+
+ if ((useVad) && (!cl_voipSend->integer))
+ Cvar_Set("cl_voipSend", "1"); // lots of things reset this.
+
+ if (cl_voipSend->modified) {
+ qboolean dontCapture = qfalse;
+ if (cls.state != CA_ACTIVE)
+ dontCapture = qtrue; // not connected to a server.
+ else if (!cl_connectedToVoipServer)
+ dontCapture = qtrue; // server doesn't support VoIP.
+ else if (clc.demoplaying)
+ dontCapture = qtrue; // playing back a demo.
+ else if ( cl_voip->integer == 0 )
+ dontCapture = qtrue; // client has VoIP support disabled.
+ else if ( audioMult == 0.0f )
+ dontCapture = qtrue; // basically silenced incoming audio.
+
+ cl_voipSend->modified = qfalse;
+
+ if (dontCapture) {
+ cl_voipSend->integer = 0;
+ return;
+ }
+
+ if (cl_voipSend->integer) {
+ initialFrame = qtrue;
+ } else {
+ finalFrame = qtrue;
+ }
+ }
+
+ // try to get more audio data from the sound card...
+
+ if (initialFrame) {
+ float gain = cl_voipGainDuringCapture->value;
+ if (gain < 0.0f) gain = 0.0f; else if (gain >= 1.0f) gain = 1.0f;
+ S_MasterGain(cl_voipGainDuringCapture->value);
+ S_StartCapture();
+ CL_VoipNewGeneration();
+ }
+
+ if ((cl_voipSend->integer) || (finalFrame)) { // user wants to capture audio?
+ int samples = S_AvailableCaptureSamples();
+ const int mult = (finalFrame) ? 1 : 12; // 12 == 240ms of audio.
+
+ // enough data buffered in audio hardware to process yet?
+ if (samples >= (clc.speexFrameSize * mult)) {
+ // audio capture is always MONO16 (and that's what speex wants!).
+ // 2048 will cover 12 uncompressed frames in narrowband mode.
+ static int16_t sampbuffer[2048];
+ float voipPower = 0.0f;
+ int speexFrames = 0;
+ int wpos = 0;
+ int pos = 0;
+
+ if (samples > (clc.speexFrameSize * 12))
+ samples = (clc.speexFrameSize * 12);
+
+ // !!! FIXME: maybe separate recording from encoding, so voipPower
+ // !!! FIXME: updates faster than 4Hz?
+
+ samples -= samples % clc.speexFrameSize;
+ S_Capture(samples, (byte *) sampbuffer); // grab from audio card.
+
+ // this will probably generate multiple speex packets each time.
+ while (samples > 0) {
+ int16_t *sampptr = &sampbuffer[pos];
+ int i, bytes;
+
+ // preprocess samples to remove noise...
+ speex_preprocess_run(clc.speexPreprocessor, sampptr);
+
+ // check the "power" of this packet...
+ for (i = 0; i < clc.speexFrameSize; i++) {
+ const float flsamp = (float) sampptr[i];
+ const float s = fabs(flsamp);
+ voipPower += s * s;
+ sampptr[i] = (int16_t) ((flsamp) * audioMult);
+ }
+
+ // encode raw audio samples into Speex data...
+ speex_bits_reset(&clc.speexEncoderBits);
+ speex_encode_int(clc.speexEncoder, sampptr,
+ &clc.speexEncoderBits);
+ bytes = speex_bits_write(&clc.speexEncoderBits,
+ (char *) &clc.voipOutgoingData[wpos+1],
+ sizeof (clc.voipOutgoingData) - (wpos+1));
+ assert((bytes > 0) && (bytes < 256));
+ clc.voipOutgoingData[wpos] = (byte) bytes;
+ wpos += bytes + 1;
+
+ // look at the data for the next packet...
+ pos += clc.speexFrameSize;
+ samples -= clc.speexFrameSize;
+ speexFrames++;
+ }
+
+ clc.voipPower = (voipPower / (32768.0f * 32768.0f *
+ ((float) (clc.speexFrameSize * speexFrames)))) *
+ 100.0f;
+
+ if ((useVad) && (clc.voipPower < cl_voipVADThreshold->value)) {
+ CL_VoipNewGeneration(); // no "talk" for at least 1/4 second.
+ } else {
+ clc.voipOutgoingDataSize = wpos;
+ clc.voipOutgoingDataFrames = speexFrames;
+
+ Com_DPrintf("VoIP: Send %d frames, %d bytes, %f power\n",
+ speexFrames, wpos, clc.voipPower);
+
+ #if 0
+ static FILE *encio = NULL;
+ if (encio == NULL) encio = fopen("voip-outgoing-encoded.bin", "wb");
+ if (encio != NULL) { fwrite(clc.voipOutgoingData, wpos, 1, encio); fflush(encio); }
+ static FILE *decio = NULL;
+ if (decio == NULL) decio = fopen("voip-outgoing-decoded.bin", "wb");
+ if (decio != NULL) { fwrite(sampbuffer, speexFrames * clc.speexFrameSize * 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, qboolean 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));
+}
+
+/*
+======================
+CL_ChangeReliableCommand
+======================
+*/
+void CL_ChangeReliableCommand( void ) {
+ int r, index, l;
+
+ r = clc.reliableSequence - (random() * 5);
+ index = clc.reliableSequence & ( MAX_RELIABLE_COMMANDS - 1 );
+ l = strlen(clc.reliableCommands[ index ]);
+ if ( l >= MAX_STRING_CHARS - 1 ) {
+ l = MAX_STRING_CHARS - 2;
+ }
+ clc.reliableCommands[ index ][ l ] = '\n';
+ clc.reliableCommands[ index ][ l+1 ] = '\0';
+}
+
+/*
+=======================================================================
+
+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 = qfalse;
+ clc.spDemoRecording = qfalse;
+ Com_Printf ("Stopped demo.\n");
+}
+
+/*
+==================
+CL_DemoFilename
+==================
+*/
+void CL_DemoFilename( int number, char *fileName ) {
+ 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, MAX_OSPATH, "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
+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;
+ char *s;
+
+ if ( Cmd_Argc() > 2 ) {
+ Com_Printf ("record <demoname>\n");
+ return;
+ }
+
+ if ( clc.demorecording ) {
+ if (!clc.spDemoRecording) {
+ Com_Printf ("Already recording.\n");
+ }
+ return;
+ }
+
+ if ( cls.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 ) {
+ s = Cmd_Argv(1);
+ Q_strncpyz( demoName, s, sizeof( demoName ) );
+ Com_sprintf (name, sizeof(name), "demos/%s.dm_%d", demoName, PROTOCOL_VERSION );
+ } else {
+ int number;
+
+ // scan for a free demo name
+ for ( number = 0 ; number <= 9999 ; number++ ) {
+ CL_DemoFilename( number, demoName );
+ Com_sprintf (name, sizeof(name), "demos/%s.dm_%d", demoName, 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 = qtrue;
+ if (Cvar_VariableValue("ui_recordSPDemo")) {
+ clc.spDemoRecording = qtrue;
+ } else {
+ clc.spDemoRecording = qfalse;
+ }
+
+
+ Q_strncpyz( clc.demoName, demoName, sizeof( clc.demoName ) );
+
+ // don't start saving messages until a non-delta compressed message is received
+ clc.demowaiting = qtrue;
+
+ // 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;
+ }
+ s = cl.gameState.stringData + cl.gameState.stringOffsets[i];
+ MSG_WriteByte (&buf, svc_configstring);
+ MSG_WriteShort (&buf, i);
+ MSG_WriteBigString (&buf, s);
+ }
+
+ // baselines
+ Com_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 (&buf, &nullstate, ent, qtrue );
+ }
+
+ 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( qtrue );
+ 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 void CL_WalkDemoExt(char *arg, char *name, int *demofile)
+{
+ int i = 0;
+ *demofile = 0;
+ while(demo_protocols[i])
+ {
+ Com_sprintf (name, MAX_OSPATH, "demos/%s.dm_%d", arg, demo_protocols[i]);
+ FS_FOpenFileRead( name, demofile, qtrue );
+ if (*demofile)
+ {
+ Com_Printf("Demo file: %s\n", name);
+ break;
+ }
+ else
+ Com_Printf("Not found: %s\n", name);
+ i++;
+ }
+}
+
+/*
+====================
+CL_CompleteDemoName
+====================
+*/
+static void CL_CompleteDemoName( char *args, int argNum )
+{
+ if( argNum == 2 )
+ {
+ char demoExt[ 16 ];
+
+ Com_sprintf( demoExt, sizeof( demoExt ), ".dm_%d", PROTOCOL_VERSION );
+ Field_CompleteFilename( "demos", demoExt, qtrue );
+ }
+}
+
+/*
+====================
+CL_PlayDemo_f
+
+demo <demoname>
+
+====================
+*/
+void CL_PlayDemo_f( void ) {
+ char name[MAX_OSPATH];
+ char *arg, *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
+ arg = Cmd_Argv(1);
+
+ CL_Disconnect( qtrue );
+
+ // check for an extension .dm_?? (?? is protocol)
+ ext_test = arg + strlen(arg) - 6;
+ if ((strlen(arg) > 6) && (ext_test[0] == '.') &&
+ ((ext_test[1] == 'd') || (ext_test[1] == 'D')) &&
+ ((ext_test[2] == 'm') || (ext_test[2] == 'M')) &&
+ (ext_test[3] == '_'))
+ {
+ protocol = atoi(ext_test+4);
+ i=0;
+ while(demo_protocols[i])
+ {
+ if (demo_protocols[i] == protocol)
+ break;
+ i++;
+ }
+ if (demo_protocols[i])
+ {
+ Com_sprintf (name, sizeof(name), "demos/%s", arg);
+ FS_FOpenFileRead( name, &clc.demofile, qtrue );
+ } else {
+ Com_Printf("Protocol %d not supported for demos\n", protocol);
+ Q_strncpyz(retry, arg, sizeof(retry));
+ retry[strlen(retry)-6] = 0;
+ CL_WalkDemoExt( retry, name, &clc.demofile );
+ }
+ } else {
+ CL_WalkDemoExt( arg, name, &clc.demofile );
+ }
+
+ if (!clc.demofile) {
+ Com_Error( ERR_DROP, "couldn't open %s", name);
+ return;
+ }
+ Q_strncpyz( clc.demoName, Cmd_Argv(1), sizeof( clc.demoName ) );
+
+ Con_Close();
+
+ cls.state = CA_CONNECTED;
+ clc.demoplaying = qtrue;
+ Q_strncpyz( cls.servername, Cmd_Argv(1), sizeof( cls.servername ) );
+
+ // read demo messages until connected
+ while ( cls.state >= CA_CONNECTED && cls.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 = qfalse;
+}
+
+
+/*
+====================
+CL_StartDemoLoop
+
+Closing the main menu will restart the demo loop
+====================
+*/
+void CL_StartDemoLoop( void ) {
+ // start the demo loop again
+ Cbuf_AddText ("d1\n");
+ Key_SetCatcher( 0 );
+}
+
+/*
+==================
+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_ShutdownAll
+=====================
+*/
+void CL_ShutdownAll(void) {
+
+#ifdef USE_CURL
+ CL_cURL_Shutdown();
+#endif
+ // clear sounds
+ S_DisableSounds();
+ // shutdown CGame
+ CL_ShutdownCGame();
+ // shutdown UI
+ CL_ShutdownUI();
+
+ // shutdown the renderer
+ if ( re.Shutdown ) {
+ re.Shutdown( qfalse ); // don't destroy window or context
+ }
+
+ cls.uiStarted = qfalse;
+ cls.cgameStarted = qfalse;
+ cls.rendererStarted = qfalse;
+ cls.soundRegistered = qfalse;
+}
+
+/*
+=================
+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 ) {
+
+ // shutdown all the client stuff
+ CL_ShutdownAll();
+
+ // 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_StartHunkUsers( qfalse );
+}
+
+/*
+=====================
+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 ) {
+ cls.state = CA_DISCONNECTED;
+ Key_SetCatcher( KEYCATCH_CONSOLE );
+ return;
+ }
+
+ if ( !com_cl_running->integer ) {
+ return;
+ }
+
+ Con_Close();
+ Key_SetCatcher( 0 );
+
+ // if we are already connected to the local host, stay connected
+ if ( cls.state >= CA_CONNECTED && !Q_stricmp( cls.servername, "localhost" ) ) {
+ cls.state = CA_CONNECTED; // so the connect screen is drawn
+ Com_Memset( cls.updateInfoString, 0, sizeof( cls.updateInfoString ) );
+ Com_Memset( clc.serverMessage, 0, sizeof( clc.serverMessage ) );
+ Com_Memset( &cl.gameState, 0, sizeof( cl.gameState ) );
+ clc.lastPacketSentTime = -9999;
+ SCR_UpdateScreen();
+ } else {
+ // clear nextmap so the cinematic shutdown doesn't execute it
+ Cvar_Set( "nextmap", "" );
+ CL_Disconnect( qtrue );
+ Q_strncpyz( cls.servername, "localhost", sizeof(cls.servername) );
+ cls.state = CA_CHALLENGING; // so the connect screen is drawn
+ Key_SetCatcher( 0 );
+ SCR_UpdateScreen();
+ clc.connectTime = -RETRANSMIT_TIMEOUT;
+ NET_StringToAdr( cls.servername, &clc.serverAddress, NA_UNSPEC);
+ // we don't need a challenge on the localhost
+
+ CL_CheckForResend();
+ }
+}
+
+/*
+=====================
+CL_ClearState
+
+Called before parsing a gamestate
+=====================
+*/
+void CL_ClearState (void) {
+
+// S_StopAllSounds();
+
+ Com_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 ) );
+}
+
+
+/*
+=====================
+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( qboolean 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.speexInitialized) {
+ int i;
+ speex_bits_destroy(&clc.speexEncoderBits);
+ speex_encoder_destroy(clc.speexEncoder);
+ speex_preprocess_state_destroy(clc.speexPreprocessor);
+ for (i = 0; i < MAX_CLIENTS; i++) {
+ speex_bits_destroy(&clc.speexDecoderBits[i]);
+ speex_decoder_destroy(clc.speexDecoder[i]);
+ }
+ }
+ Cmd_RemoveCommand ("voip");
+#endif
+
+ if ( clc.demofile ) {
+ FS_FCloseFile( clc.demofile );
+ clc.demofile = 0;
+ }
+
+ if ( uivm && showMainMenu ) {
+ VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_NONE );
+ }
+
+ SCR_StopCinematic ();
+ S_ClearSoundBuffer();
+
+ // send a disconnect message to the server
+ // send it a few times in case one is dropped
+ if ( cls.state >= CA_CONNECTED ) {
+ CL_AddReliableCommand("disconnect", qtrue);
+ CL_WritePacket();
+ CL_WritePacket();
+ CL_WritePacket();
+ }
+
+ // Remove pure paks
+ FS_PureServerSetLoadedPaks("", "");
+
+ CL_ClearState ();
+
+ // wipe the client connection
+ Com_Memset( &clc, 0, sizeof( clc ) );
+
+ cls.state = CA_DISCONNECTED;
+
+ // allow cheats locally
+ Cvar_Set( "sv_cheats", "1" );
+
+ // not connected to a pure server anymore
+ cl_connectedToPureServer = qfalse;
+
+#ifdef USE_VOIP
+ // not connected to voip server anymore.
+ cl_connectedToVoipServer = qfalse;
+#endif
+
+ // Stop recording any video
+ if( CL_VideoRecording( ) ) {
+ // Finish rendering current frame
+ SCR_UpdateScreen( );
+ CL_CloseAVI( );
+ }
+ CL_UpdateGUID( NULL, 0 );
+}
+
+
+/*
+===================
+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 ) {
+ char *cmd;
+
+ cmd = Cmd_Argv(0);
+
+ // ignore key up commands
+ if ( cmd[0] == '-' ) {
+ return;
+ }
+
+ if ( clc.demoplaying || cls.state < CA_CONNECTED || cmd[0] == '+' ) {
+ Com_Printf ("Unknown command \"%s" S_COLOR_WHITE "\"\n", cmd);
+ return;
+ }
+
+ if ( Cmd_Argc() > 1 ) {
+ CL_AddReliableCommand(string, qfalse);
+ } else {
+ CL_AddReliableCommand(cmd, qfalse);
+ }
+}
+
+/*
+===================
+CL_RequestMotd
+
+===================
+*/
+void CL_RequestMotd( void ) {
+ char info[MAX_INFO_STRING];
+
+ if ( !cl_motd->integer ) {
+ return;
+ }
+ Com_Printf( "Resolving %s\n", UPDATE_SERVER_NAME );
+ if ( !NET_StringToAdr( UPDATE_SERVER_NAME, &cls.updateServer, NA_IP ) ) {
+ Com_Printf( "Couldn't resolve address\n" );
+ return;
+ }
+ cls.updateServer.port = BigShort( PORT_UPDATE );
+ Com_Printf( "%s resolved to %i.%i.%i.%i:%i\n", UPDATE_SERVER_NAME,
+ cls.updateServer.ip[0], cls.updateServer.ip[1],
+ cls.updateServer.ip[2], cls.updateServer.ip[3],
+ BigShort( cls.updateServer.port ) );
+
+ info[0] = 0;
+
+ Com_sprintf( cls.updateChallenge, sizeof( cls.updateChallenge ), "%i", ((rand() << 16) ^ rand()) ^ Com_Milliseconds());
+
+ Info_SetValueForKey( info, "challenge", cls.updateChallenge );
+ Info_SetValueForKey( info, "renderer", cls.glconfig.renderer_string );
+ Info_SetValueForKey( info, "version", com_version->string );
+
+ NET_OutOfBandPrint( NS_CLIENT, cls.updateServer, "getmotd \"%s\"\n", info );
+}
+
+/*
+===================
+CL_RequestAuthorization
+
+Authorization server protocol
+-----------------------------
+
+All commands are text in Q3 out of band packets (leading 0xff 0xff 0xff 0xff).
+
+Whenever the client tries to get a challenge from the server it wants to
+connect to, it also blindly fires off a packet to the authorize server:
+
+getKeyAuthorize <challenge> <cdkey>
+
+cdkey may be "demo"
+
+
+#OLD The authorize server returns a:
+#OLD
+#OLD keyAthorize <challenge> <accept | deny>
+#OLD
+#OLD A client will be accepted if the cdkey is valid and it has not been used by any other IP
+#OLD address in the last 15 minutes.
+
+
+The server sends a:
+
+getIpAuthorize <challenge> <ip>
+
+The authorize server returns a:
+
+ipAuthorize <challenge> <accept | deny | demo | unknown >
+
+A client will be accepted if a valid cdkey was sent by that ip (only) in the last 15 minutes.
+If no response is received from the authorize server after two tries, the client will be let
+in anyway.
+===================
+*/
+#ifndef STANDALONE
+void CL_RequestAuthorization( void ) {
+ char nums[64];
+ int i, j, l;
+ cvar_t *fs;
+
+ if ( !cls.authorizeServer.port ) {
+ Com_Printf( "Resolving %s\n", AUTHORIZE_SERVER_NAME );
+ if ( !NET_StringToAdr( AUTHORIZE_SERVER_NAME, &cls.authorizeServer, NA_IP ) ) {
+ Com_Printf( "Couldn't resolve address\n" );
+ return;
+ }
+
+ cls.authorizeServer.port = BigShort( PORT_AUTHORIZE );
+ Com_Printf( "%s resolved to %i.%i.%i.%i:%i\n", AUTHORIZE_SERVER_NAME,
+ cls.authorizeServer.ip[0], cls.authorizeServer.ip[1],
+ cls.authorizeServer.ip[2], cls.authorizeServer.ip[3],
+ BigShort( cls.authorizeServer.port ) );
+ }
+ if ( cls.authorizeServer.type == NA_BAD ) {
+ return;
+ }
+
+ // only grab the alphanumeric values from the cdkey, to avoid any dashes or spaces
+ j = 0;
+ l = strlen( cl_cdkey );
+ if ( l > 32 ) {
+ l = 32;
+ }
+ for ( i = 0 ; i < l ; i++ ) {
+ if ( ( cl_cdkey[i] >= '0' && cl_cdkey[i] <= '9' )
+ || ( cl_cdkey[i] >= 'a' && cl_cdkey[i] <= 'z' )
+ || ( cl_cdkey[i] >= 'A' && cl_cdkey[i] <= 'Z' )
+ ) {
+ nums[j] = cl_cdkey[i];
+ j++;
+ }
+ }
+ nums[j] = 0;
+
+ fs = Cvar_Get ("cl_anonymous", "0", CVAR_INIT|CVAR_SYSTEMINFO );
+
+ NET_OutOfBandPrint(NS_CLIENT, cls.authorizeServer, "getKeyAuthorize %i %s", fs->integer, nums );
+}
+#endif
+/*
+======================================================================
+
+CONSOLE COMMANDS
+
+======================================================================
+*/
+
+/*
+==================
+CL_ForwardToServer_f
+==================
+*/
+void CL_ForwardToServer_f( void ) {
+ if ( cls.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(), qfalse);
+ }
+}
+
+/*
+==================
+CL_Disconnect_f
+==================
+*/
+void CL_Disconnect_f( void ) {
+ SCR_StopCinematic();
+ Cvar_Set("ui_singlePlayerActive", "0");
+ if ( cls.state != CA_DISCONNECTED && cls.state != CA_CINEMATIC ) {
+ Com_Error (ERR_DISCONNECT, "Disconnected from server");
+ }
+}
+
+
+/*
+================
+CL_Reconnect_f
+
+================
+*/
+void CL_Reconnect_f( void ) {
+ if ( !strlen( cls.servername ) || !strcmp( cls.servername, "localhost" ) ) {
+ Com_Printf( "Can't reconnect to localhost.\n" );
+ return;
+ }
+ Cvar_Set("ui_singlePlayerActive", "0");
+ Cbuf_AddText( va("connect %s\n", cls.servername ) );
+}
+
+/*
+================
+CL_Connect_f
+
+================
+*/
+void CL_Connect_f( void ) {
+ char *server;
+ const char *serverString;
+ int argc = Cmd_Argc();
+ netadrtype_t family = NA_UNSPEC;
+
+ if ( argc != 2 && argc != 3 ) {
+ Com_Printf( "usage: connect [-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);
+ }
+
+ Cvar_Set("ui_singlePlayerActive", "0");
+
+ // fire a message off to the motd server
+ CL_RequestMotd();
+
+ // 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 );
+
+ CL_Disconnect( qtrue );
+ Con_Close();
+
+ Q_strncpyz( cls.servername, server, sizeof(cls.servername) );
+
+ if (!NET_StringToAdr(cls.servername, &clc.serverAddress, family) ) {
+ Com_Printf ("Bad server address\n");
+ cls.state = CA_DISCONNECTED;
+ return;
+ }
+ if (clc.serverAddress.port == 0) {
+ clc.serverAddress.port = BigShort( PORT_SERVER );
+ }
+
+ serverString = NET_AdrToStringwPort(clc.serverAddress);
+
+ Com_Printf( "%s resolved to %s\n", cls.servername, serverString);
+
+ if( cl_guidServerUniq->integer )
+ CL_UpdateGUID( serverString, strlen( serverString ) );
+ else
+ CL_UpdateGUID( NULL, 0 );
+
+ // if we aren't playing on a lan, we need to authenticate
+ // with the cd key
+ if(NET_IsLocalAddress(clc.serverAddress))
+ cls.state = CA_CHALLENGING;
+ else
+ {
+ cls.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( 0 );
+ clc.connectTime = -99999; // CL_CheckForResend() will fire immediately
+ clc.connectPacketCount = 0;
+
+ // server connection string
+ Cvar_Set( "cl_currentServerAddress", server );
+}
+
+#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, qtrue, qtrue );
+ }
+}
+
+/*
+=====================
+CL_Rcon_f
+
+ Send the rest of the command line over as
+ an unconnected command.
+=====================
+*/
+void CL_Rcon_f( void ) {
+ char message[MAX_RCON_MESSAGE];
+ netadr_t to;
+
+ if ( !rcon_client_password->string ) {
+ 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 ( cls.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
+=================
+*/
+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, qfalse);
+}
+
+/*
+=================
+CL_ResetPureClientAtServer
+=================
+*/
+void CL_ResetPureClientAtServer( void ) {
+ CL_AddReliableCommand("vdr", qfalse);
+}
+
+/*
+=================
+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
+=================
+*/
+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();
+ // 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
+ FS_ConditionalRestart( clc.checksumFeed );
+
+ cls.rendererStarted = qfalse;
+ cls.uiStarted = qfalse;
+ cls.cgameStarted = qfalse;
+ cls.soundRegistered = qfalse;
+
+ // unpause so the cgame definately gets a snapshot and renders a frame
+ Cvar_Set( "cl_paused", "0" );
+
+ // if not running a server clear the whole hunk
+ if ( !com_sv_running->integer ) {
+ // clear the whole hunk
+ Hunk_Clear();
+ }
+ else {
+ // clear all the client data on the hunk
+ Hunk_ClearToMark();
+ }
+
+ // initialize the renderer interface
+ CL_InitRef();
+
+ // startup all the client stuff
+ CL_StartHunkUsers( qfalse );
+
+ // start the cgame if connected
+ if ( cls.state > CA_CONNECTED && cls.state != CA_CINEMATIC ) {
+ cls.cgameStarted = qtrue;
+ CL_InitCGame();
+ // send pure checksums
+ CL_SendPureChecksums();
+ }
+}
+
+/*
+=================
+CL_Snd_Restart
+
+Restart the sound subsystem
+=================
+*/
+void CL_Snd_Restart(void)
+{
+ S_Shutdown();
+ S_Init();
+}
+
+/*
+=================
+CL_Snd_Restart_f
+
+Restart the sound subsystem
+The cgame and game must also be forced to restart because
+handles will be invalid
+=================
+*/
+void CL_Snd_Restart_f(void)
+{
+ CL_Snd_Restart();
+ CL_Vid_Restart_f();
+}
+
+
+/*
+==================
+CL_PK3List_f
+==================
+*/
+void CL_OpenedPK3List_f( void ) {
+ Com_Printf("Opened PK3 Names: %s\n", FS_LoadedPakNames());
+}
+
+/*
+==================
+CL_PureList_f
+==================
+*/
+void CL_ReferencedPK3List_f( void ) {
+ Com_Printf("Referenced PK3 Names: %s\n", FS_ReferencedPakNames());
+}
+
+/*
+==================
+CL_Configstrings_f
+==================
+*/
+void CL_Configstrings_f( void ) {
+ int i;
+ int ofs;
+
+ if ( cls.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
+==============
+*/
+void CL_Clientinfo_f( void ) {
+ Com_Printf( "--------- Client Information ---------\n" );
+ Com_Printf( "state: %i\n", cls.state );
+ Com_Printf( "Server: %s\n", cls.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
+=================
+*/
+void CL_DownloadsComplete( void ) {
+
+#ifdef USE_CURL
+ // if we downloaded with cURL
+ if(clc.cURLUsed) {
+ clc.cURLUsed = qfalse;
+ CL_cURL_Shutdown();
+ if( clc.cURLDisconnected ) {
+ if(clc.downloadRestart) {
+ FS_Restart(clc.checksumFeed);
+ clc.downloadRestart = qfalse;
+ }
+ clc.cURLDisconnected = qfalse;
+ CL_Reconnect_f();
+ return;
+ }
+ }
+#endif
+
+ // if we downloaded files we need to restart the file system
+ if (clc.downloadRestart) {
+ clc.downloadRestart = qfalse;
+
+ 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", qfalse);
+
+ // by sending the donedl command we request a new gamestate
+ // so we don't want to load stuff yet
+ return;
+ }
+
+ // let the client game init and load data
+ cls.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 ( cls.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 = qtrue;
+ 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.
+=================
+*/
+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;
+
+ CL_AddReliableCommand(va("download %s", remoteName), qfalse);
+}
+
+/*
+=================
+CL_NextDownload
+
+A download completed or failed
+=================
+*/
+void CL_NextDownload(void)
+{
+ char *s;
+ char *remoteName, *localName;
+ qboolean useCURL = qfalse;
+
+ // A download has finished, check whether this matches a referenced checksum
+ if(*clc.downloadName)
+ {
+ 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) {
+ 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
+#ifdef USE_CURL
+ if(!(cl_allowDownload->integer & DLF_NO_REDIRECT)) {
+ 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 = qtrue;
+ }
+ }
+ 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);
+ }
+#endif /* USE_CURL */
+ if(!useCURL) {
+ if((cl_allowDownload->integer & DLF_NO_UDP)) {
+ Com_Error(ERR_DROP, "UDP Downloads are "
+ "disabled on your client. "
+ "(cl_allowDownload is %d)",
+ cl_allowDownload->integer);
+ return;
+ }
+ else {
+ CL_BeginDownload( localName, remoteName );
+ }
+ }
+ clc.downloadRestart = qtrue;
+
+ // 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) {
+ char missingfiles[1024];
+
+ if ( !(cl_allowDownload->integer & DLF_ENABLE) )
+ {
+ // autodownload is disabled on the client
+ // but it's possible that some referenced files on the server are missing
+ if (FS_ComparePaks( missingfiles, sizeof( missingfiles ), qfalse ) )
+ {
+ // NOTE TTimo I would rather have that printed as a modal message box
+ // but at this point while joining the game we don't know wether we will successfully join or not
+ Com_Printf( "\nWARNING: You are missing some files referenced by the server:\n%s"
+ "You might not be able to join the game\n"
+ "Go to the setting menu to turn on autodownload, or get the file elsewhere\n\n", missingfiles );
+ }
+ }
+ else if ( FS_ComparePaks( clc.downloadList, sizeof( clc.downloadList ) , qtrue ) ) {
+
+ Com_Printf("Need paks: %s\n", clc.downloadList );
+
+ if ( *clc.downloadList ) {
+ // if autodownloading is not enabled on the server
+ cls.state = CA_CONNECTED;
+
+ *clc.downloadTempName = *clc.downloadName = 0;
+ Cvar_Set( "cl_downloadName", "" );
+
+ CL_NextDownload();
+ return;
+ }
+
+ }
+
+ CL_DownloadsComplete();
+}
+
+/*
+=================
+CL_CheckForResend
+
+Resend a connect message if the last one has timed out
+=================
+*/
+void CL_CheckForResend( void ) {
+ int port, i;
+ char info[MAX_INFO_STRING];
+ char data[MAX_INFO_STRING];
+
+ // don't send anything if playing back a demo
+ if ( clc.demoplaying ) {
+ return;
+ }
+
+ // resend if we haven't gotten a reply yet
+ if ( cls.state != CA_CONNECTING && cls.state != CA_CHALLENGING ) {
+ return;
+ }
+
+ if ( cls.realtime - clc.connectTime < RETRANSMIT_TIMEOUT ) {
+ return;
+ }
+
+ clc.connectTime = cls.realtime; // for retransmit requests
+ clc.connectPacketCount++;
+
+
+ switch ( cls.state ) {
+ case CA_CONNECTING:
+ // requesting a challenge .. IPv6 users always get in as authorize server supports no ipv6.
+#ifndef STANDALONE
+ if (!Cvar_VariableIntegerValue("com_standalone") && clc.serverAddress.type == NA_IP && !Sys_IsLANAddress( clc.serverAddress ) )
+ CL_RequestAuthorization();
+#endif
+
+ // The challenge request shall be followed by a client challenge so no malicious server can hijack this connection.
+ Com_sprintf(data, sizeof(data), "getchallenge %d", clc.challenge);
+
+ 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", PROTOCOL_VERSION ) );
+ Info_SetValueForKey( info, "qport", va("%i", port ) );
+ Info_SetValueForKey( info, "challenge", va("%i", clc.challenge ) );
+
+ strcpy(data, "connect ");
+ // TTimo adding " " around the userinfo string to avoid truncated userinfo on the server
+ // (Com_TokenizeString tokenizes around spaces)
+ data[8] = '"';
+
+ for(i=0;i<strlen(info);i++) {
+ data[9+i] = info[i]; // + (clc.challenge)&0x3;
+ }
+ data[9+i] = '"';
+ data[10+i] = 0;
+
+ // NOTE TTimo don't forget to set the right data length!
+ NET_OutOfBandData( NS_CLIENT, clc.serverAddress, (byte *) &data[0], i+10 );
+ // 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 cls.state" );
+ }
+}
+
+/*
+===================
+CL_DisconnectPacket
+
+Sometimes the server can drop the client and the netchan based
+disconnect can be lost. If the client continues to send packets
+to the server, the server will send out of band disconnect packets
+to the client so it doesn't have to wait for the full timeout period.
+===================
+*/
+void CL_DisconnectPacket( netadr_t from ) {
+ if ( cls.state < CA_AUTHORIZING ) {
+ return;
+ }
+
+ // if not from our server, ignore it
+ if ( !NET_CompareAdr( from, clc.netchan.remoteAddress ) ) {
+ return;
+ }
+
+ // if we have received packets within three seconds, ignore it
+ // (it might be a malicious spoof)
+ if ( cls.realtime - clc.lastPacketTime < 3000 ) {
+ return;
+ }
+
+ // drop the connection
+ Com_Printf( "Server disconnected for unknown reason\n" );
+ Cvar_Set("com_errorMessage", "Server disconnected for unknown reason\n" );
+ CL_Disconnect( qtrue );
+}
+
+
+/*
+===================
+CL_MotdPacket
+
+===================
+*/
+void CL_MotdPacket( netadr_t from ) {
+ char *challenge;
+ char *info;
+
+ // if not from our server, ignore it
+ if ( !NET_CompareAdr( from, cls.updateServer ) ) {
+ return;
+ }
+
+ info = Cmd_Argv(1);
+
+ // check challenge
+ challenge = Info_ValueForKey( info, "challenge" );
+ if ( strcmp( challenge, cls.updateChallenge ) ) {
+ return;
+ }
+
+ challenge = Info_ValueForKey( info, "motd" );
+
+ Q_strncpyz( cls.updateInfoString, info, sizeof( cls.updateInfoString ) );
+ Cvar_Set( "cl_motdString", challenge );
+}
+
+/*
+===================
+CL_InitServerInfo
+===================
+*/
+void CL_InitServerInfo( serverInfo_t *server, netadr_t *address ) {
+ server->adr = *address;
+ server->clients = 0;
+ server->hostName[0] = '\0';
+ server->mapName[0] = '\0';
+ server->maxClients = 0;
+ server->maxPing = 0;
+ server->minPing = 0;
+ server->ping = -1;
+ server->game[0] = '\0';
+ server->gameType = 0;
+ server->netType = 0;
+}
+
+#define MAX_SERVERSPERPACKET 256
+
+/*
+===================
+CL_ServersResponsePacket
+===================
+*/
+void CL_ServersResponsePacket( const netadr_t* from, msg_t *msg, qboolean extended ) {
+ int i, count, total;
+ netadr_t addresses[MAX_SERVERSPERPACKET];
+ int numservers;
+ byte* buffptr;
+ byte* buffend;
+
+ Com_Printf("CL_ServersResponsePacket\n");
+
+ if (cls.numglobalservers == -1) {
+ // state to detect lack of servers or lack of response
+ cls.numglobalservers = 0;
+ cls.numGlobalServerAddresses = 0;
+ }
+
+ // parse through server response string
+ numservers = 0;
+ buffptr = msg->data;
+ buffend = buffptr + msg->cursize;
+
+ // advance to initial token
+ do
+ {
+ if(*buffptr == '\\' || (extended && *buffptr == '/'))
+ break;
+
+ buffptr++;
+ } while (buffptr < buffend);
+
+ 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;
+
+ 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] );
+ // 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_ConnectionlessPacket
+
+Responses to broadcasts, etc
+=================
+*/
+void CL_ConnectionlessPacket( netadr_t from, msg_t *msg ) {
+ char *s;
+ char *c;
+
+ MSG_BeginReadingOOB( msg );
+ MSG_ReadLong( msg ); // skip the -1
+
+ s = MSG_ReadStringLine( msg );
+
+ Cmd_TokenizeString( s );
+
+ 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"))
+ {
+ if (cls.state != CA_CONNECTING)
+ {
+ Com_DPrintf("Unwanted challenge response received. Ignored.\n");
+ return;
+ }
+
+ if(!NET_CompareAdr(from, clc.serverAddress))
+ {
+ // This challenge response is not coming from the expected address.
+ // Check whether we have a matching client challenge to prevent
+ // connection hi-jacking.
+
+ c = Cmd_Argv(2);
+
+ if(!*c || atoi(c) != clc.challenge)
+ {
+ Com_DPrintf("Challenge response received from unexpected source. Ignored.\n");
+ return;
+ }
+ }
+
+ // start sending challenge response instead of challenge request packets
+ clc.challenge = atoi(Cmd_Argv(1));
+ cls.state = CA_CHALLENGING;
+ clc.connectPacketCount = 0;
+ clc.connectTime = -99999;
+
+ // 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 ( cls.state >= CA_CONNECTED ) {
+ Com_Printf ("Dup connect received. Ignored.\n");
+ return;
+ }
+ if ( cls.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;
+ }
+ Netchan_Setup (NS_CLIENT, &clc.netchan, from, Cvar_VariableValue( "net_qport" ) );
+ cls.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;
+ }
+
+ // a disconnect message from the server, which will happen if the server
+ // dropped the connection but it is still getting packets from us
+ if (!Q_stricmp(c, "disconnect")) {
+ CL_DisconnectPacket( from );
+ return;
+ }
+
+ // echo request from server
+ if ( !Q_stricmp(c, "echo") ) {
+ NET_OutOfBandPrint( NS_CLIENT, from, "%s", Cmd_Argv(1) );
+ return;
+ }
+
+ // cd check
+ if ( !Q_stricmp(c, "keyAuthorize") ) {
+ // we don't use these now, so dump them on the floor
+ return;
+ }
+
+ // global MOTD from id
+ if ( !Q_stricmp(c, "motd") ) {
+ CL_MotdPacket( from );
+ return;
+ }
+
+ // echo request from server
+ if ( !Q_stricmp(c, "print") ) {
+ s = MSG_ReadString( msg );
+ Q_strncpyz( clc.serverMessage, s, sizeof( clc.serverMessage ) );
+ 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, qfalse );
+ return;
+ }
+
+ // list of servers sent back by a master server (extended)
+ if ( !Q_strncmp(c, "getserversExtResponse", 21) ) {
+ CL_ServersResponsePacket( &from, msg, qtrue );
+ 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 ( cls.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_CheckTimeout
+
+==================
+*/
+void CL_CheckTimeout( void ) {
+ //
+ // check timeout
+ //
+ if ( ( !CL_CheckPaused() || !sv_paused->integer )
+ && cls.state >= CA_CONNECTED && cls.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( qtrue );
+ return;
+ }
+ } else {
+ cl.timeoutcount = 0;
+ }
+}
+
+/*
+==================
+CL_CheckPaused
+Check whether client has been paused.
+==================
+*/
+qboolean 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 qtrue;
+
+ return qfalse;
+}
+
+//============================================================================
+
+/*
+==================
+CL_CheckUserinfo
+
+==================
+*/
+void CL_CheckUserinfo( void ) {
+ // don't add reliable commands when not yet connected
+ if(cls.state < CA_CHALLENGING)
+ 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 ) ), qfalse);
+ }
+}
+
+/*
+==================
+CL_Frame
+
+==================
+*/
+void CL_Frame ( int msec ) {
+
+ if ( !com_cl_running->integer ) {
+ return;
+ }
+
+#ifdef USE_CURL
+ if(clc.downloadCURLM) {
+ CL_cURL_PerformDownload();
+ // we can't process frames normally when in disconnected
+ // download mode since the ui vm expects cls.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;
+ }
+ }
+#endif
+
+ if ( cls.cddialog ) {
+ // bring up the cd error dialog if needed
+ cls.cddialog = qfalse;
+ VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_NEED_CD );
+ } else if ( cls.state == CA_DISCONNECTED && !( Key_GetCatcher( ) & KEYCATCH_UI )
+ && !com_sv_running->integer && uivm ) {
+ // if disconnected, bring up the menu
+ S_StopAllSounds();
+ VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_MAIN );
+ }
+
+ // if recording an avi, lock to a fixed fps
+ if ( CL_VideoRecording( ) && cl_aviFrameRate->integer && msec) {
+ // save the current screen
+ if ( cls.state == CA_ACTIVE || cl_forceavidemo->integer) {
+ CL_TakeVideoFrame( );
+
+ // fixed time for next frame'
+ msec = (int)ceil( (1000.0f / cl_aviFrameRate->value) * com_timescale->value );
+ if (msec == 0) {
+ msec = 1;
+ }
+ }
+ }
+
+ if( cl_autoRecordDemo->integer ) {
+ if( cls.state == CA_ACTIVE && !clc.demorecording && !clc.demoplaying ) {
+ // If not recording a demo, and we should be, start one
+ qtime_t now;
+ char *nowString;
+ char *p;
+ char mapName[ MAX_QPATH ];
+ char serverName[ MAX_OSPATH ];
+
+ Com_RealTime( &now );
+ 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 );
+
+ Q_strncpyz( serverName, cls.servername, MAX_OSPATH );
+ // Replace the ":" in the address as it is not a valid
+ // file name character
+ p = strstr( serverName, ":" );
+ if( p ) {
+ *p = '.';
+ }
+
+ 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", nowString, serverName, mapName ) );
+ }
+ else if( cls.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, 0 );
+ }
+
+ // 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
+================
+*/
+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);
+
+ if ( print_level == PRINT_ALL ) {
+ Com_Printf ("%s", msg);
+ } else if ( print_level == PRINT_WARNING ) {
+ Com_Printf (S_COLOR_YELLOW "%s", msg); // yellow
+ } else if ( print_level == PRINT_DEVELOPER ) {
+ Com_DPrintf (S_COLOR_RED "%s", msg); // red
+ }
+}
+
+
+
+/*
+============
+CL_ShutdownRef
+============
+*/
+void CL_ShutdownRef( void ) {
+ if ( !re.Shutdown ) {
+ return;
+ }
+ re.Shutdown( qtrue );
+ Com_Memset( &re, 0, sizeof( re ) );
+}
+
+/*
+============
+CL_InitRenderer
+============
+*/
+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( qboolean rendererOnly ) {
+ if (!com_cl_running) {
+ return;
+ }
+
+ if ( !com_cl_running->integer ) {
+ return;
+ }
+
+ if ( !cls.rendererStarted ) {
+ cls.rendererStarted = qtrue;
+ CL_InitRenderer();
+ }
+
+ if ( rendererOnly ) {
+ return;
+ }
+
+ if ( !cls.soundStarted ) {
+ cls.soundStarted = qtrue;
+ S_Init();
+ }
+
+ if ( !cls.soundRegistered ) {
+ cls.soundRegistered = qtrue;
+ S_BeginRegistration();
+ }
+
+ if( com_dedicated->integer ) {
+ return;
+ }
+
+ if ( !cls.uiStarted ) {
+ cls.uiStarted = qtrue;
+ CL_InitUI();
+ }
+}
+
+/*
+============
+CL_RefMalloc
+============
+*/
+void *CL_RefMalloc( int size ) {
+ return Z_TagMalloc( size, TAG_RENDERER );
+}
+
+int CL_ScaledMilliseconds(void) {
+ return Sys_Milliseconds()*com_timescale->value;
+}
+
+/*
+============
+CL_InitRef
+============
+*/
+void CL_InitRef( void ) {
+ refimport_t ri;
+ refexport_t *ret;
+
+ Com_Printf( "----- Initializing Renderer ----\n" );
+
+ 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_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_CheckRange = Cvar_CheckRange;
+
+ // cinematic stuff
+
+ ri.CIN_UploadCinematic = CIN_UploadCinematic;
+ ri.CIN_PlayCinematic = CIN_PlayCinematic;
+ ri.CIN_RunCinematic = CIN_RunCinematic;
+
+ ri.CL_WriteAVIVideoFrame = CL_WriteAVIVideoFrame;
+
+ 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" );
+}
+
+
+//===========================================================================================
+
+
+void CL_SetModel_f( void ) {
+ char *arg;
+ char name[256];
+
+ 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]
+===============
+*/
+void CL_Video_f( void )
+{
+ char filename[ MAX_OSPATH ];
+ int i, last;
+
+ 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
+ {
+ // 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
+===============
+*/
+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" );
+ }
+}
+
+/*
+====================
+CL_Init
+====================
+*/
+void CL_Init( void ) {
+ Com_Printf( "----- Client Initialization -----\n" );
+
+ Con_Init ();
+
+ CL_ClearState ();
+
+ cls.state = CA_DISCONNECTED; // no longer CA_UNINITIALIZED
+
+ cls.realtime = 0;
+
+ 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 );
+
+ cl_showMouseRate = Cvar_Get ("cl_showmouserate", "0", 0);
+
+ cl_allowDownload = Cvar_Get ("cl_allowDownload", "0", CVAR_ARCHIVE);
+#ifdef USE_CURL
+ cl_cURLLib = Cvar_Get("cl_cURLLib", DEFAULT_CURL_LIB, CVAR_ARCHIVE);
+#endif
+
+ cl_conXOffset = Cvar_Get ("cl_conXOffset", "0", 0);
+#ifdef MACOS_X
+ // 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);
+
+ // init autoswitch so the ui will have it correctly even
+ // if the cgame hasn't been started
+ Cvar_Get ("cg_autoswitch", "1", CVAR_ARCHIVE);
+
+ 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 MACOS_X
+ // 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
+
+ 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);
+
+ // ~ and `, as keys and characters
+ cl_consoleKeys = Cvar_Get( "cl_consoleKeys", "~ ` 0x7e 0x60", CVAR_ARCHIVE);
+
+ // userinfo
+ Cvar_Get ("name", "UnnamedPlayer", CVAR_USERINFO | CVAR_ARCHIVE );
+ Cvar_Get ("rate", "25000", CVAR_USERINFO | CVAR_ARCHIVE );
+ Cvar_Get ("snaps", "20", CVAR_USERINFO | CVAR_ARCHIVE );
+ Cvar_Get ("model", "sarge", CVAR_USERINFO | CVAR_ARCHIVE );
+ Cvar_Get ("headmodel", "sarge", CVAR_USERINFO | CVAR_ARCHIVE );
+ Cvar_Get ("team_model", "james", CVAR_USERINFO | CVAR_ARCHIVE );
+ Cvar_Get ("team_headmodel", "*james", CVAR_USERINFO | CVAR_ARCHIVE );
+ Cvar_Get ("g_redTeam", "Stroggs", CVAR_SERVERINFO | CVAR_ARCHIVE);
+ Cvar_Get ("g_blueTeam", "Pagans", CVAR_SERVERINFO | 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 ("teamtask", "0", CVAR_USERINFO );
+ Cvar_Get ("sex", "male", CVAR_USERINFO | CVAR_ARCHIVE );
+ Cvar_Get ("cl_anonymous", "0", CVAR_USERINFO | CVAR_ARCHIVE );
+
+ Cvar_Get ("password", "", CVAR_USERINFO);
+ Cvar_Get ("cg_predictItems", "1", CVAR_USERINFO | CVAR_ARCHIVE );
+
+#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", "all", 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);
+
+ // This is a protocol version number.
+ cl_voip = Cvar_Get ("cl_voip", "1", CVAR_USERINFO | CVAR_ARCHIVE | CVAR_LATCH);
+ Cvar_CheckRange( cl_voip, 0, 1, qtrue );
+
+ // 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->integer) && (Cvar_VariableIntegerValue("rate") < 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' and restart.\n");
+ Com_Printf("Until then, VoIP is disabled.\n");
+ Cvar_Set("cl_voip", "0");
+ }
+#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 );
+ CL_InitRef();
+
+ SCR_Init ();
+
+// Cbuf_Execute ();
+
+ Cvar_Set( "cl_running", "1" );
+
+ CL_GenerateQKey();
+ Cvar_Get( "cl_guid", "", CVAR_USERINFO | CVAR_ROM );
+ CL_UpdateGUID( NULL, 0 );
+
+ Com_Printf( "----- Client Initialization Complete -----\n" );
+}
+
+
+/*
+===============
+CL_Shutdown
+
+===============
+*/
+void CL_Shutdown( void ) {
+ static qboolean recursive = qfalse;
+
+ // check whether the client is running at all.
+ if(!(com_cl_running && com_cl_running->integer))
+ return;
+
+ Com_Printf( "----- CL_Shutdown -----\n" );
+
+ if ( recursive ) {
+ Com_Printf( "WARNING: Recursive shutdown\n" );
+ return;
+ }
+ recursive = qtrue;
+
+ CL_Disconnect( qtrue );
+
+ S_Shutdown();
+ CL_ShutdownRef();
+
+ CL_ShutdownUI();
+
+ Cmd_RemoveCommand ("cmd");
+ Cmd_RemoveCommand ("configstrings");
+ Cmd_RemoveCommand ("userinfo");
+ 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 ("localservers");
+ Cmd_RemoveCommand ("globalservers");
+ Cmd_RemoveCommand ("rcon");
+ Cmd_RemoveCommand ("ping");
+ Cmd_RemoveCommand ("serverstatus");
+ Cmd_RemoveCommand ("showip");
+ Cmd_RemoveCommand ("model");
+ Cmd_RemoveCommand ("video");
+ Cmd_RemoveCommand ("stopvideo");
+
+ Cvar_Set( "cl_running", "0" );
+
+ recursive = qfalse;
+
+ Com_Memset( &cls, 0, sizeof( cls ) );
+ Key_SetCatcher( 0 );
+
+ Com_Printf( "-----------------------\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_NAME_LENGTH);
+ Q_strncpyz(server->mapName, Info_ValueForKey(info, "mapname"), MAX_NAME_LENGTH);
+ server->maxClients = atoi(Info_ValueForKey(info, "sv_maxclients"));
+ Q_strncpyz(server->game,Info_ValueForKey(info, "game"), MAX_NAME_LENGTH);
+ 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"));
+ server->punkbuster = atoi(Info_ValueForKey(info, "punkbuster"));
+ }
+ 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
+===================
+*/
+void CL_ServerInfoPacket( netadr_t from, msg_t *msg ) {
+ int i, type;
+ char info[MAX_INFO_STRING];
+ char *infoString;
+ int prot;
+
+ infoString = MSG_ReadString( msg );
+
+ // if this isn't the correct protocol version, ignore it
+ prot = atoi( Info_ValueForKey( infoString, "protocol" ) );
+ if ( prot != PROTOCOL_VERSION ) {
+ 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;
+ cls.localServers[i].adr = from;
+ cls.localServers[i].clients = 0;
+ cls.localServers[i].hostName[0] = '\0';
+ cls.localServers[i].mapName[0] = '\0';
+ cls.localServers[i].maxClients = 0;
+ cls.localServers[i].maxPing = 0;
+ cls.localServers[i].minPing = 0;
+ cls.localServers[i].ping = -1;
+ cls.localServers[i].game[0] = '\0';
+ cls.localServers[i].gameType = 0;
+ cls.localServers[i].netType = from.type;
+ cls.localServers[i].punkbuster = 0;
+
+ Q_strncpyz( info, MSG_ReadString( msg ), MAX_INFO_STRING );
+ if (strlen(info)) {
+ if (info[strlen(info)-1] != '\n') {
+ strncat(info, "\n", sizeof(info) - 1);
+ }
+ Com_Printf( "%s: %s", NET_AdrToStringwPort( from ), info );
+ }
+}
+
+/*
+===================
+CL_GetServerStatus
+===================
+*/
+serverStatus_t *CL_GetServerStatus( netadr_t from ) {
+ serverStatus_t *serverStatus;
+ int i, oldest, oldestTime;
+
+ serverStatus = NULL;
+ 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;
+ }
+ }
+ if (oldest != -1) {
+ return &cl_serverStatusList[oldest];
+ }
+ serverStatusCount++;
+ return &cl_serverStatusList[serverStatusCount & (MAX_SERVERSTATUSREQUESTS-1)];
+}
+
+/*
+===================
+CL_ServerStatus
+===================
+*/
+int 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 = qtrue;
+ }
+ return qfalse;
+ }
+ // get the address
+ if ( !NET_StringToAdr( serverAddress, &to, NA_UNSPEC) ) {
+ return qfalse;
+ }
+ serverStatus = CL_GetServerStatus( to );
+ // if no server status string then reset the server status request for this address
+ if ( !serverStatusString ) {
+ serverStatus->retrieved = qtrue;
+ return qfalse;
+ }
+
+ // if this server status request has the same address
+ if ( NET_CompareAdr( to, serverStatus->address) ) {
+ // if we recieved an response for this server status request
+ if (!serverStatus->pending) {
+ Q_strncpyz(serverStatusString, serverStatus->string, maxLen);
+ serverStatus->retrieved = qtrue;
+ serverStatus->startTime = 0;
+ return qtrue;
+ }
+ // resend the request regularly
+ else if ( serverStatus->startTime < Com_Milliseconds() - cl_serverStatusResendTime->integer ) {
+ serverStatus->print = qfalse;
+ serverStatus->pending = qtrue;
+ serverStatus->retrieved = qfalse;
+ serverStatus->time = 0;
+ serverStatus->startTime = Com_Milliseconds();
+ NET_OutOfBandPrint( NS_CLIENT, to, "getstatus" );
+ return qfalse;
+ }
+ }
+ // if retrieved
+ else if ( serverStatus->retrieved ) {
+ serverStatus->address = to;
+ serverStatus->print = qfalse;
+ serverStatus->pending = qtrue;
+ serverStatus->retrieved = qfalse;
+ serverStatus->startTime = Com_Milliseconds();
+ serverStatus->time = 0;
+ NET_OutOfBandPrint( NS_CLIENT, to, "getstatus" );
+ return qfalse;
+ }
+ return qfalse;
+}
+
+/*
+===================
+CL_ServerStatusResponse
+===================
+*/
+void CL_ServerStatusResponse( netadr_t from, msg_t *msg ) {
+ char *s;
+ 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;
+ }
+
+ 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 = qfalse;
+ if (serverStatus->print) {
+ serverStatus->retrieved = qtrue;
+ }
+}
+
+/*
+==================
+CL_LocalServers_f
+==================
+*/
+void CL_LocalServers_f( void ) {
+ 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++) {
+ qboolean b = cls.localServers[i].visible;
+ Com_Memset(&cls.localServers[i], 0, sizeof(cls.localServers[i]));
+ cls.localServers[i].visible = b;
+ }
+ Com_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
+==================
+*/
+void CL_GlobalServers_f( void ) {
+ netadr_t to;
+ int count, i, masterNum;
+ char command[1024], *masteraddress;
+ char *cmdname;
+
+ if ((count = Cmd_Argc()) < 3 || (masterNum = atoi(Cmd_Argv(1))) < 0 || masterNum > 4)
+ {
+ Com_Printf( "usage: globalservers <master# 0-4> <protocol> [keywords]\n");
+ return;
+ }
+
+ sprintf(command, "sv_master%d", masterNum + 1);
+ masteraddress = Cvar_VariableString(command);
+
+ if(!*masteraddress)
+ {
+ Com_Printf( "CL_GlobalServers_f: Error: No master server address given.\n");
+ return;
+ }
+
+ // reset the list, waiting for response
+ // -1 is used to distinguish a "no response"
+
+ i = NET_StringToAdr(masteraddress, &to, NA_UNSPEC);
+
+ if(!i)
+ {
+ Com_Printf( "CL_GlobalServers_f: Error: could not resolve address of master %s\n", masteraddress);
+ return;
+ }
+ else if(i == 2)
+ to.port = BigShort(PORT_MASTER);
+
+ Com_Printf("Requesting servers from master %s...\n", masteraddress);
+
+ cls.numglobalservers = -1;
+ cls.pingUpdateSource = AS_GLOBAL;
+
+ // Use the extended query for IPv6 masters
+ if (to.type == NA_IP6 || to.type == NA_MULTICAST6)
+ {
+ cmdname = "getserversExt " GAMENAME_FOR_MASTER;
+
+ // TODO: test if we only have an IPv6 connection. If it's the case,
+ // request IPv6 servers only by appending " ipv6" to the command
+ }
+ else
+ cmdname = "getservers";
+ Com_sprintf( command, sizeof(command), "%s %s", cmdname, Cmd_Argv(2) );
+
+ 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 );
+}
+
+
+/*
+==================
+CL_GetPing
+==================
+*/
+void CL_GetPing( int n, char *buf, int buflen, int *pingtime )
+{
+ const char *str;
+ int time;
+ int maxPing;
+
+ if (!cl_pinglist[n].adr.port)
+ {
+ // empty 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_UpdateServerInfo
+==================
+*/
+void CL_UpdateServerInfo( int n )
+{
+ if (!cl_pinglist[n].adr.port)
+ {
+ return;
+ }
+
+ CL_SetServerInfoByAddress(cl_pinglist[n].adr, cl_pinglist[n].info, cl_pinglist[n].time );
+}
+
+/*
+==================
+CL_GetPingInfo
+==================
+*/
+void CL_GetPingInfo( int n, char *buf, int buflen )
+{
+ if (!cl_pinglist[n].adr.port)
+ {
+ // empty 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
+==================
+*/
+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
+==================
+*/
+void CL_Ping_f( void ) {
+ netadr_t to;
+ ping_t* pingptr;
+ 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);
+ }
+
+ Com_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
+==================
+*/
+qboolean CL_UpdateVisiblePings_f(int source) {
+ int slots, i;
+ char buff[MAX_STRING_CHARS];
+ int pingTime;
+ int max;
+ qboolean status = qfalse;
+
+ if (source < 0 || source > AS_FAVORITES) {
+ return qfalse;
+ }
+
+ cls.pingUpdateSource = source;
+
+ slots = CL_GetPingQueueCount();
+ if (slots < MAX_PINGREQUESTS) {
+ serverInfo_t *server = NULL;
+
+ max = (source == AS_GLOBAL) ? MAX_GLOBAL_SERVERS : MAX_OTHER_SERVERS;
+ 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 qfalse;
+ }
+ 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 = qtrue;
+ 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 = qtrue;
+ }
+ 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 = qtrue;
+ }
+ }
+
+ return status;
+}
+
+/*
+==================
+CL_ServerStatus_f
+==================
+*/
+void CL_ServerStatus_f(void) {
+ netadr_t to, *toptr = NULL;
+ char *server;
+ serverStatus_t *serverStatus;
+ int argc;
+ netadrtype_t family = NA_UNSPEC;
+
+ argc = Cmd_Argc();
+
+ if ( argc != 2 && argc != 3 )
+ {
+ if (cls.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)
+ {
+ Com_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 = qtrue;
+ serverStatus->pending = qtrue;
+}
+
+/*
+==================
+CL_ShowIP_f
+==================
+*/
+void CL_ShowIP_f(void) {
+ Sys_ShowIP();
+}
+
+#ifndef STANDALONE
+/*
+=================
+bool CL_CDKeyValidate
+=================
+*/
+qboolean CL_CDKeyValidate( const char *key, const char *checksum ) {
+ char ch;
+ byte sum;
+ char chs[3];
+ int i, len;
+
+ len = strlen(key);
+ if( len != CDKEY_LEN ) {
+ return qfalse;
+ }
+
+ if( checksum && strlen( checksum ) != CDCHKSUM_LEN ) {
+ return qfalse;
+ }
+
+ sum = 0;
+ // for loop gets rid of conditional assignment warning
+ for (i = 0; i < len; i++) {
+ ch = *key++;
+ if (ch>='a' && ch<='z') {
+ ch -= 32;
+ }
+ switch( ch ) {
+ case '2':
+ case '3':
+ case '7':
+ case 'A':
+ case 'B':
+ case 'C':
+ case 'D':
+ case 'G':
+ case 'H':
+ case 'J':
+ case 'L':
+ case 'P':
+ case 'R':
+ case 'S':
+ case 'T':
+ case 'W':
+ sum += ch;
+ continue;
+ default:
+ return qfalse;
+ }
+ }
+
+ sprintf(chs, "%02x", sum);
+
+ if (checksum && !Q_stricmp(chs, checksum)) {
+ return qtrue;
+ }
+
+ if (!checksum) {
+ return qtrue;
+ }
+
+ return qfalse;
+}
+#endif
diff --git a/code/client/cl_net_chan.c b/code/client/cl_net_chan.c
new file mode 100644
index 0000000..1433c43
--- /dev/null
+++ b/code/client/cl_net_chan.c
@@ -0,0 +1,167 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code 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,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+#include "../qcommon/q_shared.h"
+#include "../qcommon/qcommon.h"
+#include "client.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, index, srdc, sbit, 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 = 0;
+
+ 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) ];
+ index = 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[index])
+ index = 0;
+ if (string[index] > 127 || string[index] == '%') {
+ key ^= '.' << (i & 1);
+ }
+ else {
+ key ^= string[index] << (i & 1);
+ }
+ index++;
+ // 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, index;
+ byte key, *string;
+ int srdc, sbit, soob;
+
+ srdc = msg->readcount;
+ sbit = msg->bit;
+ soob = msg->oob;
+
+ msg->oob = 0;
+
+ reliableAcknowledge = MSG_ReadLong(msg);
+
+ msg->oob = soob;
+ msg->bit = sbit;
+ msg->readcount = srdc;
+
+ string = (byte *) clc.reliableCommands[ reliableAcknowledge & (MAX_RELIABLE_COMMANDS-1) ];
+ index = 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[index])
+ index = 0;
+ if (string[index] > 127 || string[index] == '%') {
+ key ^= '.' << (i & 1);
+ }
+ else {
+ key ^= string[index] << (i & 1);
+ }
+ index++;
+ // decode the data with this key
+ *(msg->data + i) = *(msg->data + i) ^ key;
+ }
+}
+
+/*
+=================
+CL_Netchan_TransmitNextFragment
+=================
+*/
+void CL_Netchan_TransmitNextFragment( netchan_t *chan ) {
+ Netchan_TransmitNextFragment( chan );
+}
+
+/*
+===============
+CL_Netchan_Transmit
+================
+*/
+void CL_Netchan_Transmit( netchan_t *chan, msg_t* msg ) {
+ MSG_WriteByte( msg, clc_EOF );
+
+ CL_Netchan_Encode( msg );
+ Netchan_Transmit( chan, msg->cursize, msg->data );
+}
+
+extern int oldsize;
+int newsize = 0;
+
+/*
+=================
+CL_Netchan_Process
+=================
+*/
+qboolean CL_Netchan_Process( netchan_t *chan, msg_t *msg ) {
+ int ret;
+
+ ret = Netchan_Process( chan, msg );
+ if (!ret)
+ return qfalse;
+ CL_Netchan_Decode( msg );
+ newsize += msg->cursize;
+ return qtrue;
+}
diff --git a/code/client/cl_parse.c b/code/client/cl_parse.c
new file mode 100644
index 0000000..83e2275
--- /dev/null
+++ b/code/client/cl_parse.c
@@ -0,0 +1,913 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code 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,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+// cl_parse.c -- parse a message received from the server
+
+#include "client.h"
+
+char *svc_strings[256] = {
+ "svc_bad",
+
+ "svc_nop",
+ "svc_gamestate",
+ "svc_configstring",
+ "svc_baseline",
+ "svc_serverCommand",
+ "svc_download",
+ "svc_snapshot",
+ "svc_EOF",
+ "svc_extension",
+ "svc_voip",
+};
+
+void SHOWNET( msg_t *msg, 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
+==================
+*/
+void CL_DeltaEntity (msg_t *msg, clSnapshot_t *frame, int newnum, entityState_t *old,
+ qboolean 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( msg, old, state, newnum );
+ }
+
+ if ( state->number == (MAX_GENTITIES-1) ) {
+ return; // entity was delta removed
+ }
+ cl.parseEntitiesNum++;
+ frame->numEntities++;
+}
+
+/*
+==================
+CL_ParsePacketEntities
+
+==================
+*/
+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, qtrue );
+
+ 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, qfalse );
+
+ 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], qfalse );
+ 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, qtrue );
+
+ 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
+ Com_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 = 0;
+
+ 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 = qtrue; // uncompressed frame
+ old = NULL;
+ clc.demowaiting = qfalse; // we can start recording now
+ } else {
+ old = &cl.snapshots[newSnap.deltaNum & PACKET_MASK];
+ if ( !old->valid ) {
+ // should never happen
+ Com_Printf ("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_Printf ("Delta frame too old.\n");
+ } else if ( cl.parseEntitiesNum - old->parseEntitiesNum > MAX_PARSE_ENTITIES-128 ) {
+ Com_Printf ("Delta parseEntitiesNum too old.\n");
+ } else {
+ newSnap.valid = qtrue; // 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 ( 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 = qfalse;
+ }
+
+ // 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 ( 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 = qtrue;
+}
+
+
+//=====================================================================
+
+int cl_connectedToPureServer;
+int cl_connectedToCheatServer;
+
+#ifdef USE_VOIP
+int cl_connectedToVoipServer;
+#endif
+
+/*
+==================
+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];
+ qboolean 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" ) );
+
+ // don't set any vars when playing a demo
+ if ( clc.demoplaying ) {
+ return;
+ }
+
+#ifdef USE_VOIP
+ // in the future, (val) will be a protocol version string, so only
+ // accept explicitly 1, not generally non-zero.
+ s = Info_ValueForKey( systemInfo, "sv_voip" );
+ if ( Cvar_VariableValue( "g_gametype" ) == GT_SINGLE_PLAYER || Cvar_VariableValue("ui_singlePlayerActive"))
+ cl_connectedToVoipServer = qfalse;
+ else
+ cl_connectedToVoipServer = (atoi( s ) == 1);
+
+#endif
+
+ s = Info_ValueForKey( systemInfo, "sv_cheats" );
+ cl_connectedToCheatServer = 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 = qfalse;
+ // 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 = qtrue;
+ }
+
+ 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)))
+ {
+ Com_Printf(S_COLOR_YELLOW "WARNING: server is not allowed to set %s=%s\n", key, value);
+ continue;
+ }
+
+ Cvar_Set(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 = 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;
+
+ Con_Close();
+
+ 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;
+ Com_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 );
+ }
+ Com_Memset (&nullstate, 0, sizeof(nullstate));
+ es = &cl.entityBaselines[ newnum ];
+ MSG_ReadDeltaEntity( 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 );
+
+ // 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
+ FS_ConditionalRestart( clc.checksumFeed );
+
+ // 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];
+ int block;
+
+ if (!*clc.downloadTempName) {
+ Com_Printf("Server sending download, but no download was requested\n");
+ CL_AddReliableCommand("stopdl", qfalse);
+ return;
+ }
+
+ // read the data
+ block = MSG_ReadShort ( msg );
+
+ if ( !block )
+ {
+ // 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 != block) {
+ Com_DPrintf( "CL_ParseDownload: Expected block %d, got %d\n", clc.downloadBlock, 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", qfalse);
+ CL_NextDownload();
+ return;
+ }
+ }
+
+ if (size)
+ FS_Write( data, size, clc.download );
+
+ CL_AddReliableCommand(va("nextdl %d", clc.downloadBlock), qfalse);
+ 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 );
+ }
+
+ // 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
+qboolean CL_ShouldIgnoreVoipSender(int sender)
+{
+ if (!cl_voip->integer)
+ return qtrue; // VoIP is disabled.
+ else if ((sender == clc.clientNum) && (!clc.demoplaying))
+ return qtrue; // ignore own voice (unless playing back a demo).
+ else if (clc.voipMuteAll)
+ return qtrue; // all channels are muted with extreme prejudice.
+ else if (clc.voipIgnore[sender])
+ return qtrue; // just ignoring this guy.
+ else if (clc.voipGain[sender] == 0.0f)
+ return qtrue; // too quiet to play.
+
+ return qfalse;
+}
+
+/*
+=====================
+CL_ParseVoip
+
+A VoIP message has been received from the server
+=====================
+*/
+static
+void CL_ParseVoip ( msg_t *msg ) {
+ static short decoded[4096]; // !!! FIXME: don't hardcode.
+
+ 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);
+ char encoded[1024];
+ int seqdiff = sequence - clc.voipIncomingSequence[sender];
+ 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.
+ }
+
+ if (!clc.speexInitialized) {
+ MSG_ReadData(msg, encoded, packetsize); // skip payload.
+ return; // can't handle VoIP without libspeex!
+ } else if (sender >= MAX_CLIENTS) {
+ MSG_ReadData(msg, encoded, packetsize); // skip payload.
+ return; // bogus sender.
+ } else if (CL_ShouldIgnoreVoipSender(sender)) {
+ MSG_ReadData(msg, encoded, packetsize); // skip payload.
+ return; // Channel is muted, bail.
+ }
+
+ // !!! FIXME: make sure data is narrowband? Does decoder handle this?
+
+ Com_DPrintf("VoIP: packet accepted!\n");
+
+ // 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);
+ speex_bits_reset(&clc.speexDecoderBits[sender]);
+ 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 bits just in case.
+ speex_bits_reset(&clc.speexDecoderBits[sender]);
+ seqdiff = 0;
+ } else if (seqdiff > 100) { // more than 2 seconds of audio dropped?
+ // just start over.
+ Com_DPrintf("VoIP: Dropped way too many (%d) frames from client #%d\n",
+ seqdiff, sender);
+ speex_bits_reset(&clc.speexDecoderBits[sender]);
+ seqdiff = 0;
+ }
+
+ if (seqdiff != 0) {
+ Com_DPrintf("VoIP: Dropped %d frames from client #%d\n",
+ seqdiff, sender);
+ // tell speex that we're missing frames...
+ for (i = 0; i < seqdiff; i++) {
+ assert((written + clc.speexFrameSize) * 2 < sizeof (decoded));
+ speex_decode_int(clc.speexDecoder[sender], NULL, decoded + written);
+ written += clc.speexFrameSize;
+ }
+ }
+
+ for (i = 0; i < frames; i++) {
+ char encoded[256];
+ const int len = MSG_ReadByte(msg);
+ if (len < 0) {
+ Com_DPrintf("VoIP: Short packet!\n");
+ break;
+ }
+ MSG_ReadData(msg, encoded, len);
+
+ // shouldn't happen, but just in case...
+ if ((written + clc.speexFrameSize) * 2 > sizeof (decoded)) {
+ Com_DPrintf("VoIP: playback %d bytes, %d samples, %d frames\n",
+ written * 2, written, i);
+ S_RawSamples(sender + 1, written, clc.speexSampleRate, 2, 1,
+ (const byte *) decoded, clc.voipGain[sender]);
+ written = 0;
+ }
+
+ speex_bits_read_from(&clc.speexDecoderBits[sender], encoded, len);
+ speex_decode_int(clc.speexDecoder[sender],
+ &clc.speexDecoderBits[sender], decoded + written);
+
+ #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 += clc.speexFrameSize;
+ }
+
+ Com_DPrintf("VoIP: playback %d bytes, %d samples, %d frames\n",
+ written * 2, written, i);
+
+ if (written > 0) {
+ S_RawSamples(sender + 1, written, clc.speexSampleRate, 2, 1,
+ (const byte *) decoded, clc.voipGain[sender]);
+ }
+
+ 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 );
+
+ // 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_extension)) {
+ 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_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\n");
+ 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_voip:
+#ifdef USE_VOIP
+ CL_ParseVoip( msg );
+#endif
+ break;
+ }
+ }
+}
+
+
diff --git a/code/client/cl_scrn.c b/code/client/cl_scrn.c
new file mode 100644
index 0000000..ec545bc
--- /dev/null
+++ b/code/client/cl_scrn.c
@@ -0,0 +1,597 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code 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,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+// cl_scrn.c -- master for refresh, status bar, console, chat, notify, etc
+
+#include "client.h"
+
+qboolean 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
+==================
+*/
+void SCR_DrawStringExt( int x, int y, float size, const char *string, float *setColor, qboolean forceColor,
+ qboolean 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 ( !noColorEscape && Q_IsColorString( s ) ) {
+ if ( !forceColor ) {
+ Com_Memcpy( color, g_color_table[ColorIndex(*(s+1))], sizeof( color ) );
+ color[3] = setColor[3];
+ re.SetColor( color );
+ }
+ 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, qboolean noColorEscape ) {
+ float color[4];
+
+ color[0] = color[1] = color[2] = 1.0;
+ color[3] = alpha;
+ SCR_DrawStringExt( x, y, BIGCHAR_WIDTH, s, color, qfalse, noColorEscape );
+}
+
+void SCR_DrawBigStringColor( int x, int y, const char *s, vec4_t color, qboolean noColorEscape ) {
+ SCR_DrawStringExt( x, y, BIGCHAR_WIDTH, s, color, qtrue, 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, qboolean forceColor,
+ qboolean 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 ) {
+ Com_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 ) * 16;
+}
+
+
+//===============================================================================
+
+/*
+=================
+SCR_DrawDemoRecording
+=================
+*/
+void SCR_DrawDemoRecording( void ) {
+ char string[1024];
+ int pos;
+
+ if ( !clc.demorecording ) {
+ return;
+ }
+ if ( clc.spDemoRecording ) {
+ return;
+ }
+
+ pos = FS_FTell( clc.demofile );
+ sprintf( string, "RECORDING %s: %ik", clc.demoName, pos / 1024 );
+
+ SCR_DrawStringExt( 320 - strlen( string ) * 4, 20, 8, string, g_color_table[7], qtrue, qfalse );
+}
+
+
+#ifdef USE_VOIP
+/*
+=================
+SCR_DrawVoipMeter
+=================
+*/
+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 (cls.state != CA_ACTIVE)
+ return; // not connected to a server.
+ else if (!cl_connectedToVoipServer)
+ 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], qtrue, qfalse );
+}
+#endif
+
+
+
+
+/*
+===============================================================================
+
+DEBUG GRAPH
+
+===============================================================================
+*/
+
+typedef struct
+{
+ float value;
+ int color;
+} graphsamp_t;
+
+static int current;
+static graphsamp_t values[1024];
+
+/*
+==============
+SCR_DebugGraph
+==============
+*/
+void SCR_DebugGraph (float value, int color)
+{
+ values[current&1023].value = value;
+ values[current&1023].color = color;
+ current++;
+}
+
+/*
+==============
+SCR_DrawDebugGraph
+==============
+*/
+void SCR_DrawDebugGraph (void)
+{
+ int a, x, y, w, i, h;
+ float v;
+ int color;
+
+ //
+ // 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 = (current-1-a+1024) & 1023;
+ v = values[i].value;
+ color = values[i].color;
+ 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 = qtrue;
+}
+
+
+//=======================================================
+
+/*
+==================
+SCR_DrawScreenField
+
+This will be called twice if rendering in stereo mode
+==================
+*/
+void SCR_DrawScreenField( stereoFrame_t stereoFrame ) {
+ re.BeginFrame( stereoFrame );
+
+ // wide aspect ratio screens need to have the sides cleared
+ // unless they are displaying game renderings
+ if ( cls.state != CA_ACTIVE && cls.state != CA_CINEMATIC ) {
+ 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 ( uivm && !VM_Call( uivm, UI_IS_FULLSCREEN )) {
+ switch( cls.state ) {
+ default:
+ Com_Error( ERR_FATAL, "SCR_DrawScreenField: bad cls.state" );
+ break;
+ case CA_CINEMATIC:
+ SCR_DrawCinematic();
+ break;
+ case CA_DISCONNECTED:
+ // force menu up
+ S_StopAllSounds();
+ VM_Call( uivm, UI_SET_ACTIVE_MENU, 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( uivm, UI_REFRESH, cls.realtime );
+ VM_Call( uivm, UI_DRAW_CONNECT_SCREEN, qfalse );
+ break;
+ case CA_LOADING:
+ case CA_PRIMED:
+ // draw the game information screen and loading progress
+ CL_CGameRendering(stereoFrame);
+
+ // also draw the connection information, so it doesn't
+ // flash away too briefly on local or lan games
+ // refresh to update the time
+ VM_Call( uivm, UI_REFRESH, cls.realtime );
+ VM_Call( uivm, UI_DRAW_CONNECT_SCREEN, qtrue );
+ break;
+ case CA_ACTIVE:
+ // always supply STEREO_CENTER as vieworg offset is now done by the engine.
+ CL_CGameRendering(stereoFrame);
+ SCR_DrawDemoRecording();
+#ifdef USE_VOIP
+ SCR_DrawVoipMeter();
+#endif
+ break;
+ }
+ }
+
+ // the menu draws next
+ if ( Key_GetCatcher( ) & KEYCATCH_UI && uivm ) {
+ VM_Call( uivm, UI_REFRESH, 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( uivm || com_dedicated->integer )
+ {
+ // XXX
+ extern cvar_t* r_anaglyphMode;
+ // if running in stereo, we need to draw the frame twice
+ if ( cls.glconfig.stereoEnabled || r_anaglyphMode->integer) {
+ 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/code/client/cl_ui.c b/code/client/cl_ui.c
new file mode 100644
index 0000000..ed32313
--- /dev/null
+++ b/code/client/cl_ui.c
@@ -0,0 +1,1154 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code 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,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+#include "client.h"
+
+#include "../botlib/botlib.h"
+
+extern botlib_export_t *botlib_export;
+
+vm_t *uivm;
+
+/*
+====================
+GetClientState
+====================
+*/
+static void GetClientState( uiClientState_t *state ) {
+ state->connectPacketCount = clc.connectPacketCount;
+ state->connState = cls.state;
+ Q_strncpyz( state->servername, cls.servername, sizeof( state->servername ) );
+ Q_strncpyz( state->updateInfoString, cls.updateInfoString, sizeof( state->updateInfoString ) );
+ Q_strncpyz( state->messageString, clc.serverMessage, sizeof( state->messageString ) );
+ state->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 ) {
+ int size;
+ fileHandle_t fileOut = FS_SV_FOpenFileWrite("servercache.dat");
+ FS_Write(&cls.numglobalservers, sizeof(int), fileOut);
+ FS_Write(&cls.numfavoriteservers, sizeof(int), fileOut);
+ 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);
+}
+
+
+/*
+====================
+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_IP );
+ 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 = qtrue;
+ (*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_IP );
+ for (i = 0; i < *count; i++) {
+ if (NET_CompareAdr( comp, servers[i].adr)) {
+ int j = i;
+ while (j < *count - 1) {
+ Com_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 );
+ 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 );
+ return;
+ }
+ break;
+ case AS_FAVORITES :
+ if (n >= 0 && n < MAX_OTHER_SERVERS) {
+ Q_strncpyz(buf, NET_AdrToStringwPort( cls.favoriteServers[n].adr) , buflen );
+ 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';
+ Info_SetValueForKey( info, "hostname", server->hostName);
+ Info_SetValueForKey( info, "mapname", server->mapName);
+ 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));
+ Info_SetValueForKey( info, "punkbuster", va("%i", server->punkbuster));
+ 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;
+}
+
+/*
+====================
+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;
+ }
+
+ res = 0;
+ switch( sortKey ) {
+ case SORT_HOST:
+ res = Q_stricmp( server1->hostName, server2->hostName );
+ 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_GAME:
+ if (server1->gameType < server2->gameType) {
+ res = -1;
+ }
+ else if (server1->gameType > server2->gameType) {
+ 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, qboolean 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 int 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 qfalse;
+}
+
+/*
+=======================
+LAN_UpdateVisiblePings
+=======================
+*/
+qboolean LAN_UpdateVisiblePings(int source ) {
+ return CL_UpdateVisiblePings_f(source);
+}
+
+/*
+====================
+LAN_GetServerStatus
+====================
+*/
+int 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;
+}
+
+/*
+====================
+CL_GetClipboardData
+====================
+*/
+static void CL_GetClipboardData( char *buf, int buflen ) {
+ char *cbd;
+
+ cbd = Sys_GetClipboardData();
+
+ if ( !cbd ) {
+ *buf = 0;
+ return;
+ }
+
+ Q_strncpyz( buf, cbd, buflen );
+
+ Z_Free( cbd );
+}
+
+/*
+====================
+Key_KeynumToStringBuf
+====================
+*/
+static void Key_KeynumToStringBuf( int keynum, char *buf, int buflen ) {
+ Q_strncpyz( buf, Key_KeynumToString( keynum ), buflen );
+}
+
+/*
+====================
+Key_GetBindingBuf
+====================
+*/
+static void Key_GetBindingBuf( int keynum, char *buf, int buflen ) {
+ char *value;
+
+ value = Key_GetBinding( keynum );
+ if ( value ) {
+ Q_strncpyz( buf, value, buflen );
+ }
+ else {
+ *buf = 0;
+ }
+}
+
+/*
+====================
+CLUI_GetCDKey
+====================
+*/
+#ifndef STANDALONE
+static void CLUI_GetCDKey( char *buf, int buflen ) {
+ cvar_t *fs;
+ fs = Cvar_Get ("fs_game", "", CVAR_INIT|CVAR_SYSTEMINFO );
+ if (UI_usesUniqueCDKey() && fs && fs->string[0] != 0) {
+ Com_Memcpy( buf, &cl_cdkey[16], 16);
+ buf[16] = 0;
+ } else {
+ Com_Memcpy( buf, cl_cdkey, 16);
+ buf[16] = 0;
+ }
+}
+
+
+/*
+====================
+CLUI_SetCDKey
+====================
+*/
+static void CLUI_SetCDKey( char *buf ) {
+ cvar_t *fs;
+ fs = Cvar_Get ("fs_game", "", CVAR_INIT|CVAR_SYSTEMINFO );
+ if (UI_usesUniqueCDKey() && fs && fs->string[0] != 0) {
+ Com_Memcpy( &cl_cdkey[16], buf, 16 );
+ cl_cdkey[32] = 0;
+ // set the flag so the fle will be written at the next opportunity
+ cvar_modifiedFlags |= CVAR_ARCHIVE;
+ } else {
+ Com_Memcpy( cl_cdkey, buf, 16 );
+ // set the flag so the fle will be written at the next opportunity
+ cvar_modifiedFlags |= CVAR_ARCHIVE;
+ }
+}
+#endif
+
+/*
+====================
+GetConfigString
+====================
+*/
+static int GetConfigString(int index, char *buf, int size)
+{
+ int offset;
+
+ if (index < 0 || index >= MAX_CONFIGSTRINGS)
+ return qfalse;
+
+ offset = cl.gameState.stringOffsets[index];
+ if (!offset) {
+ if( size ) {
+ buf[0] = 0;
+ }
+ return qfalse;
+ }
+
+ Q_strncpyz( buf, cl.gameState.stringData+offset, size);
+
+ return qtrue;
+}
+
+/*
+====================
+FloatAsInt
+====================
+*/
+static int FloatAsInt( float f ) {
+ floatint_t fi;
+ fi.f = f;
+ return fi.i;
+}
+
+/*
+====================
+CL_UISystemCalls
+
+The ui module is making a system call
+====================
+*/
+intptr_t CL_UISystemCalls( intptr_t *args ) {
+ switch( args[0] ) {
+ case UI_ERROR:
+ 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( VMA(1), VMA(2), VMA(3), args[4] );
+ return 0;
+
+ case UI_CVAR_UPDATE:
+ Cvar_Update( VMA(1) );
+ return 0;
+
+ case UI_CVAR_SET:
+ Cvar_Set( VMA(1), VMA(2) );
+ return 0;
+
+ case UI_CVAR_VARIABLEVALUE:
+ return FloatAsInt( Cvar_VariableValue( VMA(1) ) );
+
+ case UI_CVAR_VARIABLESTRINGBUFFER:
+ Cvar_VariableStringBuffer( VMA(1), VMA(2), args[3] );
+ return 0;
+
+ case UI_CVAR_SETVALUE:
+ Cvar_SetValue( VMA(1), VMF(2) );
+ return 0;
+
+ case UI_CVAR_RESET:
+ Cvar_Reset( VMA(1) );
+ return 0;
+
+ case UI_CVAR_CREATE:
+ Cvar_Get( VMA(1), VMA(2), args[3] );
+ return 0;
+
+ case UI_CVAR_INFOSTRINGBUFFER:
+ Cvar_InfoStringBuffer( args[1], VMA(2), args[3] );
+ return 0;
+
+ case UI_ARGC:
+ return Cmd_Argc();
+
+ case UI_ARGV:
+ Cmd_ArgvBuffer( args[1], VMA(2), args[3] );
+ return 0;
+
+ case UI_CMD_EXECUTETEXT:
+ if(args[1] == 0
+ && (!strncmp(VMA(2), "snd_restart", 11)
+ || !strncmp(VMA(2), "vid_restart", 11)
+ || !strncmp(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;
+ }
+ Cbuf_ExecuteText( args[1], VMA(2) );
+ return 0;
+
+ case UI_FS_FOPENFILE:
+ return FS_FOpenFileByMode( VMA(1), VMA(2), args[3] );
+
+ case UI_FS_READ:
+ FS_Read2( 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( VMA(1), VMA(2), VMA(3), args[4] );
+
+ case UI_FS_SEEK:
+ return FS_Seek( args[1], args[2], args[3] );
+
+ case UI_R_REGISTERMODEL:
+ return re.RegisterModel( VMA(1) );
+
+ case UI_R_REGISTERSKIN:
+ return re.RegisterSkin( VMA(1) );
+
+ case UI_R_REGISTERSHADERNOMIP:
+ return re.RegisterShaderNoMip( VMA(1) );
+
+ case UI_R_CLEARSCENE:
+ re.ClearScene();
+ return 0;
+
+ case UI_R_ADDREFENTITYTOSCENE:
+ re.AddRefEntityToScene( VMA(1) );
+ return 0;
+
+ case UI_R_ADDPOLYTOSCENE:
+ re.AddPolyToScene( args[1], args[2], VMA(3), 1 );
+ return 0;
+
+ case UI_R_ADDLIGHTTOSCENE:
+ re.AddLightToScene( VMA(1), VMF(2), VMF(3), VMF(4), VMF(5) );
+ return 0;
+
+ case UI_R_RENDERSCENE:
+ re.RenderScene( VMA(1) );
+ return 0;
+
+ case UI_R_SETCOLOR:
+ re.SetColor( 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], VMA(2), VMA(3) );
+ return 0;
+
+ case UI_UPDATESCREEN:
+ SCR_UpdateScreen();
+ return 0;
+
+ case UI_CM_LERPTAG:
+ re.LerpTag( VMA(1), args[2], args[3], args[4], VMF(5), VMA(6) );
+ return 0;
+
+ case UI_S_REGISTERSOUND:
+ return S_RegisterSound( VMA(1), args[2] );
+
+ case UI_S_STARTLOCALSOUND:
+ S_StartLocalSound( args[1], args[2] );
+ return 0;
+
+ case UI_KEY_KEYNUMTOSTRINGBUF:
+ Key_KeynumToStringBuf( args[1], VMA(2), args[3] );
+ return 0;
+
+ case UI_KEY_GETBINDINGBUF:
+ Key_GetBindingBuf( args[1], VMA(2), args[3] );
+ return 0;
+
+ case UI_KEY_SETBINDING:
+ Key_SetBinding( args[1], 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( 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 close the console
+ Key_SetCatcher( args[1] | ( Key_GetCatcher( ) & KEYCATCH_CONSOLE ) );
+ return 0;
+
+ case UI_GETCLIPBOARDDATA:
+ CL_GetClipboardData( VMA(1), args[2] );
+ return 0;
+
+ case UI_GETCLIENTSTATE:
+ GetClientState( VMA(1) );
+ return 0;
+
+ case UI_GETGLCONFIG:
+ CL_GetGlconfig( VMA(1) );
+ return 0;
+
+ case UI_GETCONFIGSTRING:
+ return GetConfigString( args[1], 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], VMA(2), VMA(3));
+
+ case UI_LAN_REMOVESERVER:
+ LAN_RemoveServer(args[1], 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], VMA(2), args[3], VMA(4) );
+ return 0;
+
+ case UI_LAN_GETPINGINFO:
+ LAN_GetPingInfo( args[1], 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], VMA(3), args[4] );
+ return 0;
+
+ case UI_LAN_GETSERVERINFO:
+ LAN_GetServerInfo( args[1], args[2], 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], 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( VMA(1), VMA(2), args[3] );
+
+ case UI_LAN_COMPARESERVERS:
+ return LAN_CompareServers( args[1], args[2], args[3], args[4], args[5] );
+
+ case UI_MEMORY_REMAINING:
+ return Hunk_MemoryRemaining();
+
+#ifndef STANDALONE
+ case UI_GET_CDKEY:
+ CLUI_GetCDKey( VMA(1), args[2] );
+ return 0;
+
+ case UI_SET_CDKEY:
+ CLUI_SetCDKey( VMA(1) );
+ return 0;
+#endif
+
+ case UI_SET_PBCLSTATUS:
+ return 0;
+
+ case UI_R_REGISTERFONT:
+ re.RegisterFont( VMA(1), args[2], VMA(3));
+ return 0;
+
+ case UI_MEMSET:
+ Com_Memset( VMA(1), args[2], args[3] );
+ return 0;
+
+ case UI_MEMCPY:
+ Com_Memcpy( VMA(1), VMA(2), args[3] );
+ return 0;
+
+ case UI_STRNCPY:
+ strncpy( VMA(1), 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_PC_ADD_GLOBAL_DEFINE:
+ return botlib_export->PC_AddGlobalDefine( VMA(1) );
+ case UI_PC_LOAD_SOURCE:
+ return botlib_export->PC_LoadSourceHandle( VMA(1) );
+ case UI_PC_FREE_SOURCE:
+ return botlib_export->PC_FreeSourceHandle( args[1] );
+ case UI_PC_READ_TOKEN:
+ return botlib_export->PC_ReadTokenHandle( args[1], VMA(2) );
+ case UI_PC_SOURCE_FILE_AND_LINE:
+ return botlib_export->PC_SourceFileAndLine( args[1], VMA(2), VMA(3) );
+
+ case UI_S_STOPBACKGROUNDTRACK:
+ S_StopBackgroundTrack();
+ return 0;
+ case UI_S_STARTBACKGROUNDTRACK:
+ S_StartBackgroundTrack( VMA(1), VMA(2));
+ return 0;
+
+ case UI_REAL_TIME:
+ return Com_RealTime( VMA(1) );
+
+ case UI_CIN_PLAYCINEMATIC:
+ Com_DPrintf("UI_CIN_PlayCinematic\n");
+ return CIN_PlayCinematic(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( VMA(1), VMA(2), VMA(3) );
+ return 0;
+
+#ifndef STANDALONE
+ case UI_VERIFY_CDKEY:
+ return CL_CDKeyValidate(VMA(1), VMA(2));
+#endif
+
+
+ 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 = qfalse;
+ if ( !uivm ) {
+ return;
+ }
+ VM_Call( uivm, UI_SHUTDOWN );
+ VM_Free( uivm );
+ uivm = NULL;
+}
+
+/*
+====================
+CL_InitUI
+====================
+*/
+#define UI_OLD_API_VERSION 4
+
+void CL_InitUI( void ) {
+ int v;
+ vmInterpret_t interpret;
+
+ // load the dll or bytecode
+ if ( cl_connectedToPureServer != 0 ) {
+ // if sv_pure is set we only allow qvms to be loaded
+ interpret = VMI_COMPILED;
+ }
+ else {
+ interpret = Cvar_VariableValue( "vm_ui" );
+ }
+ uivm = VM_Create( "ui", CL_UISystemCalls, interpret );
+ if ( !uivm ) {
+ Com_Error( ERR_FATAL, "VM_Create on UI failed" );
+ }
+
+ // sanity check
+ v = VM_Call( uivm, UI_GETAPIVERSION );
+ if (v == UI_OLD_API_VERSION) {
+// Com_Printf(S_COLOR_YELLOW "WARNING: loading old Quake III Arena User Interface version %d\n", v );
+ // init for this gamestate
+ VM_Call( uivm, UI_INIT, (cls.state >= CA_AUTHORIZING && cls.state < CA_ACTIVE));
+ }
+ else if (v != UI_API_VERSION) {
+ Com_Error( ERR_DROP, "User Interface is version %d, expected %d", v, UI_API_VERSION );
+ cls.uiStarted = qfalse;
+ }
+ else {
+ // init for this gamestate
+ VM_Call( uivm, UI_INIT, (cls.state >= CA_AUTHORIZING && cls.state < CA_ACTIVE) );
+ }
+
+ // reset any CVAR_CHEAT cvars registered by ui
+ if ( !clc.demoplaying && !cl_connectedToCheatServer )
+ Cvar_SetCheatState();
+}
+
+#ifndef STANDALONE
+qboolean UI_usesUniqueCDKey( void ) {
+ if (uivm) {
+ return (VM_Call( uivm, UI_HASUNIQUECDKEY) == qtrue);
+ } else {
+ return qfalse;
+ }
+}
+#endif
+
+/*
+====================
+UI_GameCommand
+
+See if the current console command is claimed by the ui
+====================
+*/
+qboolean UI_GameCommand( void ) {
+ if ( !uivm ) {
+ return qfalse;
+ }
+
+ return VM_Call( uivm, UI_CONSOLE_COMMAND, cls.realtime );
+}
diff --git a/code/client/client.h b/code/client/client.h
new file mode 100644
index 0000000..7891a83
--- /dev/null
+++ b/code/client/client.h
@@ -0,0 +1,627 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code 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,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+// client.h -- primary header for client
+
+#include "../qcommon/q_shared.h"
+#include "../qcommon/qcommon.h"
+#include "../renderer/tr_public.h"
+#include "../ui/ui_public.h"
+#include "keys.h"
+#include "snd_public.h"
+#include "../cgame/cg_public.h"
+#include "../game/bg_public.h"
+
+#ifdef USE_CURL
+#include "cl_curl.h"
+#endif /* USE_CURL */
+
+#ifdef USE_VOIP
+#include "speex/speex.h"
+#include "speex/speex_preprocess.h"
+#endif
+
+// 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
+typedef struct {
+ qboolean 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
+
+ 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
+} clSnapshot_t;
+
+
+
+/*
+=============================================================================
+
+the clientActive_t structure is wiped completely at every
+new gamestate_t, potentially several times during an established connection
+
+=============================================================================
+*/
+
+typedef struct {
+ 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
+} outPacket_t;
+
+// 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 2048
+
+extern int g_console_field_width;
+
+typedef struct {
+ 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
+ qboolean extrapolatedSnapshot; // set if any cgame frame has been forced to extrapolate
+ // cleared when CL_AdjustTimeDelta looks at it
+ qboolean 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];
+} clientActive_t;
+
+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
+
+typedef struct {
+
+ int clientNum;
+ int lastPacketSentTime; // for retransmits during connection
+ int lastPacketTime; // for timeouts
+
+ 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
+ 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];
+#ifdef USE_CURL
+ qboolean cURLEnabled;
+ qboolean cURLUsed;
+ qboolean cURLDisconnected;
+ char downloadURL[MAX_OSPATH];
+ CURL *downloadCURL;
+ CURLM *downloadCURLM;
+#endif /* USE_CURL */
+ 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
+ qboolean downloadRestart; // if true, we need to do another FS_Restart because we downloaded a pak
+
+ // demo information
+ char demoName[MAX_QPATH];
+ qboolean spDemoRecording;
+ qboolean demorecording;
+ qboolean demoplaying;
+ qboolean demowaiting; // don't record until a non-delta message is received
+ qboolean 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
+
+#ifdef USE_VOIP
+ qboolean speexInitialized;
+ int speexFrameSize;
+ int speexSampleRate;
+
+ // incoming data...
+ // !!! FIXME: convert from parallel arrays to array of a struct.
+ SpeexBits speexDecoderBits[MAX_CLIENTS];
+ void *speexDecoder[MAX_CLIENTS];
+ byte voipIncomingGeneration[MAX_CLIENTS];
+ int voipIncomingSequence[MAX_CLIENTS];
+ float voipGain[MAX_CLIENTS];
+ qboolean voipIgnore[MAX_CLIENTS];
+ qboolean voipMuteAll;
+
+ // outgoing data...
+ int voipTarget1; // these three ints make up a bit mask of 92 bits.
+ int voipTarget2; // the bits say who a VoIP pack is addressed to:
+ int voipTarget3; // (1 << clientnum). See cl_voipSendTarget cvar.
+ SpeexPreprocessState *speexPreprocessor;
+ SpeexBits speexEncoderBits;
+ void *speexEncoder;
+ 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;
+} clientConnection_t;
+
+extern clientConnection_t clc;
+
+/*
+==================================================================
+
+the clientStatic_t structure is never wiped, and is used even when
+no client connection is active at all
+
+==================================================================
+*/
+
+typedef struct {
+ netadr_t adr;
+ int start;
+ int time;
+ char info[MAX_INFO_STRING];
+} ping_t;
+
+typedef struct {
+ netadr_t adr;
+ char hostName[MAX_NAME_LENGTH];
+ char mapName[MAX_NAME_LENGTH];
+ char game[MAX_NAME_LENGTH];
+ int netType;
+ int gameType;
+ int clients;
+ int maxClients;
+ int minPing;
+ int maxPing;
+ int ping;
+ qboolean visible;
+ int punkbuster;
+} serverInfo_t;
+
+typedef struct {
+ connstate_t state; // connection status
+
+ qboolean cddialog; // bring up the cd needed dialog next frame
+
+ char servername[MAX_OSPATH]; // name of server from original connect (used by reconnect)
+
+ // when the server clears the hunk, all of these must be restarted
+ qboolean rendererStarted;
+ qboolean soundStarted;
+ qboolean soundRegistered;
+ qboolean uiStarted;
+ qboolean cgameStarted;
+
+ int framecount;
+ int frametime; // msec since last frame
+
+ int realtime; // ignores pause
+ int realFrametime; // ignoring pause, so console always works
+
+ 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;
+} clientStatic_t;
+
+extern clientStatic_t cls;
+
+//=============================================================================
+
+extern vm_t *cgvm; // interface to cgame dll or vm
+extern vm_t *uivm; // interface to ui dll or vm
+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 *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_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;
+#endif
+
+//=================================================
+
+//
+// cl_main
+//
+
+void CL_Init (void);
+void CL_FlushMemory(void);
+void CL_ShutdownAll(void);
+void CL_AddReliableCommand(const char *cmd, qboolean isDisconnectCmd);
+
+void CL_StartHunkUsers( qboolean rendererOnly );
+
+void CL_Disconnect_f (void);
+void CL_GetChallengePacket (void);
+void CL_Vid_Restart_f( void );
+void CL_Snd_Restart_f (void);
+void CL_StartDemoLoop( void );
+void CL_NextDemo( void );
+void CL_ReadDemoMessage( void );
+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 );
+
+void CL_ShutdownRef( void );
+void CL_InitRef( void );
+#ifndef STANDALONE
+qboolean CL_CDKeyValidate( const char *key, const char *checksum );
+#endif
+int CL_ServerStatus( char *serverAddress, char *serverStatusString, int maxLen );
+
+qboolean 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
+ qboolean active; // current state
+ qboolean wasPressed; // set when down, not cleared when up
+} kbutton_t;
+
+extern kbutton_t in_mlook, in_klook;
+extern kbutton_t in_strafe;
+extern kbutton_t in_speed;
+
+#ifdef USE_VOIP
+extern kbutton_t in_voiprecord;
+#endif
+
+void CL_InitInput (void);
+void CL_SendCmd (void);
+void CL_ClearState (void);
+void CL_ReadPackets (void);
+
+void CL_WritePacket( void );
+void IN_CenterView (void);
+
+void CL_VerifyCode( void );
+
+float CL_KeyState (kbutton_t *key);
+int Key_StringToKeynum( char *str );
+char *Key_KeynumToString (int keynum);
+
+//
+// cl_parse.c
+//
+extern int cl_connectedToPureServer;
+extern int cl_connectedToCheatServer;
+
+#ifdef USE_VOIP
+extern int cl_connectedToVoipServer;
+void CL_Voip_f( void );
+#endif
+
+void CL_SystemInfoChanged( void );
+void CL_ParseServerMessage( msg_t *msg );
+
+//====================================================================
+
+void CL_ServerInfoPacket( netadr_t from, msg_t *msg );
+void CL_LocalServers_f( void );
+void CL_GlobalServers_f( void );
+void CL_FavoriteServers_f( void );
+void CL_Ping_f( void );
+qboolean CL_UpdateVisiblePings_f( int source );
+
+
+//
+// console
+//
+void Con_DrawCharacter (int cx, int line, int num);
+
+void Con_CheckResize (void);
+void Con_Init (void);
+void Con_Clear_f (void);
+void Con_ToggleConsole_f (void);
+void Con_DrawNotify (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 color);
+
+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, qboolean noColorEscape ); // draws a string with embedded color control characters with fade
+void SCR_DrawBigStringColor( int x, int y, const char *s, vec4_t color, qboolean noColorEscape ); // ignores embedded color control characters
+void SCR_DrawSmallStringExt( int x, int y, const char *string, float *setColor, qboolean forceColor, qboolean 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_SetLooping (int handle, qboolean loop);
+void CIN_UploadCinematic(int handle);
+void CIN_CloseAllVideos(void);
+
+//
+// cl_cgame.c
+//
+void CL_InitCGame( void );
+void CL_ShutdownCGame( void );
+qboolean CL_GameCommand( 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 );
+void CL_Netchan_TransmitNextFragment( netchan_t *chan );
+qboolean CL_Netchan_Process( netchan_t *chan, msg_t *msg );
+
+//
+// cl_avi.c
+//
+qboolean 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 );
+qboolean CL_CloseAVI( void );
+qboolean CL_VideoRecording( void );
+
+//
+// cl_main.c
+//
+void CL_WriteDemoMessage ( msg_t *msg, int headerBytes );
+
diff --git a/code/client/keycodes.h b/code/client/keycodes.h
new file mode 100644
index 0000000..706ca1b
--- /dev/null
+++ b/code/client/keycodes.h
@@ -0,0 +1,279 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code 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,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+//
+#ifndef __KEYCODES_H__
+#define __KEYCODES_H__
+
+//
+// these are the key numbers that should be passed to KeyEvent
+//
+
+// normal keys should be passed as lowercased ascii
+
+typedef enum {
+ K_TAB = 9,
+ K_ENTER = 13,
+ K_ESCAPE = 27,
+ K_SPACE = 32,
+
+ K_BACKSPACE = 127,
+
+ K_COMMAND = 128,
+ K_CAPSLOCK,
+ K_POWER,
+ K_PAUSE,
+
+ K_UPARROW,
+ K_DOWNARROW,
+ K_LEFTARROW,
+ K_RIGHTARROW,
+
+ K_ALT,
+ K_CTRL,
+ K_SHIFT,
+ K_INS,
+ K_DEL,
+ K_PGDN,
+ K_PGUP,
+ K_HOME,
+ K_END,
+
+ K_F1,
+ K_F2,
+ K_F3,
+ K_F4,
+ K_F5,
+ K_F6,
+ K_F7,
+ K_F8,
+ K_F9,
+ K_F10,
+ K_F11,
+ K_F12,
+ K_F13,
+ K_F14,
+ K_F15,
+
+ K_KP_HOME,
+ K_KP_UPARROW,
+ K_KP_PGUP,
+ K_KP_LEFTARROW,
+ K_KP_5,
+ K_KP_RIGHTARROW,
+ K_KP_END,
+ K_KP_DOWNARROW,
+ K_KP_PGDN,
+ K_KP_ENTER,
+ K_KP_INS,
+ K_KP_DEL,
+ K_KP_SLASH,
+ K_KP_MINUS,
+ K_KP_PLUS,
+ K_KP_NUMLOCK,
+ K_KP_STAR,
+ K_KP_EQUALS,
+
+ K_MOUSE1,
+ K_MOUSE2,
+ K_MOUSE3,
+ K_MOUSE4,
+ K_MOUSE5,
+
+ K_MWHEELDOWN,
+ K_MWHEELUP,
+
+ K_JOY1,
+ K_JOY2,
+ K_JOY3,
+ K_JOY4,
+ K_JOY5,
+ K_JOY6,
+ K_JOY7,
+ K_JOY8,
+ K_JOY9,
+ K_JOY10,
+ K_JOY11,
+ K_JOY12,
+ K_JOY13,
+ K_JOY14,
+ K_JOY15,
+ K_JOY16,
+ K_JOY17,
+ K_JOY18,
+ K_JOY19,
+ K_JOY20,
+ K_JOY21,
+ K_JOY22,
+ K_JOY23,
+ K_JOY24,
+ K_JOY25,
+ K_JOY26,
+ K_JOY27,
+ K_JOY28,
+ K_JOY29,
+ K_JOY30,
+ K_JOY31,
+ K_JOY32,
+
+ K_AUX1,
+ K_AUX2,
+ K_AUX3,
+ K_AUX4,
+ K_AUX5,
+ K_AUX6,
+ K_AUX7,
+ K_AUX8,
+ K_AUX9,
+ K_AUX10,
+ K_AUX11,
+ K_AUX12,
+ K_AUX13,
+ K_AUX14,
+ K_AUX15,
+ K_AUX16,
+
+ K_WORLD_0,
+ K_WORLD_1,
+ K_WORLD_2,
+ K_WORLD_3,
+ K_WORLD_4,
+ K_WORLD_5,
+ K_WORLD_6,
+ K_WORLD_7,
+ K_WORLD_8,
+ K_WORLD_9,
+ K_WORLD_10,
+ K_WORLD_11,
+ K_WORLD_12,
+ K_WORLD_13,
+ K_WORLD_14,
+ K_WORLD_15,
+ K_WORLD_16,
+ K_WORLD_17,
+ K_WORLD_18,
+ K_WORLD_19,
+ K_WORLD_20,
+ K_WORLD_21,
+ K_WORLD_22,
+ K_WORLD_23,
+ K_WORLD_24,
+ K_WORLD_25,
+ K_WORLD_26,
+ K_WORLD_27,
+ K_WORLD_28,
+ K_WORLD_29,
+ K_WORLD_30,
+ K_WORLD_31,
+ K_WORLD_32,
+ K_WORLD_33,
+ K_WORLD_34,
+ K_WORLD_35,
+ K_WORLD_36,
+ K_WORLD_37,
+ K_WORLD_38,
+ K_WORLD_39,
+ K_WORLD_40,
+ K_WORLD_41,
+ K_WORLD_42,
+ K_WORLD_43,
+ K_WORLD_44,
+ K_WORLD_45,
+ K_WORLD_46,
+ K_WORLD_47,
+ K_WORLD_48,
+ K_WORLD_49,
+ K_WORLD_50,
+ K_WORLD_51,
+ K_WORLD_52,
+ K_WORLD_53,
+ K_WORLD_54,
+ K_WORLD_55,
+ K_WORLD_56,
+ K_WORLD_57,
+ K_WORLD_58,
+ K_WORLD_59,
+ K_WORLD_60,
+ K_WORLD_61,
+ K_WORLD_62,
+ K_WORLD_63,
+ K_WORLD_64,
+ K_WORLD_65,
+ K_WORLD_66,
+ K_WORLD_67,
+ K_WORLD_68,
+ K_WORLD_69,
+ K_WORLD_70,
+ K_WORLD_71,
+ K_WORLD_72,
+ K_WORLD_73,
+ K_WORLD_74,
+ K_WORLD_75,
+ K_WORLD_76,
+ K_WORLD_77,
+ K_WORLD_78,
+ K_WORLD_79,
+ K_WORLD_80,
+ K_WORLD_81,
+ K_WORLD_82,
+ K_WORLD_83,
+ K_WORLD_84,
+ K_WORLD_85,
+ K_WORLD_86,
+ K_WORLD_87,
+ K_WORLD_88,
+ K_WORLD_89,
+ K_WORLD_90,
+ K_WORLD_91,
+ K_WORLD_92,
+ K_WORLD_93,
+ K_WORLD_94,
+ K_WORLD_95,
+
+ K_SUPER,
+ K_COMPOSE,
+ K_MODE,
+ K_HELP,
+ K_PRINT,
+ K_SYSREQ,
+ K_SCROLLOCK,
+ K_BREAK,
+ K_MENU,
+ K_EURO,
+ K_UNDO,
+
+ // Pseudo-key that brings the console down
+ K_CONSOLE,
+
+ MAX_KEYS
+} keyNum_t;
+
+// MAX_KEYS replaces K_LAST_KEY, however some mods may have used K_LAST_KEY
+// in detecting binds, so we leave it defined to the old hardcoded value
+// of maxiumum keys to prevent mods from crashing older versions of the engine
+#define K_LAST_KEY 256
+
+// The menu code needs to get both key and char events, but
+// to avoid duplicating the paths, the char events are just
+// distinguished by or'ing in K_CHAR_FLAG (ugly)
+#define K_CHAR_FLAG 1024
+
+#endif
diff --git a/code/client/keys.h b/code/client/keys.h
new file mode 100644
index 0000000..0168468
--- /dev/null
+++ b/code/client/keys.h
@@ -0,0 +1,55 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code 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,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+#include "keycodes.h"
+
+typedef struct {
+ qboolean down;
+ int repeats; // if > 1, it is autorepeating
+ char *binding;
+} qkey_t;
+
+extern qboolean 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, qboolean showCursor, qboolean noColorEscape );
+void Field_BigDraw( field_t *edit, int x, int y, int width, qboolean showCursor, qboolean noColorEscape );
+
+#define COMMAND_HISTORY 32
+extern field_t historyEditLines[COMMAND_HISTORY];
+
+extern field_t g_consoleField;
+extern field_t chatField;
+extern int anykeydown;
+extern qboolean chat_team;
+extern int chat_playerNum;
+
+void Key_WriteBindings( fileHandle_t f );
+void Key_SetBinding( int keynum, const char *binding );
+char *Key_GetBinding( int keynum );
+qboolean Key_IsDown( int keynum );
+qboolean Key_GetOverstrikeMode( void );
+void Key_SetOverstrikeMode( qboolean state );
+void Key_ClearStates( void );
+int Key_GetKey(const char *binding);
diff --git a/code/client/libmumblelink.c b/code/client/libmumblelink.c
new file mode 100644
index 0000000..e2e1c42
--- /dev/null
+++ b/code/client/libmumblelink.c
@@ -0,0 +1,185 @@
+/* libmumblelink.c -- mumble link interface
+
+ Copyright (C) 2008 Ludwig Nussel <ludwig.nussel@suse.de>
+
+ 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.
+
+*/
+
+#ifdef WIN32
+#include <windows.h>
+#define uint32_t UINT32
+#else
+#include <unistd.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#endif
+
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "libmumblelink.h"
+
+#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));
+
+ 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), 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), 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/code/client/libmumblelink.h b/code/client/libmumblelink.h
new file mode 100644
index 0000000..7824a1f
--- /dev/null
+++ b/code/client/libmumblelink.h
@@ -0,0 +1,35 @@
+/* libmumblelink.h -- mumble link interface
+
+ Copyright (C) 2008 Ludwig Nussel <ludwig.nussel@suse.de>
+
+ 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.
+
+*/
+
+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);
diff --git a/code/client/qal.c b/code/client/qal.c
new file mode 100644
index 0000000..5ddd67f
--- /dev/null
+++ b/code/client/qal.c
@@ -0,0 +1,351 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2005 Stuart Dalton (badcdev@gmail.com)
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code 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,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+// 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;
+LPALDOPPLERVELOCITY qalDopplerVelocity;
+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 qboolean alinit_fail = qfalse;
+
+/*
+=================
+GPA
+=================
+*/
+static void *GPA(char *str)
+{
+ void *rv;
+
+ rv = Sys_LoadFunction(OpenALLib, str);
+ if(!rv)
+ {
+ Com_Printf( " Can't load symbol %s\n", str);
+ alinit_fail = qtrue;
+ return NULL;
+ }
+ else
+ {
+ Com_DPrintf( " Loaded symbol %s (%p)\n", str, rv);
+ return rv;
+ }
+}
+
+/*
+=================
+QAL_Init
+=================
+*/
+qboolean QAL_Init(const char *libname)
+{
+ if(OpenALLib)
+ return qtrue;
+
+ Com_Printf( "Loading \"%s\"...\n", libname);
+ if( (OpenALLib = Sys_LoadLibrary(libname)) == 0 )
+ {
+#ifdef _WIN32
+ return qfalse;
+#else
+ char fn[1024];
+ Q_strncpyz( fn, Sys_Cwd( ), sizeof( fn ) );
+ strncat(fn, "/", sizeof(fn) - strlen(fn) - 1);
+ strncat(fn, libname, sizeof(fn) - strlen(fn) - 1);
+
+ if( (OpenALLib = Sys_LoadLibrary(fn)) == 0 )
+ {
+ return qfalse;
+ }
+#endif
+ }
+
+ alinit_fail = qfalse;
+
+ qalEnable = GPA("alEnable");
+ qalDisable = GPA("alDisable");
+ qalIsEnabled = GPA("alIsEnabled");
+ qalGetString = GPA("alGetString");
+ qalGetBooleanv = GPA("alGetBooleanv");
+ qalGetIntegerv = GPA("alGetIntegerv");
+ qalGetFloatv = GPA("alGetFloatv");
+ qalGetDoublev = GPA("alGetDoublev");
+ qalGetBoolean = GPA("alGetBoolean");
+ qalGetInteger = GPA("alGetInteger");
+ qalGetFloat = GPA("alGetFloat");
+ qalGetDouble = GPA("alGetDouble");
+ qalGetError = GPA("alGetError");
+ qalIsExtensionPresent = GPA("alIsExtensionPresent");
+ qalGetProcAddress = GPA("alGetProcAddress");
+ qalGetEnumValue = GPA("alGetEnumValue");
+ qalListenerf = GPA("alListenerf");
+ qalListener3f = GPA("alListener3f");
+ qalListenerfv = GPA("alListenerfv");
+ qalListeneri = GPA("alListeneri");
+ qalGetListenerf = GPA("alGetListenerf");
+ qalGetListener3f = GPA("alGetListener3f");
+ qalGetListenerfv = GPA("alGetListenerfv");
+ qalGetListeneri = GPA("alGetListeneri");
+ qalGenSources = GPA("alGenSources");
+ qalDeleteSources = GPA("alDeleteSources");
+ qalIsSource = GPA("alIsSource");
+ qalSourcef = GPA("alSourcef");
+ qalSource3f = GPA("alSource3f");
+ qalSourcefv = GPA("alSourcefv");
+ qalSourcei = GPA("alSourcei");
+ qalGetSourcef = GPA("alGetSourcef");
+ qalGetSource3f = GPA("alGetSource3f");
+ qalGetSourcefv = GPA("alGetSourcefv");
+ qalGetSourcei = GPA("alGetSourcei");
+ qalSourcePlayv = GPA("alSourcePlayv");
+ qalSourceStopv = GPA("alSourceStopv");
+ qalSourceRewindv = GPA("alSourceRewindv");
+ qalSourcePausev = GPA("alSourcePausev");
+ qalSourcePlay = GPA("alSourcePlay");
+ qalSourceStop = GPA("alSourceStop");
+ qalSourceRewind = GPA("alSourceRewind");
+ qalSourcePause = GPA("alSourcePause");
+ qalSourceQueueBuffers = GPA("alSourceQueueBuffers");
+ qalSourceUnqueueBuffers = GPA("alSourceUnqueueBuffers");
+ qalGenBuffers = GPA("alGenBuffers");
+ qalDeleteBuffers = GPA("alDeleteBuffers");
+ qalIsBuffer = GPA("alIsBuffer");
+ qalBufferData = GPA("alBufferData");
+ qalGetBufferf = GPA("alGetBufferf");
+ qalGetBufferi = GPA("alGetBufferi");
+ qalDopplerFactor = GPA("alDopplerFactor");
+ qalDopplerVelocity = GPA("alDopplerVelocity");
+ qalDistanceModel = GPA("alDistanceModel");
+
+ qalcCreateContext = GPA("alcCreateContext");
+ qalcMakeContextCurrent = GPA("alcMakeContextCurrent");
+ qalcProcessContext = GPA("alcProcessContext");
+ qalcSuspendContext = GPA("alcSuspendContext");
+ qalcDestroyContext = GPA("alcDestroyContext");
+ qalcGetCurrentContext = GPA("alcGetCurrentContext");
+ qalcGetContextsDevice = GPA("alcGetContextsDevice");
+ qalcOpenDevice = GPA("alcOpenDevice");
+ qalcCloseDevice = GPA("alcCloseDevice");
+ qalcGetError = GPA("alcGetError");
+ qalcIsExtensionPresent = GPA("alcIsExtensionPresent");
+ qalcGetProcAddress = GPA("alcGetProcAddress");
+ qalcGetEnumValue = GPA("alcGetEnumValue");
+ qalcGetString = GPA("alcGetString");
+ qalcGetIntegerv = GPA("alcGetIntegerv");
+ qalcCaptureOpenDevice = GPA("alcCaptureOpenDevice");
+ qalcCaptureCloseDevice = GPA("alcCaptureCloseDevice");
+ qalcCaptureStart = GPA("alcCaptureStart");
+ qalcCaptureStop = GPA("alcCaptureStop");
+ qalcCaptureSamples = GPA("alcCaptureSamples");
+
+ if(alinit_fail)
+ {
+ QAL_Shutdown();
+ Com_Printf( " One or more symbols not found\n");
+ return qfalse;
+ }
+
+ return qtrue;
+}
+
+/*
+=================
+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;
+ qalDopplerVelocity = 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
+qboolean QAL_Init(const char *libname)
+{
+ return qtrue;
+}
+void QAL_Shutdown( void )
+{
+}
+#endif
+#endif
diff --git a/code/client/qal.h b/code/client/qal.h
new file mode 100644
index 0000000..1a2284a
--- /dev/null
+++ b/code/client/qal.h
@@ -0,0 +1,245 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2005 Stuart Dalton (badcdev@gmail.com)
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code 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,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+
+#ifndef __QAL_H__
+#define __QAL_H__
+
+#include "../qcommon/q_shared.h"
+#include "../qcommon/qcommon.h"
+
+#ifdef USE_OPENAL_DLOPEN
+#define AL_NO_PROTOTYPES
+#define ALC_NO_PROTOTYPES
+#endif
+
+#ifdef USE_LOCAL_HEADERS
+#include "../AL/al.h"
+#include "../AL/alc.h"
+#else
+#ifdef _MSC_VER
+ // MSVC users must install the OpenAL SDK which doesn't use the AL/*.h scheme.
+ #include <al.h>
+ #include <alc.h>
+#else
+ #include <AL/al.h>
+ #include <AL/alc.h>
+#endif
+#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 LPALDOPPLERVELOCITY qalDopplerVelocity;
+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 qalDopplerVelocity alDopplerVelocity
+#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
+
+qboolean QAL_Init(const char *libname);
+void QAL_Shutdown( void );
+
+#endif // __QAL_H__
diff --git a/code/client/snd_adpcm.c b/code/client/snd_adpcm.c
new file mode 100644
index 0000000..89e68f4
--- /dev/null
+++ b/code/client/snd_adpcm.c
@@ -0,0 +1,330 @@
+/***********************************************************
+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 {
+ 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/code/client/snd_codec.c b/code/client/snd_codec.c
new file mode 100644
index 0000000..0483e8d
--- /dev/null
+++ b/code/client/snd_codec.c
@@ -0,0 +1,237 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2005 Stuart Dalton (badcdev@gmail.com)
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code 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,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+#include "client.h"
+#include "snd_codec.h"
+
+static snd_codec_t *codecs;
+
+/*
+=================
+S_FileExtension
+=================
+*/
+static char *S_FileExtension(const char *fni)
+{
+ // we should search from the ending to the last '/'
+
+ char *fn = (char *) fni + strlen(fni) - 1;
+ char *eptr = NULL;
+
+ while(*fn != '/' && fn != fni)
+ {
+ if(*fn == '.')
+ {
+ eptr = fn;
+ break;
+ }
+ fn--;
+ }
+
+ return eptr;
+}
+
+/*
+=================
+S_FindCodecForFile
+
+Select an appropriate codec for a file based on its extension
+=================
+*/
+static snd_codec_t *S_FindCodecForFile(const char *filename)
+{
+ char *ext = S_FileExtension(filename);
+ snd_codec_t *codec = codecs;
+
+ if(!ext)
+ {
+ // No extension - auto-detect
+ while(codec)
+ {
+ char fn[MAX_QPATH];
+
+ // there is no extension so we do not need to subtract 4 chars
+ Q_strncpyz(fn, filename, MAX_QPATH);
+ COM_DefaultExtension(fn, MAX_QPATH, codec->ext);
+
+ // Check it exists
+ if(FS_ReadFile(fn, NULL) != -1)
+ return codec;
+
+ // Nope. Next!
+ codec = codec->next;
+ }
+
+ // Nothin'
+ return NULL;
+ }
+
+ while(codec)
+ {
+ if(!Q_stricmp(ext, codec->ext))
+ return codec;
+ codec = codec->next;
+ }
+
+ return NULL;
+}
+
+/*
+=================
+S_CodecInit
+=================
+*/
+void S_CodecInit()
+{
+ codecs = NULL;
+ S_CodecRegister(&wav_codec);
+#ifdef USE_CODEC_VORBIS
+ S_CodecRegister(&ogg_codec);
+#endif
+}
+
+/*
+=================
+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)
+{
+ snd_codec_t *codec;
+ char fn[MAX_QPATH];
+
+ codec = S_FindCodecForFile(filename);
+ if(!codec)
+ {
+ Com_Printf("Unknown extension for %s\n", filename);
+ return NULL;
+ }
+
+ strncpy(fn, filename, sizeof(fn));
+ COM_DefaultExtension(fn, sizeof(fn), codec->ext);
+
+ return codec->load(fn, info);
+}
+
+/*
+=================
+S_CodecOpenStream
+=================
+*/
+snd_stream_t *S_CodecOpenStream(const char *filename)
+{
+ snd_codec_t *codec;
+ char fn[MAX_QPATH];
+
+ codec = S_FindCodecForFile(filename);
+ if(!codec)
+ {
+ Com_Printf("Unknown extension for %s\n", filename);
+ return NULL;
+ }
+
+ strncpy(fn, filename, sizeof(fn));
+ COM_DefaultExtension(fn, sizeof(fn), codec->ext);
+
+ return codec->open(fn);
+}
+
+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, qtrue);
+ if(!hnd)
+ {
+ Com_Printf("Can't read sound file %s\n", filename);
+ return NULL;
+ }
+
+ // Allocate a stream
+ stream = 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/code/client/snd_codec.h b/code/client/snd_codec.h
new file mode 100644
index 0000000..03fcaa2
--- /dev/null
+++ b/code/client/snd_codec.h
@@ -0,0 +1,98 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2005 Stuart Dalton (badcdev@gmail.com)
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code 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,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+#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
+{
+ 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
+
+#endif // !_SND_CODEC_H_
diff --git a/code/client/snd_codec_ogg.c b/code/client/snd_codec_ogg.c
new file mode 100644
index 0000000..48da5d7
--- /dev/null
+++ b/code/client/snd_codec_ogg.c
@@ -0,0 +1,477 @@
+/*
+===========================================================================
+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>
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code 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,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+// OGG support is enabled by this define
+#ifdef USE_CODEC_VORBIS
+
+// includes for the Q3 sound system
+#include "client.h"
+#include "snd_codec.h"
+
+// includes for the OGG codec
+#include <errno.h>
+#include <vorbis/vorbisfile.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 :
+ {
+ // Quake 3 seems to have trouble with FS_SEEK_END
+ // so we use the file length and FS_SEEK_SET
+
+ // set the file position in the actual file with the Q3 function
+ retVal = FS_Seek(stream->file, (long) stream->length + (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 = 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 = 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 = 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 = Z_Malloc(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)
+ {
+ Z_Free(buffer);
+ S_OGG_CodecCloseStream(stream);
+
+ return NULL;
+ }
+
+ S_OGG_CodecCloseStream(stream);
+
+ return buffer;
+}
+
+#endif // USE_CODEC_VORBIS
diff --git a/code/client/snd_codec_wav.c b/code/client/snd_codec_wav.c
new file mode 100644
index 0000000..c0b2e2e
--- /dev/null
+++ b/code/client/snd_codec_wav.c
@@ -0,0 +1,294 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2005 Stuart Dalton (badcdev@gmail.com)
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code 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,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+#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, 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 qboolean S_ReadRIFFHeader(fileHandle_t file, snd_info_t *info)
+{
+ char dump[16];
+ int wav_format;
+ 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 qfalse;
+ }
+
+ // Save the parameters
+ wav_format = FGetLittleShort(file);
+ 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 qfalse;
+ }
+
+ 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 qfalse;
+ }
+ info->samples = (info->size / info->width) / info->channels;
+
+ return qtrue;
+}
+
+// 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, qtrue);
+ if(!file)
+ {
+ Com_Printf( S_COLOR_RED "ERROR: Could not open \"%s\"\n",
+ filename);
+ 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 = Z_Malloc(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, buffer);
+ return bytes;
+}
diff --git a/code/client/snd_dma.c b/code/client/snd_dma.c
new file mode 100644
index 0000000..428d1d4
--- /dev/null
+++ b/code/client/snd_dma.c
@@ -0,0 +1,1541 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code 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,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+/*****************************************************************************
+ * 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 int s_soundStarted;
+static qboolean 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_khz;
+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
+ Com_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, "S_FindName: NULL\n");
+ }
+ if (!name[0]) {
+ Com_Error (ERR_FATAL, "S_FindName: empty name\n");
+ }
+
+ if (strlen(name) >= MAX_QPATH) {
+ Com_Error (ERR_FATAL, "Sound name too long: %s", name);
+ }
+
+ 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];
+ Com_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 = qtrue;
+}
+
+/*
+==================
+S_RegisterSound
+
+Creates a default buzz sound if the file can't be loaded
+==================
+*/
+sfxHandle_t S_Base_RegisterSound( const char *name, qboolean compressed ) {
+ sfx_t *sfx;
+
+ compressed = qfalse;
+ if (!s_soundStarted) {
+ return 0;
+ }
+
+ if ( strlen( name ) >= MAX_QPATH ) {
+ Com_Printf( "Sound name exceeds MAX_QPATH\n" );
+ return 0;
+ }
+
+ sfx = S_FindName( name );
+ 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 = qfalse;
+ 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_BeginRegistration
+
+=====================
+*/
+void S_Base_BeginRegistration( void ) {
+ s_soundMuted = qfalse; // we can play again
+
+ if (s_numSfx == 0) {
+ SND_setup();
+
+ s_numSfx = 0;
+ Com_Memset( s_knownSfx, 0, sizeof( s_knownSfx ) );
+ Com_Memset(sfxHash, 0, sizeof(sfx_t *)*LOOP_HASH);
+
+ S_Base_RegisterSound("sound/feedback/hit.wav", qfalse); // 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 = qtrue;
+ }
+ sfx->inMemory = qtrue;
+}
+
+//=============================================================================
+
+/*
+=================
+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_StartSound
+
+Validates the parms and ques the sound up
+if pos is NULL, the sound will be dynamically sourced from the entity
+Entchannel 0 will never override a playing sound
+====================
+*/
+void S_Base_StartSound(vec3_t origin, int entityNum, int entchannel, sfxHandle_t sfxHandle ) {
+ channel_t *ch;
+ sfx_t *sfx;
+ int i, oldest, chosen, time;
+ int inplay, allowed;
+
+ 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 == qfalse) {
+ 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;
+ }
+
+ 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_Printf("dropping sound\n");
+ return;
+ }
+ }
+ }
+ ch = &s_channels[chosen];
+ ch->allocTime = sfx->lastTimeUsed;
+ }
+
+ if (origin) {
+ VectorCopy (origin, ch->origin);
+ ch->fixed_origin = qtrue;
+ } else {
+ ch->fixed_origin = qfalse;
+ }
+
+ 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 = qfalse;
+}
+
+
+/*
+==================
+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_StartSound (NULL, listener_number, channelNum, sfxHandle );
+}
+
+
+/*
+==================
+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
+ Com_Memset(loopSounds, 0, MAX_GENTITIES*sizeof(loopSound_t));
+ Com_Memset(loop_channels, 0, MAX_CHANNELS*sizeof(channel_t));
+ numLoopChannels = 0;
+
+ S_ChannelSetup();
+
+ Com_Memset(s_rawend, '\0', sizeof (s_rawend));
+
+ if (dma.samplebits == 8)
+ clear = 0x80;
+ else
+ clear = 0;
+
+ SNDDMA_BeginPainting ();
+ if (dma.buffer)
+ Com_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 = qfalse;
+// loopSounds[entityNum].sfx = 0;
+ loopSounds[entityNum].kill = qfalse;
+}
+
+/*
+==================
+S_ClearLoopingSounds
+
+==================
+*/
+void S_Base_ClearLoopingSounds( qboolean killall ) {
+ int i;
+ for ( i = 0 ; i < MAX_GENTITIES ; i++) {
+ if (killall || loopSounds[i].kill == qtrue || (loopSounds[i].sfx && loopSounds[i].sfx->soundLength == 0)) {
+ loopSounds[i].kill = qfalse;
+ 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 == qfalse) {
+ 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 = qtrue;
+ loopSounds[entityNum].kill = qtrue;
+ loopSounds[entityNum].doppler = qfalse;
+ 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 = qtrue;
+ 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 = qfalse; // 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 == qfalse) {
+ 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 = qtrue;
+ loopSounds[entityNum].kill = qfalse;
+ loopSounds[entityNum].doppler = qfalse;
+}
+
+
+
+/*
+==================
+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;
+ 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 i;
+ int src, dst;
+ float scale;
+ int intVolume;
+ 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)
+ intVolume = 0;
+ else
+ intVolume = 256 * 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] * intVolume;
+ rawsamples[dst].right = ((short *)data)[i*2+1] * intVolume;
+ }
+ }
+ 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] * intVolume;
+ rawsamples[dst].right = ((short *)data)[src*2+1] * intVolume;
+ }
+ }
+ }
+ 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] * intVolume;
+ rawsamples[dst].right = ((short *)data)[src] * intVolume;
+ }
+ }
+ else if (s_channels == 2 && width == 1)
+ {
+ intVolume *= 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] * intVolume;
+ rawsamples[dst].right = ((char *)data)[src*2+1] * intVolume;
+ }
+ }
+ else if (s_channels == 1 && width == 1)
+ {
+ intVolume *= 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) * intVolume;
+ rawsamples[dst].right = (((byte *)data)[src]-128) * intVolume;
+ }
+ }
+
+ 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;
+ }
+ // anything coming from the view entity will always be full volume
+ if (ch->entnum == listener_number) {
+ 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 qtrue if any new sounds were started since the last mix
+========================
+*/
+qboolean S_ScanChannelStarts( void ) {
+ channel_t *ch;
+ int i;
+ qboolean newSamples;
+
+ newSamples = qfalse;
+ 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 = qtrue;
+ 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( ) )
+ {
+ s_soundtime += (int)ceil( dma.speed / cl_aviFrameRate->value );
+ 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_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;
+ }
+
+ if( !loop ) {
+ s_backgroundLoop[0] = 0;
+ } else {
+ Q_strncpyz( s_backgroundLoop, loop, sizeof( s_backgroundLoop ) );
+ }
+
+ // 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(intro);
+ if(!s_backgroundStream) {
+ Com_Printf( S_COLOR_YELLOW "WARNING: couldn't open music file %s\n", intro );
+ 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", 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)
+ {
+ fileBytes = r;
+ 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 );
+ }
+ else
+ {
+ // loop
+ if(s_backgroundLoop[0])
+ {
+ S_CodecCloseStream(s_backgroundStream);
+ s_backgroundStream = NULL;
+ S_Base_StartBackgroundTrack( s_backgroundLoop, 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 = qfalse;
+ sfx->soundData = NULL;
+}
+
+// =======================================================================
+// Shutdown sound engine
+// =======================================================================
+
+void S_Base_Shutdown( void ) {
+ if ( !s_soundStarted ) {
+ return;
+ }
+
+ SNDDMA_Shutdown();
+
+ s_soundStarted = 0;
+
+ Cmd_RemoveCommand("s_info");
+}
+
+/*
+================
+S_Init
+================
+*/
+qboolean S_Base_Init( soundInterface_t *si ) {
+ qboolean r;
+
+ if( !si ) {
+ return qfalse;
+ }
+
+ s_khz = Cvar_Get ("s_khz", "22", CVAR_ARCHIVE);
+ 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);
+
+ r = SNDDMA_Init();
+
+ if ( r ) {
+ s_soundStarted = 1;
+ s_soundMuted = 1;
+// s_numSfx = 0;
+
+ Com_Memset(sfxHash, 0, sizeof(sfx_t *)*LOOP_HASH);
+
+ s_soundtime = 0;
+ s_paintedtime = 0;
+
+ S_Base_StopAllSounds( );
+ } else {
+ return qfalse;
+ }
+
+ 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->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 qtrue;
+}
diff --git a/code/client/snd_local.h b/code/client/snd_local.h
new file mode 100644
index 0000000..775e576
--- /dev/null
+++ b/code/client/snd_local.h
@@ -0,0 +1,251 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code 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,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+// snd_local.h -- private sound definations
+
+
+#include "../qcommon/q_shared.h"
+#include "../qcommon/qcommon.h"
+#include "snd_public.h"
+
+#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;
+ qboolean defaultSound; // couldn't be loaded, so use buzz
+ qboolean inMemory; // not in Memory
+ qboolean soundCompressed; // not in Memory
+ int soundCompressionMethod;
+ int soundLength;
+ char soundName[MAX_QPATH];
+ int lastTimeUsed;
+ 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
+
+typedef struct loopSound_s {
+ vec3_t origin;
+ vec3_t velocity;
+ sfx_t *sfx;
+ int mergeFrame;
+ qboolean active;
+ qboolean kill;
+ qboolean doppler;
+ float dopplerScale;
+ float oldDopplerScale;
+ int framenum;
+} loopSound_t;
+
+typedef struct
+{
+ 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
+ qboolean fixed_origin; // use origin instead of fetching entnum's origin
+ sfx_t *thesfx; // sfx structure
+ qboolean doppler;
+} 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);
+ void (*StopAllSounds)( void );
+ void (*ClearLoopingSounds)( qboolean 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, qboolean compressed );
+ 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
+qboolean 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 128
+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;
+
+qboolean S_LoadSound( sfx_t *sfx );
+
+void SND_free(sndBuffer *v);
+sndBuffer* SND_malloc( void );
+void SND_setup( 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;
+
+qboolean 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;
+
+qboolean S_AL_Init( soundInterface_t *si );
diff --git a/code/client/snd_main.c b/code/client/snd_main.c
new file mode 100644
index 0000000..7d4ed68
--- /dev/null
+++ b/code/client/snd_main.c
@@ -0,0 +1,550 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2005 Stuart Dalton (badcdev@gmail.com)
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code 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,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Foobar; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+#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 qboolean S_ValidSoundInterface( soundInterface_t *si )
+{
+ if( !si->Shutdown ) return qfalse;
+ if( !si->StartSound ) return qfalse;
+ if( !si->StartLocalSound ) return qfalse;
+ if( !si->StartBackgroundTrack ) return qfalse;
+ if( !si->StopBackgroundTrack ) return qfalse;
+ if( !si->RawSamples ) return qfalse;
+ if( !si->StopAllSounds ) return qfalse;
+ if( !si->ClearLoopingSounds ) return qfalse;
+ if( !si->AddLoopingSound ) return qfalse;
+ if( !si->AddRealLoopingSound ) return qfalse;
+ if( !si->StopLoopingSound ) return qfalse;
+ if( !si->Respatialize ) return qfalse;
+ if( !si->UpdateEntityPosition ) return qfalse;
+ if( !si->Update ) return qfalse;
+ if( !si->DisableSounds ) return qfalse;
+ if( !si->BeginRegistration ) return qfalse;
+ if( !si->RegisterSound ) return qfalse;
+ if( !si->ClearSoundBuffer ) return qfalse;
+ if( !si->SoundInfo ) return qfalse;
+ if( !si->SoundList ) return qfalse;
+
+#ifdef USE_VOIP
+ if( !si->StartCapture ) return qfalse;
+ if( !si->AvailableCaptureSamples ) return qfalse;
+ if( !si->Capture ) return qfalse;
+ if( !si->StopCapture ) return qfalse;
+ if( !si->MasterGain ) return qfalse;
+#endif
+
+ return qtrue;
+}
+
+/*
+=================
+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)
+{
+ if( si.RawSamples ) {
+ si.RawSamples( stream, samples, rate, width, channels, data, volume );
+ }
+}
+
+/*
+=================
+S_StopAllSounds
+=================
+*/
+void S_StopAllSounds( void )
+{
+ if( si.StopAllSounds ) {
+ si.StopAllSounds( );
+ }
+}
+
+/*
+=================
+S_ClearLoopingSounds
+=================
+*/
+void S_ClearLoopingSounds( qboolean 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 = qfalse;
+ s_muted->modified = qtrue;
+ }
+ }
+ else
+ {
+ if((s_muteWhenMinimized->integer && com_minimized->integer) ||
+ (s_muteWhenUnfocused->integer && com_unfocused->integer))
+ {
+ s_muted->integer = qtrue;
+ s_muted->modified = qtrue;
+ }
+ }
+
+ 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, qboolean compressed )
+{
+ if( si.RegisterSound ) {
+ return si.RegisterSound( sample, compressed );
+ } 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;
+ sfxHandle_t h;
+ char name[256];
+
+ if( !si.RegisterSound || !si.StartLocalSound ) {
+ return;
+ }
+
+ i = 1;
+ while ( i<Cmd_Argc() ) {
+ if ( !Q_strrchr(Cmd_Argv(i), '.') ) {
+ Com_sprintf( name, sizeof(name), "%s.wav", Cmd_Argv(1) );
+ } else {
+ Q_strncpyz( name, Cmd_Argv(i), sizeof(name) );
+ }
+ h = si.RegisterSound( name, qfalse );
+ if( h ) {
+ si.StartLocalSound( h, CHAN_LOCAL_SOUND );
+ }
+ i++;
+ }
+}
+
+/*
+=================
+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 ("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;
+ qboolean started = qfalse;
+
+ 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", "1", 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( );
+ }
+
+ Com_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/code/client/snd_mem.c b/code/client/snd_mem.c
new file mode 100644
index 0000000..8c3f98e
--- /dev/null
+++ b/code/client/snd_mem.c
@@ -0,0 +1,265 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code 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,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+/*****************************************************************************
+ * name: snd_mem.c
+ *
+ * desc: sound caching
+ *
+ * $Archive: /MissionPack/code/client/snd_mem.c $
+ *
+ *****************************************************************************/
+
+#include "snd_local.h"
+#include "snd_codec.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) {
+ sndBuffer *p, *q;
+ cvar_t *cv;
+ int scs;
+
+ cv = Cvar_Get( "com_soundMegs", DEF_COMSOUNDMEGS, CVAR_LATCH | CVAR_ARCHIVE );
+
+ scs = (cv->integer*1536);
+
+ buffer = malloc(scs*sizeof(sndBuffer) );
+ // allocate the stack based hunk allocator
+ sfxScratchBuffer = malloc(SND_CHUNK_SIZE * sizeof(short) * 4); //Hunk_Alloc(SND_CHUNK_SIZE * sizeof(short) * 4);
+ sfxScratchPointer = NULL;
+
+ inUse = scs*sizeof(sndBuffer);
+ p = buffer;;
+ q = p + scs;
+ while (--q > p)
+ *(sndBuffer **)q = q-1;
+
+ *(sndBuffer **)q = NULL;
+ freelist = p + scs - 1;
+
+ Com_Printf("Sound memory manager started\n");
+}
+
+/*
+================
+ResampleSfx
+
+resample / decimate to the current source rate
+================
+*/
+static void ResampleSfx( sfx_t *sfx, int inrate, int inwidth, byte *data, qboolean compressed ) {
+ int outcount;
+ int srcsample;
+ float stepscale;
+ int i;
+ int sample, samplefrac, fracstep;
+ int part;
+ sndBuffer *chunk;
+
+ stepscale = (float)inrate / dma.speed; // this is usually 0.5, 1, or 2
+
+ outcount = sfx->soundLength / stepscale;
+ sfx->soundLength = outcount;
+
+ samplefrac = 0;
+ fracstep = stepscale * 256;
+ chunk = sfx->soundData;
+
+ for (i=0 ; i<outcount ; i++)
+ {
+ srcsample = samplefrac >> 8;
+ samplefrac += fracstep;
+ if( inwidth == 2 ) {
+ sample = ( ((short *)data)[srcsample] );
+ } else {
+ sample = (int)( (unsigned char)(data[srcsample]) - 128) << 8;
+ }
+ part = (i&(SND_CHUNK_SIZE-1));
+ if (part == 0) {
+ sndBuffer *newchunk;
+ newchunk = SND_malloc();
+ if (chunk == NULL) {
+ sfx->soundData = newchunk;
+ } else {
+ chunk->next = newchunk;
+ }
+ chunk = newchunk;
+ }
+
+ chunk->sndChunk[part] = sample;
+ }
+}
+
+/*
+================
+ResampleSfx
+
+resample / decimate to the current source rate
+================
+*/
+static int ResampleSfxRaw( short *sfx, int inrate, int inwidth, int samples, byte *data ) {
+ int outcount;
+ int srcsample;
+ float stepscale;
+ int i;
+ int sample, samplefrac, fracstep;
+
+ stepscale = (float)inrate / dma.speed; // this is usually 0.5, 1, or 2
+
+ outcount = samples / stepscale;
+
+ samplefrac = 0;
+ fracstep = stepscale * 256;
+
+ for (i=0 ; i<outcount ; i++)
+ {
+ srcsample = samplefrac >> 8;
+ samplefrac += fracstep;
+ if( inwidth == 2 ) {
+ sample = LittleShort ( ((short *)data)[srcsample] );
+ } else {
+ sample = (int)( (unsigned char)(data[srcsample]) - 128) << 8;
+ }
+ sfx[i] = 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
+==============
+*/
+qboolean S_LoadSound( sfx_t *sfx )
+{
+ byte *data;
+ short *samples;
+ snd_info_t info;
+// int size;
+
+ // player specific sounds are never directly loaded
+ if ( sfx->soundName[0] == '*') {
+ return qfalse;
+ }
+
+ // load it in
+ data = S_CodecLoad(sfx->soundName, &info);
+ if(!data)
+ return qfalse;
+
+ if ( info.width == 1 ) {
+ Com_DPrintf(S_COLOR_YELLOW "WARNING: %s is a 8 bit wav file\n", sfx->soundName);
+ }
+
+ if ( info.rate != 22050 ) {
+ Com_DPrintf(S_COLOR_YELLOW "WARNING: %s is not a 22kHz wav file\n", sfx->soundName);
+ }
+
+ samples = Hunk_AllocateTempMemory(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( sfx->soundCompressed == qtrue) {
+ sfx->soundCompressionMethod = 1;
+ sfx->soundData = NULL;
+ sfx->soundLength = ResampleSfxRaw( samples, info.rate, info.width, info.samples, data + info.dataofs );
+ S_AdpcmEncodeSound(sfx, samples);
+#if 0
+ } else if (info.samples>(SND_CHUNK_SIZE*16) && info.width >1) {
+ sfx->soundCompressionMethod = 3;
+ sfx->soundData = NULL;
+ sfx->soundLength = ResampleSfxRaw( samples, info.rate, info.width, info.samples, (data + info.dataofs) );
+ encodeMuLaw( sfx, samples);
+ } else if (info.samples>(SND_CHUNK_SIZE*6400) && info.width >1) {
+ sfx->soundCompressionMethod = 2;
+ sfx->soundData = NULL;
+ sfx->soundLength = ResampleSfxRaw( samples, info.rate, info.width, info.samples, (data + info.dataofs) );
+ encodeWavelet( sfx, samples);
+#endif
+ } else {
+ sfx->soundCompressionMethod = 0;
+ sfx->soundLength = info.samples;
+ sfx->soundData = NULL;
+ ResampleSfx( sfx, info.rate, info.width, data + info.dataofs, qfalse );
+ }
+
+ Hunk_FreeTempMemory(samples);
+ Z_Free(data);
+
+ return qtrue;
+}
+
+void S_DisplayFreeMemory(void) {
+ Com_Printf("%d bytes free sound buffer memory, %d total used\n", inUse, totalInUse);
+}
diff --git a/code/client/snd_mix.c b/code/client/snd_mix.c
new file mode 100644
index 0000000..11de66d
--- /dev/null
+++ b/code/client/snd_mix.c
@@ -0,0 +1,741 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code 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,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+// snd_mix.c -- portable code to mix sounds for snd_dma.c
+
+#include "client.h"
+#include "snd_local.h"
+#if idppc_altivec && !defined(MACOS_X)
+#include <altivec.h>
+#endif
+
+static portable_samplepair_t paintbuffer[PAINTBUFFER_SIZE];
+static int snd_vol;
+
+int* snd_p;
+int snd_linear_count;
+short* snd_out;
+
+#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;
+ int count;
+
+ // 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, fdiv, fleftvol, frightvol;
+
+ samp = &paintbuffer[ bufferOffset ];
+
+ if (ch->doppler) {
+ sampleOffset = sampleOffset*ch->oldDopplerScale;
+ }
+
+ 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;
+ 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;
+ boff = ooff;
+ fdata = 0;
+ for (j=aoff; j<boff; j++) {
+ if (j == SND_CHUNK_SIZE) {
+ chunk = chunk->next;
+ if (!chunk) {
+ chunk = sc->soundData;
+ }
+ samples = chunk->sndChunk;
+ ooff -= SND_CHUNK_SIZE;
+ }
+ fdata += samples[j&(SND_CHUNK_SIZE-1)];
+ }
+ fdiv = 256 * (boff-aoff);
+ samp[i].left += (fdata * fleftvol)/fdiv;
+ samp[i].right += (fdata * 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, fdiv, fleftvol, frightvol;
+
+ samp = &paintbuffer[ bufferOffset ];
+
+ if (ch->doppler) {
+ sampleOffset = sampleOffset*ch->oldDopplerScale;
+ }
+
+ 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;
+ 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;
+ boff = ooff;
+ fdata = 0;
+ for (j=aoff; j<boff; j++) {
+ if (j == SND_CHUNK_SIZE) {
+ chunk = chunk->next;
+ if (!chunk) {
+ chunk = sc->soundData;
+ }
+ samples = chunk->sndChunk;
+ ooff -= SND_CHUNK_SIZE;
+ }
+ fdata += samples[j&(SND_CHUNK_SIZE-1)];
+ }
+ fdiv = 256 * (boff-aoff);
+ samp[i].left += (fdata * fleftvol)/fdiv;
+ samp[i].right += (fdata * 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 (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...
+ Com_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;
+
+ 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/code/client/snd_openal.c b/code/client/snd_openal.c
new file mode 100644
index 0000000..b635989
--- /dev/null
+++ b/code/client/snd_openal.c
@@ -0,0 +1,2504 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2005 Stuart Dalton (badcdev@gmail.com)
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code 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,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+#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_alAvailableDevices;
+
+/*
+=================
+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( qboolean 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..
+
+ qboolean isDefault; // Couldn't be loaded - use default FX
+ qboolean inMemory; // Sound is stored in memory
+ qboolean isLocked; // Sound is locked (can not be unloaded)
+ int lastUsedTime; // Time last used
+
+ 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 qboolean alBuffersInitialised = qfalse;
+
+// Sound effect storage, data structures
+#define MAX_SFX 4096
+static alSfx_t knownSfx[MAX_SFX];
+static int 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;
+
+ 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\n", knownSfx[sfx].filename);
+
+ Com_Printf( S_COLOR_YELLOW "WARNING: Using default sound for %s\n", knownSfx[sfx].filename);
+ knownSfx[sfx].isDefault = qtrue;
+ knownSfx[sfx].buffer = knownSfx[default_sfx].buffer;
+}
+
+/*
+=================
+S_AL_BufferUnload
+=================
+*/
+static void S_AL_BufferUnload(sfxHandle_t sfx)
+{
+ ALenum error;
+
+ if(knownSfx[sfx].filename[0] == '\0')
+ return;
+
+ if(!knownSfx[sfx].inMemory)
+ return;
+
+ // Delete it
+ S_AL_ClearError( qfalse );
+ qalDeleteBuffers(1, &knownSfx[sfx].buffer);
+ if((error = qalGetError()) != AL_NO_ERROR)
+ Com_Printf( S_COLOR_RED "ERROR: Can't delete sound buffer for %s\n",
+ knownSfx[sfx].filename);
+
+ knownSfx[sfx].inMemory = qfalse;
+}
+
+/*
+=================
+S_AL_BufferEvict
+=================
+*/
+static qboolean 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 qtrue;
+ }
+ else
+ return qfalse;
+}
+
+/*
+=================
+S_AL_BufferLoad
+=================
+*/
+static void S_AL_BufferLoad(sfxHandle_t sfx)
+{
+ ALenum error;
+ ALuint format;
+
+ void *data;
+ snd_info_t info;
+ alSfx_t *curSfx = &knownSfx[sfx];
+
+ // Nothing?
+ if(curSfx->filename[0] == '\0')
+ return;
+
+ // Player SFX
+ if(curSfx->filename[0] == '*')
+ return;
+
+ // Already done?
+ if((curSfx->inMemory) || (curSfx->isDefault))
+ return;
+
+ // Try to load
+ data = S_CodecLoad(curSfx->filename, &info);
+ if(!data)
+ {
+ S_AL_BufferUseDefault(sfx);
+ return;
+ }
+
+ format = S_AL_Format(info.width, info.channels);
+
+ // Create a buffer
+ S_AL_ClearError( qfalse );
+ qalGenBuffers(1, &curSfx->buffer);
+ if((error = qalGetError()) != AL_NO_ERROR)
+ {
+ S_AL_BufferUseDefault(sfx);
+ Z_Free(data);
+ Com_Printf( S_COLOR_RED "ERROR: Can't create a sound buffer for %s - %s\n",
+ curSfx->filename, S_AL_ErrorMsg(error));
+ 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( ) )
+ {
+ S_AL_BufferUseDefault(sfx);
+ Z_Free(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)
+ {
+ S_AL_BufferUseDefault(sfx);
+ Z_Free(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
+ Z_Free(data);
+
+ // Woo!
+ curSfx->inMemory = qtrue;
+}
+
+/*
+=================
+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);
+ knownSfx[sfx].lastUsedTime = Sys_Milliseconds();
+}
+
+/*
+=================
+S_AL_BufferInit
+=================
+*/
+static
+qboolean S_AL_BufferInit( void )
+{
+ if(alBuffersInitialised)
+ return qtrue;
+
+ // 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 = qtrue;
+
+ // All done
+ alBuffersInitialised = qtrue;
+ return qtrue;
+}
+
+/*
+=================
+S_AL_BufferShutdown
+=================
+*/
+static
+void S_AL_BufferShutdown( void )
+{
+ int i;
+
+ if(!alBuffersInitialised)
+ return;
+
+ // Unlock the default sound effect
+ knownSfx[default_sfx].isLocked = qfalse;
+
+ // Free all used effects
+ for(i = 0; i < numSfx; i++)
+ S_AL_BufferUnload(i);
+
+ // Clear the tables
+ memset(knownSfx, 0, sizeof(knownSfx));
+
+ // All undone
+ alBuffersInitialised = qfalse;
+}
+
+/*
+=================
+S_AL_RegisterSound
+=================
+*/
+static
+sfxHandle_t S_AL_RegisterSound( const char *sample, qboolean compressed )
+{
+ sfxHandle_t sfx = S_AL_BufferFind(sample);
+
+ if( s_alPrecache->integer && (!knownSfx[sfx].inMemory) && (!knownSfx[sfx].isDefault))
+ S_AL_BufferLoad(sfx);
+ knownSfx[sfx].lastUsedTime = Com_Milliseconds();
+
+ return sfx;
+}
+
+/*
+=================
+S_AL_BufferGet
+
+Return's an 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)
+
+ qboolean isActive; // Is this source currently in use?
+ qboolean isPlaying; // Is this source currently playing, or stopped?
+ qboolean isLocked; // This is locked (un-allocatable)
+ qboolean isLooping; // Is this a looping effect (attached to an entity)
+ qboolean isTracking; // Is this object tracking its owner
+
+ 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
+
+ qboolean local; // Is this local (relative to the cam)
+} src_t;
+
+#ifdef MACOS_X
+ #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 qboolean alSourcesInitialised = qfalse;
+static vec3_t lastListenerOrigin = { 0.0f, 0.0f, 0.0f };
+
+typedef struct sentity_s
+{
+ vec3_t origin;
+
+ qboolean srcAllocated; // If a src_t has been allocated to this entity
+ int srcIndex;
+
+ qboolean loopAddedThisFrame;
+ alSrcPriority_t loopPriority;
+ sfxHandle_t loopSfx;
+ qboolean 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 );
+ }
+}
+
+
+#define AL_THIRD_PERSON_THRESHOLD_SQ (48.0f*48.0f)
+
+/*
+=================
+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
+=================
+*/
+static qboolean S_AL_HearingThroughEntity( int entityNum )
+{
+ float distanceSq;
+
+ if( clc.clientNum == entityNum )
+ {
+ // FIXME: <tim@ngus.net> 28/02/06 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(
+ entityList[ entityNum ].origin,
+ lastListenerOrigin );
+
+ if( distanceSq > AL_THIRD_PERSON_THRESHOLD_SQ )
+ return qfalse; //we're the player, but third person
+ else
+ return qtrue; //we're the player
+ }
+ else
+ return qfalse; //not the player
+}
+
+/*
+=================
+S_AL_SrcInit
+=================
+*/
+static
+qboolean S_AL_SrcInit( void )
+{
+ int i;
+ int limit;
+ ALenum error;
+
+ // 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( qfalse );
+ // Allocate as many sources as possible
+ for(i = 0; i < limit; i++)
+ {
+ qalGenSources(1, &srcList[i].alSource);
+ if((error = qalGetError()) != AL_NO_ERROR)
+ break;
+ srcCount++;
+ }
+
+ // All done. Print this for informational purposes
+ Com_Printf( "Allocated %d sources.\n", srcCount);
+ alSourcesInitialised = qtrue;
+ return qtrue;
+}
+
+/*
+=================
+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 = qfalse;
+
+ qalSourceStop(srcList[i].alSource);
+ qalDeleteSources(1, &srcList[i].alSource);
+ }
+
+ memset(srcList, 0, sizeof(srcList));
+
+ alSourcesInitialised = qfalse;
+}
+
+/*
+=================
+S_AL_SrcSetup
+=================
+*/
+static void S_AL_SrcSetup(srcHandle_t src, sfxHandle_t sfx, alSrcPriority_t priority,
+ int entity, int channel, qboolean local)
+{
+ ALuint buffer;
+ src_t *curSource;
+
+ // Mark the SFX as used, and grab the raw AL buffer
+ S_AL_BufferUse(sfx);
+ buffer = S_AL_BufferGet(sfx);
+
+ // 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 = qfalse;
+ curSource->isLocked = qfalse;
+ curSource->isLooping = qfalse;
+ curSource->isTracking = qfalse;
+ curSource->curGain = s_alGain->value * s_volume->value;
+ curSource->scaleGain = curSource->curGain;
+ curSource->local = local;
+
+ // Set up OpenAL source
+ qalSourcei(curSource->alSource, AL_BUFFER, buffer);
+ 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_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_SaveLoopPos(src_t *dest, ALuint alSource)
+{
+ int error;
+
+ S_AL_ClearError(qfalse);
+
+ 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, qboolean 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 = qfalse;
+
+ if(curSource->entity != -1)
+ {
+ sentity_t *curEnt = &entityList[curSource->entity];
+
+ curEnt->srcAllocated = qfalse;
+ curEnt->srcIndex = -1;
+ curEnt->loopAddedThisFrame = qfalse;
+ curEnt->startLoopingSound = qfalse;
+ }
+
+ S_AL_NewLoopMaster(curSource, qtrue);
+ }
+
+ // Stop it if it's playing
+ if(curSource->isPlaying)
+ {
+ qalSourceStop(curSource->alSource);
+ curSource->isPlaying = qfalse;
+ }
+
+ // Remove the buffer
+ qalSourcei(curSource->alSource, AL_BUFFER, 0);
+
+ curSource->sfx = 0;
+ curSource->lastUsedTime = 0;
+ curSource->priority = 0;
+ curSource->entity = -1;
+ curSource->channel = -1;
+ if(curSource->isActive)
+ {
+ curSource->isActive = qfalse;
+ srcActiveCnt--;
+ }
+ curSource->isLocked = qfalse;
+ curSource->isTracking = qfalse;
+ curSource->local = qfalse;
+}
+
+/*
+=================
+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;
+ qboolean weakest_isplaying = qtrue;
+ 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 = qfalse;
+
+ 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 = qtrue;
+ 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 = qtrue;
+}
+
+/*
+=================
+S_AL_SrcUnlock
+
+Once unlocked, the source may be reallocated again
+=================
+*/
+static
+void S_AL_SrcUnlock(srcHandle_t src)
+{
+ srcList[src].isLocked = qfalse;
+}
+
+/*
+=================
+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 qboolean 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 qtrue;
+ }
+
+ return qfalse;
+}
+
+/*
+=================
+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, qtrue);
+
+ // Start it playing
+ srcList[src].isPlaying = qtrue;
+ 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, qfalse);
+
+ curSource = &srcList[src];
+
+ if(!origin)
+ curSource->isTracking = qtrue;
+
+ qalSourcefv(curSource->alSource, AL_POSITION, sorigin );
+ S_AL_ScaleGain(curSource, sorigin);
+
+ // Start it playing
+ curSource->isPlaying = qtrue;
+ qalSourcePlay(curSource->alSource);
+}
+
+/*
+=================
+S_AL_ClearLoopingSounds
+=================
+*/
+static
+void S_AL_ClearLoopingSounds( qboolean killall )
+{
+ int i;
+ for(i = 0; i < srcCount; i++)
+ {
+ if((srcList[i].isLooping) && (srcList[i].entity != -1))
+ entityList[srcList[i].entity].loopAddedThisFrame = qfalse;
+ }
+}
+
+/*
+=================
+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 = qtrue;
+
+ curSource->lastTimePos = -1.0;
+ curSource->lastSampleTime = Sys_Milliseconds();
+ }
+ else
+ {
+ src = sent->srcIndex;
+ curSource = &srcList[src];
+ }
+
+ sent->srcAllocated = qtrue;
+ sent->srcIndex = src;
+
+ sent->loopPriority = priority;
+ sent->loopSfx = sfx;
+
+ // If this is not set then the looping sound is stopped.
+ sent->loopAddedThisFrame = qtrue;
+
+ // 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 = qtrue;
+
+ if( S_AL_HearingThroughEntity( entityNum ) )
+ {
+ curSource->local = qtrue;
+
+ VectorClear(sorigin);
+
+ qalSourcefv(curSource->alSource, AL_POSITION, sorigin);
+ qalSourcefv(curSource->alSource, AL_VELOCITY, sorigin);
+ }
+ else
+ {
+ curSource->local = qfalse;
+
+ 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 *)velocity );
+ }
+}
+
+/*
+=================
+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, qtrue);
+
+ curSource->isPlaying = qfalse;
+ qalSourceStop(curSource->alSource);
+ qalSourcei(curSource->alSource, AL_BUFFER, 0);
+ sent->startLoopingSound = qtrue;
+ }
+
+ // The sound hasn't been started yet
+ if(sent->startLoopingSound)
+ {
+ S_AL_SrcSetup(i, sent->loopSfx, sent->loopPriority,
+ entityNum, -1, curSource->local);
+ curSource->isLooping = qtrue;
+
+ knownSfx[curSource->sfx].loopCnt++;
+ sent->startLoopingSound = qfalse;
+ }
+
+ 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, qfalse);
+ qalSourceStop(curSource->alSource);
+ curSource->isPlaying = qfalse;
+ }
+ else if(!curSfx->loopActiveCnt && curSfx->masterLoopSrc < 0)
+ curSfx->masterLoopSrc = i;
+
+ continue;
+ }
+
+ if(!curSource->isPlaying)
+ {
+ 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(qfalse);
+ 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++;
+
+ qalSourcei(curSource->alSource, AL_LOOPING, AL_TRUE);
+ curSource->isPlaying = qtrue;
+ qalSourcePlay(curSource->alSource);
+ }
+
+ // 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, qfalse);
+ qalSourceStop(curSource->alSource);
+ curSource->isPlaying = qfalse;
+ }
+ }
+ else
+ S_AL_SrcKill(i);
+
+ continue;
+ }
+
+ // Check if it's done, and flag it
+ qalGetSourcei(curSource->alSource, AL_SOURCE_STATE, &state);
+ if(state == AL_STOPPED)
+ {
+ curSource->isPlaying = qfalse;
+ 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;
+}
+
+
+//===========================================================================
+
+static srcHandle_t streamSourceHandles[MAX_RAW_STREAMS];
+static qboolean streamPlaying[MAX_RAW_STREAMS];
+static ALuint streamSources[MAX_RAW_STREAMS];
+
+/*
+=================
+S_AL_AllocateStreamChannel
+=================
+*/
+static void S_AL_AllocateStreamChannel( int stream )
+{
+ if ((stream < 0) || (stream >= MAX_RAW_STREAMS))
+ return;
+
+ // Allocate a streamSource at high priority
+ streamSourceHandles[stream] = S_AL_SrcAlloc(SRCPRI_STREAM, -2, 0);
+ if(streamSourceHandles[stream] == -1)
+ return;
+
+ // Lock the streamSource so nobody else can use it, and get the raw streamSource
+ S_AL_SrcLock(streamSourceHandles[stream]);
+ streamSources[stream] = S_AL_SrcGet(streamSourceHandles[stream]);
+
+ // make sure that after unmuting the S_AL_Gain in S_Update() does not turn
+ // volume up prematurely for this source
+ srcList[streamSourceHandles[stream]].scaleGain = 0.0f;
+
+ // Set some streamSource parameters
+ qalSourcei (streamSources[stream], AL_BUFFER, 0 );
+ qalSourcei (streamSources[stream], AL_LOOPING, AL_FALSE );
+ qalSource3f(streamSources[stream], AL_POSITION, 0.0, 0.0, 0.0);
+ qalSource3f(streamSources[stream], AL_VELOCITY, 0.0, 0.0, 0.0);
+ qalSource3f(streamSources[stream], AL_DIRECTION, 0.0, 0.0, 0.0);
+ qalSourcef (streamSources[stream], AL_ROLLOFF_FACTOR, 0.0 );
+ qalSourcei (streamSources[stream], AL_SOURCE_RELATIVE, AL_TRUE );
+}
+
+/*
+=================
+S_AL_FreeStreamChannel
+=================
+*/
+static void S_AL_FreeStreamChannel( int stream )
+{
+ if ((stream < 0) || (stream >= MAX_RAW_STREAMS))
+ return;
+
+ // Release the output streamSource
+ S_AL_SrcUnlock(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)
+{
+ 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);
+
+ // Failed?
+ if(streamSourceHandles[stream] == -1)
+ {
+ Com_Printf( S_COLOR_RED "ERROR: Can't allocate streaming streamSource\n");
+ return;
+ }
+ }
+
+ // Create a buffer, and stuff the data into it
+ qalGenBuffers(1, &buffer);
+ qalBufferData(buffer, format, (ALvoid *)data, (samples * width * channels), rate);
+
+ // Shove the data onto the streamSource
+ qalSourceQueueBuffers(streamSources[stream], 1, &buffer);
+
+ // Volume
+ S_AL_Gain (streamSources[stream], volume * s_volume->value * s_alGain->value);
+}
+
+/*
+=================
+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, and delete them
+ qalGetSourcei( streamSources[stream], AL_BUFFERS_PROCESSED, &numBuffers );
+ while( numBuffers-- )
+ {
+ ALuint buffer;
+ qalSourceUnqueueBuffers(streamSources[stream], 1, &buffer);
+ qalDeleteBuffers(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] = qfalse;
+
+ // 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] = qtrue;
+ }
+}
+
+/*
+=================
+S_AL_StreamDie
+=================
+*/
+static
+void S_AL_StreamDie( int stream )
+{
+ int numBuffers;
+
+ if ((stream < 0) || (stream >= MAX_RAW_STREAMS))
+ return;
+
+ if(streamSourceHandles[stream] == -1)
+ return;
+
+ streamPlaying[stream] = qfalse;
+ qalSourceStop(streamSources[stream]);
+
+ // Un-queue any buffers, and delete them
+ qalGetSourcei( streamSources[stream], AL_BUFFERS_PROCESSED, &numBuffers );
+ while( numBuffers-- )
+ {
+ ALuint buffer;
+ qalSourceUnqueueBuffers(streamSources[stream], 1, &buffer);
+ qalDeleteBuffers(1, &buffer);
+ }
+
+ S_AL_FreeStreamChannel(stream);
+}
+
+
+//===========================================================================
+
+
+#define NUM_MUSIC_BUFFERS 4
+#define MUSIC_BUFFER_SIZE 4096
+
+static qboolean musicPlaying = qfalse;
+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);
+ 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);
+
+ // De-queue the musicBuffers
+ qalSourceUnqueueBuffers(musicSource, NUM_MUSIC_BUFFERS, musicBuffers);
+
+ // Destroy the musicBuffers
+ qalDeleteBuffers(NUM_MUSIC_BUFFERS, musicBuffers);
+
+ // Free the musicSource
+ S_AL_MusicSourceFree();
+
+ // Unload the stream
+ S_AL_CloseMusicFiles();
+
+ musicPlaying = qfalse;
+}
+
+/*
+=================
+S_AL_MusicProcess
+=================
+*/
+static
+void S_AL_MusicProcess(ALuint b)
+{
+ ALenum error;
+ int l;
+ ALuint format;
+ snd_stream_t *curstream;
+
+ S_AL_ClearError( qfalse );
+
+ 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;
+ qboolean 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 = qtrue;
+ }
+ else if(intro && *intro && !strcmp(intro, loop))
+ issame = qtrue;
+ else
+ issame = qfalse;
+
+ // Copy the loop over
+ strncpy( 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
+ qalGenBuffers(NUM_MUSIC_BUFFERS, musicBuffers);
+
+ // 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 = qtrue;
+}
+
+/*
+=================
+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(MACOS_X)
+#define ALDRIVER_DEFAULT "/System/Library/Frameworks/OpenAL.framework/OpenAL"
+#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 velocity[3] = {0.0f, 0.0f, 0.0f};
+ 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];
+
+ VectorCopy( sorigin, lastListenerOrigin );
+
+ // Set OpenAL listener paramaters
+ qalListenerfv(AL_POSITION, (ALfloat *)sorigin);
+ qalListenerfv(AL_VELOCITY, velocity);
+ 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 = qfalse;
+ }
+
+ // 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 = qtrue;
+ s_doppler->modified = qfalse;
+ }
+
+ // Doppler parameters
+ if(s_alDopplerFactor->modified)
+ {
+ if(s_doppler->integer)
+ qalDopplerFactor(s_alDopplerFactor->value);
+ else
+ qalDopplerFactor(0.0f);
+ s_alDopplerFactor->modified = qfalse;
+ }
+ if(s_alDopplerSpeed->modified)
+ {
+ qalDopplerVelocity(s_alDopplerSpeed->value);
+ s_alDopplerSpeed->modified = qfalse;
+ }
+
+ // Clear the modified flags on the other cvars
+ s_alGain->modified = qfalse;
+ s_volume->modified = qfalse;
+ s_musicVolume->modified = qfalse;
+ s_alMinDistance->modified = qfalse;
+ s_alRolloff->modified = qfalse;
+}
+
+/*
+=================
+S_AL_DisableSounds
+=================
+*/
+static
+void S_AL_DisableSounds( void )
+{
+ S_AL_StopAllSounds();
+}
+
+/*
+=================
+S_AL_BeginRegistration
+=================
+*/
+static
+void S_AL_BeginRegistration( void )
+{
+}
+
+/*
+=================
+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(qalcIsExtensionPresent(NULL, "ALC_ENUMERATION_EXT"))
+ {
+ Com_Printf(" Device: %s\n", qalcGetString(alDevice, ALC_DEVICE_SPECIFIER));
+ Com_Printf("Available Devices:\n%s", s_alAvailableDevices->string);
+ }
+}
+
+/*
+=================
+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] = qfalse;
+ streamSources[i] = 0;
+ }
+
+ QAL_Shutdown();
+}
+
+#endif
+
+/*
+=================
+S_AL_Init
+=================
+*/
+qboolean S_AL_Init( soundInterface_t *si )
+{
+#ifdef USE_OPENAL
+ const char* device = NULL;
+ int i;
+
+ if( !si ) {
+ return qfalse;
+ }
+
+ for (i = 0; i < MAX_RAW_STREAMS; i++) {
+ streamSourceHandles[i] = -1;
+ streamPlaying[i] = qfalse;
+ streamSources[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", "2200", 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_alDriver = Cvar_Get( "s_alDriver", ALDRIVER_DEFAULT, CVAR_ARCHIVE | CVAR_LATCH );
+
+ s_alDevice = Cvar_Get("s_alDevice", "", CVAR_ARCHIVE | CVAR_LATCH);
+
+ // Load QAL
+ if( !QAL_Init( s_alDriver->string ) )
+ {
+ Com_Printf( "Failed to load library: \"%s\".\n", s_alDriver->string );
+ return qfalse;
+ }
+
+ device = s_alDevice->string;
+ if(device && !*device)
+ device = NULL;
+
+ // Device enumeration support (extension is implemented reasonably only on Windows right now).
+ if(qalcIsExtensionPresent(NULL, "ALC_ENUMERATION_EXT"))
+ {
+ char devicenames[1024] = "";
+ const char *devicelist;
+ const char *defaultdevice;
+ int curlen;
+
+ // get all available devices + the default device name.
+ devicelist = qalcGetString(NULL, ALC_DEVICE_SPECIFIER);
+ defaultdevice = qalcGetString(NULL, ALC_DEFAULT_DEVICE_SPECIFIER);
+
+#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 && !strcmp(defaultdevice, "Generic Hardware"))
+ device = "Generic Software";
+#endif
+
+ // dump a list of available devices to a cvar for the user to see.
+ 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 qfalse;
+ }
+
+ // Create OpenAL context
+ alContext = qalcCreateContext( alDevice, NULL );
+ if( !alContext )
+ {
+ QAL_Shutdown( );
+ qalcCloseDevice( alDevice );
+ Com_Printf( "Failed to create OpenAL context.\n" );
+ return qfalse;
+ }
+ 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 );
+ qalDopplerVelocity( 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 MACOS_X
+ // !!! 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
+ {
+ // !!! FIXME: 8000Hz is what Speex narrowband mode needs, but we
+ // !!! FIXME: should probably open the capture device after
+ // !!! FIXME: initializing Speex so we can change to wideband
+ // !!! FIXME: if we like.
+ Com_Printf("OpenAL default capture device is '%s'\n",
+ qalcGetString(NULL, ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER));
+ alCaptureDevice = qalcCaptureOpenDevice(NULL, 8000, AL_FORMAT_MONO16, 4096);
+ 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->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 qtrue;
+#else
+ return qfalse;
+#endif
+}
+
diff --git a/code/client/snd_public.h b/code/client/snd_public.h
new file mode 100644
index 0000000..1587f33
--- /dev/null
+++ b/code/client/snd_public.h
@@ -0,0 +1,82 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code 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,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+
+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);
+
+// 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( qboolean 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 reletive volumes for all running sounds
+// reletive 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, qboolean compressed );
+
+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
+
diff --git a/code/client/snd_wavelet.c b/code/client/snd_wavelet.c
new file mode 100644
index 0000000..41b5723
--- /dev/null
+++ b/code/client/snd_wavelet.c
@@ -0,0 +1,253 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code 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,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+#include "snd_local.h"
+
+long myftol( float f );
+
+#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];
+ float *a=b-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 qboolean madeTable = qfalse;
+
+static int NXStreamCount;
+
+void NXPutc(NXStream *stream, char out) {
+ stream[NXStreamCount++] = out;
+}
+
+
+void encodeWavelet( sfx_t *sfx, short *packets) {
+ float wksp[4097], 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 = qtrue;
+ }
+ 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 {
+ 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];
+ 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 = qtrue;
+ }
+
+ 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 {
+ 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]];
+ }
+}
+
+