1 /*
2 Copyright (C) 1997-2001 Id Software, Inc.
3 Copyright (C) 2005-2013 COR Entertainment, LLC.
4 
5 This program is free software; you can redistribute it and/or
6 modify it under the terms of the GNU General Public License
7 as published by the Free Software Foundation; either version 2
8 of the License, or (at your option) any later version.
9 
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 
14 See the GNU General Public License for more details.
15 
16 You should have received a copy of the GNU General Public License along
17 with this program; if not, write to the Free Software Foundation, Inc.,
18 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 
20 */
21 
22 #ifdef HAVE_CONFIG_H
23 #include "config.h"
24 #endif
25 
26 #include <ctype.h>
27 #if defined WIN32_VARIANT
28 #include <winsock.h>
29 #endif
30 
31 #if defined UNIX_VARIANT
32 #include <sys/time.h>
33 #if defined HAVE_UNISTD_H
34 #include <unistd.h>
35 #endif
36 #endif
37 
38 #if defined WIN32_VARIANT
39 #include <io.h>
40 #endif
41 
42 #include "client.h"
43 #include "client/qmenu.h"
44 
45 #if !defined HAVE__STRDUP
46 #if defined HAVE_STRDUP
47 #define _strdup strdup
48 #endif
49 #endif
50 
51 static int	m_main_cursor;
52 
53 extern int CL_GetPingStartTime(netadr_t adr);
54 
55 extern void RS_LoadScript(char *script);
56 extern void RS_LoadSpecialScripts(void);
57 extern void RS_ScanPathForScripts(void);
58 extern void RS_FreeUnmarked (void);
59 extern void SCR_DrawCenterString (void);
60 extern cvar_t *scriptsloaded;
61 #if defined WIN32_VARIANT
62 extern char map_music[MAX_PATH];
63 #else
64 extern char map_music[MAX_OSPATH];
65 #endif
66 extern cvar_t *background_music;
67 extern cvar_t *background_music_vol;
68 extern cvar_t *fov;
69 extern cvar_t *stats_password;
70 
71 static char *menu_in_sound		= "misc/menu1.wav";
72 static char *menu_move_sound	= "misc/menu2.wav";
73 static char *menu_out_sound		= "misc/menu3.wav";
74 
75 #define PLAYER_NAME_UNIQUE (strcmp (Cvar_VariableString ("name"), "Player") != 0)
76 
77 void SetCrosshairNames (char **list);
78 void SetHudNames (char **list);
79 void SetFontNames (char **list);
80 
81 void M_Menu_Main_f (void);
82 	static void M_Menu_PlayerConfig_f (void);
83 	static void M_Menu_Game_f (void);
84 		static void M_Menu_Credits_f( void );
85 	static void M_Menu_JoinServer_f (void);
86 			static void M_Menu_AddressBook_f( void );
87 			static void M_Menu_PlayerRanking_f( void );
88 			static void M_Menu_Tactical_f( void );
89 	static void M_Menu_StartServer_f (void);
90 			static void M_Menu_BotOptions_f (void);
91 	static void M_Menu_IRC_f (void);
92 	static void M_Menu_Options_f (void);
93 		static void M_Menu_Video_f (void);
94 		static void M_Menu_Keys_f (void);
95 	static void M_Menu_Quit_f (void);
96 
97 	static void M_Menu_Credits( void );
98 
99 qboolean	m_entersound;		// play after drawing a frame, so caching
100 								// won't disrupt the sound
101 
102 static size_t szr; // just for unused result warnings
103 
104 
105 // common callbacks
106 
StrFieldCallback(void * _self)107 static void StrFieldCallback( void *_self )
108 {
109 	menufield_s *self = (menufield_s *)_self;
110 	Cvar_Set( self->generic.localstrings[0], self->buffer);
111 }
112 
IntFieldCallback(void * _self)113 static void IntFieldCallback( void *_self )
114 {
115 	menufield_s *self = (menufield_s *)_self;
116 	Cvar_SetValue( self->generic.localstrings[0], atoi(self->buffer));
117 }
118 
PicSizeFunc(void * _self,FNT_font_t font)119 static menuvec2_t PicSizeFunc (void *_self, FNT_font_t font)
120 {
121 	menuvec2_t	ret;
122 	menuitem_s	*self = (menuitem_s *)_self;
123 
124 	ret.x = ret.y = 0;
125 
126 	// Determine if pic exists, if not return 0 size.
127 	// However, we give the benefit of the doubt if the name isn't there, and
128 	// assume it will be.
129 	if (self->generic.localstrings[0] == NULL || Draw_PicExists (self->generic.localstrings[0]))
130 	{
131 		ret.x = self->generic.localints[0]*font->size;
132 		ret.y = self->generic.localints[1]*font->size;
133 		ret.x += self->generic.localints[2];
134 	}
135 
136 	return ret;
137 }
138 
139 // most useful if this element will always draw the same pic
PicDrawFunc(void * _self,FNT_font_t font)140 static void PicDrawFunc (void *_self, FNT_font_t font)
141 {
142 	int x, y;
143 	menuitem_s *self = (menuitem_s *)_self;
144 
145 	x = Item_GetX (*self) + self->generic.localints[2];
146 	y = Item_GetY (*self);
147 
148 	Draw_StretchPic (x, y, font->size*self->generic.localints[0], font->size*self->generic.localints[1], self->generic.localstrings[0]);
149 }
150 
151 // for spin controls where each item is a texture path
PicSpinSizeFunc(void * _self,FNT_font_t font)152 static menuvec2_t PicSpinSizeFunc (void *_self, FNT_font_t font)
153 {
154 	menuvec2_t	ret;
155 	menulist_s	*self = (menulist_s *)_self;
156 
157 	ret.x = self->generic.localints[0]*font->size;
158 	ret.y = self->generic.localints[1]*font->size;
159 	ret.x += self->generic.localints[2];
160 
161 	return ret;
162 }
163 
PicSpinDrawFunc(void * _self,FNT_font_t font)164 static void PicSpinDrawFunc (void *_self, FNT_font_t font)
165 {
166 	int x, y;
167 	menulist_s *self = (menulist_s *)_self;
168 
169 	x = Item_GetX (*self);
170 	y = Item_GetY (*self);
171 	x += self->generic.localints[2];
172 
173 	if (strlen(self->itemnames[self->curvalue]) > 0)
174 		Draw_StretchPic (x, y,font->size*self->generic.localints[0], font->size*self->generic.localints[1], self->itemnames[self->curvalue]);
175 }
176 
177 
178 // name lists: list of strings terminated by 0
179 
180 static const char *onoff_names[] =
181 {
182 	"",
183 	"menu/on",
184 	0
185 };
186 
187 // when you want 0 to be on
188 static const char *offon_names[] =
189 {
190 	"menu/on",
191 	"",
192 	0
193 };
194 
IconSpinSizeFunc(void * _self,FNT_font_t font)195 static menuvec2_t IconSpinSizeFunc (void *_self, FNT_font_t font)
196 {
197 	menuvec2_t ret;
198 	menulist_s *self = (menulist_s *)_self;
199 
200 	ret.x = ret.y = font->size;
201 	ret.x += RCOLUMN_OFFSET;
202 	if ((self->generic.flags & QMF_RIGHT_COLUMN))
203 		ret.x += Menu_PredictSize (self->generic.name);
204 	return ret;
205 }
206 
IconSpinDrawFunc(void * _self,FNT_font_t font)207 static void IconSpinDrawFunc (void *_self, FNT_font_t font)
208 {
209 	int x, y;
210 	menulist_s *self = (menulist_s *)_self;
211 
212 	x = Item_GetX (*self)+RCOLUMN_OFFSET;
213 	y = Item_GetY (*self)+MenuText_UpperMargin (self, font->size);
214 	if ((self->generic.flags & QMF_RIGHT_COLUMN))
215 		x += Menu_PredictSize (self->generic.name);
216 	Draw_AlphaStretchPic (
217 		x, y, font->size, font->size, "menu/icon_border",
218 		self->generic.highlight_alpha*self->generic.highlight_alpha
219 	);
220 	if (strlen(self->itemnames[self->curvalue]) > 0)
221 		Draw_AlphaStretchPic (x, y, font->size, font->size, self->itemnames[self->curvalue], self->generic.highlight_alpha);
222 }
223 
224 #define setup_tickbox(spinctrl) \
225 { \
226 	(spinctrl).generic.type = MTYPE_SPINCONTROL; \
227 	(spinctrl).generic.itemsizecallback = IconSpinSizeFunc; \
228 	(spinctrl).generic.itemdraw = IconSpinDrawFunc; \
229 	(spinctrl).itemnames = onoff_names; \
230 	(spinctrl).generic.flags |= QMF_ALLOW_WRAP; \
231 	(spinctrl).curvalue = 0; \
232 }
233 
RadioSpinDrawFunc(void * _self,FNT_font_t font)234 static void RadioSpinDrawFunc (void *_self, FNT_font_t font)
235 {
236 	int x, y;
237 	menulist_s *self = (menulist_s *)_self;
238 
239 	x = Item_GetX (*self)+RCOLUMN_OFFSET;
240 	y = Item_GetY (*self)+MenuText_UpperMargin (self, font->size);
241 	if ((self->generic.flags & QMF_RIGHT_COLUMN))
242 		x += Menu_PredictSize (self->generic.name);
243 	Draw_AlphaStretchPic (
244 		x, y, font->size, font->size, "menu/radio_border",
245 		self->generic.highlight_alpha*self->generic.highlight_alpha
246 	);
247 	if (strlen(self->itemnames[self->curvalue]) > 0)
248 		Draw_AlphaStretchPic (x, y, font->size, font->size, self->itemnames[self->curvalue], self->generic.highlight_alpha);
249 }
250 
251 #define setup_radiobutton(spinctrl) \
252 { \
253 	(spinctrl).generic.type = MTYPE_SPINCONTROL; \
254 	(spinctrl).generic.itemsizecallback = IconSpinSizeFunc; \
255 	(spinctrl).generic.itemdraw = RadioSpinDrawFunc; \
256 	(spinctrl).itemnames = onoff_names; \
257 	(spinctrl).generic.flags |= QMF_ALLOW_WRAP; \
258 	(spinctrl).curvalue = 0; \
259 }
260 
261 #define setup_nth_window(parent,n,window,title) \
262 { \
263 	(parent).nitems = n; \
264 	(parent).num_apply_pending = 0; \
265 	\
266 	(window).generic.type = MTYPE_SUBMENU; \
267 	(window).navagable = true; \
268 	(window).nitems = 0; \
269 	(window).bordertitle = title; \
270 	(window).bordertexture = "menu/m_"; \
271 	\
272 	Menu_AddItem (&(parent), &(window)); \
273 }
274 
275 #define setup_window(parent,window,title) setup_nth_window(parent,0,window,title)
276 
277 #define setup_panel(parent,panel) \
278 { \
279 	(panel).generic.type = MTYPE_SUBMENU; \
280 	(panel).generic.flags = QMF_SNUG_LEFT; \
281 	(panel).navagable = true; \
282 	(panel).nitems = 0; \
283 	(panel).bordertexture = "menu/sm_"; \
284 	Menu_AddItem (&(parent), &(panel)); \
285 }
286 
287 // if you just want to add some text to a menu and never need to refer to it
288 // again (don't use inside a loop!)
289 #define add_text(menu,text,itflags) \
290 {\
291 	static menutxt_s it; \
292 	it.generic.type = MTYPE_TEXT; \
293 	it.generic.flags = (itflags); \
294 	it.generic.name = (text); \
295 	Menu_AddItem(&(menu), &(it)); \
296 }
297 
298 // if you just want to add an action to a menu and never need to refer to it
299 // again (don't use inside a loop!)
300 #define add_action(menu,itname,itcallback,itflags) \
301 {\
302 	static menuaction_s it; \
303 	it.generic.type = MTYPE_ACTION; \
304 	it.generic.flags = (itflags)|QMF_BUTTON; \
305 	it.generic.name = (itname); \
306 	it.generic.callback = (itcallback); \
307 	Menu_AddItem (&(menu), &(it)); \
308 }
309 
310 
311 // Should be useful for most menus
312 #define M_PushMenu_Defaults(struct) \
313 	M_PushMenu (Screen_Draw, Default_MenuKey, &(struct))
314 
refreshCursorButton(int button)315 static inline void refreshCursorButton (int button)
316 {
317 	cursor.buttonused[button] = true;
318 	cursor.buttonclicks[button] = 0;
319 }
320 
refreshAllCursorButtons(void)321 void refreshAllCursorButtons(void)
322 {
323 	int i;
324 	for (i = 0; i < MENU_CURSOR_BUTTON_MAX; i++)
325 		refreshCursorButton (i);
326 }
327 
328 //=============================================================================
329 /*	Screen layout routines -- responsible for tiling all the levels of menus
330 	on the screen and animating transitions between them. This uses a finite
331 	state machine to track which windows are active, "incoming" (will become
332 	active after the current animation is complete,) and "outgoing" (will no
333 	longer be active after the current animation is complete. If there are
334 	incoming or outgoing windows, user input is disabled until the transition
335 	animation is complete.
336 
337 	Each window is a menu tree (a menuframework_s struct with submenus.) The
338 	purpose of the animation code is to determine what x-axis offset each
339 	window should be drawn at (that is, what number of pixels should be added
340 	to the x-axis of the window when it is drawn.) The x offset for each
341 	window is recalculated each frame so that the windows tile neatly
342 	alongside each other, slide across the screen, etc.
343 
344 	The main menu is a special case, in that it will shrink into a sidebar
345 	instead of appearing partially off screen.
346 
347 	Architecturally, this is done with a simple finite-state machine.
348 */
349 
350 // M_Interp - responsible for animating the transitions as menus are added and
351 // removed from the screen.  Actually, this function just performs an
352 // interpolation between 0 and target, returning a number that should be used
353 // next time for progress. you determine roughly how many pixels a menu is
354 // going to slide, and in which direction.  Your target is that number of
355 // pixels, positive or negative depending on the direction.  Call this
356 // function repeatedly with your target, and it will return a series of pixel
357 // offsets that can be used in your animation.
M_Interp(int progress,int target)358 int M_Interp (int progress, int target)
359 {
360 	int increment = 0; // always positive
361 
362 	// Determine the movement amount this frame. Make it nice and fast,
363 	// because while slow might look cool, it's also an inconvenience.
364 	if (target != progress)
365 	{
366 		static float frametime_accum = 0;
367 
368 		// The animation speeds up as it gets further from the starting point
369 		// and slows down twice as fast as it approaches the ending point.
370 		increment = min(	abs((11*target)/10-progress)/2,
371 							abs(progress) )*40;
372 
373 		// Clamp the animation speed at a minimum so it won't freeze due to
374 		// rounding errors or take too long at either end.
375 		increment = max (increment, abs(target)/10);
376 
377 		// Scale the animation by frame time so its speed is independent of
378 		// framerate. At very high framerates, each individual frame might be
379 		// too small a time to result in an integer amount of movement. So we
380 		// just add frames together until we do get some movement.
381 		frametime_accum += cls.frametime;
382 		increment *= frametime_accum;
383 
384 		if (increment > 0)
385 			frametime_accum = 0;
386 		else
387 			return progress; // no movement, better luck next time.
388 	}
389 
390 	if (target > 0)
391 	{
392 		// increasing
393 		progress += increment;
394 		progress = min (progress, target); // make sure we don't overshoot
395 	}
396 	else if (target < 0)
397 	{
398 		// decreasing
399 		progress -= increment;
400 		progress = max (progress, target); // make sure we don't overshoot
401 	}
402 
403 	return progress;
404 }
405 
406 // linear interpolation
407 #define lerp(start,end,progress) ((start) + (double)((end)-(start))*(progress))
408 
409 #define	MAX_MENU_DEPTH	8
410 
411 #define sidebar_width ((float)(150*viddef.width)/1024.0)
412 
413 // Window wrapper
414 // (TODO: rename all mention of "layer" to "screen" or "window," haven't
415 // decided which yet.)
416 typedef struct
417 {
418 	void	(*draw) (menuframework_s *screen, menuvec2_t offset);
419 	const char *(*key) (menuframework_s *screen, int k);
420 	menuframework_s *screen;
421 } menulayer_t;
422 
423 // An "inelastic" row of windows. They always tile side by side, and the total
424 // width is always the sum of the contained windows. This struct is for
425 // convenience; it's easier to animate such a group of windows as a single
426 // unit.
427 typedef struct
428 {
429 	int			offset; // starting x-axis pixel offset for the leftmost window
430 	int			num_layers;
431 	menulayer_t	layers[MAX_MENU_DEPTH];
432 } layergroup_t;
433 
434 #define layergroup_last(g) ((g).layers[(g).num_layers-1])
435 
436 // add up all the widths of each window in the group
layergroup_width(layergroup_t * g)437 static inline int layergroup_width (layergroup_t *g)
438 {
439 	int i, ret;
440 
441 	ret = 0;
442 	for (i = 0; i < g->num_layers; i++)
443 		ret += Menu_TrueWidth (*g->layers[i].screen);
444 	return ret;
445 }
446 
447 // Add up the widths of each window in the group that cannot fit on screen,
448 // starting with the leftmost. If the final (deepest) window is itself too
449 // wide, it still won't be included.
layergroup_excesswidth(layergroup_t * g)450 static inline int layergroup_excesswidth (layergroup_t *g)
451 {
452 	int i, ret, w;
453 
454 	ret = w = layergroup_width (g);
455 	for (i = 0; i < g->num_layers-1; i++)
456 	{
457 		if (ret < viddef.width)
458 			break;
459 		ret -= Menu_TrueWidth (*g->layers[i].screen);
460 	}
461 	return w-ret;
462 }
463 
464 // Like layergroup_excesswidth, but as if the windows from the two groups were
465 // hypothetically in the same group.
layergroup_pair_excesswidth(layergroup_t * g1,layergroup_t * g2)466 static inline int layergroup_pair_excesswidth (layergroup_t *g1, layergroup_t *g2)
467 {
468 	int i, ret, w;
469 
470 
471 	if (g2->num_layers == 0)
472 		return layergroup_excesswidth (g1);
473 	if (g1->num_layers == 0)
474 		return layergroup_excesswidth (g2);
475 
476 	ret = w = layergroup_width (g1) + layergroup_width (g2);
477 	for (i = 0; i < g1->num_layers; i++)
478 	{
479 		if (ret < viddef.width)
480 			break;
481 		ret -= Menu_TrueWidth (*g1->layers[i].screen);
482 	}
483 	for (i = 0; i < g2->num_layers-1; i++)
484 	{
485 		if (ret < viddef.width)
486 			break;
487 		ret -= Menu_TrueWidth (*g2->layers[i].screen);
488 	}
489 	return w-ret;
490 }
491 
492 
layergroup_draw(layergroup_t * g)493 static void layergroup_draw (layergroup_t *g)
494 {
495 	int i;
496 	menuvec2_t offs;
497 	offs.y = 0;
498 	offs.x = g->offset;
499 	for (i = 0; i < g->num_layers; i++)
500 	{
501 		g->layers[i].draw (g->layers[i].screen, offs);
502 		offs.x += Menu_TrueWidth (*g->layers[i].screen);
503 	}
504 }
505 
506 // this holds the state machine state
507 static struct
508 {
509 	enum
510 	{
511 		mstate_steady,	// no animation, incoming & outgoing empty
512 		mstate_insert,	// menus being added, possibly some outgoing menus
513 		mstate_remove	// outgoing menus
514 	} state;
515 	layergroup_t active;
516 	layergroup_t outgoing;
517 	layergroup_t incoming;
518 	int animation; // current animation pixel offset
519 } mstate;
520 
mstate_reset(void)521 static inline void mstate_reset (void)
522 {
523 	mstate.active.num_layers = mstate.incoming.num_layers = mstate.outgoing.num_layers = 0;
524 	mstate.state = mstate_steady;
525 	refreshCursorLink ();
526 }
527 
528 #define activelayer(idx) (mstate.active.layers[(idx)])
529 
Cursor_GetLayer(void)530 int Cursor_GetLayer (void)
531 {
532 	int i;
533 	menuframework_s *screen;
534 
535 	if (cursor.menuitem == NULL)
536 		Com_Error (ERR_FATAL, "Cursor_GetLayer: unset cursor.menuitem!");
537 
538 	screen = Menu_GetItemTree (cursor.menuitem);
539 
540 	for (i = 0; i < mstate.active.num_layers; i++)
541 	{
542 		if (activelayer(i).screen == screen)
543 			return i;
544 	}
545 
546 	// We only get here if, after changing resolutions, the mouse is no longer
547 	// on screen.
548 	Com_Printf ("WARN: fake cursor.menulayer!\n");
549 	return -1;
550 }
551 
activelayer_coordidx(int xcoord)552 static int activelayer_coordidx (int xcoord)
553 {
554 	int i;
555 	xcoord -= mstate.active.offset;
556 	if (xcoord < 0 || mstate.active.num_layers == 0)
557 		return -1;
558 	for (i = 0; i < mstate.active.num_layers; i++)
559 	{
560 		xcoord -= Menu_TrueWidth (*activelayer(i).screen);
561 		if (xcoord < 0)
562 			break;
563 	}
564 	return i;
565 }
566 
567 // Figure out the starting offset for the leftmost window of the "active"
568 // (neither incoming nor outgoing) window group. Usually just equal to the
569 // maximum width of the sidebar, unless there are so many large windows that
570 // they can't all fit on screen at once, in which case it may be a negative
571 // number.
Menuscreens_Animate_Active(void)572 static inline int Menuscreens_Animate_Active (void)
573 {
574 	int shove_offset, ret, excess;
575 	excess = layergroup_excesswidth (&mstate.active);
576 	if (excess != 0)
577 		return -excess;
578 	ret = sidebar_width;
579 	shove_offset = viddef.width - layergroup_width (&mstate.active);
580 	if (shove_offset < ret)
581 		ret = shove_offset;
582 	return ret;
583 }
584 
585 // Figure out the starting offset for the leftmost window of the active window
586 // group, *if* the "incoming" windows were hypothetically added to the end of
587 // the active window group. Will be used as the "target" for the incoming-
588 // window animation. This is because when the animation is done, the incoming
589 // windows will be added to the end of the active window group, and we want
590 // the transition to be smooth.
MenuScreens_Animate_Incoming_Target(void)591 static inline int MenuScreens_Animate_Incoming_Target (void)
592 {
593 	int shove_offset, ret, excess;
594 	excess = layergroup_pair_excesswidth (&mstate.active, &mstate.incoming);
595 	if (excess != 0)
596 		return -excess;
597 	ret = sidebar_width;
598 	shove_offset = viddef.width - layergroup_width (&mstate.active) - layergroup_width (&mstate.incoming);
599 	if (shove_offset < ret)
600 		ret = shove_offset;
601 	return ret;
602 }
603 
604 // Figure out the starting offset for the leftmost window of the "active"
605 // window group, *if* the outgoing windows were hypothetically added to the
606 // end of the active window group. Will be used as the "start" for the
607 // outgoing- window animation. This is because before the animation started,
608 // the outgoing windows were at the end of the active window group, and we
609 // want the transition to be smooth.
MenuScreens_Animate_Outgoing_Start(void)610 static inline int MenuScreens_Animate_Outgoing_Start (void)
611 {
612 	int shove_offset, ret, excess;
613 	excess = layergroup_pair_excesswidth (&mstate.active, &mstate.outgoing);
614 	if (excess != 0)
615 		return -excess;
616 	ret = sidebar_width;
617 	shove_offset = viddef.width - layergroup_width (&mstate.active) - layergroup_width (&mstate.outgoing);
618 	if (shove_offset < ret)
619 		ret = shove_offset;
620 	return ret;
621 }
622 
623 void Menuscreens_Animate (void);
624 
625 // state machine state transitions
Menuscreens_Animate_Insert_To_Steady(void)626 void Menuscreens_Animate_Insert_To_Steady (void)
627 {
628 	int i;
629 	for (i = 0; i < mstate.incoming.num_layers; i++)
630 		activelayer(mstate.active.num_layers++) = mstate.incoming.layers[i];
631 	Cursor_SelectMenu (layergroup_last(mstate.active).screen);
632 	mstate.incoming.num_layers = 0;
633 	mstate.outgoing.num_layers = 0;
634 	mstate.state = mstate_steady;
635 	mstate.animation = 0;
636 	Menuscreens_Animate ();
637 }
Menuscreens_Animate_Remove_To_Steady(void)638 void Menuscreens_Animate_Remove_To_Steady (void)
639 {
640 	mstate.outgoing.num_layers = 0;
641 	mstate.state = mstate_steady;
642 	if (mstate.active.num_layers == 0)
643 		refreshCursorLink ();
644 	else
645 		Cursor_SelectMenu (layergroup_last(mstate.active).screen);
646 	mstate.animation = 0;
647 	Menuscreens_Animate ();
648 }
649 
650 void M_Main_Draw (menuvec2_t offset);
651 void CheckMainMenuMouse (void);
652 
653 // This is where the magic happens. (TODO: maybe separate the actual rendering
654 // out into a different function?)
Menuscreens_Animate(void)655 void Menuscreens_Animate (void)
656 {
657 	int shove_offset, anim_start, anim_end;
658 	menuvec2_t main_offs;
659 
660 	main_offs.x = main_offs.y = 0;
661 
662 	switch (mstate.state)
663 	{
664 	case mstate_steady:
665 	{
666 		if (mstate.active.num_layers != 0)
667 		{
668 			mstate.active.offset = Menuscreens_Animate_Active ();
669 			main_offs.x = mstate.active.offset-viddef.width;
670 		}
671 
672 		M_Main_Draw (main_offs);
673 		layergroup_draw (&mstate.active);
674 	}
675 	break;
676 	case mstate_insert:
677 	{
678 		if (mstate.active.num_layers == 0)
679 			mstate.active.offset = 0;
680 		else
681 			mstate.active.offset = Menuscreens_Animate_Active ();
682 
683 		anim_start = mstate.active.offset+viddef.width;
684 		anim_end = MenuScreens_Animate_Incoming_Target ();
685 
686 		mstate.animation = M_Interp (mstate.animation, anim_end-anim_start);
687 		if (mstate.animation <= anim_end-anim_start)
688 		{
689 			Menuscreens_Animate_Insert_To_Steady ();
690 			return;
691 		}
692 		shove_offset = anim_start+mstate.animation;
693 
694 		if (shove_offset < mstate.active.offset || mstate.active.num_layers == 0)
695 			mstate.active.offset = shove_offset;
696 
697 		// If there are outgoing windows, the incoming ones "push" them back
698 		// behind the active windows and the sidebar.
699 		if (mstate.outgoing.num_layers > 0)
700 		{
701 			int outgoing_shove, outgoing_start, outgoing_end;
702 			double outgoing_fade;
703 
704 			mstate.outgoing.offset = outgoing_start = MenuScreens_Animate_Outgoing_Start () + layergroup_width (&mstate.active);
705 
706 			outgoing_end = Menuscreens_Animate_Active () - layergroup_width (&mstate.outgoing);
707 
708 			outgoing_shove = shove_offset + layergroup_width (&mstate.active) - layergroup_width (&mstate.outgoing);
709 			if (outgoing_shove < mstate.outgoing.offset)
710 				mstate.outgoing.offset = outgoing_shove;
711 
712 			layergroup_draw (&mstate.outgoing);
713 
714 			outgoing_fade = (double)(mstate.outgoing.offset-outgoing_start)/(double)(outgoing_end-outgoing_start);
715 
716 			// Fade out the outgoing windows
717 			Draw_Fill (
718 				mstate.outgoing.offset, 0,
719 				layergroup_width (&mstate.outgoing), viddef.height,
720 				RGBA (0, 0, 0, sqrt(outgoing_fade))
721 			);
722 
723 			// Interpolate the sidebar as well.
724 			mstate.active.offset = lerp (
725 				MenuScreens_Animate_Outgoing_Start (),
726 				MenuScreens_Animate_Incoming_Target (),
727 				outgoing_fade
728 			);
729 		}
730 
731 		main_offs.x = mstate.active.offset-viddef.width;
732 		mstate.incoming.offset = shove_offset + layergroup_width (&mstate.active);
733 
734 		M_Main_Draw (main_offs);
735 		layergroup_draw (&mstate.active);
736 		layergroup_draw (&mstate.incoming);
737 	}
738 	break;
739 	case mstate_remove:
740 	{
741 		if (mstate.active.num_layers == 0)
742 			mstate.active.offset = 0;
743 		else
744 			mstate.active.offset = Menuscreens_Animate_Active ();
745 
746 		anim_start = MenuScreens_Animate_Outgoing_Start ();
747 		anim_end = mstate.active.offset + viddef.width;
748 
749 		mstate.animation = M_Interp (mstate.animation, anim_end-anim_start);
750 		if (mstate.animation >= anim_end-anim_start)
751 		{
752 			Menuscreens_Animate_Remove_To_Steady ();
753 			return;
754 		}
755 		shove_offset = anim_start+mstate.animation;
756 
757 		if (shove_offset < mstate.active.offset || mstate.active.num_layers == 0)
758 			mstate.active.offset = shove_offset;
759 
760 		main_offs.x = mstate.active.offset-viddef.width;
761 		mstate.outgoing.offset = shove_offset + layergroup_width (&mstate.active);
762 
763 		M_Main_Draw (main_offs);
764 		layergroup_draw (&mstate.active);
765 		layergroup_draw (&mstate.outgoing);
766 	}
767 	break;
768 	}
769 }
770 
771 // These functions (Push, Force Off, and Pop) are used by the outside world to
772 // control the state machine.
773 
M_PushMenu(void (* draw)(menuframework_s * screen,menuvec2_t offset),const char * (* key)(menuframework_s * screen,int k),menuframework_s * screen)774 void M_PushMenu ( void (*draw) (menuframework_s *screen, menuvec2_t offset), const char *(*key) (menuframework_s *screen, int k), menuframework_s *screen)
775 {
776 	int			i, insertion_point;
777 	qboolean	found = false;
778 
779 	if (Cvar_VariableValue ("maxclients") == 1
780 		&& Com_ServerState ())
781 		Cvar_Set ("paused", "1");
782 
783 	screen->navagable = true;
784 	Menu_AutoArrange (screen);
785 
786 	for (i = 0; i < mstate.active.num_layers; i++)
787 	{
788 		if (activelayer(i).screen == screen)
789 		{
790 			found = true;
791 			break;
792 		}
793 	}
794 
795 	if (found)
796 	{
797 		insertion_point = i;
798 		mstate.state = mstate_remove;
799 	}
800 	else
801 	{
802 		mstate.incoming.num_layers++;
803 		layergroup_last(mstate.incoming).draw = draw;
804 		layergroup_last(mstate.incoming).key = key;
805 		layergroup_last(mstate.incoming).screen = screen;
806 		mstate.state = mstate_insert;
807 		insertion_point = cursor.menulayer;
808 	}
809 
810 	for (i = insertion_point+1; i < mstate.active.num_layers; i++)
811 		mstate.outgoing.layers[mstate.outgoing.num_layers++] = activelayer(i);
812 	mstate.active.num_layers = insertion_point+1;
813 
814 	cls.key_dest = key_menu;
815 
816 	m_entersound = true;
817 }
818 
M_ForceMenuOff(void)819 void M_ForceMenuOff (void)
820 {
821 	cls.key_dest = key_game;
822 	Key_ClearStates ();
823 	Cvar_Set ("paused", "0");
824 
825 	mstate_reset ();
826 
827 	//-JD kill the music when leaving the menu of course
828 	S_StopAllSounds();
829 	background_music = Cvar_Get ("background_music", "1", CVAR_ARCHIVE);
830 	S_StartMapMusic();
831 }
832 
M_PopMenu(void)833 void M_PopMenu (void)
834 {
835 	S_StartLocalSound( menu_out_sound );
836 	if (mstate.active.num_layers == 0)
837 	{
838 		M_ForceMenuOff ();
839 		return;
840 	}
841 
842 	mstate.outgoing.layers[mstate.outgoing.num_layers++] =
843 		activelayer(--mstate.active.num_layers);
844 	mstate.state = mstate_remove;
845 }
846 
847 
Default_MenuKey(menuframework_s * m,int key)848 const char *Default_MenuKey (menuframework_s *m, int key)
849 {
850 	const char *sound = NULL;
851 
852 	// this should work no matter what
853 	if (key == K_ESCAPE)
854 	{
855 		M_PopMenu();
856 		return menu_out_sound;
857 	}
858 
859 	// the rest of these won't work unless there's a selected menu item
860 	if (cursor.menuitem == NULL)
861 		return NULL;
862 
863 	// offer the keypress to the field key parser, see if it wants it
864 	if (Field_Key (key))
865 	{
866 		Menu_ActivateItem (cursor.menuitem);
867 		return NULL;
868 	}
869 
870 	switch ( key )
871 	{
872 	case K_MWHEELUP:
873 	case K_KP_UPARROW:
874 	case K_UPARROW:
875 		Menu_AdvanceCursor (-1, false);
876 		break;
877 	case K_TAB:
878 		Menu_AdvanceCursor (1, true);
879 		break;
880 	case K_MWHEELDOWN:
881 	case K_KP_DOWNARROW:
882 	case K_DOWNARROW:
883 		Menu_AdvanceCursor (1, false);
884 		break;
885 	case K_KP_LEFTARROW:
886 	case K_LEFTARROW:
887 		Menu_SlideItem (-1);
888 		sound = menu_move_sound;
889 		break;
890 	case K_KP_RIGHTARROW:
891 	case K_RIGHTARROW:
892 		Menu_SlideItem (1);
893 		sound = menu_move_sound;
894 		break;
895 	case K_KP_ENTER:
896 	case K_ENTER:
897 		Menu_ActivateItem (cursor.menuitem);
898 		sound = menu_move_sound;
899 		break;
900 	}
901 
902 	return sound;
903 }
904 
905 /*
906 =======================================================================
907 
908 MAIN MENU
909 
910 =======================================================================
911 */
912 
913 char *main_names[] =
914 {
915 	"m_main_game",
916 	"m_main_join",
917 	"m_main_host",
918 	"m_main_options",
919 	"m_main_quit",
920 	"m_main_credits",
921 };
922 #define MAIN_ITEMS static_array_size(main_names)
923 
924 void (*main_open_funcs[MAIN_ITEMS])(void) =
925 {
926 	&M_Menu_Game_f,
927 	&M_Menu_JoinServer_f,
928 	&M_Menu_StartServer_f,
929 	&M_Menu_Options_f,
930 	&M_Menu_Quit_f,
931 	&M_Menu_Credits_f
932 };
933 
findMenuCoords(int * xoffset,int * ystart,int * totalheight,int * widest)934 void findMenuCoords (int *xoffset, int *ystart, int *totalheight, int *widest)
935 {
936 	int w, h, i;
937 	float scale;
938 
939 	scale = (float)(viddef.height)/600;
940 
941 	*totalheight = 0;
942 	*widest = -1;
943 
944 	for ( i = 0; i < MAIN_ITEMS; i++ )
945 	{
946 		Draw_GetPicSize( &w, &h, main_names[i] );
947 
948 		if ( w*scale > *widest )
949 			*widest = w*scale;
950 		*totalheight += ( h*scale + 24*scale);
951 	}
952 
953 	*ystart = ( viddef.height / 2 - 20*scale );
954 	*xoffset = ( viddef.width - *widest + 350*scale) / 2;
955 }
956 
M_Main_Draw(menuvec2_t offset)957 void M_Main_Draw (menuvec2_t offset)
958 {
959 	int i;
960 	int ystart, xstart, xend;
961 	int	xoffset;
962 	int widest = -1;
963 	int totalheight = 0;
964 	char litname[80];
965 	float scale, hscale, hscaleoffs;
966 	float widscale;
967 	int w, h;
968 	char montagepicname[16];
969 	char backgroundpic[16];
970 	char *version_warning;
971 
972 	static float mainalpha;
973 	static int montagepic = 1;
974 
975 	scale = ((float)(viddef.height))/600.0;
976 
977 	widscale = ((float)(viddef.width))/1024.0;
978 
979 	findMenuCoords(&xoffset, &ystart, &totalheight, &widest);
980 
981 	ystart = ( viddef.height / 2 - 20*scale ) + offset.y;
982 	xoffset = ( viddef.width - widest - 35*widscale) / 2 + offset.x;
983 
984 	// When animating a transition away from the main menu, the background
985 	// slides away at double speed, disappearing and leaving just the menu
986 	// items themselves. Hence some things use offset.x*1.25.
987 
988 #ifdef TACTICAL
989 	Draw_StretchPic(offset.x*1.25, offset.y, viddef.width, viddef.height, "m_main_tactical");
990 #else
991 	Draw_StretchPic(offset.x*1.25, offset.y, viddef.width, viddef.height, "m_main");
992 #endif
993 
994 	//draw the montage pics
995 	mainalpha += cls.frametime; //fade image in
996 	if(mainalpha > 4)
997 	{
998 		//switch pics at this point
999 		mainalpha = 0.1;
1000 		montagepic++;
1001 		if(montagepic > 5)
1002 		{
1003 			montagepic = 1;
1004 		}
1005 	}
1006 	sprintf(backgroundpic, "m_main_mont%i", (montagepic==1)?5:montagepic-1);
1007 	sprintf(montagepicname, "m_main_mont%i", montagepic);
1008 	Draw_StretchPic (offset.x*1.25, offset.y, viddef.width, viddef.height, backgroundpic);
1009 	Draw_AlphaStretchPic (offset.x*1.25, offset.y, viddef.width, viddef.height, montagepicname, mainalpha);
1010 
1011 
1012 	/* check for more recent program version */
1013 	version_warning = VersionUpdateNotice();
1014 	if ( version_warning != NULL )
1015 	{
1016 		extern const float light_color[4];
1017 		Menu_DrawString (
1018 			offset.x, offset.y + 5*scale,
1019 			version_warning, FNT_CMODE_QUAKE_SRS, FNT_ALIGN_LEFT, light_color
1020 		);
1021 	}
1022 
1023 	//draw the main menu buttons
1024 	for ( i = 0; i < MAIN_ITEMS; i++ )
1025 	{
1026 		strcpy( litname, main_names[i] );
1027 		if (i == m_main_cursor && cursor.menulayer == -1)
1028 			strcat( litname, "_sel");
1029 		Draw_GetPicSize( &w, &h, litname );
1030 		xstart = xoffset + 100*widscale + (20*i*widscale);
1031 		if (xstart < 0)
1032 			xstart += min(-xstart, (8-i)*20*widscale);
1033 		xend = xstart+w*widscale;
1034 		hscale = 1;
1035 		if (xstart < 0)
1036 		{
1037 			if (xend < 150*widscale)
1038 				xend = min (viddef.width+offset.x, 150*widscale);
1039 			xstart = 0;
1040 			if (xend < 50*widscale)
1041 				return;
1042 		}
1043 		hscale = (float)(xend-xstart)/(float)(w*widscale);
1044 		hscaleoffs = (float)h*scale-(float)h*hscale*scale;
1045 		Draw_StretchPic( xstart, (int)(ystart + i * 32.5*scale + 13*scale + hscaleoffs), xend-xstart, h*hscale*scale, litname );
1046 	}
1047 }
1048 
CheckMainMenuMouse(void)1049 void CheckMainMenuMouse (void)
1050 {
1051 	int ystart;
1052 	int	xoffset;
1053 	int widest;
1054 	int totalheight;
1055 	int i, oldhover;
1056 	float scale;
1057 	static int MainMenuMouseHover;
1058 
1059 	scale = (float)(viddef.height)/600;
1060 
1061 	oldhover = MainMenuMouseHover;
1062 	MainMenuMouseHover = 0;
1063 
1064 	findMenuCoords(&xoffset, &ystart, &totalheight, &widest);
1065 
1066 	i = (cursor.y - ystart - 24*scale)/(32*scale);
1067 	if (i < 0 || i >= MAIN_ITEMS)
1068 	{
1069 		if (cursor.buttonclicks[MOUSEBUTTON1]==1)
1070 		{
1071 			cursor.buttonused[MOUSEBUTTON1] = true;
1072 			cursor.buttonclicks[MOUSEBUTTON1] = 0;
1073 		}
1074 		return;
1075 	}
1076 
1077 	if (cursor.mouseaction)
1078 	{
1079 		refreshCursorLink ();
1080 		m_main_cursor = i;
1081 		cursor.mouseaction = false;
1082 	}
1083 
1084 	MainMenuMouseHover = 1 + i;
1085 
1086 	if (oldhover == MainMenuMouseHover && MainMenuMouseHover-1 == m_main_cursor &&
1087 		!cursor.buttonused[MOUSEBUTTON1] && cursor.buttonclicks[MOUSEBUTTON1]==1)
1088 	{
1089 		main_open_funcs[m_main_cursor]();
1090 		S_StartLocalSound( menu_move_sound );
1091 		cursor.buttonused[MOUSEBUTTON1] = true;
1092 		cursor.buttonclicks[MOUSEBUTTON1] = 0;
1093 	}
1094 }
1095 
M_Main_Key(int key)1096 const char *M_Main_Key (int key)
1097 {
1098 	switch (key)
1099 	{
1100 	case K_ESCAPE:
1101 		m_entersound = true;
1102 		M_PopMenu ();
1103 		break;
1104 
1105 	case K_KP_DOWNARROW:
1106 	case K_DOWNARROW:
1107 	case K_TAB:
1108 	case K_MWHEELDOWN:
1109 		if (++m_main_cursor >= MAIN_ITEMS)
1110 			m_main_cursor = 0;
1111 		break;
1112 
1113 	case K_KP_UPARROW:
1114 	case K_UPARROW:
1115 	case K_MWHEELUP:
1116 		if (--m_main_cursor < 0)
1117 			m_main_cursor = MAIN_ITEMS - 1;
1118 		break;
1119 
1120 	case K_KP_ENTER:
1121 	case K_ENTER:
1122 		m_entersound = true;
1123 		main_open_funcs[m_main_cursor]();
1124 		break;
1125 	}
1126 
1127 	return NULL;
1128 }
1129 
1130 
M_Menu_Main_f(void)1131 void M_Menu_Main_f (void)
1132 {
1133 	S_StartMenuMusic();
1134 	cls.key_dest = key_menu;
1135 	mstate_reset ();
1136 }
1137 
1138 /*
1139 =======================================================================
1140 
1141 OPTIONS MENUS - INPUT MENU
1142 
1143 =======================================================================
1144 */
1145 char *bindnames[][2] =
1146 {
1147 {"+attack", 		"attack"},
1148 {"+attack2",		"alt attack"},
1149 {"weapnext", 		"next weapon"},
1150 {"weapprev", 		"previous weapon"},
1151 {"+forward", 		"walk forward"},
1152 {"+back", 			"backpedal"},
1153 {"+speed", 			"run"},
1154 {"+moveleft", 		"step left"},
1155 {"+moveright", 		"step right"},
1156 {"+moveup",			"up / jump"},
1157 {"+movedown",		"down / crouch"},
1158 
1159 {"inven",			"inventory"},
1160 {"invuse",			"use item"},
1161 {"invdrop",			"drop item"},
1162 {"invprev",			"prev item"},
1163 {"invnext",			"next item"},
1164 
1165 {"use Alien Disruptor",	"alien disruptor" },
1166 {"use Pulse Rifle",		"chaingun" },
1167 {"use Flame Thrower",	"flame thrower" },
1168 {"use Rocket Launcher",	"rocket launcher" },
1169 {"use Alien Smartgun",	"alien smartgun" },
1170 {"use Disruptor",		"alien beamgun" },
1171 {"use Alien Vaporizer", "alien vaporizer" },
1172 {"use Violator", "the violator" },
1173 {"score",				"show scores" },
1174 {"use grapple",			"grapple hook"},
1175 {"use sproing",			"sproing"},
1176 {"use haste",			"haste"},
1177 {"use invisibility",	"invisibility"},
1178 
1179 {"vtaunt 1",			"voice taunt #1"},
1180 {"vtaunt 2",			"voice taunt #2"},
1181 {"vtaunt 3",			"voice taunt #3"},
1182 {"vtaunt 4",			"voice taunt #4"},
1183 {"vtaunt 5",			"voice taunt #5"},
1184 {"vtaunt 0",			"voice taunt auto"}
1185 };
1186 
1187 #define num_bindable_actions static_array_size(bindnames)
1188 
1189 int				keys_cursor;
1190 static int		bind_grab;
1191 
1192 static menuframework_s	s_keys_screen;
1193 static menuframework_s	s_keys_menu;
1194 static menuaction_s		s_keys_actions[num_bindable_actions];
1195 
M_UnbindCommand(const char * command)1196 static void M_UnbindCommand (const char *command)
1197 {
1198 	int		j;
1199 	int		l;
1200 	char	*b;
1201 
1202 	l = strlen(command);
1203 
1204 	for (j=0 ; j<256 ; j++)
1205 	{
1206 		b = keybindings[j];
1207 		if (!b)
1208 
1209 			continue;
1210 		if (!strncmp (b, command, l) )
1211 			Key_SetBinding (j, "");
1212 	}
1213 }
1214 
M_FindKeysForCommand(const char * command,int * twokeys)1215 static void M_FindKeysForCommand (const char *command, int *twokeys)
1216 {
1217 	int		count;
1218 	int		j;
1219 	int		l;
1220 	char	*b;
1221 
1222 	twokeys[0] = twokeys[1] = -1;
1223 	l = strlen(command);
1224 	count = 0;
1225 
1226 	for (j=0 ; j<256 ; j++)
1227 	{
1228 		b = keybindings[j];
1229 		if (!b)
1230 			continue;
1231 		if (!strncmp (b, command, l) )
1232 		{
1233 			twokeys[count] = j;
1234 			count++;
1235 			if (count == 2)
1236 				break;
1237 		}
1238 	}
1239 }
1240 
M_KeyBindingDisplayStr(const char * command,size_t bufsize,char * buf)1241 static void M_KeyBindingDisplayStr (const char *command, size_t bufsize, char *buf)
1242 {
1243 	int keys[2];
1244 
1245 	M_FindKeysForCommand (command, keys);
1246 
1247 	if (keys[0] == -1)
1248 	{
1249 		Com_sprintf (buf, bufsize, "???");
1250 	}
1251 	else
1252 	{
1253 		// Key_KeynumToString reuses the same buffer for output sometimes
1254 		int len;
1255 		Com_sprintf (buf, bufsize, "%s", Key_KeynumToString (keys[0]));
1256 		len = strlen(buf);
1257 		if (keys[1] != -1)
1258 			Com_sprintf (buf+len, bufsize-len, "  or  %s", Key_KeynumToString (keys[1]));
1259 	}
1260 }
1261 
KeySizeFunc(void * _self,FNT_font_t font)1262 static menuvec2_t KeySizeFunc (void *_self, FNT_font_t font)
1263 {
1264 	menuvec2_t ret;
1265 	char buf[1024];
1266 	menuaction_s *self = ( menuaction_s * ) _self;
1267 
1268 	M_KeyBindingDisplayStr (self->generic.localstrings[0], sizeof(buf), buf);
1269 
1270 	ret.y = font->height;
1271 	ret.x = RCOLUMN_OFFSET + Menu_PredictSize (buf);
1272 
1273 	return ret;
1274 }
1275 
DrawKeyBindingFunc(void * _self,FNT_font_t font)1276 static void DrawKeyBindingFunc( void *_self, FNT_font_t font )
1277 {
1278 	extern const float light_color[4];
1279 	char buf[1024];
1280 	menuaction_s *self = ( menuaction_s * ) _self;
1281 
1282 	M_KeyBindingDisplayStr (self->generic.localstrings[0], sizeof(buf), buf);
1283 
1284 	Menu_DrawString (
1285 		Item_GetX (*self) + RCOLUMN_OFFSET,
1286 		Item_GetY (*self) + MenuText_UpperMargin (self, font->size),
1287 		buf, FNT_CMODE_QUAKE_SRS, FNT_ALIGN_LEFT, light_color
1288 	);
1289 }
1290 
KeyBindingFunc(void * _self)1291 static void KeyBindingFunc( void *_self )
1292 {
1293 	menuaction_s *self = ( menuaction_s * ) _self;
1294 	int keys[2];
1295 
1296 	M_FindKeysForCommand( self->generic.localstrings[0], keys );
1297 
1298 	if (keys[1] != -1)
1299 		M_UnbindCommand( self->generic.localstrings[0]);
1300 
1301 	bind_grab = true;
1302 
1303 	Menu_SetStatusBar( &s_keys_menu, "press a key or button for this action" );
1304 }
1305 
Keys_MenuInit(void)1306 static void Keys_MenuInit( void )
1307 {
1308 	int i = 0;
1309 
1310 	setup_window (s_keys_screen, s_keys_menu, "CUSTOMIZE CONTROLS");
1311 
1312 	for (i = 0; i < num_bindable_actions; i++)
1313 	{
1314 		s_keys_actions[i].generic.type				= MTYPE_ACTION;
1315 		s_keys_actions[i].generic.callback			= KeyBindingFunc;
1316 		s_keys_actions[i].generic.itemdraw			= DrawKeyBindingFunc;
1317 		s_keys_actions[i].generic.localstrings[0]	= bindnames[i][0];
1318 		s_keys_actions[i].generic.name				= bindnames[i][1];
1319 		s_keys_actions[i].generic.itemsizecallback	= KeySizeFunc;
1320 		Menu_AddItem( &s_keys_menu, &s_keys_actions[i]);
1321 	}
1322 
1323 	Menu_SetStatusBar( &s_keys_menu, "enter to change, backspace to clear" );
1324 
1325 	s_keys_menu.maxlines = 30;
1326 }
1327 
Keys_MenuKey(menuframework_s * screen,int key)1328 static const char *Keys_MenuKey (menuframework_s *screen, int key)
1329 {
1330 	menuaction_s *item = ( menuaction_s * ) cursor.menuitem;
1331 
1332 	if ( bind_grab && !(cursor.buttonused[MOUSEBUTTON1]&&key==K_MOUSE1))
1333 	{
1334 		if ( key != K_ESCAPE && key != '`' )
1335 		{
1336 			char cmd[1024];
1337 
1338 			Com_sprintf (cmd, sizeof(cmd), "bind \"%s\" \"%s\"\n", Key_KeynumToString(key), item->generic.localstrings[0]);
1339 			Cbuf_InsertText (cmd);
1340 		}
1341 
1342 		//dont let selecting with mouse buttons screw everything up
1343 		refreshAllCursorButtons();
1344 		if (key==K_MOUSE1)
1345 			cursor.buttonclicks[MOUSEBUTTON1] = -1;
1346 
1347 		Menu_SetStatusBar (&s_keys_menu, "enter to change, backspace to clear");
1348 		bind_grab = false;
1349 
1350 		Menu_AutoArrange (screen);
1351 
1352 		return menu_out_sound;
1353 	}
1354 
1355 	switch ( key )
1356 	{
1357 	case K_BACKSPACE:		// delete bindings
1358 	case K_DEL:				// delete bindings
1359 	case K_KP_DEL:
1360 		M_UnbindCommand( item->generic.localstrings[0] );
1361 		return menu_out_sound;
1362 	default:
1363 		return Default_MenuKey (screen, key);
1364 	}
1365 }
1366 
M_Menu_Keys_f(void)1367 void M_Menu_Keys_f (void)
1368 {
1369 	Keys_MenuInit();
1370 	M_PushMenu (Screen_Draw, Keys_MenuKey, &s_keys_screen);
1371 }
1372 
1373 /*
1374 =======================================================================
1375 
1376 OPTIONS MENUS - GENERIC CODE FOR OPTIONS WIDGETS
1377 
1378 =======================================================================
1379 */
1380 
1381 typedef struct
1382 {
1383 	int maxchars;
1384 	int	min_value, max_value; // for numerical fields, otherwise ignore
1385 } fieldsize_t;
1386 
1387 typedef struct
1388 {
1389 	int slider_min, slider_max;
1390 	float cvar_min, cvar_max;
1391 } sliderlimit_t;
1392 
1393 typedef struct {
1394 	enum {
1395 		option_slider,
1396 		option_textcvarslider,
1397 		option_spincontrol,
1398 		option_textcvarspincontrol,
1399 		option_textcvarpicspincontrol,
1400 		option_hudspincontrol,
1401 		option_minimapspincontrol,
1402 		option_numberfield
1403 	} type;
1404 	const char *cvarname;
1405 	const char *displayname;
1406 	const char *tooltip;
1407 
1408 	// extra data - set the appropriate one
1409 	const char 			**names;
1410 	const sliderlimit_t *limits;
1411 	const fieldsize_t	*fieldsize;
1412 	#define setnames(x)		(const char **)(x), NULL, NULL
1413 	#define setlimits(x)	NULL, &(x), NULL
1414 	#define setfieldsize(x)	NULL, NULL, &(x)
1415 
1416 	// flags - optional, defaults to no flags
1417 	int flags;
1418 
1419 } option_name_t;
1420 
SpinOptionFunc(void * _self)1421 static void SpinOptionFunc (void *_self)
1422 {
1423 	menulist_s *self;
1424 	const char *cvarname;
1425 
1426 	self = (menulist_s *)_self;
1427 	cvarname = self->generic.localstrings[0];
1428 
1429 	Cvar_SetValue( cvarname, self->curvalue );
1430 }
1431 
FontSelectorSizeFunc(void * _self,FNT_font_t unused)1432 static menuvec2_t FontSelectorSizeFunc (void *_self, FNT_font_t unused)
1433 {
1434 	menuvec2_t ret;
1435 	menulist_s *self;
1436 	FNT_font_t font;
1437 
1438 	self = (menulist_s *)_self;
1439 	font = FNT_AutoGet (*(FNT_auto_t *)self->generic.localptrs[0]);
1440 
1441 	ret.y = font->height;
1442 	ret.x = RCOLUMN_OFFSET + FNT_PredictSize (font, self->itemnames[self->curvalue], false);
1443 
1444 	return ret;
1445 }
1446 
FontSelectorDrawFunc(void * _self,FNT_font_t unused)1447 static void FontSelectorDrawFunc (void *_self, FNT_font_t unused)
1448 {
1449 	extern const float light_color[4];
1450 	menulist_s *self;
1451 	FNT_font_t font;
1452 
1453 	self = (menulist_s *)_self;
1454 	font = FNT_AutoGet (*(FNT_auto_t *)self->generic.localptrs[0]);
1455 
1456 	menu_box.x = Item_GetX (*self)+RCOLUMN_OFFSET;
1457 	menu_box.y = Item_GetY (*self) + MenuText_UpperMargin (self, font->size);
1458 	menu_box.height = menu_box.width = 0;
1459 
1460 	FNT_BoundedPrint (font, self->itemnames[self->curvalue], FNT_CMODE_QUAKE_SRS, FNT_ALIGN_LEFT, &menu_box, light_color);
1461 }
1462 
TextVarSpinOptionFunc(void * _self)1463 static void TextVarSpinOptionFunc (void *_self)
1464 {
1465 	menulist_s *self;
1466 	const char *cvarname;
1467 	char *cvarval;
1468 
1469 	self = (menulist_s *)_self;
1470 	cvarname = self->generic.localstrings[0];
1471 
1472 	cvarval = strchr(self->itemnames[self->curvalue], '\0')+1;
1473 	Cvar_Set( cvarname, cvarval);
1474 }
1475 
UpdateDopplerEffectFunc(void * self)1476 static void UpdateDopplerEffectFunc( void *self )
1477 {
1478 	TextVarSpinOptionFunc (self);
1479 	R_EndFrame(); // buffer swap needed to show text box
1480 	S_UpdateDopplerFactor();
1481 }
1482 
SliderOptionFunc(void * _self)1483 static void SliderOptionFunc (void *_self)
1484 {
1485 	menuslider_s *self;
1486 	const char *cvarname;
1487 	float cvarval, sliderval, valscale;
1488 	const sliderlimit_t *limit;
1489 
1490 	self = (menulist_s *)_self;
1491 	cvarname = self->generic.localstrings[0];
1492 
1493 	limit = (const sliderlimit_t *) self->generic.localptrs[0];
1494 
1495 	sliderval = self->curvalue;
1496 
1497 	valscale = 	(limit->cvar_max-limit->cvar_min)/
1498 				(float)(limit->slider_max-limit->slider_min);
1499 	cvarval = limit->cvar_min + valscale*(sliderval-limit->slider_min);
1500 
1501 	Cvar_SetValue (cvarname, cvarval);
1502 }
1503 
NumberFieldOptionFunc(void * _self)1504 static void NumberFieldOptionFunc (void *_self)
1505 {
1506 	menufield_s *self;
1507 	int num, clamped_num;
1508 	const fieldsize_t *fieldsize;
1509 	const char *cvarname;
1510 
1511 	self = (menufield_s *)_self;
1512 	cvarname = self->generic.localstrings[0];
1513 
1514 	fieldsize = (const fieldsize_t *)self->generic.localptrs[0];
1515 
1516 	num = atoi (self->buffer);
1517 	clamped_num = clamp (num, fieldsize->min_value, fieldsize->max_value);
1518 
1519 	if (num != clamped_num)
1520 	{
1521 		Com_sprintf (self->buffer, sizeof(self->buffer), "%d", clamped_num);
1522 		self->cursor = strlen (self->buffer);
1523 	}
1524 
1525 	Cvar_SetValue (cvarname, clamped_num);
1526 }
1527 
1528 // HACKS for specific menus
1529 extern cvar_t *crosshair;
1530 #define MAX_CROSSHAIRS 256
1531 char *crosshair_names[MAX_CROSSHAIRS];
1532 int	numcrosshairs = 0;
1533 
1534 static void HudFunc( void *item );
1535 static void MinimapFunc( void *item );
1536 static void UpdateBGMusicFunc( void *_self );
1537 
1538 static float ClampCvar( float min, float max, float value );
1539 
1540 extern cvar_t *r_minimap;
1541 extern cvar_t *r_minimap_style;
1542 
1543 #define MAX_FONTS 32
1544 char *font_names[MAX_FONTS];
1545 int	numfonts = 0;
1546 
1547 // name and value lists: for spin-controls where the cvar value isn't simply
1548 // the integer index of whatever text is displaying in the control. We use
1549 // NULL terminators to separate display names and variable values, so that way
1550 // we can use the existing menu code.
1551 
1552 static const char *doppler_effect_items[] =
1553 {
1554 	"off\0000",
1555 	"normal\0001",
1556 	"high\0003",
1557 	"very high\0005",
1558 	0
1559 };
1560 
1561 int	numhuds = 0;
1562 extern cvar_t *cl_hudimage1;
1563 extern cvar_t *cl_hudimage2;
1564 #define MAX_HUDS 256
1565 char *hud_names[MAX_HUDS];
1566 
1567 // initialize a menu item as an "option."
Option_Setup(menumultival_s * item,option_name_t * optionname)1568 void Option_Setup (menumultival_s *item, option_name_t *optionname)
1569 {
1570 	int val, maxval;
1571 	char *vartextval;
1572 	int i;
1573 	float cvarval, sliderval, valscale;
1574 	const sliderlimit_t *limit;
1575 	const fieldsize_t *fieldsize;
1576 
1577 	// Do not re-allocate font/crosshair/HUD names each time the menu is
1578 	// displayed - BlackIce
1579 	if ( numfonts == 0 )
1580 		SetFontNames (font_names);
1581 
1582 	if ( numhuds == 0 )
1583 		SetHudNames (hud_names);
1584 
1585 	if ( numcrosshairs == 0 )
1586 		SetCrosshairNames (crosshair_names);
1587 
1588 	// initialize item
1589 
1590 	item->generic.name = optionname->displayname;
1591 	item->generic.tooltip = optionname->tooltip;
1592 	item->generic.localstrings[0] = optionname->cvarname;
1593 	item->generic.flags = optionname->flags;
1594 	item->generic.apply_pending = false;
1595 
1596 	switch (optionname->type)
1597 	{
1598 		case option_spincontrol:
1599 			item->generic.type = MTYPE_SPINCONTROL;
1600 			item->itemnames = optionname->names;
1601 			if (!strcmp (optionname->cvarname, "background_music"))
1602 				// FIXME HACK
1603 				item->generic.callback = UpdateBGMusicFunc;
1604 			else
1605 				item->generic.callback = SpinOptionFunc;
1606 			if (item->itemnames == onoff_names)
1607 				setup_tickbox (*item);
1608 			if (item->itemnames == offon_names)
1609 			{
1610 				setup_tickbox (*item); // because setup_tickbox overwrites itemnames
1611 				item->itemnames = offon_names;
1612 			}
1613 			break;
1614 
1615 		case option_textcvarslider:
1616 			item->generic.type = MTYPE_SLIDER;
1617 			// TODO: use the name part in a tooltip or something
1618 			item->itemnames = optionname->names;
1619 			item->minvalue = 0;
1620 			for (item->maxvalue = 0; item->itemnames[item->maxvalue+1]; item->maxvalue++)
1621 				continue;
1622 			if (item->itemnames == doppler_effect_items)
1623 				// FIXME HACK
1624 				item->generic.callback = UpdateDopplerEffectFunc;
1625 			else
1626 				item->generic.callback = TextVarSpinOptionFunc;
1627 			break;
1628 
1629 		case option_textcvarspincontrol:
1630 		case option_textcvarpicspincontrol:
1631 			item->generic.type = MTYPE_SPINCONTROL;
1632 			item->itemnames = optionname->names;
1633 			item->generic.callback = TextVarSpinOptionFunc;
1634 			// FIXME HACK
1635 			if (item->itemnames == font_names)
1636 			{
1637 				item->generic.itemsizecallback = FontSelectorSizeFunc;
1638 				item->generic.itemdraw = FontSelectorDrawFunc;
1639 				if (!strcmp (optionname->cvarname, "fnt_game"))
1640 					item->generic.localptrs[0] = &CL_gameFont;
1641 				else if (!strcmp (optionname->cvarname, "fnt_console"))
1642 					item->generic.localptrs[0] = &CL_consoleFont;
1643 				else if (!strcmp (optionname->cvarname, "fnt_menu"))
1644 					item->generic.localptrs[0] = &CL_menuFont;
1645 			}
1646 			else if (optionname->type == option_textcvarpicspincontrol)
1647 			{
1648 				item->generic.itemsizecallback = PicSpinSizeFunc;
1649 				item->generic.itemdraw = PicSpinDrawFunc;
1650 				VectorSet (item->generic.localints, 5, 5, RCOLUMN_OFFSET);
1651 			}
1652 			break;
1653 
1654 		case option_hudspincontrol:
1655 			item->generic.type = MTYPE_SPINCONTROL;
1656 			item->itemnames = optionname->names;
1657 			item->generic.callback = HudFunc;
1658 			break;
1659 
1660 		case option_minimapspincontrol:
1661 			item->generic.type = MTYPE_SPINCONTROL;
1662 			item->itemnames = optionname->names;
1663 			item->generic.callback = MinimapFunc;
1664 			break;
1665 
1666 		case option_slider:
1667 			limit = optionname->limits;
1668 			item->generic.type = MTYPE_SLIDER;
1669 			item->minvalue = limit->slider_min;
1670 			item->maxvalue = limit->slider_max;
1671 			item->generic.callback = SliderOptionFunc;
1672 			item->generic.localptrs[0] = limit;
1673 			break;
1674 
1675 		case option_numberfield:
1676 			fieldsize = optionname->fieldsize;
1677 			item->generic.type = MTYPE_FIELD;
1678 			item->generic.flags |= QMF_NUMBERSONLY;
1679 			item->generic.visible_length = fieldsize->maxchars;
1680 			item->cursor = 0;
1681 			memset (item->buffer, 0, sizeof(item->buffer));
1682 			item->generic.callback = NumberFieldOptionFunc;
1683 			item->generic.localptrs[0] = fieldsize;
1684 			break;
1685 	}
1686 
1687 	// initialize value
1688 
1689 	switch (optionname->type)
1690 	{
1691 		case option_spincontrol:
1692 			for (maxval = 0; item->itemnames[maxval]; maxval++)
1693 				continue;
1694 			maxval--;
1695 
1696 			val = ClampCvar (0, maxval, Cvar_VariableValue (optionname->cvarname));
1697 
1698 			item->curvalue = val;
1699 			Cvar_SetValue (optionname->cvarname, val);
1700 			break;
1701 
1702 		case option_hudspincontrol:
1703 		case option_textcvarspincontrol:
1704 		case option_textcvarpicspincontrol:
1705 		case option_textcvarslider:
1706 			item->curvalue = 0;
1707 			vartextval = Cvar_VariableString (optionname->cvarname);
1708 
1709 			for (i=0; item->itemnames[i]; i++)
1710 			{
1711 				char *corresponding_cvar_val = strchr(item->itemnames[i], '\0')+1;
1712 				if (!Q_strcasecmp(vartextval, corresponding_cvar_val))
1713 				{
1714 					item->curvalue = i;
1715 					break;
1716 				}
1717 			}
1718 			break;
1719 
1720 		case option_minimapspincontrol:
1721 			Cvar_SetValue("r_minimap_style", ClampCvar(0, 1, r_minimap_style->value));
1722 			Cvar_SetValue("r_minimap", ClampCvar(0, 1, r_minimap->value));
1723 			if(r_minimap_style->value == 0)
1724 				item->curvalue = 2;
1725 			else
1726 				item->curvalue = r_minimap->value;
1727 			break;
1728 
1729 		case option_slider:
1730 			limit = optionname->limits;
1731 
1732 			cvarval = ClampCvar (	limit->cvar_min, limit->cvar_max,
1733 									Cvar_VariableValue (optionname->cvarname));
1734 			Cvar_SetValue (optionname->cvarname, cvarval);
1735 
1736 			valscale = 	(float)(limit->slider_max-limit->slider_min)/
1737 						(limit->cvar_max-limit->cvar_min);
1738 			sliderval = limit->slider_min + valscale*(cvarval-limit->cvar_min);
1739 			item->curvalue = sliderval;
1740 			break;
1741 
1742 		case option_numberfield:
1743 			fieldsize = optionname->fieldsize;
1744 			Com_sprintf (item->buffer, sizeof(item->buffer), "%d", (int)Cvar_VariableValue (optionname->cvarname));
1745 			item->cursor = strlen (item->buffer);
1746 			break;
1747 	}
1748 }
1749 
1750 // all "options" menus have roughly the same layout, so we can automate some
1751 // of the grunt work
1752 
1753 typedef struct
1754 {
1755 	menuframework_s	screen;
1756 	menuframework_s	window;
1757 	menuframework_s	panel;
1758 	menumultival_s	widgets[];
1759 } options_menu_t;
1760 
1761 static options_menu_t	*last_options_menu;
1762 static option_name_t	*last_options_menu_namelist;
1763 static int				last_options_menu_nitems;
1764 
1765 // Use this anywhere to reinitialize whatever options menu is currently
1766 // showing so it reflects current cvar values.
Options_Menu_Reinitialize(void)1767 void Options_Menu_Reinitialize (void)
1768 {
1769 	int i;
1770 	for (i = 0; i < last_options_menu_nitems; i++)
1771 		Option_Setup (&last_options_menu->widgets[i], &last_options_menu_namelist[i]);
1772 }
1773 
1774 #define options_menu(menu,title) \
1775 	static struct \
1776 	{ \
1777 		menuframework_s	screen; \
1778 		menuframework_s	window; \
1779 		menuframework_s	panel; \
1780 		menumultival_s	widgets[static_array_size(menu ## _option_names)]; \
1781 	} menu; \
1782 	\
1783 	setup_window (menu.screen, menu.window, title); \
1784 	setup_panel (menu.window, menu.panel); \
1785 	\
1786 	{ \
1787 	\
1788 		int i; \
1789 		for (i = 0; i < static_array_size(menu ## _option_names); i++) \
1790 		{ \
1791 			Option_Setup (&menu.widgets[i], &(menu ## _option_names)[i]); \
1792 			Menu_AddItem( &menu.panel, &menu.widgets[i]); \
1793 		} \
1794 	} \
1795 	last_options_menu_nitems = static_array_size(menu ## _option_names); \
1796 	last_options_menu_namelist = &(menu ## _option_names)[0]; \
1797 	last_options_menu = (options_menu_t*)&menu;
1798 
1799 
1800 
1801 /*
1802 =======================================================================
1803 
1804 OPTIONS MENUS - DISPLAY OPTIONS MENU
1805 
1806 =======================================================================
1807 */
1808 
MinimapFunc(void * item)1809 static void MinimapFunc( void *item )
1810 {
1811 	menulist_s *self = (menulist_s *)item;
1812 	Cvar_SetValue("r_minimap", self->curvalue != 0);
1813 	if(r_minimap->integer) {
1814 		Cvar_SetValue("r_minimap_style", self->curvalue % 2);
1815 	}
1816 }
1817 
ClampCvar(float min,float max,float value)1818 static float ClampCvar( float min, float max, float value )
1819 {
1820 	if ( value < min ) return min;
1821 	if ( value > max ) return max;
1822 	return value;
1823 }
1824 
fontInList(char * check,int num,char ** list)1825 qboolean fontInList (char *check, int num, char **list)
1826 {
1827 	int i;
1828 	for (i=0;i<num;i++)
1829 		if (!Q_strcasecmp(check, list[i]))
1830 			return true;
1831 	return false;
1832 }
1833 
1834 // One string after another, both in the same memory block, but each with its
1835 // own null terminator.
str_combine(char * in1,char * in2)1836 char *str_combine (char *in1, char *in2)
1837 {
1838 	size_t outsize;
1839 	char *out;
1840 
1841 	outsize = strlen(in1)+1+strlen(in2)+1;
1842 	out = malloc (outsize);
1843 	memset (out, 0, outsize);
1844 	strcpy (out, in1);
1845 	strcpy (strchr(out, '\0')+1, in2);
1846 
1847 	return out;
1848 }
1849 
insertFile(char ** list,char * insert1,char * insert2,int ndefaults,int len)1850 void insertFile (char ** list, char *insert1, char *insert2, int ndefaults, int len )
1851 {
1852 	int i, j;
1853 	char *tmp;
1854 
1855 	tmp = str_combine (insert1, insert2);
1856 
1857 	for (i=ndefaults;i<len; i++)
1858 	{
1859 		if (!list[i])
1860 			break;
1861 
1862 		if (strcmp( list[i], insert1 ))
1863 		{
1864 			for (j=len; j>i ;j--)
1865 				list[j] = list[j-1];
1866 
1867 			list[i] = tmp;
1868 
1869 			return;
1870 		}
1871 	}
1872 
1873 	list[len] = tmp;
1874 }
1875 
AddFontNames(char * path,int * nfontnames,char ** list)1876 static void AddFontNames( char * path , int * nfontnames , char ** list )
1877 {
1878 	char ** fontfiles;
1879 	int nfonts = 0;
1880 	int i;
1881 
1882 	fontfiles = FS_ListFilesInFS( path , &nfonts, 0,
1883 		SFF_SUBDIR | SFF_HIDDEN | SFF_SYSTEM );
1884 
1885 	for (i=0;i<nfonts && *nfontnames<MAX_FONTS;i++)
1886 	{
1887 		int num;
1888 		char * p;
1889 
1890 		p = strstr(fontfiles[i], "fonts/"); p++;
1891 		p = strstr(p, "/"); p++;
1892 
1893 		num = strlen(p)-4;
1894 		p[num] = 0;
1895 
1896 		if (!fontInList(p, i, list))
1897 		{
1898 			insertFile (list, p, p, 1, i);
1899 			(*nfontnames)++;
1900 		}
1901 	}
1902 
1903 	if (fontfiles)
1904 		FS_FreeFileList(fontfiles, nfonts);
1905 }
1906 
SetFontNames(char ** list)1907 void SetFontNames (char **list)
1908 {
1909 	int nfontnames;
1910 
1911 	memset( list, 0, sizeof( char * ) * MAX_FONTS );
1912 
1913 	nfontnames = 0;
1914 	AddFontNames( "fonts/*.ttf" , &nfontnames , list );
1915 
1916 	numfonts = nfontnames;
1917 }
1918 
SetCrosshairNames(char ** list)1919 void SetCrosshairNames (char **list)
1920 {
1921 	char *curCrosshairFile;
1922 	char *p;
1923 	int ncrosshairs = 0, ncrosshairnames;
1924 	char **crosshairfiles;
1925 	int i;
1926 
1927 	ncrosshairnames = 4;
1928 
1929 	list[0] = str_combine("none", "none"); //the old crosshairs
1930 	list[1] = str_combine("ch1", "ch1");
1931 	list[2] = str_combine("ch2", "ch2");
1932 	list[3] = str_combine("ch3", "ch3");
1933 
1934 	crosshairfiles = FS_ListFilesInFS( "pics/crosshairs/*.tga",
1935 		&ncrosshairs, 0, SFF_SUBDIR | SFF_HIDDEN | SFF_SYSTEM );
1936 
1937 	for (i=0;i<ncrosshairs && ncrosshairnames<MAX_CROSSHAIRS;i++)
1938 	{
1939 		int num;
1940 
1941 		p = strstr(crosshairfiles[i], "/crosshairs/"); p++;
1942 		curCrosshairFile = p;
1943 
1944 		p = strstr(p, "/"); p++;
1945 
1946 		num = strlen(p)-4;
1947 		p[num] = 0;
1948 
1949 		if (!fontInList(curCrosshairFile, ncrosshairnames, list))
1950 		{
1951 			// HACK
1952 			insertFile (list,curCrosshairFile,curCrosshairFile,4,ncrosshairnames);
1953 			ncrosshairnames++;
1954 		}
1955 	}
1956 
1957 	if (crosshairfiles)
1958 		FS_FreeFileList(crosshairfiles, ncrosshairs);
1959 
1960 	numcrosshairs = ncrosshairnames;
1961 }
1962 
HudFunc(void * item)1963 static void HudFunc( void *item )
1964 {
1965 	menulist_s *self;
1966 	char hud1[MAX_OSPATH];
1967 	char hud2[MAX_OSPATH];
1968 
1969 	self = (menulist_s *)item;
1970 
1971 	if(self->curvalue == 0) { //none
1972 		sprintf(hud1, "none");
1973 		sprintf(hud2, "none");
1974 	}
1975 
1976 	if(self->curvalue == 1) {
1977 		sprintf(hud1, "pics/i_health.tga");
1978 		sprintf(hud2, "pics/i_score.tga");
1979 	}
1980 
1981 	if(self->curvalue > 1) {
1982 		sprintf(hud1, "pics/huds/%s1", hud_names[self->curvalue]);
1983 		sprintf(hud2, "pics/huds/%s2", hud_names[self->curvalue]);
1984 	}
1985 
1986 	//set the cvars, both of them
1987 	Cvar_Set( "cl_hudimage1", hud1 );
1988 	Cvar_Set( "cl_hudimage2", hud2 );
1989 }
1990 
SetHudNames(char ** list)1991 void SetHudNames (char **list)
1992 {
1993 	char *curHud, *curHudFile;
1994 	char *p;
1995 	int nhuds = 0, nhudnames;
1996 	char **hudfiles;
1997 	int i;
1998 
1999 	memset( list, 0, sizeof( char * ) * MAX_HUDS );
2000 
2001 	nhudnames = 2;
2002 
2003 	list[0] = str_combine("none", "none");
2004 	list[1] = str_combine("default", "pics/i_health.tga"); //the default hud
2005 
2006 	hudfiles = FS_ListFilesInFS( "pics/huds/*1.tga", &nhuds, 0,
2007 		SFF_SUBDIR | SFF_HIDDEN | SFF_SYSTEM );
2008 
2009 	for (i=0;i<nhuds && nhudnames<MAX_HUDS;i++)
2010 	{
2011 		int num, file_ext;
2012 
2013 		p = strstr(hudfiles[i], "/huds/"); p++;
2014 		p = strstr(p, "/"); p++;
2015 
2016 		num = strlen(p)-5;
2017 		file_ext = num+1;
2018 		p[file_ext] = 0;
2019 
2020 		// we only need this because the second part of a text cvar value list
2021 		// will be compared against cl_hudimage1 so it needs the 1 suffix.
2022 		curHudFile = _strdup (hudfiles[i]);
2023 
2024 		p[num] = 0;
2025 
2026 		curHud = p;
2027 
2028 		if (!fontInList(curHud, nhudnames, list))
2029 		{
2030 			insertFile (list,curHud,curHudFile,2,nhudnames);
2031 			nhudnames++;
2032 		}
2033 
2034 		free (curHudFile);
2035 	}
2036 
2037 	if (hudfiles)
2038 		FS_FreeFileList(hudfiles, nhuds);
2039 
2040 	numhuds = nhudnames;
2041 }
2042 
UpdateBGMusicFunc(void * _self)2043 static void UpdateBGMusicFunc( void *_self )
2044 {
2045 	menulist_s *self = (menulist_s *)_self;
2046 	Cvar_SetValue( "background_music", self->curvalue );
2047 	if ( background_music->value > 0.99f && background_music_vol->value >= 0.1f )
2048 	{
2049 		S_StartMenuMusic();
2050 	}
2051 }
2052 
2053 // name lists: list of strings terminated by 0
2054 
2055 static const char *minimap_names[] =
2056 {
2057 	"off",
2058 	"static",
2059 	"rotating",
2060 	0
2061 };
2062 static const char *playerid_names[] =
2063 {
2064 	"off",
2065 	"centered",
2066 	"over player",
2067 	0
2068 };
2069 
2070 static const char *color_names[] =
2071 {
2072 	"^2green",
2073 	"^4blue",
2074 	"^1red",
2075 	"^3yellow",
2076 	"^6purple",
2077 	0
2078 };
2079 
2080 static const char *handedness_names[] =
2081 {
2082 	"right",
2083 	"left",
2084 	"center",
2085 	0
2086 };
2087 
2088 sliderlimit_t mousespeed_limits =
2089 {
2090 	0, 110, 0.0f, 11.0f
2091 };
2092 
2093 fieldsize_t fov_limits =
2094 {
2095 	3, 10, 130
2096 };
2097 
2098 option_name_t disp_option_names[] =
2099 {
2100 	{
2101 		option_spincontrol,
2102 		"cl_precachecustom",
2103 		"precache custom models",
2104 		"Enabling this can result in slow map loading times",
2105 		setnames (onoff_names)
2106 	},
2107 	{
2108 		option_spincontrol,
2109 		"cl_showplayernames",
2110 		"identify target",
2111 		NULL,
2112 		setnames (playerid_names)
2113 	},
2114 	{
2115 		option_spincontrol,
2116 		"r_ragdolls",
2117 		"ragdolls",
2118 		NULL,
2119 		setnames (onoff_names)
2120 	},
2121 	{
2122 		option_spincontrol,
2123 		"cl_noblood",
2124 		"no blood",
2125 		NULL,
2126 		setnames (onoff_names)
2127 	},
2128 	{
2129 		option_spincontrol,
2130 		"cl_noskins",
2131 		"force martian models",
2132 		NULL,
2133 		setnames (onoff_names)
2134 	},
2135 	{
2136 		option_textcvarspincontrol,
2137 		"fnt_console",
2138 		"console font",
2139 		"select the font used to display the console",
2140 		setnames (font_names)
2141 	},
2142 	{
2143 		option_textcvarspincontrol,
2144 		"fnt_game",
2145 		"game font",
2146 		"select the font used in the game",
2147 		setnames (font_names)
2148 	},
2149 	{
2150 		option_textcvarspincontrol,
2151 		"fnt_menu",
2152 		"menu font",
2153 		"select the font used in the menu",
2154 		setnames (font_names)
2155 	},
2156 	{
2157 		option_textcvarpicspincontrol,
2158 		"crosshair",
2159 		"crosshair",
2160 		"select your crosshair",
2161 		setnames (crosshair_names)
2162 	},
2163 	{
2164 		option_hudspincontrol,
2165 		"cl_hudimage1", //multiple cvars controlled-- see HudFunc
2166 		"HUD",
2167 		"select your HUD style",
2168 		setnames (hud_names)
2169 	},
2170 	{
2171 		option_spincontrol,
2172 		"cl_disbeamclr",
2173 		"disruptor color",
2174 		"select disruptor beam color",
2175 		setnames (color_names)
2176 	},
2177 	{
2178 		option_minimapspincontrol,
2179 		NULL, //multiple cvars controlled-- see MinimapFunc
2180 		"minimap",
2181 		"select your minimap style",
2182 		setnames (minimap_names)
2183 	},
2184 	{
2185 		option_spincontrol,
2186 		"in_joystick",
2187 		"use joystick",
2188 		NULL,
2189 		setnames (onoff_names)
2190 	},
2191 	{
2192 		option_spincontrol,
2193 		"cl_drawfps",
2194 		"display framerate",
2195 		NULL,
2196 		setnames (onoff_names)
2197 	},
2198 	{
2199 		option_spincontrol,
2200 		"cl_drawtimer",
2201 		"display timer",
2202 		NULL,
2203 		setnames (onoff_names)
2204 	},
2205 	{
2206 		option_spincontrol,
2207 		"cl_simpleitems",
2208 		"simple items",
2209 		"Draw floating icons instead of 3D models for ingame items",
2210 		setnames (onoff_names)
2211 	},
2212 	{
2213 		option_spincontrol,
2214 		"hand",
2215 		"weapon handedness",
2216 		"NOTE: This does effect aim!",
2217 		setnames (handedness_names)
2218 	},
2219 	{
2220 		option_numberfield,
2221 		"fov",
2222 		"FOV",
2223 		"Horizontal field of view in degrees",
2224 		setfieldsize (fov_limits)
2225 	}
2226 };
2227 
2228 #define num_options static_array_size(disp_option_names)
2229 
2230 menumultival_s options[num_options];
2231 
OptionsResetDefaultsFunc(void * unused)2232 static void OptionsResetDefaultsFunc( void *unused )
2233 {
2234 	Cbuf_AddText ("exec default.cfg\n");
2235 	Cbuf_Execute();
2236 
2237 	Options_Menu_Reinitialize ();
2238 
2239 	CL_Snd_Restart_f();
2240 	S_StartMenuMusic();
2241 
2242 }
2243 
OptionsResetSavedFunc(void * unused)2244 static void OptionsResetSavedFunc( void *unused )
2245 {
2246 	Cbuf_AddText ("exec config.cfg\n");
2247 	Cbuf_Execute();
2248 
2249 	Options_Menu_Reinitialize ();
2250 
2251 	CL_Snd_Restart_f();
2252 	S_StartMenuMusic();
2253 
2254 }
2255 
M_Menu_Display_f(void)2256 static void M_Menu_Display_f (void)
2257 {
2258 	options_menu (disp, "DISPLAY");
2259 
2260 	M_PushMenu_Defaults (disp.screen);
2261 }
2262 
2263 /*
2264 =======================================================================
2265 
2266 OPTIONS MENUS - VIDEO OPTIONS MENU
2267 
2268 =======================================================================
2269 */
2270 
2271 sliderlimit_t brightnesscontrast_limits =
2272 {
2273 	1, 20, 0.1f, 2.0f
2274 };
2275 
2276 // FIXME: is this really supposed to be separate?
2277 sliderlimit_t bloom_limits =
2278 {
2279 	0, 20, 0.0f, 2.0f
2280 };
2281 
2282 sliderlimit_t modulate_limits =
2283 {
2284 	1, 5, 1.0f, 5.0f
2285 };
2286 
2287 fieldsize_t resolution_width_limits =
2288 {
2289 	4, 640, 2048
2290 };
2291 
2292 fieldsize_t resolution_height_limits =
2293 {
2294 	4, 480, 1536
2295 };
2296 
2297 static const char *resolution_items[] =
2298 {
2299 	"[640 480  ]\0000",
2300 	"[800 600  ]\0001",
2301 	"[960 720  ]\0002",
2302 	"[1024 768 ]\0003",
2303 	"[1152 864 ]\0004",
2304 	"[1280 960 ]\0005",
2305 	"[1280 1024]\0006",
2306 	"[1360 768 ]\0007",
2307 	"[1366 768 ]\0008",
2308 	"[1600 1200]\0009",
2309 	"[1680 1050]\00010",
2310 	"[1920 1080]\00011",
2311 	"[2048 1536]\00012",
2312 	"[custom   ]\000-1",
2313 	0
2314 };
2315 
2316 static const char *overbright_items[] =
2317 {
2318 	"low\0001",
2319 	"medium\0002",
2320 	"high\0003",
2321 	0
2322 };
2323 
2324 static const char *texquality_items[] =
2325 {
2326 	"very low\0003",
2327 	"low\0002",
2328 	"medium\0001",
2329 	"high\0000",
2330 	0
2331 };
2332 
2333 option_name_t video_option_names[] =
2334 {
2335 	{
2336 		option_textcvarspincontrol,
2337 		"gl_mode",
2338 		"video mode",
2339 		NULL,
2340 		setnames (resolution_items),
2341 		QMF_ACTION_WAIT
2342 	},
2343 	{
2344 		option_numberfield,
2345 		"vid_width",
2346 		"custom width",
2347 		"set custom horizontal screen resolution",
2348 		setfieldsize (resolution_width_limits),
2349 		QMF_ACTION_WAIT
2350 	},
2351 	{
2352 		option_numberfield,
2353 		"vid_height",
2354 		"custom height",
2355 		"set custom vertical screen resolution",
2356 		setfieldsize (resolution_height_limits),
2357 		QMF_ACTION_WAIT
2358 	},
2359 	{
2360 		option_slider,
2361 		"vid_gamma",
2362 		"texture brightness",
2363 		NULL,
2364 		setlimits (brightnesscontrast_limits)
2365 	},
2366 	{
2367 		option_slider,
2368 		"vid_contrast",
2369 		"texture contrast",
2370 		NULL,
2371 		setlimits (brightnesscontrast_limits)
2372 	},
2373 	{
2374 		option_slider,
2375 		"gl_modulate",
2376 		"lightmap brightness",
2377 		NULL,
2378 		setlimits (modulate_limits)
2379 	},
2380 	{
2381 		option_spincontrol,
2382 		"vid_fullscreen",
2383 		"fullscreen",
2384 		NULL,
2385 		setnames (onoff_names),
2386 		QMF_ACTION_WAIT
2387 	},
2388 	{
2389 		option_spincontrol,
2390 		"r_bloom",
2391 		"light bloom",
2392 		NULL,
2393 		setnames (onoff_names)
2394 	},
2395 	{
2396 		option_slider,
2397 		"r_bloom_intensity",
2398 		"bloom intensity",
2399 		NULL,
2400 		setlimits (bloom_limits)
2401 	},
2402 	{
2403 		option_textcvarslider,
2404 		"r_overbrightbits",
2405 		"overbright bits",
2406 		NULL,
2407 		setnames (overbright_items)
2408 	},
2409 	{
2410 		option_textcvarslider,
2411 		"gl_picmip",
2412 		"texture quality",
2413 		NULL,
2414 		setnames (texquality_items)
2415 	},
2416 	{
2417 		option_spincontrol,
2418 		"gl_glsl_shaders",
2419 		"GLSL shaders",
2420 		NULL,
2421 		setnames (onoff_names)
2422 	},
2423 	{
2424 		option_spincontrol,
2425 		"cl_paindist",
2426 		"pain distortion fx",
2427 		"GLSL must be enabled for this to take effect",
2428 		setnames (onoff_names)
2429 	},
2430 	{
2431 		option_spincontrol,
2432 		"cl_explosiondist",
2433 		"explosion distortion fx",
2434 		"GLSL must be enabled for this to take effect",
2435 		setnames (onoff_names)
2436 	},
2437 	{
2438 		option_spincontrol,
2439 		"cl_raindist",
2440 		"rain droplet fx",
2441 		"GLSL must be enabled for this to take effect",
2442 		setnames (onoff_names)
2443 	},
2444 	{
2445 		option_spincontrol,
2446 		"gl_finish",
2447 		"triple buffering",
2448 		"improved framerates, but displayed frame is more out of date",
2449 		setnames (offon_names),
2450 		QMF_ACTION_WAIT
2451 	},
2452 	{
2453 		option_spincontrol,
2454 		"gl_swapinterval",
2455 		"vertical sync",
2456 		"should be on unless framerates dip below your monitor's refresh rate",
2457 		setnames (onoff_names),
2458 		QMF_ACTION_WAIT
2459 	}
2460 };
2461 
2462 const char *graphical_preset_names[][3] =
2463 {
2464 	// display name, cfg name, tooltip
2465 	{
2466 		"High Compatibility",	"compatibility",
2467 		"use when all other modes fail or run slowly"
2468 	},
2469 	{
2470 		"High Performance",		"maxperformance",
2471 		"fast rendering, many effects disabled"
2472 	},
2473 	{
2474 		"Performance",			"maxperformance",
2475 		"GLSL per-pixel lighting and postprocess"
2476 	},
2477 	{
2478 		"Quality",				"quality",
2479 		"GLSL per-pixel effects on all surfaces"
2480 	},
2481 	{
2482 		"High Quality",			"maxquality",
2483 		"GLSL, shadows, light shafts from sun"
2484 	}
2485 };
2486 
2487 #define num_graphical_presets static_array_size(graphical_preset_names)
2488 
2489 static menuaction_s		s_graphical_presets[num_graphical_presets];
2490 
2491 static menuframework_s *Video_MenuInit (void);
2492 
PresetCallback(void * _self)2493 static void PresetCallback (void *_self)
2494 {
2495 	char cmd[MAX_STRING_CHARS];
2496 	menuaction_s *self = (menuaction_s *)_self;
2497 
2498 	Com_sprintf (cmd, sizeof(cmd), "exec graphical_presets/%s.cfg", self->generic.localstrings[0]);
2499 	Cmd_ExecuteString (cmd);
2500 	Cbuf_Execute ();
2501 	Video_MenuInit (); //TODO: alert user of the need to apply here
2502 }
2503 
VidApplyFunc(void * self)2504 void VidApplyFunc (void *self)
2505 {
2506 	#if defined UNIX_VARIANT
2507 	extern qboolean vid_restart;
2508 	#endif
2509 	extern cvar_t *vid_ref;
2510 
2511 	Menu_ApplyMenu (Menu_GetItemTree ((menuitem_s *)self));
2512 
2513 	RS_FreeUnmarked();
2514 	Cvar_SetValue("scriptsloaded", 0); //scripts get flushed
2515 
2516 	vid_ref->modified = true;
2517 #if defined UNIX_VARIANT
2518 	vid_restart = true;
2519 #endif
2520 
2521 	M_ForceMenuOff();
2522 }
2523 
Video_MenuInit(void)2524 static menuframework_s *Video_MenuInit (void)
2525 {
2526 	int i;
2527 
2528 	options_menu (video, "VIDEO OPTIONS");
2529 
2530 	add_text (video.window, NULL, 0); // spacer
2531 
2532 	for (i = 0; i < num_graphical_presets; i++)
2533 	{
2534 		s_graphical_presets[i].generic.type = MTYPE_ACTION;
2535 		s_graphical_presets[i].generic.flags = QMF_BUTTON|QMF_RIGHT_COLUMN;
2536 		s_graphical_presets[i].generic.callback = PresetCallback;
2537 		s_graphical_presets[i].generic.name = graphical_preset_names[i][0];
2538 		s_graphical_presets[i].generic.localstrings[0] = graphical_preset_names[i][1];
2539 		s_graphical_presets[i].generic.tooltip = graphical_preset_names[i][2];
2540 		Menu_AddItem (&video.window, &s_graphical_presets[i]);
2541 	}
2542 
2543 	add_text (video.window, NULL, 0); // spacer
2544 
2545 	add_action (video.window, "Apply", VidApplyFunc, 0);
2546 
2547 	return &video.screen;
2548 }
2549 
M_Menu_Video_f(void)2550 static void M_Menu_Video_f (void)
2551 {
2552 	menuframework_s *screen = Video_MenuInit ();
2553 	M_PushMenu_Defaults (*screen);
2554 }
2555 
2556 
2557 /*
2558 =======================================================================
2559 
2560 OPTIONS MENUS - AUDIO OPTIONS MENU
2561 
2562 =======================================================================
2563 */
2564 
2565 sliderlimit_t volume_limits =
2566 {
2567 	1, 50, 0.0f, 1.0f
2568 };
2569 
2570 option_name_t audio_option_names[] =
2571 {
2572 	{
2573 		option_spincontrol,
2574 		"cl_playtaunts",
2575 		"player taunts",
2576 		NULL,
2577 		setnames (onoff_names)
2578 	},
2579 	{
2580 		option_slider,
2581 		"s_volume",
2582 		"global volume",
2583 		NULL,
2584 		setlimits (volume_limits)
2585 	},
2586 	{
2587 		option_slider,
2588 		"background_music_vol",
2589 		"music volume",
2590 		NULL,
2591 		setlimits (volume_limits)
2592 	},
2593 	{
2594 		option_spincontrol,
2595 		"background_music",
2596 		"music",
2597 		NULL,
2598 		setnames (onoff_names)
2599 	},
2600 	{
2601 		option_textcvarslider,
2602 		"s_doppler",
2603 		"doppler effect",
2604 		NULL,
2605 		setnames (doppler_effect_items)
2606 	},
2607 };
2608 
M_Menu_Audio_f(void)2609 static void M_Menu_Audio_f (void)
2610 {
2611 	options_menu (audio, "AUDIO OPTIONS");
2612 	M_PushMenu_Defaults (audio.screen);
2613 }
2614 
2615 
2616 /*
2617 =======================================================================
2618 
2619 OPTIONS MENUS - INPUT OPTIONS MENU
2620 
2621 =======================================================================
2622 */
2623 
2624 option_name_t input_option_names[] =
2625 {
2626 	{
2627 		option_slider,
2628 		"sensitivity",
2629 		"mouse speed",
2630 		NULL,
2631 		setlimits (mousespeed_limits)
2632 	},
2633 	{
2634 		option_slider,
2635 		"menu_sensitivity",
2636 		"menu mouse speed",
2637 		NULL,
2638 		setlimits (mousespeed_limits)
2639 	},
2640 	{
2641 		option_spincontrol,
2642 		"m_accel",
2643 		"mouse acceleration",
2644 		NULL,
2645 		setnames (onoff_names)
2646 	},
2647 	{
2648 		option_spincontrol,
2649 		"m_smoothing",
2650 		"mouse smoothing",
2651 		NULL,
2652 		setnames (onoff_names)
2653 	},
2654 	{
2655 		option_spincontrol,
2656 		"cl_run",
2657 		"always run",
2658 		NULL,
2659 		setnames (onoff_names)
2660 	},
2661 };
2662 
InvertMouseFunc(void * _self)2663 static void InvertMouseFunc( void *_self )
2664 {
2665 	menulist_s *self = (menulist_s *)_self;
2666 	if(self->curvalue && m_pitch->value > 0)
2667 		Cvar_SetValue( "m_pitch", -m_pitch->value );
2668 	else if(m_pitch->value < 0)
2669 		Cvar_SetValue( "m_pitch", -m_pitch->value );
2670 }
2671 
CustomizeControlsFunc(void * unused)2672 void CustomizeControlsFunc (void *unused)
2673 {
2674 	M_Menu_Keys_f ();
2675 }
2676 
M_Menu_Input_f(void)2677 static void M_Menu_Input_f (void)
2678 {
2679 	options_menu (input, "INPUT OPTIONS");
2680 
2681 	{
2682 		static menulist_s s_options_invertmouse_box;
2683 		s_options_invertmouse_box.generic.name	= "invert mouse";
2684 		s_options_invertmouse_box.generic.callback = InvertMouseFunc;
2685 		s_options_invertmouse_box.curvalue		= m_pitch->value < 0;
2686 		setup_tickbox (s_options_invertmouse_box);
2687 		Menu_AddItem (&input.panel, &s_options_invertmouse_box);
2688 	}
2689 
2690 	add_text (input.window, NULL, 0); //spacer
2691 
2692 	add_action (input.window, "Key Bindings", CustomizeControlsFunc, 0);
2693 
2694 	M_PushMenu_Defaults (input.screen);
2695 }
2696 
2697 /*
2698 =======================================================================
2699 
2700 OPTIONS MENUS - NETWORK OPTIONS MENU
2701 
2702 =======================================================================
2703 */
2704 
2705 option_name_t net_option_names[] =
2706 {
2707 	{
2708 		option_spincontrol,
2709 		"allow_download",
2710 		"download missing files",
2711 		NULL,
2712 		setnames (onoff_names)
2713 	},
2714 	{
2715 		option_spincontrol,
2716 		"allow_download_maps",
2717 		"maps",
2718 		NULL,
2719 		setnames (onoff_names)
2720 	},
2721 	{
2722 		option_spincontrol,
2723 		"allow_download_players",
2724 		"player models/skins",
2725 		NULL,
2726 		setnames (onoff_names)
2727 	},
2728 	{
2729 		option_spincontrol,
2730 		"allow_download_models",
2731 		"models",
2732 		NULL,
2733 		setnames (onoff_names)
2734 	},
2735 	{
2736 		option_spincontrol,
2737 		"allow_download_sounds",
2738 		"sounds",
2739 		NULL,
2740 		setnames (onoff_names)
2741 	},
2742 };
2743 
M_Menu_Net_f(void)2744 static void M_Menu_Net_f (void)
2745 {
2746 	options_menu (net, "NETWORK OPTIONS");
2747 	M_PushMenu_Defaults (net.screen);
2748 }
2749 
2750 
2751 /*
2752 =============================================================================
2753 
2754 OPTIONS MENUS - IRC OPTIONS MENU
2755 
2756 =============================================================================
2757 */
2758 
2759 char			IRC_key[64];
2760 
2761 static menuframework_s	s_irc_screen;
2762 
2763 static menuframework_s	s_irc_menu;
2764 static menuaction_s		s_irc_join;
2765 static menulist_s		s_irc_joinatstartup;
2766 
2767 static menufield_s		s_irc_server;
2768 static menufield_s		s_irc_channel;
2769 static menufield_s		s_irc_port;
2770 static menulist_s		s_irc_ovnickname;
2771 static menufield_s		s_irc_nickname;
2772 static menufield_s		s_irc_kickrejoin;
2773 static menufield_s		s_irc_reconnectdelay;
2774 
JoinIRCFunc(void * unused)2775 static void JoinIRCFunc( void *unused )
2776 {
2777 	if(PLAYER_NAME_UNIQUE)
2778 		CL_InitIRC();
2779 }
2780 
QuitIRCFunc(void * unused)2781 static void QuitIRCFunc( void *unused )
2782 {
2783 	CL_IRCInitiateShutdown();
2784 }
2785 
ApplyIRCSettings(void * self)2786 static void ApplyIRCSettings( void * self )
2787 {
2788 	qboolean running = CL_IRCIsRunning( );
2789 	if ( running ) {
2790 		CL_IRCInitiateShutdown( );
2791 		CL_IRCWaitShutdown( );
2792 	}
2793 
2794 	Cvar_Set(	"cl_IRC_server" ,		s_irc_server.buffer);
2795 	Cvar_Set(	"cl_IRC_channel" ,		s_irc_channel.buffer);
2796 	Cvar_SetValue(	"cl_IRC_port" , 		atoi( s_irc_port.buffer ) );
2797 	Cvar_SetValue(	"cl_IRC_override_nickname" ,	s_irc_ovnickname.curvalue );
2798 	Cvar_Set(	"cl_IRC_nickname" ,		s_irc_nickname.buffer );
2799 	Cvar_SetValue(	"cl_IRC_kick_rejoin" ,		atoi( s_irc_kickrejoin.buffer ) );
2800 	Cvar_SetValue(	"cl_IRC_reconnect_delay" ,	atoi( s_irc_reconnectdelay.buffer ) );
2801 
2802 	if ( running )
2803 		CL_InitIRC( );
2804 
2805 	M_PopMenu( );
2806 }
2807 
2808 // TODO: use the options menu macros like everywhere else.
IRC_Settings_SubMenuInit()2809 static void IRC_Settings_SubMenuInit( )
2810 {
2811 	setup_tickbox (s_irc_joinatstartup);
2812 	s_irc_joinatstartup.generic.name	= "join at startup";
2813 	s_irc_joinatstartup.generic.localstrings[0] = "cl_IRC_connect_at_startup";
2814 	s_irc_joinatstartup.curvalue = cl_IRC_connect_at_startup->integer != 0;
2815 	s_irc_joinatstartup.generic.callback = SpinOptionFunc;
2816 	Menu_AddItem( &s_irc_menu, &s_irc_joinatstartup );
2817 
2818 	s_irc_server.generic.type		= MTYPE_FIELD;
2819 	s_irc_server.generic.name		= "server";
2820 	s_irc_server.generic.tooltip	= "Address or name of the IRC server";
2821 	s_irc_server.generic.visible_length		= LONGINPUT_SIZE;
2822 	s_irc_server.cursor			= strlen( cl_IRC_server->string );
2823 	strcpy( s_irc_server.buffer, Cvar_VariableString("cl_IRC_server") );
2824 	Menu_AddItem( &s_irc_menu, &s_irc_server );
2825 
2826 	s_irc_channel.generic.type		= MTYPE_FIELD;
2827 	s_irc_channel.generic.name		= "channel";
2828 	s_irc_channel.generic.tooltip	= "Name of the channel to join";
2829 	s_irc_channel.generic.visible_length		= LONGINPUT_SIZE;
2830 	s_irc_channel.cursor			= strlen( cl_IRC_channel->string );
2831 	strcpy( s_irc_channel.buffer, Cvar_VariableString("cl_IRC_channel") );
2832 	Menu_AddItem( &s_irc_menu, &s_irc_channel );
2833 
2834 	s_irc_port.generic.type			= MTYPE_FIELD;
2835 	s_irc_port.generic.name			= "port";
2836 	s_irc_port.generic.tooltip		= "Port to connect to on the server";
2837 	s_irc_port.generic.visible_length		= 4;
2838 	s_irc_port.cursor			= strlen( cl_IRC_port->string );
2839 	strcpy( s_irc_port.buffer, Cvar_VariableString("cl_IRC_port") );
2840 	Menu_AddItem( &s_irc_menu, &s_irc_port );
2841 
2842 	s_irc_ovnickname.generic.name		= "override nick";
2843 	s_irc_ovnickname.generic.tooltip	= "Enable this to override the default, player-based nick";
2844 	setup_tickbox (s_irc_ovnickname);
2845 	s_irc_ovnickname.curvalue		= cl_IRC_override_nickname->value ? 1 : 0;
2846 	Menu_AddItem( &s_irc_menu, &s_irc_ovnickname );
2847 
2848 	s_irc_nickname.generic.type		= MTYPE_FIELD;
2849 	s_irc_nickname.generic.name		= "nick";
2850 	s_irc_nickname.generic.tooltip	= "Nickname override to use";
2851 	s_irc_nickname.generic.visible_length		= LONGINPUT_SIZE;
2852 	s_irc_nickname.cursor			= strlen( cl_IRC_nickname->string );
2853 	strcpy( s_irc_nickname.buffer, Cvar_VariableString("cl_IRC_nickname") );
2854 	Menu_AddItem( &s_irc_menu, &s_irc_nickname );
2855 
2856 	s_irc_kickrejoin.generic.type		= MTYPE_FIELD;
2857 	s_irc_kickrejoin.generic.name		= "autorejoin";
2858 	s_irc_kickrejoin.generic.tooltip	= "Delay before automatic rejoin after kick (0 to disable)";
2859 	s_irc_kickrejoin.generic.visible_length		= 4;
2860 	s_irc_kickrejoin.cursor			= strlen( cl_IRC_kick_rejoin->string );
2861 	strcpy( s_irc_kickrejoin.buffer, Cvar_VariableString("cl_IRC_kick_rejoin") );
2862 	Menu_AddItem( &s_irc_menu, &s_irc_kickrejoin );
2863 
2864 	s_irc_reconnectdelay.generic.type	= MTYPE_FIELD;
2865 	s_irc_reconnectdelay.generic.name	= "reconnect";
2866 	s_irc_reconnectdelay.generic.tooltip= "Delay between reconnection attempts (minimum 5)";
2867 	s_irc_reconnectdelay.generic.visible_length	= 4;
2868 	s_irc_reconnectdelay.cursor		= strlen( cl_IRC_reconnect_delay->string );
2869 	strcpy( s_irc_reconnectdelay.buffer, Cvar_VariableString("cl_IRC_reconnect_delay") );
2870 	Menu_AddItem( &s_irc_menu, &s_irc_reconnectdelay );
2871 
2872 	add_action (s_irc_menu, "Apply", ApplyIRCSettings, 0);
2873 
2874 }
2875 
2876 
M_FindIRCKey(void)2877 static void M_FindIRCKey ( void )
2878 {
2879 	int		count;
2880 	int		j;
2881 	int		l;
2882 	char	*b;
2883 	int twokeys[2];
2884 
2885 	twokeys[0] = twokeys[1] = -1;
2886 	l = strlen("messagemode3");
2887 	count = 0;
2888 
2889 	for (j=0 ; j<256 ; j++)
2890 	{
2891 		b = keybindings[j];
2892 		if (!b)
2893 			continue;
2894 		if (!strncmp (b, "messagemode3", l) )
2895 		{
2896 			twokeys[count] = j;
2897 			count++;
2898 			if (count == 2)
2899 				break;
2900 		}
2901 	}
2902 	//got our key
2903 	Com_sprintf(IRC_key, sizeof(IRC_key), "(IRC Chat Key is %s)", Key_KeynumToString(twokeys[0]));
2904 }
2905 
IRC_MenuInit(void)2906 void IRC_MenuInit( void )
2907 {
2908 	if(!cl_IRC_connect_at_startup)
2909 		cl_IRC_connect_at_startup = Cvar_Get("cl_IRC_connect_at_startup", "0", CVAR_ARCHIVE);
2910 
2911 	M_FindIRCKey();
2912 
2913 	setup_window (s_irc_screen, s_irc_menu, "IRC CHAT OPTIONS");
2914 
2915 	s_irc_join.generic.type	= MTYPE_ACTION;
2916 	s_irc_join.generic.flags = QMF_BUTTON;
2917 	s_irc_join.generic.name	= "Connect Now";
2918 	s_irc_join.generic.callback = JoinIRCFunc;
2919 	Menu_AddItem( &s_irc_menu, &s_irc_join );
2920 
2921 	IRC_Settings_SubMenuInit ();
2922 
2923 	add_text (s_irc_menu, IRC_key, 0);
2924 	Menu_AutoArrange (&s_irc_screen);
2925 }
2926 
2927 
IRC_MenuDraw(menuframework_s * dummy,menuvec2_t offset)2928 void IRC_MenuDraw (menuframework_s *dummy, menuvec2_t offset)
2929 {
2930 	//warn user that they cannot join until changing default player name
2931 	if(!PLAYER_NAME_UNIQUE)
2932 		s_irc_menu.statusbar = "You must create your player name before joining a server!";
2933 	else if(CL_IRCIsConnected())
2934 		s_irc_menu.statusbar = "Connected to IRC server.";
2935 	else if(CL_IRCIsRunning())
2936 		s_irc_menu.statusbar = "Connecting to IRC server...";
2937 	else
2938 		s_irc_menu.statusbar = "Not connected to IRC server.";
2939 
2940 	// Update join/quit menu entry
2941 	if ( CL_IRCIsRunning( ) ) {
2942 		s_irc_join.generic.name	= "Disconnect Now";
2943 		s_irc_join.generic.callback = QuitIRCFunc;
2944 	} else {
2945 		s_irc_join.generic.name	= "Connect Now";
2946 		s_irc_join.generic.callback = JoinIRCFunc;
2947 	}
2948 
2949 	Screen_Draw (&s_irc_screen, offset);
2950 }
2951 
M_Menu_IRC_f(void)2952 void M_Menu_IRC_f (void)
2953 {
2954 	IRC_MenuInit();
2955 	M_PushMenu (IRC_MenuDraw, Default_MenuKey, &s_irc_screen);
2956 }
2957 
2958 
2959 /*
2960 =======================================================================
2961 
2962 OPTIONS MENUS - TOP-LEVEL OPTIONS MENU
2963 
2964 =======================================================================
2965 */
2966 
2967 static menuframework_s	s_options_screen;
2968 static menuframework_s	s_options_menu;
2969 
2970 char *option_screen_names[] =
2971 {
2972 	"Player", // whatever's first will be the default
2973 	"Display",
2974 	"Video",
2975 	"Audio",
2976 	"Input",
2977 	"Network",
2978 	"IRC Chat",
2979 };
2980 #define OPTION_SCREENS static_array_size(option_screen_names)
2981 
2982 void (*option_open_funcs[OPTION_SCREENS])(void) =
2983 {
2984 	&M_Menu_PlayerConfig_f,
2985 	&M_Menu_Display_f,
2986 	&M_Menu_Video_f,
2987 	&M_Menu_Audio_f,
2988 	&M_Menu_Input_f,
2989 	&M_Menu_Net_f,
2990 	&M_Menu_IRC_f,
2991 };
2992 
2993 static menuframework_s	s_player_config_screen;
2994 
2995 static menuaction_s		s_option_screen_actions[OPTION_SCREENS];
2996 
2997 LINKABLE(int) option_screen_height;
2998 
OptionScreenFunc(void * _self)2999 static void OptionScreenFunc (void *_self)
3000 {
3001 	menuframework_s *self = (menuframework_s *)_self;
3002 
3003 	option_open_funcs[self->generic.localints[0]]();
3004 }
3005 
M_Menu_Options_f(void)3006 void M_Menu_Options_f (void)
3007 {
3008 	int i;
3009 
3010 	setup_window (s_options_screen, s_options_menu, "OPTIONS");
3011 
3012 	for (i = 0; i < OPTION_SCREENS; i++)
3013 	{
3014 		s_option_screen_actions[i].generic.type = MTYPE_ACTION;
3015 		s_option_screen_actions[i].generic.flags = QMF_BUTTON;
3016 		s_option_screen_actions[i].generic.name = option_screen_names[i];
3017 		s_option_screen_actions[i].generic.localints[0] = i;
3018 		s_option_screen_actions[i].generic.callback = OptionScreenFunc;
3019 		Menu_AddItem (&s_options_menu, &s_option_screen_actions[i]);
3020 	}
3021 
3022 	add_text (s_options_menu, NULL, 0); //spacer
3023 
3024 	add_action (s_options_menu, "Reset to Defaults", OptionsResetDefaultsFunc, 0);
3025 	add_action (s_options_menu, "Restore from Saved", OptionsResetSavedFunc, 0);
3026 
3027 	M_PushMenu_Defaults (s_options_screen);
3028 
3029 	// select the default options screen
3030 	OptionScreenFunc (&s_option_screen_actions[0]);
3031 }
3032 
3033 /*
3034 =============================================================================
3035 
3036 END GAME MENU
3037 
3038 =============================================================================
3039 */
3040 static int credits_start_time;
3041 static const char **credits;
3042 //static char *creditsIndex[256];
3043 static char *creditsBuffer;
3044 static const char *idcredits[] =
3045 {
3046 	"+Alien Arena by COR Entertainment",
3047 	"",
3048 	"+PROGRAMMING",
3049 	"John Diamond",
3050 	"Jim Bower",
3051 	"Emmanuel Benoit",
3052 	"Max Eliaser",
3053 	"Charles Hudson",
3054 	"Lee Salzman",
3055 	"Dave Carter",
3056 	"Victor Luchits",
3057 	"Jan Rafaj",
3058 	"Shane Bayer",
3059 	"Tony Jackson",
3060 	"Stephan Stahl",
3061 	"Kyle Hunter",
3062 	"Andres Mejia",
3063 	"",
3064 	"+ART",
3065 	"John Diamond",
3066 	"Dennis -xEMPx- Zedlach",
3067 	"Franc Cassar",
3068 	"Shawn Keeth",
3069 	"Enki",
3070 	"",
3071 	"+FONTS",
3072 	"John Diamond",
3073 	"The League of Moveable Type",
3074 	"Brian Kent",
3075 	"",
3076 	"+LOGO",
3077 	"Adam -servercleaner- Szalai",
3078 	"Paul -GimpAlien-",
3079 	"",
3080 	"+LEVEL DESIGN",
3081 	"John Diamond",
3082 	"Dennis -xEMPx- Zedlach",
3083 	"Charles Hudson",
3084 	"Torben Fahrnbach",
3085 	"",
3086 	"+SOUND EFFECTS AND MUSIC",
3087 	"Music/FX Composed and Produced by",
3088 	"Paul Joyce, Whitelipper, Divinity",
3089 	"Arteria Games, Wooden Productions",
3090 	"and Soundrangers.com",
3091 	"",
3092 	"+CROSSHAIRS AND HUDS",
3093 	"Astralsin",
3094 	"Dangeresque",
3095 	"Phenax",
3096 	"Roadrage",
3097 	"Forsaken",
3098 	"Capt Crazy",
3099 	"Torok -Intimidator- Ivan",
3100 	"Stratocaster",
3101 	"ChexGuy",
3102 	"Crayon",
3103 	"",
3104 	"+LINUX PORT",
3105 	"Shane Bayer",
3106 	"",
3107 	"+FREEBSD PORT",
3108 	"Ale",
3109 	"",
3110 	"+GENTOO PORTAGE",
3111 	"Paul Bredbury",
3112 	"",
3113 	"+DEBIAN PACKAGE",
3114 	"Andres Mejia",
3115 	"",
3116 	"+LANGUAGE TRANSLATIONS",
3117 	"Ken Deguisse",
3118 	"",
3119 	"+STORYLINE",
3120 	"Sinnocent",
3121 	"",
3122 	"+SPECIAL THANKS",
3123 	"The Alien Arena Community",
3124 	"and everyone else who",
3125 	"has been loyal to the",
3126 	"game.",
3127 	"",
3128 	"",
3129 	"+ALIEN ARENA - THE STORY",
3130 	"",
3131 	"Alien Arena : Many are called, only one will reign supreme",
3132 	"",
3133 	"Eternal war ravaged the vastness of infinite space.",
3134 	"For as far back into the ages as the memories of the",
3135 	"galaxies oldest races could reach, it had been this way.",
3136 	"Planet at war with planet, conflicts ending with a",
3137 	"burned cinder orbiting a dead sun and countless billions",
3138 	"sent screaming into oblivion. War, endless and ",
3139 	"eternal, embracing all the peoples of the cosmos.",
3140 	"Scientific triumphs heralded the creation of ever more",
3141 	"deadly weapons until the very fabric of the universe itself was threatened.",
3142 	"",
3143 	"Then came the call.",
3144 	"",
3145 	"Some said it was sent by an elder race, legendary beings",
3146 	"of terrifying power who had existed since the",
3147 	"birth of the stars and who now made their home beyond",
3148 	"the fringes of known creation, others whispered",
3149 	"fearfully and looked to the skies for the coming of their",
3150 	"gods. Perhaps it didn't matter who had sent the",
3151 	"call, for all the people of the stars could at least agree",
3152 	"that the call was there.",
3153 	"",
3154 	"The wars were to end - or they would be ended. In a",
3155 	"demonstration of power whoever had sent the call",
3156 	"snuffed out the homeworld of the XXXX, the greatest",
3157 	"empire of all the stars, in a heartbeat. One moment it",
3158 	"was there, the next it was dust carried on the solar winds.",
3159 	"All races had no choice but to heed the call.",
3160 	"",
3161 	"For most the call was a distant tug, a whispered warning",
3162 	"that the wars were over, but for the greatest",
3163 	"hero of each people it was more, it was a fire raging",
3164 	"through their blood, a call to a new war, to the battle",
3165 	"to end all battles. That fire burns in your blood, compelling",
3166 	"you, the greatest warrior of your people, to fight",
3167 	"in a distant and unknown arena, your honor and the",
3168 	"future of your race at stake.",
3169 	"",
3170 	"Across the stars you traveled in search of this arena where",
3171 	"the mightiest of the mighty would do battle,",
3172 	"where you would stand face to face with your enemies",
3173 	"in a duel to the death. Strange new weapons",
3174 	"awaited you, weapons which you would have to master",
3175 	"if you were to survive and emerge victorious from",
3176 	"the complex and deadly arenas in which you were summoned to fight.",
3177 	"",
3178 	"The call to battle beats through your heart and soul",
3179 	"like the drums of war. Will you be the one to rise",
3180 	"through the ranks and conquer all others, the one who",
3181 	"stands proud as the undefeated champion of the",
3182 	"Alien Arena?",
3183 	"",
3184 	"Alien Arena (C)2007-2012 COR Entertainment, LLC",
3185 	"All Rights Reserved.",
3186 	0
3187 };
3188 
M_Credits_MenuDraw(menuframework_s * dummy,menuvec2_t offset)3189 void M_Credits_MenuDraw (menuframework_s *dummy, menuvec2_t offset)
3190 {
3191 	int i, y, scale;
3192 	FNT_font_t		font;
3193 	struct FNT_window_s	box;
3194 
3195 	font = FNT_AutoGet( CL_menuFont );
3196 	scale = font->size / 8.0;
3197 
3198 	/*
3199 	** draw the credits
3200 	*/
3201 	for ( i = 0, y = viddef.height - ( ( cls.realtime - credits_start_time ) / 40.0F ); credits[i]; y += 12*scale, i++ )
3202 	{
3203 		if ( y <= -12*scale )
3204 			continue;
3205 
3206 		box.y = offset.y + y;
3207 		box.x = offset.x;
3208 		box.height = 0;
3209 		box.width = viddef.width;
3210 
3211 		if ( credits[i][0] == '+' )
3212 		{
3213 			FNT_BoundedPrint (font, credits[i]+1, FNT_CMODE_NONE, FNT_ALIGN_CENTER, &box, FNT_colors[3]);
3214 		}
3215 		else
3216 		{
3217 			FNT_BoundedPrint (font, credits[i], FNT_CMODE_NONE, FNT_ALIGN_CENTER, &box, FNT_colors[7]);
3218 		}
3219 	}
3220 
3221 	if ( y < 0 )
3222 		credits_start_time = cls.realtime;
3223 }
3224 
M_Credits_Key(menuframework_s * dummy,int key)3225 const char *M_Credits_Key (menuframework_s *dummy, int key)
3226 {
3227 	if (key == K_ESCAPE)
3228 	{
3229 		if (creditsBuffer)
3230 			FS_FreeFile (creditsBuffer);
3231 		M_PopMenu ();
3232 	}
3233 
3234 	return menu_out_sound;
3235 
3236 }
3237 
M_Menu_Credits_f(void)3238 void M_Menu_Credits_f( void )
3239 {
3240 	static menuframework_s dummy;
3241 
3242 	CHASELINK(dummy.rwidth) = viddef.width;
3243 
3244 	creditsBuffer = NULL;
3245 	credits = idcredits;
3246 	credits_start_time = cls.realtime;
3247 
3248 	M_PushMenu (M_Credits_MenuDraw, M_Credits_Key, &dummy);
3249 }
3250 
3251 /*
3252 =============================================================================
3253 
3254 GAME MENU
3255 
3256 =============================================================================
3257 */
3258 
StartGame(void)3259 static void StartGame( void )
3260 {
3261 	extern cvar_t *name;
3262 	char pw[64];
3263 
3264 	// disable updates
3265 	cl.servercount = -1;
3266 	M_ForceMenuOff ();
3267 	Cvar_SetValue( "deathmatch", 1 );
3268 	Cvar_SetValue( "ctf", 0 );
3269 
3270 	//listen servers are passworded
3271 	sprintf(pw, "%s%4.2f", name->string, crand());
3272 	Cvar_Set ("password", pw);
3273 
3274 	Cvar_SetValue( "gamerules", 0 );		//PGM
3275 
3276 	Cbuf_AddText ("loading ; killserver ; wait ; newgame\n");
3277 	cls.key_dest = key_game;
3278 }
3279 
SinglePlayerGameFunc(void * data)3280 static void SinglePlayerGameFunc (void *data)
3281 {
3282 	char skill[2];
3283 	skill[1] = '\0';
3284 	skill[0] = ((menuaction_s*)data)->generic.localints[0]+'0';
3285 	Cvar_ForceSet ("skill", skill);
3286 	StartGame ();
3287 }
3288 
M_Menu_Game_f(void)3289 static void M_Menu_Game_f (void)
3290 {
3291 	static menuframework_s	s_game_screen;
3292 	static menuframework_s	s_game_menu;
3293 
3294 	static const char *singleplayer_skill_level_names[][2] = {
3295 		{"Easy",	"You will win"},
3296 		{"Medium",	"You might win"},
3297 		{"Hard",	"Very challenging"},
3298 		{"Ultra",	"Only the best will win"}
3299 	};
3300 	#define num_singleplayer_skill_levels  static_array_size(singleplayer_skill_level_names)
3301 	static menuaction_s		s_singleplayer_game_actions[num_singleplayer_skill_levels];
3302 
3303 	int i;
3304 
3305 	setup_window (s_game_screen, s_game_menu, "SINGLE PLAYER");
3306 
3307 	for (i = 0; i < num_singleplayer_skill_levels; i++)
3308 	{
3309 		s_singleplayer_game_actions[i].generic.type = MTYPE_ACTION;
3310 		s_singleplayer_game_actions[i].generic.flags = QMF_BUTTON;
3311 		s_singleplayer_game_actions[i].generic.name = singleplayer_skill_level_names[i][0];
3312 		s_singleplayer_game_actions[i].generic.tooltip = singleplayer_skill_level_names[i][1];
3313 		s_singleplayer_game_actions[i].generic.localints[0] = i;
3314 		s_singleplayer_game_actions[i].generic.callback = SinglePlayerGameFunc;
3315 		Menu_AddItem (&s_game_menu, &s_singleplayer_game_actions[i]);
3316 	}
3317 
3318 	Menu_AutoArrange (&s_game_screen);
3319 	Menu_Center (&s_game_screen);
3320 
3321 	M_PushMenu_Defaults (s_game_screen);
3322 }
3323 
3324 
3325 
3326 /*
3327 =============================================================================
3328 
3329 JOIN SERVER MENU
3330 
3331 =============================================================================
3332 */
3333 #define MAX_LOCAL_SERVERS 128
3334 #define MAX_SERVER_MODS 16
3335 #define SERVER_LIST_COLUMNS 4
3336 
3337 static const char *updown_names[] = {
3338 	"menu/midarrow",
3339 	"menu/dnarrow",
3340 	"menu/uparrow",
3341 	0
3342 };
3343 
3344 
3345 //Lists for all stock mutators and game modes, plus some of the more popular
3346 //custom ones. (NOTE: For non-boolean cvars, i.e. those which have values
3347 //other than 0 or 1, you get a string of the form cvar=value. In the future,
3348 //we may do something special to parse these, but since no such cvars are
3349 //actually recognized right now anyway, we currently don't.)
3350 
3351 //TODO: Have a menu to explore this list?
3352 
3353 //Names. If a cvar isn't recognized, the name of the cvar itself is used.
3354 static char mod_names[] =
3355 	//cannot be wider than this boundary:	|
3356 	"\\ctf"             "\\capture the flag"
3357 	"\\tca"             "\\team core assault"
3358 	"\\cp"              "\\cattle prod"
3359 	"\\instagib"        "\\instagib"
3360 	"\\rocket_arena"    "\\rocket arena"
3361 	"\\low_grav"        "\\low gravity"
3362 	"\\regeneration"    "\\regeneration"
3363 	"\\vampire"         "\\vampire"
3364 	"\\excessive"       "\\excessive"
3365 	"\\grapple"         "\\grappling hook"
3366 	"\\classbased"      "\\class based"
3367 	"\\g_duel"          "\\duel mode"
3368 	"\\quickweap"       "\\quick switch"
3369 	"\\anticamp"        "\\anticamp"
3370 	"\\sv_joustmode"    "\\joust mode"
3371 	"\\playerspeed"     "\\player speed"
3372 	"\\insta_rockets"   "\\insta/rockets"
3373 	"\\chaingun_arena"  "\\chaingun arena"
3374 	"\\instavap"        "\\vaporizer arena"
3375 	"\\vape_arena"      "\\vaporizer arena"
3376 	"\\testcode"        "\\code testing"
3377 	"\\testmap"         "\\map testing"
3378 	"\\dodgedelay=0"    "\\rapid dodging"
3379 	"\\g_tactical"      "\\aa tactical"
3380 	"\\g_dm_lights"     "\\player lights"
3381 	"\\";
3382 
3383 //Descriptions. If a cvar isn't recognized, "(no description)" is used.
3384 static char mods_desc[] =
3385 	//cannot be wider than this boundary:									|
3386 	"\\ctf"             "\\capture the enemy team's flag to earn points"
3387 	"\\tca"             "\\destroy the enemy team's spider node to win"
3388 	"\\cp"              "\\herd cows through your team's goal for points"
3389 	"\\instagib"        "\\disruptor only, instant kill, infinite ammo"
3390 	"\\rocket_arena"    "\\rocket launcher only, infinite ammo"
3391 	"\\low_grav"        "\\reduced gravity"
3392 	"\\regeneration"    "\\regain health over time"
3393 	"\\vampire"         "\\regain health by damaging people"
3394 	"\\excessive"       "\\all weapons enhanced, infinite ammo"
3395 	"\\grapple"         "\\spawn with a grappling hook"
3396 	"\\classbased"      "\\different races have different strengths"
3397 	"\\g_duel"          "\\wait in line for your turn to duel"
3398 	"\\quickweap"       "\\switch weapons instantly"
3399 	"\\anticamp"        "\\you are punished for holding still too long"
3400 	"\\sv_joustmode"    "\\you can still jump while in midair"
3401 	"\\playerspeed"     "\\run much faster than normal"
3402 	"\\insta_rockets"   "\\hybrid of instagib and rocket_arena"
3403 	"\\chaingun_arena"  "\\chaingun only, infinite ammo"
3404 	"\\instavap"        "\\vaporizer only, infinite ammo"
3405 	"\\vape_arena"      "\\vaporizer only, infinite ammo"
3406 	"\\testcode"        "\\server is testing experimental code"
3407 	"\\testmap"         "\\server is testing an unfinished map"
3408 	"\\dodgedelay=0"    "\\no minimum time between dodges"
3409 	"\\g_tactical"      "\\humans vs martians, destroy enemy bases"
3410 	"\\g_dm_lights"     "\\high-visibility lights on players"
3411 	"\\";
3412 
GetLine(char ** contents,int * len)3413 char *GetLine (char **contents, int *len)
3414 {
3415 	int num;
3416 	int i;
3417 	char line[2048];
3418 	char *ret;
3419 
3420 	num = 0;
3421 	line[0] = '\0';
3422 
3423 	if (*len <= 0)
3424 		return NULL;
3425 
3426 	for (i = 0; i < *len; i++) {
3427 		if ((*contents)[i] == '\n') {
3428 			*contents += (num + 1);
3429 			*len -= (num + 1);
3430 			line[num] = '\0';
3431 			ret = (char *)malloc (sizeof(line));
3432 			strcpy (ret, line);
3433 			return ret;
3434 		}
3435 		line[num] = (*contents)[i];
3436 		num++;
3437 	}
3438 
3439 	ret = (char *)malloc (sizeof(line));
3440 	strcpy (ret, line);
3441 	return ret;
3442 }
3443 
3444 
3445 SERVERDATA mservers[MAX_LOCAL_SERVERS];
3446 
3447 PLAYERSTATS thisPlayer;
3448 
3449 #define m_num_servers (s_serverlist_submenu.nitems)
3450 
3451 static char local_mods_data[16][53]; //53 is measured max tooltip width
3452 
3453 
3454 static struct
3455 {
3456 	menuframework_s	screen;
3457 	menuframework_s	menu;
3458 
3459 	menutxt_s		name;
3460 	menuaction_s	connect;
3461 
3462 	menuitem_s		levelshot;
3463 	char			levelshot_path[MAX_QPATH];
3464 
3465 	menuframework_s	serverinfo_submenu;
3466 	menuframework_s	serverinfo_table;
3467 	menuframework_s	serverinfo_rows[8];
3468 	menutxt_s		serverinfo_columns[8][2];
3469 
3470 	menuframework_s	modlist_submenu;
3471 	menuaction_s	modlist[MAX_SERVER_MODS];
3472 	char			modtxt[MAX_SERVER_MODS][48];
3473 	char			modnames[MAX_SERVER_MODS][24];
3474 
3475 	menuframework_s	playerlist_submenu;
3476 	menuaction_s	playerlist_label;
3477 	menuframework_s	playerlist_header;
3478 	menuframework_s	playerlist_scrollingmenu;
3479 	menutxt_s		playerlist_header_columns[SVDATA_PLAYERINFO];
3480 	menuframework_s	playerlist_rows[MAX_PLAYERS];
3481 	menutxt_s		playerlist_columns[MAX_PLAYERS][SVDATA_PLAYERINFO];
3482 	char			ranktxt[MAX_PLAYERS][32];
3483 } s_servers[MAX_LOCAL_SERVERS];
3484 
3485 static int serverindex;
3486 
JoinServerFunc(void * unused)3487 void JoinServerFunc (void *unused)
3488 {
3489 	int		i;
3490 	char	buffer[128];
3491 
3492 	cl.tactical = false;
3493 
3494 	remoteserver_runspeed = 300; //default
3495 	for ( i = 0; i < 16; i++)
3496 	{
3497 		if( !strcmp("aa tactical", Info_ValueForKey(mod_names, local_mods_data[i])) )
3498 		{
3499 			remoteserver_runspeed = 200; //for correct prediction
3500 			M_Menu_Tactical_f();
3501 			return;
3502 		}
3503 		else if( !strcmp("excessive", Info_ValueForKey(mod_names, local_mods_data[i])) )
3504 			remoteserver_runspeed = 450;
3505 		else if( !strcmp("playerspeed", Info_ValueForKey(mod_names, local_mods_data[i])) )
3506 			remoteserver_runspeed = 450;
3507 	} //TO DO:  We need to do the speed check on connect instead - meaning the server will need to be pinged and parsed there as well(but only if not done already through the menu).
3508 
3509 	Com_sprintf (buffer, sizeof(buffer), "connect %s\n", NET_AdrToString (mservers[serverindex].local_server_netadr));
3510 	Cbuf_AddText (buffer);
3511 	M_ForceMenuOff ();
3512 }
3513 
ModList_SubmenuInit(void)3514 void ModList_SubmenuInit (void)
3515 {
3516 	int i;
3517 	char	modstring[64];
3518 	char	*token;
3519 
3520 	s_servers[serverindex].modlist_submenu.generic.type = MTYPE_SUBMENU;
3521 	s_servers[serverindex].modlist_submenu.navagable = true;
3522 	s_servers[serverindex].modlist_submenu.nitems = 0;
3523 
3524 	for ( i = 0; i < MAX_SERVER_MODS; i++ )
3525 	{
3526 		s_servers[serverindex].modlist[i].generic.type	= MTYPE_ACTION;
3527 		s_servers[serverindex].modlist[i].generic.flags = QMF_RIGHT_COLUMN;
3528 		s_servers[serverindex].modlist[i].generic.name	= s_servers[serverindex].modnames[i];
3529 		s_servers[serverindex].modlist[i].generic.tooltip = s_servers[serverindex].modtxt[i];
3530 
3531 		Menu_AddItem( &s_servers[serverindex].modlist_submenu, &s_servers[serverindex].modlist[i] );
3532 	}
3533 
3534 	s_servers[serverindex].modlist_submenu.maxlines = 5;
3535 
3536 	//Copy modstring over since strtok will modify it
3537 	Q_strncpyz(modstring, mservers[serverindex].modInfo, sizeof(modstring));
3538 
3539 	// populate all the data
3540 	token = strtok(modstring, "%%");
3541 	for (i=0; i<MAX_SERVER_MODS; i++) {
3542 		if (!token)
3543 			break;
3544 		Com_sprintf(local_mods_data[i], sizeof(local_mods_data[i]), token);
3545 		token = strtok(NULL, "%%");
3546 
3547 		Com_sprintf (   s_servers[serverindex].modtxt[i], sizeof(s_servers[serverindex].modtxt[i]),
3548 						Info_ValueForKey(mods_desc, local_mods_data[i])
3549 					);
3550 		if (!strlen(s_servers[serverindex].modtxt[i]))
3551 			Com_sprintf (s_servers[serverindex].modtxt[i], sizeof(s_servers[serverindex].modtxt[i]), "(no description)");
3552 
3553 		Com_sprintf (   s_servers[serverindex].modnames[i], sizeof(s_servers[serverindex].modnames[i]),
3554 						Info_ValueForKey(mod_names, local_mods_data[i])
3555 					);
3556 		if (!strlen(s_servers[serverindex].modnames[i]))
3557 			Com_sprintf (s_servers[serverindex].modnames[i], sizeof(s_servers[serverindex].modnames[i]), local_mods_data[i]);
3558 	}
3559 	s_servers[serverindex].modlist_submenu.nitems = i;
3560 	s_servers[serverindex].modlist_submenu.yscroll = 0;
3561 }
3562 
ServerInfo_SubmenuInit(void)3563 void ServerInfo_SubmenuInit (void)
3564 {
3565 	size_t sizes[2] = {sizeof(menutxt_s), sizeof(menutxt_s)};
3566 
3567 	char *contents[2*7+1] = {
3568 		"Map:",			mservers[serverindex].szMapName,
3569 		"Skill:",		mservers[serverindex].skill,
3570 		"Admin:",		mservers[serverindex].szAdmin,
3571 		"Website:",		mservers[serverindex].szWebsite,
3572 		"Fraglimit:",	mservers[serverindex].fraglimit,
3573 		"Timelimit:",	mservers[serverindex].timelimit,
3574 		"Version:",		mservers[serverindex].szVersion,
3575 		"Gameplay:"
3576 	};
3577 
3578 	Com_sprintf (
3579 		s_servers[serverindex].levelshot_path,
3580 		sizeof(s_servers[serverindex].levelshot_path),
3581 		"/levelshots/%s", mservers[serverindex].fullMapName
3582 	);
3583 
3584 	s_servers[serverindex].serverinfo_submenu.generic.type = MTYPE_SUBMENU;
3585 	s_servers[serverindex].serverinfo_submenu.bordertexture = "menu/sm_";
3586 	s_servers[serverindex].serverinfo_submenu.nitems = 0;
3587 	s_servers[serverindex].serverinfo_submenu.navagable = true;
3588 
3589 	s_servers[serverindex].name.generic.type = MTYPE_TEXT;
3590 	s_servers[serverindex].name.generic.flags = QMF_RIGHT_COLUMN;
3591 	s_servers[serverindex].name.generic.name = mservers[serverindex].szHostName;
3592 	Menu_AddItem (&s_servers[serverindex].serverinfo_submenu, &s_servers[serverindex].name);
3593 
3594 	s_servers[serverindex].levelshot.generic.type = MTYPE_NOT_INTERACTIVE;
3595 	s_servers[serverindex].levelshot.generic.localstrings[0] = s_servers[serverindex].levelshot_path;
3596 	// pretty close to 16:9
3597 	VectorSet (s_servers[serverindex].levelshot.generic.localints, 21, 12, 0);
3598 	s_servers[serverindex].levelshot.generic.itemsizecallback = PicSizeFunc;
3599 	s_servers[serverindex].levelshot.generic.itemdraw = PicDrawFunc;
3600 	Menu_AddItem (&s_servers[serverindex].serverinfo_submenu, &s_servers[serverindex].levelshot);
3601 
3602 	s_servers[serverindex].serverinfo_table.generic.type = MTYPE_SUBMENU;
3603 	s_servers[serverindex].serverinfo_table.nitems = 0;
3604 
3605 	s_servers[serverindex].serverinfo_columns[0][0].generic.type		= MTYPE_TEXT;
3606 	s_servers[serverindex].serverinfo_columns[0][1].generic.type		= MTYPE_TEXT;
3607 	s_servers[serverindex].serverinfo_columns[0][1].generic.flags	= QMF_RIGHT_COLUMN;
3608 
3609 	Menu_MakeTable (&s_servers[serverindex].serverinfo_table, 7, 2, sizes, s_servers[serverindex].serverinfo_rows, s_servers[serverindex].serverinfo_rows, s_servers[serverindex].serverinfo_columns, contents);
3610 
3611 	Menu_AddItem (&s_servers[serverindex].serverinfo_submenu, &s_servers[serverindex].serverinfo_table);
3612 
3613 	s_servers[serverindex].serverinfo_rows[7].generic.type = MTYPE_SUBMENU;
3614 	s_servers[serverindex].serverinfo_rows[7].horizontal = true;
3615 	s_servers[serverindex].serverinfo_rows[7].navagable = true;
3616 	s_servers[serverindex].serverinfo_rows[7].nitems = 0;
3617 
3618 	LINK (s_servers[serverindex].serverinfo_rows[0].lwidth, s_servers[serverindex].serverinfo_rows[7].lwidth);
3619 	LINK (s_servers[serverindex].serverinfo_rows[0].rwidth, s_servers[serverindex].serverinfo_rows[7].rwidth);
3620 
3621 	s_servers[serverindex].serverinfo_columns[7][0].generic.type = MTYPE_TEXT;
3622 	s_servers[serverindex].serverinfo_columns[7][0].generic.name = contents[7*2+0];
3623 	LINK (s_servers[serverindex].serverinfo_columns[0][0].generic.x, s_servers[serverindex].serverinfo_columns[7][0].generic.x);
3624 	Menu_AddItem (&s_servers[serverindex].serverinfo_rows[7], &s_servers[serverindex].serverinfo_columns[7][0]);
3625 
3626 	ModList_SubmenuInit ();
3627 	LINK (s_servers[serverindex].serverinfo_columns[0][1].generic.x, s_servers[serverindex].modlist_submenu.generic.x);
3628 	Menu_AddItem (&s_servers[serverindex].serverinfo_rows[7], &s_servers[serverindex].modlist_submenu);
3629 
3630 	// don't add it to serverinfo_table because serverinfo_table isn't navagable
3631 	if (s_servers[serverindex].modlist_submenu.nitems != 0)
3632 		Menu_AddItem (&s_servers[serverindex].serverinfo_submenu, &s_servers[serverindex].serverinfo_rows[7]);
3633 
3634 	Menu_AddItem (&s_servers[serverindex].menu, &s_servers[serverindex].serverinfo_submenu);
3635 }
3636 
PlayerList_SubmenuInit(void)3637 void PlayerList_SubmenuInit (void)
3638 {
3639 	int i, j;
3640 
3641 	char *local_player_info_ptrs[MAX_PLAYERS*SVDATA_PLAYERINFO];
3642 	size_t sizes[3] = {sizeof(menutxt_s), sizeof(menutxt_s), sizeof(menutxt_s)};
3643 
3644 	if (mservers[serverindex].players == 0)
3645 		return;
3646 
3647 	s_servers[serverindex].playerlist_submenu.generic.type = MTYPE_SUBMENU;
3648 	s_servers[serverindex].playerlist_submenu.navagable = true;
3649 	s_servers[serverindex].playerlist_submenu.nitems = 0;
3650 
3651 	Menu_AddItem (&s_servers[serverindex].menu, &s_servers[serverindex].playerlist_submenu);
3652 
3653 	s_servers[serverindex].playerlist_label.generic.type = MTYPE_TEXT;
3654 	s_servers[serverindex].playerlist_label.generic.flags = QMF_RIGHT_COLUMN;
3655 	s_servers[serverindex].playerlist_label.generic.name = "Players:";
3656 	Menu_AddItem (&s_servers[serverindex].playerlist_submenu, &s_servers[serverindex].playerlist_label);
3657 
3658 	s_servers[serverindex].playerlist_scrollingmenu.generic.type = MTYPE_SUBMENU;
3659 	s_servers[serverindex].playerlist_scrollingmenu.navagable = true;
3660 	s_servers[serverindex].playerlist_scrollingmenu.bordertexture = "menu/sm_";
3661 	s_servers[serverindex].playerlist_scrollingmenu.nitems = 0;
3662 
3663 	s_servers[serverindex].playerlist_header.generic.type = MTYPE_SUBMENU;
3664 	s_servers[serverindex].playerlist_header.horizontal = true;
3665 	s_servers[serverindex].playerlist_header.nitems = 0;
3666 
3667 	s_servers[serverindex].playerlist_header_columns[SVDATA_PLAYERINFO_NAME].generic.name	= "^7Name";
3668 	s_servers[serverindex].playerlist_header_columns[SVDATA_PLAYERINFO_SCORE].generic.name	= "^7Score";
3669 	s_servers[serverindex].playerlist_header_columns[SVDATA_PLAYERINFO_PING].generic.name	= "^7Ping";
3670 	for (i = 0; i < SVDATA_PLAYERINFO; i++)
3671 	{
3672 		s_servers[serverindex].playerlist_header_columns[i].generic.type			= MTYPE_TEXT;
3673 		if (i > 0)
3674 			s_servers[serverindex].playerlist_header_columns[i].generic.flags	= QMF_RIGHT_COLUMN;
3675 		Menu_AddItem (&s_servers[serverindex].playerlist_header, &s_servers[serverindex].playerlist_header_columns[i]);
3676 	}
3677 
3678 	Menu_AddItem (&s_servers[serverindex].playerlist_submenu, &s_servers[serverindex].playerlist_header);
3679 
3680 	for (i = 0; i < mservers[serverindex].players; i++)
3681 	{
3682 		int ranking = mservers[serverindex].playerRankings[i];
3683 		if (ranking == 1000)
3684 			Com_sprintf(s_servers[serverindex].ranktxt[i], sizeof(s_servers[serverindex].ranktxt[i]), "Player is unranked");
3685 		else
3686 			Com_sprintf(s_servers[serverindex].ranktxt[i], sizeof(s_servers[serverindex].ranktxt[i]), "Player is ranked %i", ranking);
3687 		s_servers[serverindex].playerlist_rows[i].generic.tooltip = s_servers[serverindex].ranktxt[i];
3688 
3689 		for (j = 0; j < SVDATA_PLAYERINFO; j++)
3690 			local_player_info_ptrs[i*SVDATA_PLAYERINFO+j] = &mservers[serverindex].playerInfo[i][j][0];
3691 	}
3692 
3693 	Menu_MakeTable	(	&s_servers[serverindex].playerlist_scrollingmenu,
3694 						mservers[serverindex].players, SVDATA_PLAYERINFO,
3695 						sizes, &s_servers[serverindex].playerlist_header,
3696 						s_servers[serverindex].playerlist_rows, s_servers[serverindex].playerlist_columns,
3697 						local_player_info_ptrs
3698 					);
3699 
3700 	Menu_AddItem (&s_servers[serverindex].playerlist_submenu, &s_servers[serverindex].playerlist_scrollingmenu);
3701 
3702 	s_servers[serverindex].playerlist_scrollingmenu.maxlines = 7;
3703 
3704 	s_servers[serverindex].playerlist_scrollingmenu.nitems = mservers[serverindex].players;
3705 	s_servers[serverindex].playerlist_scrollingmenu.yscroll = 0;
3706 
3707 	LINK (s_servers[serverindex].serverinfo_submenu.rwidth, s_servers[serverindex].playerlist_scrollingmenu.rwidth);
3708 	LINK (s_servers[serverindex].serverinfo_submenu.lwidth, s_servers[serverindex].playerlist_scrollingmenu.lwidth);
3709 }
3710 
M_Menu_SelectedServer_f(void)3711 static void M_Menu_SelectedServer_f (void)
3712 {
3713 	setup_window (s_servers[serverindex].screen, s_servers[serverindex].menu, "SERVER");
3714 
3715 	ServerInfo_SubmenuInit ();
3716 	PlayerList_SubmenuInit ();
3717 
3718 	// "connect" button at the bottom
3719 	s_servers[serverindex].connect.generic.type = MTYPE_ACTION;
3720 	s_servers[serverindex].connect.generic.flags = QMF_BUTTON | QMF_RIGHT_COLUMN;
3721 	s_servers[serverindex].connect.generic.name = "Connect";
3722 	s_servers[serverindex].connect.generic.callback = JoinServerFunc;
3723 	Menu_AddItem (&s_servers[serverindex].menu, &s_servers[serverindex].connect);
3724 
3725 	s_servers[serverindex].serverinfo_submenu.statusbar = NULL;
3726 	s_servers[serverindex].connect.generic.statusbar = NULL;
3727 	if (serverIsOutdated (mservers[serverindex].szVersion))
3728 		s_servers[serverindex].serverinfo_submenu.statusbar = "Warning: server is ^1outdated!^7 It may have bugs or different gameplay.";
3729 	else if (!PLAYER_NAME_UNIQUE)
3730 		s_servers[serverindex].connect.generic.statusbar = "You must change your player name from the default before connecting!";
3731 	else
3732 		s_servers[serverindex].connect.generic.statusbar = "Hit ENTER or CLICK to connect";
3733 
3734 	M_PushMenu_Defaults (s_servers[serverindex].screen);
3735 
3736 	s_servers[serverindex].menu.default_cursor_selection = (menuitem_s *)&s_servers[serverindex].connect;
3737 }
3738 
3739 //TODO: Move this out of the menu section!
M_ParseServerInfo(netadr_t adr,char * status_string,SERVERDATA * destserver)3740 qboolean M_ParseServerInfo (netadr_t adr, char *status_string, SERVERDATA *destserver)
3741 {
3742 	char *rLine;
3743 	char *token;
3744 #ifdef TACTICAL
3745 	char *token2;
3746 	char modstring[64];
3747 	qboolean isTactical;
3748 	int i;
3749 #endif
3750 	char skillLevel[24];
3751 	char lasttoken[256];
3752 	char seps[]   = "\\";
3753 	int players = 0;
3754 	int bots = 0;
3755 	int result;
3756 
3757 	char playername[PLAYERNAME_SIZE];
3758 	int score, ping, rankTotal, starttime;
3759 	PLAYERSTATS	player;
3760 
3761 	destserver->local_server_netadr = adr;
3762 	// starttime now sourced per server.
3763 	starttime = CL_GetPingStartTime(adr);
3764 	if (starttime != 0)
3765 		destserver->ping = Sys_Milliseconds() - starttime;
3766 	else
3767 	{
3768 		// Local LAN?
3769 		destserver->ping = 1;
3770 	}
3771 	if ( destserver->ping < 1 )
3772 		destserver->ping = 1; /* for LAN and address book entries */
3773 
3774 	//parse it
3775 
3776 	result = strlen(status_string);
3777 
3778 	//server info
3779 	rLine = GetLine (&status_string, &result);
3780 
3781 	/* Establish string and get the first token: */
3782 	token = strtok( rLine, seps );
3783 	if ( token != NULL )
3784 	{
3785 		Com_sprintf(lasttoken, sizeof(lasttoken), "%s", token);
3786 		token = strtok( NULL, seps );
3787 	}
3788 
3789 	// HACK for backward compatibility
3790 	memset (destserver->modInfo, 0, sizeof(destserver->modInfo));
3791 
3792 	/* Loop through the rest of them */
3793 	while( token != NULL )
3794 	{
3795 		/* While there are tokens in "string" */
3796 		if (!Q_strcasecmp (lasttoken, "admin"))
3797 			Com_sprintf(destserver->szAdmin, sizeof(destserver->szAdmin), "%s", token);
3798 		else if (!Q_strcasecmp (lasttoken, "website"))
3799 			Com_sprintf(destserver->szWebsite, sizeof(destserver->szWebsite), "%s", token);
3800 		else if (!Q_strcasecmp (lasttoken, "fraglimit"))
3801 			Com_sprintf(destserver->fraglimit, sizeof(destserver->fraglimit), "%s", token);
3802 		else if (!Q_strcasecmp (lasttoken, "timelimit"))
3803 			Com_sprintf(destserver->timelimit, sizeof(destserver->timelimit), "%s", token);
3804 		else if (!Q_strcasecmp (lasttoken, "version"))
3805 			Com_sprintf(destserver->szVersion, sizeof(destserver->szVersion), "%s", token);
3806 		else if (!Q_strcasecmp (lasttoken, "mapname"))
3807 		{
3808 			Com_sprintf(destserver->szMapName, sizeof(destserver->szMapName), "%s", token);
3809 			Com_sprintf(destserver->fullMapName, sizeof(destserver->fullMapName), "%s", token);
3810 		}
3811 		else if (!Q_strcasecmp (lasttoken, "hostname"))
3812 			Com_sprintf(destserver->szHostName, sizeof(destserver->szHostName), "%s", token);
3813 		else if (!Q_strcasecmp (lasttoken, "maxclients"))
3814 			Com_sprintf(destserver->maxClients, sizeof(destserver->maxClients), "%s", token);
3815 		else if (!Q_strcasecmp (lasttoken, "mods"))
3816 			Com_sprintf(destserver->modInfo, sizeof(destserver->modInfo), "%s", token);
3817 		else if (!Q_strcasecmp (lasttoken, "sv_joustmode"))
3818 			destserver->joust = atoi(token);
3819 
3820 		/* Get next token: */
3821 		Com_sprintf(lasttoken, sizeof(lasttoken), "%s", token);
3822 		token = strtok( NULL, seps );
3823 	}
3824 
3825 	free (rLine);
3826 
3827 #ifdef TACTICAL
3828 	isTactical = false;
3829 
3830 	//Copy modstring over since strtok will modify it
3831 	Q_strncpyz(modstring, destserver->modInfo, sizeof(modstring));
3832 
3833 	// populate all the data
3834 	token2 = strtok(modstring, "%%");
3835 	for (i = 0; i < MAX_SERVER_MODS; i++)
3836 	{
3837 		if (!token2)
3838 			break;
3839 
3840 		if(!strcmp("g_tactical", token2))
3841 			isTactical = true;
3842 
3843 		token2 = strtok(NULL, "%%");
3844 	}
3845 	if(!isTactical)
3846 		return false;
3847 #endif
3848 
3849 	//playerinfo
3850 	rankTotal = 0;
3851 	strcpy (seps, " ");
3852 	while ((rLine = GetLine (&status_string, &result)) && players < MAX_PLAYERS)
3853 	{
3854 		/* Establish string and get the first token: */
3855 		token = strtok( rLine, seps);
3856 		score = atoi(token);
3857 
3858 		token = strtok( NULL, seps);
3859 		ping = atoi(token);
3860 
3861 		token = strtok( NULL, "\"");
3862 
3863 		if (token)
3864 			strncpy (playername, token, sizeof(playername)-1);
3865 		else
3866 			playername[0] = '\0';
3867 
3868 		free (rLine);
3869 
3870 		playername[sizeof(playername)-1] = '\0';
3871 
3872 		//get ranking
3873 		Q_strncpyz2( player.playername, playername, sizeof(player.playername));
3874 		player.totalfrags = player.totaltime = player.ranking = 0;
3875 		player = getPlayerRanking ( player );
3876 
3877 		Com_sprintf	(	destserver->playerInfo[players][SVDATA_PLAYERINFO_NAME],
3878 						SVDATA_PLAYERINFO_COLSIZE,
3879 						"%s", playername
3880 					);
3881 		Com_sprintf	(	destserver->playerInfo[players][SVDATA_PLAYERINFO_SCORE],
3882 						SVDATA_PLAYERINFO_COLSIZE,
3883 						"%i", score
3884 					);
3885 		Com_sprintf	(	destserver->playerInfo[players][SVDATA_PLAYERINFO_PING],
3886 						SVDATA_PLAYERINFO_COLSIZE,
3887 						"%i", ping
3888 					);
3889 		destserver->playerRankings[players] = player.ranking;
3890 
3891 		rankTotal += player.ranking;
3892 
3893 		players++;
3894 
3895 		if(ping == 0)
3896 			bots++;
3897 	}
3898 
3899 	if(players)
3900 	{
3901 		if(thisPlayer.ranking < (rankTotal/players) - 100)
3902 			strcpy(skillLevel, "Your Skill is ^1Higher");
3903 		else if(thisPlayer.ranking > (rankTotal/players + 100))
3904 			strcpy(skillLevel, "Your Skill is ^4Lower");
3905 		else
3906 			strcpy(skillLevel, "Your Skill is ^3Even");
3907 
3908 		Com_sprintf(destserver->skill, sizeof(destserver->skill), "%s", skillLevel);
3909 	}
3910 	else
3911 		Com_sprintf(destserver->skill, sizeof(destserver->skill), "Unknown");
3912 
3913 	destserver->players = players;
3914 
3915 	//build the string for the server (hostname - address - mapname - players/maxClients)
3916 	if(strlen(destserver->maxClients) > 2)
3917 		strcpy(destserver->maxClients, "??");
3918 
3919 	Com_sprintf (destserver->szPlayers, sizeof(destserver->szPlayers), "%i(%i)/%s", min(99,players), min(99,bots), destserver->maxClients);
3920 	Com_sprintf (destserver->szPing, sizeof(destserver->szPing), "%i", min(9999,destserver->ping));
3921 
3922 	return true;
3923 }
3924 
3925 static menuframework_s	s_serverbrowser_screen;
3926 
3927 static menuframework_s	s_joinserver_menu;
3928 
3929 static menuframework_s	s_joinserver_header;
3930 
3931 static menuframework_s	s_serverlist_submenu;
3932 static menuframework_s	s_serverlist_header;
3933 static menulist_s		s_serverlist_header_columns[SERVER_LIST_COLUMNS];
3934 static menuframework_s	s_serverlist_rows[MAX_LOCAL_SERVERS];
3935 static menutxt_s		s_serverlist_columns[MAX_LOCAL_SERVERS][SERVER_LIST_COLUMNS];
3936 
M_AddToServerList(netadr_t adr,char * status_string)3937 void M_AddToServerList (netadr_t adr, char *status_string)
3938 {
3939 	//if by some chance this gets called without the menu being up, return
3940 	if(cls.key_dest != key_menu)
3941 		return;
3942 
3943 	if (m_num_servers == MAX_LOCAL_SERVERS)
3944 		return;
3945 
3946 	if(M_ParseServerInfo (adr, status_string, &mservers[m_num_servers]))
3947 	{
3948 
3949 		CON_Clear();
3950 
3951 		m_num_servers++;
3952 	}
3953 }
3954 
M_UpdateConnectedServerInfo(netadr_t adr,char * status_string)3955 void M_UpdateConnectedServerInfo (netadr_t adr, char *status_string)
3956 {
3957 	M_ParseServerInfo (adr, status_string, &connectedserver);
3958 	remoteserver_jousting = connectedserver.joust;
3959 }
3960 
DeselectServer(void)3961 void DeselectServer (void)
3962 {
3963 	serverindex = -1;
3964 	s_servers[serverindex].serverinfo_submenu.nitems = 0;
3965 	s_servers[serverindex].playerlist_scrollingmenu.nitems = 0;
3966 	s_servers[serverindex].modlist_submenu.nitems = 0;
3967 }
3968 
SelectServer(int index)3969 void SelectServer (int index)
3970 {
3971 	// used if the player hits enter without his mouse over the server list
3972 	serverindex = index;
3973 
3974 	M_Menu_SelectedServer_f ();
3975 }
3976 
3977 //join on double click, return info on single click - to do - might consider putting player info in a tooltip on single click/right click
ClickServerFunc(void * self)3978 void ClickServerFunc( void *self )
3979 {
3980 	int		index = ( menuframework_s * ) self - s_serverlist_rows;
3981 
3982 	if(serverindex != index)
3983 	{
3984 		SelectServer (index);
3985 		if (cursor.buttonclicks[MOUSEBUTTON1] != 2)
3986 			return;
3987 	}
3988 
3989 	if(!PLAYER_NAME_UNIQUE) {
3990 		M_Menu_PlayerConfig_f();
3991 		return;
3992 	}
3993 
3994 	JoinServerFunc (NULL);
3995 }
3996 
AddressBookFunc(void * self)3997 void AddressBookFunc( void *self )
3998 {
3999 	M_Menu_AddressBook_f();
4000 }
4001 
PlayerRankingFunc(void * self)4002 void PlayerRankingFunc( void *self )
4003 {
4004 	M_Menu_PlayerRanking_f();
4005 }
4006 
SearchLocalGames(void)4007 void SearchLocalGames( void )
4008 {
4009 	m_num_servers = 0;
4010 	DeselectServer ();
4011 	s_serverlist_submenu.nitems = 0;
4012 	s_serverlist_submenu.yscroll = 0;
4013 
4014 	Draw_Fill (0, 0, viddef.width, viddef.height, RGBA (0, 0, 0, 0.85));
4015 	SCR_CenterPrint ("Fetching server list...");
4016 	SCR_DrawCenterString ();
4017 	R_EndFrame ();
4018 
4019 	// send out info packets
4020 	CL_PingServers_f();
4021 
4022 	CON_Clear();
4023 
4024 	Com_Printf (" Got %d servers- stragglers may follow.\n", m_num_servers);
4025 }
4026 
SearchLocalGamesFunc(void * self)4027 void SearchLocalGamesFunc( void *self )
4028 {
4029 	SearchLocalGames();
4030 }
4031 
4032 static qboolean QSortReverse;
4033 static int QSortColumn;
4034 
SortServerList_Compare(const void * _a,const void * _b)4035 static int SortServerList_Compare (const void *_a, const void *_b)
4036 {
4037 	int ret = 0;
4038 	const menuframework_s *a, *b;
4039 	const char *a_s, *b_s;
4040 
4041 	a = *(menuframework_s **)_a;
4042 	b = *(menuframework_s **)_b;
4043 
4044 	a_s = ((menutxt_s *)(a->items[QSortColumn]))->generic.name;
4045 	b_s = ((menutxt_s *)(b->items[QSortColumn]))->generic.name;
4046 
4047 	if (QSortColumn > 1)
4048 	{
4049 		// do numeric sort for player count and ping
4050 		if (atoi (a_s) > atoi (b_s))
4051 			ret = 1;
4052 		else if (atoi (a_s) < atoi (b_s))
4053 			ret = -1;
4054 	}
4055 	else
4056 		// because strcmp doesn't handle ^colors
4057 		while (*a_s && *b_s)
4058 		{
4059 			if (*a_s == '^')
4060 			{
4061 				a_s++;
4062 			}
4063 			else if (*b_s == '^')
4064 			{
4065 				b_s++;
4066 			}
4067 			else if (tolower(*a_s) > tolower(*b_s))
4068 			{
4069 				ret = 1;
4070 				break;
4071 			}
4072 			else if (tolower(*a_s) < tolower(*b_s))
4073 			{
4074 				ret = -1;
4075 				break;
4076 			}
4077 			a_s++;
4078 			b_s++;
4079 		}
4080 
4081 	if (QSortReverse)
4082 		return -ret;
4083 	return ret;
4084 }
4085 
SortServerList_Func(void * _self)4086 static void SortServerList_Func ( void *_self )
4087 {
4088 	int column_num, i;
4089 	menulist_s *self = (menulist_s *)_self;
4090 
4091 	column_num = self-s_serverlist_header_columns;
4092 
4093 	for (i = 0; i < SERVER_LIST_COLUMNS; i++)
4094 		if (i != column_num)
4095 			s_serverlist_header_columns[i].curvalue = 0;
4096 
4097 	if (self->curvalue == 0)
4098 	{
4099 		if (column_num == 3)
4100 		{
4101 			self->curvalue = 1;
4102 		}
4103 		else
4104 		{
4105 			s_serverlist_header_columns[3].curvalue = 1;
4106 			SortServerList_Func (&s_serverlist_header_columns[3]);
4107 			return;
4108 		}
4109 	}
4110 
4111 	QSortColumn = column_num;
4112 	QSortReverse = self->curvalue == 2;
4113 
4114 	qsort (s_serverlist_submenu.items, s_serverlist_submenu.nitems, sizeof (void*), SortServerList_Compare);
4115 	s_serverlist_submenu.yscroll = 0;
4116 }
4117 
ServerList_SubmenuInit(void)4118 void ServerList_SubmenuInit (void)
4119 {
4120 	int i, j;
4121 
4122 	s_serverlist_submenu.generic.type = MTYPE_SUBMENU;
4123 	s_serverlist_submenu.generic.flags = QMF_SUBMENU_CAPTURE;
4124 	s_serverlist_submenu.navagable = true;
4125 	s_serverlist_submenu.nitems = 0;
4126 	s_serverlist_submenu.bordertexture = "menu/sm_";
4127 
4128 	s_serverlist_header.generic.type = MTYPE_SUBMENU;
4129 	s_serverlist_header.horizontal = true;
4130 	s_serverlist_header.navagable = true;
4131 	s_serverlist_header.nitems = 0;
4132 
4133 	s_serverlist_header_columns[0].generic.name = "^3Server";
4134 	s_serverlist_header_columns[1].generic.name = "^3Map";
4135 	s_serverlist_header_columns[2].generic.name = "^3Players";
4136 	s_serverlist_header_columns[3].generic.name = "^3Ping";
4137 
4138 	for (j = 0; j < SERVER_LIST_COLUMNS; j++)
4139 	{
4140 		s_serverlist_header_columns[j].generic.type = MTYPE_SPINCONTROL;
4141 		s_serverlist_header_columns[j].generic.flags = QMF_RIGHT_COLUMN|QMF_ALLOW_WRAP;
4142 		s_serverlist_header_columns[j].itemnames = updown_names;
4143 		s_serverlist_header_columns[j].generic.itemsizecallback = IconSpinSizeFunc;
4144 		s_serverlist_header_columns[j].generic.itemdraw = IconSpinDrawFunc;
4145 		s_serverlist_header_columns[j].curvalue = 0;
4146 		s_serverlist_header_columns[j].generic.callback = SortServerList_Func;
4147 		Menu_AddItem (&s_serverlist_header, &s_serverlist_header_columns[j]);
4148 	}
4149 	s_serverlist_header_columns[3].curvalue = 1;
4150 
4151 	Menu_AddItem (&s_joinserver_menu, &s_serverlist_header);
4152 
4153 	for ( i = 0; i < MAX_LOCAL_SERVERS; i++ )
4154 	{
4155 		s_serverlist_rows[i].generic.type	= MTYPE_SUBMENU;
4156 		s_serverlist_rows[i].generic.callback = ClickServerFunc;
4157 		s_serverlist_rows[i].nitems = 0;
4158 		s_serverlist_rows[i].horizontal = true;
4159 		s_serverlist_rows[i].enable_highlight = true;
4160 
4161 		s_serverlist_columns[i][0].generic.name = mservers[i].szHostName;
4162 		s_serverlist_columns[i][1].generic.name = mservers[i].szMapName;
4163 		s_serverlist_columns[i][2].generic.name = mservers[i].szPlayers;
4164 		s_serverlist_columns[i][3].generic.name = mservers[i].szPing;
4165 
4166 		for (j = 0; j < SERVER_LIST_COLUMNS; j++)
4167 		{
4168 			s_serverlist_columns[i][j].generic.type = MTYPE_TEXT;
4169 			s_serverlist_columns[i][j].generic.flags = QMF_RIGHT_COLUMN;
4170 			LINK(s_serverlist_header_columns[j].generic.x, s_serverlist_columns[i][j].generic.x);
4171 			Menu_AddItem (&s_serverlist_rows[i], &s_serverlist_columns[i][j]);
4172 		}
4173 
4174 		LINK(s_serverlist_header.lwidth, s_serverlist_rows[i].lwidth);
4175 		LINK(s_serverlist_header.rwidth, s_serverlist_rows[i].rwidth);
4176 
4177 		Menu_AddItem( &s_serverlist_submenu, &s_serverlist_rows[i] );
4178 	}
4179 
4180 	Menu_AddItem (&s_joinserver_menu, &s_serverlist_submenu);
4181 
4182 	s_serverlist_submenu.maxlines = 25;
4183 
4184 }
4185 
ServerListHeader_SubmenuInit(void)4186 void ServerListHeader_SubmenuInit (void)
4187 {
4188 	s_joinserver_header.generic.type = MTYPE_SUBMENU;
4189 	s_joinserver_header.nitems = 0;
4190 	s_joinserver_header.horizontal = true;
4191 	s_joinserver_header.navagable = true;
4192 
4193 	// doesn't actually do anything yet
4194 	// add_action (s_joinserver_header, "Address Book", AddressBookFunc, 0);
4195 	add_action (s_joinserver_header, "Refresh", SearchLocalGamesFunc, 0);
4196 	add_action (s_joinserver_header, "Rank/Stats", PlayerRankingFunc, 0);
4197 
4198 	Menu_AddItem (&s_joinserver_menu, &s_joinserver_header);
4199 }
4200 
M_Menu_JoinServer_f(void)4201 static void M_Menu_JoinServer_f (void)
4202 {
4203 	extern cvar_t *name;
4204 
4205 	static qboolean gotServers = false;
4206 
4207 	if(!gotServers)
4208 	{
4209 		STATS_getStatsDB();
4210 		getLatestGameVersion();
4211 	}
4212 
4213 	ValidatePlayerName( name->string, (strlen(name->string)+1) );
4214 	Q_strncpyz2( thisPlayer.playername, name->string, sizeof(thisPlayer.playername) );
4215 	thisPlayer.totalfrags = thisPlayer.totaltime = thisPlayer.ranking = 0;
4216 	thisPlayer = getPlayerRanking ( thisPlayer );
4217 
4218 	serverindex = -1;
4219 
4220 	if (!gotServers)
4221 	{
4222 		setup_window (s_serverbrowser_screen, s_joinserver_menu, "SERVER LIST");
4223 
4224 		ServerListHeader_SubmenuInit ();
4225 		ServerList_SubmenuInit ();
4226 
4227 		SearchLocalGames();
4228 
4229 		s_joinserver_menu.default_cursor_selection = (menuitem_s *)&s_serverlist_submenu;
4230 	}
4231 
4232 	gotServers = true;
4233 
4234 	M_PushMenu_Defaults (s_serverbrowser_screen);
4235 }
4236 
4237 /*
4238 =============================================================================
4239 
4240 MUTATORS MENU
4241 
4242 =============================================================================
4243 */
4244 static menuframework_s s_mutators_screen;
4245 static menuframework_s s_mutators_menu;
4246 
4247 // weapon modes are different from regular mutators in that they cannot be
4248 // combined
4249 static const char *weaponModeNames[][2] =
4250 {
4251 	{"instagib",		"instagib"},
4252 	{"rocket arena",	"rocket_arena"},
4253 	{"insta/rockets",	"insta_rockets"},
4254 	{"excessive",		"excessive"},
4255 	{"class based",		"classbased"}
4256 };
4257 #define num_weapon_modes static_array_size(weaponModeNames)
4258 static menulist_s s_weaponmode_list[num_weapon_modes];
4259 
4260 static const char *mutatorNames[][2] =
4261 {
4262 	{"vampire",			"vampire"},
4263 	{"regen",			"regeneration"},
4264 	{"quick weapons",	"quickweap"},
4265 	{"anticamp",		"anticamp"},
4266 	{"speed",			"playerspeed"},
4267 	{"low gravity",		"low_grav"},
4268 	{"jousting",		"sv_joustmode"},
4269 	{"grapple hook",	"grapple"}
4270 };
4271 #define num_mutators static_array_size(mutatorNames)
4272 static menulist_s s_mutator_list[num_mutators];
4273 static menufield_s s_camptime;
4274 
4275 static char dmflags_display_buffer[128];
4276 
DMFlagCallback(void * self)4277 static void DMFlagCallback( void *self )
4278 {
4279 	menulist_s *f = ( menulist_s * ) self;
4280 	int flags;
4281 	int bit;
4282 	qboolean invert, enabled;
4283 
4284 	flags = Cvar_VariableValue( "dmflags" );
4285 
4286 	if (f != NULL)
4287 	{
4288 		bit = f->generic.localints[0];
4289 		invert = f->generic.localints[1];
4290 		enabled = f->curvalue != 0;
4291 
4292 		if (invert != enabled)
4293 			flags |= bit;
4294 		else
4295 			flags &= ~bit;
4296 	}
4297 
4298 	Cvar_SetValue ("dmflags", flags);
4299 
4300 	Com_sprintf( dmflags_display_buffer, sizeof( dmflags_display_buffer ), "(dmflags = %d)", flags );
4301 }
4302 
4303 typedef struct {
4304 	char		*display_name;
4305 	qboolean	invert;
4306 	int			bit;
4307 } DMFlag_control_t;
4308 
4309 static const DMFlag_control_t dmflag_control_names[] = {
4310 	{"falling damage",		true,	DF_NO_FALLING},
4311 	{"weapons stay",		false,	DF_WEAPONS_STAY},
4312 	{"instant powerups",	false,	DF_INSTANT_ITEMS},
4313 	{"allow powerups",		true,	DF_NO_ITEMS},
4314 	{"allow health",		true,	DF_NO_HEALTH},
4315 	{"allow armor",			true,	DF_NO_ARMOR},
4316 	{"spawn farthest",		false,	DF_SPAWN_FARTHEST},
4317 	{"same map",			false,	DF_SAME_LEVEL},
4318 	{"force respawn",		false,	DF_FORCE_RESPAWN},
4319 	{"team deathmatch",		false,	DF_SKINTEAMS},
4320 	{"allow exit", 			false,	DF_ALLOW_EXIT},
4321 	{"infinite ammo",		false,	DF_INFINITE_AMMO},
4322 	{"quad drop",			false,	DF_QUAD_DROP},
4323 	{"friendly fire",		true,	DF_NO_FRIENDLY_FIRE},
4324 	{"bot chat",			false,	DF_BOTCHAT},
4325 	{"bot fuzzy aim",		false,	DF_BOT_FUZZYAIM},
4326 	{"auto node save",		false,	DF_BOT_AUTOSAVENODES},
4327 	{"repeat level if "
4328 	 "bot wins",			true,	DF_BOT_LEVELAD},
4329 	{"bots in game",		true,	DF_BOTS}
4330 };
4331 #define num_dmflag_controls static_array_size(dmflag_control_names)
4332 
4333 static menuframework_s	s_dmflags_submenu;
4334 static menulist_s		s_dmflag_controls[num_dmflag_controls];
4335 
SetWeaponModeFunc(void * _self)4336 void SetWeaponModeFunc(void *_self)
4337 {
4338 	menulist_s *self;
4339 	int i, value;
4340 
4341 	self = (menulist_s*)_self;
4342 
4343 	value = self->curvalue;
4344 
4345 	if (self->curvalue)
4346 	{
4347 		for (i = 0; i < num_weapon_modes; i++)
4348 		{
4349 			Cvar_SetValue (weaponModeNames[i][1], 0);
4350 			s_weaponmode_list[i].curvalue = 0;
4351 		}
4352 	}
4353 
4354 	Cvar_SetValue (self->generic.localstrings[0], value);
4355 	self->curvalue = value;
4356 }
4357 
M_Menu_Mutators_f(void)4358 static void M_Menu_Mutators_f (void)
4359 {
4360 	int i;
4361 
4362 	int dmflags = Cvar_VariableValue( "dmflags" );
4363 
4364 	setup_window (s_mutators_screen, s_mutators_menu, "MUTATORS");
4365 
4366 	for (i = 0; i < num_weapon_modes; i++)
4367 	{
4368 		s_weaponmode_list[i].generic.name = weaponModeNames[i][0];
4369 		s_weaponmode_list[i].generic.callback = SetWeaponModeFunc;
4370 		s_weaponmode_list[i].generic.localstrings[0] = weaponModeNames[i][1];
4371 		s_weaponmode_list[i].curvalue = Cvar_VariableValue (weaponModeNames[i][1]);
4372 		setup_radiobutton (s_weaponmode_list[i]);
4373 		Menu_AddItem (&s_mutators_menu, &s_weaponmode_list[i]);
4374 	}
4375 
4376 	s_camptime.generic.type = MTYPE_FIELD;
4377 	s_camptime.generic.name = "camp time";
4378 	s_camptime.generic.flags = QMF_NUMBERSONLY;
4379 	s_camptime.generic.localstrings[0] = "camptime";
4380 	s_camptime.length = 3;
4381 	s_camptime.generic.visible_length = 3;
4382 	strcpy( s_camptime.buffer, Cvar_VariableString("camptime") );
4383 	s_camptime.generic.callback = IntFieldCallback;
4384 
4385 	for (i = 0; i < num_mutators; i++)
4386 	{
4387 		s_mutator_list[i].generic.name = mutatorNames[i][0];
4388 		s_mutator_list[i].generic.callback = SpinOptionFunc;
4389 		s_mutator_list[i].generic.localstrings[0] = weaponModeNames[i][1];
4390 		s_mutator_list[i].curvalue = Cvar_VariableValue (mutatorNames[i][1]);
4391 		setup_tickbox (s_mutator_list[i]);
4392 		Menu_AddItem (&s_mutators_menu, &s_mutator_list[i]);
4393 
4394 		// camptime goes after anticamp control-- we put this here so we can
4395 		// insert it in the right place in the menu
4396 		if (!strcmp (mutatorNames[i][0], "anticamp"))
4397 			Menu_AddItem( &s_mutators_menu, &s_camptime );
4398 	}
4399 
4400 	add_text (s_mutators_menu, dmflags_display_buffer, 0);
4401 
4402 	s_dmflags_submenu.generic.type = MTYPE_SUBMENU;
4403 	s_dmflags_submenu.generic.flags = QMF_SNUG_LEFT | QMF_SUBMENU_CAPTURE;
4404 	s_dmflags_submenu.navagable = true;
4405 	s_dmflags_submenu.bordertexture = "menu/sm_";
4406 	s_dmflags_submenu.nitems = 0;
4407 	s_dmflags_submenu.maxlines = 15;
4408 	for (i = 0; i < num_dmflag_controls; i++)
4409 	{
4410 		s_dmflag_controls[i].generic.name = dmflag_control_names[i].display_name;
4411 		s_dmflag_controls[i].generic.callback = DMFlagCallback;
4412 		setup_tickbox (s_dmflag_controls[i]);
4413 		s_dmflag_controls[i].generic.localints[0] = dmflag_control_names[i].bit;
4414 		s_dmflag_controls[i].generic.localints[1] = dmflag_control_names[i].invert;
4415 		s_dmflag_controls[i].curvalue = (dmflags & dmflag_control_names[i].bit) != 0;
4416 		if (dmflag_control_names[i].invert)
4417 		{
4418 			s_dmflag_controls[i].curvalue = s_dmflag_controls[i].curvalue == 0;
4419 		}
4420 
4421 		Menu_AddItem (&s_dmflags_submenu, &s_dmflag_controls[i]);
4422 	}
4423 
4424 	Menu_AddItem (&s_mutators_menu, &s_dmflags_submenu);
4425 
4426 	// initialize the dmflags display buffer
4427 	DMFlagCallback( 0 );
4428 
4429 	M_PushMenu_Defaults (s_mutators_screen);
4430 }
4431 
4432 /*
4433 =============================================================================
4434 
4435 ADD BOTS MENU
4436 
4437 =============================================================================
4438 */
4439 
4440 // For going from weapon pickup name to weapon icon. Used for displaying icon
4441 // previews of the bots' favorite weapons.
4442 static char *weapon_icon_names[][2] =
4443 {
4444 	{"Hover",			"hover"},
4445 	{"Bomber",			"bomber"},
4446 	{"Strafer",			"strafer"},
4447 	{"Grapple",			"grapple"},
4448 	{"Blaster",			"blaster"},
4449 	{"Violator",		"violator"},
4450 	{"Alien Smartgun",	"smartgun"},
4451 	{"Pulse Rifle",		"chaingun"},
4452 	{"Flame Thrower",	"flamethrower"},
4453 	{"Rocket Launcher",	"rocketlauncher"},
4454 	{"Alien Disruptor",	"disruptor"},
4455 	{"Disruptor",		"beamgun"},
4456 	{"Alien Vaporizer",	"vaporizor"} // note the different spellings
4457 };
4458 #define num_weapon_icons static_array_size(weapon_icon_names)
4459 
4460 static menuframework_s	s_addbots_screen;
4461 static menuframework_s	s_addbots_menu;
4462 static menuframework_s	s_addbots_header;
4463 static menutxt_s		s_addbots_name_label;
4464 static menutxt_s		s_addbots_skill_label;
4465 static menutxt_s		s_addbots_faveweap_label;
4466 
4467 int totalbots;
4468 
4469 #define MAX_BOTS 16
4470 struct botdata {
4471 	char	name[32];
4472 	char	model[64];
4473 	char	userinfo[MAX_INFO_STRING];
4474 	char	faveweap[64];
4475 	int		skill;
4476 
4477 	// menu entities
4478 	menuframework_s	row;
4479 	menuaction_s	action;
4480 	char			skill_buf[2];
4481 	menutxt_s		m_skill;
4482 	menutxt_s		m_faveweap;
4483 } bots[MAX_BOTS];
4484 
4485 static menulist_s		s_startmap_list;
4486 static menulist_s		s_rules_box;
4487 static menulist_s   	s_bots_bot_action[8];
4488 #define MAX_MAPS 256
4489 static char *mapnames[MAX_MAPS + 2];
4490 
4491 struct botinfo {
4492 	char name[32];
4493 	char userinfo[MAX_INFO_STRING];
4494 } bot[8];
4495 
4496 int slot;
4497 
LoadBotInfo(void)4498 void LoadBotInfo( void )
4499 {
4500 	FILE *pIn;
4501 	int i, count;
4502 	char *name;
4503 	char *skin;
4504 
4505 	char fullpath[MAX_OSPATH];
4506 
4507 	if ( !FS_FullPath( fullpath, sizeof(fullpath), BOT_GAMEDATA"/allbots.tmp" ) )
4508 	{
4509 		Com_DPrintf("LoadBotInfo: %s/allbots.tmp not found\n", BOT_GAMEDATA );
4510 		return;
4511 	}
4512 	if( (pIn = fopen( fullpath, "rb" )) == NULL )
4513 	{
4514 		Com_DPrintf("LoadBotInfo: failed file open: %s\n", fullpath );
4515 		return;
4516 	}
4517 
4518 	szr = fread(&count,sizeof (int),1,pIn);
4519 	if(count>MAX_BOTS)
4520 		count = MAX_BOTS;
4521 
4522 	for(i=0;i<count;i++)
4523 	{
4524 		char *cfg, *s;
4525 		char cfgpath[MAX_QPATH];
4526 		const char *delim = "\r\n";
4527 
4528 		szr = fread(bots[i].userinfo,sizeof(char) * MAX_INFO_STRING,1,pIn);
4529 
4530 		name = Info_ValueForKey (bots[i].userinfo, "name");
4531 		skin = Info_ValueForKey (bots[i].userinfo, "skin");
4532 		strncpy(bots[i].name, name, sizeof(bots[i].name)-1);
4533 		Com_sprintf (bots[i].model, sizeof(bots[i].model), "bots/%s_i", skin);
4534 
4535 		// defaults for .cfg data
4536 		bots[i].skill = 1; //medium
4537 		strcpy (bots[i].faveweap, "None");
4538 		Com_sprintf (bots[i].skill_buf, sizeof(bots[i].skill_buf), "%d", bots[i].skill);
4539 
4540 		// load info from config file if possible
4541 
4542 		Com_sprintf (cfgpath, sizeof(cfgpath), "%s/%s.cfg", BOT_GAMEDATA, name);
4543 		if( FS_LoadFile (cfgpath, &cfg) == -1 )
4544 		{
4545 			Com_DPrintf("LoadBotInfo: failed file open: %s\n", fullpath );
4546 			continue;
4547 		}
4548 
4549 		if ( (s = strtok( cfg, delim )) != NULL )
4550 			bots[i].skill = atoi( s );
4551 		if ( bots[i].skill < 0 )
4552 			bots[i].skill = 0;
4553 
4554 		Com_sprintf (bots[i].skill_buf, sizeof(bots[i].skill_buf), "%d", bots[i].skill);
4555 
4556 		if ( s && ((s = strtok( NULL, delim )) != NULL) )
4557 			strncpy( bots[i].faveweap, s, sizeof(bots[i].faveweap)-1 );
4558 
4559 		Z_Free (cfg);
4560 	}
4561 	totalbots = count;
4562 	fclose(pIn);
4563 }
4564 
AddbotFunc(void * self)4565 void AddbotFunc(void *self)
4566 {
4567 	int i, count;
4568 	char startmap[MAX_QPATH];
4569 	char bot_filename[MAX_OSPATH];
4570 	FILE *pOut;
4571 	menuframework_s *f = ( menuframework_s * ) self;
4572 
4573 	//get the name and copy that config string into the proper slot name
4574 	for(i = 0; i < totalbots; i++)
4575 	{
4576 		if (f == &bots[i].row)
4577 		{ //this is our selected bot
4578 			strcpy(bot[slot].name, bots[i].name);
4579 			strcpy(bot[slot].userinfo, bots[i].userinfo);
4580 			s_bots_bot_action[slot].generic.name = bots[i].name;
4581 		}
4582 	}
4583 
4584 	//save off bot file
4585 	count = 8;
4586 	for(i = 0; i < 8; i++)
4587 	{
4588 		if(!strcmp(bot[i].name, "...empty slot"))
4589 			count--;
4590 	}
4591 	strcpy( startmap, strchr( mapnames[s_startmap_list.curvalue], '\n' ) + 1 );
4592 	for(i = 0; i < strlen(startmap); i++)
4593 		startmap[i] = tolower(startmap[i]);
4594 
4595 	if(s_rules_box.curvalue == 1 || s_rules_box.curvalue == 4 || s_rules_box.curvalue == 5)
4596 	{ // team game
4597 		FS_FullWritePath( bot_filename, sizeof(bot_filename), BOT_GAMEDATA"/team.tmp" );
4598 	}
4599 	else
4600 	{ // non-team, bots per map
4601 		char relative_path[MAX_QPATH];
4602 		Com_sprintf( relative_path, sizeof(relative_path), BOT_GAMEDATA"/%s.tmp", startmap );
4603 		FS_FullWritePath( bot_filename, sizeof(bot_filename), relative_path );
4604 	}
4605 
4606 	if((pOut = fopen(bot_filename, "wb" )) == NULL)
4607 	{
4608 		Com_DPrintf("AddbotFunc: failed fopen for write: %s\n", bot_filename );
4609 		return; // bail
4610 	}
4611 
4612 	szr = fwrite(&count,sizeof (int),1,pOut); // Write number of bots
4613 
4614 	for (i = 7; i > -1; i--) {
4615 		if(strcmp(bot[i].name, "...empty slot"))
4616 			szr = fwrite(bot[i].userinfo,sizeof (char) * MAX_INFO_STRING,1,pOut);
4617 	}
4618 
4619 	fclose(pOut);
4620 
4621 	//kick back to previous menu
4622 	M_PopMenu();
4623 
4624 }
4625 
M_Menu_AddBots_f(void)4626 static void M_Menu_AddBots_f (void)
4627 {
4628 	int i, j;
4629 
4630 	totalbots = 0;
4631 
4632 	LoadBotInfo();
4633 
4634 	setup_window (s_addbots_screen, s_addbots_menu, "CHOOSE A BOT");
4635 	s_addbots_menu.maxlines = 16;
4636 
4637 	s_addbots_header.generic.type = MTYPE_SUBMENU;
4638 	s_addbots_header.horizontal = true;
4639 	s_addbots_header.nitems = 0;
4640 
4641 	s_addbots_name_label.generic.type = MTYPE_TEXT;
4642 	s_addbots_name_label.generic.name = "^3bot";
4643 	Menu_AddItem (&s_addbots_header, &s_addbots_name_label);
4644 
4645 	s_addbots_skill_label.generic.type = MTYPE_TEXT;
4646 	s_addbots_skill_label.generic.name = "^3skill";
4647 	Menu_AddItem (&s_addbots_header, &s_addbots_skill_label);
4648 
4649 	s_addbots_faveweap_label.generic.type = MTYPE_TEXT;
4650 	s_addbots_faveweap_label.generic.flags = QMF_RIGHT_COLUMN;
4651 	s_addbots_faveweap_label.generic.name = "^3favorite ^3weapon";
4652 	Menu_AddItem (&s_addbots_header, &s_addbots_faveweap_label);
4653 
4654 	Menu_AddItem (&s_addbots_menu, &s_addbots_header);
4655 
4656 	for(i = 0; i < totalbots; i++) {
4657 		bots[i].row.generic.type = MTYPE_SUBMENU;
4658 		bots[i].row.generic.flags = QMF_SNUG_LEFT;
4659 		bots[i].row.nitems = 0;
4660 		bots[i].row.horizontal = true;
4661 		bots[i].row.enable_highlight = true;
4662 
4663 		bots[i].row.generic.callback = AddbotFunc;
4664 
4665 		bots[i].action.generic.type	= MTYPE_ACTION;
4666 		bots[i].action.generic.name	= bots[i].name;
4667 		bots[i].action.generic.localstrings[0] = bots[i].model;
4668 		VectorSet (bots[i].action.generic.localints, 2, 2, RCOLUMN_OFFSET);
4669 		bots[i].action.generic.itemsizecallback = PicSizeFunc;
4670 		bots[i].action.generic.itemdraw = PicDrawFunc;
4671 		LINK(s_addbots_name_label.generic.x, bots[i].action.generic.x);
4672 		Menu_AddItem (&bots[i].row, &bots[i].action);
4673 
4674 		bots[i].m_skill.generic.type = MTYPE_TEXT;
4675 		bots[i].m_skill.generic.name = bots[i].skill_buf;
4676 		LINK(s_addbots_skill_label.generic.x, bots[i].m_skill.generic.x);
4677 		Menu_AddItem (&bots[i].row, &bots[i].m_skill);
4678 
4679 		bots[i].m_faveweap.generic.type = MTYPE_NOT_INTERACTIVE;
4680 		bots[i].m_faveweap.generic.flags = QMF_RIGHT_COLUMN;
4681 		// Start by assuming that we won't find a thumbnail image for the
4682 		// bot's favorite weapon, and set the widget up to simply show the
4683 		// weapon's name.
4684 		bots[i].m_faveweap.generic.itemsizecallback = NULL;
4685 		bots[i].m_faveweap.generic.itemdraw = NULL;
4686 		bots[i].m_faveweap.generic.name = bots[i].faveweap;
4687 		for (j = 0; j < num_weapon_icons; j++)
4688 		{
4689 			if (!strcmp (bots[i].faveweap, weapon_icon_names[j][0]))
4690 			{
4691 				// We have found a matching thumbnail image, so disable the
4692 				// display of text and instead show the image.
4693 				bots[i].m_faveweap.generic.name = NULL;
4694 				VectorSet (bots[i].m_faveweap.generic.localints, 4, 2, 0);
4695 				bots[i].m_faveweap.generic.itemsizecallback = PicSizeFunc;
4696 				bots[i].m_faveweap.generic.itemdraw = PicDrawFunc;
4697 				bots[i].m_faveweap.generic.localstrings[0] = weapon_icon_names[j][1];
4698 				break;
4699 			}
4700 		}
4701 		LINK(s_addbots_faveweap_label.generic.x, bots[i].m_faveweap.generic.x);
4702 		Menu_AddItem (&bots[i].row, &bots[i].m_faveweap);
4703 
4704 		LINK(s_addbots_header.lwidth, bots[i].row.lwidth);
4705 		LINK(s_addbots_header.rwidth, bots[i].row.rwidth);
4706 		Menu_AddItem( &s_addbots_menu, &bots[i].row );
4707 	}
4708 
4709 	M_PushMenu_Defaults (s_addbots_screen);
4710 
4711 }
4712 
4713 /*
4714 =============================================================================
4715 
4716 START SERVER MENU
4717 
4718 =============================================================================
4719 */
4720 
4721 
4722 static menuframework_s s_startserver_screen;
4723 static menuframework_s s_startserver_menu;
4724 static menuframework_s s_startserver_main_submenu;
4725 static int	  nummaps = 0;
4726 
4727 static menufield_s	s_timelimit_field;
4728 static menufield_s	s_fraglimit_field;
4729 static menufield_s	s_maxclients_field;
4730 static menufield_s	s_hostname_field;
4731 static menulist_s	s_antilag_box;
4732 static menulist_s   s_public_box;
4733 static menulist_s	s_dedicated_box;
4734 static menulist_s   s_skill_box;
4735 
4736 static menuframework_s	s_levelshot_submenu;
4737 static menuitem_s		s_levelshot_preview;
4738 static menulist_s   	s_startserver_map_data[5];
4739 
BotOptionsFunc(void * self)4740 void BotOptionsFunc( void *self )
4741 {
4742 	M_Menu_BotOptions_f();
4743 }
4744 
MutatorFunc(void * self)4745 void MutatorFunc( void *self )
4746 {
4747 	M_Menu_Mutators_f();
4748 }
Menu_FindFile(char * filename,FILE ** file)4749 int Menu_FindFile (char *filename, FILE **file)
4750 {
4751 	*file = fopen (filename, "rb");
4752 	if (!*file) {
4753 		*file = NULL;
4754 		return -1;
4755 	}
4756 	return 1;
4757 
4758 }
4759 
MapInfoFunc(void * self)4760 void MapInfoFunc( void *self ) {
4761 
4762 	// FILE *map_file; // unused
4763 	FILE *desc_file;
4764 	char line[500];
4765 	char *pLine;
4766 	char *rLine;
4767 	int result;
4768 	int i;
4769 	char seps[]   = "//";
4770 	char *token;
4771 	char startmap[128];
4772 	char path[MAX_QPATH];
4773 	static char levelshot[MAX_QPATH];
4774 
4775 	//get a map description if it is there
4776 
4777 	if(mapnames[0])
4778 		strcpy( startmap, strchr( mapnames[s_startmap_list.curvalue], '\n' ) + 1 );
4779 	else
4780 		strcpy( startmap, "missing");
4781 
4782 	Com_sprintf(path, sizeof(path), "levelshots/%s.txt", startmap);
4783 	FS_FOpenFile(path, &desc_file);
4784 	if (desc_file) {
4785 		if(fgets(line, 500, desc_file))
4786 		{
4787 			pLine = line;
4788 
4789 			result = strlen(line);
4790 
4791 			rLine = GetLine (&pLine, &result);
4792 
4793 			/* Establish string and get the first token: */
4794 			token = strtok( rLine, seps );
4795 			i = 0;
4796 			while( token != NULL && i < 5) {
4797 
4798 				/* Get next token: */
4799 				token = strtok( NULL, seps );
4800 				/* While there are tokens in "string" */
4801 				s_startserver_map_data[i].generic.type	= MTYPE_TEXT;
4802 				s_startserver_map_data[i].generic.name	= token;
4803 				s_startserver_map_data[i].generic.flags	= QMF_RIGHT_COLUMN;
4804 
4805 				i++;
4806 			}
4807 
4808 		}
4809 
4810 		fclose(desc_file);
4811 
4812 	}
4813 	else
4814 	{
4815 		for (i = 0; i < 5; i++ )
4816 		{
4817 			s_startserver_map_data[i].generic.type	= MTYPE_TEXT;
4818 			s_startserver_map_data[i].generic.name	= "no data";
4819 			s_startserver_map_data[i].generic.flags	= QMF_RIGHT_COLUMN;
4820 		}
4821 	}
4822 
4823 	Com_sprintf( levelshot, sizeof(levelshot), "/levelshots/%s", startmap );
4824 	s_levelshot_preview.generic.localstrings[0] = levelshot;
4825 
4826 }
4827 
4828 static const char *game_mode_names[] =
4829 {
4830 #ifndef TACTICAL
4831 	"deathmatch",
4832 	"ctf",
4833 #endif
4834 	"tactical",
4835 #ifndef TACTICAL
4836 	"all out assault",
4837 	"deathball",
4838 	"team core assault",
4839 	"cattle prod",
4840 	"duel",
4841 #endif
4842 	NULL
4843 };
4844 #define num_game_modes (static_array_size(game_mode_names)-1)
4845 
4846 //same order as game_mode_names
4847 static const char *map_prefixes[num_game_modes][3] =
4848 {
4849 #ifndef TACTICAL
4850 	{"dm", "tourney", NULL},
4851 	{"ctf", NULL},
4852 	{"tac", NULL},
4853 	{"aoa", NULL},
4854 	{"db", NULL},
4855 	{"tca", NULL},
4856 	{"cp", NULL},
4857 	{"dm", "tourney", NULL}
4858 #else
4859 	{"tac", NULL}
4860 #endif
4861 };
4862 
RulesChangeFunc(void * self)4863 void RulesChangeFunc ( void *self ) //this has been expanded to rebuild map list
4864 {
4865 	char *buffer;
4866 	char  mapsname[1024];
4867 	char *s;
4868 	int length;
4869 	int i, k;
4870 	FILE *fp;
4871 	char  shortname[MAX_TOKEN_CHARS];
4872 	char  longname[MAX_TOKEN_CHARS];
4873 	char  scratch[200];
4874 	char *curMap;
4875 	int nmaps = 0;
4876 	int totalmaps;
4877 	char **mapfiles;
4878 	// char *path = NULL; // unused
4879 	static char **bspnames;
4880 	int		j, l;
4881 
4882 	//clear out list first
4883 	for ( i = 0; i < nummaps; i++ )
4884 		free( mapnames[i] );
4885 
4886 	nummaps = 0;
4887 
4888 	/*
4889 	** reload the list of map names, based on rules
4890 	*/
4891 	// maps.lst normally in "data1/"
4892 	//  need  to add a function to FS_ if that is the only place it is allowed
4893 	if ( !FS_FullPath( mapsname, sizeof( mapsname ), "maps.lst" ) )
4894 	{
4895 			Com_Error( ERR_DROP, "couldn't find maps.lst\n" );
4896 		return; // for show, no maps.lst is fatal error
4897 	}
4898 	if ( ( fp = fopen( mapsname, "rb" ) ) == 0 )
4899 	{
4900 		Com_Error( ERR_DROP, "couldn't open maps.lst\n" );
4901 		return; // for "show". above is fatal error.
4902 	}
4903 
4904 	length = FS_filelength( fp );
4905 	buffer = malloc( length + 1 );
4906 	szr = fread( buffer, length, 1, fp );
4907 	buffer[length] = 0;
4908 
4909 	i = 0;
4910 	while ( i < length )
4911 	{
4912 		if ( buffer[i] == '\r' )
4913 			nummaps++;
4914 		i++;
4915 	}
4916 	totalmaps = nummaps;
4917 
4918 	if ( nummaps == 0 )
4919 	{
4920 		fclose( fp );
4921 		free( buffer );
4922 		Com_Error( ERR_DROP, "no maps in maps.lst\n" );
4923 		return; // for showing above is fatal.
4924 	}
4925 
4926 	memset( mapnames, 0, sizeof( char * ) * ( MAX_MAPS + 2 ) );
4927 
4928 	bspnames = malloc( sizeof( char * ) * ( MAX_MAPS + 2 ) );  //was + 1, but caused memory errors
4929 	memset( bspnames, 0, sizeof( char * ) * ( MAX_MAPS + 2 ) );
4930 
4931 	s = buffer;
4932 
4933 	k = 0;
4934 	for ( i = 0; i < nummaps; i++ )
4935 	{
4936 
4937 		strcpy( shortname, COM_Parse( &s ) );
4938 		l = strlen(shortname);
4939 #if defined WIN32_VARIANT
4940 		for (j=0 ; j<l ; j++)
4941 			shortname[j] = tolower(shortname[j]);
4942 #endif
4943 		//keep a list of the shortnames for later comparison to bsp files
4944 		bspnames[i] = malloc( strlen( shortname ) + 1 );
4945 		strcpy(bspnames[i], shortname);
4946 
4947 		strcpy( longname, COM_Parse( &s ) );
4948 		Com_sprintf( scratch, sizeof( scratch ), "%s\n%s", longname, shortname );
4949 
4950 		// Each game mode has one or more map name prefixes. For example, if
4951 		// the game mode is capture the flag, only maps that start with ctf
4952 		// should make it into the mapnames list.
4953 		for (j = 0; map_prefixes[s_rules_box.curvalue][j]; j++)
4954 		{
4955 			const char *curpfx = map_prefixes[s_rules_box.curvalue][j];
4956 			if (!strncmp (curpfx, shortname, strlen(curpfx)))
4957 			{
4958 				// matched an allowable prefix
4959 				mapnames[k] = malloc( strlen( scratch ) + 1 );
4960 				strcpy( mapnames[k], scratch );
4961 				k++;
4962 				break;
4963 			}
4964 		}
4965 	}
4966 	// done with maps.lst
4967 	fclose( fp );
4968 	free( buffer );
4969 
4970 	//now, check the folders and add the maps not in the list yet
4971 
4972 	mapfiles = FS_ListFilesInFS( "maps/*.bsp", &nmaps, 0,
4973 		SFF_SUBDIR | SFF_HIDDEN | SFF_SYSTEM );
4974 
4975 	for (i=0;i<nmaps && totalmaps<MAX_MAPS;i++)
4976 	{
4977 		int num;
4978 
4979 		s = strstr( mapfiles[i], "maps/"); s++;
4980 		s = strstr(s, "/"); s++;
4981 
4982 		num = strlen(s)-4;
4983 		s[num] = 0;
4984 
4985 		curMap = s;
4986 
4987 		l = strlen(curMap);
4988 
4989 #if defined WIN32_VARIANT
4990 		for (j=0 ; j<l ; j++)
4991 			curMap[j] = tolower(curMap[j]);
4992 #endif
4993 
4994 		Com_sprintf( scratch, sizeof( scratch ), "%s\n%s", "Custom Map", curMap );
4995 
4996 		//check game type, and if not already in maps.lst, add it
4997 		l = 0;
4998 		for ( j = 0 ; j < nummaps ; j++ )
4999 		{
5000 			l = Q_strcasecmp(curMap, bspnames[j]);
5001 			if(!l)
5002 				break; //already there, don't bother adding
5003 		}
5004 		if ( l )
5005 		{ //didn't find it in our list
5006 
5007 			// FIXME: copy and paste sux0rs
5008 			// Each game mode has one or more map name prefixes. For example, if
5009 			// the game mode is capture the flag, only maps that start with ctf
5010 			// should make it into the mapnames list.
5011 			for (j = 0; map_prefixes[s_rules_box.curvalue][j]; j++)
5012 			{
5013 				const char *curpfx = map_prefixes[s_rules_box.curvalue][j];
5014 				if (!strncmp (curpfx, curMap, strlen(curpfx)))
5015 				{
5016 					// matched an allowable prefix
5017 					mapnames[k] = malloc( strlen( scratch ) + 1 );
5018 					strcpy( mapnames[k], scratch );
5019 					k++;
5020 					totalmaps++;
5021 					break;
5022 				}
5023 			}
5024 		}
5025 	}
5026 
5027 	if (mapfiles)
5028 		FS_FreeFileList(mapfiles, nmaps);
5029 
5030 	for(i = k; i<=nummaps; i++)
5031 	{
5032 		free(mapnames[i]);
5033 		mapnames[i] = 0;
5034 	}
5035 
5036 	s_startmap_list.generic.name	= "initial map";
5037 	s_startmap_list.itemnames = (const char **)mapnames;
5038 	s_startmap_list.curvalue = 0;
5039 
5040 	//set map info
5041 	MapInfoFunc(NULL);
5042 }
5043 
StartServerActionFunc(void * self)5044 void StartServerActionFunc( void *self )
5045 {
5046 	char	startmap[128];
5047 	int		timelimit;
5048 	int		fraglimit;
5049 	int		maxclients;
5050 
5051 	strcpy( startmap, strchr( mapnames[s_startmap_list.curvalue], '\n' ) + 1 );
5052 
5053 	maxclients  = atoi( s_maxclients_field.buffer );
5054 	timelimit	= atoi( s_timelimit_field.buffer );
5055 	fraglimit	= atoi( s_fraglimit_field.buffer );
5056 
5057 	Cvar_SetValue( "maxclients", ClampCvar( 0, maxclients, maxclients ) );
5058 	Cvar_SetValue ("timelimit", ClampCvar( 0, timelimit, timelimit ) );
5059 	Cvar_SetValue ("fraglimit", ClampCvar( 0, fraglimit, fraglimit ) );
5060 	Cvar_Set("hostname", s_hostname_field.buffer );
5061 	Cvar_SetValue("sv_public", s_public_box.curvalue );
5062 
5063 // Running a dedicated server from menu does not always work right in Linux, if program is
5064 //  invoked from a gui menu system. Listen server should be ok.
5065 // Removing option from menu for now, since it is possible to start server running in
5066 //  background without realizing it.
5067 	if(s_dedicated_box.curvalue) {
5068 #if defined WIN32_VARIANT
5069 		Cvar_ForceSet("dedicated", "1");
5070 #else
5071 		Cvar_ForceSet("dedicated", "0");
5072 #endif
5073 		Cvar_Set("sv_maplist", startmap);
5074 		Cbuf_AddText ("setmaster master.corservers.com master2.corservers.com\n");
5075 	}
5076 	Cvar_SetValue( "skill", s_skill_box.curvalue );
5077 	Cvar_SetValue( "g_antilag", s_antilag_box.curvalue);
5078 
5079 	// The deathmatch cvar doesn't specifically indicate a pure frag-to-win
5080 	// game mode. It's actually the "enable multiplayer" cvar.
5081 	// TODO: Does Alien Arena even work with deathmatch set to 0? Might be
5082 	// able to remove it from the game.
5083 	Cvar_SetValue ("deathmatch", 1 );
5084 	Cvar_SetValue ("ctf", 0);
5085 #ifdef TACTICAL
5086 	Cvar_SetValue ("g_tactical", 1);
5087 #else
5088 	Cvar_SetValue ("g_tactical", 0);
5089 #endif
5090 	Cvar_SetValue ("tca", 0);
5091 	Cvar_SetValue ("cp", 0);
5092 	Cvar_SetValue ("g_duel", 0);
5093 	Cvar_SetValue ("gamerules", s_rules_box.curvalue );
5094 
5095 	switch (s_rules_box.curvalue)
5096 	{
5097 		case 1:
5098 			Cvar_SetValue ("ctf", 1 );
5099 			break;
5100 		case 2:
5101 			Cvar_SetValue ("g_tactical", 1);
5102 			break;
5103 		case 4:
5104 			Cvar_SetValue ("tca", 1);
5105 			break;
5106 		case 5:
5107 			Cvar_SetValue ("cp", 1);
5108 			break;
5109 		case 6:
5110 			Cvar_SetValue ("g_duel", 1);
5111 			break;
5112 		default:
5113 			break;
5114 	}
5115 
5116 	Cbuf_AddText (va("startmap %s\n", startmap));
5117 
5118 	M_ForceMenuOff ();
5119 
5120 }
5121 
M_Menu_StartServer_f(void)5122 static void M_Menu_StartServer_f (void)
5123 {
5124 	int i;
5125 
5126 
5127 	static const char *skill[] =
5128 	{
5129 		"easy",
5130 		"medium",
5131 		"hard",
5132 		0
5133 	};
5134 
5135 	setup_window (s_startserver_screen, s_startserver_menu, "HOST SERVER");
5136 	setup_panel (s_startserver_menu, s_startserver_main_submenu);
5137 
5138 	s_startmap_list.generic.type = MTYPE_SPINCONTROL;
5139 	s_startmap_list.generic.name	= "initial map";
5140 	s_startmap_list.itemnames = (const char **) mapnames;
5141 	s_startmap_list.generic.callback = MapInfoFunc;
5142 	Menu_AddItem( &s_startserver_main_submenu, &s_startmap_list );
5143 
5144 	s_levelshot_submenu.generic.type = MTYPE_SUBMENU;
5145 	s_levelshot_submenu.generic.flags = QMF_SNUG_LEFT;
5146 	s_levelshot_submenu.nitems = 0;
5147 
5148 	s_levelshot_preview.generic.type = MTYPE_NOT_INTERACTIVE;
5149 	s_levelshot_preview.generic.localstrings[0] = NULL;
5150 	VectorSet (s_levelshot_preview.generic.localints, 21, 12, 0);
5151 	s_levelshot_preview.generic.itemsizecallback = PicSizeFunc;
5152 	s_levelshot_preview.generic.itemdraw = PicDrawFunc;
5153 	Menu_AddItem (&s_levelshot_submenu, &s_levelshot_preview);
5154 
5155 	for ( i = 0; i < 5; i++) {
5156 		s_startserver_map_data[i].generic.type	= MTYPE_TEXT;
5157 		s_startserver_map_data[i].generic.name	= "no data";
5158 		s_startserver_map_data[i].generic.flags	= QMF_RIGHT_COLUMN;
5159 		Menu_AddItem( &s_levelshot_submenu, &s_startserver_map_data[i] );
5160 	}
5161 
5162 	Menu_AddItem (&s_startserver_main_submenu, &s_levelshot_submenu);
5163 
5164 	add_text (s_startserver_main_submenu, NULL, 0); //spacer
5165 
5166 	s_rules_box.generic.type = MTYPE_SPINCONTROL;
5167 	s_rules_box.generic.name	= "rules";
5168 	s_rules_box.itemnames = game_mode_names;
5169 	s_rules_box.curvalue = 0;
5170 	s_rules_box.generic.callback = RulesChangeFunc;
5171 	Menu_AddItem( &s_startserver_main_submenu, &s_rules_box );
5172 
5173 	s_antilag_box.generic.name	= "antilag";
5174 	setup_tickbox (s_antilag_box);
5175 	s_antilag_box.curvalue = 1;
5176 	Menu_AddItem( &s_startserver_main_submenu, &s_antilag_box );
5177 
5178 	s_timelimit_field.generic.type = MTYPE_FIELD;
5179 	s_timelimit_field.generic.name = "time limit";
5180 	s_timelimit_field.generic.flags = QMF_NUMBERSONLY;
5181 	s_timelimit_field.generic.tooltip = "0 = no limit";
5182 	s_timelimit_field.length = 3;
5183 	s_timelimit_field.generic.visible_length = 3;
5184 	strcpy( s_timelimit_field.buffer, Cvar_VariableString("timelimit") );
5185 	Menu_AddItem( &s_startserver_main_submenu, &s_timelimit_field );
5186 
5187 	s_fraglimit_field.generic.type = MTYPE_FIELD;
5188 	s_fraglimit_field.generic.name = "frag limit";
5189 	s_fraglimit_field.generic.flags = QMF_NUMBERSONLY;
5190 	s_fraglimit_field.generic.tooltip = "0 = no limit";
5191 	s_fraglimit_field.length = 3;
5192 	s_fraglimit_field.generic.visible_length = 3;
5193 	strcpy( s_fraglimit_field.buffer, Cvar_VariableString("fraglimit") );
5194 	Menu_AddItem( &s_startserver_main_submenu, &s_fraglimit_field );
5195 
5196 	/*
5197 	** maxclients determines the maximum number of players that can join
5198 	** the game.  If maxclients is only "1" then we should default the menu
5199 	** option to 8 players, otherwise use whatever its current value is.
5200 	** Clamping will be done when the server is actually started.
5201 	*/
5202 	s_maxclients_field.generic.type = MTYPE_FIELD;
5203 	s_maxclients_field.generic.name = "max players";
5204 	s_maxclients_field.generic.flags = QMF_NUMBERSONLY;
5205 	s_maxclients_field.length = 3;
5206 	s_maxclients_field.generic.visible_length = 3;
5207 	if ( Cvar_VariableValue( "maxclients" ) == 1 )
5208 		strcpy( s_maxclients_field.buffer, "8" );
5209 	else
5210 		strcpy( s_maxclients_field.buffer, Cvar_VariableString("maxclients") );
5211 	Menu_AddItem( &s_startserver_main_submenu, &s_maxclients_field );
5212 
5213 	s_hostname_field.generic.type = MTYPE_FIELD;
5214 	s_hostname_field.generic.name = "server name";
5215 	s_hostname_field.generic.flags = 0;
5216 	s_hostname_field.length = 12;
5217 	s_hostname_field.generic.visible_length = LONGINPUT_SIZE;
5218 	strcpy( s_hostname_field.buffer, Cvar_VariableString("hostname") );
5219 	Menu_AddItem( &s_startserver_main_submenu, &s_hostname_field );
5220 
5221 	s_public_box.generic.name = "public server";
5222 	setup_tickbox (s_public_box);
5223 	s_public_box.curvalue = 1;
5224 	Menu_AddItem( &s_startserver_main_submenu, &s_public_box );
5225 
5226 #if defined WIN32_VARIANT
5227 	s_dedicated_box.generic.name = "dedicated server";
5228 	setup_tickbox (s_dedicated_box);
5229 	Menu_AddItem( &s_startserver_main_submenu, &s_dedicated_box );
5230 #else
5231 	// may or may not need this when disabling dedicated server menu
5232 	s_dedicated_box.generic.type = -1;
5233 	s_dedicated_box.generic.name = NULL;
5234 	s_dedicated_box.curvalue = 0;
5235 #endif
5236 
5237 	s_skill_box.generic.type = MTYPE_SPINCONTROL;
5238 	s_skill_box.generic.name	= "skill level";
5239 	s_skill_box.itemnames = skill;
5240 	s_skill_box.curvalue = 1;
5241 	Menu_AddItem( &s_startserver_main_submenu, &s_skill_box );
5242 
5243 #ifndef TACTICAL
5244 	add_action (s_startserver_menu, "Mutators", MutatorFunc, QMF_RIGHT_COLUMN);
5245 #endif
5246 	add_action (s_startserver_menu, "Bot Options", BotOptionsFunc, QMF_RIGHT_COLUMN);
5247 	add_action (s_startserver_menu, "Begin", StartServerActionFunc, QMF_RIGHT_COLUMN);
5248 
5249 
5250 	// call this now to set proper inital state
5251 	RulesChangeFunc (NULL);
5252 	MapInfoFunc (NULL);
5253 
5254 	M_PushMenu_Defaults (s_startserver_screen);
5255 }
5256 
5257 /*
5258 =============================================================================
5259 
5260 BOT OPTIONS MENU
5261 
5262 =============================================================================
5263 */
5264 
5265 static menuframework_s s_botoptions_screen;
5266 static menuframework_s s_botoptions_menu;
5267 
Read_Bot_Info()5268 void Read_Bot_Info()
5269 {
5270 	FILE *pIn;
5271 	int i, count;
5272 	char *info;
5273 	char bot_filename[MAX_OSPATH];
5274 	char stem[MAX_QPATH];
5275 	char relative_path[MAX_QPATH];
5276 
5277 	if(s_rules_box.curvalue == 1 || s_rules_box.curvalue == 4 || s_rules_box.curvalue == 5)
5278 	{ // team game
5279 		strcpy( stem, "team" );
5280 	}
5281 	else
5282 	{ // non-team, bots per map
5283 		strcpy( stem, strchr( mapnames[s_startmap_list.curvalue], '\n' ) + 1 );
5284 		for(i = 0; i < strlen(stem); i++)
5285 			stem[i] = tolower( stem[i] );
5286 	}
5287 	Com_sprintf( relative_path, sizeof(relative_path), BOT_GAMEDATA"/%s.tmp", stem );
5288 	if ( !FS_FullPath( bot_filename, sizeof(bot_filename), relative_path ) )
5289 	{
5290 		Com_DPrintf("Read_Bot_Info: %s/%s not found\n", BOT_GAMEDATA, relative_path );
5291 		return;
5292 	}
5293 
5294 	if((pIn = fopen(bot_filename, "rb" )) == NULL)
5295 	{
5296 		Com_DPrintf("Read_Bot_Info: failed file open for read: %s", bot_filename );
5297 		return;
5298 	}
5299 
5300 	szr = fread(&count,sizeof (int),1,pIn);
5301 	if(count>8)
5302 		count = 8;
5303 
5304 	for(i=0;i<count;i++)
5305 	{
5306 
5307 		szr = fread(bot[i].userinfo,sizeof(char) * MAX_INFO_STRING,1,pIn);
5308 
5309 		info = Info_ValueForKey (bot[i].userinfo, "name");
5310 		strcpy(bot[i].name, info);
5311 	}
5312 
5313 	fclose(pIn);
5314 }
5315 
5316 void BotAction (void *self);
5317 
M_Menu_BotOptions_f(void)5318 static void M_Menu_BotOptions_f (void)
5319 {
5320 	int i;
5321 
5322 	for(i = 0; i < 8; i++)
5323 		strcpy(bot[i].name, "...empty slot");
5324 
5325 	Read_Bot_Info();
5326 
5327 	setup_window (s_botoptions_screen, s_botoptions_menu, "BOT OPTIONS");
5328 
5329 	for (i = 0; i < 8; i++) {
5330 		s_bots_bot_action[i].generic.type = MTYPE_ACTION;
5331 		s_bots_bot_action[i].generic.name = bot[i].name;
5332 		s_bots_bot_action[i].generic.flags = QMF_BUTTON;
5333 		s_bots_bot_action[i].generic.callback = BotAction;
5334 		s_bots_bot_action[i].curvalue = i;
5335 		Menu_AddItem( &s_botoptions_menu, &s_bots_bot_action[i]);
5336 	}
5337 
5338 	M_PushMenu_Defaults (s_botoptions_screen);
5339 }
5340 
BotAction(void * self)5341 void BotAction( void *self )
5342 {
5343 	FILE *pOut;
5344 	int i, count;
5345 
5346 	char stem[MAX_QPATH];
5347 	char relative_path[MAX_QPATH];
5348 	char bot_filename[MAX_OSPATH];
5349 
5350 	menulist_s *f = ( menulist_s * ) self;
5351 
5352 	slot = f->curvalue;
5353 
5354 	count = 8;
5355 
5356 	if(!strcmp(f->generic.name, "...empty slot")) {
5357 		//open the bot menu
5358 		M_Menu_AddBots_f();
5359 		for(i = 0; i < 8; i++) {
5360 			if(!strcmp(s_bots_bot_action[i].generic.name, "...empty slot")) {
5361 				//clear it, it's slot is empty
5362 				strcpy(bot[i].name, "...empty slot");
5363 				bot[i].userinfo[0] = 0;
5364 				count--;
5365 			}
5366 		}
5367 	}
5368 	else {
5369 		f->generic.name = "...empty slot";
5370 		//clear the bot out of the struct...hmmm...kinda hokey, but - need to know which slot
5371 		for(i = 0; i < 8; i++) {
5372 			if(!strcmp(s_bots_bot_action[i].generic.name, "...empty slot")) {
5373 				//clear it, it's slot is empty
5374 				strcpy(bot[i].name, "...empty slot");
5375 				bot[i].userinfo[0] = 0;
5376 				count--;
5377 			}
5378 		}
5379 	}
5380 
5381 	//write out bot file
5382 	if(s_rules_box.curvalue == 1 || s_rules_box.curvalue == 4 || s_rules_box.curvalue == 5)
5383 	{ // team game
5384 		strcpy( stem, "team" );
5385 	}
5386 	else
5387 	{ // non-team, bots per map
5388 		strcpy( stem, strchr( mapnames[s_startmap_list.curvalue], '\n' ) + 1 );
5389 		for(i = 0; i < strlen(stem); i++)
5390 			stem[i] = tolower( stem[i] );
5391 	}
5392 	Com_sprintf( relative_path, sizeof(relative_path), BOT_GAMEDATA"/%s.tmp", stem );
5393 	FS_FullWritePath( bot_filename, sizeof(bot_filename), relative_path );
5394 
5395 	if((pOut = fopen(bot_filename, "wb" )) == NULL)
5396 	{
5397 		Com_DPrintf("BotAction: failed fopen for write: %s\n", bot_filename );
5398 		return; // bail
5399 	}
5400 
5401 	szr = fwrite(&count,sizeof (int),1,pOut); // Write number of bots
5402 
5403 	for (i = 7; i > -1; i--) {
5404 		if(strcmp(bot[i].name, "...empty slot"))
5405 			szr = fwrite(bot[i].userinfo,sizeof (char) * MAX_INFO_STRING,1,pOut);
5406 	}
5407 
5408 	fclose(pOut);
5409 
5410 	return;
5411 }
5412 
5413 /*
5414 =============================================================================
5415 
5416 ADDRESS BOOK MENU
5417 
5418 =============================================================================
5419 */
5420 #define NUM_ADDRESSBOOK_ENTRIES 9
5421 
5422 static menuframework_s	s_addressbook_menu;
5423 static char				s_addressbook_cvarnames[NUM_ADDRESSBOOK_ENTRIES][20];
5424 static menufield_s		s_addressbook_fields[NUM_ADDRESSBOOK_ENTRIES];
5425 
M_Menu_AddressBook_f(void)5426 static void M_Menu_AddressBook_f (void)
5427 {
5428 	int i;
5429 
5430 	s_addressbook_menu.nitems = 0;
5431 
5432 	for ( i = 0; i < NUM_ADDRESSBOOK_ENTRIES; i++ )
5433 	{
5434 		cvar_t *adr;
5435 
5436 		Com_sprintf( s_addressbook_cvarnames[i], sizeof( s_addressbook_cvarnames[i] ), "adr%d", i );
5437 
5438 		adr = Cvar_Get( s_addressbook_cvarnames[i], "", CVAR_ARCHIVE );
5439 
5440 		s_addressbook_fields[i].generic.type			= MTYPE_FIELD;
5441 		s_addressbook_fields[i].generic.callback		= StrFieldCallback;
5442 		s_addressbook_fields[i].generic.localstrings[0]	= &s_addressbook_cvarnames[i][0];
5443 		s_addressbook_fields[i].cursor					= strlen (adr->string);
5444 		s_addressbook_fields[i].generic.visible_length	= LONGINPUT_SIZE;
5445 
5446 		strcpy( s_addressbook_fields[i].buffer, adr->string );
5447 
5448 		Menu_AddItem( &s_addressbook_menu, &s_addressbook_fields[i] );
5449 	}
5450 
5451 	Menu_AutoArrange (&s_addressbook_menu);
5452 	Menu_Center (&s_addressbook_menu);
5453 
5454 	M_PushMenu_Defaults (s_addressbook_menu);
5455 }
5456 
5457 /*
5458 =============================================================================
5459 
5460 PLAYER RANKING MENU
5461 
5462 =============================================================================
5463 */
5464 
5465 static menuframework_s	s_playerranking_screen;
5466 static menuframework_s	s_playerranking_menu;
5467 static menuaction_s		s_playerranking_title;
5468 static menuaction_s		s_playerranking_ttheader;
5469 static menuaction_s		s_playerranking_topten[10];
5470 char rank[32];
5471 char fragrate[32];
5472 char playername[64]; // a print field, not just name
5473 char totaltime[32];
5474 char totalfrags[32];
5475 char topTenList[10][64];
5476 
M_Menu_PlayerRanking_f(void)5477 static void M_Menu_PlayerRanking_f (void)
5478 {
5479 	extern cvar_t *name;
5480 	PLAYERSTATS player;
5481 	PLAYERSTATS topTenPlayers[10];
5482 	int i;
5483 
5484 	setup_window (s_playerranking_screen, s_playerranking_menu, "PLAYER RANKINGS");
5485 
5486 	Q_strncpyz2( player.playername, name->string, sizeof(player.playername) );
5487 
5488 	player.totalfrags = player.totaltime = player.ranking = 0;
5489 	player = getPlayerRanking ( player );
5490 
5491 	Com_sprintf(playername, sizeof(playername), "Name: %s", player.playername);
5492 	if(player.ranking > 0)
5493 		Com_sprintf(rank, sizeof(rank), "Rank: ^1%i", player.ranking);
5494 	else
5495 		Com_sprintf(rank, sizeof(rank), "Rank: ^1Unranked");
5496 	if ( player.totaltime > 1.0f )
5497 		Com_sprintf(fragrate, sizeof(fragrate), "Frag Rate: %6.2f", (float)(player.totalfrags)/(player.totaltime - 1.0f) );
5498 	else
5499 		Com_sprintf(fragrate, sizeof(fragrate), "Frag Rate: 0" );
5500 	Com_sprintf(totalfrags, sizeof(totalfrags), "Total Frags: ^1%i", player.totalfrags);
5501 	Com_sprintf(totaltime, sizeof(totaltime), "Total Time: %6.2f", player.totaltime - 1.0f);
5502 
5503 	s_playerranking_title.generic.type	= MTYPE_ACTION;
5504 	s_playerranking_title.generic.name	= "Player Ranking and Stats";
5505 	s_playerranking_title.generic.flags	= QMF_RIGHT_COLUMN;
5506 	Menu_AddItem( &s_playerranking_menu, &s_playerranking_title );
5507 
5508 	add_text(s_playerranking_menu, playername, QMF_RIGHT_COLUMN);
5509 	add_text(s_playerranking_menu, rank, QMF_RIGHT_COLUMN);
5510 	add_text(s_playerranking_menu, fragrate, QMF_RIGHT_COLUMN);
5511 	add_text(s_playerranking_menu, totalfrags, QMF_RIGHT_COLUMN);
5512 	add_text(s_playerranking_menu, totaltime, QMF_RIGHT_COLUMN);
5513 
5514 	s_playerranking_ttheader.generic.type	= MTYPE_ACTION;
5515 	s_playerranking_ttheader.generic.name	= "Top Ten Players";
5516 	s_playerranking_ttheader.generic.flags	= QMF_RIGHT_COLUMN;
5517 	Menu_AddItem (&s_playerranking_menu, &s_playerranking_ttheader);
5518 
5519 	for(i = 0; i < 10; i++) {
5520 
5521 		topTenPlayers[i].totalfrags = topTenPlayers[i].totaltime = topTenPlayers[i].ranking = 0;
5522 		topTenPlayers[i] = getPlayerByRank ( i+1, topTenPlayers[i] );
5523 
5524 		if(i < 9)
5525 			Com_sprintf(topTenList[i], sizeof(topTenList[i]), "Rank: ^1%i %s", topTenPlayers[i].ranking, topTenPlayers[i].playername);
5526 		else
5527 			Com_sprintf(topTenList[i], sizeof(topTenList[i]), "Rank:^1%i %s", topTenPlayers[i].ranking, topTenPlayers[i].playername);
5528 
5529 		s_playerranking_topten[i].generic.type	= MTYPE_TEXT;
5530 		s_playerranking_topten[i].generic.name	= topTenList[i];
5531 		s_playerranking_topten[i].generic.flags	= QMF_RIGHT_COLUMN;
5532 
5533 		Menu_AddItem( &s_playerranking_menu, &s_playerranking_topten[i] );
5534 	}
5535 
5536 	M_PushMenu_Defaults (s_playerranking_screen);
5537 }
5538 
5539 /*
5540 =============================================================================
5541 
5542 PLAYER CONFIG MENU
5543 
5544 =============================================================================
5545 */
5546 
5547 typedef struct
5548 {
5549 	menucommon_s generic;
5550 	const char *name;
5551 	const char *skin;
5552 	float w, h;
5553 	float mframe, yaw;
5554 } menumodel_s;
5555 
PlayerModelSizeFunc(void * _self,FNT_font_t font)5556 static menuvec2_t PlayerModelSizeFunc (void *_self, FNT_font_t font)
5557 {
5558 	menuvec2_t ret;
5559 	menumodel_s *self = (menumodel_s*) _self;
5560 
5561 	ret.x = (self->w+2)*font->size;
5562 	ret.y = (self->h+2)*font->size;
5563 
5564 	return ret;
5565 }
5566 
PlayerModelDrawFunc(void * _self,FNT_font_t font)5567 static void PlayerModelDrawFunc (void *_self, FNT_font_t font)
5568 {
5569 	refdef_t refdef;
5570 	char scratch[MAX_OSPATH];
5571 	FILE *modelfile;
5572 	int i;
5573 	extern float CalcFov( float fov_x, float w, float h );
5574 	float scale;
5575 	entity_t entity[3];
5576 	menumodel_s *self = (menumodel_s*) _self;
5577 
5578 	self->mframe += cls.frametime*150;
5579 	if ( self->mframe > 390 )
5580 		self->mframe = 10;
5581 	if ( self->mframe < 10)
5582 		self->mframe = 10;
5583 
5584 	self->yaw += cls.frametime*50;
5585 	if (self->yaw > 360)
5586 		self->yaw = 0;
5587 
5588 	scale = (float)(viddef.height)/600;
5589 
5590 	memset( &refdef, 0, sizeof( refdef ) );
5591 
5592 	refdef.width = self->w*font->size;
5593 	refdef.height = self->h*font->size;
5594 	refdef.x = Item_GetX(*self);
5595 	refdef.y = Item_GetY(*self);
5596 	refdef.x -= refdef.width;
5597 
5598 	Menu_DrawBox (refdef.x, refdef.y, refdef.width, refdef.height, 1, NULL, "menu/sm_");
5599 
5600 	refdef.width -= font->size;
5601 	refdef.height -= font->size;
5602 
5603 	refdef.fov_x = 35;
5604 	refdef.fov_y = CalcFov( refdef.fov_x, refdef.width, refdef.height );
5605 	refdef.time = cls.realtime*0.001;
5606 
5607 	memset( &entity, 0, sizeof( entity ) );
5608 
5609 	Com_sprintf( scratch, sizeof( scratch ), "players/%s/tris.md2", self->name );
5610 	entity[0].model = R_RegisterModel( scratch );
5611 	Com_sprintf( scratch, sizeof( scratch ), "players/%s/%s.jpg", self->name, self->skin );
5612 	entity[0].skin = R_RegisterSkin( scratch );
5613 	entity[0].flags = RF_FULLBRIGHT | RF_MENUMODEL;
5614 
5615 	Com_sprintf( scratch, sizeof( scratch ), "players/%s/weapon.md2", self->name );
5616 	entity[1].model = R_RegisterModel( scratch );
5617 	Com_sprintf( scratch, sizeof( scratch ), "players/%s/weapon.tga", self->name );
5618 	entity[1].skin = R_RegisterSkin( scratch );
5619 	entity[1].flags = RF_FULLBRIGHT | RF_MENUMODEL;
5620 
5621 	refdef.num_entities = 2;
5622 
5623 	//if a helmet or other special device
5624 	Com_sprintf( scratch, sizeof( scratch ), "players/%s/helmet.md2", self->name );
5625 	FS_FOpenFile( scratch, &modelfile );
5626 	if ( modelfile )
5627 	{
5628 		fclose(modelfile);
5629 
5630 		entity[2].model = R_RegisterModel( scratch );
5631 		Com_sprintf( scratch, sizeof( scratch ), "players/%s/helmet.tga", self->name );
5632 		entity[2].skin = R_RegisterSkin( scratch );
5633 		entity[2].flags = RF_FULLBRIGHT | RF_TRANSLUCENT | RF_MENUMODEL;
5634 		entity[2].alpha = 0.4;
5635 
5636 		refdef.num_entities = 3;
5637 	}
5638 
5639 	for (i = 0; i < refdef.num_entities; i++)
5640 	{
5641 		// seems a little odd to use frame-1 for oldframe and frame%1 for
5642 		// backlerp, but it works out
5643 		entity[i].frame = (int)(self->mframe/10);
5644 		entity[i].oldframe = (int)(self->mframe/10) - 1;
5645 		entity[i].backlerp = (float)((int)self->mframe%10)/10.0f;
5646 		entity[i].angles[1] = (int)self->yaw;
5647 
5648 		VectorSet (entity[i].origin, 80, 0, -5);
5649 		VectorCopy (entity[i].origin, entity[i].oldorigin);
5650 	}
5651 
5652 	refdef.areabits = 0;
5653 	refdef.entities = entity;
5654 	refdef.lightstyles = 0;
5655 	refdef.rdflags = RDF_NOWORLDMODEL;
5656 
5657 	R_RenderFramePlayerSetup( &refdef );
5658 }
5659 
5660 static menuframework_s	s_player_config_menu;
5661 
5662 static menufield_s		s_player_name_field;
5663 
5664 static menuframework_s	s_player_password_submenu;
5665 static menuframework_s	s_player_password_field_submenu;
5666 static menufield_s		s_player_password_field;
5667 
5668 static menuframework_s	s_player_skin_submenu;
5669 static menuframework_s	s_player_skin_controls_submenu;
5670 static menulist_s		s_player_model_box;
5671 static menulist_s		s_player_skin_box;
5672 static menuitem_s   	s_player_thumbnail;
5673 
5674 static menuframework_s	s_player_skin_preview_submenu;
5675 static menumodel_s		s_player_skin_preview;
5676 
5677 #define MAX_DISPLAYNAME 16
5678 #define MAX_PLAYERMODELS 1024
5679 
5680 typedef struct
5681 {
5682 	int		nskins;
5683 	char	**skindisplaynames;
5684 	char	displayname[MAX_DISPLAYNAME];
5685 	char	directory[MAX_OSPATH];
5686 } playermodelinfo_s;
5687 
5688 static playermodelinfo_s s_pmi[MAX_PLAYERMODELS];
5689 static char *s_pmnames[MAX_PLAYERMODELS];
5690 static int s_numplayermodels = 0;
5691 
ModelCallback(void * unused)5692 static void ModelCallback (void *unused)
5693 {
5694 	s_player_skin_box.itemnames = (const char **) s_pmi[s_player_model_box.curvalue].skindisplaynames;
5695 	s_player_skin_box.curvalue = 0;
5696 
5697 	Menu_ActivateItem ((menuitem_s *)&s_player_skin_box);
5698 }
5699 
SkinCallback(void * unused)5700 static void SkinCallback (void *unused)
5701 {
5702 	char scratch[MAX_QPATH];
5703 
5704 	Com_sprintf( scratch, sizeof( scratch ), "%s/%s",
5705 		s_pmi[s_player_model_box.curvalue].directory,
5706 		s_pmi[s_player_model_box.curvalue].skindisplaynames[s_player_skin_box.curvalue] );
5707 
5708 	Cvar_Set( "skin", scratch );
5709 }
5710 
IconOfSkinExists(char * skin,char ** pcxfiles,int npcxfiles)5711 static qboolean IconOfSkinExists( char *skin, char **pcxfiles, int npcxfiles )
5712 {
5713 	int i;
5714 	char scratch[1024];
5715 
5716 	strcpy( scratch, skin );
5717 	*strrchr( scratch, '.' ) = 0;
5718 	strcat( scratch, "_i.tga" );
5719 
5720 	for ( i = 0; i < npcxfiles; i++ )
5721 	{
5722 		if ( strcmp( pcxfiles[i], scratch ) == 0 )
5723 			return true;
5724 	}
5725 
5726 	strcpy( scratch, skin );
5727 	*strrchr( scratch, '.' ) = 0;
5728 	strcat( scratch, "_i.jpg" );
5729 
5730 	for ( i = 0; i < npcxfiles; i++ )
5731 	{
5732 		if ( strcmp( pcxfiles[i], scratch ) == 0 )
5733 			return true;
5734 	}
5735 
5736 	return false;
5737 }
5738 
PlayerConfig_ScanDirectories(void)5739 static void PlayerConfig_ScanDirectories( void )
5740 {
5741 	char scratch[1024];
5742 	int ndirs = 0, npms = 0;
5743 	char **dirnames;
5744 	int i;
5745 
5746 	// check if we need to do anything
5747 	if (s_numplayermodels != 0)
5748 		return;
5749 
5750 	//get dirs from gamedir first.
5751 	dirnames = FS_ListFilesInFS( "players/*.*", &ndirs, SFF_SUBDIR, 0 );
5752 
5753 	if ( !dirnames )
5754 		return;
5755 
5756 	/*
5757 	** go through the subdirectories
5758 	*/
5759 	npms = ndirs;
5760 	if ( npms > MAX_PLAYERMODELS )
5761 		npms = MAX_PLAYERMODELS;
5762 
5763 	for ( i = 0; i < npms; i++ )
5764 	{
5765 		int k, s;
5766 		char *a, *b, *c;
5767 		char **pcxnames;
5768 		char **skinnames;
5769 		int npcxfiles;
5770 		int nskins = 0;
5771 
5772 		if ( dirnames[i] == 0 )
5773 			continue;
5774 
5775 		// verify the existence of tris.md2
5776 		strcpy( scratch, dirnames[i] );
5777 		strcat( scratch, "/tris.md2" );
5778 		if (!FS_FileExists(scratch))
5779 		{
5780 			//try for tris.iqm if no md2
5781 			strcpy( scratch, dirnames[i] );
5782 			strcat( scratch, "/tris.iqm" );
5783 			if (!FS_FileExists(scratch))
5784 			{
5785 				free( dirnames[i] );
5786 				dirnames[i] = 0;
5787 				continue;
5788 			}
5789 		}
5790 
5791 		// verify the existence of at least one skin(note, do not mix .tga and .jpeg)
5792 		strcpy( scratch, dirnames[i] );
5793 		strcat( scratch, "/*.jpg" );
5794 		pcxnames = FS_ListFilesInFS( scratch, &npcxfiles, 0,
5795 			SFF_SUBDIR | SFF_HIDDEN | SFF_SYSTEM );
5796 
5797 		if(!pcxnames) {
5798 			// check for .tga, though this is no longer used for current models
5799 			strcpy( scratch, dirnames[i] );
5800 			strcat( scratch, "/*.tga" );
5801 			pcxnames = FS_ListFilesInFS( scratch, &npcxfiles, 0,
5802 				SFF_SUBDIR | SFF_HIDDEN | SFF_SYSTEM );
5803 		}
5804 
5805 		if ( !pcxnames )
5806 		{
5807 			free( dirnames[i] );
5808 			dirnames[i] = 0;
5809 			continue;
5810 		}
5811 
5812 		// count valid skins, which consist of a skin with a matching "_i" icon
5813 		for ( k = 0; k < npcxfiles; k++ )
5814 		{
5815 			if ( !strstr( pcxnames[k], "_i.tga" ) || !strstr( pcxnames[k], "_i.jpg" ))
5816 			{
5817 				if ( IconOfSkinExists( pcxnames[k], pcxnames, npcxfiles) )
5818 				{
5819 					nskins++;
5820 				}
5821 			}
5822 		}
5823 		if ( !nskins )
5824 			continue;
5825 
5826 		skinnames = malloc( sizeof( char * ) * ( nskins + 1 ) );
5827 		memset( skinnames, 0, sizeof( char * ) * ( nskins + 1 ) );
5828 
5829 		// copy the valid skins
5830 		for ( s = 0, k = 0; k < npcxfiles; k++ )
5831 		{
5832 			char *a, *b, *c;
5833 
5834 			if ( !strstr( pcxnames[k], "_i.tga" ) )
5835 			{
5836 				if ( IconOfSkinExists( pcxnames[k], pcxnames, npcxfiles ) )
5837 				{
5838 					a = strrchr( pcxnames[k], '/' );
5839 					b = strrchr( pcxnames[k], '\\' );
5840 
5841 					if ( a > b )
5842 						c = a;
5843 					else
5844 						c = b;
5845 
5846 					strcpy( scratch, c + 1 );
5847 
5848 					if ( strrchr( scratch, '.' ) )
5849 						*strrchr( scratch, '.' ) = 0;
5850 
5851 					skinnames[s] = _strdup( scratch );
5852 					s++;
5853 				}
5854 			}
5855 		}
5856 
5857 		// at this point we have a valid player model
5858 		s_pmi[s_numplayermodels].nskins = nskins;
5859 		s_pmi[s_numplayermodels].skindisplaynames = skinnames;
5860 
5861 		// make short name for the model
5862 		a = strrchr( dirnames[i], '/' );
5863 		b = strrchr( dirnames[i], '\\' );
5864 
5865 		if ( a > b )
5866 			c = a;
5867 		else
5868 			c = b;
5869 
5870 		strncpy( s_pmi[s_numplayermodels].displayname, c + 1, MAX_DISPLAYNAME-1 );
5871 		strcpy( s_pmi[s_numplayermodels].directory, c + 1 );
5872 
5873 		FS_FreeFileList( pcxnames, npcxfiles );
5874 
5875 		s_numplayermodels++;
5876 	}
5877 	if ( dirnames )
5878 		free( dirnames );
5879 }
5880 
pmicmpfnc(const void * _a,const void * _b)5881 static int pmicmpfnc( const void *_a, const void *_b )
5882 {
5883 	const playermodelinfo_s *a = ( const playermodelinfo_s * ) _a;
5884 	const playermodelinfo_s *b = ( const playermodelinfo_s * ) _b;
5885 
5886 	/*
5887 	** sort by male, female, then alphabetical
5888 	*/
5889 	if ( strcmp( a->directory, "male" ) == 0 )
5890 		return -1;
5891 	else if ( strcmp( b->directory, "male" ) == 0 )
5892 		return 1;
5893 
5894 	if ( strcmp( a->directory, "female" ) == 0 )
5895 		return -1;
5896 	else if ( strcmp( b->directory, "female" ) == 0 )
5897 		return 1;
5898 
5899 	return strcmp( a->directory, b->directory );
5900 }
5901 
PlayerPicDrawFunc(void * _self,FNT_font_t font)5902 static void PlayerPicDrawFunc (void *_self, FNT_font_t font)
5903 {
5904 	int x, y;
5905 	char scratch[MAX_QPATH];
5906 	menuitem_s *self = (menuitem_s *)_self;
5907 	x = Item_GetX (*self);
5908 	y = Item_GetY (*self);
5909 
5910 	Com_sprintf( scratch, sizeof( scratch ), "/players/%s_i.tga",
5911 			Cvar_VariableString ("skin") );
5912 
5913 	Draw_StretchPic (x, y, font->size*5, font->size*5, scratch);
5914 }
5915 
PasswordCallback(void * _self)5916 static void PasswordCallback (void *_self)
5917 {
5918 	menufield_s *self = (menufield_s *)_self;
5919 
5920 	//was the password changed?
5921 	if(strcmp("********", self->buffer))
5922 	{
5923 		//if this is a virgin password, don't change, just authenticate
5924 		if(!strcmp(stats_password->string, "password"))
5925 		{
5926 			Cvar_FullSet( "stats_password", self->buffer, CVAR_PROFILE);
5927 			stats_password = Cvar_Get("stats_password", "password", CVAR_PROFILE);
5928 			Cvar_FullSet( "stats_pw_hashed", "0", CVAR_PROFILE);
5929 			currLoginState.validated = false;
5930 			STATS_RequestVerification();
5931 		}
5932 		else
5933 		{
5934 			Cvar_FullSet( "stats_password", self->buffer, CVAR_PROFILE);
5935 			stats_password = Cvar_Get("stats_password", "password", CVAR_PROFILE);
5936 			Cvar_FullSet( "stats_pw_hashed", "0", CVAR_PROFILE);
5937 			STATS_RequestPwChange();
5938 		}
5939 	}
5940 }
5941 
PConfigApplyFunc(void * self)5942 void PConfigApplyFunc (void *self)
5943 {
5944 	Menu_ApplyMenu (Menu_GetItemTree ((menuitem_s *)self));
5945 }
5946 
PlayerConfigModelSizeFunc(void * _self,FNT_font_t font)5947 static menuvec2_t PlayerConfigModelSizeFunc (void *_self, FNT_font_t font)
5948 {
5949 	menuvec2_t ret;
5950 	menumodel_s *self = (menumodel_s*) _self;
5951 
5952 	ret.x = 20*font->size;
5953 	ret.y = 29*font->size;
5954 
5955 	self->w = (float)ret.x/(float)font->size;
5956 	self->h = (float)ret.y/(float)font->size;
5957 
5958 	return ret;
5959 }
5960 
PlayerConfig_MenuInit(void)5961 void PlayerConfig_MenuInit( void )
5962 {
5963 	extern cvar_t *name;
5964 	// extern cvar_t *team; // unused
5965 	// extern cvar_t *skin; // unused
5966 	char currentdirectory[1024];
5967 	char currentskin[1024];
5968 	int i = 0;
5969 	float scale;
5970 	int currentdirectoryindex = 0;
5971 	int currentskinindex = 0;
5972 	cvar_t *hand = Cvar_Get( "hand", "0", CVAR_USERINFO | CVAR_ARCHIVE );
5973 
5974 	scale = (float)(viddef.height)/600;
5975 
5976 	PlayerConfig_ScanDirectories();
5977 
5978 	if (s_numplayermodels == 0)
5979 		return;
5980 
5981 	if ( hand->value < 0 || hand->value > 2 )
5982 		Cvar_SetValue( "hand", 0 );
5983 
5984 	Q_strncpyz( currentdirectory, Cvar_VariableString ("skin"), sizeof(currentdirectory)-1);
5985 
5986 	if ( strchr( currentdirectory, '/' ) )
5987 	{
5988 		strcpy( currentskin, strchr( currentdirectory, '/' ) + 1 );
5989 		*strchr( currentdirectory, '/' ) = 0;
5990 	}
5991 	else if ( strchr( currentdirectory, '\\' ) )
5992 	{
5993 		strcpy( currentskin, strchr( currentdirectory, '\\' ) + 1 );
5994 		*strchr( currentdirectory, '\\' ) = 0;
5995 	}
5996 	else
5997 	{
5998 		strcpy( currentdirectory, "martianenforcer" );
5999 		strcpy( currentskin, "default" );
6000 	}
6001 
6002 	qsort( s_pmi, s_numplayermodels, sizeof( s_pmi[0] ), pmicmpfnc );
6003 
6004 	memset( s_pmnames, 0, sizeof( s_pmnames ) );
6005 	for ( i = 0; i < s_numplayermodels; i++ )
6006 	{
6007 		s_pmnames[i] = s_pmi[i].displayname;
6008 		if ( Q_strcasecmp( s_pmi[i].directory, currentdirectory ) == 0 )
6009 		{
6010 			int j;
6011 
6012 			currentdirectoryindex = i;
6013 
6014 			for ( j = 0; j < s_pmi[i].nskins; j++ )
6015 			{
6016 				if ( Q_strcasecmp( s_pmi[i].skindisplaynames[j], currentskin ) == 0 )
6017 				{
6018 					currentskinindex = j;
6019 					break;
6020 				}
6021 			}
6022 		}
6023 	}
6024 
6025 	setup_window (s_player_config_screen, s_player_config_menu, "PLAYER SETUP");
6026 
6027 	s_player_name_field.generic.type = MTYPE_FIELD;
6028 	s_player_name_field.generic.name = "name";
6029 	s_player_name_field.generic.localstrings[0] = "name";
6030 	s_player_name_field.generic.callback = StrFieldCallback;
6031 	s_player_name_field.length	= 20;
6032 	s_player_name_field.generic.visible_length = LONGINPUT_SIZE;
6033 	Q_strncpyz2( s_player_name_field.buffer, name->string, sizeof(s_player_name_field.buffer) );
6034 	s_player_name_field.cursor = strlen( s_player_name_field.buffer );
6035 
6036 	// Horizontal submenu with two items. The first is a password field. The
6037 	// second is an apply button for the password.
6038 	s_player_password_submenu.generic.type = MTYPE_SUBMENU;
6039 	// Keep the password field horizontally lined up:
6040 	s_player_password_submenu.generic.flags = QMF_SNUG_LEFT;
6041 	s_player_password_submenu.navagable = true;
6042 	s_player_password_submenu.horizontal = true;
6043 	s_player_password_submenu.nitems = 0;
6044 
6045 	// sub-submenu for the password field. Purely for formatting/layout
6046 	// purposes.
6047 	s_player_password_field_submenu.generic.type = MTYPE_SUBMENU;
6048 	s_player_password_field_submenu.navagable = true;
6049 	s_player_password_field_submenu.horizontal = true;
6050 	s_player_password_field_submenu.nitems = 0;
6051 	// keep the password field horizontally lined up:
6052 	LINK (s_player_config_menu.lwidth, s_player_password_field_submenu.lwidth);
6053 	// keep it vertically centered on the apply button
6054 	LINK (s_player_password_submenu.height, s_player_password_field_submenu.height);
6055 
6056 	s_player_password_field.generic.type = MTYPE_FIELD;
6057 	s_player_password_field.generic.name = "password";
6058 	s_player_password_field.generic.flags = QMF_ACTION_WAIT;
6059 	s_player_password_field.generic.callback = PasswordCallback;
6060 	s_player_password_field.length	= 20;
6061 	s_player_password_field.generic.visible_length = LONGINPUT_SIZE;
6062 	s_player_password_field.generic.statusbar = "COR Entertainment is not responsible for lost or stolen passwords";
6063 	Q_strncpyz2( s_player_password_field.buffer, "********", sizeof(s_player_password_field.buffer) );
6064 	s_player_password_field.cursor = 0;
6065 	Menu_AddItem( &s_player_password_submenu, &s_player_password_field_submenu);
6066 	Menu_AddItem( &s_player_password_field_submenu, &s_player_password_field);
6067 
6068 	add_action (s_player_password_submenu, "Apply", PConfigApplyFunc, 0);
6069 
6070 	// Horizontal submenu with two items. The first is a submenu with the
6071 	// model/skin controls. The second is just a thumbnail of the current
6072 	// selection.
6073 	s_player_skin_submenu.generic.type = MTYPE_SUBMENU;
6074 	// Keep the model/skin controls horizontally lined up:
6075 	s_player_skin_submenu.generic.flags = QMF_SNUG_LEFT;
6076 	s_player_skin_submenu.navagable = true;
6077 	s_player_skin_submenu.horizontal = true;
6078 	s_player_skin_submenu.nitems = 0;
6079 
6080 	// Vertical sub-submenu with two items. The first is the model control.
6081 	// The second is the skin control.
6082 	s_player_skin_controls_submenu.generic.type = MTYPE_SUBMENU;
6083 	s_player_skin_controls_submenu.navagable = true;
6084 	s_player_skin_controls_submenu.nitems = 0;
6085 	// keep the model/skin controls horizontally lined up:
6086 	LINK (s_player_config_menu.lwidth, s_player_skin_controls_submenu.lwidth);
6087 
6088 	s_player_model_box.generic.type = MTYPE_SPINCONTROL;
6089 	s_player_model_box.generic.name = "model";
6090 	s_player_model_box.generic.callback = ModelCallback;
6091 	s_player_model_box.curvalue = currentdirectoryindex;
6092 	s_player_model_box.itemnames = (const char **) s_pmnames;
6093 
6094 	s_player_skin_box.generic.type = MTYPE_SPINCONTROL;
6095 	s_player_skin_box.generic.callback = SkinCallback;
6096 	s_player_skin_box.generic.name = "skin";
6097 	s_player_skin_box.curvalue = currentskinindex;
6098 	s_player_skin_box.itemnames = (const char **) s_pmi[currentdirectoryindex].skindisplaynames;
6099 
6100 	Menu_AddItem( &s_player_skin_controls_submenu, &s_player_model_box );
6101 	if ( s_player_skin_box.itemnames )
6102 		Menu_AddItem( &s_player_skin_controls_submenu, &s_player_skin_box );
6103 
6104 	Menu_AddItem (&s_player_skin_submenu, &s_player_skin_controls_submenu);
6105 
6106 	// TODO: click this to cycle skins
6107 	s_player_thumbnail.generic.type = MTYPE_NOT_INTERACTIVE;
6108 	VectorSet(s_player_thumbnail.generic.localints, 5, 5, 0);
6109 	s_player_thumbnail.generic.itemsizecallback = PicSizeFunc;
6110 	s_player_thumbnail.generic.itemdraw = PlayerPicDrawFunc;
6111 	Menu_AddItem (&s_player_skin_submenu, &s_player_thumbnail);
6112 
6113 	s_player_skin_preview_submenu.generic.type = MTYPE_SUBMENU;
6114 	s_player_skin_preview_submenu.generic.flags = QMF_SNUG_LEFT;
6115 	s_player_skin_preview_submenu.nitems = 0;
6116 
6117 	Menu_AddItem( &s_player_config_menu, &s_player_name_field );
6118 	Menu_AddItem( &s_player_config_menu, &s_player_password_submenu);
6119 	Menu_AddItem( &s_player_config_menu, &s_player_skin_submenu);
6120 
6121 	s_player_skin_preview.generic.type = MTYPE_NOT_INTERACTIVE;
6122 	s_player_skin_preview.generic.namesizecallback = PlayerConfigModelSizeFunc;
6123 	s_player_skin_preview.generic.namedraw = PlayerModelDrawFunc;
6124 
6125 	Menu_AddItem (&s_player_config_menu, &s_player_skin_preview_submenu);
6126 	Menu_AddItem (&s_player_skin_preview_submenu, &s_player_skin_preview);
6127 
6128 	//add in shader support for player models, if the player goes into the menu before entering a
6129 	//level, that way we see the shaders.  We only want to do this if they are NOT loaded yet.
6130 	scriptsloaded = Cvar_Get("scriptsloaded", "0", 0);
6131 	if(!scriptsloaded->value)
6132 	{
6133 		Cvar_SetValue("scriptsloaded", 1); //this needs to be reset on vid_restart
6134 		RS_ScanPathForScripts();
6135 		RS_LoadScript("scripts/models.rscript");
6136 		RS_LoadScript("scripts/caustics.rscript");
6137 		RS_LoadSpecialScripts();
6138 	}
6139 }
6140 
PlayerConfig_MenuDraw(menuframework_s * dummy,menuvec2_t offset)6141 void PlayerConfig_MenuDraw (menuframework_s *dummy, menuvec2_t offset)
6142 {
6143 	if(!PLAYER_NAME_UNIQUE)
6144 		s_player_config_menu.statusbar = "You must change your player name before joining a server!";
6145 
6146 	if ( s_pmi[s_player_model_box.curvalue].skindisplaynames )
6147 	{
6148 		s_player_skin_preview.name = s_pmi[s_player_model_box.curvalue].directory;
6149 		s_player_skin_preview.skin = s_pmi[s_player_model_box.curvalue].skindisplaynames[s_player_skin_box.curvalue];
6150 		Screen_Draw (&s_player_config_screen, offset);
6151 	}
6152 }
6153 
M_Menu_PlayerConfig_f(void)6154 void M_Menu_PlayerConfig_f (void)
6155 {
6156 	PlayerConfig_MenuInit();
6157 	M_PushMenu (PlayerConfig_MenuDraw, Default_MenuKey, &s_player_config_screen);
6158 }
6159 
6160 /*
6161 =======================================================================
6162 
6163 ALIEN ARENA TACTICAL MENU
6164 
6165 =======================================================================
6166 */
6167 
6168 static menuframework_s	s_tactical_screen;
6169 static menuaction_s		s_tactical_title_action;
6170 
6171 #define num_tactical_teams		2
6172 #define num_tactical_classes	3
6173 static const char *tactical_skin_names[num_tactical_teams][num_tactical_classes][2] =
6174 {
6175 	//ALIEN CLASSES
6176 	{
6177 		{"Enforcer",	"martianenforcer"},
6178 		{"Warrior",		"martianwarrior"},
6179 		{"Overlord",	"martianoverlord"}
6180 	},
6181 	//HUMAN CLASSES
6182 	{
6183 		{"Lauren",		"lauren"},
6184 		{"Enforcer",	"enforcer"},
6185 		{"Commander",	"commander"}
6186 	}
6187 };
6188 
6189 static const char *tactical_team_names[num_tactical_teams] =
6190 {
6191 	"ALIENS",
6192 	"HUMANS"
6193 };
6194 
6195 static menuframework_s	s_tactical_menus[num_tactical_teams];
6196 static menuframework_s	s_tactical_columns[num_tactical_teams][num_tactical_classes];
6197 static menuaction_s 	s_tactical_skin_actions[num_tactical_teams][num_tactical_classes];
6198 static menumodel_s 		s_tactical_skin_previews[num_tactical_teams][num_tactical_classes];
6199 
TacticalJoinFunc(void * item)6200 static void TacticalJoinFunc ( void *item )
6201 {
6202 	menuaction_s *self;
6203 	char buffer[128];
6204 
6205 	self = (menuaction_s*)item;
6206 
6207 	cl.tactical = true;
6208 
6209 	//set skin and model
6210 	Com_sprintf (buffer, sizeof(buffer), "%s/default", self->generic.localstrings[0]);
6211 	Cvar_Set ("skin", buffer);
6212 
6213 	//join server
6214 	Com_sprintf (buffer, sizeof(buffer), "connect %s\n", NET_AdrToString (mservers[serverindex].local_server_netadr));
6215 	Cbuf_AddText (buffer);
6216 	M_ForceMenuOff ();
6217 }
6218 
TacticalScreen_Draw(menuframework_s * screen,menuvec2_t offset)6219 static void TacticalScreen_Draw (menuframework_s *screen, menuvec2_t offset)
6220 {
6221 	FNT_font_t font = FNT_AutoGet (CL_menuFont);
6222 	screen->x = offset.x;
6223 	Menu_AutoArrange (screen);
6224 	// force it to use up the whole screen
6225 	CHASELINK(s_tactical_screen.rwidth) = viddef.width - CHASELINK(s_tactical_screen.lwidth);
6226 	Menu_Draw (screen, font);
6227 }
6228 
M_Menu_Tactical_f(void)6229 static void M_Menu_Tactical_f (void)
6230 {
6231 	extern cvar_t *name;
6232 	float scale;
6233 	int i, j;
6234 
6235 	scale = (float)(viddef.height)/600;
6236 
6237 	for (i = 0; i < num_tactical_teams; i++)
6238 	{
6239 		// kinda hacky but this is the only place we have two windows in one
6240 		// screen
6241 		setup_nth_window (s_tactical_screen, i, s_tactical_menus[i], tactical_team_names[i]);
6242 
6243 		s_tactical_menus[i].horizontal = true;
6244 
6245 		for (j = 0; j < num_tactical_classes; j++)
6246 		{
6247 			s_tactical_columns[i][j].generic.type = MTYPE_SUBMENU;
6248 			s_tactical_columns[i][j].nitems = 0;
6249 			s_tactical_columns[i][j].navagable = true;
6250 			Menu_AddItem (&s_tactical_menus[i], &s_tactical_columns[i][j]);
6251 
6252 			s_tactical_skin_previews[i][j].generic.type = MTYPE_NOT_INTERACTIVE;
6253 			s_tactical_skin_previews[i][j].generic.namesizecallback = PlayerModelSizeFunc;
6254 			s_tactical_skin_previews[i][j].generic.namedraw = PlayerModelDrawFunc;
6255 			s_tactical_skin_previews[i][j].name = tactical_skin_names[i][j][1];
6256 			s_tactical_skin_previews[i][j].skin = "default";
6257 			s_tactical_skin_previews[i][j].h = 14;
6258 			s_tactical_skin_previews[i][j].w = 10;
6259 			Menu_AddItem (&s_tactical_columns[i][j], &s_tactical_skin_previews[i][j]);
6260 
6261 			s_tactical_skin_actions[i][j].generic.type = MTYPE_ACTION;
6262 			s_tactical_skin_actions[i][j].generic.flags = QMF_BUTTON;
6263 			s_tactical_skin_actions[i][j].generic.name = tactical_skin_names[i][j][0];
6264 			s_tactical_skin_actions[i][j].generic.localstrings[0] = tactical_skin_names[i][j][1];
6265 			s_tactical_skin_actions[i][j].generic.callback = TacticalJoinFunc;
6266 			Menu_AddItem (&s_tactical_columns[i][j], &s_tactical_skin_actions[i][j]);
6267 		}
6268 	}
6269 
6270 	//add in shader support for player models, if the player goes into the menu before entering a
6271 	//level, that way we see the shaders.  We only want to do this if they are NOT loaded yet.
6272 	scriptsloaded = Cvar_Get("scriptsloaded", "0", 0);
6273 	if(!scriptsloaded->value)
6274 	{
6275 		Cvar_SetValue("scriptsloaded", 1); //this needs to be reset on vid_restart
6276 		RS_ScanPathForScripts();
6277 		RS_LoadScript("scripts/models.rscript");
6278 		RS_LoadScript("scripts/caustics.rscript");
6279 		RS_LoadSpecialScripts();
6280 	}
6281 
6282 	M_PushMenu (TacticalScreen_Draw, Default_MenuKey, &s_tactical_screen);
6283 }
6284 
6285 
6286 /*
6287 =======================================================================
6288 
6289 QUIT MENU
6290 
6291 =======================================================================
6292 */
6293 
6294 static menuframework_s	s_quit_screen;
6295 static menuframework_s	s_quit_menu;
6296 
quitActionNo(void * blah)6297 void quitActionNo (void *blah)
6298 {
6299 	M_PopMenu();
6300 }
quitActionYes(void * blah)6301 void quitActionYes (void *blah)
6302 {
6303 	CL_Quit_f();
6304 }
6305 
M_Menu_Quit_f(void)6306 static void M_Menu_Quit_f (void)
6307 {
6308 	setup_window (s_quit_screen, s_quit_menu, "EXIT ALIEN ARENA");
6309 
6310 	add_text (s_quit_menu, "Are you sure?", 0);
6311 	add_action (s_quit_menu, "Yes", quitActionYes, 0);
6312 	add_action (s_quit_menu, "No", quitActionNo, 0);
6313 
6314 	Menu_AutoArrange (&s_quit_screen);
6315 	Menu_Center (&s_quit_screen);
6316 
6317 	M_PushMenu_Defaults (s_quit_screen);
6318 }
6319 
6320 //=============================================================================
6321 /* Menu Subsystem */
6322 
6323 
6324 /*
6325 =================
6326 M_Init
6327 =================
6328 */
M_Init(void)6329 void M_Init (void)
6330 {
6331 	Cmd_AddCommand ("menu_main", M_Menu_Main_f);
6332 	Cmd_AddCommand ("menu_quit", M_Menu_Quit_f);
6333 }
6334 
6335 
6336 /*
6337 =================================
6338 Menu Mouse Cursor
6339 =================================
6340 */
6341 
refreshCursorLink(void)6342 void refreshCursorLink (void)
6343 {
6344 	Cursor_SelectItem (NULL);
6345 	cursor.click_menuitem = NULL;
6346 }
6347 
Slider_CursorPositionX(menuslider_s * s)6348 int Slider_CursorPositionX ( menuslider_s *s )
6349 {
6350 	float		range;
6351 	FNT_font_t	font;
6352 
6353 	font = FNT_AutoGet( CL_menuFont );
6354 
6355 	range = ( s->curvalue - s->minvalue ) / ( float ) ( s->maxvalue - s->minvalue );
6356 
6357 	if ( range < 0)
6358 		range = 0;
6359 	if ( range > 1)
6360 		range = 1;
6361 
6362 	return ( int )( font->width + RCOLUMN_OFFSET + (LONGINPUT_SIZE) * font->width * range );
6363 }
6364 
newSliderValueForX(int x,menuslider_s * s)6365 int newSliderValueForX (int x, menuslider_s *s)
6366 {
6367 	float 		newValue;
6368 	int 		newValueInt;
6369 	FNT_font_t	font;
6370 	int			pos;
6371 
6372 	font = FNT_AutoGet( CL_menuFont );
6373 
6374 	pos = x - (font->width + RCOLUMN_OFFSET + CHASELINK(s->generic.x)) - Menu_GetCtrX(*(s->generic.parent));
6375 
6376 	newValue = ((float)pos)/((LONGINPUT_SIZE-1)*font->width);
6377 	newValueInt = s->minvalue + newValue * (float)( s->maxvalue - s->minvalue );
6378 
6379 	return newValueInt;
6380 }
6381 
Slider_CheckSlide(menuslider_s * s)6382 void Slider_CheckSlide( menuslider_s *s )
6383 {
6384 	if ( s->curvalue > s->maxvalue )
6385 		s->curvalue = s->maxvalue;
6386 	else if ( s->curvalue < s->minvalue )
6387 		s->curvalue = s->minvalue;
6388 
6389 	if ( s->generic.callback )
6390 		s->generic.callback( s );
6391 }
6392 
Menu_DragSlideItem(void)6393 void Menu_DragSlideItem (void)
6394 {
6395 	menuslider_s *slider = ( menuslider_s * ) cursor.menuitem;
6396 
6397 	slider->curvalue = newSliderValueForX(cursor.x, slider);
6398 	Slider_CheckSlide ( slider );
6399 }
6400 
Menu_ClickSlideItem(void)6401 void Menu_ClickSlideItem (void)
6402 {
6403 	int min, max;
6404 	menuslider_s *slider = ( menuslider_s * ) cursor.menuitem;
6405 
6406 	min = Item_GetX (*slider) + Slider_CursorPositionX(slider) - 4;
6407 	max = Item_GetX (*slider) + Slider_CursorPositionX(slider) + 4;
6408 
6409 	if (cursor.x < min)
6410 		Menu_SlideItem (-1 );
6411 	if (cursor.x > max)
6412 		Menu_SlideItem (1);
6413 }
6414 
Menu_DragVertScrollItem(void)6415 void Menu_DragVertScrollItem (void)
6416 {
6417 	float			scrollbar_pos;
6418 	menuframework_s	*menu = cursor.menuitem->generic.parent;
6419 
6420 	scrollbar_pos = (float)cursor.y - menu->scroll_top;
6421 	menu->yscroll = scrollbar_pos*menu->maxscroll/(menu->scroll_range-menu->scrollbar_size);
6422 
6423 	if (menu->yscroll < 0)
6424 		menu->yscroll = 0;
6425 	if (menu->yscroll > menu->maxscroll)
6426 		menu->yscroll = menu->maxscroll;
6427 }
6428 
M_Draw_Cursor(void)6429 void M_Draw_Cursor (void)
6430 {
6431 	Draw_Pic (cursor.x, cursor.y, "m_mouse_cursor");
6432 }
6433 
6434 
6435 // draw all menus on screen
M_Draw(void)6436 void M_Draw (void)
6437 {
6438 	if (cls.key_dest != key_menu)
6439 		return;
6440 	Draw_Fill (0, 0, viddef.width, viddef.height, RGBA(0, 0, 0, 1));
6441 	Menuscreens_Animate ();
6442 	if (mstate.state == mstate_steady)
6443 		Menu_DrawHighlight ();
6444 	M_Draw_Cursor();
6445 }
6446 
6447 // send key presses to the appropriate menu
M_Keydown(int key)6448 void M_Keydown (int key)
6449 {
6450 	const char *s;
6451 
6452 	if (mstate.state != mstate_steady)
6453 		return;
6454 
6455 	if (key == K_ESCAPE && mstate.active.num_layers > 0)
6456 	{
6457 		if ((s = layergroup_last(mstate.active).key (layergroup_last(mstate.active).screen, key)))
6458 			S_StartLocalSound (s);
6459 		return;
6460 	}
6461 
6462 	if (cursor.menulayer == -1)
6463 		M_Main_Key (key);
6464 	else if (activelayer(cursor.menulayer).key != NULL && (s = activelayer(cursor.menulayer).key (activelayer(cursor.menulayer).screen, key)))
6465 		S_StartLocalSound (s);
6466 }
6467 
6468 // send mouse movement to the appropriate menu
M_Think_MouseCursor(void)6469 void M_Think_MouseCursor (void)
6470 {
6471 	int coordidx;
6472 	menuframework_s *m;
6473 	char * sound = NULL;
6474 
6475 	if (mstate.state != mstate_steady)
6476 		return;
6477 
6478 	coordidx = activelayer_coordidx (cursor.x);
6479 	if (coordidx < 0)
6480 	{
6481 		CheckMainMenuMouse ();
6482 		return;
6483 	}
6484 
6485 	if (cursor.buttondown[MOUSEBUTTON2] && cursor.buttonclicks[MOUSEBUTTON2] == 2 && !cursor.buttonused[MOUSEBUTTON2])
6486 	{
6487 		M_PopMenu ();
6488 
6489 		// we've "used" the click sequence and will begin another
6490 		refreshCursorButton (MOUSEBUTTON2);
6491 		S_StartLocalSound (menu_out_sound);
6492 		return;
6493 	}
6494 
6495 	if (coordidx == mstate.active.num_layers)
6496 	{
6497 		if (cursor.mouseaction)
6498 			cursor.menuitem = NULL;
6499 		return;
6500 	}
6501 
6502 	if (coordidx != cursor.menulayer && cursor.mouseaction)
6503 		Cursor_SelectMenu(activelayer(coordidx).screen);
6504 
6505 	Menu_AssignCursor (activelayer(coordidx).screen);
6506 
6507 	if (cursor.menuitem == NULL)
6508 		return;
6509 
6510 	m = cursor.menuitem->generic.parent;
6511 
6512 	if (!m)
6513 		return;
6514 
6515 	if (cursor.buttondown[MOUSEBUTTON1] && !cursor.suppress_drag)
6516 	{
6517 		if (cursor.click_menuitem != NULL)
6518 			Cursor_SelectItem (cursor.click_menuitem);
6519 		else if (cursor.menuitem != NULL)
6520 			cursor.click_menuitem = cursor.menuitem;
6521 	}
6522 	else
6523 		cursor.click_menuitem = NULL;
6524 
6525 	if (!cursor.buttondown[MOUSEBUTTON1])
6526 		cursor.suppress_drag = false;
6527 	else if (!cursor.menuitem)
6528 		cursor.suppress_drag = true;
6529 
6530 	if (cursor.suppress_drag || cursor.menuitem == NULL)
6531 		return;
6532 
6533 	//MOUSE1
6534 	if (cursor.buttondown[MOUSEBUTTON1])
6535 	{
6536 		if (cursor.menuitem->generic.type == MTYPE_SLIDER)
6537 		{
6538 			Menu_DragSlideItem ();
6539 		}
6540 		else if (cursor.menuitem->generic.type == MTYPE_VERT_SCROLLBAR)
6541 		{
6542 			Menu_DragVertScrollItem ();
6543 		}
6544 		else if (!cursor.buttonused[MOUSEBUTTON1])
6545 		{
6546 			if (cursor.menuitem->generic.type == MTYPE_SPINCONTROL)
6547 				Menu_SlideItem (1);
6548 			else
6549 				Menu_ActivateItem (cursor.menuitem);
6550 
6551 			// we've "used" the click sequence and will begin another
6552 			refreshCursorButton (MOUSEBUTTON1);
6553 			sound = menu_move_sound;
6554 		}
6555 	}
6556 	//MOUSE2
6557 	else if (cursor.buttondown[MOUSEBUTTON2] && !cursor.buttonused[MOUSEBUTTON2])
6558 	{
6559 		if (cursor.menuitem->generic.type == MTYPE_SPINCONTROL)
6560 			Menu_SlideItem (-1);
6561 		else if (cursor.menuitem->generic.type == MTYPE_SLIDER)
6562 			Menu_ClickSlideItem ();
6563 		else
6564 			return;
6565 
6566 		// we've "used" the click sequence and will begin another
6567 		refreshCursorButton (MOUSEBUTTON2);
6568 		sound = menu_move_sound;
6569 	}
6570 
6571 	if ( sound )
6572 		S_StartLocalSound( sound );
6573 }
6574