/* Emacs style mode select   -*- C++ -*- */
/*-----------------------------------------------------------------------------*/

/* $Id:$*/

/* Copyright (C) 1993-1996 by id Software, Inc.*/

/* This source is available for distribution and/or modification*/
/* only under the terms of the DOOM Source Code License as*/
/* published by id Software. All rights reserved.*/

/* The source is distributed in the hope that it will be useful,*/
/* but WITHOUT ANY WARRANTY; without even the implied warranty of*/
/* FITNESS FOR A PARTICULAR PURPOSE. See the DOOM Source Code License*/
/* for more details.*/

/* $Log:$*/

/* DESCRIPTION:*/
/*	DOOM selection menu, options, episode etc.*/
/*	Sliders and icons. Kinda widget stuff.*/

/*-----------------------------------------------------------------------------*/

static const char
rcsid[] = "$Id: m_menu.c,v 1.7 1997/02/03 22:45:10 b1 Exp $";

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>


#include "doomdef.h"
#include "dstrings.h"

#include "d_main.h"

#include "i_system.h"
#include "i_video.h"
#include "z_zone.h"
#include "v_video.h"
#include "w_wad.h"

#include "r_local.h"


#include "hu_stuff.h"
#include "am_map.h"

#include "g_game.h"

#include "m_argv.h"
#include "m_swap.h"

#include "s_sound.h"

#include "doomstat.h"

/* Data.*/
#include "sounds.h"

#include "m_menu.h"


extern boolean altkey; /* c.am_map */


/* defaulted values*/

int			mouseSensitivity;       /* has default*/

/* Show messages has default, 0 = off, 1 = on*/
int			showMessages;


/* Blocky mode, has default, 0 = high, 1 = normal*/
int			detailLevel;
int			screenblocks;		/* has default*/

/* temp for screenblocks (0-9)*/
int			screenSize;

/* -1 = no quicksave slot picked!*/
static int		quickSaveSlot;

 /* 1 = message to be printed*/
static int		messageToPrint;
/* ...and here is the message string!*/
static const char*	messageString;

/* message x & y*/
/*static int		messx;
static int		messy;*/
static int		messageLastMenuActive;

/* timed message = no input from user*/
static boolean		messageNeedsInput;

static void	(*messageRoutine)(int response);

#define SAVESTRINGSIZE 	24

char gammamsg[5][26] =
{
    GAMMALVL0,
    GAMMALVL1,
    GAMMALVL2,
    GAMMALVL3,
    GAMMALVL4
};

/* we are going to be entering a savegame string*/
static int		saveStringEnter;
static int		saveSlot;	/* which slot to save in*/
static int		saveCharIndex;	/* which char we're editing*/
/* old save description before edit*/
static char		saveOldString[SAVESTRINGSIZE];

boolean			inhelpscreens;
boolean			menuactive;
boolean			screenshotPending = false;

#define SKULLXOFF		-32
#define LINEHEIGHT		16

static char	savegamestrings[10][SAVESTRINGSIZE];

static char	endstring[160];



/* MENU TYPEDEFS*/

typedef struct
{
    /* 0 = no cursor here, 1 = ok, 2 = arrows ok*/
    short	status;

    char	name[10];

    /* choice = menu item #.*/
    /* if status = 2,*/
    /*   choice=0:leftarrow,1:rightarrow*/
    void	(*routine)(int choice);

    /* hotkey in menu*/
    char	alphaKey;
} menuitem_t;



typedef struct menu_s
{
    short		numitems;	/* # of menu items*/
    struct menu_s*	prevMenu;	/* previous menu*/
    menuitem_t*		menuitems;	/* menu items*/
    void		(*routine)();	/* draw routine*/
    short		x;
    short		y;		/* x,y of menu*/
    short		lastOn;		/* last item user was on in menu*/
} menu_t;

static short	itemOn;			/* menu item skull is on*/
static short	skullAnimCounter;	/* skull animation counter*/
static short	whichSkull;		/* which skull to draw*/

/* graphic name of skulls*/
/* warning: initializer-string for array of chars is too long*/
static char	skullName[2][/*8*/9] = {"M_SKULL1","M_SKULL2"};

/* current menudef*/
static menu_t*	currentMenu;


#ifdef STATIC_RESOLUTION
#define MENU_ORGX	((SCREENWIDTH - 320) >> 1)
#define MENU_ORGY	((SCREENHEIGHT - 200) >> 1)
#else
static int MENU_ORGX;
static int MENU_ORGY;
#endif


/* PROTOTYPES*/

static void M_NewGame(int choice);
static void M_Episode(int choice);
static void M_ChooseSkill(int choice);
static void M_LoadGame(int choice);
static void M_SaveGame(int choice);
static void M_Options(int choice);
static void M_EndGame(int choice);
static void M_ReadThis(int choice);
static void M_ReadThis2(int choice);
static void M_QuitDOOM(int choice);

static void M_ChangeMessages(int choice);
static void M_ChangeSensitivity(int choice);
static void M_SfxVol(int choice);
static void M_MusicVol(int choice);
static void M_ChangeDetail(int choice);
static void M_SizeDisplay(int choice);
static void M_Sound(int choice);

static void M_FinishReadThis(int choice);
static void M_LoadSelect(int choice);
static void M_SaveSelect(int choice);
static void M_ReadSaveStrings(void);
static void M_QuickSave(void);
static void M_QuickLoad(void);

static void M_DrawMainMenu(void);
static void M_DrawReadThis1(void);
static void M_DrawReadThis2(void);
static void M_DrawNewGame(void);
static void M_DrawEpisode(void);
static void M_DrawOptions(void);
static void M_DrawSound(void);
static void M_DrawLoad(void);
static void M_DrawSave(void);

typedef void m_message_routine(int);

