//-------------------------------------------------------------------------
/*
Copyright (C) 1997, 2005 - 3D Realms Entertainment

This file is part of Shadow Warrior version 1.2

Shadow Warrior 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

Original Source: 1997 - Frank Maddin and Jim Norwood
Prepared for public release: 03/28/2005 - Charlie Wiederhold, 3D Realms
*/
//-------------------------------------------------------------------------
#include "build.h"
#include "osd.h"

#include "keys.h"
#include "names2.h"
#include "panel.h"
#include "game.h"
#include "tags.h"
#include "sector.h"
#include "sprite.h"
#include "weapon.h"
#include "player.h"
#include "jsector.h"
#include "control.h"
#include "menus.h"
#include "sw_strs.h"
#include "pal.h"
#include "demo.h"

#include "function.h"
#include "gamedefs.h"
#include "config.h"
#include "net.h"
#include "fx_man.h"
#include "music.h"
#include "text.h"

#include "colormap.h"

//#define PLOCK_VERSION TRUE


short TimeLimitTable[9] = {0,3,5,10,15,20,30,45,60};

short QuickLoadNum = -1;
char QuickLoadDescrDialog[32];
SWBOOL QuickSaveMode = FALSE;
SWBOOL SavePrompt = FALSE;
extern SWBOOL InMenuLevel, LoadGameOutsideMoveLoop, LoadGameFromDemo;
extern uint8_t RedBookSong[40];
extern SWBOOL ExitLevel, NewGame;
extern short Level, Skill;
extern SWBOOL MusicInitialized, FxInitialized;
SWBOOL MNU_CheckUserMap(MenuItem *item);
SWBOOL MNU_SaveGameCheck(MenuItem_p item);
SWBOOL MNU_TeamPlayCheck(MenuItem *item);
SWBOOL MNU_CoopPlayCheck(MenuItem *item);
SWBOOL MNU_StatCheck(MenuItem *item);
SWBOOL MNU_LoadGameCheck(MenuItem *item);
SWBOOL MNU_TenCheck(MenuItem *item);
static SWBOOL MNU_TryMusicInit(void);
static void MNU_UpLevel(void);

SWBOOL MNU_LoadSaveDraw(UserCall call, MenuItem *item);
SWBOOL MNU_LoadSaveMove(UserCall call, MenuItem *item);

SWBOOL MenuButtonAutoRun = FALSE;
SWBOOL MenuButtonAutoAim = FALSE;
// misc load-save vars
short LastSaveNum = 99;
char SaveGameDescr[10][80];
char BackupSaveGameDescr[80];
short screen_tile = -1;

SWBOOL MenuInputMode = FALSE;
int16_t MenuTextShade = 0;
SWBOOL passwordvalid = FALSE;

SWBOOL MNU_HurtTeammateCheck(MenuItem *item);
SWBOOL MNU_TeamPlayChange(void);

// Font pic table
unsigned short xlatfont[] =
{
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // 32
    2274, 2294, 2274, 2277, 2278, 2280, 2295, 2282, 2283, 2281, 2286, 2297, 2285,       // 45
    2299, 2301, 2271, 2262, 2263, 2264, 2265, 2266, 2267, 2268, 2269, 2270,     // 57
    2292, 2293, 2296, 2287, 2298, 2300, 2275, 2236, 2237, 2238, 2239, 2240,     // 69
    2241, 2242, 2243, 2244, 2245, 2246, 2247, 2248, 2249, 2250, 2251, 2252,     // 81
    2253, 2254, 2255, 2256, 2257, 2258, 2259, 2260, 2261, 2290, 2289, 2291,     // 93
    2279, 2284, 2295, 2236, 2237, 2238, 2239, 2240, 2241, 2242, 2243, 2244,     // 105
    2245, 2246, 2247, 2248, 2249, 2250, 2251, 2252, 2253, 2254, 2255, 2256,     // 117
    2257, 2258, 2259, 2260, 2261, 2282, 2288, 2283, 2272        // 126
};

int slidersettings [sldr_max] =
{
    0, SENSE_DEFAULT, FXVOL_DEFAULT, MUSIC_DEFAULT, SCRSIZE_DEFAULT,
    BRIGHTNESS_DEFAULT, BORDERTILE_DEFAULT, GAMETYPE_DEFAULT, NETLEVEL_DEFAULT,
    MONSTERS_DEFAULT, KILLLIMIT_DEFAULT, TIMELIMIT_DEFAULT, PLAYERCOLOR_DEFAULT,
    0,0,    // video mode
    32767>>12, 32767>>12,  // advanced mouse scale
};

short buttonsettings[btn_max];

// EXTERNS ////////////////////////////
#define XDIM    320
#define YDIM    200
extern char DefaultPalette[];
extern SWBOOL QuitFlag;

void TerminateGame(void);
void ResetKeys(void);

// GLOBALS ////////////////////////////

char playertextbuffer[80];              // Used for various input strings
char playerbuflen = 0;                  // Current length of the string in
// the buffer
char maxtextlen;                        // max length allowed for current

static struct { int xdim,ydim; } validresolutions[MAXVALIDMODES];
static int numvalidresolutions = 0, validbpps[8], numvalidbpps = 0;

static void UpdateValidModes(int bpp, int fs)
{
    int i, j;

    numvalidresolutions = numvalidbpps = 0;
    for (i=0; i<validmodecnt; i++)
    {
        if ((validmode[i].fs & 1) != fs) continue;

        for (j=0; j<numvalidbpps; j++)
            if (validbpps[j] == validmode[i].bpp) break;
        if (j==numvalidbpps) validbpps[numvalidbpps++] = validmode[i].bpp;

        if (validmode[i].bpp != bpp) continue;

        validresolutions[numvalidresolutions].xdim = validmode[i].xdim;
        validresolutions[numvalidresolutions].ydim = validmode[i].ydim;
        numvalidresolutions++;
    }
}
static SWBOOL ApplyModeSettings(void)
{
    int lastx, lasty, lastbpp, lastfs;
    int newx, newy, newbpp, newfs;

    lastx = xdim; lasty = ydim; lastbpp = bpp; lastfs = fullscreen;
    newx   = validresolutions[slidersettings[sldr_videores]].xdim;
    newy   = validresolutions[slidersettings[sldr_videores]].ydim;
    newbpp = validbpps[slidersettings[sldr_videobpp]];
    newfs  = buttonsettings[btn_videofs];

    if (lastx == newx && lasty == newy && lastbpp == newbpp && lastfs == newfs) return FALSE;

    if (setgamemode(newfs, newx, newy, newbpp))
        setgamemode(lastfs, lastx, lasty, lastbpp);
    else
    {
        extern int32_t ScreenMode,ScreenWidth,ScreenHeight,ScreenBPP; // Because I'm too lazy to include config.h
        ScreenMode = newfs;
        ScreenWidth = newx;
        ScreenHeight = newy;
        ScreenBPP = newbpp;

        SetupAspectRatio();
        SetRedrawScreen(Player + myconnectindex);
    }
    return FALSE;
}


MenuItem sound_i[] =
{
    {DefButton(btn_music, 0, "Music"), OPT_XS,                OPT_LINE(0), 1, m_defshade, 0, MNU_TryMusicInit, MNU_MusicCheck, NULL},
    {DefSlider(sldr_musicvolume, 0, "Music Volume"), OPT_XS,  OPT_LINE(1), 1, m_defshade, 0, MNU_TryMusicInit, MNU_MusicCheck, NULL},
    {DefInert(0, NULL), OPT_XSIDE,                            OPT_LINE(1), 0, m_defshade, 0, NULL, NULL, NULL},

    {DefButton(btn_sound, 0, "Sounds"), OPT_XS,               OPT_LINE(2), 1, m_defshade, 0, NULL, MNU_FxCheck, NULL},
    {DefSlider(sldr_sndfxvolume, 0, "Sound Volume"), OPT_XS,  OPT_LINE(3), 1, m_defshade, 0, NULL, MNU_FxCheck, NULL},
    {DefInert(0, NULL), OPT_XSIDE,                            OPT_LINE(3), 0, m_defshade, 0, NULL, NULL, NULL},

    //{DefButton(btn_talking, 0, "Talking"), OPT_XS,            OPT_LINE(4), 1, m_defshade, 0, NULL, MNU_FxCheck, NULL},
    {DefButton(btn_ambience, 0, "Ambience"), OPT_XS,          OPT_LINE(4), 1, m_defshade, 0, NULL, MNU_FxCheck, NULL},
    {DefButton(btn_flipstereo, 0, "Flip Stereo"), OPT_XS,     OPT_LINE(5), 1, m_defshade, 0, NULL, MNU_FxCheck, NULL},
    //{DefButton(btn_playcd, 0, "Play CD"), OPT_XS,         OPT_LINE(6), 1, m_defshade, 0, NULL, NULL, NULL},
    {DefNone}
};

MenuGroup soundgroup = {110,5,"^Sound",sound_i,pic_optionstitl,0,m_defshade, NULL,NULL, 0};

MenuItem parental_i[] =
{
    {DefButton(btn_parental, 0, "Kid Mode"), OPT_XS, OPT_LINE(0), 1, m_defshade, 0, NULL, NULL, NULL},
    {DefOption(KEYSC_P, "Change Password"),              OPT_XS, OPT_LINE(1), 1, m_defshade, 0, MNU_ParentalCustom, NULL, NULL},
    {DefNone}
};

MenuGroup parentalgroup = {65, 5, "^Kid Mode", parental_i, pic_newgametitl, 0, m_defshade, MNU_DoParentalPassword,NULL,0};

MenuItem screen_i[] =
{
    {DefSlider(sldr_scrsize, 0, "Screen Size"), OPT_XS,          OPT_LINE(0), 1, m_defshade, 0, NULL, NULL, NULL}, //, MNU_BorderCheck},
    {DefInert(0, NULL), OPT_XSIDE,                               OPT_LINE(0), 0, m_defshade, 0, NULL, NULL, NULL},

    {DefSlider(sldr_bordertile, 0, "Border Tile"), OPT_XS,       OPT_LINE(1), 1, m_defshade, 0, NULL, NULL, NULL}, //, MNU_BorderCheck},
    {DefInert(0, NULL), OPT_XSIDE,                               OPT_LINE(1), 0, m_defshade, 0, NULL, NULL, NULL},

    {DefSlider(sldr_brightness, KEYSC_B, "Brightness"), OPT_XS,  OPT_LINE(2), 1, m_defshade, 0, NULL, NULL, NULL},
    {DefInert(0, NULL), OPT_XSIDE,                               OPT_LINE(2), 0, m_defshade, 0, NULL, NULL, NULL},

    {DefButton(btn_videofs, 0, "Fullscreen"), OPT_XS,            OPT_LINE(4), 1, m_defshade, 0, NULL, NULL, NULL},
    {DefSlider(sldr_videobpp, 0, "Colour"), OPT_XS,              OPT_LINE(5), 1, m_defshade, 0, NULL, NULL, NULL},
    {DefInert(0, NULL), OPT_XSIDE,                               OPT_LINE(5), 0, m_defshade, 0, NULL, NULL, NULL},
    {DefSlider(sldr_videores, 0, "Resolution"), OPT_XS,          OPT_LINE(6), 1, m_defshade, 0, NULL, NULL, NULL},
    {DefInert(0, NULL), OPT_XSIDE,                               OPT_LINE(6), 0, m_defshade, 0, NULL, NULL, NULL},
    {DefOption(0, "Apply Settings"), OPT_XSIDE,                  OPT_LINE(8), 1, m_defshade, 0, ApplyModeSettings, NULL, NULL},
    {DefNone}
};

MenuGroup screengroup = {65, 5, "^Screen", screen_i, pic_newgametitl, 0, m_defshade, NULL,NULL, 0};

MenuItem mouse_i[] =
{
    {DefSlider(sldr_mouse, 0, "Mouse Speed"), OPT_XS,            OPT_LINE(0), 1, m_defshade, 0, NULL, MNU_MouseCheck, NULL},
    {DefInert(0, NULL), OPT_XSIDE,                               OPT_LINE(0), 0, m_defshade, 0, NULL, NULL, NULL},    // Blank line for mouse

    {DefButton(btn_mouse_aim, 0, "Mouse Aiming"), OPT_XS,        OPT_LINE(1), 1, m_defshade, 0, NULL, MNU_MouseCheck, NULL},
    {DefButton(btn_mouse_invert, 0, "Invert Mouse"), OPT_XS,     OPT_LINE(2), 1, m_defshade, 0, NULL, MNU_MouseCheck, NULL},
    {DefNone}
};

MenuGroup mousegroup = {65, 5, "^Mouse", mouse_i, pic_newgametitl, 0, m_defshade, NULL,NULL, 0};

MenuGroup keysetupgroup = {0, 0, NULL, NULL, 0, 0, m_defshade, MNU_KeySetupCustom, NULL, 0};

static char MouseButtonFunctions[10][MAXFUNCTIONLENGTH];
static SWBOOL MNU_SetMouseButtonFunctions(MenuItem_p item);
static SWBOOL MNU_MouseButtonPostProcess(MenuItem_p item);
static SWBOOL MNU_MouseButtonSetupCustom(UserCall call, MenuItem_p item);
MenuGroup mousebuttongroup = {0, 0, NULL, NULL, 0, 0, m_defshade, MNU_MouseButtonSetupCustom, NULL, 0};
MenuItem mousesetup_i[] =
{
    {DefLayer(0, "Left", &mousebuttongroup),          OPT_XS, OPT_LINE(0), 1, m_defshade, 0,   NULL, NULL, MNU_MouseButtonPostProcess},
    {DefInert(0, MouseButtonFunctions[0]),         OPT_XSIDE, OPT_LINE(0), 1, m_defshade, 0,   NULL, MNU_SetMouseButtonFunctions, NULL},
    {DefLayer(0, "Double Left", &mousebuttongroup),   OPT_XS, OPT_LINE(1), 1, m_defshade, 6+0, NULL, NULL, MNU_MouseButtonPostProcess},
    {DefInert(0, MouseButtonFunctions[6+0]),       OPT_XSIDE, OPT_LINE(1), 1, m_defshade, 6+0, NULL, MNU_SetMouseButtonFunctions, NULL},
    {DefLayer(0, "Right", &mousebuttongroup),         OPT_XS, OPT_LINE(2), 1, m_defshade, 1,   NULL, NULL, MNU_MouseButtonPostProcess},
    {DefInert(0, MouseButtonFunctions[1]),         OPT_XSIDE, OPT_LINE(2), 1, m_defshade, 1,   NULL, MNU_SetMouseButtonFunctions, NULL},
    {DefLayer(0, "Double Right", &mousebuttongroup),  OPT_XS, OPT_LINE(3), 1, m_defshade, 6+1, NULL, NULL, MNU_MouseButtonPostProcess},
    {DefInert(0, MouseButtonFunctions[6+1]),       OPT_XSIDE, OPT_LINE(3), 1, m_defshade, 6+1, NULL, MNU_SetMouseButtonFunctions, NULL},
    {DefLayer(0, "Middle", &mousebuttongroup),        OPT_XS, OPT_LINE(4), 1, m_defshade, 2,   NULL, NULL, MNU_MouseButtonPostProcess},
    {DefInert(0, MouseButtonFunctions[2]),         OPT_XSIDE, OPT_LINE(4), 1, m_defshade, 2,   NULL, MNU_SetMouseButtonFunctions, NULL},
    {DefLayer(0, "Double Middle", &mousebuttongroup), OPT_XS, OPT_LINE(5), 1, m_defshade, 6+2, NULL, NULL, MNU_MouseButtonPostProcess},
    {DefInert(0, MouseButtonFunctions[6+2]),       OPT_XSIDE, OPT_LINE(5), 1, m_defshade, 6+2, NULL, MNU_SetMouseButtonFunctions, NULL},
    {DefLayer(0, "Thumb", &mousebuttongroup),         OPT_XS, OPT_LINE(6), 1, m_defshade, 3,   NULL, NULL, MNU_MouseButtonPostProcess},
    {DefInert(0, MouseButtonFunctions[3]),         OPT_XSIDE, OPT_LINE(6), 1, m_defshade, 3,   NULL, MNU_SetMouseButtonFunctions, NULL},
    {DefLayer(0, "Double Thumb", &mousebuttongroup),  OPT_XS, OPT_LINE(7), 1, m_defshade, 6+3, NULL, NULL, MNU_MouseButtonPostProcess},
    {DefInert(0, MouseButtonFunctions[6+3]),       OPT_XSIDE, OPT_LINE(7), 1, m_defshade, 6+3, NULL, MNU_SetMouseButtonFunctions, NULL},
    {DefLayer(0, "Wheel Up", &mousebuttongroup),      OPT_XS, OPT_LINE(8), 1, m_defshade, 4,   NULL, NULL, MNU_MouseButtonPostProcess},
    {DefInert(0, MouseButtonFunctions[4]),         OPT_XSIDE, OPT_LINE(8), 1, m_defshade, 4,   NULL, MNU_SetMouseButtonFunctions, NULL},
    {DefLayer(0, "Wheel Down", &mousebuttongroup),    OPT_XS, OPT_LINE(9), 1, m_defshade, 5,   NULL, NULL, MNU_MouseButtonPostProcess},
    {DefInert(0, MouseButtonFunctions[5]),         OPT_XSIDE, OPT_LINE(9), 1, m_defshade, 5,   NULL, MNU_SetMouseButtonFunctions, NULL},
    {DefNone}
};
MenuGroup mousesetupgroup = {65, 5, "^Mouse Setup", mousesetup_i, pic_newgametitl, 0, m_defshade, NULL, NULL, 0};

#define JOYSTICKITEMSPERPAGE 12
#define MAXJOYSTICKBUTTONPAGES (MAXJOYBUTTONS*2 / JOYSTICKITEMSPERPAGE)
static char JoystickButtonPageName[MAXJOYSTICKBUTTONPAGES][64];
static char JoystickButtonFunctions[MAXJOYBUTTONS*2][MAXFUNCTIONLENGTH];
static char JoystickButtonNames[MAXJOYBUTTONS*2][64];
static int JoystickButtonPage = 0;
static SWBOOL MNU_JoystickButtonsInitialise(MenuItem_p item);
static SWBOOL MNU_SetJoystickButtonFunctions(MenuItem_p item);
static SWBOOL MNU_JoystickButtonPostProcess(MenuItem_p item);
static SWBOOL MNU_JoystickButtonSetupCustom(UserCall call, MenuItem_p item);
static SWBOOL MNU_JoystickButtonNextPage(void);
MenuGroup joybuttonsgroup = {0, 0, NULL, NULL, 0, 0, m_defshade, MNU_JoystickButtonSetupCustom, NULL, 0};
MenuItem joybuttons_i[MAXJOYSTICKBUTTONPAGES][JOYSTICKITEMSPERPAGE*2+3] =    // itemsperpage * Layer/Inert + Pagetext + Next + DefNone
{
    // this menu gets defined by the call to MNU_JoystickButtonsInitialise
    { {DefNone} },
};
MenuGroup joybuttonssetupgroup = {65, 5, "^Joystick Setup", NULL, pic_newgametitl, 0, m_defshade, NULL, NULL, 0};

static char JoystickAxisName[64];
static char JoystickAxisPageName[64];
static char JoystickAxisFunctions[2][MAXFUNCTIONLENGTH];
static int JoystickAxisPage = 0;
static SWBOOL MNU_JoystickAxesInitialise(MenuItem_p item);
static SWBOOL MNU_SetJoystickAxisFunctions(MenuItem_p item);
static SWBOOL MNU_JoystickAxisPostProcess(MenuItem_p item);
static SWBOOL MNU_JoystickAxisSetupCustom(UserCall call, MenuItem_p item);
static SWBOOL MNU_JoystickAxisNextPage(void);
MenuGroup joyaxesgroup = {0, 0, NULL, NULL, 0, 0, m_defshade, MNU_JoystickAxisSetupCustom, NULL, 0};
MenuItem joyaxes_i[] =
{
    {DefInert(0, JoystickAxisName), OPT_XS, OPT_LINE(0), 1, MENU_SHADE_INACTIVE, 0, NULL, NULL, NULL},

    {DefSlider(sldr_joyaxisscale, 0, "Axis Scale"),     OPT_XS, OPT_LINE(2), 1, m_defshade, 0, NULL, NULL, NULL},
    {DefInert(0, NULL),                              OPT_XSIDE, OPT_LINE(2), 0, m_defshade, 0, NULL, NULL, NULL},

    {DefSlider(sldr_joyaxisanalog, 0, "Analog"),        OPT_XS, OPT_LINE(4), 1, m_defshade, 0, NULL, NULL, NULL},
    {DefInert(0, NULL),                              OPT_XSIDE, OPT_LINE(4), 0, m_defshade, 0, NULL, NULL, NULL},
    {DefLayer(0, "Digital +ve", &joyaxesgroup),         OPT_XS, OPT_LINE(5), 1, m_defshade, 1, NULL, NULL, MNU_JoystickAxisPostProcess},
    {DefInert(0, JoystickAxisFunctions[1]),          OPT_XSIDE, OPT_LINE(5), 1, m_defshade, 1, NULL, MNU_SetJoystickAxisFunctions, NULL},
    {DefLayer(0, "Digital -ve", &joyaxesgroup),         OPT_XS, OPT_LINE(6), 1, m_defshade, 0, NULL, NULL, MNU_JoystickAxisPostProcess},
    {DefInert(0, JoystickAxisFunctions[0]),          OPT_XSIDE, OPT_LINE(6), 1, m_defshade, 0, NULL, MNU_SetJoystickAxisFunctions, NULL},

    {DefSlider(sldr_joyaxisdead, 0, "Dead Zone"),       OPT_XS, OPT_LINE(8), 1, m_defshade, 0, NULL, NULL, NULL},
    {DefInert(0, NULL),                              OPT_XSIDE, OPT_LINE(8), 0, m_defshade, 0, NULL, NULL, NULL},
    {DefSlider(sldr_joyaxissatur, 0, "Saturate"),       OPT_XS, OPT_LINE(9), 1, m_defshade, 0, NULL, NULL, NULL},
    {DefInert(0, NULL),                              OPT_XSIDE, OPT_LINE(9), 0, m_defshade, 0, NULL, NULL, NULL},

    {DefInert(0, JoystickAxisPageName),                 OPT_XS, OPT_LINE(11), 1, m_defshade, 0, NULL, NULL, NULL},
    {DefOption(0, "Next..."),                        OPT_XSIDE, OPT_LINE(11), 1, m_defshade, 0, MNU_JoystickAxisNextPage, NULL, NULL},

    {DefNone},
};
MenuGroup joyaxessetupgroup = {65, 5, "^Joystick Axes", joyaxes_i, pic_newgametitl, 0, m_defshade, NULL, NULL, 1};


