/* 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" #include "redblack.h" cvar_t *cvar_vars; static struct rbtree *cvartree; //UGLY HACK for client locations #ifdef DEDICATED_ONLY static const char *CL_Get_Loc_There (void) { return ""; } static const char *CL_Get_Loc_Here (void) { return ""; } #else const char *CL_Get_Loc_There (void); const char *CL_Get_Loc_Here (void); #endif //Server metavars const char *SV_GetClientID (void); const char *SV_GetClientIP (void); const char *SV_GetClientName (void); /* ============ Cvar_InfoValidate ============ */ static qboolean Cvar_InfoValidate (const char *s) { if (strchr (s, '\\')) return false; if (strchr (s, '"')) return false; if (strchr (s, ';')) return false; return true; } /* ============ Cvar_FindVar ============ */ cvar_t *Cvar_FindVar (const char *var_name) { cvar_t *var; const void **data; //not inited yet if (!cvartree) return NULL; data = rbfind (var_name, cvartree); if (data) { var = *(cvar_t **)data; return var; } /*for (var=cvar_vars ; var ; var=var->next) if (!strcmp (var_name, var->name)) return var;*/ return NULL; } /* ============ Cvar_VariableValue ============ */ float Cvar_VariableValue (const char *var_name) { const cvar_t *var; Q_assert (var_name != NULL); var = Cvar_FindVar (var_name); if (!var) return 0; return var->value; } int Cvar_IntValue (const char *var_name) { const cvar_t *var; Q_assert (var_name != NULL); var = Cvar_FindVar (var_name); if (!var) return 0; return var->intvalue; } static const char *Cvar_GetMetaVar (const char *var_name) { static char dateBuff[32]; if (var_name[0] == '$') { /*const char *varstring; varstring = Cvar_VariableString (var_name + 1); if (varstring[0]) return varstring;*/ if (!strcmp (var_name, "$timestamp")) { time_t now; time (&now); strftime(dateBuff, sizeof(dateBuff)-1, "%Y-%m-%d_%H%M", localtime((const time_t *)&now)); return dateBuff; } else if (!strcmp (var_name, "$date")) { time_t now; time (&now); strftime(dateBuff, sizeof(dateBuff)-1, "%Y-%m-%d", localtime((const time_t *)&now)); return dateBuff; } else if (!strcmp (var_name, "$time")) { time_t now; time (&now); strftime(dateBuff, sizeof(dateBuff)-1, "%H%M", localtime((const time_t *)&now)); return dateBuff; } else if (!strcmp (var_name, "$random")) { return va ("%u", randomMT()); } else if (!strcmp (var_name, "$inc")) { static unsigned long incNum = 0; return va ("%lu", ++incNum); } else if (!strcmp (var_name, "$loc_here")) { //aiee return CL_Get_Loc_Here (); } else if (!strcmp (var_name, "$loc_there")) { //aiee return CL_Get_Loc_There (); } else if (!strcmp (var_name, "$client.id")) { return SV_GetClientID (); } else if (!strcmp (var_name, "$client.ip")) { return SV_GetClientIP (); } else if (!strcmp (var_name, "$client.name")) { return SV_GetClientName (); } } return NULL; } /* ============ Cvar_VariableString ============ */ const char *Cvar_VariableString (const char *var_name) { const char *metavar; const cvar_t *var; Q_assert (var_name != NULL); metavar = Cvar_GetMetaVar (var_name); if (metavar) return metavar; var = Cvar_FindVar (var_name); if (var) return var->string; return ""; } /* ============ Cvar_CompleteVariable ============ */ const char *Cvar_CompleteVariable (const char *partial) { const cvar_t *cvar; int len; Q_assert (partial != NULL); len = (int)strlen(partial); if (!len) return NULL; // check exact match (note, don't use rbtree since we want some kind of order here) for (cvar=cvar_vars ; cvar ; cvar=cvar->next) if (!strcmp (partial,cvar->name)) return cvar->name; // check partial match for (cvar=cvar_vars ; cvar ; cvar=cvar->next) if (!strncmp (partial,cvar->name, len)) return cvar->name; return NULL; } static cvar_t *Cvar_Add (const char *var_name, const char *var_value, int flags) { cvar_t *var; const void **data; var = Z_TagMalloc (sizeof(cvar_t), TAGMALLOC_CVAR); var->name = CopyString (var_name, TAGMALLOC_CVAR); var->string = CopyString (var_value, TAGMALLOC_CVAR); var->modified = true; var->changed = NULL; var->latched_string = NULL; var->value = (float)atof (var->string); var->intvalue = (int)var->value; var->flags = flags; var->help = NULL; //r1: fix 0 case if (!var->intvalue && FLOAT_NE_ZERO(var->value)) var->intvalue = 1; // link the variable in var->next = cvar_vars; cvar_vars = var; //r1: insert to btree data = rbsearch (var->name, cvartree); *data = var; return var; } /* ============ Cvar_Get If the variable already exists, the value will not be set The flags will be or'ed in if the variable exists. ============ */ cvar_t * EXPORT Cvar_Get (const char *var_name, const char *var_value, int flags) { cvar_t *var; Q_assert (var_name != NULL); if (flags & (CVAR_USERINFO | CVAR_SERVERINFO)) { if (!Cvar_InfoValidate (var_name)) { Com_Printf("invalid info cvar name\n", LOG_GENERAL); return NULL; } } var = Cvar_FindVar (var_name); if (var) { var->flags |= flags; return var; } if (!var_value) return NULL; if (flags & (CVAR_USERINFO | CVAR_SERVERINFO)) { if (!Cvar_InfoValidate (var_value)) { Com_Printf("invalid info cvar value\n", LOG_GENERAL); return NULL; } } return Cvar_Add (var_name, var_value, flags); } /* ============ Cvar_GameGet ============ R1CH: Use as a wrapper to cvars requested by the mod. Can then apply filtering to them such as disallowing serverinfo set by mod. */ cvar_t * EXPORT Cvar_GameGet (const char *var_name, const char *var_value, int flags) { if (Cvar_IntValue("sv_no_game_serverinfo")) flags &= ~CVAR_SERVERINFO; return Cvar_Get (var_name, var_value, flags); } /* ============ Cvar_Set2 ============ */ static cvar_t *Cvar_Set2 (const char *var_name, const char *value, qboolean force) { cvar_t *var; char *old_string; Q_assert (var_name != NULL); Q_assert (value != NULL); if (var_name[0] == '$' && !force) { Com_Printf ("%s is write protected.\n", LOG_GENERAL, var_name); return NULL; } 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)) { Com_Printf("invalid info cvar value\n", LOG_GENERAL); return var; } } if (!force) { #ifdef _DEBUG if (var->flags & CVAR_NOSET && !Cvar_IntValue ("developer")) #else if (var->flags & CVAR_NOSET) #endif { Com_Printf ("%s is write protected.\n", LOG_GENERAL, var_name); return var; } if (var->flags & CVAR_LATCH) { if (var->latched_string) { if (strcmp(value, var->latched_string) == 0) return var; Z_Free (var->latched_string); } else { if (strcmp(value, var->string) == 0) return var; } if (Com_ServerState()) { Com_Printf ("%s will be changed for next map.\n", LOG_GENERAL, var_name); var->latched_string = CopyString(value, TAGMALLOC_CVAR); } else { //memleak fix, thanks Maniac- Z_Free (var->string); var->string = CopyString(value, TAGMALLOC_CVAR); var->value = (float)atof (var->string); var->intvalue = (int)var->value; //r1: fix 0 case if (!var->intvalue && FLOAT_NE_ZERO(var->value)) var->intvalue = 1; if (!strcmp(var->name, "game")) { FS_SetGamedir (var->string); #ifndef DEDICATED_ONLY if (!Cvar_IntValue ("dedicated")) FS_ExecConfig ("autoexec.cfg"); #endif } } return var; } } else { if (var->latched_string) { Z_Free (var->latched_string); var->latched_string = NULL; } } if (!strcmp(value, var->string)) return var; // not changed old_string = var->string; var->string = CopyString(value, TAGMALLOC_CVAR); var->value = (float)atof (var->string); var->intvalue = (int)var->value; //r1: fix 0 case if (!var->intvalue && FLOAT_NE_ZERO(var->value)) var->intvalue = 1; var->modified = true; if (var->changed) var->changed (var, old_string, var->string); if (var->flags & CVAR_USERINFO) userinfo_modified = true; // transmit at next oportunity Z_Free (old_string); // free the old value string return var; } /* ============ Cvar_ForceSet ============ */ cvar_t * EXPORT Cvar_ForceSet (const char *var_name, const char *value) { return Cvar_Set2 (var_name, value, true); } /* ============ Cvar_Set ============ */ cvar_t * EXPORT Cvar_Set (const char *var_name, const char *value) { return Cvar_Set2 (var_name, value, false); } /* ============ Cvar_FullSet ============ */ cvar_t *Cvar_FullSet (const char *var_name, const char *value, int flags) { cvar_t *var; var = Cvar_FindVar (var_name); if (!var) { // create it return Cvar_Get (var_name, value, flags); } var->modified = true; if (var->flags & CVAR_USERINFO) userinfo_modified = true; // transmit at next oportunity Z_Free (var->string); // free the old value string var->string = CopyString(value, TAGMALLOC_CVAR); var->value = (float)atof (var->string); var->intvalue = (int)var->value; //r1: fix 0 case if (!var->intvalue && FLOAT_NE_ZERO(var->value)) var->intvalue = 1; var->flags = flags; return var; } /* ============ Cvar_SetValue ============ */ void EXPORT 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), "%g", value); Cvar_Set (var_name, val); } /* ============ Cvar_GetLatchedVars Any variables with latched values will now be updated ============ */ void Cvar_GetLatchedVars (void) { cvar_t *var; char *old_string; for (var = cvar_vars ; var ; var = var->next) { if (!var->latched_string) continue; old_string = var->string; var->string = var->latched_string; var->latched_string = NULL; var->value = (float)atof(var->string); var->intvalue = (int)var->value; //r1: fix 0 case if (!var->intvalue && FLOAT_NE_ZERO(var->value)) var->intvalue = 1; if (var->changed) var->changed (var, old_string, var->string); Z_Free (old_string); if (!strcmp(var->name, "game")) { FS_SetGamedir (var->string); #ifndef DEDICATED_ONLY if (!Cvar_IntValue ("dedicated")) FS_ExecConfig ("autoexec.cfg"); #endif } } } //r1: check if any cvars are latched int Cvar_GetNumLatchedVars (void) { int latched = 0; const cvar_t *var; for (var = cvar_vars ; var ; var = var->next) { if (!var->latched_string) continue; latched++; } return latched; } /* ============ Cvar_Command Handles variable inspection and changing from the console ============ */ qboolean Cvar_Command (void) { const cvar_t *v; // check variables v = Cvar_FindVar (Cmd_Argv(0)); if (!v) return false; // perform a variable print or set if (Cmd_Argc() == 1) { Com_Printf ("\"%s\" is \"%s\"\n", LOG_GENERAL, v->name, v->string); return true; } Cvar_Set (v->name, Cmd_Argv(1)); return true; } /* ============ Cvar_Set_f Allows setting and defining of arbitrary cvars from console ============ */ static void Cvar_Set_f (void) { int c; int flags; c = Cmd_Argc(); //if (c != 3 && c != 4) if (c < 3) { Com_Printf ("usage: set [u / s] (line: set %s)\n", LOG_GENERAL, Cmd_Args()); return; } //r1: fixed so that 'set variable some thing' results in variable -> "some thing" //so that set command can be used in aliases to set things with spaces without //requring quotes. if (!strcmp(Cmd_Argv(c-1), "u")) flags = CVAR_USERINFO; else if (!strcmp(Cmd_Argv(3), "s")) flags = CVAR_SERVERINFO; else flags = 0; if (flags) { /*char string[2048]; int i; string[0] = 0; for (i=2 ; inext) { if (var->flags & CVAR_ARCHIVE) { Com_sprintf (buffer, sizeof(buffer), "set %s \"%s\"\n", var->name, var->string); fprintf (f, "%s", buffer); } } fclose (f); } } /* ============ Cvar_List_f ============ */ static int EXPORT cvarsort( const void *_a, const void *_b ) { const cvar_t *a = (const cvar_t *)_a; const cvar_t *b = (const cvar_t *)_b; return strcmp (a->name, b->name); } static void Cvar_List_f (void) { const cvar_t *var; int i, j; int len, num; cvar_t *sortedList; int argLen; argLen = (int)strlen(Cmd_Argv(1)); for (var = cvar_vars, i = 0; var ; var = var->next, i++); num = i; len = num * sizeof(cvar_t); sortedList = Z_TagMalloc (len, TAGMALLOC_CVAR); //sortedList = alloca(len); for (var = cvar_vars, i = 0; var ; var = var->next, i++) { sortedList[i] = *var; } qsort (sortedList, num, sizeof(sortedList[0]), (int (EXPORT *)(const void *, const void *))cvarsort); for (j = 0; j < num; j++) { var = &sortedList[j]; if (argLen && Q_strncasecmp (var->name, Cmd_Argv(1), argLen)) continue; if (!argLen) { if (var->flags & CVAR_ARCHIVE) Com_Printf ("*", LOG_GENERAL); else Com_Printf (" ", LOG_GENERAL); if (var->flags & CVAR_USERINFO) Com_Printf ("U", LOG_GENERAL); else Com_Printf (" ", LOG_GENERAL); if (var->flags & CVAR_SERVERINFO) Com_Printf ("S", LOG_GENERAL); else Com_Printf (" ", LOG_GENERAL); if (var->flags & CVAR_NOSET) Com_Printf ("-", LOG_GENERAL); else if (var->flags & CVAR_LATCH) Com_Printf ("L", LOG_GENERAL); else Com_Printf (" ", LOG_GENERAL); Com_Printf (" %s \"%s\"\n", LOG_GENERAL, var->name, var->string); } else { Com_Printf ("v %s\n", LOG_GENERAL, var->name); } } if (!argLen) Com_Printf ("%i cvars\n", LOG_GENERAL, i); Z_Free (sortedList); } qboolean userinfo_modified; static char *Cvar_BitInfo (int bit) { static char info[MAX_INFO_STRING]; const 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; } // returns an info string containing all the CVAR_USERINFO cvars char *Cvar_Userinfo (void) { return Cvar_BitInfo (CVAR_USERINFO); } // returns an info string containing all the CVAR_SERVERINFO cvars char *Cvar_Serverinfo (void) { return Cvar_BitInfo (CVAR_SERVERINFO); } void Cvar_Help_f (void) { cvar_t *var; if (Cmd_Argc() != 2) { Com_Printf ("Usage: cvarhelp cvarname\n", LOG_GENERAL); return; } var = Cvar_FindVar (Cmd_Argv(1)); if (!var) { Com_Printf ("Cvar %s not found.\n", LOG_GENERAL, Cmd_Argv(1)); return; } if (!var->help) { Com_Printf ("No help available for %s.\n", LOG_GENERAL, Cmd_Argv(1)); return; } Com_Printf ("%s: %s", LOG_GENERAL, var->name, var->help); } /* ============ Cvar_Init Reads in all archived cvars ============ */ void Cvar_Init (void) { cvartree = rbinit ((int (EXPORT *)(const void *, const void *))strcmp, 0); Cmd_AddCommand ("set", Cvar_Set_f); Cmd_AddCommand ("cvarlist", Cvar_List_f); Cmd_AddCommand ("cvarhelp", Cvar_Help_f); developer = Cvar_Get ("developer", "0", 0); }