15 May 2012
This commit is contained in:
parent
6b3e9e841e
commit
ce01422c4d
|
@ -697,7 +697,8 @@ enum
|
|||
kRenderTransTexture, // src*a+dest*(1-a)
|
||||
kRenderGlow, // src*a+dest -- No Z buffer checks
|
||||
kRenderTransAlpha, // src*srca+dest*(1-srca)
|
||||
kRenderTransAdd // src*a+dest
|
||||
kRenderTransAdd, // src*a+dest
|
||||
kRenderWorldGlow, // Same as kRenderGlow but not fixed size in screen space
|
||||
};
|
||||
|
||||
enum
|
||||
|
|
|
@ -75,14 +75,14 @@ void CL_PlayCDTrack_f( void )
|
|||
if( !Q_stricmp( command, "play" ))
|
||||
{
|
||||
track = bound( 1, Q_atoi( Cmd_Argv( 2 )), MAX_CDTRACKS );
|
||||
S_StartBackgroundTrack( clgame.cdtracks[track-1], NULL );
|
||||
S_StartBackgroundTrack( clgame.cdtracks[track-1], NULL, 0 );
|
||||
paused = false;
|
||||
looped = false;
|
||||
}
|
||||
else if( !Q_stricmp( command, "loop" ))
|
||||
{
|
||||
track = bound( 1, Q_atoi( Cmd_Argv( 2 )), MAX_CDTRACKS );
|
||||
S_StartBackgroundTrack( clgame.cdtracks[track-1], clgame.cdtracks[track-1] );
|
||||
S_StartBackgroundTrack( clgame.cdtracks[track-1], clgame.cdtracks[track-1], 0 );
|
||||
paused = false;
|
||||
looped = true;
|
||||
}
|
||||
|
|
|
@ -2801,11 +2801,11 @@ void pfnMP3_InitStream( char *filename, int flags )
|
|||
// g-cont. flag 1 is probably 'LOOP'
|
||||
if( flags & 1 )
|
||||
{
|
||||
S_StartBackgroundTrack( filename, filename );
|
||||
S_StartBackgroundTrack( filename, filename, 0 );
|
||||
}
|
||||
else
|
||||
{
|
||||
S_StartBackgroundTrack( filename, NULL );
|
||||
S_StartBackgroundTrack( filename, NULL, 0 );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -842,6 +842,17 @@ static void pfnHostEndGame( const char *szFinalMessage )
|
|||
Host_EndGame( szFinalMessage );
|
||||
}
|
||||
|
||||
/*
|
||||
=========
|
||||
pfnStartBackgroundTrack
|
||||
|
||||
=========
|
||||
*/
|
||||
static void pfnStartBackgroundTrack( const char *introTrack, const char *mainTrack )
|
||||
{
|
||||
S_StartBackgroundTrack( introTrack, mainTrack, 0 );
|
||||
}
|
||||
|
||||
// engine callbacks
|
||||
static ui_enginefuncs_t gEngfuncs =
|
||||
{
|
||||
|
@ -917,7 +928,7 @@ static ui_enginefuncs_t gEngfuncs =
|
|||
Sys_ShellExecute,
|
||||
Host_WriteServerConfig,
|
||||
pfnChangeInstance,
|
||||
S_StartBackgroundTrack,
|
||||
pfnStartBackgroundTrack,
|
||||
pfnHostEndGame,
|
||||
Com_RandomFloat,
|
||||
Com_RandomLong,
|
||||
|
|
|
@ -318,7 +318,14 @@ void CL_ParseRestoreSoundPacket( sizebuf_t *msg )
|
|||
pitch = BF_ReadByte( msg );
|
||||
else pitch = PITCH_NORM;
|
||||
|
||||
handle = cl.sound_index[sound]; // see precached sound
|
||||
if( flags & SND_SENTENCE )
|
||||
{
|
||||
char sentenceName[32];
|
||||
|
||||
Q_snprintf( sentenceName, sizeof( sentenceName ), "!%i", sound );
|
||||
handle = S_RegisterSound( sentenceName );
|
||||
}
|
||||
else handle = cl.sound_index[sound]; // see precached sound
|
||||
|
||||
// entity reletive
|
||||
entnum = BF_ReadWord( msg );
|
||||
|
@ -330,7 +337,7 @@ void CL_ParseRestoreSoundPacket( sizebuf_t *msg )
|
|||
BF_ReadBytes( msg, &samplePos, sizeof( samplePos ));
|
||||
BF_ReadBytes( msg, &forcedEnd, sizeof( forcedEnd ));
|
||||
|
||||
S_RestoreSound( pos, entnum, chan, handle, volume, attn, pitch, flags, samplePos, forcedEnd );
|
||||
S_RestoreSound( pos, entnum, chan, handle, volume, attn, pitch, flags, samplePos, forcedEnd, wordIndex );
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -1506,7 +1513,7 @@ void CL_ParseServerMessage( sizebuf_t *msg )
|
|||
param1 = bound( 1, param1, MAX_CDTRACKS ); // tracknum
|
||||
param2 = BF_ReadByte( msg );
|
||||
param2 = bound( 1, param2, MAX_CDTRACKS ); // loopnum
|
||||
S_StartBackgroundTrack( clgame.cdtracks[param1-1], clgame.cdtracks[param2-1] );
|
||||
S_StartBackgroundTrack( clgame.cdtracks[param1-1], clgame.cdtracks[param2-1], 0 );
|
||||
break;
|
||||
case svc_serverinfo:
|
||||
CL_ServerInfo( msg );
|
||||
|
|
|
@ -800,7 +800,7 @@ void Con_Close( void );
|
|||
// s_main.c
|
||||
//
|
||||
void S_StreamRawSamples( int samples, int rate, int width, int channels, const byte *data );
|
||||
void S_StartBackgroundTrack( const char *intro, const char *loop );
|
||||
void S_StartBackgroundTrack( const char *intro, const char *loop, long position );
|
||||
void S_StopBackgroundTrack( void );
|
||||
void S_StreamSetPause( int pause );
|
||||
void S_StartStreaming( void );
|
||||
|
@ -808,7 +808,7 @@ void S_StopStreaming( void );
|
|||
void S_BeginRegistration( void );
|
||||
sound_t S_RegisterSound( const char *sample );
|
||||
void S_EndRegistration( void );
|
||||
void S_RestoreSound( const vec3_t pos, int ent, int chan, sound_t handle, float fvol, float attn, int pitch, int flags, double sample, double end );
|
||||
void S_RestoreSound( const vec3_t pos, int ent, int chan, sound_t handle, float fvol, float attn, int pitch, int flags, double sample, double end, int wordIndex );
|
||||
void S_StartSound( const vec3_t pos, int ent, int chan, sound_t sfx, float vol, float attn, int pitch, int flags );
|
||||
void S_AmbientSound( const vec3_t pos, int ent, sound_t handle, float fvol, float attn, int pitch, int flags );
|
||||
void S_FadeClientVolume( float fadePercent, float fadeOutSeconds, float holdTime, float fadeInSeconds );
|
||||
|
|
|
@ -139,6 +139,7 @@ S_FreeChannel
|
|||
void S_FreeChannel( channel_t *ch )
|
||||
{
|
||||
ch->sfx = NULL;
|
||||
ch->name[0] = '\0';
|
||||
ch->use_loop = false;
|
||||
ch->isSentence = false;
|
||||
|
||||
|
@ -926,14 +927,15 @@ void S_StartSound( const vec3_t pos, int ent, int chan, sound_t handle, float fv
|
|||
// prepended with a '!'. Sentence names stored in the
|
||||
// sentence file do not have a leading '!'.
|
||||
VOX_LoadSound( target_chan, S_SkipSoundChar( sfx->name ));
|
||||
|
||||
sfx = target_chan->sfx; // TEST
|
||||
Q_strncpy( target_chan->name, sfx->name, sizeof( target_chan->name ));
|
||||
sfx = target_chan->sfx;
|
||||
pSource = sfx->cache;
|
||||
}
|
||||
else
|
||||
{
|
||||
// regular or streamed sound fx
|
||||
pSource = S_LoadSound( sfx );
|
||||
target_chan->name[0] = '\0';
|
||||
}
|
||||
|
||||
if( !pSource )
|
||||
|
@ -986,7 +988,7 @@ S_RestoreSound
|
|||
Restore a sound effect for the given entity on the given channel
|
||||
====================
|
||||
*/
|
||||
void S_RestoreSound( const vec3_t pos, int ent, int chan, sound_t handle, float fvol, float attn, int pitch, int flags, double sample, double end )
|
||||
void S_RestoreSound( const vec3_t pos, int ent, int chan, sound_t handle, float fvol, float attn, int pitch, int flags, double sample, double end, int wordIndex )
|
||||
{
|
||||
wavdata_t *pSource;
|
||||
sfx_t *sfx = NULL;
|
||||
|
@ -1040,8 +1042,46 @@ void S_RestoreSound( const vec3_t pos, int ent, int chan, sound_t handle, float
|
|||
target_chan->ob_gain_target = 0.0f;
|
||||
target_chan->bTraced = false;
|
||||
|
||||
// regular or streamed sound fx
|
||||
pSource = S_LoadSound( sfx );
|
||||
pSource = NULL;
|
||||
|
||||
if( S_TestSoundChar( sfx->name, '!' ))
|
||||
{
|
||||
// this is a sentence
|
||||
// link all words and load the first word
|
||||
// NOTE: sentence names stored in the cache lookup are
|
||||
// prepended with a '!'. Sentence names stored in the
|
||||
// sentence file do not have a leading '!'.
|
||||
VOX_LoadSound( target_chan, S_SkipSoundChar( sfx->name ));
|
||||
|
||||
// save the sentencename for future save\restores
|
||||
Q_strncpy( target_chan->name, sfx->name, sizeof( target_chan->name ));
|
||||
|
||||
// not a first word in sentence!
|
||||
if( wordIndex != 0 )
|
||||
{
|
||||
VOX_FreeWord( target_chan ); // release first loaded word
|
||||
target_chan->wordIndex = wordIndex; // restore current word
|
||||
VOX_LoadWord( target_chan );
|
||||
|
||||
if( target_chan->currentWord )
|
||||
{
|
||||
target_chan->sfx = target_chan->words[target_chan->wordIndex].sfx;
|
||||
sfx = target_chan->sfx;
|
||||
pSource = sfx->cache;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sfx = target_chan->sfx;
|
||||
pSource = sfx->cache;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// regular or streamed sound fx
|
||||
pSource = S_LoadSound( sfx );
|
||||
target_chan->name[0] = '\0';
|
||||
}
|
||||
|
||||
if( !pSource )
|
||||
{
|
||||
|
@ -1062,6 +1102,7 @@ void S_RestoreSound( const vec3_t pos, int ent, int chan, sound_t handle, float
|
|||
// if this is a streaming sound, play the whole thing.
|
||||
if( chan != CHAN_STREAM )
|
||||
{
|
||||
MsgDev( D_ERROR, "S_RestoreSound: %s volume 0\n", sfx->name );
|
||||
S_FreeChannel( target_chan );
|
||||
return; // not audible at all
|
||||
}
|
||||
|
@ -1137,6 +1178,7 @@ void S_AmbientSound( const vec3_t pos, int ent, sound_t handle, float fvol, floa
|
|||
|
||||
// link all words and load the first word
|
||||
VOX_LoadSound( ch, S_SkipSoundChar( sfx->name ));
|
||||
Q_strncpy( ch->name, sfx->name, sizeof( ch->name ));
|
||||
sfx = ch->sfx;
|
||||
pSource = sfx->cache;
|
||||
fvox = 1;
|
||||
|
@ -1147,6 +1189,7 @@ void S_AmbientSound( const vec3_t pos, int ent, sound_t handle, float fvol, floa
|
|||
pSource = S_LoadSound( sfx );
|
||||
ch->sfx = sfx;
|
||||
ch->isSentence = false;
|
||||
ch->name[0] = '\0';
|
||||
}
|
||||
|
||||
if( !pSource )
|
||||
|
@ -1212,7 +1255,9 @@ int S_GetCurrentStaticSounds( soundlist_t *pout, int size )
|
|||
{
|
||||
if( channels[i].entchannel == CHAN_STATIC && channels[i].sfx && channels[i].sfx->name[0] )
|
||||
{
|
||||
Q_strncpy( pout->name, channels[i].sfx->name, sizeof( pout->name ));
|
||||
if( channels[i].isSentence && channels[i].name[0] )
|
||||
Q_strncpy( pout->name, channels[i].name, sizeof( pout->name ));
|
||||
else Q_strncpy( pout->name, channels[i].sfx->name, sizeof( pout->name ));
|
||||
pout->entnum = channels[i].entnum;
|
||||
VectorCopy( channels[i].origin, pout->origin );
|
||||
pout->volume = (float)channels[i].master_vol / 255.0f;
|
||||
|
@ -1257,7 +1302,9 @@ int S_GetCurrentDynamicSounds( soundlist_t *pout, int size )
|
|||
if( channels[i].entchannel == CHAN_STATIC && looped )
|
||||
continue; // never serialize static looped sounds. It will be restoring in game code
|
||||
|
||||
Q_strncpy( pout->name, channels[i].sfx->name, sizeof( pout->name ));
|
||||
if( channels[i].isSentence && channels[i].name[0] )
|
||||
Q_strncpy( pout->name, channels[i].name, sizeof( pout->name ));
|
||||
else Q_strncpy( pout->name, channels[i].sfx->name, sizeof( pout->name ));
|
||||
pout->entnum = channels[i].entnum;
|
||||
VectorCopy( channels[i].origin, pout->origin );
|
||||
pout->volume = (float)channels[i].master_vol / 255.0f;
|
||||
|
@ -1631,13 +1678,13 @@ void S_Music_f( void )
|
|||
&& FS_FileExists( va( "media/%s.%s", main, ext[i] ), false ))
|
||||
{
|
||||
// combined track with introduction and main loop theme
|
||||
S_StartBackgroundTrack( intro, main );
|
||||
S_StartBackgroundTrack( intro, main, 0 );
|
||||
break;
|
||||
}
|
||||
else if( FS_FileExists( va( "media/%s.%s", track, ext[i] ), false ))
|
||||
{
|
||||
// single looped theme
|
||||
S_StartBackgroundTrack( track, NULL );
|
||||
S_StartBackgroundTrack( track, NULL, 0 );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -1645,7 +1692,12 @@ void S_Music_f( void )
|
|||
}
|
||||
else if( c == 3 )
|
||||
{
|
||||
S_StartBackgroundTrack( Cmd_Argv( 1 ), Cmd_Argv( 2 ));
|
||||
S_StartBackgroundTrack( Cmd_Argv( 1 ), Cmd_Argv( 2 ), 0 );
|
||||
}
|
||||
else if( c == 4 && Q_atoi( Cmd_Argv( 3 )) != 0 )
|
||||
{
|
||||
// restore command for singleplayer: all arguments are valid
|
||||
S_StartBackgroundTrack( Cmd_Argv( 1 ), Cmd_Argv( 2 ), Q_atoi( Cmd_Argv( 3 )));
|
||||
}
|
||||
else Msg( "Usage: music <musicfile> [loopfile]\n" );
|
||||
}
|
||||
|
|
|
@ -38,11 +38,19 @@ void S_CheckLerpingState( void )
|
|||
S_StartBackgroundTrack
|
||||
=================
|
||||
*/
|
||||
void S_StartBackgroundTrack( const char *introTrack, const char *mainTrack )
|
||||
void S_StartBackgroundTrack( const char *introTrack, const char *mainTrack, long position )
|
||||
{
|
||||
S_StopBackgroundTrack();
|
||||
|
||||
if( !dma.initialized ) return;
|
||||
|
||||
// check for special symbols
|
||||
if( introTrack && *introTrack == '*' )
|
||||
introTrack = NULL;
|
||||
|
||||
if( mainTrack && *mainTrack == '*' )
|
||||
mainTrack = NULL;
|
||||
|
||||
if(( !introTrack || !*introTrack ) && ( !mainTrack || !*mainTrack ))
|
||||
return;
|
||||
|
||||
|
@ -54,8 +62,15 @@ void S_StartBackgroundTrack( const char *introTrack, const char *mainTrack )
|
|||
|
||||
// open stream
|
||||
s_bgTrack.stream = FS_OpenStream( va( "media/%s", introTrack ));
|
||||
Q_strncpy( s_bgTrack.current, introTrack, sizeof( s_bgTrack.current ));
|
||||
s_bgTrack.source = cls.key_dest;
|
||||
|
||||
if( position != 0 )
|
||||
{
|
||||
// restore message, update song position
|
||||
FS_SetStreamPos( s_bgTrack.stream, position );
|
||||
}
|
||||
|
||||
S_CheckLerpingState();
|
||||
}
|
||||
|
||||
|
@ -77,6 +92,38 @@ void S_StreamSetPause( int pause )
|
|||
s_listener.stream_paused = pause;
|
||||
}
|
||||
|
||||
/*
|
||||
=================
|
||||
S_StreamGetCurrentState
|
||||
|
||||
save\restore code
|
||||
=================
|
||||
*/
|
||||
qboolean S_StreamGetCurrentState( char *currentTrack, char *loopTrack, int *position )
|
||||
{
|
||||
if( !s_bgTrack.stream )
|
||||
return false; // not active
|
||||
|
||||
if( currentTrack )
|
||||
{
|
||||
if( s_bgTrack.current[0] )
|
||||
Q_strncpy( currentTrack, s_bgTrack.current, MAX_STRING );
|
||||
else Q_strncpy( currentTrack, "*", MAX_STRING ); // no track
|
||||
}
|
||||
|
||||
if( loopTrack )
|
||||
{
|
||||
if( s_bgTrack.loopName[0] )
|
||||
Q_strncpy( loopTrack, s_bgTrack.loopName, MAX_STRING );
|
||||
else Q_strncpy( loopTrack, "*", MAX_STRING ); // no track
|
||||
}
|
||||
|
||||
if( position )
|
||||
*position = FS_GetStreamPos( s_bgTrack.stream );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
=================
|
||||
S_StreamBackgroundTrack
|
||||
|
@ -150,6 +197,7 @@ void S_StreamBackgroundTrack( void )
|
|||
{
|
||||
FS_FreeStream( s_bgTrack.stream );
|
||||
s_bgTrack.stream = FS_OpenStream( va( "media/%s", s_bgTrack.loopName ));
|
||||
Q_strncpy( s_bgTrack.current, s_bgTrack.loopName, sizeof( s_bgTrack.current ));
|
||||
|
||||
if( !s_bgTrack.stream ) return;
|
||||
S_CheckLerpingState();
|
||||
|
|
|
@ -345,8 +345,8 @@ void VOX_LoadWord( channel_t *pchan )
|
|||
|
||||
if( pSource )
|
||||
{
|
||||
int start = pchan->words[pchan->wordIndex].start;
|
||||
int end = pchan->words[pchan->wordIndex].end;
|
||||
int start = pchan->words[pchan->wordIndex].start;
|
||||
int end = pchan->words[pchan->wordIndex].end;
|
||||
|
||||
// apply mixer
|
||||
pchan->currentWord = &pchan->pMixer;
|
||||
|
@ -418,7 +418,7 @@ int VOX_MixDataToDevice( channel_t *pchan, int sampleCount, int outputRate, int
|
|||
|
||||
while( sampleCount > 0 && pchan->currentWord )
|
||||
{
|
||||
int outputCount = S_MixDataToDevice( pchan, sampleCount, outputRate, outputOffset );
|
||||
int outputCount = S_MixDataToDevice( pchan, sampleCount, outputRate, outputOffset );
|
||||
|
||||
outputOffset += outputCount;
|
||||
sampleCount -= outputCount;
|
||||
|
|
|
@ -141,8 +141,9 @@ typedef struct
|
|||
qboolean finished;
|
||||
} mixer_t;
|
||||
|
||||
typedef struct
|
||||
typedef struct channel_s
|
||||
{
|
||||
char name[16]; // keept sentence name
|
||||
sfx_t *sfx; // sfx number
|
||||
|
||||
int leftvol; // 0-255 left volume
|
||||
|
@ -196,7 +197,8 @@ typedef struct
|
|||
|
||||
typedef struct
|
||||
{
|
||||
string loopName;
|
||||
string current; // a currently playing track
|
||||
string loopName; // may be empty
|
||||
stream_t *stream;
|
||||
int source; // may be game, menu, etc
|
||||
} bg_track_t;
|
||||
|
@ -305,6 +307,7 @@ void SND_CloseMouth( channel_t *ch );
|
|||
//
|
||||
void S_StreamSoundTrack( void );
|
||||
void S_StreamBackgroundTrack( void );
|
||||
qboolean S_StreamGetCurrentState( char *currentTrack, char *loopTrack, int *position );
|
||||
|
||||
//
|
||||
// s_utils.c
|
||||
|
|
|
@ -42,6 +42,7 @@ typedef struct
|
|||
float length;
|
||||
} sentence_t;
|
||||
|
||||
extern sentence_t g_Sentences[MAX_SENTENCES];
|
||||
void VOX_LoadWord( struct channel_s *pchan );
|
||||
void VOX_FreeWord( struct channel_s *pchan );
|
||||
|
||||
#endif
|
|
@ -515,7 +515,7 @@ typedef enum
|
|||
WF_TOTALCOUNT, // must be last
|
||||
} sndformat_t;
|
||||
|
||||
// imagelib global settings
|
||||
// soundlib global settings
|
||||
typedef enum
|
||||
{
|
||||
SL_USE_LERPING = BIT(0), // lerping sounds during resample
|
||||
|
@ -558,6 +558,8 @@ void FS_FreeSound( wavdata_t *pack );
|
|||
stream_t *FS_OpenStream( const char *filename );
|
||||
wavdata_t *FS_StreamInfo( stream_t *stream );
|
||||
long FS_ReadStream( stream_t *stream, int bytes, void *buffer );
|
||||
long FS_SetStreamPos( stream_t *stream, long newpos );
|
||||
long FS_GetStreamPos( stream_t *stream );
|
||||
void FS_FreeStream( stream_t *stream );
|
||||
qboolean Sound_Process( wavdata_t **wav, int rate, int width, uint flags );
|
||||
uint Sound_GetApproxWavePlayLen( const char *filepath );
|
||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -60,7 +60,7 @@ wavdata_t *FS_LoadSound( const char *filename, const byte *buffer, size_t size )
|
|||
string path, loadname;
|
||||
qboolean anyformat = true;
|
||||
int filesize = 0;
|
||||
const loadwavformat_t *format;
|
||||
const loadwavfmt_t *format;
|
||||
byte *f;
|
||||
|
||||
Sound_Reset(); // clear old sounddata
|
||||
|
@ -153,7 +153,7 @@ stream_t *FS_OpenStream( const char *filename )
|
|||
const char *ext = FS_FileExtension( filename );
|
||||
string path, loadname;
|
||||
qboolean anyformat = true;
|
||||
const streamformat_t *format;
|
||||
const streamfmt_t *format;
|
||||
stream_t *stream;
|
||||
|
||||
Sound_Reset(); // clear old streaminfo
|
||||
|
@ -240,6 +240,36 @@ long FS_ReadStream( stream_t *stream, int bytes, void *buffer )
|
|||
return stream->format->readfunc( stream, bytes, buffer );
|
||||
}
|
||||
|
||||
/*
|
||||
================
|
||||
FS_GetStreamPos
|
||||
|
||||
get stream position (in bytes)
|
||||
================
|
||||
*/
|
||||
long FS_GetStreamPos( stream_t *stream )
|
||||
{
|
||||
if( !stream || !stream->format || !stream->format->getposfunc )
|
||||
return -1;
|
||||
|
||||
return stream->format->getposfunc( stream );
|
||||
}
|
||||
|
||||
/*
|
||||
================
|
||||
FS_SetStreamPos
|
||||
|
||||
get stream position (in bytes)
|
||||
================
|
||||
*/
|
||||
long FS_SetStreamPos( stream_t *stream, long newpos )
|
||||
{
|
||||
if( !stream || !stream->format || !stream->format->setposfunc )
|
||||
return -1;
|
||||
|
||||
return stream->format->setposfunc( stream, newpos );
|
||||
}
|
||||
|
||||
/*
|
||||
================
|
||||
FS_FreeStream
|
||||
|
|
|
@ -28,21 +28,24 @@ GNU General Public License for more details.
|
|||
|
||||
typedef struct mpeg_s
|
||||
{
|
||||
void *state; // hidden decoder state
|
||||
void *vbrtag; // valid for VBR-encoded mpegs
|
||||
void *state; // hidden decoder state
|
||||
void *vbrtag; // valid for VBR-encoded mpegs
|
||||
|
||||
int channels; // num channels
|
||||
int samples; // per one second
|
||||
int play_time;// stream size in milliseconds
|
||||
int rate; // frequency
|
||||
int outsize; // current data size
|
||||
char out[8192];// temporary buffer
|
||||
int channels; // num channels
|
||||
int samples; // per one second
|
||||
int play_time; // stream size in milliseconds
|
||||
int rate; // frequency
|
||||
int outsize; // current data size
|
||||
char out[8192]; // temporary buffer
|
||||
size_t streamsize; // size in bytes
|
||||
} mpeg_t;
|
||||
|
||||
// mpg123 exports
|
||||
int create_decoder( mpeg_t *mpg );
|
||||
int read_mpeg_header( mpeg_t *mpg, const char *data, long bufsize, long streamsize );
|
||||
int read_mpeg_stream( mpeg_t *mpg, const char *data, long bufsize );
|
||||
extern int set_current_pos( mpeg_t *mpg, int newpos, int (*pfnSeek)( void*, long, int ), void *file );
|
||||
int get_current_pos( mpeg_t *mpg, int curpos );
|
||||
void close_decoder( mpeg_t *mpg );
|
||||
|
||||
/*
|
||||
|
@ -153,6 +156,7 @@ stream_t *Stream_OpenMPG( const char *filename )
|
|||
// at this point we have valid stream
|
||||
stream = Mem_Alloc( host.soundpool, sizeof( stream_t ));
|
||||
stream->file = file;
|
||||
stream->pos = 0;
|
||||
|
||||
mpegFile = Mem_Alloc( host.soundpool, sizeof( mpeg_t ));
|
||||
|
||||
|
@ -188,8 +192,9 @@ stream_t *Stream_OpenMPG( const char *filename )
|
|||
return NULL;
|
||||
}
|
||||
|
||||
stream->pos = 0; // how many samples left from previous frame
|
||||
stream->buffsize = 0; // how many samples left from previous frame
|
||||
stream->channels = mpegFile->channels;
|
||||
stream->pos += mpegFile->outsize;
|
||||
stream->rate = mpegFile->rate;
|
||||
stream->width = 2; // always 16 bit
|
||||
stream->ptr = mpegFile;
|
||||
|
@ -209,6 +214,7 @@ long Stream_ReadMPG( stream_t *stream, long needBytes, void *buffer )
|
|||
{
|
||||
// buffer handling
|
||||
int bytesWritten = 0;
|
||||
int result;
|
||||
mpeg_t *mpg;
|
||||
|
||||
mpg = (mpeg_t *)stream->ptr;
|
||||
|
@ -220,13 +226,27 @@ long Stream_ReadMPG( stream_t *stream, long needBytes, void *buffer )
|
|||
long read_len, outsize;
|
||||
byte *data;
|
||||
|
||||
if( !stream->pos )
|
||||
if( !stream->buffsize )
|
||||
{
|
||||
if( read_mpeg_stream( mpg, NULL, 0 ) != MP3_OK )
|
||||
if( stream->timejump )
|
||||
{
|
||||
stream->timejump = false;
|
||||
read_len = FS_Read( stream->file, tempbuff, sizeof( tempbuff ));
|
||||
result = read_mpeg_stream( mpg, tempbuff, read_len );
|
||||
bytesWritten = 0;
|
||||
}
|
||||
else result = read_mpeg_stream( mpg, NULL, 0 );
|
||||
|
||||
stream->pos += mpg->outsize;
|
||||
|
||||
if( result != MP3_OK )
|
||||
{
|
||||
// if there are no bytes remainig so we can decompress the new frame
|
||||
read_len = FS_Read( stream->file, tempbuff, sizeof( tempbuff ));
|
||||
if( read_mpeg_stream( mpg, tempbuff, read_len ) != MP3_OK )
|
||||
result = read_mpeg_stream( mpg, tempbuff, read_len );
|
||||
stream->pos += mpg->outsize;
|
||||
|
||||
if( result != MP3_OK )
|
||||
break; // there was end of the stream
|
||||
}
|
||||
}
|
||||
|
@ -238,20 +258,56 @@ long Stream_ReadMPG( stream_t *stream, long needBytes, void *buffer )
|
|||
|
||||
// copy raw sample to output buffer
|
||||
data = (byte *)buffer + bytesWritten;
|
||||
Q_memcpy( data, &mpg->out[stream->pos], outsize );
|
||||
Q_memcpy( data, &mpg->out[stream->buffsize], outsize );
|
||||
bytesWritten += outsize;
|
||||
mpg->outsize -= outsize;
|
||||
stream->pos += outsize;
|
||||
stream->buffsize += outsize;
|
||||
|
||||
// continue from this sample on a next call
|
||||
if( bytesWritten >= needBytes )
|
||||
return bytesWritten;
|
||||
|
||||
stream->pos = 0; // no bytes remaining
|
||||
stream->buffsize = 0; // no bytes remaining
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
=================
|
||||
Stream_SetPosMPG
|
||||
|
||||
assume stream is valid
|
||||
=================
|
||||
*/
|
||||
long Stream_SetPosMPG( stream_t *stream, long newpos )
|
||||
{
|
||||
// update stream pos for right work GetPos function
|
||||
int newPos = set_current_pos( stream->ptr, newpos, FS_Seek, stream->file );
|
||||
|
||||
if( newPos != -1 )
|
||||
{
|
||||
stream->pos = newPos;
|
||||
stream->timejump = true;
|
||||
stream->buffsize = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
// failed to seek for some reasons
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
=================
|
||||
Stream_GetPosMPG
|
||||
|
||||
assume stream is valid
|
||||
=================
|
||||
*/
|
||||
long Stream_GetPosMPG( stream_t *stream )
|
||||
{
|
||||
return get_current_pos( stream->ptr, stream->pos );
|
||||
}
|
||||
|
||||
/*
|
||||
=================
|
||||
Stream_FreeMPG
|
||||
|
|
|
@ -23,12 +23,12 @@ GNU General Public License for more details.
|
|||
=============================================================================
|
||||
*/
|
||||
// stub
|
||||
static const loadwavformat_t load_null[] =
|
||||
static const loadwavfmt_t load_null[] =
|
||||
{
|
||||
{ NULL, NULL, NULL }
|
||||
};
|
||||
|
||||
static const loadwavformat_t load_game[] =
|
||||
static const loadwavfmt_t load_game[] =
|
||||
{
|
||||
{ "sound/%s%s.%s", "wav", Sound_LoadWAV },
|
||||
{ "%s%s.%s", "wav", Sound_LoadWAV },
|
||||
|
@ -45,16 +45,16 @@ static const loadwavformat_t load_game[] =
|
|||
=============================================================================
|
||||
*/
|
||||
// stub
|
||||
static const streamformat_t stream_null[] =
|
||||
static const streamfmt_t stream_null[] =
|
||||
{
|
||||
{ NULL, NULL, NULL, NULL, NULL }
|
||||
{ NULL, NULL, NULL, NULL, NULL, NULL, NULL }
|
||||
};
|
||||
|
||||
static const streamformat_t stream_game[] =
|
||||
static const streamfmt_t stream_game[] =
|
||||
{
|
||||
{ "%s%s.%s", "mp3", Stream_OpenMPG, Stream_ReadMPG, Stream_FreeMPG },
|
||||
{ "%s%s.%s", "wav", Stream_OpenWAV, Stream_ReadWAV, Stream_FreeWAV },
|
||||
{ NULL, NULL, NULL, NULL, NULL }
|
||||
{ "%s%s.%s", "mp3", Stream_OpenMPG, Stream_ReadMPG, Stream_SetPosMPG, Stream_GetPosMPG, Stream_FreeMPG },
|
||||
{ "%s%s.%s", "wav", Stream_OpenWAV, Stream_ReadWAV, Stream_SetPosWAV, Stream_GetPosWAV, Stream_FreeWAV },
|
||||
{ NULL, NULL, NULL, NULL, NULL, NULL, NULL }
|
||||
};
|
||||
|
||||
void Sound_Init( void )
|
||||
|
@ -89,6 +89,7 @@ byte *Sound_Copy( size_t size )
|
|||
|
||||
out = Mem_Alloc( host.soundpool, size );
|
||||
Q_memcpy( out, sound.tempbuffer, size );
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
|
|
|
@ -298,8 +298,9 @@ Stream_OpenWAV
|
|||
stream_t *Stream_OpenWAV( const char *filename )
|
||||
{
|
||||
stream_t *stream;
|
||||
int iff_data, last_chunk;
|
||||
int last_chunk = 0;
|
||||
char chunkName[4];
|
||||
int iff_data;
|
||||
file_t *file;
|
||||
short t;
|
||||
|
||||
|
@ -374,6 +375,7 @@ stream_t *Stream_OpenWAV( const char *filename )
|
|||
stream = Mem_Alloc( host.soundpool, sizeof( stream_t ));
|
||||
stream->file = file;
|
||||
stream->size = sound.samples * sound.width * sound.channels;
|
||||
stream->buffsize = FS_Tell( file ); // header length
|
||||
stream->channels = sound.channels;
|
||||
stream->width = sound.width;
|
||||
stream->rate = sound.rate;
|
||||
|
@ -406,6 +408,37 @@ long Stream_ReadWAV( stream_t *stream, long bytes, void *buffer )
|
|||
return bytes;
|
||||
}
|
||||
|
||||
/*
|
||||
=================
|
||||
Stream_SetPosWAV
|
||||
|
||||
assume stream is valid
|
||||
=================
|
||||
*/
|
||||
long Stream_SetPosWAV( stream_t *stream, long newpos )
|
||||
{
|
||||
// NOTE: stream->pos it's real file position without header size
|
||||
if( FS_Seek( stream->file, stream->buffsize + newpos, SEEK_SET ) != -1 )
|
||||
{
|
||||
stream->pos = newpos;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
=================
|
||||
Stream_GetPosWAV
|
||||
|
||||
assume stream is valid
|
||||
=================
|
||||
*/
|
||||
long Stream_GetPosWAV( stream_t *stream )
|
||||
{
|
||||
return stream->pos;
|
||||
}
|
||||
|
||||
/*
|
||||
=================
|
||||
Stream_FreeWAV
|
||||
|
|
|
@ -18,27 +18,29 @@ GNU General Public License for more details.
|
|||
|
||||
#include "common.h"
|
||||
|
||||
typedef struct loadwavformat_s
|
||||
typedef struct loadwavfmt_s
|
||||
{
|
||||
const char *formatstring;
|
||||
const char *ext;
|
||||
qboolean (*loadfunc)( const char *name, const byte *buffer, size_t filesize );
|
||||
} loadwavformat_t;
|
||||
} loadwavfmt_t;
|
||||
|
||||
typedef struct streamformat_s
|
||||
typedef struct streamfmt_s
|
||||
{
|
||||
const char *formatstring;
|
||||
const char *ext;
|
||||
|
||||
stream_t *(*openfunc)( const char *filename );
|
||||
long (*readfunc)( stream_t *stream, long bytes, void *buffer );
|
||||
long (*setposfunc)( stream_t *stream, long newpos );
|
||||
long (*getposfunc)( stream_t *stream );
|
||||
void (*freefunc)( stream_t *stream );
|
||||
} streamformat_t;
|
||||
} streamfmt_t;
|
||||
|
||||
typedef struct sndlib_s
|
||||
{
|
||||
const loadwavformat_t *loadformats;
|
||||
const streamformat_t *streamformat; // music stream
|
||||
const loadwavfmt_t *loadformats;
|
||||
const streamfmt_t *streamformat; // music stream
|
||||
|
||||
// current sound state
|
||||
int type; // sound type
|
||||
|
@ -57,17 +59,19 @@ typedef struct sndlib_s
|
|||
|
||||
typedef struct stream_s
|
||||
{
|
||||
const streamformat_t *format; // streamformat to operate
|
||||
const streamfmt_t *format; // streamformat to operate
|
||||
|
||||
// current stream state
|
||||
file_t *file; // stream file
|
||||
int width; // resolution - num bits divided by 8 (8 bit is 1, 16 bit is 2)
|
||||
int rate; // stream rate
|
||||
int channels; // stream channels
|
||||
int type; // wavtype
|
||||
size_t size; // total stream size
|
||||
int pos; // keep track wav position
|
||||
void *ptr;
|
||||
file_t *file; // stream file
|
||||
int width; // resolution - num bits divided by 8 (8 bit is 1, 16 bit is 2)
|
||||
int rate; // stream rate
|
||||
int channels; // stream channels
|
||||
int type; // wavtype
|
||||
size_t size; // total stream size
|
||||
int pos; // actual track position
|
||||
void *ptr; // internal decoder state
|
||||
int buffsize; // cached buffer size
|
||||
qboolean timejump; // true if position is changed
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -116,9 +120,13 @@ qboolean Sound_LoadMPG( const char *name, const byte *buffer, size_t filesize );
|
|||
//
|
||||
stream_t *Stream_OpenWAV( const char *filename );
|
||||
long Stream_ReadWAV( stream_t *stream, long bytes, void *buffer );
|
||||
long Stream_SetPosWAV( stream_t *stream, long newpos );
|
||||
long Stream_GetPosWAV( stream_t *stream );
|
||||
void Stream_FreeWAV( stream_t *stream );
|
||||
stream_t *Stream_OpenMPG( const char *filename );
|
||||
long Stream_ReadMPG( stream_t *stream, long bytes, void *buffer );
|
||||
long Stream_SetPosMPG( stream_t *stream, long newpos );
|
||||
long Stream_GetPosMPG( stream_t *stream );
|
||||
void Stream_FreeMPG( stream_t *stream );
|
||||
|
||||
#endif//SOUNDLIB_H
|
|
@ -818,7 +818,6 @@ void SV_InitOperatorCommands( void )
|
|||
Cmd_AddCommand( "restart", SV_Restart_f, "restarting current level" );
|
||||
Cmd_AddCommand( "reload", SV_Reload_f, "continue from latest save or restart level" );
|
||||
Cmd_AddCommand( "entpatch", SV_EntPatch_f, "write entity patch to allow external editing" );
|
||||
Cmd_AddCommand( "map_background", SV_MapBackground_f, "set background map" );
|
||||
Cmd_AddCommand( "edicts_info", SV_EdictsInfo_f, "show info about edicts" );
|
||||
Cmd_AddCommand( "entity_info", SV_EntityInfo_f, "show more info about edicts" );
|
||||
|
||||
|
@ -827,13 +826,16 @@ void SV_InitOperatorCommands( void )
|
|||
Cmd_AddCommand( "say", SV_ConSay_f, "send a chat message to everyone on the server" );
|
||||
Cmd_AddCommand( "killserver", SV_KillServer_f, "shutdown current server" );
|
||||
}
|
||||
|
||||
Cmd_AddCommand( "save", SV_Save_f, "save the game to a file" );
|
||||
Cmd_AddCommand( "load", SV_Load_f, "load a saved game file" );
|
||||
Cmd_AddCommand( "savequick", SV_QuickSave_f, "save the game to the quicksave" );
|
||||
Cmd_AddCommand( "loadquick", SV_QuickLoad_f, "load a quick-saved game file" );
|
||||
Cmd_AddCommand( "killsave", SV_DeleteSave_f, "delete a saved game file and saveshot" );
|
||||
Cmd_AddCommand( "autosave", SV_AutoSave_f, "save the game to 'autosave' file" );
|
||||
else
|
||||
{
|
||||
Cmd_AddCommand( "map_background", SV_MapBackground_f, "set background map" );
|
||||
Cmd_AddCommand( "save", SV_Save_f, "save the game to a file" );
|
||||
Cmd_AddCommand( "load", SV_Load_f, "load a saved game file" );
|
||||
Cmd_AddCommand( "savequick", SV_QuickSave_f, "save the game to the quicksave" );
|
||||
Cmd_AddCommand( "loadquick", SV_QuickLoad_f, "load a quick-saved game file" );
|
||||
Cmd_AddCommand( "killsave", SV_DeleteSave_f, "delete a saved game file and saveshot" );
|
||||
Cmd_AddCommand( "autosave", SV_AutoSave_f, "save the game to 'autosave' file" );
|
||||
}
|
||||
}
|
||||
|
||||
void SV_KillOperatorCommands( void )
|
||||
|
@ -854,7 +856,6 @@ void SV_KillOperatorCommands( void )
|
|||
Cmd_RemoveCommand( "restart" );
|
||||
Cmd_RemoveCommand( "reload" );
|
||||
Cmd_RemoveCommand( "entpatch" );
|
||||
Cmd_RemoveCommand( "map_background" );
|
||||
Cmd_RemoveCommand( "edicts_info" );
|
||||
Cmd_RemoveCommand( "entity_info" );
|
||||
|
||||
|
@ -864,11 +865,14 @@ void SV_KillOperatorCommands( void )
|
|||
Cmd_RemoveCommand( "setmaster" );
|
||||
Cmd_RemoveCommand( "killserver" );
|
||||
}
|
||||
|
||||
Cmd_RemoveCommand( "save" );
|
||||
Cmd_RemoveCommand( "load" );
|
||||
Cmd_RemoveCommand( "savequick" );
|
||||
Cmd_RemoveCommand( "loadquick" );
|
||||
Cmd_RemoveCommand( "killsave" );
|
||||
Cmd_RemoveCommand( "autosave" );
|
||||
else
|
||||
{
|
||||
Cmd_RemoveCommand( "map_background" );
|
||||
Cmd_RemoveCommand( "save" );
|
||||
Cmd_RemoveCommand( "load" );
|
||||
Cmd_RemoveCommand( "savequick" );
|
||||
Cmd_RemoveCommand( "loadquick" );
|
||||
Cmd_RemoveCommand( "killsave" );
|
||||
Cmd_RemoveCommand( "autosave" );
|
||||
}
|
||||
}
|
|
@ -38,7 +38,7 @@ half-life implementation of saverestore system
|
|||
#define LUMP_DECALS_OFFSET 0
|
||||
#define LUMP_STATIC_OFFSET 1
|
||||
#define LUMP_SOUNDS_OFFSET 2
|
||||
#define LUMP_RESERVED 3 // g-cont. lump reserved for me
|
||||
#define LUMP_MUSIC_OFFSET 3
|
||||
#define NUM_CLIENT_OFFSETS 4
|
||||
|
||||
void (__cdecl *pfnSaveGameComment)( char *buffer, int max_length ) = NULL;
|
||||
|
@ -502,8 +502,23 @@ void RestoreSound( soundlist_t *entry )
|
|||
if(( BF_GetNumBytesWritten( &sv.signon ) + 20 ) >= BF_GetMaxBytes( &sv.signon ))
|
||||
return;
|
||||
|
||||
// TODO: allow save\restore sententces too
|
||||
soundIndex = SV_SoundIndex( entry->name );
|
||||
if( entry->name[0] == '!' && Q_isdigit( entry->name + 1 ))
|
||||
{
|
||||
flags |= SND_SENTENCE;
|
||||
soundIndex = Q_atoi( entry->name + 1 );
|
||||
}
|
||||
else if( entry->name[0] == '#' && Q_isdigit( entry->name + 1 ))
|
||||
{
|
||||
flags |= SND_SENTENCE;
|
||||
soundIndex = Q_atoi( entry->name + 1 ) + 1536;
|
||||
}
|
||||
else
|
||||
{
|
||||
// precache_sound can be used twice: cache sounds when loading
|
||||
// and return sound index when server is active
|
||||
soundIndex = SV_SoundIndex( entry->name );
|
||||
}
|
||||
|
||||
ent = EDICT_NUM( entry->entnum );
|
||||
|
||||
if( entry->attenuation < 0.0f || entry->attenuation > 4.0f )
|
||||
|
@ -1039,8 +1054,9 @@ void SV_SaveClientState( SAVERESTOREDATA *pSaveData, const char *level )
|
|||
ClientSections_t sections;
|
||||
int i, decalCount;
|
||||
int id, version;
|
||||
fs_offset_t header_offset;
|
||||
fs_offset_t header_offset, position;
|
||||
soundlist_t soundInfo[MAX_CHANNELS];
|
||||
string curtrack, looptrack;
|
||||
int soundCount;
|
||||
|
||||
Q_snprintf( name, sizeof( name ), "save/%s.HL2", level );
|
||||
|
@ -1135,10 +1151,8 @@ void SV_SaveClientState( SAVERESTOREDATA *pSaveData, const char *level )
|
|||
}
|
||||
}
|
||||
|
||||
soundCount = S_GetCurrentDynamicSounds( soundInfo, MAX_CHANNELS );
|
||||
|
||||
// DYNAMIC SOUNDS SECTION
|
||||
if( soundCount != 0 )
|
||||
// DYNAMIC SOUNDS SECTION (don't go across transition)
|
||||
if( !svgame.globals->changelevel && ( soundCount = S_GetCurrentDynamicSounds( soundInfo, MAX_CHANNELS )) != 0 )
|
||||
{
|
||||
sections.offsets[LUMP_SOUNDS_OFFSET] = FS_Tell( pFile );
|
||||
FS_Write( pFile, &soundCount, sizeof( int ));
|
||||
|
@ -1148,7 +1162,7 @@ void SV_SaveClientState( SAVERESTOREDATA *pSaveData, const char *level )
|
|||
{
|
||||
soundlist_t *entry;
|
||||
byte nameSize;
|
||||
int start = FS_Tell( pFile );
|
||||
|
||||
entry = &soundInfo[i];
|
||||
|
||||
nameSize = Q_strlen( entry->name ) + 1;
|
||||
|
@ -1165,9 +1179,28 @@ int start = FS_Tell( pFile );
|
|||
FS_Write( pFile, &entry->wordIndex, sizeof( entry->wordIndex ));
|
||||
FS_Write( pFile, &entry->samplePos, sizeof( entry->samplePos ));
|
||||
FS_Write( pFile, &entry->forcedEnd, sizeof( entry->forcedEnd ));
|
||||
Msg( "Write sound %s, %d bytes\n", entry->name, FS_Tell( pFile ) - start );
|
||||
}
|
||||
|
||||
// BACKGROUND MUSIC SECTION (don't go across transition)
|
||||
if( !svgame.globals->changelevel && S_StreamGetCurrentState( curtrack, looptrack, &position ))
|
||||
{
|
||||
byte nameSize;
|
||||
|
||||
sections.offsets[LUMP_MUSIC_OFFSET] = FS_Tell( pFile );
|
||||
|
||||
// write current track
|
||||
nameSize = Q_strlen( curtrack ) + 1;
|
||||
FS_Write( pFile, &nameSize, sizeof( nameSize ));
|
||||
FS_Write( pFile, curtrack, nameSize );
|
||||
|
||||
// write loop track
|
||||
nameSize = Q_strlen( looptrack ) + 1;
|
||||
FS_Write( pFile, &nameSize, sizeof( nameSize ));
|
||||
FS_Write( pFile, looptrack, nameSize );
|
||||
|
||||
// write current track position
|
||||
FS_Write( pFile, &position, sizeof( position ));
|
||||
}
|
||||
|
||||
// AT END
|
||||
FS_Seek( pFile, header_offset, SEEK_SET );
|
||||
|
@ -1338,6 +1371,31 @@ void SV_LoadClientState( SAVERESTOREDATA *pSaveData, const char *level, qboolean
|
|||
}
|
||||
}
|
||||
|
||||
// NOTE: music automatically goes across transition, never restore it on changelevel
|
||||
if( sections.offsets[LUMP_MUSIC_OFFSET] != -1 && !adjacent )
|
||||
{
|
||||
string curtrack, looptrack;
|
||||
int position;
|
||||
byte nameSize;
|
||||
|
||||
// jump to music description
|
||||
FS_Seek( pFile, sections.offsets[LUMP_MUSIC_OFFSET], SEEK_SET );
|
||||
|
||||
// read current track
|
||||
FS_Read( pFile, &nameSize, sizeof( nameSize ));
|
||||
FS_Read( pFile, curtrack, nameSize );
|
||||
|
||||
// read loop track
|
||||
FS_Read( pFile, &nameSize, sizeof( nameSize ));
|
||||
FS_Read( pFile, looptrack, nameSize );
|
||||
|
||||
// read current track position
|
||||
FS_Read( pFile, &position, sizeof( position ));
|
||||
|
||||
BF_WriteByte( &sv.signon, svc_stufftext );
|
||||
BF_WriteString( &sv.signon, va( "music %s %s %i\n", curtrack, looptrack, position ));
|
||||
}
|
||||
|
||||
FS_Close( pFile );
|
||||
}
|
||||
|
||||
|
|
Reference in New Issue