static char AdvancedMouseAxisFunctions[4][MAXAXISFUNCTIONLENGTH] = { "", "", "", "" };
static SWBOOL MNU_SetAdvancedMouseFunctions(MenuItem_p item);
static SWBOOL MNU_MouseDigitalPostProcess(MenuItem_p item);
static SWBOOL MNU_MouseDigitalSetupCustom(UserCall call, MenuItem_p item);
MenuGroup advancedmousedigigroup = {0, 0, NULL, NULL, 0, 0, m_defshade, MNU_MouseDigitalSetupCustom, NULL, 0};
MenuItem advancedmouse_i[] =
{
    {DefSlider(sldr_mousescalex, 0, "X-Axis Scale"), OPT_XS,     OPT_LINE(0), 1, m_defshade, 0, NULL, NULL, NULL},
    {DefInert(0, NULL), OPT_XSIDE,                               OPT_LINE(0), 0, m_defshade, 0, NULL, NULL, NULL},
    {DefSlider(sldr_mousescaley, 0, "Y-Axis Scale"), OPT_XS,     OPT_LINE(1), 1, m_defshade, 0, NULL, NULL, NULL},
    {DefInert(0, NULL), OPT_XSIDE,                               OPT_LINE(1), 0, m_defshade, 0, NULL, NULL, NULL},
    {DefLayer(0, "Digital Up", &advancedmousedigigroup), OPT_XS, OPT_LINE(3), 1, m_defshade, 0, NULL, NULL, MNU_MouseDigitalPostProcess},
    {DefInert(0, AdvancedMouseAxisFunctions[0]), OPT_XSIDE,      OPT_LINE(3), 1, m_defshade, 0, NULL, MNU_SetAdvancedMouseFunctions, NULL},
    {DefLayer(0, "Digital Down", &advancedmousedigigroup), OPT_XS, OPT_LINE(4), 1, m_defshade, 1, NULL, NULL, MNU_MouseDigitalPostProcess},
    {DefInert(0, AdvancedMouseAxisFunctions[1]), OPT_XSIDE,      OPT_LINE(4), 1, m_defshade, 1, NULL, MNU_SetAdvancedMouseFunctions, NULL},
    {DefLayer(0, "Digital Left", &advancedmousedigigroup), OPT_XS, OPT_LINE(5), 1, m_defshade, 2, NULL, NULL, MNU_MouseDigitalPostProcess},
    {DefInert(0, AdvancedMouseAxisFunctions[2]), OPT_XSIDE,      OPT_LINE(5), 1, m_defshade, 2, NULL, MNU_SetAdvancedMouseFunctions, NULL},
    {DefLayer(0, "Digital Right", &advancedmousedigigroup), OPT_XS, OPT_LINE(6), 1, m_defshade, 3, NULL, NULL, MNU_MouseDigitalPostProcess},
    {DefInert(0, AdvancedMouseAxisFunctions[3]), OPT_XSIDE,      OPT_LINE(6), 1, m_defshade, 3, NULL, MNU_SetAdvancedMouseFunctions, NULL},
    {DefNone}
};
MenuGroup mouseadvancedgroup = {65, 5, "^Adv'd Mouse", advancedmouse_i, pic_newgametitl, 0, m_defshade, NULL, NULL, 0};

MenuItem inputsetup_i[] =
{
    {DefLayer(0, "Keys Setup", &keysetupgroup),OPT_XS,                 OPT_LINE(0),1,m_defshade,0,NULL,NULL,NULL},
    {DefLayer(0, "Mouse Setup", &mousesetupgroup),OPT_XS,              OPT_LINE(1),1,m_defshade,0,NULL,NULL,NULL},
    {DefLayer(0, "Joystick Buttons Setup", &joybuttonssetupgroup),OPT_XS,OPT_LINE(2),1,m_defshade,0,NULL,MNU_JoystickCheck,MNU_JoystickButtonsInitialise},
    {DefLayer(0, "Joystick Axes Setup", &joyaxessetupgroup), OPT_XS,   OPT_LINE(3),1,m_defshade,0,NULL,MNU_JoystickCheck,MNU_JoystickAxesInitialise},
    {DefLayer(0, "Advanced Mouse Setup", &mouseadvancedgroup),OPT_XS,  OPT_LINE(5),1,m_defshade,0,NULL,NULL,NULL},
    {DefOption(0, "Apply Modern Defaults"), OPT_XS,                    OPT_LINE(7),1,m_defshade,0,MNU_LoadModernDefaults,NULL,NULL},
    {DefOption(0, "Apply Classic Defaults"), OPT_XS,                   OPT_LINE(8),1,m_defshade,0,MNU_LoadClassicDefaults,NULL,NULL},
    {DefNone}
};
MenuGroup inputsetupgroup = {65, 5, "^Input Setup", inputsetup_i, pic_newgametitl, 0, m_defshade, NULL,NULL, 0};

MenuItem options_i[] =
{
    {DefLayer(0, "Screen Menu", &screengroup),OPT_XS,            OPT_LINE(0), 1, m_defshade,0,NULL, NULL, NULL},
    {DefLayer(0, "Mouse Menu", &mousegroup),OPT_XS,              OPT_LINE(1), 1, m_defshade,0,NULL, MNU_MouseCheck, NULL},
    {DefLayer(0, "Sound Menu", &soundgroup),OPT_XS,              OPT_LINE(2), 1, m_defshade,0,MNU_TryMusicInit, MNU_MusicFxCheck, NULL},
    {DefLayer(0, "Input Setup", &inputsetupgroup),OPT_XS,        OPT_LINE(3), 1,m_defshade,0,NULL,NULL,NULL},
#ifndef PLOCK_VERSION // No need for this in weener version
    {DefLayer(0, "Kid Mode", &parentalgroup),OPT_XS,             OPT_LINE(4), 1, m_defshade,0,NULL, NULL, NULL},
#endif
    {DefButton(btn_messages, 0, "Messages"), OPT_XS,             OPT_LINE(5), 1, m_defshade, 0, NULL, NULL, NULL},
//    {DefButton(btn_bobbing, 0, "View Bobbing"), OPT_XS,          OPT_LINE(7), 1, m_defshade, 0, NULL, NULL, NULL},
    {DefButton(btn_shadows, 0, "Shadows"), OPT_XS,               OPT_LINE(6), 1, m_defshade, 0, NULL, NULL, NULL},
    {DefButton(btn_auto_run, 0, "Auto Run"), OPT_XS,             OPT_LINE(7), 1, m_defshade, 0, NULL, NULL, NULL},
    {DefButton(btn_crosshair, 0, "Crosshair"), OPT_XS,           OPT_LINE(8), 1, m_defshade, 0, NULL, NULL, NULL},
    {DefButton(btn_auto_aim, 0, "Auto-Aiming"), OPT_XS,          OPT_LINE(9), 1, m_defshade, 0, NULL, NULL, NULL},
    {DefButton(btn_voxels, 0, "Voxel Sprites"), OPT_XS,          OPT_LINE(10), 1, m_defshade, 0, NULL, NULL, NULL},
    {DefButton(btn_stats, 0, "Level Stats"), OPT_XS,             OPT_LINE(11), 1, m_defshade, 0, NULL, MNU_StatCheck, NULL},
    {DefNone}
};

MenuGroup optiongroup = {100, 5, "^Options", options_i, pic_optionstitl, 0, m_defshade, NULL,NULL, 0};

MenuItem skill_i[] =
{
    {DefOption(KEYSC_E, &SkillNames[0][0]), 30, 46, pic_easy, m_defshade, 0, MNU_StartGame, NULL, NULL},
    {DefOption(KEYSC_N, &SkillNames[1][0]), 30, 62, pic_normal, m_defshade, 0, MNU_StartGame, NULL, NULL},
    {DefOption(KEYSC_H, &SkillNames[2][0]), 30, 78, pic_hard, m_defshade, 0, MNU_StartGame, NULL, NULL},
    {DefOption(KEYSC_I, &SkillNames[3][0]), 30, 94, pic_impossible, m_defshade, 0, MNU_StartGame, NULL, NULL},
    {DefNone}
};

MenuGroup skillgroup = {100, 5, "^Skill", skill_i, pic_newgametitl, 0, m_defshade, NULL, NULL, 2};

MenuItem episode_i[] =
{
    {DefLayer(KEYSC_S, &EpisodeNames[0][0], &skillgroup), 30, 46, pic_episode1, m_defshade, 0, MNU_EpisodeCustom, NULL, NULL},
    {DefLayer(KEYSC_F, &EpisodeNames[1][0], &skillgroup), 30, 78, pic_episode2, m_defshade, 0, MNU_EpisodeCustom, MNU_ShareWareCheck, MNU_ShareWareMessage},
    //{DefLayer(KEYSC_S, NULL, &skillgroup), 60, 30, pic_episode1, m_defshade, 0, MNU_EpisodeCustom},
    //{DefLayer(KEYSC_F, NULL, &skillgroup), 60, 46, pic_episode2, m_defshade, 0, MNU_EpisodeCustom},
    //{DefLayer(KEYSC_T, NULL, &skillgroup), 60, 62, pic_episode3, m_defshade, 0, MNU_EpisodeCustom},
    {DefNone}
};

MenuGroup episodegroup = {100, 5, "^Episode", episode_i, pic_newgametitl, 0, m_defshade, MNU_DoEpisodeSelect, NULL, 0};

extern char UserMapName[80];

MenuItem network_extra_i[] =
{
    {DefSlider(sldr_monsters, 0, "Monsters"),OPT_XS, OPT_LINE(0), 1, m_defshade, 0, NULL, NULL, NULL},
    {DefInert(0, NULL),               OPT_XSIDE,OPT_LINE(0), 0, m_defshade, 0, NULL, NULL, NULL},

    {DefButton(btn_teamplay,     0, "Team Play"),    OPT_XS, OPT_LINE(1), 1, m_defshade, 0, MNU_TeamPlayChange, MNU_TeamPlayCheck, NULL},
    {DefButton(btn_friendlyfire, 0, "Hurt Teammate"),OPT_XS, OPT_LINE(2), 1, m_defshade, 0, NULL, MNU_HurtTeammateCheck, NULL},
    {DefButton(btn_nuke,         0, "Play with Nuke"),OPT_XS, OPT_LINE(3), 1, m_defshade, 0, NULL, NULL, NULL},
    {DefNone}
};

MenuGroup networkextragroup = {50, 5, "^Net Options", network_extra_i, pic_newgametitl, 0, m_defshade, NULL, NULL, 0};

MenuItem network_i[] =
{
    {DefSlider(sldr_gametype, 0, "Game Type"),   OPT_XS,         OPT_LINE(0), 1, m_defshade, 0, NULL, NULL, NULL},
    {DefInert(0, NULL),                          OPT_XSIDE-38,   OPT_LINE(0), 0, m_defshade, 0, NULL, NULL, NULL},

    {DefSlider(sldr_netlevel, 0, "Level"),       OPT_XS,         OPT_LINE(1), 1, m_defshade, 0, NULL, MNU_CheckUserMap, NULL},
    {DefInert(0, NULL),                          OPT_XSIDE-70,   OPT_LINE(1), 0, m_defshade, 0, NULL, MNU_CheckUserMap, NULL},
    {DefInert(0, " "),                           OPT_XS,         OPT_LINE(2), pic_episode1, m_defshade, 0, NULL, MNU_CheckUserMap, NULL},

    {DefButton(btn_markers,      0, "Markers"),  OPT_XS,         OPT_LINE(3), 1, m_defshade, 0, NULL, NULL, NULL},

    {DefSlider(sldr_killlimit, 0, "Kill Limit"),OPT_XS,          OPT_LINE(4), 1, m_defshade, 0, NULL, MNU_CoopPlayCheck, NULL},
    {DefInert(0, NULL),               OPT_XSIDE,                 OPT_LINE(4), 0, m_defshade, 0, NULL, MNU_CoopPlayCheck, NULL},

    {DefSlider(sldr_timelimit, 0, "Time Limit"),OPT_XS,          OPT_LINE(5), 1, m_defshade, 0, NULL, MNU_CoopPlayCheck, NULL},
    {DefInert(0, NULL),               OPT_XSIDE,                 OPT_LINE(5), 0, m_defshade, 0, NULL, MNU_CoopPlayCheck, NULL},

    {DefSlider(sldr_playercolor, 0, "Player Color"),OPT_XS,      OPT_LINE(6), 1, m_defshade, 0, NULL, NULL, NULL},
    {DefInert(0, NULL),               OPT_XSIDE,                 OPT_LINE(6), 0, m_defshade, 0, NULL, NULL, NULL},

    {DefLayer(0, "Other Options", &networkextragroup),OPT_XS,    OPT_LINE(7), 1, m_defshade,0,NULL, NULL, NULL},

    {DefInert(0, UserMapName),         OPT_XSIDE,                OPT_LINE(8), pic_episode1, m_defshade, 0, NULL, NULL, NULL},
    {DefOption(KEYSC_S, "Start Game"),   OPT_XS,                    OPT_LINE(8), pic_episode1, m_defshade, 0, MNU_StartNetGame, NULL, NULL},

    {DefNone}

};

MenuGroup networkgroup = {50, 5, "^Network Game", network_i, pic_newgametitl, 0, m_defshade, NULL, NULL, 0};


MenuItem load_i[] =
{
#define SD_YSTART 26
#define SD_XSTART 5
#define SD_YOFF 13
#define SD_LINE(line) (SD_YSTART + (line * SD_YOFF))
    {DefOption(0, NULL), SD_XSTART, SD_LINE(0), 0, m_defshade, 0, MNU_GetLoadCustom, NULL, NULL},
    {DefOption(0, NULL), SD_XSTART, SD_LINE(1), 0, m_defshade, 0, MNU_GetLoadCustom, NULL, NULL},
    {DefOption(0, NULL), SD_XSTART, SD_LINE(2), 0, m_defshade, 0, MNU_GetLoadCustom, NULL, NULL},
    {DefOption(0, NULL), SD_XSTART, SD_LINE(3), 0, m_defshade, 0, MNU_GetLoadCustom, NULL, NULL},
    {DefOption(0, NULL), SD_XSTART, SD_LINE(4), 0, m_defshade, 0, MNU_GetLoadCustom, NULL, NULL},
    {DefOption(0, NULL), SD_XSTART, SD_LINE(5), 0, m_defshade, 0, MNU_GetLoadCustom, NULL, NULL},
    {DefOption(0, NULL), SD_XSTART, SD_LINE(6), 0, m_defshade, 0, MNU_GetLoadCustom, NULL, NULL},
    {DefOption(0, NULL), SD_XSTART, SD_LINE(7), 0, m_defshade, 0, MNU_GetLoadCustom, NULL, NULL},
    {DefOption(0, NULL), SD_XSTART, SD_LINE(8), 0, m_defshade, 0, MNU_GetLoadCustom, NULL, NULL},
    {DefOption(0, NULL), SD_XSTART, SD_LINE(9), 0, m_defshade, 0, MNU_GetLoadCustom, NULL, NULL},
    {DefNone}
};

MenuItem save_i[] =
{
    {DefOption(0, NULL), SD_XSTART, SD_LINE(0), 0, m_defshade, 0, MNU_GetSaveCustom, NULL, NULL},
    {DefOption(0, NULL), SD_XSTART, SD_LINE(1), 0, m_defshade, 0, MNU_GetSaveCustom, NULL, NULL},
    {DefOption(0, NULL), SD_XSTART, SD_LINE(2), 0, m_defshade, 0, MNU_GetSaveCustom, NULL, NULL},
    {DefOption(0, NULL), SD_XSTART, SD_LINE(3), 0, m_defshade, 0, MNU_GetSaveCustom, NULL, NULL},
    {DefOption(0, NULL), SD_XSTART, SD_LINE(4), 0, m_defshade, 0, MNU_GetSaveCustom, NULL, NULL},
    {DefOption(0, NULL), SD_XSTART, SD_LINE(5), 0, m_defshade, 0, MNU_GetSaveCustom, NULL, NULL},
    {DefOption(0, NULL), SD_XSTART, SD_LINE(6), 0, m_defshade, 0, MNU_GetSaveCustom, NULL, NULL},
    {DefOption(0, NULL), SD_XSTART, SD_LINE(7), 0, m_defshade, 0, MNU_GetSaveCustom, NULL, NULL},
    {DefOption(0, NULL), SD_XSTART, SD_LINE(8), 0, m_defshade, 0, MNU_GetSaveCustom, NULL, NULL},
    {DefOption(0, NULL), SD_XSTART, SD_LINE(9), 0, m_defshade, 0, MNU_GetSaveCustom, NULL, NULL},
    {DefNone}
};

// No actual submenus for this, just quit text.
MenuGroup quitgroup = {0, 0, NULL, NULL, 0, 0, m_defshade, MNU_QuitCustom, NULL, 0};
MenuGroup quickloadgroup = {0, 0, NULL, NULL, 0, 0, m_defshade, MNU_QuickLoadCustom, NULL, 0};
MenuGroup ordergroup = {0, 0, NULL, NULL, 0, 0, m_defshade, MNU_OrderCustom, NULL, 0};

// save and load function calls
MenuGroup SaveGameGroup = {100, 5, "^Save Game", save_i, pic_savegame, 0, m_defshade, MNU_LoadSaveDraw, MNU_LoadSaveMove, 0};
MenuGroup LoadGameGroup = {100, 5, "^Load Game", load_i, pic_loadgame, 0, m_defshade, MNU_LoadSaveDraw, MNU_LoadSaveMove, 0};

#define MAIN_YSTART 32
#define MAIN_YOFF 17
#define MAIN_XSTART 55
#define MAIN_LINE(line) (MAIN_YSTART + (MAIN_YOFF * line))

#define MAIN_MENU_COOL_STUFF "^Cool Stuff"
#define MAIN_MENU_HOW_TO_ORDER "^How to Order"

MenuItem main_i[] =
{
    {DefLayer(KEYSC_N, "^New Game", &episodegroup),      MAIN_XSTART, MAIN_LINE(0), pic_newgame, m_defshade, 0, NULL, NULL, NULL},
    {DefLayer(KEYSC_L, "^Load Game", &LoadGameGroup),    MAIN_XSTART, MAIN_LINE(1), pic_load, m_defshade, 0, NULL, MNU_LoadGameCheck, NULL},
    {DefLayer(KEYSC_S, "^Save Game", &SaveGameGroup),    MAIN_XSTART, MAIN_LINE(2), pic_save, m_defshade, 0, NULL, MNU_SaveGameCheck, NULL},
    {DefLayer(KEYSC_O, "^Options", &optiongroup),        MAIN_XSTART, MAIN_LINE(3), pic_options, m_defshade, 0, NULL, NULL, NULL},
    {DefLayer(KEYSC_H, "^Oh Dear", &ordergroup),         MAIN_XSTART, MAIN_LINE(4), pic_orderinfo, m_defshade, 0, NULL, NULL, NULL},
    {DefLayer(KEYSC_Q, "^Quit", &quitgroup),             MAIN_XSTART, MAIN_LINE(5), pic_quit, m_defshade, 0, NULL, NULL, NULL},
    {DefNone}
};

MenuGroup maingroup = {160, 15, NULL, main_i, pic_shadow_warrior, 0, m_defshade, NULL, NULL, 0};

CTLType ControlPanelType;

#define MaxLayers    10         // Maximum layers deep a menu can go
short       menuarrayptr;       // Index into menuarray
MenuGroup   *menuarray[MaxLayers], *currentmenu;
SWBOOL UsingMenus = FALSE;

#define MAXDIALOG       2       // Maximum number of dialog strings allowed
char *dialog[MAXDIALOG];

// Global menu setting values ////////////////////////////////////////////////////////////////////
// Mouse slider vars
#define SENSE_MIN               75
#define SENSE_MUL               10
int SENSITIVITY = SENSE_MIN + (SENSE_DEFAULT *SENSE_MUL);

// Sound vars
#define FX_MIN                  0
#define MUSIC_MIN               0
#define VOL_MUL                 16

// User input data for all devices
UserInput mnu_input, mnu_input_buffered, order_input_buffered;

// Menu function call back pointer for multiplay menus
SWBOOL(*cust_callback)(UserCall call, MenuItem_p item);
UserCall cust_callback_call;
MenuItem_p cust_callback_item;

// Prototypes ///////////////////////////////////////////////////////////////////////////////////

static void MNU_ClearDialog(void);
static SWBOOL MNU_Dialog(void);
void LoadSaveMsg(char *msg);
static void MNU_ItemPreProcess(MenuGroup *group);
static void MNU_SelectItem(MenuGroup *group, short index, SWBOOL draw);
static void MNU_PushItem(MenuItem *item, SWBOOL draw);

static int MNU_ControlAxisOffset(int num);
static int MNU_ControlAxisNum(int offset);


// F U N C T I O N S ////////////////////////////////////////////////////////////////////////////

// CUSTOM ROUTINES ////////////////////////////////////////////////////////////////////////////////
// CTW REMOVED
/*
SWBOOL
MNU_Ten(void)
    {
    TEN_Setup();

    return(FALSE);
    }
*/
// CTW REMOVED END
SWBOOL
MNU_DoEpisodeSelect(UserCall call, MenuItem *item)
{
    short w,h;
    char TempString[80];
    char *extra_text;

    extra_text = EpisodeSubtitles[0];
    MNU_MeasureString(extra_text, &w, &h);
    MNU_DrawString(30, 63, extra_text, 1, 16);
    extra_text = EpisodeSubtitles[1];
    MNU_MeasureString(extra_text, &w, &h);
    MNU_DrawString(30, 96, extra_text, 1, 16);

    return TRUE;
}

