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