1 /**
2  * @file
3  * @brief Manage cvars
4  *
5  * cvar_t variables are used to hold scalar or string variables that can be changed or displayed at the console or prog code as well as accessed directly
6  * in C code.
7  * Cvars are restricted from having the same names as commands to keep this
8  * interface from being ambiguous.
9  */
10 
11 /*
12 Copyright (C) 1997-2001 Id Software, Inc.
13 
14 This program is free software; you can redistribute it and/or
15 modify it under the terms of the GNU General Public License
16 as published by the Free Software Foundation; either version 2
17 of the License, or (at your option) any later version.
18 
19 This program is distributed in the hope that it will be useful,
20 but WITHOUT ANY WARRANTY; without even the implied warranty of
21 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
22 
23 See the GNU General Public License for more details.
24 
25 You should have received a copy of the GNU General Public License
26 along with this program; if not, write to the Free Software
27 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
28 
29 */
30 
31 #include "common.h"
32 #include "../shared/infostring.h"
33 #include <vector>
34 
35 #define CVAR_HASH_SIZE          64
36 
37 static cvar_t* cvarVarsHash[CVAR_HASH_SIZE];
38 
39 typedef std::vector<CvarListenerPtr> CvarListeners;
40 static CvarListeners cvarListeners;
41 
42 /**
43  * @brief This is set each time a CVAR_USERINFO variable is changed
44  * so that the renderer knows to update stuff accordingly
45  */
46 static bool renderModified;
47 
48 /**
49  * @brief This is set each time a CVAR_USERINFO variable is changed
50  * so that the client knows to send it to the server
51  */
52 static bool userinfoModified;
53 
Com_SetUserinfoModified(bool modified)54 void Com_SetUserinfoModified (bool modified)
55 {
56 	userinfoModified = modified;
57 }
58 
Com_IsUserinfoModified(void)59 bool Com_IsUserinfoModified (void)
60 {
61 	return userinfoModified;
62 }
63 
Com_SetRenderModified(bool modified)64 void Com_SetRenderModified (bool modified)
65 {
66 	renderModified = modified;
67 }
68 
Com_IsRenderModified(void)69 bool Com_IsRenderModified (void)
70 {
71 	return renderModified;
72 }
73 
74 /**
75  * @brief Cvar list
76  */
77 static cvar_t* cvarVars;
78 
Cvar_GetFirst(void)79 cvar_t* Cvar_GetFirst (void)
80 {
81 	return cvarVars;
82 }
83 
84 
Cvar_InfoValidate(const char * s)85 static bool Cvar_InfoValidate (const char* s)
86 {
87 	return s[strcspn(s, "\\\";")] == '\0';
88 }
89 
90 /**
91  * @brief Searches for a cvar given by parameter
92  * @param varName The cvar name as string
93  * @return Pointer to cvar_t struct or @c nullptr if no cvar with the specified name was found
94  * @sa Cvar_GetString
95  * @sa Cvar_SetValue
96  */
Cvar_FindVar(const char * varName)97 cvar_t* Cvar_FindVar (const char* varName)
98 {
99 	cvar_t* var;
100 	const unsigned hash = Com_HashKey(varName, CVAR_HASH_SIZE);
101 
102 	for (var = cvarVarsHash[hash]; var; var = var->hash_next) {
103 		if (Q_streq(varName, var->name))
104 			return var;
105 	}
106 
107 	return nullptr;
108 }
109 
110 /**
111  * @brief Returns the float value of a cvar
112  * @sa Cvar_GetString
113  * @sa Cvar_FindVar
114  * @sa Cvar_GetInteger
115  * @return 0 if not defined
116  */
Cvar_GetValue(const char * varName)117 float Cvar_GetValue (const char* varName)
118 {
119 	cvar_t* var;
120 
121 	var = Cvar_FindVar(varName);
122 	if (!var)
123 		return 0.0;
124 	return atof(var->string);
125 }
126 
127 
128 /**
129  * @brief Set a checker function for cvar values
130  * @sa Cvar_FindVar
131  * @return true if set
132  */
Cvar_SetCheckFunction(const char * varName,bool (* check)(cvar_t * cvar))133 bool Cvar_SetCheckFunction (const char* varName, bool (*check) (cvar_t* cvar))
134 {
135 	cvar_t* var;
136 
137 	var = Cvar_FindVar(varName);
138 	if (!var) {
139 		Com_Printf("Could not set check function for cvar '%s'\n", varName);
140 		return false;
141 	}
142 	var->check = check;
143 	/* execute the check */
144 	var->check(var);
145 	return true;
146 }
147 
148 /**
149  * @brief Checks cvar values
150  * @return @c true if assert isn't true and the cvar was changed to a valid value, @c false if the
151  * new value is ok and nothing was changed.
152  * @param[in] cvar Cvar to check
153  * @param[in] minVal The minimal value the cvar should have
154  * @param[in] maxVal The maximal value the cvar should have
155  * @param[in] shouldBeIntegral No floats for this cvar please
156  */
Cvar_AssertValue(cvar_t * cvar,float minVal,float maxVal,bool shouldBeIntegral)157 bool Cvar_AssertValue (cvar_t* cvar, float minVal, float maxVal, bool shouldBeIntegral)
158 {
159 	assert(cvar);
160 
161 	if (shouldBeIntegral) {
162 		if ((int)cvar->value != cvar->integer) {
163 			Com_Printf("WARNING: cvar '%s' must be integral (%f)\n", cvar->name, cvar->value);
164 			Cvar_Set(cvar->name, "%d", cvar->integer);
165 			return true;
166 		}
167 	}
168 
169 	if (cvar->value < minVal) {
170 		Com_Printf("WARNING: cvar '%s' out of range (%f < %f)\n", cvar->name, cvar->value, minVal);
171 		Cvar_SetValue(cvar->name, minVal);
172 		return true;
173 	} else if (cvar->value > maxVal) {
174 		Com_Printf("WARNING: cvar '%s' out of range (%f > %f)\n", cvar->name, cvar->value, maxVal);
175 		Cvar_SetValue(cvar->name, maxVal);
176 		return true;
177 	}
178 
179 	/* no changes */
180 	return false;
181 }
182 
183 /**
184  * @brief Returns the int value of a cvar
185  * @sa Cvar_GetValue
186  * @sa Cvar_GetString
187  * @sa Cvar_FindVar
188  * @return 0 if not defined
189  */
Cvar_GetInteger(const char * varName)190 int Cvar_GetInteger (const char* varName)
191 {
192 	const cvar_t* var;
193 
194 	var = Cvar_FindVar(varName);
195 	if (!var)
196 		return 0;
197 	return var->integer;
198 }
199 
200 /**
201  * @brief Returns the value of cvar as string
202  * @sa Cvar_GetValue
203  * @sa Cvar_FindVar
204  *
205  * Even if the cvar does not exist this function will not return a null pointer
206  * but an empty string
207  */
Cvar_GetString(const char * varName)208 const char* Cvar_GetString (const char* varName)
209 {
210 	const cvar_t* var;
211 
212 	var = Cvar_FindVar(varName);
213 	if (!var)
214 		return "";
215 	return var->string;
216 }
217 
218 /**
219  * @brief Returns the old value of cvar as string before we changed it
220  * @sa Cvar_GetValue
221  * @sa Cvar_FindVar
222  *
223  * Even if the cvar does not exist this function will not return a null pointer
224  * but an empty string
225  */
Cvar_VariableStringOld(const char * varName)226 const char* Cvar_VariableStringOld (const char* varName)
227 {
228 	cvar_t* var;
229 
230 	var = Cvar_FindVar(varName);
231 	if (!var)
232 		return "";
233 	if (var->oldString)
234 		return var->oldString;
235 	else
236 		return "";
237 }
238 
239 /**
240  * @brief Sets the cvar value back to the old value
241  * @param cvar The cvar to reset
242  */
Cvar_Reset(cvar_t * cvar)243 void Cvar_Reset (cvar_t* cvar)
244 {
245 	char* str;
246 
247 	if (cvar->oldString == nullptr)
248 		return;
249 
250 	str = Mem_StrDup(cvar->oldString);
251 	Cvar_Set(cvar->name, "%s", str);
252 	Mem_Free(str);
253 }
254 
255 /**
256  * @brief Unix like tab completion for console variables
257  * @param partial The beginning of the variable we try to complete
258  * @param[out] match The found entry of the list we are searching, in case of more than one entry their common suffix is returned.
259  * @sa Cmd_CompleteCommand
260  * @sa Key_CompleteCommand
261  */
Cvar_CompleteVariable(const char * partial,const char ** match)262 int Cvar_CompleteVariable (const char* partial, const char** match)
263 {
264 	int n = 0;
265 	for (cvar_t const* cvar = cvarVars; cvar; cvar = cvar->next) {
266 #ifndef DEBUG
267 		if (cvar->flags & CVAR_DEVELOPER)
268 			continue;
269 #endif
270 		if (Cmd_GenericCompleteFunction(cvar->name, partial, match)) {
271 			Com_Printf("[var] %-20s = \"%s\"\n", cvar->name, cvar->string);
272 			if (cvar->description)
273 				Com_Printf(S_COLOR_GREEN "      %s\n", cvar->description);
274 			++n;
275 		}
276 	}
277 	return n;
278 }
279 
280 /**
281  * @brief Function to remove the cvar and free the space
282  */
Cvar_Delete(const char * varName)283 bool Cvar_Delete (const char* varName)
284 {
285 	unsigned hash;
286 
287 	hash = Com_HashKey(varName, CVAR_HASH_SIZE);
288 	for (cvar_t** anchor = &cvarVarsHash[hash]; *anchor; anchor = &(*anchor)->hash_next) {
289 		cvar_t* const var = *anchor;
290 		if (!Q_strcasecmp(varName, var->name)) {
291 			cvarChangeListener_t* changeListener;
292 			if (var->flags != 0) {
293 				Com_Printf("Can't delete the cvar '%s' - it's a special cvar\n", varName);
294 				return false;
295 			}
296 			HASH_Delete(anchor);
297 			if (var->prev) {
298 				assert(var->prev->next == var);
299 				var->prev->next = var->next;
300 			} else
301 				cvarVars = var->next;
302 			if (var->next) {
303 				assert(var->next->prev == var);
304 				var->next->prev = var->prev;
305 			}
306 
307 			for (CvarListeners::iterator i = cvarListeners.begin(); i != cvarListeners.end(); ++i) {
308 				(*i)->onDelete(var);
309 			}
310 
311 			Mem_Free(var->name);
312 			Mem_Free(var->string);
313 			Mem_Free(var->description);
314 			Mem_Free(var->oldString);
315 			Mem_Free(var->defaultString);
316 			/* latched cvars should not be removable */
317 			assert(var->latchedString == nullptr);
318 			changeListener = var->changeListener;
319 			while (changeListener) {
320 				cvarChangeListener_t* changeListener2 = changeListener->next;
321 				Mem_Free(changeListener);
322 				changeListener = changeListener2;
323 			}
324 			Mem_Free(var);
325 
326 			return true;
327 		}
328 	}
329 	Com_Printf("Cvar '%s' wasn't found\n", varName);
330 	return false;
331 }
332 
333 /**
334  * @brief Init or return a cvar
335  * @param[in] var_name The cvar name
336  * @param[in] var_value The standard cvar value (will be set if the cvar doesn't exist)
337  * @param[in] flags CVAR_USERINFO, CVAR_LATCH, CVAR_SERVERINFO, CVAR_ARCHIVE and so on
338  * @param[in] desc This is a short description of the cvar (see console command cvarlist)
339  * @note CVAR_ARCHIVE: Cvar will be saved to config.cfg when game shuts down - and
340  * will be reloaded when game starts up the next time
341  * @note CVAR_LATCH: Latched cvars will be updated at the next map load
342  * @note CVAR_SERVERINFO: This cvar will be send in the server info response strings (server browser)
343  * @note CVAR_NOSET: This cvar can not be set from the commandline
344  * @note CVAR_USERINFO: This cvar will be added to the userinfo string when changed (network synced)
345  * @note CVAR_DEVELOPER: Only changeable if we are in development mode
346  * If the variable already exists, the value will not be set
347  * The flags will be or'ed in if the variable exists.
348  */
Cvar_Get(const char * var_name,const char * var_value,int flags,const char * desc)349 cvar_t* Cvar_Get (const char* var_name, const char* var_value, int flags, const char* desc)
350 {
351 	const unsigned hash = Com_HashKey(var_name, CVAR_HASH_SIZE);
352 
353 	if (flags & (CVAR_USERINFO | CVAR_SERVERINFO)) {
354 		if (!Cvar_InfoValidate(var_name)) {
355 			Com_Printf("invalid info cvar name\n");
356 			return nullptr;
357 		}
358 	}
359 
360 	if (cvar_t* const var = Cvar_FindVar(var_name)) {
361 		if (!var->defaultString && (flags & CVAR_CHEAT))
362 			var->defaultString = Mem_PoolStrDup(var_value, com_cvarSysPool, 0);
363 		var->flags |= flags;
364 		if (desc) {
365 			Mem_Free(var->description);
366 			var->description = Mem_PoolStrDup(desc, com_cvarSysPool, 0);
367 		}
368 		return var;
369 	}
370 
371 	if (!var_value)
372 		return nullptr;
373 
374 	if (flags & (CVAR_USERINFO | CVAR_SERVERINFO)) {
375 		if (!Cvar_InfoValidate(var_value)) {
376 			Com_Printf("invalid info cvar value '%s' of cvar '%s'\n", var_value, var_name);
377 			return nullptr;
378 		}
379 	}
380 
381 	cvar_t* const var = Mem_PoolAllocType(cvar_t, com_cvarSysPool);
382 	var->name = Mem_PoolStrDup(var_name, com_cvarSysPool, 0);
383 	var->string = Mem_PoolStrDup(var_value, com_cvarSysPool, 0);
384 	var->oldString = nullptr;
385 	var->modified = true;
386 	var->value = atof(var->string);
387 	var->integer = atoi(var->string);
388 	if (desc)
389 		var->description = Mem_PoolStrDup(desc, com_cvarSysPool, 0);
390 
391 	HASH_Add(cvarVarsHash, var, hash);
392 	/* link the variable in */
393 	var->next = cvarVars;
394 	cvarVars = var;
395 	if (var->next)
396 		var->next->prev = var;
397 
398 	var->flags = flags;
399 	if (var->flags & CVAR_CHEAT)
400 		var->defaultString = Mem_PoolStrDup(var_value, com_cvarSysPool, 0);
401 
402 	for (CvarListeners::iterator i = cvarListeners.begin(); i != cvarListeners.end(); ++i) {
403 		(*i)->onCreate(var);
404 	}
405 
406 	return var;
407 }
408 
409 /**
410  * @brief Registers a cvar listener
411  * @param listener The listener callback to register
412  */
Cvar_RegisterCvarListener(CvarListenerPtr listener)413 void Cvar_RegisterCvarListener (CvarListenerPtr listener)
414 {
415 	cvarListeners.push_back(listener);
416 }
417 
418 /**
419  * @brief Unregisters a cvar listener
420  * @param listener The listener callback to unregister
421  */
Cvar_UnRegisterCvarListener(CvarListenerPtr listener)422 void Cvar_UnRegisterCvarListener (CvarListenerPtr listener)
423 {
424 	cvarListeners.erase(std::remove(cvarListeners.begin(), cvarListeners.end(), listener), cvarListeners.end());
425 }
426 
427 /**
428  * @brief Executes the change listeners for a cvar
429  * @param cvar The cvar which change listeners are executed
430  */
Cvar_ExecuteChangeListener(const cvar_t * cvar)431 static void Cvar_ExecuteChangeListener (const cvar_t* cvar)
432 {
433 	const cvarChangeListener_t* listener = cvar->changeListener;
434 	while (listener) {
435 		listener->exec(cvar->name, cvar->oldString, cvar->string, listener->data);
436 		listener = listener->next;
437 	}
438 }
439 
Cvar_GetChangeListener(cvarChangeListenerFunc_t listenerFunc)440 static cvarChangeListener_t* Cvar_GetChangeListener (cvarChangeListenerFunc_t listenerFunc)
441 {
442 	cvarChangeListener_t* const listener = Mem_PoolAllocType(cvarChangeListener_t, com_cvarSysPool);
443 	listener->exec = listenerFunc;
444 	return listener;
445 }
446 
447 /**
448  * @brief Registers a listener that is executed each time a cvar changed its value.
449  * @sa Cvar_ExecuteChangeListener
450  * @param varName The cvar name to register the listener for
451  * @param listenerFunc The listener callback to register
452  */
Cvar_RegisterChangeListener(const char * varName,cvarChangeListenerFunc_t listenerFunc)453 cvarChangeListener_t* Cvar_RegisterChangeListener (const char* varName, cvarChangeListenerFunc_t listenerFunc)
454 {
455 	cvar_t* var = Cvar_FindVar(varName);
456 	if (!var) {
457 		Com_Printf("Could not register change listener, cvar '%s' wasn't found\n", varName);
458 		return nullptr;
459 	}
460 
461 	if (!var->changeListener) {
462 		cvarChangeListener_t* l = Cvar_GetChangeListener(listenerFunc);
463 		var->changeListener = l;
464 		return l;
465 	} else {
466 		cvarChangeListener_t* l = var->changeListener;
467 		while (l) {
468 			if (l->exec == listenerFunc) {
469 				return l;
470 			}
471 			l = l->next;
472 		}
473 
474 		l = var->changeListener;
475 		while (l) {
476 			if (!l->next) {
477 				cvarChangeListener_t* listener = Cvar_GetChangeListener(listenerFunc);
478 				l->next = listener;
479 				l->next->next = nullptr;
480 				return listener;
481 			}
482 			l = l->next;
483 		}
484 	}
485 
486 	return nullptr;
487 }
488 
489 /**
490  * @brief Unregisters a cvar change listener
491  * @param varName The cvar name to register the listener for
492  * @param listenerFunc The listener callback to unregister
493  */
Cvar_UnRegisterChangeListener(const char * varName,cvarChangeListenerFunc_t listenerFunc)494 void Cvar_UnRegisterChangeListener (const char* varName, cvarChangeListenerFunc_t listenerFunc)
495 {
496 	cvar_t* var = Cvar_FindVar(varName);
497 	if (!var) {
498 		Com_Printf("Could not unregister change listener, cvar '%s' wasn't found\n", varName);
499 		return;
500 	}
501 
502 	for (cvarChangeListener_t** anchor = &var->changeListener; *anchor; anchor = &(*anchor)->next) {
503 		cvarChangeListener_t* const l = *anchor;
504 		if (l->exec == listenerFunc) {
505 			*anchor = l->next;
506 			Mem_Free(l);
507 			return;
508 		}
509 	}
510 }
511 
512 /**
513  * @brief Sets a cvar values
514  * Handles write protection and latched cvars as expected
515  * @param[in] varName Which cvar
516  * @param[in] value Set the cvar to the value specified by 'value'
517  * @param[in] force Force the update of the cvar
518  */
Cvar_Set2(const char * varName,const char * value,bool force)519 static cvar_t* Cvar_Set2 (const char* varName, const char* value, bool force)
520 {
521 	cvar_t* var;
522 
523 	if (!value)
524 		return nullptr;
525 
526 	var = Cvar_FindVar(varName);
527 	/* create it */
528 	if (!var)
529 		return Cvar_Get(varName, value);
530 
531 	if (var->flags & (CVAR_USERINFO | CVAR_SERVERINFO)) {
532 		if (!Cvar_InfoValidate(value)) {
533 			Com_Printf("invalid info cvar value '%s' of cvar '%s'\n", value, varName);
534 			return var;
535 		}
536 	}
537 
538 	if (!force) {
539 		if (var->flags & CVAR_NOSET) {
540 			Com_Printf("%s is write protected.\n", varName);
541 			return var;
542 		}
543 #ifndef DEBUG
544 		if (var->flags & CVAR_DEVELOPER) {
545 			Com_Printf("%s is a developer cvar.\n", varName);
546 			return var;
547 		}
548 #endif
549 
550 		if (var->flags & CVAR_LATCH) {
551 			if (var->latchedString) {
552 				if (Q_streq(value, var->latchedString))
553 					return var;
554 				Mem_Free(var->latchedString);
555 				var->latchedString = nullptr;
556 			} else {
557 				if (Q_streq(value, var->string))
558 					return var;
559 			}
560 
561 			/* if we are running a server */
562 			if (Com_ServerState()) {
563 				Com_Printf("%s will be changed for next game.\n", varName);
564 				var->latchedString = Mem_PoolStrDup(value, com_cvarSysPool, 0);
565 			} else {
566 				Mem_Free(var->oldString);
567 				var->oldString = var->string;
568 				var->string = Mem_PoolStrDup(value, com_cvarSysPool, 0);
569 				var->value = atof(var->string);
570 				var->integer = atoi(var->string);
571 			}
572 
573 			if (var->check && var->check(var))
574 				Com_Printf("Invalid value for cvar %s\n", varName);
575 
576 			return var;
577 		}
578 	} else {
579 		Mem_Free(var->latchedString);
580 		var->latchedString = nullptr;
581 	}
582 
583 	if (Q_streq(value, var->string))
584 		return var;				/* not changed */
585 
586 	if (var->flags & CVAR_R_MASK)
587 		Com_SetRenderModified(true);
588 
589 	Mem_Free(var->oldString);		/* free the old value string */
590 	var->oldString = var->string;
591 	var->modified = true;
592 
593 	if (var->flags & CVAR_USERINFO)
594 		Com_SetUserinfoModified(true);	/* transmit at next opportunity */
595 
596 	var->string = Mem_PoolStrDup(value, com_cvarSysPool, 0);
597 	var->value = atof(var->string);
598 	var->integer = atoi(var->string);
599 
600 	if (var->check && var->check(var)) {
601 		Com_Printf("Invalid value for cvar %s\n", varName);
602 		return var;
603 	}
604 
605 	Cvar_ExecuteChangeListener(var);
606 
607 	return var;
608 }
609 
610 /**
611  * @brief Will set the variable even if NOSET or LATCH
612  */
Cvar_ForceSet(const char * varName,const char * value)613 cvar_t* Cvar_ForceSet (const char* varName, const char* value)
614 {
615 	return Cvar_Set2(varName, value, true);
616 }
617 
618 /**
619  * @brief Sets a cvar value
620  * @param varName Which cvar should be set
621  * @param value Which value should the cvar get
622  * @note Look after the CVAR_LATCH stuff and check for write protected cvars
623  */
Cvar_Set(const char * varName,const char * value,...)624 cvar_t* Cvar_Set (const char* varName, const char* value, ...)
625 {
626 	va_list argptr;
627 	char text[512];
628 	va_start(argptr, value);
629 	Q_vsnprintf(text, sizeof(text), value, argptr);
630 	va_end(argptr);
631 
632 	return Cvar_Set2(varName, text, false);
633 }
634 
635 /**
636  * @brief Sets a cvar from console with the given flags
637  * @note flags are:
638  * CVAR_ARCHIVE These cvars will be saved.
639  * CVAR_USERINFO Added to userinfo  when changed.
640  * CVAR_SERVERINFO Added to serverinfo when changed.
641  * CVAR_NOSET Don't allow change from console at all but can be set from the command line.
642  * CVAR_LATCH Save changes until server restart.
643  *
644  * @param varName Which cvar
645  * @param value Which value for the cvar
646  * @param flags which flags
647  * @sa Cvar_Set_f
648  */
Cvar_FullSet(const char * varName,const char * value,int flags)649 cvar_t* Cvar_FullSet (const char* varName, const char* value, int flags)
650 {
651 	cvar_t* var;
652 
653 	if (!value)
654 		return nullptr;
655 
656 	var = Cvar_FindVar(varName);
657 	/* create it */
658 	if (!var)
659 		return Cvar_Get(varName, value, flags);
660 
661 	var->modified = true;
662 
663 	/* transmit at next opportunity */
664 	if (var->flags & CVAR_USERINFO)
665 		Com_SetUserinfoModified(true);
666 
667 	Mem_Free(var->oldString);		/* free the old value string */
668 	var->oldString = var->string;
669 
670 	var->string = Mem_PoolStrDup(value, com_cvarSysPool, 0);
671 	var->value = atof(var->string);
672 	var->integer = atoi(var->string);
673 	var->flags = flags;
674 
675 	return var;
676 }
677 
678 /**
679  * @brief Expands value to a string and calls Cvar_Set
680  * @note Float values are in the format #.##
681  */
Cvar_SetValue(const char * varName,float value)682 void Cvar_SetValue (const char* varName, float value)
683 {
684 	if (value == (int) value)
685 		Cvar_Set(varName, "%i", (int)value);
686 	else
687 		Cvar_Set(varName, "%1.2f", value);
688 }
689 
690 
691 /**
692  * @brief Any variables with latched values will now be updated
693  * @note CVAR_LATCH cvars are not updated during a game (tactical mission)
694  */
Cvar_UpdateLatchedVars(void)695 void Cvar_UpdateLatchedVars (void)
696 {
697 	cvar_t* var;
698 
699 	for (var = cvarVars; var; var = var->next) {
700 		if (!var->latchedString)
701 			continue;
702 		var->oldString = var->string;
703 		var->string = var->latchedString;
704 		var->latchedString = nullptr;
705 		var->value = atof(var->string);
706 		var->integer = atoi(var->string);
707 	}
708 }
709 
710 /**
711  * @brief Handles variable inspection and changing from the console
712  * @return True if cvar exists - false otherwise
713  *
714  * You can print the current value or set a new value with this function
715  * To set a new value for a cvar from within the console just type the cvar name
716  * followed by the value. To print the current cvar's value just type the cvar name
717  * and hit enter
718  * @sa Cvar_Set_f
719  * @sa Cvar_SetValue
720  * @sa Cvar_Set
721  */
Cvar_Command(void)722 bool Cvar_Command (void)
723 {
724 	cvar_t* v;
725 
726 	/* check variables */
727 	v = Cvar_FindVar(Cmd_Argv(0));
728 	if (!v)
729 		return false;
730 
731 	/* perform a variable print or set */
732 	if (Cmd_Argc() == 1) {
733 		Com_Printf("\"%s\" is \"%s\"\n", v->name, v->string);
734 		return true;
735 	}
736 
737 	Cvar_Set(v->name, "%s", Cmd_Argv(1));
738 	return true;
739 }
740 
741 /**
742  * @brief Allows resetting cvars to old value from console
743  */
Cvar_SetOld_f(void)744 static void Cvar_SetOld_f (void)
745 {
746 	cvar_t* v;
747 
748 	if (Cmd_Argc() != 2) {
749 		Com_Printf("Usage: %s <variable>\n", Cmd_Argv(0));
750 		return;
751 	}
752 
753 	/* check variables */
754 	v = Cvar_FindVar(Cmd_Argv(1));
755 	if (!v) {
756 		Com_Printf("cvar '%s' not found\n", Cmd_Argv(1));
757 		return;
758 	}
759 	if (v->oldString)
760 		Cvar_Set(Cmd_Argv(1), "%s", v->oldString);
761 }
762 
Cvar_Define_f(void)763 static void Cvar_Define_f (void)
764 {
765 	const char* name;
766 
767 	if (Cmd_Argc() < 2) {
768 		Com_Printf("Usage: %s <cvarname> <value>\n", Cmd_Argv(0));
769 		return;
770 	}
771 
772 	name = Cmd_Argv(1);
773 
774 	if (Cvar_FindVar(name) == nullptr)
775 		Cvar_Set(name, "%s", Cmd_Argc() == 3 ? Cmd_Argv(2) : "");
776 }
777 
778 /**
779  * @brief Allows setting and defining of arbitrary cvars from console
780  */
Cvar_Set_f(void)781 static void Cvar_Set_f (void)
782 {
783 	const int c = Cmd_Argc();
784 	if (c != 3 && c != 4) {
785 		Com_Printf("Usage: %s <variable> <value> [u / s]\n", Cmd_Argv(0));
786 		return;
787 	}
788 
789 	if (c == 4) {
790 		const char* arg = Cmd_Argv(3);
791 		int flags = 0;
792 
793 		while (arg[0] != '\0') {
794 			switch (arg[0]) {
795 			case 'u':
796 				flags |= CVAR_USERINFO;
797 				break;
798 			case 's':
799 				flags |= CVAR_SERVERINFO;
800 				break;
801 			case 'a':
802 				flags |= CVAR_ARCHIVE;
803 				break;
804 			default:
805 				Com_Printf("invalid flags %c given\n", arg[0]);
806 				break;
807 			}
808 			arg++;
809 		}
810 		Cvar_FullSet(Cmd_Argv(1), Cmd_Argv(2), flags);
811 	} else {
812 		Cvar_Set(Cmd_Argv(1), "%s", Cmd_Argv(2));
813 	}
814 }
815 
816 /**
817  * @brief Allows switching boolean cvars between zero and not-zero from console
818  */
Cvar_Switch_f(void)819 static void Cvar_Switch_f (void)
820 {
821 	const int c = Cmd_Argc();
822 	if (c != 2 && c != 3) {
823 		Com_Printf("Usage: %s <variable> [u / s / a]\n", Cmd_Argv(0));
824 		return;
825 	}
826 
827 	if (c == 3) {
828 		const char* arg = Cmd_Argv(2);
829 		int flags = 0;
830 
831 		while (arg[0] != '\0') {
832 			switch (arg[0]) {
833 			case 'u':
834 				flags |= CVAR_USERINFO;
835 				break;
836 			case 's':
837 				flags |= CVAR_SERVERINFO;
838 				break;
839 			case 'a':
840 				flags |= CVAR_ARCHIVE;
841 				break;
842 			default:
843 				Com_Printf("invalid flags %c given\n", arg[0]);
844 				break;
845 			}
846 			arg++;
847 		}
848 		Cvar_FullSet(Cmd_Argv(1), va("%i", !Cvar_GetInteger(Cmd_Argv(1))), flags);
849 	} else {
850 		Com_Printf("val: %i\n", Cvar_GetInteger(Cmd_Argv(1)));
851 		Cvar_Set(Cmd_Argv(1), "%i", !Cvar_GetInteger(Cmd_Argv(1)));
852 	}
853 }
854 
855 /**
856  * @brief Allows copying variables
857  * Available via console command copy
858  */
Cvar_Copy_f(void)859 static void Cvar_Copy_f (void)
860 {
861 	int c;
862 
863 	c = Cmd_Argc();
864 	if (c < 3) {
865 		Com_Printf("Usage: %s <target> <source>\n", Cmd_Argv(0));
866 		return;
867 	}
868 
869 	Cvar_Set(Cmd_Argv(1), "%s", Cvar_GetString(Cmd_Argv(2)));
870 }
871 
872 
873 /**
874  * @brief appends lines containing "set variable value" for all variables
875  * with the archive flag set to true.
876  * @note Stores the archive cvars
877  */
Cvar_WriteVariables(qFILE * f)878 void Cvar_WriteVariables (qFILE *f)
879 {
880 	const cvar_t* var;
881 
882 	for (var = cvarVars; var; var = var->next) {
883 		if (var->flags & CVAR_ARCHIVE)
884 			FS_Printf(f, "set %s \"%s\" a\n", var->name, var->string);
885 	}
886 }
887 
888 /**
889  * @brief Checks whether there are pending cvars for the given flags
890  * @param flags The CVAR_* flags
891  * @return true if there are pending cvars, false otherwise
892  */
Cvar_PendingCvars(int flags)893 bool Cvar_PendingCvars (int flags)
894 {
895 	const cvar_t* var;
896 
897 	for (var = cvarVars; var; var = var->next) {
898 		if ((var->flags & flags) && var->modified)
899 			return true;
900 	}
901 
902 	return false;
903 }
904 
Cvar_ClearVars(int flags)905 void Cvar_ClearVars (int flags)
906 {
907 	cvar_t* var;
908 
909 	for (var = cvarVars; var; var = var->next) {
910 		if (var->flags & flags)
911 			var->modified = false;
912 	}
913 }
914 
915 /**
916  * @brief List all cvars via console command 'cvarlist'
917  */
Cvar_List_f(void)918 static void Cvar_List_f (void)
919 {
920 	cvar_t* var;
921 	int i, c, l = 0;
922 	const char* token = nullptr;
923 
924 	c = Cmd_Argc();
925 
926 	if (c == 2) {
927 		token = Cmd_Argv(1);
928 		l = strlen(token);
929 	}
930 
931 	i = 0;
932 	for (var = cvarVars; var; var = var->next, i++) {
933 		if (token && strncmp(var->name, token, l)) {
934 			i--;
935 			continue;
936 		}
937 #ifndef DEBUG
938 		/* don't show developer cvars in release mode */
939 		if (var->flags & CVAR_DEVELOPER)
940 			continue;
941 #endif
942 
943 		Com_Printf(var->flags & CVAR_ARCHIVE    ? "A" : " ");
944 		Com_Printf(var->flags & CVAR_USERINFO   ? "U" : " ");
945 		Com_Printf(var->flags & CVAR_SERVERINFO ? "S" : " ");
946 		Com_Printf(var->modified                ? "M" : " ");
947 		Com_Printf(var->flags & CVAR_DEVELOPER  ? "D" : " ");
948 		Com_Printf(var->flags & CVAR_R_IMAGES   ? "I" : " ");
949 		Com_Printf(var->flags & CVAR_NOSET      ? "-" :
950 		           var->flags & CVAR_LATCH      ? "L" : " ");
951 		Com_Printf(" %-20s \"%s\"\n", var->name, var->string);
952 		if (var->description)
953 			Com_Printf(S_COLOR_GREEN "        %s\n", var->description);
954 	}
955 	Com_Printf("%i cvars\n", i);
956 	Com_Printf("legend:\n"
957 		"S: Serverinfo\n"
958 		"L: Latched\n"
959 		"D: Developer\n"
960 		"U: Userinfo\n"
961 		"I: Image\n"
962 		"*: Archive\n"
963 		"-: Not changeable\n"
964 	);
965 }
966 
967 /**
968  * @brief Return a string with all cvars with bitflag given by parameter set
969  * @param bit The bitflag we search the global cvar array for
970  */
Cvar_BitInfo(int bit,char * info,size_t infoSize)971 static char* Cvar_BitInfo (int bit, char* info, size_t infoSize)
972 {
973 	for (cvar_t* var = cvarVars; var; var = var->next) {
974 		if (var->flags & bit)
975 			Info_SetValueForKey(info, infoSize, var->name, var->string);
976 	}
977 	return info;
978 }
979 
980 /**
981  * @brief Returns an info string containing all the CVAR_USERINFO cvars
982  */
Cvar_Userinfo(char * info,size_t infoSize)983 const char* Cvar_Userinfo (char* info, size_t infoSize)
984 {
985 	info[0] = '\0';
986 	return Cvar_BitInfo(CVAR_USERINFO, info, infoSize);
987 }
988 
989 /**
990  * @brief Returns an info string containing all the CVAR_SERVERINFO cvars
991  * @sa SV_StatusString
992  */
Cvar_Serverinfo(char * info,size_t infoSize)993 const char* Cvar_Serverinfo (char* info, size_t infoSize)
994 {
995 	info[0] = '\0';
996 	return Cvar_BitInfo(CVAR_SERVERINFO, info, infoSize);
997 }
998 
999 /**
1000  * @brief Delete a cvar - set [cvar] "" isn't working from within the scripts
1001  * @sa Cvar_Set_f
1002  */
Cvar_Del_f(void)1003 static void Cvar_Del_f (void)
1004 {
1005 	int c;
1006 
1007 	c = Cmd_Argc();
1008 	if (c != 2) {
1009 		Com_Printf("Usage: %s <variable>\n", Cmd_Argv(0));
1010 		return;
1011 	}
1012 
1013 	Cvar_Delete(Cmd_Argv(1));
1014 }
1015 
1016 /**
1017  * @brief Add a value to a cvar
1018  */
Cvar_Add_f(void)1019 static void Cvar_Add_f (void)
1020 {
1021 	cvar_t* cvar;
1022 	float value;
1023 	if (Cmd_Argc() != 3) {
1024 		Com_Printf("Usage: %s <variable> <value>\n", Cmd_Argv(0));
1025 		return;
1026 	}
1027 
1028 	cvar = Cvar_FindVar(Cmd_Argv(1));
1029 	if (!cvar) {
1030 		Com_Printf("Cvar_Add_f: %s does not exist\n", Cmd_Argv(1));
1031 		return;
1032 	}
1033 
1034 	value = cvar->value + atof(Cmd_Argv(2));
1035 	Cvar_SetValue(Cmd_Argv(1), value);
1036 }
1037 
1038 /**
1039  * @brief Apply a modulo to a cvar
1040  */
Cvar_Mod_f(void)1041 static void Cvar_Mod_f (void)
1042 {
1043 	cvar_t* cvar;
1044 	int value;
1045 	if (Cmd_Argc() != 3) {
1046 		Com_Printf("Usage: %s <variable> <value>\n", Cmd_Argv(0));
1047 		return;
1048 	}
1049 
1050 	cvar = Cvar_FindVar(Cmd_Argv(1));
1051 	if (!cvar) {
1052 		Com_Printf("Cvar_Mod_f: %s does not exist\n", Cmd_Argv(1));
1053 		return;
1054 	}
1055 
1056 	value = cvar->integer % atoi(Cmd_Argv(2));
1057 	Cvar_SetValue(Cmd_Argv(1), value);
1058 }
1059 
1060 /**
1061  * @brief Reset cheat cvar values to default
1062  * @sa CL_SendCommand
1063  */
Cvar_FixCheatVars(void)1064 void Cvar_FixCheatVars (void)
1065 {
1066 	cvar_t* var;
1067 
1068 	if (!(Com_ServerState() && !Cvar_GetInteger("sv_cheats")))
1069 		return;
1070 
1071 	for (var = cvarVars; var; var = var->next) {
1072 		if (!(var->flags & CVAR_CHEAT))
1073 			continue;
1074 
1075 		if (!var->defaultString) {
1076 			Com_Printf("Cheat cvars: Cvar %s has no default value\n", var->name);
1077 			continue;
1078 		}
1079 
1080 		if (Q_streq(var->string, var->defaultString))
1081 			continue;
1082 
1083 		/* also remove the oldString value here */
1084 		Mem_Free(var->oldString);
1085 		var->oldString = nullptr;
1086 		Mem_Free(var->string);
1087 		var->string = Mem_PoolStrDup(var->defaultString, com_cvarSysPool, 0);
1088 		var->value = atof(var->string);
1089 		var->integer = atoi(var->string);
1090 
1091 		Com_Printf("'%s' is a cheat cvar - activate sv_cheats to use it.\n", var->name);
1092 	}
1093 }
1094 
1095 #ifdef DEBUG
Cvar_PrintDebugCvars(void)1096 void Cvar_PrintDebugCvars (void)
1097 {
1098 	const cvar_t* var;
1099 
1100 	Com_Printf("Debug cvars:\n");
1101 	for (var = cvarVars; var; var = var->next) {
1102 		if ((var->flags & CVAR_DEVELOPER) || !strncmp(var->name, "debug_", 6))
1103 			Com_Printf(" * %s (%s)\n   %s\n", var->name, var->string, var->description ? var->description : "");
1104 	}
1105 	Com_Printf("\n");
1106 }
1107 #endif
1108 
1109 /**
1110  * @brief Reads in all archived cvars
1111  * @sa Qcommon_Init
1112  */
Cvar_Init(void)1113 void Cvar_Init (void)
1114 {
1115 	Cmd_AddCommand("setold", Cvar_SetOld_f, "Restore the cvar old value");
1116 	Cmd_AddCommand("del", Cvar_Del_f, "Delete a cvar");
1117 	Cmd_AddCommand("set", Cvar_Set_f, "Set a cvar value");
1118 	Cmd_AddCommand("switch", Cvar_Switch_f, "Switch a boolean cvar value");
1119 	Cmd_AddCommand("add", Cvar_Add_f, "Add a value to a cvar");
1120 	Cmd_AddCommand("define", Cvar_Define_f, "Defines a cvar if it does not exist");
1121 	Cmd_AddCommand("mod", Cvar_Mod_f, "Apply a modulo on a cvar");
1122 	Cmd_AddCommand("copy", Cvar_Copy_f, "Copy cvar target to source");
1123 	Cmd_AddCommand("cvarlist", Cvar_List_f, "Show all cvars");
1124 }
1125 
Cvar_Shutdown(void)1126 void Cvar_Shutdown (void)
1127 {
1128 	cvarVars = nullptr;
1129 	OBJZERO(cvarVarsHash);
1130 }
1131