From 5157440f45c03a255d411d6ddc8c003623e0f815 Mon Sep 17 00:00:00 2001 From: g-cont Date: Sat, 15 Oct 2011 00:00:00 +0400 Subject: [PATCH] 15 Oct 2011 --- change.log | 5 +- engine/client/cl_cmds.c | 10 +- engine/client/cl_frame.c | 16 +- engine/client/cl_menu.c | 1 + engine/client/client.h | 2 +- engine/client/gl_rmain.c | 3 +- engine/client/gl_rmisc.c | 4 +- engine/client/gl_sprite.c | 2 +- engine/client/gl_studio.c | 2 +- engine/client/s_load.c | 15 +- engine/client/s_main.c | 504 ++++++++++++++++++++++++++++++++++--- engine/client/sound.h | 31 +++ engine/common/common.c | 124 ++++++--- engine/common/common.h | 3 +- engine/common/console.c | 5 +- engine/common/filesystem.c | 6 - engine/common/host.c | 2 +- engine/common/mathlib.c | 19 ++ engine/common/mathlib.h | 2 + engine/common/model.c | 9 +- engine/menu_int.h | 1 + engine/server/sv_client.c | 4 +- engine/server/sv_game.c | 41 +-- engine/server/sv_init.c | 18 +- mainui/basemenu.cpp | 2 - mainui/basemenu.h | 1 - mainui/enginecallback.h | 3 +- mainui/extdll.h | 2 + mainui/menu_controls.cpp | 105 +++++++- mainui/menu_loadgame.cpp | 3 + mainui/menu_savegame.cpp | 3 + mainui/utils.cpp | 19 ++ mainui/utils.h | 1 + 33 files changed, 812 insertions(+), 156 deletions(-) diff --git a/change.log b/change.log index 7e7d9733..619397ef 100644 --- a/change.log +++ b/change.log @@ -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 diff --git a/engine/client/cl_cmds.c b/engine/client/cl_cmds.c index 6e52c0f3..b028c86b 100644 --- a/engine/client/cl_cmds.c +++ b/engine/client/cl_cmds.c @@ -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 } diff --git a/engine/client/cl_frame.c b/engine/client/cl_frame.c index 61a46240..673eca07 100644 --- a/engine/client/cl_frame.c +++ b/engine/client/cl_frame.c @@ -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; } diff --git a/engine/client/cl_menu.c b/engine/client/cl_menu.c index cc2e8ae2..58af9108 100644 --- a/engine/client/cl_menu.c +++ b/engine/client/cl_menu.c @@ -924,6 +924,7 @@ static ui_enginefuncs_t gEngfuncs = IN_SetCursor, pfnIsMapValid, GL_ProcessTexture, + COM_CompareFileTime, }; void UI_UnloadProgs( void ) diff --git a/engine/client/client.h b/engine/client/client.h index 95b93e45..19a2ce9c 100644 --- a/engine/client/client.h +++ b/engine/client/client.h @@ -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 ); diff --git a/engine/client/gl_rmain.c b/engine/client/gl_rmain.c index 102561ea..6b4bd0ef 100644 --- a/engine/client/gl_rmain.c +++ b/engine/client/gl_rmain.c @@ -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 ); diff --git a/engine/client/gl_rmisc.c b/engine/client/gl_rmisc.c index 39aeac63..fda51bdc 100644 --- a/engine/client/gl_rmisc.c +++ b/engine/client/gl_rmisc.c @@ -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; diff --git a/engine/client/gl_sprite.c b/engine/client/gl_sprite.c index 6df9ff51..d71f462e 100644 --- a/engine/client/gl_sprite.c +++ b/engine/client/gl_sprite.c @@ -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; } diff --git a/engine/client/gl_studio.c b/engine/client/gl_studio.c index da6d1a9b..3e349efb 100644 --- a/engine/client/gl_studio.c +++ b/engine/client/gl_studio.c @@ -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 ) diff --git a/engine/client/s_load.c b/engine/client/s_load.c index 6871fe01..771e4444 100644 --- a/engine/client/s_load.c +++ b/engine/client/s_load.c @@ -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 ) diff --git a/engine/client/s_main.c b/engine/client/s_main.c index 9a9bad9a..61e19dba 100644 --- a/engine/client/s_main.c +++ b/engine/client/s_main.c @@ -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" ); diff --git a/engine/client/sound.h b/engine/client/sound.h index 15fa1660..d46200c2 100644 --- a/engine/client/sound.h +++ b/engine/client/sound.h @@ -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 diff --git a/engine/common/common.c b/engine/common/common.c index 7103cd9c..91d54fce 100644 --- a/engine/common/common.c +++ b/engine/common/common.c @@ -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 diff --git a/engine/common/common.h b/engine/common/common.h index 6c2678fc..c680626a 100644 --- a/engine/common/common.h +++ b/engine/common/common.h @@ -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 ); diff --git a/engine/common/console.c b/engine/common/console.c index 11f49a87..d49e5e8d 100644 --- a/engine/common/console.c +++ b/engine/common/console.c @@ -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 ) diff --git a/engine/common/filesystem.c b/engine/common/filesystem.c index b0cfa4a5..e63af9b8 100644 --- a/engine/common/filesystem.c +++ b/engine/common/filesystem.c @@ -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 diff --git a/engine/common/host.c b/engine/common/host.c index a8ecbd23..e477a61e 100644 --- a/engine/common/host.c +++ b/engine/common/host.c @@ -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" ); diff --git a/engine/common/mathlib.c b/engine/common/mathlib.c index da69f38a..74cded24 100644 --- a/engine/common/mathlib.c +++ b/engine/common/mathlib.c @@ -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 diff --git a/engine/common/mathlib.h b/engine/common/mathlib.h index ab9b74df..9677fe24 100644 --- a/engine/common/mathlib.h +++ b/engine/common/mathlib.h @@ -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 diff --git a/engine/common/model.c b/engine/common/model.c index bf8ecbc7..45a54eb5 100644 --- a/engine/common/model.c +++ b/engine/common/model.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 )); diff --git a/engine/menu_int.h b/engine/menu_int.h index f137a333..953695d2 100644 --- a/engine/menu_int.h +++ b/engine/menu_int.h @@ -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 diff --git a/engine/server/sv_client.c b/engine/server/sv_client.c index 2203bead..c13a7ff8 100644 --- a/engine/server/sv_client.c +++ b/engine/server/sv_client.c @@ -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 ); } diff --git a/engine/server/sv_game.c b/engine/server/sv_game.c index 28c445c7..b70b92ea 100644 --- a/engine/server/sv_game.c +++ b/engine/server/sv_game.c @@ -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, diff --git a/engine/server/sv_init.c b/engine/server/sv_init.c index c351a672..058bff70 100644 --- a/engine/server/sv_init.c +++ b/engine/server/sv_init.c @@ -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 diff --git a/mainui/basemenu.cpp b/mainui/basemenu.cpp index a9681f7a..3487caf4 100644 --- a/mainui/basemenu.cpp +++ b/mainui/basemenu.cpp @@ -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 (); diff --git a/mainui/basemenu.h b/mainui/basemenu.h index dcf5db49..576440f4 100644 --- a/mainui/basemenu.h +++ b/mainui/basemenu.h @@ -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; diff --git a/mainui/enginecallback.h b/mainui/enginecallback.h index 57169829..9c581f36 100644 --- a/mainui/enginecallback.h +++ b/mainui/enginecallback.h @@ -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 \ No newline at end of file diff --git a/mainui/extdll.h b/mainui/extdll.h index 6d45565c..1a86543d 100644 --- a/mainui/extdll.h +++ b/mainui/extdll.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 \ No newline at end of file diff --git a/mainui/menu_controls.cpp b/mainui/menu_controls.cpp index aaf11a7d..54f0efd2 100644 --- a/mainui/menu_controls.cpp +++ b/mainui/menu_controls.cpp @@ -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 ); } /* diff --git a/mainui/menu_loadgame.cpp b/mainui/menu_loadgame.cpp index 1f831e34..0cd4b9a1 100644 --- a/mainui/menu_loadgame.cpp +++ b/mainui/menu_loadgame.cpp @@ -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; diff --git a/mainui/menu_savegame.cpp b/mainui/menu_savegame.cpp index a01ac6a2..6cca4064 100644 --- a/mainui/menu_savegame.cpp +++ b/mainui/menu_savegame.cpp @@ -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 diff --git a/mainui/utils.cpp b/mainui/utils.cpp index adb0417a..4d4c9a8f 100644 --- a/mainui/utils.cpp +++ b/mainui/utils.cpp @@ -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 diff --git a/mainui/utils.h b/mainui/utils.h index f2d81219..df7b5e34 100644 --- a/mainui/utils.h +++ b/mainui/utils.h @@ -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 );