Skip to content
Snippets Groups Projects
Select Git revision
  • b99ecde7b85ae78482762f0e73162b817e7edcfd
  • next default protected
  • fix-1277
  • fix-1258
  • delfile2
  • cleanupmusic
  • gametype-refactor-1
  • custom-map-names
  • extra-textures
  • clipmidtex
  • optimize-storewallrange
  • increase-maxconditionsets
  • acs
  • softcode-info
  • lua-gfx-2
  • better-player-states
  • lua-debug-library
  • any-resolution
  • gametype-refactor-player-spawns
  • custom-teams
  • action-args
  • SRB2_release_2.2.13
  • SRB2_release_2.2.12
  • SRB2_release_2.2.11
  • SRB2_release_2.2.10
  • SRB2_release_2.2.9
  • SRB2_release_2.2.8
  • SRB2_release_2.2.7
  • SRB2_release_2.2.6
  • SRB2_release_2.2.5
  • SRB2_release_2.2.4
  • SRB2_release_2.2.3
  • SRB2_release_2.2.2
  • SRB2_release_2.2.1
  • SRB2_release_2.2.0
  • SRB2_release_2.1.25
  • SRB2_release_2.1.24
  • SRB2_release_2.1.23
  • SRB2_release_2.1.22
  • SRB2_release_2.1.21
  • SRB2_release_2.1.20
41 results

lua_baselib.c

