/*
  Copyright (C) 2010-2011  Charles Pence <charles@charlespence.net>
  Copyright (C) 1996-1997  Id Software, Inc.

  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
#include "pch.h"
#include "quakedef.h"

glpic_t *m_box_tl;
glpic_t *m_box_ml;
glpic_t *m_box_bl;
glpic_t *m_box_tm;
glpic_t *m_box_mm;
glpic_t *m_box_mm2;
glpic_t *m_box_bm;
glpic_t *m_box_tr;
glpic_t *m_box_mr;
glpic_t *m_box_br;

glpic_t *m_qplaque;
glpic_t *m_ttl_main;
glpic_t *m_mainmenu;
glpic_t *m_ttl_sgl;
glpic_t *m_sp_menu;
glpic_t *m_p_load;
glpic_t *m_p_save;
glpic_t *m_p_multi;
glpic_t *m_mp_menu;
glpic_t *m_bigbox;
glpic_t *m_menuplyr;
glpic_t *m_p_option;
glpic_t *m_ttl_cstm;

glpic_t *m_menudot[6];
glpic_t *m_helppage[6];

typedef enum {m_none, m_main, m_singleplayer, m_load, m_save, m_multiplayer, m_setup, m_net, m_options, m_keys, m_help, m_quit, m_gameoptions} m_states;
m_states m_state;

void M_Menu_Main_f (void);
    void M_Menu_SinglePlayer_f (void);
        void M_Menu_Load_f (void);
        void M_Menu_Save_f (void);
    void M_Menu_MultiPlayer_f (void);
        void M_Menu_Setup_f (void);
        void M_Menu_Net_f (void);
    void M_Menu_Options_f (void);
        void M_Menu_Keys_f (void);
    void M_Menu_Help_f (void);
    void M_Menu_Quit_f (void);
void M_Menu_GameOptions_f (void);
void M_Menu_Search_f (void);
void M_Menu_ServerList_f (void);

void M_Main_Draw (void);
    void M_SinglePlayer_Draw (void);
        void M_Load_Draw (void);
        void M_Save_Draw (void);
    void M_MultiPlayer_Draw (void);
        void M_Setup_Draw (void);
        void M_Net_Draw (void);
    void M_Options_Draw (void);
        void M_Keys_Draw (void);
    void M_Help_Draw (void);
    void M_Quit_Draw (void);
void M_GameOptions_Draw (void);

void M_Main_Key (int key);
    void M_SinglePlayer_Key (int key);
        void M_Load_Key (int key);
        void M_Save_Key (int key);
    void M_MultiPlayer_Key (int key);
        void M_Setup_Key (int key);
        void M_Net_Key (int key);
    void M_Options_Key (int key);
        void M_Keys_Key (int key);
    void M_Help_Key (int key);
    void M_Quit_Key (int key);
void M_GameOptions_Key (int key);

qboolean    m_entersound;        // play after drawing a frame, so caching
                                // won't disrupt the sound
qboolean    m_recursiveDraw;

int            m_return_state;
qboolean    m_return_onerror;
char        m_return_reason [32];

#define StartingGame    (m_multiplayer_cursor == 1)
#define JoiningGame        (m_multiplayer_cursor == 0)

void M_ConfigureNetSubsystem(void);

/*
================
M_DrawCharacter

Draws one solid graphics character
================
*/
void M_DrawCharacter (int cx, int line, int num)
{
    Draw_Character(cx + ((vid.width - 320)>>1), line, num);
}

void M_Print (int cx, int cy, const std::string &str)
{
    for (size_t i = 0 ; i < str.length() ; i++)
    {
        M_DrawCharacter(cx, cy, str[i] + 128);
        cx += 8;
    }
}

void M_PrintWhite (int cx, int cy, const std::string &str)
{
    for (size_t i = 0 ; i < str.length() ; i++)
    {
        M_DrawCharacter(cx, cy, str[i]);
        cx += 8;
    }
}

void M_DrawPic (int x, int y, glpic_t *pic)
{
    Draw_Pic (x + ((vid.width - 320)>>1), y, pic);
}

byte identityTable[256];
byte translationTable[256];

void M_BuildTranslationTable(int top, int bottom)
{
    int        j;
    byte    *dest, *source;

    for (j = 0; j < 256; j++)
        identityTable[j] = j;
    dest = translationTable;
    source = identityTable;
    memcpy (dest, source, 256);

    if (top < 128)    // the artists made some backwards ranges.  sigh.
        memcpy (dest + TOP_RANGE, source + top, 16);
    else
        for (j=0 ; j<16 ; j++)
            dest[TOP_RANGE+j] = source[top+15-j];

    if (bottom < 128)
        memcpy (dest + BOTTOM_RANGE, source + bottom, 16);
    else
        for (j=0 ; j<16 ; j++)
            dest[BOTTOM_RANGE+j] = source[bottom+15-j];
}


void M_DrawTransPicTranslate (int x, int y, glpic_t *pic)
{
    Draw_TransPicTranslate (x + ((vid.width - 320)>>1), y, pic, translationTable);
}


void M_DrawTextBox (int x, int y, int width, int lines)
{
    int        cx, cy;
    int        n;

    // draw left side
    cx = x;
    cy = y;
    M_DrawPic (cx, cy, m_box_tl);
    for (n = 0; n < lines; n++)
    {
        cy += 8;
        M_DrawPic (cx, cy, m_box_ml);
    }
    M_DrawPic (cx, cy+8, m_box_bl);

    // draw middle
    cx += 8;
    while (width > 0)
    {
        cy = y;
        M_DrawPic (cx, cy, m_box_tm);
        for (n = 0; n < lines; n++)
        {
            cy += 8;
            if (n == 0)
                M_DrawPic(cx, cy, m_box_mm);
            else
                M_DrawPic(cx, cy, m_box_mm2);
        }
        M_DrawPic (cx, cy+8, m_box_bm);
        width -= 2;
        cx += 16;
    }

    // draw right side
    cy = y;
    M_DrawPic (cx, cy, m_box_tr);
    for (n = 0; n < lines; n++)
    {
        cy += 8;
        M_DrawPic (cx, cy, m_box_mr);
    }
    M_DrawPic (cx, cy+8, m_box_br);
}

//=============================================================================

int m_save_demonum;

/*
================
M_ToggleMenu_f
================
*/
void M_ToggleMenu_f (void)
{
    m_entersound = true;

    if (key_dest == key_menu)
    {
        if (m_state != m_main)
        {
            M_Menu_Main_f ();
            return;
        }
        key_dest = key_game;
        m_state = m_none;
        return;
    }
    if (key_dest == key_console)
    {
        Con_ToggleConsole_f ();
    }
    else
    {
        M_Menu_Main_f ();
    }
}


