/*
Copyright (C) 1996-2001 Id Software, Inc.
Copyright (C) 2002-2009 John Fitzgibbons and others
Copyright (C) 2010-2014 QuakeSpasm developers

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

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/

#include <cstring>

#include "quakedef.hpp"
#include "bgmusic.hpp"

void (*vid_menucmdfn)(void); //johnfitz
void (*vid_menudrawfn)(void);

void (*vid_menukeyfn)(int key);

enum m_state_e 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_LanConfig_f(void);

void M_Menu_GameOptions_f(void);

void M_Menu_Search_f(void);

void M_Menu_ServerList_f(void);

void M_Menu_Options_f(void);

void M_Menu_Keys_f(void);

void M_Menu_Video_f(void);

void M_Menu_Help_f(void);

void M_Menu_Quit_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_LanConfig_Draw(void);

void M_GameOptions_Draw(void);

void M_Search_Draw(void);

void M_ServerList_Draw(void);

void M_Options_Draw(void);

void M_Keys_Draw(void);

void M_Video_Draw(void);

void M_Help_Draw(void);

void M_Quit_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_LanConfig_Key(int key);

void M_GameOptions_Key(int key);

void M_Search_Key(int key);

void M_ServerList_Key(int key);

void M_Options_Key(int key);

void M_Keys_Key(int key);

void M_Video_Key(int key);

void M_Help_Key(int key);

void M_Quit_Key(int key);

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

enum m_state_e m_return_state;
bool m_return_onerror;
char m_return_reason[32];

#define StartingGame	(m_multiplayer_cursor == 1)
#define JoiningGame		(m_multiplayer_cursor == 0)
#define	IPXConfig		(m_net_cursor == 0)
#define	TCPIPConfig		(m_net_cursor == 1)

void M_ConfigureNetSubsystem(void);

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

Draws one solid graphics character
================
*/
void M_DrawCharacter(int cx, int line, int num) {
    Draw_Character(cx, line, num);
}

void M_Print(int cx, int cy, const char *str) {
    while (*str) {
        M_DrawCharacter(cx, cy, (*str) + 128);
        str++;
        cx += 8;
    }
}

void M_PrintWhite(int cx, int cy, const char *str) {
    while (*str) {
        M_DrawCharacter(cx, cy, *str);
        str++;
        cx += 8;
    }
}

void M_DrawTransPic(int x, int y, qpic_t *pic) {
    Draw_Pic(x, y, pic); //johnfitz -- simplified becuase centering is handled elsewhere
}

void M_DrawPic(int x, int y, qpic_t *pic) {
    Draw_Pic(x, y, pic); //johnfitz -- simplified becuase centering is handled elsewhere
}

void M_DrawTransPicTranslate(int x, int y, qpic_t *pic, int top, int bottom) //johnfitz -- more parameters
{
    Draw_TransPicTranslate(x, y, pic, top, bottom); //johnfitz -- simplified becuase centering is handled elsewhere
}

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

    // draw left side
    cx = x;
    cy = y;
    p = Draw_CachePic("gfx/box_tl.lmp");
    M_DrawTransPic(cx, cy, p);
    p = Draw_CachePic("gfx/box_ml.lmp");
    for (n = 0; n < lines; n++) {
        cy += 8;
        M_DrawTransPic(cx, cy, p);
    }
    p = Draw_CachePic("gfx/box_bl.lmp");
    M_DrawTransPic(cx, cy + 8, p);

    // draw middle
    cx += 8;
    while (width > 0) {
        cy = y;
        p = Draw_CachePic("gfx/box_tm.lmp");
        M_DrawTransPic(cx, cy, p);
        p = Draw_CachePic("gfx/box_mm.lmp");
        for (n = 0; n < lines; n++) {
            cy += 8;
            if (n == 1)
                p = Draw_CachePic("gfx/box_mm2.lmp");
            M_DrawTransPic(cx, cy, p);
        }
        p = Draw_CachePic("gfx/box_bm.lmp");
        M_DrawTransPic(cx, cy + 8, p);
        width -= 2;
        cx += 16;
    }

    // draw right side
    cy = y;
    p = Draw_CachePic("gfx/box_tr.lmp");
    M_DrawTransPic(cx, cy, p);
    p = Draw_CachePic("gfx/box_mr.lmp");
    for (n = 0; n < lines; n++) {
        cy += 8;
        M_DrawTransPic(cx, cy, p);
    }
    p = Draw_CachePic("gfx/box_br.lmp");
    M_DrawTransPic(cx, cy + 8, p);
}

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

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;
        }

        IN_Activate();
        key_dest = key_game;
        m_state = m_none;
        return;
    }
    if (key_dest == key_console) {
        console::toggle_console();
    } 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;
    }
    IN_Deactivate(modestate == MS_WINDOWED);
    key_dest = key_menu;
    m_state = m_main;
    m_entersound = true;
}


void M_Main_Draw(void) {
    int f;
    qpic_t *p;

    M_DrawTransPic(16, 4, Draw_CachePic("gfx/qplaque.lmp"));
    p = Draw_CachePic("gfx/ttl_main.lmp");
    M_DrawPic((320 - p->width) / 2, 4, p);
    M_DrawTransPic(72, 32, Draw_CachePic("gfx/mainmenu.lmp"));

    f = (int) (realtime * 10) % 6;

    M_DrawTransPic(54, 32 + m_main_cursor * 20, Draw_CachePic(va("gfx/menudot%i.lmp", f + 1)));
}


