1 // Emacs style mode select -*- C++ -*-
2 //-----------------------------------------------------------------------------
3 //
4 // $Id: c_cvars.cpp 4521 2014-01-22 17:55:01Z dr_sean $
5 //
6 // Copyright (C) 1998-2006 by Randy Heit (ZDoom).
7 // Copyright (C) 2006-2014 by The Odamex Team.
8 //
9 // This program is free software; you can redistribute it and/or
10 // modify it under the terms of the GNU General Public License
11 // as published by the Free Software Foundation; either version 2
12 // of the License, or (at your option) any later version.
13 //
14 // This program is distributed in the hope that it will be useful,
15 // but WITHOUT ANY WARRANTY; without even the implied warranty of
16 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 // GNU General Public License for more details.
18 //
19 // DESCRIPTION:
20 // Command-line variables
21 //
22 //-----------------------------------------------------------------------------
23
24
25 #include <cstring>
26 #include <cmath>
27 #include <stdio.h>
28
29 #include "cmdlib.h"
30 #include "c_console.h"
31 #include "c_dispatch.h"
32 #include "m_alloc.h"
33
34 #include "doomstat.h"
35 #include "c_cvars.h"
36 #include "d_player.h"
37
38 #include "d_netinf.h"
39 #include "gstrings.h"
40
41 #include "i_system.h"
42
43 bool cvar_t::m_DoNoSet = false;
44 bool cvar_t::m_UseCallback = false;
45
46 // denis - all this class does is delete the cvars during its static destruction
47 class ad_t {
48 public:
GetCVars()49 cvar_t *&GetCVars() { static cvar_t *CVars; return CVars; }
ad_t()50 ad_t() {}
~ad_t()51 ~ad_t()
52 {
53 cvar_t *cvar = GetCVars();
54 while (cvar)
55 {
56 cvar_t *next = cvar->GetNext();
57 if(cvar->m_Flags & CVAR_AUTO)
58 delete cvar;
59 cvar = next;
60 }
61 }
62 } ad;
63
GetFirstCvar(void)64 cvar_t* GetFirstCvar(void)
65 {
66 return ad.GetCVars();
67 }
68
69 int cvar_defflags;
70
cvar_t(const char * var_name,const char * def,const char * help,cvartype_t type,DWORD flags,float minval,float maxval)71 cvar_t::cvar_t(const char* var_name, const char* def, const char* help, cvartype_t type,
72 DWORD flags, float minval, float maxval)
73 {
74 InitSelf(var_name, def, help, type, flags, NULL, minval, maxval);
75 }
76
cvar_t(const char * var_name,const char * def,const char * help,cvartype_t type,DWORD flags,void (* callback)(cvar_t &),float minval,float maxval)77 cvar_t::cvar_t(const char* var_name, const char* def, const char* help, cvartype_t type,
78 DWORD flags, void (*callback)(cvar_t &), float minval, float maxval)
79 {
80 InitSelf(var_name, def, help, type, flags, callback, minval, maxval);
81 }
82
InitSelf(const char * var_name,const char * def,const char * help,cvartype_t type,DWORD var_flags,void (* callback)(cvar_t &),float minval,float maxval)83 void cvar_t::InitSelf(const char* var_name, const char* def, const char* help, cvartype_t type,
84 DWORD var_flags, void (*callback)(cvar_t &), float minval, float maxval)
85 {
86 cvar_t* dummy;
87 cvar_t* var = FindCVar(var_name, &dummy);
88
89 m_Callback = callback;
90 m_String = "";
91 m_Value = 0.0f;
92 m_Flags = 0;
93 m_LatchedString = "";
94 m_HelpText = help;
95 m_Type = type;
96
97 if (var_flags & CVAR_NOENABLEDISABLE)
98 {
99 m_MinValue = minval;
100 m_MaxValue = maxval;
101 }
102 else
103 {
104 m_MinValue = 0.0f;
105 m_MaxValue = 1.0f;
106 }
107
108 if (def)
109 m_Default = def;
110 else
111 m_Default = "";
112
113 if (var_name)
114 {
115 C_AddTabCommand(var_name);
116 m_Name = var_name;
117 m_Next = ad.GetCVars();
118 ad.GetCVars() = this;
119 }
120 else
121 m_Name = "";
122
123 if (var)
124 {
125 ForceSet(var->m_String.c_str());
126 if (var->m_Flags & CVAR_AUTO)
127 delete var;
128 else
129 var->~cvar_t();
130 }
131 else if (def)
132 ForceSet(def);
133
134 m_Flags = var_flags | CVAR_ISDEFAULT;
135 }
136
~cvar_t()137 cvar_t::~cvar_t ()
138 {
139 if (m_Name.length())
140 {
141 cvar_t *var, *dummy = NULL;
142
143 var = FindCVar (m_Name.c_str(), &dummy);
144
145 if (var == this)
146 {
147 if (dummy)
148 dummy->m_Next = m_Next;
149 else
150 ad.GetCVars() = m_Next;
151 }
152 }
153 }
154
ForceSet(const char * valstr)155 void cvar_t::ForceSet(const char* valstr)
156 {
157 // [SL] 2013-04-16 - Latched CVARs do not change values until the next map.
158 // Servers and single-player games should abide by this behavior but
159 // multiplayer clients should just do what the server tells them.
160 if (m_Flags & CVAR_LATCH && serverside &&
161 (gamestate == GS_LEVEL || gamestate == GS_INTERMISSION))
162 {
163 m_Flags |= CVAR_MODIFIED;
164 if (valstr)
165 m_LatchedString = valstr;
166 else
167 m_LatchedString.clear();
168 }
169 else
170 {
171 m_Flags |= CVAR_MODIFIED;
172
173 bool numerical_value = IsRealNum(valstr);
174 bool integral_type = m_Type == CVARTYPE_BOOL || m_Type == CVARTYPE_BYTE ||
175 m_Type == CVARTYPE_WORD || m_Type == CVARTYPE_INT;
176 bool floating_type = m_Type == CVARTYPE_FLOAT;
177 float valf = numerical_value ? atof(valstr) : 0.0f;
178
179 // perform rounding to nearest integer for integral types
180 if (integral_type)
181 valf = floor(valf + 0.5f);
182
183 valf = clamp(valf, m_MinValue, m_MaxValue);
184
185 if (numerical_value || integral_type || floating_type)
186 {
187 // generate m_String based on the clamped valf value
188 char tmp[32];
189 sprintf(tmp, "%g", valf);
190 m_String = tmp;
191 }
192 else
193 {
194 // just set m_String to valstr
195 if (valstr)
196 m_String = valstr;
197 else
198 m_String.clear();
199 }
200
201 m_Value = valf;
202
203 if (m_Flags & CVAR_USERINFO)
204 D_UserInfoChanged(this);
205 if (m_Flags & CVAR_SERVERINFO)
206 D_SendServerInfoChange(this, m_String.c_str());
207
208 if (m_UseCallback)
209 Callback();
210 }
211
212 m_Flags &= ~CVAR_ISDEFAULT;
213 }
214
215
ForceSet(float val)216 void cvar_t::ForceSet(float val)
217 {
218 char string[32];
219 sprintf(string, "%g", val);
220 ForceSet(string);
221 }
222
Set(const char * val)223 void cvar_t::Set (const char *val)
224 {
225 if (!(m_Flags & CVAR_NOSET) || !m_DoNoSet)
226 ForceSet (val);
227 }
228
Set(float val)229 void cvar_t::Set (float val)
230 {
231 if (!(m_Flags & CVAR_NOSET) || !m_DoNoSet)
232 ForceSet (val);
233 }
234
SetDefault(const char * val)235 void cvar_t::SetDefault (const char *val)
236 {
237 if(val)
238 m_Default = val;
239 else
240 m_Default = "";
241
242 if (m_Flags & CVAR_ISDEFAULT)
243 {
244 Set (val);
245 m_Flags |= CVAR_ISDEFAULT;
246 }
247 }
248
RestoreDefault()249 void cvar_t::RestoreDefault ()
250 {
251 Set(m_Default.c_str());
252 m_Flags |= CVAR_ISDEFAULT;
253 }
254
255 //
256 // cvar_t::Transfer
257 //
258 // Copies the value from one cvar to another and then removes the source cvar
259 //
Transfer(const char * fromname,const char * toname)260 void cvar_t::Transfer(const char *fromname, const char *toname)
261 {
262 cvar_t *from, *to, *dummy;
263
264 from = FindCVar(fromname, &dummy);
265 to = FindCVar(toname, &dummy);
266
267 if (from && to)
268 {
269 to->ForceSet(from->m_Value);
270 to->ForceSet(from->m_String.c_str());
271
272 // remove the old cvar
273 cvar_t *cur = ad.GetCVars();
274 while (cur->m_Next != from)
275 cur = cur->m_Next;
276
277 cur->m_Next = from->m_Next;
278 }
279 }
280
cvar_set(const char * var_name,const char * val)281 cvar_t *cvar_t::cvar_set (const char *var_name, const char *val)
282 {
283 cvar_t *var, *dummy;
284
285 if ( (var = FindCVar (var_name, &dummy)) )
286 var->Set (val);
287
288 return var;
289 }
290
cvar_forceset(const char * var_name,const char * val)291 cvar_t *cvar_t::cvar_forceset (const char *var_name, const char *val)
292 {
293 cvar_t *var, *dummy;
294
295 if ( (var = FindCVar (var_name, &dummy)) )
296 var->ForceSet (val);
297
298 return var;
299 }
300
EnableNoSet()301 void cvar_t::EnableNoSet ()
302 {
303 m_DoNoSet = true;
304 }
305
EnableCallbacks()306 void cvar_t::EnableCallbacks ()
307 {
308 m_UseCallback = true;
309 cvar_t *cvar = ad.GetCVars();
310
311 while (cvar)
312 {
313 cvar->Callback ();
314 cvar = cvar->m_Next;
315 }
316 }
317
sortcvars(const void * a,const void * b)318 static int STACK_ARGS sortcvars (const void *a, const void *b)
319 {
320 return strcmp (((*(cvar_t **)a))->name(), ((*(cvar_t **)b))->name());
321 }
322
FilterCompactCVars(TArray<cvar_t * > & cvars,DWORD filter)323 void cvar_t::FilterCompactCVars (TArray<cvar_t *> &cvars, DWORD filter)
324 {
325 cvar_t *cvar = ad.GetCVars();
326 while (cvar)
327 {
328 if (cvar->m_Flags & filter)
329 cvars.Push (cvar);
330 cvar = cvar->m_Next;
331 }
332 if (cvars.Size () > 0)
333 {
334 qsort (&cvars[0], cvars.Size (), sizeof(cvar_t *), sortcvars);
335 }
336 }
337
C_WriteCVars(byte ** demo_p,DWORD filter,bool compact)338 void cvar_t::C_WriteCVars (byte **demo_p, DWORD filter, bool compact)
339 {
340 cvar_t *cvar = ad.GetCVars();
341 byte *ptr = *demo_p;
342
343 if (compact)
344 {
345 TArray<cvar_t *> cvars;
346 ptr += sprintf ((char *)ptr, "\\\\%ux", (unsigned int)filter);
347 FilterCompactCVars (cvars, filter);
348 while (cvars.Pop (cvar))
349 {
350 ptr += sprintf ((char *)ptr, "\\%s", cvar->cstring());
351 }
352 }
353 else
354 {
355 cvar = ad.GetCVars();
356 while (cvar)
357 {
358 if (cvar->m_Flags & filter)
359 {
360 ptr += sprintf ((char *)ptr, "\\%s\\%s",
361 cvar->name(), cvar->cstring());
362 }
363 cvar = cvar->m_Next;
364 }
365 }
366
367 *demo_p = ptr + 1;
368 }
369
C_ReadCVars(byte ** demo_p)370 void cvar_t::C_ReadCVars (byte **demo_p)
371 {
372 char *ptr = *((char **)demo_p);
373 char *breakpt;
374
375 if (*ptr++ != '\\')
376 return;
377
378 if (*ptr == '\\')
379 { // compact mode
380 TArray<cvar_t *> cvars;
381 cvar_t *cvar;
382 DWORD filter;
383
384 ptr++;
385 breakpt = strchr (ptr, '\\');
386 *breakpt = 0;
387 filter = strtoul (ptr, NULL, 16);
388 *breakpt = '\\';
389 ptr = breakpt + 1;
390
391 FilterCompactCVars (cvars, filter);
392
393 while (cvars.Pop (cvar))
394 {
395 breakpt = strchr (ptr, '\\');
396 if (breakpt)
397 *breakpt = 0;
398 cvar->Set (ptr);
399 if (breakpt)
400 {
401 *breakpt = '\\';
402 ptr = breakpt + 1;
403 }
404 else
405 break;
406 }
407 }
408 else
409 {
410 char *value;
411
412 while ( (breakpt = strchr (ptr, '\\')) )
413 {
414 *breakpt = 0;
415 value = breakpt + 1;
416 if ( (breakpt = strchr (value, '\\')) )
417 *breakpt = 0;
418
419 cvar_set (ptr, value);
420
421 *(value - 1) = '\\';
422 if (breakpt)
423 {
424 *breakpt = '\\';
425 ptr = breakpt + 1;
426 }
427 else
428 {
429 break;
430 }
431 }
432 }
433 *demo_p += strlen (*((char **)demo_p)) + 1;
434 }
435
436 static struct backup_s
437 {
438 std::string name, string;
439 } CVarBackups[MAX_BACKUPCVARS];
440
441 static int numbackedup = 0;
442
443 //
444 // C_BackupCVars
445 //
446 // Backup cvars for restoration later. Called before connecting to a server
447 // or a demo starts playing to save all cvars which could be changed while
448 // by the server or by playing a demo.
449 // [SL] bitflag can be used to filter which cvars are set to default.
450 // The default value for bitflag is 0xFFFFFFFF, which effectively disables
451 // the filtering.
452 //
C_BackupCVars(unsigned int bitflag)453 void cvar_t::C_BackupCVars (unsigned int bitflag)
454 {
455 struct backup_s *backup = CVarBackups;
456 cvar_t *cvar = ad.GetCVars();
457
458 while (cvar)
459 {
460 if (cvar->m_Flags & bitflag)
461 {
462 if (backup == &CVarBackups[MAX_BACKUPCVARS])
463 I_Error ("C_BackupDemoCVars: Too many cvars to save (%d)", MAX_BACKUPCVARS);
464 backup->name = cvar->m_Name;
465 backup->string = cvar->m_String;
466 backup++;
467 }
468 cvar = cvar->m_Next;
469 }
470 numbackedup = backup - CVarBackups;
471 }
472
C_RestoreCVars(void)473 void cvar_t::C_RestoreCVars (void)
474 {
475 struct backup_s *backup = CVarBackups;
476 int i;
477
478 for (i = numbackedup; i; i--, backup++)
479 {
480 cvar_set (backup->name.c_str(), backup->string.c_str());
481 backup->name = backup->string = "";
482 }
483 numbackedup = 0;
484 UnlatchCVars();
485 }
486
FindCVar(const char * var_name,cvar_t ** prev)487 cvar_t *cvar_t::FindCVar (const char *var_name, cvar_t **prev)
488 {
489 cvar_t *var;
490
491 if (var_name == NULL)
492 return NULL;
493
494 var = ad.GetCVars();
495 *prev = NULL;
496 while (var)
497 {
498 if (iequals(var->m_Name, var_name))
499 break;
500 *prev = var;
501 var = var->m_Next;
502 }
503 return var;
504 }
505
UnlatchCVars(void)506 void cvar_t::UnlatchCVars (void)
507 {
508 cvar_t *var;
509
510 var = ad.GetCVars();
511 while (var)
512 {
513 if (var->m_Flags & (CVAR_MODIFIED | CVAR_LATCH))
514 {
515 unsigned oldflags = var->m_Flags & ~CVAR_MODIFIED;
516 var->m_Flags &= ~(CVAR_LATCH);
517 if (var->m_LatchedString.length())
518 {
519 var->Set (var->m_LatchedString.c_str());
520 var->m_LatchedString = "";
521 }
522 var->m_Flags = oldflags;
523 }
524 var = var->m_Next;
525 }
526 }
527
528 //
529 // C_SetCvarsToDefault
530 //
531 // Initialize cvars to default values after they are created.
532 // [SL] bitflag can be used to filter which cvars are set to default.
533 // The default value for bitflag is 0xFFFFFFFF, which effectively disables
534 // the filtering.
535 //
C_SetCVarsToDefaults(unsigned int bitflag)536 void cvar_t::C_SetCVarsToDefaults (unsigned int bitflag)
537 {
538 cvar_t *cvar = ad.GetCVars();
539
540 while (cvar)
541 {
542 if (cvar->m_Flags & bitflag)
543 {
544 if (cvar->m_Default.length())
545 cvar->Set (cvar->m_Default.c_str());
546 }
547
548 cvar = cvar->m_Next;
549 }
550 }
551
C_ArchiveCVars(void * f)552 void cvar_t::C_ArchiveCVars (void *f)
553 {
554 cvar_t *cvar = ad.GetCVars();
555
556 while (cvar)
557 {
558 if ((baseapp == client && (cvar->m_Flags & CVAR_CLIENTARCHIVE))
559 || (baseapp == server && (cvar->m_Flags & CVAR_SERVERARCHIVE)))
560 {
561 fprintf ((FILE *)f, "// %s\n", cvar->helptext());
562 fprintf ((FILE *)f, "set %s %s\n\n", C_QuoteString(cvar->name()).c_str(), C_QuoteString(cvar->cstring()).c_str());
563 }
564 cvar = cvar->m_Next;
565 }
566 }
567
cvarlist()568 void cvar_t::cvarlist()
569 {
570 cvar_t *var = ad.GetCVars();
571 int count = 0;
572
573 while (var)
574 {
575 unsigned flags = var->m_Flags;
576
577 count++;
578 Printf (PRINT_HIGH, "%c%c%c%c %s \"%s\"\n",
579 flags & CVAR_ARCHIVE ? 'A' :
580 flags & CVAR_CLIENTARCHIVE ? 'C' :
581 flags & CVAR_SERVERARCHIVE ? 'S' : ' ',
582 flags & CVAR_USERINFO ? 'U' : ' ',
583 flags & CVAR_SERVERINFO ? 'S' : ' ',
584 flags & CVAR_NOSET ? '-' :
585 flags & CVAR_LATCH ? 'L' :
586 flags & CVAR_UNSETTABLE ? '*' : ' ',
587 var->name(),
588 var->cstring());
589 var = var->m_Next;
590 }
591 Printf (PRINT_HIGH, "%d cvars\n", count);
592 }
593
594
C_GetValueString(const cvar_t * var)595 static std::string C_GetValueString(const cvar_t* var)
596 {
597 if (!var)
598 return "unset";
599
600 if (var->flags() & CVAR_NOENABLEDISABLE)
601 return '"' + var->str() + '"';
602
603 if (atof(var->cstring()) == 0.0f)
604 return "disabled";
605 else
606 return "enabled";
607 }
608
C_GetLatchedValueString(const cvar_t * var)609 static std::string C_GetLatchedValueString(const cvar_t* var)
610 {
611 if (!var)
612 return "unset";
613
614 if (!(var->flags() & CVAR_LATCH))
615 return C_GetValueString(var);
616
617 if (var->flags() & CVAR_NOENABLEDISABLE)
618 return '"' + var->latched() + '"';
619
620 if (atof(var->latched()) == 0.0f)
621 return "disabled";
622 else
623 return "enabled";
624 }
625
BEGIN_COMMAND(set)626 BEGIN_COMMAND (set)
627 {
628 if (argc != 3)
629 {
630 Printf (PRINT_HIGH, "usage: set <variable> <value>\n");
631 }
632 else
633 {
634 cvar_t *var, *prev;
635
636 var = cvar_t::FindCVar (argv[1], &prev);
637 if (!var)
638 var = new cvar_t(argv[1], NULL, "", CVARTYPE_NONE, CVAR_AUTO | CVAR_UNSETTABLE | cvar_defflags);
639
640 if (var->flags() & CVAR_NOSET)
641 {
642 Printf(PRINT_HIGH, "%s is write protected.\n", argv[1]);
643 return;
644 }
645 else if (multiplayer && baseapp == client && (var->flags() & CVAR_SERVERINFO))
646 {
647 Printf (PRINT_HIGH, "%s is under server control.\n", argv[1]);
648 return;
649 }
650
651 // [Russell] - Allow the user to specify either 'enable' and 'disable',
652 // this will get converted to either 1 or 0
653 // [AM] Introduce zdoom-standard "true" and "false"
654 if (!(var->flags() & CVAR_NOENABLEDISABLE))
655 {
656 if (strcmp("enabled", argv[2]) == 0 ||
657 strcmp("true", argv[2]) == 0)
658 {
659 argv[2] = (char *)"1";
660 }
661 else if (strcmp("disabled", argv[2]) == 0 ||
662 strcmp("false", argv[2]) == 0)
663 {
664 argv[2] = (char *)"0";
665 }
666 }
667
668 if (var->flags() & CVAR_LATCH)
669 {
670 // if new value is different from current value and latched value
671 if (strcmp(var->cstring(), argv[2]) && strcmp(var->latched(), argv[2]) && gamestate == GS_LEVEL)
672 Printf(PRINT_HIGH, "%s will be changed for next game.\n", argv[1]);
673 }
674
675 var->Set(argv[2]);
676 }
677 }
678 END_COMMAND (set)
679
BEGIN_COMMAND(get)680 BEGIN_COMMAND (get)
681 {
682 cvar_t *prev;
683 cvar_t *var;
684
685 if (argc < 2)
686 {
687 Printf (PRINT_HIGH, "usage: get <variable>\n");
688 return;
689 }
690
691 var = cvar_t::FindCVar (argv[1], &prev);
692
693 if (var)
694 {
695 // [AM] Determine whose control the cvar is under
696 std::string control;
697 if (multiplayer && baseapp == client && (var->flags() & CVAR_SERVERINFO))
698 control = " (server)";
699
700 // [Russell] - Don't make the user feel inadequate, tell
701 // them its either enabled, disabled or its other value
702 Printf(PRINT_HIGH, "\"%s\" is %s%s.\n",
703 var->name(), C_GetValueString(var).c_str(), control.c_str());
704
705 if (var->flags() & CVAR_LATCH && var->flags() & CVAR_MODIFIED)
706 Printf(PRINT_HIGH, "\"%s\" will be changed to %s.\n",
707 var->name(), C_GetLatchedValueString(var).c_str());
708 }
709 else
710 {
711 Printf(PRINT_HIGH, "\"%s\" is unset.\n", argv[1]);
712 }
713 }
714 END_COMMAND (get)
715
BEGIN_COMMAND(toggle)716 BEGIN_COMMAND (toggle)
717 {
718 cvar_t *prev;
719 cvar_t *var;
720
721 if (argc < 2)
722 {
723 Printf (PRINT_HIGH, "usage: toggle <variable>\n");
724 return;
725 }
726
727 var = cvar_t::FindCVar (argv[1], &prev);
728
729 if (!var)
730 {
731 Printf(PRINT_HIGH, "\"%s\" is unset.\n", argv[1]);
732 }
733 else if (var->flags() & CVAR_NOENABLEDISABLE)
734 {
735 Printf(PRINT_HIGH, "\"%s\" cannot be toggled.\n", argv[1]);
736 }
737 else
738 {
739 if (var->flags() & CVAR_LATCH && var->flags() & CVAR_MODIFIED)
740 var->Set(!atof(var->latched()));
741 else
742 var->Set(!var->value());
743
744 // [Russell] - Don't make the user feel inadequate, tell
745 // them its either enabled, disabled or its other value
746 Printf(PRINT_HIGH, "\"%s\" is %s.\n",
747 var->name(), C_GetValueString(var).c_str());
748
749 if (var->flags() & CVAR_LATCH && var->flags() & CVAR_MODIFIED)
750 Printf(PRINT_HIGH, "\"%s\" will be changed to %s.\n",
751 var->name(), C_GetLatchedValueString(var).c_str());
752 }
753 }
754 END_COMMAND (toggle)
755
BEGIN_COMMAND(cvarlist)756 BEGIN_COMMAND (cvarlist)
757 {
758 cvar_t::cvarlist();
759 }
760 END_COMMAND (cvarlist)
761
BEGIN_COMMAND(help)762 BEGIN_COMMAND (help)
763 {
764 cvar_t *prev;
765 cvar_t *var;
766
767 if (argc < 2)
768 {
769 Printf (PRINT_HIGH, "usage: help <variable>\n");
770 return;
771 }
772
773 var = cvar_t::FindCVar (argv[1], &prev);
774
775 if (!var)
776 {
777 Printf (PRINT_HIGH, "\"%s\" is unset.\n", argv[1]);
778 return;
779 }
780
781 Printf(PRINT_HIGH, "Help: %s - %s\n", var->name(), var->helptext());
782 }
783 END_COMMAND (help)
784
785 VERSION_CONTROL (c_cvars_cpp, "$Id: c_cvars.cpp 4521 2014-01-22 17:55:01Z dr_sean $")
786