/* Copyright (C) 1996-1997 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 "quakedef.h" #include "shader.h" cvar_group_t *cvar_groups; //static bucket_t *cvar_buckets[1024]; //static hashtable_t cvar_hash; typedef struct { size_t maxentries; size_t numentries; struct { const char *string; void *data; } entry[1]; } abucket_t; typedef struct { abucket_t **bucket; unsigned int numbuckets; } ahashtable_t; //not thread-safe static abucket_t *cvar_buckets[1024]; static ahashtable_t cvar_hash = {cvar_buckets, countof(cvar_buckets)}; unsigned int Hash_KeyInsensitive(const char *name, unsigned int modulus); void *AHash_GetInsensitive(ahashtable_t *table, const char *name) { abucket_t *b = table->bucket[Hash_KeyInsensitive(name, table->numbuckets)]; size_t i; if (b) for (i = 0; i < b->numentries; i++) { if (!strcasecmp(b->entry[i].string, name)) return b->entry[i].data; } return NULL; } void AHash_RemoveDataInsensitive(ahashtable_t *table, const char *name, void *data) { abucket_t *b = table->bucket[Hash_KeyInsensitive(name, table->numbuckets)]; size_t i; for (i = 0; i < b->numentries; i++) { if (b->entry[i].data == data && !strcasecmp(b->entry[i].string, name)) { //strip it. b->numentries--; //shift everything down. if (b->numentries > i) memmove(&b->entry[i], &b->entry[i+1], sizeof(*b->entry)*(b->numentries-i)); break; } } } void AHash_AddInsensitive(ahashtable_t *table, const char *name, void *data) { unsigned int idx = Hash_KeyInsensitive(name, table->numbuckets); abucket_t *b = table->bucket[idx]; if (!b) { //nothing there!... b = table->bucket[idx] = BZ_Malloc(sizeof(*b)); b->numentries = 0; b->maxentries = countof(b->entry); } else if (b->numentries == b->maxentries) { //can't add anything new size_t n = b->maxentries*2; table->bucket[idx] = BZ_Malloc(sizeof(*b)-sizeof(b->entry) + sizeof(b->entry)*n); memcpy(table->bucket[idx]->entry, b->entry, sizeof(b->entry[0])*b->numentries); table->bucket[idx]->numentries = b->numentries; table->bucket[idx]->maxentries = n; BZ_Free(b); b = table->bucket[idx]; } b->entry[b->numentries].data = data; b->entry[b->numentries].string = name; b->numentries++; } void AHash_Cleanup(ahashtable_t *table) { size_t i; for (i = 0; i < table->numbuckets; i++) { if (table->bucket[i] && !table->bucket[i]->numentries) { BZ_Free(table->bucket[i]); table->bucket[i] = NULL; } } } int cvar_watched; //cvar_t *cvar_vars; static char *cvar_null_string = ""; static char *cvar_zero_string = "0"; static char *cvar_one_string = "1"; static char *Cvar_DefaultAlloc(char *str) { char *c; if (str[0] == '\0') return cvar_null_string; if (str[0] == '0' && str[1] == '\0') return cvar_zero_string; if (str[0] == '1' && str[1] == '\0') return cvar_one_string; c = (char *)Z_Malloc(strlen(str)+1); Q_strcpy(c, str); return c; } void Cvar_DefaultFree(char *str) { if (str == cvar_null_string) return; else if (str == cvar_zero_string) return; else if (str == cvar_one_string) return; else Z_Free(str); } /* ============ Cvar_FindVar ============ */ cvar_t *Cvar_FindVar (const char *var_name) { return AHash_GetInsensitive(&cvar_hash, var_name); /* cvar_group_t *grp; cvar_t *var; for (grp=cvar_groups ; grp ; grp=grp->next) for (var=grp->cvars ; var ; var=var->next) { if (!Q_strcasecmp (var_name, var->name)) return var; if (var->name2 && !Q_strcasecmp (var_name, var->name2)) return var; } */ return NULL; } static cvar_group_t *Cvar_FindGroup (const char *group_name) { cvar_group_t *grp; for (grp=cvar_groups ; grp ; grp=grp->next) if (!Q_strcasecmp (group_name, grp->name)) return grp; return NULL; } static cvar_group_t *Cvar_GetGroup(const char *gname) { cvar_group_t *g; if (!gname) gname = "Miscilaneous vars"; g = Cvar_FindGroup(gname); if (g) return g; g = (cvar_group_t*)Z_Malloc(sizeof(cvar_group_t) + strlen(gname)+1); g->name = (char*)(g+1); strcpy((char*)g->name, gname); g->next = NULL; g->next = cvar_groups; cvar_groups = g; return g; } // converts a given single cvar flag into a human readable string static char *Cvar_FlagToName(int flag) { switch (flag) { case CVAR_ARCHIVE: return "archive"; case CVAR_USERINFO: return "userinfo"; case CVAR_SERVERINFO: return "serverinfo"; case CVAR_NOSET: return "noset"; case CVAR_MAPLATCH: return "latch"; case CVAR_POINTER: return "pointer"; case CVAR_NOTFROMSERVER: return "noserver"; case CVAR_USERCREATED: return "userset"; case CVAR_CHEAT: return "cheat"; case CVAR_SEMICHEAT: return "semicheat"; case CVAR_RENDERERLATCH: return "renderlatch"; case CVAR_VIDEOLATCH: return "videolatch"; case CVAR_SERVEROVERRIDE: return "serverlatch"; case CVAR_RENDERERCALLBACK: return "rendercallback"; case CVAR_NOUNSAFEEXPAND: return "nounsafeexpand"; case CVAR_RULESETLATCH: return "rulesetlatch"; case CVAR_SHADERSYSTEM: return "shadersystem"; case CVAR_NOSAVE: return "nosave"; case CVAR_TELLGAMECODE: return "autocvar"; case CVAR_CONFIGDEFAULT: return ""; case CVAR_NORESET: return "noreset"; } return NULL; } //lists commands, also prints restriction level #define CLF_RAW 0x1 #define CLF_LEVEL 0x2 #define CLF_ALTNAME 0x4 #define CLF_VALUES 0x8 #define CLF_DEFAULT 0x10 #define CLF_LATCHES 0x20 #define CLF_FLAGS 0x40 #define CLF_FLAGMASK 0x80 #define CLF_CHANGEDONLY 0x100 void Cvar_List_f (void) { cvar_group_t *grp; cvar_t *cmd; char *var, *search, *gsearch; int gnum, i, num = 0; int listflags = 0, cvarflags = 0; int total = 0; char strtmp[512]; char *col; static char *cvarlist_help = "cvarlist list all cvars matching given parameters\n" "Syntax: cvarlist [-FLdhlrv] [-f flag] [-g group] [cvar]\n" " -c includes only the cvars that have been changed from their defaults\n" " -F shows cvar flags\n" " -L shows latched values\n" " -a shows cvar alternate names\n" " -d shows default cvar values\n" " -f shows only cvars with a matching flag, more than one -f can be used\n" " -g shows only cvar groups using wildcards in group\n" " -h shows this help message\n" " -l shows cvar restriction levels\n" " -r removes group and list headers\n" " -v shows current values\n" " cvar indicates the cvar to show, wildcards (*,?) accepted\n" "Cvar flags are:" ; if (Cmd_Argc() == 1) goto showhelp; gsearch = search = NULL; for (i = 1; i < Cmd_Argc(); i++) { var = Cmd_Argv(i); if (*var == '-') { // short options for (var++; *var; var++) { switch (*var) { case 'g': // fix this so we can search for multiple groups i++; if (i >= Cmd_Argc()) { Con_Printf("Missing parameter for -g\nUse cvarlist -h for help\n"); return; } gsearch = Cmd_Argv(i); break; case 'c': listflags |= CLF_CHANGEDONLY; break; case 'a': listflags |= CLF_ALTNAME; break; case 'l': listflags |= CLF_LEVEL; break; case 'r': listflags |= CLF_RAW; break; case 'v': listflags |= CLF_VALUES; break; case 'd': listflags |= CLF_DEFAULT; break; case 'L': listflags |= CLF_LATCHES; break; case 'f': { char *tmpv; listflags |= CLF_FLAGMASK; i++; if (i >= Cmd_Argc()) { Con_Printf("Missing parameter for -f\nUse cvarlist -h for help\n"); return; } tmpv = Cmd_Argv(i); for (num = 1; num <= CVAR_LASTFLAG; num <<= 1) { char *tmp; tmp = Cvar_FlagToName(num); if (tmp && !stricmp(tmp, tmpv)) { cvarflags |= num; break; } } if (num > CVAR_LASTFLAG) { Con_Printf("Invalid cvar flag name\nUse cvarlist -h for help\n"); return; } } break; case 'F': listflags |= CLF_FLAGS; break; case 'h': showhelp: // list options Con_Printf("%s", cvarlist_help); for (num = 1; num <= CVAR_LASTFLAG; num <<= 1) { // no point caring about the content of var at this point var = Cvar_FlagToName(num); if (var) Con_Printf(" %s", var); } Con_Printf("\n\n"); return; case '-': break; default: Con_Printf("Invalid option for cvarlist\nUse cvarlist -h for help\n"); return; } } } else search = var; } // this is sane.. hopefully if (gsearch) Q_strlwr(gsearch); if (search) Q_strlwr(search); for (grp=cvar_groups ; grp ; grp=grp->next) { // list only cvars with group search substring if (gsearch) { Q_strncpyz(strtmp, grp->name, 512); Q_strlwr(strtmp); if (!wildcmp(gsearch, strtmp)) continue; } gnum = 0; for (cmd=grp->cvars ; cmd ; cmd=cmd->next) { // list only non-restricted cvars if ((cmd->restriction?cmd->restriction:rcon_level.ival) > Cmd_ExecLevel) continue; // list only cvars with search substring if (search) { Q_strncpyz(strtmp, cmd->name, 512); Q_strlwr(strtmp); if (!wildcmp(search, strtmp)) { if (cmd->name2) { Q_strncpyz(strtmp, cmd->name2, 512); Q_strlwr(strtmp); if (!wildcmp(search, strtmp)) continue; } else continue; } } // list only cvars with matching flags if ((listflags & CLF_FLAGMASK) && !(cmd->flags & cvarflags)) continue; if ((listflags & CLF_CHANGEDONLY) && ((cmd->flags & (CVAR_NOSET|CVAR_NOSAVE)) || (cmd->defaultstr && !strcmp(cmd->string, cmd->defaultstr)))) continue; // print cvar list header if (!(listflags & CLF_RAW) && !num) Con_TPrintf("^aCVar list:\n"); // print group header if (!(listflags & CLF_RAW) && !gnum) Con_Printf("^a%s --\n", grp->name); // print restriction level if (listflags & CLF_LEVEL) Con_Printf("(%i) ", cmd->restriction); // print cvar name if (!cmd->defaultstr || !strcmp(cmd->string, cmd->defaultstr)) col = S_COLOR_GREEN; //cvar has default value, woo. else if (cmd->flags & CVAR_ARCHIVE) col = S_COLOR_RED; //cvar will persist. oh noes. else col = S_COLOR_YELLOW; //cvar is changed, but won't be saved to a config so w/e. if (cmd->flags & CVAR_NOUNSAFEEXPAND) Con_Printf("^[%s%s\\type\\%s\\tip\\"S_COLOR_YELLOW"%s^]", col, cmd->name, cmd->name, cmd->description?cmd->description:""); //cvar is changed, but won't be saved to a config so w/e. else Con_Printf("^[%s%s\\type\\%s %s\\tip\\Default: %s\nCurrent: %s\n\n"S_COLOR_YELLOW"%s^]", col, cmd->name, cmd->name,cmd->string, cmd->defaultstr,cmd->string, cmd->description?cmd->description:""); //cvar is changed, but won't be saved to a config so w/e. total++; // print current value if (cmd->flags & CVAR_NOUNSAFEEXPAND) ; else { if (listflags & CLF_VALUES) { if (*cmd->string) Con_Printf(" %s", cmd->string); } // print default value if (cmd->defaultstr && (listflags & CLF_DEFAULT)) Con_Printf(", default \"%s\"", cmd->defaultstr); } // print alternate name if ((listflags & CLF_ALTNAME) && cmd->name2) Con_Printf(", alternate %s", cmd->name2); // print cvar flags if (listflags & CLF_FLAGS) { for (i = 1; i <= CVAR_LASTFLAG; i <<= 1) { if (i & cmd->flags) { var = Cvar_FlagToName(i); if (var) Con_Printf(" %s", var); } } } // print latched value if (listflags & CLF_LATCHES) { if (cmd->flags & CVAR_NOUNSAFEEXPAND) ; else if (cmd->latched_string) Con_Printf(", latched as \"%s\"", cmd->latched_string); } // print new line to finish individual cvar Con_Printf("\n"); num++; gnum++; } // print new line to seperate groups if (!(listflags & CLF_RAW) && gnum) Con_Printf("\n"); } if (search && !strcmp(search, "*")) Con_Printf("%i cvars\n", total); } //default values are meant to be constants. //sometimes that just doesn't make sense. //so provide a safe way to change it (MUST be initialised to NULL so that it doesn't get freed) void Cvar_SetEngineDefault(cvar_t *var, char *val) { qboolean wasdefault = (var->defaultstr == var->enginevalue); Z_Free(var->enginevalue); if (val) var->enginevalue = Z_StrDup(val); else var->enginevalue = NULL; if (wasdefault) var->defaultstr = var->enginevalue; } void Cvar_LockDefaults_f(void) { cvar_group_t *grp; cvar_t *cmd; for (grp=cvar_groups ; grp ; grp=grp->next) { for (cmd=grp->cvars ; cmd ; cmd=cmd->next) { if (cmd->flags & (CVAR_NOSET | CVAR_CHEAT)) continue; if (strcmp(cmd->string, cmd->defaultstr)) { if (cmd->defaultstr != cmd->enginevalue) Cvar_DefaultFree(cmd->defaultstr); cmd->defaultstr = Cvar_DefaultAlloc(cmd->string); } } } } void Cvar_PurgeDefaults_f(void) { cvar_group_t *grp; cvar_t *cmd; for (grp=cvar_groups ; grp ; grp=grp->next) { for (cmd=grp->cvars ; cmd ; cmd=cmd->next) { if (!cmd->enginevalue) continue; //can't reset the cvar's default if its an engine cvar. if (cmd->flags & CVAR_NOSET) continue; if (cmd->defaultstr != cmd->enginevalue) { Cvar_DefaultFree(cmd->defaultstr); cmd->defaultstr = cmd->enginevalue; } } } } //we're changing games. reset everything to engine defaults and kill all user cvars static void Cvar_Free(cvar_t *tbf); void Cvar_GamedirChange(void) { cvar_t *var, **link; cvar_group_t *grp; for (grp = cvar_groups; grp; grp = grp->next) { for (link = &grp->cvars; (var=*link); ) { if (var->flags & CVAR_NORESET) ; //don't reset it (nor kill it). else if (var->enginevalue) Cvar_ForceSet(var, var->enginevalue); else if (var->flags & CVAR_POINTER) { *link = var->next; Z_Free(var->string); if (var->defaultstr != var->enginevalue) Cvar_DefaultFree(var->defaultstr); if (var->latched_string) Z_Free(var->latched_string); if (var->name) AHash_RemoveDataInsensitive(&cvar_hash, var->name, var); if (var->name2) AHash_RemoveDataInsensitive(&cvar_hash, var->name2, var); Z_Free(var); continue; } link = &(*link)->next; } } } void Cvar_ResetAll_f(void) { cvar_group_t *grp; cvar_t *var; unsigned int bitmask; //the bits to care about unsigned int bitmaskvalue; //must match this value char *cmd = Cmd_Argv(0); if (!Q_strcasecmp(cmd, "cvar_resettodefaults_saveonly")) bitmask = CVAR_NORESET|CVAR_NOSET|CVAR_SAVE, bitmaskvalue = CVAR_SAVE; else if (!Q_strcasecmp(cmd, "cvar_resettodefaults_nosaveonly")) bitmask = CVAR_NORESET|CVAR_NOSET|CVAR_SAVE, bitmaskvalue = 0; else //others... bitmask = CVAR_NORESET|CVAR_NOSET, bitmaskvalue = 0; for (grp=cvar_groups ; grp ; grp=grp->next) { for (var=grp->cvars ; var ; var=var->next) { if (!var->enginevalue) continue; //can't reset the cvar's default if its an engine cvar. if ((var->flags & bitmask) != bitmaskvalue) continue; if (var->defaultstr != var->enginevalue) { Cvar_DefaultFree(var->defaultstr); var->defaultstr = var->enginevalue; } } } } #define CRF_ALTNAME 0x1 void Cvar_Reset_f (void) { cvar_group_t *grp; cvar_t *cmd; int i, listflags=0, exclflags; char *var; char *search, *gsearch; char strtmp[512]; char *resetval; char *pendingval; search = gsearch = NULL; exclflags = 0; // parse command line options for (i = 1; i < Cmd_Argc(); i++) { var = Cmd_Argv(i); if (*var == '-') { // short options for (var++; *var; var++) { switch (*var) { case 'a': listflags |= CRF_ALTNAME; break; case 'g': // fix this so we can search for multiple groups i++; if (i >= Cmd_Argc()) { Con_Printf("Missing parameter for -g\nUse cvarlist -h for help\n"); return; } gsearch = Cmd_Argv(i); break; case 'u': exclflags |= CVAR_USERCREATED; case 'h': Con_Printf("cvarreset resets all cvars to default values matching given parameters\n" "Syntax: cvarreset [-ahu] (-g group)/cvar\n" " -a matches cvar against alternate cvar names\n" " -g matches using wildcards in group\n" " -h shows this help message\n" " -u excludes user cvars\n" " cvar indicates the cvars to reset, wildcards (*, ?) accepted\n" "A -g or cvar is required\n"); return; default: Con_Printf("Invalid option for cvarreset\nUse cvarreset -h for help\n"); return; } } } else search = var; } if (!search && !gsearch) { Con_Printf("No group or cvars given\nUse cvarreset -h for help\n"); return; } // this should be sane.. hopefully if (search) Q_strlwr(search); if (gsearch) Q_strlwr(gsearch); if (search && !strcmp(search, "*")) search = NULL; if (gsearch && !strcmp(gsearch, "*")) gsearch = NULL; for (grp=cvar_groups ; grp ; grp=grp->next) { if (gsearch) { Q_strncpyz(strtmp, grp->name, 512); Q_strlwr(strtmp); if (!wildcmp(gsearch, strtmp)) continue; } for (cmd=grp->cvars ; cmd ; cmd=cmd->next) { // reset only non-restricted cvars if ((cmd->restriction?cmd->restriction:rcon_level.ival) > Cmd_ExecLevel) continue; // don't reset cvars with matched flags if (exclflags & cmd->flags) continue; // reset only cvars with search substring if (search) { Q_strncpyz(strtmp, cmd->name, 512); Q_strlwr(strtmp); if (!wildcmp(search, strtmp)) { if ((listflags & CRF_ALTNAME) && cmd->name2) { Q_strncpyz(strtmp, cmd->name2, 512); Q_strlwr(strtmp); if (!wildcmp(search, strtmp)) continue; } else continue; } } if ((cmd->flags & CVAR_NOSET) && !search) continue; if (cmd->flags & CVAR_NORESET) continue; // reset cvar to default only if its okay to do so if (cmd->defaultstr) resetval = cmd->defaultstr; else if (cmd->enginevalue) resetval = cmd->enginevalue; else continue; //no idea what to reset it to. pendingval = cmd->string; if (cmd->latched_string) pendingval = cmd->latched_string; if (strcmp(resetval, pendingval)) Cvar_Set(cmd, resetval); } } } /* ============ Cvar_VariableValue ============ */ float Cvar_VariableValue (const char *var_name) { cvar_t *var; var = Cvar_FindVar (var_name); if (!var) return 0; return Q_atof (var->string); } /* ============ Cvar_VariableString ============ */ char *Cvar_VariableString (const char *var_name) { cvar_t *var; var = Cvar_FindVar (var_name); if (!var) return cvar_null_string; return var->string; } void Cvar_SetNamed (const char *var_name, const char *newvalue) { cvar_t *var; var = Cvar_FindVar (var_name); if (!var) return; Cvar_Set(var, newvalue); } /* ============ Cvar_CompleteVariable ============ */ /* moved to cmd_compleatevariable char *Cvar_CompleteVariable (char *partial) { cvar_group_t *grp; cvar_t *cvar; int len; len = Q_strlen(partial); if (!len) return NULL; // check exact match for (grp=cvar_groups ; grp ; grp=grp->next) for (cvar=grp->cvars ; cvar ; cvar=cvar->next) if (!strcmp (partial,cvar->name)) return cvar->name; // check partial match for (grp=cvar_groups ; grp ; grp=grp->next) for (cvar=grp->cvars ; cvar ; cvar=cvar->next) if (!Q_strncmp (partial,cvar->name, len)) return cvar->name; return NULL; } */ static qboolean cvar_archivedvaluechanged; qboolean Cvar_UnsavedArchive(void) { return cvar_archivedvaluechanged; } void Cvar_ConfigChanged(void) { cvar_archivedvaluechanged = true; } void Cvar_Saved(void) { cvar_archivedvaluechanged = false; } /* ============ Cvar_Set ============ */ static cvar_t *Cvar_SetCore (cvar_t *var, const char *value, qboolean force) { //fixme: force should probably be a latch bitmask char *latch=NULL; COM_AssertMainThread("Cvar_SetCore"); if (!var) return NULL; if ((var->flags & CVAR_WATCHED) || cvar_watched == 2) Con_Printf("Cvar Set: %s to %s\n", var->name, value); if ((var->flags & CVAR_NOSET) && !force) { Con_Printf ("variable %s is readonly\n", var->name); return NULL; } if (force) ; else if (0)//var->flags & CVAR_SERVEROVERRIDE && !force) latch = "variable %s is under server control - latched\n"; else if (var->flags & CVAR_MAPLATCH && (sv_state || cls_state)) latch = "variable %s is latched and will be applied for the start of the next map\n"; // else if (var->flags & CVAR_LATCHFLUSH) // latch = "variable %s is latched (type flush)\n"; #ifdef HAVE_CLIENT else if (var->flags & CVAR_VIDEOLATCH && qrenderer != QR_NONE) latch = "variable %s will be changed after a vid_restart\n"; else if (var->flags & CVAR_RENDERERLATCH && qrenderer != QR_NONE) latch = "variable %s will be changed after a vid_reload\n"; #endif else if (var->flags & CVAR_RULESETLATCH) latch = "variable %s is latched due to current ruleset\n"; #ifdef HAVE_CLIENT else if (var->flags & CVAR_CHEAT && !cls.allow_cheats && cls.state) latch = "variable %s is a cheat variable - latched\n"; else if (var->flags & CVAR_SEMICHEAT && !cls.allow_semicheats && cls.state) latch = "variable %s is a cheat variable - latched\n"; #endif if ((var->flags & CVAR_WARNONCHANGE) && cl_warncmd.ival) { if (var->defaultstr && strcmp(var->defaultstr, value)) //warn if its different from the default if (!var->enginevalue || strcmp(var->enginevalue, value)) //unless it matches the ENGINE's default, in which case its probably still okay. Con_Printf (CON_WARNING"WARNING: %s has been set to \"%s\". This is NOT recommended!\n", var->name, value); } if (latch) { if (cl_warncmd.value) { //FIXME: flag that there's a latched cvar instead of spamming prints. //FIXME: apply pending rendererlatches vith a vid_reload when leaving the console/menu. if (var->latched_string) { //already latched if (strcmp(var->latched_string, value)) Con_Printf (latch, var->name); } else { //new latch if (strcmp(var->string, value)) Con_Printf (latch, var->name); } } if (var->latched_string && !strcmp(var->latched_string, value)) //no point, this would force the same return NULL; Cvar_ConfigChanged(); if (var->latched_string) Z_Free(var->latched_string); if (!strcmp(var->string, value)) //latch to the original value? remove the latch. { var->latched_string = NULL; return NULL; } var->latched_string = (char*)Z_Malloc(strlen(value)+1); strcpy(var->latched_string, value); return NULL; } #ifdef HAVE_SERVER if (var->flags & CVAR_SERVERINFO) { InfoBuf_SetKey (&svs.info, var->name, value); } #endif #ifdef HAVE_CLIENT if (var->flags & CVAR_SHADERSYSTEM) { if (var->string && value) if (strcmp(var->string, value)) Shader_NeedReload(false); } if (var->flags & CVAR_USERINFO) { char *old = InfoBuf_ValueForKey(&cls.userinfo[0], var->name); if (strcmp(old, value)) //only spam the server if it actually changed { //this helps with config execs InfoBuf_SetKey (&cls.userinfo[0], var->name, value); } } #endif latch = var->string;//save off the old value (so cvar_set(var, var->string) works) var->string = (char*)Z_Malloc (Q_strlen(value)+1); Q_strcpy (var->string, value); var->value = Q_atof (var->string); var->ival = Q_atoi (var->string); var->flags &= ~CVAR_TEAMPLAYTAINT; { char *str = COM_Parse(var->string); var->vec4[0] = atof(com_token); str = COM_Parse(str); var->vec4[1] = atof(com_token); str = COM_Parse(str); var->vec4[2] = atof(com_token); if (!str || !*str) var->vec4[3] = 1; else { str = COM_Parse(str); var->vec4[3] = atof(com_token); } } if (latch) { if (strcmp(latch, value)) { var->modified=true; //only modified if it changed. var->modifiedcount++; if (var->callback) var->callback(var, latch); if (var->flags & CVAR_TELLGAMECODE) { #ifdef HAVE_SERVER SVQ1_CvarChanged(var); #endif #ifdef HAVE_CLIENT #ifdef MENU_DAT MP_CvarChanged(var); #endif #ifdef CSQC_DAT CSQC_CvarChanged(var); #endif #endif } } if ((var->flags & CVAR_ARCHIVE) && !(var->flags & CVAR_SERVEROVERRIDE) && cl_warncmd.ival) { if (var->latched_string) { if (strcmp(var->latched_string, value)) Cvar_ConfigChanged(); } else { if (strcmp(latch, value)) Cvar_ConfigChanged(); } } Z_Free (latch); // free the old value string } if (var->latched_string) //we may as well have this here. { Z_Free(var->latched_string); var->latched_string = NULL; } return var; } qboolean Cvar_ApplyLatchFlag(cvar_t *var, char *value, unsigned int newflag, unsigned int ignoreflags) { qboolean result = true; char *latch; var->flags &= ~newflag; latch = var->latched_string; var->latched_string = NULL; if (!latch) { #ifdef warningmsg #pragma warningmsg("this means the callback will never be called") #endif latch = Z_StrDup(var->string); // var->string = NULL; } ignoreflags = var->flags & ignoreflags; //figure out which latching flags we're ignoring var->flags -= ignoreflags; //and clear them Cvar_ForceSet(var, value); var->flags |= ignoreflags; //give them back. if (var->latched_string) { //something else latched it Z_Free(var->latched_string); var->latched_string = NULL; result = false; } else var->flags |= newflag; if (latch) { if (!strcmp(var->string, latch)) Z_Free(latch); //don't allow a latch to be the same as the current value else var->latched_string = latch; } return result; } void Cvar_ForceCheatVars(qboolean semicheats, qboolean absolutecheats) { //this either unlatches if the cheat type is allowed, or enforces a default for full cheats and blank for semicheats. //this is clientside only. //if a value is enforced, it is latched to the old value. cvar_group_t *grp; cvar_t *var; char *latch; for (grp=cvar_groups ; grp ; grp=grp->next) for (var=grp->cvars ; var ; var=var->next) { if (!(var->flags & (CVAR_CHEAT|CVAR_SEMICHEAT))) continue; if (var->flags & CVAR_SERVEROVERRIDE) //server has control over it. don't force it away. continue; latch = var->latched_string; var->latched_string = NULL; if (!latch) latch = Z_StrDup(var->string); if (var->flags & CVAR_CHEAT) { if (!absolutecheats) { if (var->enginevalue) Cvar_ForceSet(var, var->enginevalue); else Cvar_ForceSet(var, var->defaultstr); } else Cvar_ForceSet(var, latch); } else if (var->flags & CVAR_SEMICHEAT) { if (var->flags & CVAR_RULESETLATCH) ; //this is too problematic. the ruleset should cover it. else if (!semicheats) Cvar_ForceSet(var, ""); else Cvar_ForceSet(var, latch); } if (latch) { if (!strcmp(var->string, latch)) Z_Free(latch); else var->latched_string = latch; } } } int Cvar_ApplyLatches(int latchflag, qboolean clearflag) { int result = 0; cvar_group_t *grp; cvar_t *var; int mask = ~0; int of; if (clearflag) //these flags are cleared from cvars. mask = ~latchflag; for (grp=cvar_groups ; grp ; grp=grp->next) for (var=grp->cvars ; var ; var=var->next) { if (var->flags & latchflag) { if (var->latched_string) { of = var->flags; var->flags &= ~latchflag; Cvar_Set(var, var->latched_string); var->flags = of; result++; } var->flags &= mask; } } return result; } cvar_t *Cvar_Set (cvar_t *var, const char *value) { return Cvar_SetCore(var, value, false); } cvar_t *Cvar_ForceSet (cvar_t *var, const char *value) { return Cvar_SetCore(var, value, true); } /* ============ Cvar_SetValue ============ */ void Cvar_SetValue (cvar_t *var, float value) { char val[32]; char *e, *p; // if (value == (int)value) // sprintf (val, "%i",(int)value); //make it look nicer. // else sprintf (val, "%f",value); p = strchr(val, '.'); if (p) { e = p + strlen(p); while (e > p && e[-1] == '0') e--; if (e[-1] == '.') e--; *e = 0; } Cvar_Set (var, val); } void Cvar_ForceSetValue (cvar_t *var, float value) { char val[32]; // if (value == (int)value) // sprintf (val, "%i",(int)value); //make it look nicer. // else sprintf (val, "%g",value); Cvar_ForceSet (var, val); } static void Cvar_Free(cvar_t *tbf) { cvar_t *var; cvar_group_t *grp; if (!(tbf->flags & CVAR_POINTER)) return; //only freeable if it was a pointer to begin with. for (grp=cvar_groups ; grp ; grp=grp->next) { if (grp->cvars == tbf) { grp->cvars = tbf->next; goto unlinked; } if (grp->cvars) for (var=grp->cvars ; var->next ; var=var->next) { if (var->next == tbf) { var->next = tbf->next; goto unlinked; } } } unlinked: Z_Free(tbf->string); if (tbf->defaultstr != tbf->enginevalue) Cvar_DefaultFree(tbf->defaultstr); if (tbf->latched_string) Z_Free(tbf->latched_string); AHash_RemoveDataInsensitive(&cvar_hash, tbf->name, tbf); if (tbf->name2) AHash_RemoveDataInsensitive(&cvar_hash, tbf->name2, tbf); Z_Free(tbf); } /* ============ Cvar_RegisterVariable Adds a freestanding variable to the variable list. ============ */ qboolean Cvar_Register (cvar_t *variable, const char *groupname) { cvar_t *old; cvar_group_t *group; char *initial; if (variable->defaultstr) initial = variable->defaultstr; else if (variable->enginevalue) initial = variable->enginevalue; else if (variable->string) initial = variable->string; else initial = ""; // check to see if it has already been defined old = Cvar_FindVar (variable->name); if (!old && variable->name2) old = Cvar_FindVar (variable->name2); if (old) { if ((old->flags & CVAR_POINTER) && !(variable->flags & CVAR_POINTER)) { group = Cvar_GetGroup(groupname); variable->modified = old->modified; variable->modifiedcount = old->modifiedcount; variable->flags |= (old->flags & CVAR_ARCHIVE); // link the variable in variable->next = group->cvars; variable->restriction = old->restriction; //exe registered vars group->cvars = variable; // make sure it can be zfreed variable->string = (char*)Z_Malloc (1); //cheat prevention - engine set default is the one that stays. if (initial != variable->enginevalue) variable->defaultstr = Cvar_DefaultAlloc(initial); else variable->defaultstr = initial; // set it through the function to be consistant if (old->latched_string) Cvar_SetCore (variable, old->latched_string, true); else Cvar_SetCore (variable, old->string, true); Cvar_Free(old); AHash_AddInsensitive(&cvar_hash, variable->name, variable); if (variable->name2) AHash_AddInsensitive(&cvar_hash, variable->name2, variable); return true; } Con_Printf ("Can't register variable %s, already defined\n", variable->name); return false; } // check for overlap with a command if (Cmd_Exists (variable->name)) { Con_Printf ("Cvar_RegisterVariable: %s is a command\n", variable->name); return false; } if (variable->name2 && (Cmd_Exists (variable->name2) || Cvar_FindVar (variable->name2))) { Con_Printf ("Cvar_RegisterVariable: %s is a command/exists\n", variable->name2); variable->name2 = NULL; } group = Cvar_GetGroup(groupname); // link the variable in variable->next = group->cvars; variable->restriction = 0; //exe registered vars group->cvars = variable; AHash_AddInsensitive(&cvar_hash, variable->name, variable); if (variable->name2) AHash_AddInsensitive(&cvar_hash, variable->name2, variable); variable->string = NULL; if (initial != variable->enginevalue) variable->defaultstr = Cvar_DefaultAlloc(initial); else variable->defaultstr = initial; // set it through the function to be consistant Cvar_SetCore (variable, initial, true); return true; } cvar_t *Cvar_Get2(const char *name, const char *defaultvalue, int flags, const char *description, const char *group) { cvar_t *var; var = Cvar_FindVar(name); if (var) { #ifdef HAVE_CLIENT if ((flags & CVAR_USERINFO) && !(var->flags & CVAR_USERINFO)) { var->flags |= CVAR_USERINFO; InfoBuf_SetKey(&cls.userinfo[0], var->name, var->string); } #endif #ifdef HAVE_SERVER if ((flags & CVAR_SERVERINFO) && !(var->flags & CVAR_SERVERINFO)) { var->flags |= CVAR_SERVERINFO; InfoBuf_SetKey (&svs.info, var->name, var->string); } #endif return var; } if (!description || !*description) description = NULL; //don't allow cvars with certain funny chars in their name. ever. such things get really messy when saved in configs or whatever. if (!*name || strchr(name, '\"') || strchr(name, '^') || strchr(name, '$') || strchr(name, ' ') || strchr(name, '\t') || strchr(name, '\r') || strchr(name, '\n') || strchr(name, ';')) return NULL; var = (cvar_t*)Z_Malloc(sizeof(cvar_t)+strlen(name)+1+(description?(strlen(description)+1):0)); var->name = (char *)(var+1); strcpy(var->name, name); var->string = (char*)defaultvalue; var->flags = flags|CVAR_POINTER|CVAR_USERCREATED; var->modifiedcount = 1; //this counter always starts at 1, for q3 compat if (description) { char *desc = var->name+strlen(var->name)+1; strcpy(desc, description); var->description = desc; } if (!Cvar_Register(var, group)) { Z_Free(var); return NULL; } return var; } //prevent the client from altering the cvar until they change map or the server resets the var to the default. void Cvar_LockFromServer(cvar_t *var, const char *str) { char *oldlatch; if (!(var->flags & CVAR_SERVEROVERRIDE)) { Con_DPrintf("Server taking control of cvar %s (%s)\n", var->name, str); var->flags |= CVAR_SERVEROVERRIDE; } oldlatch = var->latched_string; if (oldlatch) //maintaining control var->latched_string = NULL; else //taking control { oldlatch = (char*)Z_Malloc(strlen(var->string)+1); strcpy(oldlatch, var->string); } Cvar_SetCore (var, str, true); //will use all, quote included var->latched_string = oldlatch; //keep track of the original value. } /* ============ Cvar_Command Handles variable inspection and changing from the console ============ */ qboolean Cvar_Command (cvar_t *v, int level) { char *str; char buffer[65536]; int olev; // check variables if (!v) return false; if (level==RESTRICT_TEAMPLAY || (v->restriction?v->restriction:rcon_level.ival) > level) { Con_TPrintf ("You do not have the priveledges for %s\n", v->name); return true; } if (v->flags & CVAR_NOTFROMSERVER && Cmd_IsInsecure()) { Con_TPrintf ("Server tried setting %s cvar\n", v->name); return true; } // perform a variable print or set if (Cmd_Argc() == 1) { if (v->latched_string) { if (v->flags & CVAR_MAPLATCH) { Con_TPrintf ("\"%s\" is currently \"%s\"\n", v->name, COM_QuotedString(v->string, buffer, sizeof(buffer), true)); Con_TPrintf ("Will be changed to \"%s\" on the next map\n", COM_QuotedString(v->latched_string, buffer, sizeof(buffer), true)); } else if (v->flags & CVAR_VIDEOLATCH) { Con_TPrintf ("\"%s\" is \"%s\"\n", v->name, COM_QuotedString(v->string, buffer, sizeof(buffer), true)); Con_TPrintf ("Will be changed to \"%s\" on vid_restart\n", COM_QuotedString(v->latched_string, buffer, sizeof(buffer), true)); } else if (v->flags & CVAR_RENDERERLATCH) { Con_TPrintf ("\"%s\" is \"%s\"\n", v->name, COM_QuotedString(v->string, buffer, sizeof(buffer), true)); Con_TPrintf ("Will be changed to \"%s\" on vid_reload\n", COM_QuotedString(v->latched_string, buffer, sizeof(buffer), true)); } else { Con_TPrintf ("\"%s\" is \"%s\"\n", v->name, COM_QuotedString(v->latched_string, buffer, sizeof(buffer), true)); Con_TPrintf ("Effective value is \"%s\"\n", COM_QuotedString(v->string, buffer, sizeof(buffer), true)); } if (v->defaultstr) Con_TPrintf("Default: \"%s\"\n", COM_QuotedString(v->defaultstr, buffer, sizeof(buffer), true)); } else { if (v->defaultstr && !strcmp(v->string, v->defaultstr)) Con_TPrintf ("\"%s\" is \"%s\" (default)\n", v->name, COM_QuotedString(v->string, buffer, sizeof(buffer), true)); else { Con_TPrintf ("\"%s\" is \"%s\"\n", v->name, COM_QuotedString(v->string, buffer, sizeof(buffer), true)); if (v->defaultstr) Con_TPrintf("Default: \"%s\"\n", COM_QuotedString(v->defaultstr, buffer, sizeof(buffer), true)); } } return true; } if (Cmd_Argc() == 2) str = Cmd_Argv(1); else str = Cmd_Args(); if (v->flags & CVAR_NOSET) { if (cl_warncmd.value || developer.value) Con_TPrintf ("Cvar %s may not be set via the console\n", v->name); return true; } olev = Cmd_ExecLevel; Cmd_ExecLevel = level; #ifdef HAVE_CLIENT if (v->flags & CVAR_USERINFO) { int seat = CL_TargettedSplit(true); if (Cmd_FromGamecode() && cls.protocol == CP_QUAKEWORLD) { //don't bother even changing the cvar locally, just update the server's version. //fixme: quake2/quake3 latching. CL_SendSeatClientCommand(true, seat, "setinfo %s %s", v->name, COM_QuotedString(str, buffer, sizeof(buffer), false)); } else CL_SetInfo(seat, v->name, str); Cmd_ExecLevel = olev; return true; } if (v->flags & CVAR_SERVEROVERRIDE) { if (Cmd_FromGamecode()) { if (!v->defaultstr || !strcmp(v->defaultstr, str)) //returning to default { v->flags &= ~CVAR_SERVEROVERRIDE; if (v->latched_string) str = v->latched_string; //set to the latched } else { Cvar_LockFromServer(v, str); Cmd_ExecLevel = olev; return true; } } //let cvar_set latch if needed. } else if (Cmd_FromGamecode()) {//it's not latched yet if (v->defaultstr && strcmp(v->defaultstr, str)) { //lock the cvar, unless it's going to it's default value. Cvar_LockFromServer(v, str); Cmd_ExecLevel = olev; return true; } } #endif Cmd_ExecLevel = 0;//just to try to detect any bugs that could happen from this Cvar_Set (v, str); //will use all, quote included Cmd_ExecLevel = olev; return true; } static char *Cvar_AddDescription(char *buffer, size_t sizeofbuffer, const char *line, const char *cvardesc) { if (!cvardesc || !*cvardesc || strlen(line)+100 > sizeofbuffer) Q_snprintfz(buffer, sizeofbuffer, "%s\n", line); else { //fixme: de-funstring. char *out = buffer, *outend = out+sizeofbuffer-2; // 2 for \n\0 int i,pad; Q_snprintfz(out, outend-out, "%s", line); out += strlen(out); pad = (strlen(line)+8) & ~7; if (pad < 24) pad = 24; for(i = out-buffer; i < pad; i++) *out++ = ' '; *out++ = '/'; *out++ = '/'; for (;;) { if (!*cvardesc || out == outend) { *out++ = '\n'; break; } if (*cvardesc == '\n') { cvardesc++; *out++ = '\n'; if (out + pad + 2 >= outend) break; for(i = 0; i < pad; i++) *out++ = ' '; *out++ = '/'; *out++ = '/'; } else *out++ = *cvardesc++; } *out = 0; } return buffer; } /* ============ Cvar_WriteVariables Writes lines containing "set variable value" for all variables with the archive flag set to true. ============ */ void Cvar_WriteVariables (vfsfile_t *f, qboolean all) { qboolean writtengroupheader; cvar_group_t *grp; cvar_t *var; char *val; char *s; char buffer[65536]; for (grp=cvar_groups ; grp ; grp=grp->next) { writtengroupheader = false; for (var = grp->cvars ; var ; var = var->next) if (var->flags & CVAR_ARCHIVE || (all && var != &cl_warncmd)) { //yeah, don't force-save readonly cvars. if (var->flags & (CVAR_NOSET|CVAR_NOSAVE)) continue; if (!writtengroupheader) { writtengroupheader = true; s = va("\n// %s\n", grp->name); VFS_WRITE(f, s, strlen(s)); } val = var->string; //latched vars should act differently. if (var->latched_string) val = var->latched_string; if (var->flags & CVAR_USERCREATED) { if (var->flags & CVAR_ARCHIVE) s = va("seta %s %s", var->name, COM_QuotedString(val, buffer, sizeof(buffer), false)); else s = va("set %s %s", var->name, COM_QuotedString(val, buffer, sizeof(buffer), false)); } else s = va("%s %s", var->name, COM_QuotedString(val, buffer, sizeof(buffer), false)); s = Cvar_AddDescription(buffer, sizeof(buffer), s, var->description); VFS_WRITE(f, s, strlen(s)); } } } void Cvar_Hook(cvar_t *cvar, void (QDECL *callback) (struct cvar_s *var, char *oldvalue)) { cvar->callback = callback; } void Cvar_Unhook(cvar_t *cvar) { cvar->callback = NULL; } void Cvar_ForceCallback(cvar_t *var) { if (var) if (var->callback) var->callback(var, var->string); } void Cvar_ApplyCallbacks(int callbackflag) { cvar_group_t *grp; cvar_t *var; for (grp=cvar_groups ; grp ; grp=grp->next) for (var=grp->cvars ; var ; var=var->next) { if (var->flags & callbackflag) { if (var->callback) var->callback(var, var->string); } } } // standard callbacks void QDECL Cvar_Limiter_ZeroToOne_Callback(struct cvar_s *var, char *oldvalue) { if (var->value > 1) { Cvar_ForceSet(var, "1"); return; } else if (var->value < 0) { Cvar_ForceSet(var, "0"); return; } } void Cvar_Init(void) { memset(cvar_buckets, 0, sizeof(cvar_buckets)); //Hash_InitTable(&cvar_hash, sizeof(cvar_buckets)/Hash_BytesForBuckets(1), cvar_buckets); } void Cvar_Shutdown(void) { cvar_t *var; cvar_group_t *grp; while(cvar_groups) { while(cvar_groups->cvars) { var = cvar_groups->cvars; cvar_groups->cvars = var->next; if (var->defaultstr != var->enginevalue) { Cvar_DefaultFree(var->defaultstr); var->defaultstr = NULL; } Z_Free(var->latched_string); Z_Free(var->string); AHash_RemoveDataInsensitive(&cvar_hash, var->name, var); if (var->name2) AHash_RemoveDataInsensitive(&cvar_hash, var->name2, var); if (var->flags & CVAR_POINTER) Z_Free(var); else { var->string = NULL; var->latched_string = NULL; } } grp = cvar_groups; cvar_groups = grp->next; Z_Free(grp); } AHash_Cleanup(&cvar_hash); }