SWBOOL
MNU_DoParentalPassword(UserCall call, MenuItem_p item)
{
    short w,h;
    signed char MNU_InputString(char *, short);
    static SWBOOL cur_show;
    char TempString[80];
    char *extra_text;


    extra_text = "This mode should remove most of the";
    MNU_MeasureString(extra_text, &w, &h);
    MNU_DrawString(TEXT_XCENTER(w), 60, extra_text, 1, 16);
    extra_text = "offensive content.  We still recommend";
    MNU_MeasureString(extra_text, &w, &h);
    MNU_DrawString(TEXT_XCENTER(w), 70, extra_text, 1, 16);
    extra_text = "you review the game prior to play.";
    MNU_MeasureString(extra_text, &w, &h);
    MNU_DrawString(TEXT_XCENTER(w), 80, extra_text, 1, 16);

    // get input
    if (MenuInputMode)
    {
        switch (MNU_InputString(MessageInputString, 80))
        {
        case -1: // Cancel Input (pressed ESC) or Err
            KB_ClearKeysDown();
            KB_FlushKeyboardQueue();
            MenuInputMode = FALSE;
            memset(MessageInputString, '\0', sizeof(MessageInputString));
            break;
        case FALSE: // Input finished (RETURN)
            if (MessageInputString[0] == '\0')
            {
                MenuInputMode = FALSE;
                KB_ClearKeysDown();
                KB_FlushKeyboardQueue();
                memset(MessageInputString, '\0', sizeof(MessageInputString));
            }
            else
            {
                MenuInputMode = FALSE;
                KB_ClearKeysDown();
                KB_FlushKeyboardQueue();

                if (gs.Password[0] != '\0' && passwordvalid == FALSE)
                {
                    if (!Bstrcasecmp(gs.Password,MessageInputString))
                    {
                        passwordvalid = TRUE;
                        if (currentmenu->cursor == 0 && gs.ParentalLock == TRUE)
                        {
                            buttonsettings[btn_parental] = gs.ParentalLock = FALSE;
                            if (!InMenuLevel)
                                JS_ToggleLockouts();
                        }

                        if (currentmenu->cursor == 1) // Is it on the password line?
                        {
                            MenuInputMode = TRUE;
                            KB_ClearKeysDown();
                            KB_FlushKeyboardQueue();
                        }
                        //memset(gs.Password, '\0', sizeof(gs.Password));
                    }
                }
                else
                {
                    if (currentmenu->cursor == 1) // Is it on the password line?
                    {
                        strcpy(gs.Password,MessageInputString);
                        passwordvalid = FALSE;
                    }
                }

                memset(MessageInputString, '\0', sizeof(MessageInputString));
            }
            break;
        case TRUE: // Got input
            break;
        }

        //CON_Message("Password = '%s'",gs.Password);
        //CON_Message("Passwordvalid = %d",passwordvalid);

        if (gs.Password[0] != '\0' && passwordvalid == FALSE && currentmenu->cursor == 1)
        {
            sprintf(TempString,"Enter Old Password");
            MNU_MeasureString(TempString, &w, &h);
            MNU_DrawString(TEXT_XCENTER(w), MESSAGE_LINE-10, TempString,1,16);
        }
        else if (passwordvalid == TRUE && currentmenu->cursor == 1)
        {
            sprintf(TempString,"Enter New Password");
            MNU_MeasureString(TempString, &w, &h);
            MNU_DrawString(TEXT_XCENTER(w), MESSAGE_LINE-10, TempString,1,16);
        }
        else
        {
            sprintf(TempString,"Enter Password");
            MNU_MeasureString(TempString, &w, &h);
            MNU_DrawString(TEXT_XCENTER(w), MESSAGE_LINE-10, TempString,1,16);
        }

        MNU_MeasureString(MessageInputString, &w, &h);

        cur_show ^= 1;
        if (cur_show)
        {
            MNU_DrawString(TEXT_XCENTER(w), MESSAGE_LINE, MessageInputString,1,16);
            rotatesprite((TEXT_XCENTER(w)+w+7)<<16,(MESSAGE_LINE+3)<<16,64<<9,0,COINCURSOR+((totalclock>>3)%7),0,0,MenuDrawFlags,0,0,xdim-1,ydim-1);
        }
        else
        {
            MNU_DrawString(TEXT_XCENTER(w), MESSAGE_LINE, MessageInputString,1,16);
            rotatesprite((TEXT_XCENTER(w)+w+7)<<16,(MESSAGE_LINE+3)<<16,64<<9,0,COINCURSOR+((totalclock>>3)%7),0,0,MenuDrawFlags,0,0,xdim-1,ydim-1);
        }

    }

    return TRUE;
}

SWBOOL
MNU_ParentalCustom(void)
{

    if (MenuInputMode)
    {
        // toggle edit mode
        MenuInputMode = FALSE;
        memset(MessageInputString, '\0', sizeof(MessageInputString));
    }
    else
    {
        // clear keyboard buffer
        while (KB_KeyWaiting())
        {
            if (KB_Getch() == 0)
                KB_Getch();
        }

        // toggle edit mode
        MenuInputMode = TRUE;
    }

    return TRUE;
}

SWBOOL MNU_KeySetupCustom(UserCall call, MenuItem *item)
{
    static int currentkey = 0, currentcol = 0;
    static int currentmode = 0;

    if (call == uc_touchup)
        return TRUE;

    if (cust_callback == NULL)
    {
        if (call != uc_setup)
            return FALSE;
        currentkey = 0;
        currentcol = 0;
        currentmode = 0;
    }

    cust_callback = MNU_KeySetupCustom;
    cust_callback_call = call;
    cust_callback_item = item;

    {
        short w, h = 0;
        char *s = "Keys Setup";
        rotatesprite(10 << 16, (5-3) << 16, MZ, 0, 2427,
                     m_defshade, 0, MenuDrawFlags|ROTATE_SPRITE_CORNER, 0, 0, xdim - 1, ydim - 1);
        MNU_MeasureStringLarge(s, &w, &h);
        MNU_DrawStringLarge(TEXT_XCENTER(w), 5, s);
    }

    if (currentmode)
    {
        // customising a key
        char *strs[] = { "Press the key to assign to", "\"%s\" %s", "or ESCAPE to cancel." };
        char *col[2] = { "(primary)", "(secondary)" };
        short w, h = 8;
        int i, j, y;

        if (KEY_PRESSED(KEYSC_ESC))
        {
            KB_ClearKeyDown(sc_Escape);
            currentmode = 0;
        }
        else if (KB_GetLastScanCode() > 0)
        {
            KB_ClearKeyDown(KB_GetLastScanCode());

            KeyboardKeys[currentkey][currentcol] = KB_GetLastScanCode();
            if (currentkey != gamefunc_Show_Console)
            {
                CONTROL_MapKey(currentkey,
                               KeyboardKeys[currentkey][0],
                               KeyboardKeys[currentkey][1]);
            }
            else
            {
                OSD_CaptureKey(KB_GetLastScanCode());
            }

            currentmode = 0;
        }

        MNU_MeasureString(strs[0], &w, &h);

        y = (YDIM - (h+3) * SIZ(strs)) / 2;

        for (i=0; i<(int)SIZ(strs); i++)
        {
            w = 0;
            sprintf(ds,strs[i],gamefunctions[currentkey],col[currentcol]);
            for (j=0; ds[j]; j++) if (ds[j] == '_') ds[j] = ' ';
            MNU_MeasureString(ds, &w, &h);
            MNU_DrawString((XDIM - w)/2, y, ds, 0, 16);
            y += h+3;
        }

    }
    else
    {
        // key list
#define PGSIZ 14
        int topitem = 0, botitem = NUMGAMEFUNCTIONS;
        int i,j;
        char *morestr = "More...";
        const char *p;

        UserInput inpt = {FALSE,FALSE,dir_None};
        CONTROL_GetUserInput(&inpt);

        if (KEY_PRESSED(KEYSC_ESC) || inpt.button1)
        {
            KEY_PRESSED(KEYSC_ESC) = FALSE;
            cust_callback = NULL;
            CONTROL_ClearUserInput(&inpt);
            return TRUE;
        }
        else if (KB_KeyPressed(sc_Delete))
        {
            KB_ClearKeyDown(sc_Delete);
            if (currentkey != gamefunc_Show_Console)
            {
                KeyboardKeys[currentkey][currentcol] = 0xff;
                CONTROL_MapKey(currentkey,
                               KeyboardKeys[currentkey][0],
                               KeyboardKeys[currentkey][1]);
            }
        }
        else if (KB_KeyPressed(sc_Home))
        {
            currentkey = 0;
            KB_ClearKeyDown(sc_Home);
        }
        else if (KB_KeyPressed(sc_End))
        {
            currentkey = NUMGAMEFUNCTIONS-1;
            KB_ClearKeyDown(sc_End);
        }
        else if (KB_KeyPressed(sc_PgDn))
        {
            currentkey += PGSIZ;
            if (currentkey >= NUMGAMEFUNCTIONS) currentkey = NUMGAMEFUNCTIONS-1;
            KB_ClearKeyDown(sc_PgDn);
        }
        else if (KB_KeyPressed(sc_PgUp))
        {
            currentkey -= PGSIZ;
            if (currentkey < 0) currentkey = 0;
            KB_ClearKeyDown(sc_PgUp);
        }
        else if (inpt.button0)
        {
            currentmode = 1;
            KB_ClearLastScanCode();
            KB_ClearKeysDown();
        }
        else if (inpt.dir == dir_North) currentkey = max(0,currentkey-1);
        else if (inpt.dir == dir_South) currentkey = min(NUMGAMEFUNCTIONS-1,currentkey+1);
        else if (inpt.dir == dir_East) currentcol = 1;
        else if (inpt.dir == dir_West) currentcol = 0;

        if (currentkey == gamefunc_Show_Console) currentcol = 0;

        CONTROL_ClearUserInput(&inpt);

        if (NUMGAMEFUNCTIONS > PGSIZ)
        {
            topitem = currentkey - PGSIZ/2;
            botitem = topitem + PGSIZ;

            if (topitem < 0)
            {
                botitem += -topitem;
                topitem = 0;
            }
            else if (botitem >= NUMGAMEFUNCTIONS)
            {
                botitem = NUMGAMEFUNCTIONS-1;
                topitem = botitem - PGSIZ;
            }
        }

        for (i = topitem; i <= botitem; i++)
        {
            for (j = 0; gamefunctions[i][j]; j++)
            {
                if (gamefunctions[i][j] == '_') ds[j] = ' ';
                else ds[j] = gamefunctions[i][j];
            }
            ds[j] = 0;

            j = OPT_LINE(0)+(i-topitem)*8;
            MNU_DrawSmallString(OPT_XS, j, ds, (i==currentkey) ? 0 : 12, 16);

            p = getkeyname(KeyboardKeys[i][0]);
            if (!p || KeyboardKeys[i][0]==0xff) p = "  -";
            MNU_DrawSmallString(OPT_XSIDE, j, (char *)p, (i==currentkey) ? -5 : 12,
                                (i==currentkey && currentcol==0) ? 14 : 16);

            if (i == gamefunc_Show_Console) continue;

            p = getkeyname(KeyboardKeys[i][1]);
            if (!p || KeyboardKeys[i][1]==0xff) p = "  -";
            MNU_DrawSmallString(OPT_XSIDE + 4*14, j, (char *)p, (i==currentkey) ? -5 : 12,
                                (i==currentkey && currentcol==1) ? 14 : 16);
        }

        {
            short dx,dy;
            dx = 0, dy = 8;
            MNU_MeasureSmallString(morestr,&dx,&dy);
            if (topitem > 0)
                MNU_DrawSmallString(XDIM - OPT_XS - dx, OPT_LINE(0), morestr, 8,16);
            if (botitem < NUMGAMEFUNCTIONS-1)
                MNU_DrawSmallString(XDIM - OPT_XS - dx, OPT_LINE(0)+PGSIZ*8, morestr, 8,16);
        }
#undef PGSIZ
    }

    return TRUE;
}

static int MNU_SelectButtonFunction(const char *buttonname, int *currentfunc)
{
    const int PGSIZ = 9;
    const char *strs[] = { "Select the function to assign to", "%s", "or ESCAPE to cancel." };
    int topitem = 0, botitem = NUMGAMEFUNCTIONS-1;
    int i, j, y;
    short w, h=0;
    int returnval = 0;

    UserInput inpt = {FALSE,FALSE,dir_None};
    CONTROL_GetUserInput(&inpt);

    if (inpt.button1)
    {
        KB_ClearKeyDown(sc_Escape);
        returnval = -1;
    }
    else if (KB_KeyPressed(sc_Home))
    {
        *currentfunc = 0;
        KB_ClearKeyDown(sc_Home);
    }
    else if (KB_KeyPressed(sc_End))
    {
        *currentfunc = NUMGAMEFUNCTIONS-1;   // -1 because the last one is the console and the top is 'none'
        KB_ClearKeyDown(sc_End);
    }
    else if (KB_KeyPressed(sc_PgDn))
    {
        *currentfunc += PGSIZ;
        if (*currentfunc >= NUMGAMEFUNCTIONS) *currentfunc = NUMGAMEFUNCTIONS-1;
        KB_ClearKeyDown(sc_PgDn);
    }
    else if (KB_KeyPressed(sc_PgUp))
    {
        *currentfunc -= PGSIZ;
        if (*currentfunc < 0) *currentfunc = 0;
        KB_ClearKeyDown(sc_PgUp);
    }
    else if (inpt.button0)
    {
        returnval = 1;
    }
    else if (inpt.dir == dir_North) *currentfunc = max(0, *currentfunc-1);
    else if (inpt.dir == dir_South) *currentfunc = min(NUMGAMEFUNCTIONS-1, *currentfunc+1);

    CONTROL_ClearUserInput(&inpt);

    if (NUMGAMEFUNCTIONS-1 > PGSIZ)
    {
        topitem = *currentfunc - PGSIZ/2;
        botitem = topitem + PGSIZ;

        if (topitem < 0)
        {
            botitem += -topitem;
            topitem = 0;
        }
        else if (botitem >= NUMGAMEFUNCTIONS)
        {
            botitem = NUMGAMEFUNCTIONS-1;
            topitem = botitem - PGSIZ;
        }
    }

    y = OPT_LINE(0);
    for (i=0; i<(int)SIZ(strs); i++)
    {
        w = 0;
        sprintf(ds, strs[i], buttonname);
        for (j=0; ds[j]; j++) if (ds[j] == '_') ds[j] = ' ';
        MNU_MeasureString(ds, &w, &h);
        MNU_DrawString((XDIM - w)/2, y, ds, 0, 16);
        y += h;
    }

    for (i = topitem; i <= botitem; i++)
    {
        if (i == 0)
        {
            strcpy(ds, "  -none-");
        }
        else
        {
            for (j = 0; gamefunctions[i-1][j]; j++)
            {
                if (gamefunctions[i-1][j] == '_') ds[j] = ' ';
                else ds[j] = gamefunctions[i-1][j];
            }
            ds[j] = 0;
        }

        j = OPT_LINE(4)+(i-topitem)*8;
        MNU_DrawSmallString(130, j, ds, (i == *currentfunc) ? 0 : 12, 16);
    }

    {
        short dx = 0, dy = 8;
        const char *morestr = "More...";

        MNU_MeasureSmallString(morestr,&dx,&dy);
        if (topitem > 0)
            MNU_DrawSmallString(XDIM - OPT_XS - dx, OPT_LINE(4), morestr, 8,16);
        if (botitem < NUMGAMEFUNCTIONS-1)
            MNU_DrawSmallString(XDIM - OPT_XS - dx, OPT_LINE(4)+PGSIZ*8, morestr, 8,16);
    }

    return returnval;
}


static MenuItem_p mouse_button_item = NULL;

static SWBOOL MNU_MouseButtonPostProcess(MenuItem_p item)
{
    mouse_button_item = item;
    return TRUE;
}

SWBOOL MNU_MouseButtonSetupCustom(UserCall call, MenuItem_p item)
{
    static int currentfunc = 0;

    if (call == uc_touchup)
        return TRUE;

    if (cust_callback == NULL)
    {
        if (call != uc_setup)
            return FALSE;
        if (mouse_button_item->tics >= 6)
        {
            currentfunc = MouseButtonsClicked[mouse_button_item->tics % 6];
        }
        else
        {
            currentfunc = MouseButtons[mouse_button_item->tics % 6];
        }
        currentfunc++;

        cust_callback = MNU_MouseButtonSetupCustom;
        cust_callback_call = call;
        cust_callback_item = item;
    }

    {
        short w, h = 0;
        const char *s = "Mouse Setup";

        rotatesprite(10 << 16, (5-3) << 16, MZ, 0, 2427,
                     m_defshade, 0, MenuDrawFlags|ROTATE_SPRITE_CORNER, 0, 0, xdim - 1, ydim - 1);
        MNU_MeasureStringLarge(s, &w, &h);
        MNU_DrawStringLarge(TEXT_XCENTER(w), 5, s);
    }

    int selection = MNU_SelectButtonFunction(mouse_button_item->text, &currentfunc);
    switch (selection)
    {
    case -1:    //cancel
        cust_callback = NULL;
        break;
    case 1:     //acknowledge
        currentfunc--;
        if (mouse_button_item->tics >= 6)
        {
            MouseButtonsClicked[mouse_button_item->tics % 6] = currentfunc;
            CONTROL_MapButton(currentfunc, mouse_button_item->tics % 6, 1, controldevice_mouse);
        }
        else
        {
            MouseButtons[mouse_button_item->tics % 6] = currentfunc;
            CONTROL_MapButton(currentfunc, mouse_button_item->tics % 6, 0, controldevice_mouse);
        }
        MNU_SetMouseButtonFunctions(mouse_button_item);
        cust_callback = NULL;
        break;
    default: break;
    }

    return TRUE;
}

static SWBOOL MNU_SetMouseButtonFunctions(MenuItem_p item)
{
    int button, clicked, function;
    char *p;

    clicked = item->tics >= 6;
    button = item->tics % 6;
    ASSERT(button >= 0 && button <= 5);

    if (clicked)
    {
        function = MouseButtonsClicked[button];
    }
    else
    {
        function = MouseButtons[button];
    }

    if (function < 0)
    {
        strcpy(MouseButtonFunctions[item->tics], "  -");
    }
    else
    {
        strcpy(MouseButtonFunctions[item->tics], CONFIG_FunctionNumToName(function));
        for (p = MouseButtonFunctions[item->tics]; *p; p++)
        {
            if (*p == '_')
                *p = ' ';
        }
    }
    return TRUE;
}


static MenuItem_p mouse_digital_item = NULL;

static SWBOOL MNU_MouseDigitalPostProcess(MenuItem_p item)
{
    mouse_digital_item = item;
    return TRUE;
}

static SWBOOL MNU_MouseDigitalSetupCustom(UserCall call, MenuItem_p item)
{
    static int currentfunc = 0;

    if (call == uc_touchup)
        return TRUE;

    if (cust_callback == NULL)
    {
        if (call != uc_setup)
            return FALSE;
        currentfunc = MouseDigitalAxes[mouse_digital_item->tics/2][mouse_digital_item->tics%2];
        currentfunc++;

        cust_callback = MNU_MouseDigitalSetupCustom;
        cust_callback_call = call;
        cust_callback_item = item;
    }

    {
        short w, h = 0;
        const char *s = "Adv'd Mouse";

        rotatesprite(10 << 16, (5-3) << 16, MZ, 0, 2427,
                     m_defshade, 0, MenuDrawFlags|ROTATE_SPRITE_CORNER, 0, 0, xdim - 1, ydim - 1);
        MNU_MeasureStringLarge(s, &w, &h);
        MNU_DrawStringLarge(TEXT_XCENTER(w), 5, s);
    }

    int selection = MNU_SelectButtonFunction(mouse_digital_item->text, &currentfunc);
    switch (selection)
    {
    case -1:    //cancel
        cust_callback = NULL;
        break;
    case 1:     //acknowledge
        currentfunc--;
        MouseDigitalAxes[mouse_digital_item->tics/2][mouse_digital_item->tics%2] = currentfunc;
        CONTROL_MapDigitalAxis(mouse_digital_item->tics/2, currentfunc, mouse_digital_item->tics%2, controldevice_mouse);
        MNU_SetAdvancedMouseFunctions(mouse_digital_item);
        cust_callback = NULL;
        break;
    default: break;
    }

    return TRUE;
}

static SWBOOL MNU_SetAdvancedMouseFunctions(MenuItem_p item)
{
    int axis;
    char *p;

    axis = item->tics;
    ASSERT(axis >= 0 && axis < 4);

    if (MouseDigitalAxes[axis/2][axis%2] < 0)
    {
        strcpy(AdvancedMouseAxisFunctions[axis], "  -");
    }
    else
    {
        strcpy(AdvancedMouseAxisFunctions[axis], CONFIG_FunctionNumToName(MouseDigitalAxes[axis/2][axis%2]));
        for (p = AdvancedMouseAxisFunctions[axis]; *p; p++)
        {
            if (*p == '_')
                *p = ' ';
        }
    }
    return TRUE;
}


static MenuItem_p joystick_button_item = NULL;

static SWBOOL MNU_JoystickButtonsInitialise(MenuItem_p mitem)
{
    MenuItem_p item;
    MenuItem templayer = { DefLayer(0, JoystickButtonNames[0], &joybuttonsgroup), OPT_XS, OPT_LINE(0), 1, m_defshade, 0, NULL, NULL, MNU_JoystickButtonPostProcess };
    MenuItem tempinert = { DefInert(0, JoystickButtonFunctions[0]), OPT_XSIDE, OPT_LINE(0), 1, m_defshade, 0, NULL, MNU_SetJoystickButtonFunctions, NULL };
    MenuItem temppagename = { DefInert(0, JoystickButtonPageName[0]), OPT_XS, OPT_LINE(JOYSTICKITEMSPERPAGE+1), 1, m_defshade, 0, NULL, NULL, NULL };
    MenuItem tempnextpage = { DefOption(0, "Next..."), OPT_XSIDE, OPT_LINE(JOYSTICKITEMSPERPAGE+1), 1, m_defshade, 0, MNU_JoystickButtonNextPage, NULL, NULL };
    MenuItem tempnone = { DefNone };
    const char *hatdirs[] = { " Up", " Right", " Down", " Left" };
    int button, page, pageitem;

    if (joybuttonssetupgroup.items != NULL)
    {
        return TRUE;
    }

    page = 0;
    pageitem = 0;
    joybuttonssetupgroup.items = joybuttons_i[0];
    item = &joybuttons_i[0][0];

    for (button = 0; button < joynumbuttons * 2 + (joynumhats > 0) * 4; )
    {
        if (button < joynumbuttons * 2)
        {
            int dbutton = button / 2;

            strcpy(JoystickButtonNames[dbutton], getjoyname(1, dbutton));

            templayer.text = JoystickButtonNames[dbutton];
            templayer.y = OPT_LINE(pageitem);
            templayer.tics = dbutton;
            memcpy(item, &templayer, sizeof(MenuItem));
            item++;

            tempinert.text = JoystickButtonFunctions[dbutton];
            tempinert.y = OPT_LINE(pageitem);
            tempinert.tics = dbutton;
            memcpy(item, &tempinert, sizeof(MenuItem));
            item++;

            pageitem++;

            strcpy(JoystickButtonNames[dbutton + MAXJOYBUTTONS], "Double ");
            strcat(JoystickButtonNames[dbutton + MAXJOYBUTTONS], getjoyname(1, dbutton));

            templayer.text = JoystickButtonNames[dbutton + MAXJOYBUTTONS];
            templayer.y = OPT_LINE(pageitem);
            templayer.tics = 128 | dbutton;
            memcpy(item, &templayer, sizeof(MenuItem));
            item++;

            tempinert.text = JoystickButtonFunctions[dbutton + MAXJOYBUTTONS];
            tempinert.y = OPT_LINE(pageitem);
            tempinert.tics = 128 | dbutton;
            memcpy(item, &tempinert, sizeof(MenuItem));
            item++;

            pageitem++;

            button += 2;
        }
        else
        {
            int dir = button - joynumbuttons * 2;
            int dbutton = joynumbuttons + dir;

            strcpy(JoystickButtonNames[dbutton], getjoyname(2, 0));
            strcat(JoystickButtonNames[dbutton], hatdirs[dir]);

            templayer.text = JoystickButtonNames[dbutton];
            templayer.y = OPT_LINE(pageitem);
            templayer.tics = dbutton;
            memcpy(item, &templayer, sizeof(MenuItem));
            item++;

            tempinert.text = JoystickButtonFunctions[dbutton];
            tempinert.y = OPT_LINE(pageitem);
            tempinert.tics = dbutton;
            memcpy(item, &tempinert, sizeof(MenuItem));
            item++;

            pageitem++;
            button++;
        }

        if (pageitem == JOYSTICKITEMSPERPAGE || button == joynumbuttons * 2 + (joynumhats > 0) * 4)
        {
            // next page
            sprintf(JoystickButtonPageName[page], "Page %d / %d", page+1,
                    ((joynumbuttons * 2 + (joynumhats > 0) * 4) / JOYSTICKITEMSPERPAGE) + 1);

            temppagename.text = JoystickButtonPageName[page];
            memcpy(item, &temppagename, sizeof(MenuItem));
            item++;

            memcpy(item, &tempnextpage, sizeof(MenuItem));
            item++;

            memcpy(item, &tempnone, sizeof(MenuItem));

            page++;
            pageitem = 0;
            item = &joybuttons_i[page][0];
        }
    }

    return TRUE;
}