static void M_DrawSaveLoadBorder(int x,int y);
static void M_SetupNextMenu(menu_t *menudef);
static void M_DrawThermo(int x,int y,int thermWidth,int thermDot);
static void M_WriteText(int x, int y, const char *string);
static int  M_StringWidth(const char *string);
static int  M_StringHeight(const char *string);
static void M_StartMessage(const char *string, m_message_routine *routine, boolean input);
static void M_ClearMenus (void);

#if 0
static void M_DrawEmptyCell(const menu_t *menu,int item);
static void M_DrawSelCell(const menu_t *menu,int item);
static void M_StopMessage(void);
#endif





/* DOOM MENU*/

enum
{
    newgame = 0,
    options,
    loadgame,
    savegame,
    readthis,
    quitdoom,
    main_end
} main_e;

static menuitem_t MainMenu[]=
{
    {1,"M_NGAME",M_NewGame,'n'},
    {1,"M_OPTION",M_Options,'o'},
    {1,"M_LOADG",M_LoadGame,'l'},
    {1,"M_SAVEG",M_SaveGame,'s'},
    /* Another hickup with Special edition.*/
    {1,"M_RDTHIS",M_ReadThis,'r'},
    {1,"M_QUITG",M_QuitDOOM,'q'}
};

static menu_t  MainDef =
{
    main_end,
    NULL,
    MainMenu,
    M_DrawMainMenu,
    97,64,
    0
};



/* EPISODE SELECT*/

enum
{
    ep1,
    ep2,
    ep3,
    ep4,
    ep_end
} episodes_e;

static menuitem_t EpisodeMenu[]=
{
    {1,"M_EPI1", M_Episode,'k'},
    {1,"M_EPI2", M_Episode,'t'},
    {1,"M_EPI3", M_Episode,'i'},
    {1,"M_EPI4", M_Episode,'t'}
};

static menu_t  EpiDef =
{
    ep_end,		/* # of menu items*/
    &MainDef,		/* previous menu*/
    EpisodeMenu,	/* menuitem_t ->*/
    M_DrawEpisode,	/* drawing routine ->*/
    48,63,              /* x,y*/
    ep1			/* lastOn*/
};


/* NEW GAME*/

enum
{
    killthings,
    toorough,
    hurtme,
    violence,
    nightmare,
    newg_end
} newgame_e;

static menuitem_t NewGameMenu[]=
{
    {1,"M_JKILL",	M_ChooseSkill, 'i'},
    {1,"M_ROUGH",	M_ChooseSkill, 'h'},
    {1,"M_HURT",	M_ChooseSkill, 'h'},
    {1,"M_ULTRA",	M_ChooseSkill, 'u'},
    {1,"M_NMARE",	M_ChooseSkill, 'n'}
};

static menu_t  NewDef =
{
    newg_end,		/* # of menu items*/
    &EpiDef,		/* previous menu*/
    NewGameMenu,	/* menuitem_t ->*/
    M_DrawNewGame,	/* drawing routine ->*/
    48,63,              /* x,y*/
    hurtme		/* lastOn*/
};




/* OPTIONS MENU*/

enum
{
    endgame,
    messages,
    detail,
    scrnsize,
    option_empty1,
    mousesens,
    option_empty2,
    soundvol,
    opt_end
} options_e;

static menuitem_t OptionsMenu[]=
{
    {1,"M_ENDGAM",	M_EndGame,'e'},
    {1,"M_MESSG",	M_ChangeMessages,'m'},
    {1,"M_DETAIL",	M_ChangeDetail,'g'},
    {2,"M_SCRNSZ",	M_SizeDisplay,'s'},
    {-1,"",0},
    {2,"M_MSENS",	M_ChangeSensitivity,'m'},
    {-1,"",0},
    {1,"M_SVOL",	M_Sound,'s'}
};

static menu_t  OptionsDef =
{
    opt_end,
    &MainDef,
    OptionsMenu,
    M_DrawOptions,
    60,37,
    0
};


/* Read This! MENU 1 & 2*/

enum
{
    rdthsempty1,
    read1_end
} read_e;

static menuitem_t ReadMenu1[] =
{
    {1,"",M_ReadThis2,0}
};

static menu_t  ReadDef1 =
{
    read1_end,
    &MainDef,
    ReadMenu1,
    M_DrawReadThis1,
    280,185,
    0
};

enum
{
    rdthsempty2,
    read2_end
} read_e2;

static menuitem_t ReadMenu2[]=
{
    {1,"",M_FinishReadThis,0}
};

static menu_t  ReadDef2 =
{
    read2_end,
    &ReadDef1,
    ReadMenu2,
    M_DrawReadThis2,
    330,175,
    0
};


/* SOUND VOLUME MENU*/

enum
{
    sfx_vol,
    sfx_empty1,
    music_vol,
    sfx_empty2,
    sound_end
} sound_e;

static menuitem_t SoundMenu[]=
{
    {2,"M_SFXVOL",M_SfxVol,'s'},
    {-1,"",0},
    {2,"M_MUSVOL",M_MusicVol,'m'},
    {-1,"",0}
};

static menu_t  SoundDef =
{
    sound_end,
    &OptionsDef,
    SoundMenu,
    M_DrawSound,
    80,64,
    0
};


/* LOAD GAME MENU*/

enum
{
    load1,
    load2,
    load3,
    load4,
    load5,
    load6,
    load_end
} load_e;

static menuitem_t LoadMenu[]=
{
    {1,"", M_LoadSelect,'1'},
    {1,"", M_LoadSelect,'2'},
    {1,"", M_LoadSelect,'3'},
    {1,"", M_LoadSelect,'4'},
    {1,"", M_LoadSelect,'5'},
    {1,"", M_LoadSelect,'6'}
};

static menu_t  LoadDef =
{
    load_end,
    &MainDef,
    LoadMenu,
    M_DrawLoad,
    80,54,
    0
};


/* SAVE GAME MENU*/

static menuitem_t SaveMenu[]=
{
    {1,"", M_SaveSelect,'1'},
    {1,"", M_SaveSelect,'2'},
    {1,"", M_SaveSelect,'3'},
    {1,"", M_SaveSelect,'4'},
    {1,"", M_SaveSelect,'5'},
    {1,"", M_SaveSelect,'6'}
};