//=============================================================================
/* MAIN MENU */

int    m_main_cursor;
#define    MAIN_ITEMS    5


void M_Menu_Main_f (void)
{
    if (key_dest != key_menu)
    {
        m_save_demonum = cls.demonum;
        cls.demonum = -1;
    }
    key_dest = key_menu;
    m_state = m_main;
    m_entersound = true;
}


void M_Main_Draw (void)
{
    int        f;

    M_DrawPic (16, 4, m_qplaque);
    M_DrawPic ((320 - m_ttl_main->wad_width)/2, 4, m_ttl_main);
    M_DrawPic (72, 32, m_mainmenu);

    f = (int)(host_time * 10)%6;
    M_DrawPic (54, 32 + m_main_cursor * 20, m_menudot[f]);
}


void M_Main_Key (int key)
{
    switch (key)
    {
    case K_ESCAPE:
        key_dest = key_game;
        m_state = m_none;
        cls.demonum = m_save_demonum;
        if (cls.demonum != -1 && !cls.demoplayback && cls.state != ca_connected)
            CL_NextDemo ();
        break;

    case K_DOWNARROW:
        service::LocalSound("misc/menu1.wav");
        if (++m_main_cursor >= MAIN_ITEMS)
            m_main_cursor = 0;
        break;

    case K_UPARROW:
        service::LocalSound("misc/menu1.wav");
        if (--m_main_cursor < 0)
            m_main_cursor = MAIN_ITEMS - 1;
        break;

    case K_ENTER:
        m_entersound = true;

        switch (m_main_cursor)
        {
        case 0:
            M_Menu_SinglePlayer_f ();
            break;

        case 1:
            M_Menu_MultiPlayer_f ();
            break;

        case 2:
            M_Menu_Options_f ();
            break;

        case 3:
            M_Menu_Help_f ();
            break;

        case 4:
            M_Menu_Quit_f ();
            break;
        }
    }
}

//=============================================================================
/* SINGLE PLAYER MENU */

int    m_singleplayer_cursor;
#define    SINGLEPLAYER_ITEMS    3


void M_Menu_SinglePlayer_f (void)
{
    key_dest = key_menu;
    m_state = m_singleplayer;
    m_entersound = true;
}


void M_SinglePlayer_Draw (void)
{
    int        f;

    M_DrawPic (16, 4, m_qplaque);
    M_DrawPic ((320 - m_ttl_sgl->wad_width)/2, 4, m_ttl_sgl);
    M_DrawPic (72, 32, m_sp_menu);

    f = (int)(host_time * 10)%6;
    M_DrawPic (54, 32 + m_singleplayer_cursor * 20, m_menudot[f]);
}


void M_SinglePlayer_Key (int key)
{
    switch (key)
    {
    case K_ESCAPE:
        M_Menu_Main_f ();
        break;

    case K_DOWNARROW:
        service::LocalSound("misc/menu1.wav");
        if (++m_singleplayer_cursor >= SINGLEPLAYER_ITEMS)
            m_singleplayer_cursor = 0;
        break;

    case K_UPARROW:
        service::LocalSound("misc/menu1.wav");
        if (--m_singleplayer_cursor < 0)
            m_singleplayer_cursor = SINGLEPLAYER_ITEMS - 1;
        break;

    case K_ENTER:
        m_entersound = true;

        switch (m_singleplayer_cursor)
        {
        case 0:
            if (sv.active)
                if (!SCR_ModalMessage("Are you sure you want to\nstart a new game?\n"))
                    break;
            key_dest = key_game;
            if (sv.active)
                Cbuf_AddText ("disconnect\n");
            Cbuf_AddText ("maxplayers 1\n");
            Cbuf_AddText ("map start\n");
            break;

        case 1:
            M_Menu_Load_f ();
            break;

        case 2:
            M_Menu_Save_f ();
            break;
        }
    }
}

//=============================================================================
/* LOAD/SAVE MENU */

int        load_cursor;        // 0 < load_cursor < MAX_SAVEGAMES

#define    MAX_SAVEGAMES        12
char    m_filenames[MAX_SAVEGAMES][SAVEGAME_COMMENT_LENGTH+1];
int        loadable[MAX_SAVEGAMES];

void M_ScanSaves (void)
{
    int        i, j;
    char    name[MAX_OSPATH];

    for (i=0 ; i<MAX_SAVEGAMES ; i++)
    {
        strcpy (m_filenames[i], "--- UNUSED SLOT ---");
        loadable[i] = false;
        sprintf (name, "s%i.sav", i);
        if (!File::Exists(name))
            continue;
        
        File fp;
        if (!fp.OpenRead(name))
            continue;
        
        std::string buf;
        fp.GetLine(); // discard version
        strncpy (m_filenames[i], fp.GetLine().c_str(), sizeof(m_filenames[i])-1);

    // change _ back to space
        for (j=0 ; j<SAVEGAME_COMMENT_LENGTH ; j++)
        {
            if (m_filenames[i][j] == '_')
                m_filenames[i][j] = ' ';
        }
        loadable[i] = true;
        fp.Close();
    }
}

void M_Menu_Load_f (void)
{
    m_entersound = true;
    m_state = m_load;
    key_dest = key_menu;
    M_ScanSaves ();
}


void M_Menu_Save_f (void)
{
    if (!sv.active)
        return;
    if (cl.intermission)
        return;
    if (svs.maxclients != 1)
        return;
    m_entersound = true;
    m_state = m_save;
    key_dest = key_menu;
    M_ScanSaves ();
}


void M_Load_Draw (void)
{
    int        i;

    M_DrawPic ((320 - m_p_load->wad_width)/2, 4, m_p_load);

    for (i=0 ; i< MAX_SAVEGAMES; i++)
        M_Print (16, 32 + 8*i, m_filenames[i]);

// line cursor
    M_DrawCharacter (8, 32 + load_cursor*8, 12+((int)(realtime*4)&1));
}


void M_Save_Draw (void)
{
    int        i;

    M_DrawPic((320 - m_p_save->wad_width)/2, 4, m_p_save);

    for (i=0 ; i<MAX_SAVEGAMES ; i++)
        M_Print (16, 32 + 8*i, m_filenames[i]);

// line cursor
    M_DrawCharacter (8, 32 + load_cursor*8, 12+((int)(realtime*4)&1));
}


