15 Oct 2011
This commit is contained in:
parent
0ccdeddcea
commit
5157440f45
|
@ -42,12 +42,15 @@ GameUI: added support for Steam backgrounds
|
|||
GameUI: hide input symbols for "password" field in "create server" menu page
|
||||
Client: initial implementation of NetAPI
|
||||
Render: clear decals code. Add transparent rendering for 'glass' decals
|
||||
GameUI: added new function into menu interface called a pfnProcessImage, added new argument for pfnPIC_Load.
|
||||
GameUI: added new argument for pfnPIC_Load.
|
||||
GameUI: fix loading flipped Steam background images
|
||||
Client: remove gravity for R_Implosion effect
|
||||
Sound: add SND_MoveMouth16 for support 16-bit sounds lip-sync
|
||||
Engine: fix potentially crash during parsing titles.txt when file is empty
|
||||
Engine: increase MAX_VALUE field up to 2048 characters
|
||||
Console: rename con_gamemaps to con_mapfilter
|
||||
Sound: add check by PVS for dynamic entity channels
|
||||
Sound: add sound culling by geometry (cvar 's_cull' for enable or disable)
|
||||
|
||||
build 1662
|
||||
|
||||
|
|
|
@ -317,12 +317,20 @@ splash logo while map is loading
|
|||
*/
|
||||
void CL_LevelShot_f( void )
|
||||
{
|
||||
size_t ft1, ft2;
|
||||
|
||||
if( cls.scrshot_request != scrshot_plaque ) return;
|
||||
cls.scrshot_request = scrshot_inactive;
|
||||
|
||||
// check for exist
|
||||
Q_sprintf( cls.shotname, "levelshots/%s.bmp", clgame.mapname );
|
||||
if( !FS_FileExists( cls.shotname, true ))
|
||||
|
||||
// make sure what entity patch is never than bsp
|
||||
ft1 = FS_FileTime( cl.worldmodel->name, false );
|
||||
ft2 = FS_FileTime( cls.shotname, true );
|
||||
|
||||
// missing levelshot or level never than levelshot
|
||||
if( ft2 == -1 || ft1 > ft2 )
|
||||
cls.scrshot_action = scrshot_plaque; // build new frame for levelshot
|
||||
else cls.scrshot_action = scrshot_inactive; // disable - not needs
|
||||
}
|
||||
|
|
|
@ -120,9 +120,9 @@ void CL_UpdateEntityFields( cl_entity_t *ent )
|
|||
|
||||
if( applyVel || applyAvel )
|
||||
{
|
||||
ent->origin[0] += ( m_pGround->curstate.origin[0] - m_pGround->latched.prevorigin[0] ) * d;
|
||||
ent->origin[1] += ( m_pGround->curstate.origin[1] - m_pGround->latched.prevorigin[1] ) * d;
|
||||
ent->origin[2] += ( m_pGround->curstate.origin[2] - m_pGround->latched.prevorigin[2] ) * d;
|
||||
ent->origin[0] += ( m_pGround->curstate.origin[0] - m_pGround->prevstate.origin[0] ) * d;
|
||||
ent->origin[1] += ( m_pGround->curstate.origin[1] - m_pGround->prevstate.origin[1] ) * d;
|
||||
ent->origin[2] += ( m_pGround->curstate.origin[2] - m_pGround->prevstate.origin[2] ) * d;
|
||||
}
|
||||
|
||||
if( applyAvel )
|
||||
|
@ -132,7 +132,7 @@ void CL_UpdateEntityFields( cl_entity_t *ent )
|
|||
float ang1, ang2;
|
||||
|
||||
ang1 = m_pGround->curstate.angles[i];
|
||||
ang2 = m_pGround->latched.prevangles[i];
|
||||
ang2 = m_pGround->prevstate.angles[i];
|
||||
f = ang1 - ang2;
|
||||
if( d > 180 ) f -= 360;
|
||||
else if( d < -180 ) f += 360;
|
||||
|
@ -881,13 +881,15 @@ void CL_AddEntities( void )
|
|||
//
|
||||
// sound engine implementation
|
||||
//
|
||||
qboolean CL_GetEntitySpatialization( int entnum, vec3_t origin )
|
||||
qboolean CL_GetEntitySpatialization( int entnum, vec3_t origin, float *pradius )
|
||||
{
|
||||
cl_entity_t *ent;
|
||||
qboolean valid_origin;
|
||||
|
||||
ASSERT( origin != NULL );
|
||||
|
||||
if( entnum == 0 ) return true; // static sound
|
||||
|
||||
valid_origin = VectorIsNull( origin ) ? false : true;
|
||||
ent = CL_GetEntityByIndex( entnum );
|
||||
|
||||
|
@ -902,6 +904,10 @@ qboolean CL_GetEntitySpatialization( int entnum, vec3_t origin )
|
|||
VectorAverage( ent->curstate.mins, ent->curstate.maxs, origin );
|
||||
VectorAdd( origin, ent->curstate.origin, origin );
|
||||
|
||||
// setup radius
|
||||
if( pradius && ent->model != NULL )
|
||||
*pradius = ent->model->radius;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -924,6 +924,7 @@ static ui_enginefuncs_t gEngfuncs =
|
|||
IN_SetCursor,
|
||||
pfnIsMapValid,
|
||||
GL_ProcessTexture,
|
||||
COM_CompareFileTime,
|
||||
};
|
||||
|
||||
void UI_UnloadProgs( void )
|
||||
|
|
|
@ -711,7 +711,7 @@ qboolean CL_InitStudioAPI( void );
|
|||
void CL_ParsePacketEntities( sizebuf_t *msg, qboolean delta );
|
||||
qboolean CL_AddVisibleEntity( cl_entity_t *ent, int entityType );
|
||||
void CL_UpdateStudioVars( cl_entity_t *ent, entity_state_t *newstate, qboolean noInterp );
|
||||
qboolean CL_GetEntitySpatialization( int entnum, vec3_t origin );
|
||||
qboolean CL_GetEntitySpatialization( int entnum, vec3_t origin, float *pradius );
|
||||
void CL_UpdateEntityFields( cl_entity_t *ent );
|
||||
qboolean CL_IsPlayerIndex( int idx );
|
||||
|
||||
|
|
|
@ -1158,7 +1158,6 @@ void R_RenderFrame( const ref_params_t *fd, qboolean drawWorld )
|
|||
return;
|
||||
|
||||
if( drawWorld ) r_lastRefdef = *fd;
|
||||
GL_BackendStartFrame();
|
||||
|
||||
RI.params = RP_NONE;
|
||||
RI.farClip = 0;
|
||||
|
@ -1167,6 +1166,8 @@ void R_RenderFrame( const ref_params_t *fd, qboolean drawWorld )
|
|||
RI.thirdPerson = cl.thirdperson;
|
||||
RI.drawOrtho = gl_overview->integer;
|
||||
|
||||
GL_BackendStartFrame();
|
||||
|
||||
// adjust field of view for widescreen
|
||||
if( glState.wideScreen && r_adjust_fov->integer )
|
||||
V_AdjustFov( &RI.refdef.fov_x, &RI.refdef.fov_y, glState.width, glState.height, false );
|
||||
|
|
|
@ -32,7 +32,7 @@ typedef struct
|
|||
static const dmaterial_t detail_table[] =
|
||||
{
|
||||
{ "crt", "dt_conc", 'C', 0, 0 }, // concrete
|
||||
{ "rock", "dt_rock", 'C', 0, 0 },
|
||||
{ "rock", "dt_rock1", 'C', 0, 0 },
|
||||
{ "conc", "dt_conc", 'C', 0, 0 },
|
||||
{ "wall", "dt_brick", 'C', 0, 0 },
|
||||
{ "crete", "dt_conc", 'C', 0, 0 },
|
||||
|
@ -137,6 +137,8 @@ static const char *R_DetailTextureForName( const char *name )
|
|||
return NULL;
|
||||
if( !Q_strnicmp( name, "3dsky", 5 )) // xash-mod support :-)
|
||||
return NULL;
|
||||
if( !Q_strnicmp( name, "scroll", 6 ))
|
||||
return NULL;
|
||||
if( name[0] == '@' )
|
||||
return NULL;
|
||||
|
||||
|
|
|
@ -508,7 +508,7 @@ float R_GetSpriteFrameInterpolant( cl_entity_t *ent, mspriteframe_t **oldframe,
|
|||
}
|
||||
else if( frame >= psprite->numframes )
|
||||
{
|
||||
MsgDev( D_WARN, "R_GetSpriteFrame: no such frame %d (%s)\n", frame, ent->model->name );
|
||||
MsgDev( D_WARN, "R_GetSpriteFrameInterpolant: no such frame %d (%s)\n", frame, ent->model->name );
|
||||
frame = psprite->numframes - 1;
|
||||
}
|
||||
|
||||
|
|
|
@ -441,7 +441,7 @@ qboolean R_CullStudioModel( cl_entity_t *e )
|
|||
{
|
||||
vec3_t origin;
|
||||
|
||||
if( !e->model->cache.data )
|
||||
if( !e || !e->model || !e->model->cache.data )
|
||||
return true;
|
||||
|
||||
if( e == &clgame.viewent && r_lefthand->integer >= 2 )
|
||||
|
|
|
@ -161,21 +161,24 @@ S_FindName
|
|||
|
||||
==================
|
||||
*/
|
||||
sfx_t *S_FindName( const char *name, int *pfInCache )
|
||||
sfx_t *S_FindName( const char *pname, int *pfInCache )
|
||||
{
|
||||
int i;
|
||||
sfx_t *sfx;
|
||||
uint hash;
|
||||
uint i, hash;
|
||||
string name;
|
||||
|
||||
if( !name || !name[0] || !dma.initialized )
|
||||
if( !pname || !pname[0] || !dma.initialized )
|
||||
return NULL;
|
||||
|
||||
if( Q_strlen( name ) >= MAX_STRING )
|
||||
if( Q_strlen( pname ) >= MAX_STRING )
|
||||
{
|
||||
MsgDev( D_ERROR, "S_FindSound: sound name too long: %s", name );
|
||||
MsgDev( D_ERROR, "S_FindSound: sound name too long: %s", pname );
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Q_strncpy( name, pname, sizeof( name ));
|
||||
COM_FixSlashes( name );
|
||||
|
||||
// see if already loaded
|
||||
hash = Com_HashKey( name, MAX_SFX_HASH );
|
||||
for( sfx = s_sfxHashList[hash]; sfx; sfx = sfx->hashNext )
|
||||
|
|
|
@ -18,8 +18,9 @@ GNU General Public License for more details.
|
|||
#include "client.h"
|
||||
#include "con_nprint.h"
|
||||
#include "ref_params.h"
|
||||
#include "pm_local.h"
|
||||
|
||||
#define MAX_DUPLICATED_CHANNELS 4 // threshold for identical static channels (probably error)
|
||||
#define MAX_DUPLICATED_CHANNELS 4 // threshold for identical static channels (probably error)
|
||||
#define SND_CLIP_DISTANCE 1000.0f
|
||||
|
||||
dma_t dma;
|
||||
|
@ -32,6 +33,8 @@ listener_t s_listener;
|
|||
int total_channels;
|
||||
int soundtime; // sample PAIRS
|
||||
int paintedtime; // sample PAIRS
|
||||
static int trace_count = 0;
|
||||
static int last_trace_chan = 0;
|
||||
|
||||
convar_t *s_volume;
|
||||
convar_t *s_musicvolume;
|
||||
|
@ -42,8 +45,37 @@ convar_t *s_lerping;
|
|||
convar_t *s_ambient_level;
|
||||
convar_t *s_ambient_fade;
|
||||
convar_t *s_combine_sounds;
|
||||
convar_t *s_test; // cvar for testing new effects
|
||||
convar_t *snd_foliage_db_loss;
|
||||
convar_t *snd_gain;
|
||||
convar_t *snd_gain_max;
|
||||
convar_t *snd_gain_min;
|
||||
convar_t *s_refdist;
|
||||
convar_t *s_refdb;
|
||||
convar_t *dsp_off; // set to 1 to disable all dsp processing
|
||||
convar_t *s_cull; // cull sounds by geometry
|
||||
convar_t *s_test; // cvar for testing new effects
|
||||
|
||||
/*
|
||||
=============================================================================
|
||||
|
||||
SOUND COMMON UTILITES
|
||||
|
||||
=============================================================================
|
||||
*/
|
||||
// dB = 20 log (amplitude/32768) 0 to -90.3dB
|
||||
// amplitude = 32768 * 10 ^ (dB/20) 0 to +/- 32768
|
||||
// gain = amplitude/32768 0 to 1.0
|
||||
_inline float Gain_To_dB( float gain ) { return 20 * log( gain ); }
|
||||
_inline float dB_To_Gain ( float dB ) { return pow( 10, dB / 20.0f ); }
|
||||
_inline float Gain_To_Amplitude( float gain ) { return gain * 32768; }
|
||||
_inline float Amplitude_To_Gain( float amplitude ) { return amplitude / 32768; }
|
||||
|
||||
// convert sound db level to approximate sound source radius,
|
||||
// used only for determining how much of sound is obscured by world
|
||||
_inline float dB_To_Radius( float db )
|
||||
{
|
||||
return (SND_RADIUS_MIN + (SND_RADIUS_MAX - SND_RADIUS_MIN) * (db - SND_DB_MIN) / (SND_DB_MAX - SND_DB_MIN));
|
||||
}
|
||||
|
||||
/*
|
||||
=============================================================================
|
||||
|
@ -161,6 +193,74 @@ void S_UpdateSoundFade( void )
|
|||
soundfade.percent = soundfade.initial_percent * f;
|
||||
}
|
||||
|
||||
/*
|
||||
=================
|
||||
SND_ChannelOkToTrace
|
||||
|
||||
All new sounds must traceline once,
|
||||
but cap the max number of tracelines performed per frame
|
||||
for longer or looping sounds to SND_TRACE_UPDATE_MAX.
|
||||
=================
|
||||
*/
|
||||
qboolean SND_ChannelOkToTrace( channel_t *ch )
|
||||
{
|
||||
int i, j;
|
||||
|
||||
// always trace first time sound is spatialized
|
||||
if( ch->bfirstpass ) return true;
|
||||
|
||||
// if already traced max channels, return
|
||||
if( trace_count >= SND_TRACE_UPDATE_MAX )
|
||||
return false;
|
||||
|
||||
// search through all channels starting at g_snd_last_trace_chan index
|
||||
j = last_trace_chan;
|
||||
|
||||
for( i = 0; i < total_channels; i++ )
|
||||
{
|
||||
if( &( channels[j] ) == ch )
|
||||
{
|
||||
ch->bTraced = true;
|
||||
trace_count++;
|
||||
return true;
|
||||
}
|
||||
|
||||
// wrap channel index
|
||||
if( ++j >= total_channels )
|
||||
j = 0;
|
||||
}
|
||||
|
||||
// why didn't we find this channel?
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
=================
|
||||
SND_ChannelTraceReset
|
||||
|
||||
reset counters for traceline limiting per audio update
|
||||
=================
|
||||
*/
|
||||
void SND_ChannelTraceReset( void )
|
||||
{
|
||||
int i;
|
||||
|
||||
// reset search point - make sure we start counting from a new spot
|
||||
// in channel list each time
|
||||
last_trace_chan += SND_TRACE_UPDATE_MAX;
|
||||
|
||||
// wrap at total_channels
|
||||
if( last_trace_chan >= total_channels )
|
||||
last_trace_chan = last_trace_chan - total_channels;
|
||||
|
||||
// reset traceline counter
|
||||
trace_count = 0;
|
||||
|
||||
// reset channel traceline flag
|
||||
for( i = 0; i < total_channels; i++ )
|
||||
channels[i].bTraced = false;
|
||||
}
|
||||
|
||||
/*
|
||||
=================
|
||||
SND_PickDynamicChannel
|
||||
|
@ -256,6 +356,7 @@ channel_t *SND_PickStaticChannel( int entnum, sfx_t *sfx, const vec3_t pos )
|
|||
channel_t *ch = NULL;
|
||||
int i, dupe = 0;
|
||||
|
||||
#if 1 // FIXME: remove this code when predicting is will be done
|
||||
// check for dupliacte sounds
|
||||
for( i = 0; i < total_channels; i++ )
|
||||
{
|
||||
|
@ -266,7 +367,7 @@ channel_t *SND_PickStaticChannel( int entnum, sfx_t *sfx, const vec3_t pos )
|
|||
// check for duplicated static channels (same origin and same sfx)
|
||||
if( dupe > MAX_DUPLICATED_CHANNELS )
|
||||
return NULL;
|
||||
|
||||
#endif
|
||||
// check for replacement sound, or find the best one to replace
|
||||
for( i = MAX_DYNAMIC_CHANNELS; i < total_channels; i++ )
|
||||
{
|
||||
|
@ -361,53 +462,353 @@ int S_AlterChannel( int entnum, int channel, sfx_t *sfx, int vol, int pitch, int
|
|||
|
||||
/*
|
||||
=================
|
||||
SND_Spatialize
|
||||
SND_FadeToNewGain
|
||||
|
||||
always ramp channel gain changes over time
|
||||
returns ramped gain, given new target gain
|
||||
=================
|
||||
*/
|
||||
void SND_Spatialize( channel_t *ch )
|
||||
float SND_FadeToNewGain( channel_t *ch, float gain_new )
|
||||
{
|
||||
float dist, dot;
|
||||
float lscale, rscale, scale;
|
||||
vec3_t source_vec;
|
||||
float speed, frametime;
|
||||
|
||||
// anything coming from the view entity will allways be full volume
|
||||
if( S_IsClient( ch->entnum ))
|
||||
if( gain_new == -1.0 )
|
||||
{
|
||||
ch->leftvol = ch->master_vol;
|
||||
ch->rightvol = ch->master_vol;
|
||||
return;
|
||||
// if -1 passed in, just keep fading to existing target
|
||||
gain_new = ch->ob_gain_target;
|
||||
}
|
||||
|
||||
if( !ch->staticsound )
|
||||
// if first time updating, store new gain into gain & target, return
|
||||
// if gain_new is close to existing gain, store new gain into gain & target, return
|
||||
if( ch->bfirstpass || ( fabs( gain_new - ch->ob_gain ) < 0.01f ))
|
||||
{
|
||||
if( !CL_GetEntitySpatialization( ch->entnum, ch->origin ))
|
||||
ch->ob_gain = gain_new;
|
||||
ch->ob_gain_target = gain_new;
|
||||
ch->ob_gain_inc = 0.0f;
|
||||
return gain_new;
|
||||
}
|
||||
|
||||
// set up new increment to new target
|
||||
frametime = s_listener.frametime;
|
||||
speed = ( frametime / SND_GAIN_FADE_TIME ) * ( gain_new - ch->ob_gain );
|
||||
|
||||
ch->ob_gain_inc = fabs( speed );
|
||||
|
||||
// ch->ob_gain_inc = fabs( gain_new - ch->ob_gain ) / 10.0f;
|
||||
ch->ob_gain_target = gain_new;
|
||||
|
||||
// if not hit target, keep approaching
|
||||
if( fabs( ch->ob_gain - ch->ob_gain_target ) > 0.01f )
|
||||
{
|
||||
ch->ob_gain = ApproachVal( ch->ob_gain_target, ch->ob_gain, ch->ob_gain_inc );
|
||||
}
|
||||
else
|
||||
{
|
||||
// close enough, set gain = target
|
||||
ch->ob_gain = ch->ob_gain_target;
|
||||
}
|
||||
return ch->ob_gain;
|
||||
}
|
||||
|
||||
/*
|
||||
=================
|
||||
SND_GetGainObscured
|
||||
|
||||
drop gain on channel if sound emitter obscured by
|
||||
world, unbroken windows, closed doors, large solid entities etc.
|
||||
=================
|
||||
*/
|
||||
float SND_GetGainObscured( channel_t *ch, qboolean fplayersound, qboolean flooping )
|
||||
{
|
||||
float gain = 1.0f;
|
||||
vec3_t endpoint;
|
||||
int count = 1;
|
||||
pmtrace_t tr;
|
||||
|
||||
if( fplayersound ) return gain; // unchanged
|
||||
|
||||
// during signon just apply regular state machine since world hasn't been
|
||||
// created or settled yet...
|
||||
if( !CL_Active( ))
|
||||
{
|
||||
gain = SND_FadeToNewGain( ch, -1.0f );
|
||||
return gain;
|
||||
}
|
||||
|
||||
// don't do gain obscuring more than once on short one-shot sounds
|
||||
if( !ch->bfirstpass && !ch->isSentence && !flooping && ( ch->entchannel != CHAN_STREAM ))
|
||||
{
|
||||
gain = SND_FadeToNewGain( ch, -1.0f );
|
||||
return gain;
|
||||
}
|
||||
|
||||
// if long or looping sound, process N channels per frame - set 'processed' flag, clear by
|
||||
// cycling through all channels - this maintains a cap on traces per frame
|
||||
if( !SND_ChannelOkToTrace( ch ))
|
||||
{
|
||||
// just keep updating fade to existing target gain - no new trace checking
|
||||
gain = SND_FadeToNewGain( ch, -1.0 );
|
||||
return gain;
|
||||
}
|
||||
|
||||
// set up traceline from player eyes to sound emitting entity origin
|
||||
VectorCopy( ch->origin, endpoint );
|
||||
|
||||
tr = PM_PlayerTrace( clgame.pmove, s_listener.origin, endpoint, PM_STUDIO_IGNORE, 2, -1, NULL );
|
||||
|
||||
if(( tr.fraction < 1.0f || tr.allsolid || tr.startsolid ) && tr.fraction < 0.99f )
|
||||
{
|
||||
// can't see center of sound source:
|
||||
// build extents based on dB sndlvl of source,
|
||||
// test to see how many extents are visible,
|
||||
// drop gain by g_snd_obscured_loss_db per extent hidden
|
||||
vec3_t endpoints[4];
|
||||
int i, sndlvl = DIST_MULT_TO_SNDLVL( ch->dist_mult );
|
||||
vec3_t vecl, vecr, vecl2, vecr2;
|
||||
vec3_t vsrc_forward;
|
||||
vec3_t vsrc_right;
|
||||
vec3_t vsrc_up;
|
||||
float radius;
|
||||
|
||||
// get radius
|
||||
if( ch->radius > 0 ) radius = ch->radius;
|
||||
else radius = dB_To_Radius( sndlvl ); // approximate radius from soundlevel
|
||||
|
||||
// set up extent endpoints - on upward or downward diagonals, facing player
|
||||
for( i = 0; i < 4; i++ ) VectorCopy( endpoint, endpoints[i] );
|
||||
|
||||
// vsrc_forward is normalized vector from sound source to listener
|
||||
VectorSubtract( s_listener.origin, endpoint, vsrc_forward );
|
||||
VectorNormalize( vsrc_forward );
|
||||
VectorVectors( vsrc_forward, vsrc_right, vsrc_up );
|
||||
|
||||
VectorAdd( vsrc_up, vsrc_right, vecl );
|
||||
|
||||
// if src above listener, force 'up' vector to point down - create diagonals up & down
|
||||
if( endpoint[2] > s_listener.origin[2] + ( 10 * 12 ))
|
||||
vsrc_up[2] = -vsrc_up[2];
|
||||
|
||||
VectorSubtract( vsrc_up, vsrc_right, vecr );
|
||||
VectorNormalize( vecl );
|
||||
VectorNormalize( vecr );
|
||||
|
||||
// get diagonal vectors from sound source
|
||||
VectorScale( vecl, radius, vecl2 );
|
||||
VectorScale( vecr, radius, vecr2 );
|
||||
VectorScale( vecl, (radius / 2.0f), vecl );
|
||||
VectorScale( vecr, (radius / 2.0f), vecr );
|
||||
|
||||
// endpoints from diagonal vectors
|
||||
VectorAdd( endpoints[0], vecl, endpoints[0] );
|
||||
VectorAdd( endpoints[1], vecr, endpoints[1] );
|
||||
VectorAdd( endpoints[2], vecl2, endpoints[2] );
|
||||
VectorAdd( endpoints[3], vecr2, endpoints[3] );
|
||||
|
||||
// drop gain for each point on radius diagonal that is obscured
|
||||
for( count = 0, i = 0; i < 4; i++ )
|
||||
{
|
||||
// origin is null and entity not exist on client
|
||||
ch->leftvol = ch->rightvol = 0;
|
||||
return;
|
||||
// UNDONE: some endpoints are in walls - in this case, trace from the wall hit location
|
||||
tr = PM_PlayerTrace( clgame.pmove, s_listener.origin, endpoints[i], PM_STUDIO_IGNORE, 2, -1, NULL );
|
||||
|
||||
if(( tr.fraction < 1.0f || tr.allsolid || tr.startsolid ) && tr.fraction < 0.99f && !tr.startsolid )
|
||||
{
|
||||
// skip first obscured point: at least 2 points + center should be obscured to hear db loss
|
||||
if( ++count > 1 ) gain = gain * dB_To_Gain( SND_OBSCURED_LOSS_DB );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// calculate stereo seperation and distance attenuation
|
||||
VectorSubtract( ch->origin, s_listener.origin, source_vec );
|
||||
|
||||
dist = VectorNormalizeLength( source_vec ) * ch->dist_mult;
|
||||
dot = DotProduct( s_listener.right, source_vec );
|
||||
// crossfade to new gain
|
||||
gain = SND_FadeToNewGain( ch, gain );
|
||||
|
||||
return gain;
|
||||
}
|
||||
|
||||
/*
|
||||
=================
|
||||
SND_GetGain
|
||||
|
||||
The complete gain calculation, with SNDLVL given in dB is:
|
||||
GAIN = 1/dist * snd_refdist * 10 ^ (( SNDLVL - snd_refdb - (dist * snd_foliage_db_loss / 1200)) / 20 )
|
||||
for gain > SND_GAIN_THRESH, start curve smoothing with
|
||||
GAIN = 1 - 1 / (Y * GAIN ^ SND_GAIN_POWER)
|
||||
where Y = -1 / ( (SND_GAIN_THRESH ^ SND_GAIN_POWER) * ( SND_GAIN_THRESH - 1 ))
|
||||
gain curve construction
|
||||
=================
|
||||
*/
|
||||
float SND_GetGain( channel_t *ch, qboolean fplayersound, qboolean flooping, float dist )
|
||||
{
|
||||
float gain = snd_gain->value;
|
||||
|
||||
if( ch->dist_mult )
|
||||
{
|
||||
// test additional attenuation
|
||||
// at 30c, 14.7psi, 60% humidity, 1000Hz == 0.22dB / 100ft.
|
||||
// dense foliage is roughly 2dB / 100ft
|
||||
float additional_dB_loss = snd_foliage_db_loss->value * (dist / 1200);
|
||||
float additional_dist_mult = pow( 10, additional_dB_loss / 20 );
|
||||
float relative_dist = dist * ch->dist_mult * additional_dist_mult;
|
||||
|
||||
// hard code clamp gain to 10x normal (assumes volume and external clipping)
|
||||
if( relative_dist > 0.1f )
|
||||
gain *= ( 1.0f / relative_dist );
|
||||
else gain *= 10.0f;
|
||||
|
||||
// if gain passess threshold, compress gain curve such that gain smoothly approaches 1.0
|
||||
if( gain > SND_GAIN_COMP_THRESH )
|
||||
{
|
||||
float snd_gain_comp_power = SND_GAIN_COMP_EXP_MAX;
|
||||
int sndlvl = DIST_MULT_TO_SNDLVL( ch->dist_mult );
|
||||
float Y;
|
||||
|
||||
// decrease compression curve fit for higher sndlvl values
|
||||
if( sndlvl > SND_DB_MED )
|
||||
{
|
||||
// snd_gain_power varies from max to min as sndlvl varies from 90 to 140
|
||||
snd_gain_comp_power = RemapVal((float)sndlvl, SND_DB_MED, SND_DB_MAX, SND_GAIN_COMP_EXP_MAX, SND_GAIN_COMP_EXP_MIN );
|
||||
}
|
||||
|
||||
// calculate crossover point
|
||||
Y = -1.0f / ( pow( SND_GAIN_COMP_THRESH, snd_gain_comp_power ) * ( SND_GAIN_COMP_THRESH - 1 ));
|
||||
|
||||
// calculate compressed gain
|
||||
gain = 1.0f - 1.0f / (Y * pow( gain, snd_gain_comp_power ));
|
||||
gain = gain * snd_gain_max->value;
|
||||
}
|
||||
|
||||
if( gain < snd_gain_min->value )
|
||||
{
|
||||
// sounds less than snd_gain_min fall off to 0 in distance it took them to fall to snd_gain_min
|
||||
gain = snd_gain_min->value * ( 2.0f - relative_dist * snd_gain_min->value );
|
||||
if( gain <= 0.0f ) gain = 0.001f; // don't propagate 0 gain
|
||||
}
|
||||
}
|
||||
|
||||
if( fplayersound )
|
||||
{
|
||||
// player weapon sounds get extra gain - this compensates
|
||||
// for npc distance effect weapons which mix louder as L+R into L, R
|
||||
// Hack.
|
||||
if( ch->entchannel == CHAN_WEAPON )
|
||||
gain = gain * dB_To_Gain( SND_GAIN_PLAYER_WEAPON_DB );
|
||||
}
|
||||
|
||||
// modify gain if sound source not visible to player
|
||||
gain = gain * SND_GetGainObscured( ch, fplayersound, flooping );
|
||||
|
||||
return gain;
|
||||
}
|
||||
|
||||
/*
|
||||
=================
|
||||
S_SpatializeChannel
|
||||
=================
|
||||
*/
|
||||
void S_SpatializeChannel( int *left_vol, int *right_vol, int master_vol, float gain, float dot, float dist )
|
||||
{
|
||||
float lscale, rscale, scale;
|
||||
|
||||
rscale = 1.0f + dot;
|
||||
lscale = 1.0f - dot;
|
||||
|
||||
// add in distance effect
|
||||
scale = ( 1.0f - dist ) * rscale;
|
||||
ch->rightvol = (int)( ch->master_vol * scale );
|
||||
ch->rightvol = bound( 0, ch->rightvol, 255 );
|
||||
if( s_cull->integer ) scale = gain * rscale / 2;
|
||||
else scale = ( 1.0f - dist ) * rscale;
|
||||
*right_vol = (int)( master_vol * scale );
|
||||
|
||||
scale = ( 1.0f - dist ) * lscale;
|
||||
ch->leftvol = (int)( ch->master_vol * scale );
|
||||
ch->leftvol = bound( 0, ch->leftvol, 255 );
|
||||
if( s_cull->integer ) scale = gain * lscale / 2;
|
||||
else scale = ( 1.0f - dist ) * lscale;
|
||||
*left_vol = (int)( master_vol * scale );
|
||||
|
||||
*right_vol = bound( 0, *right_vol, 255 );
|
||||
*left_vol = bound( 0, *left_vol, 255 );
|
||||
}
|
||||
|
||||
/*
|
||||
=================
|
||||
SND_Spatialize
|
||||
=================
|
||||
*/
|
||||
void SND_Spatialize( channel_t *ch )
|
||||
{
|
||||
vec3_t source_vec;
|
||||
float dist, dot, gain = 1.0f;
|
||||
qboolean fplayersound = false;
|
||||
qboolean looping = false;
|
||||
wavdata_t *pSource;
|
||||
|
||||
// anything coming from the view entity will allways be full volume
|
||||
if( S_IsClient( ch->entnum ))
|
||||
{
|
||||
if( !s_cull->integer )
|
||||
{
|
||||
ch->leftvol = ch->master_vol;
|
||||
ch->rightvol = ch->master_vol;
|
||||
return;
|
||||
}
|
||||
|
||||
// sounds coming from listener actually come from a short distance directly in front of listener
|
||||
fplayersound = true;
|
||||
}
|
||||
|
||||
pSource = ch->sfx->cache;
|
||||
|
||||
if( ch->use_loop && pSource && pSource->loopStart != -1 )
|
||||
looping = true;
|
||||
|
||||
if( !ch->staticsound )
|
||||
{
|
||||
if( !CL_GetEntitySpatialization( ch->entnum, ch->origin, &ch->radius ))
|
||||
{
|
||||
// origin is null and entity not exist on client
|
||||
ch->leftvol = ch->rightvol = 0;
|
||||
ch->bfirstpass = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// source_vec is vector from listener to sound source
|
||||
// player sounds come from 1' in front of player
|
||||
if( fplayersound ) VectorScale( s_listener.forward, 12.0f, source_vec );
|
||||
else VectorSubtract( ch->origin, s_listener.origin, source_vec );
|
||||
|
||||
// normalize source_vec and get distance from listener to source
|
||||
dist = VectorNormalizeLength( source_vec );
|
||||
dot = DotProduct( s_listener.right, source_vec );
|
||||
|
||||
// for sounds with a radius, spatialize left/right evenly within the radius
|
||||
if( ch->radius > 0 && dist < ch->radius )
|
||||
{
|
||||
float interval = ch->radius * 0.5f;
|
||||
float blend = dist - interval;
|
||||
|
||||
if( blend < 0 ) blend = 0;
|
||||
blend /= interval;
|
||||
|
||||
// blend is 0.0 - 1.0, from 50% radius -> 100% radius
|
||||
// at radius * 0.5, dot is 0 (ie: sound centered left/right)
|
||||
// at radius dot == dot
|
||||
dot *= blend;
|
||||
}
|
||||
|
||||
if( s_cull->integer )
|
||||
{
|
||||
// calculate gain based on distance, atmospheric attenuation, interposed objects
|
||||
// perform compression as gain approaches 1.0
|
||||
gain = SND_GetGain( ch, fplayersound, looping, dist );
|
||||
}
|
||||
|
||||
// don't pan sounds with no attenuation
|
||||
if( ch->dist_mult <= 0 ) dot = 0.0f;
|
||||
|
||||
// fill out channel volumes for single location
|
||||
S_SpatializeChannel( &ch->leftvol, &ch->rightvol, ch->master_vol, gain, dot, dist * ch->dist_mult );
|
||||
|
||||
// if playing a word, set volume
|
||||
VOX_SetChanVol( ch );
|
||||
|
||||
// end of first time spatializing sound
|
||||
if( CL_Active( )) ch->bfirstpass = false;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -481,8 +882,16 @@ void S_StartSound( const vec3_t pos, int ent, int chan, sound_t handle, float fv
|
|||
target_chan->entchannel = chan;
|
||||
target_chan->basePitch = pitch;
|
||||
target_chan->isSentence = false;
|
||||
target_chan->radius = 0.0f;
|
||||
target_chan->sfx = sfx;
|
||||
|
||||
// initialize gain due to obscured sound source
|
||||
target_chan->bfirstpass = true;
|
||||
target_chan->ob_gain = 0.0f;
|
||||
target_chan->ob_gain_inc = 0.0f;
|
||||
target_chan->ob_gain_target = 0.0f;
|
||||
target_chan->bTraced = false;
|
||||
|
||||
pSource = NULL;
|
||||
|
||||
if( S_TestSoundChar( sfx->name, '!' ))
|
||||
|
@ -567,6 +976,7 @@ void S_AmbientSound( const vec3_t pos, int ent, sound_t handle, float fvol, floa
|
|||
sfx_t *sfx = NULL;
|
||||
int vol, fvox = 0;
|
||||
qboolean looping = false;
|
||||
float radius = SND_RADIUS_MAX;
|
||||
vec3_t origin;
|
||||
|
||||
if( !dma.initialized ) return;
|
||||
|
@ -589,12 +999,13 @@ void S_AmbientSound( const vec3_t pos, int ent, sound_t handle, float fvol, floa
|
|||
return;
|
||||
}
|
||||
|
||||
if( !pos ) pos = origin;
|
||||
if( pos ) VectorCopy( pos, origin );
|
||||
else VectorClear( origin );
|
||||
|
||||
if( ent != 0 ) CL_GetEntitySpatialization( ent, origin );
|
||||
CL_GetEntitySpatialization( ent, origin, &radius );
|
||||
|
||||
// pick a channel to play on from the static area
|
||||
ch = SND_PickStaticChannel( ent, sfx, pos );
|
||||
ch = SND_PickStaticChannel( ent, sfx, origin );
|
||||
if( !ch ) return;
|
||||
|
||||
if( S_TestSoundChar( sfx->name, '!' ))
|
||||
|
@ -624,7 +1035,7 @@ void S_AmbientSound( const vec3_t pos, int ent, sound_t handle, float fvol, floa
|
|||
return;
|
||||
}
|
||||
|
||||
VectorCopy( pos, ch->origin );
|
||||
VectorCopy( origin, ch->origin );
|
||||
|
||||
// never update positions if source entity is 0
|
||||
ch->staticsound = ( ent == 0 ) ? true : false;
|
||||
|
@ -635,6 +1046,14 @@ void S_AmbientSound( const vec3_t pos, int ent, sound_t handle, float fvol, floa
|
|||
ch->entchannel = CHAN_STATIC;
|
||||
ch->basePitch = pitch;
|
||||
ch->entnum = ent;
|
||||
ch->radius = radius;
|
||||
|
||||
// initialize gain due to obscured sound source
|
||||
ch->bfirstpass = true;
|
||||
ch->ob_gain = 0.0;
|
||||
ch->ob_gain_inc = 0.0;
|
||||
ch->ob_gain_target = 0.0;
|
||||
ch->bTraced = false;
|
||||
|
||||
SND_Spatialize( ch );
|
||||
}
|
||||
|
@ -978,7 +1397,13 @@ void S_RenderFrame( ref_params_t *fd )
|
|||
total++;
|
||||
}
|
||||
}
|
||||
Con_NPrintf( 0, "----(%i)---- painted: %i\n", total - 1, paintedtime );
|
||||
|
||||
// to differentiate modes
|
||||
if( s_cull->integer ) VectorSet( info.color, 0.0f, 1.0f, 0.0f );
|
||||
else VectorSet( info.color, 1.0f, 1.0f, 1.0f );
|
||||
info.index = 0;
|
||||
|
||||
Con_NXPrintf( &info, "----(%i)---- painted: %i\n", total - 1, paintedtime );
|
||||
}
|
||||
|
||||
S_StreamBackgroundTrack ();
|
||||
|
@ -1097,13 +1522,20 @@ qboolean S_Init( void )
|
|||
|
||||
s_volume = Cvar_Get( "volume", "0.7", CVAR_ARCHIVE, "sound volume" );
|
||||
s_musicvolume = Cvar_Get( "musicvolume", "1.0", CVAR_ARCHIVE, "background music volume" );
|
||||
s_mixahead = Cvar_Get( "_snd_mixahead", "0.1", CVAR_ARCHIVE, "how much sound to mix ahead of time" );
|
||||
s_mixahead = Cvar_Get( "_snd_mixahead", "0.1", 0, "how much sound to mix ahead of time" );
|
||||
s_show = Cvar_Get( "s_show", "0", CVAR_ARCHIVE, "show playing sounds" );
|
||||
s_lerping = Cvar_Get( "s_lerping", "0", CVAR_ARCHIVE, "apply interpolation to sound output" );
|
||||
dsp_off = Cvar_Get( "dsp_off", "0", CVAR_ARCHIVE, "set to 1 to disable all dsp processing" );
|
||||
s_ambient_level = Cvar_Get( "ambient_level", "0.3", 0, "volume of environment noises (water and wind)" );
|
||||
s_ambient_fade = Cvar_Get( "ambient_fade", "100", 0, "rate of volume fading when client is moving" );
|
||||
s_combine_sounds = Cvar_Get( "s_combine_channels", "1", CVAR_ARCHIVE, "combine the channels with same sounds" );
|
||||
s_combine_sounds = Cvar_Get( "s_combine_channels", "1", CVAR_ARCHIVE, "combine channels with same sounds" );
|
||||
snd_foliage_db_loss = Cvar_Get( "snd_foliage_db_loss", "4", 0, "foliage loss factor" );
|
||||
snd_gain_max = Cvar_Get( "snd_gain_max", "1", 0, "gain maximal threshold" );
|
||||
snd_gain_min = Cvar_Get( "snd_gain_min", "0.01", 0, "gain minimal threshold" );
|
||||
s_refdist = Cvar_Get( "s_refdist", "36", 0, "soundlevel reference distance" );
|
||||
s_refdb = Cvar_Get( "s_refdb", "60", 0, "soundlevel refernce dB" );
|
||||
snd_gain = Cvar_Get( "snd_gain", "1", 0, "sound default gain" );
|
||||
s_cull = Cvar_Get( "s_cull", "1", CVAR_ARCHIVE, "engine developer cvar for quick testing new features" );
|
||||
s_test = Cvar_Get( "s_test", "0", 0, "engine developer cvar for quick testing new features" );
|
||||
|
||||
Cmd_AddCommand( "play", S_Play_f, "playing a specified sound file" );
|
||||
|
|
|
@ -32,6 +32,23 @@ extern byte *sndpool;
|
|||
#define SOUND_32k 32000 // 32khz sample rate
|
||||
#define SOUND_44k 44100 // 44khz sample rate
|
||||
|
||||
#define SND_TRACE_UPDATE_MAX 2 // max of N channels may be checked for obscured source per frame
|
||||
#define SND_RADIUS_MAX 240.0f // max sound source radius
|
||||
#define SND_RADIUS_MIN 24.0f // min sound source radius
|
||||
#define SND_OBSCURED_LOSS_DB -2.70f // dB loss due to obscured sound source
|
||||
|
||||
// calculate gain based on atmospheric attenuation.
|
||||
// as gain excedes threshold, round off (compress) towards 1.0 using spline
|
||||
#define SND_GAIN_COMP_EXP_MAX 2.5f // Increasing SND_GAIN_COMP_EXP_MAX fits compression curve
|
||||
// more closely to original gain curve as it approaches 1.0.
|
||||
#define SND_GAIN_FADE_TIME 0.25f // xfade seconds between obscuring gain changes
|
||||
#define SND_GAIN_COMP_EXP_MIN 0.8f
|
||||
#define SND_GAIN_COMP_THRESH 0.5f // gain value above which gain curve is rounded to approach 1.0
|
||||
#define SND_DB_MAX 140.0f // max db of any sound source
|
||||
#define SND_DB_MED 90.0f // db at which compression curve changes
|
||||
#define SND_DB_MIN 60.0f // min db of any sound source
|
||||
#define SND_GAIN_PLAYER_WEAPON_DB 2.0f // increase player weapon gain by N dB
|
||||
|
||||
// fixed point stuff for real-time resampling
|
||||
#define FIX_BITS 28
|
||||
#define FIX_SCALE (1 << FIX_BITS)
|
||||
|
@ -42,6 +59,12 @@ extern byte *sndpool;
|
|||
#define FIX_FRACTION(a,b) (FIX(a)/(b))
|
||||
#define FIX_FRACPART(a) ((a) & FIX_MASK)
|
||||
|
||||
#define SNDLVL_TO_DIST_MULT( sndlvl ) \
|
||||
( sndlvl ? ((pow( 10, s_refdb->value / 20 ) / pow( 10, (float)sndlvl / 20 )) / s_refdist->value ) : 0 )
|
||||
|
||||
#define DIST_MULT_TO_SNDLVL( dist_mult ) \
|
||||
(int)( dist_mult ? ( 20 * log10( pow( 10, s_refdb->value / 20 ) / (dist_mult * s_refdist->value ))) : 0 )
|
||||
|
||||
// NOTE: clipped sound at 32760 to avoid overload
|
||||
#define CLIP( x ) (( x ) > 32760 ? 32760 : (( x ) < -32760 ? -32760 : ( x )))
|
||||
#define SWAP( a, b, t ) {(t) = (a); (a) = (b); (b) = (t);}
|
||||
|
@ -138,6 +161,14 @@ typedef struct
|
|||
qboolean localsound; // it's a local menu sound (not looped, not paused)
|
||||
mixer_t pMixer;
|
||||
|
||||
// sound culling
|
||||
qboolean bfirstpass; // true if this is first time sound is spatialized
|
||||
float ob_gain; // gain drop if sound source obscured from listener
|
||||
float ob_gain_target; // target gain while crossfading between ob_gain & ob_gain_target
|
||||
float ob_gain_inc; // crossfade increment
|
||||
qboolean bTraced; // true if channel was already checked this frame for obscuring
|
||||
float radius; // radius of this sound effect
|
||||
|
||||
// sentence mixer
|
||||
int wordIndex;
|
||||
mixer_t *currentWord; // NULL if sentence is finished
|
||||
|
|
|
@ -20,28 +20,6 @@ GNU General Public License for more details.
|
|||
#include "client.h"
|
||||
#include "library.h"
|
||||
|
||||
/*
|
||||
=============
|
||||
COM_LoadFile
|
||||
|
||||
=============
|
||||
*/
|
||||
byte *COM_LoadFile( const char *filename, int usehunk, int *pLength )
|
||||
{
|
||||
string name;
|
||||
|
||||
if( !filename || !*filename )
|
||||
{
|
||||
if( pLength ) *pLength = 0;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Q_strncpy( name, filename, sizeof( name ));
|
||||
COM_FixSlashes( name );
|
||||
|
||||
return FS_LoadFile( name, pLength, false );
|
||||
}
|
||||
|
||||
/*
|
||||
==============
|
||||
COM_ParseFile
|
||||
|
@ -283,7 +261,8 @@ COM_LoadFileForMe
|
|||
byte* COM_LoadFileForMe( const char *filename, int *pLength )
|
||||
{
|
||||
string name;
|
||||
int i;
|
||||
byte *file, *pfile;
|
||||
int iLength;
|
||||
|
||||
if( !filename || !*filename )
|
||||
{
|
||||
|
@ -291,15 +270,71 @@ byte* COM_LoadFileForMe( const char *filename, int *pLength )
|
|||
return NULL;
|
||||
}
|
||||
|
||||
// replace all backward slashes
|
||||
for( i = 0; i < Q_strlen( filename ); i++ )
|
||||
{
|
||||
if( filename[i] == '\\' ) name[i] = '/';
|
||||
else name[i] = Q_tolower( filename[i] );
|
||||
}
|
||||
name[i] = '\0';
|
||||
Q_strncpy( name, filename, sizeof( name ));
|
||||
COM_FixSlashes( name );
|
||||
|
||||
return FS_LoadFile( name, pLength, false );
|
||||
pfile = FS_LoadFile( name, &iLength, false );
|
||||
if( pLength ) *pLength = iLength;
|
||||
|
||||
if( pfile )
|
||||
{
|
||||
file = malloc( iLength + 1 );
|
||||
Q_memcpy( file, pfile, iLength );
|
||||
file[iLength] = '\0';
|
||||
Mem_Free( pfile );
|
||||
pfile = file;
|
||||
}
|
||||
|
||||
return pfile;
|
||||
}
|
||||
|
||||
/*
|
||||
=============
|
||||
COM_LoadFile
|
||||
|
||||
=============
|
||||
*/
|
||||
byte *COM_LoadFile( const char *filename, int usehunk, int *pLength )
|
||||
{
|
||||
string name;
|
||||
byte *file, *pfile;
|
||||
int iLength;
|
||||
|
||||
ASSERT( usehunk == 5 );
|
||||
|
||||
if( !filename || !*filename )
|
||||
{
|
||||
if( pLength ) *pLength = 0;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Q_strncpy( name, filename, sizeof( name ));
|
||||
COM_FixSlashes( name );
|
||||
|
||||
pfile = FS_LoadFile( name, &iLength, false );
|
||||
if( pLength ) *pLength = iLength;
|
||||
|
||||
if( pfile )
|
||||
{
|
||||
file = malloc( iLength + 1 );
|
||||
Q_memcpy( file, pfile, iLength );
|
||||
file[iLength] = '\0';
|
||||
Mem_Free( pfile );
|
||||
pfile = file;
|
||||
}
|
||||
|
||||
return pfile;
|
||||
}
|
||||
|
||||
/*
|
||||
=============
|
||||
COM_FreeFile
|
||||
|
||||
=============
|
||||
*/
|
||||
void COM_FreeFile( void *buffer )
|
||||
{
|
||||
free( buffer );
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -421,6 +456,33 @@ void Con_DPrintf( char *szFmt, ... )
|
|||
Sys_Print( buffer );
|
||||
}
|
||||
|
||||
/*
|
||||
=============
|
||||
COM_CompareFileTime
|
||||
|
||||
=============
|
||||
*/
|
||||
int COM_CompareFileTime( const char *filename1, const char *filename2, int *iCompare )
|
||||
{
|
||||
int bRet = 0;
|
||||
|
||||
*iCompare = 0;
|
||||
|
||||
if( filename1 && filename2 )
|
||||
{
|
||||
long ft1 = FS_FileTime( filename1, false );
|
||||
long ft2 = FS_FileTime( filename2, false );
|
||||
|
||||
// one of files is missing
|
||||
if( ft1 == -1 || ft2 == -1 )
|
||||
return bRet;
|
||||
|
||||
*iCompare = Host_CompareFileTime( ft1, ft2 );
|
||||
bRet = 1;
|
||||
}
|
||||
return bRet;
|
||||
}
|
||||
|
||||
/*
|
||||
=============
|
||||
pfnGetGameDir
|
||||
|
|
|
@ -76,7 +76,7 @@ typedef enum
|
|||
#include "com_model.h"
|
||||
#include "crtlib.h"
|
||||
|
||||
#define XASH_VERSION 0.9f // engine current version
|
||||
#define XASH_VERSION 0.91f // engine current version
|
||||
|
||||
// PERFORMANCE INFO
|
||||
#define MIN_FPS 15.0 // host minimum fps value for maxfps.
|
||||
|
@ -365,6 +365,7 @@ qboolean FS_WriteFile( const char *filename, const void *data, fs_offset_t len )
|
|||
int COM_FileSize( const char *filename );
|
||||
void COM_FixSlashes( char *pname );
|
||||
void COM_FreeFile( void *buffer );
|
||||
int COM_CompareFileTime( const char *filename1, const char *filename2, int *iCompare );
|
||||
search_t *FS_Search( const char *pattern, int caseinsensitive, int gamedironly );
|
||||
file_t *FS_Open( const char *filepath, const char *mode, qboolean gamedironly );
|
||||
fs_offset_t FS_Write( file_t *file, const void *data, size_t datasize );
|
||||
|
|
|
@ -1548,6 +1548,9 @@ int Con_DrawDebugLines( void )
|
|||
{
|
||||
int i, count = 0;
|
||||
int y = 20;
|
||||
int defaultX;
|
||||
|
||||
defaultX = glState.width / 4;
|
||||
|
||||
for( i = 0; i < MAX_DBG_NOTIFY; i++ )
|
||||
{
|
||||
|
@ -1557,7 +1560,7 @@ int Con_DrawDebugLines( void )
|
|||
int fontTall;
|
||||
|
||||
Con_DrawStringLen( con.notify[i].szNotify, &len, &fontTall );
|
||||
x = scr_width->integer - 10 - len;
|
||||
x = scr_width->integer - max( defaultX, len ) - 10;
|
||||
fontTall += 1;
|
||||
|
||||
if( y + fontTall > (int)scr_height->integer - 20 )
|
||||
|
|
|
@ -2297,12 +2297,6 @@ file_t *FS_OpenFile( const char *path, fs_offset_t *filesizeptr, qboolean gamedi
|
|||
return file;
|
||||
}
|
||||
|
||||
void COM_FreeFile( void *buffer )
|
||||
{
|
||||
if( buffer && Mem_IsAllocatedExt( fs_mempool, buffer ))
|
||||
Mem_Free( buffer );
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
FS_WriteFile
|
||||
|
|
|
@ -711,7 +711,7 @@ int EXPORT Host_Main( const char *progname, int bChangeGame, pfnChangeGame func
|
|||
host_clientloaded = Cvar_Get( "host_clientloaded", "0", CVAR_INIT, "inidcates a loaded client.dll" );
|
||||
host_limitlocal = Cvar_Get( "host_limitlocal", "0", 0, "apply cl_cmdrate and rate to loopback connection" );
|
||||
host_allow_materials = Cvar_Get( "host_allow_materials", "0", CVAR_LATCH|CVAR_ARCHIVE, "allow HD textures" );
|
||||
con_gamemaps = Cvar_Get( "con_gamemaps", "1", CVAR_ARCHIVE, "when true show only maps in game folder" );
|
||||
con_gamemaps = Cvar_Get( "con_mapfilter", "1", CVAR_ARCHIVE, "when true show only maps in game folder" );
|
||||
|
||||
// content control
|
||||
Cvar_Get( "violence_hgibs", "1", CVAR_ARCHIVE, "show human gib entities" );
|
||||
|
|
|
@ -63,6 +63,25 @@ int NearestPOW( int value, qboolean roundDown )
|
|||
return n;
|
||||
}
|
||||
|
||||
// remap a value in the range [A,B] to [C,D].
|
||||
float RemapVal( float val, float A, float B, float C, float D )
|
||||
{
|
||||
return C + (D - C) * (val - A) / (B - A);
|
||||
}
|
||||
|
||||
float ApproachVal( float target, float value, float speed )
|
||||
{
|
||||
float delta = target - value;
|
||||
|
||||
if( delta > speed )
|
||||
value += speed;
|
||||
else if( delta < -speed )
|
||||
value -= speed;
|
||||
else value = target;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/*
|
||||
=================
|
||||
rsqrt
|
||||
|
|
|
@ -119,6 +119,8 @@ float RadiusFromBounds( const vec3_t mins, const vec3_t maxs );
|
|||
|
||||
void AngleQuaternion( const vec3_t angles, vec4_t q );
|
||||
void QuaternionSlerp( const vec4_t p, vec4_t q, float t, vec4_t qt );
|
||||
float RemapVal( float val, float A, float B, float C, float D );
|
||||
float ApproachVal( float target, float value, float speed );
|
||||
|
||||
//
|
||||
// matrixlib.c
|
||||
|
|
|
@ -1753,7 +1753,6 @@ static void Mod_LoadBrushModel( model_t *mod, const void *buffer, qboolean *load
|
|||
{
|
||||
msurface_t *surf = mod->surfaces + mod->firstmodelsurface + j;
|
||||
mextrasurf_t *info = SURF_INFO( surf, mod );
|
||||
vec3_t normal, vup = { 0, 0, 1 };
|
||||
|
||||
if( surf->flags & SURF_CONVEYOR )
|
||||
mod->flags |= MODEL_CONVEYOR;
|
||||
|
@ -1761,10 +1760,6 @@ static void Mod_LoadBrushModel( model_t *mod, const void *buffer, qboolean *load
|
|||
// kill water backplanes for submodels (half-life rules)
|
||||
if( surf->flags & SURF_DRAWTURB )
|
||||
{
|
||||
if( surf->flags & SURF_PLANEBACK )
|
||||
VectorNegate( surf->plane->normal, normal );
|
||||
else VectorCopy( surf->plane->normal, normal );
|
||||
|
||||
if( surf->plane->type == PLANE_Z )
|
||||
{
|
||||
// kill bottom plane too
|
||||
|
@ -1872,8 +1867,10 @@ model_t *Mod_LoadModel( model_t *mod, qboolean crash )
|
|||
|
||||
// store modelname to show error
|
||||
Q_strncpy( tempname, mod->name, sizeof( tempname ));
|
||||
COM_FixSlashes( tempname );
|
||||
|
||||
buf = FS_LoadFile( tempname, NULL, false );
|
||||
|
||||
buf = COM_LoadFile( mod->name, 0, NULL );
|
||||
if( !buf )
|
||||
{
|
||||
Q_memset( mod, 0, sizeof( model_t ));
|
||||
|
|
|
@ -160,6 +160,7 @@ typedef struct ui_enginefuncs_s
|
|||
void (*pfnSetCursor)( void *hCursor ); // change cursor
|
||||
int (*pfnIsMapValid)( char *filename );
|
||||
void (*pfnProcessImage)( int texnum, float gamma, int topColor, int bottomColor );
|
||||
int (*pfnCompareFileTime)( char *filename1, char *filename2, int *iCompare );
|
||||
} ui_enginefuncs_t;
|
||||
|
||||
typedef struct
|
||||
|
|
|
@ -1661,8 +1661,8 @@ void SV_Pause_f( sv_client_t *cl )
|
|||
return;
|
||||
}
|
||||
|
||||
if( !sv.paused ) Q_snprintf( message, MAX_STRING, "^2%s^7 paused the game", cl->name );
|
||||
else Q_snprintf( message, MAX_STRING, "^2%s^7 unpaused the game", cl->name );
|
||||
if( !sv.paused ) Q_snprintf( message, MAX_STRING, "^2%s^7 paused the game\n", cl->name );
|
||||
else Q_snprintf( message, MAX_STRING, "^2%s^7 unpaused the game\n", cl->name );
|
||||
|
||||
SV_TogglePause( message );
|
||||
}
|
||||
|
|
|
@ -1034,18 +1034,18 @@ void pfnChangeLevel( const char* s1, const char* s2 )
|
|||
if( !s1 || s1[0] <= ' ' || sv.background )
|
||||
return;
|
||||
|
||||
// make sure we don't issue two changelevels at one time
|
||||
if( svs.changelevel_next_time > host.realtime )
|
||||
return;
|
||||
|
||||
svs.changelevel_next_time = host.realtime + 0.5f; // rest 1 secs if failed
|
||||
|
||||
// make sure we don't issue two changelevels
|
||||
if( svs.spawncount == last_spawncount )
|
||||
return;
|
||||
|
||||
last_spawncount = svs.spawncount;
|
||||
|
||||
// make sure we don't issue two changelevels at one time
|
||||
if( svs.changelevel_next_time > host.realtime )
|
||||
return;
|
||||
|
||||
svs.changelevel_next_time = host.realtime + 1.0f; // rest 1 secs if failed
|
||||
|
||||
SV_SkipUpdates ();
|
||||
|
||||
if( !s2 ) Cbuf_AddText( va( "changelevel %s\n", s1 )); // Quake changlevel
|
||||
|
@ -3102,33 +3102,6 @@ float pfnTime( void )
|
|||
return (float)Sys_DoubleTime();
|
||||
}
|
||||
|
||||
/*
|
||||
=============
|
||||
pfnCompareFileTime
|
||||
|
||||
=============
|
||||
*/
|
||||
int pfnCompareFileTime( const char *filename1, const char *filename2, int *iCompare )
|
||||
{
|
||||
int bRet = 0;
|
||||
|
||||
*iCompare = 0;
|
||||
|
||||
if( filename1 && filename2 )
|
||||
{
|
||||
long ft1 = FS_FileTime( filename1, false );
|
||||
long ft2 = FS_FileTime( filename2, false );
|
||||
|
||||
// one of files is missing
|
||||
if( ft1 == -1 || ft2 == -1 )
|
||||
return bRet;
|
||||
|
||||
*iCompare = Host_CompareFileTime( ft1, ft2 );
|
||||
bRet = 1;
|
||||
}
|
||||
return bRet;
|
||||
}
|
||||
|
||||
/*
|
||||
=============
|
||||
pfnStaticDecal
|
||||
|
@ -4258,7 +4231,7 @@ static enginefuncs_t gEngfuncs =
|
|||
COM_LoadFileForMe,
|
||||
COM_FreeFile,
|
||||
pfnEndSection,
|
||||
pfnCompareFileTime,
|
||||
COM_CompareFileTime,
|
||||
pfnGetGameDir,
|
||||
Cvar_RegisterVariable,
|
||||
pfnFadeClientVolume,
|
||||
|
|
|
@ -182,6 +182,7 @@ char *SV_EntityScript( void )
|
|||
{
|
||||
string entfilename;
|
||||
char *ents;
|
||||
size_t ft1, ft2;
|
||||
|
||||
if( !sv.worldmodel )
|
||||
return NULL;
|
||||
|
@ -191,10 +192,21 @@ char *SV_EntityScript( void )
|
|||
FS_StripExtension( entfilename );
|
||||
FS_DefaultExtension( entfilename, ".ent" );
|
||||
|
||||
if(( ents = FS_LoadFile( entfilename, NULL, true )))
|
||||
// make sure what entity patch is never than bsp
|
||||
ft1 = FS_FileTime( sv.worldmodel->name, false );
|
||||
ft2 = FS_FileTime( entfilename, true );
|
||||
|
||||
if( ft2 != -1 )
|
||||
{
|
||||
MsgDev( D_INFO, "^2Read entity patch:^7 %s\n", entfilename );
|
||||
return ents;
|
||||
if( ft1 > ft2 )
|
||||
{
|
||||
MsgDev( D_INFO, "^1Entity patch is older than bsp. Ignored.\n", entfilename );
|
||||
}
|
||||
else if(( ents = FS_LoadFile( entfilename, NULL, true )))
|
||||
{
|
||||
MsgDev( D_INFO, "^2Read entity patch:^7 %s\n", entfilename );
|
||||
return ents;
|
||||
}
|
||||
}
|
||||
|
||||
// use internal entities
|
||||
|
|
|
@ -1444,7 +1444,6 @@ int UI_VidInit( void )
|
|||
uiStatic.cursorY = ScreenHeight >> 1;
|
||||
uiStatic.outlineWidth = 4;
|
||||
uiStatic.sliderWidth = 6;
|
||||
uiStatic.space_draw_width = 8;
|
||||
|
||||
// all menu buttons have the same view sizes
|
||||
uiStatic.buttons_draw_width = UI_BUTTONS_WIDTH;
|
||||
|
@ -1453,7 +1452,6 @@ int UI_VidInit( void )
|
|||
UI_ScaleCoords( NULL, NULL, &uiStatic.outlineWidth, NULL );
|
||||
UI_ScaleCoords( NULL, NULL, &uiStatic.sliderWidth, NULL );
|
||||
UI_ScaleCoords( NULL, NULL, &uiStatic.buttons_draw_width, &uiStatic.buttons_draw_height );
|
||||
UI_ScaleCoords( NULL, NULL, &uiStatic.space_draw_width, NULL );
|
||||
|
||||
// trying to load colors.lst
|
||||
UI_ApplyCustomColors ();
|
||||
|
|
|
@ -370,7 +370,6 @@ typedef struct
|
|||
|
||||
int buttons_draw_width; // scaled image what we drawing
|
||||
int buttons_draw_height;
|
||||
int space_draw_width; // scaled space width
|
||||
} uiStatic_t;
|
||||
|
||||
extern uiStatic_t uiStatic;
|
||||
|
|
|
@ -173,9 +173,10 @@ inline void TextMessageSetColor( int r, int g, int b, int alpha = 255 )
|
|||
#define DrawConsoleString (*g_engfuncs.pfnDrawConsoleString)
|
||||
#define GetConsoleStringSize (*g_engfuncs.pfnDrawConsoleStringLen)
|
||||
#define ConsoleSetColor (*g_engfuncs.pfnSetConsoleDefaultColor)
|
||||
#define PIC_SetFlags (*g_engfuncs.pfnPIC_SetFlags)
|
||||
|
||||
#define RANDOM_LONG (*g_engfuncs.pfnRandomLong)
|
||||
#define RANDOM_FLOAT (*g_engfuncs.pfnRandomFloat)
|
||||
|
||||
#define COMPARE_FILE_TIME (*g_engfuncs.pfnCompareFileTime)
|
||||
|
||||
#endif//ENGINECALLBACKS_H
|
|
@ -34,6 +34,8 @@ GNU General Public License for more details.
|
|||
|
||||
#define bound( min, num, max ) ((num) >= (min) ? ((num) < (max) ? (num) : (max)) : (min))
|
||||
|
||||
typedef int (*cmpfunc)( const void *a, const void *b );
|
||||
|
||||
#include "menu_int.h"
|
||||
|
||||
#endif//EXTDLL_H
|
|
@ -34,8 +34,12 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|||
#define ID_CANCEL 5
|
||||
#define ID_KEYLIST 6
|
||||
#define ID_TABLEHINT 7
|
||||
#define ID_MSGBOX 8
|
||||
#define ID_MSGTEXT 9
|
||||
#define ID_MSGBOX1 8
|
||||
#define ID_MSGBOX2 9
|
||||
#define ID_MSGTEXT 10
|
||||
#define ID_PROMPT 11
|
||||
#define ID_YES 130
|
||||
#define ID_NO 131
|
||||
|
||||
#define MAX_KEYS 256
|
||||
#define CMD_LENGTH 38
|
||||
|
@ -60,8 +64,12 @@ typedef struct
|
|||
menuPicButton_s cancel;
|
||||
|
||||
// redefine key wait dialog
|
||||
menuAction_s msgBox;
|
||||
menuAction_s msgBox1; // small msgbox
|
||||
menuAction_s msgBox2; // large msgbox
|
||||
menuAction_s dlgMessage;
|
||||
menuAction_s promptMessage;
|
||||
menuPicButton_s yes;
|
||||
menuPicButton_s no;
|
||||
|
||||
menuScrollList_s keysList;
|
||||
menuAction_s hintMessage;
|
||||
|
@ -73,6 +81,23 @@ typedef struct
|
|||
static uiControls_t uiControls;
|
||||
extern bool hold_button_stack;
|
||||
|
||||
static void UI_ResetToDefaultsDialog( void )
|
||||
{
|
||||
// toggle main menu between active\inactive
|
||||
// show\hide reset to defaults dialog
|
||||
uiControls.defaults.generic.flags ^= QMF_INACTIVE;
|
||||
uiControls.advanced.generic.flags ^= QMF_INACTIVE;
|
||||
uiControls.done.generic.flags ^= QMF_INACTIVE;
|
||||
uiControls.cancel.generic.flags ^= QMF_INACTIVE;
|
||||
|
||||
uiControls.keysList.generic.flags ^= QMF_INACTIVE;
|
||||
|
||||
uiControls.msgBox2.generic.flags ^= QMF_HIDDEN;
|
||||
uiControls.promptMessage.generic.flags ^= QMF_HIDDEN;
|
||||
uiControls.yes.generic.flags ^= QMF_HIDDEN;
|
||||
uiControls.no.generic.flags ^= QMF_HIDDEN;
|
||||
}
|
||||
|
||||
/*
|
||||
=================
|
||||
UI_Controls_GetKeyBindings
|
||||
|
@ -215,9 +240,10 @@ static void UI_PromptDialog( void )
|
|||
uiControls.advanced.generic.flags ^= QMF_INACTIVE;
|
||||
uiControls.done.generic.flags ^= QMF_INACTIVE;
|
||||
uiControls.cancel.generic.flags ^= QMF_INACTIVE;
|
||||
|
||||
uiControls.keysList.generic.flags ^= QMF_INACTIVE;
|
||||
|
||||
uiControls.msgBox.generic.flags ^= QMF_HIDDEN;
|
||||
uiControls.msgBox1.generic.flags ^= QMF_HIDDEN;
|
||||
uiControls.dlgMessage.generic.flags ^= QMF_HIDDEN;
|
||||
}
|
||||
|
||||
|
@ -288,6 +314,15 @@ UI_Controls_KeyFunc
|
|||
static const char *UI_Controls_KeyFunc( int key, int down )
|
||||
{
|
||||
char cmd[128];
|
||||
|
||||
if( uiControls.msgBox1.generic.flags & QMF_HIDDEN )
|
||||
{
|
||||
if( down && key == K_ESCAPE && uiControls.defaults.generic.flags & QMF_INACTIVE )
|
||||
{
|
||||
UI_ResetToDefaultsDialog();
|
||||
return uiSoundNull;
|
||||
}
|
||||
}
|
||||
|
||||
if( down )
|
||||
{
|
||||
|
@ -383,6 +418,10 @@ static void UI_Controls_Callback( void *self, int event )
|
|||
UI_PopMenu();
|
||||
break;
|
||||
case ID_DEFAULTS:
|
||||
case ID_NO:
|
||||
UI_ResetToDefaultsDialog ();
|
||||
break;
|
||||
case ID_YES:
|
||||
UI_Controls_ResetKeysList ();
|
||||
break;
|
||||
case ID_ADVANCED:
|
||||
|
@ -491,14 +530,23 @@ static void UI_Controls_Init( void )
|
|||
|
||||
UI_Controls_ParseKeysList();
|
||||
|
||||
uiControls.msgBox.generic.id = ID_MSGBOX;
|
||||
uiControls.msgBox.generic.type = QMTYPE_ACTION;
|
||||
uiControls.msgBox.generic.flags = QMF_INACTIVE|QMF_HIDDEN;
|
||||
uiControls.msgBox.generic.ownerdraw = UI_MsgBox_Ownerdraw; // just a fill rectangle
|
||||
uiControls.msgBox.generic.x = 192;
|
||||
uiControls.msgBox.generic.y = 256;
|
||||
uiControls.msgBox.generic.width = 640;
|
||||
uiControls.msgBox.generic.height = 128;
|
||||
uiControls.msgBox1.generic.id = ID_MSGBOX1;
|
||||
uiControls.msgBox1.generic.type = QMTYPE_ACTION;
|
||||
uiControls.msgBox1.generic.flags = QMF_INACTIVE|QMF_HIDDEN;
|
||||
uiControls.msgBox1.generic.ownerdraw = UI_MsgBox_Ownerdraw; // just a fill rectangle
|
||||
uiControls.msgBox1.generic.x = 192;
|
||||
uiControls.msgBox1.generic.y = 256;
|
||||
uiControls.msgBox1.generic.width = 640;
|
||||
uiControls.msgBox1.generic.height = 128;
|
||||
|
||||
uiControls.msgBox2.generic.id = ID_MSGBOX2;
|
||||
uiControls.msgBox2.generic.type = QMTYPE_ACTION;
|
||||
uiControls.msgBox2.generic.flags = QMF_INACTIVE|QMF_HIDDEN;
|
||||
uiControls.msgBox2.generic.ownerdraw = UI_MsgBox_Ownerdraw; // just a fill rectangle
|
||||
uiControls.msgBox2.generic.x = 192;
|
||||
uiControls.msgBox2.generic.y = 256;
|
||||
uiControls.msgBox2.generic.width = 640;
|
||||
uiControls.msgBox2.generic.height = 256;
|
||||
|
||||
uiControls.dlgMessage.generic.id = ID_MSGTEXT;
|
||||
uiControls.dlgMessage.generic.type = QMTYPE_ACTION;
|
||||
|
@ -507,6 +555,33 @@ static void UI_Controls_Init( void )
|
|||
uiControls.dlgMessage.generic.x = 320;
|
||||
uiControls.dlgMessage.generic.y = 280;
|
||||
|
||||
uiControls.promptMessage.generic.id = ID_PROMPT;
|
||||
uiControls.promptMessage.generic.type = QMTYPE_ACTION;
|
||||
uiControls.promptMessage.generic.flags = QMF_INACTIVE|QMF_DROPSHADOW|QMF_HIDDEN;
|
||||
uiControls.promptMessage.generic.name = "Reset buttons to default?";
|
||||
uiControls.promptMessage.generic.x = 290;
|
||||
uiControls.promptMessage.generic.y = 280;
|
||||
|
||||
uiControls.yes.generic.id = ID_YES;
|
||||
uiControls.yes.generic.type = QMTYPE_BM_BUTTON;
|
||||
uiControls.yes.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW|QMF_HIDDEN;
|
||||
uiControls.yes.generic.name = "Ok";
|
||||
uiControls.yes.generic.x = 380;
|
||||
uiControls.yes.generic.y = 460;
|
||||
uiControls.yes.generic.callback = UI_Controls_Callback;
|
||||
|
||||
UI_UtilSetupPicButton( &uiControls.yes, PC_OK );
|
||||
|
||||
uiControls.no.generic.id = ID_NO;
|
||||
uiControls.no.generic.type = QMTYPE_BM_BUTTON;
|
||||
uiControls.no.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW|QMF_HIDDEN;
|
||||
uiControls.no.generic.name = "Cancel";
|
||||
uiControls.no.generic.x = 530;
|
||||
uiControls.no.generic.y = 460;
|
||||
uiControls.no.generic.callback = UI_Controls_Callback;
|
||||
|
||||
UI_UtilSetupPicButton( &uiControls.no, PC_CANCEL );
|
||||
|
||||
UI_AddItem( &uiControls.menu, (void *)&uiControls.background );
|
||||
UI_AddItem( &uiControls.menu, (void *)&uiControls.banner );
|
||||
UI_AddItem( &uiControls.menu, (void *)&uiControls.defaults );
|
||||
|
@ -515,8 +590,12 @@ static void UI_Controls_Init( void )
|
|||
UI_AddItem( &uiControls.menu, (void *)&uiControls.cancel );
|
||||
UI_AddItem( &uiControls.menu, (void *)&uiControls.hintMessage );
|
||||
UI_AddItem( &uiControls.menu, (void *)&uiControls.keysList );
|
||||
UI_AddItem( &uiControls.menu, (void *)&uiControls.msgBox );
|
||||
UI_AddItem( &uiControls.menu, (void *)&uiControls.msgBox1 );
|
||||
UI_AddItem( &uiControls.menu, (void *)&uiControls.msgBox2 );
|
||||
UI_AddItem( &uiControls.menu, (void *)&uiControls.dlgMessage );
|
||||
UI_AddItem( &uiControls.menu, (void *)&uiControls.promptMessage );
|
||||
UI_AddItem( &uiControls.menu, (void *)&uiControls.no );
|
||||
UI_AddItem( &uiControls.menu, (void *)&uiControls.yes );
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
@ -133,6 +133,9 @@ static void UI_LoadGame_GetGameList( void )
|
|||
|
||||
filenames = FS_SEARCH( "save/*.sav", &numFiles, TRUE );
|
||||
|
||||
// sort the saves in reverse order (oldest past at the end)
|
||||
qsort( filenames, numFiles, sizeof( char* ), (cmpfunc)COM_CompareSaves );
|
||||
|
||||
for ( i = 0; i < numFiles; i++ )
|
||||
{
|
||||
if( i >= UI_MAXGAMES ) break;
|
||||
|
|
|
@ -134,6 +134,9 @@ static void UI_SaveGame_GetGameList( void )
|
|||
|
||||
filenames = FS_SEARCH( "save/*.sav", &numFiles, TRUE );
|
||||
|
||||
// sort the saves in reverse order (oldest past at the end)
|
||||
qsort( filenames, numFiles, sizeof( char* ), (cmpfunc)COM_CompareSaves );
|
||||
|
||||
if ( CL_IsActive() && !gpGlobals->demoplayback )
|
||||
{
|
||||
// create new entry for current save game
|
||||
|
|
|
@ -107,6 +107,25 @@ char *StringCopy( const char *input )
|
|||
return out;
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
COM_CompareSaves
|
||||
============
|
||||
*/
|
||||
int COM_CompareSaves( const void **a, const void **b )
|
||||
{
|
||||
char *file1, *file2;
|
||||
|
||||
file1 = (char *)*a;
|
||||
file2 = (char *)*b;
|
||||
|
||||
int bResult;
|
||||
|
||||
COMPARE_FILE_TIME( file2, file1, &bResult );
|
||||
|
||||
return bResult;
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
COM_FileBase
|
||||
|
|
|
@ -123,6 +123,7 @@ extern void StringConcat( char *dst, const char *src, size_t size ); // strncat
|
|||
extern char *Info_ValueForKey( const char *s, const char *key );
|
||||
extern int KEY_GetKey( const char *binding ); // ripped out from engine
|
||||
extern char *StringCopy( const char *input ); // copy string into new memory
|
||||
extern int COM_CompareSaves( const void **a, const void **b );
|
||||
|
||||
extern void UI_LoadCustomStrings( void );
|
||||
|
||||
|
|
Reference in New Issue