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