/*
===========================================================================
Return to Castle Wolfenstein multiplayer GPL Source Code
Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company.
This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (RTCW MP Source Code).
RTCW MP Source Code is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
RTCW MP Source Code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with RTCW MP Source Code. If not, see .
In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
// cvar.c -- dynamic variable tracking
#include "../game/q_shared.h"
#include "qcommon.h"
cvar_t *cvar_vars;
cvar_t *cvar_cheats;
int cvar_modifiedFlags;
#define MAX_CVARS 1024
cvar_t cvar_indexes[MAX_CVARS];
int cvar_numIndexes;
#define FILE_HASH_SIZE 256
static cvar_t* hashTable[FILE_HASH_SIZE];
cvar_t *Cvar_Set2( const char *var_name, const char *value, qboolean force );
/*
================
return a hash value for the filename
================
*/
static long generateHashValue( const char *fname ) {
int i;
long hash;
char letter;
if ( !fname ) {
Com_Error( ERR_DROP, "null name in generateHashValue" ); //gjd
}
hash = 0;
i = 0;
while ( fname[i] != '\0' ) {
letter = tolower( fname[i] );
hash += (long)( letter ) * ( i + 119 );
i++;
}
hash &= ( FILE_HASH_SIZE - 1 );
return hash;
}
/*
============
Cvar_ValidateString
============
*/
static qboolean Cvar_ValidateString( const char *s ) {
if ( !s ) {
return qfalse;
}
if ( strchr( s, '\\' ) ) {
return qfalse;
}
if ( strchr( s, '\"' ) ) {
return qfalse;
}
if ( strchr( s, ';' ) ) {
return qfalse;
}
return qtrue;
}
/*
============
Cvar_FindVar
============
*/
static cvar_t *Cvar_FindVar( const char *var_name ) {
cvar_t *var;
long hash;
hash = generateHashValue( var_name );
for ( var = hashTable[hash] ; var ; var = var->hashNext ) {
if ( !Q_stricmp( var_name, var->name ) ) {
return var;
}
}
return NULL;
}
/*
============
Cvar_VariableValue
============
*/
float Cvar_VariableValue( const char *var_name ) {
cvar_t *var;
var = Cvar_FindVar( var_name );
if ( !var ) {
return 0;
}
return var->value;
}
/*
============
Cvar_VariableIntegerValue
============
*/
int Cvar_VariableIntegerValue( const char *var_name ) {
cvar_t *var;
var = Cvar_FindVar( var_name );
if ( !var ) {
return 0;
}
return var->integer;
}
/*
============
Cvar_VariableString
============
*/
char *Cvar_VariableString( const char *var_name ) {
cvar_t *var;
var = Cvar_FindVar( var_name );
if ( !var ) {
return "";
}
return var->string;
}
/*
============
Cvar_VariableStringBuffer
============
*/
void Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize ) {
cvar_t *var;
var = Cvar_FindVar( var_name );
if ( !var ) {
*buffer = 0;
} else {
Q_strncpyz( buffer, var->string, bufsize );
}
}
/*
============
Cvar_CommandCompletion
============
*/
void Cvar_CommandCompletion( void ( *callback )(const char *s) ) {
cvar_t *cvar;
for ( cvar = cvar_vars ; cvar ; cvar = cvar->next ) {
callback( cvar->name );
}
}
/*
============
Cvar_ClearForeignCharacters
some cvar values need to be safe from foreign characters
============
*/
char *Cvar_ClearForeignCharacters( const char *value ) {
static char clean[MAX_CVAR_VALUE_STRING];
int i,j;
j = 0;
for ( i = 0; value[i] != '\0'; i++ )
{
if ( !( value[i] & 128 ) ) {
clean[j] = value[i];
j++;
}
}
clean[j] = '\0';
return clean;
}
/*
============
Cvar_Get
If the variable already exists, the value will not be set unless CVAR_ROM
The flags will be or'ed in if the variable exists.
============
*/
cvar_t *Cvar_Get( const char *var_name, const char *var_value, int flags ) {
cvar_t *var;
long hash;
if ( !var_name || !var_value ) {
Com_Error( ERR_FATAL, "Cvar_Get: NULL parameter" );
}
if ( !Cvar_ValidateString( var_name ) ) {
Com_Printf( "invalid cvar name string: %s\n", var_name );
var_name = "BADNAME";
}
#if 0 // FIXME: values with backslash happen
if ( !Cvar_ValidateString( var_value ) ) {
Com_Printf( "invalid cvar value string: %s\n", var_value );
var_value = "BADVALUE";
}
#endif
var = Cvar_FindVar( var_name );
if ( var ) {
// if the C code is now specifying a variable that the user already
// set a value for, take the new value as the reset value
if ( ( var->flags & CVAR_USER_CREATED ) && !( flags & CVAR_USER_CREATED )
&& var_value[0] ) {
var->flags &= ~CVAR_USER_CREATED;
Z_Free( var->resetString );
var->resetString = CopyString( var_value );
// ZOID--needs to be set so that cvars the game sets as
// SERVERINFO get sent to clients
cvar_modifiedFlags |= flags;
}
var->flags |= flags;
// only allow one non-empty reset string without a warning
if ( !var->resetString[0] ) {
// we don't have a reset string yet
Z_Free( var->resetString );
var->resetString = CopyString( var_value );
} else if ( var_value[0] && strcmp( var->resetString, var_value ) ) {
Com_DPrintf( "Warning: cvar \"%s\" given initial values: \"%s\" and \"%s\"\n",
var_name, var->resetString, var_value );
}
// if we have a latched string, take that value now
if ( var->latchedString ) {
char *s;
s = var->latchedString;
var->latchedString = NULL; // otherwise cvar_set2 would free it
Cvar_Set2( var_name, s, qtrue );
Z_Free( s );
}
// TTimo
// if CVAR_USERINFO was toggled on for an existing cvar, check wether the value needs to be cleaned from foreigh characters
// (for instance, seta name "name-with-foreign-chars" in the config file, and toggle to CVAR_USERINFO happens later in CL_Init)
if ( flags & CVAR_USERINFO ) {
char *cleaned = Cvar_ClearForeignCharacters( var->string ); // NOTE: it is probably harmless to call Cvar_Set2 in all cases, but I don't want to risk it
if ( strcmp( var->string, cleaned ) ) {
Cvar_Set2( var->name, var->string, qfalse ); // call Cvar_Set2 with the value to be cleaned up for verbosity
}
}
// use a CVAR_SET for rom sets, get won't override
#if 0
// CVAR_ROM always overrides
if ( flags & CVAR_ROM ) {
Cvar_Set2( var_name, var_value, qtrue );
}
#endif
return var;
}
//
// allocate a new cvar
//
if ( cvar_numIndexes >= MAX_CVARS ) {
Com_Error( ERR_FATAL, "MAX_CVARS" );
}
var = &cvar_indexes[cvar_numIndexes];
cvar_numIndexes++;
var->name = CopyString( var_name );
var->string = CopyString( var_value );
var->modified = qtrue;
var->modificationCount = 1;
var->value = atof( var->string );
var->integer = atoi( var->string );
var->resetString = CopyString( var_value );
// link the variable in
var->next = cvar_vars;
cvar_vars = var;
var->flags = flags;
hash = generateHashValue( var_name );
var->hashNext = hashTable[hash];
hashTable[hash] = var;
return var;
}
/*
============
Cvar_Set2
============
*/
#define FOREIGN_MSG "Foreign characters are not allowed in userinfo variables.\n"
#ifndef DEDICATED
const char* CL_TranslateStringBuf( const char *string );
#endif
cvar_t *Cvar_Set2( const char *var_name, const char *value, qboolean force ) {
cvar_t *var;
Com_DPrintf( "Cvar_Set2: %s %s\n", var_name, value );
if ( !Cvar_ValidateString( var_name ) ) {
Com_Printf( "invalid cvar name string: %s\n", var_name );
var_name = "BADNAME";
}
var = Cvar_FindVar( var_name );
if ( !var ) {
if ( !value ) {
return NULL;
}
// create it
if ( !force ) {
return Cvar_Get( var_name, value, CVAR_USER_CREATED );
} else {
return Cvar_Get( var_name, value, 0 );
}
}
if ( !value ) {
value = var->resetString;
}
if ( var->flags & CVAR_USERINFO ) {
char *cleaned = Cvar_ClearForeignCharacters( value );
if ( strcmp( value, cleaned ) ) {
#ifdef DEDICATED
Com_Printf( FOREIGN_MSG );
#else
Com_Printf( CL_TranslateStringBuf( FOREIGN_MSG ) );
#endif
Com_Printf( "Using %s instead of %s\n", cleaned, value );
return Cvar_Set2( var_name, cleaned, force );
}
}
if ( !strcmp( value,var->string ) ) {
return var;
}
// note what types of cvars have been modified (userinfo, archive, serverinfo, systeminfo)
cvar_modifiedFlags |= var->flags;
if ( !force ) {
if ( var->flags & CVAR_ROM ) {
Com_Printf( "%s is read only.\n", var_name );
return var;
}
if ( var->flags & CVAR_INIT ) {
Com_Printf( "%s is write protected.\n", var_name );
return var;
}
if ( ( var->flags & CVAR_CHEAT ) && !cvar_cheats->integer ) {
Com_Printf( "%s is cheat protected.\n", var_name );
return var;
}
if ( var->flags & CVAR_LATCH ) {
if ( var->latchedString ) {
if ( strcmp( value, var->latchedString ) == 0 ) {
return var;
}
Z_Free( var->latchedString );
} else
{
if ( strcmp( value, var->string ) == 0 ) {
return var;
}
}
Com_Printf( "%s will be changed upon restarting.\n", var_name );
var->latchedString = CopyString( value );
var->modified = qtrue;
var->modificationCount++;
return var;
}
} else
{
if ( var->latchedString ) {
Z_Free( var->latchedString );
var->latchedString = NULL;
}
}
if ( !strcmp( value, var->string ) ) {
return var; // not changed
}
var->modified = qtrue;
var->modificationCount++;
Z_Free( var->string ); // free the old value string
var->string = CopyString( value );
var->value = atof( var->string );
var->integer = atoi( var->string );
return var;
}
/*
============
Cvar_Set
============
*/
void Cvar_Set( const char *var_name, const char *value ) {
Cvar_Set2( var_name, value, qtrue );
}
/*
============
Cvar_SetLatched
============
*/
void Cvar_SetLatched( const char *var_name, const char *value ) {
Cvar_Set2( var_name, value, qfalse );
}
/*
============
Cvar_SetValue
============
*/
void Cvar_SetValue( const char *var_name, float value ) {
char val[32];
if ( value == (int)value ) {
Com_sprintf( val, sizeof( val ), "%i",(int)value );
} else {
Com_sprintf( val, sizeof( val ), "%f",value );
}
Cvar_Set( var_name, val );
}
/*
============
Cvar_Reset
============
*/
void Cvar_Reset( const char *var_name ) {
Cvar_Set2( var_name, NULL, qfalse );
}
/*
============
Cvar_SetCheatState
Any testing variables will be reset to the safe values
============
*/
void Cvar_SetCheatState( void ) {
cvar_t *var;
// set all default vars to the safe value
for ( var = cvar_vars ; var ; var = var->next ) {
if ( var->flags & CVAR_CHEAT ) {
if ( strcmp( var->resetString,var->string ) ) {
Cvar_Set( var->name, var->resetString );
}
}
}
}
/*
============
Cvar_Command
Handles variable inspection and changing from the console
============
*/
qboolean Cvar_Command( void ) {
cvar_t *v;
// check variables
v = Cvar_FindVar( Cmd_Argv( 0 ) );
if ( !v ) {
return qfalse;
}
// perform a variable print or set
if ( Cmd_Argc() == 1 ) {
Com_Printf( "\"%s\" is:\"%s" S_COLOR_WHITE "\" default:\"%s" S_COLOR_WHITE "\"\n", v->name, v->string, v->resetString );
if ( v->latchedString ) {
Com_Printf( "latched: \"%s\"\n", v->latchedString );
}
return qtrue;
}
// set the value if forcing isn't required
Cvar_Set2( v->name, Cmd_Argv( 1 ), qfalse );
return qtrue;
}
/*
============
Cvar_Toggle_f
Toggles a cvar for easy single key binding
============
*/
void Cvar_Toggle_f( void ) {
int v;
if ( Cmd_Argc() != 2 ) {
Com_Printf( "usage: toggle \n" );
return;
}
v = Cvar_VariableValue( Cmd_Argv( 1 ) );
v = !v;
Cvar_Set2( Cmd_Argv( 1 ), va( "%i", v ), qfalse );
}
/*
============
Cvar_Set_f
Allows setting and defining of arbitrary cvars from console, even if they
weren't declared in C code.
============
*/
void Cvar_Set_f( void ) {
int i, c, l, len;
char combined[MAX_STRING_TOKENS];
c = Cmd_Argc();
if ( c < 3 ) {
Com_Printf( "usage: set \n" );
return;
}
combined[0] = 0;
l = 0;
for ( i = 2 ; i < c ; i++ ) {
len = strlen( Cmd_Argv( i ) + 1 );
if ( l + len >= MAX_STRING_TOKENS - 2 ) {
break;
}
strcat( combined, Cmd_Argv( i ) );
if ( i != c - 1 ) {
strcat( combined, " " );
}
l += len;
}
Cvar_Set2( Cmd_Argv( 1 ), combined, qfalse );
}
/*
============
Cvar_SetU_f
As Cvar_Set, but also flags it as userinfo
============
*/
void Cvar_SetU_f( void ) {
cvar_t *v;
if ( Cmd_Argc() != 3 ) {
Com_Printf( "usage: setu \n" );
return;
}
Cvar_Set_f();
v = Cvar_FindVar( Cmd_Argv( 1 ) );
if ( !v ) {
return;
}
v->flags |= CVAR_USERINFO;
}
/*
============
Cvar_SetS_f
As Cvar_Set, but also flags it as serverinfo
============
*/
void Cvar_SetS_f( void ) {
cvar_t *v;
if ( Cmd_Argc() != 3 ) {
Com_Printf( "usage: sets \n" );
return;
}
Cvar_Set_f();
v = Cvar_FindVar( Cmd_Argv( 1 ) );
if ( !v ) {
return;
}
v->flags |= CVAR_SERVERINFO;
}
/*
============
Cvar_SetA_f
As Cvar_Set, but also flags it as archived
============
*/
void Cvar_SetA_f( void ) {
cvar_t *v;
if ( Cmd_Argc() != 3 ) {
Com_Printf( "usage: seta \n" );
return;
}
Cvar_Set_f();
v = Cvar_FindVar( Cmd_Argv( 1 ) );
if ( !v ) {
return;
}
v->flags |= CVAR_ARCHIVE;
}
/*
============
Cvar_Reset_f
============
*/
void Cvar_Reset_f( void ) {
if ( Cmd_Argc() != 2 ) {
Com_Printf( "usage: reset \n" );
return;
}
Cvar_Reset( Cmd_Argv( 1 ) );
}
/*
============
Cvar_WriteVariables
Appends lines containing "set variable value" for all variables
with the archive flag set to qtrue.
============
*/
void Cvar_WriteVariables( fileHandle_t f ) {
cvar_t *var;
char buffer[1024];
for ( var = cvar_vars ; var ; var = var->next ) {
if ( Q_stricmp( var->name, "cl_cdkey" ) == 0 ) {
continue;
}
if ( var->flags & CVAR_ARCHIVE ) {
// write the latched value, even if it hasn't taken effect yet
if ( var->latchedString ) {
Com_sprintf( buffer, sizeof( buffer ), "seta %s \"%s\"\n", var->name, var->latchedString );
} else {
Com_sprintf( buffer, sizeof( buffer ), "seta %s \"%s\"\n", var->name, var->string );
}
FS_Printf( f, "%s", buffer );
}
}
}
/*
============
Cvar_List_f
============
*/
void Cvar_List_f( void ) {
cvar_t *var;
int i;
char *match;
if ( Cmd_Argc() > 1 ) {
match = Cmd_Argv( 1 );
} else {
match = NULL;
}
i = 0;
for ( var = cvar_vars ; var ; var = var->next, i++ )
{
if ( match && !Com_Filter( match, var->name, qfalse ) ) {
continue;
}
if ( var->flags & CVAR_SERVERINFO ) {
Com_Printf( "S" );
} else {
Com_Printf( " " );
}
if ( var->flags & CVAR_USERINFO ) {
Com_Printf( "U" );
} else {
Com_Printf( " " );
}
if ( var->flags & CVAR_ROM ) {
Com_Printf( "R" );
} else {
Com_Printf( " " );
}
if ( var->flags & CVAR_INIT ) {
Com_Printf( "I" );
} else {
Com_Printf( " " );
}
if ( var->flags & CVAR_ARCHIVE ) {
Com_Printf( "A" );
} else {
Com_Printf( " " );
}
if ( var->flags & CVAR_LATCH ) {
Com_Printf( "L" );
} else {
Com_Printf( " " );
}
if ( var->flags & CVAR_CHEAT ) {
Com_Printf( "C" );
} else {
Com_Printf( " " );
}
Com_Printf( " %s \"%s\"\n", var->name, var->string );
}
Com_Printf( "\n%i total cvars\n", i );
Com_Printf( "%i cvar indexes\n", cvar_numIndexes );
}
/*
============
Cvar_Restart_f
Resets all cvars to their hardcoded values
============
*/
void Cvar_Restart_f( void ) {
cvar_t *var;
cvar_t **prev;
prev = &cvar_vars;
while ( 1 ) {
var = *prev;
if ( !var ) {
break;
}
// don't mess with rom values, or some inter-module
// communication will get broken (com_cl_running, etc)
if ( var->flags & ( CVAR_ROM | CVAR_INIT | CVAR_NORESTART ) ) {
prev = &var->next;
continue;
}
// throw out any variables the user created
if ( var->flags & CVAR_USER_CREATED ) {
*prev = var->next;
if ( var->name ) {
Z_Free( var->name );
}
if ( var->string ) {
Z_Free( var->string );
}
if ( var->latchedString ) {
Z_Free( var->latchedString );
}
if ( var->resetString ) {
Z_Free( var->resetString );
}
// clear the var completely, since we
// can't remove the index from the list
memset( var, 0, sizeof( var ) );
continue;
}
Cvar_Set( var->name, var->resetString );
prev = &var->next;
}
}
/*
=====================
Cvar_InfoString
=====================
*/
char *Cvar_InfoString( int bit ) {
static char info[MAX_INFO_STRING];
cvar_t *var;
info[0] = 0;
for ( var = cvar_vars ; var ; var = var->next ) {
if ( var->flags & bit ) {
Info_SetValueForKey( info, var->name, var->string );
}
}
return info;
}
/*
=====================
Cvar_InfoString_Big
handles large info strings ( CS_SYSTEMINFO )
=====================
*/
char *Cvar_InfoString_Big( int bit ) {
static char info[BIG_INFO_STRING];
cvar_t *var;
info[0] = 0;
for ( var = cvar_vars ; var ; var = var->next ) {
if ( var->flags & bit ) {
Info_SetValueForKey_Big( info, var->name, var->string );
}
}
return info;
}
/*
=====================
Cvar_InfoStringBuffer
=====================
*/
void Cvar_InfoStringBuffer( int bit, char* buff, int buffsize ) {
Q_strncpyz( buff,Cvar_InfoString( bit ),buffsize );
}
/*
=====================
Cvar_Register
basically a slightly modified Cvar_Get for the interpreted modules
=====================
*/
void Cvar_Register( vmCvar_t *vmCvar, const char *varName, const char *defaultValue, int flags ) {
cvar_t *cv;
cv = Cvar_Get( varName, defaultValue, flags );
if ( !vmCvar ) {
return;
}
vmCvar->handle = cv - cvar_indexes;
vmCvar->modificationCount = -1;
Cvar_Update( vmCvar );
}
/*
=====================
Cvar_Update
updates an interpreted modules' version of a cvar
=====================
*/
void Cvar_Update( vmCvar_t *vmCvar ) {
cvar_t *cv = NULL; // bk001129
assert( vmCvar ); // bk
if ( (unsigned)vmCvar->handle >= cvar_numIndexes ) {
Com_Error( ERR_DROP, "Cvar_Update: handle out of range" );
}
cv = cvar_indexes + vmCvar->handle;
if ( cv->modificationCount == vmCvar->modificationCount ) {
return;
}
if ( !cv->string ) {
return; // variable might have been cleared by a cvar_restart
}
vmCvar->modificationCount = cv->modificationCount;
// bk001129 - mismatches.
if ( strlen( cv->string ) + 1 > MAX_CVAR_VALUE_STRING ) {
Com_Error( ERR_DROP, "Cvar_Update: src %s length %d exceeds MAX_CVAR_VALUE_STRING",
cv->string,
strlen( cv->string ),
sizeof( vmCvar->string ) );
}
// bk001212 - Q_strncpyz guarantees zero padding and dest[MAX_CVAR_VALUE_STRING-1]==0
// bk001129 - paranoia. Never trust the destination string.
// bk001129 - beware, sizeof(char*) is always 4 (for cv->string).
// sizeof(vmCvar->string) always MAX_CVAR_VALUE_STRING
//Q_strncpyz( vmCvar->string, cv->string, sizeof( vmCvar->string ) ); // id
Q_strncpyz( vmCvar->string, cv->string, MAX_CVAR_VALUE_STRING );
vmCvar->value = cv->value;
vmCvar->integer = cv->integer;
}
/*
============
Cvar_Init
Reads in all archived cvars
============
*/
void Cvar_Init( void ) {
cvar_cheats = Cvar_Get( "sv_cheats", "1", CVAR_ROM | CVAR_SYSTEMINFO );
Cmd_AddCommand( "toggle", Cvar_Toggle_f );
Cmd_AddCommand( "set", Cvar_Set_f );
Cmd_AddCommand( "sets", Cvar_SetS_f );
Cmd_AddCommand( "setu", Cvar_SetU_f );
Cmd_AddCommand( "seta", Cvar_SetA_f );
Cmd_AddCommand( "reset", Cvar_Reset_f );
Cmd_AddCommand( "cvarlist", Cvar_List_f );
Cmd_AddCommand( "cvar_restart", Cvar_Restart_f );
// NERVE - SMF - can't rely on autoexec to do this
Cvar_Get( "devdll", "1", CVAR_ROM );
}