/* Copyright (C) 1997-2001 Id Software, Inc. 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 2 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. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // cvar.c -- dynamic variable tracking #include "qcommon.h" #define CVAR_HASH_SIZE 32 cvar_t *cvar_vars; cvar_t *cvar_vars_hash[CVAR_HASH_SIZE]; static qboolean Cvar_CheatsAllowed () { // not connected if( Com_ClientState() < CA_CONNECTED ) return qtrue; // playing demo if( Com_DemoPlaying() ) return qtrue; // local server with sv_cheats 1 if( Com_ServerState() && Cvar_VariableValue("sv_cheats") ) return qtrue; return qfalse; } /* ============ Cvar_InfoValidate ============ */ static qboolean Cvar_InfoValidate (char *s, qboolean name) { if (strlen (s) >= (unsigned)(name ? MAX_INFO_KEY : MAX_INFO_VALUE)) return qfalse; if (strstr (s, "\\")) return qfalse; if (strstr (s, "\"")) return qfalse; if (strstr (s, ";")) return qfalse; if (name && strstr (s, "^")) return qfalse; return qtrue; } /* ============ Cvar_FindVar ============ */ static cvar_t *Cvar_FindVar (char *var_name) { unsigned hash; cvar_t *var; hash = Com_HashKey (var_name, CVAR_HASH_SIZE); for (var=cvar_vars_hash[hash] ; var ; var=var->next) if (!Q_stricmp(var_name, var->name)) return var; return NULL; } /* ============ Cvar_VariableValue Returns 0 if not defined or non numeric ============ */ float Cvar_VariableValue (char *var_name) { cvar_t *var; var = Cvar_FindVar (var_name); if (!var) return 0; return atof (var->string); } /* ============ Cvar_VariableString Returns an empty string if not defined ============ */ char *Cvar_VariableString (char *var_name) { cvar_t *var; var = Cvar_FindVar (var_name); if (!var) return ""; return var->string; } /* ============ Cvar_CompleteVariable Attempts to match a partial variable name for command line completion. Returns NULL if nothing fits ============ */ char *Cvar_CompleteVariable (char *partial) { cvar_t *cvar; size_t len; len = strlen(partial); if (!len) return NULL; // check exact match for (cvar=cvar_vars ; cvar ; cvar=cvar->next) if (!Q_stricmp(partial,cvar->name)) return cvar->name; // check partial match for (cvar=cvar_vars ; cvar ; cvar=cvar->next) if (!Q_strnicmp(partial,cvar->name, len)) return cvar->name; return NULL; } /* ============ Cvar_Get Creates the variable if it doesn't exist. If the variable already exists, the value will not be set The flags will be or'ed and default value overwritten in if the variable exists. ============ */ cvar_t *Cvar_Get (char *var_name, char *var_value, int flags) { unsigned hash; cvar_t *var; if (!var_name || !var_name[0]) return NULL; if (flags & (CVAR_USERINFO | CVAR_SERVERINFO)) { if (!Cvar_InfoValidate (var_name, qtrue)) { Com_Printf("invalid info cvar name\n"); return NULL; } } hash = Com_HashKey (var_name, CVAR_HASH_SIZE); for (var=cvar_vars_hash[hash] ; var ; var=var->hash_next) if (!Q_stricmp (var_name, var->name)) break; if (var) { if( var_value ) { Mem_ZoneFree(var->dvalue); // free the old default value string var->dvalue = CopyString(var_value); } if( flags & CVAR_READONLY ) { Mem_ZoneFree( var->string ); var->string = CopyString( var_value ); var->value = atof( var->string ); var->integer = Q_rint( var->value ); var->flags = flags; } if ( (flags &CVAR_USERINFO) && !(var->flags & CVAR_USERINFO) ) userinfo_modified = qtrue; // transmit at next oportunity var->flags |= flags; return var; } if (!var_value) return NULL; if (flags & (CVAR_USERINFO | CVAR_SERVERINFO)) { if (!Cvar_InfoValidate (var_value, qfalse)) { Com_Printf("invalid info cvar value\n"); return NULL; } } var = Mem_ZoneMalloc( (int)(sizeof(*var) + strlen(var_name) + 1) ); var->name = (char *)((qbyte *)var + sizeof(*var)); strcpy (var->name, var_name); var->dvalue = CopyString (var_value); var->string = CopyString (var_value); var->modified = qtrue; var->value = atof (var->string); var->integer = Q_rint( var->value ); // link the variable in var->hash_next = cvar_vars_hash[hash]; cvar_vars_hash[hash] = var; var->next = cvar_vars; cvar_vars = var; var->flags = flags; return var; } /* ============ Cvar_Set2 ============ */ cvar_t *Cvar_Set2 (char *var_name, char *value, qboolean force) { cvar_t *var; var = Cvar_FindVar (var_name); if (!var) { // create it return Cvar_Get (var_name, value, 0); } if (var->flags & (CVAR_USERINFO | CVAR_SERVERINFO)) { if (!Cvar_InfoValidate (value, qfalse)) { Com_Printf("invalid info cvar value\n"); return var; } } if (!force) { if (var->flags & (CVAR_NOSET|CVAR_READONLY)) { Com_Printf ("%s is write protected.\n", var_name); return var; } if ((var->flags & CVAR_CHEAT) && strcmp(value, var->dvalue)) { if (!Cvar_CheatsAllowed()) { Com_Printf ("%s is cheat protected.\n", var_name); return var; } } if (var->flags & (CVAR_LATCH|CVAR_LATCH_VIDEO)) { if (var->latched_string) { if (strcmp(value, var->latched_string) == 0) return var; Mem_ZoneFree (var->latched_string); } else { if (strcmp(value, var->string) == 0) return var; } if (Com_ServerState()) { Com_Printf ("%s will be changed upon restarting.\n", var->name); var->latched_string = CopyString(value); } else { if (var->flags & CVAR_LATCH_VIDEO) { Com_Printf ("%s will be changed upon restarting video.\n", var->name); var->latched_string = CopyString(value); } else { Mem_ZoneFree (var->string); // free the old value string var->string = CopyString(value); var->value = atof (var->string); var->integer = Q_rint( var->value ); var->modified = qtrue; if (!strcmp (var->name, "fs_game")) { FS_SetGamedir (var->string); Cbuf_AddText ("exec autoexec.cfg"); Cbuf_Execute (); } } } return var; } } else { if (var->latched_string) { Mem_ZoneFree (var->latched_string); var->latched_string = NULL; } } if (!strcmp(value, var->string)) return var; // not changed var->modified = qtrue; if (var->flags & CVAR_USERINFO) userinfo_modified = qtrue; // transmit at next oportunity Mem_ZoneFree (var->string); // free the old value string var->string = CopyString(value); var->value = atof (var->string); var->integer = Q_rint( var->value ); return var; } /* ============ Cvar_ForceSet Set the variable even if NOSET or LATCH ============ */ cvar_t *Cvar_ForceSet (char *var_name, char *value) { return Cvar_Set2 (var_name, value, qtrue); } /* ============ Cvar_Set Create the variable if it doesn't exist ============ */ cvar_t *Cvar_Set (char *var_name, char *value) { return Cvar_Set2 (var_name, value, qfalse); } /* ============ Cvar_FullSet ============ */ cvar_t *Cvar_FullSet (char *var_name, char *value, int flags, qboolean overwrite_flags) { cvar_t *var; var = Cvar_FindVar (var_name); if (!var) { // create it return Cvar_Get (var_name, value, flags); } else if( !overwrite_flags ) { if (var->flags & (CVAR_NOSET|CVAR_READONLY)) { Com_Printf ("%s is write protected.\n", var_name); return var; } } var->modified = qtrue; if (var->flags & CVAR_USERINFO) userinfo_modified = qtrue; // transmit at next oportunity Mem_ZoneFree (var->string); // free the old value string var->string = CopyString(value); var->value = atof (var->string); var->integer = Q_rint( var->value ); if( overwrite_flags == qtrue ) var->flags = flags; else var->flags |= flags; return var; } /* ============ Cvar_SetValue Expands value to a string and calls Cvar_Set ============ */ void Cvar_SetValue (char *var_name, float value) { char val[32]; if (value == Q_rint(value)) Q_snprintfz (val, sizeof(val), "%i", Q_rint(value)); else Q_snprintfz (val, sizeof(val), "%f", value); Cvar_Set (var_name, val); } /* ============ Cvar_GetLatchedVars Any variables with CVAR_LATCHED will now be updated ============ */ void Cvar_GetLatchedVars (int flags) { cvar_t *var; flags &= CVAR_LATCH|CVAR_LATCH_VIDEO; if (!flags) return; for (var = cvar_vars ; var ; var = var->next) { if (!(var->flags & flags)) continue; if (!var->latched_string) continue; Mem_ZoneFree (var->string); var->string = var->latched_string; var->latched_string = NULL; var->value = atof(var->string); var->integer = Q_rint( var->value ); if (!strcmp(var->name, "fs_game")) { FS_SetGamedir (var->string); Cbuf_AddText ("exec autoexec.cfg"); Cbuf_Execute (); } } } /* ============ Cvar_FixCheatVars All cheat variables with be reset to default unless cheats are allowed ============ */ void Cvar_FixCheatVars (void) { cvar_t *var; if (Cvar_CheatsAllowed()) return; for (var = cvar_vars ; var ; var = var->next) { if (!(var->flags & CVAR_CHEAT)) continue; Mem_ZoneFree (var->string); var->string = CopyString(var->dvalue); var->value = atof(var->string); var->integer = Q_rint( var->value ); } } /* ============ Cvar_Command Handles variable inspection and changing from the console // called by Cmd_ExecuteString when Cmd_Argv(0) doesn't match a known // command. Returns true if the command was a variable reference that // was handled. (print or change) ============ */ 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\" default: \"%s%s\"\n", v->name, v->string, S_COLOR_WHITE, v->dvalue, S_COLOR_WHITE); if (v->latched_string) Com_Printf ("latched: \"%s%s\"\n", v->latched_string, S_COLOR_WHITE); return qtrue; } Cvar_Set (v->name, Cmd_Argv(1)); return qtrue; } /* ============ Cvar_Set_f Allows setting and defining of arbitrary cvars from console ============ */ void Cvar_Set_f (void) { if (Cmd_Argc() != 3) { Com_Printf ("usage: set \n"); return; } Cvar_Set (Cmd_Argv(1), Cmd_Argv(2)); } void Cvar_Seta_f (void) { if (Cmd_Argc() != 3) { Com_Printf ("usage: seta \n"); return; } Cvar_FullSet (Cmd_Argv(1), Cmd_Argv(2), CVAR_ARCHIVE, qfalse); } void Cvar_Sets_f (void) { if (Cmd_Argc() != 3) { Com_Printf ("usage: sets \n"); return; } Cvar_FullSet (Cmd_Argv(1), Cmd_Argv(2), CVAR_SERVERINFO, qfalse); } void Cvar_Setu_f (void) { if (Cmd_Argc() != 3) { Com_Printf ("usage: setu \n"); return; } Cvar_FullSet (Cmd_Argv(1), Cmd_Argv(2), CVAR_USERINFO, qfalse); } /* ============= Cvar_Toggle_f ============= */ void Cvar_Toggle_f (void) { cvar_t *var; if (Cmd_Argc() != 2) { Com_Printf ("usage: toggle \n"); return; } var = Cvar_FindVar (Cmd_Argv(1)); if (!var) { Com_Printf ("no such variable: \"%s\"\n", Cmd_Argv(1)); return; } Cvar_Set (var->name, var->integer ? "0" : "1"); } /* ============ Cvar_WriteVariables Appends lines containing "set variable value" for all variables with the archive flag set to true. ============ */ void Cvar_WriteVariables (int file) { cvar_t *var; char buffer[MAX_PRINTMSG]; for (var = cvar_vars ; var ; var = var->next) { if (var->flags & CVAR_ARCHIVE) { if (var->flags & (CVAR_LATCH|CVAR_LATCH_VIDEO)) { if (var->latched_string) Q_snprintfz (buffer, sizeof(buffer), "seta %s \"%s\"\n", var->name, var->latched_string); else Q_snprintfz (buffer, sizeof(buffer), "seta %s \"%s\"\n", var->name, var->string); } else { Q_snprintfz (buffer, sizeof(buffer), "seta %s \"%s\"\n", var->name, var->string); } FS_Printf (file, "%s", buffer); } } } /* ============ Cvar_List_f ============ */ void Cvar_List_f (void) { cvar_t *var; int i; char *pattern; if( Cmd_Argc () == 1 ) pattern = NULL; // no wildcard else pattern = Cmd_Args (); Com_Printf( "\nConsole variables:\n" ); for (var = cvar_vars, i = 0 ; var ; var = var->next) { if (!pattern || Q_WildCmp( pattern, var->name ) ) { if (var->flags & CVAR_ARCHIVE) Com_Printf ("*"); else Com_Printf (" "); if (var->flags & CVAR_USERINFO) Com_Printf ("U"); else Com_Printf (" "); if (var->flags & CVAR_SERVERINFO) Com_Printf ("S"); else Com_Printf (" "); if (var->flags & (CVAR_NOSET|CVAR_READONLY) ) Com_Printf ("-"); else if (var->flags & (CVAR_LATCH|CVAR_LATCH_VIDEO)) Com_Printf ("L"); else Com_Printf (" "); i++; Com_Printf (" %s \"%s\"\n", var->name, var->string); } } Com_Printf( "%i variables\n", i ); } qboolean userinfo_modified; char *Cvar_BitInfo (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_Userinfo Returns an info string containing all the CVAR_USERINFO cvars ============ */ char *Cvar_Userinfo (void) { return Cvar_BitInfo (CVAR_USERINFO); } /* ============ Cvar_Serverinfo Returns an info string containing all the CVAR_SERVERINFO cvars ============ */ char *Cvar_Serverinfo (void) { return Cvar_BitInfo (CVAR_SERVERINFO); } /* CVar_CompleteCountPossible New function for tab-completion system Added by EvilTypeGuy Thanks to Fett erich@heintz.com */ int Cvar_CompleteCountPossible (char *partial) { cvar_t *v; size_t len; int h = 0; len = strlen(partial); if (!len) return 0; // Loop through the cvars and count all possible matches for (v = cvar_vars; v; v = v->next) if (!Q_strnicmp (partial, v->name, len)) h++; return h; } /* CVar_CompleteBuildList New function for tab-completion system Added by EvilTypeGuy Thanks to Fett erich@heintz.com Thanks to taniwha */ char **Cvar_CompleteBuildList (char *partial) { cvar_t *v; size_t len; int bpos = 0; int sizeofbuf = (Cvar_CompleteCountPossible (partial) + 1) * sizeof (char *); char **buf; len = strlen (partial); buf = (char **)Mem_TempMalloc (sizeofbuf + sizeof (char *)); // Loop through the alias list and print all matches for (v = cvar_vars; v; v = v->next) if (!Q_strnicmp(partial, v->name, len)) buf[bpos++] = v->name; buf[bpos] = NULL; return buf; } /* ============ Cvar_Init Reads in all archived cvars ============ */ void Cvar_Init (void) { Cmd_AddCommand ("set", Cvar_Set_f); Cmd_AddCommand ("seta", Cvar_Seta_f); Cmd_AddCommand ("setu", Cvar_Setu_f); Cmd_AddCommand ("sets", Cvar_Sets_f); Cmd_AddCommand ("toggle", Cvar_Toggle_f); Cmd_AddCommand ("cvarlist", Cvar_List_f); }