static menu_t  SaveDef =
{
    load_end,
    &MainDef,
    SaveMenu,
    M_DrawSave,
    80,54,
    0
};



/* M_ReadSaveStrings*/
/*  read the strings from the savegame files*/

static void M_ReadSaveStrings(void)
{
    FILE*		handle;
    int			count;
    int			i;
    char		name[256];

    for (i = 0;i < load_end;i++)
    {
        const char*	gamepath = getenv(GamePathVar);

	if (gamepath == NULL)
	{
	    if (M_CheckParm("-cdrom"))
        	sprintf(name, "%s"DIRSEPSTR SAVEGAMENAME"%d"EXTSEPSTR SAVEGAMEEXT, I_GetCDSaveDir(), i);
            else
        	sprintf(name, "%s"SAVEGAMENAME"%d"EXTSEPSTR SAVEGAMEEXT, DefaultGamePath, i);
	}
	else
	{
	    sprintf(name, "%s"SAVEGAMENAME"%d"EXTSEPSTR SAVEGAMEEXT, gamepath, i);
	}

	handle = fopen(name, "rb");
	if (handle == NULL)
	{
	    strcpy(&savegamestrings[i][0],EMPTYSTRING);
	    LoadMenu[i].status = 0;
	    continue;
	}
        count = fread((void*)&savegamestrings[i], 1, SAVESTRINGSIZE, handle);
	fclose(handle);
	LoadMenu[i].status = 1;
    }
}



/* M_LoadGame & Cie.*/

static void M_DrawLoad(void)
{
    int             i;

    V_DrawPatchDirect (MENU_ORGX+72,MENU_ORGY+28,0,W_CacheLumpName("M_LOADG",PU_CACHE));
    for (i = 0;i < load_end; i++)
    {
	M_DrawSaveLoadBorder(LoadDef.x,LoadDef.y+LINEHEIGHT*i);
	M_WriteText(LoadDef.x,LoadDef.y+LINEHEIGHT*i,savegamestrings[i]);
    }
}




/* Draw border for the savegame description*/

static void M_DrawSaveLoadBorder(int x,int y)
{
    int             i;

    V_DrawPatchDirect (MENU_ORGX+x-8,MENU_ORGY+y+7,0,W_CacheLumpName("M_LSLEFT",PU_CACHE));

    for (i = 0;i < 24;i++)
    {
	V_DrawPatchDirect (MENU_ORGX+x,MENU_ORGY+y+7,0,W_CacheLumpName("M_LSCNTR",PU_CACHE));
	x += 8;
    }

    V_DrawPatchDirect (MENU_ORGX+x,MENU_ORGY+y+7,0,W_CacheLumpName("M_LSRGHT",PU_CACHE));
}




/* User wants to load this game*/

static void M_LoadSelect(int choice)
{
    char	name[256];
    const char*	gamepath = getenv(GamePathVar);


    if (gamepath == NULL)
    {
	if (M_CheckParm("-cdrom"))
	    sprintf(name, "%s"DIRSEPSTR SAVEGAMENAME"%d"EXTSEPSTR SAVEGAMEEXT, I_GetCDSaveDir(), choice);
	else
	    sprintf(name, "%s"SAVEGAMENAME"%d"EXTSEPSTR SAVEGAMEEXT, DefaultGamePath, choice);
    }
    else
    {
	sprintf(name, "%s"SAVEGAMENAME"%d"EXTSEPSTR SAVEGAMEEXT, gamepath, choice);
    }

    G_LoadGame (name);
    M_ClearMenus ();
}


/* Selected from DOOM menu*/

static void M_LoadGame (int choice)
{
    if (netgame)
    {
	M_StartMessage(LOADNET,NULL,false);
	return;
    }

    M_SetupNextMenu(&LoadDef);
    M_ReadSaveStrings();
}



/*  M_SaveGame & Cie.*/

static void M_DrawSave(void)
{
    int             i;

    V_DrawPatchDirect (MENU_ORGX+72,MENU_ORGY+28,0,W_CacheLumpName("M_SAVEG",PU_CACHE));
    for (i = 0;i < load_end; i++)
    {
	M_DrawSaveLoadBorder(LoadDef.x,LoadDef.y+LINEHEIGHT*i);
	M_WriteText(LoadDef.x,LoadDef.y+LINEHEIGHT*i,savegamestrings[i]);
    }

    if (saveStringEnter)
    {
	i = M_StringWidth(savegamestrings[saveSlot]);
	M_WriteText(LoadDef.x + i,LoadDef.y+LINEHEIGHT*saveSlot,"_");
    }
}


/* M_Responder calls this when user is finished*/

static void M_DoSave(int slot)
{
    G_SaveGame (slot,savegamestrings[slot]);
    M_ClearMenus ();

    /* PICK QUICKSAVE SLOT YET?*/
    if (quickSaveSlot == -2)
	quickSaveSlot = slot;
}


/* User wants to save. Start string input for M_Responder*/

static void M_SaveSelect(int choice)
{
    /* we are going to be intercepting all chars*/
    saveStringEnter = 1;

    saveSlot = choice;
    strcpy(saveOldString,savegamestrings[choice]);
    if (!strcmp(savegamestrings[choice],EMPTYSTRING))
	savegamestrings[choice][0] = 0;
    saveCharIndex = strlen(savegamestrings[choice]);
}


/* Selected from DOOM menu*/

static void M_SaveGame (int choice)
{
    if (!usergame)
    {
	M_StartMessage(SAVEDEAD,NULL,false);
	return;
    }

    if (gamestate != GS_LEVEL)
	return;

    M_SetupNextMenu(&SaveDef);
    M_ReadSaveStrings();
}




/*      M_QuickSave*/

static char    tempstring[80];

static void M_QuickSaveResponse(int ch)
{
    if (ch == 'y')
    {
	M_DoSave(quickSaveSlot);
	S_StartSound(NULL,sfx_swtchx);
    }
}

