/* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. Copyright (C) 2000-2013 Darklegion Development Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous 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. Tremulous 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 Tremulous; if not, see =========================================================================== */ // cvar.c -- dynamic variable tracking #include "cvar.h" #include "cmd.h" #include "files.h" #include "q_shared.h" #include "qcommon.h" static cvar_t *cvar_vars = nullptr; cvar_t *cvar_cheats; int cvar_modifiedFlags = 0; #define MAX_CVARS 2048 static cvar_t cvar_indexes[MAX_CVARS]; static int cvar_numIndexes; #define FILE_HASH_SIZE 256 static cvar_t *hashTable[FILE_HASH_SIZE]; /* ================ return a hash value for the filename ================ */ static long generateHashValue(const char *fname) { long hash = 0; int i = 0; while (fname[i] != '\0') { char letter = tolower(fname[i]); hash += (long)(letter) * (i + 119); i++; } hash &= (FILE_HASH_SIZE - 1); return hash; } /* ============ Cvar_ValidateString ============ */ bool Cvar_ValidateString(const char *s) { if (!s) { return false; } 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) { long hash = generateHashValue(var_name); for (cvar_t *var = hashTable[hash]; var; var = var->hashNext) if (!Q_stricmp(var_name, var->name)) return var; return nullptr; } /* ============ Cvar_VariableValue ============ */ float Cvar_VariableValue(const char *var_name) { cvar_t *var = Cvar_FindVar(var_name); if (!var) return 0; return var->value; } /* ============ Cvar_VariableIntegerValue ============ */ int Cvar_VariableIntegerValue(const char *var_name) { cvar_t *var = Cvar_FindVar(var_name); if (!var) return 0; return var->integer; } /* ============ Cvar_VariableString ============ */ const char *Cvar_VariableString(const char *var_name) { cvar_t *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 = Cvar_FindVar(var_name); if (var) Q_strncpyz(buffer, var->string, bufsize); else *buffer = 0; } /* ============ Cvar_Flags ============ */ unsigned int Cvar_Flags(const char *var_name) { cvar_t *var; if (!(var = Cvar_FindVar(var_name))) return CVAR_NONEXISTENT; else if (var->modified) return var->flags | CVAR_MODIFIED; return var->flags; } /* ============ Cvar_CommandCompletion ============ */ void Cvar_CommandCompletion(void (*callback)(const char *s)) { for (cvar_t *cvar = cvar_vars; cvar; cvar = cvar->next) { if (cvar->name) callback(cvar->name); } } /* ============ Cvar_Validate ============ */ const char *Cvar_Validate(cvar_t *var, const char *value, bool warn) { static char s[MAX_CVAR_VALUE_STRING]; float valuef; bool changed = false; if (!var->validate) return value; if (!value) return nullptr; if (Q_isanumber(value)) { valuef = atof(value); if (var->integral) { if (!Q_isintegral(valuef)) { if (warn) Com_Printf("WARNING: cvar '%s' must be integral", var->name); valuef = (int)valuef; changed = true; } } } else { if (warn) Com_Printf("WARNING: cvar '%s' must be numeric", var->name); valuef = atof(var->resetString); changed = true; } if (valuef < var->min) { if (warn) { if (changed) Com_Printf(" and is"); else Com_Printf("WARNING: cvar '%s'", var->name); if (Q_isintegral(var->min)) Com_Printf(" out of range (min %d)", (int)var->min); else Com_Printf(" out of range (min %f)", var->min); } valuef = var->min; changed = true; } else if (valuef > var->max) { if (warn) { if (changed) Com_Printf(" and is"); else Com_Printf("WARNING: cvar '%s'", var->name); if (Q_isintegral(var->max)) Com_Printf(" out of range (max %d)", (int)var->max); else Com_Printf(" out of range (max %f)", var->max); } valuef = var->max; changed = true; } if (changed) { if (Q_isintegral(valuef)) { Com_sprintf(s, sizeof(s), "%d", (int)valuef); if (warn) Com_Printf(", setting to %d\n", (int)valuef); } else { Com_sprintf(s, sizeof(s), "%f", valuef); if (warn) Com_Printf(", setting to %f\n", valuef); } return s; } return value; } /* ============ 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) { if (!var_name || !var_value) { Com_Error(ERR_FATAL, "Cvar_Get: nullptr 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 cvar_t *var = Cvar_FindVar(var_name); if (var) { var_value = Cvar_Validate(var, var_value, false); // Make sure the game code cannot mark engine-added variables as gamecode vars if (var->flags & CVAR_VM_CREATED) { if (!(flags & CVAR_VM_CREATED)) var->flags &= ~CVAR_VM_CREATED; } else if (!(var->flags & CVAR_USER_CREATED)) { if (flags & CVAR_VM_CREATED) flags &= ~CVAR_VM_CREATED; } // 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) { var->flags &= ~CVAR_USER_CREATED; Z_Free(var->resetString); var->resetString = CopyString(var_value); if (flags & CVAR_ROM) { // this variable was set by the user, // so force it to value given by the engine. if (var->latchedString) Z_Free(var->latchedString); var->latchedString = CopyString(var_value); } } // Make sure servers cannot mark engine-added variables as SERVER_CREATED if (var->flags & CVAR_SERVER_CREATED) { if (!(flags & CVAR_SERVER_CREATED)) var->flags &= ~CVAR_SERVER_CREATED; } else { if (flags & CVAR_SERVER_CREATED) flags &= ~CVAR_SERVER_CREATED; } 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 = var->latchedString; var->latchedString = nullptr; // otherwise cvar_set2 would free it Cvar_Set2(var_name, s, true); Z_Free(s); } // ZOID--needs to be set so that cvars the game sets as // SERVERINFO get sent to clients cvar_modifiedFlags |= flags; if (flags & CVAR_ALTERNATE_SYSTEMINFO) { cvar_modifiedFlags |= CVAR_SYSTEMINFO; } return var; } // // allocate a new cvar // // find a free cvar int i; for (i = 0; i < MAX_CVARS; i++) if (!cvar_indexes[i].name) break; if (i >= MAX_CVARS) { if (!com_errorEntered) Com_Error(ERR_FATAL, "Error: Too many cvars, cannot create a new one!"); return nullptr; } var = &cvar_indexes[i]; if (i >= cvar_numIndexes) cvar_numIndexes = i + 1; var->name = CopyString(var_name); var->string = CopyString(var_value); var->modified = true; var->modificationCount = 1; var->value = atof(var->string); var->integer = atoi(var->string); var->resetString = CopyString(var_value); var->validate = false; var->description = nullptr; // link the variable in var->next = cvar_vars; if (cvar_vars) cvar_vars->prev = var; var->prev = nullptr; cvar_vars = var; var->flags = flags; // note what types of cvars have been modified (userinfo, archive, serverinfo, systeminfo) cvar_modifiedFlags |= var->flags; if (var->flags & CVAR_ALTERNATE_SYSTEMINFO) cvar_modifiedFlags |= CVAR_SYSTEMINFO; long hash = generateHashValue(var_name); var->hashIndex = hash; var->hashNext = hashTable[hash]; if (hashTable[hash]) hashTable[hash]->hashPrev = var; var->hashPrev = nullptr; hashTable[hash] = var; return var; } /* ============ Cvar_Print Prints the value, default, and latched string of the given variable ============ */ void Cvar_Print(cvar_t *v) { Com_Printf("\"%s\" is:\"%s" S_COLOR_WHITE "\"", v->name, v->string); if (!(v->flags & CVAR_ROM)) { if (!Q_stricmp(v->string, v->resetString)) Com_Printf(", the default"); else Com_Printf(" default:\"%s" S_COLOR_WHITE "\"", v->resetString); } Com_Printf("\n"); if (v->latchedString) Com_Printf("latched: \"%s\"\n", v->latchedString); if (v->description) Com_Printf("%s\n", v->description); } /* ============ Cvar_Set2 ============ */ cvar_t *Cvar_Set2(const char *var_name, const char *value, bool force) { if (!Cvar_ValidateString(var_name)) { Com_Printf("invalid cvar name string: %s\n", var_name); var_name = "BADNAME"; } #if 0 // FIXME if ( value && !Cvar_ValidateString( value ) ) { Com_Printf("invalid cvar value string: %s\n", value ); var_value = "BADVALUE"; } #endif cvar_t *var = Cvar_FindVar(var_name); if (!var) { if (!value) return nullptr; if (!force) return Cvar_Get(var_name, value, CVAR_USER_CREATED); return Cvar_Get(var_name, value, 0); } if (!value) value = var->resetString; value = Cvar_Validate(var, value, true); if ((var->flags & CVAR_LATCH) && var->latchedString) { if (!strcmp(value, var->string)) { Z_Free(var->latchedString); var->latchedString = nullptr; return var; } if (!strcmp(value, var->latchedString)) return var; } else if (!strcmp(value, var->string)) return var; // note what types of cvars have been modified (userinfo, archive, serverinfo, systeminfo) cvar_modifiedFlags |= var->flags; if (var->flags & CVAR_ALTERNATE_SYSTEMINFO) { cvar_modifiedFlags |= CVAR_SYSTEMINFO; } 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_LATCH) { if (var->latchedString) { if (strcmp(value, var->latchedString) == 0) return var; Z_Free(var->latchedString); var->latchedString = nullptr; } 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 = true; var->modificationCount++; return var; } if ((var->flags & CVAR_CHEAT) && !cvar_cheats->integer) { Com_Printf("%s is cheat protected.\n", var_name); return var; } } else { if (var->latchedString) { Z_Free(var->latchedString); var->latchedString = nullptr; } } if (!strcmp(value, var->string)) return var; // not changed var->modified = true; 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, true); } /* ============ Cvar_SetSafe ============ */ void Cvar_SetSafe(const char *var_name, const char *value) { unsigned flags = Cvar_Flags(var_name); if ((flags != CVAR_NONEXISTENT) && (flags & CVAR_PROTECTED)) { if (value) Com_Error(ERR_DROP, "Restricted source tried to set \"%s\" to \"%s\"", var_name, value); else Com_Error(ERR_DROP, "Restricted source tried to modify \"%s\"", var_name); return; } Cvar_Set(var_name, value); } /* ============ Cvar_SetLatched ============ */ void Cvar_SetLatched(const char *var_name, const char *value) { Cvar_Set2(var_name, value, false); } /* ============ 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_SetValueSafe ============ */ void Cvar_SetValueSafe(const char *var_name, float value) { char val[32]; if (Q_isintegral(value)) Com_sprintf(val, sizeof(val), "%i", (int)value); else Com_sprintf(val, sizeof(val), "%f", value); Cvar_SetSafe(var_name, val); } /* ============ Cvar_Reset ============ */ void Cvar_Reset(const char *var_name) { Cvar_Set2(var_name, nullptr, false); } /* ============ Cvar_ForceReset ============ */ void Cvar_ForceReset(const char *var_name) { Cvar_Set2(var_name, nullptr, true); } /* ============ Cvar_SetCheatState Any testing variables will be reset to the safe values ============ */ void Cvar_SetCheatState(void) { // set all default vars to the safe value for (cvar_t *var = cvar_vars; var; var = var->next) { if (var->flags & CVAR_CHEAT) { // the CVAR_LATCHED|CVAR_CHEAT vars might escape the reset here // because of a different var->latchedString if (var->latchedString) { Z_Free(var->latchedString); var->latchedString = nullptr; } if (strcmp(var->resetString, var->string)) Cvar_Set(var->name, var->resetString); } } } /* ============ Cvar_Command Handles variable inspection and changing from the console ============ */ bool Cvar_Command(void) { cvar_t *v = Cvar_FindVar(Cmd_Argv(0)); if (!v) { return false; } // perform a variable print or set if (Cmd_Argc() == 1) { Cvar_Print(v); return true; } // set the value if forcing isn't required Cvar_Set2(v->name, Cmd_Args(), false); return true; } /* ============ Cvar_Print_f Prints the contents of a cvar (preferred over Cvar_Command where cvar names and commands conflict) ============ */ void Cvar_Print_f(void) { if (Cmd_Argc() != 2) { Com_Printf("usage: print \n"); return; } const char *name = Cmd_Argv(1); cvar_t *cv = Cvar_FindVar(name); if (cv) Cvar_Print(cv); else Com_Printf("Cvar %s does not exist.\n", name); } /* ============ Cvar_Toggle_f Toggles a cvar for easy single key binding, optionally through a list of given values ============ */ void Cvar_Toggle_f(void) { int c = Cmd_Argc(); if (c < 2) { Com_Printf("usage: toggle [value1, value2, ...]\n"); return; } else if (c == 2) { Cvar_Set2(Cmd_Argv(1), va("%d", !Cvar_VariableValue(Cmd_Argv(1))), false); return; } else if (c == 3) { Com_Printf("toggle: nothing to toggle to\n"); return; } const char *curval = Cvar_VariableString(Cmd_Argv(1)); // don't bother checking the last arg for a match since the desired // behaviour is the same as no match (set to the first argument) for (int i = 2; i + 1 < c; i++) { if (strcmp(curval, Cmd_Argv(i)) == 0) { Cvar_Set2(Cmd_Argv(1), Cmd_Argv(i + 1), false); return; } } // fallback Cvar_Set2(Cmd_Argv(1), Cmd_Argv(2), false); } /* ============ 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 c = Cmd_Argc(); const char *cmd = Cmd_Argv(0); if (c < 2) { Com_Printf("usage: %s \n", cmd); return; } else if (c == 2) { Cvar_Print_f(); return; } cvar_t *v = Cvar_Set2(Cmd_Argv(1), Cmd_ArgsFrom(2), false); if (!v) { return; } switch (cmd[3]) { case 'a': if (!(v->flags & CVAR_ARCHIVE)) { v->flags |= CVAR_ARCHIVE; cvar_modifiedFlags |= CVAR_ARCHIVE; } break; case 'u': if (!(v->flags & CVAR_USERINFO)) { v->flags |= CVAR_USERINFO; cvar_modifiedFlags |= CVAR_USERINFO; } break; case 's': if (!(v->flags & CVAR_SERVERINFO)) { v->flags |= CVAR_SERVERINFO; cvar_modifiedFlags |= CVAR_SERVERINFO; } break; } } /* ============ 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 true. ============ */ void Cvar_WriteVariables(fileHandle_t f) { cvar_t *var; char buffer[1024]; for (var = cvar_vars; var; var = var->next) { if (!var->name) continue; if (var->flags & CVAR_ARCHIVE) { // write the latched value, even if it hasn't taken effect yet if (var->latchedString) { if (strlen(var->name) + strlen(var->latchedString) + 10 > sizeof(buffer)) { Com_Printf(S_COLOR_YELLOW "WARNING: value of variable " "\"%s\" too long to write to file\n", var->name); continue; } Com_sprintf(buffer, sizeof(buffer), "seta %s \"%s\"\n", var->name, var->latchedString); } else { if (strlen(var->name) + strlen(var->string) + 10 > sizeof(buffer)) { Com_Printf(S_COLOR_YELLOW "WARNING: value of variable " "\"%s\" too long to write to file\n", var->name); continue; } Com_sprintf(buffer, sizeof(buffer), "seta %s \"%s\"\n", var->name, var->string); } FS_Write(buffer, strlen(buffer), f); } } } /* ============ Cvar_List_f ============ */ void Cvar_List_f(void) { cvar_t *var; int i; const char *match; if (Cmd_Argc() > 1) { match = Cmd_Argv(1); } else { match = nullptr; } i = 0; for (var = cvar_vars; var; var = var->next, i++) { if (!var->name || (match && !Com_Filter(match, var->name, false))) continue; if (var->flags & CVAR_SERVERINFO) { Com_Printf("S"); } else { Com_Printf(" "); } if (var->flags & CVAR_SYSTEMINFO) { 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(" "); } if (var->flags & CVAR_USER_CREATED) { Com_Printf("?"); } 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_ListModified_f ============ */ void Cvar_ListModified_f(void) { cvar_t *var; int totalModified; char *value; const char *match; if (Cmd_Argc() > 1) { match = Cmd_Argv(1); } else { match = nullptr; } totalModified = 0; for (var = cvar_vars; var; var = var->next) { if (!var->name || !var->modificationCount) continue; value = var->latchedString ? var->latchedString : var->string; if (!strcmp(value, var->resetString)) continue; totalModified++; if (match && !Com_Filter(match, var->name, false)) continue; if (var->flags & CVAR_SERVERINFO) { Com_Printf("S"); } else { Com_Printf(" "); } if (var->flags & CVAR_SYSTEMINFO) { 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(" "); } if (var->flags & CVAR_USER_CREATED) { Com_Printf("?"); } else { Com_Printf(" "); } Com_Printf(" %s \"%s\", default \"%s\"\n", var->name, value, var->resetString); } Com_Printf("\n%i total modified cvars\n", totalModified); } /* ============ Cvar_Unset Unsets a cvar ============ */ cvar_t *Cvar_Unset(cvar_t *cv) { cvar_t *next = cv->next; // note what types of cvars have been modified (userinfo, archive, serverinfo, systeminfo) cvar_modifiedFlags |= cv->flags; if (cv->name) Z_Free(cv->name); if (cv->string) Z_Free(cv->string); if (cv->latchedString) Z_Free(cv->latchedString); if (cv->resetString) Z_Free(cv->resetString); if (cv->description) Z_Free(cv->description); if (cv->prev) cv->prev->next = cv->next; else cvar_vars = cv->next; if (cv->next) cv->next->prev = cv->prev; if (cv->hashPrev) cv->hashPrev->hashNext = cv->hashNext; else hashTable[cv->hashIndex] = cv->hashNext; if (cv->hashNext) cv->hashNext->hashPrev = cv->hashPrev; ::memset(cv, '\0', sizeof(*cv)); return next; } /* ============ Cvar_Unset_f Unsets a userdefined cvar ============ */ void Cvar_Unset_f(void) { cvar_t *cv; if (Cmd_Argc() != 2) { Com_Printf("Usage: %s \n", Cmd_Argv(0)); return; } cv = Cvar_FindVar(Cmd_Argv(1)); if (!cv) return; if (cv->flags & CVAR_USER_CREATED) Cvar_Unset(cv); else Com_Printf("Error: %s: Variable %s is not user created.\n", Cmd_Argv(0), cv->name); } /* ============ Cvar_Restart Resets all cvars to their hardcoded values and removes userdefined variables and variables added via the VMs if requested. ============ */ void Cvar_Restart(bool unsetVM) { cvar_t *curvar; curvar = cvar_vars; while (curvar) { if ((curvar->flags & CVAR_USER_CREATED) || (unsetVM && (curvar->flags & CVAR_VM_CREATED))) { // throw out any variables the user/vm created curvar = Cvar_Unset(curvar); continue; } if (!(curvar->flags & (CVAR_ROM | CVAR_INIT | CVAR_NORESTART))) { // Just reset the rest to their default values. Cvar_Set2(curvar->name, curvar->resetString, false); } curvar = curvar->next; } } /* ============ Cvar_Restart_f Resets all cvars to their hardcoded values ============ */ void Cvar_Restart_f(void) { Cvar_Restart(false); } /* ===================== 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->name && (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->name && (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_CheckRange ===================== */ void Cvar_CheckRange(cvar_t *var, float min, float max, bool integral) { var->validate = true; var->min = min; var->max = max; var->integral = integral; // Force an initial range check Cvar_Set(var->name, var->string); } /* ===================== Cvar_SetDescription ===================== */ void Cvar_SetDescription(cvar_t *var, const char *var_description) { if (var_description && var_description[0] != '\0') { if (var->description != nullptr) { Z_Free(var->description); } var->description = CopyString(var_description); } } /* ===================== 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; // There is code in Cvar_Get to prevent CVAR_ROM cvars being changed by the // user. In other words CVAR_ARCHIVE and CVAR_ROM are mutually exclusive // flags. Unfortunately some historical game code (including single player // baseq3) sets both flags. We unset CVAR_ROM for such cvars. if ((flags & (CVAR_ARCHIVE | CVAR_ROM)) == (CVAR_ARCHIVE | CVAR_ROM)) { Com_DPrintf(S_COLOR_YELLOW "WARNING: Unsetting CVAR_ROM cvar '%s', " "since it is also CVAR_ARCHIVE\n", varName); flags &= ~CVAR_ROM; } cv = Cvar_Get(varName, defaultValue, flags | CVAR_VM_CREATED); 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 = nullptr; assert(vmCvar); if (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; if (strlen(cv->string) + 1 > MAX_CVAR_VALUE_STRING) Com_Error(ERR_DROP, "Cvar_Update: src %s length %u exceeds MAX_CVAR_VALUE_STRING", cv->string, (unsigned int)strlen(cv->string)); Q_strncpyz(vmCvar->string, cv->string, MAX_CVAR_VALUE_STRING); vmCvar->value = cv->value; vmCvar->integer = cv->integer; } /* ================== Cvar_CompleteCvarName ================== */ void Cvar_CompleteCvarName(char *args, int argNum) { if (argNum == 2) { // Skip " " char *p = Com_SkipTokens(args, 1, " "); if (p > args) Field_CompleteCommand(p, false, true); } } /* ============ Cvar_Init Reads in all archived cvars ============ */ void Cvar_Init(void) { ::memset(cvar_indexes, '\0', sizeof(cvar_indexes)); ::memset(hashTable, '\0', sizeof(hashTable)); cvar_cheats = Cvar_Get("sv_cheats", "1", CVAR_ROM | CVAR_SYSTEMINFO); Cmd_AddCommand("print", Cvar_Print_f); Cmd_AddCommand("toggle", Cvar_Toggle_f); Cmd_SetCommandCompletionFunc("toggle", Cvar_CompleteCvarName); Cmd_AddCommand("set", Cvar_Set_f); Cmd_SetCommandCompletionFunc("set", Cvar_CompleteCvarName); Cmd_AddCommand("sets", Cvar_Set_f); Cmd_SetCommandCompletionFunc("sets", Cvar_CompleteCvarName); Cmd_AddCommand("setu", Cvar_Set_f); Cmd_SetCommandCompletionFunc("setu", Cvar_CompleteCvarName); Cmd_AddCommand("seta", Cvar_Set_f); Cmd_SetCommandCompletionFunc("seta", Cvar_CompleteCvarName); Cmd_AddCommand("reset", Cvar_Reset_f); Cmd_SetCommandCompletionFunc("reset", Cvar_CompleteCvarName); Cmd_AddCommand("unset", Cvar_Unset_f); Cmd_SetCommandCompletionFunc("unset", Cvar_CompleteCvarName); Cmd_AddCommand("cvarlist", Cvar_List_f); Cmd_AddCommand("cvar_modified", Cvar_ListModified_f); Cmd_AddCommand("cvar_restart", Cvar_Restart_f); }