void M_Main_Key(int key) {
    switch (key) {
        case K_ESCAPE:
        case K_BBUTTON:
            IN_Activate();
            key_dest = key_game;
            m_state = m_none;
            cls.demonum = m_save_demonum;
            if (!fitzmode && !cl_startdemos.value) /* QuakeSpasm customization: */
                break;
            if (cls.demonum != -1 && !cls.demoplayback && cls.state != ca_connected)
                CL_NextDemo();
            break;

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

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

        case K_ENTER:
        case K_KP_ENTER:
        case K_ABUTTON:
            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) {
    IN_Deactivate(modestate == MS_WINDOWED);
    key_dest = key_menu;
    m_state = m_singleplayer;
    m_entersound = true;
}


void M_SinglePlayer_Draw(void) {
    int f;
    qpic_t *p;

    M_DrawTransPic(16, 4, Draw_CachePic("gfx/qplaque.lmp"));
    p = Draw_CachePic("gfx/ttl_sgl.lmp");
    M_DrawPic((320 - p->width) / 2, 4, p);
    M_DrawTransPic(72, 32, Draw_CachePic("gfx/sp_menu.lmp"));

    f = (int) (realtime * 10) % 6;

    M_DrawTransPic(54, 32 + m_singleplayer_cursor * 20, Draw_CachePic(va("gfx/menudot%i.lmp", f + 1)));
}


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

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

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

        case K_ENTER:
        case K_KP_ENTER:
        case K_ABUTTON:
            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? (y/n)\n", 0.0f))
                            break;
                    IN_Activate();
                    key_dest = key_game;
                    if (sv.active)
                        command::buffer::add_text("disconnect\n");
                    command::buffer::add_text("maxplayers 1\n");
                    command::buffer::add_text("deathmatch 0\n"); //johnfitz
                    command::buffer::add_text("coop 0\n"); //johnfitz
                    command::buffer::add_text("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		20	/* johnfitz -- increased from 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];
    FILE *f;
    int version;

    for (i = 0; i < MAX_SAVEGAMES; i++) {
        strcpy(m_filenames[i], "--- UNUSED SLOT ---");
        loadable[i] = false;
        q_snprintf(name, sizeof(name), "%s/s%i.sav", com_gamedir, i);
        f = fopen(name, "r");
        if (!f) {
            continue;
        }
        if (fscanf(f, "%i\n", &version) != 1 ||
            fscanf(f, "%79s\n", name) != 1) {
            fclose(f);
            continue;
        }
        q_strlcpy(m_filenames[i], name, SAVEGAME_COMMENT_LENGTH + 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;
        fclose(f);
    }
}

void M_Menu_Load_f(void) {
    m_entersound = true;
    m_state = m_load;

    IN_Deactivate(modestate == MS_WINDOWED);
    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;

    IN_Deactivate(modestate == MS_WINDOWED);
    key_dest = key_menu;
    M_ScanSaves();
}


void M_Load_Draw(void) {
    int i;
    qpic_t *p;

    p = Draw_CachePic("gfx/p_load.lmp");
    M_DrawPic((320 - p->width) / 2, 4, p);

    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;
    qpic_t *p;

    p = Draw_CachePic("gfx/p_save.lmp");
    M_DrawPic((320 - p->width) / 2, 4, p);

    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:
        case K_BBUTTON:
            M_Menu_SinglePlayer_f();
            break;

        case K_ENTER:
        case K_KP_ENTER:
        case K_ABUTTON:
            S_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
            command::buffer::add_text(va("load s%i\n", load_cursor));
            return;

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

        case K_DOWNARROW:
        case K_RIGHTARROW:
            S_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:
        case K_BBUTTON:
            M_Menu_SinglePlayer_f();
            break;

        case K_ENTER:
        case K_KP_ENTER:
        case K_ABUTTON:
            m_state = m_none;
            IN_Activate();
            key_dest = key_game;
            command::buffer::add_text(va("save s%i\n", load_cursor));
            return;

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

        case K_DOWNARROW:
        case K_RIGHTARROW:
            S_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) {
    IN_Deactivate(modestate == MS_WINDOWED);
    key_dest = key_menu;
    m_state = m_multiplayer;
    m_entersound = true;
}


void M_MultiPlayer_Draw(void) {
    int f;
    qpic_t *p;

    M_DrawTransPic(16, 4, Draw_CachePic("gfx/qplaque.lmp"));
    p = Draw_CachePic("gfx/p_multi.lmp");
    M_DrawPic((320 - p->width) / 2, 4, p);
    M_DrawTransPic(72, 32, Draw_CachePic("gfx/mp_menu.lmp"));

    f = (int) (realtime * 10) % 6;

    M_DrawTransPic(54, 32 + m_multiplayer_cursor * 20, Draw_CachePic(va("gfx/menudot%i.lmp", f + 1)));

    if (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:
        case K_BBUTTON:
            M_Menu_Main_f();
            break;

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

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

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

                case 1:
                    if (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};

char setup_hostname[16];
char setup_myname[16];
int setup_oldtop;
int setup_oldbottom;
int setup_top;
int setup_bottom;

#define	NUM_SETUP_CMDS	5

void M_Menu_Setup_f(void) {
    IN_Deactivate(modestate == MS_WINDOWED);
    key_dest = key_menu;
    m_state = m_setup;
    m_entersound = true;
    std::strcpy(setup_myname, cl_name.string);
    std::strcpy(setup_hostname, hostname.string);
    setup_top = setup_oldtop = ((int) cl_color.value) >> 4;
    setup_bottom = setup_oldbottom = ((int) cl_color.value) & 15;
}


void M_Setup_Draw(void) {
    qpic_t *p;

    M_DrawTransPic(16, 4, Draw_CachePic("gfx/qplaque.lmp"));
    p = Draw_CachePic("gfx/p_multi.lmp");
    M_DrawPic((320 - p->width) / 2, 4, p);

    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");

    p = Draw_CachePic("gfx/bigbox.lmp");
    M_DrawTransPic(160, 64, p);
    p = Draw_CachePic("gfx/menuplyr.lmp");
    M_DrawTransPicTranslate(172, 72, p, setup_top, setup_bottom);

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

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

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


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

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

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

        case K_LEFTARROW:
            if (setup_cursor < 2)
                return;
            S_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:
            S_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:
        case K_KP_ENTER:
        case K_ABUTTON:
            if (setup_cursor == 0 || setup_cursor == 1)
                return;

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

            // setup_cursor == 4 (OK)
            if (std::strcmp(cl_name.string, setup_myname) != 0)
                command::buffer::add_text(va("name \"%s\"\n", setup_myname));
            if (std::strcmp(hostname.string, setup_hostname) != 0)
                convar::set("hostname", setup_hostname);
            if (setup_top != setup_oldtop || setup_bottom != setup_oldbottom)
                command::buffer::add_text(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 (strlen(setup_hostname))
                    setup_hostname[strlen(setup_hostname) - 1] = 0;
            }

            if (setup_cursor == 1) {
                if (strlen(setup_myname))
                    setup_myname[strlen(setup_myname) - 1] = 0;
            }
            break;
    }

    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;
}


void M_Setup_Char(int k) {
    int l;

    switch (setup_cursor) {
        case 0:
            l = strlen(setup_hostname);
            if (l < 15) {
                setup_hostname[l + 1] = 0;
                setup_hostname[l] = k;
            }
            break;
        case 1:
            l = strlen(setup_myname);
            if (l < 15) {
                setup_myname[l + 1] = 0;
                setup_myname[l] = k;
            }
            break;
    }
}


bool M_Setup_TextEntry(void) {
    return (setup_cursor == 0 || setup_cursor == 1);
}

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

int m_net_cursor;
int m_net_items;

const char *net_helpMessage[] =
{
    /* .........1.........2.... */
    " 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) {
    IN_Deactivate(modestate == MS_WINDOWED);
    key_dest = key_menu;
    m_state = m_net;
    m_entersound = true;
    m_net_items = 2;

    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;
    qpic_t *p;

    M_DrawTransPic(16, 4, Draw_CachePic("gfx/qplaque.lmp"));
    p = Draw_CachePic("gfx/p_multi.lmp");
    M_DrawPic((320 - p->width) / 2, 4, p);

    f = 32;

    if (ipxAvailable)
        p = Draw_CachePic("gfx/netmen3.lmp");
    else
        p = Draw_CachePic("gfx/dim_ipx.lmp");
    M_DrawTransPic(72, f, p);

    f += 19;
    if (tcpipAvailable)
        p = Draw_CachePic("gfx/netmen4.lmp");
    else
        p = Draw_CachePic("gfx/dim_tcp.lmp");
    M_DrawTransPic(72, f, p);

    f = (320 - 26 * 8) / 2;
    M_DrawTextBox(f, 96, 24, 4);
    f += 8;
    M_Print(f, 104, net_helpMessage[m_net_cursor * 4 + 0]);
    M_Print(f, 112, net_helpMessage[m_net_cursor * 4 + 1]);
    M_Print(f, 120, net_helpMessage[m_net_cursor * 4 + 2]);
    M_Print(f, 128, net_helpMessage[m_net_cursor * 4 + 3]);

    f = (int) (realtime * 10) % 6;
    M_DrawTransPic(54, 32 + m_net_cursor * 20, Draw_CachePic(va("gfx/menudot%i.lmp", f + 1)));
}


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

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

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

        case K_ENTER:
        case K_KP_ENTER:
        case K_ABUTTON:
            m_entersound = true;
            M_Menu_LanConfig_f();
            break;
    }

    if (m_net_cursor == 0 && !ipxAvailable)
        goto again;
    if (m_net_cursor == 1 && !tcpipAvailable)
        goto again;
}

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

enum {
    OPT_CUSTOMIZE = 0,
    OPT_CONSOLE, // 1
    OPT_DEFAULTS, // 2
    OPT_SCALE,
    OPT_SCRSIZE,
    OPT_GAMMA,
    OPT_CONTRAST,
    OPT_MOUSESPEED,
    OPT_SBALPHA,
    OPT_SNDVOL,
    OPT_MUSICVOL,
    OPT_MUSICEXT,
    OPT_ALWAYRUN,
    OPT_INVMOUSE,
    OPT_ALWAYSMLOOK,
    OPT_LOOKSPRING,
    OPT_LOOKSTRAFE,
    //#ifdef _WIN32
    //	OPT_USEMOUSE,
    //#endif
    OPT_VIDEO, // This is the last before OPTIONS_ITEMS
    OPTIONS_ITEMS
};

enum {
    ALWAYSRUN_OFF = 0,
    ALWAYSRUN_VANILLA,
    ALWAYSRUN_QUAKESPASM,
    ALWAYSRUN_ITEMS
};

#define	SLIDER_RANGE	10

int options_cursor;

void M_Menu_Options_f(void) {
    IN_Deactivate(modestate == MS_WINDOWED);
    key_dest = key_menu;
    m_state = m_options;
    m_entersound = true;
}


void M_AdjustSliders(int dir) {
    int curr_alwaysrun, target_alwaysrun;
    float f, l;

    S_LocalSound("misc/menu3.wav");

    switch (options_cursor) {
        case OPT_SCALE: // console and menu scale
            l = ((vid.width + 31) / 32) / 10.0;
            f = scr_conscale.value + dir * .1;
            if (f < 1) f = 1;
            else if (f > l) f = l;
            convar::set_value("scr_conscale", f);
            convar::set_value("scr_menuscale", f);
            convar::set_value("scr_sbarscale", f);
            break;
        case OPT_SCRSIZE: // screen size
            f = scr_viewsize.value + dir * 10;
            if (f > 120) f = 120;
            else if (f < 30) f = 30;
            convar::set_value("viewsize", f);
            break;
        case OPT_GAMMA: // gamma
            f = vid_gamma.value - dir * 0.05;
            if (f < 0.5) f = 0.5;
            else if (f > 1) f = 1;
            convar::set_value("gamma", f);
            break;
        case OPT_CONTRAST: // contrast
            f = vid_contrast.value + dir * 0.1;
            if (f < 1) f = 1;
            else if (f > 2) f = 2;
            convar::set_value("contrast", f);
            break;
        case OPT_MOUSESPEED: // mouse speed
            f = sensitivity.value + dir * 0.5;
            if (f > 11) f = 11;
            else if (f < 1) f = 1;
            convar::set_value("sensitivity", f);
            break;
        case OPT_SBALPHA: // statusbar alpha
            f = scr_sbaralpha.value - dir * 0.05;
            if (f < 0) f = 0;
            else if (f > 1) f = 1;
            convar::set_value("scr_sbaralpha", f);
            break;
        case OPT_MUSICVOL: // music volume
            f = bgmvolume.value + dir * 0.1;
            if (f < 0) f = 0;
            else if (f > 1) f = 1;
            convar::set_value("bgmvolume", f);
            break;
        case OPT_MUSICEXT: // enable external music vs cdaudio
            convar::set("bgm_extmusic", music::extmusic.value ? "0" : "1");
            break;
        case OPT_SNDVOL: // sfx volume
            f = sfxvolume.value + dir * 0.1;
            if (f < 0) f = 0;
            else if (f > 1) f = 1;
            convar::set_value("volume", f);
            break;

        case OPT_ALWAYRUN: // always run
            if (cl_alwaysrun.value)
                curr_alwaysrun = ALWAYSRUN_QUAKESPASM;
            else if (cl_forwardspeed.value > 200)
                curr_alwaysrun = ALWAYSRUN_VANILLA;
            else
                curr_alwaysrun = ALWAYSRUN_OFF;

            target_alwaysrun = (ALWAYSRUN_ITEMS + curr_alwaysrun + dir) % ALWAYSRUN_ITEMS;

            if (target_alwaysrun == ALWAYSRUN_VANILLA) {
                convar::set_value("cl_alwaysrun", 0);
                convar::set_value("cl_forwardspeed", 400);
                convar::set_value("cl_backspeed", 400);
            } else if (target_alwaysrun == ALWAYSRUN_QUAKESPASM) {
                convar::set_value("cl_alwaysrun", 1);
                convar::set_value("cl_forwardspeed", 200);
                convar::set_value("cl_backspeed", 200);
            } else // ALWAYSRUN_OFF
            {
                convar::set_value("cl_alwaysrun", 0);
                convar::set_value("cl_forwardspeed", 200);
                convar::set_value("cl_backspeed", 200);
            }
            break;

        case OPT_INVMOUSE: // invert mouse
            convar::set_value("m_pitch", -m_pitch.value);
            break;

        case OPT_ALWAYSMLOOK:
            if (in_mlook.state & 1)
                command::buffer::add_text("-mlook");
            else
                command::buffer::add_text("+mlook");
            break;

        case OPT_LOOKSPRING: // lookspring
            convar::set("lookspring", lookspring.value ? "0" : "1");
            break;

        case OPT_LOOKSTRAFE: // lookstrafe
            convar::set("lookstrafe", lookstrafe.value ? "0" : "1");
            break;
    }
}


void M_DrawSlider(int x, int y, float range, float value, const char *format) {
    int i;
    char buffer[5];

    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);
    q_snprintf(buffer, 5, format, value);
    M_Print(x + (SLIDER_RANGE + 1) * 8, y, buffer);
}