static SWBOOL MNU_JoystickButtonPostProcess(MenuItem_p item)
{
    joystick_button_item = item;
    return TRUE;
}

static SWBOOL MNU_JoystickButtonSetupCustom(UserCall call, MenuItem *item)
{
    static int currentfunc = 0;

    if (call == uc_touchup)
        return TRUE;

    if (cust_callback == NULL)
    {
        if (call != uc_setup)
            return FALSE;

        if (joystick_button_item->tics & 128)
        {
            currentfunc = JoystickButtonsClicked[joystick_button_item->tics & 127];
        }
        else
        {
            currentfunc = JoystickButtons[joystick_button_item->tics & 127];
        }
        currentfunc++;

        cust_callback = MNU_JoystickButtonSetupCustom;
        cust_callback_call = call;
        cust_callback_item = item;
    }

    {
        short w, h = 0;
        const char *s = "Joystick Setup";

        rotatesprite(10 << 16, (5-3) << 16, MZ, 0, 2427,
                     m_defshade, 0, MenuDrawFlags|ROTATE_SPRITE_CORNER, 0, 0, xdim - 1, ydim - 1);
        MNU_MeasureStringLarge(s, &w, &h);
        MNU_DrawStringLarge(TEXT_XCENTER(w), 5, s);
    }

    int selection = MNU_SelectButtonFunction(joystick_button_item->text, &currentfunc);
    switch (selection)
    {
    case -1:    //cancel
        cust_callback = NULL;
        break;
    case 1:     //acknowledge
        currentfunc--;
        if (joystick_button_item->tics & 128)
        {
            JoystickButtonsClicked[joystick_button_item->tics & 127] = currentfunc;
            CONTROL_MapButton(currentfunc, joystick_button_item->tics & 127, 1, controldevice_joystick);
        }
        else
        {
            JoystickButtons[joystick_button_item->tics & 127] = currentfunc;
            CONTROL_MapButton(currentfunc, joystick_button_item->tics & 127, 0, controldevice_joystick);
        }
        MNU_SetJoystickButtonFunctions(joystick_button_item);
        cust_callback = NULL;
        break;
    default: break;
    }

    return TRUE;
}

static SWBOOL MNU_JoystickButtonNextPage(void)
{
    JoystickButtonPage = (JoystickButtonPage + 1) % (((joynumbuttons * 2 + (joynumhats > 0) * 4) / JOYSTICKITEMSPERPAGE) + 1);
    joybuttonssetupgroup.items = &joybuttons_i[JoystickButtonPage][0];
    joybuttonssetupgroup.cursor = 0;
    MNU_ItemPreProcess(&joybuttonssetupgroup);
    return TRUE;
}

static SWBOOL MNU_SetJoystickButtonFunctions(MenuItem_p item)
{
    int button, clicked, function;
    char *p;

    clicked = (item->tics & 128) > 0;
    button = item->tics & 127;
    ASSERT(button >= 0 && button < MAXJOYBUTTONS);

    if (clicked)
    {
        function = JoystickButtonsClicked[button];
    }
    else
    {
        function = JoystickButtons[button];
    }
    if (function < 0)
    {
        strcpy(JoystickButtonFunctions[button + clicked*MAXJOYBUTTONS], "  -");
    }
    else
    {
        strcpy(JoystickButtonFunctions[button + clicked*MAXJOYBUTTONS], CONFIG_FunctionNumToName(function));
        for (p = JoystickButtonFunctions[button + clicked*MAXJOYBUTTONS]; *p; p++)
        {
            if (*p == '_')
                *p = ' ';
        }
    }
    return TRUE;
}


static MenuItem_p joystick_axis_item = NULL;

static SWBOOL MNU_JoystickAxesInitialise(MenuItem_p mitem)
{
    if (!CONTROL_JoyPresent)
    {
        return TRUE;
    }
    if (JoystickAxisPage < 0 || JoystickAxisPage >= joynumaxes)
    {
        JoystickAxisPage = 0;
    }

    strcpy(JoystickAxisName, getjoyname(0, JoystickAxisPage));
    sprintf(JoystickAxisPageName, "Page %d / %d", JoystickAxisPage+1, joynumaxes);
    slidersettings[sldr_joyaxisanalog] = MNU_ControlAxisOffset(JoystickAnalogAxes[JoystickAxisPage]);
    slidersettings[sldr_joyaxisscale] = JoystickAnalogScale[JoystickAxisPage] >> 13;
    slidersettings[sldr_joyaxisdead] = JoystickAnalogDead[JoystickAxisPage] >> 10;
    slidersettings[sldr_joyaxissatur] = JoystickAnalogSaturate[JoystickAxisPage] >> 10;

    return TRUE;
}

static SWBOOL MNU_JoystickAxisPostProcess(MenuItem_p item)
{
    joystick_axis_item = item;
    return TRUE;
}

static SWBOOL MNU_JoystickAxisSetupCustom(UserCall call, MenuItem *item)
{
    static int currentfunc = 0;

    if (call == uc_touchup)
        return TRUE;

    if (cust_callback == NULL)
    {
        if (call != uc_setup)
            return FALSE;

        currentfunc = JoystickDigitalAxes[JoystickAxisPage][joystick_axis_item->tics];
        currentfunc++;

        cust_callback = MNU_JoystickAxisSetupCustom;
        cust_callback_call = call;
        cust_callback_item = item;
    }

    {
        short w, h = 0;
        const char *s = "Joystick Axes";

        rotatesprite(10 << 16, (5-3) << 16, MZ, 0, 2427,
                     m_defshade, 0, MenuDrawFlags|ROTATE_SPRITE_CORNER, 0, 0, xdim - 1, ydim - 1);
        MNU_MeasureStringLarge(s, &w, &h);
        MNU_DrawStringLarge(TEXT_XCENTER(w), 5, s);
    }

    int selection = MNU_SelectButtonFunction(joystick_axis_item->text, &currentfunc);
    switch (selection)
    {
    case -1:    //cancel
        cust_callback = NULL;
        break;
    case 1:     //acknowledge
        currentfunc--;
        JoystickDigitalAxes[JoystickAxisPage][joystick_axis_item->tics] = currentfunc;
        CONTROL_MapDigitalAxis(JoystickAxisPage, currentfunc, joystick_axis_item->tics, controldevice_joystick);
        MNU_SetJoystickAxisFunctions(joystick_axis_item);
        cust_callback = NULL;
        break;
    default: break;
    }

    return TRUE;
}

static SWBOOL MNU_JoystickAxisNextPage(void)
{
    JoystickAxisPage = (JoystickAxisPage + 1) % MNU_ControlAxisOffset(analog_maxtype);
    joyaxessetupgroup.cursor = 1;
    MNU_ItemPreProcess(&joyaxessetupgroup);
    MNU_JoystickAxesInitialise(NULL);
    return TRUE;
}

static SWBOOL MNU_SetJoystickAxisFunctions(MenuItem_p item)
{
    int function;
    char *p;

    function = JoystickDigitalAxes[JoystickAxisPage][item->tics];
    if (function < 0)
    {
        strcpy(JoystickAxisFunctions[item->tics], "  -");
    }
    else
    {
        strcpy(JoystickAxisFunctions[item->tics], CONFIG_FunctionNumToName(function));
        for (p = JoystickAxisFunctions[item->tics]; *p; p++)
        {
            if (*p == '_')
                *p = ' ';
        }
    }
    return TRUE;
}


SWBOOL
MNU_OrderCustom(UserCall call, MenuItem *item)
{
    static signed char on_screen = 0,last_screen = 0;
    UserInput order_input;
    static int limitmove=0;
    UserInput tst_input;
    SWBOOL select_held = FALSE;
    int zero = 0;
    static SWBOOL DidOrderSound = FALSE;
    short choose_snd;
    static int wanghandle;

    static short RegOrderScreen[] =
    {
        5262,
        5261,
        4979,
        5111,
        5118,
        5113,
        //5111,
        //5118,
        //4979,
        //5261,
        //5262

        5114    // JBF: for my credits
    };
    static short SWOrderScreen[] =
    {
        5262,
        5110,
        5112,
        5113,
        5111,
        5118,
        4979,

        5114    // JBF: for my credits
    };
    short *OrderScreen, OrderScreenSiz;

    if (SW_SHAREWARE)
    {
        OrderScreen = SWOrderScreen;
        OrderScreenSiz = SIZ(SWOrderScreen);
    }
    else
    {
        OrderScreen = RegOrderScreen;
        OrderScreenSiz = SIZ(RegOrderScreen);
    }

    // Ignore the special touchup calls
    if (call == uc_touchup)
        return TRUE;

    if (cust_callback == NULL)
    {
        if (call != uc_setup)
            return FALSE;
    }

    if (SW_SHAREWARE && on_screen == 0 && !DidOrderSound)
    {
        DidOrderSound = TRUE;
        choose_snd = STD_RANDOM_RANGE(1000);
        if (choose_snd > 500 && !FX_SoundActive(wanghandle))
            wanghandle = PlaySound(DIGI_WANGORDER1, &zero, &zero, &zero, v3df_dontpan);
        else if (!FX_SoundActive(wanghandle))
            wanghandle = PlaySound(DIGI_WANGORDER2, &zero, &zero, &zero, v3df_dontpan);
    }

    order_input.button0 = order_input.button1 = FALSE;
    order_input.dir = dir_None;

    // Zero out the input structure
    tst_input.button0 = tst_input.button1 = FALSE;
    tst_input.dir = dir_None;

    if (!select_held)
    {
        CONTROL_GetUserInput(&tst_input);
        //order_input_buffered.dir = tst_input.dir;
        // Support a few other keys too
        if (KEY_PRESSED(KEYSC_SPACE)||KEY_PRESSED(KEYSC_ENTER))
        {
            KEY_PRESSED(KEYSC_SPACE) = FALSE;
            KEY_PRESSED(KEYSC_ENTER) = FALSE;
            tst_input.dir = dir_South;
        }
    }

    if (order_input_buffered.button0 || order_input_buffered.button1 || order_input_buffered.dir != dir_None)
    {
        if (tst_input.button0 == order_input_buffered.button0 &&
            tst_input.button1 == order_input_buffered.button1 &&
            tst_input.dir == order_input_buffered.dir)
        {
            select_held = TRUE;
        }
        else
        {
            if (labs(totalclock - limitmove) > 7)
            {
                order_input.button0 = order_input_buffered.button0;
                order_input.button1 = order_input_buffered.button1;
                order_input.dir = order_input_buffered.dir;

                order_input_buffered.button0 = tst_input.button0;
                order_input_buffered.button1 = tst_input.button1;
                order_input_buffered.dir = tst_input.dir;

                limitmove = totalclock;
            }
        }
    }
    else
    {
        select_held = FALSE;
        order_input_buffered.button0 = tst_input.button0;
        order_input_buffered.button1 = tst_input.button1;
        order_input_buffered.dir = tst_input.dir;
    }

    if (!KEY_PRESSED(KEYSC_ESC) && !order_input_buffered.button1)
    {
        cust_callback = MNU_OrderCustom;
        cust_callback_call = call;
        cust_callback_item = item;
    }
    else
    {
        KEY_PRESSED(KEYSC_ESC) = FALSE;
        cust_callback = NULL;
        DidOrderSound = FALSE;
        on_screen = 0;
        ExitMenus();
    }

    if (order_input.dir == dir_North)
    {
        on_screen--;
    }
    else if (order_input.dir == dir_South)
    {
        on_screen++;
    }
    else if (order_input.dir == dir_West)
    {
        on_screen--;
    }
    else if (order_input.dir == dir_East)
    {
        on_screen++;
    }

// CTW MODIFICATION
// I reversed the logic in here to allow the user to loop around.
// Yeah... I changed default behavior just because I wanted to.
// AND YOU CAN'T STOP ME SUCKER!!!
    if (on_screen < 0)
        on_screen = OrderScreenSiz-1;

    if (on_screen > OrderScreenSiz-1)
        on_screen = 0;
// CTW MODIFICATION END

    rotatesprite(0,0,RS_SCALE,0,OrderScreen[on_screen],0,0,
                 (ROTATE_SPRITE_CORNER|ROTATE_SPRITE_SCREEN_CLIP|ROTATE_SPRITE_NON_MASK|ROTATE_SPRITE_IGNORE_START_MOST),
                 0, 0, xdim-1, ydim-1);

    if (on_screen == OrderScreenSiz-1)
    {
        // Jonathon's credits page hack :-)

        static char *jtitle = "^Port Credits";
        static char *jtext[] =
        {
            "*GAME AND ENGINE PORT",
            " Jonathon \"JonoF\" Fowler",
            "-",
            "*\"POLYMOST\" 3D RENDERER",
            "*NETWORKING, OTHER CODE",
            " Ken \"Awesoken\" Silverman",
            "-",
            " Visit http://www.jonof.id.au/jfsw for the",
            " source code, latest news, and updates of this port."
        };
        static char *scroller[] =
        {
            "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 (GPL.TXT) for",
            "more details.",
            "",
            "",
            "",
            "",
            "Thanks to these people for their input and contributions:",
            "",
            "Richard \"TerminX\" Gobeille,",
            "Par \"Parkar\" Karlsson", // "Pär \"Parkar\" Karlsson",
            "Ben \"ProAsm\" Smit",
            "",
            "and all those who submitted bug reports and ",
            "supported the project financially!",
            "",
            "",
            "--x--",
            "",
            "",
            "",
            ""
        };
        const int numscrollerlines = SIZ(scroller);
        short dimx, dimy;
        int ycur = 54;
        unsigned ji;

        dimy = 0; MNU_MeasureString(jtitle, &dimx, &dimy);
        MNU_DrawString(160-(dimx>>1), ycur, jtitle, 0, 0);
        ycur += dimy + 8;

        for (ji = 0; ji < SIZ(jtext); ji++)
        {
            switch (jtext[ji][0])
            {
            case '-':
                ycur += 6;
                break;
            case '*':
                dimx = dimy = 0;
                MNU_MeasureString(&jtext[ji][1], &dimx, &dimy);
                MNU_DrawString(160-(dimx>>1), ycur, &jtext[ji][1], 0, 16);
                ycur += dimy+1;
                break;
            default:
                if (ji>0 && jtext[ji-1][0] == '*') ycur += 3;
                dimx = dimy = 0;
                MNU_MeasureSmallString(&jtext[ji][1], &dimx, &dimy);
                MNU_DrawSmallString(160-(dimx>>1), ycur, &jtext[ji][1], 0, 0);
                ycur += dimy+1;
                break;
            }
        }

        int m,i;
        for (m=0, i=(totalclock/104)%numscrollerlines; m<4; m++,i++)
        {
            if (i == numscrollerlines)
                i=0;
            dimx = dimy = 0;
            MNU_MeasureSmallString(scroller[i], &dimx, &dimy);
            MNU_DrawSmallString(160-(dimx>>1), 154+(m*7), scroller[i], 0, 8);
        }
    }

    //KB_ClearKeysDown();

    return TRUE;
}

SWBOOL MNU_LoadModernDefaults(void)
{
    SetDefaultKeyDefinitions(1);
    SetMouseDefaults(1);
    return TRUE;
}

SWBOOL MNU_LoadClassicDefaults(void)
{
    SetDefaultKeyDefinitions(0);
    SetMouseDefaults(0);
    return TRUE;
}


short EpisodeMenuSelection;

void
ExitMenus(void)
{
    ControlPanelType = ct_mainmenu;
    UsingMenus = FALSE;

    if (LoadGameOutsideMoveLoop)
        return;

    ResumeGame();
    SetRedrawScreen(&Player[myconnectindex]);
}

SWBOOL
MNU_StartGame(void)
{
    PLAYERp pp = Player + screenpeek;
    int handle = 0;
    int zero = 0;

    // always assumed that a demo is playing

    ready2send = 0;
    Skill = currentmenu->cursor;

    if (EpisodeMenuSelection >= 1)
        Level = 5;
    else
        Level = 1;

    ExitMenus();
    DemoPlaying = FALSE;
    ExitLevel = TRUE;
    NewGame = TRUE;
    DemoMode = FALSE;
    CameraTestMode = FALSE;

    //InitNewGame();

    if (Skill == 0)
        handle = PlaySound(DIGI_TAUNTAI3,&zero,&zero,&zero,v3df_none);
    else if (Skill == 1)
        handle = PlaySound(DIGI_NOFEAR,&zero,&zero,&zero,v3df_none);
    else if (Skill == 2)
        handle = PlaySound(DIGI_WHOWANTSWANG,&zero,&zero,&zero,v3df_none);
    else if (Skill == 3)
        handle = PlaySound(DIGI_NOPAIN,&zero,&zero,&zero,v3df_none);

    if (handle > FX_Ok)
        while (FX_SoundActive(handle))
            handleevents();

    return TRUE;
}

void ResetMenuInput(void)
{
    cust_callback = NULL;
    InputMode = FALSE;
}

SWBOOL
MNU_StartNetGame(void)
{
    extern SWBOOL ExitLevel, ShortGameMode, DemoInitOnce, FirstTimeIntoGame;
    extern short Level, Skill;
    // CTW REMOVED
    //extern int gTenActivated;
    // CTW REMOVED END
    int pnum;

    // always assumed that a demo is playing

    ready2send = 0;
    // Skill can go negative here
    Skill = gs.NetMonsters-1;
    Level = gs.NetLevel + 1;
    if (!AutoNet)
        ExitMenus();
    DemoPlaying = FALSE;
    ExitLevel = TRUE;
    NewGame = TRUE;
    // restart demo for multi-play mode
    DemoInitOnce = FALSE;
    ResetMenuInput();

    // TENSW: return if a joiner
    if (/* CTW REMOVED gTenActivated && */ !AutoNet && FirstTimeIntoGame)
        return TRUE;

    // need to set gNet vars for self
    // everone else gets a packet to set them
    gNet.AutoAim            = gs.AutoAim;
    gNet.SpawnMarkers       = gs.NetSpawnMarkers;
    gNet.HurtTeammate       = gs.NetHurtTeammate;
    gNet.Nuke               = gs.NetNuke;
    gNet.KillLimit          = gs.NetKillLimit*10;
    gNet.TimeLimit          = TimeLimitTable[gs.NetTimeLimit]*60*120;

    if (ShortGameMode)
    {
        gNet.KillLimit /= 10;
        gNet.TimeLimit /= 2;
    }

    gNet.TimeLimitClock     = gNet.TimeLimit;
    gNet.TeamPlay           = gs.NetTeamPlay;
    gNet.MultiGameType      = gs.NetGameType+1;

    if (gNet.MultiGameType == MULTI_GAME_COMMBAT_NO_RESPAWN)
    {
        gNet.MultiGameType = MULTI_GAME_COMMBAT;
        gNet.NoRespawn = TRUE;
    }
    else
    {
        gNet.NoRespawn = FALSE;
    }

    if (CommEnabled)
    {
        PACKET_NEW_GAME p;

        p.PacketType = PACKET_TYPE_NEW_GAME;
        p.Level = Level;
        p.Skill = Skill;
        p.GameType = gs.NetGameType;
        p.AutoAim = gs.AutoAim;
        p.HurtTeammate = gs.NetHurtTeammate;
        p.TeamPlay = gs.NetTeamPlay;
        p.SpawnMarkers = gs.NetSpawnMarkers;
        p.KillLimit = gs.NetKillLimit;
        p.TimeLimit = gs.NetTimeLimit;
        p.Nuke = gs.NetNuke;

        netbroadcastpacket((uint8_t*)(&p), sizeof(p));            // TENSW
    }


    return TRUE;
}


SWBOOL
MNU_EpisodeCustom(void)
{
    EpisodeMenuSelection = currentmenu->cursor;

    return TRUE;
}

SWBOOL
MNU_QuitCustom(UserCall call, MenuItem_p item)
{
    int select;
    int ret;
    extern SWBOOL DrawScreen;

    // Ignore the special touchup calls
    if (call == uc_touchup)
        return TRUE;

    if (cust_callback == NULL)
    {
        if (call != uc_setup)
            return FALSE;

        memset(dialog, 0, sizeof(dialog));

        dialog[0] = S_QUITYN;
    }

    ret = MNU_Dialog();

    if (DrawScreen)
        return TRUE;

    if (!ret)
    {
        if (!mnu_input.button1 && !KB_KeyPressed(sc_N))
        {
            cust_callback = MNU_QuitCustom;
            cust_callback_call = call;
            cust_callback_item = item;
        }
        else
        {
            cust_callback = NULL;
            ExitMenus();
        }
    }
    else
    {
        cust_callback = NULL;
        ExitMenus();
    }

    if (KB_KeyPressed(sc_Y) || KB_KeyPressed(sc_Enter) || mnu_input.button0)
    {
        if (CommPlayers >= 2)
            MultiPlayQuitFlag = TRUE;
        else
            QuitFlag = TRUE;

        ExitMenus();
    }

    KB_ClearKeysDown();

    return TRUE;
}

SWBOOL
MNU_QuickLoadCustom(UserCall call, MenuItem_p item)
{
    int select;
    extern SWBOOL ReloadPrompt;
    int bak;
    PLAYERp pp = Player + myconnectindex;
    extern short GlobInfoStringTime;
    extern SWBOOL DrawScreen;
    int ret;

    if (cust_callback == NULL)
    {
        if (call != uc_setup)
            return FALSE;

        memset(dialog, 0, sizeof(dialog));

        dialog[0] = "Load saved game";
        sprintf(QuickLoadDescrDialog,"\"%s\" (Y/N)?",SaveGameDescr[QuickLoadNum]);
        dialog[1] = QuickLoadDescrDialog;
    }

    // Ignore the special touchup calls
    if (call == uc_touchup)
        return TRUE;

    ret = MNU_Dialog();

    if (DrawScreen)
    {
        return TRUE;
    }

    if (ret == FALSE)
    {
        if (KB_KeyPressed(sc_N) || KB_KeyPressed(sc_Space) || KB_KeyPressed(sc_Enter))
        {
            cust_callback = NULL;
            if (ReloadPrompt)
            {
                ReloadPrompt = FALSE;
                bak = GlobInfoStringTime;
                GlobInfoStringTime = 999;
                PutStringInfo(pp, "Press SPACE to restart");
                GlobInfoStringTime = bak;
            }

            KB_ClearKeysDown();
            ExitMenus();
        }
        else
        {
            cust_callback = MNU_QuickLoadCustom;
            cust_callback_call = call;
            cust_callback_item = item;
        }
    }
    else
    {
        // Y pressed
        cust_callback = NULL;

        KB_ClearKeysDown();
        LoadSaveMsg("Loading...");

        PauseAction();

        ReloadPrompt = FALSE;
        if (LoadGame(QuickLoadNum) == -1)
        {
            ResumeAction();
            return FALSE;
        }

        ready2send = 1;
        LastSaveNum = -1;

        // do a load game here
        KB_ClearKeysDown();
        ExitMenus();
    }

    KB_ClearKeysDown();

    return TRUE;
}