static void M_QuickSave(void)
{
    if (!usergame)
    {
	S_StartSound(NULL,sfx_oof);
	return;
    }

    if (gamestate != GS_LEVEL)
	return;

    if (quickSaveSlot < 0)
    {
	M_StartControlPanel();
	M_ReadSaveStrings();
	M_SetupNextMenu(&SaveDef);
	quickSaveSlot = -2;	/* means to pick a slot now*/
	return;
    }
    sprintf(tempstring,QSPROMPT,savegamestrings[quickSaveSlot]);
    M_StartMessage(tempstring,M_QuickSaveResponse,true);
}




/* M_QuickLoad*/

static void M_QuickLoadResponse(int ch)
{
    if (ch == 'y')
    {
	M_LoadSelect(quickSaveSlot);
	S_StartSound(NULL,sfx_swtchx);
    }
}


static void M_QuickLoad(void)
{
    if (netgame)
    {
	M_StartMessage(QLOADNET,NULL,false);
	return;
    }

    if (quickSaveSlot < 0)
    {
	M_StartMessage(QSAVESPOT,NULL,false);
	return;
    }
    sprintf(tempstring,QLPROMPT,savegamestrings[quickSaveSlot]);
    M_StartMessage(tempstring,M_QuickLoadResponse,true);
}





/* Read This Menus*/
/* Had a "quick hack to fix romero bug"*/

static void M_DrawReadThis1(void)
{
    inhelpscreens = true;
    switch ( gamemode )
    {
      case commercial:
	V_DrawPatchDirect (MENU_ORGX,MENU_ORGY,0,W_CacheLumpName("HELP",PU_CACHE));
	break;
      case shareware:
      case registered:
      case retail:
	V_DrawPatchDirect (MENU_ORGX,MENU_ORGY,0,W_CacheLumpName("HELP1",PU_CACHE));
	break;
      default:
	break;
    }
    return;
}




/* Read This Menus - optional second page.*/

static void M_DrawReadThis2(void)
{
    inhelpscreens = true;
    switch ( gamemode )
    {
      case retail:
      case commercial:
	/* This hack keeps us from having to change menus.*/
	V_DrawPatchDirect (MENU_ORGX,MENU_ORGY,0,W_CacheLumpName("CREDIT",PU_CACHE));
	break;
      case shareware:
      case registered:
	V_DrawPatchDirect (MENU_ORGX,MENU_ORGY,0,W_CacheLumpName("HELP2",PU_CACHE));
	break;
      default:
	break;
    }
    return;
}



/* Change Sfx & Music volumes*/

static void M_DrawSound(void)
{
    V_DrawPatchDirect (MENU_ORGX+60,MENU_ORGY+38,0,W_CacheLumpName("M_SVOL",PU_CACHE));

    M_DrawThermo(SoundDef.x,SoundDef.y+LINEHEIGHT*(sfx_vol+1),
		 16,snd_SfxVolume);

    M_DrawThermo(SoundDef.x,SoundDef.y+LINEHEIGHT*(music_vol+1),
		 16,snd_MusicVolume);
}

static void M_Sound(int choice)
{
    M_SetupNextMenu(&SoundDef);
}

static void M_SfxVol(int choice)
{
    switch(choice)
    {
      case 0:
	if (snd_SfxVolume)
	    snd_SfxVolume--;
	break;
      case 1:
	if (snd_SfxVolume < 15)
	    snd_SfxVolume++;
	break;
    }

    S_SetSfxVolume(snd_SfxVolume /* *8 */);
}

static void M_MusicVol(int choice)
{
    switch(choice)
    {
      case 0:
	if (snd_MusicVolume)
	    snd_MusicVolume--;
	break;
      case 1:
	if (snd_MusicVolume < 15)
	    snd_MusicVolume++;
	break;
    }

    S_SetMusicVolume(snd_MusicVolume /* *8 */);
}





/* M_DrawMainMenu*/

static void M_DrawMainMenu(void)
{
    V_DrawPatchDirect (MENU_ORGX+94,MENU_ORGY+2,0,W_CacheLumpName("M_DOOM",PU_CACHE));
}





/* M_NewGame*/

static void M_DrawNewGame(void)
{
    V_DrawPatchDirect (MENU_ORGX+96,MENU_ORGY+14,0,W_CacheLumpName("M_NEWG",PU_CACHE));
    V_DrawPatchDirect (MENU_ORGX+54,MENU_ORGY+38,0,W_CacheLumpName("M_SKILL",PU_CACHE));
}

static void M_NewGame(int choice)
{
    if (netgame && !demoplayback)
    {
	M_StartMessage(NEWGAME,NULL,false);
	return;
    }

    if ( gamemode == commercial )
	M_SetupNextMenu(&NewDef);
    else
	M_SetupNextMenu(&EpiDef);
}



/*      M_Episode*/

static int     epi;

static void M_DrawEpisode(void)
{
    V_DrawPatchDirect (MENU_ORGX+54,MENU_ORGY+38,0,W_CacheLumpName("M_EPISOD",PU_CACHE));
}

static void M_VerifyNightmare(int ch)
{
    if (ch != 'y')
	return;

    G_DeferedInitNew(nightmare,epi+1,1);
    M_ClearMenus ();
}

static void M_ChooseSkill(int choice)
{
    if (choice == nightmare)
    {
	M_StartMessage(NIGHTMARE,M_VerifyNightmare,true);
	return;
    }

    G_DeferedInitNew(choice,epi+1,1);
    M_ClearMenus ();
}

static void M_Episode(int choice)
{
    if ( (gamemode == shareware)
	 && choice)
    {
	M_StartMessage(SWSTRING,NULL,false);
	M_SetupNextMenu(&ReadDef1);
	return;
    }

    /* Yet another hack...*/
    if ( (gamemode == registered)
	 && (choice > 2))
    {
      fprintf( logfile,
	       "M_Episode: 4th episode requires UltimateDOOM\n");
      choice = 0;
    }

    epi = choice;
    M_SetupNextMenu(&NewDef);
}