void M_Load_Key (int k)
{
    switch (k)
    {
    case K_ESCAPE:
        M_Menu_SinglePlayer_f ();
        break;

    case K_ENTER:
        service::LocalSound("misc/menu2.wav");
        if (!loadable[load_cursor])
            return;
        m_state = m_none;
        key_dest = key_game;

    // Host_Loadgame_f can't bring up the loading plaque because too much
    // stack space has been used, so do it now
        SCR_BeginLoadingPlaque ();

    // issue the load command
        Cbuf_AddText (va ("load s%i\n", load_cursor) );
        return;

    case K_UPARROW:
    case K_LEFTARROW:
        service::LocalSound("misc/menu1.wav");
        load_cursor--;
        if (load_cursor < 0)
            load_cursor = MAX_SAVEGAMES-1;
        break;

    case K_DOWNARROW:
    case K_RIGHTARROW:
        service::LocalSound("misc/menu1.wav");
        load_cursor++;
        if (load_cursor >= MAX_SAVEGAMES)
            load_cursor = 0;
        break;
    }
}


void M_Save_Key (int k)
{
    switch (k)
    {
    case K_ESCAPE:
        M_Menu_SinglePlayer_f ();
        break;

    case K_ENTER:
        m_state = m_none;
        key_dest = key_game;
        Cbuf_AddText (va("save s%i\n", load_cursor));
        return;

    case K_UPARROW:
    case K_LEFTARROW:
        service::LocalSound("misc/menu1.wav");
        load_cursor--;
        if (load_cursor < 0)
            load_cursor = MAX_SAVEGAMES-1;
        break;

    case K_DOWNARROW:
    case K_RIGHTARROW:
        service::LocalSound("misc/menu1.wav");
        load_cursor++;
        if (load_cursor >= MAX_SAVEGAMES)
            load_cursor = 0;
        break;
    }
}

//=============================================================================
/* MULTIPLAYER MENU */

int    m_multiplayer_cursor;
#define    MULTIPLAYER_ITEMS    3


void M_Menu_MultiPlayer_f (void)
{
    key_dest = key_menu;
    m_state = m_multiplayer;
    m_entersound = true;
}


void M_MultiPlayer_Draw (void)
{
    int        f;

    M_DrawPic (16, 4, m_qplaque);
    M_DrawPic ((320 - m_p_multi->wad_width)/2, 4, m_p_multi);
    M_DrawPic (72, 32, m_mp_menu);

    f = (int)(host_time * 10)%6;
    M_DrawPic (54, 32 + m_multiplayer_cursor * 20, m_menudot[f]);

    if (serialAvailable || ipxAvailable || tcpipAvailable)
        return;
    M_PrintWhite ((320/2) - ((27*8)/2), 148, "No Communications Available");
}


void M_MultiPlayer_Key (int key)
{
    switch (key)
    {
    case K_ESCAPE:
        M_Menu_Main_f ();
        break;

    case K_DOWNARROW:
        service::LocalSound("misc/menu1.wav");
        if (++m_multiplayer_cursor >= MULTIPLAYER_ITEMS)
            m_multiplayer_cursor = 0;
        break;

    case K_UPARROW:
        service::LocalSound("misc/menu1.wav");
        if (--m_multiplayer_cursor < 0)
            m_multiplayer_cursor = MULTIPLAYER_ITEMS - 1;
        break;

    case K_ENTER:
        m_entersound = true;
        switch (m_multiplayer_cursor)
        {
        case 0:
            if (serialAvailable || ipxAvailable || tcpipAvailable)
                M_Menu_Net_f ();
            break;

        case 1:
            if (serialAvailable || ipxAvailable || tcpipAvailable)
                M_Menu_Net_f ();
            break;

        case 2:
            M_Menu_Setup_f ();
            break;
        }
    }
}

//=============================================================================
/* SETUP MENU */

int        setup_cursor = 4;
int        setup_cursor_table[] = {40, 56, 80, 104, 140};

std::string setup_hostname, setup_myname;
int        setup_oldtop;
int        setup_oldbottom;
int        setup_top;
int        setup_bottom;

#define    NUM_SETUP_CMDS    5

void M_Menu_Setup_f (void)
{
    key_dest = key_menu;
    m_state = m_setup;
    m_entersound = true;
    setup_myname = Cvar_VariableString("_cl_name");
    setup_hostname = Cvar_VariableString("hostname");
    
    int c = (int)Cvar_VariableValue("_cl_color");
    setup_top = setup_oldtop = c >> 4;
    setup_bottom = setup_oldbottom = c & 15;
}


void M_Setup_Draw (void)
{
    M_DrawPic (16, 4, m_qplaque);
    M_DrawPic ((320 - m_p_multi->wad_width)/2, 4, m_p_multi);

    M_Print (64, 40, "Hostname");
    M_DrawTextBox (160, 32, 16, 1);
    M_Print (168, 40, setup_hostname);

    M_Print (64, 56, "Your name");
    M_DrawTextBox (160, 48, 16, 1);
    M_Print (168, 56, setup_myname);

    M_Print (64, 80, "Shirt color");
    M_Print (64, 104, "Pants color");

    M_DrawTextBox (64, 140-8, 14, 1);
    M_Print (72, 140, "Accept Changes");

    M_DrawPic (160, 64, m_bigbox);
    M_BuildTranslationTable(setup_top*16, setup_bottom*16);
    M_DrawTransPicTranslate (172, 72, m_menuplyr);

    M_DrawCharacter (56, setup_cursor_table [setup_cursor], 12+((int)(realtime*4)&1));

    if (setup_cursor == 0)
        M_DrawCharacter (168 + 8*setup_hostname.length(), setup_cursor_table [setup_cursor], 10+((int)(realtime*4)&1));

    if (setup_cursor == 1)
        M_DrawCharacter (168 + 8*setup_myname.length(), setup_cursor_table [setup_cursor], 10+((int)(realtime*4)&1));
}


