mirror of
https://github.com/nzp-team/fteqw.git
synced 2024-11-10 14:42:13 +00:00
648 lines
14 KiB
C
648 lines
14 KiB
C
|
/*
|
||
|
Copyright (C) 1996-1997 Id Software, Inc.
|
||
|
|
||
|
This program is free software; you can redistribute it and/or
|
||
|
modify it under the terms of the GNU General Public License
|
||
|
as published by the Free Software Foundation; either version 2
|
||
|
of the License, or (at your option) any later version.
|
||
|
|
||
|
This program is distributed in the hope that it will be useful,
|
||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||
|
|
||
|
See the GNU General Public License for more details.
|
||
|
|
||
|
You should have received a copy of the GNU General Public License
|
||
|
along with this program; if not, write to the Free Software
|
||
|
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||
|
|
||
|
*/
|
||
|
// cvar.c -- dynamic variable tracking
|
||
|
|
||
|
#ifdef SERVERONLY
|
||
|
#include "qwsvdef.h"
|
||
|
#else
|
||
|
#include "quakedef.h"
|
||
|
#endif
|
||
|
|
||
|
cvar_group_t *cvar_groups;
|
||
|
|
||
|
//cvar_t *cvar_vars;
|
||
|
char *cvar_null_string = "";
|
||
|
|
||
|
/*
|
||
|
============
|
||
|
Cvar_FindVar
|
||
|
============
|
||
|
*/
|
||
|
cvar_t *Cvar_FindVar (char *var_name)
|
||
|
{
|
||
|
cvar_group_t *grp;
|
||
|
cvar_t *var;
|
||
|
|
||
|
for (grp=cvar_groups ; grp ; grp=grp->next)
|
||
|
for (var=grp->cvars ; var ; var=var->next)
|
||
|
if (!Q_strcasecmp (var_name, var->name))
|
||
|
return var;
|
||
|
|
||
|
for (grp=cvar_groups ; grp ; grp=grp->next)
|
||
|
for (var=grp->cvars ; var ; var=var->next)
|
||
|
if (var->name2 && !Q_strcasecmp (var_name, var->name2))
|
||
|
return var;
|
||
|
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
cvar_group_t *Cvar_FindGroup (char *group_name)
|
||
|
{
|
||
|
cvar_group_t *grp;
|
||
|
|
||
|
for (grp=cvar_groups ; grp ; grp=grp->next)
|
||
|
if (!Q_strcasecmp (group_name, grp->name))
|
||
|
return grp;
|
||
|
|
||
|
return NULL;
|
||
|
}
|
||
|
cvar_group_t *Cvar_GetGroup(char *gname)
|
||
|
{
|
||
|
cvar_group_t *g;
|
||
|
if (!gname)
|
||
|
gname = "Miscilaneous vars";
|
||
|
g = Cvar_FindGroup(gname);
|
||
|
if (g)
|
||
|
return g;
|
||
|
|
||
|
g = Z_Malloc(sizeof(cvar_group_t));
|
||
|
g->name = gname;
|
||
|
g->next = NULL;
|
||
|
|
||
|
g->next = cvar_groups;
|
||
|
cvar_groups = g;
|
||
|
|
||
|
return g;
|
||
|
}
|
||
|
|
||
|
//lists commands, also prints restriction level
|
||
|
void Cvar_List_f (void)
|
||
|
{
|
||
|
cvar_group_t *grp;
|
||
|
cvar_t *cmd;
|
||
|
int num=0;
|
||
|
for (grp=cvar_groups ; grp ; grp=grp->next)
|
||
|
for (cmd=grp->cvars ; cmd ; cmd=cmd->next)
|
||
|
{
|
||
|
if ((cmd->restriction?cmd->restriction:rcon_level.value) > Cmd_ExecLevel)
|
||
|
continue;
|
||
|
if (!num)
|
||
|
Con_TPrintf(TL_CVARLISTHEADER);
|
||
|
Con_Printf("(%2i) %s\n", (int)(cmd->restriction?cmd->restriction:rcon_level.value), cmd->name);
|
||
|
num++;
|
||
|
}
|
||
|
if (num)
|
||
|
Con_Printf("\n");
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
============
|
||
|
Cvar_VariableValue
|
||
|
============
|
||
|
*/
|
||
|
float Cvar_VariableValue (char *var_name)
|
||
|
{
|
||
|
cvar_t *var;
|
||
|
|
||
|
var = Cvar_FindVar (var_name);
|
||
|
if (!var)
|
||
|
return 0;
|
||
|
return Q_atof (var->string);
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
============
|
||
|
Cvar_VariableString
|
||
|
============
|
||
|
*/
|
||
|
char *Cvar_VariableString (char *var_name)
|
||
|
{
|
||
|
cvar_t *var;
|
||
|
|
||
|
var = Cvar_FindVar (var_name);
|
||
|
if (!var)
|
||
|
return cvar_null_string;
|
||
|
return var->string;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
============
|
||
|
Cvar_CompleteVariable
|
||
|
============
|
||
|
*/
|
||
|
/* moved to cmd_compleatevariable
|
||
|
char *Cvar_CompleteVariable (char *partial)
|
||
|
{
|
||
|
cvar_group_t *grp;
|
||
|
cvar_t *cvar;
|
||
|
int len;
|
||
|
|
||
|
len = Q_strlen(partial);
|
||
|
|
||
|
if (!len)
|
||
|
return NULL;
|
||
|
|
||
|
// check exact match
|
||
|
for (grp=cvar_groups ; grp ; grp=grp->next)
|
||
|
for (cvar=grp->cvars ; cvar ; cvar=cvar->next)
|
||
|
if (!strcmp (partial,cvar->name))
|
||
|
return cvar->name;
|
||
|
|
||
|
// check partial match
|
||
|
for (grp=cvar_groups ; grp ; grp=grp->next)
|
||
|
for (cvar=grp->cvars ; cvar ; cvar=cvar->next)
|
||
|
if (!Q_strncmp (partial,cvar->name, len))
|
||
|
return cvar->name;
|
||
|
|
||
|
return NULL;
|
||
|
}
|
||
|
*/
|
||
|
|
||
|
|
||
|
#ifdef SERVERONLY
|
||
|
void SV_SendServerInfoChange(char *key, char *value);
|
||
|
#endif
|
||
|
|
||
|
/*
|
||
|
============
|
||
|
Cvar_Set
|
||
|
============
|
||
|
*/
|
||
|
cvar_t *Cvar_SetCore (cvar_t *var, char *value, qboolean force)
|
||
|
{
|
||
|
char *latch=NULL;
|
||
|
|
||
|
if (!var)
|
||
|
return NULL;
|
||
|
|
||
|
if ((var->flags & CVAR_NOSET) && !force)
|
||
|
{
|
||
|
Con_Printf ("variable %s is readonly\n", var->name);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
if (var->flags & CVAR_SERVEROVERRIDE && !force)
|
||
|
latch = "variable %s is under server control - latched\n";
|
||
|
else if (var->flags & CVAR_LATCH)
|
||
|
latch = "variable %s is latched\n";
|
||
|
else if (var->flags & CVAR_RENDERERLATCH && qrenderer)
|
||
|
latch = "variable %s will be changed after a renderer restart\n";
|
||
|
#ifndef SERVERONLY
|
||
|
else if (var->flags & CVAR_CHEAT && !cls.allow_cheats)
|
||
|
latch = "variable %s is a cheat variable - latched\n";
|
||
|
else if (var->flags & CVAR_SEMICHEAT && !cls.allow_semicheats)
|
||
|
latch = "variable %s is a cheat variable - latched\n";
|
||
|
#endif
|
||
|
|
||
|
if (latch && !force)
|
||
|
{
|
||
|
if (cl_warncmd.value)
|
||
|
Con_Printf (latch, var->name);
|
||
|
|
||
|
if (var->latched_string && !strcmp(var->latched_string, value)) //no point, this would force the same
|
||
|
return NULL;
|
||
|
if (var->latched_string)
|
||
|
Z_Free(var->latched_string);
|
||
|
if (!strcmp(var->string, value)) //latch to the origional value? remove the latch.
|
||
|
{
|
||
|
var->latched_string = NULL;
|
||
|
return NULL;
|
||
|
}
|
||
|
var->latched_string = Z_Malloc(strlen(value)+1);
|
||
|
strcpy(var->latched_string, value);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
#ifndef CLIENTONLY
|
||
|
if (var->flags & CVAR_SERVERINFO)
|
||
|
{
|
||
|
Info_SetValueForKey (svs.info, var->name, value, MAX_SERVERINFO_STRING);
|
||
|
SV_SendServerInfoChange(var->name, value);
|
||
|
// SV_BroadcastCommand ("fullserverinfo \"%s\"\n", svs.info);
|
||
|
}
|
||
|
#endif
|
||
|
#ifndef SERVERONLY
|
||
|
if (var->flags & CVAR_USERINFO)
|
||
|
{
|
||
|
Info_SetValueForKey (cls.userinfo, var->name, value, MAX_INFO_STRING);
|
||
|
if (cls.state >= ca_connected)
|
||
|
{
|
||
|
#ifdef Q2CLIENT
|
||
|
if (cls.q2server) //q2 just resends the lot. Kinda bad...
|
||
|
{
|
||
|
cls.resendinfo = true;
|
||
|
}
|
||
|
else
|
||
|
#endif
|
||
|
{
|
||
|
MSG_WriteByte (&cls.netchan.message, clc_stringcmd);
|
||
|
SZ_Print (&cls.netchan.message, va("setinfo \"%s\" \"%s\"\n", var->name, value));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
if (var->string)
|
||
|
{
|
||
|
if (strcmp(var->string, value))
|
||
|
var->modified++; //only modified if it changed.
|
||
|
|
||
|
Z_Free (var->string); // free the old value string
|
||
|
}
|
||
|
|
||
|
var->string = Z_Malloc (Q_strlen(value)+1);
|
||
|
Q_strcpy (var->string, value);
|
||
|
var->value = Q_atof (var->string);
|
||
|
|
||
|
if (var->latched_string) //we may as well have this here.
|
||
|
{
|
||
|
Z_Free(var->latched_string);
|
||
|
var->latched_string = NULL;
|
||
|
}
|
||
|
|
||
|
return var;
|
||
|
}
|
||
|
|
||
|
void Cvar_ForceCheatVars(qboolean semicheats, qboolean absolutecheats)
|
||
|
{ //this either unlatches if the cheat type is allowed, or enforces a default for full cheats and blank for semicheats.
|
||
|
//this is clientside only.
|
||
|
//if a value is enforced, it is latched to the old value.
|
||
|
cvar_group_t *grp;
|
||
|
cvar_t *var;
|
||
|
|
||
|
char *latch;
|
||
|
|
||
|
for (grp=cvar_groups ; grp ; grp=grp->next)
|
||
|
for (var=grp->cvars ; var ; var=var->next)
|
||
|
{
|
||
|
if (!(var->flags & (CVAR_CHEAT|CVAR_SEMICHEAT)))
|
||
|
continue;
|
||
|
|
||
|
latch = var->latched_string;
|
||
|
var->latched_string = NULL;
|
||
|
if (!latch)
|
||
|
{
|
||
|
latch = var->string;
|
||
|
var->string = NULL;
|
||
|
}
|
||
|
|
||
|
if (var->flags & CVAR_CHEAT)
|
||
|
{
|
||
|
if (!absolutecheats)
|
||
|
Cvar_ForceSet(var, var->defaultstr);
|
||
|
else
|
||
|
Cvar_ForceSet(var, latch);
|
||
|
}
|
||
|
if (var->flags & CVAR_SEMICHEAT)
|
||
|
{
|
||
|
if (!semicheats)
|
||
|
Cvar_ForceSet(var, "");
|
||
|
else
|
||
|
Cvar_ForceSet(var, latch);
|
||
|
}
|
||
|
|
||
|
if (latch)
|
||
|
{
|
||
|
if (!strcmp(var->string, latch))
|
||
|
Z_Free(latch);
|
||
|
else
|
||
|
var->latched_string = latch;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void Cvar_ApplyLatches(int latchflag)
|
||
|
{
|
||
|
cvar_group_t *grp;
|
||
|
cvar_t *var;
|
||
|
for (grp=cvar_groups ; grp ; grp=grp->next)
|
||
|
for (var=grp->cvars ; var ; var=var->next)
|
||
|
{
|
||
|
if (var->flags & latchflag)
|
||
|
{
|
||
|
if (var->latched_string)
|
||
|
{
|
||
|
var->flags &= ~CVAR_NOSET;
|
||
|
Cvar_ForceSet(var, var->latched_string);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
cvar_t *Cvar_Set (cvar_t *var, char *value)
|
||
|
{
|
||
|
return Cvar_SetCore(var, value, false);
|
||
|
}
|
||
|
cvar_t *Cvar_ForceSet (cvar_t *var, char *value)
|
||
|
{
|
||
|
return Cvar_SetCore(var, value, true);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
============
|
||
|
Cvar_SetValue
|
||
|
============
|
||
|
*/
|
||
|
void Cvar_SetValue (cvar_t *var, float value)
|
||
|
{
|
||
|
char val[32];
|
||
|
|
||
|
if (value == (int)value)
|
||
|
sprintf (val, "%i",(int)value); //make it look nicer.
|
||
|
else
|
||
|
sprintf (val, "%f",value);
|
||
|
Cvar_Set (var, val);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
============
|
||
|
Cvar_RegisterVariable
|
||
|
|
||
|
Adds a freestanding variable to the variable list.
|
||
|
============
|
||
|
*/
|
||
|
void Cvar_Register (cvar_t *variable, char *groupname)
|
||
|
{
|
||
|
cvar_t *old;
|
||
|
cvar_group_t *group;
|
||
|
char value[512];
|
||
|
|
||
|
// first check to see if it has allready been defined
|
||
|
old = Cvar_FindVar (variable->name);
|
||
|
if (old)
|
||
|
{
|
||
|
if (old->flags & CVAR_POINTER)
|
||
|
{
|
||
|
cvar_t *prev;
|
||
|
group = Cvar_GetGroup(groupname);
|
||
|
|
||
|
variable->next = old->next;
|
||
|
variable->latched_string = old->latched_string;
|
||
|
variable->string = old->string;
|
||
|
variable->modified = old->modified;
|
||
|
variable->value = old->value;
|
||
|
|
||
|
//cheat prevention - engine set default is the one that stays.
|
||
|
Z_Free(variable->defaultstr);
|
||
|
variable->defaultstr = Z_Malloc (strlen(variable->string)+1); //give it it's default (for server controlled vars and things)
|
||
|
strcpy (variable->defaultstr, variable->string);
|
||
|
|
||
|
if (group->cvars == old)
|
||
|
group->cvars = variable;
|
||
|
else
|
||
|
{
|
||
|
for (prev = group->cvars; prev; prev++)
|
||
|
{
|
||
|
if (prev->next == old)
|
||
|
{
|
||
|
prev->next = variable;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if (!prev) //this should never happen
|
||
|
Sys_Error("Cvar was not linked\n");
|
||
|
}
|
||
|
Z_Free(old);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
Con_Printf ("Can't register variable %s, allready defined\n", variable->name);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// check for overlap with a command
|
||
|
if (Cmd_Exists (variable->name))
|
||
|
{
|
||
|
Con_Printf ("Cvar_RegisterVariable: %s is a command\n", variable->name);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
group = Cvar_GetGroup(groupname);
|
||
|
|
||
|
// link the variable in
|
||
|
variable->next = group->cvars;
|
||
|
variable->restriction = 0; //exe registered vars
|
||
|
group->cvars = variable;
|
||
|
|
||
|
// copy the value off, because future sets will Z_Free it
|
||
|
strcpy (value, variable->string);
|
||
|
variable->string = Z_Malloc (1);
|
||
|
|
||
|
variable->defaultstr = Z_Malloc (strlen(value)+1); //give it it's default (for server controlled vars and things)
|
||
|
strcpy (variable->defaultstr, value);
|
||
|
|
||
|
// set it through the function to be consistant
|
||
|
Cvar_SetCore (variable, value, true);
|
||
|
}
|
||
|
/*
|
||
|
void Cvar_RegisterVariable (cvar_t *variable)
|
||
|
{
|
||
|
Cvar_Register(variable, NULL);
|
||
|
}
|
||
|
*/
|
||
|
cvar_t *Cvar_Get(char *name, char *defaultvalue, int flags, char *group)
|
||
|
{
|
||
|
cvar_t *var;
|
||
|
var = Cvar_FindVar(name);
|
||
|
|
||
|
if (var)
|
||
|
{
|
||
|
//allow this to change all < cvar_latch values.
|
||
|
//this allows q2 dlls to apply different flags to a cvar without destroying our important ones (like cheat).
|
||
|
var->flags = (flags & (CVAR_LATCH-1)) | (var->flags & ~(CVAR_LATCH-1));
|
||
|
return var;
|
||
|
}
|
||
|
|
||
|
var = Z_Malloc(sizeof(cvar_t)+strlen(name)+1);
|
||
|
var->name = (char *)(var+1);
|
||
|
strcpy(var->name, name);
|
||
|
var->string = defaultvalue;
|
||
|
var->flags = flags|CVAR_POINTER;
|
||
|
|
||
|
Cvar_Register(var, group);
|
||
|
|
||
|
return var;
|
||
|
}
|
||
|
|
||
|
//prevent the client from altering the cvar until they change map or the server resets the var to the default.
|
||
|
void Cvar_LockFromServer(cvar_t *var, char *str)
|
||
|
{
|
||
|
char *oldlatch;
|
||
|
|
||
|
var->flags |= CVAR_SERVEROVERRIDE;
|
||
|
|
||
|
oldlatch = var->latched_string;
|
||
|
if (oldlatch) //maintaining control
|
||
|
var->latched_string = NULL;
|
||
|
else //taking control
|
||
|
{
|
||
|
oldlatch = Z_Malloc(strlen(var->string)+1);
|
||
|
strcpy(oldlatch, var->string);
|
||
|
}
|
||
|
|
||
|
Cvar_SetCore (var, str, true); //will use all, quote included
|
||
|
|
||
|
var->latched_string = oldlatch; //keep track of the origional value.
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
============
|
||
|
Cvar_Command
|
||
|
|
||
|
Handles variable inspection and changing from the console
|
||
|
============
|
||
|
*/
|
||
|
qboolean Cvar_Command (int level)
|
||
|
{
|
||
|
cvar_t *v;
|
||
|
char *str;
|
||
|
|
||
|
// check variables
|
||
|
v = Cvar_FindVar (Cmd_Argv(0));
|
||
|
if (!v)
|
||
|
return false;
|
||
|
|
||
|
if ((v->restriction?v->restriction:rcon_level.value) > level)
|
||
|
{
|
||
|
Con_Printf ("You do not have the priveledges for %s\n", v->name);
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
if (v->flags & CVAR_NOTFROMSERVER && Cmd_FromServer())
|
||
|
{
|
||
|
Con_Printf ("Server tried setting %s cvar\n", v->name);
|
||
|
return true;
|
||
|
}
|
||
|
// perform a variable print or set
|
||
|
if (Cmd_Argc() == 1)
|
||
|
{
|
||
|
Con_Printf ("\"%s\" is \"%s\"\n", v->name, v->string);
|
||
|
if (v->latched_string)
|
||
|
Con_Printf ("Latched as \"%s\"\n", v->latched_string);
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
if (Cmd_Argc() == 2)
|
||
|
str = Cmd_Argv(1);
|
||
|
else
|
||
|
str = Cmd_Args();
|
||
|
|
||
|
if (v->flags & CVAR_NOSET)
|
||
|
{
|
||
|
Con_Printf ("Cvar %s may not be set via the console\n", v->name);
|
||
|
return true;
|
||
|
}
|
||
|
#ifndef SERVERONLY
|
||
|
if (Cmd_ExecLevel > RESTRICT_SERVER)
|
||
|
{ //directed at a secondary player.
|
||
|
char *msg;
|
||
|
msg = va("%i setinfo %s \"%s\"", Cmd_ExecLevel - RESTRICT_SERVER-1, v->name, str);
|
||
|
MSG_WriteByte (&cls.netchan.message, clc_stringcmd);
|
||
|
MSG_WriteString (&cls.netchan.message, msg);
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
if (v->flags & CVAR_SERVEROVERRIDE)
|
||
|
{
|
||
|
if (Cmd_FromServer())
|
||
|
{
|
||
|
if (!strcmp(v->defaultstr, str)) //returning to default
|
||
|
{
|
||
|
v->flags &= ~CVAR_SERVEROVERRIDE;
|
||
|
if (v->latched_string)
|
||
|
str = v->latched_string; //set to the latched
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Cvar_LockFromServer(v, str);
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
//let cvar_set latch if needed.
|
||
|
}
|
||
|
else if (Cmd_FromServer())
|
||
|
{
|
||
|
Cvar_LockFromServer(v, str);
|
||
|
return true;
|
||
|
}
|
||
|
#endif
|
||
|
Cvar_Set (v, str); //will use all, quote included
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
============
|
||
|
Cvar_WriteVariables
|
||
|
|
||
|
Writes lines containing "set variable value" for all variables
|
||
|
with the archive flag set to true.
|
||
|
============
|
||
|
*/
|
||
|
void Cvar_WriteVariables (FILE *f, qboolean all)
|
||
|
{
|
||
|
qboolean writtengroupheader;
|
||
|
cvar_group_t *grp;
|
||
|
cvar_t *var;
|
||
|
char *val;
|
||
|
|
||
|
for (grp=cvar_groups ; grp ; grp=grp->next)
|
||
|
{
|
||
|
writtengroupheader = false;
|
||
|
for (var = grp->cvars ; var ; var = var->next)
|
||
|
if (var->flags & CVAR_ARCHIVE || all)
|
||
|
{
|
||
|
if (!writtengroupheader)
|
||
|
{
|
||
|
writtengroupheader = true;
|
||
|
fprintf(f, "\n// %s\n", grp->name);
|
||
|
}
|
||
|
|
||
|
val = var->string; //latched vars should act differently.
|
||
|
if (var->latched_string)
|
||
|
val = var->latched_string;
|
||
|
|
||
|
if (var->flags & CVAR_USERCREATED)
|
||
|
{
|
||
|
if (var->flags & CVAR_ARCHIVE)
|
||
|
fprintf (f, "seta %s \"%s\"\n", var->name, val);
|
||
|
else
|
||
|
fprintf (f, "set %s \"%s\"\n", var->name, val);
|
||
|
}
|
||
|
else
|
||
|
fprintf (f, "%s \"%s\"\n", var->name, val);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void Cvar_Shutdown(void)
|
||
|
{
|
||
|
cvar_t *var;
|
||
|
cvar_group_t *grp;
|
||
|
while(cvar_groups)
|
||
|
{
|
||
|
while(cvar_groups->cvars)
|
||
|
{
|
||
|
var = cvar_groups->cvars;
|
||
|
cvar_groups->cvars = var->next;
|
||
|
|
||
|
Z_Free(var->string);
|
||
|
if (var->flags & CVAR_POINTER)
|
||
|
Z_Free(var);
|
||
|
}
|
||
|
|
||
|
grp = cvar_groups;
|
||
|
cvar_groups = grp->next;
|
||
|
Z_Free(grp);
|
||
|
}
|
||
|
}
|