// MENU FUNCTIONS /////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////
// Set some global menu related defaults
////////////////////////////////////////////////
void
MNU_InitMenus(void)
{
    pClearTextLine(Player + myconnectindex, TEXT_INFO_LINE(0));

    slidersettings[sldr_mouse] = gs.MouseSpeed/(MOUSE_SENS_MAX_VALUE/SLDR_MOUSESENSEMAX);

    slidersettings[sldr_sndfxvolume] = gs.SoundVolume / (FX_VOL_MAX_VALUE/SLDR_SNDFXVOLMAX);
    slidersettings[sldr_musicvolume] = gs.MusicVolume / (MUSIC_VOL_MAX_VALUE/SLDR_MUSICVOLMAX);
    slidersettings[sldr_scrsize] = gs.BorderNum;
    slidersettings[sldr_brightness] = gs.Brightness;
    slidersettings[sldr_bordertile] = gs.BorderTile;

    {
        int i,newx=xdim,newy=ydim;

        buttonsettings[btn_videofs] = fullscreen;

        UpdateValidModes(bpp,fullscreen);
        for (i=0; i<numvalidbpps; i++)
            if (validbpps[i] == bpp)
                slidersettings[sldr_videobpp] = i;

        i = checkvideomode(&newx, &newy, bpp, fullscreen, 1);
        if (i != 0x7fffffff && i >= 0)
            for (i=0; i<numvalidresolutions; i++)
                if (validresolutions[i].xdim == newx && validresolutions[i].ydim == newy)
                    slidersettings[sldr_videores] = i;
    }

    buttonsettings[btn_auto_run] = gs.AutoRun;
    buttonsettings[btn_auto_aim] = gs.AutoAim;
    buttonsettings[btn_messages] = gs.Messages;
    buttonsettings[btn_crosshair] = gs.Crosshair;
    //    buttonsettings[btn_bobbing] = gs.Bobbing;
    buttonsettings[btn_shadows] = gs.Shadows;

    buttonsettings[btn_mouse_aim] = gs.MouseAimingType;
    buttonsettings[btn_mouse_invert] = gs.MouseInvert;
    buttonsettings[btn_sound] = gs.FxOn;
    buttonsettings[btn_music] = gs.MusicOn;
    buttonsettings[btn_talking] = gs.Talking;

    buttonsettings[btn_voxels] = gs.Voxels;
    buttonsettings[btn_ambience] = gs.Ambient;
    buttonsettings[btn_playcd] = gs.PlayCD;
    buttonsettings[btn_flipstereo] = gs.FlipStereo;
    buttonsettings[btn_stats] = gs.Stats;

    slidersettings[sldr_gametype] = gs.NetGameType;
    slidersettings[sldr_netlevel] = gs.NetLevel;
    slidersettings[sldr_monsters] = gs.NetMonsters;
    slidersettings[sldr_killlimit] = gs.NetKillLimit;
    slidersettings[sldr_timelimit] = gs.NetTimeLimit;
    slidersettings[sldr_playercolor] = gs.NetColor;

    buttonsettings[btn_nuke] = gs.NetNuke;
    buttonsettings[btn_markers] = gs.NetSpawnMarkers;
    buttonsettings[btn_teamplay] = gs.NetTeamPlay;
    buttonsettings[btn_friendlyfire] = gs.NetHurtTeammate;
    buttonsettings[btn_parental] = gs.ParentalLock;

    slidersettings[sldr_mousescalex] = MouseAnalogScale[0]>>13;
    slidersettings[sldr_mousescaley] = MouseAnalogScale[1]>>13;

    slidersettings[sldr_joyaxisscale] = 0;
    slidersettings[sldr_joyaxisanalog] = 0;
    slidersettings[sldr_joyaxisdead] = 0;
    slidersettings[sldr_joyaxissatur] = 0;

    // Distinguish between Single or Multiplay for new game menu types
    if (numplayers > 1)
        main_i[0].child = &networkgroup;
    else
//        #ifdef SW_SHAREWARE
        main_i[0].child = &episodegroup;
//        #else
//        main_i[0].child = &skillgroup;
//        #endif
    main_i[4].text = (SW_SHAREWARE) ? MAIN_MENU_HOW_TO_ORDER : MAIN_MENU_COOL_STUFF;
    main_i[4].hotkey = (SW_SHAREWARE) ? KEYSC_H : KEYSC_C;
}

////////////////////////////////////////////////
// Measure the pixel width of a graphic string
////////////////////////////////////////////////
static char lg_xlat_num[] = {0,1,2,3,4,5,6,7,8,9};
#define FONT_LARGE_ALPHA 3706
#define FONT_LARGE_DIGIT 3732

void
MNU_MeasureStringLarge(const char *string, short *w, short *h)
{
    short ndx, width, height;
    char c;
    short pic;

    width = 0;
    height = *h;

    for (ndx = 0; (c = string[ndx]) != 0; ndx++)
    {
        if (isalpha(c))
        {
            c = toupper(c);
            pic = FONT_LARGE_ALPHA + (c - 'A');
        }
        else if (isdigit(c))
        {
            pic = FONT_LARGE_DIGIT + lg_xlat_num[(c - '0')];
        }
        else if (c == ' ')
        {
            width += 10;                 // Special case for space char
            continue;
        }
        else
        {
            continue;
        }

        width += tilesiz[pic].x+1;
        if (height < tilesiz[pic].y)
            height = tilesiz[pic].y;
    }

    *w = width;
    *h = height;
}

////////////////////////////////////////////////
// Draw a string using a graphic font
////////////////////////////////////////////////
void
MNU_DrawStringLarge(short x, short y, const char *string)
{
    int ndx, offset;
    char c;
    short pic;

    offset = x;

    for (ndx = 0; (c = string[ndx]) != 0; ndx++)
    {
        if (isalpha(c))
        {
            c = toupper(c);
            pic = FONT_LARGE_ALPHA + (c - 'A');
        }
        else if (isdigit(c))
        {
            pic = FONT_LARGE_DIGIT + lg_xlat_num[(c - '0')];
        }
        else if (c == ' ')
        {
            offset += 10;
            continue;
        }
        else
        {
            continue;
        }

        rotatesprite(offset << 16, y << 16, MZ, 0, pic, MenuTextShade, 0, MenuDrawFlags|ROTATE_SPRITE_CORNER, 0, 0, xdim - 1, ydim - 1);
        offset += tilesiz[pic].x + 1;
    }

}


////////////////////////////////////////////////
// Measure the pixel width of a graphic string
////////////////////////////////////////////////
void
MNU_MeasureString(const char *string, short *w, short *h)
{
    short ndx, width, height;
    char c;
    short ac;

    if (string[0] == '^')
    {
        MNU_MeasureStringLarge(&string[1], w, h);
        return;
    }

    width = 0;
    height = *h;

    for (ndx = 0; (c = string[ndx]) != 0; ndx++)
    {
        ac = c - '!' + STARTALPHANUM;
        if ((ac < STARTALPHANUM || ac > ENDALPHANUM)  && c != asc_Space)
            break;

        if (c > asc_Space && c < 127)
        {
            width += tilesiz[ac].x;
            if (height < tilesiz[ac].y)
                height = tilesiz[ac].y;
        }
        else if (c == asc_Space)
            width += 4;                 // Special case for space char
    }

    *w = width;
    *h = height;
}

////////////////////////////////////////////////
// Draw a string using a graphic font
//
// MenuTextShade and MenuDrawFlags
////////////////////////////////////////////////
void
MNU_DrawString(short x, short y, const char *string, short shade, short pal)
{
    int ndx, offset;
    char c;
    short ac;

    if (string[0] == '^')
    {
        MNU_DrawStringLarge(x,y, &string[1]);
        return;
    }

    offset = x;

    for (ndx = 0; (c = string[ndx]) != 0; ndx++)
    {
        ac = c - '!' + STARTALPHANUM;
        if ((ac < STARTALPHANUM || ac > ENDALPHANUM)  && c != asc_Space)
            break;

        if (c > asc_Space && c < 127)
        {
            rotatesprite(offset<<16,y<<16,MZ,0,ac, shade, pal, MenuDrawFlags, 0, 0, xdim - 1, ydim - 1);
            offset += tilesiz[ac].x;
        }
        else if (c == asc_Space)
            offset += 4;                // Special case for space char
    }

}
/*   Original code
void
MNU_DrawString(short x, short y, char *string)
{
    int ndx, offset;
    char c;

    if (string[0] == '^')
    {
        MNU_DrawStringLarge(x,y, &string[1]);
        return;
    }

    offset = x;

    for (ndx = 0; (c = string[ndx]) != 0; ndx++)
    {
        if (c > asc_Space && c < 127)
        {
            rotatesprite(offset << 16, y << 16, MZ, 0, xlatfont[c], MenuTextShade, 0, MenuDrawFlags, 0, 0, xdim - 1, ydim - 1);
            offset += tilesiz[xlatfont[c]].x;
        } else
        if (c == asc_Space)
            offset += 4;                // Special case for space char
    }

}
*/

////////////////////////////////////////////////
// Measure the pixel width of a small font string
////////////////////////////////////////////////
void
MNU_MeasureSmallString(const char *string, short *w, short *h)
{
    short ndx, width, height;
    char c;
    short ac;

    width = 0;
    height = *h;

    for (ndx = 0; (c = string[ndx]) != 0; ndx++)
    {
        ac = (c - '!') + 2930;
        if ((ac < 2930 || ac > 3023) && c != asc_Space)
            break;

        if (c > asc_Space && c < 127)
        {
            width += tilesiz[ac].x;
            if (height < tilesiz[ac].y)
                height = tilesiz[ac].y;
        }
        else if (c == asc_Space)
            width += 4;                 // Special case for space char
    }

    *w = width;
    *h = height;
}

////////////////////////////////////////////////
// Draw a string using a small graphic font
////////////////////////////////////////////////
void
MNU_DrawSmallString(short x, short y, const char *string, short shade, short pal)
{
    int ndx;
    char c;
    short ac,offset;


    offset = x;

    for (ndx = 0; (c = string[ndx]) != 0; ndx++)
    {
        ac = c - '!' + 2930;
        if ((ac < 2930 || ac > 3023)  && c != asc_Space)
            break;

        if (c > asc_Space && c < 127)
        {
            rotatesprite(offset<<16,y<<16,MZ,0,ac, shade, pal, MenuDrawFlags, 0, 0, xdim - 1, ydim - 1);

            offset += tilesiz[ac].x;

        }
        else if (c == asc_Space)
        {
            offset += 4;                // Special case for space char
        }
    }

}

////////////////////////////////////////////////
//  Get an input string from user using small font
////////////////////////////////////////////////

signed char
MNU_InputSmallString(char *name, short pix_width)
{
    char ch;
    short w, h;
    UserInput con_input;

#define ascii_backspace 8
#define ascii_esc 27
#define ascii_return 13

    if (!MoveSkip4 && !MessageInputMode)
    {
        con_input.dir = dir_None;
        CONTROL_GetUserInput(&con_input);

        if (con_input.dir == dir_North)
        {
            CON_CommandHistory(1);
        }
        else if (con_input.dir == dir_South)
        {
            CON_CommandHistory(-1);
        }
    }

    while (KB_KeyWaiting())
    {
        ch = KB_Getch();

        // skip any extended key
        if (ch == 0)
        {
            ch = KB_Getch();
            if (ch == 104) // extended enter
                ch = ascii_return;
            else
                continue;
        }

        if (ch == ascii_backspace)
        {
            name[strlen(name) - 1] = '\0';
            continue;
        }
        else if (ch == ascii_esc)
        {
            return -1;
        }
        else if (ch == ascii_return)
        {
            return FALSE;
        }
        else if (!isprint(ch))
            continue;

        MNU_MeasureSmallString(name, &w, &h);
        if (w < pix_width)
        {
            if (strlen(name) < 256) // Dont let it go too far!
                sprintf(name, "%s%c", name, ch);
        }
    }

    return TRUE;

}

////////////////////////////////////////////////
// Draw dialog text on screen
////////////////////////////////////////////////
static SWBOOL
MNU_Dialog(void)
{
    short ndx, linecnt, w[MAXDIALOG], h, x, y;

    linecnt = 0;
    h = 8;

    for (ndx = 0; ndx < MAXDIALOG && dialog[ndx]; ndx++)
    {
        MNU_MeasureString(dialog[ndx], &w[ndx], &h);
        ASSERT(w[ndx] < XDIM);
        linecnt++;
    }

    y = ((YDIM - ((h * linecnt) + (linecnt * 2))) / 2);

    for (ndx = 0; ndx < linecnt; ndx++)
    {
        x = ((XDIM - w[ndx]) / 2);
        MNU_DrawString(x, y, dialog[ndx],1,16);
        y += (h + 3);
    }

    mnu_input.button0 = mnu_input.button1 = FALSE;
    CONTROL_ClearUserInput(&mnu_input);
    CONTROL_GetUserInput(&mnu_input);

    if (KB_KeyPressed(sc_Y) || KB_KeyPressed(sc_Enter) || mnu_input.button0)
        return TRUE;
    else
        return FALSE;
}

////////////////////////////////////////////////
//  Get an input string from user
////////////////////////////////////////////////

signed char
MNU_InputString(char *name, short pix_width)
{
    char ch;
    short w, h;

#define ascii_backspace 8
#define ascii_esc 27
#define ascii_return 13

    while (KB_KeyWaiting())
    {
        ch = KB_Getch();

        ////DSPRINTF(ds, "%c %d", ch, ch);
        //MONO_PRINT(ds);

        // skip most extended keys
        if (ch == 0)
        {
            ch = KB_Getch();

            ////DSPRINTF(ds, "extended key %c %d", ch, ch);
            //MONO_PRINT(ds);

            if (ch == 104) // extended enter
                ch = ascii_return;
            else
                continue;
        }

        if (ch == ascii_backspace)
        {
            name[strlen(name) - 1] = '\0';
            continue;
        }
        else if (ch == ascii_esc)
        {
            return -1;
        }
        else if (ch == ascii_return)
        {
            return FALSE;
        }
        else if (!isprint(ch))
            continue;

        MNU_MeasureString(name, &w, &h);
        if (w < pix_width)
        {
            sprintf(name, "%s%c", name, ch);
        }
    }

    return TRUE;

}

#define SS_XSTART 146L
#define SS_YSTART SD_YSTART
#define SS_BORDER_SIZE 5L

void LoadSaveMsg(char *msg)
{
    short w,h;

    flushperms();
    DrawMenuLevelScreen();
    strcpy((char *)ds, (char *)msg);
    MNU_MeasureString(ds, &w, &h);
    MNU_DrawString(TEXT_XCENTER(w), 170, ds, 1, 16);
    nextpage();
}


////////////////////////////////////////////////
//  Load Game menu
//  This function gets called whenever you
//  press enter on one of the load game
//  spots.
//  I'm figuring it need to do the following:
//  . Load the game if there is one by calling: MNU_LoadGameCustom.
////////////////////////////////////////////////
SWBOOL
MNU_GetLoadCustom(void)
{
    short load_num;

    load_num = currentmenu->cursor;

    // no saved game exists - don't do anything
    if (SaveGameDescr[load_num][0] == '\0')
        return FALSE;

    if (InMenuLevel || DemoMode || DemoPlaying)
    {
        LoadSaveMsg("Loading...");

        if (LoadGame(load_num) == -1)
            return FALSE;

        QuickLoadNum = load_num;
        // the (Quick)Save menu should default to the last loaded game
        SaveGameGroup.cursor = load_num;

        ExitMenus();
        ExitLevel = TRUE;
        LoadGameOutsideMoveLoop = TRUE;
        if (DemoMode || DemoPlaying)
            LoadGameFromDemo = TRUE;

        return TRUE;
    }

    LoadSaveMsg("Loading...");

    PauseAction();

    if (LoadGame(load_num) == -1)
    {
        ResumeAction();
        return FALSE;
    }

    QuickLoadNum = load_num;
    // the (Quick)Save menu should default to the last loaded game
    SaveGameGroup.cursor = load_num;

    ready2send = 1;
    LastSaveNum = -1;
    ExitMenus();

    if (DemoMode)
    {
        ExitLevel = TRUE;
        DemoPlaying = FALSE;
    }

    return TRUE;
}

////////////////////////////////////////////////
//  Save Game menu
//  This function gets called whenever you
//  press enter on one of the save game
//  spots.
//  I'm figuring it need to do the following:
//  . Call MNU_GetInput to allow string input of description.
//  . Save the game if there is one by calling: MNU_SaveGameCustom.
////////////////////////////////////////////////
SWBOOL
MNU_GetSaveCustom(void)
{
    short save_num;
    extern SWBOOL InMenuLevel, LoadGameOutsideMoveLoop;

    save_num = currentmenu->cursor;

    if (InMenuLevel)
        return FALSE;

    if (MenuInputMode)
    {
        PauseAction();

        LoadSaveMsg("Saving...");

        if (SaveGame(save_num) != -1)
        {
            QuickLoadNum = save_num;

            LoadGameGroup.cursor = save_num;
            LastSaveNum = -1;
        }

        ResumeAction();
        ExitMenus();

        // toggle edit mode
        MenuInputMode = FALSE;
    }
    else
    {
        strcpy(BackupSaveGameDescr, SaveGameDescr[save_num]);

        // clear keyboard buffer
        while (KB_KeyWaiting())
        {
            if (KB_Getch() == 0)
                KB_Getch();
        }

        // toggle edit mode
        MenuInputMode = TRUE;
    }

    return TRUE;
}

////////////////////////////////////////////////
//  Load/Save Touchup function
//  This function gets called each frame by DrawMenus
////////////////////////////////////////////////

static SWBOOL
MNU_DrawLoadSave(short game_num)
{
    // screen border
    rotatesprite(SS_XSTART << 16, SS_YSTART << 16, MZ, 0, pic_loadsavescreen,
                 0, 0, MenuDrawFlags | ROTATE_SPRITE_CORNER, 0, 0, xdim - 1, ydim - 1);

    // description box
    rotatesprite((SD_XSTART) << 16, (SD_YSTART) << 16, MZ, 0, pic_savedescr,
                 0, 0, MenuDrawFlags | ROTATE_SPRITE_CORNER, 0, 0, xdim - 1, ydim - 1);

    // cursor for text boxes
    rotatesprite((SD_XSTART + 3) << 16, (SD_LINE(game_num) + 1) << 16, MZ, 0, pic_loadsavecursor,
                 0, 0, MenuDrawFlags | ROTATE_SPRITE_CORNER, 0, 0, xdim - 1, ydim - 1);

    return TRUE;
}

static char SaveGameInfo1[80];
static char SaveGameInfo2[80];

SWBOOL
MNU_LoadSaveMove(UserCall call, MenuItem_p item)
{
    short i;
    short game_num;
    short tile;
    static short SaveGameEpisode, SaveGameLevel, SaveGameSkill;
    SWBOOL GotInput = FALSE;

    if (!UsingMenus)
        return TRUE;

    game_num = currentmenu->cursor;

    // read all descr first time through - LastSaveNum starts at 99
    if (LastSaveNum == 99)
    {
        memset(SaveGameDescr, '\0', sizeof(SaveGameDescr));

        for (i = 0; i < 10; i++)
            LoadGameDescr(i, SaveGameDescr[i]);
    }

    // cursor has moved - read header
    if (game_num != LastSaveNum)
    {
        screen_tile = LoadGameFullHeader(game_num, SaveGameDescr[game_num],
                                         &SaveGameLevel, &SaveGameSkill);

        sprintf(SaveGameInfo1, "Level %d, Skill %d", SaveGameLevel, SaveGameSkill+1);
        SaveGameInfo2[0] = 0;
    }

    if (QuickSaveMode)
    {
        QuickSaveMode = FALSE;
        MenuInputMode = TRUE;
        strcpy(BackupSaveGameDescr, SaveGameDescr[game_num]);
        KB_ClearKeysDown();
        KB_FlushKeyboardQueue();
    }

    LastSaveNum = game_num;

    // input mode check
    if (MenuInputMode)
    {
        MenuItem *item = &currentmenu->items[currentmenu->cursor];

        if (SavePrompt)
        {
            if (KB_KeyPressed(sc_Y) || KB_KeyPressed(sc_Enter))
            {
                KB_ClearKeyDown(sc_Y);
                KB_ClearKeyDown(sc_Enter);
                SavePrompt = FALSE;
                // use input
                item->custom();
            }
            else if (KB_KeyPressed(sc_N))
            {
                KB_ClearKeyDown(sc_N);
                strcpy(SaveGameDescr[game_num], BackupSaveGameDescr);
                SavePrompt = FALSE;
                MenuInputMode = FALSE;
            }
        }
        else
            // get input
            switch (MNU_InputString(SaveGameDescr[game_num], 114))
            {
            case -1:                        // Cancel Input (pressed ESC) or Err
                strcpy(SaveGameDescr[game_num], BackupSaveGameDescr);
                MenuInputMode = FALSE;
                KB_ClearKeysDown();
                break;
            case FALSE:                     // Input finished (RETURN)
                // no input
                if (SaveGameDescr[game_num][0] == '\0')
                {
                    strcpy(SaveGameDescr[game_num], BackupSaveGameDescr);
                    MenuInputMode = FALSE;
                }
                else
                {
                    GotInput = TRUE;
                }
                KB_ClearKeyDown(sc_Enter);
                break;
            case TRUE:                      // Got input
                break;
            }

        if (GotInput)
        {
            if (BackupSaveGameDescr[0])
                SavePrompt = TRUE;

            if (!SavePrompt)
            {
                // use input
                item->custom();
            }
        }
    }

    return TRUE;
}