void M_Setup_Key (int k)
{
    int            l;

    switch (k)
    {
    case K_ESCAPE:
        M_Menu_MultiPlayer_f ();
        break;

    case K_UPARROW:
        service::LocalSound("misc/menu1.wav");
        setup_cursor--;
        if (setup_cursor < 0)
            setup_cursor = NUM_SETUP_CMDS-1;
        break;

    case K_DOWNARROW:
        service::LocalSound("misc/menu1.wav");
        setup_cursor++;
        if (setup_cursor >= NUM_SETUP_CMDS)
            setup_cursor = 0;
        break;

    case K_LEFTARROW:
        if (setup_cursor < 2)
            return;
        service::LocalSound("misc/menu3.wav");
        if (setup_cursor == 2)
            setup_top = setup_top - 1;
        if (setup_cursor == 3)
            setup_bottom = setup_bottom - 1;
        break;
    case K_RIGHTARROW:
        if (setup_cursor < 2)
            return;
forward:
        service::LocalSound("misc/menu3.wav");
        if (setup_cursor == 2)
            setup_top = setup_top + 1;
        if (setup_cursor == 3)
            setup_bottom = setup_bottom + 1;
        break;

    case K_ENTER:
        if (setup_cursor == 0 || setup_cursor == 1)
            return;

        if (setup_cursor == 2 || setup_cursor == 3)
            goto forward;

        // setup_cursor == 4 (OK)
        if (Cvar_VariableString("_cl_name") != setup_myname)
            Cbuf_AddText (va("name \"%s\"\n", setup_myname.c_str()));
        if (Cvar_VariableString("hostname") != setup_hostname)
            Cvar_Set("hostname", setup_hostname);
        if (setup_top != setup_oldtop || setup_bottom != setup_oldbottom)
            Cbuf_AddText( va ("color %i %i\n", setup_top, setup_bottom) );
        m_entersound = true;
        M_Menu_MultiPlayer_f ();
        break;

    case K_BACKSPACE:
        if (setup_cursor == 0)
        {
            if (setup_hostname.length())
                setup_hostname.erase(setup_hostname.length() - 1);
        }

        if (setup_cursor == 1)
        {
            if (setup_myname.length())
                setup_myname.erase(setup_myname.length() - 1);
        }
        break;

    default:
        if (k < 32 || k > 127)
            break;
        if (setup_cursor == 0)
        {
            l = setup_hostname.length();
            if (l < 15)
                setup_hostname.push_back((char)k);
        }
        if (setup_cursor == 1)
        {
            l = setup_myname.length();
            if (l < 15)
                setup_myname.push_back((char)k);
        }
    }

    if (setup_top > 13)
        setup_top = 0;
    if (setup_top < 0)
        setup_top = 13;
    if (setup_bottom > 13)
        setup_bottom = 0;
    if (setup_bottom < 0)
        setup_bottom = 13;
}

//=============================================================================
/* NET MENU */

int    m_net_cursor;
int m_net_items;
int m_net_saveHeight;

char *net_helpMessage [] =
{
/* .........1.........2.... */
  "                        ",
  " Two computers connected",
  "   through two modems.  ",
  "                        ",

  "                        ",
  " Two computers connected",
  " by a null-modem cable. ",
  "                        ",

  " Novell network LANs    ",
  " or Windows 95 DOS-box. ",
  "                        ",
  "(LAN=Local Area Network)",

  " Commonly used to play  ",
  " over the Internet, but ",
  " also used on a Local   ",
  " Area Network.          "
};

void M_Menu_Net_f (void)
{
    key_dest = key_menu;
    m_state = m_net;
    m_entersound = true;
    m_net_items = 4;

    if (m_net_cursor >= m_net_items)
        m_net_cursor = 0;
    m_net_cursor--;
    M_Net_Key (K_DOWNARROW);
}


void M_Net_Draw (void)
{
    int        f;

    M_DrawPic (16, 4, m_qplaque);
    M_DrawPic ((320 - m_p_multi->wad_width)/2, 4, m_p_multi);

    f = (int)(host_time * 10)%6;
    M_DrawPic (54, 32 + m_net_cursor * 20, m_menudot[f]);
}


void M_Net_Key (int k)
{
    switch (k)
    {
    case K_ESCAPE:
        M_Menu_MultiPlayer_f ();
        break;

    case K_DOWNARROW:
        service::LocalSound("misc/menu1.wav");
        if (++m_net_cursor >= m_net_items)
            m_net_cursor = 0;
        break;

    case K_UPARROW:
        service::LocalSound("misc/menu1.wav");
        if (--m_net_cursor < 0)
            m_net_cursor = m_net_items - 1;
        break;
    }
}

//=============================================================================
/* OPTIONS MENU */

#define    OPTIONS_ITEMS    14
#define    SLIDER_RANGE    10

int        options_cursor;

void M_Menu_Options_f (void)
{
    key_dest = key_menu;
    m_state = m_options;
    m_entersound = true;

    if ((options_cursor == 13) && (1)) // FIXME windowed
        options_cursor = 0;
}


void M_AdjustSliders (int dir)
{
    float v;
    
    service::LocalSound("misc/menu3.wav");

    switch (options_cursor)
    {
    case 3:
        break;
    case 4:
        break;
    case 5:    // mouse speed
        v = Cvar_VariableValue("sensitivity");
        v += dir * 0.5;
        if (v < 1)
            v = 1;
        if (v > 11)
            v = 11;
        Cvar_SetValue("sensitivity", v);
        break;
    case 6:    // music volume
        v = Cvar_VariableValue("bgmvolume");
        v += dir * 0.1;
        if (v < 0)
            v = 0;
        if (v > 1)
            v = 1;
        Cvar_SetValue ("bgmvolume", v);
        break;
    case 7:    // sfx volume
        v = Cvar_VariableValue("volume");
        v += dir * 0.1;
        if (v < 0)
            v = 0;
        if (v > 1)
            v = 1;
        Cvar_SetValue ("volume", v);
        break;

    case 8:    // always run
        if (Cvar_VariableValue("cl_forwardspeed") > 200)
        {
            Cvar_SetValue ("cl_forwardspeed", 200);
            Cvar_SetValue ("cl_backspeed", 200);
        }
        else
        {
            Cvar_SetValue ("cl_forwardspeed", 400);
            Cvar_SetValue ("cl_backspeed", 400);
        }
        break;

    case 9:    // invert mouse
        Cvar_SetValue ("m_pitch", -Cvar_VariableValue("m_pitch"));
        break;

    case 10:    // lookspring
        Cvar_SetValue ("lookspring", !Cvar_VariableValue("lookspring"));
        break;

    case 11:    // lookstrafe
        Cvar_SetValue ("lookstrafe", !Cvar_VariableValue("lookstrafe"));
        break;

    case 13:    // in_windowed_mouse
        Cvar_SetValue ("in_windowed_mouse", !Cvar_VariableValue("in_windowed_mouse"));
        break;
    }
}


void M_DrawSlider (int x, int y, float range)
{
    int    i;

    if (range < 0)
        range = 0;
    if (range > 1)
        range = 1;
    M_DrawCharacter (x-8, y, 128);
    for (i=0 ; i<SLIDER_RANGE ; i++)
        M_DrawCharacter (x + i*8, y, 129);
    M_DrawCharacter (x+i*8, y, 130);
    M_DrawCharacter (x + (SLIDER_RANGE-1)*8 * range, y, 131);
}

