/* cvar.c - dynamic variable tracking Copyright (C) 2007 Uncle Mike This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ #include // fabs... #include "common.h" #include "base_cmd.h" #include "eiface.h" // ARRAYSIZE convar_t *cvar_vars = NULL; // head of list CVAR_DEFINE_AUTO( cmd_scripting, "0", FCVAR_ARCHIVE|FCVAR_PRIVILEGED, "enable simple condition checking and variable operations" ); #ifdef HACKS_RELATED_HLMODS typedef struct cvar_filter_quirks_s { const char *gamedir; // gamedir to enable for const char *cvars; // list of cvars should be excluded from filter } cvar_filter_quirks_t; static cvar_filter_quirks_t cvar_filter_quirks[] = { // EXAMPLE: //{ // "valve", // "test;test1;test100" //}, { "ricochet", "r_drawviewmodel", }, { "dod", "cl_dodmusic" // Day of Defeat Beta 1.3 cvar }, }; static cvar_filter_quirks_t *cvar_active_filter_quirks = NULL; #endif CVAR_DEFINE_AUTO( cl_filterstuffcmd, "1", FCVAR_ARCHIVE | FCVAR_PRIVILEGED, "filter commands coming from server" ); /* ============ Cvar_GetList ============ */ cvar_t *GAME_EXPORT Cvar_GetList( void ) { return (cvar_t *)cvar_vars; } /* ============ Cvar_FindVar find the specified variable by name ============ */ convar_t *Cvar_FindVarExt( const char *var_name, int ignore_group ) { // TODO: ignore group for cvar #if defined(XASH_HASHED_VARS) return (convar_t *)BaseCmd_Find( HM_CVAR, var_name ); #else convar_t *var; if( !var_name ) return NULL; for( var = cvar_vars; var; var = var->next ) { if( ignore_group && FBitSet( ignore_group, var->flags )) continue; if( !Q_stricmp( var_name, var->name )) return var; } return NULL; #endif } /* ============ Cvar_BuildAutoDescription build cvar auto description that based on the setup flags ============ */ const char *Cvar_BuildAutoDescription( const char *szName, int flags ) { static char desc[256]; if( FBitSet( flags, FCVAR_GLCONFIG )) { Q_snprintf( desc, sizeof( desc ), CVAR_GLCONFIG_DESCRIPTION, szName ); return desc; } desc[0] = '\0'; if( FBitSet( flags, FCVAR_EXTDLL )) Q_strncpy( desc, "game ", sizeof( desc )); else if( FBitSet( flags, FCVAR_CLIENTDLL )) Q_strncpy( desc, "client ", sizeof( desc )); else if( FBitSet( flags, FCVAR_GAMEUIDLL )) Q_strncpy( desc, "GameUI ", sizeof( desc )); if( FBitSet( flags, FCVAR_SERVER )) Q_strncat( desc, "server ", sizeof( desc )); if( FBitSet( flags, FCVAR_USERINFO )) Q_strncat( desc, "user ", sizeof( desc )); if( FBitSet( flags, FCVAR_ARCHIVE )) Q_strncat( desc, "archived ", sizeof( desc )); if( FBitSet( flags, FCVAR_PROTECTED )) Q_strncat( desc, "protected ", sizeof( desc )); if( FBitSet( flags, FCVAR_PRIVILEGED )) Q_strncat( desc, "privileged ", sizeof( desc )); Q_strncat( desc, "cvar", sizeof( desc )); return desc; } /* ============ Cvar_UpdateInfo deal with userinfo etc ============ */ static qboolean Cvar_UpdateInfo( convar_t *var, const char *value, qboolean notify ) { if( FBitSet( var->flags, FCVAR_USERINFO )) { if ( Host_IsDedicated() ) { // g-cont. this is a very strange behavior... char *info = SV_Serverinfo(); Info_SetValueForKey( info, var->name, value, MAX_SERVERINFO_STRING ), SV_BroadcastCommand( "fullserverinfo \"%s\"\n", info ); } #if !XASH_DEDICATED else { if( !Info_SetValueForKey( CL_Userinfo(), var->name, value, MAX_INFO_STRING )) return false; // failed to change value // time to update server copy of userinfo CL_ServerCommand( true, "setinfo \"%s\" \"%s\"\n", var->name, value ); CL_LegacyUpdateInfo(); } #endif } if( FBitSet( var->flags, FCVAR_SERVER ) && notify ) { if( !FBitSet( var->flags, FCVAR_UNLOGGED )) { if( FBitSet( var->flags, FCVAR_PROTECTED )) { Log_Printf( "Server cvar \"%s\" = \"%s\"\n", var->name, "***PROTECTED***" ); SV_BroadcastPrintf( NULL, "\"%s\" changed to \"%s\"\n", var->name, "***PROTECTED***" ); } else { Log_Printf( "Server cvar \"%s\" = \"%s\"\n", var->name, value ); SV_BroadcastPrintf( NULL, "\"%s\" changed to \"%s\"\n", var->name, value ); } } } return true; } /* ============ Cvar_ValidateString deal with userinfo etc ============ */ static const char *Cvar_ValidateString( convar_t *var, const char *value ) { const char *pszValue; static char szNew[MAX_STRING]; pszValue = value; szNew[0] = 0; // this cvar's string must only contain printable characters. // strip out any other crap. we'll fill in "empty" if nothing is left if( FBitSet( var->flags, FCVAR_PRINTABLEONLY )) { char *szVal = szNew; int len = 0; // step through the string, only copying back in characters that are printable while( *pszValue && len < ( MAX_STRING - 1 )) { if( ((byte)*pszValue) < 32 ) { pszValue++; continue; } *szVal++ = *pszValue++; len++; } *szVal = '\0'; pszValue = szNew; // g-cont. is this even need? if( !COM_CheckStringEmpty( szNew ) ) Q_strncpy( szNew, "empty", sizeof( szNew )); } if( FBitSet( var->flags, FCVAR_NOEXTRAWHITEPACE )) { char *szVal = szNew; int len = 0; // step through the string, only copying back in characters that are printable while( *pszValue && len < MAX_STRING ) { if( *pszValue == ' ' ) { pszValue++; continue; } *szVal++ = *pszValue++; len++; } *szVal = '\0'; pszValue = szNew; } return pszValue; } /* ============ Cvar_ValidateVarName ============ */ static qboolean Cvar_ValidateVarName( const char *s, qboolean isvalue ) { if( !s ) return false; if( Q_strchr( s, '\\' ) && !isvalue ) return false; if( Q_strchr( s, '\"' )) return false; if( Q_strchr( s, ';' ) && !isvalue ) return false; return true; } /* ============ Cvar_UnlinkVar unlink the variable ============ */ static int Cvar_UnlinkVar( const char *var_name, int group ) { int count = 0; convar_t **prev; convar_t *var; prev = &cvar_vars; while( 1 ) { var = *prev; if( !var ) break; // do filter by name if( var_name && Q_strcmp( var->name, var_name )) { prev = &var->next; continue; } // do filter by specified group if( group && !FBitSet( var->flags, group )) { prev = &var->next; continue; } #if defined(XASH_HASHED_VARS) BaseCmd_Remove( HM_CVAR, var->name ); #endif // unlink variable from list freestring( var->string ); *prev = var->next; // only allocated cvars can throw these fields if( FBitSet( var->flags, FCVAR_ALLOCATED )) { freestring( var->name ); freestring( var->def_string ); freestring( var->desc ); Mem_Free( var ); } count++; } return count; } /* ============ Cvar_Changed Tell the engine parts about cvar changing ============ */ static void Cvar_Changed( convar_t *var ) { Assert( var != NULL ); // tell about changes SetBits( var->flags, FCVAR_CHANGED ); // tell the engine parts with global state if( FBitSet( var->flags, FCVAR_USERINFO )) host.userinfo_changed = true; if( FBitSet( var->flags, FCVAR_MOVEVARS )) host.movevars_changed = true; if( FBitSet( var->flags, FCVAR_VIDRESTART )) host.renderinfo_changed = true; if( !Q_strcmp( var->name, "sv_cheats" )) host.allow_cheats = Q_atoi( var->string ); } /* ============ Cvar_LookupVars ============ */ void Cvar_LookupVars( int checkbit, void *buffer, void *ptr, setpair_t callback ) { convar_t *var; // nothing to process ? if( !callback ) return; // force checkbit to 0 for lookup all cvars for( var = cvar_vars; var; var = var->next ) { if( checkbit && !FBitSet( var->flags, checkbit )) continue; if( buffer ) { callback( var->name, var->string, buffer, ptr ); } else { // NOTE: dlls cvars doesn't have description if( FBitSet( var->flags, FCVAR_ALLOCATED|FCVAR_EXTENDED )) callback( var->name, var->string, var->desc, ptr ); else callback( var->name, var->string, "", ptr ); } } } /* ============ Cvar_Get If the variable already exists, the value will not be set The flags will be or'ed in if the variable exists. ============ */ convar_t *Cvar_Get( const char *name, const char *value, int flags, const char *var_desc ) { convar_t *cur, *find, *var; ASSERT( name && *name ); // check for command coexisting if( Cmd_Exists( name )) { Con_DPrintf( S_ERROR "can't register variable '%s', is already defined as command\n", name ); return NULL; } var = Cvar_FindVar( name ); if( var ) { // already existed? if( FBitSet( flags, FCVAR_GLCONFIG )) { // NOTE: cvars without description produced by Cvar_FullSet // which executed from the config file. So we don't need to // change value here: we *already* have actual value from config. // in other cases we need to rewrite them if( COM_CheckStringEmpty( var->desc )) { // directly set value freestring( var->string ); var->string = copystring( value ); var->value = Q_atof( var->string ); SetBits( var->flags, flags ); // tell engine about changes Cvar_Changed( var ); } } else { SetBits( var->flags, flags ); Cvar_DirectSet( var, value ); } if( FBitSet( var->flags, FCVAR_ALLOCATED ) && Q_strcmp( var_desc, var->desc )) { if( !FBitSet( flags, FCVAR_GLCONFIG )) Con_Reportf( "%s change description from %s to %s\n", var->name, var->desc, var_desc ); // update description if needs freestring( var->desc ); var->desc = copystring( var_desc ); } return var; } // allocate a new cvar var = Z_Malloc( sizeof( *var )); var->name = copystring( name ); var->string = copystring( value ); var->def_string = copystring( value ); var->desc = copystring( var_desc ); var->value = Q_atof( var->string ); var->flags = flags|FCVAR_ALLOCATED; // link the variable in alphanumerical order for( cur = NULL, find = cvar_vars; find && Q_strcmp( find->name, var->name ) < 0; cur = find, find = find->next ); if( cur ) cur->next = var; else cvar_vars = var; var->next = find; // fill it cls.userinfo, svs.serverinfo Cvar_UpdateInfo( var, var->string, false ); // tell engine about changes Cvar_Changed( var ); #if defined(XASH_HASHED_VARS) // add to map BaseCmd_Insert( HM_CVAR, var, var->name ); #endif return var; } /* ============ Cvar_Getf ============ */ convar_t *Cvar_Getf( const char *var_name, int flags, const char *description, const char *format, ... ) { char value[MAX_VA_STRING]; va_list args; va_start( args, format ); Q_vsnprintf( value, sizeof( value ), format, args ); va_end( args ); return Cvar_Get( var_name, value, flags, description ); } /* ============ Cvar_RegisterVariable Adds a freestanding variable to the variable list. ============ */ void Cvar_RegisterVariable( convar_t *var ) { convar_t *cur, *find, *dup; ASSERT( var != NULL ); // first check to see if it has allready been defined dup = Cvar_FindVar( var->name ); if( dup ) { if( !FBitSet( dup->flags, FCVAR_TEMPORARY )) { Con_DPrintf( S_ERROR "can't register variable '%s', is already defined\n", var->name ); return; } // time to replace temp variable with real Cvar_UnlinkVar( var->name, FCVAR_TEMPORARY ); } // check for overlap with a command if( Cmd_Exists( var->name )) { Con_DPrintf( S_ERROR "can't register variable '%s', is already defined as command\n", var->name ); return; } // NOTE: all the 'long' engine cvars have an special setntinel on static declaration // (all the engine cvars should be declared through CVAR_DEFINE macros or they shouldn't working properly anyway) // so we can determine long version 'convar_t' and short version 'cvar_t' more reliable than by FCVAR_EXTDLL flag if( CVAR_CHECK_SENTINEL( var )) SetBits( var->flags, FCVAR_EXTENDED ); // copy the value off, because future sets will free it if( FBitSet( var->flags, FCVAR_EXTENDED )) var->def_string = var->string; // just swap pointers var->string = copystring( var->string ); var->value = Q_atof( var->string ); // find the supposed position in chain (alphanumerical order) for( cur = NULL, find = cvar_vars; find && Q_strcmp( find->name, var->name ) < 0; cur = find, find = find->next ); // now link variable if( cur ) cur->next = var; else cvar_vars = var; var->next = find; // fill it cls.userinfo, svs.serverinfo Cvar_UpdateInfo( var, var->string, false ); // tell engine about changes Cvar_Changed( var ); #if defined(XASH_HASHED_VARS) // add to map BaseCmd_Insert( HM_CVAR, var, var->name ); #endif } static qboolean Cvar_CanSet( const convar_t *cv ) { if( FBitSet( cv->flags, FCVAR_READ_ONLY )) { Con_Printf( "%s is read-only.\n", cv->name ); return false; } if( FBitSet( cv->flags, FCVAR_CHEAT ) && !host.allow_cheats ) { Con_Printf( "%s is cheat protected.\n", cv->name ); return false; } // just tell user about deferred changes if( FBitSet( cv->flags, FCVAR_LATCH ) && ( SV_Active() || CL_Active( ))) Con_Printf( "%s will be changed upon restarting.\n", cv->name ); return true; } /* ============ Cvar_Set2 ============ */ static convar_t *Cvar_Set2( const char *var_name, const char *value ) { convar_t *var; const char *pszValue; qboolean dll_variable = false; qboolean force = false; if( !Cvar_ValidateVarName( var_name, false )) { Con_DPrintf( S_ERROR "Invalid cvar name string: %s\n", var_name ); return NULL; } var = Cvar_FindVar( var_name ); if( !var ) { // if cvar not found, create it return Cvar_Get( var_name, value, FCVAR_USER_CREATED, NULL ); } else { if( !Cmd_CurrentCommandIsPrivileged( )) { if( FBitSet( var->flags, FCVAR_PRIVILEGED )) { Con_Printf( "%s is priveleged.\n", var->name ); return var; } if( cl_filterstuffcmd.value > 0.0f && FBitSet( var->flags, FCVAR_FILTERABLE )) { Con_Printf( "%s is filterable.\n", var->name ); return var; } } } // use this check to prevent acessing for unexisting fields // for cvar_t: latched_string, description, etc dll_variable = FBitSet( var->flags, FCVAR_EXTDLL ); // check value if( !value ) { if( !FBitSet( var->flags, FCVAR_EXTENDED|FCVAR_ALLOCATED )) { Con_Printf( "%s has no default value and can't be reset.\n", var->name ); return var; } if( dll_variable ) value = "0"; else value = var->def_string; // reset to default value } if( !Q_strcmp( value, var->string )) return var; // any latched values not allowed for game cvars if( dll_variable ) force = true; if( !force ) { if( !Cvar_CanSet( var )) return var; } pszValue = Cvar_ValidateString( var, value ); // nothing to change if( !Q_strcmp( pszValue, var->string )) return var; // fill it cls.userinfo, svs.serverinfo if( !Cvar_UpdateInfo( var, pszValue, true )) return var; // and finally changed the cvar itself freestring( var->string ); var->string = copystring( pszValue ); var->value = Q_atof( var->string ); // tell engine about changes Cvar_Changed( var ); return var; } /* ============ Cvar_DirectSet way to change value for many cvars ============ */ void GAME_EXPORT Cvar_DirectSet( convar_t *var, const char *value ) { const char *pszValue; if( unlikely( !var )) return; // ??? // lookup for registration if( unlikely( CVAR_CHECK_SENTINEL( var ) || ( var->next == NULL && !FBitSet( var->flags, FCVAR_EXTENDED|FCVAR_ALLOCATED )))) { // need to registering cvar fisrt Cvar_RegisterVariable( var ); // ok, register it // lookup for registration again if( var != Cvar_FindVar( var->name )) return; // how this possible? } if( !Cvar_CanSet( var )) return; // check value if( !value ) { if( !FBitSet( var->flags, FCVAR_EXTENDED|FCVAR_ALLOCATED )) { Con_Printf( "%s has no default value and can't be reset.\n", var->name ); return; } value = var->def_string; // reset to default value } pszValue = Cvar_ValidateString( var, value ); // nothing to change if( !Q_strcmp( pszValue, var->string )) return; // fill it cls.userinfo, svs.serverinfo if( !Cvar_UpdateInfo( var, pszValue, true )) return; // and finally changed the cvar itself freestring( var->string ); var->string = copystring( pszValue ); var->value = Q_atof( var->string ); // tell engine about changes Cvar_Changed( var ); } /* ============ Cvar_DirectSetValue functionally is the same as Cvar_SetValue but for direct cvar access ============ */ void Cvar_DirectSetValue( convar_t *var, float value ) { char val[32]; if( fabs( value - (int)value ) < 0.000001 ) Q_snprintf( val, sizeof( val ), "%d", (int)value ); else Q_snprintf( val, sizeof( val ), "%f", value ); Cvar_DirectSet( var, val ); } /* ============ Cvar_FullSet can set any protected cvars ============ */ void Cvar_FullSet( const char *var_name, const char *value, int flags ) { convar_t *var = Cvar_FindVar( var_name ); if( !var ) { Cvar_Get( var_name, value, flags, "" ); return; } freestring( var->string ); var->string = copystring( value ); var->value = Q_atof( var->string ); SetBits( var->flags, flags ); // tell engine about changes Cvar_Changed( var ); } /* ============ Cvar_Set ============ */ void GAME_EXPORT Cvar_Set( const char *var_name, const char *value ) { convar_t *var; if( !var_name ) { // there is an error in C code if this happens Con_Printf( "Cvar_Set: passed NULL variable name\n" ); return; } var = Cvar_FindVar( var_name ); if( !var ) { // there is an error in C code if this happens Con_Printf( "Cvar_Set: variable '%s' not found\n", var_name ); return; } Cvar_DirectSet( var, value ); } /* ============ Cvar_SetValue ============ */ void GAME_EXPORT Cvar_SetValue( const char *var_name, float value ) { char val[32]; if( fabs( value - (int)value ) < 0.000001 ) Q_snprintf( val, sizeof( val ), "%d", (int)value ); else Q_snprintf( val, sizeof( val ), "%f", value ); Cvar_Set( var_name, val ); } /* ============ Cvar_Reset ============ */ void Cvar_Reset( const char *var_name ) { Cvar_Set( var_name, NULL ); } /* ============ Cvar_VariableValue ============ */ float GAME_EXPORT Cvar_VariableValue( const char *var_name ) { convar_t *var; if( !var_name ) { // there is an error in C code if this happens Con_Printf( "Cvar_VariableValue: passed NULL variable name\n" ); return 0.0f; } var = Cvar_FindVar( var_name ); if( !var ) return 0.0f; return Q_atof( var->string ); } /* ============ Cvar_VariableInteger ============ */ int Cvar_VariableInteger( const char *var_name ) { convar_t *var; var = Cvar_FindVar( var_name ); if( !var ) return 0; return Q_atoi( var->string ); } /* ============ Cvar_VariableString ============ */ const char *Cvar_VariableString( const char *var_name ) { convar_t *var; if( !var_name ) { // there is an error in C code if this happens Con_Printf( "Cvar_VariableString: passed NULL variable name\n" ); return ""; } var = Cvar_FindVar( var_name ); if( !var ) return ""; return var->string; } /* ============ Cvar_Exists ============ */ qboolean Cvar_Exists( const char *var_name ) { if( Cvar_FindVar( var_name )) return true; return false; } /* ============ Cvar_SetCheatState Any testing variables will be reset to the safe values ============ */ void Cvar_SetCheatState( void ) { convar_t *var; // set all default vars to the safe value for( var = cvar_vars; var; var = var->next ) { // can't process dll cvars - missed def_string if( !FBitSet( var->flags, FCVAR_ALLOCATED|FCVAR_EXTENDED )) continue; if( FBitSet( var->flags, FCVAR_CHEAT )) { if( Q_strcmp( var->def_string, var->string )) Cvar_DirectSet( var, var->def_string ); } } } /* ============ Cvar_SetGL As Cvar_Set, but also flags it as glconfig ============ */ static void Cvar_SetGL( const char *name, const char *value ) { convar_t *var = Cvar_FindVar( name ); if( var && !FBitSet( var->flags, FCVAR_GLCONFIG )) { Con_Reportf( S_ERROR "Can't set non-GL cvar %s to %s\n", name, value ); return; } Cvar_FullSet( name, value, FCVAR_GLCONFIG ); } static qboolean Cvar_ShouldSetCvar( convar_t *v, qboolean isPrivileged ) { const char *prefixes[] = { "cl_", "gl_", "m_", "r_", "hud_", "joy_", "con_", "scr_" }; int i; if( isPrivileged ) return true; if( FBitSet( v->flags, FCVAR_PRIVILEGED )) return false; if( cl_filterstuffcmd.value <= 0.0f ) return true; #ifdef HACKS_RELATED_HLMODS // check if game-specific filter exceptions should be applied // TODO: for cmd exceptions, make generic function if( cvar_active_filter_quirks ) { const char *cur, *next; cur = cvar_active_filter_quirks->cvars; next = Q_strchr( cur, ';' ); // TODO: implement Q_strchrnul while( cur && *cur ) { size_t len = next ? next - cur : Q_strlen( cur ); // found, quit if( !Q_strnicmp( cur, v->name, len )) return true; if( next ) { cur = next + 1; next = Q_strchr( cur, ';' ); } else { // stop cur = NULL; } } } #endif if( FBitSet( v->flags, FCVAR_FILTERABLE )) return false; for( i = 0; i < ARRAYSIZE( prefixes ); i++ ) { if( !Q_strnicmp( v->name, prefixes[i], Q_strlen( prefixes[i] ))) return false; } return true; } /* ============ Cvar_Command Handles variable inspection and changing from the console ============ */ qboolean Cvar_CommandWithPrivilegeCheck( convar_t *v, qboolean isPrivileged ) { // special case for setup opengl configuration if( host.apply_opengl_config ) { Cvar_SetGL( Cmd_Argv( 0 ), Cmd_Argv( 1 ) ); return true; } // check variables if( !v ) // already found in basecmd v = Cvar_FindVar( Cmd_Argv( 0 )); if( !v ) return false; // perform a variable print or set if( Cmd_Argc() == 1 ) { if( FBitSet( v->flags, FCVAR_ALLOCATED|FCVAR_EXTENDED )) Con_Printf( "\"%s\" is \"%s\" ( ^3\"%s\"^7 )\n", v->name, v->string, v->def_string ); else Con_Printf( "\"%s\" is \"%s\"\n", v->name, v->string ); return true; } if( host.apply_game_config ) { if( !FBitSet( v->flags, FCVAR_EXTDLL )) return true; // only game.dll cvars passed } if( FBitSet( v->flags, FCVAR_SPONLY ) && CL_GetMaxClients() > 1 ) { Con_Printf( "can't set \"%s\" in multiplayer\n", v->name ); return false; } else if( !Cvar_ShouldSetCvar( v, isPrivileged )) { Con_Printf( "%s is a privileged variable\n", v->name ); return true; } else { Cvar_DirectSet( v, Cmd_Argv( 1 )); return true; } } /* ============ Cvar_WriteVariables Writes lines containing "variable value" for all variables with the specified flag set to true. ============ */ void Cvar_WriteVariables( file_t *f, int group ) { convar_t *var; for( var = cvar_vars; var; var = var->next ) { if( FBitSet( var->flags, group )) FS_Printf( f, "%s \"%s\"\n", var->name, var->string ); } } /* ============ Cvar_Toggle_f Toggles a cvar for easy single key binding ============ */ static void Cvar_Toggle_f( void ) { int v; if( Cmd_Argc() != 2 ) { Con_Printf( S_USAGE "toggle \n" ); return; } v = !Cvar_VariableInteger( Cmd_Argv( 1 )); Cvar_Set( Cmd_Argv( 1 ), v ? "1" : "0" ); } /* ============ Cvar_Set_f Allows setting and defining of arbitrary cvars from console, even if they weren't declared in C code. ============ */ static void Cvar_Set_f( void ) { int i, c, l = 0, len; char combined[MAX_CMD_TOKENS]; c = Cmd_Argc(); if( c < 3 ) { Msg( S_USAGE "set \n" ); return; } combined[0] = 0; for( i = 2; i < c; i++ ) { len = Q_strlen( Cmd_Argv(i) + 1 ); if( l + len >= MAX_CMD_TOKENS - 2 ) break; Q_strncat( combined, Cmd_Argv( i ), sizeof( combined )); if( i != c-1 ) Q_strncat( combined, " ", sizeof( combined )); l += len; } Cvar_Set2( Cmd_Argv( 1 ), combined ); } /* ============ Cvar_SetGL_f As Cvar_Set, but also flags it as glconfig ============ */ static void Cvar_SetGL_f( void ) { if( Cmd_Argc() != 3 ) { Con_Printf( S_USAGE "setgl \n" ); return; } Cvar_SetGL( Cmd_Argv( 1 ), Cmd_Argv( 2 ) ); } /* ============ Cvar_Reset_f ============ */ static void Cvar_Reset_f( void ) { if( Cmd_Argc() != 2 ) { Con_Printf( S_USAGE "reset \n" ); return; } Cvar_Reset( Cmd_Argv( 1 )); } /* ============ Cvar_List_f ============ */ static void Cvar_List_f( void ) { convar_t *var; const char *match = NULL; int count = 0; size_t matchlen = 0; if( Cmd_Argc() > 1 ) { match = Cmd_Argv( 1 ); matchlen = Q_strlen( match ); } for( var = cvar_vars; var; var = var->next ) { char value[MAX_VA_STRING]; if( var->name[0] == '@' ) continue; // never shows system cvars if( match && !Q_strnicmpext( match, var->name, matchlen )) continue; if( Q_colorstr( var->string )) Q_snprintf( value, sizeof( value ), "\"%s\"", var->string ); else Q_snprintf( value, sizeof( value ), "\"^2%s^7\"", var->string ); if( FBitSet( var->flags, FCVAR_EXTENDED|FCVAR_ALLOCATED )) Con_Printf( " %-*s %s ^3%s^7\n", 32, var->name, value, var->desc ); else Con_Printf( " %-*s %s ^3%s^7\n", 32, var->name, value, Cvar_BuildAutoDescription( var->name, var->flags )); count++; } Con_Printf( "\n%i cvars\n", count ); } /* ============ Cvar_Unlink unlink all cvars with specified flag ============ */ void Cvar_Unlink( int group ) { int count; if( Cvar_VariableInteger( "host_gameloaded" ) && FBitSet( group, FCVAR_EXTDLL )) return; if( Cvar_VariableInteger( "host_clientloaded" ) && FBitSet( group, FCVAR_CLIENTDLL )) return; if( Cvar_VariableInteger( "host_gameuiloaded" ) && FBitSet( group, FCVAR_GAMEUIDLL )) return; count = Cvar_UnlinkVar( NULL, group ); Con_Reportf( "unlink %i cvars\n", count ); } /* ============ Cvar_Init Reads in all archived cvars ============ */ void Cvar_Init( void ) { cvar_vars = NULL; cvar_active_filter_quirks = NULL; Cvar_RegisterVariable( &cmd_scripting ); Cvar_RegisterVariable( &host_developer ); // early registering for dev Cvar_RegisterVariable( &cl_filterstuffcmd ); Cmd_AddRestrictedCommand( "setgl", Cvar_SetGL_f, "change the value of a opengl variable" ); // OBSOLETE Cmd_AddRestrictedCommand( "toggle", Cvar_Toggle_f, "toggles a console variable's values (use for more info)" ); Cmd_AddRestrictedCommand( "reset", Cvar_Reset_f, "reset any type variable to initial value" ); Cmd_AddCommand( "set", Cvar_Set_f, "create or change the value of a console variable" ); Cmd_AddCommand( "cvarlist", Cvar_List_f, "display all console variables beginning with the specified prefix" ); } /* ============ Cvar_PostFSInit ============ */ void Cvar_PostFSInit( void ) { int i; for( i = 0; i < ARRAYSIZE( cvar_filter_quirks ); i++ ) { if( !Q_stricmp( cvar_filter_quirks[i].gamedir, GI->gamefolder )) { cvar_active_filter_quirks = &cvar_filter_quirks[i]; break; } } } #if XASH_ENGINE_TESTS #include "tests.h" void Test_RunCvar( void ) { convar_t *test_privileged = Cvar_Get( "test_privileged", "0", FCVAR_PRIVILEGED, "bark bark" ); convar_t *test_unprivileged = Cvar_Get( "test_unprivileged", "0", 0, "meow meow" ); convar_t *hud_filtered = Cvar_Get( "hud_filtered", "0", 0, "dummy description" ); convar_t *filtered2 = Cvar_Get( "filtered2", "0", FCVAR_FILTERABLE, "filtered2" ); Cbuf_AddText( "test_privileged 1; test_unprivileged 1; hud_filtered 1; filtered2 1\n" ); Cbuf_Execute(); TASSERT( test_privileged->value != 0.0f ); TASSERT( test_unprivileged->value != 0.0f ); TASSERT( hud_filtered->value != 0.0f ); TASSERT( filtered2->value != 0.0f ); Cvar_DirectSet( test_privileged, "0" ); Cvar_DirectSet( test_unprivileged, "0" ); Cvar_DirectSet( hud_filtered, "0" ); Cvar_DirectSet( filtered2, "0" ); Cvar_DirectSet( &cl_filterstuffcmd, "0" ); Cbuf_AddFilteredText( "test_privileged 1; test_unprivileged 1; hud_filtered 1; filtered2 1\n" ); Cbuf_Execute(); Cbuf_Execute(); Cbuf_Execute(); TASSERT( test_privileged->value == 0.0f ); TASSERT( test_unprivileged->value != 0.0f ); TASSERT( hud_filtered->value != 0.0f ); TASSERT( filtered2->value != 0.0f ); Cvar_DirectSet( test_privileged, "0" ); Cvar_DirectSet( test_unprivileged, "0" ); Cvar_DirectSet( hud_filtered, "0" ); Cvar_DirectSet( filtered2, "0" ); Cvar_DirectSet( &cl_filterstuffcmd, "1" ); Cbuf_AddFilteredText( "test_privileged 1; test_unprivileged 1; hud_filtered 1; filtered2 1\n" ); Cbuf_Execute(); Cbuf_Execute(); Cbuf_Execute(); TASSERT( test_privileged->value == 0.0f ); TASSERT( test_unprivileged->value != 0.0f ); TASSERT( hud_filtered->value == 0.0f ); TASSERT( filtered2->value == 0.0f ); } #endif