void M_DrawCheckbox(int x, int y, int on) {
#if 0
    if (on)
        M_DrawCharacter(x, y, 131);
    else
        M_DrawCharacter(x, y, 129);
#endif
    if (on)
        M_Print(x, y, "on");
    else
        M_Print(x, y, "off");
}

void M_Options_Draw(void) {
    float r, l;
    qpic_t *p;

    M_DrawTransPic(16, 4, Draw_CachePic("gfx/qplaque.lmp"));
    p = Draw_CachePic("gfx/p_option.lmp");
    M_DrawPic((320 - p->width) / 2, 4, p);

    // Draw the items in the order of the enum defined above:
    // OPT_CUSTOMIZE:
    M_Print(16, 32, "              Controls");
    // OPT_CONSOLE:
    M_Print(16, 32 + 8 * OPT_CONSOLE, "          Goto console");
    // OPT_DEFAULTS:
    M_Print(16, 32 + 8 * OPT_DEFAULTS, "          Reset config");

    // OPT_SCALE:
    M_Print(16, 32 + 8 * OPT_SCALE, "                 Scale");
    l = (vid.width / 320.0) - 1;
    r = l > 0 ? (scr_conscale.value - 1) / l : 0;
    M_DrawSlider(220, 32 + 8 * OPT_SCALE, r, scr_conscale.value, "%.1f");

    // OPT_SCRSIZE:
    M_Print(16, 32 + 8 * OPT_SCRSIZE, "           Screen size");
    r = (scr_viewsize.value - 30) / (120 - 30);
    M_DrawSlider(220, 32 + 8 * OPT_SCRSIZE, r, scr_viewsize.value, "%.0f");

    // OPT_GAMMA:
    M_Print(16, 32 + 8 * OPT_GAMMA, "            Brightness");
    r = (1.0 - vid_gamma.value) / 0.5;
    M_DrawSlider(220, 32 + 8 * OPT_GAMMA, r, vid_gamma.value, "%.2f");

    // OPT_CONTRAST:
    M_Print(16, 32 + 8 * OPT_CONTRAST, "              Contrast");
    r = vid_contrast.value - 1.0;
    M_DrawSlider(220, 32 + 8 * OPT_CONTRAST, r, vid_contrast.value, "%.1f");

    // OPT_MOUSESPEED:
    M_Print(16, 32 + 8 * OPT_MOUSESPEED, "           Mouse Speed");
    r = (sensitivity.value - 1) / 10;
    M_DrawSlider(220, 32 + 8 * OPT_MOUSESPEED, r, sensitivity.value, "%.1f");

    // OPT_SBALPHA:
    M_Print(16, 32 + 8 * OPT_SBALPHA, "       Statusbar alpha");
    r = (1.0 - scr_sbaralpha.value); // scr_sbaralpha range is 1.0 to 0.0
    M_DrawSlider(220, 32 + 8 * OPT_SBALPHA, r, scr_sbaralpha.value, "%.2f");

    // OPT_SNDVOL:
    M_Print(16, 32 + 8 * OPT_SNDVOL, "          Sound Volume");
    r = sfxvolume.value;
    M_DrawSlider(220, 32 + 8 * OPT_SNDVOL, r, sfxvolume.value, "%.1f");

    // OPT_MUSICVOL:
    M_Print(16, 32 + 8 * OPT_MUSICVOL, "          Music Volume");
    r = bgmvolume.value;
    M_DrawSlider(220, 32 + 8 * OPT_MUSICVOL, r, bgmvolume.value, "%.1f");

    // OPT_MUSICEXT:
    M_Print(16, 32 + 8 * OPT_MUSICEXT, "        External Music");
    M_DrawCheckbox(220, 32 + 8 * OPT_MUSICEXT, music::extmusic.value);

    // OPT_ALWAYRUN:
    M_Print(16, 32 + 8 * OPT_ALWAYRUN, "            Always Run");
    if (cl_alwaysrun.value)
        M_Print(220, 32 + 8 * OPT_ALWAYRUN, "quakespasm");
    else if (cl_forwardspeed.value > 200.0)
        M_Print(220, 32 + 8 * OPT_ALWAYRUN, "vanilla");
    else
        M_Print(220, 32 + 8 * OPT_ALWAYRUN, "off");

    // OPT_INVMOUSE:
    M_Print(16, 32 + 8 * OPT_INVMOUSE, "          Invert Mouse");
    M_DrawCheckbox(220, 32 + 8 * OPT_INVMOUSE, m_pitch.value < 0);

    // OPT_ALWAYSMLOOK:
    M_Print(16, 32 + 8 * OPT_ALWAYSMLOOK, "            Mouse Look");
    M_DrawCheckbox(220, 32 + 8 * OPT_ALWAYSMLOOK, in_mlook.state & 1);

    // OPT_LOOKSPRING:
    M_Print(16, 32 + 8 * OPT_LOOKSPRING, "            Lookspring");
    M_DrawCheckbox(220, 32 + 8 * OPT_LOOKSPRING, lookspring.value);

    // OPT_LOOKSTRAFE:
    M_Print(16, 32 + 8 * OPT_LOOKSTRAFE, "            Lookstrafe");
    M_DrawCheckbox(220, 32 + 8 * OPT_LOOKSTRAFE, lookstrafe.value);

    // OPT_VIDEO:
    if (vid_menudrawfn)
        M_Print(16, 32 + 8 * OPT_VIDEO, "         Video Options");

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


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

        case K_ENTER:
        case K_KP_ENTER:
        case K_ABUTTON:
            m_entersound = true;
            switch (options_cursor) {
                case OPT_CUSTOMIZE:
                    M_Menu_Keys_f();
                    break;
                case OPT_CONSOLE:
                    m_state = m_none;
                    console::toggle_console();
                    break;
                case OPT_DEFAULTS:
                    if (SCR_ModalMessage("This will reset all controls\n"
                                         "and stored cvars. Continue? (y/n)\n", 15.0f)) {
                        command::buffer::add_text("resetcfg\n");
                        command::buffer::add_text("exec default.cfg\n");
                    }
                    break;
                case OPT_VIDEO:
                    M_Menu_Video_f();
                    break;
                default:
                    M_AdjustSliders(1);
                    break;
            }
            return;

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

        case K_DOWNARROW:
            S_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 == OPTIONS_ITEMS - 1 && vid_menudrawfn == NULL) {
        if (k == K_UPARROW)
            options_cursor = OPTIONS_ITEMS - 2;
        else
            options_cursor = 0;
    }
}

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