/* M_Options*/

static char	detailNames[2][9]	= {"M_GDHIGH","M_GDLOW"};
static char	msgNames[2][9]		= {"M_MSGOFF","M_MSGON"};


static void M_DrawOptions(void)
{
    V_DrawPatchDirect (MENU_ORGX+108,MENU_ORGY+15,0,W_CacheLumpName("M_OPTTTL",PU_CACHE));

    V_DrawPatchDirect (MENU_ORGX+OptionsDef.x+175,MENU_ORGY+OptionsDef.y+LINEHEIGHT*detail,0,
		       W_CacheLumpName(detailNames[detailLevel],PU_CACHE));

    V_DrawPatchDirect (MENU_ORGX+OptionsDef.x+120,MENU_ORGY+OptionsDef.y+LINEHEIGHT*messages,0,
		       W_CacheLumpName(msgNames[showMessages],PU_CACHE));

    M_DrawThermo(OptionsDef.x,OptionsDef.y+LINEHEIGHT*(mousesens+1),
		 10,mouseSensitivity);

    M_DrawThermo(OptionsDef.x,OptionsDef.y+LINEHEIGHT*(scrnsize+1),
		 9,screenSize);
}

static void M_Options(int choice)
{
    M_SetupNextMenu(&OptionsDef);
}




/*      Toggle messages on/off*/

static void M_ChangeMessages(int choice)
{
    /* warning: unused parameter `int choice'*/
    choice = 0;
    showMessages = 1 - showMessages;

    if (!showMessages)
	players[consoleplayer].message = MSGOFF;
    else
	players[consoleplayer].message = MSGON ;

    message_dontfuckwithme = true;
}



/* M_EndGame*/

static void M_EndGameResponse(int ch)
{
    if (ch != 'y')
	return;

    currentMenu->lastOn = itemOn;
    M_ClearMenus ();
    D_StartTitle ();
}

static void M_EndGame(int choice)
{
    choice = 0;
    if (!usergame)
    {
	S_StartSound(NULL,sfx_oof);
	return;
    }

    if (netgame)
    {
	M_StartMessage(NETEND,NULL,false);
	return;
    }

    M_StartMessage(ENDGAME,M_EndGameResponse,true);
}





/* M_ReadThis*/

static void M_ReadThis(int choice)
{
    choice = 0;
    M_SetupNextMenu(&ReadDef1);
}

static void M_ReadThis2(int choice)
{
    choice = 0;
    M_SetupNextMenu(&ReadDef2);
}

static void M_FinishReadThis(int choice)
{
    choice = 0;
    M_SetupNextMenu(&MainDef);
}





/* M_QuitDOOM*/

static int	quitsounds[8] =
{
    sfx_pldeth,
    sfx_dmpain,
    sfx_popain,
    sfx_slop,
    sfx_telept,
    sfx_posit1,
    sfx_posit3,
    sfx_sgtatk
};

static int	quitsounds2[8] =
{
    sfx_vilact,
    sfx_getpow,
    sfx_boscub,
    sfx_slop,
    sfx_skeswg,
    sfx_kntdth,
    sfx_bspact,
    sfx_sgtatk
};



static void M_QuitResponse(int ch)
{
    if (ch != 'y')
	return;
    if (!netgame)
    {
	if (gamemode == commercial)
	    S_StartSound(NULL,quitsounds2[(gametic>>2)&7]);
	else
	    S_StartSound(NULL,quitsounds[(gametic>>2)&7]);
	I_WaitVBL(105);
    }
    I_Quit ();
}




static void M_QuitDOOM(int choice)
{
  /* We pick index 0 which is language sensitive,*/
  /*  or one at random, between 1 and maximum number.*/
  if (language != english )
    sprintf(endstring,"%s\n\n"DOSY, endmsg[0] );
  else
  {
    int msg;

    switch (gamemission)
    {
      case doom: msg = 1 + (gametic%7); break;
      case doom2: msg = 8 + (gametic%7); break;
      case pack_tnt:
      case pack_plut: msg = 15 + (gametic%7); break;
      default: msg = 0; break;
    }
    sprintf(endstring,"%s\n\n"DOSY, endmsg[ msg ]);
  }
  M_StartMessage(endstring,M_QuitResponse,true);
}




static void M_ChangeSensitivity(int choice)
{
    switch(choice)
    {
      case 0:
	if (mouseSensitivity)
	    mouseSensitivity--;
	break;
      case 1:
	if (mouseSensitivity < 9)
	    mouseSensitivity++;
	break;
    }
}




static void M_ChangeDetail(int choice)
{
    choice = 0;
    detailLevel = 1 - detailLevel;

    /* FIXME - does not work. Remove anyway?*/
    fprintf( logfile, "M_ChangeDetail: low detail mode n.a.\n");

    /*return;*/

    R_SetViewSize (screenblocks, detailLevel);

    if (!detailLevel)
	players[consoleplayer].message = DETAILHI;
    else
	players[consoleplayer].message = DETAILLO;
}




static void M_SizeDisplay(int choice)
{
    switch(choice)
    {
      case 0:
	if (screenSize > 0)
	{
	    screenblocks--;
	    screenSize--;
	}
	break;
      case 1:
	if (screenSize < 8)
	{
	    screenblocks++;
	    screenSize++;
	}
	break;
    }


    R_SetViewSize (screenblocks, detailLevel);
}





/*      Menu Functions*/

static void
M_DrawThermo
( int	x,
  int	y,
  int	thermWidth,
  int	thermDot )
{
    int		xx;
    int		i;

    xx = x;
    V_DrawPatchDirect (MENU_ORGX+xx,MENU_ORGY+y,0,W_CacheLumpName("M_THERML",PU_CACHE));
    xx += 8;
    for (i=0;i<thermWidth;i++)
    {
	V_DrawPatchDirect (MENU_ORGX+xx,MENU_ORGY+y,0,W_CacheLumpName("M_THERMM",PU_CACHE));
	xx += 8;
    }
    V_DrawPatchDirect (MENU_ORGX+xx,MENU_ORGY+y,0,W_CacheLumpName("M_THERMR",PU_CACHE));

    V_DrawPatchDirect (MENU_ORGX + (x+8) + thermDot*8, MENU_ORGY + y,
		       0,W_CacheLumpName("M_THERMO",PU_CACHE));
}