void M_DrawCheckbox (int x, int y, int on)
{
    if (on)
        M_Print (x, y, "on");
    else
        M_Print (x, y, "off");
}

void M_Options_Draw (void)
{
    float        r;

    M_DrawPic (16, 4, m_qplaque);
    M_DrawPic ((320 - m_p_option->wad_width)/2, 4, m_p_option);

    M_Print (16, 32, "    Customize controls");
    M_Print (16, 40, "         Go to console");
    M_Print (16, 48, "     Reset to defaults");

    M_Print (16, 72, "           Mouse Speed");
    r = (Cvar_VariableValue("sensitivity") - 1)/10;
    M_DrawSlider (220, 72, r);

    M_Print (16, 80, "       CD Music Volume");
    r = Cvar_VariableValue("bgmvolume");
    M_DrawSlider (220, 80, r);

    M_Print (16, 88, "          Sound Volume");
    r = Cvar_VariableValue("volume");
    M_DrawSlider (220, 88, r);

    M_Print (16, 96,  "            Always Run");
    M_DrawCheckbox (220, 96, Cvar_VariableValue("cl_forwardspeed") > 200);

    M_Print (16, 104, "          Invert Mouse");
    M_DrawCheckbox (220, 104, Cvar_VariableValue("m_pitch") < 0);

    M_Print (16, 112, "            Lookspring");
    M_DrawCheckbox (220, 112, Cvar_VariableValue("lookspring"));

    M_Print (16, 120, "            Lookstrafe");
    M_DrawCheckbox (220, 120, Cvar_VariableValue("lookstrafe"));

    if (1) // FIXME windowed
    {
        M_Print (16, 136, "             Use Mouse");
        M_DrawCheckbox (220, 136, Cvar_VariableValue("in_windowed_mouse"));
    }

// cursor
    M_DrawCharacter (200, 32 + options_cursor*8, 12+((int)(realtime*4)&1));
}


void M_Options_Key (int k)
{
again:
    switch (k)
    {
    case K_ESCAPE:
        M_Menu_Main_f ();
        break;

    case K_ENTER:
        m_entersound = true;
        switch (options_cursor)
        {
        case 0:
            M_Menu_Keys_f ();
            break;
        case 1:
            m_state = m_none;
            Con_ToggleConsole_f ();
            break;
        case 2:
            Cbuf_AddText ("exec default.cfg\n");
            break;
        default:
            M_AdjustSliders (1);
            break;
        }
        return;

    case K_UPARROW:
        service::LocalSound("misc/menu1.wav");
        options_cursor--;
        if (options_cursor < 0)
            options_cursor = OPTIONS_ITEMS-1;
        break;

    case K_DOWNARROW:
        service::LocalSound("misc/menu1.wav");
        options_cursor++;
        if (options_cursor >= OPTIONS_ITEMS)
            options_cursor = 0;
        break;

    case K_LEFTARROW:
        M_AdjustSliders (-1);
        break;

    case K_RIGHTARROW:
        M_AdjustSliders (1);
        break;
    }

    if (options_cursor == 4 || options_cursor == 3 ||
        options_cursor == 12 ||
        (options_cursor == 13 && 0)) // FIXME: not windowed
        goto again;
}

//=============================================================================
/* KEYS MENU */

char *bindnames[][2] =
{
{"+attack",         "attack"},
{"impulse 10",         "change weapon"},
{"+jump",             "jump / swim up"},
{"+forward",         "walk forward"},
{"+back",             "backpedal"},
{"+left",             "turn left"},
{"+right",             "turn right"},
{"+speed",             "run"},
{"+moveleft",         "step left"},
{"+moveright",         "step right"},
{"+strafe",         "sidestep"},
{"+lookup",         "look up"},
{"+lookdown",         "look down"},
{"centerview",         "center view"},
{"+mlook",             "mouse look"},
{"+klook",             "keyboard look"},
{"+moveup",            "swim up"},
{"+movedown",        "swim down"}
};

#define    NUMCOMMANDS    (sizeof(bindnames)/sizeof(bindnames[0]))

int        keys_cursor;
int        bind_grab;

void M_Menu_Keys_f (void)
{
    key_dest = key_menu;
    m_state = m_keys;
    m_entersound = true;
}


void M_FindKeysForCommand (char *command, int *twokeys)
{
    int        count;
    int        j;
    std::string b;

    twokeys[0] = twokeys[1] = -1;
    count = 0;

    for (j=0 ; j<256 ; j++)
    {
        b = Key_BindingForKey(j);
        if (!b.length())
            continue;
        if (b == std::string(command))
        {
            twokeys[count] = j;
            count++;
            if (count == 2)
                break;
        }
    }
}

void M_UnbindCommand (char *command)
{
    int        j;
    std::string b;

    for (j=0 ; j<256 ; j++)
    {
        b = Key_BindingForKey(j);
        if (!b.length())
            continue;
        if (b == std::string(command))
            Key_SetBinding (j, "");
    }
}


void M_Keys_Draw (void)
{
    int        keys[2];
    char    *name;
    int        x, y;

    M_DrawPic ((320 - m_ttl_cstm->wad_width)/2, 4, m_ttl_cstm);

    if (bind_grab)
        M_Print (12, 32, "Press a key or button for this action");
    else
        M_Print (18, 32, "Enter to change, backspace to clear");

// search for known bindings
    for (size_t i=0 ; i<NUMCOMMANDS ; i++)
    {
        y = 48 + 8*i;

        M_Print (16, y, bindnames[i][1]);
        M_FindKeysForCommand (bindnames[i][0], keys);

        if (keys[0] == -1)
            M_Print (140, y, "???");
        else
        {
            name = Key_KeynumToString (keys[0]);
            M_Print (140, y, name);
            x = strlen(name) * 8;
            if (keys[1] != -1)
            {
                M_Print (140 + x + 8, y, "or");
                M_Print (140 + x + 32, y, Key_KeynumToString (keys[1]));
            }
        }
    }

    if (bind_grab)
        M_DrawCharacter (130, 48 + keys_cursor*8, '=');
    else
        M_DrawCharacter (130, 48 + keys_cursor*8, 12+((int)(realtime*4)&1));
}