Blame
  • Forked from STJr / SRB2
    Source project has a limited visibility.
    command.c 39.88 KiB
    // SONIC ROBO BLAST 2
    //-----------------------------------------------------------------------------
    // Copyright (C) 1998-2000 by DooM Legacy Team.
    // Copyright (C) 1999-2014 by Sonic Team Junior.
    //
    // This program is free software distributed under the
    // terms of the GNU General Public License, version 2.
    // See the 'LICENSE' file for more details.
    //-----------------------------------------------------------------------------
    /// \file  command.c
    /// \brief Parse and execute commands from console input/scripts and remote server
    ///
    ///        Handles console variables, which is a simplified version
    ///        of commands, each consvar can have a function called when
    ///        it is modified.. thus it acts nearly as commands.
    ///
    ///        code shamelessly inspired by the QuakeC sources, thanks Id :)
    
    #include "doomdef.h"
    #include "doomstat.h"
    #include "command.h"
    #include "console.h"
    #include "z_zone.h"
    #include "m_menu.h"
    #include "m_misc.h"
    #include "m_fixed.h"
    #include "m_argv.h"
    #include "byteptr.h"
    #include "p_saveg.h"
    #include "g_game.h" // for player_names
    #include "d_netcmd.h"
    #include "hu_stuff.h"
    #include "p_setup.h"
    #include "lua_script.h"
    
    //========
    // protos.
    //========
    static boolean COM_Exists(const char *com_name);
    static void COM_ExecuteString(char *com_text);
    
    static void COM_Alias_f(void);
    static void COM_Echo_f(void);
    static void COM_CEcho_f(void);
    static void COM_CEchoFlags_f(void);
    static void COM_CEchoDuration_f(void);
    static void COM_Exec_f(void);
    static void COM_Wait_f(void);
    static void COM_Help_f(void);
    static void COM_Toggle_f(void);
    
    static boolean CV_Command(void);
    static consvar_t *CV_FindVar(const char *name);
    static const char *CV_StringValue(const char *var_name);
    static consvar_t *consvar_vars; // list of registered console variables
    
    static char com_token[1024];
    static char *COM_Parse(char *data);
    
    CV_PossibleValue_t CV_OnOff[] = {{0, "Off"}, {1, "On"}, {0, NULL}};
    CV_PossibleValue_t CV_YesNo[] = {{0, "No"}, {1, "Yes"}, {0, NULL}};
    CV_PossibleValue_t CV_Unsigned[] = {{0, "MIN"}, {999999999, "MAX"}, {0, NULL}};
    CV_PossibleValue_t CV_Natural[] = {{1, "MIN"}, {999999999, "MAX"}, {0, NULL}};
    
    #define COM_BUF_SIZE 8192 // command buffer size
    
    static INT32 com_wait; // one command per frame (for cmd sequences)
    
    // command aliases
    //
    typedef struct cmdalias_s
    {
    	struct cmdalias_s *next;
    	char *name;
    	char *value; // the command string to replace the alias
    } cmdalias_t;
    
    static cmdalias_t *com_alias; // aliases list
    
    // =========================================================================
    //                            COMMAND BUFFER
    // =========================================================================
    
    static vsbuf_t com_text; // variable sized buffer
    
    /** Adds text into the command buffer for later execution.
      *
      * \param ptext The text to add.
      * \sa COM_BufInsertText
      */
    void COM_BufAddText(const char *ptext)
    {
    	size_t l;
    
    	l = strlen(ptext);
    
    	if (com_text.cursize + l >= com_text.maxsize)
    	{
    		CONS_Alert(CONS_WARNING, M_GetText("Command buffer full!\n"));
    		return;
    	}
    	VS_Write(&com_text, ptext, l);
    }
    
    /** Adds command text and executes it immediately.
      *
      * \param ptext The text to execute. A newline is automatically added.
      * \sa COM_BufAddText
      */
    void COM_BufInsertText(const char *ptext)
    {
    	char *temp = NULL;
    	size_t templen;
    
    	// copy off any commands still remaining in the exec buffer
    	templen = com_text.cursize;
    	if (templen)
    	{
    		temp = M_Memcpy(ZZ_Alloc(templen), com_text.data, templen);
    		VS_Clear(&com_text);
    	}
    
    	// add the entire text of the file (or alias)
    	COM_BufAddText(ptext);
    	COM_BufExecute(); // do it right away
    
    	// add the copied off data
    	if (templen)
    	{
    		VS_Write(&com_text, temp, templen);
    		Z_Free(temp);
    	}
    }
    
    /** Flushes (executes) console commands in the buffer.
      */
    void COM_BufExecute(void)
    {
    	size_t i;
    	char *ptext;
    	char line[1024] = "";
    	INT32 quotes;
    
    	if (com_wait)
    	{
    		com_wait--;
    		return;
    	}
    
    	while (com_text.cursize)
    	{
    		// find a '\n' or; line break
    		ptext = (char *)com_text.data;
    
    		quotes = 0;
    		for (i = 0; i < com_text.cursize; i++)
    		{
    			if (ptext[i] == '\"' && !quotes && i > 0 && ptext[i-1] != ' ') // Malformed command
    				break;
    			if (ptext[i] == '\"')
    				quotes++;
    			if (!(quotes & 1) && ptext[i] == ';')
    				break; // don't break if inside a quoted string
    			if (ptext[i] == '\n' || ptext[i] == '\r')
    				break;
    		}
    
    		M_Memcpy(line, ptext, i);
    		line[i] = 0;
    
    		// flush the command text from the command buffer, _BEFORE_
    		// executing, to avoid that 'recursive' aliases overflow the
    		// command text buffer, in that case, new commands are inserted
    		// at the beginning, in place of the actual, so it doesn't
    		// overflow
    		if (i == com_text.cursize)
    			// the last command was just flushed
    			com_text.cursize = 0;
    		else
    		{
    			i++;
    			com_text.cursize -= i;
    			//memcpy(ptext, ptext+i, com_text.cursize); // Use memmove if the memory areas do overlap.
    			memmove(ptext, ptext+i, com_text.cursize);
    		}
    
    		// execute the command line
    		COM_ExecuteString(line);
    
    		// delay following commands if a wait was encountered
    		if (com_wait)
    		{
    			com_wait--;
    			break;
    		}
    	}
    }
    
    /** Executes a string immediately.  Used for skirting around WAIT commands.
      */
    void COM_ImmedExecute(const char *ptext)
    {
    	size_t i = 0, j = 0;
    	char line[1024] = "";
    	INT32 quotes;
    
    	while (i < strlen(ptext))
    	{
    
    		quotes = 0;
    		for (j = 0; i < strlen(ptext); i++,j++)
    		{
    			if (ptext[i] == '\"' && !quotes && i > 0 && ptext[i-1] != ' ') // Malformed command
    				return;
    			if (ptext[i] == '\"')
    				quotes++;
    			// don't break if inside a quoted string
    			if ((!(quotes & 1) && ptext[i] == ';') || ptext[i] == '\n' || ptext[i] == '\r')
    				break;
    		}
    
    		memcpy(line, ptext+(i-j), j);
    		line[j] = 0;
    
    		// execute the command line
    		COM_ExecuteString(line);
    
    		i++; // move to next character
    	}
    }
    
    // =========================================================================
    //                            COMMAND EXECUTION
    // =========================================================================
    
    typedef struct xcommand_s
    {
    	const char *name;
    	struct xcommand_s *next;
    	com_func_t function;
    } xcommand_t;
    
    static xcommand_t *com_commands = NULL; // current commands
    
    #define MAX_ARGS 80
    static size_t com_argc;
    static char *com_argv[MAX_ARGS];
    static const char *com_null_string = "";
    static char *com_args = NULL; // current command args or NULL
    
    static void Got_NetVar(UINT8 **p, INT32 playernum);
    
    /** Initializes command buffer and adds basic commands.
      */
    void COM_Init(void)
    {
    	// allocate command buffer
    	VS_Alloc(&com_text, COM_BUF_SIZE);
    
    	// add standard commands
    	COM_AddCommand("alias", COM_Alias_f);
    	COM_AddCommand("echo", COM_Echo_f);
    	COM_AddCommand("cecho", COM_CEcho_f);
    	COM_AddCommand("cechoflags", COM_CEchoFlags_f);
    	COM_AddCommand("cechoduration", COM_CEchoDuration_f);
    	COM_AddCommand("exec", COM_Exec_f);
    	COM_AddCommand("wait", COM_Wait_f);
    	COM_AddCommand("help", COM_Help_f);
    	COM_AddCommand("toggle", COM_Toggle_f);
    	RegisterNetXCmd(XD_NETVAR, Got_NetVar);
    }
    
    /** Gets a console command argument count.
      *
      * \return Number of arguments for the last command.
      * \sa COM_Argv
      */
    size_t COM_Argc(void)
    {
    	return com_argc;
    }
    
    /** Gets a console command argument.
      *
      * \param arg Index of the argument (0 to COM_Argc() - 1).
      * \return String pointer to the indicated argument.
      * \sa COM_Argc, COM_Args
      */
    const char *COM_Argv(size_t arg)
    {
    	if (arg >= com_argc || (signed)arg < 0)
    		return com_null_string;
    	return com_argv[arg];
    }
    
    /** Gets all console command arguments.
      *
      * \return String pointer to all arguments for the last command.
      * \sa COM_Argv
      */
    char *COM_Args(void)
    {
    	return com_args;
    }
    
    /** Checks if a parameter was passed to a console command.
      *
      * \param check The parameter to look for, e.g. "-noerror".
      * \return The index of the argument containing the parameter,
      *         or 0 if the parameter was not found.
      */
    size_t COM_CheckParm(const char *check)
    {
    	size_t i;
    
    	for (i = 1; i < com_argc; i++)
    		if (!strcasecmp(check, com_argv[i]))
    			return i;
    	return 0;
    }
    
    /** Parses a string into command-line tokens.
      *
      * \param ptext A null-terminated string. Does not need to be
      *             newline-terminated.
      */
    static void COM_TokenizeString(char *ptext)
    {
    	size_t i;
    
    	// Clear the args from the last string.
    	for (i = 0; i < com_argc; i++)
    		Z_Free(com_argv[i]);
    
    	com_argc = 0;
    	com_args = NULL;
    
    	while (com_argc < MAX_ARGS)
    	{
    		// Skip whitespace up to a newline.
    		while (*ptext != '\0' && *ptext <= ' ' && *ptext != '\n')
    			ptext++;
    
    		// A newline means end of command in buffer,
    		// thus end of this command's args too.
    		if (*ptext == '\n' || *ptext == '\0')
    			break;
    
    		if (com_argc == 1)
    			com_args = ptext;
    
    		ptext = COM_Parse(ptext);
    		if (ptext == NULL)
    			break;
    
    		com_argv[com_argc] = Z_StrDup(com_token);
    		com_argc++;
    	}
    }
    
    /** Adds a console command.
      *
      * \param name Name of the command.
      * \param func Function called when the command is run.
      */
    void COM_AddCommand(const char *name, com_func_t func)
    {
    	xcommand_t *cmd;
    
    	// fail if the command is a variable name
    	if (CV_StringValue(name)[0] != '\0')
    	{
    		I_Error("%s is a variable name\n", name);
    		return;
    	}
    
    	// fail if the command already exists
    	for (cmd = com_commands; cmd; cmd = cmd->next)
    	{
    		if (!stricmp(name, cmd->name)) //case insensitive now that we have lower and uppercase!
    		{
    			I_Error("Command %s already exists\n", name);
    			return;
    		}
    	}
    
    	cmd = ZZ_Alloc(sizeof *cmd);
    	cmd->name = name;
    	cmd->function = func;
    	cmd->next = com_commands;
    	com_commands = cmd;
    }
    
    /** Adds a console command for Lua.
      * No I_Errors allowed; return a negative code instead.
      *
      * \param name Name of the command.
      */
    int COM_AddLuaCommand(const char *name)
    {
    	xcommand_t *cmd;
    
    	// fail if the command is a variable name
    	if (CV_StringValue(name)[0] != '\0')
    		return -1;
    
    	// command already exists
    	for (cmd = com_commands; cmd; cmd = cmd->next)
    	{
    		if (!stricmp(name, cmd->name)) //case insensitive now that we have lower and uppercase!
    		{
    			// replace the built in command.
    			cmd->function = COM_Lua_f;
    			return 1;
    		}
    	}
    
    	// Add a new command.
    	cmd = ZZ_Alloc(sizeof *cmd);
    	cmd->name = name;
    	cmd->function = COM_Lua_f;
    	cmd->next = com_commands;
    	com_commands = cmd;
    	return 0;
    }
    
    /** Tests if a command exists.
      *
      * \param com_name Name to test for.
      * \return True if a command by the given name exists.
      */
    static boolean COM_Exists(const char *com_name)
    {
    	xcommand_t *cmd;
    
    	for (cmd = com_commands; cmd; cmd = cmd->next)
    		if (!stricmp(com_name, cmd->name))
    			return true;
    
    	return false;
    }
    
    /** Does command completion for the console.
      *
      * \param partial The partial name of the command (potentially).
      * \param skips   Number of commands to skip.
      * \return The complete command name, or NULL.
      * \sa CV_CompleteVar
      */
    const char *COM_CompleteCommand(const char *partial, INT32 skips)
    {
    	xcommand_t *cmd;
    	size_t len;
    
    	len = strlen(partial);
    
    	if (!len)
    		return NULL;
    
    	// check functions
    	for (cmd = com_commands; cmd; cmd = cmd->next)
    		if (!strncmp(partial, cmd->name, len))
    			if (!skips--)
    				return cmd->name;
    
    	return NULL;
    }
    
    /** Parses a single line of text into arguments and tries to execute it.
      * The text can come from the command buffer, a remote client, or stdin.
      *
      * \param ptext A single line of text.
      */
    static void COM_ExecuteString(char *ptext)
    {
    	xcommand_t *cmd;
    	cmdalias_t *a;
    
    	COM_TokenizeString(ptext);
    
    	// execute the command line
    	if (COM_Argc() == 0)
    		return; // no tokens
    
    	// check functions
    	for (cmd = com_commands; cmd; cmd = cmd->next)
    	{
    		if (!stricmp(com_argv[0], cmd->name)) //case insensitive now that we have lower and uppercase!
    		{
    			cmd->function();
    			return;
    		}
    	}
    
    	// check aliases
    	for (a = com_alias; a; a = a->next)
    	{
    		if (!stricmp(com_argv[0], a->name))
    		{
    			COM_BufInsertText(a->value);
    			return;
    		}
    	}
    
    	// check cvars
    	// Hurdler: added at Ebola's request ;)
    	// (don't flood the console in software mode with bad gr_xxx command)
    	if (!CV_Command() && con_destlines)
    		CONS_Printf(M_GetText("Unknown command '%s'\n"), COM_Argv(0));
    }
    
    // =========================================================================
    //                            SCRIPT COMMANDS
    // =========================================================================
    
    /** Creates a command name that replaces another command.
      */
    static void COM_Alias_f(void)
    {
    	cmdalias_t *a;
    	char cmd[1024];
    	size_t i, c;
    
    	if (COM_Argc() < 3)
    	{
    		CONS_Printf(M_GetText("alias <name> <command>: create a shortcut command that executes other command(s)\n"));
    		return;
    	}
    
    	a = ZZ_Alloc(sizeof *a);
    	a->next = com_alias;
    	com_alias = a;
    
    	a->name = Z_StrDup(COM_Argv(1));
    
    	// copy the rest of the command line
    	cmd[0] = 0; // start out with a null string
    	c = COM_Argc();
    	for (i = 2; i < c; i++)
    	{
    		strcat(cmd, COM_Argv(i));
    		if (i != c)
    			strcat(cmd, " ");
    	}
    	strcat(cmd, "\n");
    
    	a->value = Z_StrDup(cmd);
    }
    
    /** Prints a line of text to the console.
      */
    static void COM_Echo_f(void)
    {
    	size_t i;
    
    	for (i = 1; i < COM_Argc(); i++)
    		CONS_Printf("%s ", COM_Argv(i));
    	CONS_Printf("\n");
    }
    
    /** Displays text on the center of the screen for a short time.
      */
    static void COM_CEcho_f(void)
    {
    	size_t i;
    	char cechotext[1024] = "";
    
    	for (i = 1; i < COM_Argc(); i++)
    	{
    		strncat(cechotext, COM_Argv(i), sizeof(cechotext)-1);
    		strncat(cechotext, " ", sizeof(cechotext)-1);
    	}
    
    	cechotext[sizeof(cechotext) - 1] = '\0';
    
    	HU_DoCEcho(cechotext);
    }
    
    /** Sets drawing flags for the CECHO command.
      */
    static void COM_CEchoFlags_f(void)
    {
    	if (COM_Argc() > 1)
    	{
    		const char *arg = COM_Argv(1);
    
    		if (arg[0] && arg[0] == '0' &&
    			arg[1] && arg[1] == 'x') // Use hexadecimal!
    			HU_SetCEchoFlags(axtoi(arg+2));
    		else
    			HU_SetCEchoFlags(atoi(arg));
    	}
    	else
    		CONS_Printf(M_GetText("cechoflags <flags>: set CEcho flags, prepend with 0x to use hexadecimal\n"));
    }
    
    /** Sets the duration for CECHO commands to stay on the screen
      */
    static void COM_CEchoDuration_f(void)
    {
    	if (COM_Argc() > 1)
    		HU_SetCEchoDuration(atoi(COM_Argv(1)));
    }
    
    /** Executes a script file.
      */
    static void COM_Exec_f(void)
    {
    	UINT8 *buf = NULL;
    
    	if (COM_Argc() < 2 || COM_Argc() > 3)
    	{
    		CONS_Printf(M_GetText("exec <filename>: run a script file\n"));
    		return;
    	}
    
    	// load file
    	FIL_ReadFile(COM_Argv(1), &buf);
    
    	if (!buf)
    	{
    		if (!COM_CheckParm("-noerror"))
    			CONS_Printf(M_GetText("couldn't execute file %s\n"), COM_Argv(1));
    		return;
    	}
    
    	if (!COM_CheckParm("-silent"))
    		CONS_Printf(M_GetText("executing %s\n"), COM_Argv(1));
    
    	// insert text file into the command buffer
    	COM_BufAddText((char *)buf);
    	COM_BufAddText("\n");
    
    	// free buffer
    	Z_Free(buf);
    }
    
    /** Delays execution of the rest of the commands until the next frame.
      * Allows sequences of commands like "jump; fire; backward".
      */
    static void COM_Wait_f(void)
    {
    	if (COM_Argc() > 1)
    		com_wait = atoi(COM_Argv(1));
    	else
    		com_wait = 1; // 1 frame
    }
    
    /** Prints help on variables and commands.
      */
    static void COM_Help_f(void)
    {
    	xcommand_t *cmd;
    	consvar_t *cvar;
    	INT32 i = 0;
    
    	if (COM_Argc() > 1)
    	{
    		cvar = CV_FindVar(COM_Argv(1));
    		if (cvar)
    		{
    			CONS_Printf(M_GetText("Variable %s:\n"), cvar->name);
    			CONS_Printf(M_GetText("  flags :"));
    			if (cvar->flags & CV_SAVE)
    				CONS_Printf("AUTOSAVE ");
    			if (cvar->flags & CV_FLOAT)
    				CONS_Printf("FLOAT ");
    			if (cvar->flags & CV_NETVAR)
    				CONS_Printf("NETVAR ");
    			if (cvar->flags & CV_CALL)
    				CONS_Printf("ACTION ");
    			if (cvar->flags & CV_CHEAT)
    				CONS_Printf("CHEAT ");
    			CONS_Printf("\n");
    			if (cvar->PossibleValue)
    			{
    				if (stricmp(cvar->PossibleValue[0].strvalue, "MIN") == 0)
    				{
    					for (i = 1; cvar->PossibleValue[i].strvalue != NULL; i++)
    						if (!stricmp(cvar->PossibleValue[i].strvalue, "MAX"))
    							break;
    					CONS_Printf(M_GetText("  range from %d to %d\n"), cvar->PossibleValue[0].value,
    						cvar->PossibleValue[i].value);
    					CONS_Printf(M_GetText(" Current value: %d\n"), cvar->value);
    				}
    				else
    				{
    					const char *cvalue = NULL;
    					CONS_Printf(M_GetText("  possible value : %s\n"), cvar->name);
    					while (cvar->PossibleValue[i].strvalue)
    					{
    						CONS_Printf("    %-2d : %s\n", cvar->PossibleValue[i].value,
    							cvar->PossibleValue[i].strvalue);
    						if (cvar->PossibleValue[i].value == cvar->value)
    							cvalue = cvar->PossibleValue[i].strvalue;
    						i++;
    					}
    					if (cvalue)
    						CONS_Printf(M_GetText(" Current value: %s\n"), cvalue);
    					else
    						CONS_Printf(M_GetText(" Current value: %d\n"), cvar->value);
    				}
    			}
    			else
    				CONS_Printf(M_GetText(" Current value: %d\n"), cvar->value);
    		}
    		else
    			CONS_Printf(M_GetText("No help for this command/variable\n"));
    	}
    	else
    	{
    		// commands
    		CONS_Printf("\x82%s", M_GetText("Commands\n"));
    		for (cmd = com_commands; cmd; cmd = cmd->next)
    		{
    			CONS_Printf("%s ",cmd->name);
    			i++;
    		}
    
    		// variables
    		CONS_Printf("\n\x82%s", M_GetText("Variables\n"));
    		for (cvar = consvar_vars; cvar; cvar = cvar->next)
    		{
    			if (!(cvar->flags & CV_NOSHOWHELP))
    				CONS_Printf("%s ", cvar->name);
    			i++;
    		}
    
    		CONS_Printf("\n\x82%s", M_GetText("Read help file for more or type help <command or variable>\n"));
    
    		CONS_Debug(DBG_GAMELOGIC, "\x82Total : %d\n", i);
    	}
    }
    
    /** Toggles a console variable. Useful for on/off values.
      *
      * This works on on/off, yes/no values only
      */
    static void COM_Toggle_f(void)
    {
    	consvar_t *cvar;
    
    	if (COM_Argc() != 2)
    	{
    		CONS_Printf(M_GetText("Toggle <cvar_name>: Toggle the value of a cvar\n"));
    		return;
    	}
    	cvar = CV_FindVar(COM_Argv(1));
    	if (!cvar)
    	{
    		CONS_Alert(CONS_NOTICE, M_GetText("%s is not a cvar\n"), COM_Argv(1));
    		return;
    	}
    
    	if (!(cvar->PossibleValue == CV_YesNo || cvar->PossibleValue == CV_OnOff))
    	{
    		CONS_Alert(CONS_NOTICE, M_GetText("%s is not a boolean value\n"), COM_Argv(1));
    		return;
    	}
    
    	// netcvar don't change imediately
    	cvar->flags |= CV_SHOWMODIFONETIME;
    	CV_AddValue(cvar, +1);
    }
    
    // =========================================================================
    //                      VARIABLE SIZE BUFFERS
    // =========================================================================
    
    /** Initializes a variable size buffer.
      *
      * \param buf      Buffer to initialize.
      * \param initsize Initial size for the buffer.
      */
    void VS_Alloc(vsbuf_t *buf, size_t initsize)
    {
    #define VSBUFMINSIZE 256
    	if (initsize < VSBUFMINSIZE)
    		initsize = VSBUFMINSIZE;
    	buf->data = Z_Malloc(initsize, PU_STATIC, NULL);
    	buf->maxsize = initsize;
    	buf->cursize = 0;
    #undef VSBUFMINSIZE
    }
    
    /** Frees a variable size buffer.
      *
      * \param buf Buffer to free.
      */
    void VS_Free(vsbuf_t *buf)
    {
    	buf->cursize = 0;
    }
    
    /** Clears a variable size buffer.
      *
      * \param buf Buffer to clear.
      */
    void VS_Clear(vsbuf_t *buf)
    {
    	buf->cursize = 0;
    }
    
    /** Makes sure a variable size buffer has enough space for data of a
      * certain length.
      *
      * \param buf    The buffer. It is enlarged if necessary.
      * \param length The length of data we need to add.
      * \return Pointer to where the new data can go.
      */
    void *VS_GetSpace(vsbuf_t *buf, size_t length)
    {
    	void *data;
    
    	if (buf->cursize + length > buf->maxsize)
    	{
    		if (!buf->allowoverflow)
    			I_Error("overflow 111");
    
    		if (length > buf->maxsize)
    			I_Error("overflow l%s 112", sizeu1(length));
    
    		buf->overflowed = true;
    		CONS_Printf("VS buffer overflow");
    		VS_Clear(buf);
    	}
    
    	data = buf->data + buf->cursize;
    	buf->cursize += length;
    
    	return data;
    }
    
    /** Copies data to the end of a variable size buffer.
      *
      * \param buf    The buffer.
      * \param data   The data to copy.
      * \param length The length of the data.
      * \sa VS_Print
      */
    void VS_Write(vsbuf_t *buf, const void *data, size_t length)
    {
    	M_Memcpy(VS_GetSpace(buf, length), data, length);
    }
    
    /** Prints text in a variable buffer. Like VS_Write() plus a
      * trailing NUL.
      *
      * \param buf  The buffer.
      * \param data The NUL-terminated string.
      * \sa VS_Write
      */
    void VS_Print(vsbuf_t *buf, const char *data)
    {
    	size_t len;
    
    	len = strlen(data) + 1;
    
    	if (buf->data[buf->cursize-1])
    		M_Memcpy((UINT8 *)VS_GetSpace(buf, len), data, len); // no trailing 0
    	else
    		M_Memcpy((UINT8 *)VS_GetSpace(buf, len-1) - 1, data, len); // write over trailing 0
    }
    
    // =========================================================================
    //
    //                           CONSOLE VARIABLES
    //
    //   console variables are a simple way of changing variables of the game
    //   through the console or code, at run time.
    //
    //   console vars acts like simplified commands, because a function can be
    //   attached to them, and called whenever a console var is modified
    //
    // =========================================================================
    
    static const char *cv_null_string = "";
    
    /** Searches if a variable has been registered.
      *
      * \param name Variable to search for.
      * \return Pointer to the variable if found, or NULL.
      * \sa CV_FindNetVar
      */
    static consvar_t *CV_FindVar(const char *name)
    {
    	consvar_t *cvar;
    
    	for (cvar = consvar_vars; cvar; cvar = cvar->next)
    		if (!stricmp(name,cvar->name))
    			return cvar;
    
    	return NULL;
    }
    
    /** Builds a unique Net Variable identifier number, which is used
      * in network packets instead of the full name.
      *
      * \param s Name of the variable.
      * \return A new unique identifier.
      * \sa CV_FindNetVar
      */
    static inline UINT16 CV_ComputeNetid(const char *s)
    {
    	UINT16 ret = 0, i = 0;
    	static UINT16 premiers[16] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53};
    
    	while (*s)
    	{
    		ret = (UINT16)(ret + (*s)*premiers[i]);
    		s++;
    		i = (UINT16)((i+1) % 16);
    	}
    	return ret;
    }
    
    /** Finds a net variable based on its identifier number.
      *
      * \param netid The variable's identifier number.
      * \return A pointer to the variable itself if found, or NULL.
      * \sa CV_ComputeNetid
      */
    static consvar_t *CV_FindNetVar(UINT16 netid)
    {
    	consvar_t *cvar;
    
    	for (cvar = consvar_vars; cvar; cvar = cvar->next)
    		if (cvar->netid == netid)
    			return cvar;
    
    	return NULL;
    }
    
    static void Setvalue(consvar_t *var, const char *valstr, boolean stealth);
    
    /** Registers a variable for later use from the console.
      *
      * \param variable The variable to register.
      */
    void CV_RegisterVar(consvar_t *variable)
    {
    	// first check to see if it has already been defined
    	if (CV_FindVar(variable->name))
    	{
    		CONS_Printf(M_GetText("Variable %s is already defined\n"), variable->name);
    		return;
    	}
    
    	// check for overlap with a command
    	if (COM_Exists(variable->name))
    	{
    		CONS_Printf(M_GetText("%s is a command name\n"), variable->name);
    		return;
    	}
    
    	// check net variables
    	if (variable->flags & CV_NETVAR)
    	{
    		variable->netid = CV_ComputeNetid(variable->name);
    		if (CV_FindNetVar(variable->netid))
    			I_Error("Variables %s and %s have same netid\n", variable->name, CV_FindNetVar(variable->netid)->name);
    	}
    
    	// link the variable in
    	if (!(variable->flags & CV_HIDEN))
    	{
    		variable->next = consvar_vars;
    		consvar_vars = variable;
    	}
    	variable->string = variable->zstring = NULL;
    	variable->changed = 0; // new variable has not been modified by the user
    
    #ifdef PARANOIA
    	if ((variable->flags & CV_NOINIT) && !(variable->flags & CV_CALL))
    		I_Error("variable %s has CV_NOINIT without CV_CALL\n", variable->name);
    	if ((variable->flags & CV_CALL) && !variable->func)
    		I_Error("variable %s has CV_CALL without a function\n", variable->name);
    #endif
    
    	if (variable->flags & CV_NOINIT)
    		variable->flags &= ~CV_CALL;
    
    	Setvalue(variable, variable->defaultvalue, false);
    
    	if (variable->flags & CV_NOINIT)
    		variable->flags |= CV_CALL;
    
    	// the SetValue will set this bit
    	variable->flags &= ~CV_MODIFIED;
    }
    
    /** Finds the string value of a console variable.
      *
      * \param var_name The variable's name.
      * \return The string value or "" if the variable is not found.
      */
    static const char *CV_StringValue(const char *var_name)
    {
    	consvar_t *var;
    
    	var = CV_FindVar(var_name);
    	if (!var)
    		return cv_null_string;
    	return var->string;
    }
    
    /** Completes the name of a console variable.
      *
      * \param partial The partial name of the variable (potentially).
      * \param skips   Number of variables to skip.
      * \return The complete variable name, or NULL.
      * \sa COM_CompleteCommand
      */
    const char *CV_CompleteVar(char *partial, INT32 skips)
    {
    	consvar_t *cvar;
    	size_t len;
    
    	len = strlen(partial);
    
    	if (!len)
    		return NULL;
    
    	// check variables
    	for (cvar = consvar_vars; cvar; cvar = cvar->next)
    		if (!strncmp(partial, cvar->name, len))
    			if (!skips--)
    				return cvar->name;
    
    	return NULL;
    }
    
    /** Sets a value to a variable with less checking. Only for internal use.
      *
      * \param var    Variable to set.
      * \param valstr String value for the variable.
      */
    static void Setvalue(consvar_t *var, const char *valstr, boolean stealth)
    {
    	boolean override = false;
    	INT32 overrideval = 0;
    
    	// If we want messages informing us if cheats have been enabled or disabled,
    	// we need to rework the consvars a little bit.  This call crashes the game
    	// on load because not all variables will be registered at that time.
    /*	boolean prevcheats = false;
    	if (var->flags & CV_CHEAT)
    		prevcheats = CV_CheatsEnabled(); */
    
    	if (var->PossibleValue)
    	{
    		INT32 v = atoi(valstr);
    		if (!v && valstr[0] != '0')
    			v = INT32_MIN; // Invalid integer trigger
    
    		if (var->PossibleValue[0].strvalue && !stricmp(var->PossibleValue[0].strvalue, "MIN")) // bounded cvar
    		{
    			INT32 i;
    			// search for maximum
    			for (i = 1; var->PossibleValue[i].strvalue; i++)
    				if (!stricmp(var->PossibleValue[i].strvalue, "MAX"))
    					break;
    #ifdef PARANOIA
    			if (!var->PossibleValue[i].strvalue)
    				I_Error("Bounded cvar \"%s\" without maximum!\n", var->name);
    #endif
    
    			if ((v != INT32_MIN && v < var->PossibleValue[0].value) || !stricmp(valstr, "MIN"))
    			{
    				v = var->PossibleValue[0].value;
    				valstr = var->PossibleValue[0].strvalue;
    				override = true;
    				overrideval = v;
    			}
    			else if ((v != INT32_MIN && v > var->PossibleValue[i].value) || !stricmp(valstr, "MAX"))
    			{
    				v = var->PossibleValue[i].value;
    				valstr = var->PossibleValue[i].strvalue;
    				override = true;
    				overrideval = v;
    			}
    			if (v == INT32_MIN)
    				goto badinput;
    		}
    		else
    		{
    			INT32 i;
    
    			// check first strings
    			for (i = 0; var->PossibleValue[i].strvalue; i++)
    				if (!stricmp(var->PossibleValue[i].strvalue, valstr))
    					goto found;
    			if (v != INT32_MIN)
    			{
    				// check INT32 now
    				for (i = 0; var->PossibleValue[i].strvalue; i++)
    					if (v == var->PossibleValue[i].value)
    						goto found;
    			}
    			// Not found ... but wait, there's hope!
    			if (var->PossibleValue == CV_OnOff || var->PossibleValue == CV_YesNo)
    			{
    				overrideval = -1;
    				if (!stricmp(valstr, "on") || !stricmp(valstr, "yes"))
    					overrideval = 1;
    				else if (!stricmp(valstr, "off") || !stricmp(valstr, "no"))
    					overrideval = 0;
    
    				if (overrideval != -1)
    				{
    					for (i = 0; var->PossibleValue[i].strvalue; i++)
    						if (overrideval == var->PossibleValue[i].value)
    							goto found;
    				}
    			}
    
    			// ...or not.
    			goto badinput;
    found:
    			var->value = var->PossibleValue[i].value;
    			var->string = var->PossibleValue[i].strvalue;
    			goto finish;
    		}
    	}
    
    	// free the old value string
    	Z_Free(var->zstring);
    
    	var->string = var->zstring = Z_StrDup(valstr);
    
    	if (var->flags & CV_FLOAT)
    	{
    		double d = atof(var->string);
    		var->value = (INT32)(d * FRACUNIT);
    	}
    	else if (override)
    		var->value = overrideval;
    	else
    		var->value = atoi(var->string);
    
    finish:
    	// See the note above.
    /* 	if (var->flags & CV_CHEAT)
    	{
    		boolean newcheats = CV_CheatsEnabled();
    
    		if (!prevcheats && newcheats)
    			CONS_Printf(M_GetText("Cheats have been enabled.\n"));
    		else if (prevcheats && !newcheats)
    			CONS_Printf(M_GetText("Cheats have been disabled.\n"));
    	} */
    
    	if (var->flags & CV_SHOWMODIFONETIME || var->flags & CV_SHOWMODIF)
    	{
    		CONS_Printf(M_GetText("%s set to %s\n"), var->name, var->string);
    		var->flags &= ~CV_SHOWMODIFONETIME;
    	}
    	DEBFILE(va("%s set to %s\n", var->name, var->string));
    	var->flags |= CV_MODIFIED;
    	// raise 'on change' code
    #ifdef HAVE_BLUA
    	LUA_CVarChanged(var->name); // let consolelib know what cvar this is.
    #endif
    	if (var->flags & CV_CALL && !stealth)
    		var->func();
    
    	return;
    
    // landing point for possiblevalue failures
    badinput:
    
    	if (var != &cv_nextmap) // Suppress errors for cv_nextmap
    		CONS_Printf(M_GetText("\"%s\" is not a possible value for \"%s\"\n"), valstr, var->name);
    
    	// default value not valid... ?!
    	if (var->defaultvalue == valstr)
    		I_Error("Variable %s default value \"%s\" is not a possible value\n", var->name, var->defaultvalue);
    }
    
    //
    // Use XD_NETVAR argument:
    //      2 byte for variable identification
    //      then the value of the variable followed with a 0 byte (like str)
    //
    
    static boolean serverloading = false;
    
    static void Got_NetVar(UINT8 **p, INT32 playernum)
    {
    	consvar_t *cvar;
    	UINT16 netid;
    	char *svalue;
    	UINT8 stealth = false;
    
    	if (playernum != serverplayer && playernum != adminplayer && !serverloading)
    	{
    		// not from server or remote admin, must be hacked/buggy client
    		CONS_Alert(CONS_WARNING, M_GetText("Illegal netvar command received from %s\n"), player_names[playernum]);
    
    		if (server)
    		{
    			XBOXSTATIC UINT8 buf[2];
    
    			buf[0] = (UINT8)playernum;
    			buf[1] = KICK_MSG_CON_FAIL;
    			SendNetXCmd(XD_KICK, &buf, 2);
    		}
    		return;
    	}
    	netid = READUINT16(*p);
    	cvar = CV_FindNetVar(netid);
    	svalue = (char *)*p;
    	SKIPSTRING(*p);
    	stealth = READUINT8(*p);
    
    	if (!cvar)
    	{
    		CONS_Alert(CONS_WARNING, "Netvar not found with netid %hu\n", netid);
    		return;
    	}
    #if 0 //defined (GP2X) || defined (PSP)
    	CONS_Printf("Netvar received: %s [netid=%d] value %s\n", cvar->name, netid, svalue);
    #endif
    	DEBFILE(va("Netvar received: %s [netid=%d] value %s\n", cvar->name, netid, svalue));
    
    	Setvalue(cvar, svalue, stealth);
    }
    
    void CV_SaveNetVars(UINT8 **p)
    {
    	consvar_t *cvar;
    	UINT8 *count_p = *p;
    	UINT16 count = 0;
    
    	// send only changed cvars ...
    	// the client will reset all netvars to default before loading
    	WRITEUINT16(*p, 0x0000);
    	for (cvar = consvar_vars; cvar; cvar = cvar->next)
    		if ((cvar->flags & CV_NETVAR) && !CV_IsSetToDefault(cvar))
    		{
    			WRITEUINT16(*p, cvar->netid);
    			WRITESTRING(*p, cvar->string);
    			WRITEUINT8(*p, false);
    			++count;
    		}
    	WRITEUINT16(count_p, count);
    }
    
    void CV_LoadNetVars(UINT8 **p)
    {
    	consvar_t *cvar;
    	UINT16 count;
    
    	// prevent "invalid command received"
    	serverloading = true;
    
    	for (cvar = consvar_vars; cvar; cvar = cvar->next)
    		if (cvar->flags & CV_NETVAR)
    			Setvalue(cvar, cvar->defaultvalue, true);
    
    	count = READUINT16(*p);
    	while (count--)
    		Got_NetVar(p, 0);
    
    	serverloading = false;
    }
    
    void CV_ResetCheatNetVars(void)
    {
    	consvar_t *cvar;
    
    	// Stealthset everything back to default.
    	for (cvar = consvar_vars; cvar; cvar = cvar->next)
    		if (cvar->flags & CV_CHEAT)
    			Setvalue(cvar, cvar->defaultvalue, true);
    }
    
    // Returns true if the variable's current value is its default value
    boolean CV_IsSetToDefault(consvar_t *v)
    {
    	return (!(strcmp(v->defaultvalue, v->string)));
    }
    
    // If any cheats CVars are not at their default settings, return true.
    // Else return false.
    // This returns a UINT8 because I'm too lazy to deal with the packet structure.
    // Deal with it. =P
    UINT8 CV_CheatsEnabled(void)
    {
    	consvar_t *cvar;
    
    	for (cvar = consvar_vars; cvar; cvar = cvar->next)
    		if ((cvar->flags & CV_CHEAT) && strcmp(cvar->defaultvalue, cvar->string))
    			return 1;
    	return 0;
    }
    
    /** Sets a value to a variable, performing some checks and calling the
      * callback function if there is one.
      * Does as if "<varname> <value>" is entered at the console.
      *
      * \param var   The variable.
      * \param value The string value.
      * \sa CV_StealthSet, CV_SetValue
      */
    static void CV_SetCVar(consvar_t *var, const char *value, boolean stealth)
    {
    #ifdef PARANOIA
    	if (!var)
    		I_Error("CV_Set: no variable\n");
    	if (!var->string)
    		I_Error("CV_Set: %s no string set!\n", var->name);
    #endif
    	if (!var || !var->string || !value || !stricmp(var->string, value))
    		return; // no changes
    
    	// Don't allow skin/color changes in single player
    	if ((var == &cv_skin || var == &cv_playercolor) &&
    		!(cv_debug || devparm) && !(multiplayer || netgame)
    		&& (gamestate == GS_LEVEL || gamestate == GS_INTERMISSION))
    	{
    		return;
    	}
    
    	if (var->flags & CV_NETVAR)
    	{
    		// send the value of the variable
    		XBOXSTATIC UINT8 buf[128];
    		UINT8 *p = buf;
    		if (!(server || (adminplayer == consoleplayer)))
    		{
    			CONS_Printf(M_GetText("Only the server or admin can change: %s %s\n"), var->name, var->string);
    			return;
    		}
    
    		// Only add to netcmd buffer if in a netgame, otherwise, just change it.
    		if (netgame || multiplayer)
    		{
    			WRITEUINT16(p, var->netid);
    			WRITESTRING(p, value);
    			WRITEUINT8(p, stealth);
    
    			SendNetXCmd(XD_NETVAR, buf, p-buf);
    		}
    		else
    			Setvalue(var, value, stealth);
    	}
    	else
    		if ((var->flags & CV_NOTINNET) && netgame)
    		{
    			CONS_Printf(M_GetText("This variable can't be changed while in netgame: %s %s\n"), var->name, var->string);
    			return;
    		}
    		else
    			Setvalue(var, value, stealth);
    }
    
    /** Sets a value to a variable without calling its callback function.
      *
      * \param var   The variable.
      * \param value The string value.
      * \sa CV_Set, CV_StealthSetValue
      */
    void CV_StealthSet(consvar_t *var, const char *value)
    {
    	CV_SetCVar(var, value, true);
    }
    
    /** Sets a numeric value to a variable without calling its callback
      * function.
      *
      * \param var   The variable.
      * \param value The numeric value, converted to a string before setting.
      * \sa CV_SetValue, CV_StealthSet
      */
    void CV_StealthSetValue(consvar_t *var, INT32 value)
    {
    	char val[32];
    
    	sprintf(val, "%d", value);
    	CV_SetCVar(var, val, true);
    }
    
    // New wrapper for what used to be CV_Set()
    void CV_Set(consvar_t *var, const char *value)
    {
    	CV_SetCVar(var, value, false);
    }
    
    /** Sets a numeric value to a variable, performing some checks and
      * calling the callback function if there is one.
      *
      * \param var   The variable.
      * \param value The numeric value, converted to a string before setting.
      * \sa CV_Set, CV_StealthSetValue
      */
    void CV_SetValue(consvar_t *var, INT32 value)
    {
    	char val[32];
    
    	sprintf(val, "%d", value);
    	CV_SetCVar(var, val, false);
    }
    
    /** Adds a value to a console variable.
      * Used to increment and decrement variables from the menu.
      * Contains special cases to handle pointlimit in some multiplayer modes,
      * map number for game hosting, etc.
      *
      * \param var       The variable to add to.
      * \param increment The change in the variable; can be negative for a
      *                  decrement.
      * \sa CV_SetValue
      */
    void CV_AddValue(consvar_t *var, INT32 increment)
    {
    	INT32 newvalue, max;
    
    	// count pointlimit better
    	if (var == &cv_pointlimit && (gametype == GT_MATCH
    #ifdef CHAOSISNOTDEADYET
    		|| gametype == GT_CHAOS
    #endif
    		))
    		increment *= 50;
    	newvalue = var->value + increment;
    
    	if (var->PossibleValue)
    	{
    #define MINVAL 0
    		if (var == &cv_nextmap)
    		{
    			// Special case for the nextmap variable, used only directly from the menu
    			INT32 oldvalue = var->value - 1, gt;
    			gt = cv_newgametype.value;
    			if (increment != 0) // Going up!
    			{
    				newvalue = var->value - 1;
    				do
    				{
    					if(increment > 0) // Going up!
    					{
    						newvalue++;
    						if (newvalue == NUMMAPS)
    							newvalue = 0;
    					}
    					else // Going down!
    					{
    						newvalue--;
    						if (newvalue == -1)
    							newvalue = NUMMAPS-1;
    					}
    
    					if (newvalue == oldvalue)
    						gt = -1; // don't loop forever if there's none of a certain gametype
    
    					if(!mapheaderinfo[newvalue])
    						continue; // Don't allocate the header.  That just makes memory usage skyrocket.
    
    				} while (newvalue != oldvalue && !M_CanShowLevelInList(newvalue, gt));
    
    				var->value = newvalue + 1;
    				var->func();
    				return;
    			}
    		}
    		else if (var->PossibleValue[MINVAL].strvalue && !strcmp(var->PossibleValue[MINVAL].strvalue, "MIN"))
    		{
    			// search the next to last
    			for (max = 0; var->PossibleValue[max+1].strvalue; max++)
    				;
    
    			if (newvalue < var->PossibleValue[MINVAL].value) // add the max+1
    				newvalue += var->PossibleValue[max].value - var->PossibleValue[MINVAL].value + 1;
    
    			newvalue = var->PossibleValue[MINVAL].value + (newvalue - var->PossibleValue[MINVAL].value)
    				% (var->PossibleValue[max].value - var->PossibleValue[MINVAL].value + 1);
    
    			CV_SetValue(var, newvalue);
    #undef MINVAL
    		}
    		else
    		{
    			INT32 currentindice = -1, newindice;
    
    			// this code do not support more than same value for differant PossibleValue
    			for (max = 0; var->PossibleValue[max].strvalue; max++)
    				if (var->PossibleValue[max].value == var->value)
    					currentindice = max;
    
    			max--;
    
    			if (var == &cv_chooseskin)
    			{
    				// Special case for the chooseskin variable, used only directly from the menu
    				if (increment > 0) // Going up!
    				{
    					newvalue = var->value - 1;
    					do
    					{
    						newvalue++;
    						if (newvalue == MAXSKINS)
    							newvalue = 0;
    					} while (var->PossibleValue[newvalue].strvalue == NULL);
    					var->value = newvalue + 1;
    					var->string = var->PossibleValue[newvalue].strvalue;
    					var->func();
    					return;
    				}
    				else if (increment < 0) // Going down!
    				{
    					newvalue = var->value - 1;
    					do
    					{
    						newvalue--;
    						if (newvalue == -1)
    							newvalue = MAXSKINS-1;
    					} while (var->PossibleValue[newvalue].strvalue == NULL);
    					var->value = newvalue + 1;
    					var->string = var->PossibleValue[newvalue].strvalue;
    					var->func();
    					return;
    				}
    			}
    #ifdef PARANOIA
    			if (currentindice == -1)
    				I_Error("CV_AddValue: current value %d not found in possible value\n",
    					var->value);
    #endif
    
    			newindice = (currentindice + increment + max + 1) % (max+1);
    			CV_Set(var, var->PossibleValue[newindice].strvalue);
    		}
    	}
    	else
    		CV_SetValue(var, newvalue);
    
    	var->changed = 1; // user has changed it now
    }
    
    /** Displays or changes a variable from the console.
      * Since the user is presumed to have been directly responsible
      * for this change, the variable is marked as changed this game.
      *
      * \return False if passed command was not recognized as a console
      *         variable, otherwise true.
      * \sa CV_ClearChangedFlags
      */
    static boolean CV_Command(void)
    {
    	consvar_t *v;
    
    	// check variables
    	v = CV_FindVar(COM_Argv(0));
    	if (!v)
    		return false;
    
    	// perform a variable print or set
    	if (COM_Argc() == 1)
    	{
    		CONS_Printf(M_GetText("\"%s\" is \"%s\" default is \"%s\"\n"), v->name, v->string, v->defaultvalue);
    		return true;
    	}
    
    	CV_Set(v, COM_Argv(1));
    	v->changed = 1; // now it's been changed by (presumably) the user
    	return true;
    }
    
    /** Marks all variables as unchanged, indicating they've not been changed
      * by the user this game.
      *
      * \sa CV_Command
      * \author Graue <graue@oceanbase.org>
      */
    void CV_ClearChangedFlags(void)
    {
    	consvar_t *cvar;
    
    	for (cvar = consvar_vars; cvar; cvar = cvar->next)
    		cvar->changed = 0;
    }
    
    /** Saves console variables to a file if they have the ::CV_SAVE
      * flag set.
      *
      * \param f File to save to.
      */
    void CV_SaveVariables(FILE *f)
    {
    	consvar_t *cvar;
    
    	for (cvar = consvar_vars; cvar; cvar = cvar->next)
    		if (cvar->flags & CV_SAVE)
    		{
    			char stringtowrite[MAXTEXTCMD+1];
    
    			// Silly hack for Min/Max vars
    			if (!strcmp(cvar->string, "MAX") || !strcmp(cvar->string, "MIN"))
    				sprintf(stringtowrite, "%d", cvar->value);
    			else
    				strcpy(stringtowrite, cvar->string);
    
    			fprintf(f, "%s \"%s\"\n", cvar->name, stringtowrite);
    		}
    }
    
    //============================================================================
    //                            SCRIPT PARSE
    //============================================================================
    
    /** Parses a token out of a string. Handles script files too.
      *
      * \param data String to parse.
      * \return The data pointer after the token. NULL if no token found.
      */
    static char *COM_Parse(char *data)
    {
    	char c;
    	size_t len = 0;
    
    	com_token[0] = 0;
    
    	if (data == NULL)
    		return NULL;
    
    	// skip whitespace
    skipwhite:
    	while ((c = *data) <= ' ')
    	{
    		if (c == '\0')
    			return NULL; // end of file;
    		data++;
    	}
    
    	// skip // comments
    	if (c == '/' && data[1] == '/')
    	{
    		while (*data && *data != '\n')
    			data++;
    		goto skipwhite;
    	}
    
    	// handle quoted strings specially
    	if (c == '\"')
    	{
    		data++;
    		for (;;)
    		{
    			c = *data++;
    			if (c == '\"' || c == '\0')
    			{
    				com_token[len] = 0;
    				return data;
    			}
    			com_token[len] = c;
    			len++;
    		}
    	}
    
    	// parse single characters
    	if (c == '{' || c == '}' || c == ')' || c == '(' || c == '\'')
    	{
    		com_token[len] = c;
    		len++;
    		com_token[len] = 0;
    		return data + 1;
    	}
    
    	// parse a regular word
    	do
    	{
    		com_token[len] = c;
    		data++;
    		len++;
    		c = *data;
    		if (c == '{' || c == '}' || c == ')'|| c == '(' || c == '\'')
    			break;
    	} while (c > 32);
    
    	com_token[len] = 0;
    	return data;
    }