#if 0
static void
M_DrawEmptyCell
( const menu_t*	menu,
  int		item )
{
    V_DrawPatchDirect (MENU_ORGX+ menu->x - 10, MENU_ORGY + menu->y+item*LINEHEIGHT - 1, 0,
		       W_CacheLumpName("M_CELL1",PU_CACHE));
}

static void
M_DrawSelCell
( const menu_t*	menu,
  int		item )
{
    V_DrawPatchDirect (MENU_ORGX + menu->x - 10, MENU_ORGY + menu->y+item*LINEHEIGHT - 1, 0,
		       W_CacheLumpName("M_CELL2",PU_CACHE));
}

static void M_StopMessage(void)
{
    menuactive = messageLastMenuActive;
    messageToPrint = 0;
}
#endif



static void
M_StartMessage
( const char*		string,
  m_message_routine*	routine,
  boolean		input )
{
    messageLastMenuActive = menuactive;
    messageToPrint = 1;
    messageString = string;
    messageRoutine = routine;
    messageNeedsInput = input;
    menuactive = true;
    return;
}




/* Find string width from hu_font chars*/

static int M_StringWidth(const char* string)
{
    int             i;
    int             w = 0;
    int             c;

    for (i = 0;i < strlen(string);i++)
    {
	c = toupper(string[i]) - HU_FONTSTART;
	if (c < 0 || c >= HU_FONTSIZE)
	    w += 4;
	else
	    w += SHORT (hu_font[c]->width);
    }

    return w;
}




/*      Find string height from hu_font chars*/

static int M_StringHeight(const char* string)
{
    int             i;
    int             h;
    int             height = SHORT(hu_font[0]->height);

    h = height;
    for (i = 0;i < strlen(string);i++)
	if (string[i] == '\n')
	    h += height;

    return h;
}



/*      Write a string using the hu_font*/

static void
M_WriteText
( int		x,
  int		y,
  const char*	string)
{
    int		w;
    const char*	ch;
    int		c;
    int		cx;
    int		cy;


    ch = string;
    cx = x;
    cy = y;

    while(1)
    {
	c = *ch++;
	if (!c)
	    break;
	if (c == '\n')
	{
	    cx = x;
	    cy += 12;
	    continue;
	}

	c = toupper(c) - HU_FONTSTART;
	if (c < 0 || c>= HU_FONTSIZE)
	{
	    cx += 4;
	    continue;
	}

	w = SHORT (hu_font[c]->width);
	if (cx+w > SCREENWIDTH)
	    break;
	V_DrawPatchDirect(MENU_ORGX + cx, MENU_ORGY + cy, 0, hu_font[c]);
	cx+=w;
    }
}




/* CONTROL PANEL*/



/* M_Responder*/