const char *bindnames[][2] =
{
    {"+attack", "attack"},
    {"impulse 10", "next weapon"},
    {"impulse 12", "prev 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	std::size(bindnames)

static int keys_cursor;
static bool bind_grab;

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


void M_FindKeysForCommand(const char *command, int *threekeys) {
    int count;
    int j;
    int l;
    char *b;

    threekeys[0] = threekeys[1] = threekeys[2] = -1;
    l = strlen(command);
    count = 0;

    for (j = 0; j < MAX_KEYS; j++) {
        b = keybindings[j];
        if (!b)
            continue;
        if (!strncmp(b, command, l)) {
            threekeys[count] = j;
            count++;
            if (count == 3)
                break;
        }
    }
}

void M_UnbindCommand(const char *command) {
    int j;
    int l;
    char *b;

    l = strlen(command);

    for (j = 0; j < MAX_KEYS; j++) {
        b = keybindings[j];
        if (!b)
            continue;
        if (!strncmp(b, command, l))
            Key_SetBinding(j, NULL);
    }
}

extern qpic_t *pic_up, *pic_down;

void M_Keys_Draw(void) {
    int i, x, y;
    int keys[3];
    const char *name;
    qpic_t *p;

    p = Draw_CachePic("gfx/ttl_cstm.lmp");
    M_DrawPic((320 - p->width) / 2, 4, p);

    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 (i = 0; i < (int) 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) {
                name = Key_KeynumToString(keys[1]);
                M_Print(140 + x + 8, y, "or");
                M_Print(140 + x + 32, y, name);
                x = x + 32 + strlen(name) * 8;
                if (keys[2] != -1) {
                    M_Print(140 + x + 8, y, "or");
                    M_Print(140 + x + 32, y, Key_KeynumToString(keys[2]));
                }
            }
        }
    }

    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[3];

    if (bind_grab) {
        // defining a key
        S_LocalSound("misc/menu1.wav");
        if ((k != K_ESCAPE) && (k != '`')) {
            sprintf(cmd, "bind \"%s\" \"%s\"\n", Key_KeynumToString(k), bindnames[keys_cursor][0]);
            command::buffer::insert_text(cmd);
        }

        bind_grab = false;
        IN_Deactivate(modestate == MS_WINDOWED); // deactivate because we're returning to the menu
        return;
    }

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

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

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

        case K_ENTER: // go into bind mode
        case K_KP_ENTER:
        case K_ABUTTON:
            M_FindKeysForCommand(bindnames[keys_cursor][0], keys);
            S_LocalSound("misc/menu2.wav");
            if (keys[2] != -1)
                M_UnbindCommand(bindnames[keys_cursor][0]);
            bind_grab = true;
            IN_Activate(); // activate to allow mouse key binding
            break;

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

//=============================================================================
/* VIDEO MENU */

void M_Menu_Video_f(void) {
    (*vid_menucmdfn)(); //johnfitz
}


void M_Video_Draw(void) {
    (*vid_menudrawfn)();
}


void M_Video_Key(int key) {
    (*vid_menukeyfn)(key);
}

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

int help_page;
#define	NUM_HELP_PAGES	6


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


void M_Help_Draw(void) {
    M_DrawPic(0, 0, Draw_CachePic(va("gfx/help%i.lmp", help_page)));
}


void M_Help_Key(int key) {
    switch (key) {
        case K_ESCAPE:
        case K_BBUTTON:
            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;
enum m_state_e m_quit_prevstate;
bool wasInMenus;

void M_Menu_Quit_f(void) {
    if (m_state == m_quit)
        return;
    wasInMenus = (key_dest == key_menu);
    IN_Deactivate(modestate == MS_WINDOWED);
    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) {
    if (key == K_ESCAPE) {
        if (wasInMenus) {
            m_state = m_quit_prevstate;
            m_entersound = true;
        } else {
            IN_Activate();
            key_dest = key_game;
            m_state = m_none;
        }
    }
}


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

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

        default:
            break;
    }
}


bool M_Quit_TextEntry(void) {
    return true;
}


void M_Quit_Draw(void) //johnfitz -- modified for new quit message
{
    char msg1[] = "QuakeSpasm " QUAKESPASM_VER_STRING;
    char msg2[] = "by Ozkan Sezer,Eric Wasylishen,others"; /* msg2/msg3 are [38] at most */
    char msg3[] = "Press y to quit";
    int boxlen;

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

    //okay, this is kind of fucked up.  M_DrawTextBox will always act as if
    //width is even. Also, the width and lines values are for the interior of the box,
    //but the x and y values include the border.
    boxlen = (std::max(sizeof(msg1), std::max(sizeof(msg2),sizeof(msg3))) + 1) & ~1;
    M_DrawTextBox(160 - 4 * (boxlen + 2), 76, boxlen, 4);

    //now do the text
    M_Print(160 - 4 * (sizeof(msg1) - 1), 88, msg1);
    M_Print(160 - 4 * (sizeof(msg2) - 1), 96, msg2);
    M_PrintWhite(160 - 4 * (sizeof(msg3) - 1), 104, msg3);
}

//=============================================================================
/* LAN CONFIG MENU */

int lanConfig_cursor = -1;
int lanConfig_cursor_table[] = {72, 92, 124};
#define NUM_LANCONFIG_CMDS	3

int lanConfig_port;
char lanConfig_portname[6];
char lanConfig_joinname[22];

void M_Menu_LanConfig_f(void) {
    IN_Deactivate(modestate == MS_WINDOWED);
    key_dest = key_menu;
    m_state = m_lanconfig;
    m_entersound = true;
    if (lanConfig_cursor == -1) {
        if (JoiningGame && TCPIPConfig)
            lanConfig_cursor = 2;
        else
            lanConfig_cursor = 1;
    }
    if (StartingGame && lanConfig_cursor == 2)
        lanConfig_cursor = 1;
    lanConfig_port = DEFAULTnet_hostport;
    sprintf(lanConfig_portname, "%u", lanConfig_port);

    m_return_onerror = false;
    m_return_reason[0] = 0;
}


void M_LanConfig_Draw(void) {
    qpic_t *p;
    int basex;
    const char *startJoin;
    const char *protocol;

    M_DrawTransPic(16, 4, Draw_CachePic("gfx/qplaque.lmp"));
    p = Draw_CachePic("gfx/p_multi.lmp");
    basex = (320 - p->width) / 2;
    M_DrawPic(basex, 4, p);

    basex = 72; /* Arcane Dimensions has an oversized gfx/p_multi.lmp */

    if (StartingGame)
        startJoin = "New Game";
    else
        startJoin = "Join Game";
    if (IPXConfig)
        protocol = "IPX";
    else
        protocol = "TCP/IP";
    M_Print(basex, 32, va("%s - %s", startJoin, protocol));
    basex += 8;

    M_Print(basex, 52, "Address:");
    if (IPXConfig)
        M_Print(basex + 9 * 8, 52, my_ipx_address);
    else
        M_Print(basex + 9 * 8, 52, my_tcpip_address);

    M_Print(basex, lanConfig_cursor_table[0], "Port");
    M_DrawTextBox(basex + 8 * 8, lanConfig_cursor_table[0] - 8, 6, 1);
    M_Print(basex + 9 * 8, lanConfig_cursor_table[0], lanConfig_portname);

    if (JoiningGame) {
        M_Print(basex, lanConfig_cursor_table[1], "Search for local games...");
        M_Print(basex, 108, "Join game at:");
        M_DrawTextBox(basex + 8, lanConfig_cursor_table[2] - 8, 22, 1);
        M_Print(basex + 16, lanConfig_cursor_table[2], lanConfig_joinname);
    } else {
        M_DrawTextBox(basex, lanConfig_cursor_table[1] - 8, 2, 1);
        M_Print(basex + 8, lanConfig_cursor_table[1], "OK");
    }

    M_DrawCharacter(basex - 8, lanConfig_cursor_table[lanConfig_cursor], 12 + ((int) (realtime * 4) & 1));

    if (lanConfig_cursor == 0)
        M_DrawCharacter(basex + 9 * 8 + 8 * strlen(lanConfig_portname), lanConfig_cursor_table[0],
                        10 + ((int) (realtime * 4) & 1));

    if (lanConfig_cursor == 2)
        M_DrawCharacter(basex + 16 + 8 * strlen(lanConfig_joinname), lanConfig_cursor_table[2],
                        10 + ((int) (realtime * 4) & 1));

    if (*m_return_reason)
        M_PrintWhite(basex, 148, m_return_reason);
}


void M_LanConfig_Key(int key) {
    int l;

    switch (key) {
        case K_ESCAPE:
        case K_BBUTTON:
            M_Menu_Net_f();
            break;

        case K_UPARROW:
            S_LocalSound("misc/menu1.wav");
            lanConfig_cursor--;
            if (lanConfig_cursor < 0)
                lanConfig_cursor = NUM_LANCONFIG_CMDS - 1;
            break;

        case K_DOWNARROW:
            S_LocalSound("misc/menu1.wav");
            lanConfig_cursor++;
            if (lanConfig_cursor >= NUM_LANCONFIG_CMDS)
                lanConfig_cursor = 0;
            break;

        case K_ENTER:
        case K_KP_ENTER:
        case K_ABUTTON:
            if (lanConfig_cursor == 0)
                break;

            m_entersound = true;

            M_ConfigureNetSubsystem();

            if (lanConfig_cursor == 1) {
                if (StartingGame) {
                    M_Menu_GameOptions_f();
                    break;
                }
                M_Menu_Search_f();
                break;
            }

            if (lanConfig_cursor == 2) {
                m_return_state = m_state;
                m_return_onerror = true;
                IN_Activate();
                key_dest = key_game;
                m_state = m_none;
                command::buffer::add_text(va("connect \"%s\"\n", lanConfig_joinname));
                break;
            }

            break;

        case K_BACKSPACE:
            if (lanConfig_cursor == 0) {
                if (strlen(lanConfig_portname))
                    lanConfig_portname[strlen(lanConfig_portname) - 1] = 0;
            }

            if (lanConfig_cursor == 2) {
                if (strlen(lanConfig_joinname))
                    lanConfig_joinname[strlen(lanConfig_joinname) - 1] = 0;
            }
            break;
    }

    if (StartingGame && lanConfig_cursor == 2) {
        if (key == K_UPARROW)
            lanConfig_cursor = 1;
        else
            lanConfig_cursor = 0;
    }

    l = std::atoi(lanConfig_portname);
    if (l > 65535)
        l = lanConfig_port;
    else
        lanConfig_port = l;
    sprintf(lanConfig_portname, "%u", lanConfig_port);
}


void M_LanConfig_Char(int key) {
    int l;

    switch (lanConfig_cursor) {
        case 0:
            if (key < '0' || key > '9')
                return;
            l = strlen(lanConfig_portname);
            if (l < 5) {
                lanConfig_portname[l + 1] = 0;
                lanConfig_portname[l] = key;
            }
            break;
        case 2:
            l = strlen(lanConfig_joinname);
            if (l < 21) {
                lanConfig_joinname[l + 1] = 0;
                lanConfig_joinname[l] = key;
            }
            break;
    }
}


bool M_LanConfig_TextEntry(void) {
    return (lanConfig_cursor == 0 || lanConfig_cursor == 2);
}

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

typedef struct {
    const char *name;
    const 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"}
};

//MED 01/06/97 added hipnotic levels
level_t hipnoticlevels[] =
{
    {"start", "Command HQ"}, // 0

    {"hip1m1", "The Pumping Station"}, // 1
    {"hip1m2", "Storage Facility"},
    {"hip1m3", "The Lost Mine"},
    {"hip1m4", "Research Facility"},
    {"hip1m5", "Military Complex"},

    {"hip2m1", "Ancient Realms"}, // 6
    {"hip2m2", "The Black Cathedral"},
    {"hip2m3", "The Catacombs"},
    {"hip2m4", "The Crypt"},
    {"hip2m5", "Mortum's Keep"},
    {"hip2m6", "The Gremlin's Domain"},

    {"hip3m1", "Tur Torment"}, // 12
    {"hip3m2", "Pandemonium"},
    {"hip3m3", "Limbo"},
    {"hip3m4", "The Gauntlet"},

    {"hipend", "Armagon's Lair"}, // 16

    {"hipdm1", "The Edge of Oblivion"} // 17
};

//PGM 01/07/97 added rogue levels
//PGM 03/02/97 added dmatch level
level_t roguelevels[] =
{
    {"start", "Split Decision"},
    {"r1m1", "Deviant's Domain"},
    {"r1m2", "Dread Portal"},
    {"r1m3", "Judgement Call"},
    {"r1m4", "Cave of Death"},
    {"r1m5", "Towers of Wrath"},
    {"r1m6", "Temple of Pain"},
    {"r1m7", "Tomb of the Overlord"},
    {"r2m1", "Tempus Fugit"},
    {"r2m2", "Elemental Fury I"},
    {"r2m3", "Elemental Fury II"},
    {"r2m4", "Curse of Osiris"},
    {"r2m5", "Wizard's Keep"},
    {"r2m6", "Blood Sacrifice"},
    {"r2m7", "Last Bastion"},
    {"r2m8", "Source of Evil"},
    {"ctf1", "Division of Change"}
};

typedef struct {
    const 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}
};