void M_Keys_Key (int k)
{
    char    cmd[80];
    int        keys[2];

    if (bind_grab)
    {    // defining a key
        service::LocalSound("misc/menu1.wav");
        if (k == K_ESCAPE)
        {
            bind_grab = false;
        }
        else if (k != '`')
        {
            sprintf (cmd, "bind \"%s\" \"%s\"\n", Key_KeynumToString (k), bindnames[keys_cursor][0]);
            Cbuf_InsertText (cmd);
        }

        bind_grab = false;
        return;
    }

    switch (k)
    {
    case K_ESCAPE:
        M_Menu_Options_f ();
        break;

    case K_LEFTARROW:
    case K_UPARROW:
        service::LocalSound("misc/menu1.wav");
        keys_cursor--;
        if (keys_cursor < 0)
            keys_cursor = NUMCOMMANDS-1;
        break;

    case K_DOWNARROW:
    case K_RIGHTARROW:
        service::LocalSound("misc/menu1.wav");
        keys_cursor++;
        if (keys_cursor >= (int)NUMCOMMANDS)
            keys_cursor = 0;
        break;

    case K_ENTER:        // go into bind mode
        M_FindKeysForCommand (bindnames[keys_cursor][0], keys);
        service::LocalSound("misc/menu2.wav");
        if (keys[1] != -1)
            M_UnbindCommand (bindnames[keys_cursor][0]);
        bind_grab = true;
        break;

    case K_BACKSPACE:        // delete bindings
    case K_DEL:                // delete bindings
        service::LocalSound("misc/menu2.wav");
        M_UnbindCommand (bindnames[keys_cursor][0]);
        break;
    }
}

//=============================================================================
/* HELP MENU */

int        help_page;
#define    NUM_HELP_PAGES    6


void M_Menu_Help_f (void)
{
    key_dest = key_menu;
    m_state = m_help;
    m_entersound = true;
    help_page = 0;
}



void M_Help_Draw (void)
{
    M_DrawPic (0, 0, m_helppage[help_page]);
}


void M_Help_Key (int key)
{
    switch (key)
    {
    case K_ESCAPE:
        M_Menu_Main_f ();
        break;

    case K_UPARROW:
    case K_RIGHTARROW:
        m_entersound = true;
        if (++help_page >= NUM_HELP_PAGES)
            help_page = 0;
        break;

    case K_DOWNARROW:
    case K_LEFTARROW:
        m_entersound = true;
        if (--help_page < 0)
            help_page = NUM_HELP_PAGES-1;
        break;
    }

}

//=============================================================================
/* QUIT MENU */

int        msgNumber;
m_states    m_quit_prevstate;
qboolean    wasInMenus;

char *quitMessage [] =
{
/* .........1.........2.... */
  "  Are you gonna quit    ",
  "  this game just like   ",
  "   everything else?     ",
  "                        ",

  " Milord, methinks that  ",
  "   thou art a lowly     ",
  " quitter. Is this true? ",
  "                        ",

  " Do I need to bust your ",
  "  face open for trying  ",
  "        to quit?        ",
  "                        ",

  " Man, I oughta smack you",
  "   for trying to quit!  ",
  "     Press Y to get     ",
  "      smacked out.      ",

  " Press Y to quit like a ",
  "   big loser in life.   ",
  "  Press N to stay proud ",
  "    and successful!     ",

  "   If you press Y to    ",
  "  quit, I will summon   ",
  "  Satan all over your   ",
  "      hard drive!       ",

  "  Um, Asmodeus dislikes ",
  " his children trying to ",
  " quit. Press Y to return",
  "   to your Tinkertoys.  ",

  "  If you quit now, I'll ",
  "  throw a blanket-party ",
  "   for you next time!   ",
  "                        "
};

void M_Menu_Quit_f (void)
{
    if (m_state == m_quit)
        return;
    wasInMenus = (key_dest == key_menu);
    key_dest = key_menu;
    m_quit_prevstate = m_state;
    m_state = m_quit;
    m_entersound = true;
    msgNumber = rand()&7;
}


void M_Quit_Key (int key)
{
    switch (key)
    {
    case K_ESCAPE:
    case 'n':
    case 'N':
        if (wasInMenus)
        {
            m_state = m_quit_prevstate;
            m_entersound = true;
        }
        else
        {
            key_dest = key_game;
            m_state = m_none;
        }
        break;

    case 'Y':
    case 'y':
        key_dest = key_console;
        Host_Quit_f ();
        break;

    default:
        break;
    }

}


void M_Quit_Draw (void)
{
    if (wasInMenus)
    {
        m_state = m_quit_prevstate;
        m_recursiveDraw = true;
        M_Draw ();
        m_state = m_quit;
    }

    M_DrawTextBox (56, 76, 24, 4);
    M_Print (64, 84,  quitMessage[msgNumber*4+0]);
    M_Print (64, 92,  quitMessage[msgNumber*4+1]);
    M_Print (64, 100, quitMessage[msgNumber*4+2]);
    M_Print (64, 108, quitMessage[msgNumber*4+3]);
}

//=============================================================================
/* GAME OPTIONS MENU */

typedef struct
{
    char    *name;
    char    *description;
} level_t;

level_t        levels[] =
{
    {"start", "Entrance"},    // 0

    {"e1m1", "Slipgate Complex"},                // 1
    {"e1m2", "Castle of the Damned"},
    {"e1m3", "The Necropolis"},
    {"e1m4", "The Grisly Grotto"},
    {"e1m5", "Gloom Keep"},
    {"e1m6", "The Door To Chthon"},
    {"e1m7", "The House of Chthon"},
    {"e1m8", "Ziggurat Vertigo"},

    {"e2m1", "The Installation"},                // 9
    {"e2m2", "Ogre Citadel"},
    {"e2m3", "Crypt of Decay"},
    {"e2m4", "The Ebon Fortress"},
    {"e2m5", "The Wizard's Manse"},
    {"e2m6", "The Dismal Oubliette"},
    {"e2m7", "Underearth"},

    {"e3m1", "Termination Central"},            // 16
    {"e3m2", "The Vaults of Zin"},
    {"e3m3", "The Tomb of Terror"},
    {"e3m4", "Satan's Dark Delight"},
    {"e3m5", "Wind Tunnels"},
    {"e3m6", "Chambers of Torment"},
    {"e3m7", "The Haunted Halls"},

    {"e4m1", "The Sewage System"},                // 23
    {"e4m2", "The Tower of Despair"},
    {"e4m3", "The Elder God Shrine"},
    {"e4m4", "The Palace of Hate"},
    {"e4m5", "Hell's Atrium"},
    {"e4m6", "The Pain Maze"},
    {"e4m7", "Azure Agony"},
    {"e4m8", "The Nameless City"},

    {"end", "Shub-Niggurath's Pit"},            // 31

    {"dm1", "Place of Two Deaths"},                // 32
    {"dm2", "Claustrophobopolis"},
    {"dm3", "The Abandoned Base"},
    {"dm4", "The Bad Place"},
    {"dm5", "The Cistern"},
    {"dm6", "The Dark Zone"}
};