boolean M_Responder (const event_t* ev)
{
    int             ch;
    int             i;
    static  int     joywait = 0;
    static  int     mousewait = 0;
    static  int     mousey = 0;
    static  int     lasty = 0;
    static  int     mousex = 0;
    static  int     lastx = 0;

    ch = -1;

    if (ev->type == ev_joystick && joywait < I_GetTime())
    {
	if (ev->data3 == -1)
	{
	    ch = KEY_UPARROW;
	    joywait = I_GetTime() + 5;
	}
	else if (ev->data3 == 1)
	{
	    ch = KEY_DOWNARROW;
	    joywait = I_GetTime() + 5;
	}

	if (ev->data2 == -1)
	{
	    ch = KEY_LEFTARROW;
	    joywait = I_GetTime() + 2;
	}
	else if (ev->data2 == 1)
	{
	    ch = KEY_RIGHTARROW;
	    joywait = I_GetTime() + 2;
	}

	if (ev->data1&1)
	{
	    ch = KEY_ENTER;
	    joywait = I_GetTime() + 5;
	}
	if (ev->data1&2)
	{
	    ch = KEY_BACKSPACE;
	    joywait = I_GetTime() + 5;
	}
    }
    else
    {
	if (ev->type == ev_mouse && mousewait < I_GetTime())
	{
	    mousey += ev->data3;
	    if (mousey < lasty-30)
	    {
		ch = KEY_DOWNARROW;
		mousewait = I_GetTime() + 5;
		mousey = lasty -= 30;
	    }
	    else if (mousey > lasty+30)
	    {
		ch = KEY_UPARROW;
		mousewait = I_GetTime() + 5;
		mousey = lasty += 30;
	    }

	    mousex += ev->data2;
	    if (mousex < lastx-30)
	    {
		ch = KEY_LEFTARROW;
		mousewait = I_GetTime() + 5;
		mousex = lastx -= 30;
	    }
	    else if (mousex > lastx+30)
	    {
		ch = KEY_RIGHTARROW;
		mousewait = I_GetTime() + 5;
		mousex = lastx += 30;
	    }

	    if (ev->data1&1)
	    {
		ch = KEY_ENTER;
		mousewait = I_GetTime() + 15;
	    }

	    if (ev->data1&2)
	    {
		ch = KEY_BACKSPACE;
		mousewait = I_GetTime() + 15;
	    }
	}
	else
	    if (ev->type == ev_keydown)
	    {
		ch = ev->data1;
	    }
    }

    if (ch == -1)
	return false;


    /* Save Game string input*/
    if (saveStringEnter)
    {
	switch(ch)
	{
	  case KEY_BACKSPACE:
	    if (saveCharIndex > 0)
	    {
		saveCharIndex--;
		savegamestrings[saveSlot][saveCharIndex] = 0;
	    }
	    break;

	  case KEY_ESCAPE:
	    saveStringEnter = 0;
	    strcpy(&savegamestrings[saveSlot][0],saveOldString);
	    break;

	  case KEY_ENTER:
	    saveStringEnter = 0;
	    if (savegamestrings[saveSlot][0])
		M_DoSave(saveSlot);
	    break;

	  default:
	    ch = toupper(ch);
	    if (ch != 32)
		if (ch-HU_FONTSTART < 0 || ch-HU_FONTSTART >= HU_FONTSIZE)
		    break;
	    if (ch >= 32 && ch <= 127 &&
		saveCharIndex < SAVESTRINGSIZE-1 &&
		M_StringWidth(savegamestrings[saveSlot]) <
		(SAVESTRINGSIZE-2)*8)
	    {
		savegamestrings[saveSlot][saveCharIndex++] = ch;
		savegamestrings[saveSlot][saveCharIndex] = 0;
	    }
	    break;
	}
	return true;
    }

    /* Take care of any messages that need input*/
    if (messageToPrint)
    {
	if (messageNeedsInput == true &&
	    !(ch == ' ' || ch == 'n' || ch == 'y' || ch == KEY_ESCAPE))
	    return false;

	menuactive = messageLastMenuActive;
	messageToPrint = 0;
	if (messageRoutine)
	    messageRoutine(ch);

	menuactive = false;
	S_StartSound(NULL,sfx_swtchx);
	return true;
    }

    if (devparm && ch == KEY_F1)
    {
	G_ScreenShot ();
	return true;
    }


    /* F-Keys*/
    if (!menuactive)
	switch(ch)
	{
	  case KEY_MINUS:         /* Screen size down*/
	    if (chat_on || (AM_IsMapKey(ch) && (automapactive || (mixmap && altkey))))
		return false;
	    M_SizeDisplay(0);
	    S_StartSound(NULL,sfx_stnmov);
	    return true;

	  case KEY_EQUALS:        /* Screen size up*/
	  case KEY_PLUS:
	    if (chat_on || (AM_IsMapKey(ch) && (automapactive || (mixmap && altkey))))
		return false;
	    M_SizeDisplay(1);
	    S_StartSound(NULL,sfx_stnmov);
	    return true;

	  case KEY_F1:            /* Help key*/
	    M_StartControlPanel ();

	    if ( gamemode == retail )
	      currentMenu = &ReadDef2;
	    else
	      currentMenu = &ReadDef1;

	    itemOn = 0;
	    S_StartSound(NULL,sfx_swtchn);
	    return true;

	  case KEY_F2:            /* Save*/
	    M_StartControlPanel();
	    S_StartSound(NULL,sfx_swtchn);
	    M_SaveGame(0);
	    return true;

	  case KEY_F3:            /* Load*/
	    M_StartControlPanel();
	    S_StartSound(NULL,sfx_swtchn);
	    M_LoadGame(0);
	    return true;

	  case KEY_F4:            /* Sound Volume*/
	    M_StartControlPanel ();
	    currentMenu = &SoundDef;
	    itemOn = sfx_vol;
	    S_StartSound(NULL,sfx_swtchn);
	    return true;

	  case KEY_F5:            /* Detail toggle*/
	    M_ChangeDetail(0);
	    S_StartSound(NULL,sfx_swtchn);
	    return true;

	  case KEY_F6:            /* Quicksave*/
	    S_StartSound(NULL,sfx_swtchn);
	    M_QuickSave();
	    return true;

	  case KEY_F7:            /* End game*/
	    S_StartSound(NULL,sfx_swtchn);
	    M_EndGame(0);
	    return true;

	  case KEY_F8:            /* Toggle messages*/
	    M_ChangeMessages(0);
	    S_StartSound(NULL,sfx_swtchn);
	    return true;

	  case KEY_F9:            /* Quickload*/
	    S_StartSound(NULL,sfx_swtchn);
	    M_QuickLoad();
	    return true;

	  case KEY_F10:           /* Quit DOOM*/
	    S_StartSound(NULL,sfx_swtchn);
	    M_QuitDOOM(0);
	    return true;

	  case KEY_F11:           /* gamma toggle*/
	    usegamma++;
	    if (usegamma > 4)
		usegamma = 0;
	    players[consoleplayer].message = gammamsg[usegamma];
	    I_SetPalette (W_CacheLumpName ("PLAYPAL",PU_CACHE));
	    return true;
          case KEY_PRINT:
	    screenshotPending = true;
	    return true;
	}


    /* Pop-up menu?*/
    if (!menuactive)
    {
	if (ch == KEY_ESCAPE)
	{
	    M_StartControlPanel ();
	    S_StartSound(NULL,sfx_swtchn);
	    return true;
	}
	return false;
    }


    /* Keys usable within menu*/
    switch (ch)
    {
      case KEY_DOWNARROW:
	do
	{
	    if (itemOn+1 > currentMenu->numitems-1)
		itemOn = 0;
	    else itemOn++;
	    S_StartSound(NULL,sfx_pstop);
	} while(currentMenu->menuitems[itemOn].status==-1);
	return true;

      case KEY_UPARROW:
	do
	{
	    if (!itemOn)
		itemOn = currentMenu->numitems-1;
	    else itemOn--;
	    S_StartSound(NULL,sfx_pstop);
	} while(currentMenu->menuitems[itemOn].status==-1);
	return true;

      case KEY_LEFTARROW:
	if (currentMenu->menuitems[itemOn].routine &&
	    currentMenu->menuitems[itemOn].status == 2)
	{
	    S_StartSound(NULL,sfx_stnmov);
	    currentMenu->menuitems[itemOn].routine(0);
	}
	return true;

      case KEY_RIGHTARROW:
	if (currentMenu->menuitems[itemOn].routine &&
	    currentMenu->menuitems[itemOn].status == 2)
	{
	    S_StartSound(NULL,sfx_stnmov);
	    currentMenu->menuitems[itemOn].routine(1);
	}
	return true;

      case KEY_ENTER:
	if (currentMenu->menuitems[itemOn].routine &&
	    currentMenu->menuitems[itemOn].status)
	{
	    currentMenu->lastOn = itemOn;
	    if (currentMenu->menuitems[itemOn].status == 2)
	    {
		currentMenu->menuitems[itemOn].routine(1);      /* right arrow*/
		S_StartSound(NULL,sfx_stnmov);
	    }
	    else
	    {
		currentMenu->menuitems[itemOn].routine(itemOn);
		S_StartSound(NULL,sfx_pistol);
	    }
	}
	return true;

      case KEY_ESCAPE:
	currentMenu->lastOn = itemOn;
	M_ClearMenus ();
	S_StartSound(NULL,sfx_swtchx);
	return true;

      case KEY_BACKSPACE:
	currentMenu->lastOn = itemOn;
	if (currentMenu->prevMenu)
	{
	    currentMenu = currentMenu->prevMenu;
	    itemOn = currentMenu->lastOn;
	    S_StartSound(NULL,sfx_swtchn);
	}
	return true;

      default:
	for (i = itemOn+1;i < currentMenu->numitems;i++)
	    if (currentMenu->menuitems[i].alphaKey == ch)
	    {
		itemOn = i;
		S_StartSound(NULL,sfx_pstop);
		return true;
	    }
	for (i = 0;i <= itemOn;i++)
	    if (currentMenu->menuitems[i].alphaKey == ch)
	    {
		itemOn = i;
		S_StartSound(NULL,sfx_pstop);
		return true;
	    }
	break;

    }

    return false;
}




