Quake-2

Quake 2 GPL Source Release
Log | Files | Refs

menu.c (106717B)


      1 /*
      2 Copyright (C) 1997-2001 Id Software, Inc.
      3 
      4 This program is free software; you can redistribute it and/or
      5 modify it under the terms of the GNU General Public License
      6 as published by the Free Software Foundation; either version 2
      7 of the License, or (at your option) any later version.
      8 
      9 This program is distributed in the hope that it will be useful,
     10 but WITHOUT ANY WARRANTY; without even the implied warranty of
     11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
     12 
     13 See the GNU General Public License for more details.
     14 
     15 You should have received a copy of the GNU General Public License
     16 along with this program; if not, write to the Free Software
     17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
     18 
     19 */
     20 #include <ctype.h>
     21 #ifdef _WIN32
     22 #include <io.h>
     23 #endif
     24 #include "client.h"
     25 #include "../client/qmenu.h"
     26 
     27 static int	m_main_cursor;
     28 
     29 #define NUM_CURSOR_FRAMES 15
     30 
     31 static char *menu_in_sound		= "misc/menu1.wav";
     32 static char *menu_move_sound	= "misc/menu2.wav";
     33 static char *menu_out_sound		= "misc/menu3.wav";
     34 
     35 void M_Menu_Main_f (void);
     36 	void M_Menu_Game_f (void);
     37 		void M_Menu_LoadGame_f (void);
     38 		void M_Menu_SaveGame_f (void);
     39 		void M_Menu_PlayerConfig_f (void);
     40 			void M_Menu_DownloadOptions_f (void);
     41 		void M_Menu_Credits_f( void );
     42 	void M_Menu_Multiplayer_f( void );
     43 		void M_Menu_JoinServer_f (void);
     44 			void M_Menu_AddressBook_f( void );
     45 		void M_Menu_StartServer_f (void);
     46 			void M_Menu_DMOptions_f (void);
     47 	void M_Menu_Video_f (void);
     48 	void M_Menu_Options_f (void);
     49 		void M_Menu_Keys_f (void);
     50 	void M_Menu_Quit_f (void);
     51 
     52 	void M_Menu_Credits( void );
     53 
     54 qboolean	m_entersound;		// play after drawing a frame, so caching
     55 								// won't disrupt the sound
     56 
     57 void	(*m_drawfunc) (void);
     58 const char *(*m_keyfunc) (int key);
     59 
     60 //=============================================================================
     61 /* Support Routines */
     62 
     63 #define	MAX_MENU_DEPTH	8
     64 
     65 
     66 typedef struct
     67 {
     68 	void	(*draw) (void);
     69 	const char *(*key) (int k);
     70 } menulayer_t;
     71 
     72 menulayer_t	m_layers[MAX_MENU_DEPTH];
     73 int		m_menudepth;
     74 
     75 static void M_Banner( char *name )
     76 {
     77 	int w, h;
     78 
     79 	re.DrawGetPicSize (&w, &h, name );
     80 	re.DrawPic( viddef.width / 2 - w / 2, viddef.height / 2 - 110, name );
     81 }
     82 
     83 void M_PushMenu ( void (*draw) (void), const char *(*key) (int k) )
     84 {
     85 	int		i;
     86 
     87 	if (Cvar_VariableValue ("maxclients") == 1 
     88 		&& Com_ServerState ())
     89 		Cvar_Set ("paused", "1");
     90 
     91 	// if this menu is already present, drop back to that level
     92 	// to avoid stacking menus by hotkeys
     93 	for (i=0 ; i<m_menudepth ; i++)
     94 		if (m_layers[i].draw == draw &&
     95 			m_layers[i].key == key)
     96 		{
     97 			m_menudepth = i;
     98 		}
     99 
    100 	if (i == m_menudepth)
    101 	{
    102 		if (m_menudepth >= MAX_MENU_DEPTH)
    103 			Com_Error (ERR_FATAL, "M_PushMenu: MAX_MENU_DEPTH");
    104 		m_layers[m_menudepth].draw = m_drawfunc;
    105 		m_layers[m_menudepth].key = m_keyfunc;
    106 		m_menudepth++;
    107 	}
    108 
    109 	m_drawfunc = draw;
    110 	m_keyfunc = key;
    111 
    112 	m_entersound = true;
    113 
    114 	cls.key_dest = key_menu;
    115 }
    116 
    117 void M_ForceMenuOff (void)
    118 {
    119 	m_drawfunc = 0;
    120 	m_keyfunc = 0;
    121 	cls.key_dest = key_game;
    122 	m_menudepth = 0;
    123 	Key_ClearStates ();
    124 	Cvar_Set ("paused", "0");
    125 }
    126 
    127 void M_PopMenu (void)
    128 {
    129 	S_StartLocalSound( menu_out_sound );
    130 	if (m_menudepth < 1)
    131 		Com_Error (ERR_FATAL, "M_PopMenu: depth < 1");
    132 	m_menudepth--;
    133 
    134 	m_drawfunc = m_layers[m_menudepth].draw;
    135 	m_keyfunc = m_layers[m_menudepth].key;
    136 
    137 	if (!m_menudepth)
    138 		M_ForceMenuOff ();
    139 }
    140 
    141 
    142 const char *Default_MenuKey( menuframework_s *m, int key )
    143 {
    144 	const char *sound = NULL;
    145 	menucommon_s *item;
    146 
    147 	if ( m )
    148 	{
    149 		if ( ( item = Menu_ItemAtCursor( m ) ) != 0 )
    150 		{
    151 			if ( item->type == MTYPE_FIELD )
    152 			{
    153 				if ( Field_Key( ( menufield_s * ) item, key ) )
    154 					return NULL;
    155 			}
    156 		}
    157 	}
    158 
    159 	switch ( key )
    160 	{
    161 	case K_ESCAPE:
    162 		M_PopMenu();
    163 		return menu_out_sound;
    164 	case K_KP_UPARROW:
    165 	case K_UPARROW:
    166 		if ( m )
    167 		{
    168 			m->cursor--;
    169 			Menu_AdjustCursor( m, -1 );
    170 			sound = menu_move_sound;
    171 		}
    172 		break;
    173 	case K_TAB:
    174 		if ( m )
    175 		{
    176 			m->cursor++;
    177 			Menu_AdjustCursor( m, 1 );
    178 			sound = menu_move_sound;
    179 		}
    180 		break;
    181 	case K_KP_DOWNARROW:
    182 	case K_DOWNARROW:
    183 		if ( m )
    184 		{
    185 			m->cursor++;
    186 			Menu_AdjustCursor( m, 1 );
    187 			sound = menu_move_sound;
    188 		}
    189 		break;
    190 	case K_KP_LEFTARROW:
    191 	case K_LEFTARROW:
    192 		if ( m )
    193 		{
    194 			Menu_SlideItem( m, -1 );
    195 			sound = menu_move_sound;
    196 		}
    197 		break;
    198 	case K_KP_RIGHTARROW:
    199 	case K_RIGHTARROW:
    200 		if ( m )
    201 		{
    202 			Menu_SlideItem( m, 1 );
    203 			sound = menu_move_sound;
    204 		}
    205 		break;
    206 
    207 	case K_MOUSE1:
    208 	case K_MOUSE2:
    209 	case K_MOUSE3:
    210 	case K_JOY1:
    211 	case K_JOY2:
    212 	case K_JOY3:
    213 	case K_JOY4:
    214 	case K_AUX1:
    215 	case K_AUX2:
    216 	case K_AUX3:
    217 	case K_AUX4:
    218 	case K_AUX5:
    219 	case K_AUX6:
    220 	case K_AUX7:
    221 	case K_AUX8:
    222 	case K_AUX9:
    223 	case K_AUX10:
    224 	case K_AUX11:
    225 	case K_AUX12:
    226 	case K_AUX13:
    227 	case K_AUX14:
    228 	case K_AUX15:
    229 	case K_AUX16:
    230 	case K_AUX17:
    231 	case K_AUX18:
    232 	case K_AUX19:
    233 	case K_AUX20:
    234 	case K_AUX21:
    235 	case K_AUX22:
    236 	case K_AUX23:
    237 	case K_AUX24:
    238 	case K_AUX25:
    239 	case K_AUX26:
    240 	case K_AUX27:
    241 	case K_AUX28:
    242 	case K_AUX29:
    243 	case K_AUX30:
    244 	case K_AUX31:
    245 	case K_AUX32:
    246 		
    247 	case K_KP_ENTER:
    248 	case K_ENTER:
    249 		if ( m )
    250 			Menu_SelectItem( m );
    251 		sound = menu_move_sound;
    252 		break;
    253 	}
    254 
    255 	return sound;
    256 }
    257 
    258 //=============================================================================
    259 
    260 /*
    261 ================
    262 M_DrawCharacter
    263 
    264 Draws one solid graphics character
    265 cx and cy are in 320*240 coordinates, and will be centered on
    266 higher res screens.
    267 ================
    268 */
    269 void M_DrawCharacter (int cx, int cy, int num)
    270 {
    271 	re.DrawChar ( cx + ((viddef.width - 320)>>1), cy + ((viddef.height - 240)>>1), num);
    272 }
    273 
    274 void M_Print (int cx, int cy, char *str)
    275 {
    276 	while (*str)
    277 	{
    278 		M_DrawCharacter (cx, cy, (*str)+128);
    279 		str++;
    280 		cx += 8;
    281 	}
    282 }
    283 
    284 void M_PrintWhite (int cx, int cy, char *str)
    285 {
    286 	while (*str)
    287 	{
    288 		M_DrawCharacter (cx, cy, *str);
    289 		str++;
    290 		cx += 8;
    291 	}
    292 }
    293 
    294 void M_DrawPic (int x, int y, char *pic)
    295 {
    296 	re.DrawPic (x + ((viddef.width - 320)>>1), y + ((viddef.height - 240)>>1), pic);
    297 }
    298 
    299 
    300 /*
    301 =============
    302 M_DrawCursor
    303 
    304 Draws an animating cursor with the point at
    305 x,y.  The pic will extend to the left of x,
    306 and both above and below y.
    307 =============
    308 */
    309 void M_DrawCursor( int x, int y, int f )
    310 {
    311 	char	cursorname[80];
    312 	static qboolean cached;
    313 
    314 	if ( !cached )
    315 	{
    316 		int i;
    317 
    318 		for ( i = 0; i < NUM_CURSOR_FRAMES; i++ )
    319 		{
    320 			Com_sprintf( cursorname, sizeof( cursorname ), "m_cursor%d", i );
    321 
    322 			re.RegisterPic( cursorname );
    323 		}
    324 		cached = true;
    325 	}
    326 
    327 	Com_sprintf( cursorname, sizeof(cursorname), "m_cursor%d", f );
    328 	re.DrawPic( x, y, cursorname );
    329 }
    330 
    331 void M_DrawTextBox (int x, int y, int width, int lines)
    332 {
    333 	int		cx, cy;
    334 	int		n;
    335 
    336 	// draw left side
    337 	cx = x;
    338 	cy = y;
    339 	M_DrawCharacter (cx, cy, 1);
    340 	for (n = 0; n < lines; n++)
    341 	{
    342 		cy += 8;
    343 		M_DrawCharacter (cx, cy, 4);
    344 	}
    345 	M_DrawCharacter (cx, cy+8, 7);
    346 
    347 	// draw middle
    348 	cx += 8;
    349 	while (width > 0)
    350 	{
    351 		cy = y;
    352 		M_DrawCharacter (cx, cy, 2);
    353 		for (n = 0; n < lines; n++)
    354 		{
    355 			cy += 8;
    356 			M_DrawCharacter (cx, cy, 5);
    357 		}
    358 		M_DrawCharacter (cx, cy+8, 8);
    359 		width -= 1;
    360 		cx += 8;
    361 	}
    362 
    363 	// draw right side
    364 	cy = y;
    365 	M_DrawCharacter (cx, cy, 3);
    366 	for (n = 0; n < lines; n++)
    367 	{
    368 		cy += 8;
    369 		M_DrawCharacter (cx, cy, 6);
    370 	}
    371 	M_DrawCharacter (cx, cy+8, 9);
    372 }
    373 
    374 		
    375 /*
    376 =======================================================================
    377 
    378 MAIN MENU
    379 
    380 =======================================================================
    381 */
    382 #define	MAIN_ITEMS	5
    383 
    384 
    385 void M_Main_Draw (void)
    386 {
    387 	int i;
    388 	int w, h;
    389 	int ystart;
    390 	int	xoffset;
    391 	int widest = -1;
    392 	int totalheight = 0;
    393 	char litname[80];
    394 	char *names[] =
    395 	{
    396 		"m_main_game",
    397 		"m_main_multiplayer",
    398 		"m_main_options",
    399 		"m_main_video",
    400 		"m_main_quit",
    401 		0
    402 	};
    403 
    404 	for ( i = 0; names[i] != 0; i++ )
    405 	{
    406 		re.DrawGetPicSize( &w, &h, names[i] );
    407 
    408 		if ( w > widest )
    409 			widest = w;
    410 		totalheight += ( h + 12 );
    411 	}
    412 
    413 	ystart = ( viddef.height / 2 - 110 );
    414 	xoffset = ( viddef.width - widest + 70 ) / 2;
    415 
    416 	for ( i = 0; names[i] != 0; i++ )
    417 	{
    418 		if ( i != m_main_cursor )
    419 			re.DrawPic( xoffset, ystart + i * 40 + 13, names[i] );
    420 	}
    421 	strcpy( litname, names[m_main_cursor] );
    422 	strcat( litname, "_sel" );
    423 	re.DrawPic( xoffset, ystart + m_main_cursor * 40 + 13, litname );
    424 
    425 	M_DrawCursor( xoffset - 25, ystart + m_main_cursor * 40 + 11, (int)(cls.realtime / 100)%NUM_CURSOR_FRAMES );
    426 
    427 	re.DrawGetPicSize( &w, &h, "m_main_plaque" );
    428 	re.DrawPic( xoffset - 30 - w, ystart, "m_main_plaque" );
    429 
    430 	re.DrawPic( xoffset - 30 - w, ystart + h + 5, "m_main_logo" );
    431 }
    432 
    433 
    434 const char *M_Main_Key (int key)
    435 {
    436 	const char *sound = menu_move_sound;
    437 
    438 	switch (key)
    439 	{
    440 	case K_ESCAPE:
    441 		M_PopMenu ();
    442 		break;
    443 
    444 	case K_KP_DOWNARROW:
    445 	case K_DOWNARROW:
    446 		if (++m_main_cursor >= MAIN_ITEMS)
    447 			m_main_cursor = 0;
    448 		return sound;
    449 
    450 	case K_KP_UPARROW:
    451 	case K_UPARROW:
    452 		if (--m_main_cursor < 0)
    453 			m_main_cursor = MAIN_ITEMS - 1;
    454 		return sound;
    455 
    456 	case K_KP_ENTER:
    457 	case K_ENTER:
    458 		m_entersound = true;
    459 
    460 		switch (m_main_cursor)
    461 		{
    462 		case 0:
    463 			M_Menu_Game_f ();
    464 			break;
    465 
    466 		case 1:
    467 			M_Menu_Multiplayer_f();
    468 			break;
    469 
    470 		case 2:
    471 			M_Menu_Options_f ();
    472 			break;
    473 
    474 		case 3:
    475 			M_Menu_Video_f ();
    476 			break;
    477 
    478 		case 4:
    479 			M_Menu_Quit_f ();
    480 			break;
    481 		}
    482 	}
    483 
    484 	return NULL;
    485 }
    486 
    487 
    488 void M_Menu_Main_f (void)
    489 {
    490 	M_PushMenu (M_Main_Draw, M_Main_Key);
    491 }
    492 
    493 /*
    494 =======================================================================
    495 
    496 MULTIPLAYER MENU
    497 
    498 =======================================================================
    499 */
    500 static menuframework_s	s_multiplayer_menu;
    501 static menuaction_s		s_join_network_server_action;
    502 static menuaction_s		s_start_network_server_action;
    503 static menuaction_s		s_player_setup_action;
    504 
    505 static void Multiplayer_MenuDraw (void)
    506 {
    507 	M_Banner( "m_banner_multiplayer" );
    508 
    509 	Menu_AdjustCursor( &s_multiplayer_menu, 1 );
    510 	Menu_Draw( &s_multiplayer_menu );
    511 }
    512 
    513 static void PlayerSetupFunc( void *unused )
    514 {
    515 	M_Menu_PlayerConfig_f();
    516 }
    517 
    518 static void JoinNetworkServerFunc( void *unused )
    519 {
    520 	M_Menu_JoinServer_f();
    521 }
    522 
    523 static void StartNetworkServerFunc( void *unused )
    524 {
    525 	M_Menu_StartServer_f ();
    526 }
    527 
    528 void Multiplayer_MenuInit( void )
    529 {
    530 	s_multiplayer_menu.x = viddef.width * 0.50 - 64;
    531 	s_multiplayer_menu.nitems = 0;
    532 
    533 	s_join_network_server_action.generic.type	= MTYPE_ACTION;
    534 	s_join_network_server_action.generic.flags  = QMF_LEFT_JUSTIFY;
    535 	s_join_network_server_action.generic.x		= 0;
    536 	s_join_network_server_action.generic.y		= 0;
    537 	s_join_network_server_action.generic.name	= " join network server";
    538 	s_join_network_server_action.generic.callback = JoinNetworkServerFunc;
    539 
    540 	s_start_network_server_action.generic.type	= MTYPE_ACTION;
    541 	s_start_network_server_action.generic.flags  = QMF_LEFT_JUSTIFY;
    542 	s_start_network_server_action.generic.x		= 0;
    543 	s_start_network_server_action.generic.y		= 10;
    544 	s_start_network_server_action.generic.name	= " start network server";
    545 	s_start_network_server_action.generic.callback = StartNetworkServerFunc;
    546 
    547 	s_player_setup_action.generic.type	= MTYPE_ACTION;
    548 	s_player_setup_action.generic.flags  = QMF_LEFT_JUSTIFY;
    549 	s_player_setup_action.generic.x		= 0;
    550 	s_player_setup_action.generic.y		= 20;
    551 	s_player_setup_action.generic.name	= " player setup";
    552 	s_player_setup_action.generic.callback = PlayerSetupFunc;
    553 
    554 	Menu_AddItem( &s_multiplayer_menu, ( void * ) &s_join_network_server_action );
    555 	Menu_AddItem( &s_multiplayer_menu, ( void * ) &s_start_network_server_action );
    556 	Menu_AddItem( &s_multiplayer_menu, ( void * ) &s_player_setup_action );
    557 
    558 	Menu_SetStatusBar( &s_multiplayer_menu, NULL );
    559 
    560 	Menu_Center( &s_multiplayer_menu );
    561 }
    562 
    563 const char *Multiplayer_MenuKey( int key )
    564 {
    565 	return Default_MenuKey( &s_multiplayer_menu, key );
    566 }
    567 
    568 void M_Menu_Multiplayer_f( void )
    569 {
    570 	Multiplayer_MenuInit();
    571 	M_PushMenu( Multiplayer_MenuDraw, Multiplayer_MenuKey );
    572 }
    573 
    574 /*
    575 =======================================================================
    576 
    577 KEYS MENU
    578 
    579 =======================================================================
    580 */
    581 char *bindnames[][2] =
    582 {
    583 {"+attack", 		"attack"},
    584 {"weapnext", 		"next weapon"},
    585 {"+forward", 		"walk forward"},
    586 {"+back", 			"backpedal"},
    587 {"+left", 			"turn left"},
    588 {"+right", 			"turn right"},
    589 {"+speed", 			"run"},
    590 {"+moveleft", 		"step left"},
    591 {"+moveright", 		"step right"},
    592 {"+strafe", 		"sidestep"},
    593 {"+lookup", 		"look up"},
    594 {"+lookdown", 		"look down"},
    595 {"centerview", 		"center view"},
    596 {"+mlook", 			"mouse look"},
    597 {"+klook", 			"keyboard look"},
    598 {"+moveup",			"up / jump"},
    599 {"+movedown",		"down / crouch"},
    600 
    601 {"inven",			"inventory"},
    602 {"invuse",			"use item"},
    603 {"invdrop",			"drop item"},
    604 {"invprev",			"prev item"},
    605 {"invnext",			"next item"},
    606 
    607 {"cmd help", 		"help computer" }, 
    608 { 0, 0 }
    609 };
    610 
    611 int				keys_cursor;
    612 static int		bind_grab;
    613 
    614 static menuframework_s	s_keys_menu;
    615 static menuaction_s		s_keys_attack_action;
    616 static menuaction_s		s_keys_change_weapon_action;
    617 static menuaction_s		s_keys_walk_forward_action;
    618 static menuaction_s		s_keys_backpedal_action;
    619 static menuaction_s		s_keys_turn_left_action;
    620 static menuaction_s		s_keys_turn_right_action;
    621 static menuaction_s		s_keys_run_action;
    622 static menuaction_s		s_keys_step_left_action;
    623 static menuaction_s		s_keys_step_right_action;
    624 static menuaction_s		s_keys_sidestep_action;
    625 static menuaction_s		s_keys_look_up_action;
    626 static menuaction_s		s_keys_look_down_action;
    627 static menuaction_s		s_keys_center_view_action;
    628 static menuaction_s		s_keys_mouse_look_action;
    629 static menuaction_s		s_keys_keyboard_look_action;
    630 static menuaction_s		s_keys_move_up_action;
    631 static menuaction_s		s_keys_move_down_action;
    632 static menuaction_s		s_keys_inventory_action;
    633 static menuaction_s		s_keys_inv_use_action;
    634 static menuaction_s		s_keys_inv_drop_action;
    635 static menuaction_s		s_keys_inv_prev_action;
    636 static menuaction_s		s_keys_inv_next_action;
    637 
    638 static menuaction_s		s_keys_help_computer_action;
    639 
    640 static void M_UnbindCommand (char *command)
    641 {
    642 	int		j;
    643 	int		l;
    644 	char	*b;
    645 
    646 	l = strlen(command);
    647 
    648 	for (j=0 ; j<256 ; j++)
    649 	{
    650 		b = keybindings[j];
    651 		if (!b)
    652 			continue;
    653 		if (!strncmp (b, command, l) )
    654 			Key_SetBinding (j, "");
    655 	}
    656 }
    657 
    658 static void M_FindKeysForCommand (char *command, int *twokeys)
    659 {
    660 	int		count;
    661 	int		j;
    662 	int		l;
    663 	char	*b;
    664 
    665 	twokeys[0] = twokeys[1] = -1;
    666 	l = strlen(command);
    667 	count = 0;
    668 
    669 	for (j=0 ; j<256 ; j++)
    670 	{
    671 		b = keybindings[j];
    672 		if (!b)
    673 			continue;
    674 		if (!strncmp (b, command, l) )
    675 		{
    676 			twokeys[count] = j;
    677 			count++;
    678 			if (count == 2)
    679 				break;
    680 		}
    681 	}
    682 }
    683 
    684 static void KeyCursorDrawFunc( menuframework_s *menu )
    685 {
    686 	if ( bind_grab )
    687 		re.DrawChar( menu->x, menu->y + menu->cursor * 9, '=' );
    688 	else
    689 		re.DrawChar( menu->x, menu->y + menu->cursor * 9, 12 + ( ( int ) ( Sys_Milliseconds() / 250 ) & 1 ) );
    690 }
    691 
    692 static void DrawKeyBindingFunc( void *self )
    693 {
    694 	int keys[2];
    695 	menuaction_s *a = ( menuaction_s * ) self;
    696 
    697 	M_FindKeysForCommand( bindnames[a->generic.localdata[0]][0], keys);
    698 		
    699 	if (keys[0] == -1)
    700 	{
    701 		Menu_DrawString( a->generic.x + a->generic.parent->x + 16, a->generic.y + a->generic.parent->y, "???" );
    702 	}
    703 	else
    704 	{
    705 		int x;
    706 		const char *name;
    707 
    708 		name = Key_KeynumToString (keys[0]);
    709 
    710 		Menu_DrawString( a->generic.x + a->generic.parent->x + 16, a->generic.y + a->generic.parent->y, name );
    711 
    712 		x = strlen(name) * 8;
    713 
    714 		if (keys[1] != -1)
    715 		{
    716 			Menu_DrawString( a->generic.x + a->generic.parent->x + 24 + x, a->generic.y + a->generic.parent->y, "or" );
    717 			Menu_DrawString( a->generic.x + a->generic.parent->x + 48 + x, a->generic.y + a->generic.parent->y, Key_KeynumToString (keys[1]) );
    718 		}
    719 	}
    720 }
    721 
    722 static void KeyBindingFunc( void *self )
    723 {
    724 	menuaction_s *a = ( menuaction_s * ) self;
    725 	int keys[2];
    726 
    727 	M_FindKeysForCommand( bindnames[a->generic.localdata[0]][0], keys );
    728 
    729 	if (keys[1] != -1)
    730 		M_UnbindCommand( bindnames[a->generic.localdata[0]][0]);
    731 
    732 	bind_grab = true;
    733 
    734 	Menu_SetStatusBar( &s_keys_menu, "press a key or button for this action" );
    735 }
    736 
    737 static void Keys_MenuInit( void )
    738 {
    739 	int y = 0;
    740 	int i = 0;
    741 
    742 	s_keys_menu.x = viddef.width * 0.50;
    743 	s_keys_menu.nitems = 0;
    744 	s_keys_menu.cursordraw = KeyCursorDrawFunc;
    745 
    746 	s_keys_attack_action.generic.type	= MTYPE_ACTION;
    747 	s_keys_attack_action.generic.flags  = QMF_GRAYED;
    748 	s_keys_attack_action.generic.x		= 0;
    749 	s_keys_attack_action.generic.y		= y;
    750 	s_keys_attack_action.generic.ownerdraw = DrawKeyBindingFunc;
    751 	s_keys_attack_action.generic.localdata[0] = i;
    752 	s_keys_attack_action.generic.name	= bindnames[s_keys_attack_action.generic.localdata[0]][1];
    753 
    754 	s_keys_change_weapon_action.generic.type	= MTYPE_ACTION;
    755 	s_keys_change_weapon_action.generic.flags  = QMF_GRAYED;
    756 	s_keys_change_weapon_action.generic.x		= 0;
    757 	s_keys_change_weapon_action.generic.y		= y += 9;
    758 	s_keys_change_weapon_action.generic.ownerdraw = DrawKeyBindingFunc;
    759 	s_keys_change_weapon_action.generic.localdata[0] = ++i;
    760 	s_keys_change_weapon_action.generic.name	= bindnames[s_keys_change_weapon_action.generic.localdata[0]][1];
    761 
    762 	s_keys_walk_forward_action.generic.type	= MTYPE_ACTION;
    763 	s_keys_walk_forward_action.generic.flags  = QMF_GRAYED;
    764 	s_keys_walk_forward_action.generic.x		= 0;
    765 	s_keys_walk_forward_action.generic.y		= y += 9;
    766 	s_keys_walk_forward_action.generic.ownerdraw = DrawKeyBindingFunc;
    767 	s_keys_walk_forward_action.generic.localdata[0] = ++i;
    768 	s_keys_walk_forward_action.generic.name	= bindnames[s_keys_walk_forward_action.generic.localdata[0]][1];
    769 
    770 	s_keys_backpedal_action.generic.type	= MTYPE_ACTION;
    771 	s_keys_backpedal_action.generic.flags  = QMF_GRAYED;
    772 	s_keys_backpedal_action.generic.x		= 0;
    773 	s_keys_backpedal_action.generic.y		= y += 9;
    774 	s_keys_backpedal_action.generic.ownerdraw = DrawKeyBindingFunc;
    775 	s_keys_backpedal_action.generic.localdata[0] = ++i;
    776 	s_keys_backpedal_action.generic.name	= bindnames[s_keys_backpedal_action.generic.localdata[0]][1];
    777 
    778 	s_keys_turn_left_action.generic.type	= MTYPE_ACTION;
    779 	s_keys_turn_left_action.generic.flags  = QMF_GRAYED;
    780 	s_keys_turn_left_action.generic.x		= 0;
    781 	s_keys_turn_left_action.generic.y		= y += 9;
    782 	s_keys_turn_left_action.generic.ownerdraw = DrawKeyBindingFunc;
    783 	s_keys_turn_left_action.generic.localdata[0] = ++i;
    784 	s_keys_turn_left_action.generic.name	= bindnames[s_keys_turn_left_action.generic.localdata[0]][1];
    785 
    786 	s_keys_turn_right_action.generic.type	= MTYPE_ACTION;
    787 	s_keys_turn_right_action.generic.flags  = QMF_GRAYED;
    788 	s_keys_turn_right_action.generic.x		= 0;
    789 	s_keys_turn_right_action.generic.y		= y += 9;
    790 	s_keys_turn_right_action.generic.ownerdraw = DrawKeyBindingFunc;
    791 	s_keys_turn_right_action.generic.localdata[0] = ++i;
    792 	s_keys_turn_right_action.generic.name	= bindnames[s_keys_turn_right_action.generic.localdata[0]][1];
    793 
    794 	s_keys_run_action.generic.type	= MTYPE_ACTION;
    795 	s_keys_run_action.generic.flags  = QMF_GRAYED;
    796 	s_keys_run_action.generic.x		= 0;
    797 	s_keys_run_action.generic.y		= y += 9;
    798 	s_keys_run_action.generic.ownerdraw = DrawKeyBindingFunc;
    799 	s_keys_run_action.generic.localdata[0] = ++i;
    800 	s_keys_run_action.generic.name	= bindnames[s_keys_run_action.generic.localdata[0]][1];
    801 
    802 	s_keys_step_left_action.generic.type	= MTYPE_ACTION;
    803 	s_keys_step_left_action.generic.flags  = QMF_GRAYED;
    804 	s_keys_step_left_action.generic.x		= 0;
    805 	s_keys_step_left_action.generic.y		= y += 9;
    806 	s_keys_step_left_action.generic.ownerdraw = DrawKeyBindingFunc;
    807 	s_keys_step_left_action.generic.localdata[0] = ++i;
    808 	s_keys_step_left_action.generic.name	= bindnames[s_keys_step_left_action.generic.localdata[0]][1];
    809 
    810 	s_keys_step_right_action.generic.type	= MTYPE_ACTION;
    811 	s_keys_step_right_action.generic.flags  = QMF_GRAYED;
    812 	s_keys_step_right_action.generic.x		= 0;
    813 	s_keys_step_right_action.generic.y		= y += 9;
    814 	s_keys_step_right_action.generic.ownerdraw = DrawKeyBindingFunc;
    815 	s_keys_step_right_action.generic.localdata[0] = ++i;
    816 	s_keys_step_right_action.generic.name	= bindnames[s_keys_step_right_action.generic.localdata[0]][1];
    817 
    818 	s_keys_sidestep_action.generic.type	= MTYPE_ACTION;
    819 	s_keys_sidestep_action.generic.flags  = QMF_GRAYED;
    820 	s_keys_sidestep_action.generic.x		= 0;
    821 	s_keys_sidestep_action.generic.y		= y += 9;
    822 	s_keys_sidestep_action.generic.ownerdraw = DrawKeyBindingFunc;
    823 	s_keys_sidestep_action.generic.localdata[0] = ++i;
    824 	s_keys_sidestep_action.generic.name	= bindnames[s_keys_sidestep_action.generic.localdata[0]][1];
    825 
    826 	s_keys_look_up_action.generic.type	= MTYPE_ACTION;
    827 	s_keys_look_up_action.generic.flags  = QMF_GRAYED;
    828 	s_keys_look_up_action.generic.x		= 0;
    829 	s_keys_look_up_action.generic.y		= y += 9;
    830 	s_keys_look_up_action.generic.ownerdraw = DrawKeyBindingFunc;
    831 	s_keys_look_up_action.generic.localdata[0] = ++i;
    832 	s_keys_look_up_action.generic.name	= bindnames[s_keys_look_up_action.generic.localdata[0]][1];
    833 
    834 	s_keys_look_down_action.generic.type	= MTYPE_ACTION;
    835 	s_keys_look_down_action.generic.flags  = QMF_GRAYED;
    836 	s_keys_look_down_action.generic.x		= 0;
    837 	s_keys_look_down_action.generic.y		= y += 9;
    838 	s_keys_look_down_action.generic.ownerdraw = DrawKeyBindingFunc;
    839 	s_keys_look_down_action.generic.localdata[0] = ++i;
    840 	s_keys_look_down_action.generic.name	= bindnames[s_keys_look_down_action.generic.localdata[0]][1];
    841 
    842 	s_keys_center_view_action.generic.type	= MTYPE_ACTION;
    843 	s_keys_center_view_action.generic.flags  = QMF_GRAYED;
    844 	s_keys_center_view_action.generic.x		= 0;
    845 	s_keys_center_view_action.generic.y		= y += 9;
    846 	s_keys_center_view_action.generic.ownerdraw = DrawKeyBindingFunc;
    847 	s_keys_center_view_action.generic.localdata[0] = ++i;
    848 	s_keys_center_view_action.generic.name	= bindnames[s_keys_center_view_action.generic.localdata[0]][1];
    849 
    850 	s_keys_mouse_look_action.generic.type	= MTYPE_ACTION;
    851 	s_keys_mouse_look_action.generic.flags  = QMF_GRAYED;
    852 	s_keys_mouse_look_action.generic.x		= 0;
    853 	s_keys_mouse_look_action.generic.y		= y += 9;
    854 	s_keys_mouse_look_action.generic.ownerdraw = DrawKeyBindingFunc;
    855 	s_keys_mouse_look_action.generic.localdata[0] = ++i;
    856 	s_keys_mouse_look_action.generic.name	= bindnames[s_keys_mouse_look_action.generic.localdata[0]][1];
    857 
    858 	s_keys_keyboard_look_action.generic.type	= MTYPE_ACTION;
    859 	s_keys_keyboard_look_action.generic.flags  = QMF_GRAYED;
    860 	s_keys_keyboard_look_action.generic.x		= 0;
    861 	s_keys_keyboard_look_action.generic.y		= y += 9;
    862 	s_keys_keyboard_look_action.generic.ownerdraw = DrawKeyBindingFunc;
    863 	s_keys_keyboard_look_action.generic.localdata[0] = ++i;
    864 	s_keys_keyboard_look_action.generic.name	= bindnames[s_keys_keyboard_look_action.generic.localdata[0]][1];
    865 
    866 	s_keys_move_up_action.generic.type	= MTYPE_ACTION;
    867 	s_keys_move_up_action.generic.flags  = QMF_GRAYED;
    868 	s_keys_move_up_action.generic.x		= 0;
    869 	s_keys_move_up_action.generic.y		= y += 9;
    870 	s_keys_move_up_action.generic.ownerdraw = DrawKeyBindingFunc;
    871 	s_keys_move_up_action.generic.localdata[0] = ++i;
    872 	s_keys_move_up_action.generic.name	= bindnames[s_keys_move_up_action.generic.localdata[0]][1];
    873 
    874 	s_keys_move_down_action.generic.type	= MTYPE_ACTION;
    875 	s_keys_move_down_action.generic.flags  = QMF_GRAYED;
    876 	s_keys_move_down_action.generic.x		= 0;
    877 	s_keys_move_down_action.generic.y		= y += 9;
    878 	s_keys_move_down_action.generic.ownerdraw = DrawKeyBindingFunc;
    879 	s_keys_move_down_action.generic.localdata[0] = ++i;
    880 	s_keys_move_down_action.generic.name	= bindnames[s_keys_move_down_action.generic.localdata[0]][1];
    881 
    882 	s_keys_inventory_action.generic.type	= MTYPE_ACTION;
    883 	s_keys_inventory_action.generic.flags  = QMF_GRAYED;
    884 	s_keys_inventory_action.generic.x		= 0;
    885 	s_keys_inventory_action.generic.y		= y += 9;
    886 	s_keys_inventory_action.generic.ownerdraw = DrawKeyBindingFunc;
    887 	s_keys_inventory_action.generic.localdata[0] = ++i;
    888 	s_keys_inventory_action.generic.name	= bindnames[s_keys_inventory_action.generic.localdata[0]][1];
    889 
    890 	s_keys_inv_use_action.generic.type	= MTYPE_ACTION;
    891 	s_keys_inv_use_action.generic.flags  = QMF_GRAYED;
    892 	s_keys_inv_use_action.generic.x		= 0;
    893 	s_keys_inv_use_action.generic.y		= y += 9;
    894 	s_keys_inv_use_action.generic.ownerdraw = DrawKeyBindingFunc;
    895 	s_keys_inv_use_action.generic.localdata[0] = ++i;
    896 	s_keys_inv_use_action.generic.name	= bindnames[s_keys_inv_use_action.generic.localdata[0]][1];
    897 
    898 	s_keys_inv_drop_action.generic.type	= MTYPE_ACTION;
    899 	s_keys_inv_drop_action.generic.flags  = QMF_GRAYED;
    900 	s_keys_inv_drop_action.generic.x		= 0;
    901 	s_keys_inv_drop_action.generic.y		= y += 9;
    902 	s_keys_inv_drop_action.generic.ownerdraw = DrawKeyBindingFunc;
    903 	s_keys_inv_drop_action.generic.localdata[0] = ++i;
    904 	s_keys_inv_drop_action.generic.name	= bindnames[s_keys_inv_drop_action.generic.localdata[0]][1];
    905 
    906 	s_keys_inv_prev_action.generic.type	= MTYPE_ACTION;
    907 	s_keys_inv_prev_action.generic.flags  = QMF_GRAYED;
    908 	s_keys_inv_prev_action.generic.x		= 0;
    909 	s_keys_inv_prev_action.generic.y		= y += 9;
    910 	s_keys_inv_prev_action.generic.ownerdraw = DrawKeyBindingFunc;
    911 	s_keys_inv_prev_action.generic.localdata[0] = ++i;
    912 	s_keys_inv_prev_action.generic.name	= bindnames[s_keys_inv_prev_action.generic.localdata[0]][1];
    913 
    914 	s_keys_inv_next_action.generic.type	= MTYPE_ACTION;
    915 	s_keys_inv_next_action.generic.flags  = QMF_GRAYED;
    916 	s_keys_inv_next_action.generic.x		= 0;
    917 	s_keys_inv_next_action.generic.y		= y += 9;
    918 	s_keys_inv_next_action.generic.ownerdraw = DrawKeyBindingFunc;
    919 	s_keys_inv_next_action.generic.localdata[0] = ++i;
    920 	s_keys_inv_next_action.generic.name	= bindnames[s_keys_inv_next_action.generic.localdata[0]][1];
    921 
    922 	s_keys_help_computer_action.generic.type	= MTYPE_ACTION;
    923 	s_keys_help_computer_action.generic.flags  = QMF_GRAYED;
    924 	s_keys_help_computer_action.generic.x		= 0;
    925 	s_keys_help_computer_action.generic.y		= y += 9;
    926 	s_keys_help_computer_action.generic.ownerdraw = DrawKeyBindingFunc;
    927 	s_keys_help_computer_action.generic.localdata[0] = ++i;
    928 	s_keys_help_computer_action.generic.name	= bindnames[s_keys_help_computer_action.generic.localdata[0]][1];
    929 
    930 	Menu_AddItem( &s_keys_menu, ( void * ) &s_keys_attack_action );
    931 	Menu_AddItem( &s_keys_menu, ( void * ) &s_keys_change_weapon_action );
    932 	Menu_AddItem( &s_keys_menu, ( void * ) &s_keys_walk_forward_action );
    933 	Menu_AddItem( &s_keys_menu, ( void * ) &s_keys_backpedal_action );
    934 	Menu_AddItem( &s_keys_menu, ( void * ) &s_keys_turn_left_action );
    935 	Menu_AddItem( &s_keys_menu, ( void * ) &s_keys_turn_right_action );
    936 	Menu_AddItem( &s_keys_menu, ( void * ) &s_keys_run_action );
    937 	Menu_AddItem( &s_keys_menu, ( void * ) &s_keys_step_left_action );
    938 	Menu_AddItem( &s_keys_menu, ( void * ) &s_keys_step_right_action );
    939 	Menu_AddItem( &s_keys_menu, ( void * ) &s_keys_sidestep_action );
    940 	Menu_AddItem( &s_keys_menu, ( void * ) &s_keys_look_up_action );
    941 	Menu_AddItem( &s_keys_menu, ( void * ) &s_keys_look_down_action );
    942 	Menu_AddItem( &s_keys_menu, ( void * ) &s_keys_center_view_action );
    943 	Menu_AddItem( &s_keys_menu, ( void * ) &s_keys_mouse_look_action );
    944 	Menu_AddItem( &s_keys_menu, ( void * ) &s_keys_keyboard_look_action );
    945 	Menu_AddItem( &s_keys_menu, ( void * ) &s_keys_move_up_action );
    946 	Menu_AddItem( &s_keys_menu, ( void * ) &s_keys_move_down_action );
    947 
    948 	Menu_AddItem( &s_keys_menu, ( void * ) &s_keys_inventory_action );
    949 	Menu_AddItem( &s_keys_menu, ( void * ) &s_keys_inv_use_action );
    950 	Menu_AddItem( &s_keys_menu, ( void * ) &s_keys_inv_drop_action );
    951 	Menu_AddItem( &s_keys_menu, ( void * ) &s_keys_inv_prev_action );
    952 	Menu_AddItem( &s_keys_menu, ( void * ) &s_keys_inv_next_action );
    953 
    954 	Menu_AddItem( &s_keys_menu, ( void * ) &s_keys_help_computer_action );
    955 	
    956 	Menu_SetStatusBar( &s_keys_menu, "enter to change, backspace to clear" );
    957 	Menu_Center( &s_keys_menu );
    958 }
    959 
    960 static void Keys_MenuDraw (void)
    961 {
    962 	Menu_AdjustCursor( &s_keys_menu, 1 );
    963 	Menu_Draw( &s_keys_menu );
    964 }
    965 
    966 static const char *Keys_MenuKey( int key )
    967 {
    968 	menuaction_s *item = ( menuaction_s * ) Menu_ItemAtCursor( &s_keys_menu );
    969 
    970 	if ( bind_grab )
    971 	{	
    972 		if ( key != K_ESCAPE && key != '`' )
    973 		{
    974 			char cmd[1024];
    975 
    976 			Com_sprintf (cmd, sizeof(cmd), "bind \"%s\" \"%s\"\n", Key_KeynumToString(key), bindnames[item->generic.localdata[0]][0]);
    977 			Cbuf_InsertText (cmd);
    978 		}
    979 		
    980 		Menu_SetStatusBar( &s_keys_menu, "enter to change, backspace to clear" );
    981 		bind_grab = false;
    982 		return menu_out_sound;
    983 	}
    984 
    985 	switch ( key )
    986 	{
    987 	case K_KP_ENTER:
    988 	case K_ENTER:
    989 		KeyBindingFunc( item );
    990 		return menu_in_sound;
    991 	case K_BACKSPACE:		// delete bindings
    992 	case K_DEL:				// delete bindings
    993 	case K_KP_DEL:
    994 		M_UnbindCommand( bindnames[item->generic.localdata[0]][0] );
    995 		return menu_out_sound;
    996 	default:
    997 		return Default_MenuKey( &s_keys_menu, key );
    998 	}
    999 }
   1000 
   1001 void M_Menu_Keys_f (void)
   1002 {
   1003 	Keys_MenuInit();
   1004 	M_PushMenu( Keys_MenuDraw, Keys_MenuKey );
   1005 }
   1006 
   1007 
   1008 /*
   1009 =======================================================================
   1010 
   1011 CONTROLS MENU
   1012 
   1013 =======================================================================
   1014 */
   1015 static cvar_t *win_noalttab;
   1016 extern cvar_t *in_joystick;
   1017 
   1018 static menuframework_s	s_options_menu;
   1019 static menuaction_s		s_options_defaults_action;
   1020 static menuaction_s		s_options_customize_options_action;
   1021 static menuslider_s		s_options_sensitivity_slider;
   1022 static menulist_s		s_options_freelook_box;
   1023 static menulist_s		s_options_noalttab_box;
   1024 static menulist_s		s_options_alwaysrun_box;
   1025 static menulist_s		s_options_invertmouse_box;
   1026 static menulist_s		s_options_lookspring_box;
   1027 static menulist_s		s_options_lookstrafe_box;
   1028 static menulist_s		s_options_crosshair_box;
   1029 static menuslider_s		s_options_sfxvolume_slider;
   1030 static menulist_s		s_options_joystick_box;
   1031 static menulist_s		s_options_cdvolume_box;
   1032 static menulist_s		s_options_quality_list;
   1033 static menulist_s		s_options_compatibility_list;
   1034 static menulist_s		s_options_console_action;
   1035 
   1036 static void CrosshairFunc( void *unused )
   1037 {
   1038 	Cvar_SetValue( "crosshair", s_options_crosshair_box.curvalue );
   1039 }
   1040 
   1041 static void JoystickFunc( void *unused )
   1042 {
   1043 	Cvar_SetValue( "in_joystick", s_options_joystick_box.curvalue );
   1044 }
   1045 
   1046 static void CustomizeControlsFunc( void *unused )
   1047 {
   1048 	M_Menu_Keys_f();
   1049 }
   1050 
   1051 static void AlwaysRunFunc( void *unused )
   1052 {
   1053 	Cvar_SetValue( "cl_run", s_options_alwaysrun_box.curvalue );
   1054 }
   1055 
   1056 static void FreeLookFunc( void *unused )
   1057 {
   1058 	Cvar_SetValue( "freelook", s_options_freelook_box.curvalue );
   1059 }
   1060 
   1061 static void MouseSpeedFunc( void *unused )
   1062 {
   1063 	Cvar_SetValue( "sensitivity", s_options_sensitivity_slider.curvalue / 2.0F );
   1064 }
   1065 
   1066 static void NoAltTabFunc( void *unused )
   1067 {
   1068 	Cvar_SetValue( "win_noalttab", s_options_noalttab_box.curvalue );
   1069 }
   1070 
   1071 static float ClampCvar( float min, float max, float value )
   1072 {
   1073 	if ( value < min ) return min;
   1074 	if ( value > max ) return max;
   1075 	return value;
   1076 }
   1077 
   1078 static void ControlsSetMenuItemValues( void )
   1079 {
   1080 	s_options_sfxvolume_slider.curvalue		= Cvar_VariableValue( "s_volume" ) * 10;
   1081 	s_options_cdvolume_box.curvalue 		= !Cvar_VariableValue("cd_nocd");
   1082 	s_options_quality_list.curvalue			= !Cvar_VariableValue( "s_loadas8bit" );
   1083 	s_options_sensitivity_slider.curvalue	= ( sensitivity->value ) * 2;
   1084 
   1085 	Cvar_SetValue( "cl_run", ClampCvar( 0, 1, cl_run->value ) );
   1086 	s_options_alwaysrun_box.curvalue		= cl_run->value;
   1087 
   1088 	s_options_invertmouse_box.curvalue		= m_pitch->value < 0;
   1089 
   1090 	Cvar_SetValue( "lookspring", ClampCvar( 0, 1, lookspring->value ) );
   1091 	s_options_lookspring_box.curvalue		= lookspring->value;
   1092 
   1093 	Cvar_SetValue( "lookstrafe", ClampCvar( 0, 1, lookstrafe->value ) );
   1094 	s_options_lookstrafe_box.curvalue		= lookstrafe->value;
   1095 
   1096 	Cvar_SetValue( "freelook", ClampCvar( 0, 1, freelook->value ) );
   1097 	s_options_freelook_box.curvalue			= freelook->value;
   1098 
   1099 	Cvar_SetValue( "crosshair", ClampCvar( 0, 3, crosshair->value ) );
   1100 	s_options_crosshair_box.curvalue		= crosshair->value;
   1101 
   1102 	Cvar_SetValue( "in_joystick", ClampCvar( 0, 1, in_joystick->value ) );
   1103 	s_options_joystick_box.curvalue		= in_joystick->value;
   1104 
   1105 	s_options_noalttab_box.curvalue			= win_noalttab->value;
   1106 }
   1107 
   1108 static void ControlsResetDefaultsFunc( void *unused )
   1109 {
   1110 	Cbuf_AddText ("exec default.cfg\n");
   1111 	Cbuf_Execute();
   1112 
   1113 	ControlsSetMenuItemValues();
   1114 }
   1115 
   1116 static void InvertMouseFunc( void *unused )
   1117 {
   1118 	if ( s_options_invertmouse_box.curvalue == 0 )
   1119 	{
   1120 		Cvar_SetValue( "m_pitch", fabs( m_pitch->value ) );
   1121 	}
   1122 	else
   1123 	{
   1124 		Cvar_SetValue( "m_pitch", -fabs( m_pitch->value ) );
   1125 	}
   1126 }
   1127 
   1128 static void LookspringFunc( void *unused )
   1129 {
   1130 	Cvar_SetValue( "lookspring", s_options_lookspring_box.curvalue );
   1131 }
   1132 
   1133 static void LookstrafeFunc( void *unused )
   1134 {
   1135 	Cvar_SetValue( "lookstrafe", s_options_lookstrafe_box.curvalue );
   1136 }
   1137 
   1138 static void UpdateVolumeFunc( void *unused )
   1139 {
   1140 	Cvar_SetValue( "s_volume", s_options_sfxvolume_slider.curvalue / 10 );
   1141 }
   1142 
   1143 static void UpdateCDVolumeFunc( void *unused )
   1144 {
   1145 	Cvar_SetValue( "cd_nocd", !s_options_cdvolume_box.curvalue );
   1146 }
   1147 
   1148 static void ConsoleFunc( void *unused )
   1149 {
   1150 	/*
   1151 	** the proper way to do this is probably to have ToggleConsole_f accept a parameter
   1152 	*/
   1153 	extern void Key_ClearTyping( void );
   1154 
   1155 	if ( cl.attractloop )
   1156 	{
   1157 		Cbuf_AddText ("killserver\n");
   1158 		return;
   1159 	}
   1160 
   1161 	Key_ClearTyping ();
   1162 	Con_ClearNotify ();
   1163 
   1164 	M_ForceMenuOff ();
   1165 	cls.key_dest = key_console;
   1166 }
   1167 
   1168 static void UpdateSoundQualityFunc( void *unused )
   1169 {
   1170 	if ( s_options_quality_list.curvalue )
   1171 	{
   1172 		Cvar_SetValue( "s_khz", 22 );
   1173 		Cvar_SetValue( "s_loadas8bit", false );
   1174 	}
   1175 	else
   1176 	{
   1177 		Cvar_SetValue( "s_khz", 11 );
   1178 		Cvar_SetValue( "s_loadas8bit", true );
   1179 	}
   1180 	
   1181 	Cvar_SetValue( "s_primary", s_options_compatibility_list.curvalue );
   1182 
   1183 	M_DrawTextBox( 8, 120 - 48, 36, 3 );
   1184 	M_Print( 16 + 16, 120 - 48 + 8,  "Restarting the sound system. This" );
   1185 	M_Print( 16 + 16, 120 - 48 + 16, "could take up to a minute, so" );
   1186 	M_Print( 16 + 16, 120 - 48 + 24, "please be patient." );
   1187 
   1188 	// the text box won't show up unless we do a buffer swap
   1189 	re.EndFrame();
   1190 
   1191 	CL_Snd_Restart_f();
   1192 }
   1193 
   1194 void Options_MenuInit( void )
   1195 {
   1196 	static const char *cd_music_items[] =
   1197 	{
   1198 		"disabled",
   1199 		"enabled",
   1200 		0
   1201 	};
   1202 	static const char *quality_items[] =
   1203 	{
   1204 		"low", "high", 0
   1205 	};
   1206 
   1207 	static const char *compatibility_items[] =
   1208 	{
   1209 		"max compatibility", "max performance", 0
   1210 	};
   1211 
   1212 	static const char *yesno_names[] =
   1213 	{
   1214 		"no",
   1215 		"yes",
   1216 		0
   1217 	};
   1218 
   1219 	static const char *crosshair_names[] =
   1220 	{
   1221 		"none",
   1222 		"cross",
   1223 		"dot",
   1224 		"angle",
   1225 		0
   1226 	};
   1227 
   1228 	win_noalttab = Cvar_Get( "win_noalttab", "0", CVAR_ARCHIVE );
   1229 
   1230 	/*
   1231 	** configure controls menu and menu items
   1232 	*/
   1233 	s_options_menu.x = viddef.width / 2;
   1234 	s_options_menu.y = viddef.height / 2 - 58;
   1235 	s_options_menu.nitems = 0;
   1236 
   1237 	s_options_sfxvolume_slider.generic.type	= MTYPE_SLIDER;
   1238 	s_options_sfxvolume_slider.generic.x	= 0;
   1239 	s_options_sfxvolume_slider.generic.y	= 0;
   1240 	s_options_sfxvolume_slider.generic.name	= "effects volume";
   1241 	s_options_sfxvolume_slider.generic.callback	= UpdateVolumeFunc;
   1242 	s_options_sfxvolume_slider.minvalue		= 0;
   1243 	s_options_sfxvolume_slider.maxvalue		= 10;
   1244 	s_options_sfxvolume_slider.curvalue		= Cvar_VariableValue( "s_volume" ) * 10;
   1245 
   1246 	s_options_cdvolume_box.generic.type	= MTYPE_SPINCONTROL;
   1247 	s_options_cdvolume_box.generic.x		= 0;
   1248 	s_options_cdvolume_box.generic.y		= 10;
   1249 	s_options_cdvolume_box.generic.name	= "CD music";
   1250 	s_options_cdvolume_box.generic.callback	= UpdateCDVolumeFunc;
   1251 	s_options_cdvolume_box.itemnames		= cd_music_items;
   1252 	s_options_cdvolume_box.curvalue 		= !Cvar_VariableValue("cd_nocd");
   1253 
   1254 	s_options_quality_list.generic.type	= MTYPE_SPINCONTROL;
   1255 	s_options_quality_list.generic.x		= 0;
   1256 	s_options_quality_list.generic.y		= 20;;
   1257 	s_options_quality_list.generic.name		= "sound quality";
   1258 	s_options_quality_list.generic.callback = UpdateSoundQualityFunc;
   1259 	s_options_quality_list.itemnames		= quality_items;
   1260 	s_options_quality_list.curvalue			= !Cvar_VariableValue( "s_loadas8bit" );
   1261 
   1262 	s_options_compatibility_list.generic.type	= MTYPE_SPINCONTROL;
   1263 	s_options_compatibility_list.generic.x		= 0;
   1264 	s_options_compatibility_list.generic.y		= 30;
   1265 	s_options_compatibility_list.generic.name	= "sound compatibility";
   1266 	s_options_compatibility_list.generic.callback = UpdateSoundQualityFunc;
   1267 	s_options_compatibility_list.itemnames		= compatibility_items;
   1268 	s_options_compatibility_list.curvalue		= Cvar_VariableValue( "s_primary" );
   1269 
   1270 	s_options_sensitivity_slider.generic.type	= MTYPE_SLIDER;
   1271 	s_options_sensitivity_slider.generic.x		= 0;
   1272 	s_options_sensitivity_slider.generic.y		= 50;
   1273 	s_options_sensitivity_slider.generic.name	= "mouse speed";
   1274 	s_options_sensitivity_slider.generic.callback = MouseSpeedFunc;
   1275 	s_options_sensitivity_slider.minvalue		= 2;
   1276 	s_options_sensitivity_slider.maxvalue		= 22;
   1277 
   1278 	s_options_alwaysrun_box.generic.type = MTYPE_SPINCONTROL;
   1279 	s_options_alwaysrun_box.generic.x	= 0;
   1280 	s_options_alwaysrun_box.generic.y	= 60;
   1281 	s_options_alwaysrun_box.generic.name	= "always run";
   1282 	s_options_alwaysrun_box.generic.callback = AlwaysRunFunc;
   1283 	s_options_alwaysrun_box.itemnames = yesno_names;
   1284 
   1285 	s_options_invertmouse_box.generic.type = MTYPE_SPINCONTROL;
   1286 	s_options_invertmouse_box.generic.x	= 0;
   1287 	s_options_invertmouse_box.generic.y	= 70;
   1288 	s_options_invertmouse_box.generic.name	= "invert mouse";
   1289 	s_options_invertmouse_box.generic.callback = InvertMouseFunc;
   1290 	s_options_invertmouse_box.itemnames = yesno_names;
   1291 
   1292 	s_options_lookspring_box.generic.type = MTYPE_SPINCONTROL;
   1293 	s_options_lookspring_box.generic.x	= 0;
   1294 	s_options_lookspring_box.generic.y	= 80;
   1295 	s_options_lookspring_box.generic.name	= "lookspring";
   1296 	s_options_lookspring_box.generic.callback = LookspringFunc;
   1297 	s_options_lookspring_box.itemnames = yesno_names;
   1298 
   1299 	s_options_lookstrafe_box.generic.type = MTYPE_SPINCONTROL;
   1300 	s_options_lookstrafe_box.generic.x	= 0;
   1301 	s_options_lookstrafe_box.generic.y	= 90;
   1302 	s_options_lookstrafe_box.generic.name	= "lookstrafe";
   1303 	s_options_lookstrafe_box.generic.callback = LookstrafeFunc;
   1304 	s_options_lookstrafe_box.itemnames = yesno_names;
   1305 
   1306 	s_options_freelook_box.generic.type = MTYPE_SPINCONTROL;
   1307 	s_options_freelook_box.generic.x	= 0;
   1308 	s_options_freelook_box.generic.y	= 100;
   1309 	s_options_freelook_box.generic.name	= "free look";
   1310 	s_options_freelook_box.generic.callback = FreeLookFunc;
   1311 	s_options_freelook_box.itemnames = yesno_names;
   1312 
   1313 	s_options_crosshair_box.generic.type = MTYPE_SPINCONTROL;
   1314 	s_options_crosshair_box.generic.x	= 0;
   1315 	s_options_crosshair_box.generic.y	= 110;
   1316 	s_options_crosshair_box.generic.name	= "crosshair";
   1317 	s_options_crosshair_box.generic.callback = CrosshairFunc;
   1318 	s_options_crosshair_box.itemnames = crosshair_names;
   1319 /*
   1320 	s_options_noalttab_box.generic.type = MTYPE_SPINCONTROL;
   1321 	s_options_noalttab_box.generic.x	= 0;
   1322 	s_options_noalttab_box.generic.y	= 110;
   1323 	s_options_noalttab_box.generic.name	= "disable alt-tab";
   1324 	s_options_noalttab_box.generic.callback = NoAltTabFunc;
   1325 	s_options_noalttab_box.itemnames = yesno_names;
   1326 */
   1327 	s_options_joystick_box.generic.type = MTYPE_SPINCONTROL;
   1328 	s_options_joystick_box.generic.x	= 0;
   1329 	s_options_joystick_box.generic.y	= 120;
   1330 	s_options_joystick_box.generic.name	= "use joystick";
   1331 	s_options_joystick_box.generic.callback = JoystickFunc;
   1332 	s_options_joystick_box.itemnames = yesno_names;
   1333 
   1334 	s_options_customize_options_action.generic.type	= MTYPE_ACTION;
   1335 	s_options_customize_options_action.generic.x		= 0;
   1336 	s_options_customize_options_action.generic.y		= 140;
   1337 	s_options_customize_options_action.generic.name	= "customize controls";
   1338 	s_options_customize_options_action.generic.callback = CustomizeControlsFunc;
   1339 
   1340 	s_options_defaults_action.generic.type	= MTYPE_ACTION;
   1341 	s_options_defaults_action.generic.x		= 0;
   1342 	s_options_defaults_action.generic.y		= 150;
   1343 	s_options_defaults_action.generic.name	= "reset defaults";
   1344 	s_options_defaults_action.generic.callback = ControlsResetDefaultsFunc;
   1345 
   1346 	s_options_console_action.generic.type	= MTYPE_ACTION;
   1347 	s_options_console_action.generic.x		= 0;
   1348 	s_options_console_action.generic.y		= 160;
   1349 	s_options_console_action.generic.name	= "go to console";
   1350 	s_options_console_action.generic.callback = ConsoleFunc;
   1351 
   1352 	ControlsSetMenuItemValues();
   1353 
   1354 	Menu_AddItem( &s_options_menu, ( void * ) &s_options_sfxvolume_slider );
   1355 	Menu_AddItem( &s_options_menu, ( void * ) &s_options_cdvolume_box );
   1356 	Menu_AddItem( &s_options_menu, ( void * ) &s_options_quality_list );
   1357 	Menu_AddItem( &s_options_menu, ( void * ) &s_options_compatibility_list );
   1358 	Menu_AddItem( &s_options_menu, ( void * ) &s_options_sensitivity_slider );
   1359 	Menu_AddItem( &s_options_menu, ( void * ) &s_options_alwaysrun_box );
   1360 	Menu_AddItem( &s_options_menu, ( void * ) &s_options_invertmouse_box );
   1361 	Menu_AddItem( &s_options_menu, ( void * ) &s_options_lookspring_box );
   1362 	Menu_AddItem( &s_options_menu, ( void * ) &s_options_lookstrafe_box );
   1363 	Menu_AddItem( &s_options_menu, ( void * ) &s_options_freelook_box );
   1364 	Menu_AddItem( &s_options_menu, ( void * ) &s_options_crosshair_box );
   1365 	Menu_AddItem( &s_options_menu, ( void * ) &s_options_joystick_box );
   1366 	Menu_AddItem( &s_options_menu, ( void * ) &s_options_customize_options_action );
   1367 	Menu_AddItem( &s_options_menu, ( void * ) &s_options_defaults_action );
   1368 	Menu_AddItem( &s_options_menu, ( void * ) &s_options_console_action );
   1369 }
   1370 
   1371 void Options_MenuDraw (void)
   1372 {
   1373 	M_Banner( "m_banner_options" );
   1374 	Menu_AdjustCursor( &s_options_menu, 1 );
   1375 	Menu_Draw( &s_options_menu );
   1376 }
   1377 
   1378 const char *Options_MenuKey( int key )
   1379 {
   1380 	return Default_MenuKey( &s_options_menu, key );
   1381 }
   1382 
   1383 void M_Menu_Options_f (void)
   1384 {
   1385 	Options_MenuInit();
   1386 	M_PushMenu ( Options_MenuDraw, Options_MenuKey );
   1387 }
   1388 
   1389 /*
   1390 =======================================================================
   1391 
   1392 VIDEO MENU
   1393 
   1394 =======================================================================
   1395 */
   1396 
   1397 void M_Menu_Video_f (void)
   1398 {
   1399 	VID_MenuInit();
   1400 	M_PushMenu( VID_MenuDraw, VID_MenuKey );
   1401 }
   1402 
   1403 /*
   1404 =============================================================================
   1405 
   1406 END GAME MENU
   1407 
   1408 =============================================================================
   1409 */
   1410 static int credits_start_time;
   1411 static const char **credits;
   1412 static char *creditsIndex[256];
   1413 static char *creditsBuffer;
   1414 static const char *idcredits[] =
   1415 {
   1416 	"+QUAKE II BY ID SOFTWARE",
   1417 	"",
   1418 	"+PROGRAMMING",
   1419 	"John Carmack",
   1420 	"John Cash",
   1421 	"Brian Hook",
   1422 	"",
   1423 	"+ART",
   1424 	"Adrian Carmack",
   1425 	"Kevin Cloud",
   1426 	"Paul Steed",
   1427 	"",
   1428 	"+LEVEL DESIGN",
   1429 	"Tim Willits",
   1430 	"American McGee",
   1431 	"Christian Antkow",
   1432 	"Paul Jaquays",
   1433 	"Brandon James",
   1434 	"",
   1435 	"+BIZ",
   1436 	"Todd Hollenshead",
   1437 	"Barrett (Bear) Alexander",
   1438 	"Donna Jackson",
   1439 	"",
   1440 	"",
   1441 	"+SPECIAL THANKS",
   1442 	"Ben Donges for beta testing",
   1443 	"",
   1444 	"",
   1445 	"",
   1446 	"",
   1447 	"",
   1448 	"",
   1449 	"+ADDITIONAL SUPPORT",
   1450 	"",
   1451 	"+LINUX PORT AND CTF",
   1452 	"Dave \"Zoid\" Kirsch",
   1453 	"",
   1454 	"+CINEMATIC SEQUENCES",
   1455 	"Ending Cinematic by Blur Studio - ",
   1456 	"Venice, CA",
   1457 	"",
   1458 	"Environment models for Introduction",
   1459 	"Cinematic by Karl Dolgener",
   1460 	"",
   1461 	"Assistance with environment design",
   1462 	"by Cliff Iwai",
   1463 	"",
   1464 	"+SOUND EFFECTS AND MUSIC",
   1465 	"Sound Design by Soundelux Media Labs.",
   1466 	"Music Composed and Produced by",
   1467 	"Soundelux Media Labs.  Special thanks",
   1468 	"to Bill Brown, Tom Ozanich, Brian",
   1469 	"Celano, Jeff Eisner, and The Soundelux",
   1470 	"Players.",
   1471 	"",
   1472 	"\"Level Music\" by Sonic Mayhem",
   1473 	"www.sonicmayhem.com",
   1474 	"",
   1475 	"\"Quake II Theme Song\"",
   1476 	"(C) 1997 Rob Zombie. All Rights",
   1477 	"Reserved.",
   1478 	"",
   1479 	"Track 10 (\"Climb\") by Jer Sypult",
   1480 	"",
   1481 	"Voice of computers by",
   1482 	"Carly Staehlin-Taylor",
   1483 	"",
   1484 	"+THANKS TO ACTIVISION",
   1485 	"+IN PARTICULAR:",
   1486 	"",
   1487 	"John Tam",
   1488 	"Steve Rosenthal",
   1489 	"Marty Stratton",
   1490 	"Henk Hartong",
   1491 	"",
   1492 	"Quake II(tm) (C)1997 Id Software, Inc.",
   1493 	"All Rights Reserved.  Distributed by",
   1494 	"Activision, Inc. under license.",
   1495 	"Quake II(tm), the Id Software name,",
   1496 	"the \"Q II\"(tm) logo and id(tm)",
   1497 	"logo are trademarks of Id Software,",
   1498 	"Inc. Activision(R) is a registered",
   1499 	"trademark of Activision, Inc. All",
   1500 	"other trademarks and trade names are",
   1501 	"properties of their respective owners.",
   1502 	0
   1503 };
   1504 
   1505 static const char *xatcredits[] =
   1506 {
   1507 	"+QUAKE II MISSION PACK: THE RECKONING",
   1508 	"+BY",
   1509 	"+XATRIX ENTERTAINMENT, INC.",
   1510 	"",
   1511 	"+DESIGN AND DIRECTION",
   1512 	"Drew Markham",
   1513 	"",
   1514 	"+PRODUCED BY",
   1515 	"Greg Goodrich",
   1516 	"",
   1517 	"+PROGRAMMING",
   1518 	"Rafael Paiz",
   1519 	"",
   1520 	"+LEVEL DESIGN / ADDITIONAL GAME DESIGN",
   1521 	"Alex Mayberry",
   1522 	"",
   1523 	"+LEVEL DESIGN",
   1524 	"Mal Blackwell",
   1525 	"Dan Koppel",
   1526 	"",
   1527 	"+ART DIRECTION",
   1528 	"Michael \"Maxx\" Kaufman",
   1529 	"",
   1530 	"+COMPUTER GRAPHICS SUPERVISOR AND",
   1531 	"+CHARACTER ANIMATION DIRECTION",
   1532 	"Barry Dempsey",
   1533 	"",
   1534 	"+SENIOR ANIMATOR AND MODELER",
   1535 	"Jason Hoover",
   1536 	"",
   1537 	"+CHARACTER ANIMATION AND",
   1538 	"+MOTION CAPTURE SPECIALIST",
   1539 	"Amit Doron",
   1540 	"",
   1541 	"+ART",
   1542 	"Claire Praderie-Markham",
   1543 	"Viktor Antonov",
   1544 	"Corky Lehmkuhl",
   1545 	"",
   1546 	"+INTRODUCTION ANIMATION",
   1547 	"Dominique Drozdz",
   1548 	"",
   1549 	"+ADDITIONAL LEVEL DESIGN",
   1550 	"Aaron Barber",
   1551 	"Rhett Baldwin",
   1552 	"",
   1553 	"+3D CHARACTER ANIMATION TOOLS",
   1554 	"Gerry Tyra, SA Technology",
   1555 	"",
   1556 	"+ADDITIONAL EDITOR TOOL PROGRAMMING",
   1557 	"Robert Duffy",
   1558 	"",
   1559 	"+ADDITIONAL PROGRAMMING",
   1560 	"Ryan Feltrin",
   1561 	"",
   1562 	"+PRODUCTION COORDINATOR",
   1563 	"Victoria Sylvester",
   1564 	"",
   1565 	"+SOUND DESIGN",
   1566 	"Gary Bradfield",
   1567 	"",
   1568 	"+MUSIC BY",
   1569 	"Sonic Mayhem",
   1570 	"",
   1571 	"",
   1572 	"",
   1573 	"+SPECIAL THANKS",
   1574 	"+TO",
   1575 	"+OUR FRIENDS AT ID SOFTWARE",
   1576 	"",
   1577 	"John Carmack",
   1578 	"John Cash",
   1579 	"Brian Hook",
   1580 	"Adrian Carmack",
   1581 	"Kevin Cloud",
   1582 	"Paul Steed",
   1583 	"Tim Willits",
   1584 	"Christian Antkow",
   1585 	"Paul Jaquays",
   1586 	"Brandon James",
   1587 	"Todd Hollenshead",
   1588 	"Barrett (Bear) Alexander",
   1589 	"Dave \"Zoid\" Kirsch",
   1590 	"Donna Jackson",
   1591 	"",
   1592 	"",
   1593 	"",
   1594 	"+THANKS TO ACTIVISION",
   1595 	"+IN PARTICULAR:",
   1596 	"",
   1597 	"Marty Stratton",
   1598 	"Henk \"The Original Ripper\" Hartong",
   1599 	"Kevin Kraff",
   1600 	"Jamey Gottlieb",
   1601 	"Chris Hepburn",
   1602 	"",
   1603 	"+AND THE GAME TESTERS",
   1604 	"",
   1605 	"Tim Vanlaw",
   1606 	"Doug Jacobs",
   1607 	"Steven Rosenthal",
   1608 	"David Baker",
   1609 	"Chris Campbell",
   1610 	"Aaron Casillas",
   1611 	"Steve Elwell",
   1612 	"Derek Johnstone",
   1613 	"Igor Krinitskiy",
   1614 	"Samantha Lee",
   1615 	"Michael Spann",
   1616 	"Chris Toft",
   1617 	"Juan Valdes",
   1618 	"",
   1619 	"+THANKS TO INTERGRAPH COMPUTER SYTEMS",
   1620 	"+IN PARTICULAR:",
   1621 	"",
   1622 	"Michael T. Nicolaou",
   1623 	"",
   1624 	"",
   1625 	"Quake II Mission Pack: The Reckoning",
   1626 	"(tm) (C)1998 Id Software, Inc. All",
   1627 	"Rights Reserved. Developed by Xatrix",
   1628 	"Entertainment, Inc. for Id Software,",
   1629 	"Inc. Distributed by Activision Inc.",
   1630 	"under license. Quake(R) is a",
   1631 	"registered trademark of Id Software,",
   1632 	"Inc. Quake II Mission Pack: The",
   1633 	"Reckoning(tm), Quake II(tm), the Id",
   1634 	"Software name, the \"Q II\"(tm) logo",
   1635 	"and id(tm) logo are trademarks of Id",
   1636 	"Software, Inc. Activision(R) is a",
   1637 	"registered trademark of Activision,",
   1638 	"Inc. Xatrix(R) is a registered",
   1639 	"trademark of Xatrix Entertainment,",
   1640 	"Inc. All other trademarks and trade",
   1641 	"names are properties of their",
   1642 	"respective owners.",
   1643 	0
   1644 };
   1645 
   1646 static const char *roguecredits[] =
   1647 {
   1648 	"+QUAKE II MISSION PACK 2: GROUND ZERO",
   1649 	"+BY",
   1650 	"+ROGUE ENTERTAINMENT, INC.",
   1651 	"",
   1652 	"+PRODUCED BY",
   1653 	"Jim Molinets",
   1654 	"",
   1655 	"+PROGRAMMING",
   1656 	"Peter Mack",
   1657 	"Patrick Magruder",
   1658 	"",
   1659 	"+LEVEL DESIGN",
   1660 	"Jim Molinets",
   1661 	"Cameron Lamprecht",
   1662 	"Berenger Fish",
   1663 	"Robert Selitto",
   1664 	"Steve Tietze",
   1665 	"Steve Thoms",
   1666 	"",
   1667 	"+ART DIRECTION",
   1668 	"Rich Fleider",
   1669 	"",
   1670 	"+ART",
   1671 	"Rich Fleider",
   1672 	"Steve Maines",
   1673 	"Won Choi",
   1674 	"",
   1675 	"+ANIMATION SEQUENCES",
   1676 	"Creat Studios",
   1677 	"Steve Maines",
   1678 	"",
   1679 	"+ADDITIONAL LEVEL DESIGN",
   1680 	"Rich Fleider",
   1681 	"Steve Maines",
   1682 	"Peter Mack",
   1683 	"",
   1684 	"+SOUND",
   1685 	"James Grunke",
   1686 	"",
   1687 	"+GROUND ZERO THEME",
   1688 	"+AND",
   1689 	"+MUSIC BY",
   1690 	"Sonic Mayhem",
   1691 	"",
   1692 	"+VWEP MODELS",
   1693 	"Brent \"Hentai\" Dill",
   1694 	"",
   1695 	"",
   1696 	"",
   1697 	"+SPECIAL THANKS",
   1698 	"+TO",
   1699 	"+OUR FRIENDS AT ID SOFTWARE",
   1700 	"",
   1701 	"John Carmack",
   1702 	"John Cash",
   1703 	"Brian Hook",
   1704 	"Adrian Carmack",
   1705 	"Kevin Cloud",
   1706 	"Paul Steed",
   1707 	"Tim Willits",
   1708 	"Christian Antkow",
   1709 	"Paul Jaquays",
   1710 	"Brandon James",
   1711 	"Todd Hollenshead",
   1712 	"Barrett (Bear) Alexander",
   1713 	"Katherine Anna Kang",
   1714 	"Donna Jackson",
   1715 	"Dave \"Zoid\" Kirsch",
   1716 	"",
   1717 	"",
   1718 	"",
   1719 	"+THANKS TO ACTIVISION",
   1720 	"+IN PARTICULAR:",
   1721 	"",
   1722 	"Marty Stratton",
   1723 	"Henk Hartong",
   1724 	"Mitch Lasky",
   1725 	"Steve Rosenthal",
   1726 	"Steve Elwell",
   1727 	"",
   1728 	"+AND THE GAME TESTERS",
   1729 	"",
   1730 	"The Ranger Clan",
   1731 	"Dave \"Zoid\" Kirsch",
   1732 	"Nihilistic Software",
   1733 	"Robert Duffy",
   1734 	"",
   1735 	"And Countless Others",
   1736 	"",
   1737 	"",
   1738 	"",
   1739 	"Quake II Mission Pack 2: Ground Zero",
   1740 	"(tm) (C)1998 Id Software, Inc. All",
   1741 	"Rights Reserved. Developed by Rogue",
   1742 	"Entertainment, Inc. for Id Software,",
   1743 	"Inc. Distributed by Activision Inc.",
   1744 	"under license. Quake(R) is a",
   1745 	"registered trademark of Id Software,",
   1746 	"Inc. Quake II Mission Pack 2: Ground",
   1747 	"Zero(tm), Quake II(tm), the Id",
   1748 	"Software name, the \"Q II\"(tm) logo",
   1749 	"and id(tm) logo are trademarks of Id",
   1750 	"Software, Inc. Activision(R) is a",
   1751 	"registered trademark of Activision,",
   1752 	"Inc. Rogue(R) is a registered",
   1753 	"trademark of Rogue Entertainment,",
   1754 	"Inc. All other trademarks and trade",
   1755 	"names are properties of their",
   1756 	"respective owners.",
   1757 	0
   1758 };
   1759 
   1760 
   1761 void M_Credits_MenuDraw( void )
   1762 {
   1763 	int i, y;
   1764 
   1765 	/*
   1766 	** draw the credits
   1767 	*/
   1768 	for ( i = 0, y = viddef.height - ( ( cls.realtime - credits_start_time ) / 40.0F ); credits[i] && y < viddef.height; y += 10, i++ )
   1769 	{
   1770 		int j, stringoffset = 0;
   1771 		int bold = false;
   1772 
   1773 		if ( y <= -8 )
   1774 			continue;
   1775 
   1776 		if ( credits[i][0] == '+' )
   1777 		{
   1778 			bold = true;
   1779 			stringoffset = 1;
   1780 		}
   1781 		else
   1782 		{
   1783 			bold = false;
   1784 			stringoffset = 0;
   1785 		}
   1786 
   1787 		for ( j = 0; credits[i][j+stringoffset]; j++ )
   1788 		{
   1789 			int x;
   1790 
   1791 			x = ( viddef.width - strlen( credits[i] ) * 8 - stringoffset * 8 ) / 2 + ( j + stringoffset ) * 8;
   1792 
   1793 			if ( bold )
   1794 				re.DrawChar( x, y, credits[i][j+stringoffset] + 128 );
   1795 			else
   1796 				re.DrawChar( x, y, credits[i][j+stringoffset] );
   1797 		}
   1798 	}
   1799 
   1800 	if ( y < 0 )
   1801 		credits_start_time = cls.realtime;
   1802 }
   1803 
   1804 const char *M_Credits_Key( int key )
   1805 {
   1806 	switch (key)
   1807 	{
   1808 	case K_ESCAPE:
   1809 		if (creditsBuffer)
   1810 			FS_FreeFile (creditsBuffer);
   1811 		M_PopMenu ();
   1812 		break;
   1813 	}
   1814 
   1815 	return menu_out_sound;
   1816 
   1817 }
   1818 
   1819 extern int Developer_searchpath (int who);
   1820 
   1821 void M_Menu_Credits_f( void )
   1822 {
   1823 	int		n;
   1824 	int		count;
   1825 	char	*p;
   1826 	int		isdeveloper = 0;
   1827 
   1828 	creditsBuffer = NULL;
   1829 	count = FS_LoadFile ("credits", &creditsBuffer);
   1830 	if (count != -1)
   1831 	{
   1832 		p = creditsBuffer;
   1833 		for (n = 0; n < 255; n++)
   1834 		{
   1835 			creditsIndex[n] = p;
   1836 			while (*p != '\r' && *p != '\n')
   1837 			{
   1838 				p++;
   1839 				if (--count == 0)
   1840 					break;
   1841 			}
   1842 			if (*p == '\r')
   1843 			{
   1844 				*p++ = 0;
   1845 				if (--count == 0)
   1846 					break;
   1847 			}
   1848 			*p++ = 0;
   1849 			if (--count == 0)
   1850 				break;
   1851 		}
   1852 		creditsIndex[++n] = 0;
   1853 		credits = creditsIndex;
   1854 	}
   1855 	else
   1856 	{
   1857 		isdeveloper = Developer_searchpath (1);
   1858 		
   1859 		if (isdeveloper == 1)			// xatrix
   1860 			credits = xatcredits;
   1861 		else if (isdeveloper == 2)		// ROGUE
   1862 			credits = roguecredits;
   1863 		else
   1864 		{
   1865 			credits = idcredits;	
   1866 		}
   1867 
   1868 	}
   1869 
   1870 	credits_start_time = cls.realtime;
   1871 	M_PushMenu( M_Credits_MenuDraw, M_Credits_Key);
   1872 }
   1873 
   1874 /*
   1875 =============================================================================
   1876 
   1877 GAME MENU
   1878 
   1879 =============================================================================
   1880 */
   1881 
   1882 static int		m_game_cursor;
   1883 
   1884 static menuframework_s	s_game_menu;
   1885 static menuaction_s		s_easy_game_action;
   1886 static menuaction_s		s_medium_game_action;
   1887 static menuaction_s		s_hard_game_action;
   1888 static menuaction_s		s_load_game_action;
   1889 static menuaction_s		s_save_game_action;
   1890 static menuaction_s		s_credits_action;
   1891 static menuseparator_s	s_blankline;
   1892 
   1893 static void StartGame( void )
   1894 {
   1895 	// disable updates and start the cinematic going
   1896 	cl.servercount = -1;
   1897 	M_ForceMenuOff ();
   1898 	Cvar_SetValue( "deathmatch", 0 );
   1899 	Cvar_SetValue( "coop", 0 );
   1900 
   1901 	Cvar_SetValue( "gamerules", 0 );		//PGM
   1902 
   1903 	Cbuf_AddText ("loading ; killserver ; wait ; newgame\n");
   1904 	cls.key_dest = key_game;
   1905 }
   1906 
   1907 static void EasyGameFunc( void *data )
   1908 {
   1909 	Cvar_ForceSet( "skill", "0" );
   1910 	StartGame();
   1911 }
   1912 
   1913 static void MediumGameFunc( void *data )
   1914 {
   1915 	Cvar_ForceSet( "skill", "1" );
   1916 	StartGame();
   1917 }
   1918 
   1919 static void HardGameFunc( void *data )
   1920 {
   1921 	Cvar_ForceSet( "skill", "2" );
   1922 	StartGame();
   1923 }
   1924 
   1925 static void LoadGameFunc( void *unused )
   1926 {
   1927 	M_Menu_LoadGame_f ();
   1928 }
   1929 
   1930 static void SaveGameFunc( void *unused )
   1931 {
   1932 	M_Menu_SaveGame_f();
   1933 }
   1934 
   1935 static void CreditsFunc( void *unused )
   1936 {
   1937 	M_Menu_Credits_f();
   1938 }
   1939 
   1940 void Game_MenuInit( void )
   1941 {
   1942 	static const char *difficulty_names[] =
   1943 	{
   1944 		"easy",
   1945 		"medium",
   1946 		"hard",
   1947 		0
   1948 	};
   1949 
   1950 	s_game_menu.x = viddef.width * 0.50;
   1951 	s_game_menu.nitems = 0;
   1952 
   1953 	s_easy_game_action.generic.type	= MTYPE_ACTION;
   1954 	s_easy_game_action.generic.flags  = QMF_LEFT_JUSTIFY;
   1955 	s_easy_game_action.generic.x		= 0;
   1956 	s_easy_game_action.generic.y		= 0;
   1957 	s_easy_game_action.generic.name	= "easy";
   1958 	s_easy_game_action.generic.callback = EasyGameFunc;
   1959 
   1960 	s_medium_game_action.generic.type	= MTYPE_ACTION;
   1961 	s_medium_game_action.generic.flags  = QMF_LEFT_JUSTIFY;
   1962 	s_medium_game_action.generic.x		= 0;
   1963 	s_medium_game_action.generic.y		= 10;
   1964 	s_medium_game_action.generic.name	= "medium";
   1965 	s_medium_game_action.generic.callback = MediumGameFunc;
   1966 
   1967 	s_hard_game_action.generic.type	= MTYPE_ACTION;
   1968 	s_hard_game_action.generic.flags  = QMF_LEFT_JUSTIFY;
   1969 	s_hard_game_action.generic.x		= 0;
   1970 	s_hard_game_action.generic.y		= 20;
   1971 	s_hard_game_action.generic.name	= "hard";
   1972 	s_hard_game_action.generic.callback = HardGameFunc;
   1973 
   1974 	s_blankline.generic.type = MTYPE_SEPARATOR;
   1975 
   1976 	s_load_game_action.generic.type	= MTYPE_ACTION;
   1977 	s_load_game_action.generic.flags  = QMF_LEFT_JUSTIFY;
   1978 	s_load_game_action.generic.x		= 0;
   1979 	s_load_game_action.generic.y		= 40;
   1980 	s_load_game_action.generic.name	= "load game";
   1981 	s_load_game_action.generic.callback = LoadGameFunc;
   1982 
   1983 	s_save_game_action.generic.type	= MTYPE_ACTION;
   1984 	s_save_game_action.generic.flags  = QMF_LEFT_JUSTIFY;
   1985 	s_save_game_action.generic.x		= 0;
   1986 	s_save_game_action.generic.y		= 50;
   1987 	s_save_game_action.generic.name	= "save game";
   1988 	s_save_game_action.generic.callback = SaveGameFunc;
   1989 
   1990 	s_credits_action.generic.type	= MTYPE_ACTION;
   1991 	s_credits_action.generic.flags  = QMF_LEFT_JUSTIFY;
   1992 	s_credits_action.generic.x		= 0;
   1993 	s_credits_action.generic.y		= 60;
   1994 	s_credits_action.generic.name	= "credits";
   1995 	s_credits_action.generic.callback = CreditsFunc;
   1996 
   1997 	Menu_AddItem( &s_game_menu, ( void * ) &s_easy_game_action );
   1998 	Menu_AddItem( &s_game_menu, ( void * ) &s_medium_game_action );
   1999 	Menu_AddItem( &s_game_menu, ( void * ) &s_hard_game_action );
   2000 	Menu_AddItem( &s_game_menu, ( void * ) &s_blankline );
   2001 	Menu_AddItem( &s_game_menu, ( void * ) &s_load_game_action );
   2002 	Menu_AddItem( &s_game_menu, ( void * ) &s_save_game_action );
   2003 	Menu_AddItem( &s_game_menu, ( void * ) &s_blankline );
   2004 	Menu_AddItem( &s_game_menu, ( void * ) &s_credits_action );
   2005 
   2006 	Menu_Center( &s_game_menu );
   2007 }
   2008 
   2009 void Game_MenuDraw( void )
   2010 {
   2011 	M_Banner( "m_banner_game" );
   2012 	Menu_AdjustCursor( &s_game_menu, 1 );
   2013 	Menu_Draw( &s_game_menu );
   2014 }
   2015 
   2016 const char *Game_MenuKey( int key )
   2017 {
   2018 	return Default_MenuKey( &s_game_menu, key );
   2019 }
   2020 
   2021 void M_Menu_Game_f (void)
   2022 {
   2023 	Game_MenuInit();
   2024 	M_PushMenu( Game_MenuDraw, Game_MenuKey );
   2025 	m_game_cursor = 1;
   2026 }
   2027 
   2028 /*
   2029 =============================================================================
   2030 
   2031 LOADGAME MENU
   2032 
   2033 =============================================================================
   2034 */
   2035 
   2036 #define	MAX_SAVEGAMES	15
   2037 
   2038 static menuframework_s	s_savegame_menu;
   2039 
   2040 static menuframework_s	s_loadgame_menu;
   2041 static menuaction_s		s_loadgame_actions[MAX_SAVEGAMES];
   2042 
   2043 char		m_savestrings[MAX_SAVEGAMES][32];
   2044 qboolean	m_savevalid[MAX_SAVEGAMES];
   2045 
   2046 void Create_Savestrings (void)
   2047 {
   2048 	int		i;
   2049 	FILE	*f;
   2050 	char	name[MAX_OSPATH];
   2051 
   2052 	for (i=0 ; i<MAX_SAVEGAMES ; i++)
   2053 	{
   2054 		Com_sprintf (name, sizeof(name), "%s/save/save%i/server.ssv", FS_Gamedir(), i);
   2055 		f = fopen (name, "rb");
   2056 		if (!f)
   2057 		{
   2058 			strcpy (m_savestrings[i], "<EMPTY>");
   2059 			m_savevalid[i] = false;
   2060 		}
   2061 		else
   2062 		{
   2063 			FS_Read (m_savestrings[i], sizeof(m_savestrings[i]), f);
   2064 			fclose (f);
   2065 			m_savevalid[i] = true;
   2066 		}
   2067 	}
   2068 }
   2069 
   2070 void LoadGameCallback( void *self )
   2071 {
   2072 	menuaction_s *a = ( menuaction_s * ) self;
   2073 
   2074 	if ( m_savevalid[ a->generic.localdata[0] ] )
   2075 		Cbuf_AddText (va("load save%i\n",  a->generic.localdata[0] ) );
   2076 	M_ForceMenuOff ();
   2077 }
   2078 
   2079 void LoadGame_MenuInit( void )
   2080 {
   2081 	int i;
   2082 
   2083 	s_loadgame_menu.x = viddef.width / 2 - 120;
   2084 	s_loadgame_menu.y = viddef.height / 2 - 58;
   2085 	s_loadgame_menu.nitems = 0;
   2086 
   2087 	Create_Savestrings();
   2088 
   2089 	for ( i = 0; i < MAX_SAVEGAMES; i++ )
   2090 	{
   2091 		s_loadgame_actions[i].generic.name			= m_savestrings[i];
   2092 		s_loadgame_actions[i].generic.flags			= QMF_LEFT_JUSTIFY;
   2093 		s_loadgame_actions[i].generic.localdata[0]	= i;
   2094 		s_loadgame_actions[i].generic.callback		= LoadGameCallback;
   2095 
   2096 		s_loadgame_actions[i].generic.x = 0;
   2097 		s_loadgame_actions[i].generic.y = ( i ) * 10;
   2098 		if (i>0)	// separate from autosave
   2099 			s_loadgame_actions[i].generic.y += 10;
   2100 
   2101 		s_loadgame_actions[i].generic.type = MTYPE_ACTION;
   2102 
   2103 		Menu_AddItem( &s_loadgame_menu, &s_loadgame_actions[i] );
   2104 	}
   2105 }
   2106 
   2107 void LoadGame_MenuDraw( void )
   2108 {
   2109 	M_Banner( "m_banner_load_game" );
   2110 //	Menu_AdjustCursor( &s_loadgame_menu, 1 );
   2111 	Menu_Draw( &s_loadgame_menu );
   2112 }
   2113 
   2114 const char *LoadGame_MenuKey( int key )
   2115 {
   2116 	if ( key == K_ESCAPE || key == K_ENTER )
   2117 	{
   2118 		s_savegame_menu.cursor = s_loadgame_menu.cursor - 1;
   2119 		if ( s_savegame_menu.cursor < 0 )
   2120 			s_savegame_menu.cursor = 0;
   2121 	}
   2122 	return Default_MenuKey( &s_loadgame_menu, key );
   2123 }
   2124 
   2125 void M_Menu_LoadGame_f (void)
   2126 {
   2127 	LoadGame_MenuInit();
   2128 	M_PushMenu( LoadGame_MenuDraw, LoadGame_MenuKey );
   2129 }
   2130 
   2131 
   2132 /*
   2133 =============================================================================
   2134 
   2135 SAVEGAME MENU
   2136 
   2137 =============================================================================
   2138 */
   2139 static menuframework_s	s_savegame_menu;
   2140 static menuaction_s		s_savegame_actions[MAX_SAVEGAMES];
   2141 
   2142 void SaveGameCallback( void *self )
   2143 {
   2144 	menuaction_s *a = ( menuaction_s * ) self;
   2145 
   2146 	Cbuf_AddText (va("save save%i\n", a->generic.localdata[0] ));
   2147 	M_ForceMenuOff ();
   2148 }
   2149 
   2150 void SaveGame_MenuDraw( void )
   2151 {
   2152 	M_Banner( "m_banner_save_game" );
   2153 	Menu_AdjustCursor( &s_savegame_menu, 1 );
   2154 	Menu_Draw( &s_savegame_menu );
   2155 }
   2156 
   2157 void SaveGame_MenuInit( void )
   2158 {
   2159 	int i;
   2160 
   2161 	s_savegame_menu.x = viddef.width / 2 - 120;
   2162 	s_savegame_menu.y = viddef.height / 2 - 58;
   2163 	s_savegame_menu.nitems = 0;
   2164 
   2165 	Create_Savestrings();
   2166 
   2167 	// don't include the autosave slot
   2168 	for ( i = 0; i < MAX_SAVEGAMES-1; i++ )
   2169 	{
   2170 		s_savegame_actions[i].generic.name = m_savestrings[i+1];
   2171 		s_savegame_actions[i].generic.localdata[0] = i+1;
   2172 		s_savegame_actions[i].generic.flags = QMF_LEFT_JUSTIFY;
   2173 		s_savegame_actions[i].generic.callback = SaveGameCallback;
   2174 
   2175 		s_savegame_actions[i].generic.x = 0;
   2176 		s_savegame_actions[i].generic.y = ( i ) * 10;
   2177 
   2178 		s_savegame_actions[i].generic.type = MTYPE_ACTION;
   2179 
   2180 		Menu_AddItem( &s_savegame_menu, &s_savegame_actions[i] );
   2181 	}
   2182 }
   2183 
   2184 const char *SaveGame_MenuKey( int key )
   2185 {
   2186 	if ( key == K_ENTER || key == K_ESCAPE )
   2187 	{
   2188 		s_loadgame_menu.cursor = s_savegame_menu.cursor - 1;
   2189 		if ( s_loadgame_menu.cursor < 0 )
   2190 			s_loadgame_menu.cursor = 0;
   2191 	}
   2192 	return Default_MenuKey( &s_savegame_menu, key );
   2193 }
   2194 
   2195 void M_Menu_SaveGame_f (void)
   2196 {
   2197 	if (!Com_ServerState())
   2198 		return;		// not playing a game
   2199 
   2200 	SaveGame_MenuInit();
   2201 	M_PushMenu( SaveGame_MenuDraw, SaveGame_MenuKey );
   2202 	Create_Savestrings ();
   2203 }
   2204 
   2205 
   2206 /*
   2207 =============================================================================
   2208 
   2209 JOIN SERVER MENU
   2210 
   2211 =============================================================================
   2212 */
   2213 #define MAX_LOCAL_SERVERS 8
   2214 
   2215 static menuframework_s	s_joinserver_menu;
   2216 static menuseparator_s	s_joinserver_server_title;
   2217 static menuaction_s		s_joinserver_search_action;
   2218 static menuaction_s		s_joinserver_address_book_action;
   2219 static menuaction_s		s_joinserver_server_actions[MAX_LOCAL_SERVERS];
   2220 
   2221 int		m_num_servers;
   2222 #define	NO_SERVER_STRING	"<no server>"
   2223 
   2224 // user readable information
   2225 static char local_server_names[MAX_LOCAL_SERVERS][80];
   2226 
   2227 // network address
   2228 static netadr_t local_server_netadr[MAX_LOCAL_SERVERS];
   2229 
   2230 void M_AddToServerList (netadr_t adr, char *info)
   2231 {
   2232 	int		i;
   2233 
   2234 	if (m_num_servers == MAX_LOCAL_SERVERS)
   2235 		return;
   2236 	while ( *info == ' ' )
   2237 		info++;
   2238 
   2239 	// ignore if duplicated
   2240 	for (i=0 ; i<m_num_servers ; i++)
   2241 		if (!strcmp(info, local_server_names[i]))
   2242 			return;
   2243 
   2244 	local_server_netadr[m_num_servers] = adr;
   2245 	strncpy (local_server_names[m_num_servers], info, sizeof(local_server_names[0])-1);
   2246 	m_num_servers++;
   2247 }
   2248 
   2249 
   2250 void JoinServerFunc( void *self )
   2251 {
   2252 	char	buffer[128];
   2253 	int		index;
   2254 
   2255 	index = ( menuaction_s * ) self - s_joinserver_server_actions;
   2256 
   2257 	if ( Q_stricmp( local_server_names[index], NO_SERVER_STRING ) == 0 )
   2258 		return;
   2259 
   2260 	if (index >= m_num_servers)
   2261 		return;
   2262 
   2263 	Com_sprintf (buffer, sizeof(buffer), "connect %s\n", NET_AdrToString (local_server_netadr[index]));
   2264 	Cbuf_AddText (buffer);
   2265 	M_ForceMenuOff ();
   2266 }
   2267 
   2268 void AddressBookFunc( void *self )
   2269 {
   2270 	M_Menu_AddressBook_f();
   2271 }
   2272 
   2273 void NullCursorDraw( void *self )
   2274 {
   2275 }
   2276 
   2277 void SearchLocalGames( void )
   2278 {
   2279 	int		i;
   2280 
   2281 	m_num_servers = 0;
   2282 	for (i=0 ; i<MAX_LOCAL_SERVERS ; i++)
   2283 		strcpy (local_server_names[i], NO_SERVER_STRING);
   2284 
   2285 	M_DrawTextBox( 8, 120 - 48, 36, 3 );
   2286 	M_Print( 16 + 16, 120 - 48 + 8,  "Searching for local servers, this" );
   2287 	M_Print( 16 + 16, 120 - 48 + 16, "could take up to a minute, so" );
   2288 	M_Print( 16 + 16, 120 - 48 + 24, "please be patient." );
   2289 
   2290 	// the text box won't show up unless we do a buffer swap
   2291 	re.EndFrame();
   2292 
   2293 	// send out info packets
   2294 	CL_PingServers_f();
   2295 }
   2296 
   2297 void SearchLocalGamesFunc( void *self )
   2298 {
   2299 	SearchLocalGames();
   2300 }
   2301 
   2302 void JoinServer_MenuInit( void )
   2303 {
   2304 	int i;
   2305 
   2306 	s_joinserver_menu.x = viddef.width * 0.50 - 120;
   2307 	s_joinserver_menu.nitems = 0;
   2308 
   2309 	s_joinserver_address_book_action.generic.type	= MTYPE_ACTION;
   2310 	s_joinserver_address_book_action.generic.name	= "address book";
   2311 	s_joinserver_address_book_action.generic.flags	= QMF_LEFT_JUSTIFY;
   2312 	s_joinserver_address_book_action.generic.x		= 0;
   2313 	s_joinserver_address_book_action.generic.y		= 0;
   2314 	s_joinserver_address_book_action.generic.callback = AddressBookFunc;
   2315 
   2316 	s_joinserver_search_action.generic.type = MTYPE_ACTION;
   2317 	s_joinserver_search_action.generic.name	= "refresh server list";
   2318 	s_joinserver_search_action.generic.flags	= QMF_LEFT_JUSTIFY;
   2319 	s_joinserver_search_action.generic.x	= 0;
   2320 	s_joinserver_search_action.generic.y	= 10;
   2321 	s_joinserver_search_action.generic.callback = SearchLocalGamesFunc;
   2322 	s_joinserver_search_action.generic.statusbar = "search for servers";
   2323 
   2324 	s_joinserver_server_title.generic.type = MTYPE_SEPARATOR;
   2325 	s_joinserver_server_title.generic.name = "connect to...";
   2326 	s_joinserver_server_title.generic.x    = 80;
   2327 	s_joinserver_server_title.generic.y	   = 30;
   2328 
   2329 	for ( i = 0; i < MAX_LOCAL_SERVERS; i++ )
   2330 	{
   2331 		s_joinserver_server_actions[i].generic.type	= MTYPE_ACTION;
   2332 		strcpy (local_server_names[i], NO_SERVER_STRING);
   2333 		s_joinserver_server_actions[i].generic.name	= local_server_names[i];
   2334 		s_joinserver_server_actions[i].generic.flags	= QMF_LEFT_JUSTIFY;
   2335 		s_joinserver_server_actions[i].generic.x		= 0;
   2336 		s_joinserver_server_actions[i].generic.y		= 40 + i*10;
   2337 		s_joinserver_server_actions[i].generic.callback = JoinServerFunc;
   2338 		s_joinserver_server_actions[i].generic.statusbar = "press ENTER to connect";
   2339 	}
   2340 
   2341 	Menu_AddItem( &s_joinserver_menu, &s_joinserver_address_book_action );
   2342 	Menu_AddItem( &s_joinserver_menu, &s_joinserver_server_title );
   2343 	Menu_AddItem( &s_joinserver_menu, &s_joinserver_search_action );
   2344 
   2345 	for ( i = 0; i < 8; i++ )
   2346 		Menu_AddItem( &s_joinserver_menu, &s_joinserver_server_actions[i] );
   2347 
   2348 	Menu_Center( &s_joinserver_menu );
   2349 
   2350 	SearchLocalGames();
   2351 }
   2352 
   2353 void JoinServer_MenuDraw(void)
   2354 {
   2355 	M_Banner( "m_banner_join_server" );
   2356 	Menu_Draw( &s_joinserver_menu );
   2357 }
   2358 
   2359 
   2360 const char *JoinServer_MenuKey( int key )
   2361 {
   2362 	return Default_MenuKey( &s_joinserver_menu, key );
   2363 }
   2364 
   2365 void M_Menu_JoinServer_f (void)
   2366 {
   2367 	JoinServer_MenuInit();
   2368 	M_PushMenu( JoinServer_MenuDraw, JoinServer_MenuKey );
   2369 }
   2370 
   2371 
   2372 /*
   2373 =============================================================================
   2374 
   2375 START SERVER MENU
   2376 
   2377 =============================================================================
   2378 */
   2379 static menuframework_s s_startserver_menu;
   2380 static char **mapnames;
   2381 static int	  nummaps;
   2382 
   2383 static menuaction_s	s_startserver_start_action;
   2384 static menuaction_s	s_startserver_dmoptions_action;
   2385 static menufield_s	s_timelimit_field;
   2386 static menufield_s	s_fraglimit_field;
   2387 static menufield_s	s_maxclients_field;
   2388 static menufield_s	s_hostname_field;
   2389 static menulist_s	s_startmap_list;
   2390 static menulist_s	s_rules_box;
   2391 
   2392 void DMOptionsFunc( void *self )
   2393 {
   2394 	if (s_rules_box.curvalue == 1)
   2395 		return;
   2396 	M_Menu_DMOptions_f();
   2397 }
   2398 
   2399 void RulesChangeFunc ( void *self )
   2400 {
   2401 	// DM
   2402 	if (s_rules_box.curvalue == 0)
   2403 	{
   2404 		s_maxclients_field.generic.statusbar = NULL;
   2405 		s_startserver_dmoptions_action.generic.statusbar = NULL;
   2406 	}
   2407 	else if(s_rules_box.curvalue == 1)		// coop				// PGM
   2408 	{
   2409 		s_maxclients_field.generic.statusbar = "4 maximum for cooperative";
   2410 		if (atoi(s_maxclients_field.buffer) > 4)
   2411 			strcpy( s_maxclients_field.buffer, "4" );
   2412 		s_startserver_dmoptions_action.generic.statusbar = "N/A for cooperative";
   2413 	}
   2414 //=====
   2415 //PGM
   2416 	// ROGUE GAMES
   2417 	else if(Developer_searchpath(2) == 2)
   2418 	{
   2419 		if (s_rules_box.curvalue == 2)			// tag	
   2420 		{
   2421 			s_maxclients_field.generic.statusbar = NULL;
   2422 			s_startserver_dmoptions_action.generic.statusbar = NULL;
   2423 		}
   2424 /*
   2425 		else if(s_rules_box.curvalue == 3)		// deathball
   2426 		{
   2427 			s_maxclients_field.generic.statusbar = NULL;
   2428 			s_startserver_dmoptions_action.generic.statusbar = NULL;
   2429 		}
   2430 */
   2431 	}
   2432 //PGM
   2433 //=====
   2434 }
   2435 
   2436 void StartServerActionFunc( void *self )
   2437 {
   2438 	char	startmap[1024];
   2439 	int		timelimit;
   2440 	int		fraglimit;
   2441 	int		maxclients;
   2442 	char	*spot;
   2443 
   2444 	strcpy( startmap, strchr( mapnames[s_startmap_list.curvalue], '\n' ) + 1 );
   2445 
   2446 	maxclients  = atoi( s_maxclients_field.buffer );
   2447 	timelimit	= atoi( s_timelimit_field.buffer );
   2448 	fraglimit	= atoi( s_fraglimit_field.buffer );
   2449 
   2450 	Cvar_SetValue( "maxclients", ClampCvar( 0, maxclients, maxclients ) );
   2451 	Cvar_SetValue ("timelimit", ClampCvar( 0, timelimit, timelimit ) );
   2452 	Cvar_SetValue ("fraglimit", ClampCvar( 0, fraglimit, fraglimit ) );
   2453 	Cvar_Set("hostname", s_hostname_field.buffer );
   2454 //	Cvar_SetValue ("deathmatch", !s_rules_box.curvalue );
   2455 //	Cvar_SetValue ("coop", s_rules_box.curvalue );
   2456 
   2457 //PGM
   2458 	if((s_rules_box.curvalue < 2) || (Developer_searchpath(2) != 2))
   2459 	{
   2460 		Cvar_SetValue ("deathmatch", !s_rules_box.curvalue );
   2461 		Cvar_SetValue ("coop", s_rules_box.curvalue );
   2462 		Cvar_SetValue ("gamerules", 0 );
   2463 	}
   2464 	else
   2465 	{
   2466 		Cvar_SetValue ("deathmatch", 1 );	// deathmatch is always true for rogue games, right?
   2467 		Cvar_SetValue ("coop", 0 );			// FIXME - this might need to depend on which game we're running
   2468 		Cvar_SetValue ("gamerules", s_rules_box.curvalue );
   2469 	}
   2470 //PGM
   2471 
   2472 	spot = NULL;
   2473 	if (s_rules_box.curvalue == 1)		// PGM
   2474 	{
   2475  		if(Q_stricmp(startmap, "bunk1") == 0)
   2476   			spot = "start";
   2477  		else if(Q_stricmp(startmap, "mintro") == 0)
   2478   			spot = "start";
   2479  		else if(Q_stricmp(startmap, "fact1") == 0)
   2480   			spot = "start";
   2481  		else if(Q_stricmp(startmap, "power1") == 0)
   2482   			spot = "pstart";
   2483  		else if(Q_stricmp(startmap, "biggun") == 0)
   2484   			spot = "bstart";
   2485  		else if(Q_stricmp(startmap, "hangar1") == 0)
   2486   			spot = "unitstart";
   2487  		else if(Q_stricmp(startmap, "city1") == 0)
   2488   			spot = "unitstart";
   2489  		else if(Q_stricmp(startmap, "boss1") == 0)
   2490 			spot = "bosstart";
   2491 	}
   2492 
   2493 	if (spot)
   2494 	{
   2495 		if (Com_ServerState())
   2496 			Cbuf_AddText ("disconnect\n");
   2497 		Cbuf_AddText (va("gamemap \"*%s$%s\"\n", startmap, spot));
   2498 	}
   2499 	else
   2500 	{
   2501 		Cbuf_AddText (va("map %s\n", startmap));
   2502 	}
   2503 
   2504 	M_ForceMenuOff ();
   2505 }
   2506 
   2507 void StartServer_MenuInit( void )
   2508 {
   2509 	static const char *dm_coop_names[] =
   2510 	{
   2511 		"deathmatch",
   2512 		"cooperative",
   2513 		0
   2514 	};
   2515 //=======
   2516 //PGM
   2517 	static const char *dm_coop_names_rogue[] =
   2518 	{
   2519 		"deathmatch",
   2520 		"cooperative",
   2521 		"tag",
   2522 //		"deathball",
   2523 		0
   2524 	};
   2525 //PGM
   2526 //=======
   2527 	char *buffer;
   2528 	char  mapsname[1024];
   2529 	char *s;
   2530 	int length;
   2531 	int i;
   2532 	FILE *fp;
   2533 
   2534 	/*
   2535 	** load the list of map names
   2536 	*/
   2537 	Com_sprintf( mapsname, sizeof( mapsname ), "%s/maps.lst", FS_Gamedir() );
   2538 	if ( ( fp = fopen( mapsname, "rb" ) ) == 0 )
   2539 	{
   2540 		if ( ( length = FS_LoadFile( "maps.lst", ( void ** ) &buffer ) ) == -1 )
   2541 			Com_Error( ERR_DROP, "couldn't find maps.lst\n" );
   2542 	}
   2543 	else
   2544 	{
   2545 #ifdef _WIN32
   2546 		length = filelength( fileno( fp  ) );
   2547 #else
   2548 		fseek(fp, 0, SEEK_END);
   2549 		length = ftell(fp);
   2550 		fseek(fp, 0, SEEK_SET);
   2551 #endif
   2552 		buffer = malloc( length );
   2553 		fread( buffer, length, 1, fp );
   2554 	}
   2555 
   2556 	s = buffer;
   2557 
   2558 	i = 0;
   2559 	while ( i < length )
   2560 	{
   2561 		if ( s[i] == '\r' )
   2562 			nummaps++;
   2563 		i++;
   2564 	}
   2565 
   2566 	if ( nummaps == 0 )
   2567 		Com_Error( ERR_DROP, "no maps in maps.lst\n" );
   2568 
   2569 	mapnames = malloc( sizeof( char * ) * ( nummaps + 1 ) );
   2570 	memset( mapnames, 0, sizeof( char * ) * ( nummaps + 1 ) );
   2571 
   2572 	s = buffer;
   2573 
   2574 	for ( i = 0; i < nummaps; i++ )
   2575 	{
   2576     char  shortname[MAX_TOKEN_CHARS];
   2577     char  longname[MAX_TOKEN_CHARS];
   2578 		char  scratch[200];
   2579 		int		j, l;
   2580 
   2581 		strcpy( shortname, COM_Parse( &s ) );
   2582 		l = strlen(shortname);
   2583 		for (j=0 ; j<l ; j++)
   2584 			shortname[j] = toupper(shortname[j]);
   2585 		strcpy( longname, COM_Parse( &s ) );
   2586 		Com_sprintf( scratch, sizeof( scratch ), "%s\n%s", longname, shortname );
   2587 
   2588 		mapnames[i] = malloc( strlen( scratch ) + 1 );
   2589 		strcpy( mapnames[i], scratch );
   2590 	}
   2591 	mapnames[nummaps] = 0;
   2592 
   2593 	if ( fp != 0 )
   2594 	{
   2595 		fp = 0;
   2596 		free( buffer );
   2597 	}
   2598 	else
   2599 	{
   2600 		FS_FreeFile( buffer );
   2601 	}
   2602 
   2603 	/*
   2604 	** initialize the menu stuff
   2605 	*/
   2606 	s_startserver_menu.x = viddef.width * 0.50;
   2607 	s_startserver_menu.nitems = 0;
   2608 
   2609 	s_startmap_list.generic.type = MTYPE_SPINCONTROL;
   2610 	s_startmap_list.generic.x	= 0;
   2611 	s_startmap_list.generic.y	= 0;
   2612 	s_startmap_list.generic.name	= "initial map";
   2613 	s_startmap_list.itemnames = mapnames;
   2614 
   2615 	s_rules_box.generic.type = MTYPE_SPINCONTROL;
   2616 	s_rules_box.generic.x	= 0;
   2617 	s_rules_box.generic.y	= 20;
   2618 	s_rules_box.generic.name	= "rules";
   2619 	
   2620 //PGM - rogue games only available with rogue DLL.
   2621 	if(Developer_searchpath(2) == 2)
   2622 		s_rules_box.itemnames = dm_coop_names_rogue;
   2623 	else
   2624 		s_rules_box.itemnames = dm_coop_names;
   2625 //PGM
   2626 
   2627 	if (Cvar_VariableValue("coop"))
   2628 		s_rules_box.curvalue = 1;
   2629 	else
   2630 		s_rules_box.curvalue = 0;
   2631 	s_rules_box.generic.callback = RulesChangeFunc;
   2632 
   2633 	s_timelimit_field.generic.type = MTYPE_FIELD;
   2634 	s_timelimit_field.generic.name = "time limit";
   2635 	s_timelimit_field.generic.flags = QMF_NUMBERSONLY;
   2636 	s_timelimit_field.generic.x	= 0;
   2637 	s_timelimit_field.generic.y	= 36;
   2638 	s_timelimit_field.generic.statusbar = "0 = no limit";
   2639 	s_timelimit_field.length = 3;
   2640 	s_timelimit_field.visible_length = 3;
   2641 	strcpy( s_timelimit_field.buffer, Cvar_VariableString("timelimit") );
   2642 
   2643 	s_fraglimit_field.generic.type = MTYPE_FIELD;
   2644 	s_fraglimit_field.generic.name = "frag limit";
   2645 	s_fraglimit_field.generic.flags = QMF_NUMBERSONLY;
   2646 	s_fraglimit_field.generic.x	= 0;
   2647 	s_fraglimit_field.generic.y	= 54;
   2648 	s_fraglimit_field.generic.statusbar = "0 = no limit";
   2649 	s_fraglimit_field.length = 3;
   2650 	s_fraglimit_field.visible_length = 3;
   2651 	strcpy( s_fraglimit_field.buffer, Cvar_VariableString("fraglimit") );
   2652 
   2653 	/*
   2654 	** maxclients determines the maximum number of players that can join
   2655 	** the game.  If maxclients is only "1" then we should default the menu
   2656 	** option to 8 players, otherwise use whatever its current value is. 
   2657 	** Clamping will be done when the server is actually started.
   2658 	*/
   2659 	s_maxclients_field.generic.type = MTYPE_FIELD;
   2660 	s_maxclients_field.generic.name = "max players";
   2661 	s_maxclients_field.generic.flags = QMF_NUMBERSONLY;
   2662 	s_maxclients_field.generic.x	= 0;
   2663 	s_maxclients_field.generic.y	= 72;
   2664 	s_maxclients_field.generic.statusbar = NULL;
   2665 	s_maxclients_field.length = 3;
   2666 	s_maxclients_field.visible_length = 3;
   2667 	if ( Cvar_VariableValue( "maxclients" ) == 1 )
   2668 		strcpy( s_maxclients_field.buffer, "8" );
   2669 	else 
   2670 		strcpy( s_maxclients_field.buffer, Cvar_VariableString("maxclients") );
   2671 
   2672 	s_hostname_field.generic.type = MTYPE_FIELD;
   2673 	s_hostname_field.generic.name = "hostname";
   2674 	s_hostname_field.generic.flags = 0;
   2675 	s_hostname_field.generic.x	= 0;
   2676 	s_hostname_field.generic.y	= 90;
   2677 	s_hostname_field.generic.statusbar = NULL;
   2678 	s_hostname_field.length = 12;
   2679 	s_hostname_field.visible_length = 12;
   2680 	strcpy( s_hostname_field.buffer, Cvar_VariableString("hostname") );
   2681 
   2682 	s_startserver_dmoptions_action.generic.type = MTYPE_ACTION;
   2683 	s_startserver_dmoptions_action.generic.name	= " deathmatch flags";
   2684 	s_startserver_dmoptions_action.generic.flags= QMF_LEFT_JUSTIFY;
   2685 	s_startserver_dmoptions_action.generic.x	= 24;
   2686 	s_startserver_dmoptions_action.generic.y	= 108;
   2687 	s_startserver_dmoptions_action.generic.statusbar = NULL;
   2688 	s_startserver_dmoptions_action.generic.callback = DMOptionsFunc;
   2689 
   2690 	s_startserver_start_action.generic.type = MTYPE_ACTION;
   2691 	s_startserver_start_action.generic.name	= " begin";
   2692 	s_startserver_start_action.generic.flags= QMF_LEFT_JUSTIFY;
   2693 	s_startserver_start_action.generic.x	= 24;
   2694 	s_startserver_start_action.generic.y	= 128;
   2695 	s_startserver_start_action.generic.callback = StartServerActionFunc;
   2696 
   2697 	Menu_AddItem( &s_startserver_menu, &s_startmap_list );
   2698 	Menu_AddItem( &s_startserver_menu, &s_rules_box );
   2699 	Menu_AddItem( &s_startserver_menu, &s_timelimit_field );
   2700 	Menu_AddItem( &s_startserver_menu, &s_fraglimit_field );
   2701 	Menu_AddItem( &s_startserver_menu, &s_maxclients_field );
   2702 	Menu_AddItem( &s_startserver_menu, &s_hostname_field );
   2703 	Menu_AddItem( &s_startserver_menu, &s_startserver_dmoptions_action );
   2704 	Menu_AddItem( &s_startserver_menu, &s_startserver_start_action );
   2705 
   2706 	Menu_Center( &s_startserver_menu );
   2707 
   2708 	// call this now to set proper inital state
   2709 	RulesChangeFunc ( NULL );
   2710 }
   2711 
   2712 void StartServer_MenuDraw(void)
   2713 {
   2714 	Menu_Draw( &s_startserver_menu );
   2715 }
   2716 
   2717 const char *StartServer_MenuKey( int key )
   2718 {
   2719 	if ( key == K_ESCAPE )
   2720 	{
   2721 		if ( mapnames )
   2722 		{
   2723 			int i;
   2724 
   2725 			for ( i = 0; i < nummaps; i++ )
   2726 				free( mapnames[i] );
   2727 			free( mapnames );
   2728 		}
   2729 		mapnames = 0;
   2730 		nummaps = 0;
   2731 	}
   2732 
   2733 	return Default_MenuKey( &s_startserver_menu, key );
   2734 }
   2735 
   2736 void M_Menu_StartServer_f (void)
   2737 {
   2738 	StartServer_MenuInit();
   2739 	M_PushMenu( StartServer_MenuDraw, StartServer_MenuKey );
   2740 }
   2741 
   2742 /*
   2743 =============================================================================
   2744 
   2745 DMOPTIONS BOOK MENU
   2746 
   2747 =============================================================================
   2748 */
   2749 static char dmoptions_statusbar[128];
   2750 
   2751 static menuframework_s s_dmoptions_menu;
   2752 
   2753 static menulist_s	s_friendlyfire_box;
   2754 static menulist_s	s_falls_box;
   2755 static menulist_s	s_weapons_stay_box;
   2756 static menulist_s	s_instant_powerups_box;
   2757 static menulist_s	s_powerups_box;
   2758 static menulist_s	s_health_box;
   2759 static menulist_s	s_spawn_farthest_box;
   2760 static menulist_s	s_teamplay_box;
   2761 static menulist_s	s_samelevel_box;
   2762 static menulist_s	s_force_respawn_box;
   2763 static menulist_s	s_armor_box;
   2764 static menulist_s	s_allow_exit_box;
   2765 static menulist_s	s_infinite_ammo_box;
   2766 static menulist_s	s_fixed_fov_box;
   2767 static menulist_s	s_quad_drop_box;
   2768 
   2769 //ROGUE
   2770 static menulist_s	s_no_mines_box;
   2771 static menulist_s	s_no_nukes_box;
   2772 static menulist_s	s_stack_double_box;
   2773 static menulist_s	s_no_spheres_box;
   2774 //ROGUE
   2775 
   2776 static void DMFlagCallback( void *self )
   2777 {
   2778 	menulist_s *f = ( menulist_s * ) self;
   2779 	int flags;
   2780 	int bit = 0;
   2781 
   2782 	flags = Cvar_VariableValue( "dmflags" );
   2783 
   2784 	if ( f == &s_friendlyfire_box )
   2785 	{
   2786 		if ( f->curvalue )
   2787 			flags &= ~DF_NO_FRIENDLY_FIRE;
   2788 		else
   2789 			flags |= DF_NO_FRIENDLY_FIRE;
   2790 		goto setvalue;
   2791 	}
   2792 	else if ( f == &s_falls_box )
   2793 	{
   2794 		if ( f->curvalue )
   2795 			flags &= ~DF_NO_FALLING;
   2796 		else
   2797 			flags |= DF_NO_FALLING;
   2798 		goto setvalue;
   2799 	}
   2800 	else if ( f == &s_weapons_stay_box ) 
   2801 	{
   2802 		bit = DF_WEAPONS_STAY;
   2803 	}
   2804 	else if ( f == &s_instant_powerups_box )
   2805 	{
   2806 		bit = DF_INSTANT_ITEMS;
   2807 	}
   2808 	else if ( f == &s_allow_exit_box )
   2809 	{
   2810 		bit = DF_ALLOW_EXIT;
   2811 	}
   2812 	else if ( f == &s_powerups_box )
   2813 	{
   2814 		if ( f->curvalue )
   2815 			flags &= ~DF_NO_ITEMS;
   2816 		else
   2817 			flags |= DF_NO_ITEMS;
   2818 		goto setvalue;
   2819 	}
   2820 	else if ( f == &s_health_box )
   2821 	{
   2822 		if ( f->curvalue )
   2823 			flags &= ~DF_NO_HEALTH;
   2824 		else
   2825 			flags |= DF_NO_HEALTH;
   2826 		goto setvalue;
   2827 	}
   2828 	else if ( f == &s_spawn_farthest_box )
   2829 	{
   2830 		bit = DF_SPAWN_FARTHEST;
   2831 	}
   2832 	else if ( f == &s_teamplay_box )
   2833 	{
   2834 		if ( f->curvalue == 1 )
   2835 		{
   2836 			flags |=  DF_SKINTEAMS;
   2837 			flags &= ~DF_MODELTEAMS;
   2838 		}
   2839 		else if ( f->curvalue == 2 )
   2840 		{
   2841 			flags |=  DF_MODELTEAMS;
   2842 			flags &= ~DF_SKINTEAMS;
   2843 		}
   2844 		else
   2845 		{
   2846 			flags &= ~( DF_MODELTEAMS | DF_SKINTEAMS );
   2847 		}
   2848 
   2849 		goto setvalue;
   2850 	}
   2851 	else if ( f == &s_samelevel_box )
   2852 	{
   2853 		bit = DF_SAME_LEVEL;
   2854 	}
   2855 	else if ( f == &s_force_respawn_box )
   2856 	{
   2857 		bit = DF_FORCE_RESPAWN;
   2858 	}
   2859 	else if ( f == &s_armor_box )
   2860 	{
   2861 		if ( f->curvalue )
   2862 			flags &= ~DF_NO_ARMOR;
   2863 		else
   2864 			flags |= DF_NO_ARMOR;
   2865 		goto setvalue;
   2866 	}
   2867 	else if ( f == &s_infinite_ammo_box )
   2868 	{
   2869 		bit = DF_INFINITE_AMMO;
   2870 	}
   2871 	else if ( f == &s_fixed_fov_box )
   2872 	{
   2873 		bit = DF_FIXED_FOV;
   2874 	}
   2875 	else if ( f == &s_quad_drop_box )
   2876 	{
   2877 		bit = DF_QUAD_DROP;
   2878 	}
   2879 
   2880 //=======
   2881 //ROGUE
   2882 	else if (Developer_searchpath(2) == 2)
   2883 	{
   2884 		if ( f == &s_no_mines_box)
   2885 		{
   2886 			bit = DF_NO_MINES;
   2887 		}
   2888 		else if ( f == &s_no_nukes_box)
   2889 		{
   2890 			bit = DF_NO_NUKES;
   2891 		}
   2892 		else if ( f == &s_stack_double_box)
   2893 		{
   2894 			bit = DF_NO_STACK_DOUBLE;
   2895 		}
   2896 		else if ( f == &s_no_spheres_box)
   2897 		{
   2898 			bit = DF_NO_SPHERES;
   2899 		}
   2900 	}
   2901 //ROGUE
   2902 //=======
   2903 
   2904 	if ( f )
   2905 	{
   2906 		if ( f->curvalue == 0 )
   2907 			flags &= ~bit;
   2908 		else
   2909 			flags |= bit;
   2910 	}
   2911 
   2912 setvalue:
   2913 	Cvar_SetValue ("dmflags", flags);
   2914 
   2915 	Com_sprintf( dmoptions_statusbar, sizeof( dmoptions_statusbar ), "dmflags = %d", flags );
   2916 
   2917 }
   2918 
   2919 void DMOptions_MenuInit( void )
   2920 {
   2921 	static const char *yes_no_names[] =
   2922 	{
   2923 		"no", "yes", 0
   2924 	};
   2925 	static const char *teamplay_names[] = 
   2926 	{
   2927 		"disabled", "by skin", "by model", 0
   2928 	};
   2929 	int dmflags = Cvar_VariableValue( "dmflags" );
   2930 	int y = 0;
   2931 
   2932 	s_dmoptions_menu.x = viddef.width * 0.50;
   2933 	s_dmoptions_menu.nitems = 0;
   2934 
   2935 	s_falls_box.generic.type = MTYPE_SPINCONTROL;
   2936 	s_falls_box.generic.x	= 0;
   2937 	s_falls_box.generic.y	= y;
   2938 	s_falls_box.generic.name	= "falling damage";
   2939 	s_falls_box.generic.callback = DMFlagCallback;
   2940 	s_falls_box.itemnames = yes_no_names;
   2941 	s_falls_box.curvalue = ( dmflags & DF_NO_FALLING ) == 0;
   2942 
   2943 	s_weapons_stay_box.generic.type = MTYPE_SPINCONTROL;
   2944 	s_weapons_stay_box.generic.x	= 0;
   2945 	s_weapons_stay_box.generic.y	= y += 10;
   2946 	s_weapons_stay_box.generic.name	= "weapons stay";
   2947 	s_weapons_stay_box.generic.callback = DMFlagCallback;
   2948 	s_weapons_stay_box.itemnames = yes_no_names;
   2949 	s_weapons_stay_box.curvalue = ( dmflags & DF_WEAPONS_STAY ) != 0;
   2950 
   2951 	s_instant_powerups_box.generic.type = MTYPE_SPINCONTROL;
   2952 	s_instant_powerups_box.generic.x	= 0;
   2953 	s_instant_powerups_box.generic.y	= y += 10;
   2954 	s_instant_powerups_box.generic.name	= "instant powerups";
   2955 	s_instant_powerups_box.generic.callback = DMFlagCallback;
   2956 	s_instant_powerups_box.itemnames = yes_no_names;
   2957 	s_instant_powerups_box.curvalue = ( dmflags & DF_INSTANT_ITEMS ) != 0;
   2958 
   2959 	s_powerups_box.generic.type = MTYPE_SPINCONTROL;
   2960 	s_powerups_box.generic.x	= 0;
   2961 	s_powerups_box.generic.y	= y += 10;
   2962 	s_powerups_box.generic.name	= "allow powerups";
   2963 	s_powerups_box.generic.callback = DMFlagCallback;
   2964 	s_powerups_box.itemnames = yes_no_names;
   2965 	s_powerups_box.curvalue = ( dmflags & DF_NO_ITEMS ) == 0;
   2966 
   2967 	s_health_box.generic.type = MTYPE_SPINCONTROL;
   2968 	s_health_box.generic.x	= 0;
   2969 	s_health_box.generic.y	= y += 10;
   2970 	s_health_box.generic.callback = DMFlagCallback;
   2971 	s_health_box.generic.name	= "allow health";
   2972 	s_health_box.itemnames = yes_no_names;
   2973 	s_health_box.curvalue = ( dmflags & DF_NO_HEALTH ) == 0;
   2974 
   2975 	s_armor_box.generic.type = MTYPE_SPINCONTROL;
   2976 	s_armor_box.generic.x	= 0;
   2977 	s_armor_box.generic.y	= y += 10;
   2978 	s_armor_box.generic.name	= "allow armor";
   2979 	s_armor_box.generic.callback = DMFlagCallback;
   2980 	s_armor_box.itemnames = yes_no_names;
   2981 	s_armor_box.curvalue = ( dmflags & DF_NO_ARMOR ) == 0;
   2982 
   2983 	s_spawn_farthest_box.generic.type = MTYPE_SPINCONTROL;
   2984 	s_spawn_farthest_box.generic.x	= 0;
   2985 	s_spawn_farthest_box.generic.y	= y += 10;
   2986 	s_spawn_farthest_box.generic.name	= "spawn farthest";
   2987 	s_spawn_farthest_box.generic.callback = DMFlagCallback;
   2988 	s_spawn_farthest_box.itemnames = yes_no_names;
   2989 	s_spawn_farthest_box.curvalue = ( dmflags & DF_SPAWN_FARTHEST ) != 0;
   2990 
   2991 	s_samelevel_box.generic.type = MTYPE_SPINCONTROL;
   2992 	s_samelevel_box.generic.x	= 0;
   2993 	s_samelevel_box.generic.y	= y += 10;
   2994 	s_samelevel_box.generic.name	= "same map";
   2995 	s_samelevel_box.generic.callback = DMFlagCallback;
   2996 	s_samelevel_box.itemnames = yes_no_names;
   2997 	s_samelevel_box.curvalue = ( dmflags & DF_SAME_LEVEL ) != 0;
   2998 
   2999 	s_force_respawn_box.generic.type = MTYPE_SPINCONTROL;
   3000 	s_force_respawn_box.generic.x	= 0;
   3001 	s_force_respawn_box.generic.y	= y += 10;
   3002 	s_force_respawn_box.generic.name	= "force respawn";
   3003 	s_force_respawn_box.generic.callback = DMFlagCallback;
   3004 	s_force_respawn_box.itemnames = yes_no_names;
   3005 	s_force_respawn_box.curvalue = ( dmflags & DF_FORCE_RESPAWN ) != 0;
   3006 
   3007 	s_teamplay_box.generic.type = MTYPE_SPINCONTROL;
   3008 	s_teamplay_box.generic.x	= 0;
   3009 	s_teamplay_box.generic.y	= y += 10;
   3010 	s_teamplay_box.generic.name	= "teamplay";
   3011 	s_teamplay_box.generic.callback = DMFlagCallback;
   3012 	s_teamplay_box.itemnames = teamplay_names;
   3013 
   3014 	s_allow_exit_box.generic.type = MTYPE_SPINCONTROL;
   3015 	s_allow_exit_box.generic.x	= 0;
   3016 	s_allow_exit_box.generic.y	= y += 10;
   3017 	s_allow_exit_box.generic.name	= "allow exit";
   3018 	s_allow_exit_box.generic.callback = DMFlagCallback;
   3019 	s_allow_exit_box.itemnames = yes_no_names;
   3020 	s_allow_exit_box.curvalue = ( dmflags & DF_ALLOW_EXIT ) != 0;
   3021 
   3022 	s_infinite_ammo_box.generic.type = MTYPE_SPINCONTROL;
   3023 	s_infinite_ammo_box.generic.x	= 0;
   3024 	s_infinite_ammo_box.generic.y	= y += 10;
   3025 	s_infinite_ammo_box.generic.name	= "infinite ammo";
   3026 	s_infinite_ammo_box.generic.callback = DMFlagCallback;
   3027 	s_infinite_ammo_box.itemnames = yes_no_names;
   3028 	s_infinite_ammo_box.curvalue = ( dmflags & DF_INFINITE_AMMO ) != 0;
   3029 
   3030 	s_fixed_fov_box.generic.type = MTYPE_SPINCONTROL;
   3031 	s_fixed_fov_box.generic.x	= 0;
   3032 	s_fixed_fov_box.generic.y	= y += 10;
   3033 	s_fixed_fov_box.generic.name	= "fixed FOV";
   3034 	s_fixed_fov_box.generic.callback = DMFlagCallback;
   3035 	s_fixed_fov_box.itemnames = yes_no_names;
   3036 	s_fixed_fov_box.curvalue = ( dmflags & DF_FIXED_FOV ) != 0;
   3037 
   3038 	s_quad_drop_box.generic.type = MTYPE_SPINCONTROL;
   3039 	s_quad_drop_box.generic.x	= 0;
   3040 	s_quad_drop_box.generic.y	= y += 10;
   3041 	s_quad_drop_box.generic.name	= "quad drop";
   3042 	s_quad_drop_box.generic.callback = DMFlagCallback;
   3043 	s_quad_drop_box.itemnames = yes_no_names;
   3044 	s_quad_drop_box.curvalue = ( dmflags & DF_QUAD_DROP ) != 0;
   3045 
   3046 	s_friendlyfire_box.generic.type = MTYPE_SPINCONTROL;
   3047 	s_friendlyfire_box.generic.x	= 0;
   3048 	s_friendlyfire_box.generic.y	= y += 10;
   3049 	s_friendlyfire_box.generic.name	= "friendly fire";
   3050 	s_friendlyfire_box.generic.callback = DMFlagCallback;
   3051 	s_friendlyfire_box.itemnames = yes_no_names;
   3052 	s_friendlyfire_box.curvalue = ( dmflags & DF_NO_FRIENDLY_FIRE ) == 0;
   3053 
   3054 //============
   3055 //ROGUE
   3056 	if(Developer_searchpath(2) == 2)
   3057 	{
   3058 		s_no_mines_box.generic.type = MTYPE_SPINCONTROL;
   3059 		s_no_mines_box.generic.x	= 0;
   3060 		s_no_mines_box.generic.y	= y += 10;
   3061 		s_no_mines_box.generic.name	= "remove mines";
   3062 		s_no_mines_box.generic.callback = DMFlagCallback;
   3063 		s_no_mines_box.itemnames = yes_no_names;
   3064 		s_no_mines_box.curvalue = ( dmflags & DF_NO_MINES ) != 0;
   3065 
   3066 		s_no_nukes_box.generic.type = MTYPE_SPINCONTROL;
   3067 		s_no_nukes_box.generic.x	= 0;
   3068 		s_no_nukes_box.generic.y	= y += 10;
   3069 		s_no_nukes_box.generic.name	= "remove nukes";
   3070 		s_no_nukes_box.generic.callback = DMFlagCallback;
   3071 		s_no_nukes_box.itemnames = yes_no_names;
   3072 		s_no_nukes_box.curvalue = ( dmflags & DF_NO_NUKES ) != 0;
   3073 
   3074 		s_stack_double_box.generic.type = MTYPE_SPINCONTROL;
   3075 		s_stack_double_box.generic.x	= 0;
   3076 		s_stack_double_box.generic.y	= y += 10;
   3077 		s_stack_double_box.generic.name	= "2x/4x stacking off";
   3078 		s_stack_double_box.generic.callback = DMFlagCallback;
   3079 		s_stack_double_box.itemnames = yes_no_names;
   3080 		s_stack_double_box.curvalue = ( dmflags & DF_NO_STACK_DOUBLE ) != 0;
   3081 
   3082 		s_no_spheres_box.generic.type = MTYPE_SPINCONTROL;
   3083 		s_no_spheres_box.generic.x	= 0;
   3084 		s_no_spheres_box.generic.y	= y += 10;
   3085 		s_no_spheres_box.generic.name	= "remove spheres";
   3086 		s_no_spheres_box.generic.callback = DMFlagCallback;
   3087 		s_no_spheres_box.itemnames = yes_no_names;
   3088 		s_no_spheres_box.curvalue = ( dmflags & DF_NO_SPHERES ) != 0;
   3089 
   3090 	}
   3091 //ROGUE
   3092 //============
   3093 
   3094 	Menu_AddItem( &s_dmoptions_menu, &s_falls_box );
   3095 	Menu_AddItem( &s_dmoptions_menu, &s_weapons_stay_box );
   3096 	Menu_AddItem( &s_dmoptions_menu, &s_instant_powerups_box );
   3097 	Menu_AddItem( &s_dmoptions_menu, &s_powerups_box );
   3098 	Menu_AddItem( &s_dmoptions_menu, &s_health_box );
   3099 	Menu_AddItem( &s_dmoptions_menu, &s_armor_box );
   3100 	Menu_AddItem( &s_dmoptions_menu, &s_spawn_farthest_box );
   3101 	Menu_AddItem( &s_dmoptions_menu, &s_samelevel_box );
   3102 	Menu_AddItem( &s_dmoptions_menu, &s_force_respawn_box );
   3103 	Menu_AddItem( &s_dmoptions_menu, &s_teamplay_box );
   3104 	Menu_AddItem( &s_dmoptions_menu, &s_allow_exit_box );
   3105 	Menu_AddItem( &s_dmoptions_menu, &s_infinite_ammo_box );
   3106 	Menu_AddItem( &s_dmoptions_menu, &s_fixed_fov_box );
   3107 	Menu_AddItem( &s_dmoptions_menu, &s_quad_drop_box );
   3108 	Menu_AddItem( &s_dmoptions_menu, &s_friendlyfire_box );
   3109 
   3110 //=======
   3111 //ROGUE
   3112 	if(Developer_searchpath(2) == 2)
   3113 	{
   3114 		Menu_AddItem( &s_dmoptions_menu, &s_no_mines_box );
   3115 		Menu_AddItem( &s_dmoptions_menu, &s_no_nukes_box );
   3116 		Menu_AddItem( &s_dmoptions_menu, &s_stack_double_box );
   3117 		Menu_AddItem( &s_dmoptions_menu, &s_no_spheres_box );
   3118 	}
   3119 //ROGUE
   3120 //=======
   3121 
   3122 	Menu_Center( &s_dmoptions_menu );
   3123 
   3124 	// set the original dmflags statusbar
   3125 	DMFlagCallback( 0 );
   3126 	Menu_SetStatusBar( &s_dmoptions_menu, dmoptions_statusbar );
   3127 }
   3128 
   3129 void DMOptions_MenuDraw(void)
   3130 {
   3131 	Menu_Draw( &s_dmoptions_menu );
   3132 }
   3133 
   3134 const char *DMOptions_MenuKey( int key )
   3135 {
   3136 	return Default_MenuKey( &s_dmoptions_menu, key );
   3137 }
   3138 
   3139 void M_Menu_DMOptions_f (void)
   3140 {
   3141 	DMOptions_MenuInit();
   3142 	M_PushMenu( DMOptions_MenuDraw, DMOptions_MenuKey );
   3143 }
   3144 
   3145 /*
   3146 =============================================================================
   3147 
   3148 DOWNLOADOPTIONS BOOK MENU
   3149 
   3150 =============================================================================
   3151 */
   3152 static menuframework_s s_downloadoptions_menu;
   3153 
   3154 static menuseparator_s	s_download_title;
   3155 static menulist_s	s_allow_download_box;
   3156 static menulist_s	s_allow_download_maps_box;
   3157 static menulist_s	s_allow_download_models_box;
   3158 static menulist_s	s_allow_download_players_box;
   3159 static menulist_s	s_allow_download_sounds_box;
   3160 
   3161 static void DownloadCallback( void *self )
   3162 {
   3163 	menulist_s *f = ( menulist_s * ) self;
   3164 
   3165 	if (f == &s_allow_download_box)
   3166 	{
   3167 		Cvar_SetValue("allow_download", f->curvalue);
   3168 	}
   3169 
   3170 	else if (f == &s_allow_download_maps_box)
   3171 	{
   3172 		Cvar_SetValue("allow_download_maps", f->curvalue);
   3173 	}
   3174 
   3175 	else if (f == &s_allow_download_models_box)
   3176 	{
   3177 		Cvar_SetValue("allow_download_models", f->curvalue);
   3178 	}
   3179 
   3180 	else if (f == &s_allow_download_players_box)
   3181 	{
   3182 		Cvar_SetValue("allow_download_players", f->curvalue);
   3183 	}
   3184 
   3185 	else if (f == &s_allow_download_sounds_box)
   3186 	{
   3187 		Cvar_SetValue("allow_download_sounds", f->curvalue);
   3188 	}
   3189 }
   3190 
   3191 void DownloadOptions_MenuInit( void )
   3192 {
   3193 	static const char *yes_no_names[] =
   3194 	{
   3195 		"no", "yes", 0
   3196 	};
   3197 	int y = 0;
   3198 
   3199 	s_downloadoptions_menu.x = viddef.width * 0.50;
   3200 	s_downloadoptions_menu.nitems = 0;
   3201 
   3202 	s_download_title.generic.type = MTYPE_SEPARATOR;
   3203 	s_download_title.generic.name = "Download Options";
   3204 	s_download_title.generic.x    = 48;
   3205 	s_download_title.generic.y	 = y;
   3206 
   3207 	s_allow_download_box.generic.type = MTYPE_SPINCONTROL;
   3208 	s_allow_download_box.generic.x	= 0;
   3209 	s_allow_download_box.generic.y	= y += 20;
   3210 	s_allow_download_box.generic.name	= "allow downloading";
   3211 	s_allow_download_box.generic.callback = DownloadCallback;
   3212 	s_allow_download_box.itemnames = yes_no_names;
   3213 	s_allow_download_box.curvalue = (Cvar_VariableValue("allow_download") != 0);
   3214 
   3215 	s_allow_download_maps_box.generic.type = MTYPE_SPINCONTROL;
   3216 	s_allow_download_maps_box.generic.x	= 0;
   3217 	s_allow_download_maps_box.generic.y	= y += 20;
   3218 	s_allow_download_maps_box.generic.name	= "maps";
   3219 	s_allow_download_maps_box.generic.callback = DownloadCallback;
   3220 	s_allow_download_maps_box.itemnames = yes_no_names;
   3221 	s_allow_download_maps_box.curvalue = (Cvar_VariableValue("allow_download_maps") != 0);
   3222 
   3223 	s_allow_download_players_box.generic.type = MTYPE_SPINCONTROL;
   3224 	s_allow_download_players_box.generic.x	= 0;
   3225 	s_allow_download_players_box.generic.y	= y += 10;
   3226 	s_allow_download_players_box.generic.name	= "player models/skins";
   3227 	s_allow_download_players_box.generic.callback = DownloadCallback;
   3228 	s_allow_download_players_box.itemnames = yes_no_names;
   3229 	s_allow_download_players_box.curvalue = (Cvar_VariableValue("allow_download_players") != 0);
   3230 
   3231 	s_allow_download_models_box.generic.type = MTYPE_SPINCONTROL;
   3232 	s_allow_download_models_box.generic.x	= 0;
   3233 	s_allow_download_models_box.generic.y	= y += 10;
   3234 	s_allow_download_models_box.generic.name	= "models";
   3235 	s_allow_download_models_box.generic.callback = DownloadCallback;
   3236 	s_allow_download_models_box.itemnames = yes_no_names;
   3237 	s_allow_download_models_box.curvalue = (Cvar_VariableValue("allow_download_models") != 0);
   3238 
   3239 	s_allow_download_sounds_box.generic.type = MTYPE_SPINCONTROL;
   3240 	s_allow_download_sounds_box.generic.x	= 0;
   3241 	s_allow_download_sounds_box.generic.y	= y += 10;
   3242 	s_allow_download_sounds_box.generic.name	= "sounds";
   3243 	s_allow_download_sounds_box.generic.callback = DownloadCallback;
   3244 	s_allow_download_sounds_box.itemnames = yes_no_names;
   3245 	s_allow_download_sounds_box.curvalue = (Cvar_VariableValue("allow_download_sounds") != 0);
   3246 
   3247 	Menu_AddItem( &s_downloadoptions_menu, &s_download_title );
   3248 	Menu_AddItem( &s_downloadoptions_menu, &s_allow_download_box );
   3249 	Menu_AddItem( &s_downloadoptions_menu, &s_allow_download_maps_box );
   3250 	Menu_AddItem( &s_downloadoptions_menu, &s_allow_download_players_box );
   3251 	Menu_AddItem( &s_downloadoptions_menu, &s_allow_download_models_box );
   3252 	Menu_AddItem( &s_downloadoptions_menu, &s_allow_download_sounds_box );
   3253 
   3254 	Menu_Center( &s_downloadoptions_menu );
   3255 
   3256 	// skip over title
   3257 	if (s_downloadoptions_menu.cursor == 0)
   3258 		s_downloadoptions_menu.cursor = 1;
   3259 }
   3260 
   3261 void DownloadOptions_MenuDraw(void)
   3262 {
   3263 	Menu_Draw( &s_downloadoptions_menu );
   3264 }
   3265 
   3266 const char *DownloadOptions_MenuKey( int key )
   3267 {
   3268 	return Default_MenuKey( &s_downloadoptions_menu, key );
   3269 }
   3270 
   3271 void M_Menu_DownloadOptions_f (void)
   3272 {
   3273 	DownloadOptions_MenuInit();
   3274 	M_PushMenu( DownloadOptions_MenuDraw, DownloadOptions_MenuKey );
   3275 }
   3276 /*
   3277 =============================================================================
   3278 
   3279 ADDRESS BOOK MENU
   3280 
   3281 =============================================================================
   3282 */
   3283 #define NUM_ADDRESSBOOK_ENTRIES 9
   3284 
   3285 static menuframework_s	s_addressbook_menu;
   3286 static menufield_s		s_addressbook_fields[NUM_ADDRESSBOOK_ENTRIES];
   3287 
   3288 void AddressBook_MenuInit( void )
   3289 {
   3290 	int i;
   3291 
   3292 	s_addressbook_menu.x = viddef.width / 2 - 142;
   3293 	s_addressbook_menu.y = viddef.height / 2 - 58;
   3294 	s_addressbook_menu.nitems = 0;
   3295 
   3296 	for ( i = 0; i < NUM_ADDRESSBOOK_ENTRIES; i++ )
   3297 	{
   3298 		cvar_t *adr;
   3299 		char buffer[20];
   3300 
   3301 		Com_sprintf( buffer, sizeof( buffer ), "adr%d", i );
   3302 
   3303 		adr = Cvar_Get( buffer, "", CVAR_ARCHIVE );
   3304 
   3305 		s_addressbook_fields[i].generic.type = MTYPE_FIELD;
   3306 		s_addressbook_fields[i].generic.name = 0;
   3307 		s_addressbook_fields[i].generic.callback = 0;
   3308 		s_addressbook_fields[i].generic.x		= 0;
   3309 		s_addressbook_fields[i].generic.y		= i * 18 + 0;
   3310 		s_addressbook_fields[i].generic.localdata[0] = i;
   3311 		s_addressbook_fields[i].cursor			= 0;
   3312 		s_addressbook_fields[i].length			= 60;
   3313 		s_addressbook_fields[i].visible_length	= 30;
   3314 
   3315 		strcpy( s_addressbook_fields[i].buffer, adr->string );
   3316 
   3317 		Menu_AddItem( &s_addressbook_menu, &s_addressbook_fields[i] );
   3318 	}
   3319 }
   3320 
   3321 const char *AddressBook_MenuKey( int key )
   3322 {
   3323 	if ( key == K_ESCAPE )
   3324 	{
   3325 		int index;
   3326 		char buffer[20];
   3327 
   3328 		for ( index = 0; index < NUM_ADDRESSBOOK_ENTRIES; index++ )
   3329 		{
   3330 			Com_sprintf( buffer, sizeof( buffer ), "adr%d", index );
   3331 			Cvar_Set( buffer, s_addressbook_fields[index].buffer );
   3332 		}
   3333 	}
   3334 	return Default_MenuKey( &s_addressbook_menu, key );
   3335 }
   3336 
   3337 void AddressBook_MenuDraw(void)
   3338 {
   3339 	M_Banner( "m_banner_addressbook" );
   3340 	Menu_Draw( &s_addressbook_menu );
   3341 }
   3342 
   3343 void M_Menu_AddressBook_f(void)
   3344 {
   3345 	AddressBook_MenuInit();
   3346 	M_PushMenu( AddressBook_MenuDraw, AddressBook_MenuKey );
   3347 }
   3348 
   3349 /*
   3350 =============================================================================
   3351 
   3352 PLAYER CONFIG MENU
   3353 
   3354 =============================================================================
   3355 */
   3356 static menuframework_s	s_player_config_menu;
   3357 static menufield_s		s_player_name_field;
   3358 static menulist_s		s_player_model_box;
   3359 static menulist_s		s_player_skin_box;
   3360 static menulist_s		s_player_handedness_box;
   3361 static menulist_s		s_player_rate_box;
   3362 static menuseparator_s	s_player_skin_title;
   3363 static menuseparator_s	s_player_model_title;
   3364 static menuseparator_s	s_player_hand_title;
   3365 static menuseparator_s	s_player_rate_title;
   3366 static menuaction_s		s_player_download_action;
   3367 
   3368 #define MAX_DISPLAYNAME 16
   3369 #define MAX_PLAYERMODELS 1024
   3370 
   3371 typedef struct
   3372 {
   3373 	int		nskins;
   3374 	char	**skindisplaynames;
   3375 	char	displayname[MAX_DISPLAYNAME];
   3376 	char	directory[MAX_QPATH];
   3377 } playermodelinfo_s;
   3378 
   3379 static playermodelinfo_s s_pmi[MAX_PLAYERMODELS];
   3380 static char *s_pmnames[MAX_PLAYERMODELS];
   3381 static int s_numplayermodels;
   3382 
   3383 static int rate_tbl[] = { 2500, 3200, 5000, 10000, 25000, 0 };
   3384 static const char *rate_names[] = { "28.8 Modem", "33.6 Modem", "Single ISDN",
   3385 	"Dual ISDN/Cable", "T1/LAN", "User defined", 0 };
   3386 
   3387 void DownloadOptionsFunc( void *self )
   3388 {
   3389 	M_Menu_DownloadOptions_f();
   3390 }
   3391 
   3392 static void HandednessCallback( void *unused )
   3393 {
   3394 	Cvar_SetValue( "hand", s_player_handedness_box.curvalue );
   3395 }
   3396 
   3397 static void RateCallback( void *unused )
   3398 {
   3399 	if (s_player_rate_box.curvalue != sizeof(rate_tbl) / sizeof(*rate_tbl) - 1)
   3400 		Cvar_SetValue( "rate", rate_tbl[s_player_rate_box.curvalue] );
   3401 }
   3402 
   3403 static void ModelCallback( void *unused )
   3404 {
   3405 	s_player_skin_box.itemnames = s_pmi[s_player_model_box.curvalue].skindisplaynames;
   3406 	s_player_skin_box.curvalue = 0;
   3407 }
   3408 
   3409 static void FreeFileList( char **list, int n )
   3410 {
   3411 	int i;
   3412 
   3413 	for ( i = 0; i < n; i++ )
   3414 	{
   3415 		if ( list[i] )
   3416 		{
   3417 			free( list[i] );
   3418 			list[i] = 0;
   3419 		}
   3420 	}
   3421 	free( list );
   3422 }
   3423 
   3424 static qboolean IconOfSkinExists( char *skin, char **pcxfiles, int npcxfiles )
   3425 {
   3426 	int i;
   3427 	char scratch[1024];
   3428 
   3429 	strcpy( scratch, skin );
   3430 	*strrchr( scratch, '.' ) = 0;
   3431 	strcat( scratch, "_i.pcx" );
   3432 
   3433 	for ( i = 0; i < npcxfiles; i++ )
   3434 	{
   3435 		if ( strcmp( pcxfiles[i], scratch ) == 0 )
   3436 			return true;
   3437 	}
   3438 
   3439 	return false;
   3440 }
   3441 
   3442 static qboolean PlayerConfig_ScanDirectories( void )
   3443 {
   3444 	char findname[1024];
   3445 	char scratch[1024];
   3446 	int ndirs = 0, npms = 0;
   3447 	char **dirnames;
   3448 	char *path = NULL;
   3449 	int i;
   3450 
   3451 	extern char **FS_ListFiles( char *, int *, unsigned, unsigned );
   3452 
   3453 	s_numplayermodels = 0;
   3454 
   3455 	/*
   3456 	** get a list of directories
   3457 	*/
   3458 	do 
   3459 	{
   3460 		path = FS_NextPath( path );
   3461 		Com_sprintf( findname, sizeof(findname), "%s/players/*.*", path );
   3462 
   3463 		if ( ( dirnames = FS_ListFiles( findname, &ndirs, SFF_SUBDIR, 0 ) ) != 0 )
   3464 			break;
   3465 	} while ( path );
   3466 
   3467 	if ( !dirnames )
   3468 		return false;
   3469 
   3470 	/*
   3471 	** go through the subdirectories
   3472 	*/
   3473 	npms = ndirs;
   3474 	if ( npms > MAX_PLAYERMODELS )
   3475 		npms = MAX_PLAYERMODELS;
   3476 
   3477 	for ( i = 0; i < npms; i++ )
   3478 	{
   3479 		int k, s;
   3480 		char *a, *b, *c;
   3481 		char **pcxnames;
   3482 		char **skinnames;
   3483 		int npcxfiles;
   3484 		int nskins = 0;
   3485 
   3486 		if ( dirnames[i] == 0 )
   3487 			continue;
   3488 
   3489 		// verify the existence of tris.md2
   3490 		strcpy( scratch, dirnames[i] );
   3491 		strcat( scratch, "/tris.md2" );
   3492 		if ( !Sys_FindFirst( scratch, 0, SFF_SUBDIR | SFF_HIDDEN | SFF_SYSTEM ) )
   3493 		{
   3494 			free( dirnames[i] );
   3495 			dirnames[i] = 0;
   3496 			Sys_FindClose();
   3497 			continue;
   3498 		}
   3499 		Sys_FindClose();
   3500 
   3501 		// verify the existence of at least one pcx skin
   3502 		strcpy( scratch, dirnames[i] );
   3503 		strcat( scratch, "/*.pcx" );
   3504 		pcxnames = FS_ListFiles( scratch, &npcxfiles, 0, SFF_SUBDIR | SFF_HIDDEN | SFF_SYSTEM );
   3505 
   3506 		if ( !pcxnames )
   3507 		{
   3508 			free( dirnames[i] );
   3509 			dirnames[i] = 0;
   3510 			continue;
   3511 		}
   3512 
   3513 		// count valid skins, which consist of a skin with a matching "_i" icon
   3514 		for ( k = 0; k < npcxfiles-1; k++ )
   3515 		{
   3516 			if ( !strstr( pcxnames[k], "_i.pcx" ) )
   3517 			{
   3518 				if ( IconOfSkinExists( pcxnames[k], pcxnames, npcxfiles - 1 ) )
   3519 				{
   3520 					nskins++;
   3521 				}
   3522 			}
   3523 		}
   3524 		if ( !nskins )
   3525 			continue;
   3526 
   3527 		skinnames = malloc( sizeof( char * ) * ( nskins + 1 ) );
   3528 		memset( skinnames, 0, sizeof( char * ) * ( nskins + 1 ) );
   3529 
   3530 		// copy the valid skins
   3531 		for ( s = 0, k = 0; k < npcxfiles-1; k++ )
   3532 		{
   3533 			char *a, *b, *c;
   3534 
   3535 			if ( !strstr( pcxnames[k], "_i.pcx" ) )
   3536 			{
   3537 				if ( IconOfSkinExists( pcxnames[k], pcxnames, npcxfiles - 1 ) )
   3538 				{
   3539 					a = strrchr( pcxnames[k], '/' );
   3540 					b = strrchr( pcxnames[k], '\\' );
   3541 
   3542 					if ( a > b )
   3543 						c = a;
   3544 					else
   3545 						c = b;
   3546 
   3547 					strcpy( scratch, c + 1 );
   3548 
   3549 					if ( strrchr( scratch, '.' ) )
   3550 						*strrchr( scratch, '.' ) = 0;
   3551 
   3552 					skinnames[s] = strdup( scratch );
   3553 					s++;
   3554 				}
   3555 			}
   3556 		}
   3557 
   3558 		// at this point we have a valid player model
   3559 		s_pmi[s_numplayermodels].nskins = nskins;
   3560 		s_pmi[s_numplayermodels].skindisplaynames = skinnames;
   3561 
   3562 		// make short name for the model
   3563 		a = strrchr( dirnames[i], '/' );
   3564 		b = strrchr( dirnames[i], '\\' );
   3565 
   3566 		if ( a > b )
   3567 			c = a;
   3568 		else
   3569 			c = b;
   3570 
   3571 		strncpy( s_pmi[s_numplayermodels].displayname, c + 1, MAX_DISPLAYNAME-1 );
   3572 		strcpy( s_pmi[s_numplayermodels].directory, c + 1 );
   3573 
   3574 		FreeFileList( pcxnames, npcxfiles );
   3575 
   3576 		s_numplayermodels++;
   3577 	}
   3578 	if ( dirnames )
   3579 		FreeFileList( dirnames, ndirs );
   3580 }
   3581 
   3582 static int pmicmpfnc( const void *_a, const void *_b )
   3583 {
   3584 	const playermodelinfo_s *a = ( const playermodelinfo_s * ) _a;
   3585 	const playermodelinfo_s *b = ( const playermodelinfo_s * ) _b;
   3586 
   3587 	/*
   3588 	** sort by male, female, then alphabetical
   3589 	*/
   3590 	if ( strcmp( a->directory, "male" ) == 0 )
   3591 		return -1;
   3592 	else if ( strcmp( b->directory, "male" ) == 0 )
   3593 		return 1;
   3594 
   3595 	if ( strcmp( a->directory, "female" ) == 0 )
   3596 		return -1;
   3597 	else if ( strcmp( b->directory, "female" ) == 0 )
   3598 		return 1;
   3599 
   3600 	return strcmp( a->directory, b->directory );
   3601 }
   3602 
   3603 
   3604 qboolean PlayerConfig_MenuInit( void )
   3605 {
   3606 	extern cvar_t *name;
   3607 	extern cvar_t *team;
   3608 	extern cvar_t *skin;
   3609 	char currentdirectory[1024];
   3610 	char currentskin[1024];
   3611 	int i = 0;
   3612 
   3613 	int currentdirectoryindex = 0;
   3614 	int currentskinindex = 0;
   3615 
   3616 	cvar_t *hand = Cvar_Get( "hand", "0", CVAR_USERINFO | CVAR_ARCHIVE );
   3617 
   3618 	static const char *handedness[] = { "right", "left", "center", 0 };
   3619 
   3620 	PlayerConfig_ScanDirectories();
   3621 
   3622 	if (s_numplayermodels == 0)
   3623 		return false;
   3624 
   3625 	if ( hand->value < 0 || hand->value > 2 )
   3626 		Cvar_SetValue( "hand", 0 );
   3627 
   3628 	strcpy( currentdirectory, skin->string );
   3629 
   3630 	if ( strchr( currentdirectory, '/' ) )
   3631 	{
   3632 		strcpy( currentskin, strchr( currentdirectory, '/' ) + 1 );
   3633 		*strchr( currentdirectory, '/' ) = 0;
   3634 	}
   3635 	else if ( strchr( currentdirectory, '\\' ) )
   3636 	{
   3637 		strcpy( currentskin, strchr( currentdirectory, '\\' ) + 1 );
   3638 		*strchr( currentdirectory, '\\' ) = 0;
   3639 	}
   3640 	else
   3641 	{
   3642 		strcpy( currentdirectory, "male" );
   3643 		strcpy( currentskin, "grunt" );
   3644 	}
   3645 
   3646 	qsort( s_pmi, s_numplayermodels, sizeof( s_pmi[0] ), pmicmpfnc );
   3647 
   3648 	memset( s_pmnames, 0, sizeof( s_pmnames ) );
   3649 	for ( i = 0; i < s_numplayermodels; i++ )
   3650 	{
   3651 		s_pmnames[i] = s_pmi[i].displayname;
   3652 		if ( Q_stricmp( s_pmi[i].directory, currentdirectory ) == 0 )
   3653 		{
   3654 			int j;
   3655 
   3656 			currentdirectoryindex = i;
   3657 
   3658 			for ( j = 0; j < s_pmi[i].nskins; j++ )
   3659 			{
   3660 				if ( Q_stricmp( s_pmi[i].skindisplaynames[j], currentskin ) == 0 )
   3661 				{
   3662 					currentskinindex = j;
   3663 					break;
   3664 				}
   3665 			}
   3666 		}
   3667 	}
   3668 
   3669 	s_player_config_menu.x = viddef.width / 2 - 95; 
   3670 	s_player_config_menu.y = viddef.height / 2 - 97;
   3671 	s_player_config_menu.nitems = 0;
   3672 
   3673 	s_player_name_field.generic.type = MTYPE_FIELD;
   3674 	s_player_name_field.generic.name = "name";
   3675 	s_player_name_field.generic.callback = 0;
   3676 	s_player_name_field.generic.x		= 0;
   3677 	s_player_name_field.generic.y		= 0;
   3678 	s_player_name_field.length	= 20;
   3679 	s_player_name_field.visible_length = 20;
   3680 	strcpy( s_player_name_field.buffer, name->string );
   3681 	s_player_name_field.cursor = strlen( name->string );
   3682 
   3683 	s_player_model_title.generic.type = MTYPE_SEPARATOR;
   3684 	s_player_model_title.generic.name = "model";
   3685 	s_player_model_title.generic.x    = -8;
   3686 	s_player_model_title.generic.y	 = 60;
   3687 
   3688 	s_player_model_box.generic.type = MTYPE_SPINCONTROL;
   3689 	s_player_model_box.generic.x	= -56;
   3690 	s_player_model_box.generic.y	= 70;
   3691 	s_player_model_box.generic.callback = ModelCallback;
   3692 	s_player_model_box.generic.cursor_offset = -48;
   3693 	s_player_model_box.curvalue = currentdirectoryindex;
   3694 	s_player_model_box.itemnames = s_pmnames;
   3695 
   3696 	s_player_skin_title.generic.type = MTYPE_SEPARATOR;
   3697 	s_player_skin_title.generic.name = "skin";
   3698 	s_player_skin_title.generic.x    = -16;
   3699 	s_player_skin_title.generic.y	 = 84;
   3700 
   3701 	s_player_skin_box.generic.type = MTYPE_SPINCONTROL;
   3702 	s_player_skin_box.generic.x	= -56;
   3703 	s_player_skin_box.generic.y	= 94;
   3704 	s_player_skin_box.generic.name	= 0;
   3705 	s_player_skin_box.generic.callback = 0;
   3706 	s_player_skin_box.generic.cursor_offset = -48;
   3707 	s_player_skin_box.curvalue = currentskinindex;
   3708 	s_player_skin_box.itemnames = s_pmi[currentdirectoryindex].skindisplaynames;
   3709 
   3710 	s_player_hand_title.generic.type = MTYPE_SEPARATOR;
   3711 	s_player_hand_title.generic.name = "handedness";
   3712 	s_player_hand_title.generic.x    = 32;
   3713 	s_player_hand_title.generic.y	 = 108;
   3714 
   3715 	s_player_handedness_box.generic.type = MTYPE_SPINCONTROL;
   3716 	s_player_handedness_box.generic.x	= -56;
   3717 	s_player_handedness_box.generic.y	= 118;
   3718 	s_player_handedness_box.generic.name	= 0;
   3719 	s_player_handedness_box.generic.cursor_offset = -48;
   3720 	s_player_handedness_box.generic.callback = HandednessCallback;
   3721 	s_player_handedness_box.curvalue = Cvar_VariableValue( "hand" );
   3722 	s_player_handedness_box.itemnames = handedness;
   3723 
   3724 	for (i = 0; i < sizeof(rate_tbl) / sizeof(*rate_tbl) - 1; i++)
   3725 		if (Cvar_VariableValue("rate") == rate_tbl[i])
   3726 			break;
   3727 
   3728 	s_player_rate_title.generic.type = MTYPE_SEPARATOR;
   3729 	s_player_rate_title.generic.name = "connect speed";
   3730 	s_player_rate_title.generic.x    = 56;
   3731 	s_player_rate_title.generic.y	 = 156;
   3732 
   3733 	s_player_rate_box.generic.type = MTYPE_SPINCONTROL;
   3734 	s_player_rate_box.generic.x	= -56;
   3735 	s_player_rate_box.generic.y	= 166;
   3736 	s_player_rate_box.generic.name	= 0;
   3737 	s_player_rate_box.generic.cursor_offset = -48;
   3738 	s_player_rate_box.generic.callback = RateCallback;
   3739 	s_player_rate_box.curvalue = i;
   3740 	s_player_rate_box.itemnames = rate_names;
   3741 
   3742 	s_player_download_action.generic.type = MTYPE_ACTION;
   3743 	s_player_download_action.generic.name	= "download options";
   3744 	s_player_download_action.generic.flags= QMF_LEFT_JUSTIFY;
   3745 	s_player_download_action.generic.x	= -24;
   3746 	s_player_download_action.generic.y	= 186;
   3747 	s_player_download_action.generic.statusbar = NULL;
   3748 	s_player_download_action.generic.callback = DownloadOptionsFunc;
   3749 
   3750 	Menu_AddItem( &s_player_config_menu, &s_player_name_field );
   3751 	Menu_AddItem( &s_player_config_menu, &s_player_model_title );
   3752 	Menu_AddItem( &s_player_config_menu, &s_player_model_box );
   3753 	if ( s_player_skin_box.itemnames )
   3754 	{
   3755 		Menu_AddItem( &s_player_config_menu, &s_player_skin_title );
   3756 		Menu_AddItem( &s_player_config_menu, &s_player_skin_box );
   3757 	}
   3758 	Menu_AddItem( &s_player_config_menu, &s_player_hand_title );
   3759 	Menu_AddItem( &s_player_config_menu, &s_player_handedness_box );
   3760 	Menu_AddItem( &s_player_config_menu, &s_player_rate_title );
   3761 	Menu_AddItem( &s_player_config_menu, &s_player_rate_box );
   3762 	Menu_AddItem( &s_player_config_menu, &s_player_download_action );
   3763 
   3764 	return true;
   3765 }
   3766 
   3767 void PlayerConfig_MenuDraw( void )
   3768 {
   3769 	extern float CalcFov( float fov_x, float w, float h );
   3770 	refdef_t refdef;
   3771 	char scratch[MAX_QPATH];
   3772 
   3773 	memset( &refdef, 0, sizeof( refdef ) );
   3774 
   3775 	refdef.x = viddef.width / 2;
   3776 	refdef.y = viddef.height / 2 - 72;
   3777 	refdef.width = 144;
   3778 	refdef.height = 168;
   3779 	refdef.fov_x = 40;
   3780 	refdef.fov_y = CalcFov( refdef.fov_x, refdef.width, refdef.height );
   3781 	refdef.time = cls.realtime*0.001;
   3782 
   3783 	if ( s_pmi[s_player_model_box.curvalue].skindisplaynames )
   3784 	{
   3785 		static int yaw;
   3786 		int maxframe = 29;
   3787 		entity_t entity;
   3788 
   3789 		memset( &entity, 0, sizeof( entity ) );
   3790 
   3791 		Com_sprintf( scratch, sizeof( scratch ), "players/%s/tris.md2", s_pmi[s_player_model_box.curvalue].directory );
   3792 		entity.model = re.RegisterModel( scratch );
   3793 		Com_sprintf( scratch, sizeof( scratch ), "players/%s/%s.pcx", s_pmi[s_player_model_box.curvalue].directory, s_pmi[s_player_model_box.curvalue].skindisplaynames[s_player_skin_box.curvalue] );
   3794 		entity.skin = re.RegisterSkin( scratch );
   3795 		entity.flags = RF_FULLBRIGHT;
   3796 		entity.origin[0] = 80;
   3797 		entity.origin[1] = 0;
   3798 		entity.origin[2] = 0;
   3799 		VectorCopy( entity.origin, entity.oldorigin );
   3800 		entity.frame = 0;
   3801 		entity.oldframe = 0;
   3802 		entity.backlerp = 0.0;
   3803 		entity.angles[1] = yaw++;
   3804 		if ( ++yaw > 360 )
   3805 			yaw -= 360;
   3806 
   3807 		refdef.areabits = 0;
   3808 		refdef.num_entities = 1;
   3809 		refdef.entities = &entity;
   3810 		refdef.lightstyles = 0;
   3811 		refdef.rdflags = RDF_NOWORLDMODEL;
   3812 
   3813 		Menu_Draw( &s_player_config_menu );
   3814 
   3815 		M_DrawTextBox( ( refdef.x ) * ( 320.0F / viddef.width ) - 8, ( viddef.height / 2 ) * ( 240.0F / viddef.height) - 77, refdef.width / 8, refdef.height / 8 );
   3816 		refdef.height += 4;
   3817 
   3818 		re.RenderFrame( &refdef );
   3819 
   3820 		Com_sprintf( scratch, sizeof( scratch ), "/players/%s/%s_i.pcx", 
   3821 			s_pmi[s_player_model_box.curvalue].directory,
   3822 			s_pmi[s_player_model_box.curvalue].skindisplaynames[s_player_skin_box.curvalue] );
   3823 		re.DrawPic( s_player_config_menu.x - 40, refdef.y, scratch );
   3824 	}
   3825 }
   3826 
   3827 const char *PlayerConfig_MenuKey (int key)
   3828 {
   3829 	int i;
   3830 
   3831 	if ( key == K_ESCAPE )
   3832 	{
   3833 		char scratch[1024];
   3834 
   3835 		Cvar_Set( "name", s_player_name_field.buffer );
   3836 
   3837 		Com_sprintf( scratch, sizeof( scratch ), "%s/%s", 
   3838 			s_pmi[s_player_model_box.curvalue].directory, 
   3839 			s_pmi[s_player_model_box.curvalue].skindisplaynames[s_player_skin_box.curvalue] );
   3840 
   3841 		Cvar_Set( "skin", scratch );
   3842 
   3843 		for ( i = 0; i < s_numplayermodels; i++ )
   3844 		{
   3845 			int j;
   3846 
   3847 			for ( j = 0; j < s_pmi[i].nskins; j++ )
   3848 			{
   3849 				if ( s_pmi[i].skindisplaynames[j] )
   3850 					free( s_pmi[i].skindisplaynames[j] );
   3851 				s_pmi[i].skindisplaynames[j] = 0;
   3852 			}
   3853 			free( s_pmi[i].skindisplaynames );
   3854 			s_pmi[i].skindisplaynames = 0;
   3855 			s_pmi[i].nskins = 0;
   3856 		}
   3857 	}
   3858 	return Default_MenuKey( &s_player_config_menu, key );
   3859 }
   3860 
   3861 
   3862 void M_Menu_PlayerConfig_f (void)
   3863 {
   3864 	if (!PlayerConfig_MenuInit())
   3865 	{
   3866 		Menu_SetStatusBar( &s_multiplayer_menu, "No valid player models found" );
   3867 		return;
   3868 	}
   3869 	Menu_SetStatusBar( &s_multiplayer_menu, NULL );
   3870 	M_PushMenu( PlayerConfig_MenuDraw, PlayerConfig_MenuKey );
   3871 }
   3872 
   3873 
   3874 /*
   3875 =======================================================================
   3876 
   3877 GALLERY MENU
   3878 
   3879 =======================================================================
   3880 */
   3881 #if 0
   3882 void M_Menu_Gallery_f( void )
   3883 {
   3884 	extern void Gallery_MenuDraw( void );
   3885 	extern const char *Gallery_MenuKey( int key );
   3886 
   3887 	M_PushMenu( Gallery_MenuDraw, Gallery_MenuKey );
   3888 }
   3889 #endif
   3890 
   3891 /*
   3892 =======================================================================
   3893 
   3894 QUIT MENU
   3895 
   3896 =======================================================================
   3897 */
   3898 
   3899 const char *M_Quit_Key (int key)
   3900 {
   3901 	switch (key)
   3902 	{
   3903 	case K_ESCAPE:
   3904 	case 'n':
   3905 	case 'N':
   3906 		M_PopMenu ();
   3907 		break;
   3908 
   3909 	case 'Y':
   3910 	case 'y':
   3911 		cls.key_dest = key_console;
   3912 		CL_Quit_f ();
   3913 		break;
   3914 
   3915 	default:
   3916 		break;
   3917 	}
   3918 
   3919 	return NULL;
   3920 
   3921 }
   3922 
   3923 
   3924 void M_Quit_Draw (void)
   3925 {
   3926 	int		w, h;
   3927 
   3928 	re.DrawGetPicSize (&w, &h, "quit");
   3929 	re.DrawPic ( (viddef.width-w)/2, (viddef.height-h)/2, "quit");
   3930 }
   3931 
   3932 
   3933 void M_Menu_Quit_f (void)
   3934 {
   3935 	M_PushMenu (M_Quit_Draw, M_Quit_Key);
   3936 }
   3937 
   3938 
   3939 
   3940 //=============================================================================
   3941 /* Menu Subsystem */
   3942 
   3943 
   3944 /*
   3945 =================
   3946 M_Init
   3947 =================
   3948 */
   3949 void M_Init (void)
   3950 {
   3951 	Cmd_AddCommand ("menu_main", M_Menu_Main_f);
   3952 	Cmd_AddCommand ("menu_game", M_Menu_Game_f);
   3953 		Cmd_AddCommand ("menu_loadgame", M_Menu_LoadGame_f);
   3954 		Cmd_AddCommand ("menu_savegame", M_Menu_SaveGame_f);
   3955 		Cmd_AddCommand ("menu_joinserver", M_Menu_JoinServer_f);
   3956 			Cmd_AddCommand ("menu_addressbook", M_Menu_AddressBook_f);
   3957 		Cmd_AddCommand ("menu_startserver", M_Menu_StartServer_f);
   3958 			Cmd_AddCommand ("menu_dmoptions", M_Menu_DMOptions_f);
   3959 		Cmd_AddCommand ("menu_playerconfig", M_Menu_PlayerConfig_f);
   3960 			Cmd_AddCommand ("menu_downloadoptions", M_Menu_DownloadOptions_f);
   3961 		Cmd_AddCommand ("menu_credits", M_Menu_Credits_f );
   3962 	Cmd_AddCommand ("menu_multiplayer", M_Menu_Multiplayer_f );
   3963 	Cmd_AddCommand ("menu_video", M_Menu_Video_f);
   3964 	Cmd_AddCommand ("menu_options", M_Menu_Options_f);
   3965 		Cmd_AddCommand ("menu_keys", M_Menu_Keys_f);
   3966 	Cmd_AddCommand ("menu_quit", M_Menu_Quit_f);
   3967 }
   3968 
   3969 
   3970 /*
   3971 =================
   3972 M_Draw
   3973 =================
   3974 */
   3975 void M_Draw (void)
   3976 {
   3977 	if (cls.key_dest != key_menu)
   3978 		return;
   3979 
   3980 	// repaint everything next frame
   3981 	SCR_DirtyScreen ();
   3982 
   3983 	// dim everything behind it down
   3984 	if (cl.cinematictime > 0)
   3985 		re.DrawFill (0,0,viddef.width, viddef.height, 0);
   3986 	else
   3987 		re.DrawFadeScreen ();
   3988 
   3989 	m_drawfunc ();
   3990 
   3991 	// delay playing the enter sound until after the
   3992 	// menu has been drawn, to avoid delay while
   3993 	// caching images
   3994 	if (m_entersound)
   3995 	{
   3996 		S_StartLocalSound( menu_in_sound );
   3997 		m_entersound = false;
   3998 	}
   3999 }
   4000 
   4001 
   4002 /*
   4003 =================
   4004 M_Keydown
   4005 =================
   4006 */
   4007 void M_Keydown (int key)
   4008 {
   4009 	const char *s;
   4010 
   4011 	if (m_keyfunc)
   4012 		if ( ( s = m_keyfunc( key ) ) != 0 )
   4013 			S_StartLocalSound( ( char * ) s );
   4014 }
   4015 
   4016