//MED 01/06/97  added hipnotic episodes
episode_t hipnoticepisodes[] =
{
    {"Scourge of Armagon", 0, 1},
    {"Fortress of the Dead", 1, 5},
    {"Dominion of Darkness", 6, 6},
    {"The Rift", 12, 4},
    {"Final Level", 16, 1},
    {"Deathmatch Arena", 17, 1}
};

//PGM 01/07/97 added rogue episodes
//PGM 03/02/97 added dmatch episode
episode_t rogueepisodes[] =
{
    {"Introduction", 0, 1},
    {"Hell's Fortress", 1, 7},
    {"Corridors of Time", 8, 8},
    {"Deathmatch Arena", 16, 1}
};

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

void M_Menu_GameOptions_f(void) {
    IN_Deactivate(modestate == MS_WINDOWED);
    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) {
    qpic_t *p;
    int x;

    M_DrawTransPic(16, 4, Draw_CachePic("gfx/qplaque.lmp"));
    p = Draw_CachePic("gfx/p_multi.lmp");
    M_DrawPic((320 - p->width) / 2, 4, p);

    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 (coop.value)
        M_Print(160, 64, "Cooperative");
    else
        M_Print(160, 64, "Deathmatch");

    M_Print(0, 72, "        Teamplay");
    if (rogue) {
        const char *msg;

        switch ((int) teamplay.value) {
            case 1: msg = "No Friendly Fire";
                break;
            case 2: msg = "Friendly Fire";
                break;
            case 3: msg = "Tag";
                break;
            case 4: msg = "Capture the Flag";
                break;
            case 5: msg = "One Flag CTF";
                break;
            case 6: msg = "Three Team CTF";
                break;
            default: msg = "Off";
                break;
        }
        M_Print(160, 72, msg);
    } else {
        const char *msg;

        switch ((int) teamplay.value) {
            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");
    if (skill.value == 0)
        M_Print(160, 80, "Easy difficulty");
    else if (skill.value == 1)
        M_Print(160, 80, "Normal difficulty");
    else if (skill.value == 2)
        M_Print(160, 80, "Hard difficulty");
    else
        M_Print(160, 80, "Nightmare difficulty");

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

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

    M_Print(0, 112, "         Episode");
    // MED 01/06/97 added hipnotic episodes
    if (hipnotic)
        M_Print(160, 112, hipnoticepisodes[startepisode].description);
        // PGM 01/07/97 added rogue episodes
    else if (rogue)
        M_Print(160, 112, rogueepisodes[startepisode].description);
    else
        M_Print(160, 112, episodes[startepisode].description);

    M_Print(0, 120, "           Level");
    // MED 01/06/97 added hipnotic episodes
    if (hipnotic) {
        M_Print(160, 120, hipnoticlevels[hipnoticepisodes[startepisode].firstLevel + startlevel].description);
        M_Print(160, 128, hipnoticlevels[hipnoticepisodes[startepisode].firstLevel + startlevel].name);
    }
    // PGM 01/07/97 added rogue episodes
    else if (rogue) {
        M_Print(160, 120, roguelevels[rogueepisodes[startepisode].firstLevel + startlevel].description);
        M_Print(160, 128, roguelevels[rogueepisodes[startepisode].firstLevel + startlevel].name);
    } else {
        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 f;

    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:
            convar::set("coop", coop.value ? "0" : "1");
            break;

        case 3:
            count = (rogue) ? 6 : 2;
            f = teamplay.value + dir;
            if (f > count) f = 0;
            else if (f < 0) f = count;
            convar::set_value("teamplay", f);
            break;

        case 4:
            f = skill.value + dir;
            if (f > 3) f = 0;
            else if (f < 0) f = 3;
            convar::set_value("skill", f);
            break;

        case 5:
            f = fraglimit.value + dir * 10;
            if (f > 100) f = 0;
            else if (f < 0) f = 100;
            convar::set_value("fraglimit", f);
            break;

        case 6:
            f = timelimit.value + dir * 5;
            if (f > 60) f = 0;
            else if (f < 0) f = 60;
            convar::set_value("timelimit", f);
            break;

        case 7:
            startepisode += dir;
            //MED 01/06/97 added hipnotic count
            if (hipnotic)
                count = 6;
                //PGM 01/07/97 added rogue count
                //PGM 03/02/97 added 1 for dmatch episode
            else if (rogue)
                count = 4;
            else if (registered.value)
                count = 7;
            else
                count = 2;

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

            if (startepisode >= count)
                startepisode = 0;

            startlevel = 0;
            break;

        case 8:
            startlevel += dir;
            //MED 01/06/97 added hipnotic episodes
            if (hipnotic)
                count = hipnoticepisodes[startepisode].levels;
                //PGM 01/06/97 added hipnotic episodes
            else if (rogue)
                count = rogueepisodes[startepisode].levels;
            else
                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:
        case K_BBUTTON:
            M_Menu_Net_f();
            break;

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

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

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

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

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

                if (hipnotic)
                    command::buffer::add_text(va(
                        "map %s\n", hipnoticlevels[hipnoticepisodes[startepisode].firstLevel + startlevel].name));
                else if (rogue)
                    command::buffer::add_text(va("map %s\n", roguelevels[rogueepisodes[startepisode].firstLevel + startlevel].name));
                else
                    command::buffer::add_text(va("map %s\n", levels[episodes[startepisode].firstLevel + startlevel].name));

                return;
            }

            M_NetStart_Change(1);
            break;
    }
}

//=============================================================================
/* SEARCH MENU */

bool searchComplete = false;
double searchCompleteTime;

void M_Menu_Search_f(void) {
    IN_Deactivate(modestate == MS_WINDOWED);
    key_dest = key_menu;
    m_state = m_search;
    m_entersound = false;
    slistSilent = true;
    slistLocal = false;
    searchComplete = false;
    NET_Slist_f();
}


void M_Search_Draw(void) {
    qpic_t *p;
    int x;

    p = Draw_CachePic("gfx/p_multi.lmp");
    M_DrawPic((320 - p->width) / 2, 4, p);
    x = (320 / 2) - ((12 * 8) / 2) + 4;
    M_DrawTextBox(x - 8, 32, 12, 1);
    M_Print(x, 40, "Searching...");

    if (slistInProgress) {
        NET_Poll();
        return;
    }

    if (!searchComplete) {
        searchComplete = true;
        searchCompleteTime = realtime;
    }

    if (hostCacheCount) {
        M_Menu_ServerList_f();
        return;
    }

    M_PrintWhite((320 / 2) - ((22 * 8) / 2), 64, "No Quake servers found");
    if ((realtime - searchCompleteTime) < 3.0)
        return;

    M_Menu_LanConfig_f();
}


void M_Search_Key(int key) {
}

//=============================================================================
/* SLIST MENU */

int slist_cursor;
bool slist_sorted;

void M_Menu_ServerList_f(void) {
    IN_Deactivate(modestate == MS_WINDOWED);
    key_dest = key_menu;
    m_state = m_slist;
    m_entersound = true;
    slist_cursor = 0;
    m_return_onerror = false;
    m_return_reason[0] = 0;
    slist_sorted = false;
}


void M_ServerList_Draw(void) {
    int n;
    qpic_t *p;

    if (!slist_sorted) {
        slist_sorted = true;
        NET_SlistSort();
    }

    p = Draw_CachePic("gfx/p_multi.lmp");
    M_DrawPic((320 - p->width) / 2, 4, p);
    for (n = 0; n < hostCacheCount; n++)
        M_Print(16, 32 + 8 * n, NET_SlistPrintServer(n));
    M_DrawCharacter(0, 32 + slist_cursor * 8, 12 + ((int) (realtime * 4) & 1));

    if (*m_return_reason)
        M_PrintWhite(16, 148, m_return_reason);
}


void M_ServerList_Key(int k) {
    switch (k) {
        case K_ESCAPE:
        case K_BBUTTON:
            M_Menu_LanConfig_f();
            break;

        case K_SPACE:
            M_Menu_Search_f();
            break;

        case K_UPARROW:
        case K_LEFTARROW:
            S_LocalSound("misc/menu1.wav");
            slist_cursor--;
            if (slist_cursor < 0)
                slist_cursor = hostCacheCount - 1;
            break;

        case K_DOWNARROW:
        case K_RIGHTARROW:
            S_LocalSound("misc/menu1.wav");
            slist_cursor++;
            if (slist_cursor >= hostCacheCount)
                slist_cursor = 0;
            break;

        case K_ENTER:
        case K_KP_ENTER:
        case K_ABUTTON:
            S_LocalSound("misc/menu2.wav");
            m_return_state = m_state;
            m_return_onerror = true;
            slist_sorted = false;
            IN_Activate();
            key_dest = key_game;
            m_state = m_none;
            command::buffer::add_text(va("connect \"%s\"\n", NET_SlistPrintServerName(slist_cursor)));
            break;

        default:
            break;
    }
}

//=============================================================================
/* Credits menu -- used by the 2021 re-release */

void M_Menu_Credits_f(void) {
}

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

void M_Init(void) {
    command::add("togglemenu", M_ToggleMenu_f);
    command::add("menu_main", M_Menu_Main_f);
    command::add("menu_singleplayer", M_Menu_SinglePlayer_f);
    command::add("menu_load", M_Menu_Load_f);
    command::add("menu_save", M_Menu_Save_f);
    command::add("menu_multiplayer", M_Menu_MultiPlayer_f);
    command::add("menu_setup", M_Menu_Setup_f);
    command::add("menu_options", M_Menu_Options_f);
    command::add("menu_keys", M_Menu_Keys_f);
    command::add("menu_video", M_Menu_Video_f);
    command::add("help", M_Menu_Help_f);
    command::add("menu_quit", M_Menu_Quit_f);
    command::add("menu_credits", M_Menu_Credits_f); // needed by the 2021 re-release
}


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

    if (!m_recursiveDraw) {
        if (scr_con_current) {
            Draw_ConsoleBackground();
            S_ExtraUpdate();
        }

        Draw_FadeScreen(); //johnfitz -- fade even if console fills screen
    } else {
        m_recursiveDraw = false;
    }

    GL_SetCanvas(CANVAS_MENU); //johnfitz

    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_video:
            M_Video_Draw();
            break;

        case m_help:
            M_Help_Draw();
            break;

        case m_quit:
            if (!fitzmode) {
                /* QuakeSpasm customization: */
                /* Quit now! S.A. */
                key_dest = key_console;
                Host_Quit_f();
            }
            M_Quit_Draw();
            break;

        case m_lanconfig:
            M_LanConfig_Draw();
            break;

        case m_gameoptions:
            M_GameOptions_Draw();
            break;

        case m_search:
            M_Search_Draw();
            break;

        case m_slist:
            M_ServerList_Draw();
            break;
    }

    if (m_entersound) {
        S_LocalSound("misc/menu2.wav");
        m_entersound = false;
    }

    S_ExtraUpdate();
}


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_video:
            M_Video_Key(key);
            return;

        case m_help:
            M_Help_Key(key);
            return;

        case m_quit:
            M_Quit_Key(key);
            return;

        case m_lanconfig:
            M_LanConfig_Key(key);
            return;

        case m_gameoptions:
            M_GameOptions_Key(key);
            return;

        case m_search:
            M_Search_Key(key);
            break;

        case m_slist:
            M_ServerList_Key(key);
            return;
    }
}


void M_Charinput(int key) {
    switch (m_state) {
        case m_setup:
            M_Setup_Char(key);
            return;
        case m_quit:
            M_Quit_Char(key);
            return;
        case m_lanconfig:
            M_LanConfig_Char(key);
            return;
        default:
            return;
    }
}


bool M_TextEntry(void) {
    switch (m_state) {
        case m_setup:
            return M_Setup_TextEntry();
        case m_quit:
            return M_Quit_TextEntry();
        case m_lanconfig:
            return M_LanConfig_TextEntry();
        default:
            return false;
    }
}


void M_ConfigureNetSubsystem(void) {
    // enable/disable net systems to match desired config
    command::buffer::add_text("stopdemo\n");

    if (IPXConfig || TCPIPConfig)
        net_hostport = lanConfig_port;
}