/* M_StartControlPanel*/

void M_StartControlPanel (void)
{
    /* intro might call this repeatedly*/
    if (menuactive)
	return;

    menuactive = 1;
    currentMenu = &MainDef;         /* JDC*/
    itemOn = currentMenu->lastOn;   /* JDC*/
}



/* M_Drawer*/
/* Called after the view has been rendered,*/
/* but before it has been blitted.*/

void M_Drawer (void)
{
    static short	x;
    static short	y;
    short		i;
    short		max;
    char		string[40];
    int			start;

    inhelpscreens = false;


    /* Horiz. & Vertically center string and print it.*/
    if (messageToPrint)
    {
	start = 0;
	y = 100 - M_StringHeight(messageString)/2;
	while(*(messageString+start))
	{
	    for (i = 0;i < strlen(messageString+start);i++)
		if (*(messageString+start+i) == '\n')
		{
		    memset(string,0,40);
		    strncpy(string,messageString+start,i);
		    start += i+1;
		    break;
		}

	    if (i == strlen(messageString+start))
	    {
		strcpy(string,messageString+start);
		start += i;
	    }

	    x = 160 - M_StringWidth(string)/2;
	    M_WriteText(x,y,string);
	    y += SHORT(hu_font[0]->height);
	}
	return;
    }

    if (!menuactive)
	return;

    if (currentMenu->routine)
	currentMenu->routine();         /* call Draw routine*/

    /* DRAW MENU*/
    x = currentMenu->x;
    y = currentMenu->y;
    max = currentMenu->numitems;

    for (i=0;i<max;i++)
    {
	if (currentMenu->menuitems[i].name[0])
	    V_DrawPatchDirect (MENU_ORGX + x, MENU_ORGY + y,0,
			       W_CacheLumpName(currentMenu->menuitems[i].name ,PU_CACHE));
	y += LINEHEIGHT;
    }


    /* DRAW SKULL*/
    V_DrawPatchDirect(MENU_ORGX+x+SKULLXOFF, MENU_ORGY+currentMenu->y-5+itemOn*LINEHEIGHT, 0,
		      W_CacheLumpName(skullName[whichSkull],PU_CACHE));

}



/* M_ClearMenus*/

static void M_ClearMenus (void)
{
    menuactive = 0;
    /* if (!netgame && usergame && paused)*/
    /*       sendpause = true;*/
}





/* M_SetupNextMenu*/

static void M_SetupNextMenu(menu_t *menudef)
{
    currentMenu = menudef;
    itemOn = currentMenu->lastOn;
}



/* M_Ticker*/

void M_Ticker (void)
{
    if (--skullAnimCounter <= 0)
    {
	whichSkull ^= 1;
	skullAnimCounter = 8;
    }
}



/* M_Init*/

void M_Init (void)
{
    currentMenu = &MainDef;
    menuactive = 0;
    itemOn = currentMenu->lastOn;
    whichSkull = 0;
    skullAnimCounter = 10;
    screenSize = screenblocks - 3;
    messageToPrint = 0;
    messageString = NULL;
    messageLastMenuActive = menuactive;
    quickSaveSlot = -1;

    /* Here we could catch other version dependencies,*/
    /*  like HELP1/2, and four episodes.*/


    switch ( gamemode )
    {
      case commercial:
	/* This is used because DOOM 2 had only one HELP*/
        /*  page. I use CREDIT as second page now, but*/
	/*  kept this hack for educational purposes.*/
	MainMenu[readthis] = MainMenu[quitdoom];
	MainDef.numitems--;
	MainDef.y += 8;
	NewDef.prevMenu = &MainDef;
	ReadDef1.routine = M_DrawReadThis1;
	ReadDef1.x = 330;
	ReadDef1.y = 165;
	ReadMenu1[0].routine = M_FinishReadThis;
	break;
      case shareware:
	/* Episode 2 and 3 are handled,*/
	/*  branching to an ad screen.*/
      case registered:
	/* We need to remove the fourth episode.*/
	EpiDef.numitems--;
	break;
      case retail:
	/* We are fine.*/
      default:
	break;
    }

}


#ifndef STATIC_RESOLUTION
void M_initForResolution(void)
{
  MENU_ORGX = (SCREENWIDTH - 320) >> 1;
  MENU_ORGY = (SCREENHEIGHT - 200) >> 1;
}
#endif



/* for strict ANSI conformity */
void M_InitMenuData(void)
{
}
