#include "quakedef.h" #include "colormatrix.h" enum { MAIN_ITEMS = 5, SINGLEPLAYER_ITEMS = 3, MULTIPLAYER_ITEMS = 3, NUM_SETUP_CMDS = 5, OPTIONS_ITEMS = 12, SLIDER_RANGE = 10, NUM_HELP_PAGES = 6, NUM_LANCONFIG_CMDS = 2, NUM_GAMEOPTIONS = 9, }; char savs[Nsav][Nsavcm]; int savcanld[Nsav]; int m_return_state; bool m_return_onerror; char m_return_reason[32]; static int m_save_demonum; static byte identityTable[256]; static byte translationTable[256]; static int m_main_cursor; static int m_singleplayer_cursor; static int load_cursor; // 0 < load_cursor < Nsav static int m_multiplayer_cursor; static int setup_cursor = 4; static int setup_cursor_table[] = {40, 56, 80, 104, 140}; static char setup_hostname[16]; static char setup_myname[16]; static int setup_oldtop; static int setup_oldbottom; static int setup_top; static int setup_bottom; static bool m_entersound; // play after drawing a frame, so caching won't disrupt the sound static bool m_recursiveDraw; static int options_cursor; static int help_page; static int msgNumber; static int m_quit_prevstate; static bool wasInMenus; static int lanConfig_cursor = -1; static int lanConfig_cursor_table[] = {72, 92}; static char lanConfig_portname[8]; static char lanConfig_joinname[22]; static int startepisode; static int startlevel; static int maxplayers; static bool m_serverInfoMessage = false; static double m_serverInfoMessageTime; static int gameoptions_cursor_table[] = {40, 56, 64, 72, 80, 88, 96, 112, 120}; static int gameoptions_cursor; static int keys_cursor; static int bind_grab; #define StartingGame (m_multiplayer_cursor == 1) #define JoiningGame (m_multiplayer_cursor == 0) enum { m_none, m_main, m_singleplayer, m_load, m_save, m_multiplayer, m_setup, m_options, m_keys, m_help, m_quit, m_lanconfig, m_gameoptions, } m_state; void M_Menu_Main_f(cmd_t *c); static void M_Menu_SinglePlayer_f(cmd_t *c); static void M_Menu_Load_f(cmd_t *c); static void M_Menu_Save_f(cmd_t *c); static void M_Menu_MultiPlayer_f(cmd_t *c); static void M_Menu_Setup_f(cmd_t *c); static void M_Menu_Options_f(cmd_t *c); static void M_Menu_Keys_f(cmd_t *c); static void M_Menu_Help_f(cmd_t *c); void M_Menu_Quit_f(cmd_t *c); static void M_Menu_LanConfig_f(cmd_t *c); static void M_Menu_GameOptions_f(cmd_t *c); static void M_Main_Draw(void); static void M_SinglePlayer_Draw(void); static void M_Load_Draw(void); static void M_Save_Draw(void); static void M_MultiPlayer_Draw(void); static void M_Setup_Draw(void); static void M_Options_Draw(void); static void M_Keys_Draw(void); static void M_Help_Draw(void); static void M_Quit_Draw(void); static void M_LanConfig_Draw(void); static void M_GameOptions_Draw(void); static void M_Main_Key(int key); static void M_SinglePlayer_Key(int key); static void M_Load_Key(int key); static void M_Save_Key(int key); static void M_MultiPlayer_Key(int key); static void M_Setup_Key(int key); static void M_Options_Key(int key); static void M_Keys_Key(int key); static void M_Help_Key(int key); static void M_Quit_Key(int key); static void M_LanConfig_Key(int key); static void M_GameOptions_Key(int key); /* Draws one solid graphics character */ static void M_DrawCharacter(int cx, int line, int num) { Draw_Character(cx + ((vid.width - 320) >> 1), line, num); } static void M_Print(int cx, int cy, const char *str) { while(*str) { M_DrawCharacter(cx, cy, (*str) + 128); str++; cx += 8; } } static void M_PrintWhite(int cx, int cy, const char *str) { while(*str) { M_DrawCharacter(cx, cy, *str); str++; cx += 8; } } static void M_DrawTransPic(int x, int y, qpic_t *pic) { Draw_TransPic(x + ((vid.width - 320) >> 1), y, pic); } static void M_BuildTranslationTable(int top, int bottom) { int j; byte *dest, *source; for(j = 0; j < 256; j++) identityTable[j] = j; dest = translationTable; source = identityTable; memmove(dest, source, 256); if(top < 128) // the artists made some backwards ranges. sigh. memmove(dest + TOP_RANGE, source + top, 16); else { for(j = 0; j < 16; j++) dest[TOP_RANGE + j] = source[top + 15 - j]; } if(bottom < 128) memmove(dest + BOTTOM_RANGE, source + bottom, 16); else { for(j = 0; j < 16; j++) dest[BOTTOM_RANGE + j] = source[bottom + 15 - j]; } } static void M_DrawTransPicTranslate(int x, int y, qpic_t *pic) { Draw_TransPicTranslate(x + ((vid.width - 320) >> 1), y, pic, translationTable); } static void M_DrawTextBox(int x, int y, int width, int lines) { qpic_t *p; int cx, cy, n; // draw left side cx = x; cy = y; p = Draw_CachePic("gfx/box_tl.lmp"); M_DrawTransPic(cx, cy, p); p = Draw_CachePic("gfx/box_ml.lmp"); for(n = 0; n < lines; n++) { cy += 8; M_DrawTransPic(cx, cy, p); } p = Draw_CachePic("gfx/box_bl.lmp"); M_DrawTransPic(cx, cy + 8, p); // draw middle cx += 8; while(width > 0) { cy = y; p = Draw_CachePic("gfx/box_tm.lmp"); M_DrawTransPic(cx, cy, p); p = Draw_CachePic("gfx/box_mm.lmp"); for(n = 0; n < lines; n++) { cy += 8; if(n == 1) p = Draw_CachePic("gfx/box_mm2.lmp"); M_DrawTransPic(cx, cy, p); } p = Draw_CachePic("gfx/box_bm.lmp"); M_DrawTransPic(cx, cy + 8, p); width -= 2; cx += 16; } // draw right side cy = y; p = Draw_CachePic("gfx/box_tr.lmp"); M_DrawTransPic(cx, cy, p); p = Draw_CachePic("gfx/box_mr.lmp"); for(n = 0; n < lines; n++) { cy += 8; M_DrawTransPic(cx, cy, p); } p = Draw_CachePic("gfx/box_br.lmp"); M_DrawTransPic(cx, cy + 8, p); } void M_ToggleMenu_f(cmd_t *c) { USED(c); m_entersound = true; if(key_dest == key_menu) { if(m_state != m_main) { M_Menu_Main_f(nil); return; } key_dest = key_game; m_state = m_none; return; } if(key_dest == key_console) Con_ToggleConsole_f(nil); else { IN_Grabm(false); M_Menu_Main_f(nil); } } /* MAIN MENU */ void M_Menu_Main_f(cmd_t *c) { USED(c); if(key_dest != key_menu) { m_save_demonum = cls.demonum; cls.demonum = -1; } key_dest = key_menu; m_state = m_main; m_entersound = true; } static void M_Main_Draw(void) { int f; qpic_t *p; M_DrawTransPic(16, 4, Draw_CachePic("gfx/qplaque.lmp")); p = Draw_CachePic("gfx/ttl_main.lmp"); M_DrawTransPic((320 - p->width) / 2, 4, p); M_DrawTransPic(72, 32, Draw_CachePic("gfx/mainmenu.lmp")); f = (int)(host_time * 10) % 6; M_DrawTransPic(54, 32 + m_main_cursor * 20, Draw_CachePic(va("gfx/menudot%d.lmp", f + 1))); } static void M_Main_Key(int key) { switch(key) { case K_ESCAPE: key_dest = key_game; m_state = m_none; cls.demonum = m_save_demonum; if(cls.state != ca_connected && !cls.demoplayback && cls.demonum != -1) CL_NextDemo(); if(cls.state == ca_connected || cls.demoplayback) IN_Grabm(true); else Con_ToggleConsole_f(nil); break; case K_DOWNARROW: localsfx("misc/menu1.wav"); if(++m_main_cursor >= MAIN_ITEMS) m_main_cursor = 0; break; case K_UPARROW: localsfx("misc/menu1.wav"); if(--m_main_cursor < 0) m_main_cursor = MAIN_ITEMS - 1; break; case K_ENTER: m_entersound = true; switch(m_main_cursor) { case 0: M_Menu_SinglePlayer_f(nil); break; case 1: M_Menu_MultiPlayer_f(nil); break; case 2: M_Menu_Options_f(nil); break; case 3: M_Menu_Help_f(nil); break; case 4: M_Menu_Quit_f(nil); break; } } } /* SINGLE PLAYER MENU */ static void M_Menu_SinglePlayer_f(cmd_t *c) { USED(c); key_dest = key_menu; m_state = m_singleplayer; m_entersound = true; } static void M_SinglePlayer_Draw(void) { int f; qpic_t *p; M_DrawTransPic(16, 4, Draw_CachePic("gfx/qplaque.lmp")); p = Draw_CachePic("gfx/ttl_sgl.lmp"); M_DrawTransPic((320 - p->width) / 2, 4, p); M_DrawTransPic(72, 32, Draw_CachePic("gfx/sp_menu.lmp")); f = (int)(host_time * 10) % 6; M_DrawTransPic( 54, 32 + m_singleplayer_cursor * 20, Draw_CachePic(va("gfx/menudot%d.lmp", f + 1)) ); } static void M_SinglePlayer_Key(int key) { switch(key) { case K_ESCAPE: M_Menu_Main_f(nil); break; case K_DOWNARROW: localsfx("misc/menu1.wav"); if(++m_singleplayer_cursor >= SINGLEPLAYER_ITEMS) m_singleplayer_cursor = 0; break; case K_UPARROW: localsfx("misc/menu1.wav"); if(--m_singleplayer_cursor < 0) m_singleplayer_cursor = SINGLEPLAYER_ITEMS - 1; break; case K_ENTER: m_entersound = true; switch(m_singleplayer_cursor) { case 0: if(sv.active && !SCR_ModalMessage("Are you sure you want to\nstart a new game?\n")) break; key_dest = key_game; if(sv.active) Cbuf_AddText("disconnect\n"); Cbuf_AddText("maxplayers 1\n"); Cbuf_AddText("map start\n"); break; case 1: M_Menu_Load_f(nil); break; case 2: M_Menu_Save_f(nil); break; } } } /* LOAD/SAVE MENU */ static void M_Menu_Load_f(cmd_t *c) { USED(c); m_entersound = true; m_state = m_load; key_dest = key_menu; savnames(); } static void M_Menu_Save_f(cmd_t *c) { USED(c); if(!sv.active) return; if(cl.intermission) return; if(svs.maxclients != 1) return; m_entersound = true; m_state = m_save; key_dest = key_menu; savnames(); } static void M_Load_Draw(void) { int i; qpic_t *p; p = Draw_CachePic("gfx/p_load.lmp"); M_DrawTransPic((320 - p->width) / 2, 4, p); for(i = 0; i < Nsav; i++) M_Print(16, 32 + 8 * i, savs[i]); // line cursor M_DrawCharacter(8, 32 + load_cursor * 8, 12 + ((int)(realtime * 4) & 1)); } static void M_Save_Draw(void) { int i; qpic_t *p; p = Draw_CachePic("gfx/p_save.lmp"); M_DrawTransPic((320 - p->width) / 2, 4, p); for(i = 0; i < Nsav; i++) M_Print(16, 32 + 8 * i, savs[i]); // line cursor M_DrawCharacter(8, 32 + load_cursor * 8, 12 + ((int)(realtime * 4) & 1)); } static void M_Load_Key(int k) { switch(k) { case K_ESCAPE: M_Menu_SinglePlayer_f(nil); break; case K_ENTER: localsfx("misc/menu2.wav"); if(!savcanld[load_cursor]) return; m_state = m_none; key_dest = key_game; // Host_Loadgame_f can't bring up the loading plaque because too much // stack space has been used, so do it now SCR_BeginLoadingPlaque(); // issue the load command Cbuf_AddText(va("load s%d\n", load_cursor)); return; case K_UPARROW: case K_LEFTARROW: localsfx("misc/menu1.wav"); load_cursor--; if(load_cursor < 0) load_cursor = Nsav - 1; break; case K_DOWNARROW: case K_RIGHTARROW: localsfx("misc/menu1.wav"); load_cursor++; if(load_cursor >= Nsav) load_cursor = 0; break; } } static void M_Save_Key(int k) { switch(k) { case K_ESCAPE: M_Menu_SinglePlayer_f(nil); break; case K_ENTER: m_state = m_none; key_dest = key_game; Cbuf_AddText(va("save s%d\n", load_cursor)); return; case K_UPARROW: case K_LEFTARROW: localsfx("misc/menu1.wav"); load_cursor--; if(load_cursor < 0) load_cursor = Nsav - 1; break; case K_DOWNARROW: case K_RIGHTARROW: localsfx("misc/menu1.wav"); load_cursor++; if(load_cursor >= Nsav) load_cursor = 0; break; } } /* MULTIPLAYER MENU */ static void M_Menu_MultiPlayer_f(cmd_t *c) { USED(c); key_dest = key_menu; m_state = m_multiplayer; m_entersound = true; } static void M_MultiPlayer_Draw(void) { int f; qpic_t *p; M_DrawTransPic(16, 4, Draw_CachePic("gfx/qplaque.lmp")); p = Draw_CachePic("gfx/p_multi.lmp"); M_DrawTransPic((320 - p->width) / 2, 4, p); M_DrawTransPic(72, 32, Draw_CachePic("gfx/mp_menu.lmp")); f = (int)(host_time * 10) % 6; M_DrawTransPic( 54, 32 + m_multiplayer_cursor * 20, Draw_CachePic(va("gfx/menudot%d.lmp", f + 1)) ); } static void M_MultiPlayer_Key(int key) { switch(key) { case K_ESCAPE: M_Menu_Main_f(nil); break; case K_DOWNARROW: localsfx("misc/menu1.wav"); if(++m_multiplayer_cursor >= MULTIPLAYER_ITEMS) m_multiplayer_cursor = 0; break; case K_UPARROW: localsfx("misc/menu1.wav"); if(--m_multiplayer_cursor < 0) m_multiplayer_cursor = MULTIPLAYER_ITEMS - 1; break; case K_ENTER: m_entersound = true; switch(m_multiplayer_cursor) { case 0: case 1: M_Menu_LanConfig_f(nil); break; case 2: M_Menu_Setup_f(nil); break; } } } /* SETUP MENU */ static void M_Menu_Setup_f(cmd_t *c) { USED(c); key_dest = key_menu; m_state = m_setup; m_entersound = true; strcpy(setup_myname, cl_name.string); strcpy(setup_hostname, hostname.string); setup_top = setup_oldtop = ((int)cl_color.value) >> 4; setup_bottom = setup_oldbottom = ((int)cl_color.value) & 15; } static void M_Setup_Draw(void) { qpic_t *p; M_DrawTransPic(16, 4, Draw_CachePic("gfx/qplaque.lmp")); p = Draw_CachePic("gfx/p_multi.lmp"); M_DrawTransPic((320 - p->width) / 2, 4, p); M_Print(64, 40, "Hostname"); M_DrawTextBox(160, 32, 16, 1); M_Print(168, 40, setup_hostname); M_Print(64, 56, "Your name"); M_DrawTextBox(160, 48, 16, 1); M_Print(168, 56, setup_myname); M_Print(64, 80, "Shirt color"); M_Print(64, 104, "Pants color"); M_DrawTextBox(64, 140 - 8, 14, 1); M_Print(72, 140, "Accept Changes"); p = Draw_CachePic("gfx/bigbox.lmp"); M_DrawTransPic(160, 64, p); p = Draw_CachePic("gfx/menuplyr.lmp"); M_BuildTranslationTable(setup_top * 16, setup_bottom * 16); M_DrawTransPicTranslate(172, 72, p); M_DrawCharacter(56, setup_cursor_table[setup_cursor], 12 + ((int)(realtime * 4) & 1)); if(setup_cursor == 0) M_DrawCharacter( 168 + 8 * strlen(setup_hostname), setup_cursor_table[setup_cursor], 10 + ((int)(realtime * 4) & 1) ); if(setup_cursor == 1) M_DrawCharacter( 168 + 8 * strlen(setup_myname), setup_cursor_table[setup_cursor], 10 + ((int)(realtime * 4) & 1) ); } static void M_Setup_Key(int k) { int l; switch(k) { case K_ESCAPE: M_Menu_MultiPlayer_f(nil); break; case K_UPARROW: localsfx("misc/menu1.wav"); setup_cursor--; if(setup_cursor < 0) setup_cursor = NUM_SETUP_CMDS - 1; break; case K_DOWNARROW: localsfx("misc/menu1.wav"); setup_cursor++; if(setup_cursor >= NUM_SETUP_CMDS) setup_cursor = 0; break; case K_LEFTARROW: if(setup_cursor < 2) return; localsfx("misc/menu3.wav"); if(setup_cursor == 2) setup_top = setup_top - 1; if(setup_cursor == 3) setup_bottom = setup_bottom - 1; break; case K_RIGHTARROW: if(setup_cursor < 2) return; forward: localsfx("misc/menu3.wav"); if(setup_cursor == 2) setup_top = setup_top + 1; if(setup_cursor == 3) setup_bottom = setup_bottom + 1; break; case K_ENTER: if(setup_cursor == 0 || setup_cursor == 1) return; if(setup_cursor == 2 || setup_cursor == 3) goto forward; // setup_cursor == 4 (OK) if(strcmp(cl_name.string, setup_myname) != 0) Cbuf_AddText(va("name \"%s\"\n", setup_myname)); if(strcmp(hostname.string, setup_hostname) != 0) setcvar("hostname", setup_hostname); if(setup_top != setup_oldtop || setup_bottom != setup_oldbottom) Cbuf_AddText(va("color %d %d\n", setup_top, setup_bottom)); m_entersound = true; M_Menu_MultiPlayer_f(nil); break; case K_BACKSPACE: if(setup_cursor == 0 && strlen(setup_hostname) > 0) setup_hostname[strlen(setup_hostname) - 1] = 0; else if(setup_cursor == 1 && strlen(setup_myname) > 0) setup_myname[strlen(setup_myname) - 1] = 0; break; default: if(k < 32 || k > 127) break; if(setup_cursor == 0) { l = strlen(setup_hostname); if(l < 15) { setup_hostname[l + 1] = 0; setup_hostname[l] = k; } } if(setup_cursor == 1) { l = strlen(setup_myname); if(l < 15) { setup_myname[l + 1] = 0; setup_myname[l] = k; } } } if(setup_top > 13) setup_top = 0; if(setup_top < 0) setup_top = 13; if(setup_bottom > 13) setup_bottom = 0; if(setup_bottom < 0) setup_bottom = 13; } /* OPTIONS MENU */ static void M_Menu_Options_f(cmd_t *c) { USED(c); key_dest = key_menu; m_state = m_options; m_entersound = true; } static void M_AdjustSliders(int dir) { localsfx("misc/menu3.wav"); switch(options_cursor) { case 3: // screen size scr_viewsize.value += dir * 10; scr_viewsize.value = clamp(scr_viewsize.value, 100, 120); setcvarv("viewsize", scr_viewsize.value); break; case 4: // brightness v_brightness.value += dir * 0.05; v_brightness.value = clamp(v_brightness.value, 0.5, 4.5); setcvarv("v_brightness", v_brightness.value); break; case 5: // contrast v_contrast.value += dir * 0.05; v_contrast.value = clamp(v_contrast.value, 0.5, 1.5); setcvarv("v_contrast", v_contrast.value); break; case 6: // saturation v_saturation.value += dir * 0.05; v_saturation.value = clamp(v_saturation.value, 0, 2.0); setcvarv("v_saturation", v_saturation.value); break; case 7: // mouse speed sensitivity.value += dir * 0.5; sensitivity.value = clamp(sensitivity.value, 1, 11); setcvarv("sensitivity", sensitivity.value); break; case 8: // sfx volume volume.value += dir * 0.1; volume.value = clamp(volume.value, 0, 1); setcvarv("volume", volume.value); break; case 9: // music volume bgmvolume.value += dir * 0.1; bgmvolume.value = clamp(bgmvolume.value, 0, 1); setcvarv("bgmvolume", bgmvolume.value); break; case 10: // always run if(cl_forwardspeed.value > 200) { setcvarv("cl_forwardspeed", 200); setcvarv("cl_backspeed", 200); } else { setcvarv("cl_forwardspeed", 400); setcvarv("cl_backspeed", 400); } break; case 11: // invert mouse setcvarv("m_pitch", -m_pitch.value); break; } } static void M_DrawSlider(int x, int y, float range) { int i; if(range < 0) range = 0; else if(range > 1) range = 1; M_DrawCharacter(x - 8, y, 128); for(i = 0; i < SLIDER_RANGE; i++) M_DrawCharacter(x + i * 8, y, 129); M_DrawCharacter(x + i * 8, y, 130); M_DrawCharacter(x + (SLIDER_RANGE - 1) * 8 * range, y, 131); } static void M_DrawCheckbox(int x, int y, int on) { /* if(on) M_DrawCharacter(x, y, 131); else M_DrawCharacter(x, y, 129); */ if(on) M_Print(x, y, "on"); else M_Print(x, y, "off"); } static void M_Options_Draw(void) { float r; int y; qpic_t *p; M_DrawTransPic(16, 4, Draw_CachePic("gfx/qplaque.lmp")); p = Draw_CachePic("gfx/p_option.lmp"); M_DrawTransPic((320 - p->width) / 2, 4, p); y = 32; M_Print(16, y, " Customize controls"); y += 8; M_Print(16, y, " Go to console"); y += 8; M_Print(16, y, " Reset to defaults"); y += 8; M_Print(16, y, " Screen size"); r = (scr_viewsize.value - 100) / (120 - 100); M_DrawSlider(220, y, r); y += 8; M_Print(16, y, " Brightness"); r = (v_brightness.value - 0.5) / (4.5 - 0.5); M_DrawSlider(220, y, r); y += 8; M_Print(16, y, " Contrast"); r = (v_contrast.value - 0.5) / (1.5 - 0.5); M_DrawSlider(220, y, r); y += 8; M_Print(16, y, " Saturation"); r = (v_saturation.value - 0.0) / (2 - 0.0); M_DrawSlider(220, y, r); y += 8; M_Print(16, y, " Mouse Speed"); r = (sensitivity.value - 1) / 10; M_DrawSlider(220, y, r); y += 8; M_Print(16, y, " Sound Volume"); r = volume.value; M_DrawSlider(220, y, r); y += 8; M_Print(16, y, " Music Volume"); r = bgmvolume.value; M_DrawSlider(220, y, r); y += 8; M_Print(16, y, " Always Run"); M_DrawCheckbox(220, y, cl_forwardspeed.value > 200); y += 8; M_Print(16, y, " Invert Mouse"); M_DrawCheckbox(220, y, m_pitch.value < 0); y += 8; // cursor M_DrawCharacter(200, 32 + options_cursor * 8, 12 + ((int)(realtime * 4) & 1)); } static void M_Options_Key(int k) { switch(k) { case K_ESCAPE: M_Menu_Main_f(nil); break; case K_ENTER: m_entersound = true; switch(options_cursor) { case 0: M_Menu_Keys_f(nil); break; case 1: m_state = m_none; Con_ToggleConsole_f(nil); break; case 2: Cbuf_AddText("exec default.cfg\n"); break; default: M_AdjustSliders(1); break; } return; case K_UPARROW: localsfx("misc/menu1.wav"); options_cursor--; if(options_cursor < 0) options_cursor = OPTIONS_ITEMS - 1; break; case K_DOWNARROW: localsfx("misc/menu1.wav"); options_cursor++; if(options_cursor >= OPTIONS_ITEMS) options_cursor = 0; break; case K_LEFTARROW: M_AdjustSliders(-1); break; case K_RIGHTARROW: M_AdjustSliders(1); break; } } /* KEYS MENU */ static const char *bindnames[][2] = { {"+attack", "attack"}, {"+jump", "jump / swim up"}, {"+forward", "walk forward"}, {"+back", "backpedal"}, {"+moveleft", "step left"}, {"+moveright", "step right"}, {"+moveup", "swim up"}, {"+movedown", "swim down"}, {"impulse 10", "next weapon"}, {"impulse 12", "prev weapon"}, {"+speed", "run / walk"}, {"+strafe", "sidestep"}, {"echo Quicksaving...; wait; save quick", "quick save"}, {"echo Quickloading...; wait; load quick", "quick load"}, {"+showscores", "show scores"}, {"messagemode", "say to all"}, {"messagemode2", "say to team"}, }; static void M_Menu_Keys_f(cmd_t *c) { USED(c); key_dest = key_menu; m_state = m_keys; m_entersound = true; } static void M_FindKeysForCommand(const char *command, int *twokeys) { int count, j, l; const char *b; twokeys[0] = twokeys[1] = -1; l = strlen(command); count = 0; for(j = 0; j < 256; j++) { b = keybindings[j]; if(!b) continue; if(strncmp(b, command, l) == 0) { twokeys[count] = j; count++; if(count == 2) break; } } } static void M_UnbindCommand(const char *command) { int j, l; const char *b; l = strlen(command); for(j = 0; j < 256; j++) { b = keybindings[j]; if(!b) continue; if(strncmp(b, command, l) == 0) Key_SetBinding(j, ""); } } static void M_Keys_Draw(void) { int i, keys[2], x, y; const char *name; qpic_t *p; p = Draw_CachePic("gfx/ttl_cstm.lmp"); M_DrawTransPic((320 - p->width) / 2, 4, p); if(bind_grab) M_Print(12, 32, "Press a key or button for this action"); else M_Print(18, 32, "Enter to change, backspace to clear"); // search for known bindings for(i = 0; i < nelem(bindnames); i++) { y = 48 + 8 * i; M_Print(16, y, bindnames[i][1]); M_FindKeysForCommand(bindnames[i][0], keys); if(keys[0] == -1) { M_Print(140, y, "???"); } else { name = Key_KeynumToString(keys[0]); M_Print(140, y, name); x = strlen(name) * 8; if(keys[1] != -1) { M_Print(140 + x + 8, y, "or"); M_Print(140 + x + 32, y, Key_KeynumToString(keys[1])); } } } if(bind_grab) M_DrawCharacter(130, 48 + keys_cursor * 8, '='); else M_DrawCharacter(130, 48 + keys_cursor * 8, 12 + ((int)(realtime * 4) & 1)); } static void M_Keys_Key(int k) { char cmd[80]; int keys[2]; if(bind_grab) { // defining a key localsfx("misc/menu1.wav"); if(k != '`') { sprint(cmd, "bind \"%s\" \"%s\"\n", Key_KeynumToString(k), bindnames[keys_cursor][0]); Cbuf_InsertText(cmd); } bind_grab = false; return; } switch(k) { case K_ESCAPE: M_Menu_Options_f(nil); break; case K_LEFTARROW: case K_UPARROW: localsfx("misc/menu1.wav"); keys_cursor--; if(keys_cursor < 0) keys_cursor = nelem(bindnames) - 1; break; case K_DOWNARROW: case K_RIGHTARROW: localsfx("misc/menu1.wav"); keys_cursor++; if(keys_cursor >= nelem(bindnames)) keys_cursor = 0; break; case K_ENTER: // go into bind mode M_FindKeysForCommand(bindnames[keys_cursor][0], keys); localsfx("misc/menu2.wav"); if(keys[1] != -1) M_UnbindCommand(bindnames[keys_cursor][0]); bind_grab = true; break; case K_BACKSPACE: // delete bindings case K_DEL: // delete bindings localsfx("misc/menu2.wav"); M_UnbindCommand(bindnames[keys_cursor][0]); break; } } /* HELP MENU */ static void M_Menu_Help_f(cmd_t *c) { USED(c); key_dest = key_menu; m_state = m_help; m_entersound = true; help_page = 0; } static void M_Help_Draw(void) { M_DrawTransPic(0, 0, Draw_CachePic(va("gfx/help%d.lmp", help_page))); } static void M_Help_Key(int key) { switch(key) { case K_ESCAPE: M_Menu_Main_f(nil); break; case K_UPARROW: case K_RIGHTARROW: m_entersound = true; if(++help_page >= NUM_HELP_PAGES) help_page = 0; break; case K_DOWNARROW: case K_LEFTARROW: m_entersound = true; if(--help_page < 0) help_page = NUM_HELP_PAGES - 1; break; } } /* QUIT MENU */ void M_Menu_Quit_f(cmd_t *c) { USED(c); if(m_state == m_quit) return; wasInMenus = key_dest == key_menu; key_dest = key_menu; m_quit_prevstate = m_state; m_state = m_quit; m_entersound = true; msgNumber = rand() & 7; } static void M_Quit_Key(int key) { switch(key) { case K_ESCAPE: case 'n': case 'N': if(wasInMenus) { m_state = m_quit_prevstate; m_entersound = true; } else { key_dest = key_game; m_state = m_none; } break; case 'Y': case 'y': key_dest = key_console; Host_Quit_f(nil); break; default: break; } } static void M_Quit_Draw(void) { static const char *quitMessage[] = { /* .........1.........2.... */ " Are you gonna quit ", " this game just like ", " everything else? ", " ", " Milord, methinks that ", " thou art a lowly ", " quitter. Is this true? ", " ", " Do I need to bust your ", " face open for trying ", " to quit? ", " ", " Man, I oughta smack you", " for trying to quit! ", " Press Y to get ", " smacked out. ", " Press Y to quit like a ", " big loser in life. ", " Press N to stay proud ", " and successful! ", " If you press Y to ", " quit, I will summon ", " Satan all over your ", " hard drive! ", " Um, Asmodeus dislikes ", " his children trying to ", " quit. Press Y to return", " to your Tinkertoys. ", " If you quit now, I'll ", " throw a blanket-party ", " for you next time! ", " " }; if(wasInMenus) { m_state = m_quit_prevstate; m_recursiveDraw = true; M_Draw(); m_state = m_quit; } M_DrawTextBox(56, 76, 24, 4); M_Print(64, 84, quitMessage[msgNumber * 4 + 0]); M_Print(64, 92, quitMessage[msgNumber * 4 + 1]); M_Print(64, 100, quitMessage[msgNumber * 4 + 2]); M_Print(64, 108, quitMessage[msgNumber * 4 + 3]); } /* LAN CONFIG MENU */ static void M_Menu_LanConfig_f(cmd_t *c) { USED(c); key_dest = key_menu; m_state = m_lanconfig; m_entersound = true; if(lanConfig_cursor == -1) lanConfig_cursor = 1; snprint(lanConfig_portname, sizeof(lanConfig_portname), "%s", myip.srv); m_return_onerror = false; m_return_reason[0] = 0; } static void M_LanConfig_Draw(void) { qpic_t *p; int basex; const char *startJoin; M_DrawTransPic(16, 4, Draw_CachePic("gfx/qplaque.lmp")); p = Draw_CachePic("gfx/p_multi.lmp"); basex = (320 - p->width) / 2 + 36; M_DrawTransPic(basex, 4, p); if(StartingGame) startJoin = "New Game"; else startJoin = "Join Game"; M_Print(basex, 32, va("%s - %s", startJoin, "UDP/IP")); basex += 8; M_Print(basex, 52, "Address:"); M_Print(basex + 9 * 8, 52, myip.ip); M_Print(basex, lanConfig_cursor_table[0], "Port"); M_DrawTextBox(basex + 8 * 8, lanConfig_cursor_table[0] - 8, 6, 1); M_Print(basex + 9 * 8, lanConfig_cursor_table[0], lanConfig_portname); if(JoiningGame) { M_Print(basex, lanConfig_cursor_table[1], "Join game at:"); M_DrawTextBox(basex + 8, lanConfig_cursor_table[1] + 16 - 8, 22, 1); M_Print(basex + 16, lanConfig_cursor_table[1] + 16, lanConfig_joinname); } else { M_DrawTextBox(basex, lanConfig_cursor_table[1] - 8, 2, 1); M_Print(basex + 8, lanConfig_cursor_table[1], "OK"); } M_DrawCharacter( basex - 8, lanConfig_cursor_table[lanConfig_cursor], 12 + ((int)(realtime * 4) & 1) ); if(lanConfig_cursor == 0) M_DrawCharacter( basex + 9 * 8 + 8 * strlen(lanConfig_portname), lanConfig_cursor_table[0], 10 + ((int)(realtime * 4) & 1) ); if(JoiningGame && lanConfig_cursor == 1) M_DrawCharacter( basex + 16 + 8 * strlen(lanConfig_joinname), lanConfig_cursor_table[1] + 16, 10 + ((int)(realtime * 4) & 1) ); if(*m_return_reason) M_PrintWhite(basex, 148, m_return_reason); } static void M_LanConfig_Key(int key) { int l; switch(key) { case K_ESCAPE: M_Menu_MultiPlayer_f(nil); break; case K_UPARROW: localsfx("misc/menu1.wav"); lanConfig_cursor--; if(lanConfig_cursor < 0) lanConfig_cursor = NUM_LANCONFIG_CMDS - 1; break; case K_DOWNARROW: localsfx("misc/menu1.wav"); lanConfig_cursor++; if(lanConfig_cursor >= NUM_LANCONFIG_CMDS) lanConfig_cursor = 0; break; case K_ENTER: if(lanConfig_cursor == 0) break; m_entersound = true; Cbuf_AddText("stopdemo\n"); Cbuf_AddText(va("port %s\n", lanConfig_portname)); if(lanConfig_cursor == 1) { if(StartingGame) { M_Menu_GameOptions_f(nil); break; } else { m_return_state = m_state; m_return_onerror = true; key_dest = key_game; m_state = m_none; Cbuf_AddText(va("connect \"%s\"\n", lanConfig_joinname)); } break; } break; case K_BACKSPACE: if(lanConfig_cursor == 0) { if(strlen(lanConfig_portname)) lanConfig_portname[strlen(lanConfig_portname) - 1] = 0; } if(JoiningGame && lanConfig_cursor == 1) { if(strlen(lanConfig_joinname)) lanConfig_joinname[strlen(lanConfig_joinname) - 1] = 0; } break; default: if(key < 32 || key > 127) break; if(JoiningGame && lanConfig_cursor == 1) { l = strlen(lanConfig_joinname); if(l < 21) { lanConfig_joinname[l + 1] = 0; lanConfig_joinname[l] = key; } } if(key < '0' || key > '9') break; if(lanConfig_cursor == 0) { l = strlen(lanConfig_portname); if(l < 5) { lanConfig_portname[l + 1] = 0; lanConfig_portname[l] = key; } } } l = atoi(lanConfig_portname); if(l > 0 && l < 65535) sprint(lanConfig_portname, "%d", l); } /* GAME OPTIONS MENU */ typedef struct { const char *name; const char *description; } level_t; static const level_t levels[] = { {"start", "Entrance"}, // 0 {"e1m1", "Slipgate Complex"}, // 1 {"e1m2", "Castle of the Damned"}, {"e1m3", "The Necropolis"}, {"e1m4", "The Grisly Grotto"}, {"e1m5", "Gloom Keep"}, {"e1m6", "The Door To Chthon"}, {"e1m7", "The House of Chthon"}, {"e1m8", "Ziggurat Vertigo"}, {"e2m1", "The Installation"}, // 9 {"e2m2", "Ogre Citadel"}, {"e2m3", "Crypt of Decay"}, {"e2m4", "The Ebon Fortress"}, {"e2m5", "The Wizard's Manse"}, {"e2m6", "The Dismal Oubliette"}, {"e2m7", "Underearth"}, {"e3m1", "Termination Central"}, // 16 {"e3m2", "The Vaults of Zin"}, {"e3m3", "The Tomb of Terror"}, {"e3m4", "Satan's Dark Delight"}, {"e3m5", "Wind Tunnels"}, {"e3m6", "Chambers of Torment"}, {"e3m7", "The Haunted Halls"}, {"e4m1", "The Sewage System"}, // 23 {"e4m2", "The Tower of Despair"}, {"e4m3", "The Elder God Shrine"}, {"e4m4", "The Palace of Hate"}, {"e4m5", "Hell's Atrium"}, {"e4m6", "The Pain Maze"}, {"e4m7", "Azure Agony"}, {"e4m8", "The Nameless City"}, {"end", "Shub-Niggurath's Pit"}, // 31 {"dm1", "Place of Two Deaths"}, // 32 {"dm2", "Claustrophobopolis"}, {"dm3", "The Abandoned Base"}, {"dm4", "The Bad Place"}, {"dm5", "The Cistern"}, {"dm6", "The Dark Zone"} }; // MED 01/06/97 added hipnotic levels static const level_t hipnoticlevels[] = { {"start", "Command HQ"}, // 0 {"hip1m1", "The Pumping Station"}, // 1 {"hip1m2", "Storage Facility"}, {"hip1m3", "The Lost Mine"}, {"hip1m4", "Research Facility"}, {"hip1m5", "Military Complex"}, {"hip2m1", "Ancient Realms"}, // 6 {"hip2m2", "The Black Cathedral"}, {"hip2m3", "The Catacombs"}, {"hip2m4", "The Crypt"}, {"hip2m5", "Mortum's Keep"}, {"hip2m6", "The Gremlin's Domain"}, {"hip3m1", "Tur Torment"}, // 12 {"hip3m2", "Pandemonium"}, {"hip3m3", "Limbo"}, {"hip3m4", "The Gauntlet"}, {"hipend", "Armagon's Lair"}, // 16 {"hipdm1", "The Edge of Oblivion"}, // 17 }; // PGM 01/07/97 added rogue levels // PGM 03/02/97 added dmatch level static const level_t roguelevels[] = { {"start", "Split Decision"}, {"r1m1", "Deviant's Domain"}, {"r1m2", "Dread Portal"}, {"r1m3", "Judgement Call"}, {"r1m4", "Cave of Death"}, {"r1m5", "Towers of Wrath"}, {"r1m6", "Temple of Pain"}, {"r1m7", "Tomb of the Overlord"}, {"r2m1", "Tempus Fugit"}, {"r2m2", "Elemental Fury I"}, {"r2m3", "Elemental Fury II"}, {"r2m4", "Curse of Osiris"}, {"r2m5", "Wizard's Keep"}, {"r2m6", "Blood Sacrifice"}, {"r2m7", "Last Bastion"}, {"r2m8", "Source of Evil"}, {"ctf1", "Division of Change"} }; typedef struct { const char *description; int firstLevel; int levels; } episode_t; static const episode_t episodes[] = { {"Welcome to Quake", 0, 1}, {"Doomed Dimension", 1, 8}, {"Realm of Black Magic", 9, 7}, {"Netherworld", 16, 7}, {"The Elder World", 23, 8}, {"Final Level", 31, 1}, {"Deathmatch Arena", 32, 6} }; // MED 01/06/97 added hipnotic episodes static const episode_t hipnoticepisodes[] = { {"Scourge of Armagon", 0, 1}, {"Fortress of the Dead", 1, 5}, {"Dominion of Darkness", 6, 6}, {"The Rift", 12, 4}, {"Final Level", 16, 1}, {"Deathmatch Arena", 17, 1} }; // PGM 01/07/97 added rogue episodes // PGM 03/02/97 added dmatch episode static const episode_t rogueepisodes[] = { {"Introduction", 0, 1}, {"Hell's Fortress", 1, 7}, {"Corridors of Time", 8, 8}, {"Deathmatch Arena", 16, 1} }; static void M_Menu_GameOptions_f(cmd_t *c) { USED(c); key_dest = key_menu; m_state = m_gameoptions; m_entersound = true; if(maxplayers == 0) maxplayers = svs.maxclients; if(maxplayers < 2) maxplayers = svs.maxclientslimit; } static void M_GameOptions_Draw(void) { qpic_t *p; int x; const char *msg; M_DrawTransPic(16, 4, Draw_CachePic("gfx/qplaque.lmp")); p = Draw_CachePic("gfx/p_multi.lmp"); M_DrawTransPic((320 - p->width) / 2, 4, p); M_DrawTextBox(152, 32, 10, 1); M_Print(160, 40, "begin game"); M_Print(0, 56, " Max players"); M_Print(160, 56, va("%d", maxplayers)); M_Print(0, 64, " Game Type"); if(coop.value) M_Print(160, 64, "Cooperative"); else M_Print(160, 64, "Deathmatch"); M_Print(0, 72, " Teamplay"); if(rogue) { switch((int)teamplay.value) { case 1: msg = "No Friendly Fire"; break; case 2: msg = "Friendly Fire"; break; case 3: msg = "Tag"; break; case 4: msg = "Capture the Flag"; break; case 5: msg = "One Flag CTF"; break; case 6: msg = "Three Team CTF"; break; default: msg = "Off"; break; } M_Print(160, 72, msg); } else { switch((int)teamplay.value) { case 1: msg = "No Friendly Fire"; break; case 2: msg = "Friendly Fire"; break; default: msg = "Off"; break; } M_Print(160, 72, msg); } M_Print(0, 80, " Skill"); if(skill.value == 0) M_Print(160, 80, "Easy difficulty"); else if(skill.value == 1) M_Print(160, 80, "Normal difficulty"); else if(skill.value == 2) M_Print(160, 80, "Hard difficulty"); else M_Print(160, 80, "Nightmare difficulty"); M_Print(0, 88, " Frag Limit"); if(fraglimit.value == 0) M_Print(160, 88, "none"); else M_Print(160, 88, va("%d frags", (int)fraglimit.value)); M_Print(0, 96, " Time Limit"); if(timelimit.value == 0) M_Print(160, 96, "none"); else M_Print(160, 96, va("%d minutes", (int)timelimit.value)); M_Print(0, 112, " Episode"); // MED 01/06/97 added hipnotic episodes if(hipnotic) M_Print(160, 112, hipnoticepisodes[startepisode].description); // PGM 01/07/97 added rogue episodes else if(rogue) M_Print(160, 112, rogueepisodes[startepisode].description); else M_Print(160, 112, episodes[startepisode].description); M_Print(0, 120, " Level"); // MED 01/06/97 added hipnotic episodes if(hipnotic) { M_Print( 160, 120, hipnoticlevels[hipnoticepisodes[startepisode].firstLevel + startlevel].description ); M_Print( 160, 128, hipnoticlevels[hipnoticepisodes[startepisode].firstLevel + startlevel].name ); } // PGM 01/07/97 added rogue episodes else if(rogue) { M_Print( 160, 120, roguelevels[rogueepisodes[startepisode].firstLevel + startlevel].description ); M_Print(160, 128, roguelevels[rogueepisodes[startepisode].firstLevel + startlevel].name); } else { M_Print(160, 120, levels[episodes[startepisode].firstLevel + startlevel].description); M_Print(160, 128, levels[episodes[startepisode].firstLevel + startlevel].name); } // line cursor M_DrawCharacter( 144, gameoptions_cursor_table[gameoptions_cursor], 12 + ((int)(realtime * 4) & 1) ); if(m_serverInfoMessage) { if((realtime - m_serverInfoMessageTime) < 5.0) { x = (320 - 26 * 8) / 2; M_DrawTextBox(x, 138, 24, 4); x += 8; M_Print(x, 146, " More than 4 players "); M_Print(x, 154, " requires using command "); M_Print(x, 162, "line parameters; please "); M_Print(x, 170, " see techinfo.txt. "); } else { m_serverInfoMessage = false; } } } static void M_NetStart_Change(int dir) { int count; switch(gameoptions_cursor) { case 1: maxplayers += dir; if(maxplayers > svs.maxclientslimit) { maxplayers = svs.maxclientslimit; m_serverInfoMessage = true; m_serverInfoMessageTime = realtime; } if(maxplayers < 2) maxplayers = 2; break; case 2: setcvarv("coop", coop.value ? 0 : 1); break; case 3: count = rogue ? 6 : 2; setcvarv("teamplay", teamplay.value + dir); if(teamplay.value > count) setcvarv("teamplay", 0); else if(teamplay.value < 0) setcvarv("teamplay", count); break; case 4: setcvarv("skill", skill.value + dir); if(skill.value > 3) setcvarv("skill", 0); else if(skill.value < 0) setcvarv("skill", 3); break; case 5: setcvarv("fraglimit", fraglimit.value + dir * 10); if(fraglimit.value > 100) setcvarv("fraglimit", 0); else if(fraglimit.value < 0) setcvarv("fraglimit", 100); break; case 6: setcvarv("timelimit", timelimit.value + dir * 5); if(timelimit.value > 60) setcvarv("timelimit", 0); else if(timelimit.value < 0) setcvarv("timelimit", 60); break; case 7: startepisode += dir; // MED 01/06/97 added hipnotic count if(hipnotic) count = 6; // PGM 01/07/97 added rogue count // PGM 03/02/97 added 1 for dmatch episode else if(rogue) count = 4; else if(registered.value) count = 7; else count = 2; if(startepisode < 0) startepisode = count - 1; else if(startepisode >= count) startepisode = 0; startlevel = 0; break; case 8: startlevel += dir; // MED 01/06/97 added hipnotic episodes if(hipnotic) count = hipnoticepisodes[startepisode].levels; // PGM 01/06/97 added hipnotic episodes else if(rogue) count = rogueepisodes[startepisode].levels; else count = episodes[startepisode].levels; if(startlevel < 0) startlevel = count - 1; else if(startlevel >= count) startlevel = 0; break; } } static void M_GameOptions_Key(int key) { switch(key) { case K_ESCAPE: M_Menu_MultiPlayer_f(nil); break; case K_UPARROW: localsfx("misc/menu1.wav"); gameoptions_cursor--; if(gameoptions_cursor < 0) gameoptions_cursor = NUM_GAMEOPTIONS - 1; break; case K_DOWNARROW: localsfx("misc/menu1.wav"); gameoptions_cursor++; if(gameoptions_cursor >= NUM_GAMEOPTIONS) gameoptions_cursor = 0; break; case K_LEFTARROW: if(gameoptions_cursor == 0) break; localsfx("misc/menu3.wav"); M_NetStart_Change(-1); break; case K_RIGHTARROW: if(gameoptions_cursor == 0) break; localsfx("misc/menu3.wav"); M_NetStart_Change(1); break; case K_ENTER: localsfx("misc/menu2.wav"); if(gameoptions_cursor == 0) { if(sv.active) Cbuf_AddText("disconnect\n"); Cbuf_AddText(va("maxplayers %d\n", maxplayers)); SCR_BeginLoadingPlaque(); if(hipnotic) Cbuf_AddText( va("map %s\n", hipnoticlevels[hipnoticepisodes[startepisode].firstLevel + startlevel].name) ); else if(rogue) Cbuf_AddText( va("map %s\n", roguelevels[rogueepisodes[startepisode].firstLevel + startlevel].name) ); else Cbuf_AddText( va("map %s\n", levels[episodes[startepisode].firstLevel + startlevel].name) ); return; } M_NetStart_Change(1); break; } } /* Menu Subsystem */ void M_Init(void) { Cmd_AddCommand("togglemenu", M_ToggleMenu_f); Cmd_AddCommand("menu_main", M_Menu_Main_f); Cmd_AddCommand("menu_singleplayer", M_Menu_SinglePlayer_f); Cmd_AddCommand("menu_load", M_Menu_Load_f); Cmd_AddCommand("menu_save", M_Menu_Save_f); Cmd_AddCommand("menu_multiplayer", M_Menu_MultiPlayer_f); Cmd_AddCommand("menu_setup", M_Menu_Setup_f); Cmd_AddCommand("menu_options", M_Menu_Options_f); Cmd_AddCommand("menu_keys", M_Menu_Keys_f); Cmd_AddCommand("help", M_Menu_Help_f); Cmd_AddCommand("menu_quit", M_Menu_Quit_f); Cmd_AddCommand("menu_lan_config", M_Menu_LanConfig_f); Cmd_AddCommand("menu_game_options", M_Menu_GameOptions_f); } void M_Draw(void) { if(m_state == m_none || key_dest != key_menu) return; if(!m_recursiveDraw) { if(scr_con_current) Draw_ConsoleBackground(vid.height); else Draw_FadeScreen(); scr_fullupdate = 0; } else { m_recursiveDraw = false; } switch(m_state) { case m_none: break; case m_main: M_Main_Draw(); break; case m_singleplayer: M_SinglePlayer_Draw(); break; case m_load: M_Load_Draw(); break; case m_save: M_Save_Draw(); break; case m_multiplayer: M_MultiPlayer_Draw(); break; case m_setup: M_Setup_Draw(); break; case m_options: M_Options_Draw(); break; case m_keys: M_Keys_Draw(); break; case m_help: M_Help_Draw(); break; case m_quit: M_Quit_Draw(); break; case m_lanconfig: M_LanConfig_Draw(); break; case m_gameoptions: M_GameOptions_Draw(); break; } if(m_entersound) { localsfx("misc/menu2.wav"); m_entersound = false; } } void M_Keydown(int key) { switch(m_state) { case m_none: return; case m_main: M_Main_Key(key); break; case m_singleplayer: M_SinglePlayer_Key(key); break; case m_load: M_Load_Key(key); break; case m_save: M_Save_Key(key); break; case m_multiplayer: M_MultiPlayer_Key(key); break; case m_setup: M_Setup_Key(key); break; case m_options: M_Options_Key(key); break; case m_keys: M_Keys_Key(key); break; case m_help: M_Help_Key(key); break; case m_quit: M_Quit_Key(key); break; case m_lanconfig: M_LanConfig_Key(key); break; case m_gameoptions: M_GameOptions_Key(key); break; } }