SWBOOL
MNU_LoadSaveDraw(UserCall call, MenuItem_p item)
{
    short i;
    short game_num;
    short tile;

    game_num = currentmenu->cursor;

    // misc drawing
    MNU_DrawLoadSave(game_num);

    // print game descriptions
    for (i = 0; i < 10; i++)
    {
        if (i == game_num && MenuInputMode && !SavePrompt)
        {
            static SWBOOL cur_show;
            char tmp[sizeof(SaveGameDescr[0])];

            //cur_show ^= 1;
            cur_show = (totalclock & 32);
            if (cur_show)
            {
                // add a cursor to the end
                sprintf(tmp, "%s_", SaveGameDescr[i]);
            }
            else
                strcpy(tmp, SaveGameDescr[i]);

            MNU_DrawString(SD_XSTART + 4, SD_YSTART + (i * SD_YOFF) + 2, tmp, 1, 16);
        }
        else if (SaveGameDescr[i][0] != '\0')
        {
            MNU_DrawString(SD_XSTART + 4, SD_YSTART + (i * SD_YOFF) + 2, SaveGameDescr[i], 1, 16);
        }
    }

    if (screen_tile != -1)
    {
        // draw 160x100 save screen
        rotatesprite((SS_XSTART + SS_BORDER_SIZE) << 16, (SS_YSTART + SS_BORDER_SIZE) << 16, (1 << 16), 0 + 512, screen_tile,
                     0, 0, MenuDrawFlags | ROTATE_SPRITE_CORNER | ROTATE_SPRITE_NON_MASK | ROTATE_SPRITE_YFLIP, 0, 0, xdim - 1, ydim - 1);

        // draw info string
        MNU_DrawString(SS_XSTART + 13, SS_YSTART + 100 + 10, SaveGameInfo1, 1, 16);
        MNU_DrawString(SS_XSTART + 13, SS_YSTART + 100 + 18, SaveGameInfo2, 1, 16);

        if (SavePrompt)
        {
            MNU_DrawString(SS_XSTART + SS_BORDER_SIZE + 5, SS_YSTART + SS_BORDER_SIZE + 47, "Overwrite previous", 1, 16);
            MNU_DrawString(SS_XSTART + SS_BORDER_SIZE + 5, SS_YSTART + SS_BORDER_SIZE + 47 + 12, "  saved game (Y/N)", 1, 16);
        }
    }
    else
    {
        // draw 160x100 black pic
        rotatesprite((SS_XSTART + SS_BORDER_SIZE) << 16, (SS_YSTART + SS_BORDER_SIZE) << 16, (1 << 16), 0, pic_loadsavescreenbak,
                     0, 0, MenuDrawFlags | ROTATE_SPRITE_CORNER | ROTATE_SPRITE_NON_MASK, 0, 0, xdim - 1, ydim - 1);

        MNU_DrawString(SS_XSTART + SS_BORDER_SIZE + 60, SS_YSTART + SS_BORDER_SIZE + 47, "Empty", 1, 16);
    }


    return TRUE;
}

SWBOOL
MNU_ShareWareCheck(MenuItem *item)
{
    if (SW_SHAREWARE)
    {
        SET(item->flags, mf_disabled);
    }

    return TRUE;
}

SWBOOL
MNU_CheckUserMap(MenuItem *item)
{
    if (UserMapName[0] == '\0')
        RESET(item->flags, mf_disabled);
    else
        SET(item->flags, mf_disabled);
    return TRUE;
}

SWBOOL
MNU_ShareWareMessage(MenuItem *item)
{
    char *extra_text;
    short w,h;

    if (SW_SHAREWARE)
    {
        extra_text = "Be sure to call 800-3DREALMS today";
        MNU_MeasureString(extra_text, &w, &h);
        MNU_DrawString(TEXT_XCENTER(w), 110, extra_text, 1, 16);
        extra_text = "and order the game.";
        MNU_MeasureString(extra_text, &w, &h);
        MNU_DrawString(TEXT_XCENTER(w), 120, extra_text, 1, 16);
        extra_text = "You are only playing the first ";
        MNU_MeasureString(extra_text, &w, &h);
        MNU_DrawString(TEXT_XCENTER(w), 130, extra_text, 1, 16);
        extra_text = "four levels, and are missing most";
        MNU_MeasureString(extra_text, &w, &h);
        MNU_DrawString(TEXT_XCENTER(w), 140, extra_text, 1, 16);
        extra_text = "of the game, weapons and monsters.";
        MNU_MeasureString(extra_text, &w, &h);
        MNU_DrawString(TEXT_XCENTER(w), 150, extra_text, 1, 16);
        extra_text = "See the ordering information.";
        MNU_MeasureString(extra_text, &w, &h);
        MNU_DrawString(TEXT_XCENTER(w), 160, extra_text, 1, 16);
        SET(item->flags, mf_disabled);
    }
    return TRUE;
}

SWBOOL
MNU_SaveGameCheck(MenuItem *item)
{
    extern SWBOOL InMenuLevel;
    extern SWBOOL DemoMode;

    if (0)      // JBF: Until we fix the symbol table dilemma, saving is off limits
    {
        SET(item->flags, mf_disabled);
        return TRUE;
    }

    if (CommEnabled || numplayers > 1 || DemoMode)
    {
        SET(item->flags, mf_disabled);
        return TRUE;
    }

    if (InMenuLevel)
        SET(item->flags, mf_disabled);
    else
    {
        if (TEST(Player[myconnectindex].Flags, PF_DEAD))
            SET(item->flags, mf_disabled);
        else
            RESET(item->flags, mf_disabled);
    }


    return TRUE;
}

SWBOOL
MNU_TenCheck(MenuItem *item)
{
    if (CommEnabled || numplayers > 1)
    {
        SET(item->flags, mf_disabled);
        return TRUE;
    }

    return TRUE;
}

SWBOOL
MNU_LoadGameCheck(MenuItem *item)
{

    if (0)      // JBF: Until we fix the symbol table dilemma, loading is off limits
    {
        SET(item->flags, mf_disabled);
        return TRUE;
    }

    if (CommEnabled || numplayers > 1)
    {
        SET(item->flags, mf_disabled);
        return TRUE;
    }

    return TRUE;
}

SWBOOL
MNU_StatCheck(MenuItem *item)
{
    if (CommEnabled || numplayers > 1)
    {
        SET(item->flags, mf_disabled);
        return TRUE;
    }

    return TRUE;
}

SWBOOL
MNU_HurtTeammateCheck(MenuItem *item)
{
    switch (gs.NetGameType+1)
    {
    // deathmatch and deathmatch no respawn
    case MULTI_GAME_COMMBAT:
    case MULTI_GAME_COMMBAT_NO_RESPAWN:
        if (gs.NetTeamPlay)
            RESET(item->flags, mf_disabled);
        else
            SET(item->flags, mf_disabled);
        break;
    // co-op
    case MULTI_GAME_COOPERATIVE:
        RESET(item->flags, mf_disabled);
        break;
    }

    return TRUE;
}

SWBOOL
MNU_TeamPlayCheck(MenuItem *item)
{
    switch (gs.NetGameType+1)
    {
    // co-op
    case MULTI_GAME_COOPERATIVE:
        SET(item->flags, mf_disabled);
        break;
    default:
        RESET(item->flags, mf_disabled);
        break;
    }

    return TRUE;
}

SWBOOL
MNU_CoopPlayCheck(MenuItem *item)
{
    switch (gs.NetGameType+1)
    {
    // co-op
    case MULTI_GAME_COOPERATIVE:
        SET(item->flags, mf_disabled);
        break;
    default:
        RESET(item->flags, mf_disabled);
        break;
    }

    return TRUE;
}

SWBOOL
MNU_TeamPlayChange(void)
{
    // if team play changes then do a pre process again
    MNU_ItemPreProcess(currentmenu);
    return TRUE;
}

SWBOOL
MNU_MouseCheck(MenuItem *item)
{
    if (!CONTROL_MousePresent)
    {
        SET(item->flags, mf_disabled);
    }
    else
    {
        RESET(item->flags, mf_disabled);
    }

    return TRUE;
}

SWBOOL
MNU_JoystickCheck(MenuItem *item)
{
    if (!CONTROL_JoyPresent)
    {
        SET(item->flags, mf_disabled);
    }
    else
    {
        RESET(item->flags, mf_disabled);
    }

    return TRUE;
}

// This is only called when Enter is pressed
static SWBOOL
MNU_TryMusicInit(void)
{
    if (PlaySong(0, RedBookSong[Level], TRUE, FALSE))
    {
        if (currentmenu->cursor == 0)
            MNU_MusicCheck(&currentmenu->items[currentmenu->cursor+1]);
    }

    return TRUE;
}

SWBOOL
MNU_MusicCheck(MenuItem *item)
{
    if (SW_SHAREWARE)
    {
        if (MusicDevice < 0 || !MusicInitialized)
        {
            SET(item->flags, mf_disabled);
        }
        else
        {
            RESET(item->flags, mf_disabled);
        }
    }
    else
    {
        // Redbook audio stuff
        //JBF
        //if (!cdvalid)
        //    {
        //    SET(item->flags, mf_disabled); // Just don't let CD Redbook ever be invalid!
        //    }
        //else
        {
            RESET(item->flags, mf_disabled);
        }
    }

    return TRUE;
}

SWBOOL
MNU_FxCheck(MenuItem *item)
{
    if (FXDevice < 0 || !FxInitialized)
    {
        SET(item->flags, mf_disabled);
    }
    else
    {
        RESET(item->flags, mf_disabled);
    }

    return TRUE;
}

SWBOOL
MNU_MusicFxCheck(MenuItem *item)
{
    if (FXDevice < 0 && MusicDevice < 0)
    {
        SET(item->flags, mf_disabled);
    }
    else
    {
        RESET(item->flags, mf_disabled);
    }

    return TRUE;
}

////////////////////////////////////////////////
// Do a toggle button
////////////////////////////////////////////////
void
MNU_DoButton(MenuItem_p item, SWBOOL draw)
{
    int x, y;
    SWBOOL state;
    int last_value;
    short shade = MENU_SHADE_DEFAULT;
    extern char LevelSong[];
    char *extra_text = NULL;
    PLAYERp pp = &Player[myconnectindex];
    int button_x,zero=0;
    int handle=0;
    extern SWBOOL MusicInitialized,FxInitialized;

    button_x = OPT_XSIDE;

    x = item->x;
    y = item->y;

    if (TEST(item->flags, mf_disabled))
    {
        shade = MENU_SHADE_INACTIVE;
    }

    if (!draw)
    {
        switch (item->button)
        {
        case btn_nuke:
            gs.NetNuke = state = buttonsettings[item->button];
            break;
        case btn_voxels:
            gs.Voxels = state = buttonsettings[item->button];
            break;
        case btn_stats:
            gs.Stats = state = buttonsettings[item->button];
            break;
        case btn_markers:
            gs.NetSpawnMarkers = state = buttonsettings[item->button];
            break;
        case btn_teamplay:
            gs.NetTeamPlay = state = buttonsettings[item->button];
            break;
        case btn_friendlyfire:
            gs.NetHurtTeammate = state = buttonsettings[item->button];
            break;
        case btn_crosshair:
            gs.Crosshair = state = buttonsettings[item->button];
            break;
        case btn_auto_aim:
            last_value = gs.AutoAim;
            gs.AutoAim = state = buttonsettings[item->button];
            if (gs.AutoAim != last_value)
                MenuButtonAutoAim = TRUE;
            break;
        case btn_messages:
            gs.Messages = state = buttonsettings[item->button];
            break;
        case btn_auto_run:
            last_value = gs.AutoRun;
            gs.AutoRun = state = buttonsettings[item->button];
            if (gs.AutoRun != last_value)
                MenuButtonAutoRun = TRUE;
            break;
        case btn_mouse_aim:
            last_value = gs.MouseAimingType;
            gs.MouseAimingType = state = buttonsettings[item->button];
            if (gs.MouseAimingType != last_value)
            {
                //RESET(pp->Flags, PF_MOUSE_AIMING_ON);
                //gs.MouseAimingOn = FALSE;
            }
            //extra_text = gs.MouseAimingType ? "Momentary" : "Toggle";
            break;
        case btn_mouse_invert:
            gs.MouseInvert = state = buttonsettings[item->button];
            break;
//        case btn_bobbing:
//            gs.Bobbing = state = buttonsettings[item->button];
//            break;
        case btn_sound:

            if (!FxInitialized)
                break;

            last_value = gs.FxOn;
            gs.FxOn = state = buttonsettings[item->button];
            if (gs.FxOn != last_value)
            {
                if (!gs.FxOn)
                    StopFX();
            }
            break;
        case btn_music:
            last_value = gs.MusicOn;
            gs.MusicOn = state = buttonsettings[item->button];
            if (gs.MusicOn != last_value)
            {
                SWBOOL bak;

                if (gs.MusicOn)
                {
                    bak = DemoMode;
                    PlaySong(LevelSong, RedBookSong[Level], TRUE, TRUE);
                    DemoMode = bak;
                }
                else
                {
                    bak = DemoMode;
                    StopSong();
                    DemoMode = bak;

                    if (SW_SHAREWARE)
                    {
                        handle = PlaySound(DIGI_NOLIKEMUSIC,&zero,&zero,&zero,v3df_none);

                        if (handle > FX_Ok)
                            while (FX_SoundActive(handle))
                                handleevents();
                    }
                }
            }
            break;
        case btn_talking:
            gs.Talking = state = buttonsettings[item->button];
            break;
        case btn_playcd:
            last_value = gs.PlayCD;
            gs.PlayCD = state = buttonsettings[item->button];
            break;
        case btn_ambience:
            last_value = gs.Ambient;
            gs.Ambient = state = buttonsettings[item->button];
            if (gs.Ambient != last_value)
            {
                if (!InMenuLevel)
                {
                    if (gs.Ambient)
                        StartAmbientSound();
                    else
                        StopAmbientSound();
                }
            }
            break;
        case btn_flipstereo:
            last_value = gs.FlipStereo;
            gs.FlipStereo = state = buttonsettings[item->button];
            if (gs.FlipStereo != last_value)
                FlipStereo();
            break;
        case btn_shadows:
            gs.Shadows = state = buttonsettings[item->button];
            break;

        case btn_parental:
            if (gs.Password[0] != '\0' && gs.ParentalLock == TRUE)
            {
                if (passwordvalid)
                {
                    state = buttonsettings[btn_parental] = gs.ParentalLock = FALSE;
                    if (!InMenuLevel)
                        JS_ToggleLockouts();
                }
                else
                {
                    state = buttonsettings[btn_parental] = gs.ParentalLock = TRUE;
                    MenuInputMode = TRUE;
                    memset(MessageInputString, '\0', sizeof(MessageInputString));
                    KB_ClearKeysDown();
                    KB_FlushKeyboardQueue();
                }
            }
            else
            {
                gs.ParentalLock = state = buttonsettings[item->button];
                if (!InMenuLevel)
                    JS_ToggleLockouts();
            }
            break;

        case btn_videofs:
        {
            int lastx, lasty, lastbpp, newoffset, i;

            state = buttonsettings[btn_videofs];

            lastx   = validresolutions[slidersettings[sldr_videores]].xdim;
            lasty   = validresolutions[slidersettings[sldr_videores]].ydim;
            lastbpp = validbpps[slidersettings[sldr_videobpp]];
            UpdateValidModes(lastbpp, buttonsettings[btn_videofs]);

            // check if the last bpp is still a valid choice
            for (i=0; i<numvalidbpps; i++)
                if (validbpps[i] == lastbpp) break;
            if (i == numvalidbpps)
            {
                // it wasn't
                slidersettings[sldr_videobpp] = 0;
                lastbpp = validbpps[0];
                UpdateValidModes(lastbpp, buttonsettings[btn_videofs]);
            }
            else
            {
                slidersettings[sldr_videobpp] = i;
            }

            // find the nearest resolution to the one last selected
            newoffset = 0;
            for (i=0; i<numvalidresolutions; i++)
            {
                if (abs(lastx * lasty - validresolutions[i].xdim         * validresolutions[i].ydim) <
                    abs(lastx * lasty - validresolutions[newoffset].xdim * validresolutions[newoffset].ydim))
                    newoffset = i;
            }
            slidersettings[sldr_videores] = newoffset;
        } break;

        default:
            state = buttonsettings[item->button];
            break;
        }
    }

    if (!draw)
        return;

    switch (item->button)
    {
    case btn_mouse_aim:
        extra_text = gs.MouseAimingType ? "Momentary" : "Toggle";
        break;
    default: break;
    }


    state = buttonsettings[item->button];

    // Draw the button
    if (item->text)
    {
        if (state)
        {
            // set
            rotatesprite(button_x << 16, y << 16, MZ, 0, pic_radiobuttn2, shade, 0, MenuDrawFlags, 0, 0, xdim - 1, ydim - 1);
        }
        else
        {
            // not set
            rotatesprite(button_x << 16, y << 16, MZ, 0, pic_radiobuttn1, shade, 0, MenuDrawFlags, 0, 0, xdim - 1, ydim - 1);
        }

        MenuTextShade = shade;
        MNU_DrawString(x, y, item->text, MenuTextShade, 16);

        if (extra_text)
            MNU_DrawString(OPT_XSIDE + tilesiz[pic_radiobuttn1].x + 6, y, extra_text, MenuTextShade, 16);
        MenuTextShade = MENU_SHADE_DEFAULT;
    }
    else
    {
        if (state)
            rotatesprite(x << 16, y << 16, MZ, 0, pic_radiobuttn2, 2, 0, MenuDrawFlags, 0, 0, xdim - 1, ydim - 1);
        else
            rotatesprite(x << 16, y << 16, MZ, 0, pic_radiobuttn1, 2, 0, MenuDrawFlags, 0, 0, xdim - 1, ydim - 1);

        x += tilesiz[pic_radiobuttn1].x + 4;

        // Draw the menu item text
        rotatesprite(x << 16, y << 16, MZ, 0, item->pic, 2, 0, MenuDrawFlags, 0, 0, xdim - 1, ydim - 1);
    }

}

//char *gametype[] = {"War [Respawn]","Cooperative","War [No Respawn]"};
char *gametype[] = {"WangBang (spawn)","WangBang (no spawn)","Cooperative"};
char *playercolors[] = {"Brown","Gray","Purple","Red","Yellow","Olive","Green","Blue"};
char *monsterskills[] = {"No Monsters","Easy","Normal","Hard","Insane!"};