typedef struct
{
    char    *description;
    int        firstLevel;
    int        levels;
} episode_t;

episode_t    episodes[] =
{
    {"Welcome to Quake", 0, 1},
    {"Doomed Dimension", 1, 8},
    {"Realm of Black Magic", 9, 7},
    {"Netherworld", 16, 7},
    {"The Elder World", 23, 8},
    {"Final Level", 31, 1},
    {"Deathmatch Arena", 32, 6}
};

int    startepisode;
int    startlevel;
int maxplayers;
qboolean m_serverInfoMessage = false;
double m_serverInfoMessageTime;

void M_Menu_GameOptions_f (void)
{
    key_dest = key_menu;
    m_state = m_gameoptions;
    m_entersound = true;
    if (maxplayers == 0)
        maxplayers = svs.maxclients;
    if (maxplayers < 2)
        maxplayers = svs.maxclientslimit;
}


int gameoptions_cursor_table[] = {40, 56, 64, 72, 80, 88, 96, 112, 120};
#define    NUM_GAMEOPTIONS    9
int        gameoptions_cursor;

void M_GameOptions_Draw (void)
{
    int        x;
    char *msg;

    M_DrawPic (16, 4, m_qplaque);
    M_DrawPic ((320 - m_p_multi->wad_width)/2, 4, m_p_multi);

    M_DrawTextBox (152, 32, 10, 1);
    M_Print (160, 40, "begin game");

    M_Print (0, 56, "      Max players");
    M_Print (160, 56, va("%i", maxplayers) );

    M_Print (0, 64, "        Game Type");
    if (Cvar_VariableValue("coop"))
        M_Print (160, 64, "Cooperative");
    else
        M_Print (160, 64, "Deathmatch");

    M_Print (0, 72, "        Teamplay");

    switch ((int)Cvar_VariableValue("teamplay"))
    {
        case 1: msg = "No Friendly Fire"; break;
        case 2: msg = "Friendly Fire"; break;
        default: msg = "Off"; break;
    }
    M_Print (160, 72, msg);

    M_Print (0, 80, "            Skill");
    switch ((int)Cvar_VariableValue("skill"))
    {
        case 0: M_Print (160, 80, "Easy difficulty"); break;
        case 2: M_Print (160, 80, "Hard difficulty"); break;
        case 3: M_Print (160, 80, "Nightmare difficulty"); break;
        default: M_Print (160, 80, "Normal difficulty"); break;
    }   

    M_Print (0, 88, "       Frag Limit");
    if (Cvar_VariableValue("fraglimit") == 0)
        M_Print (160, 88, "none");
    else
        M_Print (160, 88, va("%i frags", (int)Cvar_VariableValue("fraglimit")));

    M_Print (0, 96, "       Time Limit");
    if (Cvar_VariableValue("timelimit") == 0)
        M_Print (160, 96, "none");
    else
        M_Print (160, 96, va("%i minutes", (int)Cvar_VariableValue("timelimit")));

    M_Print (0, 112, "         Episode");
    M_Print (160, 112, episodes[startepisode].description);

    M_Print (0, 120, "           Level");
    M_Print (160, 120, levels[episodes[startepisode].firstLevel + startlevel].description);
    M_Print (160, 128, levels[episodes[startepisode].firstLevel + startlevel].name);

// line cursor
    M_DrawCharacter (144, gameoptions_cursor_table[gameoptions_cursor], 12+((int)(realtime*4)&1));

    if (m_serverInfoMessage)
    {
        if ((realtime - m_serverInfoMessageTime) < 5.0)
        {
            x = (320-26*8)/2;
            M_DrawTextBox (x, 138, 24, 4);
            x += 8;
            M_Print (x, 146, "  More than 4 players   ");
            M_Print (x, 154, " requires using command ");
            M_Print (x, 162, "line parameters; please ");
            M_Print (x, 170, "   see techinfo.txt.    ");
        }
        else
        {
            m_serverInfoMessage = false;
        }
    }
}


void M_NetStart_Change (int dir)
{
    int count;
    float v;

    switch (gameoptions_cursor)
    {
    case 1:
        maxplayers += dir;
        if (maxplayers > svs.maxclientslimit)
        {
            maxplayers = svs.maxclientslimit;
            m_serverInfoMessage = true;
            m_serverInfoMessageTime = realtime;
        }
        if (maxplayers < 2)
            maxplayers = 2;
        break;

    case 2:
        Cvar_SetValue ("coop", !Cvar_VariableValue("coop"));
        break;

    case 3:
        v = Cvar_VariableValue("teamplay");
        v += dir;
        if (v > 2)
            v = 0;
        else if (v < 0)
            v = 2;
        Cvar_SetValue ("teamplay", v);
        break;

    case 4:
        v = Cvar_VariableValue("skill");
        v += dir;
        if (v > 3)
            v = 0;
        else if (v < 0)
            v = 3;
        Cvar_SetValue ("skill", v);
        break;

    case 5:
        v = Cvar_VariableValue("fraglimit");
        v += dir * 10;
        if (v > 100)
            v = 0;
        else if (v < 0)
            v = 100;
        Cvar_SetValue ("fraglimit", v);
        break;

    case 6:
        v = Cvar_VariableValue("timelimit");
        v += dir * 5;
        if (v > 60)
            v = 0;
        else if (v < 0)
            v = 60;
        Cvar_SetValue("timelimit", v);
        break;

    case 7:
        startepisode += dir;
        if (Cvar_VariableValue("registered"))
            count = 7;
        else
            count = 2;

        if (startepisode < 0)
            startepisode = count - 1;

        if (startepisode >= count)
            startepisode = 0;

        startlevel = 0;
        break;

    case 8:
        startlevel += dir;
        count = episodes[startepisode].levels;

        if (startlevel < 0)
            startlevel = count - 1;

        if (startlevel >= count)
            startlevel = 0;
        break;
    }
}

