/*
==============================================================================

                                 DOOM Retro
           The classic, refined DOOM source port. For Windows PC.

==============================================================================

    Copyright © 1993-2026 by id Software LLC, a ZeniMax Media company.
    Copyright © 2013-2026 by Brad Harding <mailto:brad@doomretro.com>.

    This file is a part of DOOM Retro.

    DOOM Retro 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 3 of the license, or (at your
    option) any later version.

    DOOM Retro 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 DOOM Retro. If not, see <https://www.gnu.org/licenses/>.

    DOOM is a registered trademark of id Software LLC, a ZeniMax Media
    company, in the US and/or other countries, and is used without
    permission. All other trademarks are the property of their respective
    holders. DOOM Retro is in no way affiliated with nor endorsed by
    id Software.

==============================================================================
*/

#include <float.h>

#if defined(_WIN32)
#include <Windows.h>
#include <ShellAPI.h>
#endif

#include "am_map.h"
#include "c_cmds.h"
#include "c_console.h"
#include "d_deh.h"
#include "d_main.h"
#include "doomstat.h"
#include "g_game.h"
#include "hu_stuff.h"
#include "i_colors.h"
#include "i_controller.h"
#include "i_system.h"
#include "i_timer.h"
#include "m_cheat.h"
#include "m_config.h"
#include "m_menu.h"
#include "m_misc.h"
#include "m_random.h"
#include "md5.h"
#include "p_inter.h"
#include "p_local.h"
#include "p_setup.h"
#include "p_tick.h"
#include "r_sky.h"
#include "s_sound.h"
#include "sc_man.h"
#include "st_lib.h"
#include "st_stuff.h"
#include "v_video.h"
#include "version.h"
#include "w_wad.h"

#define ALIASFORMAT                     BOLDITALICS("alias") " [[" BOLD("\"") "]" BOLDITALICS("command") "[" BOLD(";") " " \
                                        BOLDITALICS("command") " ..." BOLD("\"") "]]"
#define BINDFORMAT                      BOLDITALICS("control") " [" BOLDITALICS("+action") "|[" BOLD("\"") "]" BOLDITALICS("command") "[" \
                                        BOLD(";") " " BOLDITALICS("command") " ..." BOLD("\"") "]]"
#define CMDLISTFORMAT                   "[" BOLDITALICS("searchstring") "]"
#define CONDUMPFORMAT                   "[" BOLDITALICS("filename") "[" BOLD(".txt") "]]"
#define CVARLISTFORMAT                  "[" BOLDITALICS("searchstring") "]"
#define EXECFORMAT                      BOLDITALICS("filename") "[" BOLD(".cfg") "]"
#define EXPLODEFORMAT                   BOLD("barrels") "|" BOLD("missiles")
#define FASTMONSTERSFORMAT              "[" BOLD("on") "|" BOLD("off") "]"
#define FREEZEFORMAT                    "[" BOLD("on") "|" BOLD("off") "]"
#define GODFORMAT                       "[" BOLD("on") "|" BOLD("off") "]"
#define GIVEFORMAT                      BOLD("ammo") "|" BOLD("armor") "|" BOLD("health") "|" BOLD("keys") "|" BOLD("weapons") "|" \
                                        BOLD("powerups") "|" BOLD("all") "|" BOLDITALICS("item")
#define IFFORMAT                        BOLDITALICS("CVAR") " " BOLD("is") " " BOLDITALICS("value") " " BOLD("then") " [" BOLD("\"") "]" \
                                        BOLDITALICS("command") "[" BOLD(";") " " BOLDITALICS("command") " ..." BOLD("\"") "]"
#define INFINITEAMMOFORMAT              "[" BOLD("on") "|" BOLD("off") "]"
#define KILLFORMAT                      BOLD("player") "|" BOLD("all") "|[[" BOLD("un") "]" BOLD("friendly") " ]" BOLDITALICS("monster")
#define LOADFORMAT                      BOLD("1") ".." BOLD("8") "|" BOLDITALICS("filename") "[" BOLD(".save") "]"
#define MAPFORMAT1                      BOLD("E") BOLDITALICS("x") BOLD("M") BOLDITALICS("y") "[" BOLD("B") "]|" BOLDITALICS("title") "|" \
                                        BOLD("first") "|" BOLD("previous") "|" BOLD("next") "|" BOLD("last") "|" BOLD("random")
#define MAPFORMAT2                      BOLD("MAP") BOLDITALICS("xy") "|" BOLDITALICS("title") "|" BOLD("first") "|" BOLD("previous") "|" \
                                        BOLD("next") "|" BOLD("last") "|" BOLD("random")
#define NOCLIPFORMAT                    "[" BOLD("on") "|" BOLD("off") "]"
#define NOMONSTERSFORMAT                "[" BOLD("on") "|" BOLD("off") "]"
#define NOTARGETFORMAT                  "[" BOLD("on") "|" BOLD("off") "]"
#define PISTOLSTARTFORMAT               "[" BOLD("on") "|" BOLD("off") "]"
#define PLAYFORMAT                      BOLDITALICS("soundeffect") "|" BOLDITALICS("music")
#define NAMEFORMAT                      "[[" BOLD("un") "]" BOLD("friendly") " ]" BOLDITALICS("monster") " " BOLDITALICS("name")
#define PRINTFORMAT                     "[" BOLD("\x93") "]" BOLDITALICS("message") "[" BOLD("\x94") "]"
#define REGENHEALTHFORMAT               "[" BOLD("on") "|" BOLD("off") "]"
#define REMOVEFORMAT                    BOLD("decorations") "|" BOLD("corpses") "|" BOLD("bloodsplats") "|" BOLD("items") "|" \
                                        BOLDITALICS("item") "|" BOLD("everything")
#define RESETFORMAT                     BOLDITALICS("CVAR")
#define RESPAWNITEMSFORMAT              "[" BOLD("on") "|" BOLD("off") "]"
#define RESPAWNMONSTERSFORMAT           "[" BOLD("on") "|" BOLD("off") "]"
#define RESURRECTFORMAT                 BOLD("player") "|" BOLD("all") "|[[" BOLD("un") "]" BOLD("friendly") " ]" BOLDITALICS("monster")
#define SAVEFORMAT                      BOLD("1") ".." BOLD("8") "|" BOLDITALICS("filename") "[" BOLD(".save") "]"
#define SPAWNFORMAT                     BOLDITALICS("item") "|[[" BOLD("un") "]" BOLD("friendly") " ]" BOLDITALICS("monster")
#define TAKEFORMAT                      BOLD("ammo") "|" BOLD("armor") "|" BOLD("health") "|" BOLD("keys") "|" BOLD("weapons") "|" \
                                        BOLD("powerups") "|" BOLD("all") "|" BOLDITALICS("item")
#define TELEPORTFORMAT                  BOLDITALICS("x") " " BOLDITALICS("y") "[ " BOLDITALICS("z") "]"
#define TIMERFORMAT                     BOLDITALICS("minutes")
#define TOGGLEFORMAT                    BOLDITALICS("CVAR")
#define UNBINDFORMAT                    BOLDITALICS("control") "|" BOLDITALICS("+action")
#define VANILLAFORMAT                   "[" BOLD("on") "|" BOLD("off") "]"

#define WEAPONDESCRIPTION_SHAREWARE     "Your currently equipped weapon (" BOLD("fists") ", " BOLD("chainsaw") ", " \
                                        BOLD("pistol") ", " BOLD("shotgun") ", " BOLD("chaingun") " or " BOLD("rocketlauncher") ")."
#define WEAPONDESCRIPTION_DOOM2         "Your currently equipped weapon (" BOLD("fists") ", " BOLD("chainsaw") ", " \
                                        BOLD("pistol") ", " BOLD("shotgun") ", " BOLD("supershotgun") ", " BOLD("chaingun") ", " \
                                        BOLD("rocketlauncher") ", " BOLD("plasmarifle") " or " BOLD("bfg9000") ")."
#define WEAPONDESCRIPTION_LEGACYOFRUST  "Your currently equipped weapon (" BOLD("fists") ", " BOLD("chainsaw") ", " \
                                        BOLD("pistol") ", " BOLD("shotgun") ", " BOLD("supershotgun") ", " BOLD("chaingun") ", " \
                                        BOLD("rocketlauncher") ", " BOLD("incinerator") " or " BOLD("calamityblade") ")."

#define DEADPLAYERWARNING1              "You can't change this CVAR right now because you're dead!"
#define DEADPLAYERWARNING2              "%s can't change this CVAR right now because %s %s dead!"
#define NEXTMAPWARNING1                 "You won't see the results of changing this CVAR until the next map!"
#define NEXTMAPWARNING2                 "%s won't see the results of changing this CVAR until the next map!"
#define NOGAMECCMDWARNING1              "You can't use this CCMD right now because you're not playing a game!"
#define NOGAMECCMDWARNING2              "%s can't use this CCMD right now because %s %s playing a game!"
#define NOGAMECVARWARNING1              "You can't change this CVAR right now because you're not playing a game!"
#define NOGAMECVARWARNING2              "%s can't change this CVAR right now because %s %s playing a game!"
#define NIGHTMAREWARNING1               "You can't change this CVAR right now because you're playing a game in " ITALICS("%s") "%s"
#define NIGHTMAREWARNING2               "%s can't change this CVAR right now because %s %s playing a game in " ITALICS("%s") "%s"

#define INTEGERCVARWITHDEFAULT          "It is currently " BOLD("%s") " and is " BOLD("%s") " by default."
#define INTEGERCVARWITHNODEFAULT        "It is currently " BOLD("%s") "."
#define INTEGERCVARISDEFAULT            "It is currently its default of " BOLD("%s") "."
#define DEGREESCVARWITHDEFAULT          "It is currently " BOLD("%i\xB0") " and is " BOLD("%i\xB0") " by default."
#define DEGREESCVARISDEFAULT            "It is currently its default of " BOLD("%i\xB0") "."
#define PERCENTCVARWITHDEFAULT          "It is currently " BOLD("%s%%") " and is " BOLD("%s%%") " by default."
#define PERCENTCVARWITHNODEFAULT        "It is currently " BOLD("%s%%") "."
#define PERCENTCVARISDEFAULT            "It is currently its default of " BOLD("%s%%") "."
#define STRINGCVARWITHDEFAULT           "It is currently " BOLD("\"%s\"") " and is " BOLD("\"%s\"") " by default."
#define STRINGCVARWITHNODEFAULT         "It is currently " BOLD("%s%s%s") "."
#define STRINGCVARISDEFAULT             "It is currently its default of " BOLD("\"%s\"") "."
#define TIMECVARWITHNODEFAULT1          "It is currently " BOLD(MONOSPACED("%02i") ":" MONOSPACED("%02i") "." MONOSPACED("%02i")) "."
#define TIMECVARWITHNODEFAULT2          "It is currently " BOLD(MONOSPACED("%i") ":" MONOSPACED("%02i") ":" MONOSPACED("%02i") "." MONOSPACED("%02i")) "."

#define INTEGERCVARCHANGED              "%s changed the " BOLD("%s") " CVAR from " BOLD("%s") " to " BOLD("%s") "."
#define INTEGERCVARCHANGEDFROMDEFAULT   "%s changed the " BOLD("%s") " CVAR from its default of " BOLD("%s") " to " BOLD("%s") "."
#define INTEGERCVARCHANGEDTODEFAULT     "%s changed the " BOLD("%s") " CVAR from " BOLD("%s") " back to its default of " BOLD("%s") "."
#define DEGREESCVARCHANGED              "%s changed the " BOLD("%s") " CVAR from " BOLD("%i\xB0") " to " BOLD("%i\xB0") "."
#define DEGREESCVARCHANGEDFROMDEFAULT   "%s changed the " BOLD("%s") " CVAR from its default of " BOLD("%i\xB0") " to " BOLD("%i\xB0") "."
#define DEGREESCVARCHANGEDTODEFAULT     "%s changed the " BOLD("%s") " CVAR from " BOLD("%i\xB0") " back to its default of " BOLD("%i\xB0") "."
#define PERCENTCVARCHANGED              "%s changed the " BOLD("%s") " CVAR from " BOLD("%s%%") " to " BOLD("%s%%") "."
#define PERCENTCVARCHANGEDFROMDEFAULT   "%s changed the " BOLD("%s") " CVAR from its default of " BOLD("%s%%") " to " BOLD("%s%%") "."
#define PERCENTCVARCHANGEDTODEFAULT     "%s changed the " BOLD("%s") " CVAR from " BOLD("%s%%") " back to its default of " BOLD("%s%%") "."
#define STRINGCVARCHANGED               "%s changed the " BOLD("%s") " CVAR from " BOLD("\"%s\"") " to " BOLD("\"%s\"") "."
#define STRINGCVARCHANGEDFROMDEFAULT    "%s changed the " BOLD("%s") " CVAR from its default of " BOLD("\"%s\"") " to " BOLD("\"%s\"") "."
#define STRINGCVARCHANGEDTODEFAULT      "%s changed the " BOLD("%s") " CVAR from " BOLD("\"%s\"") " back to its default of " BOLD("\"%s\"") "."

#define INTEGERCVARSAMEWARNING          "The " BOLD("%s") " CVAR is already " BOLD("%s") "!"
#define INTEGERCVARSAMEDEFAULTWARNING   "The " BOLD("%s") " CVAR is already its default of " BOLD("%s") "!"
#define DEGREESCVARSAMEWARNING          "The " BOLD("%s") " CVAR is already " BOLD("%i\xB0") "!"
#define DEGREESCVARSAMEDEFAULTWARNING   "The " BOLD("%s") " CVAR is already its default of " BOLD("%i\xB0") "!"
#define PERCENTCVARSAMEWARNING          "The " BOLD("%s") " CVAR is already " BOLD("%s%%") "!"
#define PERCENTCVARSAMEDEFAULTWARNING   "The " BOLD("%s") " CVAR is already its default of " BOLD("%s%%") "!"
#define STRINGCVARSAMEWARNING           "The " BOLD("%s") " CVAR is already " BOLD("\"%s\"") "!"
#define STRINGCVARSAMEDEFAULTWARNING    "The " BOLD("%s") " CVAR is already its default of " BOLD("\"%s\"") "!"

#define INDENT                          "       "

alias_t     aliases[MAXALIASES];

static int  ammo;
static int  armor;
static int  armortype;
static int  health;
static int  weapon;

static int  mapcmdepisode;
static int  mapcmdmap;
static char mapcmdlump[7];
static char mapcmdmapnum[7];

bool        executingalias = false;
bool        healthcvar = false;
bool        nobindoutput = false;
bool        parsingcfgfile = false;
bool        quitcmd = false;
bool        resettingcvar = false;
bool        togglingcvar = false;
bool        togglingvanilla = false;
bool        vanilla = false;

const control_t controls[] =
{
    { "escape",        keyboardcontrol,   KEY_ESCAPE                },
    { "F12",           keyboardcontrol,   KEY_F12                   },
    { "printscreen",   keyboardcontrol,   KEY_PRINTSCREEN           },
    { "delete",        keyboardcontrol,   KEY_DELETE                },
    { "insert",        keyboardcontrol,   KEY_INSERT                },
    { "tilde",         keyboardcontrol,   '`'                       },
    { "1",             keyboardcontrol,   '1'                       },
    { "2",             keyboardcontrol,   '2'                       },
    { "3",             keyboardcontrol,   '3'                       },
    { "4",             keyboardcontrol,   '4'                       },
    { "5",             keyboardcontrol,   '5'                       },
    { "6",             keyboardcontrol,   '6'                       },
    { "7",             keyboardcontrol,   '7'                       },
    { "8",             keyboardcontrol,   '8'                       },
    { "9",             keyboardcontrol,   '9'                       },
    { "0",             keyboardcontrol,   '0'                       },
    { "-",             keyboardcontrol,   '-'                       },
    { "=",             keyboardcontrol,   '='                       },
    { "+",             keyboardcontrol,   '='                       },
    { "backspace",     keyboardcontrol,   KEY_BACKSPACE             },
    { "tab",           keyboardcontrol,   KEY_TAB                   },
    { "q",             keyboardcontrol,   'q'                       },
    { "w",             keyboardcontrol,   'w'                       },
    { "e",             keyboardcontrol,   'e'                       },
    { "r",             keyboardcontrol,   'r'                       },
    { "t",             keyboardcontrol,   't'                       },
    { "y",             keyboardcontrol,   'y'                       },
    { "u",             keyboardcontrol,   'u'                       },
    { "i",             keyboardcontrol,   'i'                       },
    { "o",             keyboardcontrol,   'o'                       },
    { "p",             keyboardcontrol,   'p'                       },
    { "[",             keyboardcontrol,   '['                       },
    { "]",             keyboardcontrol,   ']'                       },
    { "\\",            keyboardcontrol,   '\\'                      },
    { "a",             keyboardcontrol,   'a'                       },
    { "s",             keyboardcontrol,   's'                       },
    { "d",             keyboardcontrol,   'd'                       },
    { "f",             keyboardcontrol,   'f'                       },
    { "g",             keyboardcontrol,   'g'                       },
    { "h",             keyboardcontrol,   'h'                       },
    { "j",             keyboardcontrol,   'j'                       },
    { "k",             keyboardcontrol,   'k'                       },
    { "l",             keyboardcontrol,   'l'                       },
    { ";",             keyboardcontrol,   ';'                       },
    { "'",             keyboardcontrol,   '\''                      },
    { "enter",         keyboardcontrol,   KEY_ENTER                 },
    { "shift",         keyboardcontrol,   KEY_SHIFT                 },
    { "z",             keyboardcontrol,   'z'                       },
    { "x",             keyboardcontrol,   'x'                       },
    { "c",             keyboardcontrol,   'c'                       },
    { "v",             keyboardcontrol,   'v'                       },
    { "b",             keyboardcontrol,   'b'                       },
    { "n",             keyboardcontrol,   'n'                       },
    { "m",             keyboardcontrol,   'm'                       },
    { ",",             keyboardcontrol,   ','                       },
    { ".",             keyboardcontrol,   '.'                       },
    { "/",             keyboardcontrol,   '/'                       },
    { "ctrl",          keyboardcontrol,   KEY_CTRL                  },
    { "alt",           keyboardcontrol,   KEY_ALT                   },
    { "space",         keyboardcontrol,   KEY_SPACE                 },
    { "numlock",       keyboardcontrol,   KEY_NUMLOCK               },
    { "capslock",      keyboardcontrol,   KEY_CAPSLOCK              },
    { "scrolllock",    keyboardcontrol,   KEY_SCROLLLOCK            },
    { "home",          keyboardcontrol,   KEY_HOME                  },
    { "up",            keyboardcontrol,   KEY_UPARROW               },
    { "pageup",        keyboardcontrol,   KEY_PAGEUP                },
    { "left",          keyboardcontrol,   KEY_LEFTARROW             },
    { "right",         keyboardcontrol,   KEY_RIGHTARROW            },
    { "end",           keyboardcontrol,   KEY_END                   },
    { "down",          keyboardcontrol,   KEY_DOWNARROW             },
    { "pagedown",      keyboardcontrol,   KEY_PAGEDOWN              },
    { "numpad0",       keyboardcontrol,   KEYP_0                    },
    { "numpad1",       keyboardcontrol,   KEYP_1                    },
    { "numpad2",       keyboardcontrol,   KEYP_2                    },
    { "numpad3",       keyboardcontrol,   KEYP_3                    },
    { "numpad4",       keyboardcontrol,   KEYP_4                    },
    { "numpad5",       keyboardcontrol,   KEYP_5                    },
    { "numpad6",       keyboardcontrol,   KEYP_6                    },
    { "numpad7",       keyboardcontrol,   KEYP_7                    },
    { "numpad8",       keyboardcontrol,   KEYP_8                    },
    { "numpad9",       keyboardcontrol,   KEYP_9                    },
    { "numpad/",       keyboardcontrol,   KEYP_DIVIDE               },
    { "numpad+",       keyboardcontrol,   KEYP_PLUS                 },
    { "numpad-",       keyboardcontrol,   KEYP_MINUS                },
    { "numpad*",       keyboardcontrol,   KEYP_MULTIPLY             },
    { "numpad.",       keyboardcontrol,   KEYP_PERIOD               },
    { "numpad\\",      keyboardcontrol,   KEYP_BACKSLASH            },

    { "mouse1",        mousecontrol,      0                         },
    { "mouse2",        mousecontrol,      1                         },
    { "mouse3",        mousecontrol,      2                         },
    { "mouse4",        mousecontrol,      3                         },
    { "mouse5",        mousecontrol,      4                         },
    { "mouse6",        mousecontrol,      5                         },
    { "mouse7",        mousecontrol,      6                         },
    { "mouse8",        mousecontrol,      7                         },
    { "wheelup",       mousecontrol,      MOUSE_WHEELUP             },
    { "wheeldown",     mousecontrol,      MOUSE_WHEELDOWN           },
    { "wheelleft",     mousecontrol,      MOUSE_WHEELLEFT           },
    { "wheelright",    mousecontrol,      MOUSE_WHEELRIGHT          },

    { "button1",       controllercontrol, CONTROLLER_A              },
    { "gamepad1",      controllercontrol, CONTROLLER_A              },
    { "button2",       controllercontrol, CONTROLLER_B              },
    { "gamepad2",      controllercontrol, CONTROLLER_B              },
    { "button3",       controllercontrol, CONTROLLER_X              },
    { "gamepad3",      controllercontrol, CONTROLLER_X              },
    { "button4",       controllercontrol, CONTROLLER_Y              },
    { "gamepad4",      controllercontrol, CONTROLLER_Y              },
    { "back",          controllercontrol, CONTROLLER_BACK           },
    { "guide",         controllercontrol, CONTROLLER_GUIDE          },
    { "start",         controllercontrol, CONTROLLER_START          },
    { "leftthumb",     controllercontrol, CONTROLLER_LEFT_THUMB     },
    { "rightthumb",    controllercontrol, CONTROLLER_RIGHT_THUMB    },
    { "leftshoulder",  controllercontrol, CONTROLLER_LEFT_SHOULDER  },
    { "rightshoulder", controllercontrol, CONTROLLER_RIGHT_SHOULDER },
    { "dpadup",        controllercontrol, CONTROLLER_DPAD_UP        },
    { "dpaddown",      controllercontrol, CONTROLLER_DPAD_DOWN      },
    { "dpadleft",      controllercontrol, CONTROLLER_DPAD_LEFT      },
    { "dpadright",     controllercontrol, CONTROLLER_DPAD_RIGHT     },
    { "misc1",         controllercontrol, CONTROLLER_MISC1          },
    { "paddle1",       controllercontrol, CONTROLLER_PADDLE1        },
    { "paddle2",       controllercontrol, CONTROLLER_PADDLE2        },
    { "paddle3",       controllercontrol, CONTROLLER_PADDLE3        },
    { "paddle4",       controllercontrol, CONTROLLER_PADDLE4        },
    { "touchpad",      controllercontrol, CONTROLLER_TOUCHPAD       },
    { "lefttrigger",   controllercontrol, CONTROLLER_LEFT_TRIGGER   },
    { "righttrigger",  controllercontrol, CONTROLLER_RIGHT_TRIGGER  },

    { "",              0,                 0                         }
};

static void alwaysrunactionfunc(void);
static void automapactionfunc(void);
static void backactionfunc(void);
static void bfg9000actionfunc(void);
static void chaingunactionfunc(void);
static void chainsawactionfunc(void);
static void clearmarkactionfunc(void);
static void consoleactionfunc(void);
static void fireactionfunc(void);
static void fistsactionfunc(void);
static void followmodeactionfunc(void);
static void forwardactionfunc(void);
static void lookcenteractionfunc(void);
static void lookdownactionfunc(void);
static void lookupactionfunc(void);
static void fullzoomactionfunc(void);
static void gridactionfunc(void);
static void jumpactionfunc(void);
static void leftactionfunc(void);
static void markactionfunc(void);
static void menuactionfunc(void);
static void nextweaponactionfunc(void);
static void pathactionfunc(void);
static void pistolactionfunc(void);
static void plasmarifleactionfunc(void);
static void prevweaponactionfunc(void);
static void rightactionfunc(void);
static void rocketlauncheractionfunc(void);
static void rotatemodeactionfunc(void);
static void screenshotactionfunc(void);
static void shotgunactionfunc(void);
static void sizedownactionfunc(void);
static void sizeupactionfunc(void);
static void strafeleftactionfunc(void);
static void straferightactionfunc(void);
static void supershotgunactionfunc(void);
static void useactionfunc(void);
static void weapon1actionfunc(void);
static void weapon2actionfunc(void);
static void weapon3actionfunc(void);
static void weapon4actionfunc(void);
static void weapon5actionfunc(void);
static void weapon6actionfunc(void);
static void weapon7actionfunc(void);
static void zoominactionfunc(void);
static void zoomoutactionfunc(void);

static void AdjustScreenSize(int value);

action_t actions[] =
{
    { "+alwaysrun",      "+alwaysrun",      true,  false, alwaysrunactionfunc,      &keyboardalwaysrun,      &keyboardalwaysrun2,      &mousealwaysrun,      &controlleralwaysrun,      NULL            },
    { "+automap",        "+automap",        false, false, automapactionfunc,        &keyboardautomap,        &keyboardautomap2,        &mouseautomap,        &controllerautomap,        NULL            },
    { "+back",           "+back",           true,  false, backactionfunc,           &keyboardback,           &keyboardback2,           &mouseback,           &controllerback,           NULL            },
    { "+bfg9000",        "+bfg9000",        true,  false, bfg9000actionfunc,        &keyboardbfg9000,        &keyboardbfg90002,        &mousebfg9000,        &controllerbfg9000,        NULL            },
    { "+chaingun",       "+chaingun",       true,  false, chaingunactionfunc,       &keyboardchaingun,       &keyboardchaingun2,       &mousechaingun,       &controllerchaingun,       NULL            },
    { "+chainsaw",       "+chainsaw",       true,  false, chainsawactionfunc,       &keyboardchainsaw,       &keyboardchainsaw2,       &mousechainsaw,       &controllerchainsaw,       NULL            },
    { "+clearmark",      "+clearmark",      true,  true,  clearmarkactionfunc,      &keyboardclearmark,      &keyboardclearmark2,      &mouseclearmark,      &controllerclearmark,      NULL            },
    { "+console",        "+console",        false, false, consoleactionfunc,        &keyboardconsole,        &keyboardconsole2,        &mouseconsole,        &controllerconsole,        NULL            },
    { "+fire",           "+fire",           true,  false, fireactionfunc,           &keyboardfire,           &keyboardfire2,           &mousefire,           &controllerfire,           NULL            },
    { "+fists",          "+fists",          true,  false, fistsactionfunc,          &keyboardfists,          &keyboardfists2,          &mousefists,          &controllerfists,          NULL            },
    { "+followmode",     "+followmode",     true,  true,  followmodeactionfunc,     &keyboardfollowmode,     &keyboardfollowmode2,     &mousefollowmode,     &controllerfollowmode,     NULL            },
    { "+forward",        "+forward",        true,  false, forwardactionfunc,        &keyboardforward,        &keyboardforward2,        &mouseforward,        &controllerforward,        NULL            },
    { "+freelook",       "+freelook",       true,  false, NULL,                     &keyboardfreelook,       &keyboardfreelook2,       &mousefreelook,       &controllerfreelook,       NULL            },
    { "+lookcenter",     "+lookcentre",     true,  false, lookcenteractionfunc,     &keyboardlookcenter,     &keyboardlookcenter2,     &mouselookcenter,     &controllerlookcenter,     NULL            },
    { "+lookdown",       "+lookdown",       true,  false, lookdownactionfunc,       &keyboardlookdown,       &keyboardlookdown2,       &mouselookdown,       &controllerlookdown,       NULL            },
    { "+lookup",         "+lookup",         true,  false, lookupactionfunc,         &keyboardlookup,         &keyboardlookup2,         &mouselookup,         &controllerlookup,         NULL            },
    { "+fullzoom",       "+maxzoom",        true,  true,  fullzoomactionfunc,       &keyboardfullzoom,       &keyboardfullzoom2,       &mousefullzoom,       &controllerfullzoom,       NULL            },
    { "+grid",           "+grid",           true,  true,  gridactionfunc,           &keyboardgrid,           &keyboardgrid2,           &mousegrid,           &controllergrid,           NULL            },
    { "+jump",           "+jump",           true,  false, jumpactionfunc,           &keyboardjump,           &keyboardjump2,           &mousejump,           &controllerjump,           NULL            },
    { "+left",           "+left",           true,  false, leftactionfunc,           &keyboardleft,           &keyboardleft2,           &mouseleft,           &controllerleft,           NULL            },
    { "+mark",           "+mark",           true,  true,  markactionfunc,           &keyboardmark,           &keyboardmark2,           &mousemark,           &controllermark,           NULL            },
    { "+menu",           "+menu",           true,  false, menuactionfunc,           &keyboardmenu,           &keyboardmenu2,           &mousemenu,           &controllermenu,           NULL            },
    { "+nextweapon",     "+nextweapon",     true,  false, nextweaponactionfunc,     &keyboardnextweapon,     &keyboardnextweapon2,     &mousenextweapon,     &controllernextweapon,     NULL            },
    { "+path",           "+path",           true,  false, pathactionfunc,           &keyboardpath,           &keyboardpath2,           &mousepath,           &controllerpath,           NULL            },
    { "+pistol",         "+pistol",         true,  false, pistolactionfunc,         &keyboardpistol,         &keyboardpistol2,         &mousepistol,         &controllerpistol,         NULL            },
    { "+plasmarifle",    "+plasmarifle",    true,  false, plasmarifleactionfunc,    &keyboardplasmarifle,    &keyboardplasmarifle2,    &mouseplasmarifle,    &controllerplasmarifle,    NULL            },
    { "+prevweapon",     "+prevweapon",     true,  false, prevweaponactionfunc,     &keyboardprevweapon,     &keyboardprevweapon2,     &mouseprevweapon,     &controllerprevweapon,     NULL            },
    { "+right",          "+right",          true,  false, rightactionfunc,          &keyboardright,          &keyboardright2,          &mouseright,          &controllerright,          NULL            },
    { "+rocketlauncher", "+rocketlauncher", true,  false, rocketlauncheractionfunc, &keyboardrocketlauncher, &keyboardrocketlauncher2, &mouserocketlauncher, &controllerrocketlauncher, NULL            },
    { "+rotatemode",     "+rotatemode",     true,  true,  rotatemodeactionfunc,     &keyboardrotatemode,     &keyboardrotatemode2,     &mouserotatemode,     &controllerrotatemode,     NULL            },
    { "+run",            "+run",            true,  false, NULL,                     &keyboardrun,            &keyboardrun2,            &mouserun,            &controllerrun,            NULL            },
    { "+screenshot",     "+screenshot",     false, false, screenshotactionfunc,     &keyboardscreenshot,     &keyboardscreenshot2,     &mousescreenshot,     &controllerscreenshot,     NULL            },
    { "+sizedown",       "+sizedown",       true,  false, sizedownactionfunc,       &keyboardsizedown,       &keyboardsizedown2,       &mousesizedown,       &controllersizedown,       NULL            },
    { "+sizeup",         "+sizeup",         true,  false, sizeupactionfunc,         &keyboardsizeup,         &keyboardsizeup2,         &mousesizeup,         &controllersizeup,         NULL            },
    { "+shotgun",        "+shotgun",        true,  false, shotgunactionfunc,        &keyboardshotgun,        &keyboardshotgun2,        &mouseshotgun,        &controllershotgun,        NULL            },
    { "+strafe",         "+strafe",         true,  false, NULL,                     &keyboardstrafe,         &keyboardstrafe2,         &mousestrafe,         &controllerstrafe,         NULL            },
    { "+strafeleft",     "+strafeleft",     true,  false, strafeleftactionfunc,     &keyboardstrafeleft,     &keyboardstrafeleft2,     &mousestrafeleft,     &controllerstrafeleft,     NULL            },
    { "+straferight",    "+straferight",    true,  false, straferightactionfunc,    &keyboardstraferight,    &keyboardstraferight2,    &mousestraferight,    &controllerstraferight,    NULL            },
    { "+supershotgun",   "+supershotgun",   true,  false, supershotgunactionfunc,   &keyboardsupershotgun,   &keyboardsupershotgun2,   &mousesupershotgun,   &controllersupershotgun,   NULL            },
    { "+use",            "+use",            true,  false, useactionfunc,            &keyboarduse,            &keyboarduse2,            &mouseuse,            &controlleruse,            &controlleruse2 },
    { "+weapon1",        "+weapon1",        true,  false, weapon1actionfunc,        &keyboardweapon1A,       &keyboardweapon1B,        &mouseweapon1,        &controllerweapon1,        NULL            },
    { "+weapon2",        "+weapon2",        true,  false, weapon2actionfunc,        &keyboardweapon2A,       &keyboardweapon2B,        &mouseweapon2,        &controllerweapon2,        NULL            },
    { "+weapon3",        "+weapon3",        true,  false, weapon3actionfunc,        &keyboardweapon3A,       &keyboardweapon3B,        &mouseweapon3,        &controllerweapon3,        NULL            },
    { "+weapon4",        "+weapon4",        true,  false, weapon4actionfunc,        &keyboardweapon4A,       &keyboardweapon4B,        &mouseweapon4,        &controllerweapon4,        NULL            },
    { "+weapon5",        "+weapon5",        true,  false, weapon5actionfunc,        &keyboardweapon5A,       &keyboardweapon5B,        &mouseweapon5,        &controllerweapon5,        NULL            },
    { "+weapon6",        "+weapon6",        true,  false, weapon6actionfunc,        &keyboardweapon6A,       &keyboardweapon6B,        &mouseweapon6,        &controllerweapon6,        NULL            },
    { "+weapon7",        "+weapon7",        true,  false, weapon7actionfunc,        &keyboardweapon7A,       &keyboardweapon7B,        &mouseweapon7,        &controllerweapon7,        NULL            },
    { "+zoomin",         "+zoomin",         true,  true,  zoominactionfunc,         &keyboardzoomin,         &keyboardzoomin2,         &mousezoomin,         &controllerzoomin,         NULL            },
    { "+zoomout",        "+zoomout",        true,  true,  zoomoutactionfunc,        &keyboardzoomout,        &keyboardzoomout2,        &mousezoomout,        &controllerzoomout,        NULL            },
    { "",                "",                false, false, NULL,                     NULL,                    NULL,                     NULL,                 NULL,                      NULL            }
};

static bool alivefunc1(char *cmd, char *parms);
static bool cheatfunc1(char *cmd, char *parms);
static bool ingameccmdfunc1(char *cmd, char *parms);
static bool inaftergameccmdfunc1(char *cmd, char *parms);
static bool ingamecvarfunc1(char *cmd, char *parms);
static bool nightmarefunc1(char *cmd, char *parms);
static bool nullfunc1(char *cmd, char *parms);

static void bindlistfunc2(char *cmd, char *parms);
static void clearfunc2(char *cmd, char *parms);
static void cmdlistfunc2(char *cmd, char *parms);
static bool condumpfunc1(char *cmd, char *parms);
static void condumpfunc2(char *cmd, char *parms);
static void cvarlistfunc2(char *cmd, char *parms);
static void endgamefunc2(char *cmd, char *parms);
static void exitmapfunc2(char *cmd, char *parms);
static void fastmonstersfunc2(char *cmd, char *parms);
static void freezefunc2(char *cmd, char *parms);
static bool givefunc1(char *cmd, char *parms);
static void givefunc2(char *cmd, char *parms);
static void godfunc2(char *cmd, char *parms);
static void iffunc2(char *cmd, char *parms);
static void infiniteammofunc2(char *cmd, char *parms);
static bool killfunc1(char *cmd, char *parms);
static void killfunc2(char *cmd, char *parms);
static void licensefunc2(char *cmd, char *parms);
static void loadfunc2(char *cmd, char *parms);
static bool mapfunc1(char *cmd, char *parms);
static void mapfunc2(char *cmd, char *parms);
static void maplistfunc2(char *cmd, char *parms);
static void mapstatsfunc2(char *cmd, char *parms);
static bool namefunc1(char *cmd, char *parms);
static void namefunc2(char *cmd, char *parms);
static void newgamefunc2(char *cmd, char *parms);
static void noclipfunc2(char *cmd, char *parms);
static void nomonstersfunc2(char *cmd, char *parms);
static void notargetfunc2(char *cmd, char *parms);
static void palettefunc2(char *cmd, char *parms);
static void pistolstartfunc2(char *cmd, char *parms);
static bool playfunc1(char *cmd, char *parms);
static void playfunc2(char *cmd, char *parms);
static void playerstatsfunc2(char *cmd, char *parms);
static void printfunc2(char *cmd, char *parms);
static void quitfunc2(char *cmd, char *parms);
static void readmefunc2(char *cmd, char *parms);
static void regenhealthfunc2(char *cmd, char *parms);
static void releasenotesfunc2(char *cmd, char *parms);
static void resetfunc2(char *cmd, char *parms);
static void resetallfunc2(char *cmd, char *parms);
static void respawnitemsfunc2(char *cmd, char *parms);
static void respawnmonstersfunc2(char *cmd, char *parms);
static void restartmapfunc2(char *cmd, char *parms);
static bool resurrectfunc1(char *cmd, char *parms);
static void resurrectfunc2(char *cmd, char *parms);
static void savefunc2(char *cmd, char *parms);
static bool spawnfunc1(char *cmd, char *parms);
static void spawnfunc2(char *cmd, char *parms);
static bool takefunc1(char *cmd, char *parms);
static void takefunc2(char *cmd, char *parms);
static bool teleportfunc1(char *cmd, char *parms);
static void teleportfunc2(char *cmd, char *parms);
static void thinglistfunc2(char *cmd, char *parms);
static void timerfunc2(char *cmd, char *parms);
static void togglefunc2(char *cmd, char *parms);
static void unbindfunc2(char *cmd, char *parms);
static void vanillafunc2(char *cmd, char *parms);
static void wikifunc2(char *cmd, char *parms);

static bool boolfunc1(char *cmd, char *parms);
static void boolfunc2(char *cmd, char *parms);
static void colorfunc2(char *cmd, char *parms);
static bool floatfunc1(char *cmd, char *parms);
static void floatfunc2(char *cmd, char *parms);
static bool intfunc1(char *cmd, char *parms);
static void intfunc2(char *cmd, char *parms);
static void strfunc2(char *cmd, char *parms);
static void timefunc2(char *cmd, char *parms);

static void alwaysrunfunc2(char *cmd, char *parms);
static void am_displayfunc2(char *cmd, char *parms);
static void am_externalfunc2(char *cmd, char *parms);
static void am_followmodefunc2(char *cmd, char *parms);
static void am_gridsizefunc2(char *cmd, char *parms);
static void am_pathfunc2(char *cmd, char *parms);
static void am_rotatemodefunc2(char *cmd, char *parms);
static bool armortypefunc1(char *cmd, char *parms);
static void armortypefunc2(char *cmd, char *parms);
static void autotiltfunc2(char *cmd, char *parms);
static void englishfunc2(char *cmd, char *parms);
static void episodefunc2(char *cmd, char *parms);
static void expansionfunc2(char *cmd, char *parms);
static void freelookfunc2(char *cmd, char *parms);
static bool joy_deadzonecvarsfunc1(char *cmd, char *parms);
static void joy_deadzonecvarsfunc2(char *cmd, char *parms);
static void joy_sensitivitycvarsfunc2(char *cmd, char *parms);
static bool playercvarsfunc1(char *cmd, char *parms);
static void playercvarsfunc2(char *cmd, char *parms);
static void r_antialiasingfunc2(char *cmd, char *parms);
static void r_bloodfunc2(char *cmd, char *parms);
static void r_bloodsplats_translucencyfunc2(char *cmd, char *parms);
static void r_brightmapsfunc2(char *cmd, char *parms);
static void r_corpses_mirroredfunc2(char *cmd, char *parms);
static void r_detailfunc2(char *cmd, char *parms);
static void r_diskiconfunc2(char *cmd, char *parms);
static void r_ditheredlightingfunc2(char *cmd, char *parms);
static void r_fixmaperrorsfunc2(char *cmd, char *parms);
static void r_fovfunc2(char *cmd, char *parms);
static bool r_gammafunc1(char *cmd, char *parms);
static void r_gammafunc2(char *cmd, char *parms);
static void r_hudfunc2(char *cmd, char *parms);
static void r_hud_translucencyfunc2(char *cmd, char *parms);
static void r_lowpixelsizefunc2(char *cmd, char *parms);
static void r_mirroredweaponsfunc2(char *cmd, char *parms);
static void r_randomstartframesfunc2(char *cmd, char *parms);
static void r_rockettrails_translucencyfunc2(char *cmd, char *parms);
static void r_screensizefunc2(char *cmd, char *parms);
static void r_shadows_translucencyfunc2(char *cmd, char *parms);
static void r_sprites_translucencyfunc2(char *cmd, char *parms);
static void r_texturesfunc2(char *cmd, char *parms);
static void r_textures_translucencyfunc2(char *cmd, char *parms);
static void s_randommusicfunc2(char *cmd, char *parms);
static bool s_volumecvarsfunc1(char *cmd, char *parms);
static void s_volumecvarsfunc2(char *cmd, char *parms);
static void savegamefunc2(char *cmd, char *parms);
static void skilllevelfunc2(char *cmd, char *parms);
static void turbofunc2(char *cmd, char *parms);
static void vid_aspectratiofunc2(char *cmd, char *parms);
static void vid_bluefunc2(char *cmd, char *parms);
static void vid_borderlesswindowfunc2(char *cmd, char *parms);
static void vid_brightnessfunc2(char *cmd, char *parms);
static void vid_contrastfunc2(char *cmd, char *parms);
static void vid_displayfunc2(char *cmd, char *parms);
static void vid_fullscreenfunc2(char *cmd, char *parms);
static void vid_greenfunc2(char *cmd, char *parms);
static void vid_motionblurfunc2(char *cmd, char *parms);
static void vid_pillarboxesfunc2(char *cmd, char *parms);
static void vid_redfunc2(char *cmd, char *parms);
static void vid_saturationfunc2(char *cmd, char *parms);
static bool vid_scaleapifunc1(char *cmd, char *parms);
static void vid_scaleapifunc2(char *cmd, char *parms);
static bool vid_scalefilterfunc1(char *cmd, char *parms);
static void vid_scalefilterfunc2(char *cmd, char *parms);
static void vid_screenresolutionfunc2(char *cmd, char *parms);
static void vid_showfpsfunc2(char *cmd, char *parms);
static void vid_vsyncfunc2(char *cmd, char *parms);
static void vid_widescreenfunc2(char *cmd, char *parms);
static void vid_windowposfunc2(char *cmd, char *parms);
static void vid_windowsizefunc2(char *cmd, char *parms);
static bool weaponfunc1(char *cmd, char *parms);
static void weaponfunc2(char *cmd, char *parms);
static void weaponrecoilfunc2(char *cmd, char *parms);

static int C_LookupValueFromAlias(const char *text, const valuealiastype_t valuealiastype)
{
    for (int i = 0; *valuealiases[i].text; i++)
        if (valuealiastype == valuealiases[i].type && M_StringCompare(text, valuealiases[i].text))
            return valuealiases[i].value;

    return INT_MIN;
}

char *C_LookupAliasFromValue(const int value, const valuealiastype_t valuealiastype)
{
    for (int i = 0; *valuealiases[i].text; i++)
        if (valuealiastype == valuealiases[i].type && value == valuealiases[i].value)
            return M_StringDuplicate(valuealiases[i].text);

    return commify(value);
}

#define CCMD(name, alt1, alt2, cond, func, parms, form, desc) \
    { #name, #alt1, #alt2, cond, func, parms, CT_CCMD, 0, 0, NULL, 0, 0, 0, form, desc, 0, 0 }
#define CHEATCCMD(name, parms) \
    { #name, "", "", cheatfunc1, NULL, parms, CT_CHEAT, 0, 0, NULL, 0, 0, 0, "", "", 0, 0 }
#define BOOLCVAR(name, alt1, alt2, cond, func, flags, desc) \
    { #name, #alt1, #alt2, cond, func, 1, CT_CVAR, (CF_BOOLEAN | flags), 0, &name, BOOLVALUEALIAS, \
      false, true, "", desc, name##_default, 0 }
#define INTCVAR(name, alt1, alt2, cond, func, flags, aliases, desc) \
    { #name, #alt1, #alt2, cond, func, 1, CT_CVAR, (CF_INTEGER | flags), 0, &name, aliases, \
      name##_min, name##_max, "", desc, name##_default, 0 }
#define COLORCVAR(name, alt1, desc) \
    { #name, #alt1, "", intfunc1, colorfunc2, 1, CT_CVAR, (CF_INTEGER | CF_COLOR), 0, &name, 0, \
      name##_min, name##_max, "", desc, name##_default, 0 }
#define PERCENTCVAR(name, alt1, alt2, cond, func, desc) \
    { #name, #alt1, #alt2, cond, func, 1, CT_CVAR, (CF_INTEGER | CF_PERCENT), 0, &name, 0, \
      name##_min, name##_max, "", desc, name##_default, 0 }
#define FLOATCVAR(name, alt1, alt2, cond, func, flags, desc) \
    { #name, #alt1, #alt2, cond, func, 1, CT_CVAR, (CF_FLOAT | flags), 0, &name, 0, \
      (int)name##_min, (int)name##_max, "", desc, name##_default, 0 }
#define FLOATCVAR2(name, alt1, alt2, cond, func, flags, desc) \
    { #name, #alt1, #alt2, cond, func, 1, CT_CVAR, (CF_FLOAT | flags), 0, &name, 0, 0, 0, "", \
      desc, name##_default, 0 }
#define STRCVAR(name, alt1, alt2, cond, func, flags, length, desc) \
    { #name, #alt1, #alt2, cond, func, 1, CT_CVAR, (CF_STRING | flags), length, &name, 0, 0, 0, \
      "", desc, 0, name##_default }
#define TIMECVAR(name, alt1, alt2, cond, func, desc) \
    { #name, #alt1, #alt2, cond, func, 1, CT_CVAR, (CF_TIME | CF_READONLY), 0, &name, 0, 0, 0, "", \
      desc, 0, "" }
#define OTHERCVAR(name, alt1, alt2, cond, func, desc) \
    { #name, #alt1, #alt2, cond, func, 1, CT_CVAR, CF_OTHER, 0, &name, 0, 0, 0, "", desc, 0, \
      name##_default }

consolecmd_t consolecmds[] =
{
    CCMD(alias, "", "", nullfunc1, aliasfunc2, true, ALIASFORMAT,
        "Creates an " BOLDITALICS("alias") " that executes a string of " BOLDITALICS("commands") "."),
    BOOLCVAR(alwaysrun, "", "", boolfunc1, alwaysrunfunc2, 0,
        "Toggles you to always run instead of walk."),
    COLORCVAR(am_allmapcdwallcolor, am_allmapcdwallcolour,
        "The color of unmapped lines in the automap indicating a change in a ceiling's height once "
        "you have a computer area map power-up (" BOLD("0") " to " BOLD("255") ")."),
    COLORCVAR(am_allmapfdwallcolor, am_allmapfdwallcolour,
        "The color of unmapped lines in the automap indicating a change in a floor's height once "
        "you have a computer area map power-up (" BOLD("0") " to " BOLD("255") ")."),
    COLORCVAR(am_allmapwallcolor, am_allmapwallcolour,
        "The color of unmapped solid walls in the automap once you have a computer area map "
        "power-up (" BOLD("0") " to " BOLD("255") ")."),
    BOOLCVAR(am_antialiasing, "", "", boolfunc1, boolfunc2, 0,
        "Toggles anti-aliasing in the automap."),
    BOOLCVAR(am_author, "", "", boolfunc1, boolfunc2, 0,
        "Toggles displaying the map's author (if available) in the automap."),
    COLORCVAR(am_backcolor, am_backcolour,
        "The color of the automap's background (" BOLD("0") " to " BOLD("255") ")."),
    COLORCVAR(am_bloodsplatcolor, am_bloodsplatcolour,
        "The color of blood splats in the automap when you cheat (" BOLD("0") " to " BOLD("255") ")."),
    COLORCVAR(am_bluedoorcolor, am_bluedoorcolour,
        "The color of doors in the automap unlocked with a blue keycard or skull key (" BOLD("0")
        " to " BOLD("255") ")."),
    COLORCVAR(am_bluekeycolor, am_bluekeycolour,
        "The color of blue keycards and skull keys in the automap when you cheat (" BOLD("0") " to " BOLD("255") ")."),
    COLORCVAR(am_cdwallcolor, am_cdwallcolour,
        "The color of lines in the automap indicating a change in a ceiling's height (" BOLD("0")
        " to " BOLD("255") ")."),
    COLORCVAR(am_corpsecolor, am_corpsecolour,
        "The color of corpses in the automap when you cheat (" BOLD("0") " to " BOLD("255") ")."),
    BOOLCVAR(am_correctaspectratio, "", "", boolfunc1, boolfunc2, 0,
        "Toggles aspect ratio correction in the automap."),
    COLORCVAR(am_crosshaircolor, am_crosshaircolour,
        "The color of the crosshair in the automap when follow mode is off (" BOLD("0") " to " BOLD("255") ")."),
    INTCVAR(am_display, "", "", intfunc1, am_displayfunc2, 0, 0,
        "The display used to show the external automap."),
    BOOLCVAR(am_dynamic, "", "", boolfunc1, boolfunc2, 0,
        "Toggles dynamically updating the automap while it is open."),
    BOOLCVAR(am_external, "", "", boolfunc1, am_externalfunc2, 0,
        "Toggles showing the automap on an external display."),
    COLORCVAR(am_fdwallcolor, am_fdwallcolour,
        "The color of lines in the automap indicating a change in a floor's height (" BOLD("0")
        " to " BOLD("255") ")."),
    BOOLCVAR(am_followmode, "", "", ingamecvarfunc1, am_followmodefunc2, CF_MAPRESET,
        "Toggles following you in the automap."),
    BOOLCVAR(am_grid, "", "", boolfunc1, boolfunc2, 0,
        "Toggles the grid in the automap."),
    COLORCVAR(am_gridcolor, am_gridcolour,
        "The color of the grid in the automap (" BOLD("0") " to " BOLD("255") ")."),
    OTHERCVAR(am_gridsize, "", "", nullfunc1, am_gridsizefunc2,
        "The size of the grid in the automap (" BOLD(ITALICS("width") "\xD7" ITALICS("height")) ")."),
    COLORCVAR(am_markcolor, am_markcolour,
        "The color of marks in the automap (" BOLD("0") " to " BOLD("255") ")."),
    BOOLCVAR(am_mousepanning, "", "", boolfunc1, boolfunc2, 0,
        "Toggles clicking and dragging the mouse to pan around the automap when follow mode is off."),
    BOOLCVAR(am_path, "", "", boolfunc1, am_pathfunc2, 0,
        "Toggles your path in the automap."),
    COLORCVAR(am_pathcolor, am_pathcolour,
        "The color of your path in the automap (" BOLD("0") " to " BOLD("255") ")."),
    INTCVAR(am_pathlength, "", "", intfunc1, colorfunc2, 0, PATHLENGTHVALUEALIAS,
        "The length of your path in the automap (" BOLD("short") ", " BOLD("medium") ", "
        BOLD("long") " or " BOLD("endless") ")."),
    COLORCVAR(am_playercolor, am_playercolour,
        "The color of your arrow in the automap (" BOLD("0") " to " BOLD("255") ")."),
    BOOLCVAR(am_playerstats, "", "", boolfunc1, boolfunc2, 0,
        "Toggles your stats in the automap."),
    COLORCVAR(am_playerstatscolor, am_playerstatscolour,
        "The color of your stats in the automap (" BOLD("0") " to " BOLD("255") ")."),
    COLORCVAR(am_reddoorcolor, am_reddoorcolour,
        "The color of doors in the automap unlocked with a red keycard or skull key (" BOLD("0")
        " to " BOLD("255") ")."),
    COLORCVAR(am_redkeycolor, am_redkeycolour,
        "The color of red keycards and skull keys in the automap when you cheat (" BOLD("0")
        " to " BOLD("255") ")."),
    BOOLCVAR(am_rotatemode, "", "", boolfunc1, am_rotatemodefunc2, 0,
        "Toggles rotating around you in the automap."),
    INTCVAR(am_secretcolor, am_secretcolour, "", intfunc1, colorfunc2, CF_COLOR, SECRETCOLORVALUEALIAS,
        "The color of undiscovered secrets in the automap when you cheat (" BOLD("none") ", or "
        BOLD("0") " to " BOLD("255") ")."),
    COLORCVAR(am_teleportercolor, am_teleportercolour,
        "The color of teleporter lines in the automap (" BOLD("0") " to " BOLD("255") ")."),
    COLORCVAR(am_thingcolor, am_thingcolour,
        "The color of things in the automap when you cheat (" BOLD("0") " to " BOLD("255") ")."),
    COLORCVAR(am_tswallcolor, am_tswallcolour,
        "The color of lines in the automap indicating no change in height (" BOLD("0") " to "
        BOLD("255") ")."),
    COLORCVAR(am_wallcolor, am_wallcolour,
        "The color of solid walls in the automap (" BOLD("0") " to " BOLD("255") ")."),
    COLORCVAR(am_yellowdoorcolor, am_yellowdoorcolour,
        "The color of doors in the automap unlocked with a yellow keycard or skull key (" BOLD("0")
        " to " BOLD("255") ")."),
    COLORCVAR(am_yellowkeycolor, am_yellowkeycolour,
        "The color of yellow keycards and skull keys in the automap when you cheat (" BOLD("0")
        " to " BOLD("255") ")."),
    INTCVAR(ammo, "", "", playercvarsfunc1, playercvarsfunc2, 0, 0,
        "The amount of ammo you have for your currently equipped weapon."),
    BOOLCVAR(animatedstats, "", "", boolfunc1, boolfunc2, 0,
        "Toggles animating your health, armor and ammo in the status bar and widescreen HUD when "
        "they change."),
    PERCENTCVAR(armor, armour, "", playercvarsfunc1, playercvarsfunc2,
        "Your armor (" BOLD("0%") " to " BOLD("200%") ")."),
    INTCVAR(armortype, armourtype, "", armortypefunc1, armortypefunc2, 0, ARMORTYPEVALUEALIAS,
        "Your armor type (" BOLD("none") ", " BOLD("green") " or " BOLD("blue") ")."),
    BOOLCVAR(autoaim, "", "", boolfunc1, boolfunc2, 0,
        "Toggles vertical autoaiming as you fire your weapon while using freelook."),
    BOOLCVAR(autofire, "", "", boolfunc1, boolfunc2, 0,
        "Toggles automatically firing your weapon if there is a monster in front of you."),
    BOOLCVAR(autoload, "", "", boolfunc1, boolfunc2, CF_PISTOLSTART,
        "Toggles automatically loading the last savegame when you die."),
    BOOLCVAR(autosave, "", "", boolfunc1, boolfunc2, 0,
        "Toggles automatically saving the game at the start of each map."),
    BOOLCVAR(autoswitch, "", "", boolfunc1, boolfunc2, 0,
        "Toggles automatically switching your weapon when picking up a new one or better ammo."),
    BOOLCVAR(autotilt, "", "", boolfunc1, autotiltfunc2, 0,
        "Toggles automatically tilting your view when going up or down a flight of stairs."),
    BOOLCVAR(autouse, "", "", boolfunc1, boolfunc2, 0,
        "Toggles automatically using doors and switches in front of you."),
    CCMD(bind, "", "", nullfunc1, bindfunc2, true, BINDFORMAT,
        "Binds an " BOLDITALICS("+action") " or a string of " BOLDITALICS("commands") " to a "
        BOLDITALICS("control") "."),
    CCMD(bindlist, "", "", nullfunc1, bindlistfunc2, false, "",
        "Lists all controls bound to an " BOLDITALICS("+action") " or a string of commands."),
    BOOLCVAR(centerweapon, centreweapon, "", boolfunc1, boolfunc2, 0,
        "Toggles centering your weapon when fired."),
    CCMD(clear, "", "", nullfunc1, clearfunc2, false, "",
        "Clears the console."),
    CCMD(cmdlist, "", ccmdlist, nullfunc1, cmdlistfunc2, true, CMDLISTFORMAT,
        "Lists all console commands."),
    CCMD(condump, "", "", condumpfunc1, condumpfunc2, true, CONDUMPFORMAT,
        "Dumps the contents of the console to a file."),
    INTCVAR(con_edgecolor, con_edgecolour, "", intfunc1, intfunc2, CF_COLOR, EDGECOLORVALUEALIAS,
        "The color of the console's bottom edge (" BOLD("auto") ", or "BOLD("0") " to " BOLD("255") ")."),
    INTCVAR(con_timestampformat, "", "", intfunc1, intfunc2, 0, TIMESTAMPVALUEALIAS,
        "The format of the timestamps in the console (" BOLD("standard") " or " BOLD("military") ")."),
    BOOLCVAR(con_timestamps, "", "", boolfunc1, boolfunc2, 0,
        "Toggles timestamps in the console."),
    INTCVAR(con_warninglevel, "", "", intfunc1, intfunc2, 0, 0,
        "The console's warning level (" BOLD("0") ", " BOLD("1") " or " BOLD("2") ")."),
    INTCVAR(crosshair, "", "", intfunc1, intfunc2, 0, CROSSHAIRVALUEALIAS,
        "Toggles your crosshair (" BOLD("none") ", " BOLD("cross") ", " BOLD("angle") ", "
        BOLD("dot") ", " BOLD("bigcross") ", " BOLD("circle") ", " BOLD("bigcircle") ", "
        BOLD("chevron") ", " BOLD("chevrons") " or " BOLD("arcs") ")."),
    COLORCVAR(crosshaircolor, crosshaircolour,
        "The color of your crosshair (" BOLD("0") " to " BOLD("255") ")."),
    CCMD(cvarlist, "", "", nullfunc1, cvarlistfunc2, true, CVARLISTFORMAT,
        "Lists all console variables."),
    CCMD(endgame, "", "", nullfunc1, endgamefunc2, false, "",
        "Ends the game."),
    INTCVAR(english, "", "", intfunc1, englishfunc2, 0, ENGLISHVALUEALIAS,
        "Toggles the use of American or British English (" BOLD("american") " or " BOLD("british") ")."),
    INTCVAR(episode, "", "", intfunc1, episodefunc2, 0, 0,
        "The currently selected " ITALICS("DOOM") " episode in the menu (" BOLD("1") " to " BOLD("6") ")."),
    CCMD(exec, "", "", nullfunc1, execfunc2, true, EXECFORMAT,
        "Executes all commands in a file."),
    CCMD(exitmap, "", "", alivefunc1, exitmapfunc2, false, "",
        "Exits the current map."),
    INTCVAR(expansion, "", "", intfunc1, expansionfunc2, 0, 0,
        "The currently selected " ITALICS("DOOM II") " expansion in the menu (" BOLD("1") " to " BOLD("3") ")."),
    CCMD(explode, "", "", killfunc1, killfunc2, true, EXPLODEFORMAT,
        "Explodes all " BOLD("barrels") " or " BOLD("missiles") "."),
    COLORCVAR(facebackcolor, facebackcolour,
        "The color of your face's background in the status bar (" BOLD("0") " to " BOLD("255") ")."),
    CCMD(fastmonsters, "", "", nightmarefunc1, fastmonstersfunc2, true, FASTMONSTERSFORMAT,
        "Toggles fast monsters."),
    BOOLCVAR(flashkeys, "", "", boolfunc1, boolfunc2, 0,
        "Toggles flashing the keycard or skull key that is needed when you try to open a locked door."),
    BOOLCVAR(freelook, mouselook, "", boolfunc1, freelookfunc2, 0,
        "Toggles freely looking up and down using the mouse or a controller."),
    CCMD(freeze, "", "", alivefunc1, freezefunc2, true, FREEZEFORMAT,
        "Toggles freeze mode."),
    TIMECVAR(gametime, "", "", nullfunc1, timefunc2,
        "The amount of time " ITALICS(DOOMRETRO_NAME) " has been running."),
    CCMD(give, "", "", givefunc1, givefunc2, true, GIVEFORMAT,
        "Gives " BOLD("ammo") ", " BOLD("armor") ", " BOLD("health") ", " BOLD("keys") ", " BOLD("weapons")
        ", " BOLD("powerups") ", or " BOLD("all") " or certain " BOLDITALICS("items") " to you."),
    CCMD(god, "", "", alivefunc1, godfunc2, true, GODFORMAT,
        "Toggles god mode."),
    BOOLCVAR(groupmessages, "", "", boolfunc1, boolfunc2, 0,
        "Toggles the grouping of identical player messages."),
    PERCENTCVAR(health, "", "", playercvarsfunc1, playercvarsfunc2,
        "Your health (" BOLD("-99%") " to " BOLD("200%") ")."),
    CHEATCCMD(idbeholda, false),
    CHEATCCMD(idbeholdi, false),
    CHEATCCMD(idbeholdl, false),
    CHEATCCMD(idbeholdr, false),
    CHEATCCMD(idbeholds, false),
    CHEATCCMD(idbeholdv, false),
    CHEATCCMD(idchoppers, false),
    CHEATCCMD(idclev, true),
    CHEATCCMD(idclip, false),
    CHEATCCMD(iddqd, false),
    CHEATCCMD(iddt, false),
    CHEATCCMD(idfa, false),
    CHEATCCMD(idkfa, false),
    CHEATCCMD(idmus, true),
    CHEATCCMD(idmypos, false),
    CHEATCCMD(idspispopd, false),
    CCMD(if, "", "", nullfunc1, iffunc2, true, IFFORMAT,
        "Executes a string of " BOLDITALICS("commands") " if a " BOLDITALICS("CVAR") " equals a "
        BOLDITALICS("value") "."),
    BOOLCVAR(infighting, "", "", boolfunc1, boolfunc2, 0,
        "Toggles infighting amongst monsters once you die."),
    CCMD(infiniteammo, "", "", ingameccmdfunc1, infiniteammofunc2, true, INFINITEAMMOFORMAT,
        "Toggles an infinite amount of ammo for all of your weapons."),
    BOOLCVAR(infiniteheight, "", "", boolfunc1, boolfunc2, 0,
        "Toggles giving you and everything else in the current map infinite height."),
    BOOLCVAR(joy_analog, joy_analogue, "", boolfunc1, boolfunc2, 0,
        "Toggles whether movement using a controller's thumbsticks is analog or digital."),
    BOOLCVAR(joy_autoaim_horizontal, "", "", boolfunc1, boolfunc2, 0,
        "Toggles horizontal autoaiming as you fire your weapon using a controller."),
    FLOATCVAR(joy_deadzone_left, "", "", joy_deadzonecvarsfunc1, joy_deadzonecvarsfunc2, CF_PERCENT,
        "The dead zone of a controller's left thumbstick (" BOLD("0%") " to " BOLD("30%") ")."),
    FLOATCVAR(joy_deadzone_right, "", "", joy_deadzonecvarsfunc1, joy_deadzonecvarsfunc2, CF_PERCENT,
        "The dead zone of a controller's right thumbstick (" BOLD("0%") " to " BOLD("30%") ")."),
    BOOLCVAR(joy_invertyaxis, "", "", boolfunc1, boolfunc2, 0,
        "Toggles inverting the vertical axis of a controller's right thumbstick when you look up or down."),
    PERCENTCVAR(joy_rumble_barrels, "", "", intfunc1, intfunc2,
        "The amount a controller rumbles when you are near an exploding barrel (" BOLD("0%") " to " BOLD("200%") ")."),
    PERCENTCVAR(joy_rumble_damage, "", "", intfunc1, intfunc2,
        "The amount a controller rumbles when you take damage (" BOLD("0%") " to " BOLD("200%") ")."),
    BOOLCVAR(joy_rumble_fall, "", "", boolfunc1, boolfunc2, 0,
        "Toggles rumbling a controller when you land after a fall."),
    BOOLCVAR(joy_rumble_pickup, "", "", boolfunc1, boolfunc2, 0,
        "Toggles rumbling a controller when you pick something up."),
    PERCENTCVAR(joy_rumble_weapons, "", "", intfunc1, intfunc2,
        "The amount a controller rumbles when you fire your weapon (" BOLD("0%") " to " BOLD("200%") ")."),
    FLOATCVAR(joy_sensitivity_horizontal, "", "", floatfunc1, joy_sensitivitycvarsfunc2, 0,
        "The horizontal sensitivity of a controller's thumbsticks (" BOLD("0") " to " BOLD("128") ")."),
    FLOATCVAR(joy_sensitivity_vertical, "", "", floatfunc1, joy_sensitivitycvarsfunc2, 0,
        "The vertical sensitivity of a controller's thumbsticks (" BOLD("0") " to " BOLD("128") ")."),
    BOOLCVAR(joy_swapthumbsticks, "", "", boolfunc1, boolfunc2, 0,
        "Toggles swapping a controller's left and right thumbsticks."),
    INTCVAR(joy_thumbsticks, "", "", intfunc1, intfunc2, 0, 0,
        "The number of thumbsticks on a controller (" BOLD("1") " or " BOLD("2") ")."),
    CCMD(kill, "", "", killfunc1, killfunc2, true, KILLFORMAT,
        "Kills the " BOLD("player") ", " BOLD("all") " monsters or a type of " BOLDITALICS("monster") "."),
    CCMD(license, licence, "", nullfunc1, licensefunc2, false, "",
        "Shows the " ITALICS(DOOMRETRO_LICENSE ".")),
    CCMD(load, "", "", nullfunc1, loadfunc2, true, LOADFORMAT,
        "Loads a savegame."),
    BOOLCVAR(m_acceleration, "", "", boolfunc1, boolfunc2, 0,
        "Toggles the acceleration of mouse movement."),
    BOOLCVAR(m_doubleclick_use, "", "", boolfunc1, boolfunc2, 0,
        "Toggles double-clicking a mouse button to perform the " BOLD("+use") " action."),
    BOOLCVAR(m_invertyaxis, "", "", boolfunc1, boolfunc2, 0,
        "Toggles inverting the mouse's vertical axis when using freelook."),
    BOOLCVAR(m_novertical, "", "", boolfunc1, boolfunc2, 0,
        "Toggles no vertical movement of the mouse."),
    BOOLCVAR(m_pointer, "", "", boolfunc1, boolfunc2, 0,
        "Toggles the mouse pointer in the menu."),
    FLOATCVAR(m_sensitivity_horizontal, m_sensitivity, "", floatfunc1, floatfunc2, 0,
        "The mouse's horizontal sensitivity (" BOLD("0") " to " BOLD("128") ")."),
    FLOATCVAR(m_sensitivity_vertical, "", "", floatfunc1, floatfunc2, 0,
        "The mouse's vertical sensitivity (" BOLD("0") " to " BOLD("128") ")."),
    BOOLCVAR(m_smoothing, "", "", boolfunc1, boolfunc2, 0,
        "Toggles the smoothing of mouse movement."),
    CCMD(map, "", warp, mapfunc1, mapfunc2, true, MAPFORMAT1,
        "Warps you to another map."),
    CCMD(maplist, "", "", nullfunc1, maplistfunc2, false, "",
        "Lists all of the maps available to play."),
    CCMD(mapstats, "", "", nullfunc1, mapstatsfunc2, false, "",
        "Shows stats about the current map."),
    TIMECVAR(maptime, "", "", nullfunc1, timefunc2,
        "The amount of time you have been in the current map."),
    BOOLCVAR(melt, "", "", boolfunc1, boolfunc2, 0,
        "Toggles a melting effect when transitioning between some screens."),
    BOOLCVAR(menuhighlight, "", "", boolfunc1, boolfunc2, 0,
        "Toggles the highlighting of items selected in the menu."),
    BOOLCVAR(menushadow, "", "", boolfunc1, boolfunc2, 0,
        "Toggles shadows cast by items in the menu."),
    BOOLCVAR(menuspin, "", "", boolfunc1, boolfunc2, 0,
        "Toggles spinning your view in the menu's background."),
    BOOLCVAR(messages, "", "", boolfunc1, boolfunc2, 0,
        "Toggles displaying player messages at the top of the screen."),
    PERCENTCVAR(movebob, "", "", intfunc1, intfunc2,
        "The amount your view bobs as you move (" BOLD("0%") " to " BOLD("100%") ")."),
    CCMD(name, "", "", namefunc1, namefunc2, true, NAMEFORMAT,
        "Gives a " BOLDITALICS("name") " to the " BOLDITALICS("monster") " nearest to you."),
    BOOLCVAR(negativehealth, "", "", boolfunc1, boolfunc2, 0,
        "Toggles allowing your health to be less than " BOLD("0%") " when you die."),
    CCMD(newgame, "", "", nullfunc1, newgamefunc2, true, "",
        "Starts a new game."),
    CCMD(noclip, "", "", ingameccmdfunc1, noclipfunc2, true, NOCLIPFORMAT,
        "Toggles no clipping mode."),
    CCMD(nomonsters, "", "", nullfunc1, nomonstersfunc2, true, NOMONSTERSFORMAT,
        "Toggles the presence of monsters in maps."),
    CCMD(notarget, "", "", ingameccmdfunc1, notargetfunc2, true, NOTARGETFORMAT,
        "Toggles monsters not targeting you."),
    BOOLCVAR(obituaries, "", "", boolfunc1, boolfunc2, 0,
        "Toggles displaying obituaries when you or monsters are killed."),
    CCMD(palette, "", "", nullfunc1, palettefunc2, false, "",
        "Shows all 256 colors in the current palette."),
    CCMD(pistolstart, "", "", nullfunc1, pistolstartfunc2, true, PISTOLSTARTFORMAT,
        "Toggles you starting each map with 100% health, no armor, and only your pistol with 50 bullets."),
    CCMD(play, "", "", playfunc1, playfunc2, true, PLAYFORMAT,
        "Plays a " BOLDITALICS("sound effect") " or " BOLDITALICS("music") " lump."),
    INTCVAR(playergender, "", "", intfunc1, intfunc2, 0, GENDERVALUEALIAS,
        "Your gender (" BOLD("male") ", " BOLD("female") " or " BOLD("other") ")."),
    STRCVAR(playername, "", "", nullfunc1, strfunc2, 0, 16,
        "Your name."),
    CCMD(playerstats, "", "", nullfunc1, playerstatsfunc2, false, "",
        "Shows stats about you."),
    CCMD(print, "", "", ingameccmdfunc1, printfunc2, true, PRINTFORMAT,
        "Prints a player \"" BOLDITALICS("message") "\"."),
    CCMD(quit, "", exit, nullfunc1, quitfunc2, false, "",
        "Quits to the " DESKTOP "."),
    BOOLCVAR(r_althud, "", "", boolfunc1, boolfunc2, 0,
        "Toggles an alternate heads-up display when widescreen."),
    BOOLCVAR(r_althud_ammobars, "", "", boolfunc1, boolfunc2, 0,
        "Toggles additional bars indicating the amount of ammo the player has for all ammo types in the alternate HUD."),
    BOOLCVAR(r_althudfont, "", "", boolfunc1, boolfunc2, 0,
        "Toggles displaying messages in an alternate font when the alternate HUD is displayed."),
    BOOLCVAR(r_antialiasing, "", "", boolfunc1, r_antialiasingfunc2, 0,
        "Toggles anti-aliasing when the graphic detail is low."),
    INTCVAR(r_berserkeffect, "", "", intfunc1, intfunc2, 0, 0,
        "The intensity of the red effect when you have a berserk power-up and your fists equipped ("
        BOLD("0") " to " BOLD("8") ")."),
    INTCVAR(r_blood, "", "", intfunc1, r_bloodfunc2, 0, BLOODVALUEALIAS,
        "The colors of the blood spilled by you and monsters (" BOLD("all") ", " BOLD("none") ", "
        BOLD("red") ", " BOLD("green") " or " BOLD("nofuzz") ")."),
    BOOLCVAR(r_blood_gibs, "", "", boolfunc1, boolfunc2, 0,
        "Toggles spawning blood when monsters are gibbed."),
    BOOLCVAR(r_blood_melee, "", "", boolfunc1, boolfunc2, 0,
        "Toggles spawning blood during melee attacks from monsters."),
    INTCVAR(r_bloodsplats_max, "", "", intfunc1, intfunc2, 0, 0,
        "The maximum number of blood splats allowed in each map (" BOLD("0") " to " BOLD("1,048,576") ")."),
    INTCVAR(r_bloodsplats_total, "", "", intfunc1, intfunc2, CF_READONLY, 0,
        "The total number of blood splats in the current map."),
    BOOLCVAR(r_bloodsplats_translucency, "", "", boolfunc1, r_bloodsplats_translucencyfunc2, 0,
        "Toggles the translucency of blood splats."),
    INTCVAR(r_bloodsplats_visible, "", "", intfunc1, intfunc2, CF_READONLY, 0,
        "The number of blood splats currently visible."),
    BOOLCVAR(r_brightmaps, "", "", boolfunc1, r_brightmapsfunc2, 0,
        "Toggles brightmaps on some wall textures."),
    BOOLCVAR(r_corpses_color, r_corpses_colour, "", boolfunc1, boolfunc2, 0,
        "Toggles randomly colored marine corpses."),
    BOOLCVAR(r_corpses_gib, "", "", boolfunc1, boolfunc2, 0,
        "Toggles some corpses gibbing when barrels or rockets explode nearby."),
    BOOLCVAR(r_corpses_mirrored, "", "", boolfunc1, r_corpses_mirroredfunc2, 0,
        "Toggles randomly mirrored corpses."),
    BOOLCVAR(r_corpses_moreblood, "", "", boolfunc1, boolfunc2, 0,
        "Toggles blood splats spawned around corpses at the start of each map."),
    BOOLCVAR(r_corpses_nudge, "", "", boolfunc1, boolfunc2, 0,
        "Toggles corpses and the items they drop being nudged when walked over."),
    BOOLCVAR(r_corpses_slide, "", "", boolfunc1, boolfunc2, 0,
        "Toggles corpses sliding when barrels or rockets explode nearby."),
    BOOLCVAR(r_corpses_smearblood, "", "", boolfunc1, boolfunc2, 0,
        "Toggles corpses leaving blood splats behind as they slide."),
    BOOLCVAR(r_damageeffect, "", "", boolfunc1, boolfunc2, 0,
        "Toggles the red effect when you take damage."),
    INTCVAR(r_detail, "", "", intfunc1, r_detailfunc2, 0, DETAILVALUEALIAS,
        "Toggles the graphic detail (" BOLD("high") " or " BOLD("low") ")."),
    BOOLCVAR(r_diskicon, r_discicon, "", boolfunc1, r_diskiconfunc2, 0,
        "Toggles showing a disk icon when loading and saving."),
    BOOLCVAR(r_ditheredlighting, "", "", boolfunc1, r_ditheredlightingfunc2, 0,
        "Toggles dithered lighting cast on textures and sprites."),
    PERCENTCVAR(r_extralighting, "", "", intfunc1, intfunc2,
        "The extra lighting applied to the current map (" BOLD("0%") " to " BOLD("100%") ")."),
    BOOLCVAR(r_fixmaperrors, "", "", boolfunc1, r_fixmaperrorsfunc2, CF_NEXTMAP,
        "Toggles fixing many mapping errors in the official " ITALICS("DOOM") " and " ITALICS("DOOM II") " WADs."),
    BOOLCVAR(r_fixspriteoffsets, "", "", boolfunc1, boolfunc2, 0,
        "Toggles fixing sprite offsets."),
    BOOLCVAR(r_floatbob, "", "", boolfunc1, boolfunc2, 0,
        "Toggles some power-ups bobbing up and down."),
    INTCVAR(r_fov, "", "", intfunc1, r_fovfunc2, 0, 0,
        "Your field of view (" BOLD("45\xB0") " to " BOLD("135\xB0") ")."),
    FLOATCVAR2(r_gamma, "", "", r_gammafunc1, r_gammafunc2, 0,
        "The screen's gamma correction level (" BOLD("off") ", or " BOLD("0.50") " to " BOLD("2.0") ")."),
    BOOLCVAR(r_graduallighting, "", "", boolfunc1, boolfunc2, 0,
        "Toggles gradual lighting under doors and crushing sectors."),
    BOOLCVAR(r_homindicator, "", "", boolfunc1, boolfunc2, 0,
        "Toggles a flashing \"Hall Of Mirrors\" indicator."),
    BOOLCVAR(r_hud, "", "", boolfunc1, r_hudfunc2, 0,
        "Toggles a heads-up display when widescreen."),
    BOOLCVAR(r_hud_translucency, "", "", boolfunc1, r_hud_translucencyfunc2, 0,
        "Toggles the translucency of the heads-up display when widescreen."),
    BOOLCVAR(r_linearskies, "", "", boolfunc1, boolfunc2, 0,
        "Toggles horizontally linear skies."),
    BOOLCVAR(r_liquid_bob, "", "", boolfunc1, boolfunc2, 0,
        "Toggles the bobbing of liquid sectors."),
    BOOLCVAR(r_liquid_bobsprites, "", "", boolfunc1, boolfunc2, 0,
        "Toggles the bobbing of sprites in liquid sectors."),
    BOOLCVAR(r_liquid_clipsprites, "", "", boolfunc1, boolfunc2, 0,
        "Toggles clipping the bottom of sprites in liquid sectors."),
    BOOLCVAR(r_liquid_current, "", "", boolfunc1, boolfunc2, 0,
        "Toggles a slight current being applied to liquid sectors."),
    BOOLCVAR(r_liquid_lowerview, "", "", boolfunc1, boolfunc2, 0,
        "Toggles lowering your view when you are in a liquid sector."),
    BOOLCVAR(r_liquid_swirl, "", "", boolfunc1, boolfunc2, 0,
        "Toggles the swirl of liquid sectors."),
    OTHERCVAR(r_lowpixelsize, "", "", nullfunc1, r_lowpixelsizefunc2,
        "The size of each pixel when the graphic detail is low (" BOLD(ITALICS("width") "\xD7" ITALICS("height")) ")."),
    BOOLCVAR(r_mirroredweapons, "", "", boolfunc1, r_mirroredweaponsfunc2, 0,
        "Toggles randomly mirroring the weapons dropped by monsters."),
    BOOLCVAR(r_percolumnlighting, "", "", boolfunc1, boolfunc2, 0,
        "Toggles per-column lighting cast on monsters."),
    BOOLCVAR(r_pickupeffect, "", "", boolfunc1, boolfunc2, 0,
        "Toggles the gold effect when you pick something up."),
    BOOLCVAR(r_playerweapon, "", "", boolfunc1, boolfunc2, 0,
        "Toggles showing your weapon."),
    BOOLCVAR(r_playerweapon_translucency, "", "", boolfunc1, boolfunc2, 0,
        "Toggles the translucency effect when firing your weapon."),
    BOOLCVAR(r_radsuiteffect, "", "", boolfunc1, boolfunc2, 0,
        "Toggles the green effect while you wear a radiation shielding suit power-up."),
    BOOLCVAR(r_randomstartframes, "", "", boolfunc1, r_randomstartframesfunc2, CF_NEXTMAP,
        "Toggles randomizing the start frames of certain sprites."),
    BOOLCVAR(r_rockettrails, "", "", boolfunc1, boolfunc2, 0,
        "Toggles the trail of smoke behind rockets fired by you and cyberdemons."),
    BOOLCVAR(r_rockettrails_translucency, "", "", boolfunc1,
        r_rockettrails_translucencyfunc2, 0,
        "Toggles the translucency of the trail of smoke behind rockets fired by you and cyberdemons."),
    INTCVAR(r_screensize, "", "", intfunc1, r_screensizefunc2, 0, 0,
        "The screen size (" BOLD("0") " to " BOLD("8") ")."),
    BOOLCVAR(r_shadows, "", "", boolfunc1, boolfunc2, 0,
        "Toggles sprites casting shadows."),
    BOOLCVAR(r_shadows_translucency, "", "", boolfunc1, r_shadows_translucencyfunc2, 0,
        "Toggles the translucency of shadows cast by sprites."),
    BOOLCVAR(r_shake_barrels, "", "", boolfunc1, boolfunc2, 0,
        "Toggles shaking your view when you are near an exploding barrel."),
    BOOLCVAR(r_shake_berserk, "", "", boolfunc1, boolfunc2, 0,
        "Toggles shaking your view when you have a berserk power-up and punch something."),
    BOOLCVAR(r_shake_damage, "", "", boolfunc1, boolfunc2, 0,
        "Toggles shaking the screen when you take damage."),
    BOOLCVAR(r_sprites_translucency, "", "", boolfunc1, r_sprites_translucencyfunc2, 0,
        "Toggles the translucency of certain sprites."),
    BOOLCVAR(r_teleportzoom, "", "", boolfunc1, boolfunc2, 0,
        "Toggles a zoom effect when you teleport."),
    BOOLCVAR(r_textures, "", "", boolfunc1, r_texturesfunc2, 0,
        "Toggles showing all textures."),
    BOOLCVAR(r_textures_translucency, "", "", boolfunc1, r_textures_translucencyfunc2, 0,
        "Toggles the translucency of certain " ITALICS("BOOM-") "compatible wall textures."),
    CCMD(readme, "", "", nullfunc1, readmefunc2, false, "",
        "Shows the accompanying readme file for the currently loaded PWAD."),
    CCMD(regenhealth, "", "", ingameccmdfunc1, regenhealthfunc2, true, REGENHEALTHFORMAT,
        "Toggles regenerating your health by 1% every second when it's less than 100%."),
    CCMD(releasenotes, "", "", nullfunc1, releasenotesfunc2, false, "",
        "Opens a list of what's changed since the last version of " ITALICS(DOOMRETRO_NAME ".")),
    CCMD(remove, "", "", killfunc1, killfunc2, true, REMOVEFORMAT,
        "Removes all " BOLD("decorations") ", " BOLD("corpses") ", " BOLD("bloodsplats") ", "
        BOLD("items") ", certain " BOLDITALICS("items") ", or " BOLD("everything") "."),
    CCMD(reset, "", "", nullfunc1, resetfunc2, true, RESETFORMAT,
        "Resets a " BOLDITALICS("CVAR") " to its default."),
    CCMD(resetall, "", "", nullfunc1, resetallfunc2, false, "",
        "Resets all CVARs and bound controls to their defaults."),
    CCMD(respawnitems, "", "", nullfunc1, respawnitemsfunc2, true, RESPAWNITEMSFORMAT,
        "Toggles respawning items."),
    CCMD(respawnmonsters, "", "", nightmarefunc1, respawnmonstersfunc2, true, RESPAWNMONSTERSFORMAT,
        "Toggles respawning monsters."),
    CCMD(restartmap, "", "", inaftergameccmdfunc1, restartmapfunc2, false, "",
        "Restarts the current map."),
    CCMD(resurrect, "", "", resurrectfunc1, resurrectfunc2, true, RESURRECTFORMAT,
        "Resurrects the " BOLD("player") ", " BOLD("all") " monsters, or a type of " BOLDITALICS("monster") "."),
    CHEATCCMD(ryhan, false),
    INTCVAR(s_channels, "", "", intfunc1, intfunc2, 0, 0,
        "The number of sound effects that can be played at the same time (" BOLD("8") " to " BOLD("64") ")."),
    BOOLCVAR(s_fullsfx, "", "", boolfunc1, boolfunc2, 0,
        "Toggles playing sound effects in full as things are removed from the map."),
    BOOLCVAR(s_lowermenumusic, "", "", boolfunc1, boolfunc2, 0,
        "Toggles lowering the music's volume in the menu and console."),
    BOOLCVAR(s_musicinbackground, "", "", boolfunc1, boolfunc2, 0,
        "Toggles continuing to play music in the background when " ITALICS(DOOMRETRO_NAME) "'s window loses focus."),
    PERCENTCVAR(s_musicvolume, "", "", s_volumecvarsfunc1, s_volumecvarsfunc2,
        "The volume level of music (" BOLD("0%") " to " BOLD("100%") ")."),
    BOOLCVAR(s_randommusic, "", "", boolfunc1, s_randommusicfunc2, 0,
        "Toggles randomizing the music for each map."),
    BOOLCVAR(s_randompitch, "", "", boolfunc1, boolfunc2, 0,
        "Toggles randomizing the pitch of sound effects made by monsters."),
    PERCENTCVAR(s_sfxvolume, "", "", s_volumecvarsfunc1, s_volumecvarsfunc2,
        "The volume level of sound effects (" BOLD("0%") " to " BOLD("100%") ")."),
    BOOLCVAR(s_stereo, "", "", boolfunc1, boolfunc2, 0,
        "Toggles playing sound effects in mono or stereo."),
    CCMD(save, "", "", alivefunc1, savefunc2, true, SAVEFORMAT,
        "Saves the game."),
    INTCVAR(savegame, "", "", intfunc1, savegamefunc2, 0, 0,
        "The currently selected savegame in the menu (" BOLD("1") " to " BOLD("8") ")."),
    BOOLCVAR(secretmessages, "", "", boolfunc1, boolfunc2, 0,
        "Toggles displaying a message when you find a secret."),
    INTCVAR(skilllevel, "", "", intfunc1, skilllevelfunc2, 0, 0,
        "The currently selected skill level in the menu (" BOLD("1") " to " BOLD("5") ")."),
    BOOLCVAR(smoothtransitions, "", "", boolfunc1, boolfunc2, 0,
        "Toggles the effects when transitioning between some screens."),
    CCMD(spawn, "", summon, spawnfunc1, spawnfunc2, true, SPAWNFORMAT,
        "Spawns an " BOLDITALICS("item") " or " BOLDITALICS("monster") " in front of you."),
    PERCENTCVAR(stillbob, "", "", intfunc1, intfunc2,
        "The amount your view and weapon bob up and down when you stand still (" BOLD("0%") " to " BOLD("100%") ")."),
    INTCVAR(sucktime, "", "", intfunc1, intfunc2, 0, SUCKSVALUEALIAS,
        "The amount of time you must complete a map before you \"SUCK\" (" BOLD("off") ", or "
        BOLD("1") " to " BOLD("24") " hours)."),
    CCMD(take, "", "", takefunc1, takefunc2, true, TAKEFORMAT,
        "Takes " BOLD("ammo") ", " BOLD("armor") ", " BOLD("health") ", " BOLD("keys") ", " BOLD("weapons")
        ", " BOLD("powerups") ", or " BOLD("all") " or certain " BOLDITALICS("items") " away from you."),
    CCMD(teleport, "", "", teleportfunc1, teleportfunc2, true, TELEPORTFORMAT,
        "Teleports you to (" BOLDITALICS("x") ", " BOLDITALICS("y") ", " BOLDITALICS("z") ") in "
        "the current map."),
    CCMD(thinglist, "", "", ingameccmdfunc1, thinglistfunc2, false, "",
        "Lists all things in the current map."),
    CCMD(timer, "", "", nullfunc1, timerfunc2, true, TIMERFORMAT,
        "Sets a timer to exit each map after a number of " BOLDITALICS("minutes") "."),
    CCMD(toggle, "", "", nullfunc1, togglefunc2, true, TOGGLEFORMAT,
        "Toggles a " BOLDITALICS("CVAR") " " BOLD("on") " or " BOLD("off") "."),
    BOOLCVAR(tossdrop, "", "", boolfunc1, boolfunc2, 0,
        "Toggles tossing items dropped by monsters when they die."),
    PERCENTCVAR(turbo, "", "", intfunc1, turbofunc2,
        "The speed you move (" BOLD("10%") " to " BOLD("400%") ")."),
    CCMD(unbind, "", "", nullfunc1, unbindfunc2, true, UNBINDFORMAT,
        "Unbinds the " BOLDITALICS("+action") " from a " BOLDITALICS("control") "."),
    INTCVAR(units, "", "", intfunc1, intfunc2, 0, UNITSVALUEALIAS,
        "The units used by certain stats (" BOLD("imperial") " or " BOLD("metric") ")."),
    CCMD(vanilla, "", "", nullfunc1, vanillafunc2, true, VANILLAFORMAT,
        "Toggles vanilla mode."),
    STRCVAR(version, "", "", nullfunc1, strfunc2, CF_READONLY, 16,
        ITALICS(DOOMRETRO_NAME "'s") " version."),
    INTCVAR(vid_aspectratio, "", "", intfunc1, vid_aspectratiofunc2, 0, RATIOVALUEALIAS,
        "The aspect ratio of the display when widescreen (" BOLD("16:9") ", " BOLD("16:10") ", "
        BOLD("21:9") ", " BOLD("32:9") " or " BOLD("auto") ")."),
    PERCENTCVAR(vid_blue, "", "", intfunc1, vid_bluefunc2,
        "The intensity of blue on the screen (" BOLD("-100%") " to " BOLD("100%") ")."),
    BOOLCVAR(vid_borderlesswindow, "", "", boolfunc1, vid_borderlesswindowfunc2, 0,
        "Toggles using a borderless window when fullscreen."),
    PERCENTCVAR(vid_brightness, "", "", intfunc1, vid_brightnessfunc2,
        "The screen's brightness (" BOLD("-100%") " to " BOLD("100%") ")."),
    INTCVAR(vid_capfps, "", "", intfunc1, intfunc2, 0, CAPVALUEALIAS,
        "The number of frames at which to cap the framerate (" BOLD("off") ", or " BOLD("35") " to "
        BOLD("1,000") "). There is no interpolation between frames when this CVAR is " BOLD("35") "."),
    PERCENTCVAR(vid_contrast, "", "", intfunc1, vid_contrastfunc2,
        "The screen's contrast (" BOLD("-100%") " to " BOLD("100%") ")."),
    INTCVAR(vid_display, "", "", intfunc1, vid_displayfunc2, 0, 0,
        "The display used to play " ITALICS(DOOMRETRO_NAME) " on."),
#if !defined(_WIN32)
    STRCVAR(vid_driver, "", "", nullfunc1, strfunc2, 0, 16,
        "The video driver used to play " ITALICS(DOOMRETRO_NAME) "."),
#endif
    BOOLCVAR(vid_fullscreen, "", "", boolfunc1, vid_fullscreenfunc2, 0,
        "Toggles between fullscreen and a window."),
    PERCENTCVAR(vid_green, "", "", intfunc1, vid_greenfunc2,
        "The intensity of green on the screen (" BOLD("-100%") " to " BOLD("100%") ")."),
    PERCENTCVAR(vid_motionblur, "", "", intfunc1, vid_motionblurfunc2,
        "The amount of motion blur when you turn quickly (" BOLD("0%") " to " BOLD("100%") ")."),
    BOOLCVAR(vid_pillarboxes, "", "", boolfunc1, vid_pillarboxesfunc2, 0,
        "Toggles using the pillarboxes either side of the screen for certain effects when not widescreen."),
    PERCENTCVAR(vid_red, "", "", intfunc1, vid_redfunc2,
        "The intensity of red on the screen (" BOLD("-100%") " to " BOLD("100%") ")."),
    PERCENTCVAR(vid_saturation, "", "", intfunc1, vid_saturationfunc2,
        "The screen's saturation (" BOLD("-100%") " to " BOLD("100%") ")."),
#if defined(_WIN32)
    STRCVAR(vid_scaleapi, "", "", vid_scaleapifunc1, vid_scaleapifunc2, 0, 16,
        "The API used to scale every frame (" BOLD("\"direct3d\"") ", " BOLD("\"opengl\"") " or "
        BOLD("\"software\"") ")."),
#else
    STRCVAR(vid_scaleapi, "", "", vid_scaleapifunc1, vid_scaleapifunc2, 0, 16,
        "The API used to scale every frame (" BOLD("\"opengl\"") ", " BOLD("\"opengles\"") ", "
        BOLD("\"opengles2\"") " or " BOLD("\"software\"") ")."),
#endif
    STRCVAR(vid_scalefilter, "", "", vid_scalefilterfunc1, vid_scalefilterfunc2, 0, 16,
        "The filter applied when scaling every frame (" BOLD("\"nearest\"") ", "
        BOLD("\"linear\"") " or " BOLD("\"nearest_linear\"") ")."),
    OTHERCVAR(vid_screenresolution, "", "", nullfunc1, vid_screenresolutionfunc2,
        "The screen's resolution when fullscreen (" BOLD("desktop") " or " BOLD(ITALICS("width")
        "\xD7" ITALICS("height")) ")."),
    BOOLCVAR(vid_showfps, "", "", boolfunc1, vid_showfpsfunc2, 0,
        "Toggles showing the number of frames per second."),
#if defined(__APPLE__)
    INTCVAR(vid_vsync, "", "", intfunc1, vid_vsyncfunc2, 0, VSYNCVALUEALIAS,
        "Toggles vertical sync with the display's refresh rate (" BOLD("on") " or " BOLD("off") ")."),
#else
    INTCVAR(vid_vsync, "", "", intfunc1, vid_vsyncfunc2, 0, VSYNCVALUEALIAS,
        "Toggles vertical sync with the display's refresh rate (" BOLD("on") ", " BOLD("off") " or "
        BOLD("adaptive") ")."),
#endif
    BOOLCVAR(vid_widescreen, "", "", boolfunc1, vid_widescreenfunc2, 0,
        "Toggles widescreen."),
    OTHERCVAR(vid_windowpos, "", vid_windowposition, nullfunc1, vid_windowposfunc2,
        "The position of the window on the desktop (" BOLD("centered") " or " BOLD("(")
        BOLDITALICS("x") BOLD(",") BOLDITALICS("y") BOLD(")") ")."),
    OTHERCVAR(vid_windowsize, "", "", nullfunc1, vid_windowsizefunc2,
        "The size of the window on the desktop (" BOLD(ITALICS("width") "\xD7" ITALICS("height")) ")."),
#if defined(_WIN32)
    STRCVAR(wad, "", "", nullfunc1, strfunc2, CF_READONLY, MAX_PATH,
        "The last WAD(s) to be opened by the WAD launcher."),
#endif
    STRCVAR(wadfolder, "", "", nullfunc1, strfunc2, 0, MAX_PATH,
        "The folder the currently loaded WAD is in."),
    INTCVAR(weapon, "", "", weaponfunc1, weaponfunc2, 0, WEAPONVALUEALIAS,
        "Your currently equipped weapon (" BOLD("fists") ", " BOLD("chainsaw") ", " BOLD("pistol") ", "
        BOLD("shotgun") ", " BOLD("chaingun") ", " BOLD("rocketlauncher") ", "
        BOLD("plasmarifle") " or " BOLD("bfg9000") ")."),
    PERCENTCVAR(weaponbob, "", "", intfunc1, intfunc2,
        "The amount your weapon bobs up and down as you move (" BOLD("0%") " to " BOLD("100%") ")."),
    BOOLCVAR(weaponbounce, "", "", boolfunc1, boolfunc2, 0,
        "Toggles the bounce of your weapon when you land after a fall."),
    BOOLCVAR(weaponrecoil, "", "", boolfunc1, weaponrecoilfunc2, 0,
        "Toggles the recoil of your weapon when you fire it."),
    CCMD(wiki, help, "", nullfunc1, wikifunc2, false, "",
        "Opens the " ITALICS(DOOMRETRO_WIKINAME ".")),

    { "", "", "", nullfunc1, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, "", "" }
};

static bool run(void)
{
    return ((gamekeydown[keyboardrun] || gamekeydown[keyboardrun2] || mousebuttons[mouserun]
        || (controllerbuttons & controllerrun)) ^ alwaysrun);
}

static bool strafe(void)
{
    return (gamekeydown[keyboardstrafe] || gamekeydown[keyboardstrafe2] || mousebuttons[mousestrafe]
        || (controllerbuttons & controllerstrafe));
}

static void alwaysrunactionfunc(void)
{
    G_ToggleAlwaysRun(ev_none);
    I_InitKeyboard();
}

static void automapactionfunc(void)
{
    if (gamestate != GS_LEVEL || mapwindow)
        return;

    if (!automapactive)
        AM_Start(true);
    else
        AM_Stop();
}

static void backactionfunc(void)
{
    if (gamestate == GS_LEVEL)
    {
        viewplayer->cmd.forwardmove -= forwardmove[run()];
        P_MovePlayer();
    }
}

static void bfg9000actionfunc(void)
{
    if (gamestate == GS_LEVEL)
        P_ChangeWeapon(wp_bfg, false);
}

static void chaingunactionfunc(void)
{
    if (gamestate == GS_LEVEL)
        P_ChangeWeapon(wp_chaingun, false);
}

static void chainsawactionfunc(void)
{
    if (gamestate == GS_LEVEL)
        P_ChangeWeapon(wp_chainsaw, false);
}

static void clearmarkactionfunc(void)
{
    if (gamestate == GS_LEVEL && (automapactive || mapwindow))
        AM_ClearMarks();
}

static void consoleactionfunc(void)
{
    if (consoleactive)
        C_HideConsole();
    else
        C_ShowConsole(false);
}

static void fireactionfunc(void)
{
    if (gamestate == GS_LEVEL)
        P_FireWeapon();
}

static void fistsactionfunc(void)
{
    if (gamestate == GS_LEVEL && (!viewplayer->weaponowned[wp_chainsaw] || viewplayer->powers[pw_strength]))
        P_ChangeWeapon(wp_fist, false);
}

static void followmodeactionfunc(void)
{
    if (gamestate == GS_LEVEL && (automapactive || mapwindow))
        AM_ToggleFollowMode(!am_followmode);
}

static void forwardactionfunc(void)
{
    if (gamestate == GS_LEVEL)
    {
        viewplayer->cmd.forwardmove += forwardmove[run()];
        P_MovePlayer();
    }
}

static void lookcenteractionfunc(void)
{
    if (gamestate == GS_LEVEL && canfreelook && !automapactive && !mapwindow)
    {
        if (viewplayer->pitch > 0)
            viewplayer->pitch -= MIN(viewplayer->pitch, 16 * MLOOKUNIT);
        else if (viewplayer->pitch < 0)
            viewplayer->pitch += MIN(-viewplayer->pitch, 16 * MLOOKUNIT);

        viewplayer->oldpitch = viewplayer->pitch;
    }
}

static void lookdownactionfunc(void)
{
    if (gamestate == GS_LEVEL && canfreelook && !automapactive && !mapwindow)
    {
        viewplayer->pitch = BETWEEN(-PITCHMAX * MLOOKUNIT, viewplayer->pitch - 16 * MLOOKUNIT,
            PITCHMAX * MLOOKUNIT);
        viewplayer->oldpitch = viewplayer->pitch;
    }
}

static void lookupactionfunc(void)
{
    if (gamestate == GS_LEVEL && canfreelook && !automapactive && !mapwindow)
    {
        viewplayer->pitch = BETWEEN(-PITCHMAX * MLOOKUNIT, viewplayer->pitch + 16 * MLOOKUNIT,
            PITCHMAX * MLOOKUNIT);
        viewplayer->oldpitch = viewplayer->pitch;
    }
}

static void fullzoomactionfunc(void)
{
    if (gamestate == GS_LEVEL && (automapactive || mapwindow))
        AM_ToggleFullzoom();
}

static void gridactionfunc(void)
{
    if (gamestate == GS_LEVEL && (automapactive || mapwindow))
        AM_ToggleGrid();
}

static void jumpactionfunc(void)
{
    if (gamestate == GS_LEVEL && !nojump)
        viewplayer->cmd.buttons |= BT_JUMP;
}

static void leftactionfunc(void)
{
    if (gamestate == GS_LEVEL)
    {
        if (strafe())
            viewplayer->cmd.sidemove -= sidemove[run()];
        else
            viewplayer->cmd.angleturn -= angleturn[run()];
    }
}

static void markactionfunc(void)
{
    if (gamestate == GS_LEVEL && (automapactive || mapwindow))
        AM_AddMark();
}

static void menuactionfunc(void)
{
    M_OpenMainMenu();
    S_StartSound(NULL, sfx_swtchn);
}

static void nextweaponactionfunc(void)
{
    if (gamestate == GS_LEVEL)
        G_NextWeapon();
}

static void pathactionfunc(void)
{
    if (gamestate == GS_LEVEL && (automapactive || mapwindow))
        AM_TogglePath(!am_path);
}

static void pistolactionfunc(void)
{
    if (gamestate == GS_LEVEL)
        P_ChangeWeapon(wp_pistol, false);
}

static void plasmarifleactionfunc(void)
{
    if (gamestate == GS_LEVEL)
        P_ChangeWeapon(wp_plasma, false);
}

static void prevweaponactionfunc(void)
{
    if (gamestate == GS_LEVEL)
        G_PrevWeapon();
}

static void rightactionfunc(void)
{
    if (gamestate == GS_LEVEL)
    {
        if (strafe())
            viewplayer->cmd.sidemove += sidemove[run()];
        else
            viewplayer->cmd.angleturn += angleturn[run()];
    }
}

static void rocketlauncheractionfunc(void)
{
    if (gamestate == GS_LEVEL)
        P_ChangeWeapon(wp_missile, false);
}

static void rotatemodeactionfunc(void)
{
    if (gamestate == GS_LEVEL && (automapactive || mapwindow))
        AM_ToggleRotateMode(!am_rotatemode);
}

static void screenshotactionfunc(void)
{
    G_ScreenShot();
    S_StartSound(NULL, sfx_scrsht);
    memset(screens[0], nearestwhite, SCREENAREA);
    D_FadeScreen(true);
}

static void shotgunactionfunc(void)
{
    if (gamestate == GS_LEVEL)
    {
        P_ChangeWeapon(wp_shotgun, false);
        viewplayer->preferredshotgun = wp_shotgun;
    }
}

static void sizedownactionfunc(void)
{
    if (gamestate == GS_LEVEL && r_screensize > r_screensize_min)
        AdjustScreenSize(r_screensize - 1);
}

static void sizeupactionfunc(void)
{
    if (gamestate == GS_LEVEL && r_screensize < r_screensize_max)
        AdjustScreenSize(r_screensize + 1);
}

static void strafeleftactionfunc(void)
{
    if (gamestate == GS_LEVEL)
        viewplayer->cmd.sidemove -= sidemove[run()];
}

static void straferightactionfunc(void)
{
    if (gamestate == GS_LEVEL)
        viewplayer->cmd.sidemove += sidemove[run()];
}

static void supershotgunactionfunc(void)
{
    if (gamestate == GS_LEVEL)
    {
        P_ChangeWeapon(wp_supershotgun, false);
        viewplayer->preferredshotgun = wp_supershotgun;
    }
}

static void useactionfunc(void)
{
    if (gamestate == GS_LEVEL)
        P_UseLines();
}

static void weapon1actionfunc(void)
{
    if (gamestate == GS_LEVEL)
        P_ChangeWeapon(wp_fist, true);
}

static void weapon2actionfunc(void)
{
    if (gamestate == GS_LEVEL)
        P_ChangeWeapon(wp_pistol, false);
}

static void weapon3actionfunc(void)
{
    if (gamestate == GS_LEVEL)
        P_ChangeWeapon(wp_shotgun, true);
}

static void weapon4actionfunc(void)
{
    if (gamestate == GS_LEVEL)
        P_ChangeWeapon(wp_chaingun, false);
}

static void weapon5actionfunc(void)
{
    if (gamestate == GS_LEVEL)
        P_ChangeWeapon(wp_missile, false);
}

static void weapon6actionfunc(void)
{
    if (gamestate == GS_LEVEL)
        P_ChangeWeapon(wp_plasma, false);
}

static void weapon7actionfunc(void)
{
    if (gamestate == GS_LEVEL)
        P_ChangeWeapon(wp_bfg, false);
}

static void zoominactionfunc(void)
{
    if (gamestate == GS_LEVEL && (automapactive || mapwindow))
        AM_ToggleZoomIn();
}

static void zoomoutactionfunc(void)
{
    if (gamestate == GS_LEVEL && (automapactive || mapwindow))
        AM_ToggleZoomOut();
}

int C_GetIndex(const char *cmd)
{
    int i = 0;

    while (*consolecmds[i].name)
    {
        if (M_StringCompare(cmd, consolecmds[i].name) || M_StringCompare(cmd, consolecmds[i].alternate))
            break;

        i++;
    }

    return i;
}

static void C_ShowDescription(int index)
{
    char    description[255];

    M_StringCopy(description, consolecmds[index].description, sizeof(description));
    description[0] = tolower(description[0]);

    if (english == english_british)
        M_AmericanToBritishEnglish(description);

    if (consolecmds[index].type == CT_CCMD)
        C_Output("This CCMD %s", description);
    else
        C_Output("This CVAR %s%s", (M_StringStartsWith(description, "toggles") ? "" :
            ((consolecmds[index].flags & CF_READONLY) ? "is " : "changes ")), description);
}

static void C_ShowFormat(int index)
{
    char    name[255];
    char    format[255];

    M_StringCopy(name, (english == english_american || M_StringCompare(consolecmds[index].altspelling, EMPTYVALUE) ?
        consolecmds[index].name : consolecmds[index].altspelling), sizeof(name));
    M_StringCopy(format, consolecmds[index].format, sizeof(format));

    if (english == english_british)
        M_AmericanToBritishEnglish(format);

    C_Output(BOLD("%s") " %s", name, format);
}

static void C_ShowWarning(int index)
{
    const int   flags = consolecmds[index].flags;

    if (flags & CF_READONLY)
        C_Warning(0, "It is read-only.");
    else if (flags & CF_MAPRESET)
        C_Warning(0, "It is reset to its default at the start of each map.");
    else if ((flags & CF_NEXTMAP) && gamestate == GS_LEVEL)
        C_Warning(0, "Changing it won't be effective until the next map.");
    else if ((flags & CF_PISTOLSTART) && pistolstart)
        C_Warning(0, "It has no effect while the " BOLD("pistolstart") " CCMD is used.");
}

static char *C_FormatColorValue(char *string, bool replaceauto)
{
    char    output[1024] = "";
    char    *src = string;
    char    *dst = output;

    while (*src)
    {
        if (replaceauto && M_StringStartsWith(src, "auto"))
        {
            if ((src == string || !isalnum((unsigned char)src[-1]))
                && !isalnum((unsigned char)src[4]))
            {
                M_StringCopy(dst, "{auto}", sizeof(output) - (size_t)(dst - output));
                dst += 6;
                src += 4;
                continue;
            }
        }

        if (isdigit((unsigned char)*src))
        {
            char    numstr[4] = "";
            int     num;
            int     len = 0;

            while (len < 3 && isdigit((unsigned char)src[len]))
            {
                numstr[len] = src[len];
                len++;
            }

            numstr[len] = '\0';
            num = atoi(numstr);

            if (num >= 0 && num <= 255
                && (src == string || !isalnum((unsigned char)src[-1]))
                && !isalnum((unsigned char)src[len]))
            {
                dst += sprintf(dst, "{%d}", num);
                src += len;
                continue;
            }
        }

        *dst++ = *src++;
    }

    *dst = '\0';

    return M_StringDuplicate(output);
}

static bool alivefunc1(char *cmd, char *parms)
{
    if (gamestate != GS_LEVEL)
        return ingameccmdfunc1(cmd, parms);
    else if (viewplayer->health > 0)
        return true;
    else
    {
        C_Input("%s", consoleinput);
        C_ShowDescription(C_GetIndex(cmd));

        if (isdefaultplayername())
            C_Warning(0, DEADPLAYERWARNING1);
        else
            C_Warning(0, DEADPLAYERWARNING2, playername, pronoun(personal),
                (playergender == playergender_other ? "are" : "is"));

        consoleinput[0] = '\0';

        return false;
    }
}

static bool cheatfunc1(char *cmd, char *parms)
{
    if (M_StringCompare(cmd, cheat_clev.sequence))
    {
        bool    result;

        if (gamemode == commercial
            && gamestate != GS_LEVEL && gamestate != GS_INTERMISSION && gamestate != GS_FINALE)
            gamemission = D_GetGameMissionForExpansion();

        if (legacyofrust)
        {
            mapcmdepisode = parms[0] - '0';
            mapcmdmap = parms[1] - '0';

            if (mapcmdepisode == 9 && mapcmdmap == 9)
                mapcmdmap = 99;
            else if (mapcmdepisode <= 2 && !mapcmdmap)
                mapcmdmap = 14 + mapcmdepisode;
            else if (mapcmdepisode <= 2 && mapcmdmap <= 7)
                mapcmdmap = (mapcmdepisode - 1) * 7 + mapcmdmap;
            else
                mapcmdmap = mapcmdepisode * 10 + mapcmdmap;

            M_snprintf(mapcmdmapnum, sizeof(mapcmdmapnum), "MAP%02i", mapcmdmap);
        }
        else if (gamemode == commercial)
        {
            mapcmdepisode = 1;
            mapcmdmap = (parms[0] - '0') * 10 + parms[1] - '0';
            M_snprintf(mapcmdmapnum, sizeof(mapcmdmapnum), "MAP%c%c", parms[0], parms[1]);
        }
        else
        {
            mapcmdepisode = parms[0] - '0';
            mapcmdmap = parms[1] - '0';
            M_snprintf(mapcmdmapnum, sizeof(mapcmdmapnum), "E%cM%c", parms[0], parms[1]);
        }

        result = (W_CheckNumForName(mapcmdmapnum) >= 0
            && (gamemission != pack_nerve || mapcmdmap <= 9)
            && (gamemission != pack_masterlevels || mapcmdmap <= 21)
            && (!BTSX || W_GetNumLumps(mapcmdmapnum) > 1));

        if (gamestate == GS_LEVEL)
            return result;
        else if (result)
        {
            if (legacyofrust && mapcmdmap != 99)
                M_snprintf(mapcmdmapnum, sizeof(mapcmdmapnum), "E%cM%c", parms[0], parms[1]);

            S_StartSound(NULL, sfx_getpow);
            ST_PlayerCheated(cheat_clev_xy.sequence, "xy", NULL, true);
            mapfunc2("map", mapcmdmapnum);
            return true;
        }
    }
    else if (M_StringCompare(cmd, cheat_mus.sequence) && !nomusic && musicvolume)
    {
        if (parms[0] >= '0' && parms[0] <= '9' && parms[1] >= '0' && parms[1] <= '9')
        {
            int musnum = (parms[0] - '0') * 10 + (parms[1] - '0');

            if (musnum < IDMUS_MAX)
            {
                if (gamemission == pack_nerve)
                    musnum = mus[musnum][5];
                else if (gamemission == pack_masterlevels)
                    musnum = mus[musnum][6];
                else if (bfgedition && gamemission == doom2)
                    musnum = mus[musnum][4];
                else
                    musnum = mus[musnum][gamemode];

                if (musnum != mus_none)
                {
                    char    lump[9];
                    char    *temp = uppercase(s_music[musnum].name1);

                    M_snprintf(lump, sizeof(lump), "D_%s", temp);

                    if (W_CheckNumForName(lump) >= 0)
                    {
                        static char msg[80];

                        S_StartSound(NULL, sfx_getpow);

                        S_ChangeMusic(musnum, 1, true, false);
                        ST_PlayerCheated(cheat_mus.sequence, "xy", NULL, false);
                        M_snprintf(msg, sizeof(msg), s_STSTR_MUS, temp);
                        C_Output(msg);
                        HU_SetPlayerMessage(msg, false, false);
                        message_dontfuckwithme = true;
                    }

                    free(temp);
                }
                else
                    idmus = false;
            }
            else
                idmus = false;

            return true;
        }

        return false;
    }
    else if (gamestate != GS_LEVEL)
        return false;
    else if (M_StringCompare(cmd, cheat_god.sequence))
        return (gameskill != sk_nightmare);
    else if (M_StringCompare(cmd, cheat_ammonokey.sequence))
        return (gameskill != sk_nightmare && viewplayer->health > 0);
    else if (M_StringCompare(cmd, cheat_ammo.sequence))
        return (gameskill != sk_nightmare && viewplayer->health > 0);
    else if (M_StringCompare(cmd, cheat_noclip.sequence))
        return (gamemode != commercial && gameskill != sk_nightmare && viewplayer->health > 0);
    else if (M_StringCompare(cmd, cheat_commercial_noclip.sequence))
        return (gamemode == commercial && gameskill != sk_nightmare && viewplayer->health > 0);
    else if (M_StringCompare(cmd, cheat_powerup[0].sequence))
        return (gameskill != sk_nightmare && viewplayer->health > 0);
    else if (M_StringCompare(cmd, cheat_powerup[1].sequence))
        return (gameskill != sk_nightmare && viewplayer->health > 0);
    else if (M_StringCompare(cmd, cheat_powerup[2].sequence))
        return (gameskill != sk_nightmare && viewplayer->health > 0);
    else if (M_StringCompare(cmd, cheat_powerup[3].sequence))
        return (gameskill != sk_nightmare && viewplayer->health > 0);
    else if (M_StringCompare(cmd, cheat_powerup[4].sequence))
        return (gameskill != sk_nightmare && viewplayer->health > 0);
    else if (M_StringCompare(cmd, cheat_powerup[5].sequence))
        return (gameskill != sk_nightmare && viewplayer->health > 0);
    else if (M_StringCompare(cmd, cheat_choppers.sequence))
        return (gameskill != sk_nightmare && viewplayer->health > 0);
    else if (M_StringCompare(cmd, cheat_buddha.sequence))
        return (gameskill != sk_nightmare && viewplayer->health > 0);
    else if (M_StringCompare(cmd, cheat_mypos.sequence))
        return true;
    else if (M_StringCompare(cmd, cheat_amap.sequence))
        return (gameskill != sk_nightmare && (automapactive || mapwindow));

    return false;
}

static bool ingameccmdfunc1(char *cmd, char *parms)
{
    if (gamestate == GS_LEVEL)
        return true;

    if (!togglingvanilla)
    {
        C_Input("%s", consoleinput);
        C_ShowDescription(C_GetIndex(cmd));

        if (isdefaultplayername())
            C_Warning(0, NOGAMECCMDWARNING1);
        else
            C_Warning(0, NOGAMECCMDWARNING2, playername, pronoun(personal),
                (playergender == playergender_other ? "aren't" : "isn't"));

        consoleinput[0] = '\0';
    }

    return false;
}

static bool inaftergameccmdfunc1(char *cmd, char *parms)
{
    if (gamestate == GS_LEVEL || gamestate == GS_INTERMISSION)
        return true;

    if (!togglingvanilla)
    {
        C_Input("%s", consoleinput);
        C_ShowDescription(C_GetIndex(cmd));

        if (isdefaultplayername())
            C_Warning(0, NOGAMECCMDWARNING1);
        else
            C_Warning(0, NOGAMECCMDWARNING2, playername, pronoun(personal),
                (playergender == playergender_other ? "aren't" : "isn't"));

        consoleinput[0] = '\0';
    }

    return false;
}

static bool ingamecvarfunc1(char *cmd, char *parms)
{
    if (gamestate == GS_LEVEL)
        return true;

    if (!togglingvanilla)
    {
        C_Input("%s", consoleinput);
        C_ShowDescription(C_GetIndex(cmd));

        if (isdefaultplayername())
            C_Warning(0, NOGAMECVARWARNING1);
        else
            C_Warning(0, NOGAMECVARWARNING2, playername, pronoun(personal),
                (playergender == playergender_other ? "aren't" : "isn't"));

        consoleinput[0] = '\0';
    }

    return false;
}

static bool nightmarefunc1(char *cmd, char *parms)
{
    char    *nightmare = *skilllevels[sk_nightmare];

    if (gamestate != GS_LEVEL)
        return ingameccmdfunc1(cmd, parms);

    if (gameskill != sk_nightmare)
        return true;

    C_Input("%s", consoleinput);
    C_ShowDescription(C_GetIndex(cmd));

    if (isdefaultplayername())
        C_Warning(0, NIGHTMAREWARNING1,
            nightmare, (ispunctuation(nightmare[strlen(nightmare) - 1]) ? "" : "."));
    else
        C_Warning(0, NIGHTMAREWARNING2,
            playername, pronoun(personal), (playergender == playergender_other ? "are" : "is"),
            nightmare, (ispunctuation(nightmare[strlen(nightmare) - 1]) ? "" : "."));

    consoleinput[0] = '\0';

    return false;
}

static bool nullfunc1(char *cmd, char *parms)
{
    return true;
}

//
// alias CCMD
//
bool C_ExecuteAlias(const char *alias)
{
    if (!executingalias)
        for (int i = 0; i < MAXALIASES; i++)
            if (M_StringCompare(alias, aliases[i].name))
            {
                char    *string = M_StringDuplicate(aliases[i].string);
                char    *strings[255] = { "" };
                int     j = 0;

                strings[0] = strtok(string, ";");
                executingalias = true;

                while (strings[j])
                {
                    if (!C_ValidateInput(trimwhitespace(strings[j])))
                        break;

                    strings[++j] = strtok(NULL, ";");
                }

                executingalias = false;
                C_Input("%s", alias);
                free(string);

                return true;
            }

    return false;
}

void aliasfunc2(char *cmd, char *parms)
{
    char    parm1[128] = "";
    char    parm2[128] = "";

    if (sscanf(parms, "%127s %127[^\n]", parm1, parm2) <= 0)
    {
        const int   i = C_GetIndex(cmd);

        C_ShowFormat(i);
        C_ShowDescription(i);
        return;
    }

    M_StripQuotes(parm1);

    for (int i = 0; *consolecmds[i].name; i++)
        if (M_StringCompare(parm1, consolecmds[i].name))
        {
            C_Warning(0, "An alias cannot be the same as an existing CVAR or CCMD.");
            return;
        }

    if (!*parm2)
    {
        for (int i = 0; i < MAXALIASES; i++)
            if (*aliases[i].name && M_StringCompare(parm1, aliases[i].name))
            {
                aliases[i].name[0] = '\0';
                aliases[i].string[0] = '\0';
                M_SaveCVARs();
                C_Output("The " BOLD("%s") " alias has been removed.", parm1);

                return;
            }

        return;
    }

    M_StripQuotes(parm2);

    for (int i = 0; i < MAXALIASES; i++)
        if (*aliases[i].name && M_StringCompare(parm1, aliases[i].name))
        {
            M_StringCopy(aliases[i].string, parm2, sizeof(aliases[0].string));
            M_SaveCVARs();
            C_Output("The " BOLD("%s") " alias has been updated.", parm1);

            return;
        }

    for (int i = 0; i < MAXALIASES; i++)
        if (!*aliases[i].name)
        {
            M_StringCopy(aliases[i].name, parm1, sizeof(aliases[0].name));
            M_StringCopy(aliases[i].string, parm2, sizeof(aliases[0].string));
            C_Output("The " BOLD("%s") " alias has been created.", parm1);
            M_SaveCVARs();

            return;
        }
}

bool IsControlBound(const controltype_t type, const int control, const bool automaponly)
{
    if (type == keyboardcontrol && *keyactionlist[control])
        return true;
    else if (type == mousecontrol && *mouseactionlist[control])
        return true;

    for (int i = 0; *actions[i].action; i++)
        if (type == keyboardcontrol)
        {
            if (actions[i].keyboard1
                && *(int *)actions[i].keyboard1
                && control == *(int *)actions[i].keyboard1
                && automaponly == actions[i].automaponly)
                return true;
            else if (actions[i].keyboard2
                && *(int *)actions[i].keyboard2
                && control == *(int *)actions[i].keyboard2
                && automaponly == actions[i].automaponly)
                return true;
        }
        else if (type == mousecontrol)
        {
            if (actions[i].mouse1
                && *(int *)actions[i].mouse1 != -1
                && control == *(int *)actions[i].mouse1
                && automaponly == actions[i].automaponly)
                return true;
        }
        else if (type == controllercontrol)
        {
            if (actions[i].controller1
                && *(int *)actions[i].controller1
                && control == *(int *)actions[i].controller1
                && automaponly == actions[i].automaponly)
                return true;
            else if (actions[i].controller2
                && *(int *)actions[i].controller2
                && control == *(int *)actions[i].controller2
                && automaponly == actions[i].automaponly)
                return true;
        }

    return false;
}

static int GetControlWithAction(int action)
{
    int control = 0;

    while (controls[control].type)
    {
        if ((actions[action].keyboard1 && controls[control].value == *(int *)actions[action].keyboard1)
            || (actions[action].keyboard2 && controls[control].value == *(int *)actions[action].keyboard2)
            || (actions[action].mouse1 && controls[control].value == *(int *)actions[action].mouse1)
            || (actions[action].controller1 && controls[control].value == *(int *)actions[action].controller1)
            || (actions[action].controller2 && controls[control].value == *(int *)actions[action].controller2))
            return control;

        control++;
    }

    return -1;
}

//
// bind CCMD
//
static void C_UnbindDuplicates(const int keep, const controltype_t type, const int control)
{
    for (int i = 0; *actions[i].action; i++)
        if (i != keep && actions[i].automaponly == actions[keep].automaponly)
        {
            if (type == keyboardcontrol)
            {
                if (actions[i].keyboard1 && controls[control].value == *(int *)actions[i].keyboard1)
                {
                    if (strlen(controls[i].control) == 1)
                        C_Warning(0, "The " BOLD("%s") " action has been unbound from the " BOLD("\x91%s\x92") " key.",
                            actions[i].action, controls[control].control);
                    else
                        C_Warning(0, "The " BOLD("%s") " action has been unbound from the " BOLD("%s") " key.",
                            actions[i].action, controls[control].control);

                    *(int *)actions[i].keyboard1 = 0;
                }

                if (actions[i].keyboard2 && controls[control].value == *(int *)actions[i].keyboard2)
                {
                    if (strlen(controls[i].control) == 1)
                        C_Warning(0, "The " BOLD("%s") " action has been unbound from the " BOLD("\x91%s\x92") " key.",
                            actions[i].action, controls[control].control);
                    else
                        C_Warning(0, "The " BOLD("%s") " action has been unbound from the " BOLD("%s") " key.",
                            actions[i].action, controls[control].control);

                    *(int *)actions[i].keyboard2 = 0;
                }
            }
            else if (type == mousecontrol)
            {
                if (actions[i].mouse1 && controls[control].value == *(int *)actions[i].mouse1)
                {
                    C_Warning(0, "The " BOLD("%s") " action has been unbound from " BOLD("%s") ".",
                        actions[i].action, controls[control].control);
                    *(int *)actions[i].mouse1 = -1;
                }
            }
            else if (type == controllercontrol)
            {
                if (actions[i].controller1 && controls[control].value == *(int *)actions[i].controller1)
                {
                    C_Warning(0, "The " BOLD("%s") " action has been unbound from " BOLD("%s") ".",
                        actions[i].action, controls[control].control);
                    *(int *)actions[i].controller1 = 0;
                }

                if (actions[i].controller2 && controls[control].value == *(int *)actions[i].controller2)
                {
                    C_Warning(0, "Controls may only be bound to one action. The duplicate " BOLD("%s")
                        " action has therefore been unbound from " BOLD("%s") ".",
                        actions[i].action, controls[control].control);
                    *(int *)actions[i].controller2 = 0;
                }
            }
        }

    if (type == keyboardcontrol)
    {
        if (*keyactionlist[controls[control].value])
        {
            if (strlen(controls[control].control) == 1)
                C_Warning(0, BOLD("\"%s\"") " has been unbound from the " BOLD("\x91%s\x92") " key.",
                    keyactionlist[controls[control].value], controls[control].control);
            else
                C_Warning(0, BOLD("\"%s\"") " has been unbound from the " BOLD("%s") " key.",
                    keyactionlist[controls[control].value], controls[control].control);

            keyactionlist[controls[control].value][0] = '\0';
        }
    }
    else if (type == mousecontrol)
    {
        const int   mousevalue = controls[control].value;

        if (mousevalue >= 0 && mousevalue < (int)(sizeof(mouseactionlist) / sizeof(mouseactionlist[0])))
        {
            if (*mouseactionlist[mousevalue])
            {
                C_Warning(0, BOLD("\"%s\"") " has been unbound from " BOLD("%s") ".",
                    mouseactionlist[mousevalue], controls[control].control);
                mouseactionlist[mousevalue][0] = '\0';
            }
        }
    }
}

void bindfunc2(char *cmd, char *parms)
{
    int         i = 0;
    int         action = 0;
    char        parm1[128] = "";
    char        parm2[128] = "";
    const bool  freelookcontrols = (keyboardfreelook || keyboardfreelook2 || keyboardlookcenter || keyboardlookcenter2
        || keyboardlookdown || keyboardlookdown2 || keyboardlookup || keyboardlookup2 || controllerfreelook
        || controllerlookcenter || controllerlookdown || controllerlookup || mousefreelook != -1
        || mouselookcenter != -1 || mouselookdown != -1 || mouselookup != -1);

    if (sscanf(parms, "%127s %127[^\n]", parm1, parm2) <= 0)
    {
        i = C_GetIndex(cmd);

        C_ShowFormat(i);
        C_ShowDescription(i);
        return;
    }

    M_StripQuotes(parm1);

    while (controls[i].type)
    {
        if (M_StringCompare(parm1, controls[i].control))
            break;

        i++;
    }

    if (*controls[i].control)
    {
        if (M_StringCompare(cmd, "unbind"))
        {
            while (*actions[action].action)
            {
                switch (controls[i].type)
                {
                    case keyboardcontrol:
                        if (actions[action].keyboard1 && controls[i].value == *(int *)actions[action].keyboard1)
                        {
                            if (!nobindoutput)
                                C_Output("The " BOLD("%s") " action has been unbound from the " BOLD("%s") " control.",
                                    actions[action].action, controls[i].control);

                            *(int *)actions[action].keyboard1 = 0;
                            M_SaveCVARs();
                        }

                        if (actions[action].keyboard2 && controls[i].value == *(int *)actions[action].keyboard2)
                        {
                            if (!nobindoutput)
                                C_Output("The " BOLD("%s") " action has been unbound from the " BOLD("%s") " control.",
                                    actions[action].action, controls[i].control);

                            *(int *)actions[action].keyboard2 = 0;
                            M_SaveCVARs();
                        }

                        break;

                    case mousecontrol:
                        if (actions[action].mouse1 && controls[i].value == *(int *)actions[action].mouse1)
                        {
                            if (!nobindoutput)
                                C_Output("The " BOLD("%s") " action has been unbound from the " BOLD("%s") " control.",
                                    actions[action].action, controls[i].control);

                            *(int *)actions[action].mouse1 = -1;
                            M_SaveCVARs();
                        }

                        break;

                    case controllercontrol:
                        if (actions[action].controller1 && controls[i].value == *(int *)actions[action].controller1)
                        {
                            if (!nobindoutput)
                                C_Output("The " BOLD("%s") " action has been unbound from the " BOLD("%s") " control.",
                                    actions[action].action, controls[i].control);

                            *(int *)actions[action].controller1 = 0;
                            M_SaveCVARs();
                        }

                        if (actions[action].controller2 && controls[i].value == *(int *)actions[action].controller2)
                        {
                            if (!nobindoutput)
                                C_Output("The " BOLD("%s") " action has been unbound from the " BOLD("%s") " control.",
                                    actions[action].action, controls[i].control);

                            *(int *)actions[action].controller2 = 0;
                            M_SaveCVARs();
                        }

                        break;

                    default:
                        break;
                }

                action++;
            }

            if (controls[i].type == keyboardcontrol && *keyactionlist[controls[i].value])
            {
                C_Output(BOLD("\"%s\"") " has been unbound from the " BOLD("%s") " control.",
                    keyactionlist[controls[i].value], controls[i].control);
                keyactionlist[controls[i].value][0] = '\0';
            }
            else if (controls[i].type == mousecontrol && *mouseactionlist[controls[i].value])
            {
                C_Output(BOLD("\"%s\"") " has been unbound from the " BOLD("%s") " control.",
                    mouseactionlist[controls[i].value], controls[i].control);
                mouseactionlist[controls[i].value][0] = '\0';
            }
        }
        else if (!*parm2)
        {
            while (*actions[action].action)
            {
                if (controls[i].type == keyboardcontrol)
                {
                    if (actions[action].keyboard1 && controls[i].value == *(int *)actions[action].keyboard1)
                        C_Output(actions[action].action);
                    else if (actions[action].keyboard2 && controls[i].value == *(int *)actions[action].keyboard2)
                        C_Output(actions[action].action);
                }
                else if (controls[i].type == mousecontrol)
                {
                    if (actions[action].mouse1 && controls[i].value == *(int *)actions[action].mouse1)
                        C_Output(actions[action].action);
                }
                else if (controls[i].type == controllercontrol)
                {
                    if (actions[action].controller1 && controls[i].value == *(int *)actions[action].controller1)
                        C_Output(actions[action].action);
                    else if (actions[action].controller2 && controls[i].value == *(int *)actions[action].controller2)
                        C_Output(actions[action].action);
                }

                action++;
            }
        }
        else
        {
            while (*actions[action].action)
            {
                if (M_StringCompare(parm2, actions[action].action) || M_StringCompare(parm2, actions[action].oldaction))
                    break;

                action++;
            }

            if (*actions[action].action)
            {
                bool    bound = false;

                switch (controls[i].type)
                {
                    case keyboardcontrol:
                        if (actions[action].keyboard1)
                        {
                            if (actions[action].keyboard2
                                && *(int *)actions[action].keyboard1
                                && *(int *)actions[action].keyboard1 != controls[i].value)
                            {
                                if (*(int *)actions[action].keyboard2)
                                {
                                    *(int *)actions[action].keyboard2 = *(int *)actions[action].keyboard1;
                                    *(int *)actions[action].keyboard1 = controls[i].value;
                                }
                                else
                                    *(int *)actions[action].keyboard2 = controls[i].value;
                            }
                            else
                                *(int *)actions[action].keyboard1 = controls[i].value;

                            bound = true;
                            C_UnbindDuplicates(action, keyboardcontrol, i);

#if defined(_WIN32)
                            if (M_StringCompare(actions[action].action, "+screenshot"))
                            {
                                if (keyboardscreenshot == KEY_PRINTSCREEN || keyboardscreenshot2 == KEY_PRINTSCREEN)
                                {
                                    RegisterHotKey(NULL, 1, MOD_ALT, VK_SNAPSHOT);
                                    RegisterHotKey(NULL, 2, 0, VK_SNAPSHOT);
                                }
                                else
                                {
                                    UnregisterHotKey(NULL, 1);
                                    UnregisterHotKey(NULL, 2);
                                }
                            }
#endif
                        }

                        break;

                    case mousecontrol:
                        if (actions[action].mouse1)
                        {
                            *(int *)actions[action].mouse1 = controls[i].value;
                            bound = true;
                            C_UnbindDuplicates(action, mousecontrol, i);
                        }

                        break;

                    case controllercontrol:
                        if (actions[action].controller1)
                        {
                            if (actions[action].controller2
                                && *(int *)actions[action].controller1
                                && *(int *)actions[action].controller1 != controls[i].value)
                            {
                                if (*(int *)actions[action].controller2)
                                {
                                    *(int *)actions[action].controller2 = *(int *)actions[action].controller1;
                                    *(int *)actions[action].controller1 = controls[i].value;
                                }
                                else
                                    *(int *)actions[action].controller2 = controls[i].value;
                            }
                            else
                                *(int *)actions[action].controller1 = controls[i].value;

                            bound = true;
                            C_UnbindDuplicates(action, controllercontrol, i);
                        }

                        break;

                    default:
                        break;
                }

                M_SaveCVARs();

                if (bound)
                {
                    if (!nobindoutput)
                    {
                        if (strlen(controls[i].control) == 1)
                            C_Output("The " BOLD("%s") " action was bound to the " BOLD("\x91%s\x92") " key.",
                                parm2, controls[i].control);
                        else
                            C_Output("The " BOLD("%s") " action was bound to the " BOLD("%s") " control.",
                                parm2, controls[i].control);
                    }
                }
                else
                {
                    if (strlen(controls[i].control) == 1)
                        C_Warning(0, "The " BOLD("%s") " action can't be bound to the " BOLD("\x91%s\x92") " key.",
                            parm2, controls[i].control);
                    else
                        C_Warning(0, "The " BOLD("%s") " action can't be bound to the " BOLD("%s") " control.",
                            parm2, controls[i].control);

                    return;
                }
            }
            else
            {
                if (controls[i].type == keyboardcontrol)
                {
                    M_StripQuotes(parm2);
                    M_StringCopy(keyactionlist[controls[i].value], parm2, sizeof(keyactionlist[0]));
                    M_SaveCVARs();

                    if (!nobindoutput)
                    {
                        if (strlen(controls[i].control) == 1)
                            C_Output(BOLD("\"%s\"") " was bound to the " BOLD("\x91%s\x92") " key.",
                                parm2, controls[i].control);
                        else
                            C_Output(BOLD("\"%s\"") " was bound to the " BOLD("%s") " key.",
                                parm2, controls[i].control);
                    }
                }
                else if (controls[i].type == mousecontrol)
                {
                    M_StripQuotes(parm2);
                    M_StringCopy(mouseactionlist[controls[i].value], parm2, sizeof(mouseactionlist[0]));
                    M_SaveCVARs();

                    if (!nobindoutput)
                        C_Output(BOLD("\"%s\"") " was bound to the " BOLD("%s") " control.",
                            parm2, controls[i].control);
                }
            }
        }
    }
    else if (M_StringCompare(cmd, "unbind"))
    {
        while (*actions[action].action)
        {
            if (M_StringCompare(parms, actions[action].action))
            {
                if (actions[action].keyboard1 && *(int *)actions[action].keyboard1)
                {
                    int control = GetControlWithAction(action);

                    if (strlen(controls[control].control) == 1)
                        C_Output("The " BOLD("%s") " action has been unbound from the " BOLD("\x91%s\x92") " control.",
                            actions[action].action, controls[control].control);
                    else
                        C_Output("The " BOLD("%s") " action has been unbound from the " BOLD("%s") " control.",
                            actions[action].action, controls[control].control);

                    *(int *)actions[action].keyboard1 = 0;
                }

                if (actions[action].keyboard2 && *(int *)actions[action].keyboard2)
                {
                    int control = GetControlWithAction(action);

                    if (strlen(controls[control].control) == 1)
                        C_Output("The " BOLD("%s") " action has been unbound from the " BOLD("\x91%s\x92") " control.",
                            actions[action].action, controls[control].control);
                    else
                        C_Output("The " BOLD("%s") " action has been unbound from the " BOLD("%s") " control.",
                            actions[action].action, controls[control].control);

                    *(int *)actions[action].keyboard2 = 0;
                }

                if (actions[action].mouse1 && *(int *)actions[action].mouse1 != -1)
                {
                    C_Output("The " BOLD("%s") " action has been unbound from the " BOLD("%s") " control.",
                        actions[action].action, controls[GetControlWithAction(action)].control);
                    *(int *)actions[action].mouse1 = -1;
                }

                if (actions[action].controller1 && *(int *)actions[action].controller1)
                {
                    C_Output("The " BOLD("%s") " action has been unbound from the " BOLD("%s") " control.",
                        actions[action].action, controls[GetControlWithAction(action)].control);
                    *(int *)actions[action].controller1 = 0;
                }

                if (actions[action].controller2 && *(int *)actions[action].controller2)
                {
                    C_Output("The " BOLD("%s") " action has been unbound from the " BOLD("%s") " control.",
                        actions[action].action, controls[GetControlWithAction(action)].control);
                    *(int *)actions[action].controller2 = 0;
                }

                break;
            }

            action++;
        }
    }
    else
    {
        C_Warning(0, BOLD("%s") " isn't a valid action or control.", parm1);
        return;
    }

    if (freelookcontrols != (keyboardfreelook || keyboardfreelook2 || keyboardlookcenter || keyboardlookcenter2
        || keyboardlookdown || keyboardlookdown2 || keyboardlookup || keyboardlookup2 || controllerfreelook
        || controllerlookcenter || controllerlookdown || controllerlookup || mousefreelook != -1
        || mouselookcenter != -1 || mouselookdown != -1 || mouselookup != -1))
    {
        if (gamestate == GS_LEVEL)
        {
            suppresswarnings = true;
            R_InitSkyMap();
            suppresswarnings = false;
        }

        R_InitColumnFunctions();
    }
}

//
// bindlist CCMD
//
static void C_DisplayBinds(const char *action, const int value, const controltype_t type, const int *tabs)
{
    for (int i = 0; controls[i].type; i++)
    {
        if (controls[i].type == type && controls[i].value == value)
        {
            const char  *control = controls[i].control;

            if (strlen(control) == 1)
                C_TabbedOutput(tabs, "\x91%s\x92\t%s", (control[0] == '=' ? "+" : control), action);
            else
                C_TabbedOutput(tabs, "%s\t%s", control, action);

            break;
        }
    }
}

static bool C_ActionIsBound(const action_t *action)
{
    return ((action->keyboard1 && *(int *)action->keyboard1)
        || (action->keyboard2 && *(int *)action->keyboard2)
        || (action->mouse1 && *(int *)action->mouse1 != -1)
        || (action->controller1 && *(int *)action->controller1)
        || (action->controller2 && *(int *)action->controller2));
}

static void bindlistfunc2(char *cmd, char *parms)
{
    const int   tabs[MAXTABS] = { 110 };

    C_Header(tabs, bindlist, BINDLISTHEADER);

    for (int i = 0; *actions[i].action; i++)
    {
        if (actions[i].keyboard1)
            C_DisplayBinds(actions[i].action, *(int *)actions[i].keyboard1, keyboardcontrol, tabs);

        if (actions[i].keyboard2)
            C_DisplayBinds(actions[i].action, *(int *)actions[i].keyboard2, keyboardcontrol, tabs);
    }

    for (int i = 0; controls[i].type; i++)
    {
        const int   value = controls[i].value;

        if (controls[i].type == keyboardcontrol && keyactionlist[value][0])
        {
            const char  *control = controls[i].control;

            if (strlen(control) == 1)
                C_TabbedOutput(tabs, "\x91%s\x92\t\"%s\"", (control[0] == '=' ? "+" : control), keyactionlist[value]);
            else
                C_TabbedOutput(tabs, "%s\t\x93%s\x94", control, keyactionlist[value]);
        }
    }

    for (int i = 0; *actions[i].action; i++)
        if (actions[i].mouse1)
            C_DisplayBinds(actions[i].action, *(int *)actions[i].mouse1, mousecontrol, tabs);

    for (int i = 0; controls[i].type; i++)
    {
        const int   value = controls[i].value;

        if (controls[i].type == mousecontrol && mouseactionlist[value][0])
            C_TabbedOutput(tabs, "%s\t%s", controls[i].control, mouseactionlist[value]);
    }

    for (int i = 0; *actions[i].action; i++)
    {
        if (actions[i].controller1)
            C_DisplayBinds(actions[i].action, *(int *)actions[i].controller1, controllercontrol, tabs);

        if (actions[i].controller2)
            C_DisplayBinds(actions[i].action, *(int *)actions[i].controller2, controllercontrol, tabs);
    }

    for (int i = 0; *actions[i].action; i++)
        if (!C_ActionIsBound(&actions[i]))
            C_TabbedOutput(tabs, "\x96\t%s", actions[i].action);

    for (int i = 0; controls[i].type; i++)
        if (!IsControlBound(controls[i].type, controls[i].value, false))
        {
            const char  *control = controls[i].control;

            if (strlen(control) == 1)
                C_TabbedOutput(tabs, "\x91%s\x92\t\x96", (control[0] == '=' ? "+" : control));
            else
                C_TabbedOutput(tabs, "%s\t\x96", control);
        }
}

//
// clear CCMD
//
static void clearfunc2(char *cmd, char *parms)
{
    C_ClearConsole();
}

//
// cmdlist CCMD
//
static void cmdlistfunc2(char *cmd, char *parms)
{
    const int   tabs[MAXTABS] = { 326 };
    const int   columnwidth = tabs[0] - 15;

    for (int i = 0, count = 0; *consolecmds[i].name; i++)
        if (consolecmds[i].type == CT_CCMD)
        {
            char    name[255];
            char    format[255];
            char    description[255];
            int     len;

            M_StringCopy(name, (english == english_american || M_StringCompare(consolecmds[i].altspelling, EMPTYVALUE) ?
                consolecmds[i].name : consolecmds[i].altspelling), sizeof(name));

            if (*parms && !wildcard(name, parms))
                continue;

            if (++count == 1)
                C_Header(tabs, cmdlist, CMDLISTHEADER);

            if (M_StringCompare(name, "map"))
                M_snprintf(format, sizeof(format), BOLD("map") " %s", (gamemission == doom ? MAPFORMAT1 : MAPFORMAT2));
            else
                M_snprintf(format, sizeof(format), BOLD("%s") " %s", name, consolecmds[i].format);

            len = (int)strlen(format);

            while (C_TextWidth(format, NULL, true, true) > columnwidth)
            {
                if (len >= 2 && format[len - 2] == ' ')
                {
                    format[len - 2] = '.';
                    format[len - 1] = '.';
                    format[len] = '.';
                    format[len + 1] = '\0';
                }
                else if (len >= 1)
                {
                    format[len - 1] = '.';
                    format[len] = '.';
                    format[len + 1] = '.';
                    format[len + 2] = '\0';
                }

                len--;
            }

            M_StringCopy(description, consolecmds[i].description, sizeof(description));

            if (english == english_british)
            {
                M_AmericanToBritishEnglish(format);
                M_AmericanToBritishEnglish(description);
            }

            C_TabbedOutput(tabs, "%s\t" BOLDOFF ITALICSOFF "%s", format, description);
        }
}

//
// condump CCMD
//
static int indentation(const char *string)
{
    const int   len = (int)strlen(string);
    int         count = 0;

    for (int i = 0; i < len; i++)
        if (string[i] == ' ')
            count++;
        else
            break;

    return count;
}

#if defined(_WIN32)
static void condumpwriteline(FILE *file, const char *string)
{
    WCHAR   wide[CONSOLETEXTMAXLENGTH * 2];
    char    utf8[CONSOLETEXTMAXLENGTH * 4];
    int     widelen;

    if (!(widelen = MultiByteToWideChar(CP_ACP, 0, string, -1, wide, (int)(sizeof(wide) / sizeof(*wide))))
        || !WideCharToMultiByte(CP_UTF8, 0, wide, widelen, utf8, sizeof(utf8), NULL, NULL))
    {
        fputs(string, file);
        return;
    }

    fputs(utf8, file);
}
#endif

static bool condumpfunc1(char *cmd, char *parms)
{
    return (numconsolestrings > 1);
}

static void condumpfunc2(char *cmd, char *parms)
{
    char        consolefolder[MAX_PATH];
    char        filename[MAX_PATH];
    const char  *appdatafolder = M_GetAppDataFolder();
    FILE        *file;

    M_snprintf(consolefolder, sizeof(consolefolder),
        "%s" DIR_SEPARATOR_S DOOMRETRO_CONSOLEFOLDER, appdatafolder);
    M_MakeDirectory(consolefolder);

    if (!*parms)
    {
        int count = 0;

        M_snprintf(filename, sizeof(filename), "%s" DIR_SEPARATOR_S "%s.txt", consolefolder, cmd);

        while (M_FileExists(filename))
        {
            char    *temp = commify(++count);

            M_snprintf(filename, sizeof(filename),
                "%s" DIR_SEPARATOR_S "%s (%s).txt", consolefolder, cmd, temp);
            free(temp);
        }
    }
    else
        M_snprintf(filename, sizeof(filename), "%s" DIR_SEPARATOR_S "%s%s",
            consolefolder, parms, (strchr(parms, '.') ? "" : ".txt"));

#if defined(_WIN32)
    if ((file = fopen(filename, "wb")))
#else
    if ((file = fopen(filename, "wt")))
#endif
    {
        char    *temp1 = commify((int64_t)numconsolestrings);

#if defined(_WIN32)
        fputs("\xEF\xBB\xBF", file);
#endif

        for (int i = 1; i < numconsolestrings - 1; i++)
        {
            stringtype_t    type = console[i].stringtype;

            if (type == dividerstring)
#if defined(_WIN32)
                condumpwriteline(file, DIVIDERSTRING "\n");
#else
                fprintf(file, "%s\n", DIVIDERSTRING);
#endif
            else
            {
                char            *string = M_StringDuplicate(console[i].string);
                char            line[CONSOLETEXTMAXLENGTH * 2];
                int             len;
                int             linepos = 0;
                unsigned int    outpos = 0;
                int             tabcount = 0;

                if (type == obituarystring || type == playerobituarystring)
                {
                    if (obituaries)
                        C_BuildObituaryString(i);
                    else
                        continue;
                }

                if (type == warningstring && con_warninglevel < console[i].warninglevel && !devparm)
                    continue;

                if ((type == playermessagestring || type == playerwarningstring
                    || type == obituarystring || type == playerobituarystring)
                    && console[i].count > 1)
                {
                    char    buffer[CONSOLETEXTMAXLENGTH];
                    char    *temp2 = commify(console[i].count);

                    M_snprintf(buffer, sizeof(buffer), "%s (%s)", string, temp2);
                    free(temp2);
                    free(string);
                    string = M_StringDuplicate(buffer);
                }

                M_StringReplaceAll(string, "(AM)", "am", false);
                M_StringReplaceAll(string, "(PM)", "pm", false);
                M_StringReplaceAll(string, "\x95", "*", false);

                if (!(len = (int)strlen(string)))
                {
                    free(string);
                    continue;
                }

                for (int inpos = (indentation(string) - 1) / 2; inpos < len; inpos++)
                {
                    const unsigned char letter = string[inpos];

                    if (letter == '\t')
                    {
                        const unsigned int  tabstop = console[i].tabs[tabcount] / 6;

                        if (outpos < tabstop)
                        {
                            for (unsigned int spaces = 0; spaces < tabstop - outpos; spaces++)
                                if (linepos < (int)sizeof(line) - 1)
                                    line[linepos++] = ' ';

                            outpos = tabstop;
                            tabcount++;
                        }
                        else
                        {
                            if (linepos < (int)sizeof(line) - 1)
                                line[linepos++] = ' ';

                            outpos++;
                        }
                    }
                    else if (letter != '\n'
                        && letter != BOLDONCHAR && letter != BOLDOFFCHAR
                        && letter != ITALICSONCHAR && letter != ITALICSOFFCHAR
                        && letter != MONOSPACEDONCHAR && letter != MONOSPACEDOFFCHAR)
                    {
                        if (linepos < (int)sizeof(line) - 1)
                            line[linepos++] = letter;

                        outpos++;
                    }
                }

                if (type == playermessagestring || type == playerwarningstring
                    || type == obituarystring || type == playerobituarystring)
                {
                    int         spaces = (int)strlen(DIVIDERSTRING) - 10 - outpos;
                    struct tm   timestamp = console[i].timestamp;

                    if (con_timestampformat == con_timestampformat_military)
                        spaces += 2;

                    for (int j = 0; j < spaces; j++)
                        if (linepos < (int)sizeof(line) - 1)
                            line[linepos++] = ' ';

                    if (con_timestampformat == con_timestampformat_military)
                        M_snprintf(line + linepos, sizeof(line) - linepos, "%02i:%02i:%02i",
                            timestamp.tm_hour, timestamp.tm_min, timestamp.tm_sec);
                    else
                    {
                        const int   hour = timestamp.tm_hour;

                        M_snprintf(line + linepos, sizeof(line) - linepos, "%2i:%02i:%02i%s",
                            (hour ? hour - 12 * (hour > 12) : 12), timestamp.tm_min,
                            timestamp.tm_sec, (hour < 12 ? "AM" : "PM"));
                    }

                    linepos = (int)strlen(line);
                }

                line[(linepos < (int)sizeof(line) - 2 ? linepos : (int)sizeof(line) - 2)] = '\n';
                line[(linepos < (int)sizeof(line) - 2 ? linepos : (int)sizeof(line) - 2) + 1] = '\0';

#if defined(_WIN32)
                condumpwriteline(file, line);
#else
                fputs(line, file);
#endif

                free(string);
            }
        }

        fclose(file);

        C_Output("%s lines from the console were dumped into " BOLD("%s") ".", temp1, filename);
        free(temp1);
    }
    else
        C_Warning(0, BOLD("%s") " couldn't be created.", filename);
}

//
// cvarlist CCMD
//
static void cvarlistfunc2(char *cmd, char *parms)
{
    const int   tabs[MAXTABS] = { 195, 310 };

    for (int i = 0, count = 0; *consolecmds[i].name; i++)
        if (consolecmds[i].type == CT_CVAR)
        {
            char    name[255];
            char    description[255];

            M_StringCopy(name, (english == english_american
                || M_StringCompare(consolecmds[i].altspelling, EMPTYVALUE) ? consolecmds[i].name :
                consolecmds[i].altspelling), sizeof(name));

            if (*parms && !wildcard(name, parms))
                continue;

            if (++count == 1)
                C_Header(tabs, cvarlist, CVARLISTHEADER);

            M_StringCopy(description, consolecmds[i].description, sizeof(description));

            if (english == english_british)
                M_AmericanToBritishEnglish(description);

            if (M_StringCompare(name, stringize(ammo)))
            {
                if (gamestate == GS_LEVEL)
                {
                    int value = viewplayer->ammo[weaponinfo[viewplayer->readyweapon].ammotype];

                    if (value == consolecmds[i].defaultnumber)
                        C_TabbedOutput(tabs, BOLD("%s") "\t" BOLD("%i") "\t%s", name, value, description);
                    else
                        C_TabbedOutput(tabs, BOLD("%s") "\t" BOLDER("%i") "\t%s", name, value, description);
                }
                else
                    C_TabbedOutput(tabs, BOLD("%s") "\t" BOLD("%i") "\t%s", name, ammo_default, description);
            }
            else if (M_StringCompare(name, stringize(armor)))
            {
                if (gamestate == GS_LEVEL)
                {
                    int value = viewplayer->armor;

                    if (value == consolecmds[i].defaultnumber)
                        C_TabbedOutput(tabs, BOLD("%s") "\t" BOLD("%i%%") "\t%s", name, value, description);
                    else
                        C_TabbedOutput(tabs, BOLD("%s") "\t" BOLDER("%i%%") "\t%s", name, value, description);
                }
                else
                    C_TabbedOutput(tabs, BOLD("%s") "\t" BOLD("%i%%") "\t%s", name, armor_default, description);
            }
            else if (M_StringCompare(name, stringize(armortype)))
            {
                if (gamestate == GS_LEVEL)
                {
                    int     value = viewplayer->armortype;
                    char    *temp = C_LookupAliasFromValue(value, ARMORTYPEVALUEALIAS);

                    if (value == consolecmds[i].defaultnumber)
                        C_TabbedOutput(tabs, BOLD("%s") "\t" BOLD("%s") "\t%s", name, temp, description);
                    else
                        C_TabbedOutput(tabs, BOLD("%s") "\t" BOLDER("%s") "\t%s", name, temp, description);

                    free(temp);
                }
                else
                    C_TabbedOutput(tabs, BOLD("%s") "\t" BOLD("none") "\t%s", name, description);
            }
            else if (M_StringCompare(name, stringize(health)))
            {
                if (gamestate == GS_LEVEL)
                {
                    int value = (negativehealth && minuspatch && !viewplayer->health ?
                        viewplayer->negativehealth : viewplayer->health);

                    if (value == consolecmds[i].defaultnumber)
                        C_TabbedOutput(tabs, BOLD("%s") "\t" BOLD("%i%%") "\t%s", name, value, description);
                    else
                        C_TabbedOutput(tabs, BOLD("%s") "\t" BOLDER("%i%%") "\t%s", name, value, description);
                }
                else
                    C_TabbedOutput(tabs, BOLD("%s") "\t" BOLD("%i%%") "\t%s", name, health_default, description);
            }
            else if (M_StringCompare(name, stringize(weapon)))
            {
                if (legacyofrust)
                    M_StringCopy(description, WEAPONDESCRIPTION_LEGACYOFRUST, sizeof(description));
                else if (gamemode == shareware)
                    M_StringCopy(description, WEAPONDESCRIPTION_SHAREWARE, sizeof(description));
                else if (gamemission != doom)
                    M_StringCopy(description, WEAPONDESCRIPTION_DOOM2, sizeof(description));

                if (gamestate == GS_LEVEL)
                {
                    const int   value = viewplayer->readyweapon;
                    char        *temp = C_LookupAliasFromValue(value, WEAPONVALUEALIAS);

                    if (value == consolecmds[i].defaultnumber)
                        C_TabbedOutput(tabs, BOLD("%s") "\t" BOLD("%s") "\t%s", name, temp, description);
                    else
                        C_TabbedOutput(tabs, BOLD("%s") "\t" BOLDER("%s") "\t%s", name, temp, description);

                    free(temp);
                }
                else
                {
                    char    *temp = C_LookupAliasFromValue(weapon_default, WEAPONVALUEALIAS);

                    C_TabbedOutput(tabs, BOLD("%s") "\t" BOLD("%s") "\t%s", name, temp, description);
                    free(temp);
                }
            }
            else if (M_StringCompare(name, stringize(r_fov)))
            {
                const int   value = *(int *)consolecmds[i].variable;

                if (value == consolecmds[i].defaultnumber)
                    C_TabbedOutput(tabs, BOLD("%s") "\t" BOLD("%i\xB0") "\t%s", name, value, description);
                else
                    C_TabbedOutput(tabs, BOLD("%s") "\t" BOLDER("%i\xB0") "\t%s", name, value, description);
            }
            else if (consolecmds[i].flags & CF_BOOLEAN)
            {
                const bool  value = *(bool *)consolecmds[i].variable;
                char        *temp = C_LookupAliasFromValue(value, consolecmds[i].aliases);

                if (value == consolecmds[i].defaultnumber)
                    C_TabbedOutput(tabs, BOLD("%s") "\t" BOLD("%s") "\t%s", name, temp, description);
                else
                    C_TabbedOutput(tabs, BOLD("%s") "\t" BOLDER("%s") "\t%s", name, temp, description);

                free(temp);
            }
            else if ((consolecmds[i].flags & CF_INTEGER) && (consolecmds[i].flags & CF_PERCENT))
            {
                const int   value = *(int *)consolecmds[i].variable;

                if (value == consolecmds[i].defaultnumber)
                    C_TabbedOutput(tabs, BOLD("%s") "\t" BOLD("%i%%") "\t%s", name, value, description);
                else
                    C_TabbedOutput(tabs, BOLD("%s") "\t" BOLDER("%i%%") "\t%s", name, value, description);
            }
            else if (consolecmds[i].flags & CF_INTEGER)
            {
                const int   value = *(int *)consolecmds[i].variable;
                char        *temp1 = C_LookupAliasFromValue(value, consolecmds[i].aliases);

                if (consolecmds[i].flags & CF_COLOR)
                {
                    char    *temp2 = C_FormatColorValue(temp1, false);

                    if (value == consolecmds[i].defaultnumber)
                        C_TabbedOutput(tabs, BOLD("%s") "\t" BOLD("%s") "\t%s", name, temp2, description);
                    else
                        C_TabbedOutput(tabs, BOLD("%s") "\t" BOLDER("%s") "\t%s", name, temp2, description);

                    free(temp2);
                }
                else if (value == consolecmds[i].defaultnumber)
                    C_TabbedOutput(tabs, BOLD("%s") "\t" BOLD("%s") "\t%s", name, temp1, description);
                else
                    C_TabbedOutput(tabs, BOLD("%s") "\t" BOLDER("%s") "\t%s", name, temp1, description);

                free(temp1);
            }
            else if (consolecmds[i].flags & CF_FLOAT)
            {
                const float value = *(float *)consolecmds[i].variable;

                if (consolecmds[i].flags & CF_PERCENT)
                {
                    char    *temp = striptrailingzero(value, 1);

                    if (value == consolecmds[i].defaultnumber)
                        C_TabbedOutput(tabs, BOLD("%s") "\t" BOLD("%s%%") "\t%s", name, temp, description);
                    else
                        C_TabbedOutput(tabs, BOLD("%s") "\t" BOLDER("%s%%") "\t%s", name, temp, description);

                    free(temp);
                }
                else if (M_StringCompare(consolecmds[i].name, stringize(r_gamma)))
                {
                    char    buffer[128];
                    int     len;

                    M_snprintf(buffer, sizeof(buffer), "%.2f", value);
                    len = (int)strlen(buffer);

                    if (len >= 2 && buffer[len - 1] == '0' && buffer[len - 2] == '0')
                        buffer[len - 1] = '\0';

                    if (value == consolecmds[i].defaultnumber)
                        C_TabbedOutput(tabs, BOLD("%s") "\t" BOLD("%s") "\t%s", name, buffer, description);
                    else
                        C_TabbedOutput(tabs, BOLD("%s") "\t" BOLDER("%s") "\t%s", name, buffer, description);
                }
                else
                {
                    char    *temp = striptrailingzero(value, 1);

                    if (value == consolecmds[i].defaultnumber)
                        C_TabbedOutput(tabs, BOLD("%s") "\t" BOLD("%s") "\t%s", name, temp, description);
                    else
                        C_TabbedOutput(tabs, BOLD("%s") "\t" BOLDER("%s") "\t%s", name, temp, description);

                    free(temp);
                }
            }
            else if (M_StringCompare(name, stringize(version)))
                C_TabbedOutput(tabs, BOLD("%s") "\t" BOLD("%s") "\t%s",
                    name, *(char **)consolecmds[i].variable, description);
            else if (consolecmds[i].flags & CF_STRING)
            {
                const char  *value = *(char **)consolecmds[i].variable;

                if (M_StringCompare(value, consolecmds[i].defaultstring))
                    C_TabbedOutput(tabs, BOLD("%s") "\t" BOLD("\"%.14s%s\"") "\t%s",
                        name, value, (strlen(value) > 14 ? "..." : ""), description);
                else
                    C_TabbedOutput(tabs, BOLD("%s") "\t" BOLDER("\"%.14s%s\"") "\t%s",
                        name, value, (strlen(value) > 14 ? "..." : ""), description);
            }
            else if (consolecmds[i].flags & CF_TIME)
            {
                int         tics = *(int *)consolecmds[i].variable;
                int         seconds = tics / TICRATE;
                const int   milliseconds = (tics % TICRATE) * (1000 / TICRATE);
                const int   hours = seconds / 3600;
                const int   minutes = ((seconds %= 3600)) / 60;

                if (!tics)
                    C_TabbedOutput(tabs, BOLD("%s")
                        "\t" BOLD(MONOSPACED("%02i") ":" MONOSPACED("%02i") "." MONOSPACED("%02i")) "\t%s",
                        name, minutes, seconds % 60, milliseconds / 10, description);
                else if (!hours)
                    C_TabbedOutput(tabs, BOLD("%s")
                        "\t" BOLDER(MONOSPACED("%02i") ":" MONOSPACED("%02i") "." MONOSPACED("%02i")) "\t%s",
                        name, minutes, seconds % 60, milliseconds / 10, description);
                else
                    C_TabbedOutput(tabs, BOLD("%s")
                        "\t" BOLDER(MONOSPACED("%i") ":" MONOSPACED("%02i") ":" MONOSPACED("%02i") "." MONOSPACED("%02i")) "\t%s",
                        name, hours, minutes, seconds % 60, milliseconds / 10, description);
            }
            else if (consolecmds[i].flags & CF_OTHER)
            {
                char        temp[255];
                const char  *value = *(char **)consolecmds[i].variable;

                M_StringCopy(temp, value, sizeof(temp));

                if (english == english_british)
                    M_AmericanToBritishEnglish(temp);

                if (M_StringCompare(value, consolecmds[i].defaultstring))
                    C_TabbedOutput(tabs, BOLD("%s") "\t" BOLD("%s") "\t%s",
                        name, temp, description);
                else
                    C_TabbedOutput(tabs, BOLD("%s") "\t" BOLDER("%s") "\t%s",
                        name, temp, description);
            }
        }
}

//
// endgame CCMD
//
static void endgamefunc2(char *cmd, char *parms)
{
    if (gamestate != GS_LEVEL && gamestate != GS_INTERMISSION)
    {
        const int   i = C_GetIndex(cmd);

        C_ShowFormat(i);
        C_ShowDescription(i);

        if (isdefaultplayername())
            C_Warning(0, NOGAMECCMDWARNING1);
        else
            C_Warning(0, NOGAMECCMDWARNING2, playername, pronoun(personal),
                (playergender == playergender_other ? "aren't" : "isn't"));
    }
    else
        M_EndGame(0);
}

//
// exec CCMD
//
void execfunc2(char *cmd, char *parms)
{
    if (!*parms)
    {
        const int   i = C_GetIndex(cmd);

        C_ShowFormat(i);
        C_ShowDescription(i);
    }
    else
    {
        char    filename[MAX_PATH];
        char    strparm[512] = "";
        char    *temp;
        FILE    *file;
        int     linecount = 0;

        if (strchr(parms, '.'))
            M_StringCopy(filename, parms, sizeof(filename));
        else
            M_snprintf(filename, sizeof(filename), "%s.cfg", parms);

        if (!(file = fopen(filename, "rt")))
        {
            C_Warning(0, BOLD("%s") " couldn't be opened.", parms);
            return;
        }

        parsingcfgfile = true;

        while (fgets(strparm, sizeof(strparm), file))
        {
            if (strparm[0] == ';')
                continue;

            if (C_ValidateInput(strparm))
                linecount++;
        }

        parsingcfgfile = false;
        fclose(file);

        temp = commify(linecount);
        C_Output("%s line%s have been parsed in " BOLD("%s") ".",
            temp, (linecount == 1 ? "" : "s"), leafname(parms));
        free(temp);
    }
}

//
// exitmap CCMD
//
static void exitmapfunc2(char *cmd, char *parms)
{
    G_ExitLevel();
    C_HideConsoleFast();
    viewplayer->cheated++;
    stat_cheatsentered = SafeAdd(stat_cheatsentered, 1);
    M_SaveCVARs();
}

//
// fastmonsters CCMD
//
static void fastmonstersfunc2(char *cmd, char *parms)
{
    if (*parms)
    {
        const int   value = C_LookupValueFromAlias(parms, BOOLVALUEALIAS);

        if (value == 0 && fastparm)
            fastparm = false;
        else if (value == 1 && !fastparm)
            fastparm = true;
        else
            return;
    }
    else
        fastparm = !fastparm;

    G_SetFastParms(fastparm);

    if (fastparm)
    {
        C_Output(s_STSTR_FMON);
        HU_SetPlayerMessage(s_STSTR_FMON, false, false);
    }
    else
    {
        C_Output(s_STSTR_FMOFF);
        HU_SetPlayerMessage(s_STSTR_FMOFF, false, false);
    }
}

//
// freeze CCMD
//
static void freezefunc2(char *cmd, char *parms)
{
    if (*parms)
    {
        const int   value = C_LookupValueFromAlias(parms, BOOLVALUEALIAS);

        if (value == 0 && (viewplayer->cheats & CF_FREEZE))
            viewplayer->cheats &= ~CF_FREEZE;
        else if (value == 1 && !(viewplayer->cheats & CF_FREEZE))
            viewplayer->cheats |= CF_FREEZE;
        else
            return;
    }
    else
        viewplayer->cheats ^= CF_FREEZE;

    if (viewplayer->cheats & CF_FREEZE)
    {
        mobj_t  *mo = viewplayer->mo;

        C_Output(s_STSTR_FON);
        HU_SetPlayerMessage(s_STSTR_FON, false, false);
        viewplayer->cheated++;
        stat_cheatsentered = SafeAdd(stat_cheatsentered, 1);
        M_SaveCVARs();

        mo->momx = 0;
        mo->momy = 0;
        mo->momz = 0;
    }
    else
    {
        C_Output(s_STSTR_FOFF);
        HU_SetPlayerMessage(s_STSTR_FOFF, false, false);
    }

    C_HideConsole();
}

//
// give CCMD
//
static bool givefunc1(char *cmd, char *parms)
{
    bool    result = false;
    char    *parm = removenonalpha(parms);

    if (!*parm || gamestate != GS_LEVEL)
        return true;

    if (M_StringCompare(parm, "all") || M_StringCompare(parm, "everything")
        || M_StringCompare(parm, "health") || M_StringCompare(parm, "fullhealth")
        || M_StringCompare(parm, "weapons") || M_StringCompare(parm, "allweapons")
        || M_StringCompare(parm, "ammo") || M_StringCompare(parm, "fullammo")
        || M_StringCompare(parm, "ammunition") || M_StringCompare(parm, "fullammunition")
        || M_StringCompare(parm, "armor") || M_StringCompare(parm, "fullarmor")
        || M_StringCompare(parm, "armour") || M_StringCompare(parm, "fullarmour")
        || M_StringCompare(parm, "keys") || M_StringCompare(parm, "allkeys")
        || M_StringCompare(parm, "keycards") || M_StringCompare(parm, "allkeycards")
        || M_StringCompare(parm, "skullkeys") || M_StringCompare(parm, "allskullkeys")
        || M_StringCompare(parm, "pistol") || M_StringCompare(parm, "powerups"))
        result = true;
    else
    {
        for (int i = 0, num = -1; i < NUMMOBJTYPES; i++)
        {
            char    *temp1 = (*mobjinfo[i].name1 ? removenonalpha(mobjinfo[i].name1) : NULL);
            char    *temp2 = (*mobjinfo[i].name2 ? removenonalpha(mobjinfo[i].name2) : NULL);
            char    *temp3 = (*mobjinfo[i].name3 ? removenonalpha(mobjinfo[i].name3) : NULL);

            if ((mobjinfo[i].flags & MF_SPECIAL)
                && ((*mobjinfo[i].name1 && M_StringCompare(parm, temp1))
                    || (*mobjinfo[i].name2 && M_StringCompare(parm, temp2))
                    || (*mobjinfo[i].name3 && M_StringCompare(parm, temp3))
                    || (sscanf(parm, "%10d", &num) == 1 && num == mobjinfo[i].doomednum && num != -1)))
                result = true;

            if (temp1)
                free(temp1);

            if (temp2)
                free(temp2);

            if (temp3)
                free(temp3);

            if (result)
                break;
        }
    }

    free(parm);
    return result;
}

static void givefunc2(char *cmd, char *parms)
{
    char    *parm = removenonalpha(parms);

    if (!*parm || gamestate != GS_LEVEL)
    {
        const int   i = C_GetIndex(cmd);

        C_ShowFormat(i);
        C_ShowDescription(i);

        if (gamestate != GS_LEVEL)
        {
            if (isdefaultplayername())
                C_Warning(0, NOGAMECCMDWARNING1);
            else
                C_Warning(0, NOGAMECCMDWARNING2, playername, pronoun(personal),
                    (playergender == playergender_other ? "aren't" : "isn't"));
        }
    }
    else
    {
        if (M_StringCompare(parm, "all") || M_StringCompare(parm, "everything"))
        {
            bool    result = false;

            if (P_GiveBackpack(false, false))
                result = true;

            if (P_GiveMegaHealth(false))
                result = true;

            if (P_GiveAllWeapons())
                result = true;

            if (P_GiveFullAmmo())
                result = true;

            if (P_GiveArmor(blue_armor_class, false))
                result = true;

            if (P_GiveAllCards())
                result = true;

            for (int i = 0; i < NUMPOWERS; i++)
                if (P_GivePower(i, false))
                    result = true;

            if (result)
            {
                P_AddBonus();
                S_StartSound(viewplayer->mo, sfx_itemup);

                if (isdefaultplayername())
                    C_PlayerMessage("You have been given everything.");
                else
                    C_PlayerMessage("%s has been given everything.", playername);

                C_HideConsole();
            }
            else
            {
                if (isdefaultplayername())
                    C_Warning(0, "You already have everything.");
                else
                    C_Warning(0, "%s already has everything.", playername);

                free(parm);
                return;
            }
        }
        else if (M_StringCompare(parm, "health") || M_StringCompare(parm, "fullhealth"))
        {
            if (P_GiveMegaHealth(false))
            {
                P_AddBonus();
                S_StartSound(viewplayer->mo, sfx_itemup);

                if (isdefaultplayername())
                    C_PlayerMessage("You have been given full health.");
                else
                    C_PlayerMessage("%s has been given full health.", playername);

                C_HideConsole();
            }
            else
            {
                if (isdefaultplayername())
                    C_Warning(0, "You already have full health.");
                else
                    C_Warning(0, "%s already has full health.", playername);

                free(parm);
                return;
            }
        }
        else if (M_StringCompare(parm, "weapons") || M_StringCompare(parm, "allweapons"))
        {
            if (P_GiveAllWeapons())
            {
                P_AddBonus();
                S_StartSound(viewplayer->mo, sfx_itemup);

                if (isdefaultplayername())
                    C_PlayerMessage("You have been given all your weapons.");
                else
                    C_PlayerMessage("%s has been given all %s weapons.",
                        playername, pronoun(possessive));

                C_HideConsole();
            }
            else
            {
                if (isdefaultplayername())
                    C_Warning(0, "You already have all your weapons.");
                else
                    C_Warning(0, "%s already has all %s weapons.",
                        playername, pronoun(possessive));

                free(parm);
                return;
            }
        }
        else if (M_StringCompare(parm, "ammo") || M_StringCompare(parm, "fullammo")
                || M_StringCompare(parm, "ammunition") || M_StringCompare(parm, "fullammunition"))
        {
            if (P_GiveFullAmmo())
            {
                P_AddBonus();
                S_StartSound(viewplayer->mo, sfx_itemup);

                if (isdefaultplayername())
                    C_PlayerMessage("You have been given full ammo for all your weapons.");
                else
                    C_PlayerMessage("%s has been given full ammo for all %s weapons.",
                        playername, pronoun(possessive));

                C_HideConsole();
            }
            else if (P_GiveBackpack(false, false) && P_GiveFullAmmo())
            {
                P_AddBonus();
                S_StartSound(viewplayer->mo, sfx_itemup);

                if (isdefaultplayername())
                    C_PlayerMessage("You have been given a backpack and full ammo for all your weapons.");
                else
                    C_PlayerMessage("%s has been given a backpack and full ammo for all %s weapons.",
                        playername, pronoun(possessive));

                C_HideConsole();
            }
            else
            {
                if (isdefaultplayername())
                    C_Warning(0, "You already have full ammo for all your weapons.");
                else
                    C_Warning(0, "%s already has full ammo for all %s weapons.",
                        playername, pronoun(possessive));

                free(parm);
                return;
            }
        }
        else if (M_StringCompare(parm, "armor") || M_StringCompare(parm, "fullarmor")
            || M_StringCompare(parm, "armour") || M_StringCompare(parm, "fullarmour"))
        {
            if (P_GiveArmor(blue_armor_class, false))
            {
                P_AddBonus();
                S_StartSound(viewplayer->mo, sfx_itemup);

                if (isdefaultplayername())
                    C_PlayerMessage("You have been given full %s.",
                        (english == english_american ? "armor" : "armour"));
                else
                    C_PlayerMessage("%s has been given full %s.",
                        playername, (english == english_american ? "armor" : "armour"));

                C_HideConsole();
            }
            else
            {
                if (isdefaultplayername())
                    C_Warning(0, "You already have full %s.",
                        (english == english_american ? "armor" : "armour"));
                else
                    C_Warning(0, "%s already has full %s.",
                        playername, (english == english_american ? "armor" : "armour"));

                free(parm);
                return;
            }
        }
        else if (M_StringCompare(parm, "keys") || M_StringCompare(parm, "allkeys"))
        {
            if (P_GiveAllCards())
            {
                P_AddBonus();
                S_StartSound(viewplayer->mo, sfx_itemup);

                if (isdefaultplayername())
                    C_PlayerMessage("You have been given all keycards and skull keys.");
                else
                    C_PlayerMessage("%s has been given all keycards and skull keys.", playername);

                C_HideConsole();
            }
            else
            {
                if (isdefaultplayername())
                    C_Warning(0, "You already have all keycards and skull keys.");
                else
                    C_Warning(0, "%s already has all keycards and skull keys.", playername);

                free(parm);
                return;
            }
        }
        else if (M_StringCompare(parm, "keycards") || M_StringCompare(parm, "allkeycards"))
        {
            if (P_GiveAllKeyCards())
            {
                P_AddBonus();
                S_StartSound(viewplayer->mo, sfx_itemup);

                if (isdefaultplayername())
                    C_PlayerMessage("You have been given all keycards.");
                else
                    C_PlayerMessage("%s has been given all keycards.", playername);

                C_HideConsole();
            }
            else
            {
                if (isdefaultplayername())
                    C_Warning(0, "You already have all keycards.");
                else
                    C_Warning(0, "%s already has all keycards.", playername);

                free(parm);
                return;
            }
        }
        else if (M_StringCompare(parm, "skullkeys") || M_StringCompare(parm, "allskullkeys"))
        {
            if (P_GiveAllSkullKeys())
            {
                P_AddBonus();
                S_StartSound(viewplayer->mo, sfx_itemup);

                if (isdefaultplayername())
                    C_PlayerMessage("You have been given all skull keys.");
                else
                    C_PlayerMessage("%s has been given all skull keys.", playername);

                C_HideConsole();
            }
            else
            {
                if (isdefaultplayername())
                    C_Warning(0, "You already have all skull keys.");
                else
                    C_Warning(0, "%s already has all skull keys.", playername);

                free(parm);
                return;
            }
        }
        else if (M_StringCompare(parm, "pistol"))
        {
            if (viewplayer->weaponowned[wp_pistol])
            {
                if (isdefaultplayername())
                    C_Warning(0, "You already have a pistol.");
                else
                    C_Warning(0, "%s already has a pistol.", playername);

                free(parm);
                return;
            }

            viewplayer->weaponowned[wp_pistol] = true;
            oldweaponsowned[wp_pistol] = true;
            viewplayer->pendingweapon = wp_pistol;
            viewplayer->ammo[am_clip] = MAX(1, viewplayer->ammo[am_clip]);

            if (isdefaultplayername())
                C_PlayerMessage("You have been given a pistol.");
            else
                C_PlayerMessage("%s has been given a pistol.", playername);

            C_HideConsole();
            free(parm);

            return;
        }
        else if (M_StringCompare(parm, "powerups"))
        {
            bool    result = false;

            for (int i = 0; i < NUMPOWERS; i++)
                if (P_GivePower(i, false))
                    result = true;

            if (result)
            {
                if (isdefaultplayername())
                    C_PlayerMessage("You have been given all the power-ups.");
                else
                    C_PlayerMessage("%s has been given all the power-ups.", playername);

                C_HideConsole();
            }
            else
            {
                if (isdefaultplayername())
                    C_Warning(0, "You already have all the power-ups.");
                else
                    C_Warning(0, "%s already has all the power-ups.", playername);
            }

            free(parm);
            return;
        }
        else
        {
            for (int i = 0, num = -1; i < NUMMOBJTYPES; i++)
            {
                bool    result = false;
                char    *temp1 = (*mobjinfo[i].name1 ? removenonalpha(mobjinfo[i].name1) : NULL);
                char    *temp2 = (*mobjinfo[i].name2 ? removenonalpha(mobjinfo[i].name2) : NULL);
                char    *temp3 = (*mobjinfo[i].name3 ? removenonalpha(mobjinfo[i].name3) : NULL);

                if ((mobjinfo[i].flags & MF_SPECIAL)
                    && ((*mobjinfo[i].name1 && M_StringCompare(parm, temp1))
                        || (*mobjinfo[i].name2 && M_StringCompare(parm, temp2))
                        || (*mobjinfo[i].name3 && M_StringCompare(parm, temp3))
                        || (sscanf(parm, "%10d", &num) == 1 && num == mobjinfo[i].doomednum && num != -1)))
                {
                    if (gamemode != commercial && (i == MT_SUPERSHOTGUN || i == MT_MEGA))
                        C_Warning(0, "%s can't be given %s %s in " ITALICS("%s") "!",
                            C_GetPlayerName(), (isvowel(mobjinfo[i].name1[0]) ? "an" : "a"),
                            mobjinfo[i].name1, gamedescription);
                    else if (gamemode == shareware && (i == MT_MISC7 || i == MT_MISC8 || i == MT_MISC9
                        || i == MT_MISC20 || i == MT_MISC21 || i == MT_MISC25 || i == MT_MISC28))
                    {
                        if (isdefaultplayername())
                            C_Warning(0, "You can't be given %s %s in the shareware version of " ITALICS("DOOM") "! "
                                "You can buy the full version on " ITALICS("Steam") ", etc.",
                                (isvowel(mobjinfo[i].name1[0]) ? "an" : "a"), mobjinfo[i].name1);
                        else
                            C_Warning(0, "%s can't be given %s %s in the shareware version of " ITALICS("DOOM") "! "
                                "%s can buy the full version on " ITALICS("Steam") ", etc.",
                                playername, (isvowel(mobjinfo[i].name1[0]) ? "an" : "a"),
                                mobjinfo[i].name1, titlecase(pronoun(personal)));
                    }
                    else
                    {
                        mobj_t      *thing = P_SpawnMobj(viewx, viewy, viewz, i);
                        const bool  freeze = !!(viewplayer->cheats & CF_FREEZE);

                        if (freeze)
                            viewplayer->cheats &= ~CF_FREEZE;

                        if (viewplayer->health <= 0)
                        {
                            if (isdefaultplayername())
                                C_Warning(0, "You can't be given %s %s when you are dead!",
                                    (isvowel(mobjinfo[i].name1[0]) ? "an" : "a"), mobjinfo[i].name1);
                            else
                                C_Warning(0, "%s can't be given %s %s when %s %s dead!",
                                    playername, (isvowel(mobjinfo[i].name1[0]) ? "an" : "a"), mobjinfo[i].name1,
                                    pronoun(personal), (playergender == playergender_other ? "are" : "is"));
                        }
                        else if (P_TouchSpecialThing(thing, viewplayer->mo, false, false))
                        {
                            if (thing->type == MT_MISC0 || thing->type == MT_MISC1)
                            {
                                if (isdefaultplayername())
                                    C_PlayerMessage("You have been given %s.", mobjinfo[i].name1);
                                else
                                    C_PlayerMessage("%s has been given %s.", playername, mobjinfo[i].name1);
                            }
                            else
                            {
                                if (isdefaultplayername())
                                    C_PlayerMessage("You have been given %s %s.",
                                        (isvowel(mobjinfo[i].name1[0]) ? "an" : "a"), mobjinfo[i].name1);
                                else
                                    C_PlayerMessage("%s has been given %s %s.",
                                        playername, (isvowel(mobjinfo[i].name1[0]) ? "an" : "a"), mobjinfo[i].name1);
                            }

                            C_HideConsole();
                        }
                        else
                        {
                            C_Warning(0, "%s can't be given another %s!", C_GetPlayerName(), mobjinfo[i].name1);

                            P_RemoveMobj(thing);
                        }

                        if (freeze)
                            viewplayer->cheats |= CF_FREEZE;

                        result = true;
                    }
                }

                if (temp1)
                    free(temp1);

                if (temp2)
                    free(temp2);

                if (temp3)
                    free(temp3);

                if (result)
                    break;
            }
        }

        viewplayer->cheated++;
        stat_cheatsentered = SafeAdd(stat_cheatsentered, 1);
        M_SaveCVARs();
        free(parm);
    }
}

//
// god CCMD
//
static void godfunc2(char *cmd, char *parms)
{
    if (*parms)
    {
        const int   value = C_LookupValueFromAlias(parms, BOOLVALUEALIAS);

        if (value == 0 && (viewplayer->cheats & CF_GODMODE))
            viewplayer->cheats &= ~CF_GODMODE;
        else if (value == 1 && !(viewplayer->cheats & CF_GODMODE))
            viewplayer->cheats |= CF_GODMODE;
        else
            return;
    }
    else
        viewplayer->cheats ^= CF_GODMODE;

    if (viewplayer->cheats & CF_GODMODE)
    {
        C_Output(s_STSTR_GODON);
        viewplayer->cheated++;
        stat_cheatsentered = SafeAdd(stat_cheatsentered, 1);
        M_SaveCVARs();
    }
    else
        C_Output(s_STSTR_GODOFF);
}

//
// if CCMD
//
static bool match(bool value, const char *toggle)
{
    return ((value && M_StringCompare(toggle, "on")) || (!value && M_StringCompare(toggle, "off")));
}

static void iffunc2(char *cmd, char *parms)
{
    char    parm1[64] = "";
    char    parm2[64] = "";
    char    parm3[128] = "";

    if (sscanf(parms, "%63s is %63s then %127[^\n]", parm1, parm2, parm3) != 3
        && sscanf(parms, "%63s %63s then %127[^\n]", parm1, parm2, parm3) != 3)
    {
        const int   i = C_GetIndex(cmd);

        C_ShowFormat(i);
        C_ShowDescription(i);
        return;
    }

    M_StripQuotes(parm1);

    for (int i = 0; *consolecmds[i].name; i++)
        if (M_StringCompare(parm1, consolecmds[i].name))
        {
            bool    condition = false;

            M_StripQuotes(parm2);

            if (M_StringCompare(parm1, "ammo"))
            {
                int value = INT_MIN;

                if (sscanf(parms, "%10d", &value) == 1)
                    condition = (value != INT_MIN
                        && value == viewplayer->ammo[weaponinfo[viewplayer->readyweapon].ammotype]);
            }
            else if (M_StringCompare(parm1, "armor") || M_StringCompare(parm1, "armour"))
            {
                int value = INT_MIN;

                if (sscanf(parms, "%10d", &value) == 1)
                    condition = (value != INT_MIN && value == viewplayer->armor);
            }
            else if (M_StringCompare(parm1, "armortype") || M_StringCompare(parm1, "armourtype"))
            {
                int value = C_LookupValueFromAlias(parm2, ARMORTYPEVALUEALIAS);

                if (value != INT_MIN || sscanf(parms, "%10d", &value) == 1)
                    condition = (value != INT_MIN && value == viewplayer->armortype);
            }
            else if (M_StringCompare(parm1, "health"))
            {
                int value = INT_MIN;

                if (sscanf(parms, "%10d", &value) == 1)
                    condition = (value != INT_MIN && value == viewplayer->health);
            }
            else if (consolecmds[i].type == CT_CVAR)
            {
                if (consolecmds[i].flags & CF_BOOLEAN)
                {
                    int value = C_LookupValueFromAlias(parm2, BOOLVALUEALIAS);

                    condition = ((value == 0 || value == 1) && value == *(bool *)consolecmds[i].variable);
                }
                else if (consolecmds[i].flags & CF_INTEGER)
                {
                    int value = C_LookupValueFromAlias(parm2, consolecmds[i].aliases);

                    if (value != INT_MIN || sscanf(parms, "%10d", &value) == 1)
                        condition = (value != INT_MIN && value == *(int *)consolecmds[i].variable);
                }
                else if (consolecmds[i].flags & CF_FLOAT)
                {
                    float   value = FLT_MIN;

                    if (sscanf(parms, "%10f", &value) == 1)
                        condition = (value != FLT_MIN && value == *(float *)consolecmds[i].variable);
                }
                else
                    condition = M_StringCompare(parm2, *(char **)consolecmds[i].variable);
            }
            else if (M_StringCompare(parm1, "fastmonsters"))
                condition = match(fastparm, parm2);
            else if (M_StringCompare(parm1, "freeze"))
                condition = match((gamestate == GS_LEVEL && (viewplayer->cheats & CF_FREEZE)), parm2);
            else if (M_StringCompare(parm1, "god"))
                condition = match((gamestate == GS_LEVEL && (viewplayer->cheats & CF_GODMODE)), parm2);
            else if (M_StringCompare(parm1, "infiniteammo"))
                condition = match(infiniteammo, parm2);
            else if (M_StringCompare(parm1, "noclip"))
                condition = match((gamestate == GS_LEVEL && (viewplayer->cheats & CF_NOCLIP)), parm2);
            else if (M_StringCompare(parm1, "nomonsters"))
                condition = match(nomonsters, parm2);
            else if (M_StringCompare(parm1, "notarget"))
                condition = match((gamestate == GS_LEVEL && (viewplayer->cheats & CF_NOTARGET)), parm2);
            else if (M_StringCompare(parm1, "pistolstart"))
                condition = match(pistolstart, parm2);
            else if (M_StringCompare(parm1, "regenhealth"))
                condition = match(regenhealth, parm2);
            else if (M_StringCompare(parm1, "respawnitems"))
                condition = match(respawnitems, parm2);
            else if (M_StringCompare(parm1, "respawnmonsters"))
                condition = match(respawnmonsters, parm2);
            else if (M_StringCompare(parm1, "vanilla"))
                condition = match(vanilla, parm2);

            if (condition)
            {
                char    *strings[255] = { "" };
                int     j = 0;

                M_StripQuotes(parm3);
                strings[0] = strtok(parm3, ";");

                while (strings[j])
                {
                    if (!C_ValidateInput(trimwhitespace(strings[j])))
                        break;

                    strings[++j] = strtok(NULL, ";");
                }
            }

            break;
        }
}

//
// infiniteammo CCMD
//
static void infiniteammofunc2(char *cmd, char *parms)
{
    if (*parms)
    {
        const int   value = C_LookupValueFromAlias(parms, BOOLVALUEALIAS);

        if (value == 0 && infiniteammo)
            infiniteammo = false;
        else if (value == 1 && !infiniteammo)
            infiniteammo = true;
        else
            return;
    }
    else
        infiniteammo = !infiniteammo;

    if (infiniteammo)
    {
        C_Output(s_STSTR_IAON);
        HU_SetPlayerMessage(s_STSTR_IAON, false, false);
        viewplayer->cheated++;
        stat_cheatsentered = SafeAdd(stat_cheatsentered, 1);
        M_SaveCVARs();
    }
    else
    {
        C_Output(s_STSTR_IAOFF);
        HU_SetPlayerMessage(s_STSTR_IAOFF, false, false);
        P_CheckAmmo(viewplayer->readyweapon);
    }
}

//
// kill CCMD
//
static bool     killcmdfriendly;
static int      killcmdtype = NUMMOBJTYPES;
static mobj_t   *killcmdmobj;
bool            massacre;

static bool killfunc1(char *cmd, char *parms)
{
    bool    result = false;
    char    *parm = removenonalpha(parms);

    if (!*parm || gamestate != GS_LEVEL)
        return true;

    killcmdmobj = NULL;

    if ((killcmdfriendly = M_StringStartsWith(parm, "friendly")))
        M_StringReplaceAll(parm, "friendly", "", false);
    else if (M_StringStartsWith(parm, "unfriendly"))
        M_StringReplaceAll(parm, "unfriendly", "", false);
    else if (M_StringCompare(parm, "dog") || M_StringCompare(parm, "dogs"))
        killcmdfriendly = true;

    if (M_StringCompare(parm, "player") || M_StringCompare(parm, "me") || (*playername && M_StringCompare(parm, playername)))
        result = (viewplayer->health > 0);
    else if (M_StringCompare(parm, "monster") || M_StringCompare(parm, "monsters") || M_StringCompare(parm, "all")
        || M_StringCompare(parm, "friend") || M_StringCompare(parm, "friends")
        || M_StringCompare(parm, "missile") || M_StringCompare(parm, "missiles")
        || M_StringCompare(parm, "projectile") || M_StringCompare(parm, "projectiles")
        || M_StringCompare(parm, "item") || M_StringCompare(parm, "items")
        || M_StringCompare(parm, "decoration") || M_StringCompare(parm, "decorations")
        || M_StringCompare(parm, "corpse") || M_StringCompare(parm, "corpses")
        || M_StringCompare(parm, "blood") || M_StringCompare(parm, "bloodsplat") || M_StringCompare(parm, "bloodsplats")
        || M_StringCompare(parm, "everything"))
        result = true;
    else
    {
        for (int i = 0, num = -1; i < NUMMOBJTYPES; i++)
            if (*mobjinfo[i].name1)
            {
                char    *temp1 = removenonalpha(mobjinfo[i].name1);
                char    *temp2 = (*mobjinfo[i].plural1 ? removenonalpha(mobjinfo[i].plural1) : NULL);
                char    *temp3 = (*mobjinfo[i].name2 ? removenonalpha(mobjinfo[i].name2) : NULL);
                char    *temp4 = (*mobjinfo[i].plural2 ? removenonalpha(mobjinfo[i].plural2) : NULL);
                char    *temp5 = (*mobjinfo[i].name3 ? removenonalpha(mobjinfo[i].name3) : NULL);
                char    *temp6 = (*mobjinfo[i].plural3 ? removenonalpha(mobjinfo[i].plural3) : NULL);

                if (M_StringStartsWith(parm, "all"))
                    M_StringReplaceAll(parm, "all", "", false);

                killcmdtype = mobjinfo[i].doomednum;

                if (killcmdtype >= 0
                    && (M_StringCompare(parm, temp1)
                        || (*mobjinfo[i].plural1 && M_StringCompare(parm, temp2))
                        || (*mobjinfo[i].name2 && M_StringCompare(parm, temp3))
                        || (*mobjinfo[i].plural2 && M_StringCompare(parm, temp4))
                        || (*mobjinfo[i].name3 && M_StringCompare(parm, temp5))
                        || (*mobjinfo[i].plural3 && M_StringCompare(parm, temp6))
                        || (sscanf(parm, "%10d", &num) == 1 && num == killcmdtype && num != -1)))
                {
                    if (killcmdtype == WolfensteinSS && !allowwolfensteinss)
                        result = false;
                    else
                        result = ((mobjinfo[i].flags & MF_SHOOTABLE)
                            || (mobjinfo[i].flags & MF_SPECIAL)
                            || (mobjinfo[i].flags2 & MF2_DECORATION));
                }

                if (temp1)
                    free(temp1);

                if (temp2)
                    free(temp2);

                if (temp3)
                    free(temp3);

                if (temp4)
                    free(temp4);

                if (temp5)
                    free(temp5);

                if (temp6)
                    free(temp6);

                if (result)
                    break;
            }

        if (!result)
            for (thinker_t *th = thinkers[th_mobj].cnext; th != &thinkers[th_mobj]; th = th->cnext)
            {
                mobj_t  *mobj = (mobj_t *)th;

                if (*mobj->name)
                {
                    char    *temp = removenonalpha(mobj->name);

                    if (M_StringCompare(parm, temp))
                    {
                        killcmdmobj = mobj;
                        result = true;
                    }

                    free(temp);

                    if (result)
                        break;
                }
            }
    }

    free(parm);
    return result;
}

void A_Fall(mobj_t *actor, player_t *player, pspdef_t *psp);

static void killfunc2(char *cmd, char *parms)
{
    char    *parm = removenonalpha(parms);

    if (!*parm)
    {
        const int   i = C_GetIndex(cmd);

        C_ShowFormat(i);
        C_ShowDescription(i);

        if (gamestate != GS_LEVEL)
        {
            if (isdefaultplayername())
                C_Warning(0, NOGAMECCMDWARNING1);
            else
                C_Warning(0, NOGAMECCMDWARNING2, playername, pronoun(personal),
                    (playergender == playergender_other ? "aren't" : "isn't"));
        }
    }
    else
    {
        char    buffer[1024];
        char    *killed = (M_StringCompare(cmd, "explode") ? "exploded" : (M_StringCompare(cmd, "remove") ? "removed" : "killed"));

        if (M_StringCompare(parm, "player") || M_StringCompare(parm, "me") || (*playername && M_StringCompare(parm, playername)))
        {
            if (viewplayer->cheats & CF_GODMODE)
            {
                if (isdefaultplayername())
                    C_Warning(0, "You can't kill yourself in god mode!");
                else
                    C_Warning(0, "%s can't kill %s in god mode!", playername, pronoun(reflexive));

                return;
            }

            if (viewplayer->powers[pw_invulnerability])
            {
                if (isdefaultplayername())
                    C_Warning(0, "You can't kill yourself when you have %s %s!",
                        (isvowel(powerups[pw_invulnerability][0]) ? "an" : "a"),
                        powerups[pw_invulnerability]);
                else
                    C_Warning(0, "%s can't kill %s when %s %s %s %s!",
                        playername,
                        pronoun(reflexive),
                        pronoun(personal),
                        (playergender == playergender_other ? "have" : "has"),
                        (isvowel(powerups[pw_invulnerability][0]) ? "an" : "a"),
                        powerups[pw_invulnerability]);

                return;
            }

            if (viewplayer->cheats & CF_BUDDHA)
            {
                if (isdefaultplayername())
                    C_Warning(0, "You can't kill yourself in buddha mode!");
                else
                    C_Warning(0, "%s can't kill %s in buddha mode!", playername, pronoun(reflexive));

                playercvarsfunc2(stringize(health), "1");
                return;
            }

            viewplayer->damagecount = MIN(viewplayer->health, 100);
            P_AnimateHealth(viewplayer->health);
            viewplayer->health = 0;
            viewplayer->mo->health = 0;
            healthhighlight = I_GetTimeMS() + HUD_HEALTH_HIGHLIGHT_WAIT;
            viewplayer->attacker = NULL;

            viewplayer->mo->flags2 |= MF2_MASSACRE;
            massacre = true;
            P_KillMobj(viewplayer->mo, NULL, viewplayer->mo, false);
            massacre = false;

            if (isdefaultplayername())
                M_snprintf(buffer, sizeof(buffer), "You killed yourself!");
            else
                M_snprintf(buffer, sizeof(buffer), "%s killed %s!", playername, pronoun(reflexive));

            C_PlayerWarning("%s", buffer);
            HU_SetPlayerMessage(buffer, false, false);
            message_warning = true;
            C_HideConsole();
        }
        else
        {
            const bool  friends = (M_StringCompare(parm, "friend") || M_StringCompare(parm, "friends")
                            || ((M_StringCompare(parm, "monster") || M_StringCompare(parm, "monsters")) && killcmdfriendly));
            const bool  enemies = (M_StringCompare(parm, "monster") || M_StringCompare(parm, "monsters"));
            const bool  all = M_StringCompare(parm, "all");
            int         kills = 0;

            if (friends || enemies || all)
            {
                for (int i = 0; i < numsectors; i++)
                    for (mobj_t *thing = sectors[i].thinglist; thing; thing = thing->snext)
                    {
                        const int   flags = thing->flags;

                        if (all || !!(flags & MF_FRIEND) == friends)
                        {
                            if ((thing->flags2 & MF2_MONSTERMISSILE)
                                || thing->type == MT_ROCKET
                                || thing->type == MT_PLASMA
                                || thing->type == MT_BFG)
                            {
                                thing->flags2 |= MF2_MASSACRE;
                                P_ExplodeMissile(thing);
                            }
                            else if (thing->health > 0)
                            {
                                const mobjtype_t    type = thing->type;

                                if (type == MT_PAIN)
                                {
                                    A_Fall(thing, NULL, NULL);
                                    P_SetMobjState(thing, S_PAIN_DIE6);
                                    viewplayer->monsterskilled[MT_PAIN]++;
                                    stat_monsterskilled[MT_PAIN] = SafeAdd(stat_monsterskilled[MT_PAIN], 1);

                                    if (!(flags & MF_FRIEND))
                                        viewplayer->killcount++;

                                    stat_monsterskilled_total = SafeAdd(stat_monsterskilled_total, 1);
                                    kills++;
                                }
                                else if ((flags & MF_SHOOTABLE)
                                    && type != MT_PLAYER && type != MT_BARREL
                                    && (type != MT_LAMP || !legacyofrust)
                                    && (type != MT_HEAD || !hacx))
                                {
                                    thing->flags2 |= MF2_MASSACRE;
                                    massacre = true;

                                    P_DamageMobj(thing, viewplayer->mo, viewplayer->mo, thing->health, false, false, false);
                                    massacre = false;
                                    kills++;
                                }
                            }
                        }
                    }

                if (kills)
                {
                    char    *temp = commify(kills);

                    if (isdefaultplayername())
                    {
                        if (kills == 1)
                            M_snprintf(buffer, sizeof(buffer), "You %s the only %smonster in this map!",
                                killed, (friends ? "friendly " : ""));
                        else
                            M_snprintf(buffer, sizeof(buffer), "You %s all %s %smonsters in this map!",
                                killed, temp, (friends ? "friendly " : ""));
                    }
                    else
                    {
                        if (kills == 1)
                            M_snprintf(buffer, sizeof(buffer), "%s %s the only %smonster in this map!",
                                playername, killed, (friends ? "friendly " : ""));
                        else
                            M_snprintf(buffer, sizeof(buffer), "%s %s all %s %smonsters in this map!",
                                playername, killed, temp, (friends ? "friendly " : ""));
                    }

                    C_PlayerMessage("%s", buffer);
                    C_HideConsole();
                    HU_SetPlayerMessage(buffer, false, false);
                    viewplayer->cheated++;
                    stat_cheatsentered = SafeAdd(stat_cheatsentered, 1);
                    M_SaveCVARs();
                    free(temp);
                }
                else
                    C_Warning(0, "There are no %smonsters to %s.", (friends ? "friendly " : ""), cmd);
            }
            else if (M_StringCompare(parm, "missile") || M_StringCompare(parm, "missiles")
                || M_StringCompare(parm, "projectile") || M_StringCompare(parm, "projectiles"))
            {
                for (int i = 0; i < numsectors; i++)
                    for (mobj_t *thing = sectors[i].thinglist; thing; thing = thing->snext)
                        if ((thing->flags2 & MF2_MONSTERMISSILE)
                            || thing->type == MT_ROCKET
                            || thing->type == MT_PLASMA
                            || thing->type == MT_BFG)
                        {
                            P_ExplodeMissile(thing);
                            kills++;
                        }

                if (kills)
                {
                    char    *temp = commify(kills);

                    if (isdefaultplayername())
                        M_snprintf(buffer, sizeof(buffer), "You %s %s missile%s.",
                            killed, temp, (kills == 1 ? "" : "s"));
                    else
                        M_snprintf(buffer, sizeof(buffer), "%s %s %s missile%s.",
                            playername, killed, temp, (kills == 1 ? "" : "s"));

                    C_PlayerMessage(buffer);
                    C_HideConsole();
                    HU_SetPlayerMessage(buffer, false, false);
                    viewplayer->cheated++;
                    stat_cheatsentered = SafeAdd(stat_cheatsentered, 1);
                    M_SaveCVARs();
                    free(temp);
                }
                else
                    C_Warning(0, "There are no missiles to %s.", cmd);
            }
            else if (M_StringCompare(parm, "item") || M_StringCompare(parm, "items"))
            {
                for (int i = 0; i < numsectors; i++)
                    for (mobj_t *thing = sectors[i].thinglist; thing; thing = thing->snext)
                        if (thing->flags & MF_SPECIAL)
                        {
                            P_SpawnMobj(thing->x, thing->y, thing->z, MT_IFOG);
                            S_StartSound(thing, sfx_itmbk);
                            P_RemoveMobj(thing);
                            kills++;
                        }

                if (kills)
                {
                    char    *temp = commify(kills);

                    if (isdefaultplayername())
                        M_snprintf(buffer, sizeof(buffer), "You %s %s item%s.",
                            killed, temp, (kills == 1 ? "" : "s"));
                    else
                        M_snprintf(buffer, sizeof(buffer), "%s %s %s item%s.",
                            playername, killed, temp, (kills == 1 ? "" : "s"));

                    C_PlayerMessage(buffer);
                    C_HideConsole();
                    HU_SetPlayerMessage(buffer, false, false);
                    free(temp);
                }
                else
                    C_Warning(0, "There are no items to %s.", cmd);
            }
            else if (M_StringCompare(parm, "decoration") || M_StringCompare(parm, "decorations"))
            {
                for (int i = 0; i < numsectors; i++)
                    for (mobj_t *thing = sectors[i].thinglist; thing; thing = thing->snext)
                        if (thing->flags2 & MF2_DECORATION)
                        {
                            P_SpawnMobj(thing->x, thing->y, thing->z, MT_TFOG);
                            S_StartSound(thing, sfx_telept);
                            P_RemoveMobj(thing);
                            kills++;
                        }

                if (kills)
                {
                    char    *temp = commify(kills);

                    if (isdefaultplayername())
                        M_snprintf(buffer, sizeof(buffer), "You %s %s decoration%s.",
                            killed, temp, (kills == 1 ? "" : "s"));
                    else
                        M_snprintf(buffer, sizeof(buffer), "%s %s %s decoration%s.",
                            playername, killed, temp, (kills == 1 ? "" : "s"));

                    C_PlayerMessage(buffer);
                    C_HideConsole();
                    HU_SetPlayerMessage(buffer, false, false);
                    viewplayer->cheated++;
                    stat_cheatsentered = SafeAdd(stat_cheatsentered, 1);
                    M_SaveCVARs();
                    free(temp);
                }
                else
                    C_Warning(0, "There are no decorations to %s.", cmd);
            }
            else if (M_StringCompare(parm, "everything"))
            {
                for (int i = 0; i < numsectors; i++)
                    for (mobj_t *thing = sectors[i].thinglist; thing; thing = thing->snext)
                    {
                        const int   flags = thing->flags;
                        const int   flags2 = thing->flags2;

                        if (((flags & MF_SHOOTABLE) && !thing->player) || (flags & MF_CORPSE) || (flags2 & MF2_DECORATION))
                        {
                            P_SpawnMobj(thing->x, thing->y, thing->z, MT_TFOG);
                            S_StartSound(thing, sfx_telept);
                            P_RemoveMobj(thing);
                            kills++;
                        }
                        else if ((flags & MF_SPECIAL)
                            || (flags2 & MF2_MONSTERMISSILE)
                            || thing->type == MT_ROCKET
                            || thing->type == MT_PLASMA
                            || thing->type == MT_BFG)
                        {
                            P_SpawnMobj(thing->x, thing->y, thing->z, MT_IFOG);
                            S_StartSound(thing, sfx_itmbk);
                            P_RemoveMobj(thing);
                            kills++;
                        }
                    }

                P_RemoveBloodSplats();

                if (kills)
                {
                    if (isdefaultplayername())
                        M_snprintf(buffer, sizeof(buffer), "You %s everything.", killed);
                    else
                        M_snprintf(buffer, sizeof(buffer), "%s %s everything.", playername, killed);

                    C_PlayerMessage(buffer);
                    C_HideConsole();
                    HU_SetPlayerMessage(buffer, false, false);
                    viewplayer->cheated++;
                    stat_cheatsentered = SafeAdd(stat_cheatsentered, 1);
                    M_SaveCVARs();
                }
                else
                    C_Warning(0, "There is nothing to %s.", cmd);
            }
            else if (M_StringCompare(parm, "corpse") || M_StringCompare(parm, "corpses"))
            {
                for (int i = 0; i < numsectors; i++)
                    for (mobj_t *thing = sectors[i].thinglist; thing; thing = thing->snext)
                        if (thing->flags & MF_CORPSE)
                        {
                            P_SpawnMobj(thing->x, thing->y, thing->z, MT_TFOG);
                            S_StartSound(thing, sfx_telept);
                            P_RemoveMobj(thing);
                            kills++;
                        }

                if (kills)
                {
                    if (isdefaultplayername())
                        M_snprintf(buffer, sizeof(buffer), "You %s all corpses.", killed);
                    else
                        M_snprintf(buffer, sizeof(buffer), "%s %s all corpses.", playername, killed);

                    C_PlayerMessage(buffer);
                    C_HideConsole();
                    HU_SetPlayerMessage(buffer, false, false);
                }
                else
                    C_Warning(0, "There are no corpses to %s.", cmd);
            }
            else if (M_StringCompare(parm, "blood") || M_StringCompare(parm, "bloodsplat") || M_StringCompare(parm, "bloodsplats"))
            {
                if (r_bloodsplats_total)
                {
                    P_RemoveBloodSplats();

                    if (isdefaultplayername())
                        M_snprintf(buffer, sizeof(buffer), "You %s all blood splats.", killed);
                    else
                        M_snprintf(buffer, sizeof(buffer), "%s %s all blood splats.", playername, killed);

                    C_PlayerMessage(buffer);
                    C_HideConsole();
                    HU_SetPlayerMessage(buffer, false, false);
                }
                else
                    C_Warning(0, "There are no blood splats to %s.", cmd);
            }
            else if (killcmdmobj)
            {
                char    *temp = sentencecase(parm);

                if (killcmdmobj->type == MT_PAIN)
                {
                    if (killcmdmobj->health > 0)
                    {
                        A_Fall(killcmdmobj, NULL, NULL);
                        P_SetMobjState(killcmdmobj, S_PAIN_DIE6);
                        viewplayer->monsterskilled[MT_PAIN]++;
                        stat_monsterskilled[MT_PAIN] = SafeAdd(stat_monsterskilled[MT_PAIN], 1);

                        if (!(killcmdmobj->flags & MF_FRIEND))
                            viewplayer->killcount++;

                        stat_monsterskilled_total = SafeAdd(stat_monsterskilled_total, 1);

                        if (isdefaultplayername())
                            M_snprintf(buffer, sizeof(buffer), "You %s %s.", killed, temp);
                        else
                            M_snprintf(buffer, sizeof(buffer), "%s %s %s.", playername, killed, temp);

                        C_PlayerMessage(buffer);
                        C_HideConsole();
                        HU_SetPlayerMessage(buffer, false, false);
                        viewplayer->cheated++;
                        stat_cheatsentered = SafeAdd(stat_cheatsentered, 1);
                        M_SaveCVARs();
                        free(temp);
                    }
                }
                else
                {
                    killcmdmobj->flags2 |= MF2_MASSACRE;
                    massacre = true;
                    P_DamageMobj(killcmdmobj, viewplayer->mo, viewplayer->mo, killcmdmobj->health, false, false, false);
                    massacre = false;

                    if (isdefaultplayername())
                        M_snprintf(buffer, sizeof(buffer), "You %s %s.", killed, temp);
                    else
                        M_snprintf(buffer, sizeof(buffer), "%s %s %s.", playername, killed, temp);

                    C_PlayerMessage(buffer);
                    C_HideConsole();
                    HU_SetPlayerMessage(buffer, false, false);
                    viewplayer->cheated++;
                    stat_cheatsentered = SafeAdd(stat_cheatsentered, 1);
                    M_SaveCVARs();
                    free(temp);
                }
            }
            else
            {
                const int   type = P_FindDoomedNum(killcmdtype);

                massacre = true;

                for (int i = 0; i < numsectors; i++)
                    for (mobj_t *thing = sectors[i].thinglist; thing; thing = thing->snext)
                        if (type == thing->type && !!(thing->flags & MF_FRIEND) == killcmdfriendly)
                        {
                            if (type == MT_PAIN)
                            {
                                if (thing->health > 0)
                                {
                                    A_Fall(thing, NULL, NULL);
                                    P_SetMobjState(thing, S_PAIN_DIE6);
                                    viewplayer->monsterskilled[MT_PAIN]++;
                                    stat_monsterskilled[MT_PAIN] = SafeAdd(stat_monsterskilled[MT_PAIN], 1);

                                    if (!(thing->flags & MF_FRIEND))
                                        viewplayer->killcount++;

                                    stat_monsterskilled_total = SafeAdd(stat_monsterskilled_total, 1);
                                    kills++;
                                }
                            }
                            else if ((thing->flags & MF_SHOOTABLE) && thing->health > 0)
                            {
                                thing->flags2 |= MF2_MASSACRE;
                                massacre = true;
                                P_DamageMobj(thing, viewplayer->mo, viewplayer->mo, thing->health, false, false, false);
                                massacre = false;
                                kills++;
                            }
                            else if (type == MT_BARREL)
                            {
                                P_DamageMobj(thing, viewplayer->mo, viewplayer->mo, thing->health, false, false, false);
                                kills++;
                            }
                            else if (thing->flags & MF_SPECIAL)
                            {
                                P_SpawnMobj(thing->x, thing->y, thing->z, MT_IFOG);
                                S_StartSound(thing, sfx_itmbk);
                                P_RemoveMobj(thing);
                                kills++;
                            }
                            else if (thing->flags2 & MF2_DECORATION)
                            {
                                P_SpawnMobj(thing->x, thing->y, thing->z, MT_TFOG);
                                S_StartSound(thing, sfx_itmbk);
                                P_RemoveMobj(thing);
                                kills++;
                            }
                        }

                if (kills)
                {
                    char    *temp = commify(kills);

                    if (isdefaultplayername())
                    {
                        if (kills == 1)
                            M_snprintf(buffer, sizeof(buffer), "You %s the only %s in this map.",
                                killed,
                                mobjinfo[type].name1);
                        else
                            M_snprintf(buffer, sizeof(buffer), "You %s all %s %s in this map.",
                                killed,
                                temp,
                                mobjinfo[type].plural1);
                    }
                    else
                    {
                        if (kills == 1)
                            M_snprintf(buffer, sizeof(buffer), "%s %s the only %s in this map.",
                                playername,
                                killed,
                                mobjinfo[type].name1);
                        else
                            M_snprintf(buffer, sizeof(buffer), "%s %s all %s %s in this map.",
                                playername,
                                killed,
                                temp,
                                mobjinfo[type].plural1);
                    }

                    C_PlayerMessage(buffer);
                    C_HideConsole();
                    HU_SetPlayerMessage(buffer, false, false);
                    viewplayer->cheated++;
                    stat_cheatsentered = SafeAdd(stat_cheatsentered, 1);
                    M_SaveCVARs();
                    free(temp);
                }
                else
                {
                    if (gamemode != commercial)
                    {
                        if (killcmdtype >= ArchVile && killcmdtype <= MonsterSpawner)
                            C_Warning(0, "There are no %s in " ITALICS("%s") ".",
                                mobjinfo[type].plural1, gamedescription);
                        else if (gamemode == shareware && (killcmdtype == Cyberdemon || killcmdtype == SpiderMastermind))
                            C_Warning(0, "There are no %s in the shareware version of " ITALICS("DOOM") ". "
                                "You can buy the full version on " ITALICS("Steam") ", etc.", mobjinfo[type].plural1);
                        else
                            C_Warning(0, "There are no %s to %s.", mobjinfo[type].plural1, cmd);
                    }
                    else
                        C_Warning(0, "There are no %s to %s.", mobjinfo[type].plural1, cmd);
                }
            }
        }

        free(parm);
    }
}

//
// license CCMD
//
static void licensefunc2(char *cmd, char *parms)
{
    C_Output("Opening the " ITALICS(DOOMRETRO_LICENSE) "...");

#if defined(_WIN32)
    D_OpenURLInBrowser(DOOMRETRO_LICENSEURL, "The " ITALICS(DOOMRETRO_LICENSE) " wouldn't open!");
#elif defined(__linux__) || defined(__FreeBSD__) || defined(__HAIKU__)
    if (!M_system("xdg-open " DOOMRETRO_LICENSEURL))
        C_Warning(0, "The " ITALICS(DOOMRETRO_LICENSE) " wouldn't open!");
#elif defined(__APPLE__)
    if (!M_system("open " DOOMRETRO_LICENSEURL))
        C_Warning(0, "The " ITALICS(DOOMRETRO_LICENSE) " wouldn't open!");
#endif
}

//
// load CCMD
//
static void loadfunc2(char *cmd, char *parms)
{
    char    buffer[1024];

    if (!*parms)
    {
        const int   i = C_GetIndex(cmd);

        C_ShowFormat(i);
        C_ShowDescription(i);
        return;
    }

    if (strlen(parms) == 1 && parms[0] >= '1' && parms[0] <= '8')
    {
        M_snprintf(buffer, sizeof(buffer), "%s" DOOMRETRO_SAVEGAME, savegamefolder, parms[0] - '1');
        G_LoadGame(buffer);
    }
    else
    {
        M_snprintf(buffer, sizeof(buffer), "%s%s%s",
            (M_StringStartsWith(parms, savegamefolder) ? "" : savegamefolder), parms, (M_StringEndsWith(parms, ".save") ? "" : ".save"));
        G_LoadGame(buffer);
    }
}

//
// map CCMD
//
static void C_SetMapCmdMapNum(void)
{
    if (!*mapcmdmapnum)
    {
        const int   ep = (BTSX ? (BTSXE1 ? 1 : (BTSXE2 ? 2 : 3)) : mapcmdepisode);
        const char  *label = trimwhitespace(P_GetLabel(ep, mapcmdmap));

        if (legacyofrust && mapcmdmap != 99)
        {
            if (mapcmdmap <= 7)
                M_snprintf(mapcmdmapnum, sizeof(mapcmdmapnum), "E1M%i", mapcmdmap);
            else if (mapcmdmap == 15)
                M_StringCopy(mapcmdmapnum, "E1M0", sizeof(mapcmdmapnum));
            else if (mapcmdmap == 16)
                M_StringCopy(mapcmdmapnum, "E2M0", sizeof(mapcmdmapnum));
            else
                M_snprintf(mapcmdmapnum, sizeof(mapcmdmapnum), "E2M%i", mapcmdmap - 7);
        }
        else if (*label)
            M_StringCopy(mapcmdmapnum, label, sizeof(mapcmdmapnum));
        else if (BTSX && M_StringEndsWith(mapcmdlump, "C"))
        {
            if (M_StringEndsWith(mapcmdlump, "C"))
                M_StringCopy(mapcmdmapnum, mapcmdlump, sizeof(mapcmdmapnum));
            else
                M_snprintf(mapcmdmapnum, sizeof(mapcmdmapnum), "E%iM%i", ep, mapcmdmap);
        }
        else
            M_StringCopy(mapcmdmapnum, mapcmdlump, sizeof(mapcmdmapnum));
    }
}

static bool mapfunc1(char *cmd, char *parms)
{
    if (!*parms)
        return true;
    else
    {
        bool    result = false;
        char    *temp1 = removenonalpha(parms);
        char    *parm = uppercase(temp1);

        mapcmdepisode = 0;
        mapcmdmap = 0;
        speciallumpname[0] = '\0';
        mapcmdmapnum[0] = '\0';

        if (M_StringCompare(parm, "first"))
        {
            if (gamemode == commercial)
            {
                if (gamemap != 1)
                {
                    mapcmdepisode = gameepisode;
                    mapcmdmap = 1;
                    M_StringCopy(mapcmdlump, "MAP01", sizeof(mapcmdlump));
                    result = true;
                }
            }
            else
            {
                if (!(gameepisode == 1 && gamemap == 1))
                {
                    mapcmdepisode = 1;
                    mapcmdmap = 1;
                    M_StringCopy(mapcmdlump, "E1M1", sizeof(mapcmdlump));
                    result = true;
                }
            }
        }
        else if ((M_StringCompare(parm, "previous") || M_StringCompare(parm, "prev")) && gamestate != GS_TITLESCREEN)
        {
            if (gamemode == commercial)
            {
                if (gamemap != 1)
                {
                    mapcmdepisode = gameepisode;
                    mapcmdmap = gamemap - 1;
                    M_snprintf(mapcmdlump, sizeof(mapcmdlump), "MAP%02i", mapcmdmap);
                    result = true;
                }
            }
            else
            {
                if (gamemap == 1)
                {
                    if (gameepisode != 1)
                    {
                        mapcmdepisode = gameepisode - 1;
                        mapcmdmap = 8;
                        result = true;
                    }
                }
                else
                {
                    mapcmdepisode = gameepisode;
                    mapcmdmap = gamemap - 1;
                    result = true;
                }

                M_snprintf(mapcmdlump, sizeof(mapcmdlump), "E%iM%i", mapcmdepisode, mapcmdmap);
            }
        }
        else if (M_StringCompare(parm, "next") && gamestate != GS_TITLESCREEN)
        {
            if (gamemode == commercial)
            {
                if (gamemap != (gamemission == pack_nerve ? 8 : (gamemission == pack_masterlevels ? 20 : 30)))
                {
                    mapcmdepisode = gameepisode;
                    mapcmdmap = gamemap + 1;
                    M_snprintf(mapcmdlump, sizeof(mapcmdlump), "MAP%02i", mapcmdmap);
                    result = true;
                }
            }
            else
            {
                if (gamemap == 8)
                {
                    if (gameepisode != (gamemode == retail ? (sigil ? (sigil2 ? 6 : 5) : 4) : (gamemode == shareware || chex ? 1 : 3)))
                    {
                        mapcmdepisode = gameepisode + 1;
                        mapcmdmap = 1;
                        result = true;
                    }
                }
                else
                {
                    mapcmdepisode = gameepisode;
                    mapcmdmap = gamemap + 1;
                    result = true;
                }

                M_snprintf(mapcmdlump, sizeof(mapcmdlump), "E%iM%i", mapcmdepisode, mapcmdmap);
            }
        }
        else if (M_StringCompare(parm, "last"))
        {
            if (gamemode == commercial)
            {
                if (gamemission == pack_nerve)
                {
                    if (gamemap != 8)
                    {
                        mapcmdepisode = gameepisode;
                        mapcmdmap = 8;
                        M_StringCopy(mapcmdlump, "MAP08", sizeof(mapcmdlump));
                        result = true;
                    }
                }
                else if (gamemission == pack_masterlevels)
                {
                    if (gamemap != 20)
                    {
                        mapcmdepisode = gameepisode;
                        mapcmdmap = 20;
                        M_StringCopy(mapcmdlump, "MAP20", sizeof(mapcmdlump));
                        result = true;
                    }
                }
                else
                {
                    if (gamemap != 30)
                    {
                        mapcmdepisode = gameepisode;
                        mapcmdmap = 30;
                        M_StringCopy(mapcmdlump, "MAP30", sizeof(mapcmdlump));
                        result = true;
                    }
                }
            }
            else if (gamemode == shareware || chex)
            {
                if (!(gameepisode == 1 && gamemap == 8))
                {
                    mapcmdepisode = 1;
                    mapcmdmap = 8;
                    M_StringCopy(mapcmdlump, "E1M8", sizeof(mapcmdlump));
                    result = true;
                }
            }
            else if (gamemode == retail)
            {
                if (sigil2)
                {
                    if (!(gameepisode == 6 && gamemap == 8))
                    {
                        mapcmdepisode = 6;
                        mapcmdmap = 8;
                        M_StringCopy(mapcmdlump, "E6M8", sizeof(mapcmdlump));
                        result = true;
                    }
                }
                else if (sigil)
                {
                    if (!(gameepisode == 5 && gamemap == 8))
                    {
                        mapcmdepisode = 5;
                        mapcmdmap = 8;
                        M_StringCopy(mapcmdlump, "E5M8", sizeof(mapcmdlump));
                        result = true;
                    }
                }
                else if (!(gameepisode == 4 && gamemap == 8))
                {
                    mapcmdepisode = 4;
                    mapcmdmap = 8;
                    M_StringCopy(mapcmdlump, "E4M8", sizeof(mapcmdlump));
                    result = true;
                }
            }
            else
            {
                if (!(gameepisode == 3 && gamemap == 8))
                {
                    mapcmdepisode = 3;
                    mapcmdmap = 8;
                    M_StringCopy(mapcmdlump, "E3M8", sizeof(mapcmdlump));
                    result = true;
                }
            }
        }
        else if (M_StringCompare(parm, "random"))
        {
            if (gamemode == commercial)
            {
                mapcmdepisode = gameepisode;
                mapcmdmap = M_BigRandomIntNoRepeat(1,
                    (gamemission == pack_nerve ? 8 : (gamemission == pack_masterlevels ? 20 : 30)), gamemap);
                M_snprintf(mapcmdlump, sizeof(mapcmdlump), "MAP%02i", mapcmdmap);
                result = true;
            }
            else
            {
                mapcmdepisode = (gamemode == shareware || chex ? 1 :
                    M_BigRandomIntNoRepeat(1, (gamemode == retail ? (sigil ? (sigil2 ? 6 : 5) : 4) : 3), gameepisode));
                mapcmdmap = M_BigRandomIntNoRepeat(1, (chex ? 5 : 8), gamemap);

                if (mapcmdepisode == 1 && mapcmdmap == 4 && (M_BigRandom() & 1) && gamemode != shareware && !E1M4)
                    M_StringCopy(mapcmdlump, "E1M4B", sizeof(mapcmdlump));
                else if (mapcmdepisode == 1 && mapcmdmap == 8 && (M_BigRandom() & 1) && gamemode != shareware && !E1M8)
                    M_StringCopy(mapcmdlump, "E1M8B", sizeof(mapcmdlump));
                else
                    M_snprintf(mapcmdlump, sizeof(mapcmdlump), "E%iM%i", mapcmdepisode, mapcmdmap);

                result = true;
            }
        }
        else if (M_StringCompare(parm, "E1M4B") || M_StringCompare(parm, "phobosmissioncontrol"))
        {
            mapcmdepisode = 1;
            mapcmdmap = 4;
            M_StringCopy(speciallumpname, "E1M4B", sizeof(speciallumpname));
            M_StringCopy(mapcmdlump, "E1M4B", sizeof(mapcmdlump));
            result = (gamemission == doom && gamemode != shareware && !E1M4);
        }
        else if (M_StringCompare(parm, "E1M8B") || M_StringCompare(parm, "techgonebad"))
        {
            mapcmdepisode = 1;
            mapcmdmap = 8;
            M_StringCopy(speciallumpname, "E1M8B", sizeof(speciallumpname));
            M_StringCopy(mapcmdlump, "E1M8B", sizeof(mapcmdlump));
            result = (gamemission == doom && gamemode != shareware && !E1M8);
        }
        else
        {
            M_StringCopy(mapcmdlump, parm, sizeof(mapcmdlump));

            if (gamemode == commercial)
            {
                mapcmdepisode = 1;

                if (legacyofrust && sscanf(parm, "E%dM%d", &mapcmdepisode, &mapcmdmap) == 2)
                {
                    char    lump[6];

                    if (mapcmdepisode <= 2 && !mapcmdmap)
                        mapcmdmap = 14 + mapcmdepisode;
                    else if (mapcmdepisode <= 2 && mapcmdmap <= 7)
                        mapcmdmap = (mapcmdepisode - 1) * 7 + mapcmdmap;
                    else
                        mapcmdmap = 0;

                    M_snprintf(lump, sizeof(lump), "MAP%02d", mapcmdmap);
                    result = (W_CheckNumForName(lump) >= 0);
                }
                else if (sscanf(parm, "MAP%d", &mapcmdmap) == 1)
                {
                    if (!((BTSX && W_GetNumLumps(parm) == 1)
                        || (gamemission == pack_nerve && mapcmdmap > 9)
                        || (gamemission == pack_masterlevels && mapcmdmap > 21)))
                    {
                        if (gamestate != GS_LEVEL
                            && (gamemission == pack_nerve || gamemission == pack_masterlevels))
                        {
                            gamemission = doom2;
                            expansion = 1;
                        }

                        result = (W_CheckNumForName(parm) >= 0);
                    }
                }
                else if (BTSX)
                {
                    if (sscanf(parm, "MAP%02dC", &mapcmdmap) == 1)
                        result = (W_CheckNumForName(parm) >= 0);
                    else
                    {
                        if ((sscanf(parm, "E%dM%d", &mapcmdepisode, &mapcmdmap) == 2)
                            && ((mapcmdepisode == 1 && BTSXE1)
                                || (mapcmdepisode == 2 && BTSXE2)
                                || (mapcmdepisode == 3 && BTSXE3)))
                        {
                            char    lump[6];

                            M_snprintf(lump, sizeof(lump), "MAP%d", mapcmdmap);
                            result = (W_GetNumLumps(lump) == 2);
                        }
                    }
                }
            }
            else if (sscanf(parm, "E%dM%d", &mapcmdepisode, &mapcmdmap) == 2)
                result = (chex && mapcmdepisode > 1 ? false : (W_CheckNumForName(parm) >= 0));
        }

        if (!result)
        {
            for (int i = 0; i < numlumps; i++)
            {
                char    wadname[MAX_PATH];
                bool    replaced;
                bool    pwad;
                char    mapinfoname[128];
                char    *temp2 = uppercase(lumpinfo[i]->name);

                M_StringCopy(mapcmdlump, temp2, sizeof(mapcmdlump));
                free(temp2);

                if (gamemode == commercial)
                {
                    mapcmdepisode = 1;

                    if (sscanf(mapcmdlump, "MAP%d", &mapcmdmap) != 1)
                        continue;
                }
                else
                {
                    if (sscanf(mapcmdlump, "E%dM%dB", &mapcmdepisode, &mapcmdmap) == 2 && gamemode != shareware)
                        M_StringCopy(speciallumpname, mapcmdlump, sizeof(speciallumpname));
                    else if (sscanf(mapcmdlump, "E%dM%d", &mapcmdepisode, &mapcmdmap) != 2)
                        continue;
                }

                M_StringCopy(wadname, leafname(lumpinfo[i]->wadfile->path), sizeof(wadname));
                replaced = (W_GetNumLumps(mapcmdlump) > 1 && !chex && !FREEDOOM);
                pwad = (lumpinfo[i]->wadfile->type == PWAD);
                M_StringCopy(mapinfoname, P_GetMapName(mapcmdepisode, mapcmdmap), sizeof(mapinfoname));

                switch (gamemission)
                {
                    case doom:
                        if (!replaced || pwad)
                        {
                            if (mapcmdmap > 9)
                                continue;

                            temp2 = removenonalpha(*mapinfoname ? mapinfoname : *mapnames[(mapcmdepisode - 1) * 9 + mapcmdmap - 1]);

                            if (M_StringCompare(parm, temp2))
                                result = true;

                            free(temp2);
                        }

                        break;

                    case doom2:
                        if ((!D_IsNERVEWAD(wadname) && ((!replaced || pwad || nerve) && (pwad || !BTSX))) || hacx)
                        {
                            if (BTSX)
                            {
                                if (!D_IsDOOM2IWAD(wadname))
                                {
                                    temp2 = removenonalpha(*mapnames2[mapcmdmap - 1]);

                                    if (M_StringCompare(parm, temp2))
                                        result = true;

                                    free(temp2);
                                }
                            }
                            else
                            {
                                temp2 = removenonalpha(*mapinfoname ? mapinfoname :
                                    (bfgedition ? *mapnames2_bfg[mapcmdmap - 1] : *mapnames2[mapcmdmap - 1]));

                                if (M_StringCompare(parm, temp2))
                                    result = true;

                                free(temp2);
                            }
                        }

                        break;

                    case pack_nerve:
                        if (D_IsNERVEWAD(wadname))
                        {
                            temp2 = removenonalpha(*mapnamesn[mapcmdmap - 1]);

                            if (M_StringCompare(parm, temp2))
                                result = true;

                            free(temp2);
                        }

                        break;

                    case pack_masterlevels:
                        if (D_IsMasterLevelsWAD(wadname))
                        {
                            temp2 = removenonalpha(*mapinfoname ? mapinfoname : *mapnamesm[mapcmdmap - 1]);

                            if (M_StringCompare(parm, temp2))
                                result = true;

                            free(temp2);
                        }

                        break;

                    case pack_plut:
                        if (!replaced || pwad)
                        {
                            temp2 = removenonalpha(*mapnamesp[mapcmdmap - 1]);

                            if (M_StringCompare(parm, temp2))
                                result = true;

                            free(temp2);
                        }

                        break;

                    case pack_tnt:
                        if (!replaced || pwad)
                        {
                            temp2 = removenonalpha(*mapnamest[mapcmdmap - 1]);

                            if (M_StringCompare(parm, temp2))
                                result = true;

                            free(temp2);
                        }

                        break;

                    default:
                        break;
                }

                if (result)
                    break;
            }
        }

        C_SetMapCmdMapNum();

        free(temp1);
        free(parm);

        return result;
    }
}

static void mapfunc2(char *cmd, char *parms)
{
    char    buffer[1024];

    if (!*parms)
    {
        C_ShowDescription(C_GetIndex(cmd));
        C_Output(BOLD("%s") " %s", cmd, (gamemission == doom ? MAPFORMAT1 : MAPFORMAT2));
        return;
    }

    if ((samelevel = (gameepisode == mapcmdepisode && gamemap == mapcmdmap && !*speciallumpname)))
        M_snprintf(buffer, sizeof(buffer), s_STSTR_CLEVSAME, mapcmdmapnum);
    else
        M_snprintf(buffer, sizeof(buffer), s_STSTR_CLEV, C_GetPlayerName(), mapcmdmapnum);

    C_Output("%s", buffer);
    HU_SetPlayerMessage(buffer, false, false);

    if (legacyofrust)
        mapcmdepisode = 1;

    if (P_IsSecret(mapcmdepisode, mapcmdmap))
        message_secret = true;
    else if (gamemode == commercial)
    {
        if (mapcmdmap == 31 || mapcmdmap == 32
            || (mapcmdmap == 33 && bfgedition)
            || (gamemission == pack_nerve && mapcmdmap == 9)
            || (gamemission == pack_masterlevels && mapcmdmap == 21))
            message_secret = true;
    }
    else if (mapcmdmap == 9)
        message_secret = true;

    gameepisode = mapcmdepisode;

    if (gamemission == doom)
    {
        episode = gameepisode;
        EpiDef.laston = episode - 1;
    }

    gamemap = mapcmdmap;
    quicksaveslot = -1;

    if (r_diskicon)
    {
        drawdisk = true;
        drawdisktics = DRAWDISKTICS;
    }

    if (gamestate == GS_LEVEL)
    {
        idclevtics = TICRATE;
        C_HideConsole();
    }
    else
    {
        G_DeferredInitNew(skilllevel - 1, gameepisode, gamemap);
        C_HideConsoleFast();
    }

    viewplayer->cheated++;
    stat_cheatsentered = SafeAdd(stat_cheatsentered, 1);
    M_SaveCVARs();
}

#define AA      "Andre Arsenault"
#define AD      "Andrew Dowswell"
#define AI      "Arya Iwakura"
#define AM      "American McGee"
#define BK      "Brian Kidby"
#define CB      "Christopher Buteau"
#define CK      "Christen Klie"
#define DB      "David Blanshine"
#define DC      "Dario Casali"
#define DC2     "David Calvin"
#define DJ      "Dean Johnson"
#define DO      "Drake O'Brien"
#define JA      "John Anderson"
#define JD      "Jim Dethlefsen"
#define JF      "Jim Flynn"
#define JL      "Jim Lowell"
#define JM      "Jim Mentzer"
#define JM2     "John Minadeo"
#define JR      "John Romero"
#define JS      "Jimmy Sieben"
#define JW      "John Wakelin"
#define MB      "Michael Bukowski"
#define MC      "Milo Casali"
#define MS      "Mark Snell"
#define PT      "Paul Turnbull"
#define RM      "Russell Meakim"
#define RP      "Robin Patenall"
#define SG      "Shawn Green"
#define SK      "Sverre Kvernmo"
#define SP      "Sandy Petersen"
#define TH      "Tom Hall"
#define TH2     "Ty Halderman"
#define TM      "Tom Mustaine"
#define TW      "Tim Willits"
#define WW      "William D Whitaker"
#define AMSP    AM ", " SP
#define BKTH2   BK ", " TH2
#define DC2DB   DC2 ", " DB
#define DCMC    DC ", " MC
#define DCTH2   DC ", " TH2
#define JRTH    JR ", " TH
#define JSTH2   JS ", " TH2
#define MSJL    MS ", " JL
#define RPJM2   RP ", " JM2
#define SPTH    SP ", " TH

char *authors[][7] =
{
    /* xy      doom   doom2 tnt    plut  nerve masterlevels */
    /* 00 */ { "",    "",   "",    DCMC, "",   "" },
    /* 01 */ { "",    SP,   TM,    DCMC, RM,   TW },
    /* 02 */ { "",    AM,   JW,    DCMC, AI,   TW },
    /* 03 */ { "",    AM,   RPJM2, DCMC, RM,   CK },
    /* 04 */ { "",    AM,   TH2,   DCMC, RM,   CK },
    /* 05 */ { "",    AM,   JD,    DCMC, AI,   CK },
    /* 06 */ { "",    AM,   JSTH2, DCMC, AI,   CK },
    /* 07 */ { "",    AMSP, AD,    DCMC, AI,   TM },
    /* 08 */ { "",    SP,   JM2,   DCMC, AI,   CK },
    /* 09 */ { "",    SP,   JSTH2, DCMC, RM,   CK },
    /* 10 */ { "",    SPTH, TM,    DCMC, "",   SK },
    /* 11 */ { JR,    JR,   DJ,    DCMC, "",   JA },
    /* 12 */ { JR,    SP,   JL,    DCMC, "",   JA },
    /* 13 */ { JR,    SP,   BKTH2, DCMC, "",   JA },
    /* 14 */ { JRTH,  AM,   RP,    DCMC, "",   JA },
    /* 15 */ { JR,    JR,   WW,    DCMC, "",   JA },
    /* 16 */ { JR,    SP,   AA,    DCMC, "",   JF },
    /* 17 */ { JR,    JR,   TM,    DCMC, "",   JF },
    /* 18 */ { SPTH,  SP,   DCTH2, DCMC, "",   SK },
    /* 19 */ { JR,    SP,   TH2,   DCMC, "",   SK },
    /* 20 */ { DC2DB, JR,   DO,    DCMC, "",   SK },
    /* 21 */ { SPTH,  SP,   DO,    DCMC, "",   SK },
    /* 22 */ { SPTH,  AM,   CB,    DCMC, "",   "" },
    /* 23 */ { SPTH,  SP,   PT,    DCMC, "",   "" },
    /* 24 */ { SPTH,  SP,   DJ,    DCMC, "",   "" },
    /* 25 */ { SP,    SG,   JM,    DCMC, "",   "" },
    /* 26 */ { SP,    JR,   MSJL,  DCMC, "",   "" },
    /* 27 */ { SPTH,  SP,   DO,    DCMC, "",   "" },
    /* 28 */ { SP,    SP,   MC,    DCMC, "",   "" },
    /* 29 */ { SP,    JR,   JS,    DCMC, "",   "" },
    /* 30 */ { "",    SP,   JS,    DCMC, "",   "" },
    /* 31 */ { SP,    SP,   DC,    DCMC, "",   "" },
    /* 32 */ { SP,    SP,   DC,    DCMC, "",   "" },
    /* 33 */ { SPTH,  MB,   "",    "",   "",   "" },
    /* 34 */ { SP,    "",   "",    "",   "",   "" },
    /* 35 */ { SP,    "",   "",    "",   "",   "" },
    /* 36 */ { SP,    "",   "",    "",   "",   "" },
    /* 37 */ { SPTH,  "",   "",    "",   "",   "" },
    /* 38 */ { SP,    "",   "",    "",   "",   "" },
    /* 39 */ { SP,    "",   "",    "",   "",   "" },
    /* 40 */ { "",    "",   "",    "",   "",   "" },
    /* 41 */ { AM,    "",   "",    "",   "",   "" },
    /* 42 */ { JR,    "",   "",    "",   "",   "" },
    /* 43 */ { SG,    "",   "",    "",   "",   "" },
    /* 44 */ { AM,    "",   "",    "",   "",   "" },
    /* 45 */ { TW,    "",   "",    "",   "",   "" },
    /* 46 */ { JR,    "",   "",    "",   "",   "" },
    /* 47 */ { JA,    "",   "",    "",   "",   "" },
    /* 48 */ { SG,    "",   "",    "",   "",   "" },
    /* 49 */ { TW,    "",   "",    "",   "",   "" },
    /* 50 */ { "",    "",   "",    "",   "",   "" },
    /* 51 */ { JR,    "",   "",    "",   "",   "" },
    /* 52 */ { JR,    "",   "",    "",   "",   "" },
    /* 53 */ { JR,    "",   "",    "",   "",   "" },
    /* 54 */ { JR,    "",   "",    "",   "",   "" },
    /* 55 */ { JR,    "",   "",    "",   "",   "" },
    /* 56 */ { JR,    "",   "",    "",   "",   "" },
    /* 57 */ { JR,    "",   "",    "",   "",   "" },
    /* 58 */ { JR,    "",   "",    "",   "",   "" },
    /* 59 */ { JR,    "",   "",    "",   "",   "" },
    /* 60 */ { "",    "",   "",    "",   "",   "" },
    /* 61 */ { JR,    "",   "",    "",   "",   "" },
    /* 62 */ { JR,    "",   "",    "",   "",   "" },
    /* 63 */ { JR,    "",   "",    "",   "",   "" },
    /* 64 */ { JR,    "",   "",    "",   "",   "" },
    /* 65 */ { JR,    "",   "",    "",   "",   "" },
    /* 66 */ { JR,    "",   "",    "",   "",   "" },
    /* 67 */ { JR,    "",   "",    "",   "",   "" },
    /* 68 */ { JR,    "",   "",    "",   "",   "" },
    /* 69 */ { JR,    "",   "",    "",   "",   "" }
};

//
// maplist CCMD
//
static void RemoveMapNum(char *title)
{
    const char  *pos = strchr(title, ':');

    if (pos)
    {
        int index = (int)(pos - title) + 1;

        memmove(title, title + index, strlen(title) - index + 1);

        if (title[0] == ' ')
            memmove(title, title + 1, strlen(title));
    }
}

static const char *GetAuthor(int ep, int map, bool replaced)
{
    const char  *author = P_GetMapAuthor(ep, map);

    if (*author)
        return author;
    else if (gamemission == doom)
    {
        if (!replaced && ep * 10 + map < arrlen(authors) && authors[ep * 10 + map][gamemission])
            return authors[ep * 10 + map][gamemission];
        else if (REKKR)
            return "Matthew Little";
    }
    else if (!replaced && map < arrlen(authors) && authors[map][gamemission])
        return authors[map][gamemission];

    return "\x96";
}

static const char *TruncateMaplistText(char *dest, size_t destsize, const char *src)
{
    M_StringCopy(dest, src, destsize);

    if (strlen(dest) <= 30)
        return dest;

    dest[30] = '\0';

    while (strlen(dest) > 0 && dest[strlen(dest) - 1] == ' ')
        dest[strlen(dest) - 1] = '\0';

    M_StringCopy(dest + strlen(dest), "...", destsize - strlen(dest));
    return dest;
}

static void maplistfunc2(char *cmd, char *parms)
{
    const int   tabs[MAXTABS] = { 40, 93, 296, 496 };
    int         count = 0;
    char        (*maps)[256] = I_Malloc(numlumps * sizeof(*maps));

    C_Header(tabs, maplist, MAPLISTHEADER);

    // search through lumps for maps
    for (int i = numlumps - 1; i >= 0; i--)
    {
        int     ep = 1;
        int     map = 1;
        char    lump[9];
        char    wadname[MAX_PATH];
        bool    replaced;
        bool    pwad;
        char    mapinfoname[128];
        char    author[128];
        char    truncatedtitle[128];
        char    truncatedauthor[128];
        char    *temp1 = uppercase(lumpinfo[i]->name);

        M_StringCopy(lump, temp1, sizeof(lump));
        free(temp1);

        if (gamemode == commercial)
        {
            char    tail[2];

            if (sscanf(lump, "MAP%d%1s", &map, tail) != 1)
                continue;
        }
        else
        {
            if ((M_StringCompare(lump, "E1M4B") && (gamemode == shareware || E1M4))
                || (M_StringCompare(lump, "E1M8B") && (gamemode == shareware || E1M8)))
                continue;
            else if (sscanf(lump, "E%dM%d", &ep, &map) != 2)
                continue;
        }

        M_StringCopy(wadname, leafname(lumpinfo[i]->wadfile->path), sizeof(wadname));
        replaced = (W_GetNumLumps(lump) > 1 && !chex && !FREEDOOM && !nerve);
        pwad = (lumpinfo[i]->wadfile->type == PWAD);
        M_StringCopy(mapinfoname, P_GetMapName(ep, map), sizeof(mapinfoname));
        M_StringCopy(author, GetAuthor(ep, map, replaced), sizeof(author));
        TruncateMaplistText(truncatedauthor, sizeof(truncatedauthor), author);

        if (!*author)
            M_StringCopy(author, "\x96", sizeof(author));

        switch (gamemission)
        {
            case doom:
                if (!replaced || pwad)
                {
                    if (replaced && dehcount == 1 && !*mapinfoname)
                        M_snprintf(maps[count++], sizeof(maps[0]), MONOSPACED("%s") "\t\x96\t%s\t%s",
                            lump, truncatedauthor, wadname);
                    else
                    {
                        if (M_StringCompare(lump, "E1M4B"))
                        {
                            temp1 = titlecase(s_HUSTR_E1M4B);
                            M_StringCopy(author, s_AUTHOR_ROMERO, sizeof(author));
                        }
                        else if (M_StringCompare(lump, "E1M8B"))
                        {
                            temp1 = titlecase(s_HUSTR_E1M8B);
                            M_StringCopy(author, s_AUTHOR_ROMERO, sizeof(author));
                        }
                        else
                            temp1 = titlecase(*mapinfoname ? mapinfoname : *mapnames[(ep - 1) * 9 + map - 1]);

                        RemoveMapNum(temp1);
                        M_snprintf(maps[count++], sizeof(maps[0]), MONOSPACED("%s") "\t" ITALICS("%.30s%s") "\t%.30s%s\t%s",
                            lump, temp1, (strlen(temp1) > 30 ? "..." : ""), author, (strlen(author) > 30 ? "..." : ""), wadname);
                        free(temp1);
                    }
                }

                break;

            case doom2:
                if ((!D_IsNERVEWAD(wadname) && (!replaced || pwad || nerve || masterlevels)) || hacx || harmony)
                {
                    if (BTSX)
                    {
                        if (!D_IsDOOM2IWAD(wadname))
                        {
                            char    *temp2;

                            temp1 = M_SubString(*mapnames2[map - 1], 0, 5);
                            temp2 = titlecase(*mapnames2[map - 1]);
                            RemoveMapNum(temp2);
                            TruncateMaplistText(truncatedtitle, sizeof(truncatedtitle), temp2);
                            TruncateMaplistText(truncatedauthor, sizeof(truncatedauthor), author);
                            M_snprintf(maps[count++], sizeof(maps[0]), MONOSPACED("%s") "\t" ITALICS("%s") "\t%s\t%s",
                                temp1, truncatedtitle, truncatedauthor, wadname);
                            free(temp1);
                            free(temp2);
                        }
                    }
                    else
                    {
                        if (legacyofrust && D_IsLegacyOfRustWAD(wadname))
                        {
                            if (map <= 7)
                                M_snprintf(lump, sizeof(lump), "E1M%i", map);
                            else if (map == 15)
                                M_StringCopy(lump, "E1M0", sizeof(lump));
                            else if (map == 16)
                                M_StringCopy(lump, "E2M0", sizeof(lump));
                            else if (map != 99)
                                M_snprintf(lump, sizeof(lump), "E2M%i", map - 7);
                        }

                        if (replaced && dehcount == 1 && !nerve && !masterlevels && !*mapinfoname)
                            M_snprintf(maps[count++], sizeof(maps[0]), MONOSPACED("%s") "\t\x96\t%s\t%s",
                                lump, truncatedauthor, wadname);
                        else
                        {
                            temp1 = titlecase(*mapinfoname ? mapinfoname : (bfgedition ? *mapnames2_bfg[map - 1] : *mapnames2[map - 1]));
                            RemoveMapNum(temp1);
                            TruncateMaplistText(truncatedtitle, sizeof(truncatedtitle), temp1);
                            TruncateMaplistText(truncatedauthor, sizeof(truncatedauthor), author);
                            M_snprintf(maps[count++], sizeof(maps[0]), MONOSPACED("%s") "\t" ITALICS("%s") "\t%s\t%s",
                                lump, truncatedtitle, truncatedauthor, wadname);
                            free(temp1);
                        }
                    }
                }

                break;

            case pack_nerve:
                if (D_IsNERVEWAD(wadname))
                {
                    temp1 = titlecase(*mapinfoname ? mapinfoname : *mapnamesn[map - 1]);
                    RemoveMapNum(temp1);
                    TruncateMaplistText(truncatedtitle, sizeof(truncatedtitle), temp1);
                    TruncateMaplistText(truncatedauthor, sizeof(truncatedauthor), author);
                    M_snprintf(maps[count++], sizeof(maps[0]), MONOSPACED("%s") "\t" ITALICS("%s") "\t%s\t%s",
                        lump, truncatedtitle, truncatedauthor, wadname);
                    free(temp1);
                }

                break;

            case pack_masterlevels:
                if (D_IsMasterLevelsWAD(wadname))
                {
                    temp1 = titlecase(*mapinfoname ? mapinfoname : *mapnamesm[map - 1]);
                    RemoveMapNum(temp1);
                    TruncateMaplistText(truncatedtitle, sizeof(truncatedtitle), temp1);
                    TruncateMaplistText(truncatedauthor, sizeof(truncatedauthor), author);
                    M_snprintf(maps[count++], sizeof(maps[0]), MONOSPACED("%s") "\t" ITALICS("%s") "\t%s\t%s",
                        lump, truncatedtitle, truncatedauthor, wadname);
                    free(temp1);
                }

                break;

            case pack_plut:
                if (!replaced || pwad)
                {
                    if (replaced && dehcount == 1 && !*mapinfoname)
                        M_snprintf(maps[count++], sizeof(maps[0]), MONOSPACED("%s") "\t\x96\t%s\t%s",
                            lump, truncatedauthor, wadname);
                    else
                    {
                        temp1 = titlecase(*mapinfoname ? mapinfoname : *mapnamesp[map - 1]);
                        RemoveMapNum(temp1);
                        TruncateMaplistText(truncatedtitle, sizeof(truncatedtitle), temp1);
                        TruncateMaplistText(truncatedauthor, sizeof(truncatedauthor), author);
                        M_snprintf(maps[count++], sizeof(maps[0]), MONOSPACED("%s") "\t" ITALICS("%s") "\t%s\t%s",
                            lump, truncatedtitle, truncatedauthor, wadname);
                        free(temp1);
                    }
                }

                break;

            case pack_tnt:
                if (!replaced || pwad)
                {
                    if (replaced && dehcount == 1 && !*mapinfoname)
                        M_snprintf(maps[count++], sizeof(maps[0]), MONOSPACED("%s") "\t\x96\t%s\t%s",
                            lump, truncatedauthor, wadname);
                    else
                    {
                        temp1 = titlecase(*mapinfoname ? mapinfoname : *mapnamest[map - 1]);
                        RemoveMapNum(temp1);
                        TruncateMaplistText(truncatedtitle, sizeof(truncatedtitle), temp1);
                        TruncateMaplistText(truncatedauthor, sizeof(truncatedauthor), author);
                        M_snprintf(maps[count++], sizeof(maps[0]), MONOSPACED("%s") "\t" ITALICS("%s") "\t%s\t%s",
                            lump, truncatedtitle, truncatedauthor, wadname);
                        free(temp1);
                    }
                }

                break;

            default:
                break;
        }
    }

    // sort the map list
    for (int i = 0; i < count; i++)
        for (int j = i + 1; j < count; j++)
            if (strcmp(maps[i], maps[j]) > 0)
            {
                char    temp[256];

                M_StringCopy(temp, maps[i], sizeof(temp));
                M_StringCopy(maps[i], maps[j], sizeof(maps[i]));
                M_StringCopy(maps[j], temp, sizeof(maps[j]));
            }

    // display the map list
    for (int i = 0; i < count; i++)
        C_TabbedOutput(tabs, MONOSPACED("%3i") ".\t%s", i + 1, maps[i]);

    free(maps);
}

//
// mapstats CCMD
//
static void OutputReleaseDate(const int tabs[MAXTABS], char *wadname)
{
    if (M_StringCompare(wadname, "DOOM1.WAD"))
        C_TabbedOutput(tabs, INDENT "Release date\tDecember 10, 1993");
    else if (D_IsDOOM1IWAD(wadname))
    {
        if (kex)
            C_TabbedOutput(tabs, INDENT "Release date\tAugust 8, 2024");
        else if (unity)
            C_TabbedOutput(tabs, INDENT "Release date\tJuly 26, 2019");
        else if (bfgedition)
            C_TabbedOutput(tabs, INDENT "Release date\tOctober 16, 2012");
        else if (gamemode == retail)
            C_TabbedOutput(tabs, INDENT "Release date\tApril 30, 1995");
        else
            C_TabbedOutput(tabs, INDENT "Release date\tDecember 10, 1993");
    }
    else if (M_StringCompare(wadname, "SIGIL_v1_0.wad")
        || M_StringCompare(wadname, "SIGIL.wad"))
        C_TabbedOutput(tabs, INDENT "Release date\tMay 1, 2019");
    else if (M_StringCompare(wadname, "SIGIL_v1_1.wad"))
        C_TabbedOutput(tabs, INDENT "Release date\tMay 31, 2019");
    else if (M_StringCompare(wadname, "SIGIL_v1_21.wad")
        || M_StringCompare(wadname, "SIGIL_v1_2.wad"))
        C_TabbedOutput(tabs, INDENT "Release date\tSeptember 10, 2019");
    else if (M_StringCompare(wadname, "SIGIL_V1_23.wad")
        || M_StringCompare(wadname, "SIGIL_V1_23_REG.wad"))
        C_TabbedOutput(tabs, INDENT "Release date\tAugust 3, 2025");
    else if (M_StringCompare(wadname, "SIGIL_II_V1_0.WAD")
        || M_StringCompare(wadname, "SIGIL_II_MP3_V1_0.WAD")
        || M_StringCompare(wadname, "SIGIL2.WAD"))
        C_TabbedOutput(tabs, INDENT "Release date\tDecember 10, 2023");
    else if (D_IsDOOM2IWAD(wadname))
    {
        if (kex)
            C_TabbedOutput(tabs, INDENT "Release date\tAugust 8, 2024");
        else if (unity)
            C_TabbedOutput(tabs, INDENT "Release date\tJuly 26, 2019");
        else if (bfgedition)
            C_TabbedOutput(tabs, INDENT "Release date\tOctober 16, 2012");
        else
            C_TabbedOutput(tabs, INDENT "Release date\tSeptember 30, 1994");
    }
    else if (M_StringCompare(wadname, "NERVE.WAD"))
        C_TabbedOutput(tabs, INDENT "Release date\tMay 26, 2010");
    else if (M_StringCompare(wadname, "masterlevels.wad"))
    {
        if (kex)
            C_TabbedOutput(tabs, INDENT "Release date\tAugust 8, 2024");
        else
            C_TabbedOutput(tabs, INDENT "Release date\tOctober 3, 2024");
    }
    else if (M_StringCompare(wadname, "PLUTONIA.WAD") || M_StringCompare(wadname, "TNT.WAD"))
        C_TabbedOutput(tabs, INDENT "Release date\tJune 17, 1996");
    else if (onehumanity)
        C_TabbedOutput(tabs, INDENT "Release date\tMarch 2, 2022");
    else if (REKKRSL)
        C_TabbedOutput(tabs, INDENT "Release date\tOctober 11, 2021");
    else if (REKKR)
        C_TabbedOutput(tabs, INDENT "Release date\tJuly 10, 2018");
    else if (legacyofrust)
        C_TabbedOutput(tabs, INDENT "Release date\tAugust 8, 2024");
}

static int GetNumMaps(int lumpnum)
{
    int count = 0;

    for (int i = 0; i < numlumps; i++)
    {
        char    lump[9];
        int     ep = -1;
        int     map = -1;

        M_StringCopy(lump, lumpinfo[i]->name, sizeof(lump));

        if (M_StringCompare(lumpinfo[lumpnum]->wadfile->path, lumpinfo[i]->wadfile->path))
        {
            if (gamemode == commercial)
            {
                if (sscanf(lump, "MAP%d", &map) == 1 && map != -1)
                    count++;
            }
            else
            {
                if ((M_StringCompare(lump, "E1M4B") && (gamemode == shareware || E1M4))
                    || (M_StringCompare(lump, "E1M8B") && (gamemode == shareware || E1M8)))
                    count++;
                else if (sscanf(lump, "E%dM%d", &ep, &map) == 2 && ep != -1 && map != -1)
                    count++;
            }
        }
    }

    return count;
}

static void mapstatsfunc2(char *cmd, char *parms)
{
    struct
    {
        const char  *title;
        const char  *artist;
    } tntmusic[] = {
        { "Sadistic",                   "L.A. Sieben"       },
        { "Smells Like Burning Corpse", "L.A. Sieben"       },
        { "Message for the Archvile",   "Bobby Prince"      },
        { "Death's Bells",              "L.A. Sieben"       },
        { "More",                       "Tom Mustaine"      },
        { "Agony Rhapsody",             "L.A. Sieben"       },
        { "Soldier Of Chaos",           "Jonathan El-Bizri" },
        { "Into The Beast's Belly",     "L.A. Sieben"       },
        { "Sadistic",                   "L.A. Sieben"       },
        { "Infinite",                   "Tom Mustaine"      },
        { "Let's Kill At Will",         "Jonathan El-Bizri" },
        { "The Dave D. Taylor Blues",   "Bobby Prince"      },
        { "Death's Bells",              "L.A. Sieben"       },
        { "Cold Subtleness",            "Jonathan El-Bizri" },
        { "Smells Like Burning Corpse", "L.A. Sieben"       },
        { "Blood Jungle",               "Jonathan El-Bizri" },
        { "More",                       "Tom Mustaine"      },
        { "Infinite",                   "Tom Mustaine"      },
        { "Countdown To Death",         "Bobby Prince"      },
        { "Horizon",                    "Tom Mustaine"      },
        { "Into Sandy's City",          "Bobby Prince"      },
        { "AimShootKill",               "Tom Mustaine"      },
        { "Bye Bye American Pie",       "Bobby Prince"      },
        { "Between Levels",             "Bobby Prince"      },
        { "DOOM",                       "Bobby Prince"      },
        { "Blood Jungle",               "Jonathan El-Bizri" },
        { "Into The Beast's Belly",     "L.A. Sieben"       },
        { "AimShootKill",               "Tom Mustaine"      },
        { "Death's Bells",              "L.A. Sieben"       },
        { "Into The Beast's Belly",     "L.A. Sieben"       },
        { "Legion Of The Lost",         "L.A. Sieben"       },
        { "Into Sandy's City",          "Bobby Prince"      }
    };

    struct
    {
        const char  *title;
        const char  *artist;
    } legacyofrustmusic[] = {
        { "Welcome To Die",                    "Xaser Acheron" },
        { "The Shores Of Heaven",              "Xaser Acheron" },
        { "Bilge Punks",                       "Xaser Acheron" },
        { "Callous Regard",                    "Xaser Acheron" },
        { "They're All Going To Laugh At You", "Xaser Acheron" },
        { "Cloudy With A Chance Of Spiders",   "Xaser Acheron" },
        { "Tactical Blasphemy",                "Xaser Acheron" },
        { "Opening To Hell",                   "Bobby Prince"  },
        { "Bonk",                              "Xaser Acheron" },
        { "Cliff Driver",                      "Xaser Acheron" },
        { "Wizard Science",                    "Xaser Acheron" },
        { "Deja Vuvuzela",                     "Xaser Acheron" },
        { "Disgusto!",                         "Xaser Acheron" },
        { "March Of The Vespers",              "Xaser Acheron" },
        { "The Skeleton World",                "Xaser Acheron" },
        { "Hell Force Five",                   "Xaser Acheron" }
    };

    const int   tabs[MAXTABS] = { 137 };
    char        *temp;
    int         lump = -1;
    int         wadtype = IWAD;
    const char  *author = P_GetMapAuthor(gameepisode, gamemap);
    const char  *mapinfolabel = trimwhitespace(P_GetLabel(gameepisode, gamemap));
    char        wadname[MAX_PATH];
    const int   partime = G_GetParTime();
    int         outside = 0;
    int         min_x = INT_MAX;
    int         max_x = INT_MIN;
    int         min_y = INT_MAX;
    int         max_y = INT_MIN;
    int         max_ceilingheight = INT_MIN;
    int         min_floorheight = INT_MAX;
    int         width;
    int         height;
    int         depth;
    const int   maptoepisodeindex = (gamemode == commercial ? gamemap : gameepisode * 10 + gamemap);
    const int   currentepisode = (maptoepisodeindex < maxmaptoepisode ? maptoepisode[maptoepisodeindex] : 0);

    if (gamestate != GS_LEVEL && gamestate != GS_INTERMISSION)
    {
        const int   i = C_GetIndex(cmd);

        C_ShowFormat(i);
        C_ShowDescription(i);

        if (isdefaultplayername())
            C_Warning(0, NOGAMECCMDWARNING1);
        else
            C_Warning(0, NOGAMECCMDWARNING2, playername, pronoun(personal),
                (playergender == playergender_other ? "aren't" : "isn't"));

        return;
    }

    if (BTSX || KDIKDIZD || legacyofrust || *mapinfolabel)
    {
        char    lumpname[9];

        M_snprintf(lumpname, sizeof(lumpname), "MAP%02i", gamemap);

        if ((lump = W_CheckNumForName(lumpname)) >= 0)
            wadtype = lumpinfo[lump]->wadfile->type;
    }

    if (lump == -1)
    {
        lump = (nerve && gamemission == doom2 ? W_GetLastNumForName(mapnum) : W_CheckNumForName(mapnum));

        if (lump == -1)
            return;

        if (D_IsDOOM2IWAD(lumpinfo[lump]->wadfile->path)
            || M_StringEndsWith(lumpinfo[lump]->wadfile->path, "chex.wad")
            || M_StringEndsWith(lumpinfo[lump]->wadfile->path, "rekkrsa.wad"))
            wadtype = IWAD;
        else
            wadtype = lumpinfo[lump]->wadfile->type;
    }

    C_Header(tabs, mapstats, MAPSTATSHEADER);

    if (wadtype == IWAD)
    {
        if (gamemode == commercial)
        {
            if (gamemission == pack_nerve)
                C_TabbedOutput(tabs, "Map\t%i of 9", gamemap);
            else if (gamemission == pack_masterlevels)
                C_TabbedOutput(tabs, "Map\t%i of 21", gamemap);
            else if (legacyofrust)
                C_TabbedOutput(tabs, "Map\t%i of 16", gamemap);
            else
                C_TabbedOutput(tabs, "Map\t%i of %i", gamemap, (bfgedition ? 33 : 32));
        }
        else
            C_TabbedOutput(tabs, "Map\t%i of 9", gamemap);
    }
    else
        C_TabbedOutput(tabs, "Map\t%i of %i", gamemap, GetNumMaps(lump));

    C_TabbedOutput(tabs, "Label\t%s", mapnum);

    if (!M_StringCompare(maptitle, mapnum))
    {
        temp = titlecase(maptitle);
        C_TabbedOutput(tabs, "Title\t" ITALICS("%s"), temp);
        free(temp);

        if (gamemode == commercial)
        {
            if (gamemap == 11)
            {
                if (M_StringCompare(maptitle, s_HUSTR_11))
                    C_TabbedOutput(tabs, INDENT "Alternative\t" ITALICS("%s"), s_HUSTR_11_ALT);
            }
            else if (gamemap == 31)
            {
                if (M_StringCompare(maptitle, s_HUSTR_31))
                    C_TabbedOutput(tabs, INDENT "Alternative\t" ITALICS("%s"), s_HUSTR_31_BFG);
            }
            else if (gamemap == 32)
            {
                if (M_StringCompare(maptitle, s_HUSTR_32))
                    C_TabbedOutput(tabs, INDENT "Alternative\t" ITALICS("%s"), s_HUSTR_32_BFG);
            }
        }
        else if (gameepisode == 3 && gamemap == 7)
        {
            if (M_StringCompare(maptitle, s_HUSTR_E3M7))
                C_TabbedOutput(tabs, INDENT "Alternative\t" ITALICS("%s"), s_HUSTR_E3M7_ALT);
        }
    }

    if (*author)
        C_TabbedOutput(tabs, "Author\t%s", author);
    else if (gamemission == doom)
    {
        if (M_StringCompare(mapnum, "E1M4B") || M_StringCompare(mapnum, "E1M8B"))
            C_TabbedOutput(tabs, "Author\t%s", s_AUTHOR_ROMERO);
        else if (canmodify && gameepisode * 10 + gamemap < arrlen(authors) && authors[gameepisode * 10 + gamemap][gamemission])
            C_TabbedOutput(tabs, "Author\t%s", authors[gameepisode * 10 + gamemap][gamemission]);
        else if (REKKR)
            C_TabbedOutput(tabs, "Author\tMatthew Little");
    }
    else if (canmodify && gamemap < arrlen(authors) && authors[gamemap][gamemission])
        C_TabbedOutput(tabs, "Author\t%s", authors[gamemap][gamemission]);

    if (gamemode == commercial)
    {
        if (gamemission == pack_nerve)
        {
            temp = titlecase(*expansions[1]);
            C_TabbedOutput(tabs, "Expansion\t" ITALICS("%s") " (%s)", temp, (masterlevels ? "2 of 3" : "2 of 2"));
            free(temp);
        }
        else if (gamemission == pack_masterlevels)
        {
            temp = titlecase(*expansions[2]);
            C_TabbedOutput(tabs, "Expansion\t" ITALICS("%s") " (%s)", temp, (nerve ? "3 of 3" : "2 of 2"));
            free(temp);
        }
        else
        {
            if (customepisodes && currentepisode > 0)
            {
                if (**episodes[currentepisode - 1])
                {
                    temp = titlecase(*episodes[currentepisode - 1]);

                    if (currentepisode == 1 && EpiDef.numitems == 1)
                        C_TabbedOutput(tabs, "Episode\t" ITALICS("%s"), temp);
                    else
                        C_TabbedOutput(tabs, "Episode\t" ITALICS("%s") " (%i of %i)",
                            temp, currentepisode, EpiDef.numitems);

                    free(temp);
                }
                else
                    C_TabbedOutput(tabs, "Episode\t%i of %i",
                        currentepisode, EpiDef.numitems);
            }
            else if (nerve)
            {
                temp = titlecase(*expansions[0]);
                C_TabbedOutput(tabs, "Expansion\t" ITALICS("%s") " (1 of 2)", temp);
                free(temp);
            }
        }
    }
    else
    {
        if (customepisodes)
        {
            if (currentepisode > 0 && **episodes[currentepisode - 1])
            {
                temp = titlecase(*episodes[currentepisode - 1]);
                C_TabbedOutput(tabs, "Episode\t" ITALICS("%s") " (%i of %i)",
                    temp, currentepisode, EpiDef.numitems);
                free(temp);
            }
            else
                C_TabbedOutput(tabs, "Episode\t%i of %i",
                    currentepisode, EpiDef.numitems);
        }
        else if (!chex && !hacx)
        {
            temp = titlecase(*episodes[gameepisode - 1]);
            C_TabbedOutput(tabs, "Episode\t" ITALICS("%s") " (%i of %i)",
                temp, gameepisode,
                (gamemode == retail ? (sigil ? (sigil2 ? 6 : 5) : 4) : (gamemode == shareware ? 1 : 3)));
            free(temp);
        }
    }

    if (secretmap)
        C_TabbedOutput(tabs, "Secret\tYes");

    M_StringCopy(wadname, leafname(lumpinfo[lump]->wadfile->path), sizeof(wadname));

    C_TabbedOutput(tabs, "%s\t%s", (wadtype == IWAD ? "IWAD" : "PWAD"), wadname);

    OutputReleaseDate(tabs, wadname);

    if (wadtype == PWAD)
    {
        if (W_GetNumLumps("DEHACKED") > 1)
            C_TabbedOutput(tabs, INDENT "DEHACKED\tYes");

        if (*mapinfolump)
            C_TabbedOutput(tabs, INDENT "%s\tYes", mapinfolump);

        if (PLAYPALs > 2)
            C_TabbedOutput(tabs, INDENT "PLAYPAL\tYes");
    }

    C_TabbedOutput(tabs, INDENT "MD5\t" MONOSPACED("%s"),
        MD5(lumpinfo[lump]->wadfile->path));

    if (wadtype == PWAD)
    {
        M_StringCopy(wadname, leafname(lumpinfo[W_GetLastNumForName("PLAYPAL")]->wadfile->path), sizeof(wadname));

        C_TabbedOutput(tabs, "IWAD\t%s", wadname);

        OutputReleaseDate(tabs, wadname);

        C_TabbedOutput(tabs, INDENT "MD5\t" MONOSPACED("%s"),
            MD5(lumpinfo[W_GetLastNumForName("PLAYPAL")]->wadfile->path));
    }

    C_TabbedOutput(tabs, "Compatibility\t%s",
        (id24compatible ? ITALICS("ID24") :
            (mbf21compatible ? ITALICS("MBF21") :
                (mbfcompatible ? ITALICS("MBF") :
                    (boomcompatible ? ITALICS("BOOM") :
                        (numsegs < 32768 ? "Vanilla" : "Limit removing"))))));

    if (partime)
    {
        int hours = partime / 3600;

        if (hours)
            C_TabbedOutput(tabs, "Par time\t" MONOSPACED("%02i") ":" MONOSPACED("%02i") ":" MONOSPACED("%02i"),
                hours, (partime % 3600) / 60, partime % 60);
        else
            C_TabbedOutput(tabs, "Par time\t" MONOSPACED("%02i") ":" MONOSPACED("%02i"),
                partime / 60, partime % 60);
    }

    temp = commify(numspawnedthings);
    C_TabbedOutput(tabs, "Things\t%s", temp);
    free(temp);

    temp = commify(totalkills);
    C_TabbedOutput(tabs, INDENT "Monsters\t%s", temp);
    free(temp);

    temp = commify(totalpickups);
    C_TabbedOutput(tabs, INDENT "Pickups\t%s", temp);
    free(temp);

    temp = commify(numdecorations);
    C_TabbedOutput(tabs, INDENT "Decorations\t%s", temp);
    free(temp);

    temp = commify(barrelcount);
    C_TabbedOutput(tabs, INDENT "Barrels\t%s", temp);
    free(temp);

    if (player1starts > 1)
    {
        temp = commify(player1starts - 1);
        C_TabbedOutput(tabs, INDENT "Voodoo dolls\t%s", temp);
        free(temp);
    }

    temp = commify(numlines);
    C_TabbedOutput(tabs, "Linedefs\t%s", temp);
    free(temp);

    temp = commify(numspeciallines);
    C_TabbedOutput(tabs, INDENT "Special\t%s", temp);
    free(temp);

    temp = commify(numsides);
    C_TabbedOutput(tabs, "Sides\t%s", temp);
    free(temp);

    temp = commify(numvertexes);
    C_TabbedOutput(tabs, "Vertices\t%s", temp);
    free(temp);

    temp = commify(numsegs);
    C_TabbedOutput(tabs, "Segs\t%s", temp);
    free(temp);

    temp = commify(numsubsectors);
    C_TabbedOutput(tabs, "Subsectors\t%s", temp);
    free(temp);

    temp = commify(numnodes);
    C_TabbedOutput(tabs, "Nodes\t%s", temp);
    free(temp);

    C_TabbedOutput(tabs, INDENT "Format\t%s", nodeformats[nodeformat]);

    if (nodeformat != DOOMBSP)
        C_TabbedOutput(tabs, INDENT "Compressed\t%s",
            (nodeformat == ZNOD || nodeformat == ZGLN || nodeformat == ZGL2 || nodeformat == ZGL3) ? "Yes" : "No");

    temp = commify(numsectors);
    C_TabbedOutput(tabs, "Sectors\t%s", temp);
    free(temp);

    for (int i = 0; i < numsubsectors; i++)
    {
        const short picnum = subsectors[i].sector->ceilingpic;

        if (picnum == skyflatnum || (picnum & PL_SKYFLAT) || (picnum & PL_FLATMAPPING) == PL_FLATMAPPING)
            outside++;
    }

    if (numsubsectors)
    {
        outside = outside * 100 / numsubsectors;
        C_TabbedOutput(tabs, INDENT "Inside/outside\t%i%%/%i%%", 100 - outside, outside);
    }

    temp = commify(totalsecrets);
    C_TabbedOutput(tabs, INDENT "Secret\t%s", temp);
    free(temp);

    temp = commify(numliquid);
    C_TabbedOutput(tabs, INDENT "Liquid\t%s", temp);
    free(temp);

    temp = commify(numdamaging);
    C_TabbedOutput(tabs, INDENT "Damaging\t%s", temp);
    free(temp);

    if (blockmaprebuilt)
        C_TabbedOutput(tabs, "Blockmap\tRebuilt");

    for (int i = 0; i < numvertexes; i++)
    {
        const fixed_t   x = vertexes[i].x;
        const fixed_t   y = vertexes[i].y;

        if (x < min_x)
            min_x = x;
        else if (x > max_x)
            max_x = x;

        if (y < min_y)
            min_y = y;
        else if (y > max_y)
            max_y = y;
    }

    width = ((max_x >> FRACBITS) - (min_x >> FRACBITS)) / UNITSPERFOOT;
    height = ((max_y >> FRACBITS) - (min_y >> FRACBITS)) / UNITSPERFOOT;

    for (int i = 0; i < numsectors; i++)
    {
        if (max_ceilingheight < sectors[i].ceilingheight)
            max_ceilingheight = sectors[i].ceilingheight;

        if (min_floorheight > sectors[i].floorheight)
            min_floorheight = sectors[i].floorheight;
    }

    depth = ((max_ceilingheight >> FRACBITS) - (min_floorheight >> FRACBITS)) / UNITSPERFOOT;

    if (units == units_metric)
    {
        const float metricwidth = width / FEETPERMETER;
        const float metricheight = height / FEETPERMETER;
        const float metricdepth = depth / FEETPERMETER;

        if (metricwidth < METERSPERKILOMETER && metricheight < METERSPERKILOMETER && metricdepth < METERSPERKILOMETER)
        {
            char    *temp1 = striptrailingzero(metricwidth, 1);
            char    *temp2 = striptrailingzero(metricheight, 1);
            char    *temp3 = striptrailingzero(metricdepth, 1);

            C_TabbedOutput(tabs, "Dimensions\t%sx%sx%s %s",
                temp1, temp2, temp3, (english == english_american ? "meters" : "metres"));
            free(temp1);
            free(temp2);
            free(temp3);
        }
        else
        {
            char    *temp1 = striptrailingzero(metricwidth / METERSPERKILOMETER, 2);
            char    *temp2 = striptrailingzero(metricheight / METERSPERKILOMETER, 2);
            char    *temp3 = striptrailingzero(metricdepth / METERSPERKILOMETER, 2);

            C_TabbedOutput(tabs, "Dimensions\t%sx%sx%s %s",
                temp1, temp2, temp3, (english == english_american ? "kilometers" : "kilometres"));
            free(temp1);
            free(temp2);
            free(temp3);
        }
    }
    else
    {
        if (width < FEETPERMILE && height < FEETPERMILE)
        {
            char    *temp1 = commify(width);
            char    *temp2 = commify(height);
            char    *temp3 = commify(depth);

            C_TabbedOutput(tabs, "Dimensions\t%sx%sx%s feet", temp1, temp2, temp3);
            free(temp1);
            free(temp2);
            free(temp3);
        }
        else
        {
            char    *temp1 = striptrailingzero((float)width / FEETPERMILE, 2);
            char    *temp2 = striptrailingzero((float)height / FEETPERMILE, 2);
            char    *temp3 = striptrailingzero((float)depth / FEETPERMILE, 2);

            C_TabbedOutput(tabs, "Dimensions\t%sx%sx%s miles", temp1, temp2, temp3);
            free(temp1);
            free(temp2);
            free(temp3);
        }
    }

    if (mus_playing && !nomusic)
    {
        int                 lumps;
        char                namebuf[9];
        const musicinfo_t   *musicinfo = NULL;
        const musicinfo_t   *music = mus_playing;
        const char          *musicartist = P_GetMapMusicComposer(gameepisode, gamemap);
        const char          *musictitle = P_GetMapMusicTitle(gameepisode, gamemap);
        const Mix_MusicType musictype = Mix_GetMusicType(NULL);

        M_StringCopy(namebuf, lumpinfo[mus_playing->lumpnum]->name, sizeof(namebuf));

        for (int i = 1; i < NUMMUSIC; i++)
        {
            char    lumpname[9];

            if ((*s_music[i].IDKFA && M_StringCompare(namebuf, s_music[i].IDKFA)))
            {
                musicinfo = &s_music[i];
                break;
            }

            M_snprintf(lumpname, sizeof(lumpname), "H_%s", s_music[i].name2);

            if (M_StringCompare(namebuf, lumpname))
            {
                musicinfo = &s_music[i];
                break;
            }

            M_snprintf(lumpname, sizeof(lumpname), "D_%s", s_music[i].name2);

            if (M_StringCompare(namebuf, lumpname))
            {
                musicinfo = &s_music[i];
                break;
            }
        }

        if (musicinfo)
            music = musicinfo;

        C_TabbedOutput(tabs, "Music lump\t%s", namebuf);

        lumps = W_GetNumLumps(namebuf);

        if (*musictitle)
            C_TabbedOutput(tabs, INDENT "Title\t" ITALICS("%s"), musictitle);
        else if (sigil && gameepisode == 5)
            C_TabbedOutput(tabs, INDENT "Title\t" ITALICS("%s"), (buckethead ? music->title2 : music->title1));
        else if (sigil2 && gameepisode == 6)
            C_TabbedOutput(tabs, INDENT "Title\t" ITALICS("%s"), (thorr ? music->title2 : music->title1));
        else if (legacyofrust && gamemap <= 15)
            C_TabbedOutput(tabs, INDENT "Title\t" ITALICS("%s"), legacyofrustmusic[gamemap - 1].title);
        else if (namebuf[0] == 'H' && namebuf[1] == '_' && !M_StringCompare(music->title1, "n/a"))
            C_TabbedOutput(tabs, INDENT "Title\t" ITALICS("%s"), music->title1);
        else if (lumps == 1 && wadtype == IWAD && gamemission == pack_tnt)
            C_TabbedOutput(tabs, INDENT "Title\t" ITALICS("%s"), tntmusic[gamemap - 1].title);
        else if (!M_StringCompare(music->title1, "n/a")
            && (((gamemode == commercial || gameepisode > 1) && lumps == 1 && wadtype == IWAD)
                || (gamemode != commercial && gameepisode == 1 && lumps == 2)
                || gamemode == shareware
                || gamemission == pack_nerve))
            C_TabbedOutput(tabs, INDENT "Title\t" ITALICS("%s"), music->title1);

        if (*musicartist)
            C_TabbedOutput(tabs, INDENT "Artist\t%s", musicartist);
        else if (sigil && gameepisode == 5)
            C_TabbedOutput(tabs, INDENT "Artist\t%s", (buckethead ? "Buckethead" : "James Paddock"));
        else if (sigil2 && gameepisode == 6)
            C_TabbedOutput(tabs, INDENT "Artist\t%s", (thorr ? "Thorr" : "James Paddock"));
        else if (legacyofrust && gamemap <= 15)
            C_TabbedOutput(tabs, INDENT "Artist\t%s", legacyofrustmusic[gamemap - 1].artist);
        else if (namebuf[0] == 'H' && namebuf[1] == '_')
            C_TabbedOutput(tabs, INDENT "Artist\tAndrew Hulshult");
        else if (lumps == 1 && wadtype == IWAD && gamemission == pack_tnt)
            C_TabbedOutput(tabs, INDENT "Artist\t%s", tntmusic[gamemap - 1].artist);
        else if (((gamemode == commercial || gameepisode > 1) && lumps == 1 && wadtype == IWAD)
            || (gamemode != commercial && gameepisode == 1 && lumps == 2)
            || gamemode == shareware
            || gamemission == pack_nerve)
            C_TabbedOutput(tabs, INDENT "Artist\tBobby Prince");

        if (musmusictype)
            C_TabbedOutput(tabs, INDENT "Format\tMUS");
        else if (musictype == MUS_WAV)
            C_TabbedOutput(tabs, INDENT "Format\tWAV");
        else if (musictype == MUS_MOD)
            C_TabbedOutput(tabs, INDENT "Format\tMOD");
        else if (midimusictype || musictype == MUS_MID)
            C_TabbedOutput(tabs, INDENT "Format\tMIDI");
        else if (musictype == MUS_OGG)
            C_TabbedOutput(tabs, INDENT "Format\tOgg Vorbis");
        else if (musictype == MUS_MP3)
            C_TabbedOutput(tabs, INDENT "Format\tMP3");
        else if (musictype == MUS_FLAC)
            C_TabbedOutput(tabs, INDENT "Format\tFLAC");
        else if (musictype == MUS_OPUS)
            C_TabbedOutput(tabs, INDENT "Format\tOpus");

        if (lumpinfo[mus_playing->lumpnum]->wadfile->type == PWAD)
        {
            C_TabbedOutput(tabs, INDENT "PWAD\t%s", leafname(lumpinfo[mus_playing->lumpnum]->wadfile->path));

            if (namebuf[0] == 'H' && namebuf[1] == '_')
                C_TabbedOutput(tabs, INDENT INDENT "Release date\tAugust 8, 2024");

            if (!M_StringCompare(lumpinfo[lump]->wadfile->path, lumpinfo[mus_playing->lumpnum]->wadfile->path))
                C_TabbedOutput(tabs, INDENT INDENT "MD5\t" MONOSPACED("%s"),
                    MD5(lumpinfo[mus_playing->lumpnum]->wadfile->path));
        }

        if (s_randommusic)
            C_TabbedOutput(tabs, INDENT "Randomly chosen\tYes");
    }
}

//
// name CCMD
//
static bool namecmdfriendly;
static bool namecmdanymonster;
static char namecmdnew[128];
static char namecmdold[128];
static int  namecmdtype = NUMMOBJTYPES;

static bool namefunc1(char *cmd, char *parms)
{
    if (!*parms || gamestate != GS_LEVEL)
        return true;

    if (M_StringStartsWith(parms, "player"))
    {
        M_StringCopy(namecmdold, "player", sizeof(namecmdold));
        M_StringReplaceAll(parms, "player", "", false);
        M_StringCopy(namecmdnew, parms, sizeof(namecmdnew));
        return (namecmdnew[0] != '\0' && strlen(namecmdnew) < 33);
    }

    if (gamestate == GS_LEVEL)
    {
        if ((namecmdfriendly = M_StringStartsWith(parms, "friendly")))
            M_StringReplaceAll(parms, "friendly", "", false);
        else if (M_StringStartsWith(parms, "unfriendly"))
            M_StringReplaceAll(parms, "unfriendly", "", false);

        if (M_StringStartsWith(parms, "monster"))
        {
            M_StringCopy(namecmdold, "monster", sizeof(namecmdold));
            M_StringReplaceAll(parms, "monster", "", false);
            M_StringCopy(namecmdnew, trimnonalpha(parms), sizeof(namecmdnew));
            namecmdanymonster = true;

            return (namecmdnew[0] != '\0' && strlen(namecmdnew) < 64);
        }
        else
            namecmdanymonster = false;

        for (int i = 0; i < NUMMOBJTYPES; i++)
            if ((mobjinfo[i].flags & MF_SHOOTABLE) && i != MT_PLAYER && i != MT_BARREL)
            {
                bool    result = false;

                if (*mobjinfo[i].name1 && M_StringStartsWith(parms, mobjinfo[i].name1))
                {
                    M_StringCopy(namecmdold, mobjinfo[i].name1, sizeof(namecmdold));
                    M_StringReplaceAll(parms, mobjinfo[i].name1, "", false);
                    M_StringCopy(namecmdnew, trimnonalpha(parms), sizeof(namecmdnew));
                    namecmdtype = i;
                    result = true;
                }
                else if (*mobjinfo[i].name2 && M_StringStartsWith(parms, mobjinfo[i].name2))
                {
                    M_StringCopy(namecmdold, mobjinfo[i].name2, sizeof(namecmdold));
                    M_StringReplaceAll(parms, mobjinfo[i].name2, "", false);
                    M_StringCopy(namecmdnew, trimnonalpha(parms), sizeof(namecmdnew));
                    namecmdtype = i;
                    result = true;
                }
                else if (*mobjinfo[i].name3 && M_StringStartsWith(parms, mobjinfo[i].name3))
                {
                    M_StringCopy(namecmdold, mobjinfo[i].name3, sizeof(namecmdold));
                    M_StringReplaceAll(parms, mobjinfo[i].name3, "", false);
                    M_StringCopy(namecmdnew, trimnonalpha(parms), sizeof(namecmdnew));
                    namecmdtype = i;
                    result = true;
                }

                if (result)
                    return (namecmdnew[0] != '\0' && strlen(namecmdnew) < 64);
            }
    }

    return false;
}

static void namefunc2(char *cmd, char *parms)
{
    if (!*parms)
    {
        const int   i = C_GetIndex(cmd);

        C_ShowFormat(i);
        C_ShowDescription(i);

        if (gamestate != GS_LEVEL)
        {
            if (isdefaultplayername())
                C_Warning(0, NOGAMECCMDWARNING1);
            else
                C_Warning(0, NOGAMECCMDWARNING2, playername, pronoun(personal),
                    (playergender == playergender_other ? "aren't" : "isn't"));
        }
    }
    else if (M_StringCompare(namecmdold, "player")
        || (*playername && M_StringCompare(namecmdold, playername))
        || M_StringCompare(namecmdold, "me"))
    {
        if (isdefaultplayername())
            C_PlayerMessage("You have been named " BOLD("%s") ".", namecmdnew);
        else if (*namecmdnew)
            C_PlayerMessage("%s has been renamed " BOLD("%s") ".", playername, namecmdnew);
        else
            C_PlayerMessage("You no longer have a name.");

        M_StripQuotes(namecmdnew);
        playername = M_StringDuplicate(namecmdnew);
        M_SaveCVARs();
    }
    else
    {
        mobj_t  *bestmobj = NULL;
        fixed_t bestdist = FIXED_MAX;

        for (thinker_t *th = thinkers[th_mobj].cnext; th != &thinkers[th_mobj]; th = th->cnext)
        {
            mobj_t              *mobj = (mobj_t *)th;
            const int           flags = mobj->flags;
            const mobjtype_t    type = mobj->type;

            if (((namecmdanymonster && (flags & MF_SHOOTABLE) && type != MT_BARREL && type != MT_PLAYER) || type == namecmdtype)
                && ((namecmdfriendly && (flags & MF_FRIEND)) || !namecmdfriendly)
                && mobj->health > 0
                && P_CheckSight(viewplayer->mo, mobj))
            {
                fixed_t dist = P_ApproxDistance(mobj->x - viewx, mobj->y - viewy);

                if (dist < bestdist)
                {
                    bestdist = dist;
                    bestmobj = mobj;

                    if (namecmdanymonster)
                        M_StringCopy(namecmdold, mobj->info->name1, sizeof(namecmdold));
                }
            }
        }

        if (bestmobj)
        {
            M_StripQuotes(namecmdnew);

            C_PlayerMessage("The nearest %s%s to %s has been %s " BOLD("%s") ".",
                (namecmdfriendly ? "friendly " : ""), namecmdold, C_GetPlayerName(),
                (*bestmobj->name ? "renamed" : "named"), namecmdnew);

            M_StringCopy(bestmobj->name, namecmdnew, sizeof(bestmobj->name));
        }
        else
            C_Warning(0, "%s %s%s couldn't be found nearby.",
                (isvowel(namecmdold[0]) ? "An" : "A"), (namecmdfriendly ? "friendly " : ""), namecmdold);
    }
}

//
// newgame CCMD
//
static void newgamefunc2(char *cmd, char *parms)
{
    C_HideConsoleFast();

    if (viewplayer)
        viewplayer->cheats = 0;

    G_DeferredInitNew((skill_t)(skilllevel - 1), (gamemode == commercial ? expansion : episode), 1);
}

//
// noclip CCMD
//
static void noclipfunc2(char *cmd, char *parms)
{
    if (*parms)
    {
        const int   value = C_LookupValueFromAlias(parms, BOOLVALUEALIAS);

        if (value == 0 && (viewplayer->cheats & CF_NOCLIP))
            viewplayer->cheats &= ~CF_NOCLIP;
        else if (value == 1 && !(viewplayer->cheats & CF_NOCLIP))
            viewplayer->cheats |= CF_NOCLIP;
        else
            return;
    }
    else
        viewplayer->cheats ^= CF_NOCLIP;

    if (viewplayer->cheats & CF_NOCLIP)
    {
        C_Output(s_STSTR_NCON);
        HU_SetPlayerMessage(s_STSTR_NCON, false, false);
        viewplayer->cheated++;
        stat_cheatsentered = SafeAdd(stat_cheatsentered, 1);
        M_SaveCVARs();
    }
    else
    {
        C_Output(s_STSTR_NCOFF);
        HU_SetPlayerMessage(s_STSTR_NCOFF, false, false);
    }
}

//
// nomonsters CCMD
//
static void nomonstersfunc2(char *cmd, char *parms)
{
    if (*parms)
    {
        const int   value = C_LookupValueFromAlias(parms, BOOLVALUEALIAS);

        if (value == 0 && nomonsters)
            nomonsters = false;
        else if (value == 1 && !nomonsters)
            nomonsters = true;
        else
            return;
    }
    else
        nomonsters = !nomonsters;

    if (nomonsters)
    {
        if (gamestate == GS_LEVEL)
            for (int i = 0; i < numsectors; i++)
            {
                mobj_t  *thing = sectors[i].thinglist;

                while (thing)
                {
                    const mobjtype_t    type = thing->type;

                    if (((thing->flags & MF_SHOOTABLE) || (thing->flags2 & MF2_MONSTERMISSILE))
                        && type != MT_PLAYER && type != MT_BARREL && type != MT_BOSSBRAIN)
                        P_RemoveMobj(thing);

                    thing = thing->snext;
                }
            }

        C_Output(s_STSTR_NMON);
        HU_SetPlayerMessage(s_STSTR_NMON, false, false);
        viewplayer->cheated++;
        stat_cheatsentered = SafeAdd(stat_cheatsentered, 1);
        M_SaveCVARs();
    }
    else
    {
        C_Output(s_STSTR_NMOFF);
        HU_SetPlayerMessage(s_STSTR_NMOFF, false, false);

        if (gamestate == GS_LEVEL)
        {
            if (isdefaultplayername())
                C_Warning(0, NEXTMAPWARNING1);
            else
                C_Warning(0, NEXTMAPWARNING2, playername);
        }
    }
}

//
// notarget CCMD
//
static void notargetfunc2(char *cmd, char *parms)
{
    if (*parms)
    {
        const int   value = C_LookupValueFromAlias(parms, BOOLVALUEALIAS);

        if (value == 0 && (viewplayer->cheats & CF_NOTARGET))
            viewplayer->cheats &= ~CF_NOTARGET;
        else if (value == 1 && !(viewplayer->cheats & CF_NOTARGET))
            viewplayer->cheats |= CF_NOTARGET;
        else
            return;
    }
    else
        viewplayer->cheats ^= CF_NOTARGET;

    if (viewplayer->cheats & CF_NOTARGET)
    {
        for (int i = 0; i < numsectors; i++)
        {
            for (mobj_t *thing = sectors[i].thinglist; thing; thing = thing->snext)
            {
                if (thing->target && thing->target->player)
                    P_SetTarget(&thing->target, NULL);

                if (thing->tracer && thing->tracer->player)
                    P_SetTarget(&thing->tracer, NULL);

                if (thing->lastenemy && thing->lastenemy->player)
                    P_SetTarget(&thing->lastenemy, NULL);
            }

            P_SetTarget(&sectors[i].soundtarget, NULL);
        }

        C_Output(s_STSTR_NTON);
        HU_SetPlayerMessage(s_STSTR_NTON, false, false);
        viewplayer->cheated++;
        stat_cheatsentered = SafeAdd(stat_cheatsentered, 1);
        M_SaveCVARs();
    }
    else
    {
        C_Output(s_STSTR_NTOFF);
        HU_SetPlayerMessage(s_STSTR_NTOFF, false, false);
    }
}

//
// pistolstart CCMD
//
static void pistolstartfunc2(char *cmd, char *parms)
{
    if (*parms)
    {
        const int   value = C_LookupValueFromAlias(parms, BOOLVALUEALIAS);

        if (value == 0 && pistolstart)
            pistolstart = false;
        else if (value == 1 && !pistolstart)
            pistolstart = true;
        else
            return;
    }
    else
        pistolstart = !pistolstart;

    if (pistolstart)
    {
        C_Output(s_STSTR_PSON);
        HU_SetPlayerMessage(s_STSTR_PSON, false, false);
    }
    else
    {
        C_Output(s_STSTR_PSOFF);
        HU_SetPlayerMessage(s_STSTR_PSOFF, false, false);
    }

    if (gamestate == GS_LEVEL)
    {
        if (isdefaultplayername())
            C_Warning(0, NEXTMAPWARNING1);
        else
            C_Warning(0, NEXTMAPWARNING2, playername);
    }
}

//
// play CCMD
//
static int  playcmdid;
static int  playcmdtype;
static char *playcmdname;

static bool playfunc1(char *cmd, char *parms)
{
    char    *parm = removenonalpha(parms);

    if (!*parm)
        return true;

    for (int i = 1; i < NUMSFX; i++)
    {
        char    namebuf[9];

        M_snprintf(namebuf, sizeof(namebuf), "ds%s", s_sfx[i].name2);

        if (M_StringCompare(parms, namebuf) && W_CheckNumForName(namebuf) >= 0)
        {
            playcmdid = i;
            playcmdtype = 1;
            playcmdname = uppercase(namebuf);
            free(parm);
            return true;
        }
    }

    for (int i = 1; i < NUMMUSIC; i++)
    {
        char    namebuf[9];

        M_snprintf(namebuf, sizeof(namebuf), "h_%s", s_music[i].name2);

        if (W_CheckNumForName(namebuf) == -1)
            M_snprintf(namebuf, sizeof(namebuf), "d_%s", s_music[i].name2);

        if (M_StringCompare(parms, namebuf) && W_CheckNumForName(namebuf) >= 0)
        {
            playcmdid = i;
            playcmdtype = 2;
            playcmdname = uppercase(namebuf);
            free(parm);
            return true;
        }

        if (!M_StringCompare(s_music[i].title1, "n/a"))
        {
            char    *titlebuf = removenonalpha(s_music[i].title1);

            if (M_StringCompare(parm, titlebuf))
            {
                M_snprintf(namebuf, sizeof(namebuf), "h_%s", s_music[i].name2);

                if (W_CheckNumForName(namebuf) == -1)
                    M_snprintf(namebuf, sizeof(namebuf), "d_%s", s_music[i].name2);

                if (W_CheckNumForName(namebuf) >= 0)
                {
                    playcmdid = i;
                    playcmdtype = 3;
                    playcmdname = M_StringDuplicate(s_music[i].title1);
                    free(parm);
                    free(titlebuf);
                    return true;
                }
            }

            free(titlebuf);

            if (!M_StringCompare(s_music[i].title1, s_music[i].title2))
            {
                titlebuf = removenonalpha(s_music[i].title2);

                if (M_StringCompare(parm, titlebuf))
                {
                    M_snprintf(namebuf, sizeof(namebuf), "h_%s", s_music[i].name2);

                    if (W_CheckNumForName(namebuf) == -1)
                        M_snprintf(namebuf, sizeof(namebuf), "d_%s", s_music[i].name2);

                    if (W_CheckNumForName(namebuf) >= 0)
                    {
                        playcmdid = i;
                        playcmdtype = 3;
                        playcmdname = M_StringDuplicate(s_music[i].title2);
                        free(parm);
                        free(titlebuf);
                        return true;
                    }
                }

                free(titlebuf);

                if (*s_music[i].title3)
                {
                    titlebuf = removenonalpha(s_music[i].title3);

                    if (M_StringCompare(parm, titlebuf))
                    {
                        M_snprintf(namebuf, sizeof(namebuf), "h_%s", s_music[i].name2);

                        if (W_CheckNumForName(namebuf) == -1)
                            M_snprintf(namebuf, sizeof(namebuf), "d_%s", s_music[i].name2);

                        if (W_CheckNumForName(namebuf) >= 0)
                        {
                            playcmdid = i;
                            playcmdtype = 3;
                            playcmdname = M_StringDuplicate(s_music[i].title3);
                            free(titlebuf);
                            free(parm);
                            return true;
                        }
                    }

                    free(titlebuf);
                }
            }
        }
    }

    free(parm);
    return false;
}

static void playfunc2(char *cmd, char *parms)
{
    if (!*parms)
    {
        const int   i = C_GetIndex(cmd);

        C_ShowFormat(i);
        C_ShowDescription(i);
    }
    else
    {
        if (playcmdtype == 1)
        {
            S_StartSound(NULL, playcmdid);
            C_Output("Playing " BOLD("%s") "...", playcmdname);
        }
        else if (playcmdtype == 2)
        {
            S_ChangeMusic(playcmdid, true, true, false);

            if (consoleactive)
                S_LowerMusicVolume();

            C_Output("Playing " BOLD("%s") "...", playcmdname);
        }
        else if (playcmdtype == 3)
        {
            S_ChangeMusic(playcmdid, true, true, false);

            if (consoleactive)
                S_LowerMusicVolume();

            C_Output("Playing " ITALICS("%s") "...", playcmdname);
        }

        free(playcmdname);
    }
}

static skill_t favoriteskilllevel(void)
{
    uint64_t    skilllevelstat = 0;
    skill_t     favorite = gameskill;

    if (skilllevelstat < stat_skilllevel_imtooyoungtodie)
    {
        skilllevelstat = stat_skilllevel_imtooyoungtodie;
        favorite = sk_baby;
    }

    if (skilllevelstat < stat_skilllevel_heynottoorough)
    {
        skilllevelstat = stat_skilllevel_heynottoorough;
        favorite = sk_easy;
    }

    if (skilllevelstat < stat_skilllevel_hurtmeplenty)
    {
        skilllevelstat = stat_skilllevel_hurtmeplenty;
        favorite = sk_medium;
    }

    if (skilllevelstat < stat_skilllevel_ultraviolence)
    {
        skilllevelstat = stat_skilllevel_ultraviolence;
        favorite = sk_hard;
    }

    if (skilllevelstat < stat_skilllevel_nightmare)
        favorite = sk_nightmare;

    return favorite;
}

static weapontype_t favoriteweapon(bool total)
{
    weapontype_t    favorite = wp_nochange;
    uint64_t        shotsfiredstat = 0;

    if (total)
    {
        if (shotsfiredstat < stat_shotsfired[wp_fist])
        {
            shotsfiredstat = stat_shotsfired[wp_fist];
            favorite = wp_fist;
        }

        if (shotsfiredstat < stat_shotsfired[wp_chainsaw])
        {
            shotsfiredstat = stat_shotsfired[wp_chainsaw];
            favorite = wp_chainsaw;
        }

        if (shotsfiredstat < stat_shotsfired[wp_pistol])
        {
            shotsfiredstat = stat_shotsfired[wp_pistol];
            favorite = wp_pistol;
        }

        if (shotsfiredstat < stat_shotsfired[wp_shotgun])
        {
            shotsfiredstat = stat_shotsfired[wp_shotgun];
            favorite = wp_shotgun;
        }

        if (shotsfiredstat < stat_shotsfired[wp_supershotgun])
        {
            shotsfiredstat = stat_shotsfired[wp_supershotgun];
            favorite = wp_supershotgun;
        }

        if (shotsfiredstat < stat_shotsfired[wp_chaingun])
        {
            shotsfiredstat = stat_shotsfired[wp_chaingun];
            favorite = wp_chaingun;
        }

        if (shotsfiredstat < stat_shotsfired[wp_missile])
        {
            shotsfiredstat = stat_shotsfired[wp_missile];
            favorite = wp_missile;
        }

        if (shotsfiredstat < stat_shotsfired[wp_plasma])
        {
            shotsfiredstat = stat_shotsfired[wp_plasma];
            favorite = wp_plasma;
        }

        if (shotsfiredstat < stat_shotsfired[wp_bfg])
        {
            shotsfiredstat = stat_shotsfired[wp_bfg];
            favorite = wp_bfg;
        }

        if (legacyofrust)
        {
            if (shotsfiredstat < stat_shotsfired_incinerator)
            {
                shotsfiredstat = stat_shotsfired_incinerator;
                favorite = wp_incinerator;
            }

            if (shotsfiredstat < stat_shotsfired_calamityblade)
                favorite = wp_calamityblade;
        }
    }
    else
    {
        for (int i = 0; i < NUMWEAPONS; i++)
            if (shotsfiredstat < viewplayer->shotsfired[i])
            {
                shotsfiredstat = viewplayer->shotsfired[i];
                favorite = i;
            }

        if (legacyofrust)
        {
            if (shotsfiredstat < viewplayer->shotsfired_incinerator)
            {
                shotsfiredstat = viewplayer->shotsfired_incinerator;
                favorite = wp_incinerator;
            }

            if (shotsfiredstat < viewplayer->shotsfired_calamityblade)
                favorite = wp_calamityblade;
        }
    }

    return favorite;
}

char *C_DistanceTraveled(double feet, bool allowzero)
{
    char    result[20] = "";

    if (feet > 0.0 || allowzero)
    {
        if (units == units_imperial)
        {
            if (feet >= 1.0)
            {
                if (feet < FEETPERMILE)
                {
                    char    *temp = commify((int64_t)feet);

                    M_snprintf(result, sizeof(result), "%s %s",
                        temp, (M_StringCompare(temp, "1") ? "foot" : "feet"));
                    free(temp);
                }
                else
                {
                    char    *temp1 = striptrailingzero(feet / FEETPERMILE, 2);
                    char    *temp2 = commifystring(temp1);

                    M_snprintf(result, sizeof(result), "%s miles", temp2);
                    free(temp2);
                    free(temp1);
                }
            }
            else if (allowzero)
                M_StringCopy(result, "0 feet", sizeof(result));
        }
        else
        {
            const double    meters = feet / FEETPERMETER;

            if (meters >= 0.1)
            {
                if (meters < METERSPERKILOMETER)
                {
                    char    *temp1 = striptrailingzero(meters, 1);
                    char    *temp2 = commifystring(temp1);

                    if (!M_StringCompare(temp2, "0.0"))
                        M_snprintf(result, sizeof(result), "%s %s",
                            temp2, (english == english_american ? "meters" : "metres"));

                    free(temp2);
                    free(temp1);
                }
                else
                {
                    char    *temp1 = striptrailingzero(meters / METERSPERKILOMETER, 2);
                    char    *temp2 = commifystring(temp1);

                    M_snprintf(result, sizeof(result), "%s %s",
                        temp1, (english == english_american ? "kilometers" : "kilometres"));
                    free(temp2);
                    free(temp1);
                }
            }
            else if (allowzero)
                M_StringCopy(result, (english == english_american ? "0 meters" : "0 metres"), sizeof(result));
        }
    }

    return M_StringDuplicate(result);
}

//
// playerstats CCMD
//
static void ShowMonsterKillStat_Game(const int tabs[MAXTABS], const mobjtype_t type)
{
    if (type > MT_NULL && type < NUMMOBJTYPES)
    {
        char    *temp1 = sentencecase(mobjinfo[type].plural1);
        char    *temp2 = commify(viewplayer->monsterskilled[type]);
        char    *temp3 = commify(monstercount[type]);
        char    *temp4 = commifystat(stat_monsterskilled[type]);

        C_TabbedOutput(tabs, INDENT "%s\t%s of %s (%i%%)\t%s",
            temp1, temp2, temp3,
            (monstercount[type] ? viewplayer->monsterskilled[type] * 100 / monstercount[type] : 0), temp4);

        free(temp1);
        free(temp2);
        free(temp3);
        free(temp4);
    }
}

static void C_PlayerStats_Game(void)
{
    const int       tabs[MAXTABS] = { 230, 365 };
    skill_t         favoriteskilllevel1 = favoriteskilllevel();
    weapontype_t    favoriteweapon1 = favoriteweapon(false);
    weapontype_t    favoriteweapon2 = favoriteweapon(true);
    int             time1 = maptime / TICRATE;
    int             time2 = (int)(stat_timeplayed / TICRATE);
    int             hours1;
    int             hours2;
    char            *temp1;
    char            *temp2;
    char            *temp3;
    char            *temp4;
    char            *temp5;
    int             killcount = 0;
    int             shotsfired1 = 0;
    uint64_t        shotsfired2 = 0;
    int             shotssuccessful1 = 0;
    uint64_t        shotssuccessful2 = 0;

    C_Header(tabs, playerstats, PLAYERSTATSHEADER);

    if (viewplayer->cheats & (CF_ALLMAP | CF_ALLMAP_THINGS))
        C_TabbedOutput(tabs, "Map explored\t100%%\t\x96");
    else
        C_TabbedOutput(tabs, "Map explored\t%i%%\t\x96",
            (numvisiblelines ? nummappedlines * 100 / numvisiblelines : 0));

    temp1 = commifystat(stat_mapsfinished);
    temp2 = commifystat(stat_mapsstarted);
    C_TabbedOutput(tabs, "Maps finished\t\x96\t%s of %s (%i%%)",
        temp1, temp2, (stat_mapsstarted ? (int)(stat_mapsfinished * 100 / stat_mapsstarted) : 0));
    free(temp1);
    free(temp2);

    temp1 = commify(viewplayer->gamessaved);
    temp2 = commifystat(stat_gamessaved);
    C_TabbedOutput(tabs, "Games saved\t%s\t%s", temp1, temp2);
    free(temp1);
    free(temp2);

    temp1 = commify(viewplayer->gamesloaded);
    temp2 = commifystat(stat_gamesloaded);
    C_TabbedOutput(tabs, "Games loaded\t%s\t%s", temp1, temp2);
    free(temp1);
    free(temp2);

    if (favoriteskilllevel1 == sk_none)
    {
        temp1 = titlecase(*skilllevels[gameskill]);

        if (temp1[strlen(temp1) - 1] == '.')
            temp1[strlen(temp1) - 1] = '\0';

        C_TabbedOutput(tabs, "%s skill level\t" ITALICS("%s") "\t" ITALICS("%s"),
            (english == english_american ? "Favorite" : "Favourite"), temp1, temp1);
    }
    else
    {
        temp1 = titlecase(*skilllevels[gameskill]);
        temp2 = titlecase(*skilllevels[favoriteskilllevel1]);

        if (temp1[strlen(temp1) - 1] == '.')
            temp1[strlen(temp1) - 1] = '\0';

        if (temp2[strlen(temp2) - 1] == '.')
            temp2[strlen(temp2) - 1] = '\0';

        C_TabbedOutput(tabs, "%s skill level\t" ITALICS("%s") "\t" ITALICS("%s"),
            (english == english_american ? "Favorite" : "Favourite"), temp1, temp2);
        free(temp2);
    }

    free(temp1);

    for (int i = 0; i < NUMMOBJTYPES; i++)
        killcount += viewplayer->monsterskilled[i];

    temp1 = commify(killcount);
    temp2 = commify(totalkills);
    temp3 = commifystat(stat_monsterskilled_total);
    C_TabbedOutput(tabs, "Monsters %s %s\t%s of %s (%i%%)\t%s",
        C_GetPlayerName(), s_KILLED, temp1, temp2, (totalkills ? killcount * 100 / totalkills : 0), temp3);
    free(temp1);
    free(temp2);
    free(temp3);

    if (gamemode == commercial)
    {
        ShowMonsterKillStat_Game(tabs, MT_BABY);
        ShowMonsterKillStat_Game(tabs, MT_VILE);
    }

    if (legacyofrust)
        ShowMonsterKillStat_Game(tabs, MT_BANSHEE);

    ShowMonsterKillStat_Game(tabs, MT_BRUISER);
    ShowMonsterKillStat_Game(tabs, MT_HEAD);

    if (gamemode == commercial)
        ShowMonsterKillStat_Game(tabs, MT_CHAINGUY);

    if (gamemode != shareware)
        ShowMonsterKillStat_Game(tabs, MT_CYBORG);

    if (legacyofrust)
        ShowMonsterKillStat_Game(tabs, MT_GHOUL);

    if (gamemode == commercial)
        ShowMonsterKillStat_Game(tabs, MT_KNIGHT);

    ShowMonsterKillStat_Game(tabs, MT_TROOP);
    ShowMonsterKillStat_Game(tabs, MT_SKULL);

    if (gamemode == commercial)
        ShowMonsterKillStat_Game(tabs, MT_FATSO);

    if (legacyofrust)
        ShowMonsterKillStat_Game(tabs, MT_MINDWEAVER);

    if (gamemode == commercial)
        ShowMonsterKillStat_Game(tabs, MT_PAIN);

    ShowMonsterKillStat_Game(tabs, MT_SERGEANT);

    if (gamemode == commercial)
        ShowMonsterKillStat_Game(tabs, MT_UNDEAD);

    if (legacyofrust)
        ShowMonsterKillStat_Game(tabs, MT_SHOCKTROOPER);

    ShowMonsterKillStat_Game(tabs, MT_SHOTGUY);
    ShowMonsterKillStat_Game(tabs, MT_SHADOWS);

    if (gamemode != shareware)
        ShowMonsterKillStat_Game(tabs, MT_SPIDER);

    if (legacyofrust)
    {
        ShowMonsterKillStat_Game(tabs, MT_TYRANT);
        ShowMonsterKillStat_Game(tabs, MT_VASSAGO);
    }

    ShowMonsterKillStat_Game(tabs, MT_POSSESSED);

    temp1 = commify(viewplayer->infightcount);
    temp2 = commifystat(stat_monsterskilled_infighting);
    C_TabbedOutput(tabs, "Monsters %s while infighting\t%s\t%s", s_KILLED, temp1, temp2);
    free(temp1);
    free(temp2);

    if (!M_StringCompare(s_KILLED, s_GIBBED))
    {
        temp1 = commify(viewplayer->monstersgibbed);
        temp2 = commifystat(stat_monstersgibbed);
        C_TabbedOutput(tabs, "Monsters %s\t%s\t%s", s_GIBBED, temp1, temp2);
        free(temp1);
        free(temp2);
    }

    temp1 = commify(viewplayer->respawncount);
    temp2 = commifystat(stat_monstersrespawned);
    C_TabbedOutput(tabs, "Monsters respawned\t%s\t%s", temp1, temp2);
    free(temp1);
    free(temp2);

    temp1 = commify(viewplayer->resurrectioncount);
    temp2 = commifystat(stat_monstersresurrected);
    C_TabbedOutput(tabs, "Monsters resurrected\t%s\t%s", temp1, temp2);
    free(temp1);
    free(temp2);

    temp1 = commify(viewplayer->telefragcount);
    temp2 = commifystat(stat_monsterstelefragged);
    C_TabbedOutput(tabs, "Monsters telefragged\t%s\t%s", temp1, temp2);
    free(temp1);
    free(temp2);

    temp1 = sentencecase(mobjinfo[MT_BARREL].plural1);
    temp2 = commify(viewplayer->monsterskilled[MT_BARREL]);
    temp3 = commify(barrelcount);
    temp4 = commifystat(stat_barrelsexploded);
    C_TabbedOutput(tabs, "%s exploded\t%s of %s (%i%%)\t%s",
        temp1, temp2, temp3,
        (barrelcount ? viewplayer->monsterskilled[MT_BARREL] * 100 / barrelcount : 0), temp4);
    free(temp1);
    free(temp2);
    free(temp3);
    free(temp4);

    temp1 = commify(viewplayer->itemcount);
    temp2 = commify(totalitems);
    temp3 = commifystat(stat_itemspickedup);
    C_TabbedOutput(tabs, "Items picked up\t%s of %s (%i%%)\t%s",
        temp1, temp2, (totalitems ? viewplayer->itemcount * 100 / totalitems : 0), temp3);
    free(temp1);
    free(temp2);
    free(temp3);

    temp1 = commify((int64_t)viewplayer->itemspickedup_ammo_bullets
        + viewplayer->itemspickedup_ammo_shells
        + viewplayer->itemspickedup_ammo_rockets
        + (legacyofrust ? viewplayer->itemspickedup_ammo_fuel : viewplayer->itemspickedup_ammo_cells));
    temp2 = commify(stat_itemspickedup_ammo[am_clip]
        + stat_itemspickedup_ammo[am_shell]
        + stat_itemspickedup_ammo[am_misl]
        + (legacyofrust ? stat_itemspickedup_ammo_fuel : stat_itemspickedup_ammo[am_cell]));
    C_TabbedOutput(tabs, INDENT "Ammo\t%s\t%s", temp1, temp2);
    free(temp1);
    free(temp2);

    temp1 = sentencecase(weaponinfo[wp_pistol].ammoplural);
    temp2 = commify(viewplayer->itemspickedup_ammo_bullets);
    temp3 = commifystat(stat_itemspickedup_ammo[am_clip]);
    C_TabbedOutput(tabs, INDENT INDENT "%s\t%s\t%s", temp1, temp2, temp3);
    free(temp1);
    free(temp2);
    free(temp3);

    temp1 = sentencecase(weaponinfo[wp_shotgun].ammoplural);
    temp2 = commify(viewplayer->itemspickedup_ammo_shells);
    temp3 = commifystat(stat_itemspickedup_ammo[am_shell]);
    C_TabbedOutput(tabs, INDENT INDENT "%s\t%s\t%s", temp1, temp2, temp3);
    free(temp1);
    free(temp2);
    free(temp3);

    temp1 = sentencecase(weaponinfo[wp_missile].ammoplural);
    temp2 = commify(viewplayer->itemspickedup_ammo_rockets);
    temp3 = commifystat(stat_itemspickedup_ammo[am_misl]);
    C_TabbedOutput(tabs, INDENT INDENT "%s\t%s\t%s", temp1, temp2, temp3);
    free(temp1);
    free(temp2);
    free(temp3);

    if (legacyofrust)
    {
        temp1 = sentencecase(weaponinfo[wp_incinerator].ammoplural);
        temp2 = commify(viewplayer->itemspickedup_ammo_fuel);
        temp3 = commifystat(stat_itemspickedup_ammo_fuel);
        C_TabbedOutput(tabs, INDENT INDENT "%s\t%s\t%s", temp1, temp2, temp3);
        free(temp1);
        free(temp2);
        free(temp3);
    }
    else if (gamemode != shareware)
    {
        temp1 = sentencecase(weaponinfo[wp_plasma].ammoplural);
        temp2 = commify(viewplayer->itemspickedup_ammo_cells);
        temp3 = commifystat(stat_itemspickedup_ammo[am_cell]);
        C_TabbedOutput(tabs, INDENT INDENT "%s\t%s\t%s", temp1, temp2, temp3);
        free(temp1);
        free(temp2);
        free(temp3);
    }

    temp1 = commify(viewplayer->itemspickedup_armor);
    temp2 = commifystat(stat_itemspickedup_armor);
    C_TabbedOutput(tabs, INDENT "%s\t%s\t%s",
        (english == english_american ? "Armor" : "Armour"), temp1, temp2);
    free(temp1);
    free(temp2);

    temp1 = commify(viewplayer->itemspickedup_health);
    temp2 = commifystat(stat_itemspickedup_health);
    C_TabbedOutput(tabs, INDENT "Health\t%s\t%s", temp1, temp2);
    free(temp1);
    free(temp2);

    temp1 = commify(viewplayer->itemspickedup_keys);
    temp2 = commifystat(stat_itemspickedup_keys);
    C_TabbedOutput(tabs, INDENT "Keycards and skull keys\t%s\t%s", temp1, temp2);
    free(temp1);
    free(temp2);

    temp1 = commify(viewplayer->itemspickedup_powerups);
    temp2 = commifystat(stat_itemspickedup_powerups);
    C_TabbedOutput(tabs, INDENT "Power-ups\t%s\t%s", temp1, temp2);
    free(temp1);
    free(temp2);

    temp1 = commify(viewplayer->secretcount);
    temp2 = commify(totalsecrets);
    temp3 = commifystat(stat_secretsfound);
    C_TabbedOutput(tabs, "Secrets found\t%s of %s (%i%%)\t%s",
        temp1, temp2, (totalsecrets ? viewplayer->secretcount * 100 / totalsecrets : 0), temp3);
    free(temp1);
    free(temp2);
    free(temp3);

    hours1 = time1 / 3600;
    hours2 = time2 / 3600;
    time1 %= 3600;
    time2 %= 3600;
    temp2 = commify(hours2);

    if (sucktime && hours1 >= sucktime)
    {
        if (hours2 >= 100)
            C_TabbedOutput(tabs, "Time played\t%s\tOver %s hours!", s_STSTR_SUCKS, temp2);
        else if (hours2)
            C_TabbedOutput(tabs, "Time played\t%s\t" MONOSPACED("%i") ":" MONOSPACED("%02i") ":" MONOSPACED("%02i"),
                s_STSTR_SUCKS, hours2, time2 / 60, time2 % 60);
        else
            C_TabbedOutput(tabs, "Time played\t%s\t" MONOSPACED("%02i") ":" MONOSPACED("%02i"),
                s_STSTR_SUCKS, time2 / 60, time2 % 60);
    }
    else if (hours1)
    {
        if (hours2 >= 100)
            C_TabbedOutput(tabs, "Time played\t" MONOSPACED("%i") ":" MONOSPACED("%02i") ":" MONOSPACED("%02i")
                "\tOver %s hours!",
                hours1, time1 / 60, time1 % 60, temp2);
        else if (hours2)
            C_TabbedOutput(tabs, "Time played\t" MONOSPACED("%i") ":" MONOSPACED("%02i") ":" MONOSPACED("%02i")
                "\t" MONOSPACED("%i") ":" MONOSPACED("%02i") ":" MONOSPACED("%02i") "",
                hours1, time1 / 60, time1 % 60, hours2, time2 / 60, time2 % 60);
        else
            C_TabbedOutput(tabs, "Time played\t" MONOSPACED("%i") ":" MONOSPACED("%02i") ":" MONOSPACED("%02i")
                "\t" MONOSPACED("%02i") ":" MONOSPACED("%02i"),
                hours1, time1 / 60, time1 % 60, time2 / 60, time2 % 60);
    }
    else
    {
        if (hours2 >= 100)
            C_TabbedOutput(tabs, "Time played\t" MONOSPACED("%02i") ":" MONOSPACED("%02i")
                "\tOver %s hours!",
                time1 / 60, time1 % 60, temp2);
        else if (hours2)
            C_TabbedOutput(tabs, "Time played\t" MONOSPACED("%02i") ":" MONOSPACED("%02i")
                "\t" MONOSPACED("%i") ":" MONOSPACED("%02i") ":" MONOSPACED("%02i"),
                time1 / 60, time1 % 60, hours2, time2 / 60, time2 % 60);
        else
            C_TabbedOutput(tabs, "Time played\t" MONOSPACED("%02i") ":" MONOSPACED("%02i")
                "\t" MONOSPACED("%02i") ":" MONOSPACED("%02i"),
                time1 / 60, time1 % 60, time2 / 60, time2 % 60);
    }

    free(temp2);

    temp1 = commify(viewplayer->damageinflicted);
    temp2 = commifystat(stat_damageinflicted);
    C_TabbedOutput(tabs, "Damage inflicted\t%s\t%s", temp1, temp2);
    free(temp1);
    free(temp2);

    temp1 = commify(viewplayer->damagereceived);
    temp2 = commifystat(stat_damagereceived);
    C_TabbedOutput(tabs, "Damage received\t%s\t%s", temp1, temp2);
    free(temp1);
    free(temp2);

    temp1 = commify(viewplayer->deaths);
    temp2 = commifystat(stat_deaths);
    C_TabbedOutput(tabs, "Deaths\t%s\t%s", temp1, temp2);
    free(temp1);
    free(temp2);

    temp1 = commify(viewplayer->suicides);
    temp2 = commifystat(stat_suicides);
    C_TabbedOutput(tabs, "Suicides\t%s\t%s", temp1, temp2);
    free(temp1);
    free(temp2);

    temp1 = commify(viewplayer->cheated);
    temp2 = commifystat(stat_cheatsentered);
    C_TabbedOutput(tabs, "Cheats entered\t%s\t%s", temp1, temp2);
    free(temp1);
    free(temp2);

    shotssuccessful1 = viewplayer->shotssuccessful[wp_fist]
        + viewplayer->shotssuccessful[wp_chainsaw]
        + viewplayer->shotssuccessful[wp_pistol]
        + viewplayer->shotssuccessful[wp_shotgun]
        + viewplayer->shotssuccessful[wp_supershotgun]
        + viewplayer->shotssuccessful[wp_chaingun]
        + viewplayer->shotssuccessful[wp_missile]
        + (legacyofrust ? viewplayer->shotssuccessful_incinerator : viewplayer->shotssuccessful[wp_plasma])
        + (legacyofrust ? viewplayer->shotssuccessful_calamityblade : viewplayer->shotssuccessful[wp_bfg]);
    temp1 = commify(shotssuccessful1);

    shotsfired1 = viewplayer->shotsfired[wp_fist]
        + viewplayer->shotsfired[wp_chainsaw]
        + viewplayer->shotsfired[wp_pistol]
        + viewplayer->shotsfired[wp_shotgun]
        + viewplayer->shotsfired[wp_supershotgun]
        + viewplayer->shotsfired[wp_chaingun]
        + viewplayer->shotsfired[wp_missile]
        + (legacyofrust ? viewplayer->shotsfired_incinerator : viewplayer->shotsfired[wp_plasma])
        + (legacyofrust ? viewplayer->shotsfired_calamityblade : viewplayer->shotsfired[wp_bfg]);
    temp2 = commify(shotsfired1);

    shotssuccessful2 = stat_shotssuccessful[wp_fist]
        + stat_shotssuccessful[wp_chainsaw]
        + stat_shotssuccessful[wp_pistol]
        + stat_shotssuccessful[wp_shotgun]
        + stat_shotssuccessful[wp_supershotgun]
        + stat_shotssuccessful[wp_chaingun]
        + stat_shotssuccessful[wp_missile]
        + (legacyofrust ? stat_shotssuccessful_incinerator : stat_shotssuccessful[wp_plasma])
        + (legacyofrust ? stat_shotssuccessful_calamityblade : stat_shotssuccessful[wp_bfg]);
    temp3 = commify(shotssuccessful2);

    shotsfired2 = stat_shotsfired[wp_fist]
        + stat_shotsfired[wp_chainsaw]
        + stat_shotsfired[wp_pistol]
        + stat_shotsfired[wp_shotgun]
        + stat_shotsfired[wp_supershotgun]
        + stat_shotsfired[wp_chaingun]
        + stat_shotsfired[wp_missile]
        + (legacyofrust ? stat_shotsfired_incinerator : stat_shotsfired[wp_plasma])
        + (legacyofrust ? stat_shotsfired_calamityblade : stat_shotsfired[wp_bfg]);
    temp4 = commify(shotsfired2);

    C_TabbedOutput(tabs, "Shots successful/fired\t%s of %s (%i%%)\t%s of %s (%i%%)",
        temp1, temp2, (shotsfired1 ? shotssuccessful1 * 100 / shotsfired1 : 0), temp3, temp4,
        (shotsfired2 ? (int)(shotssuccessful2 * 100 / shotsfired2) : 0));
    free(temp1);
    free(temp2);
    free(temp3);
    free(temp4);

    temp1 = sentencecase(weaponinfo[wp_fist].name);
    temp2 = commify(viewplayer->shotssuccessful[wp_fist]);
    temp3 = commify(viewplayer->shotsfired[wp_fist]);
    temp4 = commifystat(stat_shotssuccessful[wp_fist]);
    temp5 = commifystat(stat_shotsfired[wp_fist]);
    C_TabbedOutput(tabs, INDENT "%s\t%s of %s (%i%%)\t%s of %s (%i%%)",
        temp1, temp2, temp3,
        (viewplayer->shotsfired[wp_fist] ?
            viewplayer->shotssuccessful[wp_fist] * 100 / viewplayer->shotsfired[wp_fist] : 0),
        temp4, temp5,
        (stat_shotsfired[wp_fist] ?
            (int)(stat_shotssuccessful[wp_fist] * 100 / stat_shotsfired[wp_fist]) : 0));
    free(temp1);
    free(temp2);
    free(temp3);
    free(temp4);
    free(temp5);

    temp1 = sentencecase(weaponinfo[wp_chainsaw].name);
    temp2 = commify(viewplayer->shotssuccessful[wp_chainsaw]);
    temp3 = commify(viewplayer->shotsfired[wp_chainsaw]);
    temp4 = commifystat(stat_shotssuccessful[wp_chainsaw]);
    temp5 = commifystat(stat_shotsfired[wp_chainsaw]);
    C_TabbedOutput(tabs, INDENT "%s\t%s of %s (%i%%)\t%s of %s (%i%%)",
        temp1, temp2, temp3,
        (viewplayer->shotsfired[wp_chainsaw] ?
            viewplayer->shotssuccessful[wp_chainsaw] * 100 / viewplayer->shotsfired[wp_chainsaw] : 0),
        temp4, temp5,
        (stat_shotsfired[wp_chainsaw] ?
            (int)(stat_shotssuccessful[wp_chainsaw] * 100 / stat_shotsfired[wp_chainsaw]) : 0));
    free(temp1);
    free(temp2);
    free(temp3);
    free(temp4);
    free(temp5);

    temp1 = sentencecase(weaponinfo[wp_pistol].name);
    temp2 = commify(viewplayer->shotssuccessful[wp_pistol]);
    temp3 = commify(viewplayer->shotsfired[wp_pistol]);
    temp4 = commifystat(stat_shotssuccessful[wp_pistol]);
    temp5 = commifystat(stat_shotsfired[wp_pistol]);
    C_TabbedOutput(tabs, INDENT "%s\t%s of %s (%i%%)\t%s of %s (%i%%)",
        temp1, temp2, temp3,
        (viewplayer->shotsfired[wp_pistol] ?
            viewplayer->shotssuccessful[wp_pistol] * 100 / viewplayer->shotsfired[wp_pistol] : 0),
        temp4, temp5,
        (stat_shotsfired[wp_pistol] ?
            (int)(stat_shotssuccessful[wp_pistol] * 100 / stat_shotsfired[wp_pistol]) : 0));
    free(temp1);
    free(temp2);
    free(temp3);
    free(temp4);
    free(temp5);

    temp1 = sentencecase(weaponinfo[wp_shotgun].name);
    temp2 = commify(viewplayer->shotssuccessful[wp_shotgun]);
    temp3 = commify(viewplayer->shotsfired[wp_shotgun]);
    temp4 = commifystat(stat_shotssuccessful[wp_shotgun]);
    temp5 = commifystat(stat_shotsfired[wp_shotgun]);
    C_TabbedOutput(tabs, INDENT "%s\t%s of %s (%i%%)\t%s of %s (%i%%)",
        temp1, temp2, temp3,
        (viewplayer->shotsfired[wp_shotgun] ?
            viewplayer->shotssuccessful[wp_shotgun] * 100 / viewplayer->shotsfired[wp_shotgun] : 0),
        temp4, temp5,
        (stat_shotsfired[wp_shotgun] ?
            (int)(stat_shotssuccessful[wp_shotgun] * 100 / stat_shotsfired[wp_shotgun]) : 0));
    free(temp1);
    free(temp2);
    free(temp3);
    free(temp4);
    free(temp5);

    if (gamemode == commercial)
    {
        temp1 = sentencecase(weaponinfo[wp_supershotgun].name);
        temp2 = commify(viewplayer->shotssuccessful[wp_supershotgun]);
        temp3 = commify(viewplayer->shotsfired[wp_supershotgun]);
        temp4 = commifystat(stat_shotssuccessful[wp_supershotgun]);
        temp5 = commifystat(stat_shotsfired[wp_supershotgun]);
        C_TabbedOutput(tabs, INDENT "%s\t%s of %s (%i%%)\t%s of %s (%i%%)",
            temp1, temp2, temp3,
            (viewplayer->shotsfired[wp_supershotgun] ?
                viewplayer->shotssuccessful[wp_supershotgun] * 100 / viewplayer->shotsfired[wp_supershotgun] : 0),
            temp4, temp5,
            (stat_shotsfired[wp_supershotgun] ?
                (int)(stat_shotssuccessful[wp_supershotgun] * 100 / stat_shotsfired[wp_supershotgun]) : 0));
        free(temp1);
        free(temp2);
        free(temp3);
        free(temp4);
        free(temp5);
    }

    temp1 = sentencecase(weaponinfo[wp_chaingun].name);
    temp2 = commify(viewplayer->shotssuccessful[wp_chaingun]);
    temp3 = commify(viewplayer->shotsfired[wp_chaingun]);
    temp4 = commifystat(stat_shotssuccessful[wp_chaingun]);
    temp5 = commifystat(stat_shotsfired[wp_chaingun]);
    C_TabbedOutput(tabs, INDENT "%s\t%s of %s (%i%%)\t%s of %s (%i%%)",
        temp1, temp2, temp3,
        (viewplayer->shotsfired[wp_chaingun] ?
            viewplayer->shotssuccessful[wp_chaingun] * 100 / viewplayer->shotsfired[wp_chaingun] : 0),
        temp4, temp5,
        (stat_shotsfired[wp_chaingun] ?
            (int)(stat_shotssuccessful[wp_chaingun] * 100 / stat_shotsfired[wp_chaingun]) : 0));
    free(temp1);
    free(temp2);
    free(temp3);
    free(temp4);
    free(temp5);

    temp1 = sentencecase(weaponinfo[wp_missile].name);
    temp2 = commify(viewplayer->shotssuccessful[wp_missile]);
    temp3 = commify(viewplayer->shotsfired[wp_missile]);
    temp4 = commifystat(stat_shotssuccessful[wp_missile]);
    temp5 = commifystat(stat_shotsfired[wp_missile]);
    C_TabbedOutput(tabs, INDENT "%s\t%s of %s (%i%%)\t%s of %s (%i%%)",
        temp1, temp2, temp3,
        (viewplayer->shotsfired[wp_missile] ?
            viewplayer->shotssuccessful[wp_missile] * 100 / viewplayer->shotsfired[wp_missile] : 0),
        temp4, temp5,
        (stat_shotsfired[wp_missile] ?
            (int)(stat_shotssuccessful[wp_missile] * 100 / stat_shotsfired[wp_missile]) : 0));
    free(temp1);
    free(temp2);
    free(temp3);
    free(temp4);
    free(temp5);

    if (legacyofrust)
    {
        temp1 = sentencecase(weaponinfo[wp_incinerator].name);
        temp2 = commify(viewplayer->shotssuccessful_incinerator);
        temp3 = commify(viewplayer->shotsfired_incinerator);
        temp4 = commifystat(stat_shotssuccessful_incinerator);
        temp5 = commifystat(stat_shotsfired_incinerator);
        C_TabbedOutput(tabs, INDENT "%s\t%s of %s (%i%%)\t%s of %s (%i%%)",
            temp1, temp2, temp3,
            (viewplayer->shotsfired_incinerator ?
                viewplayer->shotssuccessful_incinerator * 100 / viewplayer->shotsfired_incinerator : 0),
            temp4, temp5,
            (stat_shotsfired_incinerator ?
                (int)(stat_shotssuccessful_incinerator * 100 / stat_shotsfired_incinerator) : 0));
        free(temp1);
        free(temp2);
        free(temp3);
        free(temp4);
        free(temp5);

        temp1 = sentencecase(weaponinfo[wp_calamityblade].name);
        temp2 = commify(viewplayer->shotssuccessful_calamityblade);
        temp3 = commify(viewplayer->shotsfired_calamityblade);
        temp4 = commifystat(stat_shotssuccessful_calamityblade);
        temp5 = commifystat(stat_shotsfired_calamityblade);
        C_TabbedOutput(tabs, INDENT "%s\t%s of %s (%i%%)\t%s of %s (%i%%)",
            temp1, temp2, temp3,
            (viewplayer->shotsfired_calamityblade ?
                viewplayer->shotssuccessful_calamityblade * 100 / viewplayer->shotsfired_calamityblade : 0),
            temp4, temp5,
            (stat_shotsfired_calamityblade ?
                (int)(stat_shotssuccessful_calamityblade * 100 / stat_shotsfired_calamityblade) : 0));
        free(temp1);
        free(temp2);
        free(temp3);
        free(temp4);
        free(temp5);
    }
    else if (gamemode != shareware)
    {
        temp1 = sentencecase(weaponinfo[wp_plasma].name);
        temp2 = commify(viewplayer->shotssuccessful[wp_plasma]);
        temp3 = commify(viewplayer->shotsfired[wp_plasma]);
        temp4 = commifystat(stat_shotssuccessful[wp_plasma]);
        temp5 = commifystat(stat_shotsfired[wp_plasma]);
        C_TabbedOutput(tabs, INDENT "%s\t%s of %s (%i%%)\t%s of %s (%i%%)",
            temp1, temp2, temp3,
            (viewplayer->shotsfired[wp_plasma] ?
                viewplayer->shotssuccessful[wp_plasma] * 100 / viewplayer->shotsfired[wp_plasma] : 0),
            temp4, temp5,
            (stat_shotsfired[wp_plasma] ?
                (int)(stat_shotssuccessful[wp_plasma] * 100 / stat_shotsfired[wp_plasma]) : 0));
        free(temp1);
        free(temp2);
        free(temp3);
        free(temp4);
        free(temp5);

        temp1 = sentencecase(weaponinfo[wp_bfg].name);
        temp2 = commify(viewplayer->shotssuccessful[wp_bfg]);
        temp3 = commify(viewplayer->shotsfired[wp_bfg]);
        temp4 = commifystat(stat_shotssuccessful[wp_bfg]);
        temp5 = commifystat(stat_shotsfired[wp_bfg]);
        C_TabbedOutput(tabs, INDENT "%s\t%s of %s (%i%%)\t%s of %s (%i%%)",
            temp1, temp2, temp3,
            (viewplayer->shotsfired[wp_bfg] ?
                viewplayer->shotssuccessful[wp_bfg] * 100 / viewplayer->shotsfired[wp_bfg] : 0),
            temp4, temp5,
            (stat_shotsfired[wp_bfg] ?
                (int)(stat_shotssuccessful[wp_bfg] * 100 / stat_shotsfired[wp_bfg]) : 0));
        free(temp1);
        free(temp2);
        free(temp3);
        free(temp4);
        free(temp5);
    }

    temp1 = titlecase(weaponinfo[(favoriteweapon1 == wp_nochange ?
        viewplayer->readyweapon : favoriteweapon1)].name);
    temp2 = titlecase(weaponinfo[(favoriteweapon2 == wp_nochange ?
        viewplayer->readyweapon : favoriteweapon2)].name);
    C_TabbedOutput(tabs, "%s weapon\t%s\t%s",
        (english == english_american ? "Favorite" : "Favourite"), temp1, temp2);
    free(temp1);
    free(temp2);

    temp1 = C_DistanceTraveled(viewplayer->distancetraveled, true);
    temp2 = C_DistanceTraveled((double)stat_distancetraveled, true);
    C_TabbedOutput(tabs, "Distance %s\t%s\t%s",
        (english == english_american ? "traveled" : "travelled"), temp1, temp2);
    free(temp1);
    free(temp2);

    temp1 = commify(viewplayer->automapopened);
    temp2 = commify(stat_automapopened);
    C_TabbedOutput(tabs, "Automap opened\t%s\t%s", temp1, temp2);
    free(temp1);
    free(temp2);
}

static void ShowMonsterKillStat_NoGame(const int tabs[MAXTABS], const mobjtype_t type)
{
    if (type > MT_NULL && type < NUMMOBJTYPES)
    {
        char    *temp1 = sentencecase(mobjinfo[type].plural1);
        char    *temp2 = commifystat(stat_monsterskilled[type]);

        C_TabbedOutput(tabs, INDENT "%s\t\x96\t%s", temp1, temp2);
        free(temp1);
        free(temp2);
    }
}

static void C_PlayerStats_NoGame(void)
{
    const int       tabs[MAXTABS] = { 230, 365 };
    skill_t         favoriteskilllevel1 = favoriteskilllevel();
    weapontype_t    favoriteweapon1 = favoriteweapon(true);
    int             time1 = (int)(stat_timeplayed / TICRATE);
    int             hours1 = time1 / 3600;
    char            *temp1;
    char            *temp2;
    char            *temp3;
    uint64_t        shotsfired = 0;
    uint64_t        shotssuccessful = 0;

    C_Header(tabs, playerstats, PLAYERSTATSHEADER);

    temp1 = commifystat(stat_mapsfinished);
    temp2 = commifystat(stat_mapsstarted);
    C_TabbedOutput(tabs, "Maps finished\t\x96\t%s of %s (%i%%)",
        temp1, temp2, (stat_mapsstarted ? (int)(stat_mapsfinished * 100 / stat_mapsstarted) : 0));
    free(temp1);
    free(temp2);

    temp1 = commifystat(stat_gamessaved);
    C_TabbedOutput(tabs, "Games saved\t\x96\t%s", temp1);
    free(temp1);

    temp1 = commifystat(stat_gamesloaded);
    C_TabbedOutput(tabs, "Games loaded\t\x96\t%s", temp1);
    free(temp1);

    if (favoriteskilllevel1 == sk_none)
        C_TabbedOutput(tabs, "%s skill level\t\x96\t\x96",
            (english == english_american ? "Favorite" : "Favourite"));
    else
    {
        temp1 = titlecase(*skilllevels[favoriteskilllevel1]);

        if (temp1[strlen(temp1) - 1] == '.')
            temp1[strlen(temp1) - 1] = '\0';

        C_TabbedOutput(tabs, "%s skill level\t\x96\t" ITALICS("%s"),
            (english == english_american ? "Favorite" : "Favourite"), temp1);
        free(temp1);
    }

    temp1 = commifystat(stat_monsterskilled_total);
    C_TabbedOutput(tabs, "Monsters %s %s\t\x96\t%s", C_GetPlayerName(), s_KILLED, temp1);
    free(temp1);

    if (gamemode == commercial)
    {
        ShowMonsterKillStat_NoGame(tabs, MT_BABY);
        ShowMonsterKillStat_NoGame(tabs, MT_VILE);
    }

    if (legacyofrust)
        ShowMonsterKillStat_NoGame(tabs, MT_BANSHEE);

    ShowMonsterKillStat_NoGame(tabs, MT_BRUISER);
    ShowMonsterKillStat_NoGame(tabs, MT_HEAD);

    if (gamemode == commercial)
        ShowMonsterKillStat_NoGame(tabs, MT_CHAINGUY);

    if (gamemode != shareware)
        ShowMonsterKillStat_NoGame(tabs, MT_CYBORG);

    if (legacyofrust)
        ShowMonsterKillStat_NoGame(tabs, MT_GHOUL);

    if (gamemode == commercial)
        ShowMonsterKillStat_NoGame(tabs, MT_KNIGHT);

    ShowMonsterKillStat_NoGame(tabs, MT_TROOP);
    ShowMonsterKillStat_NoGame(tabs, MT_SKULL);

    if (gamemode == commercial)
        ShowMonsterKillStat_NoGame(tabs, MT_FATSO);

    if (legacyofrust)
        ShowMonsterKillStat_NoGame(tabs, MT_MINDWEAVER);

    if (gamemode == commercial)
        ShowMonsterKillStat_NoGame(tabs, MT_PAIN);

    ShowMonsterKillStat_NoGame(tabs, MT_SERGEANT);

    if (gamemode == commercial)
        ShowMonsterKillStat_NoGame(tabs, MT_UNDEAD);

    if (legacyofrust)
        ShowMonsterKillStat_NoGame(tabs, MT_SHOCKTROOPER);

    ShowMonsterKillStat_NoGame(tabs, MT_SHOTGUY);
    ShowMonsterKillStat_NoGame(tabs, MT_SHADOWS);

    if (gamemode != shareware)
        ShowMonsterKillStat_NoGame(tabs, MT_SPIDER);

    if (legacyofrust)
    {
        ShowMonsterKillStat_NoGame(tabs, MT_TYRANT);
        ShowMonsterKillStat_NoGame(tabs, MT_VASSAGO);
    }

    ShowMonsterKillStat_NoGame(tabs, MT_POSSESSED);

    temp1 = commifystat(stat_monsterskilled_infighting);
    C_TabbedOutput(tabs, "Monsters %s while infighting\t\x96\t%s", s_KILLED, temp1);
    free(temp1);

    if (!M_StringCompare(s_KILLED, s_GIBBED))
    {
        temp1 = commify(viewplayer->monstersgibbed);
        C_TabbedOutput(tabs, "Monsters %s\t\x96\t%s", s_GIBBED, temp1);
        free(temp1);
    }

    temp1 = commifystat(stat_monstersrespawned);
    C_TabbedOutput(tabs, "Monsters respawned\t\x96\t%s", temp1);
    free(temp1);

    temp1 = commifystat(stat_monstersresurrected);
    C_TabbedOutput(tabs, "Monsters resurrected\t\x96\t%s", temp1);
    free(temp1);

    temp1 = commifystat(stat_monsterstelefragged);
    C_TabbedOutput(tabs, "Monsters telefragged\t\x96\t%s", temp1);
    free(temp1);

    temp1 = sentencecase(mobjinfo[MT_BARREL].plural1);
    temp2 = commifystat(stat_barrelsexploded);
    C_TabbedOutput(tabs, "%s exploded\t\x96\t%s", temp1, temp2);
    free(temp1);
    free(temp2);

    temp1 = commifystat(stat_itemspickedup);
    C_TabbedOutput(tabs, "Items picked up\t\x96\t%s", temp1);
    free(temp1);

    temp1 = commify(stat_itemspickedup_ammo[am_clip]
        + stat_itemspickedup_ammo[am_shell]
        + stat_itemspickedup_ammo[am_misl]
        + (legacyofrust ? stat_itemspickedup_ammo_fuel : stat_itemspickedup_ammo[am_cell]));
    C_TabbedOutput(tabs, INDENT "Ammo\t\x96\t%s", temp1);
    free(temp1);

    temp1 = sentencecase(weaponinfo[wp_pistol].ammoplural);
    temp2 = commifystat(stat_itemspickedup_ammo[am_clip]);
    C_TabbedOutput(tabs, INDENT INDENT "%s\t\x96\t%s", temp1, temp2);
    free(temp1);
    free(temp2);

    temp1 = sentencecase(weaponinfo[wp_shotgun].ammoplural);
    temp2 = commifystat(stat_itemspickedup_ammo[am_shell]);
    C_TabbedOutput(tabs, INDENT INDENT "%s\t\x96\t%s", temp1, temp2);
    free(temp1);
    free(temp2);

    temp1 = sentencecase(weaponinfo[wp_missile].ammoplural);
    temp2 = commifystat(stat_itemspickedup_ammo[am_misl]);
    C_TabbedOutput(tabs, INDENT INDENT "%s\t\x96\t%s", temp1, temp2);
    free(temp1);
    free(temp2);

    if (legacyofrust)
    {
        temp1 = sentencecase(weaponinfo[wp_incinerator].ammoplural);
        temp2 = commifystat(stat_itemspickedup_ammo_fuel);
        C_TabbedOutput(tabs, INDENT INDENT "%s\t\x96\t%s", temp1, temp2);
        free(temp1);
        free(temp2);
    }
    else if (gamemode != shareware)
    {
        temp1 = sentencecase(weaponinfo[wp_plasma].ammoplural);
        temp2 = commifystat(stat_itemspickedup_ammo[am_cell]);
        C_TabbedOutput(tabs, INDENT INDENT "%s\t\x96\t%s", temp1, temp2);
        free(temp1);
        free(temp2);
    }

    temp1 = commifystat(stat_itemspickedup_armor);
    C_TabbedOutput(tabs, INDENT "%s\t\x96\t%s",
        (english == english_american ? "Armor" : "Armour"), temp1);
    free(temp1);

    temp1 = commifystat(stat_itemspickedup_health);
    C_TabbedOutput(tabs, INDENT "Health\t\x96\t%s", temp1);
    free(temp1);

    temp1 = commifystat(stat_itemspickedup_keys);
    C_TabbedOutput(tabs, INDENT "Keycards and skull keys\t\x96\t%s", temp1);
    free(temp1);

    temp1 = commifystat(stat_itemspickedup_powerups);
    C_TabbedOutput(tabs, INDENT "Power-ups\t\x96\t%s", temp1);
    free(temp1);

    temp1 = commifystat(stat_secretsfound);
    C_TabbedOutput(tabs, "Secrets found\t\x96\t%s", temp1);
    free(temp1);

    if (hours1 >= 100)
    {
        temp1 = commify(hours1);
        C_TabbedOutput(tabs, "Time played\t\x96\tOver %s hours!", temp1);
        free(temp1);
    }
    else
    {
        time1 %= 3600;

        if (hours1)
            C_TabbedOutput(tabs, "Time played\t\x96\t" MONOSPACED("%i") ":" MONOSPACED("%02i") ":" MONOSPACED("%02i"),
                hours1, time1 / 60, time1 % 60);
        else
            C_TabbedOutput(tabs, "Time played\t\x96\t" MONOSPACED("%02i") ":" MONOSPACED("%02i"),
                time1 / 60, time1 % 60);
    }

    temp1 = commifystat(stat_damageinflicted);
    C_TabbedOutput(tabs, "Damage inflicted\t\x96\t%s", temp1);
    free(temp1);

    temp1 = commifystat(stat_damagereceived);
    C_TabbedOutput(tabs, "Damage received\t\x96\t%s", temp1);
    free(temp1);

    temp1 = commifystat(stat_deaths);
    C_TabbedOutput(tabs, "Deaths\t\x96\t%s", temp1);
    free(temp1);

    temp1 = commifystat(stat_suicides);
    C_TabbedOutput(tabs, "Suicides\t\x96\t%s", temp1);
    free(temp1);

    temp1 = commifystat(stat_cheatsentered);
    C_TabbedOutput(tabs, "Cheats entered\t\x96\t%s", temp1);
    free(temp1);

    shotssuccessful = stat_shotssuccessful[wp_fist]
        + stat_shotssuccessful[wp_chainsaw]
        + stat_shotssuccessful[wp_pistol]
        + stat_shotssuccessful[wp_shotgun]
        + stat_shotssuccessful[wp_supershotgun]
        + stat_shotssuccessful[wp_chaingun]
        + stat_shotssuccessful[wp_missile]
        + (legacyofrust ? stat_shotssuccessful_incinerator : stat_shotssuccessful[wp_plasma])
        + (legacyofrust ? stat_shotssuccessful_calamityblade : stat_shotssuccessful[wp_bfg]);
    temp1 = commify(shotssuccessful);
    shotsfired = stat_shotsfired[wp_fist]
        + stat_shotsfired[wp_chainsaw]
        + stat_shotsfired[wp_pistol]
        + stat_shotsfired[wp_shotgun]
        + stat_shotsfired[wp_supershotgun]
        + stat_shotsfired[wp_chaingun]
        + stat_shotsfired[wp_missile]
        + (legacyofrust ? stat_shotsfired_incinerator : stat_shotsfired[wp_plasma])
        + (legacyofrust ? stat_shotsfired_calamityblade : stat_shotsfired[wp_bfg]);
    temp2 = commify(shotsfired);
    C_TabbedOutput(tabs, "Shots successful/fired\t\x96\t%s of %s (%i%%)",
        temp1, temp2, (shotsfired ? (int)(shotssuccessful * 100 / shotsfired) : 0));
    free(temp1);
    free(temp2);

    temp1 = sentencecase(weaponinfo[wp_fist].name);
    temp2 = commifystat(stat_shotssuccessful[wp_fist]);
    temp3 = commifystat(stat_shotsfired[wp_fist]);
    C_TabbedOutput(tabs, INDENT "%s\t\x96\t%s of %s (%i%%)",
        temp1, temp2, temp3, (stat_shotsfired[wp_fist] ? (int)(stat_shotssuccessful[wp_fist] * 100 / stat_shotsfired[wp_fist]) : 0));
    free(temp1);
    free(temp2);
    free(temp3);

    temp1 = sentencecase(weaponinfo[wp_chainsaw].name);
    temp2 = commifystat(stat_shotssuccessful[wp_chainsaw]);
    temp3 = commifystat(stat_shotsfired[wp_chainsaw]);
    C_TabbedOutput(tabs, INDENT "%s\t\x96\t%s of %s (%i%%)",
        temp1, temp2, temp3, (stat_shotsfired[wp_chainsaw] ? (int)(stat_shotssuccessful[wp_chainsaw] * 100 / stat_shotsfired[wp_chainsaw]) : 0));
    free(temp1);
    free(temp2);
    free(temp3);

    temp1 = sentencecase(weaponinfo[wp_pistol].name);
    temp2 = commifystat(stat_shotssuccessful[wp_pistol]);
    temp3 = commifystat(stat_shotsfired[wp_pistol]);
    C_TabbedOutput(tabs, INDENT "%s\t\x96\t%s of %s (%i%%)",
        temp1, temp2, temp3, (stat_shotsfired[wp_pistol] ? (int)(stat_shotssuccessful[wp_pistol] * 100 / stat_shotsfired[wp_pistol]) : 0));
    free(temp1);
    free(temp2);
    free(temp3);

    temp1 = sentencecase(weaponinfo[wp_shotgun].name);
    temp2 = commifystat(stat_shotssuccessful[wp_shotgun]);
    temp3 = commifystat(stat_shotsfired[wp_shotgun]);
    C_TabbedOutput(tabs, INDENT "%s\t\x96\t%s of %s (%i%%)",
        temp1, temp2, temp3, (stat_shotsfired[wp_shotgun] ? (int)(stat_shotssuccessful[wp_shotgun] * 100 / stat_shotsfired[wp_shotgun]) : 0));
    free(temp1);
    free(temp2);
    free(temp3);

    if (gamemode == commercial)
    {
        temp1 = sentencecase(weaponinfo[wp_supershotgun].name);
        temp2 = commifystat(stat_shotssuccessful[wp_supershotgun]);
        temp3 = commifystat(stat_shotsfired[wp_supershotgun]);
        C_TabbedOutput(tabs, INDENT "%s\t\x96\t%s of %s (%i%%)",
            temp1, temp2, temp3,
            (stat_shotsfired[wp_supershotgun] ? (int)(stat_shotssuccessful[wp_supershotgun] * 100 / stat_shotsfired[wp_supershotgun]) : 0));
        free(temp1);
        free(temp2);
        free(temp3);
    }

    temp1 = sentencecase(weaponinfo[wp_chaingun].name);
    temp2 = commifystat(stat_shotssuccessful[wp_chaingun]);
    temp3 = commifystat(stat_shotsfired[wp_chaingun]);
    C_TabbedOutput(tabs, INDENT "%s\t\x96\t%s of %s (%i%%)",
        temp1, temp2, temp3, (stat_shotsfired[wp_chaingun] ? (int)(stat_shotssuccessful[wp_chaingun] * 100 / stat_shotsfired[wp_chaingun]) : 0));
    free(temp1);
    free(temp2);
    free(temp3);

    temp1 = sentencecase(weaponinfo[wp_missile].name);
    temp2 = commifystat(stat_shotssuccessful[wp_missile]);
    temp3 = commifystat(stat_shotsfired[wp_missile]);
    C_TabbedOutput(tabs, INDENT "%s\t\x96\t%s of %s (%i%%)",
        temp1, temp2, temp3,
        (stat_shotsfired[wp_missile] ? (int)(stat_shotssuccessful[wp_missile] * 100 / stat_shotsfired[wp_missile]) : 0));
    free(temp1);
    free(temp2);
    free(temp3);

    if (legacyofrust)
    {
        temp1 = sentencecase(weaponinfo[wp_incinerator].name);
        temp2 = commifystat(stat_shotssuccessful_incinerator);
        temp3 = commifystat(stat_shotsfired_incinerator);
        C_TabbedOutput(tabs, INDENT "%s\t\x96\t%s of %s (%i%%)",
            temp1, temp2, temp3,
            (stat_shotsfired_incinerator ? (int)(stat_shotssuccessful_incinerator * 100 / stat_shotsfired_incinerator) : 0));
        free(temp1);
        free(temp2);
        free(temp3);

        temp1 = sentencecase(weaponinfo[wp_calamityblade].name);
        temp2 = commifystat(stat_shotssuccessful[wp_calamityblade]);
        temp3 = commifystat(stat_shotsfired[wp_calamityblade]);
        C_TabbedOutput(tabs, INDENT "%s\t\x96\t%s of %s (%i%%)",
            temp1, temp2, temp3,
            (stat_shotsfired[wp_calamityblade] ? (int)(stat_shotssuccessful[wp_calamityblade] * 100 / stat_shotsfired[wp_calamityblade]) : 0));
        free(temp1);
        free(temp2);
        free(temp3);
    }
    else if (gamemode != shareware)
    {
        temp1 = sentencecase(weaponinfo[wp_plasma].name);
        temp2 = commifystat(stat_shotssuccessful[wp_plasma]);
        temp3 = commifystat(stat_shotsfired[wp_plasma]);
        C_TabbedOutput(tabs, INDENT "%s\t\x96\t%s of %s (%i%%)",
            temp1, temp2, temp3,
            (stat_shotsfired[wp_plasma] ? (int)(stat_shotssuccessful[wp_plasma] * 100 / stat_shotsfired[wp_plasma]) : 0));
        free(temp1);
        free(temp2);
        free(temp3);

        temp1 = sentencecase(weaponinfo[wp_bfg].name);
        temp2 = commifystat(stat_shotssuccessful[wp_bfg]);
        temp3 = commifystat(stat_shotsfired[wp_bfg]);
        C_TabbedOutput(tabs, INDENT "%s\t\x96\t%s of %s (%i%%)",
            temp1, temp2, temp3,
            (stat_shotsfired[wp_bfg] ? (int)(stat_shotssuccessful[wp_bfg] * 100 / stat_shotsfired[wp_bfg]) : 0));
        free(temp1);
        free(temp2);
        free(temp3);
    }

    if (favoriteweapon1 == wp_nochange)
        C_TabbedOutput(tabs, "%s weapon\t\x96\t\x96",
            (english == english_american ? "Favorite" : "Favourite"));
    else
    {
        temp1 = titlecase(weaponinfo[favoriteweapon1].name);
        C_TabbedOutput(tabs, "%s weapon\t\x96\t%s",
            (english == english_american ? "Favorite" : "Favourite"), temp1);
        free(temp1);
    }

    temp1 = C_DistanceTraveled((double)stat_distancetraveled, true);
    C_TabbedOutput(tabs, "Distance %s\t\x96\t%s",
        (english == english_american ? "traveled" : "travelled"), temp1);
    free(temp1);

    temp1 = commify(stat_automapopened);
    C_TabbedOutput(tabs, "Automap opened\t\x96\t%s", temp1);
    free(temp1);
}

static void playerstatsfunc2(char *cmd, char *parms)
{
    if (gamestate == GS_LEVEL || gamestate == GS_INTERMISSION)
        C_PlayerStats_Game();
    else
        C_PlayerStats_NoGame();
}

//
// palette CCMD
//
static void palettefunc2(char *cmd, char *parms)
{
    M_ShowPalette();
}

//
// print CCMD
//
static void printfunc2(char *cmd, char *parms)
{
    if (!*parms)
    {
        const int   i = C_GetIndex(cmd);

        C_ShowFormat(i);
        C_ShowDescription(i);
    }
    else
        HU_PlayerMessage(parms, true, false);
}

//
// quit CCMD
//
static void quitfunc2(char *cmd, char *parms)
{
    quitcmd = true;
}

//
// readme CCMD
//
static void readmefunc2(char *cmd, char *parms)
{
    if (!*pwadfile)
        C_Warning(0, "A PWAD hasn't been loaded.");
    else
    {
        char    *temp1 = removeext(GetCorrectCase(pwadfile));
        char    *readme = M_StringJoin(temp1, ".txt", NULL);

        if (BTSXE1)
            readme = M_StringDuplicate("btsx_e1.txt");
        else if (BTSXE2)
            readme = M_StringDuplicate("btsx_e2.txt");
        else if (BTSXE3)
            readme = M_StringDuplicate("btsx_e3.txt");
        else if (KDIKDIZD)
            readme = M_StringDuplicate("kdikdizd.txt");

        if (!M_FileExists(readme))
            C_Warning(0, BOLD("%s") " wasn't found.", leafname(readme));
        else
        {
#if defined(_WIN32)
            if (!ShellExecute(NULL, "open", readme, NULL, NULL, SW_SHOWNORMAL))
                C_Warning(0, BOLD("%s") " wouldn't open!", leafname(readme));
#elif defined(__linux__) || defined(__FreeBSD__) || defined(__HAIKU__)
            char    *temp2 = M_StringJoin("xdg-open ", readme, NULL);

            if (!M_system(temp2))
                C_Warning(0, BOLD("%s") " wouldn't open!", leafname(readme));

            free(temp2);
#elif defined(__APPLE__)
            char    *temp2 = M_StringJoin("open ", readme, NULL);

            if (!M_system(temp2))
                C_Warning(0, BOLD("%s") " wouldn't open!", leafname(readme));

            free(temp2);
#endif
        }

        free(temp1);
        free(readme);
    }
}

//
// regenhealth CCMD
//
static void regenhealthfunc2(char *cmd, char *parms)
{
    if (*parms)
    {
        const int   value = C_LookupValueFromAlias(parms, BOOLVALUEALIAS);

        if (value == 0 && regenhealth)
            regenhealth = false;
        else if (value == 1 && !regenhealth)
            regenhealth = true;
        else
            return;
    }
    else
        regenhealth = !regenhealth;

    if (regenhealth)
    {
        C_Output(s_STSTR_RHON);
        HU_SetPlayerMessage(s_STSTR_RHON, false, false);
        viewplayer->cheated++;
        stat_cheatsentered = SafeAdd(stat_cheatsentered, 1);
        M_SaveCVARs();
    }
    else
    {
        C_Output(s_STSTR_RHOFF);
        HU_SetPlayerMessage(s_STSTR_RHOFF, false, false);
    }
}

//
// releasenotes CCMD
//
static void releasenotesfunc2(char *cmd, char *parms)
{
    C_Output("Opening the release notes for " ITALICS(DOOMRETRO_NAMEANDVERSIONSTRING "..."));

#if defined(_WIN32)
    D_OpenURLInBrowser(DOOMRETRO_RELEASENOTESURL, "The release notes for " ITALICS(DOOMRETRO_NAMEANDVERSIONSTRING) " wouldn't open!");
#elif defined(__linux__) || defined(__FreeBSD__) || defined(__HAIKU__)
    if (!M_system("xdg-open " DOOMRETRO_RELEASENOTESURL))
        C_Warning(0, "The release notes for " ITALICS(DOOMRETRO_NAMEANDVERSIONSTRING) " wouldn't open!");
#elif defined(__APPLE__)
    if (!M_system("open " DOOMRETRO_RELEASENOTESURL))
        C_Warning(0, "The release notes for " ITALICS(DOOMRETRO_NAMEANDVERSIONSTRING) " wouldn't open!");
#endif
}

//
// reset CCMD
//
static void resetfunc2(char *cmd, char *parms)
{
    if (!*parms)
    {
        const int   i = C_GetIndex(cmd);

        C_ShowFormat(i);
        C_ShowDescription(i);
        return;
    }

    if (M_StringCompare(parms, "all"))
    {
        resetallfunc2("resetall", "");
        return;
    }

    if (M_StringCompare(parms, "ammo")
        || M_StringCompare(parms, "armor") || M_StringCompare(parms, "armour")
        || M_StringCompare(parms, "armortype") || M_StringCompare(parms, "armourtype")
        || M_StringCompare(parms, "health")
        || M_StringCompare(parms, "weapon"))
        return;

    resettingcvar = true;

    for (int i = 0; *consolecmds[i].name; i++)
    {
        const int   flags = consolecmds[i].flags;

        if (consolecmds[i].type == CT_CVAR && !(flags & CF_READONLY))
        {
            char    name[255];

            M_StringCopy(name, (english == english_american || M_StringCompare(consolecmds[i].altspelling, EMPTYVALUE) ?
                consolecmds[i].name : consolecmds[i].altspelling), sizeof(name));

            if (!wildcard(name, parms))
                continue;

            if (flags & CF_BOOLEAN)
            {
                char    *temp1 = C_LookupAliasFromValue((bool)consolecmds[i].defaultnumber, consolecmds[i].aliases);

                if (*(bool *)consolecmds[i].variable != (bool)consolecmds[i].defaultnumber)
                {
                    char    *temp2 = uncommify(temp1);
                    char    *temp3 = M_StringJoin(name, " ", temp2, NULL);

                    C_ValidateInput(temp3);
                    C_Output("The " BOLD("%s") " CVAR has been reset to its default of " BOLD("%s") ".",
                        name, temp1);
                    free(temp2);
                    free(temp3);
                }
                else
                    C_Warning(0, "The " BOLD("%s") " CVAR is already set to its default of " BOLD("%s") ".",
                        name, temp1);

                free(temp1);
            }
            else if (flags & CF_COLOR)
            {
                char    *temp1 = C_LookupAliasFromValue((int)consolecmds[i].defaultnumber, consolecmds[i].aliases);
                char    *temp2 = C_FormatColorValue(temp1, false);

                if (*(int *)consolecmds[i].variable != (int)consolecmds[i].defaultnumber)
                {
                    char    *temp3 = M_StringJoin(name, " ", temp2, NULL);

                    C_ValidateInput(temp3);
                    C_Output("The " BOLD("%s") " CVAR has been reset to its default of %s.",
                        name, temp1);

                    free(temp3);
                }
                else
                    C_Warning(0, "The " BOLD("%s") " CVAR is already set to its default of %s.",
                        name, temp1);

                free(temp2);
                free(temp1);
            }
            else if (flags & CF_PERCENT)
            {
                char    *temp1 = C_LookupAliasFromValue((int)consolecmds[i].defaultnumber, consolecmds[i].aliases);

                if (*(int *)consolecmds[i].variable != (int)consolecmds[i].defaultnumber)
                {
                    char    *temp2 = uncommify(temp1);
                    char    *temp3 = M_StringJoin(name, " ", temp2, NULL);

                    C_ValidateInput(temp3);
                    C_Output("The " BOLD("%s") " CVAR has been reset to its default of " BOLD("%s%%") ".",
                        name, temp1);
                    free(temp2);
                    free(temp3);
                }
                else
                    C_Warning(0, "The " BOLD("%s") " CVAR is already set to its default of " BOLD("%s%%") ".",
                        name, temp1);

                free(temp1);
            }
            else if (flags & CF_INTEGER)
            {
                char    *temp1 = C_LookupAliasFromValue((int)consolecmds[i].defaultnumber, consolecmds[i].aliases);

                if (*(int *)consolecmds[i].variable != (int)consolecmds[i].defaultnumber)
                {
                    char    *temp2 = uncommify(temp1);
                    char    *temp3 = M_StringJoin(name, " ", temp2, NULL);

                    C_ValidateInput(temp3);
                    C_Output("The " BOLD("%s") " CVAR has been reset to its default of " BOLD("%s") ".",
                        name, temp1);
                    free(temp2);
                    free(temp3);
                }
                else
                    C_Warning(0, "The " BOLD("%s") " CVAR is already set to its default of " BOLD("%s") ".",
                        name, temp1);

                free(temp1);
            }
            else if (flags & CF_FLOAT)
            {
                char    *temp1 = striptrailingzero(consolecmds[i].defaultnumber, 1);

                if (*(float *)consolecmds[i].variable != consolecmds[i].defaultnumber)
                {
                    char    *temp2 = M_StringJoin(name, " ", temp1, NULL);

                    C_ValidateInput(temp2);
                    C_Output("The " BOLD("%s") " CVAR has been reset to its default of " BOLD("%s") ".",
                        name, temp1);
                    free(temp2);
                }
                else
                    C_Warning(0, "The " BOLD("%s") " CVAR is already set to its default of " BOLD("%s") ".",
                        name, temp1);

                free(temp1);
            }
            else
            {
                char    *temp = M_StringJoin(name, " ", (*consolecmds[i].defaultstring ? consolecmds[i].defaultstring : EMPTYVALUE),
                            NULL);

                if (!M_StringCompare(*(char **)consolecmds[i].variable, consolecmds[i].defaultstring))
                {
                    C_ValidateInput(temp);
                    C_Output("The " BOLD("%s") " CVAR has been reset to its default of " BOLD("\"%s\"") ".",
                        name, consolecmds[i].defaultstring);
                    free(temp);
                }
                else
                    C_Warning(0, "The " BOLD("%s") " CVAR is already set to its default of " BOLD("\"%s\"") ".",
                        name, consolecmds[i].defaultstring);
            }

#if defined(_WIN32)
            if (M_StringCompare(name, stringize(wadfolder)))
            {
                if (wad && *wad)
                    free(wad);

                wad = M_StringDuplicate(wad_default);
                M_SaveCVARs();
            }
#endif
        }
    }

    resettingcvar = false;
}

//
// resetall CCMD
//
static void C_VerifyResetAll(const int key)
{
    messagetoprint = false;
    C_ShowConsole(false);
    consoleheight = CONSOLEHEIGHT;

    if (key == 'y')
    {
        resettingcvar = true;

        // reset all CVARs to default values
        for (int i = 0; *consolecmds[i].name; i++)
        {
            const int   flags = consolecmds[i].flags;
            const char  *name = consolecmds[i].name;

            if (consolecmds[i].type == CT_CVAR && !(flags & CF_READONLY))
            {
                if (M_StringCompare(name, "ammo")
                    || M_StringCompare(name, "armor") || M_StringCompare(name, "armour")
                    || M_StringCompare(name, "armortype") || M_StringCompare(name, "armourtype")
                    || M_StringCompare(name, "health")
                    || M_StringCompare(name, "weapon"))
                    continue;

                if (flags & CF_BOOLEAN)
                {
                    char    *temp1 = C_LookupAliasFromValue((bool)consolecmds[i].defaultnumber, consolecmds[i].aliases);
                    char    *temp2 = uncommify(temp1);

                    consolecmds[i].func2(consolecmds[i].name, temp2);
                    free(temp1);
                    free(temp2);
                }
                else if (flags & CF_INTEGER)
                {
                    char    *temp1 = C_LookupAliasFromValue((int)consolecmds[i].defaultnumber, consolecmds[i].aliases);
                    char    *temp2 = uncommify(temp1);

                    consolecmds[i].func2(consolecmds[i].name, temp2);
                    free(temp1);
                    free(temp2);
                }
                else if (flags & CF_FLOAT)
                {
                    char    *temp = striptrailingzero(consolecmds[i].defaultnumber, 2);

                    consolecmds[i].func2(consolecmds[i].name, temp);
                    free(temp);
                }
                else
                    consolecmds[i].func2(consolecmds[i].name,
                        (*consolecmds[i].defaultstring ? consolecmds[i].defaultstring : EMPTYVALUE));
            }
        }

#if defined(_WIN32)
        if (wad && *wad)
            free(wad);

        wad = M_StringDuplicate(wad_default);
#endif

        resettingcvar = false;

        // unbind all controls
        for (int i = 0; *actions[i].action; i++)
        {
            if (actions[i].keyboard1)
                *(int *)actions[i].keyboard1 = 0;

            if (actions[i].keyboard2)
                *(int *)actions[i].keyboard2 = 0;

            if (actions[i].mouse1)
                *(int *)actions[i].mouse1 = -1;

            if (actions[i].controller1)
                *(int *)actions[i].controller1 = 0;

            if (actions[i].controller2)
                *(int *)actions[i].controller2 = 0;
        }

        for (int i = 0; i < NUMKEYS; i++)
            keyactionlist[i][0] = '\0';

        for (int i = 0; i < MAXMOUSEBUTTONS + 4; i++)
            mouseactionlist[i][0] = '\0';

        // reset stretched sky
        if (gamestate == GS_LEVEL)
        {
            suppresswarnings = true;
            R_InitSkyMap();
            suppresswarnings = false;

            R_InitColumnFunctions();
        }

        // set all controls to defaults
        keyboardalwaysrun = KEYALWAYSRUN_DEFAULT;
        keyboardalwaysrun2 = KEYALWAYSRUN2_DEFAULT;
        keyboardautomap = KEYAUTOMAP_DEFAULT;
        keyboardautomap2 = KEYAUTOMAP2_DEFAULT;
        keyboardback = KEYDOWN_DEFAULT;
        keyboardback2 = KEYDOWN2_DEFAULT;
        keyboardbfg9000 = KEYBFG9000_DEFAULT;
        keyboardbfg90002 = KEYBFG90002_DEFAULT;
        keyboardchaingun = KEYCHAINGUN_DEFAULT;
        keyboardchaingun2 = KEYCHAINGUN2_DEFAULT;
        keyboardchainsaw = KEYCHAINSAW_DEFAULT;
        keyboardchainsaw2 = KEYCHAINSAW2_DEFAULT;
        keyboardclearmark = KEYCLEARMARK_DEFAULT;
        keyboardclearmark2 = KEYCLEARMARK2_DEFAULT;
        keyboardconsole = KEYCONSOLE_DEFAULT;
        keyboardconsole2 = KEYCONSOLE2_DEFAULT;
        keyboardfire = KEYFIRE_DEFAULT;
        keyboardfire2 = KEYFIRE2_DEFAULT;
        keyboardfists = KEYFISTS_DEFAULT;
        keyboardfists2 = KEYFISTS2_DEFAULT;
        keyboardfollowmode = KEYFOLLOWMODE_DEFAULT;
        keyboardfollowmode2 = KEYFOLLOWMODE2_DEFAULT;
        keyboardforward = KEYUP_DEFAULT;
        keyboardforward2 = KEYUP2_DEFAULT;
        keyboardfreelook = KEYFREELOOK_DEFAULT;
        keyboardfreelook2 = KEYFREELOOK2_DEFAULT;
        keyboardfullzoom = KEYFULLZOOM_DEFAULT;
        keyboardfullzoom2 = KEYFULLZOOM2_DEFAULT;
        keyboardgrid = KEYGRID_DEFAULT;
        keyboardgrid2 = KEYGRID2_DEFAULT;
        keyboardjump = KEYJUMP_DEFAULT;
        keyboardjump2 = KEYJUMP2_DEFAULT;
        keyboardleft = KEYLEFT_DEFAULT;
        keyboardleft2 = KEYLEFT2_DEFAULT;
        keyboardlookcenter = KEYLOOKCENTER_DEFAULT;
        keyboardlookcenter2 = KEYLOOKCENTER2_DEFAULT;
        keyboardlookdown = KEYLOOKDOWN_DEFAULT;
        keyboardlookdown2 = KEYLOOKDOWN2_DEFAULT;
        keyboardlookup = KEYLOOKUP_DEFAULT;
        keyboardlookup2 = KEYLOOKUP2_DEFAULT;
        keyboardmark = KEYMARK_DEFAULT;
        keyboardmark2 = KEYMARK2_DEFAULT;
        keyboardmenu = KEYMENU_DEFAULT;
        keyboardmenu2 = KEYMENU2_DEFAULT;
        keyboardnextweapon = KEYNEXTWEAPON_DEFAULT;
        keyboardnextweapon2 = KEYNEXTWEAPON2_DEFAULT;
        keyboardpistol = KEYPISTOL_DEFAULT;
        keyboardpistol2 = KEYPISTOL2_DEFAULT;
        keyboardplasmarifle = KEYPLASMARIFLE_DEFAULT;
        keyboardplasmarifle2 = KEYPLASMARIFLE2_DEFAULT;
        keyboardprevweapon = KEYPREVWEAPON_DEFAULT;
        keyboardprevweapon2 = KEYPREVWEAPON2_DEFAULT;
        keyboardright = KEYRIGHT_DEFAULT;
        keyboardright2 = KEYRIGHT2_DEFAULT;
        keyboardrocketlauncher = KEYROCKETLAUNCHER_DEFAULT;
        keyboardrocketlauncher2 = KEYROCKETLAUNCHER2_DEFAULT;
        keyboardrotatemode = KEYROTATEMODE_DEFAULT;
        keyboardrotatemode2 = KEYROTATEMODE2_DEFAULT;
        keyboardrun = KEYRUN_DEFAULT;
        keyboardrun2 = KEYRUN2_DEFAULT;
        keyboardscreenshot = KEYSCREENSHOT_DEFAULT;
        keyboardscreenshot2 = KEYSCREENSHOT2_DEFAULT;
        keyboardshotgun = KEYSHOTGUN_DEFAULT;
        keyboardshotgun2 = KEYSHOTGUN2_DEFAULT;
        keyboardsizedown = KEYSIZEDOWN_DEFAULT;
        keyboardsizedown2 = KEYSIZEDOWN2_DEFAULT;
        keyboardsizeup = KEYSIZEUP_DEFAULT;
        keyboardsizeup2 = KEYSIZEUP2_DEFAULT;
        keyboardstrafe = KEYSTRAFE_DEFAULT;
        keyboardstrafe2 = KEYSTRAFE2_DEFAULT;
        keyboardstrafeleft = KEYSTRAFELEFT_DEFAULT;
        keyboardstrafeleft2 = KEYSTRAFELEFT2_DEFAULT;
        keyboardstraferight = KEYSTRAFERIGHT_DEFAULT;
        keyboardstraferight2 = KEYSTRAFERIGHT2_DEFAULT;
        keyboardsupershotgun = KEYSUPERSHOTGUN_DEFAULT;
        keyboardsupershotgun2 = KEYSUPERSHOTGUN2_DEFAULT;
        keyboarduse = KEYUSE_DEFAULT;
        keyboarduse2 = KEYUSE2_DEFAULT;
        keyboardweapon1A = KEYWEAPON1A_DEFAULT;
        keyboardweapon1B = KEYWEAPON1B_DEFAULT;
        keyboardweapon2A = KEYWEAPON2A_DEFAULT;
        keyboardweapon2B = KEYWEAPON2B_DEFAULT;
        keyboardweapon3A = KEYWEAPON3A_DEFAULT;
        keyboardweapon3B = KEYWEAPON3B_DEFAULT;
        keyboardweapon4A = KEYWEAPON4A_DEFAULT;
        keyboardweapon4B = KEYWEAPON4B_DEFAULT;
        keyboardweapon5A = KEYWEAPON5A_DEFAULT;
        keyboardweapon5B = KEYWEAPON5B_DEFAULT;
        keyboardweapon6A = KEYWEAPON6A_DEFAULT;
        keyboardweapon6B = KEYWEAPON6B_DEFAULT;
        keyboardweapon7A = KEYWEAPON7A_DEFAULT;
        keyboardweapon7B = KEYWEAPON7B_DEFAULT;
        keyboardzoomin = KEYZOOMIN_DEFAULT;
        keyboardzoomin2 = KEYZOOMIN2_DEFAULT;
        keyboardzoomout = KEYZOOMOUT_DEFAULT;
        keyboardzoomout2 = KEYZOOMOUT2_DEFAULT;

        mousealwaysrun = MOUSEALWAYSRUN_DEFAULT;
        mouseautomap = MOUSEAUTOMAP_DEFAULT;
        mouseback = MOUSEBACK_DEFAULT;
        mousebfg9000 = MOUSEBFG9000_DEFAULT;
        mousechaingun = MOUSECHAINGUN_DEFAULT;
        mousechainsaw = MOUSECHAINSAW_DEFAULT;
        mouseclearmark = MOUSECLEARMARK_DEFAULT;
        mouseconsole = MOUSECONSOLE_DEFAULT;
        mousefire = MOUSEFIRE_DEFAULT;
        mousefists = MOUSEFISTS_DEFAULT;
        mousefollowmode = MOUSEFOLLOWMODE_DEFAULT;
        mouseforward = MOUSEFORWARD_DEFAULT;
        mousefreelook = MOUSEFREELOOK_DEFAULT;
        mousefullzoom = MOUSEFULLZOOM_DEFAULT;
        mousegrid = MOUSEGRID_DEFAULT;
        mousejump = MOUSEJUMP_DEFAULT;
        mouseleft = MOUSELEFT_DEFAULT;
        mouselookcenter = MOUSELOOKCENTER_DEFAULT;
        mouselookdown = MOUSELOOKDOWN_DEFAULT;
        mouselookup = MOUSELOOKUP_DEFAULT;
        mousemark = MOUSEMARK_DEFAULT;
        mousemenu = MOUSEMENU_DEFAULT;
        mousenextweapon = MOUSENEXTWEAPON_DEFAULT;
        mousepistol = MOUSEPISTOL_DEFAULT;
        mouseplasmarifle = MOUSEPLASMARIFLE_DEFAULT;
        mouseprevweapon = MOUSEPREVWEAPON_DEFAULT;
        mouseright = MOUSERIGHT_DEFAULT;
        mouserocketlauncher = MOUSEROCKETLAUNCHER_DEFAULT;
        mouserotatemode = MOUSEROTATEMODE_DEFAULT;
        mouserun = MOUSERUN_DEFAULT;
        mousescreenshot = MOUSESCREENSHOT_DEFAULT;
        mouseshotgun = MOUSESHOTGUN_DEFAULT;
        mousesizedown = MOUSESIZEDOWN_DEFAULT;
        mousesizeup = MOUSESIZEUP_DEFAULT;
        mousestrafe = MOUSESTRAFE_DEFAULT;
        mousestrafeleft = MOUSESTRAFELEFT_DEFAULT;
        mousestraferight = MOUSESTRAFERIGHT_DEFAULT;
        mousesupershotgun = MOUSESUPERSHOTGUN_DEFAULT;
        mouseuse = MOUSEUSE_DEFAULT;
        mouseweapon1 = MOUSEWEAPON1_DEFAULT;
        mouseweapon2 = MOUSEWEAPON2_DEFAULT;
        mouseweapon3 = MOUSEWEAPON3_DEFAULT;
        mouseweapon4 = MOUSEWEAPON4_DEFAULT;
        mouseweapon5 = MOUSEWEAPON5_DEFAULT;
        mouseweapon6 = MOUSEWEAPON6_DEFAULT;
        mouseweapon7 = MOUSEWEAPON7_DEFAULT;
        mousezoomin = MOUSEZOOMIN_DEFAULT;
        mousezoomout = MOUSEZOOMOUT_DEFAULT;

        controlleralwaysrun = CONTROLLERALWAYSRUN_DEFAULT;
        controllerautomap = CONTROLLERAUTOMAP_DEFAULT;
        controllerback = CONTROLLERBACK_DEFAULT;
        controllerbfg9000 = CONTROLLERBFG9000_DEFAULT;
        controllerchaingun = CONTROLLERCHAINGUN_DEFAULT;
        controllerchainsaw = CONTROLLERCHAINSAW_DEFAULT;
        controllerclearmark = CONTROLLERCLEARMARK_DEFAULT;
        controllerconsole = CONTROLLERCONSOLE_DEFAULT;
        controllerfire = CONTROLLERFIRE_DEFAULT;
        controllerfists = CONTROLLERFISTS_DEFAULT;
        controllerfollowmode = CONTROLLERFOLLOWMODE_DEFAULT;
        controllerforward = CONTROLLERFORWARD_DEFAULT;
        controllerfreelook = CONTROLLERFREELOOK_DEFAULT;
        controllerfullzoom = CONTROLLERFULLZOOM_DEFAULT;
        controllergrid = CONTROLLERGRID_DEFAULT;
        controllerjump = CONTROLLERJUMP_DEFAULT;
        controllerleft = CONTROLLERLEFT_DEFAULT;
        controllerlookcenter = CONTROLLERLOOKCENTER_DEFAULT;
        controllerlookdown = CONTROLLERLOOKDOWN_DEFAULT;
        controllerlookup = CONTROLLERLOOKUP_DEFAULT;
        controllermark = CONTROLLERMARK_DEFAULT;
        controllermenu = CONTROLLERMENU_DEFAULT;
        controllernextweapon = CONTROLLERNEXTWEAPON_DEFAULT;
        controllerpistol = CONTROLLERPISTOL_DEFAULT;
        controllerplasmarifle = CONTROLLERPLASMARIFLE_DEFAULT;
        controllerprevweapon = CONTROLLERPREVWEAPON_DEFAULT;
        controllerright = CONTROLLERRIGHT_DEFAULT;
        controllerrocketlauncher = CONTROLLERROCKETLAUNCHER_DEFAULT;
        controllerrotatemode = CONTROLLERROTATEMODE_DEFAULT;
        controllerrun = CONTROLLERRUN_DEFAULT;
        controllerscreenshot = CONTROLLERSCREENSHOT_DEFAULT;
        controllershotgun = CONTROLLERSHOTGUN_DEFAULT;
        controllersizedown = CONTROLLERSIZEDOWN_DEFAULT;
        controllersizeup = CONTROLLERSIZEUP_DEFAULT;
        controllerstrafe = CONTROLLERSTRAFE_DEFAULT;
        controllerstrafeleft = CONTROLLERSTRAFELEFT_DEFAULT;
        controllerstraferight = CONTROLLERSTRAFERIGHT_DEFAULT;
        controllersupershotgun = CONTROLLERSUPERSHOTGUN_DEFAULT;
        controlleruse = CONTROLLERUSE_DEFAULT;
        controlleruse2 = CONTROLLERUSE2_DEFAULT;
        controllerweapon1 = CONTROLLERWEAPON1_DEFAULT;
        controllerweapon2 = CONTROLLERWEAPON2_DEFAULT;
        controllerweapon3 = CONTROLLERWEAPON3_DEFAULT;
        controllerweapon4 = CONTROLLERWEAPON4_DEFAULT;
        controllerweapon5 = CONTROLLERWEAPON5_DEFAULT;
        controllerweapon6 = CONTROLLERWEAPON6_DEFAULT;
        controllerweapon7 = CONTROLLERWEAPON7_DEFAULT;
        controllerzoomin = CONTROLLERZOOMIN_DEFAULT;
        controllerzoomout = CONTROLLERZOOMOUT_DEFAULT;

        // clear all aliases
        for (int i = 0; i < MAXALIASES; i++)
        {
            aliases[i].name[0] = '\0';
            aliases[i].string[0] = '\0';
        }

        M_SaveCVARs();

        C_Output("All CVARs and controls have been reset to their defaults.");
    }
}

static void resetallfunc2(char *cmd, char *parms)
{
    static char resetallstring[320];

    M_StringCopy(resetallstring, "Are you sure you want to reset all CVARs\nand controls to their defaults?",
        sizeof(resetallstring));
    C_HideConsole();
    M_StartButtonMessage(resetallstring, &C_VerifyResetAll, false);

    SDL_StopTextInput();
    S_StartSound(NULL, sfx_swtchn);
}

//
// respawnitems CCMD
//
static void respawnitemsfunc2(char *cmd, char *parms)
{
    if (*parms)
    {
        const int   value = C_LookupValueFromAlias(parms, BOOLVALUEALIAS);

        if (value == 0 && respawnitems)
            respawnitems = false;
        else if (value == 1 && !respawnitems)
            respawnitems = true;
        else
            return;
    }
    else
        respawnitems = !respawnitems;

    if (respawnitems)
    {
        C_Output(s_STSTR_RION);
        HU_SetPlayerMessage(s_STSTR_RION, false, false);
        viewplayer->cheated++;
        stat_cheatsentered = SafeAdd(stat_cheatsentered, 1);
        M_SaveCVARs();
    }
    else
    {
        C_Output(s_STSTR_RIOFF);
        HU_SetPlayerMessage(s_STSTR_RIOFF, false, false);
    }
}

//
// respawnmonsters CCMD
//
static void respawnmonstersfunc2(char *cmd, char *parms)
{
    if (*parms)
    {
        const int   value = C_LookupValueFromAlias(parms, BOOLVALUEALIAS);

        if (value == 0 && respawnmonsters)
            respawnmonsters = false;
        else if (value == 1 && !respawnmonsters)
            respawnmonsters = true;
        else
            return;
    }
    else
        respawnmonsters = !respawnmonsters;

    if (respawnmonsters)
    {
        C_Output(s_STSTR_RMON);
        HU_SetPlayerMessage(s_STSTR_RMON, false, false);
    }
    else
    {
        C_Output(s_STSTR_RMOFF);
        HU_SetPlayerMessage(s_STSTR_RMOFF, false, false);
    }
}

//
// restartmap CCMD
//
static void restartmapfunc2(char *cmd, char *parms)
{
    viewplayer->playerstate = PST_REBORN;

    if (M_StringCompare(mapnum, "E1M4B") || M_StringCompare(mapnum, "E1M8B"))
        M_StringCopy(speciallumpname, mapnum, sizeof(speciallumpname));

    quicksaveslot = -1;

    G_DoLoadLevel();
    C_HideConsoleFast();
}

//
// resurrect CCMD
//
static int      resurrectcmdtype = NUMMOBJTYPES;
static mobj_t   *resurrectcmdmobj;

static bool resurrectfunc1(char *cmd, char *parms)
{
    bool    result = false;
    char    *parm = removenonalpha(parms);

    if (!*parm || gamestate != GS_LEVEL)
        return true;

    resurrectcmdmobj = NULL;

    if (M_StringCompare(parm, "player") || M_StringCompare(parm, "me") || (*playername && M_StringCompare(parm, playername)))
        result = (viewplayer->health <= 0);
    else if (M_StringCompare(parm, "monster") || M_StringCompare(parm, "monsters") || M_StringCompare(parm, "all")
        || M_StringCompare(parm, "friend") || M_StringCompare(parm, "friends")
        || M_StringCompare(parm, "friendly monster") || M_StringCompare(parm, "friendly monsters"))
        result = true;
    else
    {
        for (int i = 0, num = -1; i < NUMMOBJTYPES; i++)
            if (*mobjinfo[i].name1)
            {
                char    *temp1 = removenonalpha(mobjinfo[i].name1);
                char    *temp2 = (*mobjinfo[i].plural1 ? removenonalpha(mobjinfo[i].plural1) : NULL);
                char    *temp3 = (*mobjinfo[i].name2 ? removenonalpha(mobjinfo[i].name2) : NULL);
                char    *temp4 = (*mobjinfo[i].plural2 ? removenonalpha(mobjinfo[i].plural2) : NULL);
                char    *temp5 = (*mobjinfo[i].name3 ? removenonalpha(mobjinfo[i].name3) : NULL);
                char    *temp6 = (*mobjinfo[i].plural3 ? removenonalpha(mobjinfo[i].plural3) : NULL);

                if (M_StringStartsWith(parm, "all"))
                    M_StringReplaceAll(parm, "all", "", false);

                resurrectcmdtype = mobjinfo[i].doomednum;

                if (resurrectcmdtype >= 0
                    && (M_StringCompare(parm, temp1)
                        || (*mobjinfo[i].plural1 && M_StringCompare(parm, temp2))
                        || (*mobjinfo[i].name2 && M_StringCompare(parm, temp3))
                        || (*mobjinfo[i].plural2 && M_StringCompare(parm, temp4))
                        || (*mobjinfo[i].name3 && M_StringCompare(parm, temp5))
                        || (*mobjinfo[i].plural3 && M_StringCompare(parm, temp6))
                        || (sscanf(parm, "%10d", &num) == 1 && num == resurrectcmdtype && num != -1)))
                {
                    if (resurrectcmdtype == WolfensteinSS && !allowwolfensteinss)
                        result = false;
                    else
                        result = (mobjinfo[i].flags & MF_SHOOTABLE);
                }

                if (temp1)
                    free(temp1);

                if (temp2)
                    free(temp2);

                if (temp3)
                    free(temp3);

                if (temp4)
                    free(temp4);

                if (temp5)
                    free(temp5);

                if (temp6)
                    free(temp6);

                if (result)
                    break;
            }

        if (!result)
            for (thinker_t *th = thinkers[th_mobj].cnext; th != &thinkers[th_mobj]; th = th->cnext)
            {
                mobj_t  *mobj = (mobj_t *)th;

                if (*mobj->name)
                {
                    char    *temp = removenonalpha(mobj->name);

                    if (M_StringCompare(parm, temp))
                    {
                        resurrectcmdmobj = mobj;
                        result = true;
                    }

                    free(temp);

                    if (result)
                        break;
                }
            }
    }

    free(parm);
    return result;
}

static void resurrectfunc2(char *cmd, char *parms)
{
    char    *parm = removenonalpha(parms);

    if (!*parm || gamestate != GS_LEVEL)
    {
        const int   i = C_GetIndex(cmd);

        C_ShowFormat(i);
        C_ShowDescription(i);

        if (gamestate != GS_LEVEL)
        {
            if (isdefaultplayername())
                C_Warning(0, NOGAMECCMDWARNING1);
            else
                C_Warning(0, NOGAMECCMDWARNING2, playername, pronoun(personal),
                    (playergender == playergender_other ? "aren't" : "isn't"));
        }
    }
    else
    {
        char    buffer[1024];
        bool    cheated = false;

        if (M_StringCompare(parm, "player") || M_StringCompare(parm, "me") || (*playername && M_StringCompare(parm, playername)))
        {
            P_ResurrectPlayer(initial_health);

            if (isdefaultplayername())
                M_StringCopy(buffer, "You resurrected yourself!", sizeof(buffer));
            else
                M_snprintf(buffer, sizeof(buffer), "%s resurrected %s!", playername, pronoun(reflexive));

            C_PlayerWarning("%s", buffer);
            HU_SetPlayerMessage(buffer, false, false);
            message_warning = true;
            cheated = true;
        }
        else
        {
            bool    friends = (M_StringCompare(parm, "friend") || M_StringCompare(parm, "friends")
                        || M_StringCompare(parm, "friendly monster") || M_StringCompare(parm, "friendly monsters"));
            bool    enemies = (M_StringCompare(parm, "monster") || M_StringCompare(parm, "monsters"));
            bool    all = M_StringCompare(parm, "all");
            int     resurrected = 0;

            if (friends || enemies || all)
            {
                for (int i = 0; i < numsectors; i++)
                    for (mobj_t *thing = sectors[i].thinglist; thing; thing = thing->snext)
                    {
                        const int   flags = thing->flags;

                        if (all || !!(flags & MF_FRIEND) == friends)
                            if ((flags & MF_CORPSE) && !(thing->flags2 & MF2_DECORATION)
                                && thing->type != MT_PLAYER && thing->info->raisestate != S_NULL)
                            {
                                P_ResurrectMobj(thing);
                                resurrected++;

                                if (flags & MF_FRIEND)
                                    cheated = true;
                            }
                    }

                if (resurrected)
                {
                    char    *temp = commify(resurrected);

                    M_snprintf(buffer, sizeof(buffer), "%s%s dead monster%s in this map %s been resurrected.",
                        (resurrected == 1 ? "The " : "All "), temp, (resurrected == 1 ? "" : "s"), (resurrected == 1 ? "has" : "have"));
                    C_Output("%s", buffer);
                    C_HideConsole();
                    HU_SetPlayerMessage(buffer, false, false);
                    free(temp);
                }
                else
                    C_Warning(0, "There are no dead monsters in this map to resurrect.");
            }
            else if (resurrectcmdmobj)
            {
                char    *temp = sentencecase(parm);

                if ((resurrectcmdmobj->flags & MF_CORPSE) && !(resurrectcmdmobj->flags2 & MF2_DECORATION)
                    && resurrectcmdmobj->type != MT_PLAYER && resurrectcmdmobj->info->raisestate != S_NULL)
                    P_ResurrectMobj(resurrectcmdmobj);

                if (resurrectcmdmobj->flags & MF_FRIEND)
                    cheated = true;

                M_snprintf(buffer, sizeof(buffer), "%s was resurrected.", temp);
                C_Output("%s", buffer);
                C_HideConsole();
                HU_SetPlayerMessage(buffer, false, false);
                free(temp);
            }
            else
            {
                const int   type = P_FindDoomedNum(resurrectcmdtype);

                for (int i = 0; i < numsectors; i++)
                    for (mobj_t *thing = sectors[i].thinglist; thing; thing = thing->snext)
                    {
                        if (type == thing->type && (thing->flags & MF_CORPSE) && !(thing->flags2 & MF2_DECORATION)
                            && type != MT_PLAYER && thing->info->raisestate != S_NULL)
                        {
                            P_ResurrectMobj(thing);
                            resurrected++;

                            if (thing->flags & MF_FRIEND)
                                cheated = true;
                        }
                    }

                if (resurrected)
                {
                    char    *temp = commify(resurrected);

                    M_snprintf(buffer, sizeof(buffer), "%s %s %s in this map %s been resurrected.",
                        (resurrected == 1 ? "The" : "All"), temp, (resurrected == 1 ? mobjinfo[type].name1 : mobjinfo[type].plural1),
                        (resurrected == 1 ? "has" : "have"));
                    C_Output("%s", buffer);
                    C_HideConsole();
                    HU_SetPlayerMessage(buffer, false, false);
                    free(temp);
                }
                else
                {
                    if (gamemode != commercial)
                    {
                        if (resurrectcmdtype >= ArchVile && resurrectcmdtype <= MonsterSpawner)
                        {
                            C_Warning(0, "There are no %s in " ITALICS("%s") ".",
                                mobjinfo[type].plural1, gamedescription);
                            return;
                        }
                        else if (gamemode == shareware && (resurrectcmdtype == Cyberdemon || resurrectcmdtype == SpiderMastermind))
                        {
                            C_Warning(0, "There are no %s in the shareware version of " ITALICS("DOOM") ". "
                                "You can buy the full version on " ITALICS("Steam") ", etc.",
                                mobjinfo[type].plural1, gamedescription);
                            return;
                        }
                    }

                    C_Warning(0, "There are no dead %s to resurrect.", mobjinfo[type].plural1);
                }
            }
        }

        free(parm);

        if (cheated)
        {
            viewplayer->cheated++;
            stat_cheatsentered = SafeAdd(stat_cheatsentered, 1);
            M_SaveCVARs();
        }
    }
}

//
// save CCMD
//
static void savefunc2(char *cmd, char *parms)
{
    char    buffer[1024];

    if (!*parms)
    {
        const int   i = C_GetIndex(cmd);

        C_ShowFormat(i);
        C_ShowDescription(i);
        return;
    }

    if (strlen(parms) == 1 && parms[0] >= '1' && parms[0] <= '8')
    {
        M_snprintf(buffer, sizeof(buffer), "%s" DOOMRETRO_SAVEGAME, savegamefolder, parms[0] - '1');
        G_SaveGame(parms[0] - '1', maptitle, buffer);
    }
    else
    {
        M_snprintf(buffer, sizeof(buffer), "%s%s%s",
            (M_StringStartsWith(parms, savegamefolder) ? "" : savegamefolder),
            parms, (M_StringEndsWith(parms, ".save") ? "" : ".save"));
        G_SaveGame(-1, maptitle, buffer);
    }
}

//
// spawn CCMD
//
static int  spawncmdtype = NUMMOBJTYPES;
static bool spawncmdfriendly;

static bool spawnfunc1(char *cmd, char *parms)
{
    bool    result = false;
    char    *parm = removenonalpha(parms);

    if (!*parm)
        return true;

    if (gamestate == GS_LEVEL)
    {
        int num = -1;

        spawncmdfriendly = false;

        if (M_StringCompare(parm, "unfriendlydog"))
            M_StringReplaceAll(parm, "unfriendly", "", false);
        else if (M_StringCompare(parm, "dog"))
            spawncmdfriendly = true;
        else if (M_StringStartsWith(parm, "friendly"))
        {
            spawncmdfriendly = true;
            M_StringReplaceAll(parm, "friendly", "", false);
        }
        else if (M_StringStartsWith(parm, "unfriendly"))
            M_StringReplaceAll(parm, "unfriendly", "", false);

        for (int i = 0; i < NUMMOBJTYPES; i++)
        {
            char    *temp1 = (*mobjinfo[i].name1 ? removenonalpha(mobjinfo[i].name1) : NULL);
            char    *temp2 = (*mobjinfo[i].name2 ? removenonalpha(mobjinfo[i].name2) : NULL);
            char    *temp3 = (*mobjinfo[i].name3 ? removenonalpha(mobjinfo[i].name3) : NULL);

            spawncmdtype = mobjinfo[i].doomednum;

            if (spawncmdtype >= 0
                && ((*mobjinfo[i].name1 && M_StringCompare(parm, temp1))
                    || (*mobjinfo[i].name2 && M_StringCompare(parm, temp2))
                    || (*mobjinfo[i].name3 && M_StringCompare(parm, temp3))
                    || (sscanf(parm, "%10d", &num) == 1 && num == spawncmdtype && num != -1))
                && (!spawncmdfriendly || (mobjinfo[i].flags & MF_SHOOTABLE)))
                result = true;

            if (temp1)
                free(temp1);

            if (temp2)
                free(temp2);

            if (temp3)
                free(temp3);

            if (result)
                break;
        }
    }

    free(parm);
    return result;
}

static void spawnfunc2(char *cmd, char *parms)
{
    char    *parm = removenonalpha(parms);

    if (!*parm)
    {
        const int   i = C_GetIndex(cmd);

        C_ShowFormat(i);
        C_ShowDescription(i);

        if (gamestate != GS_LEVEL)
        {
            if (isdefaultplayername())
                C_Warning(0, NOGAMECCMDWARNING1);
            else
                C_Warning(0, NOGAMECCMDWARNING2, playername, pronoun(personal),
                    (playergender == playergender_other ? "aren't" : "isn't"));
        }
    }
    else
    {
        bool        spawn = true;
        const int   type = P_FindDoomedNum(spawncmdtype);
        char        buffer[128];

        if (gamemode != commercial)
        {
            if (spawncmdtype >= ArchVile && spawncmdtype <= MonsterSpawner && !REKKR)
            {
                M_StringCopy(buffer, mobjinfo[type].plural1, sizeof(buffer));

                if (!*buffer)
                    M_snprintf(buffer, sizeof(buffer), "%ss", mobjinfo[type].name1);

                buffer[0] = toupper(buffer[0]);
                C_Warning(0, "%s can't be spawned in " ITALICS("%s") ".", buffer, gamedescription);
                spawn = false;
            }

            if (gamemode == shareware
                && (spawncmdtype == SpiderMastermind
                    || spawncmdtype == Cyberdemon
                    || spawncmdtype == CellPack
                    || spawncmdtype == HelperDog
                    || spawncmdtype == PlasmaRifle
                    || spawncmdtype == BFG9000
                    || spawncmdtype == Berserk
                    || spawncmdtype == Cell))
            {
                M_StringCopy(buffer, mobjinfo[type].plural1, sizeof(buffer));

                if (!*buffer)
                    M_snprintf(buffer, sizeof(buffer), "%ss", mobjinfo[type].name1);

                buffer[0] = toupper(buffer[0]);
                C_Warning(0, "%s can't be spawned in the shareware version of " ITALICS("DOOM") ". "
                    "You can buy the full version on " ITALICS("Steam") ", etc.", buffer);
                spawn = false;
            }
        }
        else if (spawncmdtype == WolfensteinSS && !allowwolfensteinss)
        {
            M_snprintf(buffer, sizeof(buffer), "%ss", mobjinfo[type].name1);
            buffer[0] = toupper(buffer[0]);

            if (bfgedition)
                C_Warning(0, "%s can't be spawned in " ITALICS("%s (BFG Edition)") ".",
                    buffer, gamedescription);
            else
                C_Warning(0, "%s can't be spawned in " ITALICS("%s") ".",
                    buffer, gamedescription);

            spawn = false;
        }

        if (spawn)
        {
            const int       distance = 80;
            const fixed_t   forwardx = viewcos * distance;
            const fixed_t   forwardy = viewsin * distance;
            fixed_t         x = viewx + forwardx;
            fixed_t         y = viewy + forwardy;
            sector_t        *sector = R_PointInSubsector(x, y)->sector;
            bool            found = false;

            if (mobjinfo[type].height <= sector->ceilingheight - sector->floorheight
                && !P_CheckLineSide(viewplayer->mo, x, y))
                found = true;
            else
                for (int i = 15; i >= 1; i--)
                {
                    const double    phi = i * (M_PI / 8.0);
                    const double    cosine = cos(phi);
                    const double    sine = sin(phi);

                    x = viewx + (fixed_t)(forwardx * cosine - forwardy * sine);
                    y = viewy + (fixed_t)(forwardx * sine + forwardy * cosine);

                    sector = R_PointInSubsector(x, y)->sector;

                    if (mobjinfo[type].height <= sector->ceilingheight - sector->floorheight
                        && !P_CheckLineSide(viewplayer->mo, x, y))
                    {
                        found = true;
                        break;
                    }
                }

            if (found)
            {
                mapthing_t  mthing = { 0 };
                mobj_t      *thing;

                mthing.x = x >> FRACBITS;
                mthing.y = y >> FRACBITS;
                mthing.type = spawncmdtype;
                mthing.options = (MTF_EASY | MTF_NORMAL | MTF_HARD);

                if ((thing = P_SpawnMapThing(&mthing, true)))
                {
                    const int   flags = thing->flags;
                    mobj_t      *fog;

                    thing->angle = R_PointToAngle2(x, y, viewx, viewy);
                    thing->id = thingid++;

                    if (flags & MF_SHOOTABLE)
                    {
                        if (spawncmdfriendly)
                        {
                            thing->flags |= MF_FRIEND;
                            stat_cheatsentered = SafeAdd(stat_cheatsentered, 1);
                            M_SaveCVARs();
                        }

                        thing->flags2 |= MF2_SPAWNEDBYPLAYER;
                        massacre = false;

                        if (flags & MF_NOGRAVITY)
                        {
                            thing->z = thing->floorz + 32 * FRACUNIT;
                            thing->flags2 &= ~MF2_FEETARECLIPPED;
                        }

                        if (!(viewplayer->cheats & CF_FREEZE))
                        {
                            fog = P_SpawnMobj(x, y, ((flags & MF_NOGRAVITY) ? thing->z : ONFLOORZ), MT_TFOG);
                            fog->angle = thing->angle;
                            S_StartSound(fog, sfx_telept);
                        }
                    }
                    else
                    {
                        if (flags & MF_SPECIAL)
                        {
                            stat_cheatsentered = SafeAdd(stat_cheatsentered, 1);
                            M_SaveCVARs();
                        }

                        if (!(viewplayer->cheats & CF_FREEZE))
                        {
                            fog = P_SpawnMobj(x, y, ((flags & MF_SPAWNCEILING) ? ONCEILINGZ :
                                ((thing->flags2 & MF2_FLOATBOB) ? thing->floorz + 14 * FRACUNIT : ONFLOORZ)), MT_IFOG);
                            fog->angle = thing->angle;
                            S_StartSound(fog, sfx_itmbk);
                        }
                    }

                    if (thing->type == MT_MISC0 || thing->type == MT_MISC1)
                        C_PlayerMessage("%s spawned %s.", C_GetPlayerName(), mobjinfo[type].name1);
                    else
                        C_PlayerMessage("%s spawned %s %s%s.",
                            C_GetPlayerName(), (isvowel(mobjinfo[type].name1[0]) ? "an" : "a"),
                            (spawncmdfriendly ? "friendly " : ""), mobjinfo[type].name1);

                    C_HideConsole();
                }
            }
            else
            {
                if (spawncmdtype == GreenArmor || spawncmdtype == BlueArmor)
                    C_Warning(0, "There isn't enough room around %s to spawn %s!",
                        C_GetPlayerName(), mobjinfo[type].name1);
                else
                    C_Warning(0, "There isn't enough room around %s to spawn %s %s%s!",
                        C_GetPlayerName(), (isvowel(mobjinfo[type].name1[0]) ? "an" : "a"),
                        (spawncmdfriendly ? "friendly " : ""), mobjinfo[type].name1);
            }
        }

        free(parm);
    }
}

//
// take CCMD
//
static bool takefunc1(char *cmd, char *parms)
{
    bool    result = false;
    char    *parm = removenonalpha(parms);

    if (!*parm || gamestate != GS_LEVEL)
        return true;

    if (M_StringCompare(parm, "all") || M_StringCompare(parm, "everything")
        || M_StringCompare(parm, "health") || M_StringCompare(parm, "allhealth")
        || M_StringCompare(parm, "weapons") || M_StringCompare(parm, "allweapons")
        || M_StringCompare(parm, "ammo") || M_StringCompare(parm, "allammo")
        || M_StringCompare(parm, "ammunition") || M_StringCompare(parm, "allammunition")
        || M_StringCompare(parm, "armor") || M_StringCompare(parm, "allarmor")
        || M_StringCompare(parm, "armour") || M_StringCompare(parm, "allarmour")
        || M_StringCompare(parm, "keys") || M_StringCompare(parm, "allkeys")
        || M_StringCompare(parm, "keycards") || M_StringCompare(parm, "allkeycards")
        || M_StringCompare(parm, "skullkeys") || M_StringCompare(parm, "allskullkeys")
        || M_StringCompare(parm, "pistol") || M_StringCompare(parm, "powerups"))
        result = true;
    else
        for (int i = 0, num = -1; i < NUMMOBJTYPES; i++)
        {
            char    *temp1 = (*mobjinfo[i].name1 ? removenonalpha(mobjinfo[i].name1) : NULL);
            char    *temp2 = (*mobjinfo[i].name2 ? removenonalpha(mobjinfo[i].name2) : NULL);
            char    *temp3 = (*mobjinfo[i].name3 ? removenonalpha(mobjinfo[i].name3) : NULL);

            if ((mobjinfo[i].flags & MF_SPECIAL)
                && ((*mobjinfo[i].name1 && M_StringCompare(parm, temp1))
                    || (*mobjinfo[i].name2 && M_StringCompare(parm, temp2))
                    || (*mobjinfo[i].name3 && M_StringCompare(parm, temp3))
                    || (sscanf(parm, "%10d", &num) == 1 && num == mobjinfo[i].doomednum && num != -1)))
                result = true;

            if (temp1)
                free(temp1);

            if (temp2)
                free(temp2);

            if (temp3)
                free(temp3);

            if (result)
                break;
        }

    free(parm);
    return result;
}

static void takefunc2(char *cmd, char *parms)
{
    char    *parm = removenonalpha(parms);

    if (!*parm || gamestate != GS_LEVEL)
    {
        const int   i = C_GetIndex(cmd);

        C_ShowFormat(i);
        C_ShowDescription(i);

        if (gamestate != GS_LEVEL)
        {
            if (isdefaultplayername())
                C_Warning(0, NOGAMECCMDWARNING1);
            else
                C_Warning(0, NOGAMECCMDWARNING2, playername, pronoun(personal),
                    (playergender == playergender_other ? "aren't" : "isn't"));
        }
    }
    else
    {
        bool    result = false;

        if (M_StringCompare(parm, "all") || M_StringCompare(parm, "everything"))
        {
            P_TakeBackpack();

            if (viewplayer->health > initial_health)
            {
                healthcvar = true;
                P_DamageMobj(viewplayer->mo, viewplayer->mo, viewplayer->mo, viewplayer->health - initial_health, false, false, false);
                healthcvar = false;
                result = true;
            }

            for (weapontype_t i = wp_pistol; i < NUMWEAPONS; i++)
                if (viewplayer->weaponowned[i])
                {
                    viewplayer->weaponowned[i] = false;
                    oldweaponsowned[i] = false;
                    result = true;
                }

            viewplayer->pendingweapon = wp_fist;

            for (ammotype_t i = 0; i < NUMAMMO; i++)
                if (viewplayer->ammo[i])
                {
                    viewplayer->ammo[i] = 0;
                    result = true;
                }

            if (viewplayer->armor)
            {
                P_AnimateArmor(viewplayer->armor);
                viewplayer->armor = 0;
                viewplayer->armortype = armortype_none;
                result = true;
            }

            for (int i = 0; i < NUMCARDS; i++)
                if (viewplayer->cards[i] > 0)
                {
                    viewplayer->cards[i] = 0;
                    result = true;
                }

            for (int i = 0; i < NUMPOWERS; i++)
                if (viewplayer->powers[i])
                {
                    viewplayer->powers[i] = (i == pw_allmap || i == pw_strength ? 0 : STARTFLASHING);
                    result = true;
                }

            if (result)
            {
                C_PlayerWarning("Everything was taken from %s!", C_GetPlayerName());
                C_HideConsole();
            }
            else if (isdefaultplayername())
                C_Warning(0, "You don't have anything!");
            else
                C_Warning(0, "%s doesn't have anything!", playername);
        }
        else if (M_StringCompare(parm, "health") || M_StringCompare(parm, "allhealth"))
        {
            if (viewplayer->health > 0 && !(viewplayer->cheats & CF_GODMODE) && !viewplayer->powers[pw_invulnerability])
            {
                healthcvar = true;
                P_DamageMobj(viewplayer->mo, viewplayer->mo, viewplayer->mo,
                    viewplayer->health - !!(viewplayer->cheats & CF_BUDDHA), false, false, false);
                healthcvar = false;

                if (isdefaultplayername())
                    C_PlayerWarning("You killed yourself!");
                else
                    C_PlayerWarning("%s killed %s!", playername, pronoun(reflexive));

                C_HideConsole();
            }
            else
            {
                if (isdefaultplayername())
                    C_Warning(0, "You are already dead!");
                else
                    C_Warning(0, "%s is already dead!", playername);
            }
        }
        else if (M_StringCompare(parm, "weapons") || M_StringCompare(parm, "allweapons"))
        {
            for (weapontype_t i = wp_pistol; i < NUMWEAPONS; i++)
                if (viewplayer->weaponowned[i])
                {
                    viewplayer->weaponowned[i] = false;
                    oldweaponsowned[i] = false;
                    result = true;
                }

            viewplayer->pendingweapon = wp_fist;

            if (result)
            {
                C_PlayerWarning("All weapons have been taken from %s!", C_GetPlayerName());
                C_HideConsole();
            }
            else if (isdefaultplayername())
                C_Warning(0, "You don't have any weapons!");
            else
                C_Warning(0, "%s doesn't have any weapons!", playername);
        }
        else if (M_StringCompare(parm, "ammo") || M_StringCompare(parm, "allammo")
                || M_StringCompare(parm, "ammunition") || M_StringCompare(parm, "allammunition"))
        {
            for (ammotype_t i = 0; i < NUMAMMO; i++)
                if (viewplayer->ammo[i])
                {
                    viewplayer->ammo[i] = 0;
                    result = true;
                }

            viewplayer->pendingweapon = wp_fist;

            if (result)
            {
                C_PlayerWarning("All ammo was taken from %s!", C_GetPlayerName());
                C_HideConsole();
            }
            else if (isdefaultplayername())
                C_Warning(0, "You don't have any ammo!");
            else
                C_Warning(0, "%s doesn't have any ammo!", playername);
        }
        else if (M_StringCompare(parm, "armor") || M_StringCompare(parm, "allarmor")
                || M_StringCompare(parm, "armour") || M_StringCompare(parm, "allarmour"))
        {
            if (viewplayer->armor)
            {
                P_AnimateArmor(viewplayer->armor);
                viewplayer->armor = 0;
                viewplayer->armortype = armortype_none;
                C_PlayerWarning("All %s was taken from %s!",
                    (english == english_american ? "armor" : "armour"), C_GetPlayerName());
                C_HideConsole();
            }
            else if (isdefaultplayername())
                C_Warning(0, "You don't have any %s!",
                    (english == english_american ? "armor" : "armour"));
            else
                C_Warning(0, "%s doesn't have any %s!",
                    playername, (english == english_american ? "armor" : "armour"));
        }
        else if (M_StringCompare(parm, "keys") || M_StringCompare(parm, "allkeys"))
        {
            for (int i = 0; i < NUMCARDS; i++)
                if (viewplayer->cards[i] > 0)
                {
                    viewplayer->cards[i] = 0;
                    result = true;
                }

            if (result)
            {
                P_LookForCards();
                C_PlayerWarning("All keycards and skull keys have been taken from %s!", C_GetPlayerName());
                C_HideConsole();
            }
            else if (isdefaultplayername())
                C_Warning(0, "You don't have any keycards or skull keys!");
            else
                C_Warning(0, "%s doesn't have any keycards or skull keys!", playername);
        }
        else if (M_StringCompare(parm, "keycards") || M_StringCompare(parm, "allkeycards"))
        {
            if (viewplayer->cards[it_bluecard] > 0 || viewplayer->cards[it_redcard] > 0 || viewplayer->cards[it_yellowcard] > 0)
            {
                viewplayer->cards[it_bluecard] = 0;
                viewplayer->cards[it_redcard] = 0;
                viewplayer->cards[it_yellowcard] = 0;
                P_LookForCards();
                C_PlayerWarning("All keycards have been taken from %s!", C_GetPlayerName());
                C_HideConsole();
            }
            else if (isdefaultplayername())
                C_Warning(0, "You don't have any keycards!");
            else
                C_Warning(0, "%s doesn't have any keycards!", playername);
        }
        else if (M_StringCompare(parm, "skullkeys") || M_StringCompare(parm, "allskullkeys"))
        {
            if (viewplayer->cards[it_blueskull] > 0 || viewplayer->cards[it_redskull] > 0 || viewplayer->cards[it_yellowskull] > 0)
            {
                viewplayer->cards[it_blueskull] = 0;
                viewplayer->cards[it_redskull] = 0;
                viewplayer->cards[it_yellowskull] = 0;
                P_LookForCards();
                C_PlayerWarning("All skull keys have been taken from %s!", C_GetPlayerName());
                C_HideConsole();
            }
            else if (isdefaultplayername())
                C_Warning(0, "You don't have any skull keys!");
            else
                C_Warning(0, "%s doesn't have any skull keys!", playername);
        }
        else if (M_StringCompare(parm, "pistol"))
        {
            if (viewplayer->weaponowned[wp_pistol])
            {
                viewplayer->weaponowned[wp_pistol] = false;
                oldweaponsowned[wp_pistol] = false;

                P_CheckAmmo(viewplayer->readyweapon);

                C_PlayerWarning("A pistol was taken from %s!", C_GetPlayerName());
                C_HideConsole();
            }
            else if (isdefaultplayername())
                C_Warning(0, "You don't have a pistol!");
            else
                C_Warning(0, "%s doesn't have a pistol!", playername);
        }
        else if (M_StringCompare(parm, "powerups"))
        {
            for (int i = 0; i < NUMPOWERS; i++)
                if (viewplayer->powers[i])
                {
                    viewplayer->powers[i] = 0;
                    result = true;
                }

            if (result)
            {
                C_PlayerWarning("All power-ups have been taken from %s!", C_GetPlayerName());
                C_HideConsole();
            }
            else if (isdefaultplayername())
                C_Warning(0, "You don't have any power-ups!");
            else
                C_Warning(0, "%s doesn't have any power-ups!", playername);
        }
        else
            for (int i = 0, num = -1; i < NUMMOBJTYPES; i++)
            {
                char    *temp1 = (*mobjinfo[i].name1 ? removenonalpha(mobjinfo[i].name1) : NULL);
                char    *temp2 = (*mobjinfo[i].name2 ? removenonalpha(mobjinfo[i].name2) : NULL);
                char    *temp3 = (*mobjinfo[i].name3 ? removenonalpha(mobjinfo[i].name3) : NULL);

                if ((mobjinfo[i].flags & MF_SPECIAL)
                    && ((*mobjinfo[i].name1 && M_StringCompare(parm, temp1))
                        || (*mobjinfo[i].name2 && M_StringCompare(parm, temp2))
                        || (*mobjinfo[i].name3 && M_StringCompare(parm, temp3))
                        || (sscanf(parm, "%10d", &num) == 1 && num == mobjinfo[i].doomednum && num != -1)))
                {
                    if (P_TakeSpecialThing(i))
                    {
                        if (i == MT_MISC0 || i == MT_MISC1)
                        {
                            char    *temp4 = sentencecase(mobjinfo[i].name1);

                            C_PlayerWarning("%s was taken from %s!", temp4, C_GetPlayerName());
                            free(temp4);
                        }
                        else
                            C_PlayerWarning("%s %s was taken from %s!",
                                (isvowel(mobjinfo[i].name1[0]) ? "An" : "A"), mobjinfo[i].name1, C_GetPlayerName());

                        if (viewplayer->health <= 0)
                        {
                            healthcvar = true;
                            P_KillMobj(viewplayer->mo, NULL, viewplayer->mo, false);
                            healthcvar = false;
                        }

                        C_HideConsole();
                        result = true;
                    }
                    else if (isdefaultplayername())
                        C_Warning(0, "You don't have %s %s!",
                            (isvowel(mobjinfo[i].name1[0]) ? "an" : "a"), mobjinfo[i].name1);
                    else
                        C_Warning(0, "%s doesn't have %s %s!",
                            playername, (isvowel(mobjinfo[i].name1[0]) ? "an" : "a"), mobjinfo[i].name1);
                }

                if (temp1)
                    free(temp1);

                if (temp2)
                    free(temp2);

                if (temp3)
                    free(temp3);

                if (result)
                    break;
            }

        free(parm);
    }
}

//
// teleport CCMD
//
static bool teleportfunc1(char *cmd, char *parms)
{
    if (!*parms || gamestate != GS_LEVEL)
        return true;
    else
    {
        fixed_t x, y;

        return (sscanf(parms, "%10d %10d", &x, &y) == 2);
    }
}

static void teleportfunc2(char *cmd, char *parms)
{
    if (!*parms || gamestate != GS_LEVEL)
    {
        const int   i = C_GetIndex(cmd);

        C_ShowFormat(i);
        C_ShowDescription(i);

        if (gamestate != GS_LEVEL)
        {
            if (isdefaultplayername())
                C_Warning(0, NOGAMECCMDWARNING1);
            else
                C_Warning(0, NOGAMECCMDWARNING2, playername, pronoun(personal),
                    (playergender == playergender_other ? "aren't" : "isn't"));
        }
    }
    else
    {
        fixed_t x, y;
        fixed_t z = ONFLOORZ;

        if (sscanf(parms, "%10d %10d %10d", &x, &y, &z) >= 2)
        {
            mobj_t          *mo = viewplayer->mo;
            const fixed_t   oldx = viewx;
            const fixed_t   oldy = viewy;
            const fixed_t   oldz = mo->z;

            x <<= FRACBITS;
            y <<= FRACBITS;

            if (z != ONFLOORZ)
                z <<= FRACBITS;

            if (x == oldx && y == oldy)
            {
                if (isdefaultplayername())
                    C_Warning(0, "You are already there!");
                else
                    C_Warning(0, "%s is already there!", playername);
            }
            else if (P_TeleportMove(mo, x, y, z, false))
            {
                // spawn teleport fog at source
                mobj_t  *fog = P_SpawnMobj(oldx, oldy, oldz, MT_TFOG);

                fog->angle = viewangle;
                S_StartSound(fog, sfx_telept);

                // spawn teleport fog at destination
                viewplayer->viewz = mo->z + viewplayer->viewheight;
                fog = P_SpawnMobj(x + 20 * viewcos, y + 20 * viewsin, z, MT_TFOG);
                fog->angle = viewangle;
                S_StartSound(fog, sfx_telept);

                mo->reactiontime = 18;
                viewplayer->psprites[ps_weapon].sx = 0;
                viewplayer->psprites[ps_weapon].sy = WEAPONTOP;
                viewplayer->momx = 0;
                viewplayer->momy = 0;
                mo->momx = 0;
                mo->momy = 0;
                mo->momz = 0;
                viewplayer->pitch = 0;
                viewplayer->oldpitch = 0;
                viewplayer->recoil = 0;
                viewplayer->oldrecoil = 0;

                if (r_teleportzoom)
                {
                    teleportzoomduration = 350;
                    teleportzoom = I_GetTimeMS() + teleportzoomduration;
                    setsizeneeded = true;
                }

                viewplayer->cheated++;
                stat_cheatsentered = SafeAdd(stat_cheatsentered, 1);
                M_SaveCVARs();

                if (z == ONFLOORZ)
                {
                    if (isdefaultplayername())
                        C_PlayerMessage("You were teleported to (%i, %i).", x >> FRACBITS, y >> FRACBITS);
                    else
                        C_PlayerMessage("%s was teleported to (%i, %i).", playername, x >> FRACBITS, y >> FRACBITS);
                }
                else
                {
                    if (isdefaultplayername())
                        C_PlayerMessage("You were teleported to (%i, %i, %i).",
                            x >> FRACBITS, y >> FRACBITS, z >> FRACBITS);
                    else
                        C_PlayerMessage("%s was teleported to (%i, %i, %i).",
                            playername, x >> FRACBITS, y >> FRACBITS, z >> FRACBITS);
                }

                C_HideConsoleFast();
            }
            else
            {
                if (isdefaultplayername())
                    C_Warning(0, "You can't teleport to (%i, %i%s)!",
                        x >> FRACBITS, y >> FRACBITS,
                        (z == ONFLOORZ ? "" : M_StringJoin(", ", commify(z >> FRACBITS), NULL)));
                else
                    C_Warning(0, "%s can't teleport to (%i, %i%s)!",
                        playername, x >> FRACBITS, y >> FRACBITS,
                        (z == ONFLOORZ ? "" : M_StringJoin(", ", commify(z >> FRACBITS), NULL)));
            }
        }
    }
}

//
// thinglist CCMD
//
static void thinglistfunc2(char *cmd, char *parms)
{
    const int   tabs[MAXTABS] = { 50, 100, 400, 550 };

    C_Header(tabs, thinglist, THINGLISTHEADER);

    for (thinker_t *th = thinkers[th_mobj].cnext; th != &thinkers[th_mobj]; th = th->cnext)
    {
        mobj_t      *mobj = (mobj_t *)th;
        mobjtype_t  type = mobj->type;
        const int   flags = mobj->flags;
        char        name[128];
        char        *temp1;
        char        *temp2;
        const int   angle = (int)(mobj->angle * 90.0 / ANG90);

        if (mobj == viewplayer->mo)
            M_StringCopy(name, C_GetPlayerName(), sizeof(name));
        else if (*mobj->name)
            M_StringCopy(name, mobj->name, sizeof(name));
        else
            M_snprintf(name, sizeof(name), "%s%s",
                ((flags & MF_CORPSE) && !(mobj->flags2 & MF2_DECORATION) ? "dead " :
                    ((flags & MF_FRIEND) && type != MT_PLAYER ? "friendly " :
                    ((flags & MF_DROPPED) ? "dropped " : ""))),
                (type == MT_PLAYER ? "voodoo doll" : (*mobj->info->name1 ? mobj->info->name1 : "\x96")));

        temp1 = commify(mobj->info->doomednum);
        temp2 = sentencecase(name);

        if (mobj->id >= 0)
            C_TabbedOutput(tabs, MONOSPACED("%4i") ".\t%s\t%s\t(%i, %i, %i)\t%i\xB0",
                mobj->id, temp1, temp2, mobj->x >> FRACBITS, mobj->y >> FRACBITS,
                mobj->z >> FRACBITS, (angle == 360 ? 0 : angle));
        else
            C_TabbedOutput(tabs, "\t%s\t%s\t(%i, %i, %i)\t%i\xB0",
                temp1, temp2, mobj->x >> FRACBITS, mobj->y >> FRACBITS,
                mobj->z >> FRACBITS, (angle == 360 ? 0 : angle));

        free(temp1);
        free(temp2);
    }
}

//
// timer CCMD
//
static void timerfunc2(char *cmd, char *parms)
{
    if (!*parms)
    {
        const int   i = C_GetIndex(cmd);

        C_ShowFormat(i);
        C_ShowDescription(i);
    }
    else
    {
        int value;

        M_StringReplaceAll(parms, ",", "", false);

        if (M_StringCompare(parms, "off"))
            value = 0;
        else if (sscanf(parms, "%10d", &value) != 1)
            return;

        value = BETWEEN(0, value, TIMERMAXMINUTES);

        if (!togglingvanilla)
        {
            if (!value)
            {
                if (timer)
                    C_Output("The timer has been cleared.");
                else
                    C_Warning(0, "No timer has been set.");
            }
            else
            {
                char    *temp1 = commify(value);
                char    *temp2 = titlecase(playername);

                if (timer)
                    C_Output("The timer has been %s to %s minute%s. "
                        "%s will automatically exit each map once the timer runs out.",
                        (value == timer ? "reset" : "changed"), temp1, (value == 1 ? "" : "s"),
                        (isdefaultplayername() ? "You" : temp2));
                else
                    C_Output("A timer has been set for %s minute%s. "
                        "%s will automatically exit each map once the timer runs out.",
                        temp1, (value == 1 ? "" : "s"),
                        (isdefaultplayername() ? "You" : temp2));

                free(temp1);
                free(temp2);
            }
        }

        P_SetTimer(value);
    }
}

//
// toggle CCMD
//
static void togglefunc2(char *cmd, char *parms)
{
    if (!*parms)
    {
        const int   i = C_GetIndex(cmd);

        C_ShowFormat(i);
        C_ShowDescription(i);
        return;
    }

    togglingcvar = true;

    for (int i = 0; *consolecmds[i].name; i++)
    {
        const int   flags = consolecmds[i].flags;

        if (consolecmds[i].type == CT_CVAR && M_StringCompare(parms, consolecmds[i].name)
            && !(flags & CF_READONLY) && (flags & CF_BOOLEAN))
        {
            char    *temp1 = C_LookupAliasFromValue(!(*(bool *)consolecmds[i].variable), consolecmds[i].aliases);
            char    *temp2 = M_StringJoin(parms, " ", temp1, NULL);

            C_ValidateInput(temp2);
            C_Output("The " BOLD("%s") " CVAR has been toggled " BOLD("%s") ".", parms, temp1);
            free(temp1);
            free(temp2);
            M_SaveCVARs();
            break;
        }
    }

    togglingcvar = false;
}

//
// unbind CCMD
//
static void unbindfunc2(char *cmd, char *parms)
{
    if (!*parms)
    {
        const int   i = C_GetIndex(cmd);

        C_ShowFormat(i);
        C_ShowDescription(i);
        return;
    }

    bindfunc2(cmd, parms);
}

//
// vanilla CCMD
//
static void vanillafunc2(char *cmd, char *parms)
{
    static bool buddhamode;
    static bool hud;
    static bool nomousestrafe;
    static bool showfps;

    if (*parms)
    {
        const int   value = C_LookupValueFromAlias(parms, BOOLVALUEALIAS);

        if (value == 0 && vanilla)
            vanilla = false;
        else if (value == 1 && !vanilla)
            vanilla = true;
        else
            return;
    }
    else
        vanilla = !vanilla;

    nobindoutput = true;
    togglingvanilla = true;

    if (vanilla)
    {
        hud = r_hud;
        showfps = vid_showfps;

        SC_Open(W_CheckNumForName("VANILLA"));

        while (SC_GetString())
        {
            char    *temp1 = M_StringDuplicate(sc_String);
            char    *temp2;

            SC_GetString();
            temp2 = M_StringJoin(temp1, " ", sc_String, NULL);
            C_ValidateInput(temp2);
            free(temp1);
            free(temp2);
        }

        SC_Close();

        if ((buddhamode = (viewplayer->cheats & CF_BUDDHA)))
            viewplayer->cheats &= ~CF_BUDDHA;

        if ((nomousestrafe = (mousestrafe != MOUSESTRAFE_DEFAULT)))
            bindfunc2("bind", "mouse2 +strafe");
    }
    else
    {
        if (buddhamode)
            viewplayer->cheats |= CF_BUDDHA;

        r_hud = hud;
        vid_showfps = showfps;

        if (nomousestrafe)
            bindfunc2("unbind", "mouse2 +strafe");

        M_LoadCVARs(configfile);
    }

    if (gamestate == GS_LEVEL)
        C_HideConsole();

    I_RestartGraphics(false);
    AM_InitPixelSize();

    nobindoutput = false;
    togglingvanilla = false;

    if (vanilla)
    {
        C_Output(s_STSTR_VON);
        HU_SetPlayerMessage(s_STSTR_VON, false, false);

        if (isdefaultplayername())
            C_Warning(0, "Changes to any CVARs won't be saved while you are in vanilla mode.");
        else
            C_Warning(0, "Changes to any CVARs won't be saved while %s is in vanilla mode.",
                playername);
    }
    else
    {
        C_Output(s_STSTR_VOFF);
        HU_SetPlayerMessage(s_STSTR_VOFF, false, false);
    }
}

//
// wiki CCMD
//
static void wikifunc2(char *cmd, char *parms)
{
    C_Output("Opening the " ITALICS(DOOMRETRO_WIKINAME) "...");

#if defined(_WIN32)
    D_OpenURLInBrowser(DOOMRETRO_WIKIURL, "The " ITALICS(DOOMRETRO_WIKINAME) " wouldn't open!");
#elif defined(__linux__) || defined(__FreeBSD__) || defined(__HAIKU__)
    if (!M_system("xdg-open " DOOMRETRO_WIKIURL))
        C_Warning(0, "The " ITALICS(DOOMRETRO_WIKINAME) " wouldn't open!");
#elif defined(__APPLE__)
    if (!M_system("open " DOOMRETRO_WIKIURL))
        C_Warning(0, "The " ITALICS(DOOMRETRO_WIKINAME) " wouldn't open!");
#endif
}

//
// bool CVARs
//
static bool boolfunc1(char *cmd, char *parms)
{
    return (!*parms || C_LookupValueFromAlias(parms, BOOLVALUEALIAS) != INT_MIN);
}

static void boolfunc2(char *cmd, char *parms)
{
    for (int i = 0; *consolecmds[i].name; i++)
        if (M_StringCompare(cmd, consolecmds[i].name) && consolecmds[i].type == CT_CVAR && (consolecmds[i].flags & CF_BOOLEAN))
        {
            if (*parms && !(consolecmds[i].flags & CF_READONLY))
            {
                const int   value = C_LookupValueFromAlias(parms, BOOLVALUEALIAS);
                char        *temp1 = C_LookupAliasFromValue(*(bool *)consolecmds[i].variable, consolecmds[i].aliases);

                if (value == *(bool *)consolecmds[i].variable)
                {
                    if (!resettingcvar && !togglingvanilla)
                    {
                        if (value == (bool)consolecmds[i].defaultnumber)
                            C_Warning(0, INTEGERCVARSAMEDEFAULTWARNING, consolecmds[i].name, temp1);
                        else
                            C_Warning(0, INTEGERCVARSAMEWARNING, consolecmds[i].name, temp1);
                    }
                }
                else if (value == 0 || value == 1)
                {
                    char    *temp2 = C_LookupAliasFromValue(value, consolecmds[i].aliases);

                    if (!resettingcvar && !togglingvanilla)
                    {
                        if (*(bool *)consolecmds[i].variable == (bool)consolecmds[i].defaultnumber)
                            C_Output(INTEGERCVARCHANGEDFROMDEFAULT,
                                C_GetPlayerName(), consolecmds[i].name, temp1, temp2);
                        else if (value == (bool)consolecmds[i].defaultnumber)
                            C_Output(INTEGERCVARCHANGEDTODEFAULT,
                                C_GetPlayerName(), consolecmds[i].name, temp1, temp2);
                        else
                            C_Output(INTEGERCVARCHANGED,
                                C_GetPlayerName(), consolecmds[i].name, temp1, temp2);
                    }

                    *(bool *)consolecmds[i].variable = value;
                    M_SaveCVARs();

                    free(temp2);
                }

                free(temp1);
            }
            else
            {
                char    *temp1 = C_LookupAliasFromValue(*(bool *)consolecmds[i].variable, BOOLVALUEALIAS);

                C_ShowDescription(i);

                if (*(bool *)consolecmds[i].variable == (bool)consolecmds[i].defaultnumber)
                    C_Output(INTEGERCVARISDEFAULT, temp1);
                else
                {
                    char    *temp2 = C_LookupAliasFromValue((bool)consolecmds[i].defaultnumber, BOOLVALUEALIAS);

                    C_Output(INTEGERCVARWITHDEFAULT, temp1, temp2);
                    free(temp2);
                }

                C_ShowWarning(i);

                free(temp1);
            }

            break;
        }
}

//
// color CVARs
//
static void colorfunc2(char *cmd, char *parms)
{
    intfunc2(cmd, parms);

    if (M_StringStartsWith(cmd, "am_"))
        AM_SetColors();
}

//
// float CVARs
//
static bool floatfunc1(char *cmd, char *parms)
{
    if (!*parms)
        return true;

    for (int i = 0; *consolecmds[i].name; i++)
        if (M_StringCompare(cmd, consolecmds[i].name) && consolecmds[i].type == CT_CVAR && (consolecmds[i].flags & CF_FLOAT))
        {
            float   value;

            M_StringReplaceAll(parms, ",", "", false);

            return (sscanf(parms, "%10f", &value) == 1);
        }

    return false;
}

static void floatfunc2(char *cmd, char *parms)
{
    for (int i = 0; *consolecmds[i].name; i++)
        if (M_StringCompare(cmd, consolecmds[i].name) && consolecmds[i].type == CT_CVAR && (consolecmds[i].flags & CF_FLOAT))
        {
            if (*parms && !(consolecmds[i].flags & CF_READONLY))
            {
                float   value;

                M_StringReplaceAll(parms, ",", "", false);

                if (sscanf(parms, "%10f", &value) == 1)
                {
                    char    *temp1 = striptrailingzero(*(float *)consolecmds[i].variable, 1);

                    if (value == *(float *)consolecmds[i].variable)
                    {
                        if (!resettingcvar && !togglingvanilla)
                        {
                            if (value == consolecmds[i].defaultnumber)
                                C_Warning(0, INTEGERCVARSAMEDEFAULTWARNING, consolecmds[i].name, temp1);
                            else
                                C_Warning(0, INTEGERCVARSAMEWARNING, consolecmds[i].name, temp1);
                        }
                    }
                    else
                    {
                        char    *temp2 = striptrailingzero(value, 1);

                        if (!resettingcvar && !togglingvanilla)
                        {
                            if (*(float *)consolecmds[i].variable == consolecmds[i].defaultnumber)
                                C_Output(INTEGERCVARCHANGEDFROMDEFAULT,
                                    C_GetPlayerName(), consolecmds[i].name, temp1, temp2);
                            else if (value == consolecmds[i].defaultnumber)
                                C_Output(INTEGERCVARCHANGEDTODEFAULT,
                                    C_GetPlayerName(), consolecmds[i].name, temp1, temp2);
                            else
                                C_Output(INTEGERCVARCHANGED,
                                    C_GetPlayerName(), consolecmds[i].name, temp1, temp2);
                        }

                        *(float *)consolecmds[i].variable = value;
                        M_SaveCVARs();

                        free(temp2);
                    }

                    free(temp1);
                }
            }
            else
            {
                char    *temp1 = striptrailingzero(*(float *)consolecmds[i].variable, 1);

                C_ShowDescription(i);

                if (*(float *)consolecmds[i].variable == (float)consolecmds[i].defaultnumber)
                    C_Output(((consolecmds[i].flags & CF_READONLY) ? INTEGERCVARWITHNODEFAULT : INTEGERCVARISDEFAULT), temp1);
                else
                {
                    char    *temp2 = striptrailingzero(consolecmds[i].defaultnumber, 1);

                    C_Output(((consolecmds[i].flags & CF_READONLY) ? INTEGERCVARWITHNODEFAULT : INTEGERCVARWITHDEFAULT), temp1, temp2);
                    free(temp2);
                }

                free(temp1);

                C_ShowWarning(i);
            }

            break;
        }
}

//
// integer CVARs
//
static bool intfunc1(char *cmd, char *parms)
{
    if (!*parms)
        return true;

    for (int i = 0; *consolecmds[i].name; i++)
        if (M_StringCompare(cmd, consolecmds[i].name) && consolecmds[i].type == CT_CVAR && (consolecmds[i].flags & CF_INTEGER))
        {
            int value = C_LookupValueFromAlias(parms, consolecmds[i].aliases);

            M_StringReplaceAll(parms, ",", "", false);

            return ((value != INT_MIN || sscanf(parms, "%10d", &value) == 1)
                && value >= consolecmds[i].minimumvalue && value <= consolecmds[i].maximumvalue);
        }

    return false;
}

static void intfunc2(char *cmd, char *parms)
{
    for (int i = 0; *consolecmds[i].name; i++)
        if (M_StringCompare(cmd, consolecmds[i].name) && consolecmds[i].type == CT_CVAR && (consolecmds[i].flags & CF_INTEGER))
        {
            if (*parms && !(consolecmds[i].flags & CF_READONLY))
            {
                int value = C_LookupValueFromAlias(parms, consolecmds[i].aliases);

                M_StringReplaceAll(parms, ",", "", false);

                if (value != INT_MIN || sscanf(parms, "%10d", &value) == 1)
                {
                    char    *temp1 = C_LookupAliasFromValue(*(int *)consolecmds[i].variable, consolecmds[i].aliases);

                    if (value == *(int *)consolecmds[i].variable)
                    {
                        if (!resettingcvar && !togglingvanilla)
                        {
                            if (consolecmds[i].flags & CF_PERCENT)
                            {
                                if (value == (int)consolecmds[i].defaultnumber)
                                    C_Warning(0, PERCENTCVARSAMEDEFAULTWARNING, consolecmds[i].name, temp1);
                                else
                                    C_Warning(0, PERCENTCVARSAMEWARNING, consolecmds[i].name, temp1);
                            }
                            else if (consolecmds[i].flags & CF_COLOR)
                            {
                                char    *temp2 = C_FormatColorValue(temp1, false);

                                if (value == (int)consolecmds[i].defaultnumber)
                                    C_Warning(0, INTEGERCVARSAMEDEFAULTWARNING, consolecmds[i].name, temp2);
                                else
                                    C_Warning(0, INTEGERCVARSAMEWARNING, consolecmds[i].name, temp2);

                                free(temp2);
                            }
                            else
                            {
                                if (value == (int)consolecmds[i].defaultnumber)
                                    C_Warning(0, INTEGERCVARSAMEDEFAULTWARNING, consolecmds[i].name, temp1);
                                else
                                    C_Warning(0, INTEGERCVARSAMEWARNING, consolecmds[i].name, temp1);
                            }
                        }
                    }
                    else
                    {
                        char    *temp2 = C_LookupAliasFromValue(value, consolecmds[i].aliases);

                        if (!resettingcvar && !togglingvanilla)
                        {
                            if (consolecmds[i].flags & CF_PERCENT)
                            {
                                if (*(int *)consolecmds[i].variable == (int)consolecmds[i].defaultnumber)
                                    C_Output(PERCENTCVARCHANGEDFROMDEFAULT,
                                        C_GetPlayerName(), consolecmds[i].name, temp1, temp2);
                                else if (value == (int)consolecmds[i].defaultnumber)
                                    C_Output(PERCENTCVARCHANGEDTODEFAULT,
                                        C_GetPlayerName(), consolecmds[i].name, temp1, temp2);
                                else
                                    C_Output(PERCENTCVARCHANGED,
                                        C_GetPlayerName(), consolecmds[i].name, temp1, temp2);
                            }
                            else if (consolecmds[i].flags & CF_COLOR)
                            {
                                char    *temp3 = C_FormatColorValue(temp1, false);
                                char    *temp4 = C_FormatColorValue(temp2, false);

                                if (*(int *)consolecmds[i].variable == (int)consolecmds[i].defaultnumber)
                                    C_Output(INTEGERCVARCHANGEDFROMDEFAULT,
                                        C_GetPlayerName(), consolecmds[i].name, temp3, temp4);
                                else if (value == (int)consolecmds[i].defaultnumber)
                                    C_Output(INTEGERCVARCHANGEDTODEFAULT,
                                        C_GetPlayerName(), consolecmds[i].name, temp3, temp4);
                                else
                                    C_Output(INTEGERCVARCHANGED,
                                        C_GetPlayerName(), consolecmds[i].name, temp3, temp4);

                                free(temp3);
                                free(temp4);
                            }
                            else
                            {
                                if (*(int *)consolecmds[i].variable == (int)consolecmds[i].defaultnumber)
                                    C_Output(INTEGERCVARCHANGEDFROMDEFAULT,
                                        C_GetPlayerName(), consolecmds[i].name, temp1, temp2);
                                else if (value == (int)consolecmds[i].defaultnumber)
                                    C_Output(INTEGERCVARCHANGEDTODEFAULT,
                                        C_GetPlayerName(), consolecmds[i].name, temp1, temp2);
                                else
                                    C_Output(INTEGERCVARCHANGED,
                                        C_GetPlayerName(), consolecmds[i].name, temp1, temp2);
                            }
                        }

                        *(int *)consolecmds[i].variable = value;
                        M_SaveCVARs();

                        free(temp2);
                    }

                    free(temp1);
                }
            }
            else
            {
                C_ShowDescription(i);

                if (consolecmds[i].flags & CF_PERCENT)
                {
                    char    *temp1 = commify(*(int *)consolecmds[i].variable);

                    if (*(int *)consolecmds[i].variable == (int)consolecmds[i].defaultnumber)
                        C_Output(PERCENTCVARISDEFAULT, temp1);
                    else
                    {
                        char    *temp2 = commify((int)consolecmds[i].defaultnumber);

                        C_Output(PERCENTCVARWITHDEFAULT, temp1, temp2);
                        free(temp2);
                    }

                    free(temp1);
                }
                else
                {
                    char    *temp1 = C_LookupAliasFromValue(*(int *)consolecmds[i].variable, consolecmds[i].aliases);

                    if (consolecmds[i].flags & CF_COLOR)
                    {
                        char    *temp3 = C_FormatColorValue(temp1, false);

                        if (*(int *)consolecmds[i].variable == (int)consolecmds[i].defaultnumber)
                            C_Output(INTEGERCVARISDEFAULT, temp3);
                        else
                        {
                            char    *temp2 = C_LookupAliasFromValue((int)consolecmds[i].defaultnumber, consolecmds[i].aliases);
                            char    *temp4 = C_FormatColorValue(temp2, false);

                            C_Output(INTEGERCVARWITHDEFAULT, temp3, temp4);
                            free(temp2);
                            free(temp4);
                        }

                        free(temp3);
                    }
                    else if (*(int *)consolecmds[i].variable == (int)consolecmds[i].defaultnumber)
                        C_Output(((consolecmds[i].flags & CF_READONLY) ? INTEGERCVARWITHNODEFAULT : INTEGERCVARISDEFAULT), temp1);
                    else
                    {
                        char    *temp2 = C_LookupAliasFromValue((int)consolecmds[i].defaultnumber, consolecmds[i].aliases);

                        C_Output(((consolecmds[i].flags & CF_READONLY) ? INTEGERCVARWITHNODEFAULT : INTEGERCVARWITHDEFAULT), temp1, temp2);
                        free(temp2);
                    }

                    free(temp1);
                }

                C_ShowWarning(i);
            }

            break;
        }
}

//
// string CVARs
//
static void strfunc2(char *cmd, char *parms)
{
    for (int i = 0; *consolecmds[i].name; i++)
        if (M_StringCompare(cmd, consolecmds[i].name) && consolecmds[i].type == CT_CVAR && (consolecmds[i].flags & CF_STRING))
        {
            if (M_StringCompare(parms, EMPTYVALUE) && !(consolecmds[i].flags & CF_READONLY))
            {
                if (!resettingcvar && !togglingvanilla)
                {
                    if (!*(char **)consolecmds[i].variable)
                    {
                        if (!consolecmds[i].defaultstring)
                            C_Warning(0, STRINGCVARSAMEDEFAULTWARNING, consolecmds[i].name, "");
                        else
                            C_Warning(0, STRINGCVARSAMEWARNING, consolecmds[i].name, "");
                    }
                    else if (M_StringCompare(*(char **)consolecmds[i].variable, consolecmds[i].defaultstring))
                        C_Output(STRINGCVARCHANGEDFROMDEFAULT,
                            C_GetPlayerName(), consolecmds[i].name, *(char **)consolecmds[i].variable, "");
                    else if (M_StringCompare(parms, consolecmds[i].defaultstring))
                        C_Output(STRINGCVARCHANGEDTODEFAULT,
                            C_GetPlayerName(), consolecmds[i].name, *(char **)consolecmds[i].variable, "");
                    else
                        C_Output(STRINGCVARCHANGED,
                            C_GetPlayerName(), consolecmds[i].name, *(char **)consolecmds[i].variable, "");
                }

                *(char **)consolecmds[i].variable = "";
                M_SaveCVARs();
            }
            else if (*parms)
            {
                if (!(consolecmds[i].flags & CF_READONLY))
                {
                    if (!resettingcvar && !togglingvanilla)
                    {
                        if (M_StringCompare(parms, *(char **)consolecmds[i].variable))
                        {
                            if (M_StringCompare(parms, consolecmds[i].defaultstring))
                                C_Warning(0, STRINGCVARSAMEDEFAULTWARNING, consolecmds[i].name, parms);
                            else
                                C_Warning(0, STRINGCVARSAMEWARNING, consolecmds[i].name, parms);
                        }
                        else if (M_StringCompare(*(char **)consolecmds[i].variable, consolecmds[i].defaultstring))
                            C_Output(STRINGCVARCHANGEDFROMDEFAULT,
                                C_GetPlayerName(), consolecmds[i].name, *(char **)consolecmds[i].variable, parms);
                        else if (M_StringCompare(parms, consolecmds[i].defaultstring))
                            C_Output(STRINGCVARCHANGEDTODEFAULT,
                                C_GetPlayerName(), consolecmds[i].name, *(char **)consolecmds[i].variable, parms);
                        else
                            C_Output(STRINGCVARCHANGED,
                                C_GetPlayerName(), consolecmds[i].name, *(char **)consolecmds[i].variable, parms);
                    }

                    *(char **)consolecmds[i].variable = M_StringDuplicate(parms);
                    M_StripQuotes(trimwhitespace(*(char **)consolecmds[i].variable));
                    M_SaveCVARs();
                }
            }
            else
            {
                C_ShowDescription(i);

                if (consolecmds[i].flags & CF_READONLY)
                {
                    if (M_StringCompare(consolecmds[i].name, stringize(version)))
                        C_Output(STRINGCVARWITHNODEFAULT, "", *(char **)consolecmds[i].variable, "");
                    else
                        C_Output(STRINGCVARWITHNODEFAULT, "\"", *(char **)consolecmds[i].variable, "\"");
                }
                else if (M_StringCompare(*(char **)consolecmds[i].variable, consolecmds[i].defaultstring))
                    C_Output(STRINGCVARISDEFAULT, *(char **)consolecmds[i].variable);
                else
                    C_Output(STRINGCVARWITHDEFAULT, *(char **)consolecmds[i].variable, consolecmds[i].defaultstring);

                C_ShowWarning(i);
            }

            break;
        }
}

//
// time CVARs
//
static void timefunc2(char *cmd, char *parms)
{
    for (int i = 0; *consolecmds[i].name; i++)
        if (M_StringCompare(cmd, consolecmds[i].name) && consolecmds[i].type == CT_CVAR && (consolecmds[i].flags & CF_TIME))
        {
            int         tics = *(int *)consolecmds[i].variable;
            int         seconds = tics / TICRATE;
            const int   milliseconds = (tics % TICRATE) * (1000 / TICRATE);
            const int   hours = seconds / 3600;
            const int   minutes = ((seconds %= 3600)) / 60;

            C_ShowDescription(i);

            if (hours)
                C_Output(TIMECVARWITHNODEFAULT2, hours, minutes, seconds % 60, milliseconds / 10);
            else
                C_Output(TIMECVARWITHNODEFAULT1, minutes, seconds % 60, milliseconds / 10);

            C_ShowWarning(i);
            break;
        }
}

//
// alwaysrun CVAR
//
static void alwaysrunfunc2(char *cmd, char *parms)
{
    boolfunc2(cmd, parms);

    if (!consoleactive)
        I_InitKeyboard();
}

//
// am_display CVAR
//
static void am_displayfunc2(char *cmd, char *parms)
{
    const int   am_display_old = am_display;

    intfunc2(cmd, parms);

    if (am_display != am_display_old && am_external)
    {
        I_DestroyExternalAutomap();

        if (I_CreateExternalAutomap())
        {
            if (gamestate == GS_LEVEL)
                AM_Start(false);
        }
        else
        {
            mapscreen = *screens;

            if (gamestate == GS_LEVEL)
                AM_Stop();
        }

        AM_SetAutomapSize(r_screensize);
    }
}

//
// am_external CVAR
//
static void am_externalfunc2(char *cmd, char *parms)
{
    const bool  am_external_old = am_external;

    boolfunc2(cmd, parms);

    if (am_external != am_external_old)
    {
        if (am_external)
        {
            if (I_CreateExternalAutomap())
            {
                if (gamestate == GS_LEVEL)
                    AM_Start(false);
            }
        }
        else
        {
            if (mapwindow && gamestate == GS_LEVEL)
                AM_Stop();

            I_DestroyExternalAutomap();
            mapscreen = *screens;
        }

        AM_SetAutomapSize(r_screensize);
    }
}

//
// am_followmode CVAR
//
static void am_followmodefunc2(char *cmd, char *parms)
{
    const bool  am_followmode_old = am_followmode;

    boolfunc2(cmd, parms);

    if (automapactive && am_followmode != am_followmode_old)
        AM_ToggleFollowMode(am_followmode);
}

//
// am_gridsize CVAR
//
static void am_gridsizefunc2(char *cmd, char *parms)
{
    if (*parms)
    {
        if (M_StringCompare(am_gridsize, parms))
        {
            if (!resettingcvar && !togglingvanilla)
            {
                if (M_StringCompare(parms, am_gridsize_default))
                    C_Warning(0, INTEGERCVARSAMEDEFAULTWARNING, stringize(am_gridsize), parms);
                else
                    C_Warning(0, INTEGERCVARSAMEWARNING, stringize(am_gridsize), parms);
            }

            return;
        }
        else if (!resettingcvar && !togglingvanilla)
        {
            if (M_StringCompare(am_gridsize, am_gridsize_default))
                C_Output(INTEGERCVARCHANGEDFROMDEFAULT,
                    C_GetPlayerName(), stringize(am_gridsize), am_gridsize, parms);
            else if (M_StringCompare(parms, am_gridsize_default))
                C_Output(INTEGERCVARCHANGEDTODEFAULT,
                    C_GetPlayerName(), stringize(am_gridsize), am_gridsize, parms);
            else
                C_Output(INTEGERCVARCHANGED,
                    C_GetPlayerName(), stringize(am_gridsize), am_gridsize, parms);
        }

        am_gridsize = M_StringDuplicate(parms);
        AM_GetGridSize();

        M_SaveCVARs();
    }
    else
    {
        const int   i = C_GetIndex(stringize(am_gridsize));

        C_ShowDescription(i);

        if (M_StringCompare(am_gridsize, am_gridsize_default))
            C_Output(INTEGERCVARISDEFAULT, am_gridsize);
        else
            C_Output(INTEGERCVARWITHDEFAULT, am_gridsize, am_gridsize_default);

        C_ShowWarning(i);
    }
}

//
// am_path CVAR
//
static void am_pathfunc2(char *cmd, char *parms)
{
    const bool  am_path_old = am_path;

    boolfunc2(cmd, parms);

    if (am_path && !am_path_old)
    {
        viewplayer->cheated++;
        stat_cheatsentered = SafeAdd(stat_cheatsentered, 1);
        M_SaveCVARs();
    }
}

//
// am_rotatemode CVAR
//
static void am_rotatemodefunc2(char *cmd, char *parms)
{
    const bool  am_rotatemode_old = am_rotatemode;

    boolfunc2(cmd, parms);

    if (automapactive && am_rotatemode != am_rotatemode_old)
        AM_ToggleRotateMode(am_rotatemode);
}

//
// armortype CVAR
//
static bool armortypefunc1(char *cmd, char *parms)
{
    return (!*parms || (C_LookupValueFromAlias(parms, ARMORTYPEVALUEALIAS) != INT_MIN && gamestate == GS_LEVEL));
}

static void armortypefunc2(char *cmd, char *parms)
{
    const int   armortype_old = armortype;

    intfunc2(cmd, parms);

    if (armortype != armortype_old)
    {
        if (viewplayer->armortype == armortype_none)
        {
            P_AnimateArmor(viewplayer->armor);
            viewplayer->armor = 0;
        }
    }
}

//
// autotilt CVAR
//
static void autotiltfunc2(char *cmd, char *parms)
{
    const bool  autotilt_old = autotilt;

    boolfunc2(cmd, parms);

    if (autotilt != autotilt_old && gamestate == GS_LEVEL)
    {
        suppresswarnings = true;
        R_InitSkyMap();
        suppresswarnings = false;

        R_InitColumnFunctions();
    }
}

//
// english CVAR
//
static void englishfunc2(char *cmd, char *parms)
{
    const int   english_old = english;

    intfunc2(cmd, parms);

    if (english != english_old)
    {
        ST_InitStatBar();
        D_TranslateDehStrings();
        M_TranslateAutocomplete();
    }
}

//
// episode CVAR
//
static void episodefunc2(char *cmd, char *parms)
{
    const int   episode_old = episode;

    intfunc2(cmd, parms);

    if (episode != episode_old && gamemode != commercial)
        EpiDef.laston = MIN(episode, (gamemode == retail ? (sigil ? (sigil2 ? 6 : 5) : 4) :
            (gamemode == shareware || chex ? 1 : 3))) - 1;
}

//
// expansion CVAR
//
static void expansionfunc2(char *cmd, char *parms)
{
    const int   expansion_old = expansion;

    intfunc2(cmd, parms);

    if (expansion != expansion_old && gamemode == commercial)
    {
        ExpDef.laston = ((expansion >= 1 && expansion <= ExpDef.numitems) ? expansion - 1 : 0);

        if (gamestate != GS_LEVEL && gamestate != GS_INTERMISSION && gamestate != GS_FINALE)
            gamemission = D_GetGameMissionForExpansion();
    }
}

//
// freelook CVAR
//
static void freelookfunc2(char *cmd, char *parms)
{
    const bool  freelook_old = freelook;

    boolfunc2(cmd, parms);

    if (freelook != freelook_old && gamestate == GS_LEVEL)
    {
        suppresswarnings = true;
        R_InitSkyMap();
        suppresswarnings = false;

        R_InitColumnFunctions();

        if (!freelook)
        {
            viewplayer->pitch = 0;
            viewplayer->oldpitch = 0;
            viewplayer->recoil = 0;
            viewplayer->oldrecoil = 0;
        }
    }
}

//
// joy_deadzone_left and joy_deadzone_right CVARs
//
static bool joy_deadzonecvarsfunc1(char *cmd, char *parms)
{
    float   value;

    return (!*parms || sscanf(parms, "%10f%%", &value) == 1 || sscanf(parms, "%10f", &value) == 1);
}

static void joy_deadzonecvarsfunc2(char *cmd, char *parms)
{
    if (*parms)
    {
        float   value;

        if (sscanf(parms, "%10f%%", &value) == 1 || sscanf(parms, "%10f", &value) == 1)
        {
            if (M_StringCompare(cmd, stringize(joy_deadzone_left)))
            {
                char    *temp1 = striptrailingzero(joy_deadzone_left, 1);

                if (joy_deadzone_left == value)
                {
                    if (!resettingcvar && !togglingvanilla)
                    {
                        if (value == joy_deadzone_left_default)
                            C_Warning(0, INTEGERCVARSAMEDEFAULTWARNING, stringize(joy_deadzone_left), temp1);
                        else
                            C_Warning(0, INTEGERCVARSAMEWARNING, stringize(joy_deadzone_left), temp1);
                    }

                    return;
                }
                else if (!resettingcvar && !togglingvanilla)
                {
                    char    *temp2 = striptrailingzero(value, 1);

                    if (joy_deadzone_left == joy_deadzone_left_default)
                        C_Output(INTEGERCVARCHANGEDFROMDEFAULT, C_GetPlayerName(), cmd, temp1, temp2);
                    else if (value == joy_deadzone_left_default)
                        C_Output(INTEGERCVARCHANGEDTODEFAULT, C_GetPlayerName(), cmd, temp1, temp2);
                    else
                        C_Output(INTEGERCVARCHANGED, C_GetPlayerName(), cmd, temp1, temp2);

                    free(temp2);
                }

                joy_deadzone_left = BETWEENF(joy_deadzone_left_min, value, joy_deadzone_left_max);
                I_SetControllerLeftDeadZone();
                M_SaveCVARs();

                free(temp1);
            }
            else
            {
                char    *temp1 = striptrailingzero(joy_deadzone_right, 1);

                if (joy_deadzone_right == value)
                {
                    if (!resettingcvar && !togglingvanilla)
                    {
                        if (value == joy_deadzone_right_default)
                            C_Warning(0, INTEGERCVARSAMEDEFAULTWARNING, stringize(joy_deadzone_right), temp1);
                        else
                            C_Warning(0, INTEGERCVARSAMEWARNING, stringize(joy_deadzone_right), temp1);
                    }

                    return;
                }
                else if (!resettingcvar && !togglingvanilla)
                {
                    char    *temp2 = striptrailingzero(value, 1);

                    if (joy_deadzone_right == joy_deadzone_right_default)
                        C_Output(INTEGERCVARCHANGEDFROMDEFAULT, C_GetPlayerName(), cmd, temp1, temp2);
                    else if (value == joy_deadzone_right_default)
                        C_Output(INTEGERCVARCHANGEDTODEFAULT, C_GetPlayerName(), cmd, temp1, temp2);
                    else
                        C_Output(INTEGERCVARCHANGED, C_GetPlayerName(), cmd, temp1, temp2);

                    free(temp2);
                }

                joy_deadzone_right = BETWEENF(joy_deadzone_right_min, value, joy_deadzone_right_max);
                I_SetControllerRightDeadZone();
                M_SaveCVARs();

                free(temp1);
            }
        }
    }
    else if (M_StringCompare(cmd, stringize(joy_deadzone_left)))
    {
        char        *temp1 = striptrailingzero(joy_deadzone_left, 1);
        const int   i = C_GetIndex(cmd);

        C_ShowDescription(i);

        if (joy_deadzone_left == joy_deadzone_left_default)
            C_Output(PERCENTCVARISDEFAULT, temp1);
        else
        {
            char    *temp2 = striptrailingzero(joy_deadzone_left_default, 1);

            C_Output(PERCENTCVARWITHDEFAULT, temp1, temp2);
            free(temp2);
        }

        free(temp1);

        C_ShowWarning(i);
    }
    else
    {
        char        *temp1 = striptrailingzero(joy_deadzone_right, 1);
        const int   i = C_GetIndex(cmd);

        C_ShowDescription(i);

        if (joy_deadzone_right == joy_deadzone_right_default)
            C_Output(PERCENTCVARISDEFAULT, temp1);
        else
        {
            char    *temp2 = striptrailingzero(joy_deadzone_right_default, 1);

            C_Output(PERCENTCVARWITHDEFAULT, temp1, temp2);
            free(temp2);
        }

        free(temp1);

        C_ShowWarning(i);
    }
}

//
// joy_sensitivity_horizontal and joy_sensitivity_vertical CVARs
//
static void joy_sensitivitycvarsfunc2(char *cmd, char *parms)
{
    const float joy_sensitivity_horizontal_old = joy_sensitivity_horizontal;
    const float joy_sensitivity_vertical_old = joy_sensitivity_vertical;

    floatfunc2(cmd, parms);

    if (joy_sensitivity_horizontal != joy_sensitivity_horizontal_old)
        I_SetControllerHorizontalSensitivity();
    else if (joy_sensitivity_vertical != joy_sensitivity_vertical_old)
        I_SetControllerVerticalSensitivity();
}

//
// ammo, armor and health CVARs
//
static bool playercvarsfunc1(char *cmd, char *parms)
{
    if (!*parms)
        return true;

    if (gamestate != GS_LEVEL)
        return false;

    if (M_StringCompare(cmd, stringize(ammo)))
    {
        int value;

        if (sscanf(parms, "%10d", &value) == 1 && value > viewplayer->maxammo[weaponinfo[viewplayer->readyweapon].ammotype])
            return false;
    }
    else if (M_StringCompare(cmd, stringize(armor)))
    {
        int value;

        if (sscanf(parms, "%10d", &value) == 1 && value > max_armor)
            return false;
    }
    else if (M_StringCompare(cmd, stringize(health)))
    {
        int value;

        if (sscanf(parms, "%10d", &value) == 1 && (value < -99 || value > maxhealth))
            return false;
    }

    return intfunc1(cmd, parms);
}

static void playercvarsfunc2(char *cmd, char *parms)
{
    int value;

    if (resettingcvar)
        return;

    if (M_StringCompare(cmd, stringize(ammo)))
    {
        const weapontype_t  readyweapon = viewplayer->readyweapon;
        const ammotype_t    ammotype = weaponinfo[readyweapon].ammotype;

        if (*parms)
        {
            if (sscanf(parms, "%10d", &value) == 1 && ammotype != am_noammo && viewplayer->health > 0)
            {
                char    *temp1 = commify(viewplayer->ammo[ammotype]);
                char    *temp2 = commify(value);

                ammohighlight = I_GetTimeMS() + HUD_AMMO_HIGHLIGHT_WAIT;

                if (value > viewplayer->maxammo[ammotype] && !viewplayer->backpack)
                    P_GiveBackpack(false, false);

                if ((value = MIN(value, viewplayer->maxammo[ammotype])) > viewplayer->ammo[ammotype])
                {
                    P_UpdateAmmoStat(ammotype, value - viewplayer->ammo[ammotype]);
                    P_AddBonus();
                    S_StartSound(viewplayer->mo, sfx_itemup);
                }
                else
                    S_StartSound(viewplayer->mo, weaponinfo[readyweapon].sound);

                if (viewplayer->ammo[ammotype] == value)
                {
                    if (!resettingcvar)
                    {
                        if (value == ammo_default)
                            C_Warning(0, INTEGERCVARSAMEDEFAULTWARNING, stringize(ammo), temp1);
                        else
                            C_Warning(0, INTEGERCVARSAMEWARNING, stringize(ammo), temp1);
                    }

                    free(temp1);
                    free(temp2);
                    return;
                }
                else if (!resettingcvar)
                {
                    if (viewplayer->ammo[ammotype] == ammo_default)
                        C_Output(INTEGERCVARCHANGEDFROMDEFAULT,
                            C_GetPlayerName(), stringize(ammo), temp1, temp2);
                    else if (value == ammo_default)
                        C_Output(INTEGERCVARCHANGEDTODEFAULT,
                            C_GetPlayerName(), stringize(ammo), temp1, temp2);
                    else
                        C_Output(INTEGERCVARCHANGED,
                            C_GetPlayerName(), stringize(ammo), temp1, temp2);
                }

                P_AnimateAmmo(viewplayer->ammo[ammotype] - value, ammotype);
                viewplayer->ammo[ammotype] = value;
                P_CheckAmmo(readyweapon);

                if (viewplayer->pendingweapon != wp_nochange)
                    C_HideConsole();

                free(temp1);
                free(temp2);
            }
        }
        else
        {
            char    *temp = commify(gamestate == GS_LEVEL ? (ammotype == am_noammo ?
                        0 : viewplayer->ammo[ammotype]) : ammo_default);

            C_ShowDescription(C_GetIndex(cmd));
            C_Output(INTEGERCVARWITHNODEFAULT, temp);

            if (gamestate != GS_LEVEL)
            {
                if (isdefaultplayername())
                    C_Warning(0, NOGAMECVARWARNING1);
                else
                    C_Warning(0, NOGAMECVARWARNING2, playername, pronoun(personal),
                        (playergender == playergender_other ? "aren't" : "isn't"));
            }

            free(temp);
        }
    }
    else if (M_StringCompare(cmd, stringize(armor)))
    {
        if (*parms)
        {
            if (sscanf(parms, "%10d", &value) == 1)
            {
                char    *temp1 = commify(viewplayer->armor);
                char    *temp2 = commify(value);

                armorhighlight = I_GetTimeMS() + HUD_ARMOR_HIGHLIGHT_WAIT;

                if ((value = MIN(value, max_armor)) > viewplayer->armor)
                {
                    P_UpdateArmorStat(value - viewplayer->armor);
                    P_AddBonus();
                    S_StartSound(viewplayer->mo, sfx_itemup);
                }
                else
                    S_StartSound(NULL, sfx_plpain);

                P_AnimateArmor(viewplayer->armor - value);

                if (viewplayer->armor == value)
                {
                    if (!resettingcvar)
                    {
                        if (value == armor_default)
                            C_Warning(0, PERCENTCVARSAMEDEFAULTWARNING, stringize(armor), temp1);
                        else
                            C_Warning(0, PERCENTCVARSAMEWARNING, stringize(armor), temp1);
                    }

                    free(temp1);
                    free(temp2);
                    return;
                }
                else if (!resettingcvar)
                {
                    if (viewplayer->armor == armor_default)
                        C_Output(PERCENTCVARCHANGEDFROMDEFAULT,
                            C_GetPlayerName(), stringize(armor), temp1, temp2);
                    else if (value == armor_default)
                        C_Output(PERCENTCVARCHANGEDTODEFAULT,
                            C_GetPlayerName(), stringize(armor), temp1, temp2);
                    else
                        C_Output(PERCENTCVARCHANGED,
                            C_GetPlayerName(), stringize(armor), temp1, temp2);
                }

                if (!(viewplayer->armor = value))
                    viewplayer->armortype = armortype_none;
                else if (!viewplayer->armortype)
                    viewplayer->armortype = green_armor_class;

                free(temp1);
                free(temp2);
            }
        }
        else
        {
            char    *temp = commify(gamestate == GS_LEVEL ? viewplayer->armor : armor_default);

            C_ShowDescription(C_GetIndex(cmd));
            C_Output(PERCENTCVARWITHNODEFAULT, temp);

            if (gamestate != GS_LEVEL)
            {
                if (isdefaultplayername())
                    C_Warning(0, NOGAMECVARWARNING1);
                else
                    C_Warning(0, NOGAMECVARWARNING2, playername, pronoun(personal),
                        (playergender == playergender_other ? "aren't" : "isn't"));
            }

            free(temp);
        }
    }
    else if (M_StringCompare(cmd, stringize(health))
        && !(viewplayer->cheats & CF_GODMODE)
        && !viewplayer->powers[pw_invulnerability])
    {
        if (*parms)
        {
            if (sscanf(parms, "%10d", &value) == 1)
            {
                if (value == viewplayer->health)
                {
                    if (!resettingcvar)
                    {
                        if (value == health_default)
                            C_Warning(0, PERCENTCVARSAMEDEFAULTWARNING,
                                stringize(health), commify(viewplayer->health));
                        else
                            C_Warning(0, PERCENTCVARSAMEWARNING,
                                stringize(health), commify(viewplayer->health));
                    }

                    return;
                }
                else if (!negativehealth || value != viewplayer->negativehealth)
                {
                    char    *temp1;
                    char    *temp2;

                    value = BETWEEN(((viewplayer->cheats & CF_BUDDHA) ? 1 : HUD_NUMBER_MIN), value, maxhealth);

                    healthhighlight = I_GetTimeMS() + HUD_HEALTH_HIGHLIGHT_WAIT;
                    P_AnimateHealth(viewplayer->health - value);

                    temp1 = commify(viewplayer->health);
                    temp2 = commify(value);

                    if (viewplayer->health <= 0)
                    {
                        if (value <= 0)
                        {
                            if (value < viewplayer->health)
                                viewplayer->damagecount = viewplayer->health - value;

                            if (!resettingcvar)
                                C_Output(PERCENTCVARCHANGED,
                                    C_GetPlayerName(), stringize(health), temp1, temp2);

                            viewplayer->health = value;
                            viewplayer->negativehealth = value;
                            viewplayer->mo->health = value;

                            I_UpdateControllerLEDByHealth(viewplayer->health);
                        }
                        else
                        {
                            char    buffer[1024];

                            P_ResurrectPlayer(value);
                            P_AddBonus();

                            if (isdefaultplayername())
                                M_StringCopy(buffer, "You resurrected yourself!", sizeof(buffer));
                            else
                                M_snprintf(buffer, sizeof(buffer), "%s resurrected %s!",
                                    playername, pronoun(reflexive));

                            C_PlayerWarning("%s", buffer);
                            HU_SetPlayerMessage(buffer, false, false);
                            message_warning = true;
                        }
                    }
                    else
                    {
                        if (value < viewplayer->health)
                        {
                            if (!resettingcvar)
                            {
                                if (viewplayer->health == health_default)
                                    C_Output(PERCENTCVARCHANGEDFROMDEFAULT,
                                        C_GetPlayerName(), stringize(health), temp1, temp2);
                                else if (value == health_default)
                                    C_Output(PERCENTCVARCHANGEDTODEFAULT,
                                        C_GetPlayerName(), stringize(health), temp1, temp2);
                                else
                                    C_Output(PERCENTCVARCHANGED,
                                        C_GetPlayerName(), stringize(health), temp1, temp2);
                            }

                            viewplayer->damagecount = viewplayer->health - value;
                            viewplayer->health = value;
                            viewplayer->negativehealth = value;
                            viewplayer->mo->health = value;
                            healthcvar = true;

                            I_UpdateControllerLEDByHealth(viewplayer->health);

                            if (value <= 0)
                            {
                                P_KillMobj(viewplayer->mo, NULL, viewplayer->mo, false);
                                C_HideConsole();
                            }
                            else
                                S_StartSound(NULL, sfx_plpain);
                        }
                        else
                        {
                            if (!resettingcvar)
                            {
                                if (viewplayer->health == health_default)
                                    C_Output(PERCENTCVARCHANGEDFROMDEFAULT,
                                        C_GetPlayerName(), stringize(health), temp1, temp2);
                                else if (value == health_default)
                                    C_Output(PERCENTCVARCHANGEDTODEFAULT,
                                        C_GetPlayerName(), stringize(health), temp1, temp2);
                                else
                                    C_Output(PERCENTCVARCHANGED,
                                        C_GetPlayerName(), stringize(health), temp1, temp2);
                            }

                            P_UpdateHealthStat(value - viewplayer->health);
                            viewplayer->health = value;
                            viewplayer->negativehealth = value;
                            viewplayer->mo->health = value;

                            I_UpdateControllerLEDByHealth(viewplayer->health);
                            P_AddBonus();
                            S_StartSound(viewplayer->mo, sfx_itemup);
                        }
                    }

                    free(temp1);
                    free(temp2);
                }
            }
        }
        else
        {
            char    *temp = commify(gamestate == GS_LEVEL ? (negativehealth && minuspatch && !viewplayer->health ?
                                viewplayer->negativehealth : viewplayer->health) : health_default);

            C_ShowDescription(C_GetIndex(cmd));
            C_Output(PERCENTCVARWITHNODEFAULT, temp);

            if (gamestate != GS_LEVEL)
            {
                if (isdefaultplayername())
                    C_Warning(0, NOGAMECVARWARNING1);
                else
                    C_Warning(0, NOGAMECVARWARNING2, playername, pronoun(personal),
                        (playergender == playergender_other ? "aren't" : "isn't"));
            }

            free(temp);
        }
    }
}

//
// r_antialiasing CVAR
//
static void r_antialiasingfunc2(char *cmd, char *parms)
{
    const bool   r_antialiasing_old = r_antialiasing;

    boolfunc2(cmd, parms);

    if (r_antialiasing != r_antialiasing_old)
        GetPixelSize();
}

//
// r_blood CVAR
//
static void r_bloodfunc2(char *cmd, char *parms)
{
    const int   r_blood_old = r_blood;

    intfunc2(cmd, parms);

    if (r_blood != r_blood_old)
    {
        R_InitColumnFunctions();

        for (int i = 0; i < numsectors; i++)
            for (bloodsplat_t *splat = sectors[i].splatlist; splat; splat = splat->next)
                P_SetBloodSplatColor(splat);
    }
}

//
// r_bloodsplats_translucency CVAR
//
static void r_bloodsplats_translucencyfunc2(char *cmd, char *parms)
{
    const bool   r_bloodsplats_translucency_old = r_bloodsplats_translucency;

    boolfunc2(cmd, parms);

    if (r_bloodsplats_translucency != r_bloodsplats_translucency_old)
    {
        R_InitColumnFunctions();

        for (int i = 0; i < numsectors; i++)
            for (bloodsplat_t *splat = sectors[i].splatlist; splat; splat = splat->next)
                P_SetBloodSplatColor(splat);
    }
}

//
// r_brightmaps CVAR
//
static void r_brightmapsfunc2(char *cmd, char *parms)
{
    const bool   r_brightmaps_old = r_brightmaps;

    boolfunc2(cmd, parms);

    if (r_brightmaps != r_brightmaps_old)
    {
        I_SetPalette(&PLAYPAL[st_palette * 768]);
        R_InitColumnFunctions();
    }
}

//
// r_corpses_mirrored CVAR
//
static void r_corpses_mirroredfunc2(char *cmd, char *parms)
{
    const bool   r_corpses_mirrored_old = r_corpses_mirrored;

    boolfunc2(cmd, parms);

    if (r_corpses_mirrored != r_corpses_mirrored_old)
        for (int i = 0; i < numsectors; i++)
            for (mobj_t *thing = sectors[i].thinglist; thing; thing = thing->snext)
            {
                if ((thing->flags & MF_CORPSE)
                    && !(thing->flags2 & MF2_NOMIRROREDCORPSE)
                    && (thing->type != MT_PAIN || !doom4vanilla))
                {
                    if (r_corpses_mirrored)
                    {
                        if (M_BigRandom() & 1)
                            thing->flags2 |= MF2_MIRRORED;
                    }
                    else
                        thing->flags2 &= ~MF2_MIRRORED;
                }
            }
}

//
// r_detail CVAR
//
static void r_detailfunc2(char *cmd, char *parms)
{
    const int   r_detail_old = r_detail;

    intfunc2(cmd, parms);

    if (r_detail != r_detail_old)
    {
        STLib_Init();
        R_InitColumnFunctions();
    }
}

//
// r_diskicon CVAR
//
static void r_diskiconfunc2(char *cmd, char *parms)
{
    const bool   r_diskicon_old = r_diskicon;

    boolfunc2(cmd, parms);

    if (r_diskicon && !r_diskicon_old)
    {
        drawdisk = true;
        drawdisktics = TICRATE;
    }
}

//
// r_ditheredlighting CVAR
//
static void r_ditheredlightingfunc2(char *cmd, char *parms)
{
    const bool   r_ditheredlighting_old = r_ditheredlighting;

    boolfunc2(cmd, parms);

    if (r_ditheredlighting != r_ditheredlighting_old)
    {
        I_SetPalette(&PLAYPAL[st_palette * 768]);
        R_InitColumnFunctions();
    }
}

//
// r_fixmaperrors CVAR
//
static void r_fixmaperrorsfunc2(char *cmd, char *parms)
{
    const bool   r_fixmaperrors_old = r_fixmaperrors;

    boolfunc2(cmd, parms);

    if (r_fixmaperrors != r_fixmaperrors_old && gamestate == GS_LEVEL && !togglingvanilla && !resettingcvar)
    {
        if (isdefaultplayername())
            C_Warning(0, NEXTMAPWARNING1);
        else
            C_Warning(0, NEXTMAPWARNING2, playername);
    }
}

//
// r_fov CVAR
//
static void r_fovfunc2(char *cmd, char *parms)
{
    if (*parms)
    {
        int value;

        if (sscanf(parms, "%10d", &value) == 1)
        {
            if (value == r_fov)
            {
                if (!resettingcvar && !togglingvanilla)
                {
                    if (value == r_fov_default)
                        C_Warning(0, DEGREESCVARSAMEDEFAULTWARNING, stringize(r_fov), r_fov);
                    else
                        C_Warning(0, DEGREESCVARSAMEWARNING, stringize(r_fov), r_fov);
                }

                return;
            }
            else if (!resettingcvar && !togglingvanilla)
            {
                if (r_fov == r_fov_default)
                    C_Output(DEGREESCVARCHANGEDFROMDEFAULT,
                        C_GetPlayerName(), stringize(r_fov), r_fov, value);
                else if (value == r_fov_default)
                    C_Output(DEGREESCVARCHANGEDTODEFAULT,
                        C_GetPlayerName(), stringize(r_fov), r_fov, value);
                else
                    C_Output(DEGREESCVARCHANGED,
                        C_GetPlayerName(), stringize(r_fov), r_fov, value);
            }

            r_fov = value;
            M_SaveCVARs();
            I_RestartGraphics(false);

            if (gamestate == GS_LEVEL)
                S_StartSound(NULL, sfx_stnmov);
        }
    }
    else
    {
        const int   i = C_GetIndex(cmd);

        C_ShowDescription(i);

        if (r_fov == r_fov_default)
            C_Output(DEGREESCVARISDEFAULT, r_fov);
        else
            C_Output(DEGREESCVARWITHDEFAULT, r_fov, r_fov_default);

        C_ShowWarning(i);
    }
}

//
// r_gamma CVAR
//
static bool r_gammafunc1(char *cmd, char *parms)
{
    return (C_LookupValueFromAlias(parms, GAMMAVALUEALIAS) != INT_MIN || floatfunc1(cmd, parms));
}

static void r_gammafunc2(char *cmd, char *parms)
{
    if (*parms)
    {
        float   value = (float)C_LookupValueFromAlias(parms, GAMMAVALUEALIAS);

        if ((value != INT_MIN || sscanf(parms, "%10f", &value) == 1))
        {
            char    *temp1 = striptrailingzero(r_gamma, 1);

            if (r_gamma == value)
            {
                if (!resettingcvar && !togglingvanilla)
                {
                    if (value == r_gamma_default)
                        C_Warning(0, INTEGERCVARSAMEDEFAULTWARNING,
                            stringize(r_gamma), (r_gamma == 1.0f ? "off" : temp1));
                    else
                        C_Warning(0, INTEGERCVARSAMEWARNING,
                            stringize(r_gamma), (r_gamma == 1.0f ? "off" : temp1));
                }

                free(temp1);
                return;
            }
            else if (!resettingcvar && !togglingvanilla)
            {
                char    *temp2 = striptrailingzero(value, 1);

                if (r_gamma == r_gamma_default)
                    C_Output(INTEGERCVARCHANGEDFROMDEFAULT,
                        C_GetPlayerName(), stringize(r_gamma),
                        (r_gamma == 1.0f ? "off" : temp1), (value == 1.0f ? "off" : temp2));
                else if (value == r_gamma_default)
                    C_Output(INTEGERCVARCHANGEDTODEFAULT,
                        C_GetPlayerName(), stringize(r_gamma),
                        (r_gamma == 1.0f ? "off" : temp1), (value == 1.0f ? "off" : temp2));
                else
                    C_Output(INTEGERCVARCHANGED,
                        C_GetPlayerName(), stringize(r_gamma),
                        (r_gamma == 1.0f ? "off" : temp1), (value == 1.0f ? "off" : temp2));

                free(temp2);
            }

            r_gamma = BETWEENF(r_gamma_min, value, r_gamma_max);
            I_SetGamma(r_gamma);
            I_UpdateColors();
            M_SaveCVARs();

            free(temp1);
        }
    }
    else
    {
        char        buffer1[128];
        int         len;
        const int   i = C_GetIndex(cmd);

        M_snprintf(buffer1, sizeof(buffer1), "%.2f", r_gamma);
        len = (int)strlen(buffer1);

        if (len >= 2 && buffer1[len - 1] == '0' && buffer1[len - 2] == '0')
            buffer1[len - 1] = '\0';

        C_ShowDescription(i);

        if (r_gamma == r_gamma_default)
            C_Output(INTEGERCVARISDEFAULT, (r_gamma == 1.0f ? "off" : buffer1));
        else
        {
            char    buffer2[128];

            M_snprintf(buffer2, sizeof(buffer2), "%.2f", r_gamma_default);
            len = (int)strlen(buffer2);

            if (len >= 2 && buffer2[len - 1] == '0' && buffer2[len - 2] == '0')
                buffer2[len - 1] = '\0';

            C_Output(INTEGERCVARWITHDEFAULT, (r_gamma == 1.0f ? "off" : buffer1),
                (r_gamma_default == 1.0f ? "off" : buffer2));
        }

        C_ShowWarning(i);
    }
}

//
// r_hud CVAR
//
static void r_hudfunc2(char *cmd, char *parms)
{
    const bool  r_hud_old = r_hud;

    boolfunc2(cmd, parms);

    if (r_hud != r_hud_old && r_hud && !togglingvanilla)
    {
        if (r_screensize != r_screensize_max || !vid_widescreen)
        {
            if (r_screensize != r_screensize_max)
            {
                r_screensize = r_screensize_max;
                C_IntegerCVAROutput(stringize(r_screensize), r_screensize);
                R_SetViewSize(r_screensize);
            }

            if (!vid_widescreen)
                I_StartPillarboxAnimation(false);

            S_StartSound(NULL, sfx_stnmov);
        }
    }
}

//
// r_hud_translucency CVAR
//
static void r_hud_translucencyfunc2(char *cmd, char *parms)
{
    const bool   r_hud_translucency_old = r_hud_translucency;

    boolfunc2(cmd, parms);

    if (r_hud_translucency != r_hud_translucency_old)
        HU_SetTranslucency();
}

//
// r_lowpixelsize CVAR
//
static void r_lowpixelsizefunc2(char *cmd, char *parms)
{
    if (*parms)
    {
        if (M_StringCompare(r_lowpixelsize, parms))
        {
            if (!resettingcvar && !togglingvanilla)
            {
                if (M_StringCompare(parms, r_lowpixelsize_default))
                    C_Warning(0, INTEGERCVARSAMEDEFAULTWARNING, stringize(r_lowpixelsize), parms);
                else
                    C_Warning(0, INTEGERCVARSAMEWARNING, stringize(r_lowpixelsize), parms);
            }

            return;
        }
        else if (!resettingcvar && !togglingvanilla)
        {
            if (M_StringCompare(r_lowpixelsize, r_lowpixelsize_default))
                C_Output(INTEGERCVARCHANGEDFROMDEFAULT,
                    C_GetPlayerName(), stringize(r_lowpixelsize), r_lowpixelsize, parms);
            else if (M_StringCompare(parms, r_lowpixelsize_default))
                C_Output(INTEGERCVARCHANGEDTODEFAULT,
                    C_GetPlayerName(), stringize(r_lowpixelsize), r_lowpixelsize, parms);
            else
                C_Output(INTEGERCVARCHANGED,
                    C_GetPlayerName(), stringize(r_lowpixelsize), r_lowpixelsize, parms);
        }

        r_lowpixelsize = M_StringDuplicate(parms);
        GetPixelSize();

        M_SaveCVARs();
    }
    else
    {
        const int   i = C_GetIndex(stringize(r_lowpixelsize));

        C_ShowDescription(i);

        if (M_StringCompare(r_lowpixelsize, r_lowpixelsize_default))
            C_Output(INTEGERCVARISDEFAULT, r_lowpixelsize);
        else
            C_Output(INTEGERCVARWITHDEFAULT, r_lowpixelsize, r_lowpixelsize_default);

        C_ShowWarning(i);
    }
}

//
// r_mirroredweapons CVAR
//
static void r_mirroredweaponsfunc2(char *cmd, char *parms)
{
    const bool   r_mirroredweapons_old = r_mirroredweapons;

    boolfunc2(cmd, parms);

    if (r_mirroredweapons != r_mirroredweapons_old)
        for (int i = 0; i < numsectors; i++)
            for (mobj_t *thing = sectors[i].thinglist; thing; thing = thing->snext)
            {
                const mobjtype_t    type = thing->type;

                if (((type >= MT_MISC25 && type <= MT_SUPERSHOTGUN) && (thing->flags & MF_SPECIAL))
                    || (thing->flags & MF_DROPPED))
                {
                    if (r_mirroredweapons)
                    {
                        if (M_BigRandom() & 1)
                            thing->flags2 |= MF2_MIRRORED;
                    }
                    else
                        thing->flags2 &= ~MF2_MIRRORED;
                }
            }
}

//
// r_randomstartframes CVAR
//
static void r_randomstartframesfunc2(char *cmd, char *parms)
{
    const bool   r_randomstartframes_old = r_randomstartframes;

    boolfunc2(cmd, parms);

    if (r_randomstartframes != r_randomstartframes_old)
    {
        if (r_randomstartframes)
        {
            for (int i = 0; i < numsectors; i++)
                for (mobj_t *thing = sectors[i].thinglist; thing; thing = thing->snext)
                {
                    const mobjinfo_t *info = thing->info;

                    if (info->frames > 1)
                    {
                        const int   numframes = M_BigRandomInt(0, info->frames);
                        state_t *st = thing->state;

                        for (int j = 0; j < numframes && st->nextstate != S_NULL; j++)
                            st = &states[st->nextstate];

                        thing->state = st;
                    }
                }
        }
        else
            for (int i = 0; i < numsectors; i++)
                for (mobj_t *thing = sectors[i].thinglist; thing; thing = thing->snext)
                {
                    const mobjinfo_t *info = thing->info;

                    if (info->frames > 1)
                        thing->state = &states[info->spawnstate];
                }
    }
}

static void AdjustScreenSize(int value)
{
    r_screensize = value;
    S_StartSound(NULL, sfx_stnmov);
    R_SetViewSize(r_screensize);

    if (!togglingvanilla)
    {
        if (r_hud != (r_screensize == r_screensize_max))
        {
            r_hud = (r_screensize == r_screensize_max);
            C_StringCVAROutput(stringize(r_hud), (r_hud ? "on" : "off"));
        }

        if (vid_widescreen && r_screensize < r_screensize_max - 1)
            I_StartPillarboxAnimation(true);
        else if (!vid_widescreen && r_screensize == r_screensize_max)
            I_StartPillarboxAnimation(false);

        M_SaveCVARs();
    }

    if (r_playerweapon)
        skippsprinterp = 1;
}

//
// r_screensize CVAR
//
static void r_screensizefunc2(char *cmd, char *parms)
{
    if (*parms)
    {
        const int   value = parms[0] - '0';

        if (strlen(parms) == 1 && value >= r_screensize_min && value <= r_screensize_max && value != r_screensize)
        {
            char    *temp1 = commify(r_screensize);

            if (r_screensize == value)
            {
                if (!resettingcvar && !togglingvanilla)
                {
                    if (value == r_screensize_default)
                        C_Warning(0, INTEGERCVARSAMEDEFAULTWARNING, stringize(r_screensize), temp1);
                    else
                        C_Warning(0, INTEGERCVARSAMEWARNING, stringize(r_screensize), temp1);
                }

                free(temp1);
                return;
            }
            else if (!resettingcvar && !togglingvanilla)
            {
                char    *temp2 = commify(value);

                if (r_screensize == r_screensize_default)
                    C_Output(INTEGERCVARCHANGEDFROMDEFAULT,
                        C_GetPlayerName(), stringize(r_screensize), temp1, temp2);
                else if (value == r_screensize_default)
                    C_Output(INTEGERCVARCHANGEDTODEFAULT,
                        C_GetPlayerName(), stringize(r_screensize), temp1, temp2);
                else
                    C_Output(INTEGERCVARCHANGED,
                        C_GetPlayerName(), stringize(r_screensize), temp1, temp2);

                free(temp2);
            }

            AdjustScreenSize(value);

            free(temp1);
        }
    }
    else
    {
        char        *temp1 = commify(r_screensize);
        const int   i = C_GetIndex(cmd);

        C_ShowDescription(i);

        if (r_screensize == r_screensize_default)
            C_Output(INTEGERCVARISDEFAULT, temp1);
        else
        {
            char    *temp2 = commify(r_screensize_default);

            C_Output(INTEGERCVARWITHDEFAULT, temp1, temp2);
            free(temp2);
        }

        free(temp1);

        C_ShowWarning(i);
    }
}

//
// r_shadows_translucency CVAR
//
static void r_shadows_translucencyfunc2(char *cmd, char *parms)
{
    const bool   r_shadows_translucency_old = r_shadows_translucency;

    boolfunc2(cmd, parms);

    if (r_shadows_translucency != r_shadows_translucency_old)
        for (int i = 0; i < numsectors; i++)
            for (mobj_t *thing = sectors[i].thinglist; thing; thing = thing->snext)
                P_SetShadowColumnFunction(thing);
}

//
// r_sprites_translucency CVAR
//
static void r_sprites_translucencyfunc2(char *cmd, char *parms)
{
    const bool   r_sprites_translucency_old = r_sprites_translucency;

    boolfunc2(cmd, parms);

    if (r_sprites_translucency != r_sprites_translucency_old)
    {
        R_InitColumnFunctions();

        for (int i = 0; i < numsectors; i++)
            for (mobj_t *thing = sectors[i].thinglist; thing; thing = thing->snext)
                thing->colfunc = thing->info->colfunc;
    }
}

//
// r_rockettrails_translucency CVAR
//
static void r_rockettrails_translucencyfunc2(char *cmd, char *parms)
{
    const bool  r_rockettrails_translucency_old = r_rockettrails_translucency;

    boolfunc2(cmd, parms);

    if (r_rockettrails_translucency != r_rockettrails_translucency_old)
        R_InitColumnFunctions();
}

//
// r_textures CVAR
//
static void r_texturesfunc2(char *cmd, char *parms)
{
    const bool  r_textures_old = r_textures;

    boolfunc2(cmd, parms);

    if (r_textures != r_textures_old)
    {
        R_InitColumnFunctions();

        for (int i = 0; i < numsectors; i++)
        {
            for (mobj_t *mo = sectors[i].thinglist; mo; mo = mo->snext)
            {
                mo->colfunc = mo->info->colfunc;
                P_SetShadowColumnFunction(mo);
            }

            for (bloodsplat_t *splat = sectors[i].splatlist; splat; splat = splat->next)
                P_SetBloodSplatColor(splat);
        }
    }
}

//
// r_textures_translucency CVAR
//
static void r_textures_translucencyfunc2(char *cmd, char *parms)
{
    const bool   r_textures_translucency_old = r_textures_translucency;

    boolfunc2(cmd, parms);

    if (r_textures_translucency != r_textures_translucency_old)
    {
        R_InitColumnFunctions();

        for (int i = 0; i < numsectors; i++)
            for (mobj_t *thing = sectors[i].thinglist; thing; thing = thing->snext)
                thing->colfunc = thing->info->colfunc;
    }
}

//
// s_randommusic CVAR
//
static void s_randommusicfunc2(char *cmd, char *parms)
{
    const bool  s_randommusic_old = s_randommusic;

    boolfunc2(cmd, parms);

    if (s_randommusic != s_randommusic_old && gamestate == GS_LEVEL)
        S_Start();
}

//
// s_musicvolume and s_sfxvolume CVARs
//
static bool s_volumecvarsfunc1(char *cmd, char *parms)
{
    int value;

    if (!*parms)
        return true;

    if (sscanf(parms, "%10d%%", &value) != 1 && sscanf(parms, "%10d", &value) != 1)
        return false;

    return ((M_StringCompare(cmd, stringize(s_musicvolume)) && value >= s_musicvolume_min && value <= s_musicvolume_max)
        || (M_StringCompare(cmd, stringize(s_sfxvolume)) && value >= s_sfxvolume_min && value <= s_sfxvolume_max));
}

static void s_volumecvarsfunc2(char *cmd, char *parms)
{
    if (*parms)
    {
        int value;

        if (sscanf(parms, "%10d%%", &value) != 1 && sscanf(parms, "%10d", &value) != 1)
            return;

        if (M_StringCompare(cmd, stringize(s_musicvolume)))
        {
            char    *temp1 = commify(s_musicvolume);

            if (s_musicvolume == value)
            {
                if (!resettingcvar && !togglingvanilla)
                {
                    if (value == s_musicvolume_default)
                        C_Warning(0, INTEGERCVARSAMEDEFAULTWARNING, stringize(s_musicvolume), temp1);
                    else
                        C_Warning(0, INTEGERCVARSAMEWARNING, stringize(s_musicvolume), temp1);
                }

                free(temp1);
                return;
            }
            else if (!resettingcvar && !togglingvanilla)
            {
                char    *temp2 = commify(value);

                if (s_musicvolume == s_musicvolume_default)
                    C_Output(INTEGERCVARCHANGEDFROMDEFAULT,
                        C_GetPlayerName(), stringize(s_musicvolume), temp1, temp2);
                else if (value == s_musicvolume_default)
                    C_Output(INTEGERCVARCHANGEDTODEFAULT,
                        C_GetPlayerName(), stringize(s_musicvolume), temp1, temp2);
                else
                    C_Output(INTEGERCVARCHANGED,
                        C_GetPlayerName(), stringize(s_musicvolume), temp1, temp2);

                free(temp2);
            }

            s_musicvolume = value;
            musicvolume = (s_musicvolume * 31 + 50) / 100;

            if (consoleactive)
                S_LowerMusicVolume();

            M_SaveCVARs();
            free(temp1);
        }
        else
        {
            char    *temp1 = commify(s_musicvolume);

            if (s_sfxvolume == value)
            {
                if (!resettingcvar && !togglingvanilla)
                {
                    if (value == s_sfxvolume_default)
                        C_Warning(0, INTEGERCVARSAMEDEFAULTWARNING, stringize(s_sfxvolume), temp1);
                    else
                        C_Warning(0, INTEGERCVARSAMEWARNING, stringize(s_sfxvolume), temp1);
                }

                free(temp1);
                return;
            }
            else if (!resettingcvar && !togglingvanilla)
            {
                char    *temp2 = commify(value);

                if (s_sfxvolume == s_sfxvolume_default)
                    C_Output(INTEGERCVARCHANGEDFROMDEFAULT,
                        C_GetPlayerName(), stringize(s_sfxvolume), temp1, temp2);
                else if (value == s_sfxvolume_default)
                    C_Output(INTEGERCVARCHANGEDTODEFAULT,
                        C_GetPlayerName(), stringize(s_sfxvolume), temp1, temp2);
                else
                    C_Output(INTEGERCVARCHANGED,
                        C_GetPlayerName(), stringize(s_sfxvolume), temp1, temp2);

                free(temp2);
            }

            s_sfxvolume = value;
            sfxvolume = (s_sfxvolume * 31 + 50) / 100;
            S_SetSfxVolume(sfxvolume * (MIX_MAX_VOLUME - 1) / 31);
            M_SaveCVARs();
            free(temp1);
        }
    }
    else if (M_StringCompare(cmd, stringize(s_musicvolume)))
    {
        char        *temp1 = commify(s_musicvolume);
        const int   i = C_GetIndex(cmd);

        C_ShowDescription(i);

        if (s_musicvolume == s_musicvolume_default)
            C_Output(PERCENTCVARISDEFAULT, temp1);
        else
        {
            char    *temp2 = commify(s_musicvolume_default);

            C_Output(PERCENTCVARWITHDEFAULT, temp1, temp2);
            free(temp2);
        }

        free(temp1);

        C_ShowWarning(i);
    }
    else
    {
        char        *temp1 = commify(s_sfxvolume);
        const int   i = C_GetIndex(cmd);

        C_ShowDescription(i);

        if (s_sfxvolume == s_sfxvolume_default)
            C_Output(PERCENTCVARISDEFAULT, temp1);
        else
        {
            char    *temp2 = commify(s_sfxvolume_default);

            C_Output(PERCENTCVARWITHDEFAULT, temp1, temp2);
            free(temp2);
        }

        free(temp1);

        C_ShowWarning(i);
    }
}

//
// savegame CVAR
//
static void savegamefunc2(char *cmd, char *parms)
{
    const int   savegame_old = savegame;

    intfunc2(cmd, parms);

    if (savegame != savegame_old)
    {
        SaveDef.laston = savegame - 1;
        LoadDef.laston = savegame - 1;
    }
}

//
// skilllevel CVAR
//
static void skilllevelfunc2(char *cmd, char *parms)
{
    const int   skilllevel_old = skilllevel;

    intfunc2(cmd, parms);

    if (skilllevel != skilllevel_old)
        NewDef.laston = skilllevel - 1;
}

//
// turbo CVAR
//
static void turbofunc2(char *cmd, char *parms)
{
    const int   turbo_old = turbo;

    intfunc2(cmd, parms);

    if (turbo != turbo_old)
    {
        if (turbo > turbo_default)
        {
            viewplayer->cheated++;
            stat_cheatsentered = SafeAdd(stat_cheatsentered, 1);
        }

        G_SetMovementSpeed(turbo);
    }
}

//
// vid_aspectratio CVAR
//
static void vid_aspectratiofunc2(char *cmd, char *parms)
{
    const int   vid_aspectratio_old = vid_aspectratio;

    intfunc2(cmd, parms);

    if (vid_aspectratio != vid_aspectratio_old)
    {
        I_RestartGraphics(false);
        S_StartSound(NULL, sfx_stnmov);
    }
}

//
// vid_blue CVAR
//
static void vid_bluefunc2(char *cmd, char *parms)
{
    const int   vid_blue_old = vid_blue;

    intfunc2(cmd, parms);

    if (vid_blue != vid_blue_old)
        I_UpdateColors();
}

//
// vid_borderlesswindow CVAR
//
static void vid_borderlesswindowfunc2(char *cmd, char *parms)
{
    const bool  vid_borderlesswindow_old = vid_borderlesswindow;

    boolfunc2(cmd, parms);

    if (vid_borderlesswindow != vid_borderlesswindow_old && vid_fullscreen)
        I_RestartGraphics(true);
}

//
// vid_brightness CVAR
//
static void vid_brightnessfunc2(char *cmd, char *parms)
{
    const int   vid_brightness_old = vid_brightness;

    intfunc2(cmd, parms);

    if (vid_brightness != vid_brightness_old)
        I_UpdateColors();
}

//
// vid_contrast CVAR
//
static void vid_contrastfunc2(char *cmd, char *parms)
{
    const int   vid_contrast_old = vid_contrast;

    intfunc2(cmd, parms);

    if (vid_contrast != vid_contrast_old)
        I_UpdateColors();
}

//
// vid_display CVAR
//
static void vid_displayfunc2(char *cmd, char *parms)
{
    const int   vid_display_old = vid_display;

    intfunc2(cmd, parms);

    if (vid_display != vid_display_old)
        I_RestartGraphics(false);
}

//
// vid_fullscreen CVAR
//
static void vid_fullscreenfunc2(char *cmd, char *parms)
{
    const bool  vid_fullscreen_old = vid_fullscreen;

    boolfunc2(cmd, parms);

    if (vid_fullscreen != vid_fullscreen_old)
    {
        vid_fullscreen = !vid_fullscreen;
        I_ToggleFullscreen(false);
    }
}

//
// vid_green CVAR
//
static void vid_greenfunc2(char *cmd, char *parms)
{
    const int   vid_green_old = vid_green;

    intfunc2(cmd, parms);

    if (vid_green != vid_green_old)
        I_UpdateColors();
}

//
// vid_motionblur CVAR
//
static void vid_motionblurfunc2(char *cmd, char *parms)
{
    const int   vid_motionblur_old = vid_motionblur;

    intfunc2(cmd, parms);

    if (vid_motionblur != vid_motionblur_old)
        I_RestartGraphics(true);
}

//
// vid_pillarboxes CVAR
//
static void vid_pillarboxesfunc2(char *cmd, char *parms)
{
    const bool  vid_pillarboxes_old = vid_pillarboxes;

    boolfunc2(cmd, parms);

    if (vid_pillarboxes != vid_pillarboxes_old)
        I_UpdateColors();
}

//
// vid_red CVAR
//
static void vid_redfunc2(char *cmd, char *parms)
{
    const int   vid_red_old = vid_red;

    intfunc2(cmd, parms);

    if (vid_red != vid_red_old)
        I_UpdateColors();
}

//
// vid_saturation CVAR
//
static void vid_saturationfunc2(char *cmd, char *parms)
{
    const int   vid_saturation_old = vid_saturation;

    intfunc2(cmd, parms);

    if (vid_saturation != vid_saturation_old)
        I_UpdateColors();
}

//
// vid_scaleapi CVAR
//
static bool vid_scaleapifunc1(char *cmd, char *parms)
{
    return (!*parms
#if defined(_WIN32)
        || M_StringCompare(parms, vid_scaleapi_direct3d)
#endif
        || M_StringCompare(parms, vid_scaleapi_opengl)
#if !defined(_WIN32)
        || M_StringCompare(parms, vid_scaleapi_opengles)
        || M_StringCompare(parms, vid_scaleapi_opengles2)
#endif
        || M_StringCompare(parms, vid_scaleapi_software));
}

static void vid_scaleapifunc2(char *cmd, char *parms)
{
    const char  *vid_scaleapi_old = M_StringDuplicate(vid_scaleapi);

    strfunc2(cmd, parms);

    if (!M_StringCompare(vid_scaleapi, vid_scaleapi_old))
        I_RestartGraphics(true);
}

//
// vid_scalefilter CVAR
//
static bool vid_scalefilterfunc1(char *cmd, char *parms)
{
    return (!*parms || M_StringCompare(parms, vid_scalefilter_nearest)
        || M_StringCompare(parms, vid_scalefilter_linear)
        || M_StringCompare(parms, vid_scalefilter_nearest_linear));
}

static void vid_scalefilterfunc2(char *cmd, char *parms)
{
    const char  *vid_scalefilter_old = M_StringDuplicate(vid_scalefilter);

    strfunc2(cmd, parms);

    if (!M_StringCompare(vid_scalefilter, vid_scalefilter_old))
        I_RestartGraphics(true);
}

//
// vid_screenresolution CVAR
//
static void vid_screenresolutionfunc2(char *cmd, char *parms)
{
    if (*parms)
    {
        if (M_StringCompare(vid_screenresolution, parms))
        {
            if (!resettingcvar && !togglingvanilla)
            {
                if (M_StringCompare(parms, vid_screenresolution_default))
                    C_Warning(0, INTEGERCVARSAMEDEFAULTWARNING, stringize(vid_screenresolution), parms);
                else
                    C_Warning(0, INTEGERCVARSAMEWARNING, stringize(vid_screenresolution), parms);
            }

            return;
        }
        else if (!resettingcvar && !togglingvanilla)
        {
            if (M_StringCompare(vid_screenresolution, vid_screenresolution_default))
                C_Output(INTEGERCVARCHANGEDFROMDEFAULT,
                    C_GetPlayerName(), stringize(vid_screenresolution), vid_screenresolution, parms);
            else if (M_StringCompare(parms, vid_screenresolution_default))
                C_Output(INTEGERCVARCHANGEDTODEFAULT,
                    C_GetPlayerName(), stringize(vid_screenresolution), vid_screenresolution, parms);
            else
                C_Output(INTEGERCVARCHANGED,
                    C_GetPlayerName(), stringize(vid_screenresolution), vid_screenresolution, parms);
        }

        vid_screenresolution = M_StringDuplicate(parms);
        GetScreenResolution();
        M_SaveCVARs();

        if (vid_fullscreen)
            I_RestartGraphics(false);
    }
    else
    {
        const int   i = C_GetIndex(cmd);

        C_ShowDescription(i);

        if (M_StringCompare(vid_screenresolution, vid_screenresolution_default))
            C_Output(INTEGERCVARISDEFAULT, vid_screenresolution);
        else
            C_Output(INTEGERCVARWITHDEFAULT, vid_screenresolution, vid_screenresolution_default);

        C_ShowWarning(i);
    }
}

//
// vid_showfps CVAR
//
static void vid_showfpsfunc2(char *cmd, char *parms)
{
    const bool  vid_showfps_old = vid_showfps;

    boolfunc2(cmd, parms);

    if (vid_showfps != vid_showfps_old)
    {
        I_UpdateBlitFunc(viewplayer->damagecount);

        if (vid_showfps)
            starttime = SDL_GetPerformanceCounter();
        else
        {
            framespersecond = 0;
            framecount = -1;
        }
    }
}

//
// vid_vsync CVAR
//
static void vid_vsyncfunc2(char *cmd, char *parms)
{
    const int   vid_vsync_old = vid_vsync;

    intfunc2(cmd, parms);

    if (vid_vsync != vid_vsync_old)
        I_RestartGraphics(true);
}

//
// vid_widescreen CVAR
//
static void vid_widescreenfunc2(char *cmd, char *parms)
{
    const bool  vid_widescreen_old = vid_widescreen;

    boolfunc2(cmd, parms);

    if (vid_widescreen != vid_widescreen_old && !togglingvanilla)
    {
        if (r_screensize != r_screensize_max - 1)
        {
            r_screensize = r_screensize_max - 1;
            C_IntegerCVAROutput(stringize(r_screensize), r_screensize);
            M_SaveCVARs();
        }

        if (r_hud)
        {
            r_hud = false;
            C_StringCVAROutput(stringize(r_hud), "off");
            M_SaveCVARs();
        }

        I_StartPillarboxAnimation(!vid_widescreen);
        S_StartSound(NULL, sfx_stnmov);
    }
}

//
// vid_windowpos CVAR
//
static void vid_windowposfunc2(char *cmd, char *parms)
{
    if (*parms)
    {
        char    *parm = removespaces(parms);

        if (M_StringCompare(vid_windowpos, parms))
        {
            if (!resettingcvar && !togglingvanilla)
            {
                if (M_StringCompare(parms, vid_windowpos_default))
                    C_Warning(0, INTEGERCVARSAMEDEFAULTWARNING, stringize(vid_windowpos), parm);
                else
                    C_Warning(0, INTEGERCVARSAMEWARNING, stringize(vid_windowpos), parm);
            }

            return;
        }
        else if (!resettingcvar && !togglingvanilla)
        {
            if (M_StringCompare(vid_windowpos, vid_windowpos_default))
                C_Output(INTEGERCVARCHANGEDFROMDEFAULT,
                    C_GetPlayerName(), stringize(vid_windowpos), vid_windowpos, parm);
            else if (M_StringCompare(parm, vid_windowpos_default))
                C_Output(INTEGERCVARCHANGEDTODEFAULT,
                    C_GetPlayerName(), stringize(vid_windowpos), vid_windowpos, parm);
            else
                C_Output(INTEGERCVARCHANGED,
                    C_GetPlayerName(), stringize(vid_windowpos), vid_windowpos, parm);
        }

        vid_windowpos = M_StringDuplicate(parm);
        GetWindowPosition();
        M_SaveCVARs();

        if (!vid_fullscreen)
        {
            if (M_StringCompare(vid_windowpos, vid_windowpos_centered)
                || M_StringCompare(vid_windowpos, vid_windowpos_centred))
                SDL_SetWindowPosition(window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
            else
                SDL_SetWindowPosition(window, windowx, windowy);
        }

        free(parm);
    }
    else
    {
        const int   i = C_GetIndex(cmd);
        char        temp[255];

        C_ShowDescription(i);

        M_StringCopy(temp, vid_windowpos, sizeof(temp));

        if (english == english_british)
            M_AmericanToBritishEnglish(temp);

        if (M_StringCompare(vid_windowpos, vid_windowpos_default))
            C_Output(INTEGERCVARISDEFAULT, temp);
        else
            C_Output(INTEGERCVARWITHDEFAULT, temp, vid_windowpos_default);

        C_ShowWarning(i);
    }
}

//
// vid_windowsize CVAR
//
static void vid_windowsizefunc2(char *cmd, char *parms)
{
    if (*parms)
    {
        if (M_StringCompare(vid_windowsize, parms))
        {
            if (!resettingcvar && !togglingvanilla)
            {
                if (M_StringCompare(parms, vid_windowsize_default))
                    C_Warning(0, INTEGERCVARSAMEDEFAULTWARNING, stringize(vid_windowsize), parms);
                else
                    C_Warning(0, INTEGERCVARSAMEWARNING, stringize(vid_windowsize), parms);
            }

            return;
        }
        else if (!resettingcvar && !togglingvanilla)
        {
            if (M_StringCompare(vid_windowsize, vid_windowsize_default))
                C_Output(INTEGERCVARCHANGEDFROMDEFAULT,
                    C_GetPlayerName(), stringize(vid_windowsize), vid_windowsize, parms);
            else if (M_StringCompare(parms, vid_windowsize_default))
                C_Output(INTEGERCVARCHANGEDTODEFAULT,
                    C_GetPlayerName(), stringize(vid_windowsize), vid_windowsize, parms);
            else
                C_Output(INTEGERCVARCHANGED,
                    C_GetPlayerName(), stringize(vid_windowsize), vid_windowsize, parms);
        }

        vid_windowsize = M_StringDuplicate(parms);
        GetWindowSize();
        M_SaveCVARs();

        if (!vid_fullscreen)
            SDL_SetWindowSize(window, windowwidth, windowheight);
    }
    else
    {
        const int   i = C_GetIndex(cmd);

        C_ShowDescription(i);

        if (M_StringCompare(vid_windowsize, vid_windowsize_default))
            C_Output(INTEGERCVARISDEFAULT, vid_windowsize);
        else
            C_Output(INTEGERCVARWITHDEFAULT, vid_windowsize, vid_windowsize_default);

        C_ShowWarning(i);
    }
}

//
// weapon CVAR
//
static bool weaponfunc1(char *cmd, char *parms)
{
    if (!*parms)
        return true;
    else if (gamestate != GS_LEVEL || viewplayer->pendingweapon != wp_nochange || viewplayer->health <= 0)
        return false;
    else
    {
        int value = INT_MIN;

        if (sscanf(parms, "%1d", &value) == 1)
            for (int i = 0; i < NUMWEAPONS; i++)
                if (value == weaponinfo[i].key - '0')
                {
                    value = i;

                    if (value == wp_fist)
                    {
                        if (viewplayer->readyweapon == wp_fist
                            && viewplayer->weaponowned[wp_chainsaw])
                            value = wp_chainsaw;
                        else if (viewplayer->readyweapon == wp_chainsaw)
                            value = wp_fist;
                    }
                    else if (value == wp_shotgun)
                    {
                        if (viewplayer->readyweapon == wp_shotgun
                            && viewplayer->weaponowned[wp_supershotgun])
                            value = wp_supershotgun;
                        else if (viewplayer->readyweapon == wp_supershotgun)
                            value = wp_shotgun;
                    }

                    break;
                }

        if (value == INT_MIN)
            value = C_LookupValueFromAlias(parms, WEAPONVALUEALIAS);

        return (value != INT_MIN
            && value != viewplayer->readyweapon
            && viewplayer->weaponowned[value]
            && ((viewplayer->ammo[weaponinfo[value].ammotype] >= weaponinfo[value].ammopershot
                || weaponinfo[value].ammotype == am_noammo) || infiniteammo));
    }
}

static void weaponfunc2(char *cmd, char *parms)
{
    if (*parms)
    {
        int value = INT_MIN;

        if (sscanf(parms, "%1d", &value) == 1)
            for (int i = 0; i < NUMWEAPONS; i++)
                if (value == weaponinfo[i].key - '0')
                {
                    value = i;

                    if (value == wp_fist)
                    {
                        if (viewplayer->readyweapon == wp_fist
                            && viewplayer->weaponowned[wp_chainsaw])
                            value = wp_chainsaw;
                        else if (viewplayer->readyweapon == wp_chainsaw)
                            value = wp_fist;
                    }
                    else if (value == wp_shotgun)
                    {
                        if (viewplayer->readyweapon == wp_shotgun
                            && viewplayer->weaponowned[wp_supershotgun])
                            value = wp_supershotgun;
                        else if (viewplayer->readyweapon == wp_supershotgun)
                            value = wp_shotgun;
                    }

                    break;
                }

        if (value == INT_MIN)
            value = C_LookupValueFromAlias(parms, WEAPONVALUEALIAS);

        if (viewplayer->readyweapon == value)
        {
            if (!resettingcvar && !togglingvanilla)
            {
                if (M_StringCompare(parms, C_LookupAliasFromValue(weapon_default, WEAPONVALUEALIAS)))
                    C_Warning(0, INTEGERCVARSAMEDEFAULTWARNING, stringize(weapon), parms);
                else
                    C_Warning(0, INTEGERCVARSAMEWARNING, stringize(weapon), parms);
            }

            return;
        }
        else if (!resettingcvar && !togglingvanilla)
        {
            char    *temp = C_LookupAliasFromValue((gamestate == GS_LEVEL ? viewplayer->readyweapon : weapon_default), WEAPONVALUEALIAS);

            if (viewplayer->readyweapon == weapon_default)
                C_Output(INTEGERCVARCHANGEDFROMDEFAULT,
                    C_GetPlayerName(), stringize(weapon), temp, parms);
            else if (M_StringCompare(parms, C_LookupAliasFromValue(weapon_default, WEAPONVALUEALIAS)))
                C_Output(INTEGERCVARCHANGEDTODEFAULT,
                    C_GetPlayerName(), stringize(weapon), temp, parms);
            else
                C_Output(INTEGERCVARCHANGED,
                    C_GetPlayerName(), stringize(weapon), temp, parms);

            free(temp);
        }

        viewplayer->pendingweapon = value;

        if (value == wp_fist)
            viewplayer->fistorchainsaw = wp_fist;
        else if (value == wp_chainsaw)
            viewplayer->fistorchainsaw = wp_chainsaw;
        else if (value == wp_shotgun)
            viewplayer->preferredshotgun = wp_shotgun;
        else if (value == wp_supershotgun)
            viewplayer->preferredshotgun = wp_supershotgun;

        C_HideConsole();
    }
    else
    {
        char    *temp = C_LookupAliasFromValue((gamestate == GS_LEVEL ? viewplayer->readyweapon : weapon_default), WEAPONVALUEALIAS);
        char    description[255];

        if (gamemode == shareware)
            M_StringCopy(description, WEAPONDESCRIPTION_SHAREWARE, sizeof(description));
        else if (legacyofrust)
            M_StringCopy(description, WEAPONDESCRIPTION_LEGACYOFRUST, sizeof(description));
        else if (gamemission != doom)
            M_StringCopy(description, WEAPONDESCRIPTION_DOOM2, sizeof(description));
        else
            M_StringCopy(description, consolecmds[C_GetIndex(cmd)].description, sizeof(description));

        description[0] = tolower(description[0]);

        C_Output("This CVAR changes %s", description);
        C_Output(INTEGERCVARWITHNODEFAULT, temp);

        if (gamestate != GS_LEVEL)
        {
            if (isdefaultplayername())
                C_Warning(0, NOGAMECVARWARNING1);
            else
                C_Warning(0, NOGAMECVARWARNING2, playername, pronoun(personal),
                    (playergender == playergender_other ? "aren't" : "isn't"));
        }

        free(temp);
    }
}

//
// weaponrecoil CVAR
//
static void weaponrecoilfunc2(char *cmd, char *parms)
{
    const bool  weaponrecoil_old = weaponrecoil;

    boolfunc2(cmd, parms);

    if (weaponrecoil != weaponrecoil_old)
    {
        if (gamestate == GS_LEVEL)
        {
            suppresswarnings = true;
            R_InitSkyMap();
            suppresswarnings = false;

            R_InitColumnFunctions();

            if (!weaponrecoil)
            {
                viewplayer->recoil = 0;
                viewplayer->oldrecoil = 0;
            }
        }
    }
}