void
MNU_DoSlider(short dir, MenuItem_p item, SWBOOL draw)
{
    short offset, i, barwidth;
    int x, y, knobx;
    short shade = MENU_SHADE_DEFAULT;
    char *extra_text=NULL;
    char tmp_text[256];

    memset(tmp_text,0,256);

    if (TEST(item->flags, mf_disabled))
    {
        shade = MENU_SHADE_INACTIVE;
        dir = 0;
    }

    switch (item->slider)
    {
    case sldr_mouse:
        barwidth = SLDR_MOUSESENSEMAX;
        offset = slidersettings[sldr_mouse] += dir;

        if (TEST(item->flags, mf_disabled))
            break;

        offset = max(offset, 0);
        offset = min(offset, SLDR_MOUSESENSEMAX-1);

        slidersettings[sldr_mouse] = offset;

        gs.MouseSpeed = offset * (MOUSE_SENS_MAX_VALUE/SLDR_MOUSESENSEMAX);
        CONTROL_SetMouseSensitivity(gs.MouseSpeed);
        break;

    case sldr_sndfxvolume:
        barwidth = SLDR_SNDFXVOLMAX;
        offset = slidersettings[sldr_sndfxvolume] += dir;

        if (TEST(item->flags, mf_disabled))
            break;

        offset = max(offset, 0);
        offset = min(offset, SLDR_SNDFXVOLMAX-1);

        slidersettings[sldr_sndfxvolume] = offset;
        gs.SoundVolume = FX_MIN + (offset * VOL_MUL);
        FX_SetVolume(gs.SoundVolume);
        break;

    case sldr_musicvolume:
        barwidth = SLDR_MUSICVOLMAX;
        offset = slidersettings[sldr_musicvolume] += dir;
        if (TEST(item->flags, mf_disabled))
            break;

        offset = max(offset, 0);
        offset = min(offset, SLDR_MUSICVOLMAX-1);

        slidersettings[sldr_musicvolume] = offset;
        gs.MusicVolume = MUSIC_MIN + (offset * VOL_MUL);
        SetSongVolume(gs.MusicVolume);
        break;

    case sldr_scrsize:
    {
        short bnum;

        barwidth = SLDR_SCRSIZEMAX;
        slidersettings[sldr_scrsize] = gs.BorderNum;
        slidersettings[sldr_scrsize] -= dir;
        offset = slidersettings[sldr_scrsize];

        if (TEST(item->flags, mf_disabled))
            break;

        ////DSPRINTF(ds,"BorderNum %d",gs.BorderNum);
        //MONO_PRINT(ds);

        offset = max(offset, 0);
        offset = min(offset, SLDR_SCRSIZEMAX - 1);

        bnum = offset;

        offset = (SLDR_SCRSIZEMAX-1) - offset;
        slidersettings[sldr_scrsize] = offset;

        if (!BorderAdjust)
            gs.BorderNum = bnum;

        SetBorder(&Player[myconnectindex], bnum);

        break;
    }

    case sldr_brightness:
        barwidth = SLDR_BRIGHTNESSMAX;
        offset = slidersettings[sldr_brightness] += dir;

        if (TEST(item->flags, mf_disabled))
            break;

        offset = max(offset, 0);
        offset = min(offset, SLDR_BRIGHTNESSMAX - 1);
        slidersettings[sldr_brightness] = offset;

        if (gs.Brightness != offset)
        {
            gs.Brightness = offset;
            COVERsetbrightness(gs.Brightness,&palette_data[0][0]);
        }
        break;

    case sldr_bordertile:
        barwidth = SLDR_BORDERTILEMAX;
        offset = slidersettings[sldr_bordertile] += dir;

        if (TEST(item->flags, mf_disabled))
            break;

        offset = max(offset, 0);
        offset = min(offset, SLDR_BORDERTILEMAX - 1);
        slidersettings[sldr_bordertile] = offset;

        if (gs.BorderTile != offset)
        {
            gs.BorderTile = offset;

            SetRedrawScreen(&Player[myconnectindex]);
        }
        break;

    case sldr_gametype:
        barwidth = SLDR_GAMETYPEMAX;
        offset = slidersettings[sldr_gametype] += dir;

        if (TEST(item->flags, mf_disabled))
            break;

        offset = max(offset, 0);
        offset = min(offset, SLDR_GAMETYPEMAX - 1);
        slidersettings[sldr_gametype] = offset;

        extra_text = gametype[offset];
        MNU_DrawString(OPT_XSIDE, item->y, extra_text, 1, 16);
        gs.NetGameType = offset;
        // friendly fire menu
        MNU_ItemPreProcess(currentmenu);
        break;

    case sldr_netlevel:
        barwidth = SLDR_NETLEVELMAX;
        offset = slidersettings[sldr_netlevel] += dir;

        if (TEST(item->flags, mf_disabled))
            break;

        offset = max(offset, 0);
        offset = min(offset, SLDR_NETLEVELMAX - 1);
        slidersettings[sldr_netlevel] = offset;

        // Show the currently selected level on next line
        //extra_text = MNU_LevelName[offset];
        //MNU_DrawString(OPT_XS, item->y+10, extra_text, 1, 16);
        sprintf(tmp_text, "L%02d: %s", offset+1, LevelInfo[offset+1].Description);
        MNU_DrawString(OPT_XS, item->y+10, tmp_text, 1, 16);
        gs.NetLevel = offset;
        break;

    case sldr_monsters:
        barwidth = SLDR_MONSTERSMAX;
        offset = slidersettings[sldr_monsters] += dir;

        if (TEST(item->flags, mf_disabled))
            break;

        offset = max(offset, 0);
        offset = min(offset, SLDR_MONSTERSMAX - 1);
        slidersettings[sldr_monsters] = offset;

        extra_text = monsterskills[offset];
        MNU_DrawString(OPT_XSIDE+54, item->y, extra_text, 1, 16);
        gs.NetMonsters = offset;
        break;

    case sldr_killlimit:
        barwidth = SLDR_KILLLIMITMAX;
        offset = slidersettings[sldr_killlimit] += dir;

        if (TEST(item->flags, mf_disabled))
            break;

        offset = max(offset, 0);
        offset = min(offset, SLDR_KILLLIMITMAX - 1);
        slidersettings[sldr_killlimit] = offset;

        if (offset == 0)
        {
            strcpy(tmp_text,"Infinite\n");
        }
        else
        {
            sprintf(tmp_text,"%d",offset*10);
            //itoa(offset*10,tmp_text,10);
        }
        MNU_DrawString(OPT_XSIDE+101, item->y, tmp_text, 1, 16);
        gs.NetKillLimit = offset;
        break;

    case sldr_timelimit:
        barwidth = SLDR_TIMELIMITMAX;
        offset = slidersettings[sldr_timelimit] += dir;

        if (TEST(item->flags, mf_disabled))
            break;

        offset = max(offset, 0);
        offset = min(offset, SLDR_TIMELIMITMAX - 1);
        slidersettings[sldr_timelimit] = offset;

        if (offset == 0)
        {
            strcpy(tmp_text,"Infinite\n");
        }
        else
        {
            sprintf(tmp_text,"%d Minutes\n",TimeLimitTable[offset]);
        }

        MNU_DrawString(OPT_XSIDE+86, item->y, tmp_text, 1, 16);
        gs.NetTimeLimit = offset;
        break;

    case sldr_playercolor:
        barwidth = SLDR_PLAYERCOLORMAX;
        offset = slidersettings[sldr_playercolor] += dir;

        if (TEST(item->flags, mf_disabled))
            break;

        offset = max(offset, 0);
        offset = min(offset, SLDR_PLAYERCOLORMAX - 1);
        slidersettings[sldr_playercolor] = offset;

        extra_text = playercolors[offset];
        MNU_DrawString(OPT_XSIDE+78, item->y, extra_text, 1, PALETTE_PLAYER0+offset);
        gs.NetColor = offset;
        break;

    case sldr_videores:
    {
        offset = max(0,min(slidersettings[sldr_videores] + dir, numvalidresolutions-1));
        barwidth = numvalidresolutions;

        if (TEST(item->flags, mf_disabled))
            break;

        slidersettings[sldr_videores] = offset;

        sprintf(tmp_text, "%dx%d", validresolutions[offset].xdim, validresolutions[offset].ydim);
        MNU_DrawString(OPT_XSIDE, item->y+OPT_YINC, tmp_text, 1, 16);
    } break;

    case sldr_videobpp:
    {
        offset = max(0,min(slidersettings[sldr_videobpp] + dir, numvalidbpps-1));
        barwidth = numvalidbpps;

        if (TEST(item->flags, mf_disabled))
            break;

        if (slidersettings[sldr_videobpp] != offset)
        {
            int lastx, lasty, newoffset, i;

            slidersettings[sldr_videobpp] = offset;

            // find the nearest resolution to the one last selected
            lastx = validresolutions[slidersettings[sldr_videores]].xdim;
            lasty = validresolutions[slidersettings[sldr_videores]].ydim;
            UpdateValidModes(validbpps[offset], buttonsettings[btn_videofs]);
            newoffset = 0;
            for (i=0; i<numvalidresolutions; i++)
            {
                if (abs(lastx * lasty - validresolutions[i].xdim         * validresolutions[i].ydim) <
                    abs(lastx * lasty - validresolutions[newoffset].xdim * validresolutions[newoffset].ydim))
                    newoffset = i;
            }
            slidersettings[sldr_videores] = newoffset;
        }

        sprintf(tmp_text, "%d bpp", validbpps[offset]);
        MNU_DrawString(OPT_XSIDE+tilesiz[pic_slidelend].x+tilesiz[pic_sliderend].x+(barwidth+1)*tilesiz[pic_slidebar].x, item->y, tmp_text, 1, 16);
    } break;

    case sldr_mousescalex:
    case sldr_mousescaley:
        barwidth = 8+1+8;
        offset = slidersettings[item->slider] + dir;

        if (TEST(item->flags, mf_disabled))
            break;

        offset = max(offset, 0);
        offset = min(offset, barwidth-1);

        if (slidersettings[item->slider] != offset)
        {
            slidersettings[item->slider] = offset;
            MouseAnalogScale[item->slider - sldr_mousescalex] = offset<<13;
            CONTROL_SetAnalogAxisScale(item->slider - sldr_mousescalex, offset<<13, controldevice_mouse);
        }

        sprintf(tmp_text, "%.2f", (float)(slidersettings[item->slider]<<13) / 65535.f);
        MNU_DrawSmallString(OPT_XSIDE+tilesiz[pic_slidelend].x+tilesiz[pic_sliderend].x+(MAX_SLDR_WIDTH+1)*tilesiz[pic_slidebar].x, item->y+4, tmp_text, 1, 16);
        break;

    case sldr_joyaxisscale:
        barwidth = 8+1+8;
        offset = slidersettings[item->slider] + dir;

        if (TEST(item->flags, mf_disabled))
            break;

        offset = max(offset, 0);
        offset = min(offset, barwidth-1);

        if (slidersettings[item->slider] != offset)
        {
            slidersettings[item->slider] = offset;
            JoystickAnalogScale[JoystickAxisPage] = offset<<13;
            CONTROL_SetAnalogAxisScale(JoystickAxisPage, offset<<13, controldevice_joystick);
        }

        sprintf(tmp_text, "%.2f", (float)(slidersettings[item->slider]<<13) / 65535.f);
        MNU_DrawSmallString(OPT_XSIDE+tilesiz[pic_slidelend].x+tilesiz[pic_sliderend].x+(MAX_SLDR_WIDTH+1)*tilesiz[pic_slidebar].x, item->y+4, tmp_text, 1, 16);
        break;

    case sldr_joyaxisanalog:
    {
        const char *p;

        barwidth = MNU_ControlAxisOffset(analog_maxtype);
        offset = slidersettings[item->slider] + dir;

        if (TEST(item->flags, mf_disabled))
            break;

        offset = max(offset, 0);
        offset = min(offset, barwidth-1);

        if (slidersettings[item->slider] != offset)
        {
            slidersettings[item->slider] = offset;
            JoystickAnalogAxes[JoystickAxisPage] = MNU_ControlAxisNum(offset);
            CONTROL_MapAnalogAxis(JoystickAxisPage, MNU_ControlAxisNum(offset), controldevice_joystick);
        }

        p = CONFIG_AnalogNumToName(MNU_ControlAxisNum(offset));
        while (*p != 0 && *p != '_') p++;
        if (*p == '_') p++;
        MNU_DrawSmallString(OPT_XSIDE+tilesiz[pic_slidelend].x+tilesiz[pic_sliderend].x+(barwidth+1)*tilesiz[pic_slidebar].x, item->y+4, p, 1, 16);
    }
    break;

    case sldr_joyaxisdead:
    case sldr_joyaxissatur:
        barwidth = (32768>>10)+1;
        offset = slidersettings[item->slider] + dir;

        if (TEST(item->flags, mf_disabled))
            break;

        offset = max(offset, 0);
        offset = min(offset, barwidth-1);

        if (slidersettings[item->slider] != offset)
        {
            slidersettings[item->slider] = offset;
            if (item->slider == sldr_joyaxisdead)
            {
                JoystickAnalogDead[JoystickAxisPage] = min((offset<<10), 32767);
                CONTROL_SetJoyAxisDead(JoystickAxisPage, JoystickAnalogDead[JoystickAxisPage]);
            }
            else
            {
                JoystickAnalogSaturate[JoystickAxisPage] = min((offset<<10), 32767);
                CONTROL_SetJoyAxisSaturate(JoystickAxisPage, JoystickAnalogSaturate[JoystickAxisPage]);
            }
        }

        sprintf(tmp_text, "%.2f%%", (float)(slidersettings[item->slider]<<10) / 32767.f);
        MNU_DrawSmallString(OPT_XSIDE+tilesiz[pic_slidelend].x+tilesiz[pic_sliderend].x+(MAX_SLDR_WIDTH+1)*tilesiz[pic_slidebar].x, item->y+4, tmp_text, 1, 16);
        break;

    default:
        return;
    }

    if (!draw)
        return;

    // Now draw it
    item++;
    x = item->x;
    y = item->y;

    // Draw the left end cap of the bar
    rotatesprite(x << 16, y << 16, MZ, 0, pic_slidelend, shade, 0, MenuDrawFlags, 0, 0, xdim - 1, ydim - 1);

    x += tilesiz[pic_slidelend].x;
    knobx = x;

    // Draw the in between sections
    for (i = 0; i < min(barwidth,MAX_SLDR_WIDTH); i++)
    {
        rotatesprite(x << 16, y << 16, MZ, 0, pic_slidebar, shade, 0, MenuDrawFlags, 0, 0, xdim - 1, ydim - 1);
        x += tilesiz[pic_slidebar].x;
    }

    // Draw the right end cap
    rotatesprite(x << 16, y << 16, MZ, 0, pic_sliderend, shade, 0, MenuDrawFlags, 0, 0, xdim - 1, ydim - 1);

    // Draw the knob, compressing the X coordinate if the bar is too wide
    if (barwidth > MAX_SLDR_WIDTH)
    {
        knobx += offset * (MAX_SLDR_WIDTH*tilesiz[pic_slidebar].x-tilesiz[pic_sliderknob].x) / (barwidth-1);
    }
    else
    {
        knobx += tilesiz[pic_slidebar].x * offset;
    }
    rotatesprite(knobx << 16, (y + 2) << 16, MZ, 0, pic_sliderknob, shade, 0, MenuDrawFlags, 0, 0, xdim - 1, ydim - 1);
}

////////////////////////////////////////////////
// Start up menu array
////////////////////////////////////////////////
static void
MNU_SetupMenu(void)
{
    MenuGroup *rootmenu;

    static MenuGroup *rootmenulist[] =
    {
        &maingroup,
        &SaveGameGroup,
        &LoadGameGroup,
        &soundgroup,
        &optiongroup,
        &quickloadgroup,
        &quitgroup,
        &ordergroup,
        &episodegroup,
    };

    rootmenu = rootmenulist[ControlPanelType];
    ASSERT(ControlPanelType < ct_max);

    menuarrayptr = 0;
    menuarray[0] = currentmenu = rootmenu;
    if (ControlPanelType == ct_mainmenu)

        mnu_input_buffered.button0 = mnu_input_buffered.button1 = FALSE;
    mnu_input_buffered.dir = dir_None;
    order_input_buffered.button0 = order_input_buffered.button1 = FALSE;
    order_input_buffered.dir = dir_None;
    ResetKeys();

    // custom cust_callback starts out as null
    cust_callback = NULL;

    // for QuitCustom and QuickLoadCustom
    if (currentmenu->items == NULL)
    {
        if (currentmenu->draw_custom)
            currentmenu->draw_custom(uc_setup, NULL);
    }

    if (ControlPanelType == ct_mainmenu)
        currentmenu->cursor = 0;

    // disable any items necessary
    MNU_ItemPreProcess(currentmenu);
}

////////////////////////////////////////////////
// Draw an item
////////////////////////////////////////////////
static void
MNU_ClearFlags(MenuGroup *node)
{
    MenuItem *i;

    if (!node->items)
        return;

    for (i = node->items; i->type != mt_none; i++)
    {
        i->flags &= ~MenuSelectFlags;
        if (i->child)
            MNU_ClearFlags((MenuGroup *) i->child);
    }
}

////////////////////////////////////////////////
// Pop a group off the menu stack
////////////////////////////////////////////////
static void
MNU_PopGroup(void)
{
    if (!menuarrayptr)
        return;

    currentmenu = menuarray[--menuarrayptr];

    SetFragBar(Player + myconnectindex);
    //PanelRefresh(Player + myconnectindex);
}

////////////////////////////////////////////////
// Push a group on to the menu stack
////////////////////////////////////////////////
static void
MNU_PushGroup(MenuGroup *node)
{
    if (menuarrayptr == MaxLayers - 1)
        return;

    currentmenu = menuarray[++menuarrayptr] = node;

    SetFragBar(Player + myconnectindex);
}

////////////////////////////////////////////////
// Setup a new menu subgroup
////////////////////////////////////////////////
static void
MNU_SetupGroup(void)
{
    MNU_SelectItem(currentmenu, currentmenu->cursor, FALSE);
    MNU_DrawMenu();
}

static void
MNU_ItemPreProcess(MenuGroup *group)
{
    MenuItem *item;

    if (!group->items)
        return;

    // process all items when going down a level
    // to see if anything is disabled
    for (item = group->items; item->type != mt_none; item++)
    {
        if (item->preprocess)
            item->preprocess(item);
    }
}

void
MNU_ItemPostProcess(MenuGroup *group)
{
    MenuItem *item;
    int zero = 0;

    if (!group->items)
        return;

    item = &currentmenu->items[currentmenu->cursor];

    if (item->postprocess)
    {
        item->postprocess(item);
    }
}

////////////////////////////////////////////////
// Go to next menu subgroup
////////////////////////////////////////////////
static void
MNU_DownLevel(MenuGroup *group)
{

    if (!group)
    {
        TerminateGame();
        printf("MNU_DownLevel() - NULL card\n");
        exit(0);
    }

    MNU_PushGroup(group);

    if (group->items == NULL)
    {
        if (group->draw_custom && group->draw_custom(uc_setup, NULL))
            MNU_PopGroup();
    }

    MNU_ItemPreProcess(currentmenu);

    MNU_SetupGroup();

    SetRedrawScreen(&Player[myconnectindex]);
}

////////////////////////////////////////////////
// Go to previous menu subgroup
////////////////////////////////////////////////
static void
MNU_UpLevel(void)
{
    int zero = 0;
    static int handle1=0;
    // if run out of menus then EXIT
    if (!menuarrayptr)
    {
        if (!FX_SoundActive(handle1))
            handle1 = PlaySound(DIGI_STARCLINK,&zero,&zero,&zero,v3df_dontpan);
        ExitMenus();
        return;
    }

    if (currentmenu->items)
        currentmenu->items[currentmenu->cursor].flags &= ~mf_selected;
    MNU_PopGroup();
    MNU_SetupGroup();

    SetRedrawScreen(&Player[myconnectindex]);
}

////////////////////////////////////////////////
// Do a menu item action
////////////////////////////////////////////////
static void
MNU_DoItem(void)
{
    MenuItem *item;

    item = &currentmenu->items[currentmenu->cursor];
    if (!item) return;

    if (TEST(item->flags, mf_disabled))
    {
        // Try to process again
        if (item->preprocess)
            item->preprocess(item);

        // Check once more
        if (TEST(item->flags, mf_disabled))
            return;
    }

    switch (item->type)
    {
    case mt_option:
        if (item->custom != NULL)
            item->custom();
        break;
    case mt_button:
        MNU_PushItem(item, FALSE);
        if (item->custom != NULL)
            item->custom();
        break;
    case mt_layer:
        if (item->custom != NULL)
            item->custom();
        MNU_DownLevel(item->child);
        break;
    default: break;
    }
}

////////////////////////////////////////////////
// Draw an item icon or cursor
////////////////////////////////////////////////
static void
MNU_DrawItemIcon(MenuItem *item)
{
    //void BorderRefreshClip(PLAYERp pp, short x, short y, short x2, short y2);
    int x = item->x, y = item->y;
    int scale = MZ;
    short w,h;

    if (item->text)
    {
        scale /= 2;
        x -= mulscale17(tilesiz[pic_yinyang].x,scale) + 2;
        y += 4;
    }
    else
    {
        scale -= (1<<13);
        x -= ((tilesiz[pic_yinyang].x) / 2) - 3;
        y += 8;
    }

    rotatesprite(x << 16, y << 16,
                 scale, 0, pic_yinyang, item->shade, 0, MenuDrawFlags, 0, 0, xdim - 1, ydim - 1);

    SetRedrawScreen(&Player[myconnectindex]);
    //BorderRefreshClip(&Player[myconnectindex], x - 24, y - 24, x + 24, y + 24);
}

////////////////////////////////////////////////
// Draw an item
////////////////////////////////////////////////
static void
MNU_DrawItem(MenuItem *item)
{
    char *ptr;
    short px, py;

    MNU_ItemPostProcess(currentmenu);  // Put this in so things can be drawn on item select

    if (!item->pic)
        return;

    MNU_DrawItemIcon(item);

    // if text string skip this part
    if (item->text)
        return;

    if (TEST(item->flags, mf_selected) && !TEST(item->flags, mf_disabled))
    {
        // Highlighted
        if (item->type != mt_button)
            rotatesprite(item->x << 16, item->y << 16, MZ, 0, item->pic,
                         -30 + STD_RANDOM_RANGE(50), PALETTE_MENU_HIGHLIGHT, MenuDrawFlags,
                         0, 0, xdim - 1, ydim - 1);
        else
            rotatesprite((item->x + tilesiz[pic_radiobuttn1].x + 4) << 16, item->y << 16,
                         MZ, 0, item->pic, item->shade, PALETTE_MENU_HIGHLIGHT, MenuDrawFlags, 0, 0, xdim - 1, ydim - 1);
    }
    else
    {
        // Un highlighted
        if (item->type != mt_button)
            rotatesprite(item->x << 16, item->y << 16, MZ, 0, item->pic,
                         item->shade, 0, MenuDrawFlags, 0, 319, 199, 0);
        else
            rotatesprite((item->x + tilesiz[pic_radiobuttn1].x + 4) << 16, item->y << 16,
                         MZ, 0, item->pic, item->shade, 0, MenuDrawFlags, 0, 0, xdim - 1, ydim - 1);
    }
}

////////////////////////////////////////////////
// Draw the menu contents
////////////////////////////////////////////////
static void
MNU_DrawMenuContents(void)
{
    MenuItem *item;
    short w,h;

    ASSERT(currentmenu != NULL);

    if (currentmenu->text)
    {
        // Draw the backdrop bar
        rotatesprite(10 << 16, (currentmenu->y-3) << 16, MZ, 0, 2427,
                     currentmenu->shade, 0, MenuDrawFlags|ROTATE_SPRITE_CORNER, 0, 0, xdim - 1, ydim - 1);
        MNU_MeasureStringLarge(currentmenu->text, &w, &h);
        MNU_DrawString(TEXT_XCENTER(w), currentmenu->y, currentmenu->text, 1, 16);
    }
    else if (currentmenu->titlepic)
    {
        rotatesprite(currentmenu->x << 16, currentmenu->y << 16, MZ, 0, currentmenu->titlepic,
                     currentmenu->shade, 0, MenuDrawFlags, 0, 0, xdim - 1, ydim - 1);
    }

    if (!currentmenu->items)
        return;

    for (item = currentmenu->items; item->type != mt_none; item++)
    {
        if (item->pic)
        {
            if (item->type == mt_button)
            {
                // all drawing done here also
                MNU_DoButton(item, TRUE);
            }
            else
            {
                if (item->text)
                {
                    if (TEST(item->flags, mf_disabled))
                        MenuTextShade = MENU_SHADE_INACTIVE;
                    MNU_DrawString(item->x, item->y, item->text, MenuTextShade, 16);
                    MenuTextShade = MENU_SHADE_DEFAULT;
                }
                else
                {
                    rotatesprite(item->x << 16, item->y << 16, MZ, 0, item->pic,
                                 item->shade, 0, MenuDrawFlags, 0, 0, xdim - 1, ydim - 1);
                }
            }
        }

        // Is there a slider attached to this item?  Draw it.
        if (item->type == mt_slider)
            MNU_DoSlider(0, item, TRUE);
    }

    MNU_SelectItem(currentmenu, currentmenu->cursor, TRUE);

    if (currentmenu->draw_custom)
        currentmenu->draw_custom(uc_touchup, NULL);
}

////////////////////////////////////////////////
// Draw the menu
////////////////////////////////////////////////
void
MNU_DrawMenu(void)
{
    if (cust_callback != NULL)
    {
        cust_callback(cust_callback_call, cust_callback_item);
        return;
    }

    if (currentmenu->items || currentmenu->titlepic)
    {
        MNU_DrawMenuContents();
    }
}

////////////////////////////////////////////////
// Select a menu item
////////////////////////////////////////////////
void
MNU_SelectItem(MenuGroup *group, short index, SWBOOL draw)
{
    MenuItem *item;

    if (index != group->cursor)
    {
        item = &group->items[group->cursor];
        item->flags &= ~mf_selected;
        if (draw)
            MNU_DrawItem(item);
    }

    group->cursor = index;
    item = &group->items[group->cursor];
    item->flags |= mf_selected;
    if (draw)
        MNU_DrawItem(item);
}

////////////////////////////////////////////////
// Toggle a menu radio button on/off
////////////////////////////////////////////////
static void
MNU_PushItem(MenuItem *item, SWBOOL draw)
{
    if (item->type != mt_button)
        return;

    buttonsettings[item->button] ^= 1;

//    if (draw)
    MNU_DoButton(item, draw);
}

////////////////////////////////////////////////
// Go to next item on menu
////////////////////////////////////////////////
static void
MNU_NextItem(void)
{
    MenuTag type;
    MenuFlags flag;

    type = currentmenu->items[currentmenu->cursor + 1].type;
    flag = currentmenu->items[currentmenu->cursor + 1].flags;

    if (type == mt_none)
        MNU_SelectItem(currentmenu, 0, FALSE);
    else
        MNU_SelectItem(currentmenu, currentmenu->cursor + 1, FALSE);

    type = currentmenu->items[currentmenu->cursor].type;
    flag = currentmenu->items[currentmenu->cursor].flags;

    if (type == mt_inert || flag == mf_disabled)
        MNU_NextItem();
}

////////////////////////////////////////////////
// Go to previous item on menu
////////////////////////////////////////////////
static void
MNU_PrevItem(void)
{
    MenuTag type;
    MenuFlags flag;

    if (!currentmenu->cursor)
        while (currentmenu->items[++currentmenu->cursor].type != mt_none) ;

    MNU_SelectItem(currentmenu, currentmenu->cursor - 1, FALSE);

    type = currentmenu->items[currentmenu->cursor].type;
    flag = currentmenu->items[currentmenu->cursor].flags;
    if (type == mt_inert || flag == mf_disabled)
        MNU_PrevItem();
}