void M_GameOptions_Key (int key)
{
    switch (key)
    {
    case K_ESCAPE:
        M_Menu_Net_f ();
        break;

    case K_UPARROW:
        service::LocalSound("misc/menu1.wav");
        gameoptions_cursor--;
        if (gameoptions_cursor < 0)
            gameoptions_cursor = NUM_GAMEOPTIONS-1;
        break;

    case K_DOWNARROW:
        service::LocalSound("misc/menu1.wav");
        gameoptions_cursor++;
        if (gameoptions_cursor >= NUM_GAMEOPTIONS)
            gameoptions_cursor = 0;
        break;

    case K_LEFTARROW:
        if (gameoptions_cursor == 0)
            break;
        service::LocalSound("misc/menu3.wav");
        M_NetStart_Change (-1);
        break;

    case K_RIGHTARROW:
        if (gameoptions_cursor == 0)
            break;
        service::LocalSound("misc/menu3.wav");
        M_NetStart_Change (1);
        break;

    case K_ENTER:
        service::LocalSound("misc/menu2.wav");
        if (gameoptions_cursor == 0)
        {
            if (sv.active)
                Cbuf_AddText ("disconnect\n");
            Cbuf_AddText ("listen 0\n");    // so host_netport will be re-examined
            Cbuf_AddText ( va ("maxplayers %u\n", maxplayers) );
            SCR_BeginLoadingPlaque ();

            Cbuf_AddText ( va ("map %s\n", levels[episodes[startepisode].firstLevel + startlevel].name) );

            return;
        }

        M_NetStart_Change (1);
        break;
    }
}

//=============================================================================
/* Menu Subsystem */


void M_Init (void)
{
    int i;

    Cmd_AddCommand ("togglemenu", M_ToggleMenu_f);

    Cmd_AddCommand ("menu_main", M_Menu_Main_f);
    Cmd_AddCommand ("menu_singleplayer", M_Menu_SinglePlayer_f);
    Cmd_AddCommand ("menu_load", M_Menu_Load_f);
    Cmd_AddCommand ("menu_save", M_Menu_Save_f);
    Cmd_AddCommand ("menu_multiplayer", M_Menu_MultiPlayer_f);
    Cmd_AddCommand ("menu_setup", M_Menu_Setup_f);
    Cmd_AddCommand ("menu_options", M_Menu_Options_f);
    Cmd_AddCommand ("menu_keys", M_Menu_Keys_f);
    Cmd_AddCommand ("help", M_Menu_Help_f);
    Cmd_AddCommand ("menu_quit", M_Menu_Quit_f);

    // Load menu pictures
    m_box_tl = Draw_PicFromFile("gfx/box_tl.lmp");
    m_box_ml = Draw_PicFromFile("gfx/box_ml.lmp");
    m_box_bl = Draw_PicFromFile("gfx/box_bl.lmp");
    m_box_tm = Draw_PicFromFile("gfx/box_tm.lmp");
    m_box_mm = Draw_PicFromFile("gfx/box_mm.lmp");
    m_box_mm2 = Draw_PicFromFile("gfx/box_mm2.lmp");
    m_box_bm = Draw_PicFromFile("gfx/box_bm.lmp");
    m_box_tr = Draw_PicFromFile("gfx/box_tr.lmp");
    m_box_mr = Draw_PicFromFile("gfx/box_mr.lmp");
    m_box_br = Draw_PicFromFile("gfx/box_br.lmp");

    m_qplaque = Draw_PicFromFile("gfx/qplaque.lmp");
    m_ttl_main = Draw_PicFromFile("gfx/ttl_main.lmp");
    m_mainmenu = Draw_PicFromFile("gfx/mainmenu.lmp");
    m_ttl_sgl = Draw_PicFromFile("gfx/ttl_sgl.lmp");
    m_sp_menu = Draw_PicFromFile("gfx/sp_menu.lmp");
    m_p_load = Draw_PicFromFile("gfx/p_load.lmp");
    m_p_save = Draw_PicFromFile("gfx/p_save.lmp");
    m_p_multi = Draw_PicFromFile("gfx/p_multi.lmp");
    m_mp_menu = Draw_PicFromFile("gfx/mp_menu.lmp");
    m_bigbox = Draw_PicFromFile("gfx/bigbox.lmp");
    m_menuplyr = Draw_PicFromFile("gfx/menuplyr.lmp");
    m_p_option = Draw_PicFromFile("gfx/p_option.lmp");
    m_ttl_cstm = Draw_PicFromFile("gfx/ttl_cstm.lmp");

    for (i = 0 ; i < 6 ; i++)
    {
        m_menudot[i] = Draw_PicFromFile(va("gfx/menudot%i.lmp", i+1));
        m_helppage[i] = Draw_PicFromFile(va("gfx/help%i.lmp", i));
    }
}


void M_Draw (void)
{
    if (m_state == m_none || key_dest != key_menu)
        return;

    if (!m_recursiveDraw)
    {
        if (scr_con_current)
            Draw_ConsoleBackground (vid.height);
        else
            Draw_FadeScreen ();
    }
    else
    {
        m_recursiveDraw = false;
    }

    switch (m_state)
    {
    case m_none:
        break;

    case m_main:
        M_Main_Draw ();
        break;

    case m_singleplayer:
        M_SinglePlayer_Draw ();
        break;

    case m_load:
        M_Load_Draw ();
        break;

    case m_save:
        M_Save_Draw ();
        break;

    case m_multiplayer:
        M_MultiPlayer_Draw ();
        break;

    case m_setup:
        M_Setup_Draw ();
        break;

    case m_net:
        M_Net_Draw ();
        break;

    case m_options:
        M_Options_Draw ();
        break;

    case m_keys:
        M_Keys_Draw ();
        break;

    case m_help:
        M_Help_Draw ();
        break;

    case m_quit:
        M_Quit_Draw ();
        break;

    case m_gameoptions:
        M_GameOptions_Draw ();
        break;
    }

    if (m_entersound)
    {
        service::LocalSound("misc/menu2.wav");
        m_entersound = false;
    }
}


void M_Keydown (int key)
{
    switch (m_state)
    {
    case m_none:
        return;

    case m_main:
        M_Main_Key (key);
        return;

    case m_singleplayer:
        M_SinglePlayer_Key (key);
        return;

    case m_load:
        M_Load_Key (key);
        return;

    case m_save:
        M_Save_Key (key);
        return;

    case m_multiplayer:
        M_MultiPlayer_Key (key);
        return;

    case m_setup:
        M_Setup_Key (key);
        return;

    case m_net:
        M_Net_Key (key);
        return;

    case m_options:
        M_Options_Key (key);
        return;

    case m_keys:
        M_Keys_Key (key);
        return;

    case m_help:
        M_Help_Key (key);
        return;

    case m_quit:
        M_Quit_Key (key);
        return;

    case m_gameoptions:
        M_GameOptions_Key (key);
        return;
    }
}


void M_ConfigureNetSubsystem(void)
{
    Cbuf_AddText ("stopdemo\n");
}