////////////////////////////////////////////////
// Find hotkey press on current menu, if any.
////////////////////////////////////////////////
static SWBOOL
MNU_DoHotkey(void)
{
    MenuItem_p item;
    short index;

    if (!currentmenu->items) return FALSE;

    index = 0;
    for (item = currentmenu->items; item->type != mt_none; item++)
    {
        if (KEY_PRESSED(item->hotkey) && item->hotkey != 0)
        {
            MNU_SelectItem(currentmenu, index, FALSE);
            return TRUE;
        }
        index++;
    }

    return FALSE;
}

////////////////////////////////////////////////
// Setup Menus
////////////////////////////////////////////////
void
SetupMenu(void)
{
    if (!UsingMenus && !ConPanel)       // Doing this check for multiplay
    // menus
    {
        MNU_SetupMenu();

        // Clear the previous ESC key press
        KEY_PRESSED(KEYSC_ESC) = FALSE;
        UsingMenus = TRUE;
    }
}

////////////////////////////////////////////////
// Setup the main menu
// This function will not loop if in modem
// or network game, otherwise it stops the
// game play until user finished in menus.
////////////////////////////////////////////////
#define MNU_SENSITIVITY 10              // The menu's mouse sensitivity, should be real low

void MNU_DoMenu(CTLType type, PLAYERp pp)
{
    SWBOOL resetitem;
    UCHAR key;
    int zero = 0;
    static int handle2 = 0;
    static int limitmove=0;
    static SWBOOL select_held=FALSE;

    resetitem = TRUE;

    if (cust_callback != NULL)
    {
        cust_callback(cust_callback_call, cust_callback_item);
        return;
    }

    //ControlPanelType = type;
    SetupMenu();

    // Zero out the input structure
    mnu_input.button0 = mnu_input.button1 = FALSE;
    mnu_input.dir = dir_None;

    // should not get input if you are editing a save game slot
    if (totalclock < limitmove) limitmove = totalclock;
    if (!MenuInputMode)
    {
        UserInput tst_input;
        SWBOOL select_held = FALSE;


        // Zero out the input structure
        tst_input.button0 = tst_input.button1 = FALSE;
        tst_input.dir = dir_None;

        if (!select_held)
        {
            CONTROL_GetUserInput(&tst_input);
            mnu_input_buffered.dir = tst_input.dir;
        }

        if (mnu_input_buffered.button0 || mnu_input_buffered.button1)
        {
            if (tst_input.button0 == mnu_input_buffered.button0 &&
                tst_input.button1 == mnu_input_buffered.button1)
            {
                select_held = TRUE;
            }
            else if (totalclock - limitmove > 7)
            {
                mnu_input.button0 = mnu_input_buffered.button0;
                mnu_input.button1 = mnu_input_buffered.button1;

                mnu_input_buffered.button0 = tst_input.button0;
                mnu_input_buffered.button1 = tst_input.button1;
            }
        }
        else
        {
            select_held = FALSE;
            mnu_input_buffered.button0 = tst_input.button0;
            mnu_input_buffered.button1 = tst_input.button1;
        }

        if (totalclock - limitmove > 7 && !select_held)
        {
            mnu_input.dir = mnu_input_buffered.dir;

            if (mnu_input.dir != dir_None)
                if (!FX_SoundActive(handle2))
                    handle2 = PlaySound(DIGI_STAR,&zero,&zero,&zero,v3df_dontpan);

            limitmove = totalclock;
            mnu_input_buffered.dir = dir_None;
        }
    }

    if (mnu_input.dir == dir_North)
    {
        MNU_PrevItem();
        resetitem = TRUE;
    }
    else if (mnu_input.dir == dir_South)
    {
        MNU_NextItem();
        resetitem = TRUE;
    }
    else if (mnu_input.button0)
    {
        static int handle5=0;
        if (!FX_SoundActive(handle5))
            handle5 = PlaySound(DIGI_SWORDSWOOSH,&zero,&zero,&zero,v3df_dontpan);
        KB_ClearKeysDown();
        MNU_DoItem();
        resetitem = TRUE;
    }
    else if (mnu_input.dir == dir_West
             && currentmenu->items[currentmenu->cursor].type == mt_slider)
    {
        MNU_DoSlider(-1, &currentmenu->items[currentmenu->cursor], FALSE);
        resetitem = TRUE;
    }
    else if (mnu_input.dir == dir_East
             && currentmenu->items[currentmenu->cursor].type == mt_slider)
    {
        MNU_DoSlider(1, &currentmenu->items[currentmenu->cursor], FALSE);
        resetitem = TRUE;
    }
    else if (mnu_input.button1 || BUTTON(gamefunc_Show_Menu))
    {
        static int handle3=0;
        CONTROL_ClearButton(gamefunc_Show_Menu);
        if (!FX_SoundActive(handle3))
            handle3 = PlaySound(DIGI_SWORDSWOOSH,&zero,&zero,&zero,v3df_dontpan);
        MNU_UpLevel();
        resetitem = TRUE;
    }
    else if (MNU_DoHotkey())
    {
        static int handle4=0;
        if (!FX_SoundActive(handle4))
            handle4 = PlaySound(DIGI_STAR,&zero,&zero,&zero,v3df_dontpan);
        resetitem = TRUE;
        mnu_input_buffered.button0 = mnu_input_buffered.button1 = FALSE;
    }
    else
        resetitem = FALSE;

    // !FRANK! I added this because the old custom was only called for drawing
    // Needed one for drawing and moving.
    if (currentmenu->move_custom)
        currentmenu->move_custom(uc_setup, NULL);

    if (resetitem)
    {
        KB_ClearKeysDown();
        ResetKeys();
    }
}

////////////////////////////////////////////////
//  Checks to see if we should be in menus
////////////////////////////////////////////////
void
MNU_CheckForMenus(void)
{
    extern SWBOOL GamePaused;

    if (UsingMenus)
    {
        //if (MoveSkip2 == 0)
        MNU_DoMenu(ct_mainmenu, Player + myconnectindex);
    }
    else
    {
        if ((KEY_PRESSED(KEYSC_ESC) || BUTTON(gamefunc_Show_Menu)) && dimensionmode == 3 && !ConPanel)
        {
            KEY_PRESSED(KEYSC_ESC) = 0;
            CONTROL_ClearButton(gamefunc_Show_Menu);
            KB_ClearKeysDown();
            // setup sliders/buttons
            MNU_InitMenus();
            MNU_DoMenu(ct_mainmenu, Player + myconnectindex);
            pMenuClearTextLine(Player + myconnectindex);
            PauseGame();
        }
    }
}

void
MNU_CheckForMenusAnyKey(void)
{
    if (UsingMenus)
    {
        //if (MoveSkip2 == 0)
        MNU_DoMenu(ct_mainmenu, Player + myconnectindex);
    }
    else
    {
        if (KeyPressed())
        {
            ResetKeys();
            KB_ClearKeysDown();
            MNU_InitMenus();
            MNU_DoMenu(ct_mainmenu, Player + myconnectindex);
            pMenuClearTextLine(Player + myconnectindex);
        }
    }
}

static int MNU_ControlAxisOffset(int num)
{
    switch (num)
    {
    case analog_turning: return 0;
    case analog_strafing: return 1;
    case analog_moving: return 2;
    case analog_lookingupanddown: return 3;
    case analog_maxtype: return 4;
    default: return 0;
    }
}

static int MNU_ControlAxisNum(int offset)
{
    switch (offset)
    {
    case 0: return analog_turning;
    case 1: return analog_strafing;
    case 2: return analog_moving;
    case 3: return analog_lookingupanddown;
    default: return analog_turning;
    }
}



///////////////////////////////////////////////////////////////////////////////////////////////////
// Miscellaneous Routines
///////////////////////////////////////////////////////////////////////////////////////////////////

typedef struct RGB_color_typ
{
    unsigned char red;
    unsigned char green;
    unsigned char blue;
} RGB_color, *RGB_color_ptr;

#define PALETTE_MASK            0x3c6
#define PALETTE_READ            0x3c7
#define PALETTE_WRITE           0x3c8
#define PALETTE_DATA            0x3c9

unsigned char palette_data[256][3];     // Global palette array

// V E R T I C A L   R E T R A C E   V A R I A B L E S //////////////////////////////////////////

#define VGA_INPUT_STATUS_1      0x3DA   // VGA status register 1, bit 3 is the vsync
// 1 = retrace in progress
// 0 = no retrace
#define VGA_VSYNC_MASK          0x08    // Masks off unwanted bits of status register.


// These routines are not used and should not be used.  Would interfere with VESA palette
// cards.
#if 0
/////////////////////////////////////////////////
// WaitForVsync
// Waits for a vertical retrace to occur.  If one is in progress, it waits for the next one.
/////////////////////////////////////////////////
void
WaitForVsync(void)
{
    while (inp(VGA_INPUT_STATUS_1) & VGA_VSYNC_MASK) ;
    // Retrace in progress, wait.

    // Wait for vsync, and exit.
    while (!inp(VGA_INPUT_STATUS_1) & VGA_VSYNC_MASK) ;
}

void
Get_Palette(unsigned char *pal)
{
    int i;

    outp(PALETTE_READ, 0);
    for (i = 0; i < 768; i++)
        pal[i] = inp(PALETTE_DATA);
}
void
Set_Palette(unsigned char *buff)
{
    int i;

    outp(PALETTE_WRITE, 0);             // Resets color ram pointer to 1st
    // color
    for (i = 0; i < 768; i++)
        outp(PALETTE_DATA, buff[i]);
}

#endif


/*
=================================================================================================
=
=       FadeOut - Fades the palette to color at assigned click rate
=
=================================================================================================
*/
// Heres some temp timer junk for this routine.  Replace it with game timer stuff later.
//unsigned int *clock  = (unsigned int *)0x046C;

void
Fade_Timer(int clicks)
{
//        unsigned int now;
    int now;

    now = totalclock;

    while (abs(totalclock - now) < clicks) handleevents();
}

void
FadeIn(unsigned char startcolor, unsigned int clicks)
{
    int i, palreg, usereg, tmpreg1 = 0, tmpreg2 = 0;
    RGB_color color;
    unsigned char temp_pal[768], *palette;

    if (getrendermode() >= 3) return;

    palette = &palette_data[0][0];

    color.red = palette_data[startcolor][0];
    color.green = palette_data[startcolor][1];
    color.blue = palette_data[startcolor][2];

    usereg = 0;
    for (i = 0; i < 768; i++)
    {
        if (usereg == 0)
            temp_pal[i] = color.red;
        else if (usereg == 1)
            temp_pal[i] = color.green;
        else
            temp_pal[i] = color.blue;

        if (++usereg > 2)
            usereg = 0;
    }

    for (i = 0; i < 32; i++)
    {
        for (palreg = 0; palreg < 768; palreg++)
        {
            tmpreg1 = (int)(temp_pal[palreg]) + 2;
            tmpreg2 = (int)(temp_pal[palreg]) - 2;
            if (tmpreg1 > 255)
                tmpreg1 = 255;
            if (tmpreg2 < 0)
                tmpreg2 = 0;

            if (temp_pal[palreg] < palette[palreg])
            {
                if ((temp_pal[palreg] = tmpreg1) > palette[palreg])
                    temp_pal[palreg] = palette[palreg];
            }
            else if (temp_pal[palreg] > palette[palreg])
                if ((temp_pal[palreg] = tmpreg2) < palette[palreg])
                    temp_pal[palreg] = palette[palreg];

        }

        set_pal(&temp_pal[0]);

        // Delay clicks
        Fade_Timer(clicks);
    }
}

void
FadeOut(unsigned char targetcolor, unsigned int clicks)
{
    int i, palreg, usereg = 0, tmpreg1 = 0, tmpreg2 = 0;
    RGB_color color;
    unsigned char temp_pal[768];

    if (getrendermode() >= 3) return;

    color.red = palette_data[targetcolor][0];
    color.green = palette_data[targetcolor][1];
    color.blue = palette_data[targetcolor][2];

    memcpy(&temp_pal[0], &palette_data[0][0], 768);

    for (i = 0; i < 32; i++)
    {
        for (palreg = 0; palreg < 768; palreg++)
        {
            tmpreg1 = (int)(temp_pal[palreg]) + 2;
            tmpreg2 = (int)(temp_pal[palreg]) - 2;
            if (tmpreg1 > 255)
                tmpreg1 = 255;
            if (tmpreg2 < 0)
                tmpreg2 = 0;

            if (usereg == 0)
            {
                if (temp_pal[palreg] < color.red)
                {
                    if ((temp_pal[palreg] = tmpreg1) > color.red)
                        temp_pal[palreg] = color.red;
                }
                else if (temp_pal[palreg] > color.red)
                    if ((temp_pal[palreg] = tmpreg2) < color.red)
                        temp_pal[palreg] = color.red;
            }
            else if (usereg == 1)
            {
                if (temp_pal[palreg] < color.green)
                {
                    if ((temp_pal[palreg] = tmpreg1) > color.green)
                        temp_pal[palreg] = color.green;
                }
                else if (temp_pal[palreg] > color.green)
                    if ((temp_pal[palreg] = tmpreg2) < color.green)
                        temp_pal[palreg] = color.green;
            }
            else if (usereg == 2)
            {
                if (temp_pal[palreg] < color.blue)
                {
                    if ((temp_pal[palreg] = tmpreg1) > color.blue)
                        temp_pal[palreg] = color.blue;
                }
                else if (temp_pal[palreg] > color.blue)
                    if ((temp_pal[palreg] = tmpreg2) < color.blue)
                        temp_pal[palreg] = color.blue;
            }

            if (++usereg > 2)
                usereg = 0;
        }


        set_pal(&temp_pal[0]);

        // Delay clicks
        Fade_Timer(clicks);
    }
}

//////////////////////////////////////////////////////////////////////////////
#define FADE_DAMAGE_FACTOR  3   // 100 health / 32 shade cycles = 3.125

// Fades from 100% to 62.5% somewhat quickly,
//  then from 62.5% to 37.5% slowly,
//  then from 37.5% to 0% quickly.
// This seems to capture the pain caused by enemy shots, plus the extreme
//  fade caused by being blinded or intense pain.
// Perhaps the next step would be to apply a gentle smoothing to the
//  intersections of these lines.
static int faderamp[32] =
{
    // y=64-4x
    252,240,224,208,192,176,

    // y=44.8-(16/20)x
    160,156,152,152,148,
    144,140,136,136,132,
    128,124,120,120,116,
    112,108,104,104,100,

    // y=128-4x
    96,80,64,48,32,16
};

unsigned char ppalette[MAX_SW_PLAYERS_REG][768];

//////////////////////////////////////////
// Set the amount of redness for damage
// the player just took
//////////////////////////////////////////
void
SetFadeAmt(PLAYERp pp, short damage, unsigned char startcolor)
{
    int palreg, usereg = 0, tmpreg1 = 0, tmpreg2 = 0;
    short fadedamage=0;
    RGB_color color;

    //CON_ConMessage("SetAmt: fadeamt = %d, startcolor = %d, pp = %d",pp->FadeAmt,startcolor,pp->StartColor);

    if (abs(pp->FadeAmt) > 0 && startcolor == pp->StartColor)
        return;

    // Don't ever over ride flash bomb
    if (pp->StartColor == 1 && abs(pp->FadeAmt) > 0)
        return;

    // Reset the palette
    if (pp == Player + screenpeek)
    {
        if (getrendermode() < 3)
            COVERsetbrightness(gs.Brightness,&palette_data[0][0]);
        else
            setpalettefade(0,0,0,0);
        if (pp->FadeAmt <= 0)
            GetPaletteFromVESA(&ppalette[screenpeek][0]);
    }

    if (damage < -150 && damage > -1000) fadedamage = 150;
    else if (damage < -1000) // Underwater
        fadedamage = abs(damage+1000);
    else
        fadedamage = abs(damage);

    if (damage >= -5 && damage < 0)
        fadedamage += 10;

    // Don't let red to TOO red
    if (startcolor == COLOR_PAIN && fadedamage > 100) fadedamage = 100;

    pp->FadeAmt = fadedamage / FADE_DAMAGE_FACTOR;

    if (pp->FadeAmt <= 0)
    {
        pp->FadeAmt = 0;
        return;
    }

    // It's a health item, just do a preset flash amount
    if (damage > 0)
        pp->FadeAmt = 3;

    pp->StartColor = startcolor;

    pp->FadeTics = 0;

    // Set player's palette to current game palette
    GetPaletteFromVESA(pp->temp_pal);

    color.red = palette_data[pp->StartColor][0];
    color.green = palette_data[pp->StartColor][1];
    color.blue = palette_data[pp->StartColor][2];

    for (palreg = 0; palreg < 768; palreg++)
    {
        tmpreg1 = (int)(pp->temp_pal[palreg]) + ((2 * pp->FadeAmt) + 4);
        tmpreg2 = (int)(pp->temp_pal[palreg]) - ((2 * pp->FadeAmt) + 4);
        if (tmpreg1 > 255)
            tmpreg1 = 255;
        if (tmpreg2 < 0)
            tmpreg2 = 0;

        if (usereg == 0)
        {
            if (pp->temp_pal[palreg] < color.red)
            {
                if ((pp->temp_pal[palreg] = tmpreg1) > color.red)
                    pp->temp_pal[palreg] = color.red;
            }
            else if (pp->temp_pal[palreg] > color.red)
                if ((pp->temp_pal[palreg] = tmpreg2) < color.red)
                    pp->temp_pal[palreg] = color.red;
        }
        else if (usereg == 1)
        {
            if (pp->temp_pal[palreg] < color.green)
            {
                if ((pp->temp_pal[palreg] = tmpreg1) > color.green)
                    pp->temp_pal[palreg] = color.green;
            }
            else if (pp->temp_pal[palreg] > color.green)
                if ((pp->temp_pal[palreg] = tmpreg2) < color.green)
                    pp->temp_pal[palreg] = color.green;
        }
        else if (usereg == 2)
        {
            if (pp->temp_pal[palreg] < color.blue)
            {
                if ((pp->temp_pal[palreg] = tmpreg1) > color.blue)
                    pp->temp_pal[palreg] = color.blue;
            }
            else if (pp->temp_pal[palreg] > color.blue)
                if ((pp->temp_pal[palreg] = tmpreg2) < color.blue)
                    pp->temp_pal[palreg] = color.blue;
        }

        if (++usereg > 2)
            usereg = 0;
    }

    // Do initial palette set
    if (pp == Player + screenpeek)
    {
        if (getrendermode() < 3) set_pal(pp->temp_pal);
        else setpalettefade(color.red, color.green, color.blue, faderamp[min(31,max(0,32-abs(pp->FadeAmt)))]);
        if (damage < -1000)
            pp->FadeAmt = 1000;  // Don't call DoPaletteFlash for underwater stuff
    }
}

//////////////////////////////////////////
// Do the screen reddness based on damage
//////////////////////////////////////////
#define MAXFADETICS     5
void
DoPaletteFlash(PLAYERp pp)
{
    int i, palreg, tmpreg1 = 0, tmpreg2 = 0;
    unsigned char *pal_ptr = &ppalette[screenpeek][0];


    if (pp->FadeAmt <= 1)
    {
        pp->FadeAmt = 0;
        pp->StartColor = 0;
        if (pp == Player + screenpeek)
        {
            if (getrendermode() < 3)
                COVERsetbrightness(gs.Brightness,&palette_data[0][0]);
            else
                setpalettefade(0,0,0,0);
            memcpy(pp->temp_pal, palette_data, sizeof(palette_data));
            DoPlayerDivePalette(pp);  // Check Dive again
            DoPlayerNightVisionPalette(pp);  // Check Night Vision again
        }

        return;
    }


    pp->FadeTics += synctics;           // Add this frame's tic amount to
    // counter

    if (pp->FadeTics >= MAXFADETICS)
    {
        while (pp->FadeTics >= MAXFADETICS)
        {
            pp->FadeTics -= MAXFADETICS;

            pp->FadeAmt--;              // Decrement FadeAmt till it gets to
            // 0 again.
        }
    }
    else
        return;                         // Return if they were not >
    // MAXFADETICS

    if (pp->FadeAmt > 32)
        return;

    if (pp->FadeAmt <= 1)
    {
        pp->FadeAmt = 0;
        pp->StartColor = 0;
        if (pp == Player + screenpeek)
        {
            if (getrendermode() < 3)
                COVERsetbrightness(gs.Brightness,&palette_data[0][0]);
            else
                setpalettefade(0,0,0,0);
            memcpy(pp->temp_pal, palette_data, sizeof(palette_data));
            DoPlayerDivePalette(pp);  // Check Dive again
            DoPlayerNightVisionPalette(pp);  // Check Night Vision again
        }
        return;
    }
    else
    {
        //CON_Message("gamavalues = %d, %d, %d",pp->temp_pal[pp->StartColor],pp->temp_pal[pp->StartColor+1],pp->temp_pal[pp->StartColor+2]);
        for (palreg = 0; palreg < 768; palreg++)
        {
            tmpreg1 = (int)(pp->temp_pal[palreg]) + 2;
            tmpreg2 = (int)(pp->temp_pal[palreg]) - 2;
            if (tmpreg1 > 255)
                tmpreg1 = 255;
            if (tmpreg2 < 0)
                tmpreg2 = 0;

            if (pp->temp_pal[palreg] < pal_ptr[palreg])
            {
                if ((pp->temp_pal[palreg] = tmpreg1) > pal_ptr[palreg])
                    pp->temp_pal[palreg] = pal_ptr[palreg];
            }
            else if (pp->temp_pal[palreg] > pal_ptr[palreg])
                if ((pp->temp_pal[palreg] = tmpreg2) < pal_ptr[palreg])
                    pp->temp_pal[palreg] = pal_ptr[palreg];

        }

        // Only hard set the palette if this is currently the player's view
        if (pp == Player + screenpeek)
        {
            if (getrendermode() < 3) set_pal(pp->temp_pal);
            else
            {
                setpalettefade(
                    palette_data[pp->StartColor][0],
                    palette_data[pp->StartColor][1],
                    palette_data[pp->StartColor][2],
                    faderamp[min(31,max(0,32-abs(pp->FadeAmt)))]
                    );
            }
        }

    }

}

void ResetPalette(PLAYERp pp)
{
    if (getrendermode() < 3)
        COVERsetbrightness(gs.Brightness,&palette_data[0][0]);
    else
        setpalettefade(0,0,0,0);
    memcpy(pp->temp_pal, palette_data, sizeof(palette_data));
    //DoPlayerDivePalette(pp);  // Check Dive again
    //DoPlayerNightVisionPalette(pp);  // Check Night Vision again
    pp->FadeAmt = 0;
    pp->StartColor = 0;
    pp->FadeTics = 0;
}

// vim:ts=4:sw=4:enc=utf-8:


