1 // SONIC ROBO BLAST 2
2 //-----------------------------------------------------------------------------
3 // Copyright (C) 1993-1996 by id Software, Inc.
4 // Copyright (C) 1998-2000 by DooM Legacy Team.
5 // Copyright (C) 2011-2016 by Matthew "Kaito Sinclaire" Walsh.
6 // Copyright (C) 1999-2020 by Sonic Team Junior.
7 //
8 // This program is free software distributed under the
9 // terms of the GNU General Public License, version 2.
10 // See the 'LICENSE' file for more details.
11 //-----------------------------------------------------------------------------
12 /// \file m_menu.c
13 /// \brief XMOD's extremely revamped menu system.
14
15 #ifdef __GNUC__
16 #include <unistd.h>
17 #endif
18
19 #include "m_menu.h"
20
21 #include "doomdef.h"
22 #include "d_main.h"
23 #include "d_netcmd.h"
24 #include "console.h"
25 #include "r_local.h"
26 #include "hu_stuff.h"
27 #include "g_game.h"
28 #include "g_input.h"
29 #include "m_argv.h"
30
31 // Data.
32 #include "sounds.h"
33 #include "s_sound.h"
34 #include "i_system.h"
35 #include "i_threads.h"
36
37 // Addfile
38 #include "filesrch.h"
39
40 #include "v_video.h"
41 #include "i_video.h"
42 #include "keys.h"
43 #include "z_zone.h"
44 #include "w_wad.h"
45 #include "p_local.h"
46 #include "p_setup.h"
47 #include "f_finale.h"
48 #include "lua_hook.h"
49
50 #ifdef HWRENDER
51 #include "hardware/hw_main.h"
52 #endif
53
54 #include "d_net.h"
55 #include "mserv.h"
56 #include "m_misc.h"
57 #include "m_anigif.h"
58 #include "byteptr.h"
59 #include "st_stuff.h"
60 #include "i_sound.h"
61 #include "fastcmp.h"
62
63 #include "i_joy.h" // for joystick menu controls
64
65 // Condition Sets
66 #include "m_cond.h"
67
68 // And just some randomness for the exits.
69 #include "m_random.h"
70
71 #if defined(HAVE_SDL)
72 #include "SDL.h"
73 #if SDL_VERSION_ATLEAST(2,0,0)
74 #include "sdl/sdlmain.h" // JOYSTICK_HOTPLUG
75 #endif
76 #endif
77
78 #if defined (__GNUC__) && (__GNUC__ >= 4)
79 #define FIXUPO0
80 #endif
81
82 #define SKULLXOFF -32
83 #define LINEHEIGHT 16
84 #define STRINGHEIGHT 8
85 #define FONTBHEIGHT 20
86 #define SMALLLINEHEIGHT 8
87 #define SLIDER_RANGE 9
88 #define SLIDER_WIDTH 78
89 #define SERVERS_PER_PAGE 11
90
91 typedef enum
92 {
93 QUITMSG = 0,
94 QUITMSG1,
95 QUITMSG2,
96 QUITMSG3,
97 QUITMSG4,
98 QUITMSG5,
99 QUITMSG6,
100 QUITMSG7,
101
102 QUIT2MSG,
103 QUIT2MSG1,
104 QUIT2MSG2,
105 QUIT2MSG3,
106 QUIT2MSG4,
107 QUIT2MSG5,
108 QUIT2MSG6,
109
110 QUIT3MSG,
111 QUIT3MSG1,
112 QUIT3MSG2,
113 QUIT3MSG3,
114 QUIT3MSG4,
115 QUIT3MSG5,
116 QUIT3MSG6,
117 NUM_QUITMESSAGES
118 } text_enum;
119
120 #ifdef HAVE_THREADS
121 I_mutex m_menu_mutex;
122 #endif
123
124 M_waiting_mode_t m_waiting_mode = M_NOT_WAITING;
125
126 const char *quitmsg[NUM_QUITMESSAGES];
127
128 // Stuff for customizing the player select screen Tails 09-22-2003
129 description_t description[MAXSKINS];
130 INT16 char_on = -1, startchar = 0;
131 static char *char_notes = NULL;
132
133 boolean menuactive = false;
134 boolean fromlevelselect = false;
135
136 typedef enum
137 {
138 LLM_CREATESERVER,
139 LLM_LEVELSELECT,
140 LLM_RECORDATTACK,
141 LLM_NIGHTSATTACK
142 } levellist_mode_t;
143
144 levellist_mode_t levellistmode = LLM_CREATESERVER;
145 UINT8 maplistoption = 0;
146
147 static char joystickInfo[MAX_JOYSTICKS+1][29];
148 #ifndef NONET
149 static UINT32 serverlistpage;
150 #endif
151
152 static UINT8 numsaves = 0;
153 static saveinfo_t* savegameinfo = NULL; // Extra info about the save games.
154 static patch_t *savselp[7];
155
156 INT16 startmap; // Mario, NiGHTS, or just a plain old normal game?
157
158 static INT16 itemOn = 1; // menu item skull is on, Hack by Tails 09-18-2002
159 static INT16 skullAnimCounter = 10; // skull animation counter
160
161 static boolean setupcontrols_secondaryplayer;
162 static INT32 (*setupcontrols)[2]; // pointer to the gamecontrols of the player being edited
163
164 // shhh... what am I doing... nooooo!
165 static INT32 vidm_testingmode = 0;
166 static INT32 vidm_previousmode;
167 static INT32 vidm_selected = 0;
168 static INT32 vidm_nummodes;
169 static INT32 vidm_column_size;
170
171 // new menus
172 static tic_t recatkdrawtimer = 0;
173 static tic_t ntsatkdrawtimer = 0;
174
175 static tic_t charseltimer = 0;
176 static fixed_t char_scroll = 0;
177 #define charscrollamt 128*FRACUNIT
178
179 static tic_t keydown = 0;
180
181 //
182 // PROTOTYPES
183 //
184
185 static void M_GoBack(INT32 choice);
186 static void M_StopMessage(INT32 choice);
187 static boolean stopstopmessage = false;
188
189 #ifndef NONET
190 static void M_HandleServerPage(INT32 choice);
191 static void M_RoomMenu(INT32 choice);
192 #endif
193
194 // Prototyping is fun, innit?
195 // ==========================================================================
196 // NEEDED FUNCTION PROTOTYPES GO HERE
197 // ==========================================================================
198
199 // the haxor message menu
200 menu_t MessageDef;
201
202 menu_t SPauseDef;
203
204 // Level Select
205 static levelselect_t levelselect = {0, NULL};
206 static UINT8 levelselectselect[3];
207 static patch_t *levselp[2][3];
208 static INT32 lsoffs[2];
209
210 #define lsrow levelselectselect[0]
211 #define lscol levelselectselect[1]
212 #define lshli levelselectselect[2]
213
214 #define lshseperation 101
215 #define lsbasevseperation (62*vid.height)/(BASEVIDHEIGHT*vid.dupy) //62
216 #define lsheadingheight 16
217 #define getheadingoffset(row) (levelselect.rows[row].header[0] ? lsheadingheight : 0)
218 #define lsvseperation(row) lsbasevseperation + getheadingoffset(row)
219 #define lswide(row) levelselect.rows[row].mapavailable[3]
220
221 #define lsbasex 19
222 #define lsbasey 59+lsheadingheight
223
224 // Sky Room
225 static void M_CustomLevelSelect(INT32 choice);
226 static void M_CustomWarp(INT32 choice);
227 FUNCNORETURN static ATTRNORETURN void M_UltimateCheat(INT32 choice);
228 static void M_LoadGameLevelSelect(INT32 choice);
229 static void M_AllowSuper(INT32 choice);
230 static void M_GetAllEmeralds(INT32 choice);
231 static void M_DestroyRobots(INT32 choice);
232 static void M_LevelSelectWarp(INT32 choice);
233 static void M_Credits(INT32 choice);
234 static void M_SoundTest(INT32 choice);
235 static void M_PandorasBox(INT32 choice);
236 static void M_EmblemHints(INT32 choice);
237 static void M_HandleEmblemHints(INT32 choice);
238 UINT32 hintpage = 1;
239 static void M_HandleChecklist(INT32 choice);
240 menu_t SR_MainDef, SR_UnlockChecklistDef;
241
242 static UINT8 check_on;
243
244 // Misc. Main Menu
245 static void M_SinglePlayerMenu(INT32 choice);
246 static void M_Options(INT32 choice);
247 static void M_SelectableClearMenus(INT32 choice);
248 static void M_Retry(INT32 choice);
249 static void M_EndGame(INT32 choice);
250 static void M_MapChange(INT32 choice);
251 static void M_ChangeLevel(INT32 choice);
252 static void M_ConfirmSpectate(INT32 choice);
253 static void M_ConfirmEnterGame(INT32 choice);
254 static void M_ConfirmTeamScramble(INT32 choice);
255 static void M_ConfirmTeamChange(INT32 choice);
256 static void M_SecretsMenu(INT32 choice);
257 static void M_SetupChoosePlayer(INT32 choice);
258 static UINT8 M_SetupChoosePlayerDirect(INT32 choice);
259 static void M_QuitSRB2(INT32 choice);
260 menu_t SP_MainDef, OP_MainDef;
261 menu_t MISC_ScrambleTeamDef, MISC_ChangeTeamDef;
262
263 // Single Player
264 static void M_StartTutorial(INT32 choice);
265 static void M_LoadGame(INT32 choice);
266 static void M_HandleTimeAttackLevelSelect(INT32 choice);
267 static void M_TimeAttackLevelSelect(INT32 choice);
268 static void M_TimeAttack(INT32 choice);
269 static void M_NightsAttackLevelSelect(INT32 choice);
270 static void M_NightsAttack(INT32 choice);
271 static void M_Statistics(INT32 choice);
272 static void M_ReplayTimeAttack(INT32 choice);
273 static void M_ChooseTimeAttack(INT32 choice);
274 static void M_ChooseNightsAttack(INT32 choice);
275 static void M_ModeAttackEndGame(INT32 choice);
276 static void M_SetGuestReplay(INT32 choice);
277 static void M_HandleChoosePlayerMenu(INT32 choice);
278 static void M_ChoosePlayer(INT32 choice);
279 static void M_MarathonLiveEventBackup(INT32 choice);
280 static void M_Marathon(INT32 choice);
281 static void M_HandleMarathonChoosePlayer(INT32 choice);
282 static void M_StartMarathon(INT32 choice);
283 menu_t SP_LevelStatsDef;
284 static menu_t SP_TimeAttackDef, SP_ReplayDef, SP_GuestReplayDef, SP_GhostDef;
285 static menu_t SP_NightsAttackDef, SP_NightsReplayDef, SP_NightsGuestReplayDef, SP_NightsGhostDef;
286 static menu_t SP_MarathonDef;
287
288 // Multiplayer
289 static void M_SetupMultiPlayer(INT32 choice);
290 static void M_SetupMultiPlayer2(INT32 choice);
291 static void M_StartSplitServerMenu(INT32 choice);
292 static void M_StartServer(INT32 choice);
293 static void M_ServerOptions(INT32 choice);
294 #ifndef NONET
295 static void M_StartServerMenu(INT32 choice);
296 static void M_ConnectMenu(INT32 choice);
297 static void M_ConnectMenuModChecks(INT32 choice);
298 static void M_Refresh(INT32 choice);
299 static void M_Connect(INT32 choice);
300 static void M_ChooseRoom(INT32 choice);
301 menu_t MP_MainDef;
302 #endif
303
304 // Options
305 // Split into multiple parts due to size
306 // Controls
307 menu_t OP_ChangeControlsDef;
308 menu_t OP_MPControlsDef, OP_MiscControlsDef;
309 menu_t OP_P1ControlsDef, OP_P2ControlsDef, OP_MouseOptionsDef;
310 menu_t OP_Mouse2OptionsDef, OP_Joystick1Def, OP_Joystick2Def;
311 menu_t OP_CameraOptionsDef, OP_Camera2OptionsDef;
312 menu_t OP_PlaystyleDef;
313 static void M_VideoModeMenu(INT32 choice);
314 static void M_Setup1PControlsMenu(INT32 choice);
315 static void M_Setup2PControlsMenu(INT32 choice);
316 static void M_Setup1PJoystickMenu(INT32 choice);
317 static void M_Setup2PJoystickMenu(INT32 choice);
318 static void M_Setup1PPlaystyleMenu(INT32 choice);
319 static void M_Setup2PPlaystyleMenu(INT32 choice);
320 static void M_AssignJoystick(INT32 choice);
321 static void M_ChangeControl(INT32 choice);
322
323 // Video & Sound
324 static void M_VideoOptions(INT32 choice);
325 menu_t OP_VideoOptionsDef, OP_VideoModeDef, OP_ColorOptionsDef;
326 #ifdef HWRENDER
327 static void M_OpenGLOptionsMenu(void);
328 menu_t OP_OpenGLOptionsDef;
329 #ifdef ALAM_LIGHTING
330 menu_t OP_OpenGLLightingDef;
331 #endif // ALAM_LIGHTING
332 #endif // HWRENDER
333 menu_t OP_SoundOptionsDef;
334 menu_t OP_SoundAdvancedDef;
335
336 //Misc
337 menu_t OP_DataOptionsDef, OP_ScreenshotOptionsDef, OP_EraseDataDef;
338 menu_t OP_ServerOptionsDef;
339 menu_t OP_MonitorToggleDef;
340 static void M_ScreenshotOptions(INT32 choice);
341 static void M_SetupScreenshotMenu(void);
342 static void M_EraseData(INT32 choice);
343
344 static void M_Addons(INT32 choice);
345 static void M_AddonsOptions(INT32 choice);
346 static patch_t *addonsp[NUM_EXT+5];
347
348 #define addonmenusize 9 // number of items actually displayed in the addons menu view, formerly (2*numaddonsshown + 1)
349 #define numaddonsshown 4 // number of items to each side of the currently selected item, unless at top/bottom ends of directory
350
351 static void M_DrawLevelPlatterHeader(INT32 y, const char *header, boolean headerhighlight, boolean allowlowercase);
352
353 // Drawing functions
354 static void M_DrawGenericMenu(void);
355 static void M_DrawGenericScrollMenu(void);
356 static void M_DrawCenteredMenu(void);
357 static void M_DrawAddons(void);
358 static void M_DrawChecklist(void);
359 static void M_DrawSoundTest(void);
360 static void M_DrawEmblemHints(void);
361 static void M_DrawPauseMenu(void);
362 static void M_DrawServerMenu(void);
363 static void M_DrawLevelPlatterMenu(void);
364 static void M_DrawImageDef(void);
365 static void M_DrawLoad(void);
366 static void M_DrawLevelStats(void);
367 static void M_DrawTimeAttackMenu(void);
368 static void M_DrawNightsAttackMenu(void);
369 static void M_DrawMarathon(void);
370 static void M_DrawSetupChoosePlayerMenu(void);
371 static void M_DrawControlsDefMenu(void);
372 static void M_DrawCameraOptionsMenu(void);
373 static void M_DrawPlaystyleMenu(void);
374 static void M_DrawControl(void);
375 static void M_DrawMainVideoMenu(void);
376 static void M_DrawVideoMode(void);
377 static void M_DrawColorMenu(void);
378 static void M_DrawScreenshotMenu(void);
379 static void M_DrawMonitorToggles(void);
380 #ifndef NONET
381 static void M_DrawConnectMenu(void);
382 static void M_DrawMPMainMenu(void);
383 static void M_DrawRoomMenu(void);
384 #endif
385 static void M_DrawJoystick(void);
386 static void M_DrawSetupMultiPlayerMenu(void);
387
388 // Handling functions
389 static boolean M_ExitPandorasBox(void);
390 static boolean M_QuitMultiPlayerMenu(void);
391 static void M_HandleAddons(INT32 choice);
392 static void M_HandleLevelPlatter(INT32 choice);
393 static void M_HandleSoundTest(INT32 choice);
394 static void M_HandleImageDef(INT32 choice);
395 static void M_HandleLoadSave(INT32 choice);
396 static void M_HandleLevelStats(INT32 choice);
397 static void M_HandlePlaystyleMenu(INT32 choice);
398 #ifndef NONET
399 static boolean M_CancelConnect(void);
400 static void M_HandleConnectIP(INT32 choice);
401 #endif
402 static void M_HandleSetupMultiPlayer(INT32 choice);
403 static void M_HandleVideoMode(INT32 choice);
404
405 static void M_ResetCvars(void);
406
407 // Consvar onchange functions
408 static void Newgametype_OnChange(void);
409 static void Dummymares_OnChange(void);
410
411 // ==========================================================================
412 // CONSOLE VARIABLES AND THEIR POSSIBLE VALUES GO HERE.
413 // ==========================================================================
414
415 consvar_t cv_showfocuslost = CVAR_INIT ("showfocuslost", "Yes", CV_SAVE, CV_YesNo, NULL);
416
417 static CV_PossibleValue_t map_cons_t[] = {
418 {1,"MIN"},
419 {NUMMAPS, "MAX"},
420 {0,NULL}
421 };
422 consvar_t cv_nextmap = CVAR_INIT ("nextmap", "1", CV_HIDEN|CV_CALL, map_cons_t, Nextmap_OnChange);
423
424 static CV_PossibleValue_t skins_cons_t[MAXSKINS+1] = {{1, DEFAULTSKIN}};
425 consvar_t cv_chooseskin = CVAR_INIT ("chooseskin", DEFAULTSKIN, CV_HIDEN|CV_CALL, skins_cons_t, Nextmap_OnChange);
426
427 // This gametype list is integral for many different reasons.
428 // When you add gametypes here, don't forget to update them in deh_tables.c and doomstat.h!
429 CV_PossibleValue_t gametype_cons_t[NUMGAMETYPES+1];
430
431 consvar_t cv_newgametype = CVAR_INIT ("newgametype", "Co-op", CV_HIDEN|CV_CALL, gametype_cons_t, Newgametype_OnChange);
432
433 static CV_PossibleValue_t serversort_cons_t[] = {
434 {0,"Ping"},
435 {1,"Modified State"},
436 {2,"Most Players"},
437 {3,"Least Players"},
438 {4,"Max Player Slots"},
439 {5,"Gametype"},
440 {0,NULL}
441 };
442 consvar_t cv_serversort = CVAR_INIT ("serversort", "Ping", CV_HIDEN | CV_CALL, serversort_cons_t, M_SortServerList);
443
444 // first time memory
445 consvar_t cv_tutorialprompt = CVAR_INIT ("tutorialprompt", "On", CV_SAVE, CV_OnOff, NULL);
446
447 // autorecord demos for time attack
448 static consvar_t cv_autorecord = CVAR_INIT ("autorecord", "Yes", 0, CV_YesNo, NULL);
449
450 CV_PossibleValue_t ghost_cons_t[] = {{0, "Hide"}, {1, "Show"}, {2, "Show All"}, {0, NULL}};
451 CV_PossibleValue_t ghost2_cons_t[] = {{0, "Hide"}, {1, "Show"}, {0, NULL}};
452
453 consvar_t cv_ghost_bestscore = CVAR_INIT ("ghost_bestscore", "Show", CV_SAVE, ghost_cons_t, NULL);
454 consvar_t cv_ghost_besttime = CVAR_INIT ("ghost_besttime", "Show", CV_SAVE, ghost_cons_t, NULL);
455 consvar_t cv_ghost_bestrings = CVAR_INIT ("ghost_bestrings", "Show", CV_SAVE, ghost_cons_t, NULL);
456 consvar_t cv_ghost_last = CVAR_INIT ("ghost_last", "Show", CV_SAVE, ghost_cons_t, NULL);
457 consvar_t cv_ghost_guest = CVAR_INIT ("ghost_guest", "Show", CV_SAVE, ghost2_cons_t, NULL);
458
459 //Console variables used solely in the menu system.
460 //todo: add a way to use non-console variables in the menu
461 // or make these consvars legitimate like color or skin.
462 static CV_PossibleValue_t dummyteam_cons_t[] = {{0, "Spectator"}, {1, "Red"}, {2, "Blue"}, {0, NULL}};
463 static CV_PossibleValue_t dummyscramble_cons_t[] = {{0, "Random"}, {1, "Points"}, {0, NULL}};
464 static CV_PossibleValue_t ringlimit_cons_t[] = {{0, "MIN"}, {9999, "MAX"}, {0, NULL}};
465 static CV_PossibleValue_t liveslimit_cons_t[] = {{1, "MIN"}, {99, "MAX"}, {-1, "Infinite"}, {0, NULL}};
466 static CV_PossibleValue_t contlimit_cons_t[] = {{0, "MIN"}, {99, "MAX"}, {0, NULL}};
467 static CV_PossibleValue_t dummymares_cons_t[] = {
468 {-1, "END"}, {0,"Overall"}, {1,"Mare 1"}, {2,"Mare 2"}, {3,"Mare 3"}, {4,"Mare 4"}, {5,"Mare 5"}, {6,"Mare 6"}, {7,"Mare 7"}, {8,"Mare 8"}, {0,NULL}
469 };
470
471 static consvar_t cv_dummyteam = CVAR_INIT ("dummyteam", "Spectator", CV_HIDEN, dummyteam_cons_t, NULL);
472 static consvar_t cv_dummyscramble = CVAR_INIT ("dummyscramble", "Random", CV_HIDEN, dummyscramble_cons_t, NULL);
473 static consvar_t cv_dummyrings = CVAR_INIT ("dummyrings", "0", CV_HIDEN, ringlimit_cons_t, NULL);
474 static consvar_t cv_dummylives = CVAR_INIT ("dummylives", "0", CV_HIDEN, liveslimit_cons_t, NULL);
475 static consvar_t cv_dummycontinues = CVAR_INIT ("dummycontinues", "0", CV_HIDEN, contlimit_cons_t, NULL);
476 static consvar_t cv_dummymares = CVAR_INIT ("dummymares", "Overall", CV_HIDEN|CV_CALL, dummymares_cons_t, Dummymares_OnChange);
477
478 CV_PossibleValue_t marathon_cons_t[] = {{0, "Standard"}, {1, "Live Event Backup"}, {2, "Ultimate"}, {0, NULL}};
479 CV_PossibleValue_t loadless_cons_t[] = {{0, "Realtime"}, {1, "In-game"}, {0, NULL}};
480
481 consvar_t cv_dummymarathon = CVAR_INIT ("dummymarathon", "Standard", CV_HIDEN, marathon_cons_t, NULL);
482 consvar_t cv_dummycutscenes = CVAR_INIT ("dummycutscenes", "Off", CV_HIDEN, CV_OnOff, NULL);
483 consvar_t cv_dummyloadless = CVAR_INIT ("dummyloadless", "In-game", CV_HIDEN, loadless_cons_t, NULL);
484
485 // ==========================================================================
486 // ORGANIZATION START.
487 // ==========================================================================
488 // Note: Never should we be jumping from one category of menu options to another
489 // without first going to the Main Menu.
490 // Note: Ignore the above if you're working with the Pause menu.
491 // Note: (Prefix)_MainMenu should be the target of all Main Menu options that
492 // point to submenus.
493
494 // ---------
495 // Main Menu
496 // ---------
497 static menuitem_t MainMenu[] =
498 {
499 {IT_STRING|IT_CALL, NULL, "1 Player", M_SinglePlayerMenu, 76},
500 #ifndef NONET
501 {IT_STRING|IT_SUBMENU, NULL, "Multiplayer", &MP_MainDef, 84},
502 #else
503 {IT_STRING|IT_CALL, NULL, "Multiplayer", M_StartSplitServerMenu, 84},
504 #endif
505 {IT_STRING|IT_CALL, NULL, "Extras", M_SecretsMenu, 92},
506 {IT_CALL |IT_STRING, NULL, "Addons", M_Addons, 100},
507 {IT_STRING|IT_CALL, NULL, "Options", M_Options, 108},
508 {IT_STRING|IT_CALL, NULL, "Quit Game", M_QuitSRB2, 116},
509 };
510
511 typedef enum
512 {
513 singleplr = 0,
514 multiplr,
515 secrets,
516 addons,
517 options,
518 quitdoom
519 } main_e;
520
521 static menuitem_t MISC_AddonsMenu[] =
522 {
523 {IT_KEYHANDLER | IT_NOTHING, NULL, "", M_HandleAddons, 0}, // dummy menuitem for the control func
524 };
525
526 // ---------------------------------
527 // Pause Menu Mode Attacking Edition
528 // ---------------------------------
529 static menuitem_t MAPauseMenu[] =
530 {
531 {IT_CALL | IT_STRING, NULL, "Emblem Hints...", M_EmblemHints, 32},
532
533 {IT_CALL | IT_STRING, NULL, "Continue", M_SelectableClearMenus,48},
534 {IT_CALL | IT_STRING, NULL, "Retry", M_ModeAttackRetry, 56},
535 {IT_CALL | IT_STRING, NULL, "Abort", M_ModeAttackEndGame, 64},
536 };
537
538 typedef enum
539 {
540 mapause_hints,
541 mapause_continue,
542 mapause_retry,
543 mapause_abort
544 } mapause_e;
545
546 // ---------------------
547 // Pause Menu MP Edition
548 // ---------------------
549 static menuitem_t MPauseMenu[] =
550 {
551 {IT_STRING | IT_CALL, NULL, "Add-ons...", M_Addons, 8},
552 {IT_STRING | IT_SUBMENU, NULL, "Scramble Teams...", &MISC_ScrambleTeamDef, 16},
553 {IT_STRING | IT_CALL, NULL, "Switch Gametype/Level...", M_MapChange, 24},
554
555 {IT_STRING | IT_CALL, NULL, "Continue", M_SelectableClearMenus,40},
556 {IT_STRING | IT_CALL, NULL, "Player 1 Setup", M_SetupMultiPlayer, 48}, // splitscreen
557 {IT_STRING | IT_CALL, NULL, "Player 2 Setup", M_SetupMultiPlayer2, 56}, // splitscreen
558
559 {IT_STRING | IT_CALL, NULL, "Spectate", M_ConfirmSpectate, 48},
560 {IT_STRING | IT_CALL, NULL, "Enter Game", M_ConfirmEnterGame, 48},
561 {IT_STRING | IT_SUBMENU, NULL, "Switch Team...", &MISC_ChangeTeamDef, 48},
562 {IT_STRING | IT_CALL, NULL, "Player Setup", M_SetupMultiPlayer, 56}, // alone
563 {IT_STRING | IT_CALL, NULL, "Options", M_Options, 64},
564
565 {IT_STRING | IT_CALL, NULL, "Return to Title", M_EndGame, 80},
566 {IT_STRING | IT_CALL, NULL, "Quit Game", M_QuitSRB2, 88},
567 };
568
569 typedef enum
570 {
571 mpause_addons = 0,
572 mpause_scramble,
573 mpause_switchmap,
574
575 mpause_continue,
576 mpause_psetupsplit,
577 mpause_psetupsplit2,
578 mpause_spectate,
579 mpause_entergame,
580 mpause_switchteam,
581 mpause_psetup,
582 mpause_options,
583
584 mpause_title,
585 mpause_quit
586 } mpause_e;
587
588 // ---------------------
589 // Pause Menu SP Edition
590 // ---------------------
591 static menuitem_t SPauseMenu[] =
592 {
593 // Pandora's Box will be shifted up if both options are available
594 {IT_CALL | IT_STRING, NULL, "Pandora's Box...", M_PandorasBox, 16},
595 {IT_CALL | IT_STRING, NULL, "Emblem Hints...", M_EmblemHints, 24},
596 {IT_CALL | IT_STRING, NULL, "Level Select...", M_LoadGameLevelSelect, 32},
597
598 {IT_CALL | IT_STRING, NULL, "Continue", M_SelectableClearMenus,48},
599 {IT_CALL | IT_STRING, NULL, "Retry", M_Retry, 56},
600 {IT_CALL | IT_STRING, NULL, "Options", M_Options, 64},
601
602 {IT_CALL | IT_STRING, NULL, "Return to Title", M_EndGame, 80},
603 {IT_CALL | IT_STRING, NULL, "Quit Game", M_QuitSRB2, 88},
604 };
605
606 typedef enum
607 {
608 spause_pandora = 0,
609 spause_hints,
610 spause_levelselect,
611
612 spause_continue,
613 spause_retry,
614 spause_options,
615
616 spause_title,
617 spause_quit
618 } spause_e;
619
620 // -----------------
621 // Misc menu options
622 // -----------------
623 // Prefix: MISC_
624 static menuitem_t MISC_ScrambleTeamMenu[] =
625 {
626 {IT_STRING|IT_CVAR, NULL, "Scramble Method", &cv_dummyscramble, 30},
627 {IT_WHITESTRING|IT_CALL, NULL, "Confirm", M_ConfirmTeamScramble, 90},
628 };
629
630 static menuitem_t MISC_ChangeTeamMenu[] =
631 {
632 {IT_STRING|IT_CVAR, NULL, "Select Team", &cv_dummyteam, 30},
633 {IT_WHITESTRING|IT_CALL, NULL, "Confirm", M_ConfirmTeamChange, 90},
634 };
635
636 gtdesc_t gametypedesc[NUMGAMETYPES] =
637 {
638 {{ 54, 54}, "Play through the single-player campaign with your friends, teaming up to beat Dr Eggman's nefarious challenges!"},
639 {{103, 103}, "Speed your way through the main acts, competing in several different categories to see who's the best."},
640 {{190, 190}, "There's not much to it - zoom through the level faster than everyone else."},
641 {{ 66, 66}, "Sling rings at your foes in a free-for-all battle. Use the special weapon rings to your advantage!"},
642 {{153, 37}, "Sling rings at your foes in a color-coded battle. Use the special weapon rings to your advantage!"},
643 {{123, 123}, "Whoever's IT has to hunt down everyone else. If you get caught, you have to turn on your former friends!"},
644 {{150, 150}, "Try and find a good hiding place in these maps - we dare you."},
645 {{ 37, 153}, "Steal the flag from the enemy's base and bring it back to your own, but watch out - they could just as easily steal yours!"},
646 };
647
648 static menuitem_t MISC_ChangeLevelMenu[] =
649 {
650 {IT_KEYHANDLER | IT_NOTHING, NULL, "", M_HandleLevelPlatter, 0}, // dummy menuitem for the control func
651 };
652
653 static menuitem_t MISC_HelpMenu[] =
654 {
655 {IT_KEYHANDLER | IT_NOTHING, NULL, "HELPN01", M_HandleImageDef, 0},
656 {IT_KEYHANDLER | IT_NOTHING, NULL, "HELPN02", M_HandleImageDef, 0},
657 {IT_KEYHANDLER | IT_NOTHING, NULL, "HELPN03", M_HandleImageDef, 0},
658 {IT_KEYHANDLER | IT_NOTHING, NULL, "HELPM01", M_HandleImageDef, 0},
659 {IT_KEYHANDLER | IT_NOTHING, NULL, "HELPM02", M_HandleImageDef, 0},
660 };
661
662 // --------------------------------
663 // Sky Room and all of its submenus
664 // --------------------------------
665 // Prefix: SR_
666
667 // Pause Menu Pandora's Box Options
668 static menuitem_t SR_PandorasBox[] =
669 {
670 {IT_STRING | IT_CALL, NULL, "Mid-game add-ons...", M_Addons, 0},
671
672 {IT_STRING | IT_CVAR, NULL, "Rings", &cv_dummyrings, 20},
673 {IT_STRING | IT_CVAR, NULL, "Lives", &cv_dummylives, 30},
674 {IT_STRING | IT_CVAR, NULL, "Continues", &cv_dummycontinues, 40},
675
676 {IT_STRING | IT_CVAR, NULL, "Gravity", &cv_gravity, 60},
677 {IT_STRING | IT_CVAR, NULL, "Throw Rings", &cv_ringslinger, 70},
678
679 {IT_STRING | IT_CALL, NULL, "Enable Super form", M_AllowSuper, 90},
680 {IT_STRING | IT_CALL, NULL, "Get All Emeralds", M_GetAllEmeralds, 100},
681 {IT_STRING | IT_CALL, NULL, "Destroy All Robots", M_DestroyRobots, 110},
682
683 {IT_STRING | IT_CALL, NULL, "Ultimate Cheat", M_UltimateCheat, 130},
684 };
685
686 // Sky Room Custom Unlocks
687 static menuitem_t SR_MainMenu[] =
688 {
689 {IT_STRING|IT_SUBMENU,NULL, "Extras Checklist", &SR_UnlockChecklistDef, 0},
690 {IT_DISABLED, NULL, "", NULL, 0}, // Custom1
691 {IT_DISABLED, NULL, "", NULL, 0}, // Custom2
692 {IT_DISABLED, NULL, "", NULL, 0}, // Custom3
693 {IT_DISABLED, NULL, "", NULL, 0}, // Custom4
694 {IT_DISABLED, NULL, "", NULL, 0}, // Custom5
695 {IT_DISABLED, NULL, "", NULL, 0}, // Custom6
696 {IT_DISABLED, NULL, "", NULL, 0}, // Custom7
697 {IT_DISABLED, NULL, "", NULL, 0}, // Custom8
698 {IT_DISABLED, NULL, "", NULL, 0}, // Custom9
699 {IT_DISABLED, NULL, "", NULL, 0}, // Custom10
700 {IT_DISABLED, NULL, "", NULL, 0}, // Custom11
701 {IT_DISABLED, NULL, "", NULL, 0}, // Custom12
702 {IT_DISABLED, NULL, "", NULL, 0}, // Custom13
703 {IT_DISABLED, NULL, "", NULL, 0}, // Custom14
704 {IT_DISABLED, NULL, "", NULL, 0}, // Custom15
705 {IT_DISABLED, NULL, "", NULL, 0}, // Custom16
706 {IT_DISABLED, NULL, "", NULL, 0}, // Custom17
707 {IT_DISABLED, NULL, "", NULL, 0}, // Custom18
708 {IT_DISABLED, NULL, "", NULL, 0}, // Custom19
709 {IT_DISABLED, NULL, "", NULL, 0}, // Custom20
710 {IT_DISABLED, NULL, "", NULL, 0}, // Custom21
711 {IT_DISABLED, NULL, "", NULL, 0}, // Custom22
712 {IT_DISABLED, NULL, "", NULL, 0}, // Custom23
713 {IT_DISABLED, NULL, "", NULL, 0}, // Custom24
714 {IT_DISABLED, NULL, "", NULL, 0}, // Custom25
715 {IT_DISABLED, NULL, "", NULL, 0}, // Custom26
716 {IT_DISABLED, NULL, "", NULL, 0}, // Custom27
717 {IT_DISABLED, NULL, "", NULL, 0}, // Custom28
718 {IT_DISABLED, NULL, "", NULL, 0}, // Custom29
719 {IT_DISABLED, NULL, "", NULL, 0}, // Custom30
720 {IT_DISABLED, NULL, "", NULL, 0}, // Custom31
721 {IT_DISABLED, NULL, "", NULL, 0}, // Custom32
722
723 };
724
725 static menuitem_t SR_LevelSelectMenu[] =
726 {
727 {IT_KEYHANDLER | IT_NOTHING, NULL, "", M_HandleLevelPlatter, 0}, // dummy menuitem for the control func
728 };
729
730 static menuitem_t SR_UnlockChecklistMenu[] =
731 {
732 {IT_KEYHANDLER | IT_STRING, NULL, "", M_HandleChecklist, 0},
733 };
734
735 static menuitem_t SR_SoundTestMenu[] =
736 {
737 {IT_KEYHANDLER | IT_STRING, NULL, "", M_HandleSoundTest, 0},
738 };
739
740 static menuitem_t SR_EmblemHintMenu[] =
741 {
742 {IT_STRING | IT_ARROWS, NULL, "Page", M_HandleEmblemHints, 10},
743 {IT_STRING|IT_CVAR, NULL, "Emblem Radar", &cv_itemfinder, 20},
744 {IT_WHITESTRING|IT_CALL, NULL, "Back", M_GoBack, 30}
745 };
746
747 // --------------------------------
748 // 1 Player and all of its submenus
749 // --------------------------------
750 // Prefix: SP_
751
752 // Single Player Main
753 static menuitem_t SP_MainMenu[] =
754 {
755 // Note: If changing the positions here, also change them in M_SinglePlayerMenu()
756 {IT_CALL | IT_STRING, NULL, "Start Game", M_LoadGame, 76},
757 {IT_SECRET, NULL, "Record Attack", M_TimeAttack, 84},
758 {IT_SECRET, NULL, "NiGHTS Mode", M_NightsAttack, 92},
759 {IT_SECRET, NULL, "Marathon Run", M_Marathon, 100},
760 {IT_CALL | IT_STRING, NULL, "Tutorial", M_StartTutorial, 108},
761 {IT_CALL | IT_STRING | IT_CALL_NOTMODIFIED, NULL, "Statistics", M_Statistics, 116}
762 };
763
764 enum
765 {
766 spstartgame,
767 sprecordattack,
768 spnightsmode,
769 spmarathon,
770 sptutorial,
771 spstatistics
772 };
773
774 // Single Player Load Game
775 static menuitem_t SP_LoadGameMenu[] =
776 {
777 {IT_KEYHANDLER | IT_NOTHING, NULL, "", M_HandleLoadSave, 0}, // dummy menuitem for the control func
778 };
779
780 // Single Player Level Select
781 static menuitem_t SP_LevelSelectMenu[] =
782 {
783 {IT_KEYHANDLER | IT_NOTHING, NULL, "", M_HandleLevelPlatter, 0}, // dummy menuitem for the control func
784 };
785
786 // Single Player Time Attack Level Select
787 static menuitem_t SP_TimeAttackLevelSelectMenu[] =
788 {
789 {IT_KEYHANDLER | IT_NOTHING, NULL, "", M_HandleLevelPlatter, 0}, // dummy menuitem for the control func
790 };
791
792 // Single Player Time Attack
793 static menuitem_t SP_TimeAttackMenu[] =
794 {
795 {IT_STRING|IT_KEYHANDLER, NULL, "Level Select...", M_HandleTimeAttackLevelSelect, 62},
796 {IT_STRING|IT_CVAR, NULL, "Character", &cv_chooseskin, 72},
797
798 {IT_DISABLED, NULL, "Guest Option...", &SP_GuestReplayDef, 100},
799 {IT_DISABLED, NULL, "Replay...", &SP_ReplayDef, 110},
800 {IT_DISABLED, NULL, "Ghosts...", &SP_GhostDef, 120},
801 {IT_WHITESTRING|IT_CALL|IT_CALL_NOTMODIFIED, NULL, "Start", M_ChooseTimeAttack, 130},
802 };
803
804 enum
805 {
806 talevel,
807 taplayer,
808
809 taguest,
810 tareplay,
811 taghost,
812 tastart
813 };
814
815 static menuitem_t SP_ReplayMenu[] =
816 {
817 {IT_WHITESTRING|IT_CALL, NULL, "Replay Best Score", M_ReplayTimeAttack, 0},
818 {IT_WHITESTRING|IT_CALL, NULL, "Replay Best Time", M_ReplayTimeAttack, 8},
819 {IT_WHITESTRING|IT_CALL, NULL, "Replay Best Rings", M_ReplayTimeAttack,16},
820
821 {IT_WHITESTRING|IT_CALL, NULL, "Replay Last", M_ReplayTimeAttack,29},
822 {IT_WHITESTRING|IT_CALL, NULL, "Replay Guest", M_ReplayTimeAttack,37},
823
824 {IT_WHITESTRING|IT_SUBMENU, NULL, "Back", &SP_TimeAttackDef, 50}
825 };
826
827 static menuitem_t SP_NightsReplayMenu[] =
828 {
829 {IT_WHITESTRING|IT_CALL, NULL, "Replay Best Score", M_ReplayTimeAttack, 8},
830 {IT_WHITESTRING|IT_CALL, NULL, "Replay Best Time", M_ReplayTimeAttack,16},
831
832 {IT_WHITESTRING|IT_CALL, NULL, "Replay Last", M_ReplayTimeAttack,29},
833 {IT_WHITESTRING|IT_CALL, NULL, "Replay Guest", M_ReplayTimeAttack,37},
834
835 {IT_WHITESTRING|IT_SUBMENU, NULL, "Back", &SP_NightsAttackDef, 50}
836 };
837
838 static menuitem_t SP_GuestReplayMenu[] =
839 {
840 {IT_WHITESTRING|IT_CALL, NULL, "Save Best Score as Guest", M_SetGuestReplay, 0},
841 {IT_WHITESTRING|IT_CALL, NULL, "Save Best Time as Guest", M_SetGuestReplay, 8},
842 {IT_WHITESTRING|IT_CALL, NULL, "Save Best Rings as Guest", M_SetGuestReplay,16},
843 {IT_WHITESTRING|IT_CALL, NULL, "Save Last as Guest", M_SetGuestReplay,24},
844
845 {IT_WHITESTRING|IT_CALL, NULL, "Delete Guest Replay", M_SetGuestReplay,37},
846
847 {IT_WHITESTRING|IT_SUBMENU, NULL, "Back", &SP_TimeAttackDef, 50}
848 };
849
850 static menuitem_t SP_NightsGuestReplayMenu[] =
851 {
852 {IT_WHITESTRING|IT_CALL, NULL, "Save Best Score as Guest", M_SetGuestReplay, 8},
853 {IT_WHITESTRING|IT_CALL, NULL, "Save Best Time as Guest", M_SetGuestReplay,16},
854 {IT_WHITESTRING|IT_CALL, NULL, "Save Last as Guest", M_SetGuestReplay,24},
855
856 {IT_WHITESTRING|IT_CALL, NULL, "Delete Guest Replay", M_SetGuestReplay,37},
857
858 {IT_WHITESTRING|IT_SUBMENU, NULL, "Back", &SP_NightsAttackDef, 50}
859 };
860
861 static menuitem_t SP_GhostMenu[] =
862 {
863 {IT_STRING|IT_CVAR, NULL, "Best Score", &cv_ghost_bestscore, 0},
864 {IT_STRING|IT_CVAR, NULL, "Best Time", &cv_ghost_besttime, 8},
865 {IT_STRING|IT_CVAR, NULL, "Best Rings", &cv_ghost_bestrings,16},
866 {IT_STRING|IT_CVAR, NULL, "Last", &cv_ghost_last, 24},
867
868 {IT_STRING|IT_CVAR, NULL, "Guest", &cv_ghost_guest, 37},
869
870 {IT_WHITESTRING|IT_SUBMENU, NULL, "Back", &SP_TimeAttackDef, 50}
871 };
872
873 static menuitem_t SP_NightsGhostMenu[] =
874 {
875 {IT_STRING|IT_CVAR, NULL, "Best Score", &cv_ghost_bestscore, 8},
876 {IT_STRING|IT_CVAR, NULL, "Best Time", &cv_ghost_besttime, 16},
877 {IT_STRING|IT_CVAR, NULL, "Last", &cv_ghost_last, 24},
878
879 {IT_STRING|IT_CVAR, NULL, "Guest", &cv_ghost_guest, 37},
880
881 {IT_WHITESTRING|IT_SUBMENU, NULL, "Back", &SP_NightsAttackDef, 50}
882 };
883
884 // Single Player Nights Attack Level Select
885 static menuitem_t SP_NightsAttackLevelSelectMenu[] =
886 {
887 {IT_KEYHANDLER | IT_NOTHING, NULL, "", M_HandleLevelPlatter, 0}, // dummy menuitem for the control func
888 };
889
890 // Single Player Nights Attack
891 static menuitem_t SP_NightsAttackMenu[] =
892 {
893 {IT_STRING|IT_KEYHANDLER, NULL, "Level Select...", &M_HandleTimeAttackLevelSelect, 52},
894 {IT_STRING|IT_CVAR, NULL, "Character", &cv_chooseskin, 62},
895 {IT_STRING|IT_CVAR, NULL, "Show Records For", &cv_dummymares, 72},
896
897 {IT_DISABLED, NULL, "Guest Option...", &SP_NightsGuestReplayDef, 100},
898 {IT_DISABLED, NULL, "Replay...", &SP_NightsReplayDef, 110},
899 {IT_DISABLED, NULL, "Ghosts...", &SP_NightsGhostDef, 120},
900 {IT_WHITESTRING|IT_CALL|IT_CALL_NOTMODIFIED, NULL, "Start", M_ChooseNightsAttack, 130},
901 };
902
903 enum
904 {
905 nalevel,
906 nachar,
907 narecords,
908
909 naguest,
910 nareplay,
911 naghost,
912 nastart
913 };
914
915 // Marathon
916 static menuitem_t SP_MarathonMenu[] =
917 {
918 {IT_STRING|IT_KEYHANDLER, NULL, "Character", M_HandleMarathonChoosePlayer, 90},
919 {IT_STRING|IT_CVAR, NULL, "Category", &cv_dummymarathon, 100},
920 {IT_STRING|IT_CVAR, NULL, "Timer", &cv_dummyloadless, 110},
921 {IT_STRING|IT_CVAR, NULL, "Cutscenes", &cv_dummycutscenes, 120},
922 {IT_WHITESTRING|IT_CALL, NULL, "Start", M_StartMarathon, 130},
923 };
924
925 enum
926 {
927 marathonplayer,
928 marathonultimate,
929 marathonloadless,
930 marathoncutscenes,
931 marathonstart
932 };
933
934 // Statistics
935 static menuitem_t SP_LevelStatsMenu[] =
936 {
937 {IT_KEYHANDLER | IT_NOTHING, NULL, "", M_HandleLevelStats, 0}, // dummy menuitem for the control func
938 };
939
940 // Player menu dummy
941 static menuitem_t SP_PlayerMenu[] =
942 {
943 {IT_NOTHING | IT_KEYHANDLER, NULL, "", M_HandleChoosePlayerMenu, 0}, // dummy menuitem for the control func
944 };
945
946 // -----------------------------------
947 // Multiplayer and all of its submenus
948 // -----------------------------------
949 // Prefix: MP_
950
951 // Separated splitscreen and normal servers.
952 static menuitem_t MP_SplitServerMenu[] =
953 {
954 {IT_STRING|IT_CALL, NULL, "Select Gametype/Level...", M_MapChange, 100},
955 #ifdef NONET // In order to keep player setup accessible.
956 {IT_STRING|IT_CALL, NULL, "Player 1 setup...", M_SetupMultiPlayer, 110},
957 {IT_STRING|IT_CALL, NULL, "Player 2 setup...", M_SetupMultiPlayer2, 120},
958 #endif
959 {IT_STRING|IT_CALL, NULL, "More Options...", M_ServerOptions, 130},
960 {IT_WHITESTRING|IT_CALL, NULL, "Start", M_StartServer, 140},
961 };
962
963 #ifndef NONET
964
965 static menuitem_t MP_MainMenu[] =
966 {
967 {IT_HEADER, NULL, "Join a game", NULL, 0},
968 {IT_STRING|IT_CALL, NULL, "Server browser...", M_ConnectMenuModChecks, 12},
969 {IT_STRING|IT_KEYHANDLER, NULL, "Specify IPv4 address:", M_HandleConnectIP, 22},
970 {IT_HEADER, NULL, "Host a game", NULL, 54},
971 {IT_STRING|IT_CALL, NULL, "Internet/LAN...", M_StartServerMenu, 66},
972 {IT_STRING|IT_CALL, NULL, "Splitscreen...", M_StartSplitServerMenu, 76},
973 {IT_HEADER, NULL, "Player setup", NULL, 94},
974 {IT_STRING|IT_CALL, NULL, "Player 1...", M_SetupMultiPlayer, 106},
975 {IT_STRING|IT_CALL, NULL, "Player 2... ", M_SetupMultiPlayer2, 116},
976 };
977
978 static menuitem_t MP_ServerMenu[] =
979 {
980 {IT_STRING|IT_CALL, NULL, "Room...", M_RoomMenu, 10},
981 {IT_STRING|IT_CVAR|IT_CV_STRING, NULL, "Server Name", &cv_servername, 20},
982 {IT_STRING|IT_CVAR, NULL, "Max Players", &cv_maxplayers, 46},
983 {IT_STRING|IT_CVAR, NULL, "Allow Add-on Downloading", &cv_downloading, 56},
984 {IT_STRING|IT_CALL, NULL, "Select Gametype/Level...", M_MapChange, 100},
985 {IT_STRING|IT_CALL, NULL, "More Options...", M_ServerOptions, 130},
986 {IT_WHITESTRING|IT_CALL, NULL, "Start", M_StartServer, 140},
987 };
988
989 enum
990 {
991 mp_server_room = 0,
992 mp_server_name,
993 mp_server_maxpl,
994 mp_server_waddl,
995 mp_server_levelgt,
996 mp_server_options,
997 mp_server_start
998 };
999
1000 static menuitem_t MP_ConnectMenu[] =
1001 {
1002 {IT_STRING | IT_CALL, NULL, "Room...", M_RoomMenu, 4},
1003 {IT_STRING | IT_CVAR, NULL, "Sort By", &cv_serversort, 12},
1004 {IT_STRING | IT_KEYHANDLER, NULL, "Page", M_HandleServerPage, 20},
1005 {IT_STRING | IT_CALL, NULL, "Refresh", M_Refresh, 28},
1006
1007 {IT_STRING | IT_SPACE, NULL, "", M_Connect, 48-4},
1008 {IT_STRING | IT_SPACE, NULL, "", M_Connect, 60-4},
1009 {IT_STRING | IT_SPACE, NULL, "", M_Connect, 72-4},
1010 {IT_STRING | IT_SPACE, NULL, "", M_Connect, 84-4},
1011 {IT_STRING | IT_SPACE, NULL, "", M_Connect, 96-4},
1012 {IT_STRING | IT_SPACE, NULL, "", M_Connect, 108-4},
1013 {IT_STRING | IT_SPACE, NULL, "", M_Connect, 120-4},
1014 {IT_STRING | IT_SPACE, NULL, "", M_Connect, 132-4},
1015 {IT_STRING | IT_SPACE, NULL, "", M_Connect, 144-4},
1016 {IT_STRING | IT_SPACE, NULL, "", M_Connect, 156-4},
1017 {IT_STRING | IT_SPACE, NULL, "", M_Connect, 168-4},
1018 };
1019
1020 enum
1021 {
1022 mp_connect_room,
1023 mp_connect_sort,
1024 mp_connect_page,
1025 mp_connect_refresh,
1026 FIRSTSERVERLINE
1027 };
1028
1029 menuitem_t MP_RoomMenu[] =
1030 {
1031 {IT_STRING | IT_CALL, NULL, "<Unlisted Mode>", M_ChooseRoom, 9},
1032 {IT_DISABLED, NULL, "", M_ChooseRoom, 18},
1033 {IT_DISABLED, NULL, "", M_ChooseRoom, 27},
1034 {IT_DISABLED, NULL, "", M_ChooseRoom, 36},
1035 {IT_DISABLED, NULL, "", M_ChooseRoom, 45},
1036 {IT_DISABLED, NULL, "", M_ChooseRoom, 54},
1037 {IT_DISABLED, NULL, "", M_ChooseRoom, 63},
1038 {IT_DISABLED, NULL, "", M_ChooseRoom, 72},
1039 {IT_DISABLED, NULL, "", M_ChooseRoom, 81},
1040 {IT_DISABLED, NULL, "", M_ChooseRoom, 90},
1041 {IT_DISABLED, NULL, "", M_ChooseRoom, 99},
1042 {IT_DISABLED, NULL, "", M_ChooseRoom, 108},
1043 {IT_DISABLED, NULL, "", M_ChooseRoom, 117},
1044 {IT_DISABLED, NULL, "", M_ChooseRoom, 126},
1045 {IT_DISABLED, NULL, "", M_ChooseRoom, 135},
1046 {IT_DISABLED, NULL, "", M_ChooseRoom, 144},
1047 {IT_DISABLED, NULL, "", M_ChooseRoom, 153},
1048 {IT_DISABLED, NULL, "", M_ChooseRoom, 162},
1049 };
1050
1051 #endif
1052
1053 static menuitem_t MP_PlayerSetupMenu[] =
1054 {
1055 {IT_KEYHANDLER, NULL, "", M_HandleSetupMultiPlayer, 0}, // name
1056 {IT_KEYHANDLER, NULL, "", M_HandleSetupMultiPlayer, 0}, // skin
1057 {IT_KEYHANDLER, NULL, "", M_HandleSetupMultiPlayer, 0}, // colour
1058 {IT_KEYHANDLER, NULL, "", M_HandleSetupMultiPlayer, 0}, // default
1059 };
1060
1061 // ------------------------------------
1062 // Options and most (?) of its submenus
1063 // ------------------------------------
1064 // Prefix: OP_
1065 static menuitem_t OP_MainMenu[] =
1066 {
1067 {IT_SUBMENU | IT_STRING, NULL, "Player 1 Controls...", &OP_P1ControlsDef, 10},
1068 {IT_SUBMENU | IT_STRING, NULL, "Player 2 Controls...", &OP_P2ControlsDef, 20},
1069 {IT_CVAR | IT_STRING, NULL, "Controls per key", &cv_controlperkey, 30},
1070
1071 {IT_CALL | IT_STRING, NULL, "Video Options...", M_VideoOptions, 50},
1072 {IT_SUBMENU | IT_STRING, NULL, "Sound Options...", &OP_SoundOptionsDef, 60},
1073
1074 {IT_CALL | IT_STRING, NULL, "Server Options...", M_ServerOptions, 80},
1075
1076 {IT_SUBMENU | IT_STRING, NULL, "Data Options...", &OP_DataOptionsDef, 100},
1077 };
1078
1079 static menuitem_t OP_P1ControlsMenu[] =
1080 {
1081 {IT_CALL | IT_STRING, NULL, "Control Configuration...", M_Setup1PControlsMenu, 10},
1082 {IT_SUBMENU | IT_STRING, NULL, "Mouse Options...", &OP_MouseOptionsDef, 20},
1083 {IT_SUBMENU | IT_STRING, NULL, "Gamepad Options...", &OP_Joystick1Def , 30},
1084
1085 {IT_SUBMENU | IT_STRING, NULL, "Camera Options...", &OP_CameraOptionsDef, 50},
1086
1087 {IT_STRING | IT_CVAR, NULL, "Automatic braking", &cv_autobrake, 70},
1088 {IT_CALL | IT_STRING, NULL, "Play Style...", M_Setup1PPlaystyleMenu, 80},
1089 };
1090
1091 static menuitem_t OP_P2ControlsMenu[] =
1092 {
1093 {IT_CALL | IT_STRING, NULL, "Control Configuration...", M_Setup2PControlsMenu, 10},
1094 {IT_SUBMENU | IT_STRING, NULL, "Second Mouse Options...", &OP_Mouse2OptionsDef, 20},
1095 {IT_SUBMENU | IT_STRING, NULL, "Second Gamepad Options...", &OP_Joystick2Def , 30},
1096
1097 {IT_SUBMENU | IT_STRING, NULL, "Camera Options...", &OP_Camera2OptionsDef, 50},
1098
1099 {IT_STRING | IT_CVAR, NULL, "Automatic braking", &cv_autobrake2, 70},
1100 {IT_CALL | IT_STRING, NULL, "Play Style...", M_Setup2PPlaystyleMenu, 80},
1101 };
1102
1103 static menuitem_t OP_ChangeControlsMenu[] =
1104 {
1105 {IT_HEADER, NULL, "Movement", NULL, 0},
1106 {IT_SPACE, NULL, NULL, NULL, 0}, // padding
1107 {IT_CALL | IT_STRING2, NULL, "Move Forward", M_ChangeControl, gc_forward },
1108 {IT_CALL | IT_STRING2, NULL, "Move Backward", M_ChangeControl, gc_backward },
1109 {IT_CALL | IT_STRING2, NULL, "Move Left", M_ChangeControl, gc_strafeleft },
1110 {IT_CALL | IT_STRING2, NULL, "Move Right", M_ChangeControl, gc_straferight },
1111 {IT_CALL | IT_STRING2, NULL, "Jump", M_ChangeControl, gc_jump },
1112 {IT_CALL | IT_STRING2, NULL, "Spin", M_ChangeControl, gc_spin },
1113 {IT_HEADER, NULL, "Camera", NULL, 0},
1114 {IT_SPACE, NULL, NULL, NULL, 0}, // padding
1115 {IT_CALL | IT_STRING2, NULL, "Look Up", M_ChangeControl, gc_lookup },
1116 {IT_CALL | IT_STRING2, NULL, "Look Down", M_ChangeControl, gc_lookdown },
1117 {IT_CALL | IT_STRING2, NULL, "Look Left", M_ChangeControl, gc_turnleft },
1118 {IT_CALL | IT_STRING2, NULL, "Look Right", M_ChangeControl, gc_turnright },
1119 {IT_CALL | IT_STRING2, NULL, "Center View", M_ChangeControl, gc_centerview },
1120 {IT_CALL | IT_STRING2, NULL, "Toggle Mouselook", M_ChangeControl, gc_mouseaiming },
1121 {IT_CALL | IT_STRING2, NULL, "Toggle Third-Person", M_ChangeControl, gc_camtoggle},
1122 {IT_CALL | IT_STRING2, NULL, "Reset Camera", M_ChangeControl, gc_camreset },
1123 {IT_HEADER, NULL, "Meta", NULL, 0},
1124 {IT_SPACE, NULL, NULL, NULL, 0}, // padding
1125 {IT_CALL | IT_STRING2, NULL, "Game Status",
1126 M_ChangeControl, gc_scores },
1127 {IT_CALL | IT_STRING2, NULL, "Pause / Run Retry", M_ChangeControl, gc_pause },
1128 {IT_CALL | IT_STRING2, NULL, "Screenshot", M_ChangeControl, gc_screenshot },
1129 {IT_CALL | IT_STRING2, NULL, "Toggle GIF Recording", M_ChangeControl, gc_recordgif },
1130 {IT_CALL | IT_STRING2, NULL, "Open/Close Menu (ESC)", M_ChangeControl, gc_systemmenu },
1131 {IT_CALL | IT_STRING2, NULL, "Change Viewpoint", M_ChangeControl, gc_viewpoint },
1132 {IT_CALL | IT_STRING2, NULL, "Console", M_ChangeControl, gc_console },
1133 {IT_HEADER, NULL, "Multiplayer", NULL, 0},
1134 {IT_SPACE, NULL, NULL, NULL, 0}, // padding
1135 {IT_CALL | IT_STRING2, NULL, "Talk", M_ChangeControl, gc_talkkey },
1136 {IT_CALL | IT_STRING2, NULL, "Talk (Team only)", M_ChangeControl, gc_teamkey },
1137 {IT_HEADER, NULL, "Ringslinger (Match, CTF, Tag, H&S)", NULL, 0},
1138 {IT_SPACE, NULL, NULL, NULL, 0}, // padding
1139 {IT_CALL | IT_STRING2, NULL, "Fire", M_ChangeControl, gc_fire },
1140 {IT_CALL | IT_STRING2, NULL, "Fire Normal", M_ChangeControl, gc_firenormal },
1141 {IT_CALL | IT_STRING2, NULL, "Toss Flag", M_ChangeControl, gc_tossflag },
1142 {IT_CALL | IT_STRING2, NULL, "Next Weapon", M_ChangeControl, gc_weaponnext },
1143 {IT_CALL | IT_STRING2, NULL, "Prev Weapon", M_ChangeControl, gc_weaponprev },
1144 {IT_CALL | IT_STRING2, NULL, "Normal / Infinity", M_ChangeControl, gc_wepslot1 },
1145 {IT_CALL | IT_STRING2, NULL, "Automatic", M_ChangeControl, gc_wepslot2 },
1146 {IT_CALL | IT_STRING2, NULL, "Bounce", M_ChangeControl, gc_wepslot3 },
1147 {IT_CALL | IT_STRING2, NULL, "Scatter", M_ChangeControl, gc_wepslot4 },
1148 {IT_CALL | IT_STRING2, NULL, "Grenade", M_ChangeControl, gc_wepslot5 },
1149 {IT_CALL | IT_STRING2, NULL, "Explosion", M_ChangeControl, gc_wepslot6 },
1150 {IT_CALL | IT_STRING2, NULL, "Rail", M_ChangeControl, gc_wepslot7 },
1151 {IT_HEADER, NULL, "Add-ons", NULL, 0},
1152 {IT_SPACE, NULL, NULL, NULL, 0}, // padding
1153 {IT_CALL | IT_STRING2, NULL, "Custom Action 1", M_ChangeControl, gc_custom1 },
1154 {IT_CALL | IT_STRING2, NULL, "Custom Action 2", M_ChangeControl, gc_custom2 },
1155 {IT_CALL | IT_STRING2, NULL, "Custom Action 3", M_ChangeControl, gc_custom3 },
1156 };
1157
1158 static menuitem_t OP_Joystick1Menu[] =
1159 {
1160 {IT_STRING | IT_CALL, NULL, "Select Gamepad...", M_Setup1PJoystickMenu, 10},
1161 {IT_STRING | IT_CVAR, NULL, "Move \x17 Axis" , &cv_moveaxis , 30},
1162 {IT_STRING | IT_CVAR, NULL, "Move \x18 Axis" , &cv_sideaxis , 40},
1163 {IT_STRING | IT_CVAR, NULL, "Camera \x17 Axis" , &cv_lookaxis , 50},
1164 {IT_STRING | IT_CVAR, NULL, "Camera \x18 Axis" , &cv_turnaxis , 60},
1165 {IT_STRING | IT_CVAR, NULL, "Jump Axis" , &cv_jumpaxis , 70},
1166 {IT_STRING | IT_CVAR, NULL, "Spin Axis" , &cv_spinaxis , 80},
1167 {IT_STRING | IT_CVAR, NULL, "Fire Axis" , &cv_fireaxis , 90},
1168 {IT_STRING | IT_CVAR, NULL, "Fire Normal Axis" , &cv_firenaxis ,100},
1169
1170 {IT_STRING | IT_CVAR, NULL, "First-Person Vert-Look", &cv_alwaysfreelook, 120},
1171 {IT_STRING | IT_CVAR, NULL, "Third-Person Vert-Look", &cv_chasefreelook, 130},
1172 {IT_STRING | IT_CVAR | IT_CV_FLOATSLIDER, NULL, "Analog Deadzone", &cv_deadzone, 140},
1173 {IT_STRING | IT_CVAR | IT_CV_FLOATSLIDER, NULL, "Digital Deadzone", &cv_digitaldeadzone, 150},
1174 };
1175
1176 static menuitem_t OP_Joystick2Menu[] =
1177 {
1178 {IT_STRING | IT_CALL, NULL, "Select Gamepad...", M_Setup2PJoystickMenu, 10},
1179 {IT_STRING | IT_CVAR, NULL, "Move \x17 Axis" , &cv_moveaxis2 , 30},
1180 {IT_STRING | IT_CVAR, NULL, "Move \x18 Axis" , &cv_sideaxis2 , 40},
1181 {IT_STRING | IT_CVAR, NULL, "Camera \x17 Axis" , &cv_lookaxis2 , 50},
1182 {IT_STRING | IT_CVAR, NULL, "Camera \x18 Axis" , &cv_turnaxis2 , 60},
1183 {IT_STRING | IT_CVAR, NULL, "Jump Axis" , &cv_jumpaxis2 , 70},
1184 {IT_STRING | IT_CVAR, NULL, "Spin Axis" , &cv_spinaxis2 , 80},
1185 {IT_STRING | IT_CVAR, NULL, "Fire Axis" , &cv_fireaxis2 , 90},
1186 {IT_STRING | IT_CVAR, NULL, "Fire Normal Axis" , &cv_firenaxis2 ,100},
1187
1188 {IT_STRING | IT_CVAR, NULL, "First-Person Vert-Look", &cv_alwaysfreelook2,120},
1189 {IT_STRING | IT_CVAR, NULL, "Third-Person Vert-Look", &cv_chasefreelook2, 130},
1190 {IT_STRING | IT_CVAR | IT_CV_FLOATSLIDER, NULL, "Analog Deadzone", &cv_deadzone2,140},
1191 {IT_STRING | IT_CVAR | IT_CV_FLOATSLIDER, NULL, "Digital Deadzone", &cv_digitaldeadzone2,150},
1192 };
1193
1194 static menuitem_t OP_JoystickSetMenu[1+MAX_JOYSTICKS];
1195
1196 static menuitem_t OP_MouseOptionsMenu[] =
1197 {
1198 {IT_STRING | IT_CVAR, NULL, "Use Mouse", &cv_usemouse, 10},
1199
1200
1201 {IT_STRING | IT_CVAR, NULL, "First-Person MouseLook", &cv_alwaysfreelook, 30},
1202 {IT_STRING | IT_CVAR, NULL, "Third-Person MouseLook", &cv_chasefreelook, 40},
1203 {IT_STRING | IT_CVAR, NULL, "Mouse Move", &cv_mousemove, 50},
1204 {IT_STRING | IT_CVAR, NULL, "Invert Y Axis", &cv_invertmouse, 60},
1205 {IT_STRING | IT_CVAR | IT_CV_SLIDER,
1206 NULL, "Mouse X Sensitivity", &cv_mousesens, 70},
1207 {IT_STRING | IT_CVAR | IT_CV_SLIDER,
1208 NULL, "Mouse Y Sensitivity", &cv_mouseysens, 80},
1209 };
1210
1211 static menuitem_t OP_Mouse2OptionsMenu[] =
1212 {
1213 {IT_STRING | IT_CVAR, NULL, "Use Mouse 2", &cv_usemouse2, 10},
1214 {IT_STRING | IT_CVAR, NULL, "Second Mouse Serial Port",
1215 &cv_mouse2port, 20},
1216 {IT_STRING | IT_CVAR, NULL, "First-Person MouseLook", &cv_alwaysfreelook2, 30},
1217 {IT_STRING | IT_CVAR, NULL, "Third-Person MouseLook", &cv_chasefreelook2, 40},
1218 {IT_STRING | IT_CVAR, NULL, "Mouse Move", &cv_mousemove2, 50},
1219 {IT_STRING | IT_CVAR, NULL, "Invert Y Axis", &cv_invertmouse2, 60},
1220 {IT_STRING | IT_CVAR | IT_CV_SLIDER,
1221 NULL, "Mouse X Sensitivity", &cv_mousesens2, 70},
1222 {IT_STRING | IT_CVAR | IT_CV_SLIDER,
1223 NULL, "Mouse Y Sensitivity", &cv_mouseysens2, 80},
1224 };
1225
1226 static menuitem_t OP_CameraOptionsMenu[] =
1227 {
1228 {IT_HEADER, NULL, "General Toggles", NULL, 0},
1229 {IT_STRING | IT_CVAR, NULL, "Third-person Camera" , &cv_chasecam , 6},
1230 {IT_STRING | IT_CVAR, NULL, "Flip Camera with Gravity" , &cv_flipcam , 11},
1231 {IT_STRING | IT_CVAR, NULL, "Orbital Looking" , &cv_cam_orbit , 16},
1232 {IT_STRING | IT_CVAR, NULL, "Downhill Slope Adjustment", &cv_cam_adjust, 21},
1233
1234 {IT_HEADER, NULL, "Camera Positioning", NULL, 30},
1235 {IT_STRING | IT_CVAR | IT_CV_INTEGERSTEP, NULL, "Camera Distance", &cv_cam_savedist[0][0], 36},
1236 {IT_STRING | IT_CVAR | IT_CV_INTEGERSTEP, NULL, "Camera Height", &cv_cam_saveheight[0][0], 41},
1237 {IT_STRING | IT_CVAR | IT_CV_FLOATSLIDER, NULL, "Camera Spacial Speed", &cv_cam_speed, 46},
1238 {IT_STRING | IT_CVAR | IT_CV_FLOATSLIDER, NULL, "Rotation Speed", &cv_cam_turnmultiplier, 51},
1239
1240 {IT_HEADER, NULL, "Display Options", NULL, 60},
1241 {IT_STRING | IT_CVAR, NULL, "Crosshair", &cv_crosshair, 66},
1242 };
1243
1244 static menuitem_t OP_Camera2OptionsMenu[] =
1245 {
1246 {IT_HEADER, NULL, "General Toggles", NULL, 0},
1247 {IT_STRING | IT_CVAR, NULL, "Third-person Camera" , &cv_chasecam2 , 6},
1248 {IT_STRING | IT_CVAR, NULL, "Flip Camera with Gravity" , &cv_flipcam2 , 11},
1249 {IT_STRING | IT_CVAR, NULL, "Orbital Looking" , &cv_cam2_orbit , 16},
1250 {IT_STRING | IT_CVAR, NULL, "Downhill Slope Adjustment", &cv_cam2_adjust, 21},
1251
1252 {IT_HEADER, NULL, "Camera Positioning", NULL, 30},
1253 {IT_STRING | IT_CVAR | IT_CV_INTEGERSTEP, NULL, "Camera Distance", &cv_cam_savedist[0][1], 36},
1254 {IT_STRING | IT_CVAR | IT_CV_INTEGERSTEP, NULL, "Camera Height", &cv_cam_saveheight[0][1], 41},
1255 {IT_STRING | IT_CVAR | IT_CV_FLOATSLIDER, NULL, "Camera Spacial Speed", &cv_cam2_speed, 46},
1256 {IT_STRING | IT_CVAR | IT_CV_FLOATSLIDER, NULL, "Rotation Speed", &cv_cam2_turnmultiplier, 51},
1257
1258 {IT_HEADER, NULL, "Display Options", NULL, 60},
1259 {IT_STRING | IT_CVAR, NULL, "Crosshair", &cv_crosshair2, 66},
1260 };
1261
1262 static menuitem_t OP_CameraExtendedOptionsMenu[] =
1263 {
1264 {IT_HEADER, NULL, "General Toggles", NULL, 0},
1265 {IT_STRING | IT_CVAR, NULL, "Third-person Camera" , &cv_chasecam , 6},
1266 {IT_STRING | IT_CVAR, NULL, "Flip Camera with Gravity" , &cv_flipcam , 11},
1267 {IT_STRING | IT_CVAR, NULL, "Orbital Looking" , &cv_cam_orbit , 16},
1268 {IT_STRING | IT_CVAR, NULL, "Downhill Slope Adjustment", &cv_cam_adjust, 21},
1269
1270 {IT_HEADER, NULL, "Camera Positioning", NULL, 30},
1271 {IT_STRING | IT_CVAR | IT_CV_INTEGERSTEP, NULL, "Camera Distance", &cv_cam_savedist[1][0], 36},
1272 {IT_STRING | IT_CVAR | IT_CV_INTEGERSTEP, NULL, "Camera Height", &cv_cam_saveheight[1][0], 41},
1273 {IT_STRING | IT_CVAR | IT_CV_FLOATSLIDER, NULL, "Camera Spacial Speed", &cv_cam_speed, 46},
1274 {IT_STRING | IT_CVAR | IT_CV_FLOATSLIDER, NULL, "Rotation Speed", &cv_cam_turnmultiplier, 51},
1275
1276 {IT_HEADER, NULL, "Automatic Camera Options", NULL, 60},
1277 {IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Shift to player angle", &cv_cam_shiftfacing[0], 66},
1278 {IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Turn to player angle", &cv_cam_turnfacing[0], 71},
1279 {IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Turn to ability", &cv_cam_turnfacingability[0], 76},
1280 {IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Turn to spindash", &cv_cam_turnfacingspindash[0], 81},
1281 {IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Turn to input", &cv_cam_turnfacinginput[0], 86},
1282
1283 {IT_HEADER, NULL, "Locked Camera Options", NULL, 95},
1284 {IT_STRING | IT_CVAR, NULL, "Lock button behavior", &cv_cam_centertoggle[0], 101},
1285 {IT_STRING | IT_CVAR, NULL, "Sideways movement", &cv_cam_lockedinput[0], 106},
1286 {IT_STRING | IT_CVAR, NULL, "Targeting assist", &cv_cam_lockonboss[0], 111},
1287
1288 {IT_HEADER, NULL, "Display Options", NULL, 120},
1289 {IT_STRING | IT_CVAR, NULL, "Crosshair", &cv_crosshair, 126},
1290 };
1291
1292 static menuitem_t OP_Camera2ExtendedOptionsMenu[] =
1293 {
1294 {IT_HEADER, NULL, "General Toggles", NULL, 0},
1295 {IT_STRING | IT_CVAR, NULL, "Third-person Camera" , &cv_chasecam2 , 6},
1296 {IT_STRING | IT_CVAR, NULL, "Flip Camera with Gravity" , &cv_flipcam2 , 11},
1297 {IT_STRING | IT_CVAR, NULL, "Orbital Looking" , &cv_cam2_orbit , 16},
1298 {IT_STRING | IT_CVAR, NULL, "Downhill Slope Adjustment", &cv_cam2_adjust, 21},
1299
1300 {IT_HEADER, NULL, "Camera Positioning", NULL, 30},
1301 {IT_STRING | IT_CVAR | IT_CV_INTEGERSTEP, NULL, "Camera Distance", &cv_cam_savedist[1][1], 36},
1302 {IT_STRING | IT_CVAR | IT_CV_INTEGERSTEP, NULL, "Camera Height", &cv_cam_saveheight[1][1], 41},
1303 {IT_STRING | IT_CVAR | IT_CV_FLOATSLIDER, NULL, "Camera Spacial Speed", &cv_cam2_speed, 46},
1304 {IT_STRING | IT_CVAR | IT_CV_FLOATSLIDER, NULL, "Rotation Speed", &cv_cam2_turnmultiplier, 51},
1305
1306 {IT_HEADER, NULL, "Automatic Camera Options", NULL, 60},
1307 {IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Shift to player angle", &cv_cam_shiftfacing[1], 66},
1308 {IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Turn to player angle", &cv_cam_turnfacing[1], 71},
1309 {IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Turn to ability", &cv_cam_turnfacingability[1], 76},
1310 {IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Turn to spindash", &cv_cam_turnfacingspindash[1], 81},
1311 {IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Turn to input", &cv_cam_turnfacinginput[1], 86},
1312
1313 {IT_HEADER, NULL, "Locked Camera Options", NULL, 95},
1314 {IT_STRING | IT_CVAR, NULL, "Lock button behavior", &cv_cam_centertoggle[1], 101},
1315 {IT_STRING | IT_CVAR, NULL, "Sideways movement", &cv_cam_lockedinput[1], 106},
1316 {IT_STRING | IT_CVAR, NULL, "Targeting assist", &cv_cam_lockonboss[1], 111},
1317
1318 {IT_HEADER, NULL, "Display Options", NULL, 120},
1319 {IT_STRING | IT_CVAR, NULL, "Crosshair", &cv_crosshair2, 126},
1320 };
1321
1322 enum
1323 {
1324 op_video_resolution = 1,
1325 #if (defined (__unix__) && !defined (MSDOS)) || defined (UNIXCOMMON) || defined (HAVE_SDL)
1326 op_video_fullscreen,
1327 #endif
1328 op_video_vsync,
1329 op_video_renderer,
1330 };
1331
1332 static menuitem_t OP_VideoOptionsMenu[] =
1333 {
1334 {IT_HEADER, NULL, "Screen", NULL, 0},
1335 {IT_STRING | IT_CALL, NULL, "Set Resolution...", M_VideoModeMenu, 6},
1336
1337 #if (defined (__unix__) && !defined (MSDOS)) || defined (UNIXCOMMON) || defined (HAVE_SDL)
1338 {IT_STRING|IT_CVAR, NULL, "Fullscreen", &cv_fullscreen, 11},
1339 #endif
1340 {IT_STRING | IT_CVAR, NULL, "Vertical Sync", &cv_vidwait, 16},
1341 #ifdef HWRENDER
1342 {IT_STRING | IT_CVAR, NULL, "Renderer", &cv_renderer, 21},
1343 #else
1344 {IT_TRANSTEXT | IT_PAIR, "Renderer", "Software", &cv_renderer, 21},
1345 #endif
1346
1347 {IT_HEADER, NULL, "Color Profile", NULL, 30},
1348 {IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Brightness (F11)", &cv_globalgamma,36},
1349 {IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Saturation", &cv_globalsaturation, 41},
1350 {IT_SUBMENU|IT_STRING, NULL, "Advanced Settings...", &OP_ColorOptionsDef, 46},
1351
1352 {IT_HEADER, NULL, "Heads-Up Display", NULL, 55},
1353 {IT_STRING | IT_CVAR, NULL, "Show HUD", &cv_showhud, 61},
1354 {IT_STRING | IT_CVAR | IT_CV_SLIDER,
1355 NULL, "HUD Transparency", &cv_translucenthud, 66},
1356 {IT_STRING | IT_CVAR, NULL, "Score/Time/Rings", &cv_timetic, 71},
1357 {IT_STRING | IT_CVAR, NULL, "Show Powerups", &cv_powerupdisplay, 76},
1358 {IT_STRING | IT_CVAR, NULL, "Local ping display", &cv_showping, 81}, // shows ping next to framerate if we want to.
1359 {IT_STRING | IT_CVAR, NULL, "Show player names", &cv_seenames, 86},
1360
1361 {IT_HEADER, NULL, "Console", NULL, 95},
1362 {IT_STRING | IT_CVAR, NULL, "Background color", &cons_backcolor, 101},
1363 {IT_STRING | IT_CVAR, NULL, "Text Size", &cv_constextsize, 106},
1364
1365 {IT_HEADER, NULL, "Chat", NULL, 115},
1366 {IT_STRING | IT_CVAR, NULL, "Chat Mode", &cv_consolechat, 121},
1367 {IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Chat Box Width", &cv_chatwidth, 126},
1368 {IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Chat Box Height", &cv_chatheight, 131},
1369 {IT_STRING | IT_CVAR, NULL, "Message Fadeout Time", &cv_chattime, 136},
1370 {IT_STRING | IT_CVAR, NULL, "Chat Notifications", &cv_chatnotifications, 141},
1371 {IT_STRING | IT_CVAR, NULL, "Spam Protection", &cv_chatspamprotection, 146},
1372 {IT_STRING | IT_CVAR, NULL, "Chat background tint", &cv_chatbacktint, 151},
1373
1374 {IT_HEADER, NULL, "Level", NULL, 160},
1375 {IT_STRING | IT_CVAR, NULL, "Draw Distance", &cv_drawdist, 166},
1376 {IT_STRING | IT_CVAR, NULL, "Weather Draw Dist.", &cv_drawdist_precip, 171},
1377 {IT_STRING | IT_CVAR, NULL, "NiGHTS Hoop Draw Dist.", &cv_drawdist_nights, 176},
1378
1379 {IT_HEADER, NULL, "Diagnostic", NULL, 184},
1380 {IT_STRING | IT_CVAR, NULL, "Show FPS", &cv_ticrate, 190},
1381 {IT_STRING | IT_CVAR, NULL, "Clear Before Redraw", &cv_homremoval, 195},
1382 {IT_STRING | IT_CVAR, NULL, "Show \"FOCUS LOST\"", &cv_showfocuslost, 200},
1383
1384 #ifdef HWRENDER
1385 {IT_HEADER, NULL, "Renderer", NULL, 208},
1386 {IT_CALL | IT_STRING, NULL, "OpenGL Options...", M_OpenGLOptionsMenu, 214},
1387 #endif
1388 };
1389
1390 static menuitem_t OP_VideoModeMenu[] =
1391 {
1392 {IT_KEYHANDLER | IT_NOTHING, NULL, "", M_HandleVideoMode, 0}, // dummy menuitem for the control func
1393 };
1394
1395 static menuitem_t OP_ColorOptionsMenu[] =
1396 {
1397 {IT_STRING | IT_CALL, NULL, "Reset to defaults", M_ResetCvars, 0},
1398
1399 {IT_HEADER, NULL, "Red", NULL, 9},
1400 {IT_DISABLED, NULL, NULL, NULL, 35},
1401 {IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Hue", &cv_rhue, 15},
1402 {IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Saturation", &cv_rsaturation, 20},
1403 {IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Brightness", &cv_rgamma, 25},
1404
1405 {IT_HEADER, NULL, "Yellow", NULL, 34},
1406 {IT_DISABLED, NULL, NULL, NULL, 73},
1407 {IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Hue", &cv_yhue, 40},
1408 {IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Saturation", &cv_ysaturation, 45},
1409 {IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Brightness", &cv_ygamma, 50},
1410
1411 {IT_HEADER, NULL, "Green", NULL, 59},
1412 {IT_DISABLED, NULL, NULL, NULL, 112},
1413 {IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Hue", &cv_ghue, 65},
1414 {IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Saturation", &cv_gsaturation, 70},
1415 {IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Brightness", &cv_ggamma, 75},
1416
1417 {IT_HEADER, NULL, "Cyan", NULL, 84},
1418 {IT_DISABLED, NULL, NULL, NULL, 255},
1419 {IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Hue", &cv_chue, 90},
1420 {IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Saturation", &cv_csaturation, 95},
1421 {IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Brightness", &cv_cgamma, 100},
1422
1423 {IT_HEADER, NULL, "Blue", NULL, 109},
1424 {IT_DISABLED, NULL, NULL, NULL, 152},
1425 {IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Hue", &cv_bhue, 115},
1426 {IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Saturation", &cv_bsaturation, 120},
1427 {IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Brightness", &cv_bgamma, 125},
1428
1429 {IT_HEADER, NULL, "Magenta", NULL, 134},
1430 {IT_DISABLED, NULL, NULL, NULL, 181},
1431 {IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Hue", &cv_mhue, 140},
1432 {IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Saturation", &cv_msaturation, 145},
1433 {IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Brightness", &cv_mgamma, 150},
1434 };
1435
1436 #ifdef HWRENDER
1437 static menuitem_t OP_OpenGLOptionsMenu[] =
1438 {
1439 {IT_HEADER, NULL, "3D Models", NULL, 0},
1440 {IT_STRING|IT_CVAR, NULL, "Models", &cv_glmodels, 12},
1441 {IT_STRING|IT_CVAR, NULL, "Frame interpolation", &cv_glmodelinterpolation, 22},
1442 {IT_STRING|IT_CVAR, NULL, "Ambient lighting", &cv_glmodellighting, 32},
1443
1444 {IT_HEADER, NULL, "General", NULL, 51},
1445 {IT_STRING|IT_CVAR, NULL, "Shaders", &cv_glshaders, 63},
1446 {IT_STRING|IT_CVAR, NULL, "Lack of perspective", &cv_glshearing, 73},
1447 {IT_STRING|IT_CVAR, NULL, "Field of view", &cv_fov, 83},
1448
1449 {IT_HEADER, NULL, "Miscellaneous", NULL, 102},
1450 {IT_STRING|IT_CVAR, NULL, "Bit depth", &cv_scr_depth, 114},
1451 {IT_STRING|IT_CVAR, NULL, "Texture filter", &cv_glfiltermode, 124},
1452 {IT_STRING|IT_CVAR, NULL, "Anisotropic", &cv_glanisotropicmode, 134},
1453 #ifdef ALAM_LIGHTING
1454 {IT_SUBMENU|IT_STRING, NULL, "Lighting...", &OP_OpenGLLightingDef, 144},
1455 #endif
1456 #if defined (_WINDOWS) && (!((defined (__unix__) && !defined (MSDOS)) || defined (UNIXCOMMON) || defined (HAVE_SDL)))
1457 {IT_STRING|IT_CVAR, NULL, "Fullscreen", &cv_fullscreen, 154},
1458 #endif
1459 };
1460
1461 #ifdef ALAM_LIGHTING
1462 static menuitem_t OP_OpenGLLightingMenu[] =
1463 {
1464 {IT_STRING|IT_CVAR, NULL, "Coronas", &cv_glcoronas, 0},
1465 {IT_STRING|IT_CVAR, NULL, "Coronas size", &cv_glcoronasize, 10},
1466 {IT_STRING|IT_CVAR, NULL, "Dynamic lighting", &cv_gldynamiclighting, 20},
1467 {IT_STRING|IT_CVAR, NULL, "Static lighting", &cv_glstaticlighting, 30},
1468 };
1469 #endif // ALAM_LIGHTING
1470
1471 #endif
1472
1473 static menuitem_t OP_SoundOptionsMenu[] =
1474 {
1475 {IT_HEADER, NULL, "Game Audio", NULL, 0},
1476 {IT_STRING | IT_CVAR, NULL, "Sound Effects", &cv_gamesounds, 6},
1477 {IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Sound Volume", &cv_soundvolume, 11},
1478
1479 {IT_STRING | IT_CVAR, NULL, "Digital Music", &cv_gamedigimusic, 21},
1480 {IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Digital Music Volume", &cv_digmusicvolume, 26},
1481
1482 {IT_STRING | IT_CVAR, NULL, "MIDI Music", &cv_gamemidimusic, 36},
1483 {IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "MIDI Music Volume", &cv_midimusicvolume, 41},
1484
1485 {IT_STRING | IT_CVAR, NULL, "Music Preference", &cv_musicpref, 51},
1486
1487 {IT_HEADER, NULL, "Miscellaneous", NULL, 61},
1488 {IT_STRING | IT_CVAR, NULL, "Closed Captioning", &cv_closedcaptioning, 67},
1489 {IT_STRING | IT_CVAR, NULL, "Reset Music Upon Dying", &cv_resetmusic, 72},
1490 {IT_STRING | IT_CVAR, NULL, "Default 1-Up sound", &cv_1upsound, 77},
1491
1492 {IT_STRING | IT_SUBMENU, NULL, "Advanced Settings...", &OP_SoundAdvancedDef, 87},
1493 };
1494
1495 #ifdef HAVE_OPENMPT
1496 #define OPENMPT_MENUOFFSET 32
1497 #else
1498 #define OPENMPT_MENUOFFSET 0
1499 #endif
1500
1501 #ifdef HAVE_MIXERX
1502 #define MIXERX_MENUOFFSET 81
1503 #else
1504 #define MIXERX_MENUOFFSET 0
1505 #endif
1506
1507 static menuitem_t OP_SoundAdvancedMenu[] =
1508 {
1509 #ifdef HAVE_OPENMPT
1510 {IT_HEADER, NULL, "OpenMPT Settings", NULL, 0},
1511 {IT_STRING | IT_CVAR, NULL, "Instrument Filter", &cv_modfilter, 12},
1512 #endif
1513
1514 #ifdef HAVE_MIXERX
1515 {IT_HEADER, NULL, "MIDI Settings", NULL, OPENMPT_MENUOFFSET},
1516 {IT_STRING | IT_CVAR, NULL, "MIDI Player", &cv_midiplayer, OPENMPT_MENUOFFSET+12},
1517 {IT_STRING | IT_CVAR | IT_CV_STRING, NULL, "FluidSynth Sound Font File", &cv_midisoundfontpath, OPENMPT_MENUOFFSET+24},
1518 {IT_STRING | IT_CVAR | IT_CV_STRING, NULL, "TiMidity++ Config Folder", &cv_miditimiditypath, OPENMPT_MENUOFFSET+51},
1519 #endif
1520
1521 {IT_HEADER, NULL, "Miscellaneous", NULL, OPENMPT_MENUOFFSET+MIXERX_MENUOFFSET},
1522 {IT_STRING | IT_CVAR, NULL, "Play Sound Effects if Unfocused", &cv_playsoundsifunfocused, OPENMPT_MENUOFFSET+MIXERX_MENUOFFSET+12},
1523 {IT_STRING | IT_CVAR, NULL, "Play Music if Unfocused", &cv_playmusicifunfocused, OPENMPT_MENUOFFSET+MIXERX_MENUOFFSET+22},
1524 {IT_STRING | IT_CVAR, NULL, "Let Levels Force Reset Music", &cv_resetmusicbyheader, OPENMPT_MENUOFFSET+MIXERX_MENUOFFSET+32},
1525 };
1526
1527 #undef OPENMPT_MENUOFFSET
1528 #undef MIXERX_MENUOFFSET
1529
1530 static menuitem_t OP_DataOptionsMenu[] =
1531 {
1532 {IT_STRING | IT_CALL, NULL, "Add-on Options...", M_AddonsOptions, 10},
1533 {IT_STRING | IT_CALL, NULL, "Screenshot Options...", M_ScreenshotOptions, 20},
1534
1535 {IT_STRING | IT_SUBMENU, NULL, "\x85" "Erase Data...", &OP_EraseDataDef, 40},
1536 };
1537
1538 static menuitem_t OP_ScreenshotOptionsMenu[] =
1539 {
1540 {IT_HEADER, NULL, "General", NULL, 0},
1541 {IT_STRING|IT_CVAR, NULL, "Use color profile", &cv_screenshot_colorprofile, 6},
1542
1543 {IT_HEADER, NULL, "Screenshots (F8)", NULL, 16},
1544 {IT_STRING|IT_CVAR, NULL, "Storage Location", &cv_screenshot_option, 22},
1545 {IT_STRING|IT_CVAR|IT_CV_STRING, NULL, "Custom Folder", &cv_screenshot_folder, 27},
1546 {IT_STRING|IT_CVAR, NULL, "Memory Level", &cv_zlib_memory, 42},
1547 {IT_STRING|IT_CVAR, NULL, "Compression Level", &cv_zlib_level, 47},
1548 {IT_STRING|IT_CVAR, NULL, "Strategy", &cv_zlib_strategy, 52},
1549 {IT_STRING|IT_CVAR, NULL, "Window Size", &cv_zlib_window_bits, 57},
1550
1551 {IT_HEADER, NULL, "Movie Mode (F9)", NULL, 64},
1552 {IT_STRING|IT_CVAR, NULL, "Storage Location", &cv_movie_option, 70},
1553 {IT_STRING|IT_CVAR|IT_CV_STRING, NULL, "Custom Folder", &cv_movie_folder, 75},
1554 {IT_STRING|IT_CVAR, NULL, "Capture Mode", &cv_moviemode, 90},
1555
1556 {IT_STRING|IT_CVAR, NULL, "Downscaling", &cv_gif_downscale, 95},
1557 {IT_STRING|IT_CVAR, NULL, "Region Optimizing", &cv_gif_optimize, 100},
1558 {IT_STRING|IT_CVAR, NULL, "Local Color Table", &cv_gif_localcolortable, 105},
1559
1560 {IT_STRING|IT_CVAR, NULL, "Downscaling", &cv_apng_downscale, 95},
1561 {IT_STRING|IT_CVAR, NULL, "Memory Level", &cv_zlib_memorya, 100},
1562 {IT_STRING|IT_CVAR, NULL, "Compression Level", &cv_zlib_levela, 105},
1563 {IT_STRING|IT_CVAR, NULL, "Strategy", &cv_zlib_strategya, 110},
1564 {IT_STRING|IT_CVAR, NULL, "Window Size", &cv_zlib_window_bitsa, 115},
1565 };
1566
1567 enum
1568 {
1569 op_screenshot_colorprofile = 1,
1570 op_screenshot_storagelocation = 3,
1571 op_screenshot_folder = 4,
1572 op_movie_folder = 11,
1573 op_screenshot_capture = 12,
1574 op_screenshot_gif_start = 13,
1575 op_screenshot_gif_end = 15,
1576 op_screenshot_apng_start = 16,
1577 op_screenshot_apng_end = 20,
1578 };
1579
1580 static menuitem_t OP_EraseDataMenu[] =
1581 {
1582 {IT_STRING | IT_CALL, NULL, "Erase Record Data", M_EraseData, 10},
1583 {IT_STRING | IT_CALL, NULL, "Erase Extras Data", M_EraseData, 20},
1584
1585 {IT_STRING | IT_CALL, NULL, "\x85" "Erase ALL Data", M_EraseData, 40},
1586 };
1587
1588 static menuitem_t OP_AddonsOptionsMenu[] =
1589 {
1590 {IT_HEADER, NULL, "Menu", NULL, 0},
1591 {IT_STRING|IT_CVAR, NULL, "Location", &cv_addons_option, 12},
1592 {IT_STRING|IT_CVAR|IT_CV_STRING, NULL, "Custom Folder", &cv_addons_folder, 22},
1593 {IT_STRING|IT_CVAR, NULL, "Identify add-ons via", &cv_addons_md5, 50},
1594 {IT_STRING|IT_CVAR, NULL, "Show unsupported file types", &cv_addons_showall, 60},
1595
1596 {IT_HEADER, NULL, "Search", NULL, 78},
1597 {IT_STRING|IT_CVAR, NULL, "Matching", &cv_addons_search_type, 90},
1598 {IT_STRING|IT_CVAR, NULL, "Case-sensitive", &cv_addons_search_case, 100},
1599 };
1600
1601 enum
1602 {
1603 op_addons_folder = 2,
1604 };
1605
1606 static menuitem_t OP_ServerOptionsMenu[] =
1607 {
1608 {IT_HEADER, NULL, "General", NULL, 0},
1609 #ifndef NONET
1610 {IT_STRING | IT_CVAR | IT_CV_STRING,
1611 NULL, "Server name", &cv_servername, 7},
1612 {IT_STRING | IT_CVAR, NULL, "Max Players", &cv_maxplayers, 21},
1613 {IT_STRING | IT_CVAR, NULL, "Allow Add-on Downloading", &cv_downloading, 26},
1614 {IT_STRING | IT_CVAR, NULL, "Allow players to join", &cv_allownewplayer, 31},
1615 {IT_STRING | IT_CVAR, NULL, "Minutes for reconnecting", &cv_rejointimeout, 36},
1616 #endif
1617 {IT_STRING | IT_CVAR, NULL, "Map progression", &cv_advancemap, 41},
1618 {IT_STRING | IT_CVAR, NULL, "Intermission Timer", &cv_inttime, 46},
1619
1620 {IT_HEADER, NULL, "Characters", NULL, 55},
1621 {IT_STRING | IT_CVAR, NULL, "Force a character", &cv_forceskin, 61},
1622 {IT_STRING | IT_CVAR, NULL, "Restrict character changes", &cv_restrictskinchange, 66},
1623
1624 {IT_HEADER, NULL, "Items", NULL, 75},
1625 {IT_STRING | IT_CVAR, NULL, "Item respawn delay", &cv_itemrespawntime, 81},
1626 {IT_STRING | IT_SUBMENU, NULL, "Mystery Item Monitor Toggles...", &OP_MonitorToggleDef, 86},
1627
1628 {IT_HEADER, NULL, "Cooperative", NULL, 95},
1629 {IT_STRING | IT_CVAR, NULL, "Players required for exit", &cv_playersforexit, 101},
1630 {IT_STRING | IT_CVAR, NULL, "Starposts", &cv_coopstarposts, 106},
1631 {IT_STRING | IT_CVAR, NULL, "Life sharing", &cv_cooplives, 111},
1632 {IT_STRING | IT_CVAR, NULL, "Post-goal free roaming", &cv_exitmove, 116},
1633
1634 {IT_HEADER, NULL, "Race, Competition", NULL, 125},
1635 {IT_STRING | IT_CVAR, NULL, "Level completion countdown", &cv_countdowntime, 131},
1636 {IT_STRING | IT_CVAR, NULL, "Item Monitors", &cv_competitionboxes, 136},
1637
1638 {IT_HEADER, NULL, "Ringslinger (Match, CTF, Tag, H&S)", NULL, 145},
1639 {IT_STRING | IT_CVAR, NULL, "Time Limit", &cv_timelimit, 151},
1640 {IT_STRING | IT_CVAR, NULL, "Score Limit", &cv_pointlimit, 156},
1641 {IT_STRING | IT_CVAR, NULL, "Overtime on Tie", &cv_overtime, 161},
1642 {IT_STRING | IT_CVAR, NULL, "Player respawn delay", &cv_respawntime, 166},
1643
1644 {IT_STRING | IT_CVAR, NULL, "Item Monitors", &cv_matchboxes, 176},
1645 {IT_STRING | IT_CVAR, NULL, "Weapon Rings", &cv_specialrings, 181},
1646 {IT_STRING | IT_CVAR, NULL, "Power Stones", &cv_powerstones, 186},
1647
1648 {IT_STRING | IT_CVAR, NULL, "Flag respawn delay", &cv_flagtime, 196},
1649 {IT_STRING | IT_CVAR, NULL, "Hiding time", &cv_hidetime, 201},
1650
1651 {IT_HEADER, NULL, "Teams", NULL, 210},
1652 {IT_STRING | IT_CVAR, NULL, "Autobalance sizes", &cv_autobalance, 216},
1653 {IT_STRING | IT_CVAR, NULL, "Scramble on Map Change", &cv_scrambleonchange, 221},
1654
1655 #ifndef NONET
1656 {IT_HEADER, NULL, "Advanced", NULL, 230},
1657 {IT_STRING | IT_CVAR | IT_CV_STRING, NULL, "Master server", &cv_masterserver, 236},
1658
1659 {IT_STRING | IT_CVAR, NULL, "Join delay", &cv_joindelay, 251},
1660 {IT_STRING | IT_CVAR, NULL, "Attempts to resynchronise", &cv_resynchattempts, 256},
1661
1662 {IT_STRING | IT_CVAR, NULL, "Show IP Address of Joiners", &cv_showjoinaddress, 261},
1663 #endif
1664 };
1665
1666 static menuitem_t OP_MonitorToggleMenu[] =
1667 {
1668 // Printing handled by drawing function
1669 {IT_STRING|IT_CALL, NULL, "Reset to defaults", M_ResetCvars, 15},
1670 {IT_STRING|IT_CVAR|IT_CV_INVISSLIDER, NULL, "Recycler", &cv_recycler, 30},
1671 {IT_STRING|IT_CVAR|IT_CV_INVISSLIDER, NULL, "Teleport", &cv_teleporters, 40},
1672 {IT_STRING|IT_CVAR|IT_CV_INVISSLIDER, NULL, "Super Ring", &cv_superring, 50},
1673 {IT_STRING|IT_CVAR|IT_CV_INVISSLIDER, NULL, "Super Sneakers", &cv_supersneakers, 60},
1674 {IT_STRING|IT_CVAR|IT_CV_INVISSLIDER, NULL, "Invincibility", &cv_invincibility, 70},
1675 {IT_STRING|IT_CVAR|IT_CV_INVISSLIDER, NULL, "Whirlwind Shield", &cv_jumpshield, 80},
1676 {IT_STRING|IT_CVAR|IT_CV_INVISSLIDER, NULL, "Elemental Shield", &cv_watershield, 90},
1677 {IT_STRING|IT_CVAR|IT_CV_INVISSLIDER, NULL, "Attraction Shield", &cv_ringshield, 100},
1678 {IT_STRING|IT_CVAR|IT_CV_INVISSLIDER, NULL, "Force Shield", &cv_forceshield, 110},
1679 {IT_STRING|IT_CVAR|IT_CV_INVISSLIDER, NULL, "Armageddon Shield", &cv_bombshield, 120},
1680 {IT_STRING|IT_CVAR|IT_CV_INVISSLIDER, NULL, "1 Up", &cv_1up, 130},
1681 {IT_STRING|IT_CVAR|IT_CV_INVISSLIDER, NULL, "Eggman Box", &cv_eggmanbox, 140},
1682 };
1683
1684 // ==========================================================================
1685 // ALL MENU DEFINITIONS GO HERE
1686 // ==========================================================================
1687
1688 // Main Menu and related
1689 menu_t MainDef = CENTERMENUSTYLE(MN_MAIN, NULL, MainMenu, NULL, 72);
1690
1691 menu_t MISC_AddonsDef =
1692 {
1693 MN_AD_MAIN,
1694 NULL,
1695 sizeof (MISC_AddonsMenu)/sizeof (menuitem_t),
1696 &MainDef,
1697 MISC_AddonsMenu,
1698 M_DrawAddons,
1699 50, 28,
1700 0,
1701 NULL
1702 };
1703
1704 menu_t MAPauseDef = PAUSEMENUSTYLE(MAPauseMenu, 40, 72);
1705 menu_t SPauseDef = PAUSEMENUSTYLE(SPauseMenu, 40, 72);
1706 menu_t MPauseDef = PAUSEMENUSTYLE(MPauseMenu, 40, 72);
1707
1708 // Misc Main Menu
1709 menu_t MISC_ScrambleTeamDef = DEFAULTMENUSTYLE(MN_SPECIAL, NULL, MISC_ScrambleTeamMenu, &MPauseDef, 27, 40);
1710 menu_t MISC_ChangeTeamDef = DEFAULTMENUSTYLE(MN_SPECIAL, NULL, MISC_ChangeTeamMenu, &MPauseDef, 27, 40);
1711
1712 // MP Gametype and map change menu
1713 menu_t MISC_ChangeLevelDef =
1714 {
1715 MN_SPECIAL,
1716 NULL,
1717 sizeof (MISC_ChangeLevelMenu)/sizeof (menuitem_t),
1718 &MainDef, // Doesn't matter.
1719 MISC_ChangeLevelMenu,
1720 M_DrawLevelPlatterMenu,
1721 0, 0,
1722 0,
1723 NULL
1724 };
1725
1726 menu_t MISC_HelpDef = IMAGEDEF(MISC_HelpMenu);
1727
1728 static INT32 highlightflags, recommendedflags, warningflags;
1729
1730
1731 // Sky Room
1732 menu_t SR_PandoraDef =
1733 {
1734 MTREE2(MN_SR_MAIN, MN_SR_PANDORA),
1735 "M_PANDRA",
1736 sizeof (SR_PandorasBox)/sizeof (menuitem_t),
1737 &SPauseDef,
1738 SR_PandorasBox,
1739 M_DrawGenericMenu,
1740 60, 30,
1741 0,
1742 M_ExitPandorasBox
1743 };
1744
1745 menu_t SR_MainDef = DEFAULTMENUSTYLE(MN_SR_MAIN, "M_SECRET", SR_MainMenu, &MainDef, 60, 40);
1746
1747 menu_t SR_LevelSelectDef = MAPPLATTERMENUSTYLE(
1748 MTREE2(MN_SR_MAIN, MN_SR_LEVELSELECT),
1749 NULL, SR_LevelSelectMenu);
1750
1751 menu_t SR_UnlockChecklistDef =
1752 {
1753 MTREE2(MN_SR_MAIN, MN_SR_UNLOCKCHECKLIST),
1754 "M_SECRET",
1755 1,
1756 &SR_MainDef,
1757 SR_UnlockChecklistMenu,
1758 M_DrawChecklist,
1759 30, 30,
1760 0,
1761 NULL
1762 };
1763
1764 menu_t SR_SoundTestDef =
1765 {
1766 MTREE2(MN_SR_MAIN, MN_SR_SOUNDTEST),
1767 NULL,
1768 sizeof (SR_SoundTestMenu)/sizeof (menuitem_t),
1769 &SR_MainDef,
1770 SR_SoundTestMenu,
1771 M_DrawSoundTest,
1772 60, 150,
1773 0,
1774 NULL
1775 };
1776
1777 menu_t SR_EmblemHintDef =
1778 {
1779 MTREE2(MN_SR_MAIN, MN_SR_EMBLEMHINT),
1780 NULL,
1781 sizeof (SR_EmblemHintMenu)/sizeof (menuitem_t),
1782 &SPauseDef,
1783 SR_EmblemHintMenu,
1784 M_DrawEmblemHints,
1785 60, 150,
1786 0,
1787 NULL
1788 };
1789
1790 // Single Player
1791 menu_t SP_MainDef = //CENTERMENUSTYLE(NULL, SP_MainMenu, &MainDef, 72);
1792 {
1793 MN_SP_MAIN,
1794 NULL,
1795 sizeof(SP_MainMenu)/sizeof(menuitem_t),
1796 &MainDef,
1797 SP_MainMenu,
1798 M_DrawCenteredMenu,
1799 BASEVIDWIDTH/2, 72,
1800 0,
1801 NULL
1802 };
1803
1804 menu_t SP_LoadDef =
1805 {
1806 MTREE2(MN_SP_MAIN, MN_SP_LOAD),
1807 "M_PICKG",
1808 1,
1809 &SP_MainDef,
1810 SP_LoadGameMenu,
1811 M_DrawLoad,
1812 68, 46,
1813 0,
1814 NULL
1815 };
1816
1817 menu_t SP_LevelSelectDef = MAPPLATTERMENUSTYLE(
1818 MTREE4(MN_SP_MAIN, MN_SP_LOAD, MN_SP_PLAYER, MN_SP_LEVELSELECT),
1819 NULL, SP_LevelSelectMenu);
1820
1821 menu_t SP_LevelStatsDef =
1822 {
1823 MTREE2(MN_SP_MAIN, MN_SP_LEVELSTATS),
1824 "M_STATS",
1825 1,
1826 &SP_MainDef,
1827 SP_LevelStatsMenu,
1828 M_DrawLevelStats,
1829 280, 185,
1830 0,
1831 NULL
1832 };
1833
1834 menu_t SP_TimeAttackLevelSelectDef = MAPPLATTERMENUSTYLE(
1835 MTREE3(MN_SP_MAIN, MN_SP_TIMEATTACK, MN_SP_TIMEATTACK_LEVELSELECT),
1836 "M_ATTACK", SP_TimeAttackLevelSelectMenu);
1837
1838 static menu_t SP_TimeAttackDef =
1839 {
1840 MTREE2(MN_SP_MAIN, MN_SP_TIMEATTACK),
1841 "M_ATTACK",
1842 sizeof (SP_TimeAttackMenu)/sizeof (menuitem_t),
1843 &MainDef, // Doesn't matter.
1844 SP_TimeAttackMenu,
1845 M_DrawTimeAttackMenu,
1846 32, 40,
1847 0,
1848 NULL
1849 };
1850 static menu_t SP_ReplayDef =
1851 {
1852 MTREE3(MN_SP_MAIN, MN_SP_TIMEATTACK, MN_SP_REPLAY),
1853 "M_ATTACK",
1854 sizeof(SP_ReplayMenu)/sizeof(menuitem_t),
1855 &SP_TimeAttackDef,
1856 SP_ReplayMenu,
1857 M_DrawTimeAttackMenu,
1858 32, 120,
1859 0,
1860 NULL
1861 };
1862 static menu_t SP_GuestReplayDef =
1863 {
1864 MTREE3(MN_SP_MAIN, MN_SP_TIMEATTACK, MN_SP_GUESTREPLAY),
1865 "M_ATTACK",
1866 sizeof(SP_GuestReplayMenu)/sizeof(menuitem_t),
1867 &SP_TimeAttackDef,
1868 SP_GuestReplayMenu,
1869 M_DrawTimeAttackMenu,
1870 32, 120,
1871 0,
1872 NULL
1873 };
1874 static menu_t SP_GhostDef =
1875 {
1876 MTREE3(MN_SP_MAIN, MN_SP_TIMEATTACK, MN_SP_GHOST),
1877 "M_ATTACK",
1878 sizeof(SP_GhostMenu)/sizeof(menuitem_t),
1879 &SP_TimeAttackDef,
1880 SP_GhostMenu,
1881 M_DrawTimeAttackMenu,
1882 32, 120,
1883 0,
1884 NULL
1885 };
1886
1887 menu_t SP_NightsAttackLevelSelectDef = MAPPLATTERMENUSTYLE(
1888 MTREE3(MN_SP_MAIN, MN_SP_NIGHTSATTACK, MN_SP_NIGHTS_LEVELSELECT),
1889 "M_NIGHTS", SP_NightsAttackLevelSelectMenu);
1890
1891 static menu_t SP_NightsAttackDef =
1892 {
1893 MTREE2(MN_SP_MAIN, MN_SP_NIGHTSATTACK),
1894 "M_NIGHTS",
1895 sizeof (SP_NightsAttackMenu)/sizeof (menuitem_t),
1896 &MainDef, // Doesn't matter.
1897 SP_NightsAttackMenu,
1898 M_DrawNightsAttackMenu,
1899 32, 40,
1900 0,
1901 NULL
1902 };
1903 static menu_t SP_NightsReplayDef =
1904 {
1905 MTREE3(MN_SP_MAIN, MN_SP_NIGHTSATTACK, MN_SP_NIGHTS_REPLAY),
1906 "M_NIGHTS",
1907 sizeof(SP_NightsReplayMenu)/sizeof(menuitem_t),
1908 &SP_NightsAttackDef,
1909 SP_NightsReplayMenu,
1910 M_DrawNightsAttackMenu,
1911 32, 120,
1912 0,
1913 NULL
1914 };
1915 static menu_t SP_NightsGuestReplayDef =
1916 {
1917 MTREE3(MN_SP_MAIN, MN_SP_NIGHTSATTACK, MN_SP_NIGHTS_GUESTREPLAY),
1918 "M_NIGHTS",
1919 sizeof(SP_NightsGuestReplayMenu)/sizeof(menuitem_t),
1920 &SP_NightsAttackDef,
1921 SP_NightsGuestReplayMenu,
1922 M_DrawNightsAttackMenu,
1923 32, 120,
1924 0,
1925 NULL
1926 };
1927 static menu_t SP_NightsGhostDef =
1928 {
1929 MTREE3(MN_SP_MAIN, MN_SP_NIGHTSATTACK, MN_SP_NIGHTS_GHOST),
1930 "M_NIGHTS",
1931 sizeof(SP_NightsGhostMenu)/sizeof(menuitem_t),
1932 &SP_NightsAttackDef,
1933 SP_NightsGhostMenu,
1934 M_DrawNightsAttackMenu,
1935 32, 120,
1936 0,
1937 NULL
1938 };
1939
1940 static menu_t SP_MarathonDef =
1941 {
1942 MTREE2(MN_SP_MAIN, MN_SP_MARATHON),
1943 "M_RATHON",
1944 sizeof(SP_MarathonMenu)/sizeof(menuitem_t),
1945 &MainDef, // Doesn't matter.
1946 SP_MarathonMenu,
1947 M_DrawMarathon,
1948 32, 40,
1949 0,
1950 NULL
1951 };
1952
1953 menu_t SP_PlayerDef =
1954 {
1955 MTREE3(MN_SP_MAIN, MN_SP_LOAD, MN_SP_PLAYER),
1956 "M_PICKP",
1957 sizeof (SP_PlayerMenu)/sizeof (menuitem_t),
1958 &SP_MainDef,
1959 SP_PlayerMenu,
1960 M_DrawSetupChoosePlayerMenu,
1961 24, 32,
1962 0,
1963 NULL
1964 };
1965
1966 // Multiplayer
1967
1968 menu_t MP_SplitServerDef =
1969 {
1970 MTREE2(MN_MP_MAIN, MN_MP_SPLITSCREEN),
1971 "M_MULTI",
1972 sizeof (MP_SplitServerMenu)/sizeof (menuitem_t),
1973 #ifndef NONET
1974 &MP_MainDef,
1975 #else
1976 &MainDef,
1977 #endif
1978 MP_SplitServerMenu,
1979 M_DrawServerMenu,
1980 27, 30 - 50,
1981 0,
1982 NULL
1983 };
1984
1985 #ifndef NONET
1986
1987 menu_t MP_MainDef =
1988 {
1989 MN_MP_MAIN,
1990 "M_MULTI",
1991 sizeof (MP_MainMenu)/sizeof (menuitem_t),
1992 &MainDef,
1993 MP_MainMenu,
1994 M_DrawMPMainMenu,
1995 27, 40,
1996 0,
1997 M_CancelConnect
1998 };
1999
2000 menu_t MP_ServerDef =
2001 {
2002 MTREE2(MN_MP_MAIN, MN_MP_SERVER),
2003 "M_MULTI",
2004 sizeof (MP_ServerMenu)/sizeof (menuitem_t),
2005 &MP_MainDef,
2006 MP_ServerMenu,
2007 M_DrawServerMenu,
2008 27, 30,
2009 0,
2010 NULL
2011 };
2012
2013 menu_t MP_ConnectDef =
2014 {
2015 MTREE2(MN_MP_MAIN, MN_MP_CONNECT),
2016 "M_MULTI",
2017 sizeof (MP_ConnectMenu)/sizeof (menuitem_t),
2018 &MP_MainDef,
2019 MP_ConnectMenu,
2020 M_DrawConnectMenu,
2021 27,24,
2022 0,
2023 M_CancelConnect
2024 };
2025
2026 menu_t MP_RoomDef =
2027 {
2028 MTREE2(MN_MP_MAIN, MN_MP_ROOM),
2029 "M_MULTI",
2030 sizeof (MP_RoomMenu)/sizeof (menuitem_t),
2031 &MP_ConnectDef,
2032 MP_RoomMenu,
2033 M_DrawRoomMenu,
2034 27, 32,
2035 0,
2036 NULL
2037 };
2038 #endif
2039
2040 menu_t MP_PlayerSetupDef =
2041 {
2042 #ifdef NONET
2043 MTREE2(MN_MP_MAIN, MN_MP_PLAYERSETUP),
2044 #else
2045 MTREE3(MN_MP_MAIN, MN_MP_SPLITSCREEN, MN_MP_PLAYERSETUP),
2046 #endif
2047 "M_SPLAYR",
2048 sizeof (MP_PlayerSetupMenu)/sizeof (menuitem_t),
2049 &MainDef, // doesn't matter
2050 MP_PlayerSetupMenu,
2051 M_DrawSetupMultiPlayerMenu,
2052 19, 22,
2053 0,
2054 M_QuitMultiPlayerMenu
2055 };
2056
2057 // Options
2058 menu_t OP_MainDef = DEFAULTMENUSTYLE(
2059 MN_OP_MAIN,
2060 "M_OPTTTL", OP_MainMenu, &MainDef, 50, 30);
2061
2062 menu_t OP_ChangeControlsDef = CONTROLMENUSTYLE(
2063 MTREE3(MN_OP_MAIN, 0, MN_OP_CHANGECONTROLS), // second level set on runtime
2064 OP_ChangeControlsMenu, &OP_MainDef);
2065
2066 menu_t OP_P1ControlsDef = {
2067 MTREE2(MN_OP_MAIN, MN_OP_P1CONTROLS),
2068 "M_CONTRO",
2069 sizeof(OP_P1ControlsMenu)/sizeof(menuitem_t),
2070 &OP_MainDef,
2071 OP_P1ControlsMenu,
2072 M_DrawControlsDefMenu,
2073 50, 30, 0, NULL};
2074 menu_t OP_P2ControlsDef = {
2075 MTREE2(MN_OP_MAIN, MN_OP_P2CONTROLS),
2076 "M_CONTRO",
2077 sizeof(OP_P2ControlsMenu)/sizeof(menuitem_t),
2078 &OP_MainDef,
2079 OP_P2ControlsMenu,
2080 M_DrawControlsDefMenu,
2081 50, 30, 0, NULL};
2082
2083 menu_t OP_MouseOptionsDef = DEFAULTMENUSTYLE(
2084 MTREE3(MN_OP_MAIN, MN_OP_P1CONTROLS, MN_OP_P1MOUSE),
2085 "M_CONTRO", OP_MouseOptionsMenu, &OP_P1ControlsDef, 35, 30);
2086 menu_t OP_Mouse2OptionsDef = DEFAULTMENUSTYLE(
2087 MTREE3(MN_OP_MAIN, MN_OP_P2CONTROLS, MN_OP_P2MOUSE),
2088 "M_CONTRO", OP_Mouse2OptionsMenu, &OP_P2ControlsDef, 35, 30);
2089
2090 menu_t OP_Joystick1Def = DEFAULTMENUSTYLE(
2091 MTREE3(MN_OP_MAIN, MN_OP_P1CONTROLS, MN_OP_P1JOYSTICK),
2092 "M_CONTRO", OP_Joystick1Menu, &OP_P1ControlsDef, 50, 30);
2093 menu_t OP_Joystick2Def = DEFAULTMENUSTYLE(
2094 MTREE3(MN_OP_MAIN, MN_OP_P2CONTROLS, MN_OP_P2JOYSTICK),
2095 "M_CONTRO", OP_Joystick2Menu, &OP_P2ControlsDef, 50, 30);
2096
2097 menu_t OP_JoystickSetDef =
2098 {
2099 MTREE4(MN_OP_MAIN, 0, 0, MN_OP_JOYSTICKSET), // second and third level set on runtime
2100 "M_CONTRO",
2101 sizeof (OP_JoystickSetMenu)/sizeof (menuitem_t),
2102 &OP_Joystick1Def,
2103 OP_JoystickSetMenu,
2104 M_DrawJoystick,
2105 60, 40,
2106 0,
2107 NULL
2108 };
2109
2110 menu_t OP_CameraOptionsDef = {
2111 MTREE3(MN_OP_MAIN, MN_OP_P1CONTROLS, MN_OP_P1CAMERA),
2112 "M_CONTRO",
2113 sizeof (OP_CameraOptionsMenu)/sizeof (menuitem_t),
2114 &OP_P1ControlsDef,
2115 OP_CameraOptionsMenu,
2116 M_DrawCameraOptionsMenu,
2117 35, 30,
2118 0,
2119 NULL
2120 };
2121 menu_t OP_Camera2OptionsDef = {
2122 MTREE3(MN_OP_MAIN, MN_OP_P2CONTROLS, MN_OP_P2CAMERA),
2123 "M_CONTRO",
2124 sizeof (OP_Camera2OptionsMenu)/sizeof (menuitem_t),
2125 &OP_P2ControlsDef,
2126 OP_Camera2OptionsMenu,
2127 M_DrawCameraOptionsMenu,
2128 35, 30,
2129 0,
2130 NULL
2131 };
2132
2133 static menuitem_t OP_PlaystyleMenu[] = {{IT_KEYHANDLER | IT_NOTHING, NULL, "", M_HandlePlaystyleMenu, 0}};
2134
2135 menu_t OP_PlaystyleDef = {
2136 MTREE3(MN_OP_MAIN, MN_OP_P1CONTROLS, MN_OP_PLAYSTYLE), ///@TODO the second level should be set in runtime
2137 NULL,
2138 1,
2139 &OP_P1ControlsDef,
2140 OP_PlaystyleMenu,
2141 M_DrawPlaystyleMenu,
2142 0, 0, 0, NULL
2143 };
2144
M_VideoOptions(INT32 choice)2145 static void M_VideoOptions(INT32 choice)
2146 {
2147 (void)choice;
2148
2149 OP_VideoOptionsMenu[op_video_renderer].status = (IT_TRANSTEXT | IT_PAIR);
2150 OP_VideoOptionsMenu[op_video_renderer].patch = "Renderer";
2151 OP_VideoOptionsMenu[op_video_renderer].text = "Software";
2152
2153 #ifdef HWRENDER
2154 if (vid.glstate != VID_GL_LIBRARY_ERROR)
2155 {
2156 OP_VideoOptionsMenu[op_video_renderer].status = (IT_STRING | IT_CVAR);
2157 OP_VideoOptionsMenu[op_video_renderer].patch = NULL;
2158 OP_VideoOptionsMenu[op_video_renderer].text = "Renderer";
2159 }
2160 #endif
2161
2162 M_SetupNextMenu(&OP_VideoOptionsDef);
2163 }
2164
2165 menu_t OP_VideoOptionsDef =
2166 {
2167 MTREE2(MN_OP_MAIN, MN_OP_VIDEO),
2168 "M_VIDEO",
2169 sizeof (OP_VideoOptionsMenu)/sizeof (menuitem_t),
2170 &OP_MainDef,
2171 OP_VideoOptionsMenu,
2172 M_DrawMainVideoMenu,
2173 30, 30,
2174 0,
2175 NULL
2176 };
2177 menu_t OP_VideoModeDef =
2178 {
2179 MTREE3(MN_OP_MAIN, MN_OP_VIDEO, MN_OP_VIDEOMODE),
2180 "M_VIDEO",
2181 1,
2182 &OP_VideoOptionsDef,
2183 OP_VideoModeMenu,
2184 M_DrawVideoMode,
2185 48, 26,
2186 0,
2187 NULL
2188 };
2189 menu_t OP_ColorOptionsDef =
2190 {
2191 MTREE3(MN_OP_MAIN, MN_OP_VIDEO, MN_OP_COLOR),
2192 "M_VIDEO",
2193 sizeof (OP_ColorOptionsMenu)/sizeof (menuitem_t),
2194 &OP_VideoOptionsDef,
2195 OP_ColorOptionsMenu,
2196 M_DrawColorMenu,
2197 30, 30,
2198 0,
2199 NULL
2200 };
2201 menu_t OP_SoundOptionsDef = DEFAULTSCROLLMENUSTYLE(
2202 MTREE2(MN_OP_MAIN, MN_OP_SOUND),
2203 "M_SOUND", OP_SoundOptionsMenu, &OP_MainDef, 30, 30);
2204 menu_t OP_SoundAdvancedDef = DEFAULTMENUSTYLE(
2205 MTREE2(MN_OP_MAIN, MN_OP_SOUND),
2206 "M_SOUND", OP_SoundAdvancedMenu, &OP_SoundOptionsDef, 30, 30);
2207
2208 menu_t OP_ServerOptionsDef = DEFAULTSCROLLMENUSTYLE(
2209 MTREE2(MN_OP_MAIN, MN_OP_SERVER),
2210 "M_SERVER", OP_ServerOptionsMenu, &OP_MainDef, 30, 30);
2211
2212 menu_t OP_MonitorToggleDef =
2213 {
2214 MTREE3(MN_OP_MAIN, MN_OP_SOUND, MN_OP_MONITORTOGGLE),
2215 "M_SERVER",
2216 sizeof (OP_MonitorToggleMenu)/sizeof (menuitem_t),
2217 &OP_ServerOptionsDef,
2218 OP_MonitorToggleMenu,
2219 M_DrawMonitorToggles,
2220 30, 30,
2221 0,
2222 NULL
2223 };
2224
2225 #ifdef HWRENDER
M_OpenGLOptionsMenu(void)2226 static void M_OpenGLOptionsMenu(void)
2227 {
2228 if (rendermode == render_opengl)
2229 M_SetupNextMenu(&OP_OpenGLOptionsDef);
2230 else
2231 M_StartMessage(M_GetText("You must be in OpenGL mode\nto access this menu.\n\n(Press a key)\n"), NULL, MM_NOTHING);
2232 }
2233
2234 menu_t OP_OpenGLOptionsDef = DEFAULTMENUSTYLE(
2235 MTREE3(MN_OP_MAIN, MN_OP_VIDEO, MN_OP_OPENGL),
2236 "M_VIDEO", OP_OpenGLOptionsMenu, &OP_VideoOptionsDef, 30, 30);
2237 #ifdef ALAM_LIGHTING
2238 menu_t OP_OpenGLLightingDef = DEFAULTMENUSTYLE(
2239 MTREE4(MN_OP_MAIN, MN_OP_VIDEO, MN_OP_OPENGL, MN_OP_OPENGL_LIGHTING),
2240 "M_VIDEO", OP_OpenGLLightingMenu, &OP_OpenGLOptionsDef, 60, 40);
2241 #endif // ALAM_LIGHTING
2242 #endif // HWRENDER
2243
2244 menu_t OP_DataOptionsDef = DEFAULTMENUSTYLE(
2245 MTREE2(MN_OP_MAIN, MN_OP_DATA),
2246 "M_DATA", OP_DataOptionsMenu, &OP_MainDef, 60, 30);
2247
2248 menu_t OP_ScreenshotOptionsDef =
2249 {
2250 MTREE3(MN_OP_MAIN, MN_OP_DATA, MN_OP_SCREENSHOTS),
2251 "M_SCREEN",
2252 sizeof (OP_ScreenshotOptionsMenu)/sizeof (menuitem_t),
2253 &OP_DataOptionsDef,
2254 OP_ScreenshotOptionsMenu,
2255 M_DrawScreenshotMenu,
2256 30, 30,
2257 0,
2258 NULL
2259 };
2260
2261 menu_t OP_AddonsOptionsDef = DEFAULTMENUSTYLE(
2262 MTREE3(MN_OP_MAIN, MN_OP_DATA, MN_OP_ADDONS),
2263 "M_ADDONS", OP_AddonsOptionsMenu, &OP_DataOptionsDef, 30, 30);
2264
2265 menu_t OP_EraseDataDef = DEFAULTMENUSTYLE(
2266 MTREE3(MN_OP_MAIN, MN_OP_DATA, MN_OP_ERASEDATA),
2267 "M_DATA", OP_EraseDataMenu, &OP_DataOptionsDef, 60, 30);
2268
2269 // ==========================================================================
2270 // CVAR ONCHANGE EVENTS GO HERE
2271 // ==========================================================================
2272 // (there's only a couple anyway)
2273
2274 // Prototypes
2275 static INT32 M_GetFirstLevelInList(INT32 gt);
2276 static boolean M_CanShowLevelOnPlatter(INT32 mapnum, INT32 gt);
2277
2278 // Nextmap. Used for Level select.
Nextmap_OnChange(void)2279 void Nextmap_OnChange(void)
2280 {
2281 char *leveltitle;
2282 char tabase[256];
2283 #ifdef OLDNREPLAYNAME
2284 char tabaseold[256];
2285 #endif
2286 short i;
2287 boolean active;
2288
2289 // Update the string in the consvar.
2290 Z_Free(cv_nextmap.zstring);
2291 leveltitle = G_BuildMapTitle(cv_nextmap.value);
2292 cv_nextmap.string = cv_nextmap.zstring = leveltitle ? leveltitle : Z_StrDup(G_BuildMapName(cv_nextmap.value));
2293
2294 if (currentMenu == &SP_NightsAttackDef)
2295 {
2296 CV_StealthSetValue(&cv_dummymares, 0);
2297 // Hide the record changing CVAR if only one mare is available.
2298 if (!nightsrecords[cv_nextmap.value-1] || nightsrecords[cv_nextmap.value-1]->nummares < 2)
2299 SP_NightsAttackMenu[narecords].status = IT_DISABLED;
2300 else
2301 SP_NightsAttackMenu[narecords].status = IT_STRING|IT_CVAR;
2302
2303 // Do the replay things.
2304 active = false;
2305 SP_NightsAttackMenu[naguest].status = IT_DISABLED;
2306 SP_NightsAttackMenu[nareplay].status = IT_DISABLED;
2307 SP_NightsAttackMenu[naghost].status = IT_DISABLED;
2308
2309 // Check if file exists, if not, disable REPLAY option
2310 sprintf(tabase,"%s"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s-%s",srb2home, timeattackfolder, G_BuildMapName(cv_nextmap.value), skins[cv_chooseskin.value-1].name);
2311
2312 #ifdef OLDNREPLAYNAME
2313 sprintf(tabaseold,"%s"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s",srb2home, timeattackfolder, G_BuildMapName(cv_nextmap.value));
2314 #endif
2315
2316 for (i = 0; i < 4; i++) {
2317 SP_NightsReplayMenu[i].status = IT_DISABLED;
2318 SP_NightsGuestReplayMenu[i].status = IT_DISABLED;
2319 }
2320
2321 if (FIL_FileExists(va("%s-score-best.lmp", tabase))) {
2322 SP_NightsReplayMenu[0].status = IT_WHITESTRING|IT_CALL;
2323 SP_NightsGuestReplayMenu[0].status = IT_WHITESTRING|IT_CALL;
2324 active = true;
2325 }
2326 if (FIL_FileExists(va("%s-time-best.lmp", tabase))) {
2327 SP_NightsReplayMenu[1].status = IT_WHITESTRING|IT_CALL;
2328 SP_NightsGuestReplayMenu[1].status = IT_WHITESTRING|IT_CALL;
2329 active = true;
2330 }
2331 if (FIL_FileExists(va("%s-last.lmp", tabase))) {
2332 SP_NightsReplayMenu[2].status = IT_WHITESTRING|IT_CALL;
2333 SP_NightsGuestReplayMenu[2].status = IT_WHITESTRING|IT_CALL;
2334 active = true;
2335 }
2336 if (FIL_FileExists(va("%s"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s-guest.lmp", srb2home, timeattackfolder, G_BuildMapName(cv_nextmap.value)))) {
2337 SP_NightsReplayMenu[3].status = IT_WHITESTRING|IT_CALL;
2338 SP_NightsGuestReplayMenu[3].status = IT_WHITESTRING|IT_CALL;
2339 active = true;
2340 }
2341
2342 // Old style name compatibility
2343 #ifdef OLDNREPLAYNAME
2344 if (FIL_FileExists(va("%s-score-best.lmp", tabaseold))) {
2345 SP_NightsReplayMenu[0].status = IT_WHITESTRING|IT_CALL;
2346 SP_NightsGuestReplayMenu[0].status = IT_WHITESTRING|IT_CALL;
2347 active = true;
2348 }
2349 if (FIL_FileExists(va("%s-time-best.lmp", tabaseold))) {
2350 SP_NightsReplayMenu[1].status = IT_WHITESTRING|IT_CALL;
2351 SP_NightsGuestReplayMenu[1].status = IT_WHITESTRING|IT_CALL;
2352 active = true;
2353 }
2354 if (FIL_FileExists(va("%s-last.lmp", tabaseold))) {
2355 SP_NightsReplayMenu[2].status = IT_WHITESTRING|IT_CALL;
2356 SP_NightsGuestReplayMenu[2].status = IT_WHITESTRING|IT_CALL;
2357 active = true;
2358 }
2359 #endif
2360
2361 if (active) {
2362 SP_NightsAttackMenu[naguest].status = IT_WHITESTRING|IT_SUBMENU;
2363 SP_NightsAttackMenu[nareplay].status = IT_WHITESTRING|IT_SUBMENU;
2364 SP_NightsAttackMenu[naghost].status = IT_WHITESTRING|IT_SUBMENU;
2365 }
2366
2367 else if(itemOn == nareplay) // Reset lastOn so replay isn't still selected when not available.
2368 {
2369 currentMenu->lastOn = itemOn;
2370 itemOn = nastart;
2371 }
2372 }
2373 else if (currentMenu == &SP_TimeAttackDef)
2374 {
2375 active = false;
2376 SP_TimeAttackMenu[taguest].status = IT_DISABLED;
2377 SP_TimeAttackMenu[tareplay].status = IT_DISABLED;
2378 SP_TimeAttackMenu[taghost].status = IT_DISABLED;
2379
2380 // Check if file exists, if not, disable REPLAY option
2381 sprintf(tabase,"%s"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s-%s",srb2home, timeattackfolder, G_BuildMapName(cv_nextmap.value), skins[cv_chooseskin.value-1].name);
2382 for (i = 0; i < 5; i++) {
2383 SP_ReplayMenu[i].status = IT_DISABLED;
2384 SP_GuestReplayMenu[i].status = IT_DISABLED;
2385 }
2386 if (FIL_FileExists(va("%s-time-best.lmp", tabase))) {
2387 SP_ReplayMenu[0].status = IT_WHITESTRING|IT_CALL;
2388 SP_GuestReplayMenu[0].status = IT_WHITESTRING|IT_CALL;
2389 active = true;
2390 }
2391 if (FIL_FileExists(va("%s-score-best.lmp", tabase))) {
2392 SP_ReplayMenu[1].status = IT_WHITESTRING|IT_CALL;
2393 SP_GuestReplayMenu[1].status = IT_WHITESTRING|IT_CALL;
2394 active = true;
2395 }
2396 if (FIL_FileExists(va("%s-rings-best.lmp", tabase))) {
2397 SP_ReplayMenu[2].status = IT_WHITESTRING|IT_CALL;
2398 SP_GuestReplayMenu[2].status = IT_WHITESTRING|IT_CALL;
2399 active = true;
2400 }
2401 if (FIL_FileExists(va("%s-last.lmp", tabase))) {
2402 SP_ReplayMenu[3].status = IT_WHITESTRING|IT_CALL;
2403 SP_GuestReplayMenu[3].status = IT_WHITESTRING|IT_CALL;
2404 active = true;
2405 }
2406 if (FIL_FileExists(va("%s"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s-guest.lmp", srb2home, timeattackfolder, G_BuildMapName(cv_nextmap.value)))) {
2407 SP_ReplayMenu[4].status = IT_WHITESTRING|IT_CALL;
2408 SP_GuestReplayMenu[4].status = IT_WHITESTRING|IT_CALL;
2409 active = true;
2410 }
2411 if (active) {
2412 SP_TimeAttackMenu[taguest].status = IT_WHITESTRING|IT_SUBMENU;
2413 SP_TimeAttackMenu[tareplay].status = IT_WHITESTRING|IT_SUBMENU;
2414 SP_TimeAttackMenu[taghost].status = IT_WHITESTRING|IT_SUBMENU;
2415 }
2416 else if(itemOn == tareplay) // Reset lastOn so replay isn't still selected when not available.
2417 {
2418 currentMenu->lastOn = itemOn;
2419 itemOn = tastart;
2420 }
2421
2422 if (mapheaderinfo[cv_nextmap.value-1] && mapheaderinfo[cv_nextmap.value-1]->forcecharacter[0] != '\0')
2423 CV_Set(&cv_chooseskin, mapheaderinfo[cv_nextmap.value-1]->forcecharacter);
2424 }
2425 }
2426
Dummymares_OnChange(void)2427 static void Dummymares_OnChange(void)
2428 {
2429 if (!nightsrecords[cv_nextmap.value-1])
2430 {
2431 CV_StealthSetValue(&cv_dummymares, 0);
2432 return;
2433 }
2434 else
2435 {
2436 UINT8 mares = nightsrecords[cv_nextmap.value-1]->nummares;
2437
2438 if (cv_dummymares.value < 0)
2439 CV_StealthSetValue(&cv_dummymares, mares);
2440 else if (cv_dummymares.value > mares)
2441 CV_StealthSetValue(&cv_dummymares, 0);
2442 }
2443 }
2444
2445 // Newgametype. Used for gametype changes.
Newgametype_OnChange(void)2446 static void Newgametype_OnChange(void)
2447 {
2448 if (menuactive)
2449 {
2450 if(!mapheaderinfo[cv_nextmap.value-1])
2451 P_AllocMapHeader((INT16)(cv_nextmap.value-1));
2452
2453 if (!M_CanShowLevelOnPlatter(cv_nextmap.value-1, cv_newgametype.value))
2454 CV_SetValue(&cv_nextmap, M_GetFirstLevelInList(cv_newgametype.value));
2455 }
2456 }
2457
Screenshot_option_Onchange(void)2458 void Screenshot_option_Onchange(void)
2459 {
2460 OP_ScreenshotOptionsMenu[op_screenshot_folder].status =
2461 (cv_screenshot_option.value == 3 ? IT_CVAR|IT_STRING|IT_CV_STRING : IT_DISABLED);
2462 }
2463
Moviemode_mode_Onchange(void)2464 void Moviemode_mode_Onchange(void)
2465 {
2466 INT32 i, cstart, cend;
2467 for (i = op_screenshot_gif_start; i <= op_screenshot_apng_end; ++i)
2468 OP_ScreenshotOptionsMenu[i].status = IT_DISABLED;
2469
2470 switch (cv_moviemode.value)
2471 {
2472 case MM_GIF:
2473 cstart = op_screenshot_gif_start;
2474 cend = op_screenshot_gif_end;
2475 break;
2476 case MM_APNG:
2477 cstart = op_screenshot_apng_start;
2478 cend = op_screenshot_apng_end;
2479 break;
2480 default:
2481 return;
2482 }
2483 for (i = cstart; i <= cend; ++i)
2484 OP_ScreenshotOptionsMenu[i].status = IT_STRING|IT_CVAR;
2485 }
2486
Addons_option_Onchange(void)2487 void Addons_option_Onchange(void)
2488 {
2489 OP_AddonsOptionsMenu[op_addons_folder].status =
2490 (cv_addons_option.value == 3 ? IT_CVAR|IT_STRING|IT_CV_STRING : IT_DISABLED);
2491 }
2492
Moviemode_option_Onchange(void)2493 void Moviemode_option_Onchange(void)
2494 {
2495 OP_ScreenshotOptionsMenu[op_movie_folder].status =
2496 (cv_movie_option.value == 3 ? IT_CVAR|IT_STRING|IT_CV_STRING : IT_DISABLED);
2497 }
2498
2499 // ==========================================================================
2500 // END ORGANIZATION STUFF.
2501 // ==========================================================================
2502
2503 // current menudef
2504 menu_t *currentMenu = &MainDef;
2505
2506 // =========================================================================
2507 // MENU PRESENTATION PARAMETER HANDLING (BACKGROUNDS)
2508 // =========================================================================
2509
2510 // menu IDs are equal to current/prevMenu in most cases, except MN_SPECIAL when we don't want to operate on Message, Pause, etc.
2511 UINT32 prevMenuId = 0;
2512 UINT32 activeMenuId = 0;
2513
2514 menupres_t menupres[NUMMENUTYPES];
2515
M_InitMenuPresTables(void)2516 void M_InitMenuPresTables(void)
2517 {
2518 INT32 i;
2519
2520 // Called in d_main before SOC can get to the tables
2521 // Set menupres defaults
2522 for (i = 0; i < NUMMENUTYPES; i++)
2523 {
2524 // so-called "undefined"
2525 menupres[i].fadestrength = -1;
2526 menupres[i].hidetitlepics = -1; // inherits global hidetitlepics
2527 menupres[i].ttmode = TTMODE_NONE;
2528 menupres[i].ttscale = UINT8_MAX;
2529 menupres[i].ttname[0] = 0;
2530 menupres[i].ttx = INT16_MAX;
2531 menupres[i].tty = INT16_MAX;
2532 menupres[i].ttloop = INT16_MAX;
2533 menupres[i].tttics = UINT16_MAX;
2534 menupres[i].enterwipe = -1;
2535 menupres[i].exitwipe = -1;
2536 menupres[i].bgcolor = -1;
2537 menupres[i].titlescrollxspeed = INT32_MAX;
2538 menupres[i].titlescrollyspeed = INT32_MAX;
2539 menupres[i].bghide = true;
2540 // default true
2541 menupres[i].enterbubble = true;
2542 menupres[i].exitbubble = true;
2543
2544 if (i != MN_MAIN)
2545 {
2546 menupres[i].muslooping = true;
2547 }
2548 if (i == MN_SP_TIMEATTACK)
2549 strncpy(menupres[i].musname, "_recat", 7);
2550 else if (i == MN_SP_NIGHTSATTACK)
2551 strncpy(menupres[i].musname, "_nitat", 7);
2552 else if (i == MN_SP_MARATHON)
2553 strncpy(menupres[i].musname, "spec8", 6);
2554 else if (i == MN_SP_PLAYER || i == MN_SR_PLAYER)
2555 strncpy(menupres[i].musname, "_chsel", 7);
2556 else if (i == MN_SR_SOUNDTEST)
2557 {
2558 *menupres[i].musname = '\0';
2559 menupres[i].musstop = true;
2560 }
2561 }
2562 }
2563
2564 // ====================================
2565 // TREE ITERATION
2566 // ====================================
2567
2568 // UINT32 menutype - current menutype_t
2569 // INT32 level - current level up the tree, higher means younger
2570 // INT32 *retval - Return value
2571 // void *input - Pointer to input of any type
2572 //
2573 // return true - stop iterating
2574 // return false - continue
2575 typedef boolean (*menutree_iterator)(UINT32, INT32, INT32 *, void **, boolean fromoldest);
2576
2577 // HACK: Used in the ChangeMusic iterator because we only allow
2578 // a single input. Maybe someday use this struct program-wide.
2579 typedef struct
2580 {
2581 char musname[7];
2582 UINT16 mustrack;
2583 boolean muslooping;
2584 } menupresmusic_t;
2585
M_IterateMenuTree(menutree_iterator itfunc,void * input)2586 static INT32 M_IterateMenuTree(menutree_iterator itfunc, void *input)
2587 {
2588 INT32 i, retval = 0;
2589 UINT32 bitmask, menutype;
2590
2591 for (i = NUMMENULEVELS; i >= 0; i--)
2592 {
2593 bitmask = ((1 << MENUBITS) - 1) << (MENUBITS*i);
2594 menutype = (activeMenuId & bitmask) >> (MENUBITS*i);
2595 if (itfunc(menutype, i, &retval, &input, false))
2596 break;
2597 }
2598
2599 return retval;
2600 }
2601
2602 // ====================================
2603 // ITERATORS
2604 // ====================================
2605
MIT_GetMenuAtLevel(UINT32 menutype,INT32 level,INT32 * retval,void ** input,boolean fromoldest)2606 static boolean MIT_GetMenuAtLevel(UINT32 menutype, INT32 level, INT32 *retval, void **input, boolean fromoldest)
2607 {
2608 INT32 *inputptr = (INT32*)*input;
2609 INT32 targetlevel = *inputptr;
2610 if (menutype)
2611 {
2612 if (level == targetlevel || targetlevel < 0)
2613 {
2614 *retval = menutype;
2615 return true;
2616 }
2617 }
2618 else if (targetlevel >= 0)
2619 {
2620 // offset targetlevel by failed attempts; this should only happen in beginning of iteration
2621 if (fromoldest)
2622 (*inputptr)++;
2623 else
2624 (*inputptr)--; // iterating backwards, so count from highest
2625 }
2626 return false;
2627 }
2628
MIT_SetCurBackground(UINT32 menutype,INT32 level,INT32 * retval,void ** input,boolean fromoldest)2629 static boolean MIT_SetCurBackground(UINT32 menutype, INT32 level, INT32 *retval, void **input, boolean fromoldest)
2630 {
2631 char *defaultname = (char*)*input;
2632
2633 (void)retval;
2634 (void)fromoldest;
2635
2636 if (!menutype) // if there's nothing in this level, do nothing
2637 return false;
2638
2639 if (menupres[menutype].bgcolor >= 0)
2640 {
2641 curbgcolor = menupres[menutype].bgcolor;
2642 return true;
2643 }
2644 else if (menupres[menutype].bghide && titlemapinaction) // hide the background
2645 {
2646 curbghide = true;
2647 return true;
2648 }
2649 else if (menupres[menutype].bgname[0])
2650 {
2651 strncpy(curbgname, menupres[menutype].bgname, 8);
2652 curbgxspeed = menupres[menutype].titlescrollxspeed != INT32_MAX ? menupres[menutype].titlescrollxspeed : titlescrollxspeed;
2653 curbgyspeed = menupres[menutype].titlescrollyspeed != INT32_MAX ? menupres[menutype].titlescrollyspeed : titlescrollyspeed;
2654 return true;
2655 }
2656 else if (!level)
2657 {
2658 if (M_GetYoungestChildMenu() == MN_SP_PLAYER || !defaultname || !defaultname[0])
2659 curbgcolor = 31;
2660 else if (titlemapinaction) // hide the background by default in titlemap
2661 curbghide = true;
2662 else
2663 {
2664 strncpy(curbgname, defaultname, 9);
2665 curbgxspeed = (gamestate == GS_TIMEATTACK) ? 0 : titlescrollxspeed;
2666 curbgyspeed = (gamestate == GS_TIMEATTACK) ? 0 : titlescrollyspeed;
2667 }
2668 }
2669 return false;
2670 }
2671
MIT_ChangeMusic(UINT32 menutype,INT32 level,INT32 * retval,void ** input,boolean fromoldest)2672 static boolean MIT_ChangeMusic(UINT32 menutype, INT32 level, INT32 *retval, void **input, boolean fromoldest)
2673 {
2674 menupresmusic_t *defaultmusic = (menupresmusic_t*)*input;
2675
2676 (void)retval;
2677 (void)fromoldest;
2678
2679 if (!menutype) // if there's nothing in this level, do nothing
2680 return false;
2681
2682 if (menupres[menutype].musname[0])
2683 {
2684 S_ChangeMusic(menupres[menutype].musname, menupres[menutype].mustrack, menupres[menutype].muslooping);
2685 return true;
2686 }
2687 else if (menupres[menutype].musstop)
2688 {
2689 S_StopMusic();
2690 return true;
2691 }
2692 else if (menupres[menutype].musignore)
2693 return true;
2694 else if (!level && defaultmusic && defaultmusic->musname[0])
2695 S_ChangeMusic(defaultmusic->musname, defaultmusic->mustrack, defaultmusic->muslooping);
2696 return false;
2697 }
2698
MIT_SetCurFadeValue(UINT32 menutype,INT32 level,INT32 * retval,void ** input,boolean fromoldest)2699 static boolean MIT_SetCurFadeValue(UINT32 menutype, INT32 level, INT32 *retval, void **input, boolean fromoldest)
2700 {
2701 UINT8 defaultvalue = *(UINT8*)*input;
2702
2703 (void)retval;
2704 (void)fromoldest;
2705
2706 if (!menutype) // if there's nothing in this level, do nothing
2707 return false;
2708
2709 if (menupres[menutype].fadestrength >= 0)
2710 {
2711 curfadevalue = (menupres[menutype].fadestrength % 32);
2712 return true;
2713 }
2714 else if (!level)
2715 curfadevalue = (gamestate == GS_TIMEATTACK) ? 0 : (defaultvalue % 32);
2716 return false;
2717 }
2718
MIT_SetCurTitlePics(UINT32 menutype,INT32 level,INT32 * retval,void ** input,boolean fromoldest)2719 static boolean MIT_SetCurTitlePics(UINT32 menutype, INT32 level, INT32 *retval, void **input, boolean fromoldest)
2720 {
2721 (void)input;
2722 (void)retval;
2723 (void)fromoldest;
2724
2725 if (!menutype) // if there's nothing in this level, do nothing
2726 return false;
2727
2728 if (menupres[menutype].hidetitlepics >= 0)
2729 {
2730 curhidepics = menupres[menutype].hidetitlepics;
2731 return true;
2732 }
2733 else if (menupres[menutype].ttmode == TTMODE_USER)
2734 {
2735 if (menupres[menutype].ttname[0])
2736 {
2737 curhidepics = menupres[menutype].hidetitlepics;
2738 curttmode = menupres[menutype].ttmode;
2739 curttscale = (menupres[menutype].ttscale != UINT8_MAX ? menupres[menutype].ttscale : ttscale);
2740 strncpy(curttname, menupres[menutype].ttname, 9);
2741 curttx = (menupres[menutype].ttx != INT16_MAX ? menupres[menutype].ttx : ttx);
2742 curtty = (menupres[menutype].tty != INT16_MAX ? menupres[menutype].tty : tty);
2743 curttloop = (menupres[menutype].ttloop != INT16_MAX ? menupres[menutype].ttloop : ttloop);
2744 curtttics = (menupres[menutype].tttics != UINT16_MAX ? menupres[menutype].tttics : tttics);
2745 }
2746 else
2747 curhidepics = menupres[menutype].hidetitlepics;
2748 return true;
2749 }
2750 else if (menupres[menutype].ttmode != TTMODE_NONE)
2751 {
2752 curhidepics = menupres[menutype].hidetitlepics;
2753 curttmode = menupres[menutype].ttmode;
2754 curttscale = (menupres[menutype].ttscale != UINT8_MAX ? menupres[menutype].ttscale : ttscale);
2755 return true;
2756 }
2757 else if (!level)
2758 {
2759 curhidepics = hidetitlepics;
2760 curttmode = ttmode;
2761 curttscale = ttscale;
2762 strncpy(curttname, ttname, 9);
2763 curttx = ttx;
2764 curtty = tty;
2765 curttloop = ttloop;
2766 curtttics = tttics;
2767 }
2768 return false;
2769 }
2770
2771 // ====================================
2772 // TREE RETRIEVAL
2773 // ====================================
2774
M_GetYoungestChildMenu(void)2775 UINT8 M_GetYoungestChildMenu(void) // aka the active menu
2776 {
2777 INT32 targetlevel = -1;
2778 return M_IterateMenuTree(MIT_GetMenuAtLevel, &targetlevel);
2779 }
2780
2781 // ====================================
2782 // EFFECTS
2783 // ====================================
2784
M_ChangeMenuMusic(const char * defaultmusname,boolean defaultmuslooping)2785 void M_ChangeMenuMusic(const char *defaultmusname, boolean defaultmuslooping)
2786 {
2787 menupresmusic_t defaultmusic;
2788
2789 if (!defaultmusname)
2790 defaultmusname = "";
2791
2792 strncpy(defaultmusic.musname, defaultmusname, 7);
2793 defaultmusic.musname[6] = 0;
2794 defaultmusic.mustrack = 0;
2795 defaultmusic.muslooping = defaultmuslooping;
2796
2797 M_IterateMenuTree(MIT_ChangeMusic, &defaultmusic);
2798 }
2799
M_SetMenuCurBackground(const char * defaultname)2800 void M_SetMenuCurBackground(const char *defaultname)
2801 {
2802 char name[9];
2803 strncpy(name, defaultname, 8);
2804 M_IterateMenuTree(MIT_SetCurBackground, &name);
2805 }
2806
M_SetMenuCurFadeValue(UINT8 defaultvalue)2807 void M_SetMenuCurFadeValue(UINT8 defaultvalue)
2808 {
2809 M_IterateMenuTree(MIT_SetCurFadeValue, &defaultvalue);
2810 }
2811
M_SetMenuCurTitlePics(void)2812 void M_SetMenuCurTitlePics(void)
2813 {
2814 M_IterateMenuTree(MIT_SetCurTitlePics, NULL);
2815 }
2816
2817 // ====================================
2818 // MENU STATE
2819 // ====================================
2820
2821 static INT32 exitlevel, enterlevel, anceslevel;
2822 static INT16 exittype, entertype;
2823 static INT16 exitwipe, enterwipe;
2824 static boolean exitbubble, enterbubble;
2825 static INT16 exittag, entertag;
2826
M_HandleMenuPresState(menu_t * newMenu)2827 static void M_HandleMenuPresState(menu_t *newMenu)
2828 {
2829 INT32 i;
2830 UINT32 bitmask;
2831 SINT8 prevtype, activetype, menutype;
2832
2833 if (!newMenu)
2834 return;
2835
2836 // Look for MN_SPECIAL here, because our iterators can't look at new menu IDs
2837 for (i = 0; i <= NUMMENULEVELS; i++)
2838 {
2839 bitmask = ((1 << MENUBITS) - 1) << (MENUBITS*i);
2840 menutype = (newMenu->menuid & bitmask) >> (MENUBITS*i);
2841 prevtype = (currentMenu->menuid & bitmask) >> (MENUBITS*i);
2842 if (menutype == MN_SPECIAL || prevtype == MN_SPECIAL)
2843 return;
2844 }
2845
2846 if (currentMenu && newMenu && currentMenu->menuid == newMenu->menuid) // same menu?
2847 return;
2848
2849 exittype = entertype = exitlevel = enterlevel = anceslevel = exitwipe = enterwipe = -1;
2850 exitbubble = enterbubble = true;
2851
2852 prevMenuId = currentMenu ? currentMenu->menuid : 0;
2853 activeMenuId = newMenu ? newMenu->menuid : 0;
2854
2855 // Set defaults for presentation values
2856 strncpy(curbgname, "TITLESKY", 9);
2857 curfadevalue = 16;
2858 curhidepics = hidetitlepics;
2859 curbgcolor = -1;
2860 curbgxspeed = titlescrollxspeed;
2861 curbgyspeed = titlescrollyspeed;
2862 curbghide = (gamestate != GS_TIMEATTACK); // show in time attack, hide in other menus
2863
2864 curttmode = ttmode;
2865 curttscale = ttscale;
2866 strncpy(curttname, ttname, 9);
2867 curttx = ttx;
2868 curtty = tty;
2869 curttloop = ttloop;
2870 curtttics = tttics;
2871
2872 // don't do the below during the in-game menus
2873 if (gamestate != GS_TITLESCREEN && gamestate != GS_TIMEATTACK)
2874 return;
2875
2876 M_SetMenuCurFadeValue(16);
2877 M_SetMenuCurTitlePics();
2878
2879 // Loop through both menu IDs in parallel and look for type changes
2880 // The youngest child in activeMenuId is the entered menu
2881 // The youngest child in prevMenuId is the exited menu
2882
2883 // 0. Get the type and level of each menu, and level of common ancestor
2884 // 1. Get the wipes for both, then run the exit wipe
2885 // 2. Change music (so that execs can change it again later)
2886 // 3. Run each exit exec on the prevMenuId up to the common ancestor (UNLESS NoBubbleExecs)
2887 // 4. Run each entrance exec on the activeMenuId down from the common ancestor (UNLESS NoBubbleExecs)
2888 // 5. Run the entrance wipe
2889
2890 // Get the parameters for each menu
2891 for (i = NUMMENULEVELS; i >= 0; i--)
2892 {
2893 bitmask = ((1 << MENUBITS) - 1) << (MENUBITS*i);
2894 prevtype = (prevMenuId & bitmask) >> (MENUBITS*i);
2895 activetype = (activeMenuId & bitmask) >> (MENUBITS*i);
2896
2897 if (prevtype && (exittype < 0))
2898 {
2899 exittype = prevtype;
2900 exitlevel = i;
2901 exitwipe = menupres[exittype].exitwipe;
2902 exitbubble = menupres[exittype].exitbubble;
2903 exittag = menupres[exittype].exittag;
2904 }
2905
2906 if (activetype && (entertype < 0))
2907 {
2908 entertype = activetype;
2909 enterlevel = i;
2910 enterwipe = menupres[entertype].enterwipe;
2911 enterbubble = menupres[entertype].enterbubble;
2912 entertag = menupres[entertype].entertag;
2913 }
2914
2915 if (prevtype && activetype && prevtype == activetype && anceslevel < 0)
2916 {
2917 anceslevel = i;
2918 break;
2919 }
2920 }
2921
2922 // if no common ancestor (top menu), force a wipe. Look for a specified wipe first.
2923 // Don't force a wipe if you're actually going to/from the main menu
2924 if (anceslevel < 0 && exitwipe < 0 && newMenu != &MainDef && currentMenu != &MainDef)
2925 {
2926 for (i = NUMMENULEVELS; i >= 0; i--)
2927 {
2928 bitmask = ((1 << MENUBITS) - 1) << (MENUBITS*i);
2929 prevtype = (prevMenuId & bitmask) >> (MENUBITS*i);
2930
2931 if (menupres[prevtype].exitwipe >= 0)
2932 {
2933 exitwipe = menupres[prevtype].exitwipe;
2934 break;
2935 }
2936 }
2937
2938 if (exitwipe < 0)
2939 exitwipe = menupres[MN_MAIN].exitwipe;
2940 }
2941
2942 // do the same for enter wipe
2943 if (anceslevel < 0 && enterwipe < 0 && newMenu != &MainDef && currentMenu != &MainDef)
2944 {
2945 for (i = NUMMENULEVELS; i >= 0; i--)
2946 {
2947 bitmask = ((1 << MENUBITS) - 1) << (MENUBITS*i);
2948 activetype = (activeMenuId & bitmask) >> (MENUBITS*i);
2949
2950 if (menupres[activetype].enterwipe >= 0)
2951 {
2952 exitwipe = menupres[activetype].enterwipe;
2953 break;
2954 }
2955 }
2956
2957 if (enterwipe < 0)
2958 enterwipe = menupres[MN_MAIN].enterwipe;
2959 }
2960
2961 // Change the music
2962 M_ChangeMenuMusic("_title", false);
2963
2964 // Run the linedef execs
2965 if (titlemapinaction)
2966 {
2967 // Run the exit tags
2968 if (enterlevel <= exitlevel) // equals is an edge case
2969 {
2970 if (exitbubble)
2971 {
2972 for (i = exitlevel; i > anceslevel; i--) // don't run the common ancestor's exit tag
2973 {
2974 bitmask = ((1 << MENUBITS) - 1) << (MENUBITS*i);
2975 menutype = (prevMenuId & bitmask) >> (MENUBITS*i);
2976 if (menupres[menutype].exittag)
2977 P_LinedefExecute(menupres[menutype].exittag, players[displayplayer].mo, NULL);
2978 }
2979 }
2980 else if (exittag)
2981 P_LinedefExecute(exittag, players[displayplayer].mo, NULL);
2982 }
2983
2984 // Run the enter tags
2985 if (enterlevel >= exitlevel) // equals is an edge case
2986 {
2987 if (enterbubble)
2988 {
2989 for (i = anceslevel+1; i <= enterlevel; i++) // don't run the common ancestor's enter tag
2990 {
2991 bitmask = ((1 << MENUBITS) - 1) << (MENUBITS*i);
2992 menutype = (activeMenuId & bitmask) >> (MENUBITS*i);
2993 if (menupres[menutype].entertag)
2994 P_LinedefExecute(menupres[menutype].entertag, players[displayplayer].mo, NULL);
2995 }
2996 }
2997 else if (entertag)
2998 P_LinedefExecute(entertag, players[displayplayer].mo, NULL);
2999 }
3000 }
3001
3002
3003 // Set the wipes for next frame
3004 if (
3005 (exitwipe >= 0 && enterlevel <= exitlevel) ||
3006 (enterwipe >= 0 && enterlevel >= exitlevel) ||
3007 (anceslevel < 0 && newMenu != &MainDef && currentMenu != &MainDef)
3008 )
3009 {
3010 if (gamestate == GS_TIMEATTACK)
3011 wipetypepre = ((exitwipe && enterlevel <= exitlevel) || anceslevel < 0) ? exitwipe : -1; // force default
3012 else
3013 // HACK: INT16_MAX signals to not wipe
3014 // because 0 is a valid index and -1 means default
3015 wipetypepre = ((exitwipe && enterlevel <= exitlevel) || anceslevel < 0) ? exitwipe : INT16_MAX;
3016 wipetypepost = ((enterwipe && enterlevel >= exitlevel) || anceslevel < 0) ? enterwipe : INT16_MAX;
3017 wipegamestate = FORCEWIPE;
3018
3019 // If just one of the above is a force not-wipe,
3020 // mirror the other wipe.
3021 if (wipetypepre != INT16_MAX && wipetypepost == INT16_MAX)
3022 wipetypepost = wipetypepre;
3023 else if (wipetypepost != INT16_MAX && wipetypepre == INT16_MAX)
3024 wipetypepre = wipetypepost;
3025
3026 // D_Display runs the next step of processing
3027 }
3028 }
3029
3030 // =========================================================================
3031 // BASIC MENU HANDLING
3032 // =========================================================================
3033
M_GoBack(INT32 choice)3034 static void M_GoBack(INT32 choice)
3035 {
3036 (void)choice;
3037
3038 if (currentMenu->prevMenu)
3039 {
3040 //If we entered the game search menu, but didn't enter a game,
3041 //make sure the game doesn't still think we're in a netgame.
3042 if (!Playing() && netgame && multiplayer)
3043 {
3044 netgame = multiplayer = false;
3045 }
3046
3047 if ((currentMenu->prevMenu == &MainDef) && (currentMenu == &SP_TimeAttackDef || currentMenu == &SP_NightsAttackDef || currentMenu == &SP_MarathonDef))
3048 {
3049 // D_StartTitle does its own wipe, since GS_TIMEATTACK is now a complete gamestate.
3050
3051 if (levelselect.rows)
3052 {
3053 Z_Free(levelselect.rows);
3054 levelselect.rows = NULL;
3055 }
3056
3057 menuactive = false;
3058 wipetypepre = menupres[M_GetYoungestChildMenu()].exitwipe;
3059 I_UpdateMouseGrab();
3060 D_StartTitle();
3061 }
3062 else
3063 M_SetupNextMenu(currentMenu->prevMenu);
3064 }
3065 else
3066 M_ClearMenus(true);
3067 }
3068
M_ChangeCvar(INT32 choice)3069 static void M_ChangeCvar(INT32 choice)
3070 {
3071 consvar_t *cv = (consvar_t *)currentMenu->menuitems[itemOn].itemaction;
3072
3073 if (choice == -1)
3074 {
3075 if (cv == &cv_playercolor)
3076 {
3077 SINT8 skinno = R_SkinAvailable(cv_chooseskin.string);
3078 if (skinno != -1)
3079 CV_SetValue(cv,skins[skinno].prefcolor);
3080 return;
3081 }
3082 CV_Set(cv,cv->defaultvalue);
3083 return;
3084 }
3085
3086 choice = (choice<<1) - 1;
3087
3088 if (cv->flags & CV_FLOAT)
3089 {
3090 if (((currentMenu->menuitems[itemOn].status & IT_CVARTYPE) == IT_CV_SLIDER)
3091 ||((currentMenu->menuitems[itemOn].status & IT_CVARTYPE) == IT_CV_INVISSLIDER)
3092 ||((currentMenu->menuitems[itemOn].status & IT_CVARTYPE) == IT_CV_NOMOD)
3093 || !(currentMenu->menuitems[itemOn].status & IT_CV_INTEGERSTEP))
3094 {
3095 char s[20];
3096 float n = FIXED_TO_FLOAT(cv->value)+(choice)*(1.0f/16.0f);
3097 sprintf(s,"%ld%s",(long)n,M_Ftrim(n));
3098 CV_Set(cv,s);
3099 }
3100 else
3101 CV_SetValue(cv,FIXED_TO_FLOAT(cv->value)+(choice));
3102 }
3103 else
3104 CV_AddValue(cv,choice);
3105 }
3106
M_ChangeStringCvar(INT32 choice)3107 static boolean M_ChangeStringCvar(INT32 choice)
3108 {
3109 consvar_t *cv = (consvar_t *)currentMenu->menuitems[itemOn].itemaction;
3110 char buf[MAXSTRINGLENGTH];
3111 size_t len;
3112
3113 if (shiftdown && choice >= 32 && choice <= 127)
3114 choice = shiftxform[choice];
3115
3116 switch (choice)
3117 {
3118 case KEY_BACKSPACE:
3119 len = strlen(cv->string);
3120 if (len > 0)
3121 {
3122 M_Memcpy(buf, cv->string, len);
3123 buf[len-1] = 0;
3124 CV_Set(cv, buf);
3125 }
3126 return true;
3127 default:
3128 if (choice >= 32 && choice <= 127)
3129 {
3130 len = strlen(cv->string);
3131 if (len < MAXSTRINGLENGTH - 1)
3132 {
3133 M_Memcpy(buf, cv->string, len);
3134 buf[len++] = (char)choice;
3135 buf[len] = 0;
3136 CV_Set(cv, buf);
3137 }
3138 return true;
3139 }
3140 break;
3141 }
3142 return false;
3143 }
3144
3145 // resets all cvars on a menu - assumes that all that have itemactions are cvars
M_ResetCvars(void)3146 static void M_ResetCvars(void)
3147 {
3148 INT32 i;
3149 consvar_t *cv;
3150 for (i = 0; i < currentMenu->numitems; i++)
3151 {
3152 if (!(currentMenu->menuitems[i].status & IT_CVAR) || !(cv = (consvar_t *)currentMenu->menuitems[i].itemaction))
3153 continue;
3154 CV_SetValue(cv, atoi(cv->defaultvalue));
3155 }
3156 }
3157
M_NextOpt(void)3158 static void M_NextOpt(void)
3159 {
3160 INT16 oldItemOn = itemOn; // prevent infinite loop
3161 do
3162 {
3163 if (itemOn + 1 > currentMenu->numitems - 1)
3164 itemOn = 0;
3165 else
3166 itemOn++;
3167 } while (oldItemOn != itemOn && ( (currentMenu->menuitems[itemOn].status & IT_TYPE) & IT_SPACE ));
3168 }
3169
M_PrevOpt(void)3170 static void M_PrevOpt(void)
3171 {
3172 INT16 oldItemOn = itemOn; // prevent infinite loop
3173 do
3174 {
3175 if (!itemOn)
3176 itemOn = currentMenu->numitems - 1;
3177 else
3178 itemOn--;
3179 } while (oldItemOn != itemOn && ( (currentMenu->menuitems[itemOn].status & IT_TYPE) & IT_SPACE ));
3180 }
3181
3182 // lock out further input in a tic when important buttons are pressed
3183 // (in other words -- stop bullshit happening by mashing buttons in fades)
3184 static boolean noFurtherInput = false;
3185
Command_Manual_f(void)3186 static void Command_Manual_f(void)
3187 {
3188 if (modeattacking)
3189 return;
3190 M_StartControlPanel();
3191 currentMenu = &MISC_HelpDef;
3192 itemOn = 0;
3193 }
3194
3195 //
3196 // M_Responder
3197 //
M_Responder(event_t * ev)3198 boolean M_Responder(event_t *ev)
3199 {
3200 INT32 ch = -1;
3201 // INT32 i;
3202 static tic_t joywait = 0, mousewait = 0;
3203 static INT32 pjoyx = 0, pjoyy = 0;
3204 static INT32 pmousex = 0, pmousey = 0;
3205 static INT32 lastx = 0, lasty = 0;
3206 void (*routine)(INT32 choice); // for some casting problem
3207
3208 if (dedicated || (demoplayback && titledemo)
3209 || gamestate == GS_INTRO || gamestate == GS_ENDING || gamestate == GS_CUTSCENE
3210 || gamestate == GS_CREDITS || gamestate == GS_EVALUATION || gamestate == GS_GAMEEND)
3211 return false;
3212
3213 if (gamestate == GS_TITLESCREEN && finalecount < TICRATE)
3214 return false;
3215
3216 if (CON_Ready())
3217 return false;
3218
3219 if (noFurtherInput)
3220 {
3221 // Ignore input after enter/escape/other buttons
3222 // (but still allow shift keyup so caps doesn't get stuck)
3223 return false;
3224 }
3225 else if (menuactive)
3226 {
3227 if (ev->type == ev_keydown)
3228 {
3229 keydown++;
3230 ch = ev->data1;
3231
3232 // added 5-2-98 remap virtual keys (mouse & joystick buttons)
3233 switch (ch)
3234 {
3235 case KEY_MOUSE1:
3236 case KEY_JOY1:
3237 ch = KEY_ENTER;
3238 break;
3239 case KEY_JOY1 + 3:
3240 ch = 'n';
3241 break;
3242 case KEY_MOUSE1 + 1:
3243 case KEY_JOY1 + 1:
3244 ch = KEY_ESCAPE;
3245 break;
3246 case KEY_JOY1 + 2:
3247 ch = KEY_BACKSPACE;
3248 break;
3249 case KEY_HAT1:
3250 ch = KEY_UPARROW;
3251 break;
3252 case KEY_HAT1 + 1:
3253 ch = KEY_DOWNARROW;
3254 break;
3255 case KEY_HAT1 + 2:
3256 ch = KEY_LEFTARROW;
3257 break;
3258 case KEY_HAT1 + 3:
3259 ch = KEY_RIGHTARROW;
3260 break;
3261 }
3262 }
3263 else if (ev->type == ev_joystick && ev->data1 == 0 && joywait < I_GetTime())
3264 {
3265 const INT32 jdeadzone = (JOYAXISRANGE * cv_digitaldeadzone.value) / FRACUNIT;
3266 if (ev->data3 != INT32_MAX)
3267 {
3268 if (Joystick.bGamepadStyle || abs(ev->data3) > jdeadzone)
3269 {
3270 if (ev->data3 < 0 && pjoyy >= 0)
3271 {
3272 ch = KEY_UPARROW;
3273 joywait = I_GetTime() + NEWTICRATE/7;
3274 }
3275 else if (ev->data3 > 0 && pjoyy <= 0)
3276 {
3277 ch = KEY_DOWNARROW;
3278 joywait = I_GetTime() + NEWTICRATE/7;
3279 }
3280 pjoyy = ev->data3;
3281 }
3282 else
3283 pjoyy = 0;
3284 }
3285
3286 if (ev->data2 != INT32_MAX)
3287 {
3288 if (Joystick.bGamepadStyle || abs(ev->data2) > jdeadzone)
3289 {
3290 if (ev->data2 < 0 && pjoyx >= 0)
3291 {
3292 ch = KEY_LEFTARROW;
3293 joywait = I_GetTime() + NEWTICRATE/17;
3294 }
3295 else if (ev->data2 > 0 && pjoyx <= 0)
3296 {
3297 ch = KEY_RIGHTARROW;
3298 joywait = I_GetTime() + NEWTICRATE/17;
3299 }
3300 pjoyx = ev->data2;
3301 }
3302 else
3303 pjoyx = 0;
3304 }
3305 }
3306 else if (ev->type == ev_mouse && mousewait < I_GetTime())
3307 {
3308 pmousey += ev->data3;
3309 if (pmousey < lasty-30)
3310 {
3311 ch = KEY_DOWNARROW;
3312 mousewait = I_GetTime() + NEWTICRATE/7;
3313 pmousey = lasty -= 30;
3314 }
3315 else if (pmousey > lasty + 30)
3316 {
3317 ch = KEY_UPARROW;
3318 mousewait = I_GetTime() + NEWTICRATE/7;
3319 pmousey = lasty += 30;
3320 }
3321
3322 pmousex += ev->data2;
3323 if (pmousex < lastx - 30)
3324 {
3325 ch = KEY_LEFTARROW;
3326 mousewait = I_GetTime() + NEWTICRATE/7;
3327 pmousex = lastx -= 30;
3328 }
3329 else if (pmousex > lastx+30)
3330 {
3331 ch = KEY_RIGHTARROW;
3332 mousewait = I_GetTime() + NEWTICRATE/7;
3333 pmousex = lastx += 30;
3334 }
3335 }
3336 else if (ev->type == ev_keyup) // Preserve event for other responders
3337 keydown = 0;
3338 }
3339 else if (ev->type == ev_keydown) // Preserve event for other responders
3340 ch = ev->data1;
3341
3342 if (ch == -1)
3343 return false;
3344 else if (ch == gamecontrol[gc_systemmenu][0] || ch == gamecontrol[gc_systemmenu][1]) // allow remappable ESC key
3345 ch = KEY_ESCAPE;
3346
3347 // F-Keys
3348 if (!menuactive)
3349 {
3350 noFurtherInput = true;
3351 switch (ch)
3352 {
3353 case KEY_F1: // Help key
3354 Command_Manual_f();
3355 return true;
3356
3357 case KEY_F2: // Empty
3358 return true;
3359
3360 case KEY_F3: // Toggle HUD
3361 CV_SetValue(&cv_showhud, !cv_showhud.value);
3362 return true;
3363
3364 case KEY_F4: // Sound Volume
3365 if (modeattacking)
3366 return true;
3367 M_StartControlPanel();
3368 M_Options(0);
3369 // Uncomment the below if you want the menu to reset to the top each time like before. M_SetupNextMenu will fix it automatically.
3370 //OP_SoundOptionsDef.lastOn = 0;
3371 M_SetupNextMenu(&OP_SoundOptionsDef);
3372 return true;
3373
3374 case KEY_F5: // Video Mode
3375 if (modeattacking)
3376 return true;
3377 M_StartControlPanel();
3378 M_Options(0);
3379 M_VideoModeMenu(0);
3380 return true;
3381
3382 case KEY_F6: // Empty
3383 return true;
3384
3385 case KEY_F7: // Options
3386 if (modeattacking)
3387 return true;
3388 M_StartControlPanel();
3389 M_Options(0);
3390 M_SetupNextMenu(&OP_MainDef);
3391 return true;
3392
3393 // Screenshots on F8 now handled elsewhere
3394 // Same with Moviemode on F9
3395
3396 case KEY_F10: // Quit SRB2
3397 M_QuitSRB2(0);
3398 return true;
3399
3400 case KEY_F11: // Gamma Level
3401 CV_AddValue(&cv_globalgamma, 1);
3402 return true;
3403
3404 // Spymode on F12 handled in game logic
3405
3406 case KEY_ESCAPE: // Pop up menu
3407 if (chat_on)
3408 HU_clearChatChars();
3409 else
3410 M_StartControlPanel();
3411 return true;
3412 }
3413 noFurtherInput = false; // turns out we didn't care
3414 return false;
3415 }
3416
3417 routine = currentMenu->menuitems[itemOn].itemaction;
3418
3419 // Handle menuitems which need a specific key handling
3420 if (routine && (currentMenu->menuitems[itemOn].status & IT_TYPE) == IT_KEYHANDLER)
3421 {
3422 if (shiftdown && ch >= 32 && ch <= 127)
3423 ch = shiftxform[ch];
3424 routine(ch);
3425 return true;
3426 }
3427
3428 if (currentMenu->menuitems[itemOn].status == IT_MSGHANDLER)
3429 {
3430 if (currentMenu->menuitems[itemOn].alphaKey != MM_EVENTHANDLER)
3431 {
3432 if (ch == ' ' || ch == 'n' || ch == 'y' || ch == KEY_ESCAPE || ch == KEY_ENTER || ch == KEY_DEL)
3433 {
3434 if (routine)
3435 routine(ch);
3436 if (stopstopmessage)
3437 stopstopmessage = false;
3438 else
3439 M_StopMessage(0);
3440 noFurtherInput = true;
3441 return true;
3442 }
3443 return true;
3444 }
3445 else
3446 {
3447 // dirty hack: for customising controls, I want only buttons/keys, not moves
3448 if (ev->type == ev_mouse || ev->type == ev_mouse2 || ev->type == ev_joystick
3449 || ev->type == ev_joystick2)
3450 return true;
3451 if (routine)
3452 {
3453 void (*otherroutine)(event_t *sev) = currentMenu->menuitems[itemOn].itemaction;
3454 otherroutine(ev); //Alam: what a hack
3455 }
3456 return true;
3457 }
3458 }
3459
3460 // BP: one of the more big hack i have never made
3461 if (routine && (currentMenu->menuitems[itemOn].status & IT_TYPE) == IT_CVAR)
3462 {
3463 if ((currentMenu->menuitems[itemOn].status & IT_CVARTYPE) == IT_CV_STRING)
3464 {
3465 if (M_ChangeStringCvar(ch))
3466 return true;
3467 else
3468 routine = NULL;
3469 }
3470 else
3471 routine = M_ChangeCvar;
3472 }
3473
3474 // Keys usable within menu
3475 switch (ch)
3476 {
3477 case KEY_DOWNARROW:
3478 M_NextOpt();
3479 S_StartSound(NULL, sfx_menu1);
3480 return true;
3481
3482 case KEY_UPARROW:
3483 M_PrevOpt();
3484 S_StartSound(NULL, sfx_menu1);
3485 return true;
3486
3487 case KEY_LEFTARROW:
3488 if (routine && ((currentMenu->menuitems[itemOn].status & IT_TYPE) == IT_ARROWS
3489 || (currentMenu->menuitems[itemOn].status & IT_TYPE) == IT_CVAR))
3490 {
3491 S_StartSound(NULL, sfx_menu1);
3492 routine(0);
3493 }
3494 return true;
3495
3496 case KEY_RIGHTARROW:
3497 if (routine && ((currentMenu->menuitems[itemOn].status & IT_TYPE) == IT_ARROWS
3498 || (currentMenu->menuitems[itemOn].status & IT_TYPE) == IT_CVAR))
3499 {
3500 S_StartSound(NULL, sfx_menu1);
3501 routine(1);
3502 }
3503 return true;
3504
3505 case KEY_ENTER:
3506 noFurtherInput = true;
3507 currentMenu->lastOn = itemOn;
3508 if (routine)
3509 {
3510 if (((currentMenu->menuitems[itemOn].status & IT_TYPE)==IT_CALL
3511 || (currentMenu->menuitems[itemOn].status & IT_TYPE)==IT_SUBMENU)
3512 && (currentMenu->menuitems[itemOn].status & IT_CALLTYPE))
3513 {
3514 #ifndef DEVELOP
3515 if (((currentMenu->menuitems[itemOn].status & IT_CALLTYPE) & IT_CALL_NOTMODIFIED) && modifiedgame && !savemoddata)
3516 {
3517 S_StartSound(NULL, sfx_skid);
3518 M_StartMessage(M_GetText("This cannot be done in a modified game.\n\n(Press a key)\n"), NULL, MM_NOTHING);
3519 return true;
3520 }
3521 #endif
3522 }
3523 S_StartSound(NULL, sfx_menu1);
3524 switch (currentMenu->menuitems[itemOn].status & IT_TYPE)
3525 {
3526 case IT_CVAR:
3527 case IT_ARROWS:
3528 routine(1); // right arrow
3529 break;
3530 case IT_CALL:
3531 routine(itemOn);
3532 break;
3533 case IT_SUBMENU:
3534 currentMenu->lastOn = itemOn;
3535 M_SetupNextMenu((menu_t *)currentMenu->menuitems[itemOn].itemaction);
3536 break;
3537 }
3538 }
3539 return true;
3540
3541 case KEY_ESCAPE:
3542 noFurtherInput = true;
3543 currentMenu->lastOn = itemOn;
3544
3545 M_GoBack(0);
3546
3547 return true;
3548
3549 case KEY_BACKSPACE:
3550 if ((currentMenu->menuitems[itemOn].status) == IT_CONTROL)
3551 {
3552 // detach any keys associated with the game control
3553 G_ClearControlKeys(setupcontrols, currentMenu->menuitems[itemOn].alphaKey);
3554 S_StartSound(NULL, sfx_shldls);
3555 return true;
3556 }
3557
3558 if (routine && ((currentMenu->menuitems[itemOn].status & IT_TYPE) == IT_ARROWS
3559 || (currentMenu->menuitems[itemOn].status & IT_TYPE) == IT_CVAR))
3560 {
3561 consvar_t *cv = (consvar_t *)currentMenu->menuitems[itemOn].itemaction;
3562
3563 if (cv == &cv_chooseskin
3564 || cv == &cv_nextmap
3565 || cv == &cv_newgametype)
3566 return true;
3567
3568 if (currentMenu != &OP_SoundOptionsDef || itemOn > 3)
3569 S_StartSound(NULL, sfx_menu1);
3570 routine(-1);
3571 return true;
3572 }
3573
3574 // Why _does_ backspace go back anyway?
3575 //currentMenu->lastOn = itemOn;
3576 //if (currentMenu->prevMenu)
3577 // M_SetupNextMenu(currentMenu->prevMenu);
3578 return false;
3579
3580 default:
3581 CON_Responder(ev);
3582 break;
3583 }
3584
3585 return true;
3586 }
3587
3588 //
3589 // M_Drawer
3590 // Called after the view has been rendered,
3591 // but before it has been blitted.
3592 //
M_Drawer(void)3593 void M_Drawer(void)
3594 {
3595 boolean wipe = WipeInAction;
3596
3597 if (currentMenu == &MessageDef)
3598 menuactive = true;
3599
3600 if (menuactive)
3601 {
3602 // now that's more readable with a faded background (yeah like Quake...)
3603 if (!wipe && (curfadevalue || (gamestate != GS_TITLESCREEN && gamestate != GS_TIMEATTACK)))
3604 V_DrawFadeScreen(0xFF00, (gamestate != GS_TITLESCREEN && gamestate != GS_TIMEATTACK) ? 16 : curfadevalue);
3605
3606 if (currentMenu->drawroutine)
3607 currentMenu->drawroutine(); // call current menu Draw routine
3608
3609 // Draw version down in corner
3610 // ... but only in the MAIN MENU. I'm a picky bastard.
3611 if (currentMenu == &MainDef)
3612 {
3613 if (customversionstring[0] != '\0')
3614 {
3615 V_DrawThinString(vid.dupx, vid.height - 17*vid.dupy, V_NOSCALESTART|V_TRANSLUCENT, "Mod version:");
3616 V_DrawThinString(vid.dupx, vid.height - 9*vid.dupy, V_NOSCALESTART|V_TRANSLUCENT|V_ALLOWLOWERCASE, customversionstring);
3617 }
3618 else
3619 {
3620 #ifdef DEVELOP // Development -- show revision / branch info
3621 V_DrawThinString(vid.dupx, vid.height - 17*vid.dupy, V_NOSCALESTART|V_TRANSLUCENT|V_ALLOWLOWERCASE, compbranch);
3622 V_DrawThinString(vid.dupx, vid.height - 9*vid.dupy, V_NOSCALESTART|V_TRANSLUCENT|V_ALLOWLOWERCASE, comprevision);
3623 #else // Regular build
3624 V_DrawThinString(vid.dupx, vid.height - 9*vid.dupy, V_NOSCALESTART|V_TRANSLUCENT|V_ALLOWLOWERCASE, va("%s", VERSIONSTRING));
3625 #endif
3626 }
3627 }
3628 }
3629
3630 // focus lost notification goes on top of everything, even the former everything
3631 if (window_notinfocus && cv_showfocuslost.value)
3632 {
3633 M_DrawTextBox((BASEVIDWIDTH/2) - (60), (BASEVIDHEIGHT/2) - (16), 13, 2);
3634 if (gamestate == GS_LEVEL && (P_AutoPause() || paused))
3635 V_DrawCenteredString(BASEVIDWIDTH/2, (BASEVIDHEIGHT/2) - (4), V_YELLOWMAP, "Game Paused");
3636 else
3637 V_DrawCenteredString(BASEVIDWIDTH/2, (BASEVIDHEIGHT/2) - (4), V_YELLOWMAP, "Focus Lost");
3638 }
3639 }
3640
3641 //
3642 // M_StartControlPanel
3643 //
M_StartControlPanel(void)3644 void M_StartControlPanel(void)
3645 {
3646 // time attack HACK
3647 if (modeattacking && demoplayback)
3648 {
3649 G_CheckDemoStatus();
3650 return;
3651 }
3652
3653 // intro might call this repeatedly
3654 if (menuactive)
3655 {
3656 CON_ToggleOff(); // move away console
3657 return;
3658 }
3659
3660 menuactive = true;
3661
3662 if (!Playing())
3663 {
3664 // Secret menu!
3665 MainMenu[singleplr].alphaKey = (M_AnySecretUnlocked()) ? 76 : 84;
3666 MainMenu[multiplr].alphaKey = (M_AnySecretUnlocked()) ? 84 : 92;
3667 MainMenu[secrets].status = (M_AnySecretUnlocked()) ? (IT_STRING | IT_CALL) : (IT_DISABLED);
3668
3669 currentMenu = &MainDef;
3670 itemOn = singleplr;
3671 }
3672 else if (modeattacking)
3673 {
3674 currentMenu = &MAPauseDef;
3675 MAPauseMenu[mapause_hints].status = (M_SecretUnlocked(SECRET_EMBLEMHINTS)) ? (IT_STRING | IT_CALL) : (IT_DISABLED);
3676 itemOn = mapause_continue;
3677 }
3678 else if (!(netgame || multiplayer)) // Single Player
3679 {
3680 if (gamestate != GS_LEVEL || ultimatemode) // intermission, so gray out stuff.
3681 {
3682 SPauseMenu[spause_pandora].status = (M_SecretUnlocked(SECRET_PANDORA)) ? (IT_GRAYEDOUT) : (IT_DISABLED);
3683 SPauseMenu[spause_retry].status = IT_GRAYEDOUT;
3684 }
3685 else
3686 {
3687 INT32 numlives = 2;
3688
3689 SPauseMenu[spause_pandora].status = (M_SecretUnlocked(SECRET_PANDORA) && !marathonmode) ? (IT_STRING | IT_CALL) : (IT_DISABLED);
3690
3691 if (&players[consoleplayer])
3692 {
3693 numlives = players[consoleplayer].lives;
3694 if (players[consoleplayer].playerstate != PST_LIVE)
3695 ++numlives;
3696 }
3697
3698 // The list of things that can disable retrying is (was?) a little too complex
3699 // for me to want to use the short if statement syntax
3700 if (numlives <= 1 || G_IsSpecialStage(gamemap))
3701 SPauseMenu[spause_retry].status = (IT_GRAYEDOUT);
3702 else
3703 SPauseMenu[spause_retry].status = (IT_STRING | IT_CALL);
3704 }
3705
3706 // We can always use level select though. :33
3707 SPauseMenu[spause_levelselect].status = (gamecomplete == 1) ? (IT_STRING | IT_CALL) : (IT_DISABLED);
3708
3709 // And emblem hints.
3710 SPauseMenu[spause_hints].status = (M_SecretUnlocked(SECRET_EMBLEMHINTS) && !marathonmode) ? (IT_STRING | IT_CALL) : (IT_DISABLED);
3711
3712 // Shift up Pandora's Box if both pandora and levelselect are active
3713 /*if (SPauseMenu[spause_pandora].status != (IT_DISABLED)
3714 && SPauseMenu[spause_levelselect].status != (IT_DISABLED))
3715 SPauseMenu[spause_pandora].alphaKey = 24;
3716 else
3717 SPauseMenu[spause_pandora].alphaKey = 32;*/
3718
3719 currentMenu = &SPauseDef;
3720 itemOn = spause_continue;
3721 }
3722 else // multiplayer
3723 {
3724 MPauseMenu[mpause_switchmap].status = IT_DISABLED;
3725 MPauseMenu[mpause_addons].status = IT_DISABLED;
3726 MPauseMenu[mpause_scramble].status = IT_DISABLED;
3727 MPauseMenu[mpause_psetupsplit].status = IT_DISABLED;
3728 MPauseMenu[mpause_psetupsplit2].status = IT_DISABLED;
3729 MPauseMenu[mpause_spectate].status = IT_DISABLED;
3730 MPauseMenu[mpause_entergame].status = IT_DISABLED;
3731 MPauseMenu[mpause_switchteam].status = IT_DISABLED;
3732 MPauseMenu[mpause_psetup].status = IT_DISABLED;
3733
3734 if ((server || IsPlayerAdmin(consoleplayer)))
3735 {
3736 MPauseMenu[mpause_switchmap].status = IT_STRING | IT_CALL;
3737 MPauseMenu[mpause_addons].status = IT_STRING | IT_CALL;
3738 if (G_GametypeHasTeams())
3739 MPauseMenu[mpause_scramble].status = IT_STRING | IT_SUBMENU;
3740 }
3741
3742 if (splitscreen)
3743 {
3744 MPauseMenu[mpause_psetupsplit].status = MPauseMenu[mpause_psetupsplit2].status = IT_STRING | IT_CALL;
3745 MPauseMenu[mpause_psetup].text = "Player 1 Setup";
3746 }
3747 else
3748 {
3749 MPauseMenu[mpause_psetup].status = IT_STRING | IT_CALL;
3750 MPauseMenu[mpause_psetup].text = "Player Setup";
3751
3752 if (G_GametypeHasTeams())
3753 MPauseMenu[mpause_switchteam].status = IT_STRING | IT_SUBMENU;
3754 else if (G_GametypeHasSpectators())
3755 MPauseMenu[((&players[consoleplayer] && players[consoleplayer].spectator) ? mpause_entergame : mpause_spectate)].status = IT_STRING | IT_CALL;
3756 else // in this odd case, we still want something to be on the menu even if it's useless
3757 MPauseMenu[mpause_spectate].status = IT_GRAYEDOUT;
3758 }
3759
3760 currentMenu = &MPauseDef;
3761 itemOn = mpause_continue;
3762 }
3763
3764 CON_ToggleOff(); // move away console
3765 }
3766
M_EndModeAttackRun(void)3767 void M_EndModeAttackRun(void)
3768 {
3769 G_ClearModeAttackRetryFlag();
3770 M_ModeAttackEndGame(0);
3771 }
3772
3773 //
3774 // M_ClearMenus
3775 //
M_ClearMenus(boolean callexitmenufunc)3776 void M_ClearMenus(boolean callexitmenufunc)
3777 {
3778 if (!menuactive)
3779 return;
3780
3781 if (currentMenu->quitroutine && callexitmenufunc && !currentMenu->quitroutine())
3782 return; // we can't quit this menu (also used to set parameter from the menu)
3783
3784 // Save the config file. I'm sick of crashing the game later and losing all my changes!
3785 COM_BufAddText(va("saveconfig \"%s\" -silent\n", configfile));
3786
3787 if (currentMenu == &MessageDef) // Oh sod off!
3788 currentMenu = &MainDef; // Not like it matters
3789 menuactive = false;
3790 hidetitlemap = false;
3791
3792 I_UpdateMouseGrab();
3793 }
3794
3795 //
3796 // M_SetupNextMenu
3797 //
M_SetupNextMenu(menu_t * menudef)3798 void M_SetupNextMenu(menu_t *menudef)
3799 {
3800 INT16 i;
3801
3802 #if defined (MASTERSERVER) && defined (HAVE_THREADS)
3803 if (currentMenu == &MP_RoomDef || currentMenu == &MP_ConnectDef)
3804 {
3805 I_lock_mutex(&ms_QueryId_mutex);
3806 {
3807 ms_QueryId++;
3808 }
3809 I_unlock_mutex(ms_QueryId_mutex);
3810 }
3811
3812 if (currentMenu == &MP_ConnectDef)
3813 {
3814 I_lock_mutex(&ms_ServerList_mutex);
3815 {
3816 if (ms_ServerList)
3817 {
3818 free(ms_ServerList);
3819 ms_ServerList = NULL;
3820 }
3821 }
3822 I_unlock_mutex(ms_ServerList_mutex);
3823 }
3824 #endif/*HAVE_THREADS*/
3825
3826 if (currentMenu->quitroutine)
3827 {
3828 // If you're going from a menu to itself, why are you running the quitroutine? You're not quitting it! -SH
3829 if (currentMenu != menudef && !currentMenu->quitroutine())
3830 return; // we can't quit this menu (also used to set parameter from the menu)
3831 }
3832
3833 M_HandleMenuPresState(menudef);
3834
3835 currentMenu = menudef;
3836 itemOn = currentMenu->lastOn;
3837
3838 // in case of...
3839 if (itemOn >= currentMenu->numitems)
3840 itemOn = currentMenu->numitems - 1;
3841
3842 // the curent item can be disabled,
3843 // this code go up until an enabled item found
3844 if (( (currentMenu->menuitems[itemOn].status & IT_TYPE) & IT_SPACE ))
3845 {
3846 for (i = 0; i < currentMenu->numitems; i++)
3847 {
3848 if (!( (currentMenu->menuitems[i].status & IT_TYPE) & IT_SPACE ))
3849 {
3850 itemOn = i;
3851 break;
3852 }
3853 }
3854 }
3855
3856 hidetitlemap = false;
3857 }
3858
3859 // Guess I'll put this here, idk
M_MouseNeeded(void)3860 boolean M_MouseNeeded(void)
3861 {
3862 return (currentMenu == &MessageDef && currentMenu->prevMenu == &OP_ChangeControlsDef);
3863 }
3864
3865 //
3866 // M_Ticker
3867 //
M_Ticker(void)3868 void M_Ticker(void)
3869 {
3870 // reset input trigger
3871 noFurtherInput = false;
3872
3873 if (dedicated)
3874 return;
3875
3876 if (--skullAnimCounter <= 0)
3877 skullAnimCounter = 8;
3878
3879 //added : 30-01-98 : test mode for five seconds
3880 if (vidm_testingmode > 0)
3881 {
3882 // restore the previous video mode
3883 if (--vidm_testingmode == 0)
3884 setmodeneeded = vidm_previousmode + 1;
3885 }
3886
3887 if (currentMenu == &OP_ScreenshotOptionsDef)
3888 M_SetupScreenshotMenu();
3889
3890 #if defined (MASTERSERVER) && defined (HAVE_THREADS)
3891 I_lock_mutex(&ms_ServerList_mutex);
3892 {
3893 if (ms_ServerList)
3894 {
3895 CL_QueryServerList(ms_ServerList);
3896 free(ms_ServerList);
3897 ms_ServerList = NULL;
3898 }
3899 }
3900 I_unlock_mutex(ms_ServerList_mutex);
3901 #endif
3902 }
3903
3904 //
3905 // M_Init
3906 //
M_Init(void)3907 void M_Init(void)
3908 {
3909 int i;
3910
3911 COM_AddCommand("manual", Command_Manual_f);
3912
3913 CV_RegisterVar(&cv_nextmap);
3914 CV_RegisterVar(&cv_newgametype);
3915 CV_RegisterVar(&cv_chooseskin);
3916 CV_RegisterVar(&cv_autorecord);
3917
3918 if (dedicated)
3919 return;
3920
3921 // Menu hacks
3922 CV_RegisterVar(&cv_dummyteam);
3923 CV_RegisterVar(&cv_dummyscramble);
3924 CV_RegisterVar(&cv_dummyrings);
3925 CV_RegisterVar(&cv_dummylives);
3926 CV_RegisterVar(&cv_dummycontinues);
3927 CV_RegisterVar(&cv_dummymares);
3928 CV_RegisterVar(&cv_dummymarathon);
3929 CV_RegisterVar(&cv_dummyloadless);
3930 CV_RegisterVar(&cv_dummycutscenes);
3931
3932 quitmsg[QUITMSG] = M_GetText("Eggman's tied explosives\nto your girlfriend, and\nwill activate them if\nyou press the 'Y' key!\nPress 'N' to save her!\n\n(Press 'Y' to quit)");
3933 quitmsg[QUITMSG1] = M_GetText("What would Tails say if\nhe saw you quitting the game?\n\n(Press 'Y' to quit)");
3934 quitmsg[QUITMSG2] = M_GetText("Hey!\nWhere do ya think you're goin'?\n\n(Press 'Y' to quit)");
3935 quitmsg[QUITMSG3] = M_GetText("Forget your studies!\nPlay some more!\n\n(Press 'Y' to quit)");
3936 quitmsg[QUITMSG4] = M_GetText("You're trying to say you\nlike Sonic 2K6 better than\nthis, right?\n\n(Press 'Y' to quit)");
3937 quitmsg[QUITMSG5] = M_GetText("Don't leave yet -- there's a\nsuper emerald around that corner!\n\n(Press 'Y' to quit)");
3938 quitmsg[QUITMSG6] = M_GetText("You'd rather work than play?\n\n(Press 'Y' to quit)");
3939 quitmsg[QUITMSG7] = M_GetText("Go ahead and leave. See if I care...\n*sniffle*\n\n(Press 'Y' to quit)");
3940
3941 quitmsg[QUIT2MSG] = M_GetText("If you leave now,\nEggman will take over the world!\n\n(Press 'Y' to quit)");
3942 quitmsg[QUIT2MSG1] = M_GetText("Don't quit!\nThere are animals\nto save!\n\n(Press 'Y' to quit)");
3943 quitmsg[QUIT2MSG2] = M_GetText("Aw c'mon, just bop\na few more robots!\n\n(Press 'Y' to quit)");
3944 quitmsg[QUIT2MSG3] = M_GetText("Did you get all those Chaos Emeralds?\n\n(Press 'Y' to quit)");
3945 quitmsg[QUIT2MSG4] = M_GetText("If you leave, I'll use\nmy spin attack on you!\n\n(Press 'Y' to quit)");
3946 quitmsg[QUIT2MSG5] = M_GetText("Don't go!\nYou might find the hidden\nlevels!\n\n(Press 'Y' to quit)");
3947 quitmsg[QUIT2MSG6] = M_GetText("Hit the 'N' key, Sonic!\nThe 'N' key!\n\n(Press 'Y' to quit)");
3948
3949 quitmsg[QUIT3MSG] = M_GetText("Are you really going to give up?\nWe certainly would never give you up.\n\n(Press 'Y' to quit)");
3950 quitmsg[QUIT3MSG1] = M_GetText("Come on, just ONE more netgame!\n\n(Press 'Y' to quit)");
3951 quitmsg[QUIT3MSG2] = M_GetText("Press 'N' to unlock\nthe Ultimate Cheat!\n\n(Press 'Y' to quit)");
3952 quitmsg[QUIT3MSG3] = M_GetText("Why don't you go back and try\njumping on that house to\nsee what happens?\n\n(Press 'Y' to quit)");
3953 quitmsg[QUIT3MSG4] = M_GetText("Every time you press 'Y', an\nSRB2 Developer cries...\n\n(Press 'Y' to quit)");
3954 quitmsg[QUIT3MSG5] = M_GetText("You'll be back to play soon, though...\n......right?\n\n(Press 'Y' to quit)");
3955 quitmsg[QUIT3MSG6] = M_GetText("Aww, is Egg Rock Zone too\ndifficult for you?\n\n(Press 'Y' to quit)");
3956
3957 /*
3958 Well the menu sucks for forcing us to have an item set
3959 at all if every item just calls the same function, and
3960 nothing more. Now just automate the definition.
3961 */
3962 for (i = 0; i <= MAX_JOYSTICKS; ++i)
3963 {
3964 OP_JoystickSetMenu[i].status = ( IT_NOTHING|IT_CALL );
3965 OP_JoystickSetMenu[i].itemaction = M_AssignJoystick;
3966 }
3967
3968 #ifndef NONET
3969 CV_RegisterVar(&cv_serversort);
3970 #endif
3971 }
3972
M_InitCharacterTables(void)3973 void M_InitCharacterTables(void)
3974 {
3975 UINT8 i;
3976
3977 // Setup description table
3978 for (i = 0; i < MAXSKINS; i++)
3979 {
3980 description[i].used = false;
3981 strcpy(description[i].notes, "???");
3982 strcpy(description[i].picname, "");
3983 strcpy(description[i].nametag, "");
3984 strcpy(description[i].skinname, "");
3985 strcpy(description[i].displayname, "");
3986 description[i].prev = description[i].next = 0;
3987 description[i].charpic = NULL;
3988 description[i].namepic = NULL;
3989 description[i].oppositecolor = description[i].tagtextcolor = description[i].tagoutlinecolor = 0;
3990 }
3991 }
3992
3993 // ==========================================================================
3994 // SPECIAL MENU OPTION DRAW ROUTINES GO HERE
3995 // ==========================================================================
3996
3997 // Converts a string into question marks.
3998 // Used for the secrets menu, to hide yet-to-be-unlocked stuff.
M_CreateSecretMenuOption(const char * str)3999 static const char *M_CreateSecretMenuOption(const char *str)
4000 {
4001 static char qbuf[32];
4002 int i;
4003
4004 for (i = 0; i < 31; ++i)
4005 {
4006 if (!str[i])
4007 {
4008 qbuf[i] = '\0';
4009 return qbuf;
4010 }
4011 else if (str[i] != ' ')
4012 qbuf[i] = '?';
4013 else
4014 qbuf[i] = ' ';
4015 }
4016
4017 qbuf[31] = '\0';
4018 return qbuf;
4019 }
4020
M_DrawThermo(INT32 x,INT32 y,consvar_t * cv)4021 static void M_DrawThermo(INT32 x, INT32 y, consvar_t *cv)
4022 {
4023 INT32 xx = x, i;
4024 lumpnum_t leftlump, rightlump, centerlump[2], cursorlump;
4025 patch_t *p;
4026
4027 leftlump = W_GetNumForName("M_THERML");
4028 rightlump = W_GetNumForName("M_THERMR");
4029 centerlump[0] = W_GetNumForName("M_THERMM");
4030 centerlump[1] = W_GetNumForName("M_THERMM");
4031 cursorlump = W_GetNumForName("M_THERMO");
4032
4033 V_DrawScaledPatch(xx, y, 0, p = W_CachePatchNum(leftlump,PU_PATCH));
4034 xx += p->width - p->leftoffset;
4035 for (i = 0; i < 16; i++)
4036 {
4037 V_DrawScaledPatch(xx, y, V_WRAPX, W_CachePatchNum(centerlump[i & 1], PU_PATCH));
4038 xx += 8;
4039 }
4040 V_DrawScaledPatch(xx, y, 0, W_CachePatchNum(rightlump, PU_PATCH));
4041
4042 xx = (cv->value - cv->PossibleValue[0].value) * (15*8) /
4043 (cv->PossibleValue[1].value - cv->PossibleValue[0].value);
4044
4045 V_DrawScaledPatch((x + 8) + xx, y, 0, W_CachePatchNum(cursorlump, PU_PATCH));
4046 }
4047
4048 // A smaller 'Thermo', with range given as percents (0-100)
M_DrawSlider(INT32 x,INT32 y,const consvar_t * cv,boolean ontop)4049 static void M_DrawSlider(INT32 x, INT32 y, const consvar_t *cv, boolean ontop)
4050 {
4051 INT32 i;
4052 INT32 range;
4053 patch_t *p;
4054
4055 x = BASEVIDWIDTH - x - SLIDER_WIDTH;
4056
4057 V_DrawScaledPatch(x, y, 0, W_CachePatchName("M_SLIDEL", PU_PATCH));
4058
4059 p = W_CachePatchName("M_SLIDEM", PU_PATCH);
4060 for (i = 1; i < SLIDER_RANGE; i++)
4061 V_DrawScaledPatch (x+i*8, y, 0,p);
4062
4063 if (ontop)
4064 {
4065 V_DrawCharacter(x - 6 - (skullAnimCounter/5), y,
4066 '\x1C' | V_YELLOWMAP, false);
4067 V_DrawCharacter(x+i*8 + 8 + (skullAnimCounter/5), y,
4068 '\x1D' | V_YELLOWMAP, false);
4069 }
4070
4071 p = W_CachePatchName("M_SLIDER", PU_PATCH);
4072 V_DrawScaledPatch(x+i*8, y, 0, p);
4073
4074 // draw the slider cursor
4075 p = W_CachePatchName("M_SLIDEC", PU_PATCH);
4076
4077 for (i = 0; cv->PossibleValue[i+1].strvalue; i++);
4078
4079 if (cv->flags & CV_FLOAT)
4080 range = (INT32)(atof(cv->defaultvalue)*FRACUNIT);
4081 else
4082 range = atoi(cv->defaultvalue);
4083
4084 if (range != cv->value)
4085 {
4086 range = ((range - cv->PossibleValue[0].value) * 100 /
4087 (cv->PossibleValue[i].value - cv->PossibleValue[0].value));
4088
4089 if (range < 0)
4090 range = 0;
4091 else if (range > 100)
4092 range = 100;
4093
4094 V_DrawMappedPatch(x + 2 + (SLIDER_RANGE*8*range)/100, y, V_TRANSLUCENT, p, yellowmap);
4095 }
4096
4097 range = ((cv->value - cv->PossibleValue[0].value) * 100 /
4098 (cv->PossibleValue[i].value - cv->PossibleValue[0].value));
4099
4100 if (range < 0)
4101 range = 0;
4102 else if (range > 100)
4103 range = 100;
4104
4105 V_DrawMappedPatch(x + 2 + (SLIDER_RANGE*8*range)/100, y, 0, p, yellowmap);
4106 }
4107
4108 //
4109 // Draw a textbox, like Quake does, because sometimes it's difficult
4110 // to read the text with all the stuff in the background...
4111 //
M_DrawTextBox(INT32 x,INT32 y,INT32 width,INT32 boxlines)4112 void M_DrawTextBox(INT32 x, INT32 y, INT32 width, INT32 boxlines)
4113 {
4114 // Solid color textbox.
4115 V_DrawFill(x+5, y+5, width*8+6, boxlines*8+6, 159);
4116 //V_DrawFill(x+8, y+8, width*8, boxlines*8, 31);
4117 /*
4118 patch_t *p;
4119 INT32 cx, cy, n;
4120 INT32 step, boff;
4121
4122 step = 8;
4123 boff = 8;
4124
4125 // draw left side
4126 cx = x;
4127 cy = y;
4128 V_DrawScaledPatch(cx, cy, 0, W_CachePatchNum(viewborderlump[BRDR_TL], PU_PATCH));
4129 cy += boff;
4130 p = W_CachePatchNum(viewborderlump[BRDR_L], PU_PATCH);
4131 for (n = 0; n < boxlines; n++)
4132 {
4133 V_DrawScaledPatch(cx, cy, V_WRAPY, p);
4134 cy += step;
4135 }
4136 V_DrawScaledPatch(cx, cy, 0, W_CachePatchNum(viewborderlump[BRDR_BL], PU_PATCH));
4137
4138 // draw middle
4139 V_DrawFlatFill(x + boff, y + boff, width*step, boxlines*step, st_borderpatchnum);
4140
4141 cx += boff;
4142 cy = y;
4143 while (width > 0)
4144 {
4145 V_DrawScaledPatch(cx, cy, V_WRAPX, W_CachePatchNum(viewborderlump[BRDR_T], PU_PATCH));
4146 V_DrawScaledPatch(cx, y + boff + boxlines*step, V_WRAPX, W_CachePatchNum(viewborderlump[BRDR_B], PU_PATCH));
4147 width--;
4148 cx += step;
4149 }
4150
4151 // draw right side
4152 cy = y;
4153 V_DrawScaledPatch(cx, cy, 0, W_CachePatchNum(viewborderlump[BRDR_TR], PU_PATCH));
4154 cy += boff;
4155 p = W_CachePatchNum(viewborderlump[BRDR_R], PU_PATCH);
4156 for (n = 0; n < boxlines; n++)
4157 {
4158 V_DrawScaledPatch(cx, cy, V_WRAPY, p);
4159 cy += step;
4160 }
4161 V_DrawScaledPatch(cx, cy, 0, W_CachePatchNum(viewborderlump[BRDR_BR], PU_PATCH));
4162 */
4163 }
4164
4165 static fixed_t staticalong = 0;
4166
M_DrawStaticBox(fixed_t x,fixed_t y,INT32 flags,fixed_t w,fixed_t h)4167 static void M_DrawStaticBox(fixed_t x, fixed_t y, INT32 flags, fixed_t w, fixed_t h)
4168 {
4169 patch_t *patch;
4170 fixed_t sw, pw;
4171
4172 patch = W_CachePatchName("LSSTATIC", PU_PATCH);
4173 pw = patch->width - (sw = w*2); //FixedDiv(w, scale); -- for scale FRACUNIT/2
4174
4175 /*if (pw > 0) -- model code for modders providing weird LSSTATIC
4176 {
4177 if (staticalong > pw)
4178 staticalong -= pw;
4179 }
4180 else
4181 staticalong = 0;*/
4182
4183 if (staticalong > pw) // simplified for base LSSTATIC
4184 staticalong -= pw;
4185
4186 V_DrawCroppedPatch(x<<FRACBITS, y<<FRACBITS, FRACUNIT/2, flags, patch, staticalong, 0, sw, h*2); // FixedDiv(h, scale)); -- for scale FRACUNIT/2
4187
4188 staticalong += sw; //M_RandomRange(sw/2, 2*sw); -- turns out less randomisation looks better because immediately adjacent frames can't end up close to each other
4189
4190 W_UnlockCachedPatch(patch);
4191 }
4192
4193 //
4194 // Draw border for the savegame description
4195 //
4196 #if 0 // once used for joysticks and savegames, now no longer
M_DrawSaveLoadBorder(INT32 x,INT32 y)4197 static void M_DrawSaveLoadBorder(INT32 x,INT32 y)
4198 {
4199 INT32 i;
4200
4201 V_DrawScaledPatch (x-8,y+7,0,W_CachePatchName("M_LSLEFT",PU_PATCH));
4202
4203 for (i = 0;i < 24;i++)
4204 {
4205 V_DrawScaledPatch (x,y+7,0,W_CachePatchName("M_LSCNTR",PU_PATCH));
4206 x += 8;
4207 }
4208
4209 V_DrawScaledPatch (x,y+7,0,W_CachePatchName("M_LSRGHT",PU_PATCH));
4210 }
4211 #endif
4212
4213 // horizontally centered text
M_CentreText(INT32 y,const char * string)4214 static void M_CentreText(INT32 y, const char *string)
4215 {
4216 INT32 x;
4217 //added : 02-02-98 : centre on 320, because V_DrawString centers on vid.width...
4218 x = (BASEVIDWIDTH - V_StringWidth(string, V_OLDSPACING))>>1;
4219 V_DrawString(x,y,V_OLDSPACING,string);
4220 }
4221
4222 //
4223 // M_DrawMapEmblems
4224 //
4225 // used by pause & statistics to draw a row of emblems for a map
4226 //
M_DrawMapEmblems(INT32 mapnum,INT32 x,INT32 y)4227 static void M_DrawMapEmblems(INT32 mapnum, INT32 x, INT32 y)
4228 {
4229 UINT8 lasttype = UINT8_MAX, curtype;
4230 emblem_t *emblem = M_GetLevelEmblems(mapnum);
4231
4232 while (emblem)
4233 {
4234 switch (emblem->type)
4235 {
4236 case ET_SCORE: case ET_TIME: case ET_RINGS:
4237 curtype = 1; break;
4238 case ET_NGRADE: case ET_NTIME:
4239 curtype = 2; break;
4240 case ET_MAP:
4241 curtype = 3; break;
4242 default:
4243 curtype = 0; break;
4244 }
4245
4246 // Shift over if emblem is of a different discipline
4247 if (lasttype != UINT8_MAX && lasttype != curtype)
4248 x -= 4;
4249 lasttype = curtype;
4250
4251 if (emblem->collected)
4252 V_DrawSmallMappedPatch(x, y, 0, W_CachePatchName(M_GetEmblemPatch(emblem, false), PU_PATCH),
4253 R_GetTranslationColormap(TC_DEFAULT, M_GetEmblemColor(emblem), GTC_CACHE));
4254 else
4255 V_DrawSmallScaledPatch(x, y, 0, W_CachePatchName("NEEDIT", PU_PATCH));
4256
4257 emblem = M_GetLevelEmblems(-1);
4258 x -= 12;
4259 }
4260 }
4261
M_DrawMenuTitle(void)4262 static void M_DrawMenuTitle(void)
4263 {
4264 if (currentMenu->menutitlepic)
4265 {
4266 patch_t *p = W_CachePatchName(currentMenu->menutitlepic, PU_PATCH);
4267
4268 if (p->height > 24) // title is larger than normal
4269 {
4270 INT32 xtitle = (BASEVIDWIDTH - (p->width/2))/2;
4271 INT32 ytitle = (30 - (p->height/2))/2;
4272
4273 if (xtitle < 0)
4274 xtitle = 0;
4275 if (ytitle < 0)
4276 ytitle = 0;
4277
4278 V_DrawSmallScaledPatch(xtitle, ytitle, 0, p);
4279 }
4280 else
4281 {
4282 INT32 xtitle = (BASEVIDWIDTH - p->width)/2;
4283 INT32 ytitle = (30 - p->height)/2;
4284
4285 if (xtitle < 0)
4286 xtitle = 0;
4287 if (ytitle < 0)
4288 ytitle = 0;
4289
4290 V_DrawScaledPatch(xtitle, ytitle, 0, p);
4291 }
4292 }
4293 }
4294
M_DrawGenericMenu(void)4295 static void M_DrawGenericMenu(void)
4296 {
4297 INT32 x, y, i, cursory = 0;
4298
4299 // DRAW MENU
4300 x = currentMenu->x;
4301 y = currentMenu->y;
4302
4303 // draw title (or big pic)
4304 M_DrawMenuTitle();
4305
4306 for (i = 0; i < currentMenu->numitems; i++)
4307 {
4308 if (i == itemOn)
4309 cursory = y;
4310 switch (currentMenu->menuitems[i].status & IT_DISPLAY)
4311 {
4312 case IT_PATCH:
4313 if (currentMenu->menuitems[i].patch && currentMenu->menuitems[i].patch[0])
4314 {
4315 if (currentMenu->menuitems[i].status & IT_CENTER)
4316 {
4317 patch_t *p;
4318 p = W_CachePatchName(currentMenu->menuitems[i].patch, PU_PATCH);
4319 V_DrawScaledPatch((BASEVIDWIDTH - p->width)/2, y, 0, p);
4320 }
4321 else
4322 {
4323 V_DrawScaledPatch(x, y, 0,
4324 W_CachePatchName(currentMenu->menuitems[i].patch, PU_PATCH));
4325 }
4326 }
4327 /* FALLTHRU */
4328 case IT_NOTHING:
4329 case IT_DYBIGSPACE:
4330 y += LINEHEIGHT;
4331 break;
4332 case IT_BIGSLIDER:
4333 M_DrawThermo(x, y, (consvar_t *)currentMenu->menuitems[i].itemaction);
4334 y += LINEHEIGHT;
4335 break;
4336 case IT_STRING:
4337 case IT_WHITESTRING:
4338 if (currentMenu->menuitems[i].alphaKey)
4339 y = currentMenu->y+currentMenu->menuitems[i].alphaKey;
4340 if (i == itemOn)
4341 cursory = y;
4342
4343 if ((currentMenu->menuitems[i].status & IT_DISPLAY)==IT_STRING)
4344 V_DrawString(x, y, 0, currentMenu->menuitems[i].text);
4345 else
4346 V_DrawString(x, y, V_YELLOWMAP, currentMenu->menuitems[i].text);
4347
4348 // Cvar specific handling
4349 switch (currentMenu->menuitems[i].status & IT_TYPE)
4350 case IT_CVAR:
4351 {
4352 consvar_t *cv = (consvar_t *)currentMenu->menuitems[i].itemaction;
4353 switch (currentMenu->menuitems[i].status & IT_CVARTYPE)
4354 {
4355 case IT_CV_SLIDER:
4356 M_DrawSlider(x, y, cv, (i == itemOn));
4357 case IT_CV_NOPRINT: // color use this
4358 case IT_CV_INVISSLIDER: // monitor toggles use this
4359 break;
4360 case IT_CV_STRING:
4361 M_DrawTextBox(x, y + 4, MAXSTRINGLENGTH, 1);
4362 V_DrawString(x + 8, y + 12, V_ALLOWLOWERCASE, cv->string);
4363 if (skullAnimCounter < 4 && i == itemOn)
4364 V_DrawCharacter(x + 8 + V_StringWidth(cv->string, 0), y + 12,
4365 '_' | 0x80, false);
4366 y += 16;
4367 break;
4368 default:
4369 V_DrawRightAlignedString(BASEVIDWIDTH - x, y,
4370 ((cv->flags & CV_CHEAT) && !CV_IsSetToDefault(cv) ? V_REDMAP : V_YELLOWMAP), cv->string);
4371 if (i == itemOn)
4372 {
4373 V_DrawCharacter(BASEVIDWIDTH - x - 10 - V_StringWidth(cv->string, 0) - (skullAnimCounter/5), y,
4374 '\x1C' | V_YELLOWMAP, false);
4375 V_DrawCharacter(BASEVIDWIDTH - x + 2 + (skullAnimCounter/5), y,
4376 '\x1D' | V_YELLOWMAP, false);
4377 }
4378 break;
4379 }
4380 break;
4381 }
4382 y += STRINGHEIGHT;
4383 break;
4384 case IT_STRING2:
4385 V_DrawString(x, y, 0, currentMenu->menuitems[i].text);
4386 /* FALLTHRU */
4387 case IT_DYLITLSPACE:
4388 y += SMALLLINEHEIGHT;
4389 break;
4390 case IT_GRAYPATCH:
4391 if (currentMenu->menuitems[i].patch && currentMenu->menuitems[i].patch[0])
4392 V_DrawMappedPatch(x, y, 0,
4393 W_CachePatchName(currentMenu->menuitems[i].patch,PU_PATCH), graymap);
4394 y += LINEHEIGHT;
4395 break;
4396 case IT_TRANSTEXT:
4397 if (currentMenu->menuitems[i].alphaKey)
4398 y = currentMenu->y+currentMenu->menuitems[i].alphaKey;
4399 /* FALLTHRU */
4400 case IT_TRANSTEXT2:
4401 V_DrawString(x, y, V_TRANSLUCENT, currentMenu->menuitems[i].text);
4402 y += SMALLLINEHEIGHT;
4403 break;
4404 case IT_QUESTIONMARKS:
4405 if (currentMenu->menuitems[i].alphaKey)
4406 y = currentMenu->y+currentMenu->menuitems[i].alphaKey;
4407
4408 V_DrawString(x, y, V_TRANSLUCENT|V_OLDSPACING, M_CreateSecretMenuOption(currentMenu->menuitems[i].text));
4409 y += SMALLLINEHEIGHT;
4410 break;
4411 case IT_HEADERTEXT: // draws 16 pixels to the left, in yellow text
4412 if (currentMenu->menuitems[i].alphaKey)
4413 y = currentMenu->y+currentMenu->menuitems[i].alphaKey;
4414
4415 //V_DrawString(x-16, y, V_YELLOWMAP, currentMenu->menuitems[i].text);
4416 M_DrawLevelPlatterHeader(y - (lsheadingheight - 12), currentMenu->menuitems[i].text, true, false);
4417 y += SMALLLINEHEIGHT;
4418 break;
4419 }
4420 }
4421
4422 // DRAW THE SKULL CURSOR
4423 if (((currentMenu->menuitems[itemOn].status & IT_DISPLAY) == IT_PATCH)
4424 || ((currentMenu->menuitems[itemOn].status & IT_DISPLAY) == IT_NOTHING))
4425 {
4426 V_DrawScaledPatch(currentMenu->x + SKULLXOFF, cursory - 5, 0,
4427 W_CachePatchName("M_CURSOR", PU_PATCH));
4428 }
4429 else
4430 {
4431 V_DrawScaledPatch(currentMenu->x - 24, cursory, 0,
4432 W_CachePatchName("M_CURSOR", PU_PATCH));
4433 V_DrawString(currentMenu->x, cursory, V_YELLOWMAP, currentMenu->menuitems[itemOn].text);
4434 }
4435 }
4436
4437 const char *PlaystyleNames[4] = {"Strafe", "Standard", "Simple", "Old Analog??"};
4438 const char *PlaystyleDesc[4] = {
4439 // Legacy
4440 "The play style used for\n"
4441 "old-school SRB2.\n"
4442 "\n"
4443 "This play style is identical\n"
4444 "to Standard, except that the\n"
4445 "player always looks in the\n"
4446 "direction of the camera."
4447 ,
4448
4449 // Standard
4450 "The default play style,\n"
4451 "designed for full control\n"
4452 "with a keyboard and mouse.\n"
4453 "\n"
4454 "The camera rotates only when\n"
4455 "you tell it to. The player\n"
4456 "looks in the direction they're\n"
4457 "moving, but acts in the direction\n"
4458 "the camera is facing.\n"
4459 "\n"
4460 "Mastery of this play style will\n"
4461 "open up the highest level of play!"
4462 ,
4463
4464 // Simple
4465 "A play style designed for\n"
4466 "gamepads and hassle-free play.\n"
4467 "\n"
4468 "The camera rotates automatically\n"
4469 "as you move, and the player faces\n"
4470 "and acts in the direction\n"
4471 "they're moving.\n"
4472 "\n"
4473 "Hold \x82" "Center View\x80 to lock the\n"
4474 "camera behind the player!\n"
4475 ,
4476
4477 // Old Analog
4478 "I see.\n"
4479 "\n"
4480 "You really liked the old analog mode,\n"
4481 "so when 2.2 came out, you opened up\n"
4482 "your config file and brought it back.\n"
4483 "\n"
4484 "That's absolutely valid, but I implore\n"
4485 "you to try the new Simple play style\n"
4486 "instead!"
4487 };
4488
4489 static UINT8 playstyle_activeplayer = 0, playstyle_currentchoice = 0;
4490
M_DrawControlsDefMenu(void)4491 static void M_DrawControlsDefMenu(void)
4492 {
4493 UINT8 opt = 0;
4494
4495 M_DrawGenericMenu();
4496
4497 if (currentMenu == &OP_P1ControlsDef)
4498 {
4499 opt = cv_directionchar[0].value ? 1 : 0;
4500 opt = playstyle_currentchoice = cv_useranalog[0].value ? 3 - opt : opt;
4501
4502 if (opt == 2)
4503 {
4504 OP_CameraOptionsDef.menuitems = OP_CameraExtendedOptionsMenu;
4505 OP_CameraOptionsDef.numitems = sizeof (OP_CameraExtendedOptionsMenu) / sizeof (menuitem_t);
4506 }
4507 else
4508 {
4509 OP_CameraOptionsDef.menuitems = OP_CameraOptionsMenu;
4510 OP_CameraOptionsDef.numitems = sizeof (OP_CameraOptionsMenu) / sizeof (menuitem_t);
4511 }
4512 }
4513 else
4514 {
4515 opt = cv_directionchar[1].value ? 1 : 0;
4516 opt = playstyle_currentchoice = cv_useranalog[1].value ? 3 - opt : opt;
4517
4518 if (opt == 2)
4519 {
4520 OP_Camera2OptionsDef.menuitems = OP_Camera2ExtendedOptionsMenu;
4521 OP_Camera2OptionsDef.numitems = sizeof (OP_Camera2ExtendedOptionsMenu) / sizeof (menuitem_t);
4522 }
4523 else
4524 {
4525 OP_Camera2OptionsDef.menuitems = OP_Camera2OptionsMenu;
4526 OP_Camera2OptionsDef.numitems = sizeof (OP_Camera2OptionsMenu) / sizeof (menuitem_t);
4527 }
4528 }
4529
4530 V_DrawRightAlignedString(BASEVIDWIDTH - currentMenu->x, currentMenu->y + 80, V_YELLOWMAP, PlaystyleNames[opt]);
4531 }
4532
4533 #define scrollareaheight 72
4534
4535 // note that alphakey is multiplied by 2 for scrolling menus to allow greater usage in UINT8 range.
M_DrawGenericScrollMenu(void)4536 static void M_DrawGenericScrollMenu(void)
4537 {
4538 INT32 x, y, i, max, bottom, tempcentery, cursory = 0;
4539
4540 // DRAW MENU
4541 x = currentMenu->x;
4542 y = currentMenu->y;
4543
4544 if (currentMenu->menuitems[currentMenu->numitems-1].alphaKey < scrollareaheight)
4545 tempcentery = currentMenu->y; // Not tall enough to scroll, but this thinker is used in case it becomes so
4546 else if ((currentMenu->menuitems[itemOn].alphaKey*2 - currentMenu->menuitems[0].alphaKey*2) <= scrollareaheight)
4547 tempcentery = currentMenu->y - currentMenu->menuitems[0].alphaKey*2;
4548 else if ((currentMenu->menuitems[currentMenu->numitems-1].alphaKey*2 - currentMenu->menuitems[itemOn].alphaKey*2) <= scrollareaheight)
4549 tempcentery = currentMenu->y - currentMenu->menuitems[currentMenu->numitems-1].alphaKey*2 + 2*scrollareaheight;
4550 else
4551 tempcentery = currentMenu->y - currentMenu->menuitems[itemOn].alphaKey*2 + scrollareaheight;
4552
4553 for (i = 0; i < currentMenu->numitems; i++)
4554 {
4555 if (currentMenu->menuitems[i].status != IT_DISABLED && currentMenu->menuitems[i].alphaKey*2 + tempcentery >= currentMenu->y)
4556 break;
4557 }
4558
4559 for (bottom = currentMenu->numitems; bottom > 0; bottom--)
4560 {
4561 if (currentMenu->menuitems[bottom-1].status != IT_DISABLED)
4562 break;
4563 }
4564
4565 for (max = bottom; max > 0; max--)
4566 {
4567 if (currentMenu->menuitems[max-1].status != IT_DISABLED && currentMenu->menuitems[max-1].alphaKey*2 + tempcentery <= (currentMenu->y + 2*scrollareaheight))
4568 break;
4569 }
4570
4571 if (i)
4572 V_DrawString(currentMenu->x - 20, currentMenu->y - (skullAnimCounter/5), V_YELLOWMAP, "\x1A"); // up arrow
4573 if (max != bottom)
4574 V_DrawString(currentMenu->x - 20, currentMenu->y + 2*scrollareaheight + (skullAnimCounter/5), V_YELLOWMAP, "\x1B"); // down arrow
4575
4576 // draw title (or big pic)
4577 M_DrawMenuTitle();
4578
4579 for (; i < max; i++)
4580 {
4581 y = currentMenu->menuitems[i].alphaKey*2 + tempcentery;
4582 if (i == itemOn)
4583 cursory = y;
4584 switch (currentMenu->menuitems[i].status & IT_DISPLAY)
4585 {
4586 case IT_PATCH:
4587 case IT_DYBIGSPACE:
4588 case IT_BIGSLIDER:
4589 case IT_STRING2:
4590 case IT_DYLITLSPACE:
4591 case IT_GRAYPATCH:
4592 case IT_TRANSTEXT2:
4593 // unsupported
4594 break;
4595 case IT_NOTHING:
4596 break;
4597 case IT_STRING:
4598 case IT_WHITESTRING:
4599 if (i != itemOn && (currentMenu->menuitems[i].status & IT_DISPLAY)==IT_STRING)
4600 V_DrawString(x, y, 0, currentMenu->menuitems[i].text);
4601 else
4602 V_DrawString(x, y, V_YELLOWMAP, currentMenu->menuitems[i].text);
4603
4604 // Cvar specific handling
4605 switch (currentMenu->menuitems[i].status & IT_TYPE)
4606 case IT_CVAR:
4607 {
4608 consvar_t *cv = (consvar_t *)currentMenu->menuitems[i].itemaction;
4609 switch (currentMenu->menuitems[i].status & IT_CVARTYPE)
4610 {
4611 case IT_CV_SLIDER:
4612 M_DrawSlider(x, y, cv, (i == itemOn));
4613 case IT_CV_NOPRINT: // color use this
4614 case IT_CV_INVISSLIDER: // monitor toggles use this
4615 break;
4616 case IT_CV_STRING:
4617 #if 1
4618 if (y + 12 > (currentMenu->y + 2*scrollareaheight))
4619 break;
4620 M_DrawTextBox(x, y + 4, MAXSTRINGLENGTH, 1);
4621 V_DrawString(x + 8, y + 12, V_ALLOWLOWERCASE, cv->string);
4622 if (skullAnimCounter < 4 && i == itemOn)
4623 V_DrawCharacter(x + 8 + V_StringWidth(cv->string, 0), y + 12,
4624 '_' | 0x80, false);
4625 #else // cool new string type stuff, not ready for limelight
4626 if (i == itemOn)
4627 {
4628 V_DrawFill(x-2, y-1, MAXSTRINGLENGTH*8 + 4, 8+3, 159);
4629 V_DrawString(x, y, V_ALLOWLOWERCASE, cv->string);
4630 if (skullAnimCounter < 4)
4631 V_DrawCharacter(x + V_StringWidth(cv->string, 0), y, '_' | 0x80, false);
4632 }
4633 else
4634 V_DrawRightAlignedString(BASEVIDWIDTH - x, y,
4635 V_YELLOWMAP|V_ALLOWLOWERCASE, cv->string);
4636 #endif
4637 break;
4638 default:
4639 V_DrawRightAlignedString(BASEVIDWIDTH - x, y,
4640 ((cv->flags & CV_CHEAT) && !CV_IsSetToDefault(cv) ? V_REDMAP : V_YELLOWMAP), cv->string);
4641 if (i == itemOn)
4642 {
4643 V_DrawCharacter(BASEVIDWIDTH - x - 10 - V_StringWidth(cv->string, 0) - (skullAnimCounter/5), y,
4644 '\x1C' | V_YELLOWMAP, false);
4645 V_DrawCharacter(BASEVIDWIDTH - x + 2 + (skullAnimCounter/5), y,
4646 '\x1D' | V_YELLOWMAP, false);
4647 }
4648 break;
4649 }
4650 break;
4651 }
4652 break;
4653 case IT_TRANSTEXT:
4654 switch (currentMenu->menuitems[i].status & IT_TYPE)
4655 {
4656 case IT_PAIR:
4657 V_DrawString(x, y,
4658 V_TRANSLUCENT, currentMenu->menuitems[i].patch);
4659 V_DrawRightAlignedString(BASEVIDWIDTH - x, y,
4660 V_TRANSLUCENT, currentMenu->menuitems[i].text);
4661 break;
4662 default:
4663 V_DrawString(x, y,
4664 V_TRANSLUCENT, currentMenu->menuitems[i].text);
4665 }
4666 break;
4667 case IT_QUESTIONMARKS:
4668 V_DrawString(x, y, V_TRANSLUCENT|V_OLDSPACING, M_CreateSecretMenuOption(currentMenu->menuitems[i].text));
4669 break;
4670 case IT_HEADERTEXT:
4671 //V_DrawString(x-16, y, V_YELLOWMAP, currentMenu->menuitems[i].text);
4672 M_DrawLevelPlatterHeader(y - (lsheadingheight - 12), currentMenu->menuitems[i].text, true, false);
4673 break;
4674 }
4675 }
4676
4677 // DRAW THE SKULL CURSOR
4678 V_DrawScaledPatch(currentMenu->x - 24, cursory, 0,
4679 W_CachePatchName("M_CURSOR", PU_PATCH));
4680 }
4681
M_DrawPauseMenu(void)4682 static void M_DrawPauseMenu(void)
4683 {
4684 if (!netgame && !multiplayer && (gamestate == GS_LEVEL || gamestate == GS_INTERMISSION))
4685 {
4686 emblem_t *emblem_detail[3] = {NULL, NULL, NULL};
4687 char emblem_text[3][20];
4688 INT32 i;
4689
4690 M_DrawTextBox(27, 16, 32, 6);
4691
4692 // Draw any and all emblems at the top.
4693 M_DrawMapEmblems(gamemap, 272, 28);
4694
4695 if (mapheaderinfo[gamemap-1]->actnum != 0)
4696 V_DrawString(40, 28, V_YELLOWMAP, va("%s %d", mapheaderinfo[gamemap-1]->lvlttl, mapheaderinfo[gamemap-1]->actnum));
4697 else
4698 V_DrawString(40, 28, V_YELLOWMAP, mapheaderinfo[gamemap-1]->lvlttl);
4699
4700 // Set up the detail boxes.
4701 {
4702 emblem_t *emblem = M_GetLevelEmblems(gamemap);
4703 while (emblem)
4704 {
4705 INT32 emblemslot;
4706 char targettext[9], currenttext[9];
4707
4708 switch (emblem->type)
4709 {
4710 case ET_SCORE:
4711 snprintf(targettext, 9, "%d", emblem->var);
4712 snprintf(currenttext, 9, "%u", G_GetBestScore(gamemap));
4713
4714 targettext[8] = 0;
4715 currenttext[8] = 0;
4716
4717 emblemslot = 0;
4718 break;
4719 case ET_TIME:
4720 emblemslot = emblem->var; // dumb hack
4721 snprintf(targettext, 9, "%i:%02i.%02i",
4722 G_TicsToMinutes((tic_t)emblemslot, false),
4723 G_TicsToSeconds((tic_t)emblemslot),
4724 G_TicsToCentiseconds((tic_t)emblemslot));
4725
4726 emblemslot = (INT32)G_GetBestTime(gamemap); // dumb hack pt ii
4727 if ((tic_t)emblemslot == UINT32_MAX)
4728 snprintf(currenttext, 9, "-:--.--");
4729 else
4730 snprintf(currenttext, 9, "%i:%02i.%02i",
4731 G_TicsToMinutes((tic_t)emblemslot, false),
4732 G_TicsToSeconds((tic_t)emblemslot),
4733 G_TicsToCentiseconds((tic_t)emblemslot));
4734
4735 targettext[8] = 0;
4736 currenttext[8] = 0;
4737
4738 emblemslot = 1;
4739 break;
4740 case ET_RINGS:
4741 snprintf(targettext, 9, "%d", emblem->var);
4742 snprintf(currenttext, 9, "%u", G_GetBestRings(gamemap));
4743
4744 targettext[8] = 0;
4745 currenttext[8] = 0;
4746
4747 emblemslot = 2;
4748 break;
4749 case ET_NGRADE:
4750 snprintf(targettext, 9, "%u", P_GetScoreForGrade(gamemap, 0, emblem->var));
4751 snprintf(currenttext, 9, "%u", G_GetBestNightsScore(gamemap, 0));
4752
4753 targettext[8] = 0;
4754 currenttext[8] = 0;
4755
4756 emblemslot = 1;
4757 break;
4758 case ET_NTIME:
4759 emblemslot = emblem->var; // dumb hack pt iii
4760 snprintf(targettext, 9, "%i:%02i.%02i",
4761 G_TicsToMinutes((tic_t)emblemslot, false),
4762 G_TicsToSeconds((tic_t)emblemslot),
4763 G_TicsToCentiseconds((tic_t)emblemslot));
4764
4765 emblemslot = (INT32)G_GetBestNightsTime(gamemap, 0); // dumb hack pt iv
4766 if ((tic_t)emblemslot == UINT32_MAX)
4767 snprintf(currenttext, 9, "-:--.--");
4768 else
4769 snprintf(currenttext, 9, "%i:%02i.%02i",
4770 G_TicsToMinutes((tic_t)emblemslot, false),
4771 G_TicsToSeconds((tic_t)emblemslot),
4772 G_TicsToCentiseconds((tic_t)emblemslot));
4773
4774 targettext[8] = 0;
4775 currenttext[8] = 0;
4776
4777 emblemslot = 2;
4778 break;
4779 default:
4780 goto bademblem;
4781 }
4782 if (emblem_detail[emblemslot])
4783 goto bademblem;
4784
4785 emblem_detail[emblemslot] = emblem;
4786 snprintf(emblem_text[emblemslot], 20, "%8s /%8s", currenttext, targettext);
4787 emblem_text[emblemslot][19] = 0;
4788
4789 bademblem:
4790 emblem = M_GetLevelEmblems(-1);
4791 }
4792 }
4793 for (i = 0; i < 3; ++i)
4794 {
4795 emblem_t *emblem = emblem_detail[i];
4796 if (!emblem)
4797 continue;
4798
4799 if (emblem->collected)
4800 V_DrawSmallMappedPatch(40, 44 + (i*8), 0, W_CachePatchName(M_GetEmblemPatch(emblem, false), PU_PATCH),
4801 R_GetTranslationColormap(TC_DEFAULT, M_GetEmblemColor(emblem), GTC_CACHE));
4802 else
4803 V_DrawSmallScaledPatch(40, 44 + (i*8), 0, W_CachePatchName("NEEDIT", PU_PATCH));
4804
4805 switch (emblem->type)
4806 {
4807 case ET_SCORE:
4808 case ET_NGRADE:
4809 V_DrawString(56, 44 + (i*8), V_YELLOWMAP, "SCORE:");
4810 break;
4811 case ET_TIME:
4812 case ET_NTIME:
4813 V_DrawString(56, 44 + (i*8), V_YELLOWMAP, "TIME:");
4814 break;
4815 case ET_RINGS:
4816 V_DrawString(56, 44 + (i*8), V_YELLOWMAP, "RINGS:");
4817 break;
4818 }
4819 V_DrawRightAlignedString(284, 44 + (i*8), V_MONOSPACE, emblem_text[i]);
4820 }
4821 }
4822
4823 M_DrawGenericMenu();
4824 }
4825
M_DrawCenteredMenu(void)4826 static void M_DrawCenteredMenu(void)
4827 {
4828 INT32 x, y, i, cursory = 0;
4829
4830 // DRAW MENU
4831 x = currentMenu->x;
4832 y = currentMenu->y;
4833
4834 // draw title (or big pic)
4835 M_DrawMenuTitle();
4836
4837 for (i = 0; i < currentMenu->numitems; i++)
4838 {
4839 if (i == itemOn)
4840 cursory = y;
4841 switch (currentMenu->menuitems[i].status & IT_DISPLAY)
4842 {
4843 case IT_PATCH:
4844 if (currentMenu->menuitems[i].patch && currentMenu->menuitems[i].patch[0])
4845 {
4846 if (currentMenu->menuitems[i].status & IT_CENTER)
4847 {
4848 patch_t *p;
4849 p = W_CachePatchName(currentMenu->menuitems[i].patch, PU_PATCH);
4850 V_DrawScaledPatch((BASEVIDWIDTH - p->width)/2, y, 0, p);
4851 }
4852 else
4853 {
4854 V_DrawScaledPatch(x, y, 0,
4855 W_CachePatchName(currentMenu->menuitems[i].patch, PU_PATCH));
4856 }
4857 }
4858 /* FALLTHRU */
4859 case IT_NOTHING:
4860 case IT_DYBIGSPACE:
4861 y += LINEHEIGHT;
4862 break;
4863 case IT_BIGSLIDER:
4864 M_DrawThermo(x, y, (consvar_t *)currentMenu->menuitems[i].itemaction);
4865 y += LINEHEIGHT;
4866 break;
4867 case IT_STRING:
4868 case IT_WHITESTRING:
4869 if (currentMenu->menuitems[i].alphaKey)
4870 y = currentMenu->y+currentMenu->menuitems[i].alphaKey;
4871 if (i == itemOn)
4872 cursory = y;
4873
4874 if ((currentMenu->menuitems[i].status & IT_DISPLAY)==IT_STRING)
4875 V_DrawCenteredString(x, y, 0, currentMenu->menuitems[i].text);
4876 else
4877 V_DrawCenteredString(x, y, V_YELLOWMAP, currentMenu->menuitems[i].text);
4878
4879 // Cvar specific handling
4880 switch(currentMenu->menuitems[i].status & IT_TYPE)
4881 case IT_CVAR:
4882 {
4883 consvar_t *cv = (consvar_t *)currentMenu->menuitems[i].itemaction;
4884 switch(currentMenu->menuitems[i].status & IT_CVARTYPE)
4885 {
4886 case IT_CV_SLIDER:
4887 M_DrawSlider(x, y, cv, (i == itemOn));
4888 case IT_CV_NOPRINT: // color use this
4889 break;
4890 case IT_CV_STRING:
4891 M_DrawTextBox(x, y + 4, MAXSTRINGLENGTH, 1);
4892 V_DrawString(x + 8, y + 12, V_ALLOWLOWERCASE, cv->string);
4893 if (skullAnimCounter < 4 && i == itemOn)
4894 V_DrawCharacter(x + 8 + V_StringWidth(cv->string, 0), y + 12,
4895 '_' | 0x80, false);
4896 y += 16;
4897 break;
4898 default:
4899 V_DrawString(BASEVIDWIDTH - x - V_StringWidth(cv->string, 0), y,
4900 ((cv->flags & CV_CHEAT) && !CV_IsSetToDefault(cv) ? V_REDMAP : V_YELLOWMAP), cv->string);
4901 if (i == itemOn)
4902 {
4903 V_DrawCharacter(BASEVIDWIDTH - x - 10 - V_StringWidth(cv->string, 0) - (skullAnimCounter/5), y,
4904 '\x1C' | V_YELLOWMAP, false);
4905 V_DrawCharacter(BASEVIDWIDTH - x + 2 + (skullAnimCounter/5), y,
4906 '\x1D' | V_YELLOWMAP, false);
4907 }
4908 break;
4909 }
4910 break;
4911 }
4912 y += STRINGHEIGHT;
4913 break;
4914 case IT_STRING2:
4915 V_DrawCenteredString(x, y, 0, currentMenu->menuitems[i].text);
4916 /* FALLTHRU */
4917 case IT_DYLITLSPACE:
4918 y += SMALLLINEHEIGHT;
4919 break;
4920 case IT_QUESTIONMARKS:
4921 if (currentMenu->menuitems[i].alphaKey)
4922 y = currentMenu->y+currentMenu->menuitems[i].alphaKey;
4923
4924 V_DrawCenteredString(x, y, V_TRANSLUCENT|V_OLDSPACING, M_CreateSecretMenuOption(currentMenu->menuitems[i].text));
4925 y += SMALLLINEHEIGHT;
4926 break;
4927 case IT_GRAYPATCH:
4928 if (currentMenu->menuitems[i].patch && currentMenu->menuitems[i].patch[0])
4929 V_DrawMappedPatch(x, y, 0,
4930 W_CachePatchName(currentMenu->menuitems[i].patch,PU_PATCH), graymap);
4931 y += LINEHEIGHT;
4932 break;
4933 }
4934 }
4935
4936 // DRAW THE SKULL CURSOR
4937 if (((currentMenu->menuitems[itemOn].status & IT_DISPLAY) == IT_PATCH)
4938 || ((currentMenu->menuitems[itemOn].status & IT_DISPLAY) == IT_NOTHING))
4939 {
4940 V_DrawScaledPatch(x + SKULLXOFF, cursory - 5, 0,
4941 W_CachePatchName("M_CURSOR", PU_PATCH));
4942 }
4943 else
4944 {
4945 V_DrawScaledPatch(x - V_StringWidth(currentMenu->menuitems[itemOn].text, 0)/2 - 24, cursory, 0,
4946 W_CachePatchName("M_CURSOR", PU_PATCH));
4947 V_DrawCenteredString(x, cursory, V_YELLOWMAP, currentMenu->menuitems[itemOn].text);
4948 }
4949 }
4950
4951 //
4952 // M_StringHeight
4953 //
4954 // Find string height from hu_font chars
4955 //
M_StringHeight(const char * string)4956 static inline size_t M_StringHeight(const char *string)
4957 {
4958 size_t h = 8, i;
4959
4960 for (i = 0; i < strlen(string); i++)
4961 if (string[i] == '\n')
4962 h += 8;
4963
4964 return h;
4965 }
4966
4967 // ==========================================================================
4968 // Extraneous menu patching functions
4969 // ==========================================================================
4970
4971 //
4972 // M_PatchSkinNameTable
4973 //
4974 // Like M_PatchLevelNameTable, but for cv_chooseskin
4975 //
M_PatchSkinNameTable(void)4976 static void M_PatchSkinNameTable(void)
4977 {
4978 INT32 j;
4979
4980 memset(skins_cons_t, 0, sizeof (skins_cons_t));
4981
4982 for (j = 0; j < MAXSKINS; j++)
4983 {
4984 if (skins[j].name[0] != '\0' && R_SkinUsable(-1, j))
4985 {
4986 skins_cons_t[j].strvalue = skins[j].realname;
4987 skins_cons_t[j].value = j+1;
4988 }
4989 else
4990 {
4991 skins_cons_t[j].strvalue = NULL;
4992 skins_cons_t[j].value = 0;
4993 }
4994 }
4995
4996 CV_SetValue(&cv_chooseskin, 1);
4997 Nextmap_OnChange();
4998
4999 return;
5000 }
5001
5002 //
5003 // M_LevelAvailableOnPlatter
5004 //
5005 // Okay, you know that the level SHOULD show up on the platter already.
5006 // The only question is whether it should be as a question mark,
5007 // (hinting as to its existence), or as its pure, unfettered self.
5008 //
M_LevelAvailableOnPlatter(INT32 mapnum)5009 static boolean M_LevelAvailableOnPlatter(INT32 mapnum)
5010 {
5011 if (M_MapLocked(mapnum+1))
5012 return false; // not unlocked
5013
5014 switch (levellistmode)
5015 {
5016 case LLM_CREATESERVER:
5017 if (!(mapheaderinfo[mapnum]->typeoflevel & TOL_COOP))
5018 return true;
5019
5020 if (mapnum+1 == spstage_start)
5021 return true;
5022
5023 #ifndef DEVELOP
5024 if (mapvisited[mapnum]) // MV_MP
5025 #endif
5026 return true;
5027
5028 /* FALLTHRU */
5029 case LLM_RECORDATTACK:
5030 case LLM_NIGHTSATTACK:
5031 #ifndef DEVELOP
5032 if (mapvisited[mapnum] & MV_MAX)
5033 return true;
5034
5035 if (mapheaderinfo[mapnum]->menuflags & LF2_NOVISITNEEDED)
5036 #endif
5037 return true;
5038
5039 return false;
5040 case LLM_LEVELSELECT:
5041 default:
5042 return true;
5043 }
5044 return true;
5045 }
5046
5047 //
5048 // M_CanShowLevelOnPlatter
5049 //
5050 // Determines whether to show a given map in the various level-select lists.
5051 // Set gt = -1 to ignore gametype.
5052 //
M_CanShowLevelOnPlatter(INT32 mapnum,INT32 gt)5053 static boolean M_CanShowLevelOnPlatter(INT32 mapnum, INT32 gt)
5054 {
5055 // Does the map exist?
5056 if (!mapheaderinfo[mapnum])
5057 return false;
5058
5059 // Does the map have a name?
5060 if (!mapheaderinfo[mapnum]->lvlttl[0])
5061 return false;
5062
5063 /*if (M_MapLocked(mapnum+1))
5064 return false; // not unlocked*/
5065
5066 switch (levellistmode)
5067 {
5068 case LLM_CREATESERVER:
5069 // Should the map be hidden?
5070 if (mapheaderinfo[mapnum]->menuflags & LF2_HIDEINMENU)
5071 return false;
5072
5073 if (G_IsSpecialStage(mapnum+1))
5074 return false;
5075
5076 if (gt == GT_COOP && (mapheaderinfo[mapnum]->typeoflevel & TOL_COOP))
5077 return true;
5078
5079 if (gt == GT_COMPETITION && (mapheaderinfo[mapnum]->typeoflevel & TOL_COMPETITION))
5080 return true;
5081
5082 if (gt == GT_CTF && (mapheaderinfo[mapnum]->typeoflevel & TOL_CTF))
5083 return true;
5084
5085 if ((gt == GT_MATCH || gt == GT_TEAMMATCH) && (mapheaderinfo[mapnum]->typeoflevel & TOL_MATCH))
5086 return true;
5087
5088 if ((gt == GT_TAG || gt == GT_HIDEANDSEEK) && (mapheaderinfo[mapnum]->typeoflevel & TOL_TAG))
5089 return true;
5090
5091 if (gt == GT_RACE && (mapheaderinfo[mapnum]->typeoflevel & TOL_RACE))
5092 return true;
5093
5094 if (gt >= 0 && gt < gametypecount && (mapheaderinfo[mapnum]->typeoflevel & gametypetol[gt]))
5095 return true;
5096
5097 return false;
5098
5099 case LLM_LEVELSELECT:
5100 if (!(mapheaderinfo[mapnum]->levelselect & maplistoption))
5101 return false;
5102
5103 return true;
5104 case LLM_RECORDATTACK:
5105 if (!(mapheaderinfo[mapnum]->menuflags & LF2_RECORDATTACK))
5106 return false;
5107
5108 return true;
5109 case LLM_NIGHTSATTACK:
5110 if (!(mapheaderinfo[mapnum]->menuflags & LF2_NIGHTSATTACK))
5111 return false;
5112
5113 return true;
5114 }
5115
5116 // Hmm? Couldn't decide?
5117 return false;
5118 }
5119
5120 #if 0
M_CountLevelsToShowOnPlatter(INT32 gt)5121 static INT32 M_CountLevelsToShowOnPlatter(INT32 gt)
5122 {
5123 INT32 mapnum, count = 0;
5124
5125 for (mapnum = 0; mapnum < NUMMAPS; mapnum++)
5126 if (M_CanShowLevelOnPlatter(mapnum, gt))
5127 count++;
5128
5129 return count;
5130 }
5131 #endif
5132
5133 #if 0
M_SetNextMapOnPlatter(void)5134 static boolean M_SetNextMapOnPlatter(void)
5135 {
5136 INT32 row, col = 0;
5137 while (col < 3)
5138 {
5139 row = 0;
5140 while (row < levelselect.numrows)
5141 {
5142 if (levelselect.rows[row].maplist[col] == cv_nextmap.value)
5143 {
5144 lsrow = row;
5145 lscol = col;
5146 return true;
5147 }
5148 row++;
5149 }
5150 col++;
5151 }
5152 return true;
5153 }
5154 #endif
5155
M_GametypeHasLevels(INT32 gt)5156 static boolean M_GametypeHasLevels(INT32 gt)
5157 {
5158 INT32 mapnum;
5159
5160 for (mapnum = 0; mapnum < NUMMAPS; mapnum++)
5161 if (M_CanShowLevelOnPlatter(mapnum, gt))
5162 return true;
5163
5164 return false;
5165 }
5166
M_CountRowsToShowOnPlatter(INT32 gt)5167 static INT32 M_CountRowsToShowOnPlatter(INT32 gt)
5168 {
5169 INT32 mapnum = 0, prevmapnum = 0, col = 0, rows = 0;
5170
5171 while (mapnum < NUMMAPS)
5172 {
5173 if (M_CanShowLevelOnPlatter(mapnum, gt))
5174 {
5175 if (rows == 0)
5176 rows++;
5177 else
5178 {
5179 if (col == 2
5180 || (mapheaderinfo[prevmapnum]->menuflags & LF2_WIDEICON)
5181 || (mapheaderinfo[mapnum]->menuflags & LF2_WIDEICON)
5182 || !(fastcmp(mapheaderinfo[mapnum]->selectheading, mapheaderinfo[prevmapnum]->selectheading)))
5183 {
5184 col = 0;
5185 rows++;
5186 }
5187 else
5188 col++;
5189 }
5190 prevmapnum = mapnum;
5191 }
5192 mapnum++;
5193 }
5194
5195 if (levellistmode == LLM_CREATESERVER)
5196 rows++;
5197
5198 return rows;
5199 }
5200
5201 //
5202 // M_CacheLevelPlatter
5203 //
5204 // Cache every patch used by the level platter.
5205 //
M_CacheLevelPlatter(void)5206 static void M_CacheLevelPlatter(void)
5207 {
5208 levselp[0][0] = W_CachePatchName("SLCT1LVL", PU_PATCH);
5209 levselp[0][1] = W_CachePatchName("SLCT2LVL", PU_PATCH);
5210 levselp[0][2] = W_CachePatchName("BLANKLVL", PU_PATCH);
5211
5212 levselp[1][0] = W_CachePatchName("SLCT1LVW", PU_PATCH);
5213 levselp[1][1] = W_CachePatchName("SLCT2LVW", PU_PATCH);
5214 levselp[1][2] = W_CachePatchName("BLANKLVW", PU_PATCH);
5215 }
5216
5217 //
5218 // M_PrepareLevelPlatter
5219 //
5220 // Prepares a tasty dish of zones and acts!
5221 // Call before any attempt to access a level platter.
5222 //
M_PrepareLevelPlatter(INT32 gt,boolean nextmappick)5223 static boolean M_PrepareLevelPlatter(INT32 gt, boolean nextmappick)
5224 {
5225 INT32 numrows = M_CountRowsToShowOnPlatter(gt);
5226 INT32 mapnum = 0, prevmapnum = 0, col = 0, row = 0, startrow = 0;
5227
5228 if (!numrows)
5229 return false;
5230
5231 if (levelselect.rows)
5232 Z_Free(levelselect.rows);
5233 levelselect.rows = NULL;
5234
5235 levelselect.numrows = numrows;
5236 levelselect.rows = Z_Realloc(levelselect.rows, numrows*sizeof(levelselectrow_t), PU_STATIC, NULL);
5237 if (!levelselect.rows)
5238 I_Error("Insufficient memory to prepare level platter");
5239
5240 // done here so lsrow and lscol can be set if cv_nextmap is on the platter
5241 lsrow = lscol = lshli = lsoffs[0] = lsoffs[1] = 0;
5242
5243 if (levellistmode == LLM_CREATESERVER)
5244 {
5245 sprintf(levelselect.rows[0].header, "Gametype");
5246 lswide(0) = true;
5247 levelselect.rows[row].mapavailable[2] = levelselect.rows[row].mapavailable[1] = levelselect.rows[row].mapavailable[0] = false;
5248 startrow = row = 1;
5249
5250 Z_Free(char_notes);
5251 char_notes = NULL;
5252 }
5253
5254 while (mapnum < NUMMAPS)
5255 {
5256 if (M_CanShowLevelOnPlatter(mapnum, gt))
5257 {
5258 const UINT8 actnum = mapheaderinfo[mapnum]->actnum;
5259 const boolean headingisname = (fastcmp(mapheaderinfo[mapnum]->selectheading, mapheaderinfo[mapnum]->lvlttl));
5260 const boolean wide = (mapheaderinfo[mapnum]->menuflags & LF2_WIDEICON);
5261
5262 // preparing next position to drop mapnum into
5263 if (levelselect.rows[startrow].maplist[0])
5264 {
5265 if (col == 2 // no more space on the row?
5266 || wide
5267 || (mapheaderinfo[prevmapnum]->menuflags & LF2_WIDEICON)
5268 || !(fastcmp(mapheaderinfo[mapnum]->selectheading, mapheaderinfo[prevmapnum]->selectheading))) // a new heading is starting?
5269 {
5270 col = 0;
5271 row++;
5272 }
5273 else
5274 col++;
5275 }
5276
5277 levelselect.rows[row].maplist[col] = mapnum+1; // putting the map on the platter
5278 levelselect.rows[row].mapavailable[col] = M_LevelAvailableOnPlatter(mapnum);
5279
5280 if ((lswide(row) = wide)) // intentionally assignment
5281 {
5282 levelselect.rows[row].maplist[2] = levelselect.rows[row].maplist[1] = levelselect.rows[row].maplist[0];
5283 levelselect.rows[row].mapavailable[2] = levelselect.rows[row].mapavailable[1] = levelselect.rows[row].mapavailable[0];
5284 }
5285
5286 if (nextmappick && cv_nextmap.value == mapnum+1) // A little quality of life improvement.
5287 {
5288 lsrow = row;
5289 lscol = col;
5290 }
5291
5292 // individual map name
5293 if (levelselect.rows[row].mapavailable[col])
5294 {
5295 if (headingisname)
5296 {
5297 if (actnum)
5298 sprintf(levelselect.rows[row].mapnames[col], "ACT %d", actnum);
5299 else
5300 sprintf(levelselect.rows[row].mapnames[col], "THE ACT");
5301 }
5302 else if (wide)
5303 {
5304 // Yes, with LF2_WIDEICON it'll continue on over into the next 17+1 char block. That's alright; col is always zero, the string is contiguous, and the maximum length is lvlttl[22] + ' ' + ZONE + ' ' + INT32, which is about 39 or so - barely crossing into the third column.
5305 char* mapname = G_BuildMapTitle(mapnum+1);
5306 strcpy(levelselect.rows[row].mapnames[col], (const char *)mapname);
5307 Z_Free(mapname);
5308 }
5309 else
5310 {
5311 char mapname[22+1+11]; // lvlttl[22] + ' ' + INT32
5312
5313 if (actnum)
5314 sprintf(mapname, "%s %d", mapheaderinfo[mapnum]->lvlttl, actnum);
5315 else
5316 strcpy(mapname, mapheaderinfo[mapnum]->lvlttl);
5317
5318 if (strlen(mapname) >= 17)
5319 strcpy(mapname+17-3, "...");
5320
5321 strcpy(levelselect.rows[row].mapnames[col], (const char *)mapname);
5322 }
5323 }
5324 else
5325 sprintf(levelselect.rows[row].mapnames[col], "???");
5326
5327 // creating header text
5328 if (!col && ((row == startrow) || !(fastcmp(mapheaderinfo[mapnum]->selectheading, mapheaderinfo[levelselect.rows[row-1].maplist[0]-1]->selectheading))))
5329 {
5330 if (!levelselect.rows[row].mapavailable[col])
5331 sprintf(levelselect.rows[row].header, "???");
5332 else
5333 {
5334 sprintf(levelselect.rows[row].header, "%s", mapheaderinfo[mapnum]->selectheading);
5335 if (!(mapheaderinfo[mapnum]->levelflags & LF_NOZONE) && headingisname)
5336 {
5337 sprintf(levelselect.rows[row].header + strlen(levelselect.rows[row].header), " ZONE");
5338 }
5339 }
5340 }
5341
5342 prevmapnum = mapnum;
5343 }
5344
5345 mapnum++;
5346 }
5347
5348 #ifdef SYMMETRICAL_PLATTER
5349 // horizontally space out rows with missing right sides
5350 for (; row >= 0; row--)
5351 {
5352 if (!levelselect.rows[row].maplist[2] // no right side
5353 && levelselect.rows[row].maplist[0] && levelselect.rows[row].maplist[1]) // all the left filled in
5354 {
5355 levelselect.rows[row].maplist[2] = levelselect.rows[row].maplist[1];
5356 STRBUFCPY(levelselect.rows[row].mapnames[2], levelselect.rows[row].mapnames[1]);
5357 levelselect.rows[row].mapavailable[2] = levelselect.rows[row].mapavailable[1];
5358
5359 levelselect.rows[row].maplist[1] = -1; // diamond
5360 levelselect.rows[row].mapnames[1][0] = '\0';
5361 levelselect.rows[row].mapavailable[1] = false;
5362 }
5363 }
5364 #endif
5365
5366 M_CacheLevelPlatter();
5367
5368 return true;
5369 }
5370
5371 #define ifselectvalnextmapnobrace(column) if ((selectval = levelselect.rows[lsrow].maplist[column]) && levelselect.rows[lsrow].mapavailable[column])\
5372 {\
5373 CV_SetValue(&cv_nextmap, selectval);
5374
5375 #define ifselectvalnextmap(column) ifselectvalnextmapnobrace(column)}
5376
5377 //
5378 // M_HandleLevelPlatter
5379 //
5380 // Reacts to your key inputs. Basically a mini menu thinker.
5381 //
M_HandleLevelPlatter(INT32 choice)5382 static void M_HandleLevelPlatter(INT32 choice)
5383 {
5384 boolean exitmenu = false; // exit to previous menu
5385 INT32 selectval;
5386 UINT8 iter;
5387
5388 switch (choice)
5389 {
5390 case KEY_DOWNARROW:
5391 if (lsrow == levelselect.numrows-1)
5392 {
5393 if (levelselect.numrows < 3)
5394 {
5395 if (!lsoffs[0]) // prevent sound spam
5396 {
5397 lsoffs[0] = -8;
5398 S_StartSound(NULL,sfx_s3kb7);
5399 }
5400 return;
5401 }
5402 lsrow = UINT8_MAX;
5403 }
5404 lsrow++;
5405
5406 lsoffs[0] = lsvseperation(lsrow);
5407
5408 if (levelselect.rows[lsrow].header[0])
5409 lshli = lsrow;
5410 // no else needed - headerless lines associate upwards, so moving down to a row without a header is identity
5411
5412 S_StartSound(NULL,sfx_s3kb7);
5413
5414 ifselectvalnextmap(lscol) else ifselectvalnextmap(0)
5415 break;
5416
5417 case KEY_UPARROW:
5418 iter = lsrow;
5419 if (!lsrow)
5420 {
5421 if (levelselect.numrows < 3)
5422 {
5423 if (!lsoffs[0]) // prevent sound spam
5424 {
5425 lsoffs[0] = 8;
5426 S_StartSound(NULL,sfx_s3kb7);
5427 }
5428 return;
5429 }
5430 lsrow = levelselect.numrows;
5431 }
5432 lsrow--;
5433
5434 lsoffs[0] = -lsvseperation(iter);
5435
5436 if (levelselect.rows[lsrow].header[0])
5437 lshli = lsrow;
5438 else
5439 {
5440 iter = lsrow;
5441 do
5442 iter = ((iter == 0) ? levelselect.numrows-1 : iter-1);
5443 while ((iter != lsrow) && !(levelselect.rows[iter].header[0]));
5444 lshli = iter;
5445 }
5446
5447 S_StartSound(NULL,sfx_s3kb7);
5448
5449 ifselectvalnextmap(lscol) else ifselectvalnextmap(0)
5450 break;
5451
5452 case KEY_ENTER:
5453 if (!(levellistmode == LLM_CREATESERVER && !lsrow))
5454 {
5455 ifselectvalnextmapnobrace(lscol)
5456 lsoffs[0] = lsoffs[1] = 0;
5457 S_StartSound(NULL,sfx_menu1);
5458 if (gamestate == GS_TIMEATTACK)
5459 M_SetupNextMenu(currentMenu->prevMenu);
5460 else if (currentMenu == &MISC_ChangeLevelDef)
5461 {
5462 if (currentMenu->prevMenu && currentMenu->prevMenu != &MPauseDef)
5463 M_SetupNextMenu(currentMenu->prevMenu);
5464 else
5465 M_ChangeLevel(0);
5466 Z_Free(levelselect.rows);
5467 levelselect.rows = NULL;
5468 }
5469 else
5470 M_LevelSelectWarp(0);
5471 Nextmap_OnChange();
5472 }
5473 else if (!lsoffs[0]) // prevent sound spam
5474 {
5475 lsoffs[0] = -8;
5476 S_StartSound(NULL,sfx_s3kb2);
5477 }
5478 break;
5479 }
5480 /* FALLTHRU */
5481 case KEY_RIGHTARROW:
5482 if (levellistmode == LLM_CREATESERVER && !lsrow)
5483 {
5484 INT32 startinggametype = cv_newgametype.value;
5485 do
5486 CV_AddValue(&cv_newgametype, 1);
5487 while (cv_newgametype.value != startinggametype && !M_GametypeHasLevels(cv_newgametype.value));
5488 S_StartSound(NULL,sfx_menu1);
5489 lscol = 0;
5490
5491 Z_Free(char_notes);
5492 char_notes = NULL;
5493
5494 if (!M_PrepareLevelPlatter(cv_newgametype.value, false))
5495 I_Error("Unidentified level platter failure!");
5496 }
5497 else if (lscol < 2)
5498 {
5499 lscol++;
5500
5501 lsoffs[1] = (lswide(lsrow) ? 8 : -lshseperation);
5502 S_StartSound(NULL,sfx_s3kb7);
5503
5504 ifselectvalnextmap(lscol) else ifselectvalnextmap(0)
5505 }
5506 else if (!lsoffs[1]) // prevent sound spam
5507 {
5508 lsoffs[1] = 8;
5509 S_StartSound(NULL,sfx_s3kb7);
5510 }
5511 break;
5512
5513 case KEY_LEFTARROW:
5514 if (levellistmode == LLM_CREATESERVER && !lsrow)
5515 {
5516 INT32 startinggametype = cv_newgametype.value;
5517 do
5518 CV_AddValue(&cv_newgametype, -1);
5519 while (cv_newgametype.value != startinggametype && !M_GametypeHasLevels(cv_newgametype.value));
5520 S_StartSound(NULL,sfx_menu1);
5521 lscol = 0;
5522
5523 Z_Free(char_notes);
5524 char_notes = NULL;
5525
5526 if (!M_PrepareLevelPlatter(cv_newgametype.value, false))
5527 I_Error("Unidentified level platter failure!");
5528 }
5529 else if (lscol > 0)
5530 {
5531 lscol--;
5532
5533 lsoffs[1] = (lswide(lsrow) ? -8 : lshseperation);
5534 S_StartSound(NULL,sfx_s3kb7);
5535
5536 ifselectvalnextmap(lscol) else ifselectvalnextmap(0)
5537 }
5538 else if (!lsoffs[1]) // prevent sound spam
5539 {
5540 lsoffs[1] = -8;
5541 S_StartSound(NULL,sfx_s3kb7);
5542 }
5543 break;
5544
5545 case KEY_ESCAPE:
5546 exitmenu = true;
5547 break;
5548
5549 default:
5550 break;
5551 }
5552
5553 if (exitmenu)
5554 {
5555 if (gamestate != GS_TIMEATTACK)
5556 {
5557 Z_Free(levelselect.rows);
5558 levelselect.rows = NULL;
5559 }
5560
5561 if (currentMenu->prevMenu)
5562 {
5563 M_SetupNextMenu(currentMenu->prevMenu);
5564 Nextmap_OnChange();
5565 }
5566 else
5567 M_ClearMenus(true);
5568
5569 Z_Free(char_notes);
5570 char_notes = NULL;
5571 }
5572 }
5573
5574 void M_DrawLevelPlatterHeader(INT32 y, const char *header, boolean headerhighlight, boolean allowlowercase)
5575 {
5576 y += lsheadingheight - 12;
5577 V_DrawString(19, y, (headerhighlight ? V_YELLOWMAP : 0)|(allowlowercase ? V_ALLOWLOWERCASE : 0), header);
5578 y += 9;
5579 V_DrawFill(19, y, 281, 1, (headerhighlight ? yellowmap[3] : 3));
5580 V_DrawFill(300, y, 1, 1, 26);
5581 y++;
5582 V_DrawFill(19, y, 282, 1, 26);
5583 }
5584
5585 static void M_DrawLevelPlatterWideMap(UINT8 row, UINT8 col, INT32 x, INT32 y, boolean highlight)
5586 {
5587 patch_t *patch;
5588
5589 INT32 map = levelselect.rows[row].maplist[col];
5590 if (map <= 0)
5591 return;
5592
5593 // A 564x100 image of the level as entry MAPxxW
5594 if (!(levelselect.rows[row].mapavailable[col]))
5595 {
5596 V_DrawSmallScaledPatch(x, y, 0, levselp[1][2]);
5597 M_DrawStaticBox(x, y, V_80TRANS, 282, 50);
5598 }
5599 else
5600 {
5601 if (W_CheckNumForName(va("%sW", G_BuildMapName(map))) != LUMPERROR)
5602 patch = W_CachePatchName(va("%sW", G_BuildMapName(map)), PU_PATCH);
5603 else
5604 patch = levselp[1][2]; // don't static to indicate that it's just a normal level
5605
5606 V_DrawSmallScaledPatch(x, y, 0, patch);
5607 }
5608
5609 V_DrawFill(x, y+50, 282, 8,
5610 ((mapheaderinfo[map-1]->unlockrequired < 0)
5611 ? 159 : 63));
5612
5613 V_DrawString(x, y+50, (highlight ? V_YELLOWMAP : 0), levelselect.rows[row].mapnames[col]);
5614 }
5615
5616 static void M_DrawLevelPlatterMap(UINT8 row, UINT8 col, INT32 x, INT32 y, boolean highlight)
5617 {
5618 patch_t *patch;
5619
5620 INT32 map = levelselect.rows[row].maplist[col];
5621 if (map <= 0)
5622 return;
5623
5624 // A 160x100 image of the level as entry MAPxxP
5625 if (!(levelselect.rows[row].mapavailable[col]))
5626 {
5627 V_DrawSmallScaledPatch(x, y, 0, levselp[0][2]);
5628 M_DrawStaticBox(x, y, V_80TRANS, 80, 50);
5629 }
5630 else
5631 {
5632 if (W_CheckNumForName(va("%sP", G_BuildMapName(map))) != LUMPERROR)
5633 patch = W_CachePatchName(va("%sP", G_BuildMapName(map)), PU_PATCH);
5634 else
5635 patch = levselp[0][2]; // don't static to indicate that it's just a normal level
5636
5637 V_DrawSmallScaledPatch(x, y, 0, patch);
5638 }
5639
5640 V_DrawFill(x, y+50, 80, 8,
5641 ((mapheaderinfo[map-1]->unlockrequired < 0)
5642 ? 159 : 63));
5643
5644 if (strlen(levelselect.rows[row].mapnames[col]) > 6) // "AERIAL GARDEN" vs "ACT 18" - "THE ACT" intentionally compressed
5645 V_DrawThinString(x, y+50, (highlight ? V_YELLOWMAP : 0), levelselect.rows[row].mapnames[col]);
5646 else
5647 V_DrawString(x, y+50, (highlight ? V_YELLOWMAP : 0), levelselect.rows[row].mapnames[col]);
5648 }
5649
5650 static void M_DrawLevelPlatterRow(UINT8 row, INT32 y)
5651 {
5652 UINT8 col;
5653 const boolean rowhighlight = (row == lsrow);
5654 if (levelselect.rows[row].header[0])
5655 {
5656 M_DrawLevelPlatterHeader(y, levelselect.rows[row].header, (rowhighlight || (row == lshli)), false);
5657 y += lsheadingheight;
5658 }
5659
5660 if (levellistmode == LLM_CREATESERVER && !row)
5661 {
5662 if (!char_notes)
5663 char_notes = V_WordWrap(0, 282 - 8, V_ALLOWLOWERCASE, gametypedesc[cv_newgametype.value].notes);
5664
5665 V_DrawFill(lsbasex, y, 282, 50, 27);
5666 V_DrawString(lsbasex + 4, y + 4, V_RETURN8|V_ALLOWLOWERCASE, char_notes);
5667
5668 V_DrawFill(lsbasex, y+50, 141, 8, gametypedesc[cv_newgametype.value].col[0]);
5669 V_DrawFill(lsbasex+141, y+50, 141, 8, gametypedesc[cv_newgametype.value].col[1]);
5670
5671 V_DrawString(lsbasex, y+50, 0, gametype_cons_t[cv_newgametype.value].strvalue);
5672
5673 if (!lsrow)
5674 {
5675 V_DrawCharacter(lsbasex - 10 - (skullAnimCounter/5), y+25,
5676 '\x1C' | V_YELLOWMAP, false);
5677 V_DrawCharacter(lsbasex+282 + 2 + (skullAnimCounter/5), y+25,
5678 '\x1D' | V_YELLOWMAP, false);
5679 }
5680 }
5681 else if (lswide(row))
5682 M_DrawLevelPlatterWideMap(row, 0, lsbasex, y, rowhighlight);
5683 else
5684 {
5685 for (col = 0; col < 3; col++)
5686 M_DrawLevelPlatterMap(row, col, lsbasex+(col*lshseperation), y, (rowhighlight && (col == lscol)));
5687 }
5688 }
5689
5690 // new menus
5691 static void M_DrawRecordAttackForeground(void)
5692 {
5693 patch_t *fg = W_CachePatchName("RECATKFG", PU_PATCH);
5694 patch_t *clock = W_CachePatchName("RECCLOCK", PU_PATCH);
5695 angle_t fa;
5696
5697 INT32 i;
5698 INT32 height = (fg->height / 2);
5699 INT32 dupz = (vid.dupx < vid.dupy ? vid.dupx : vid.dupy);
5700
5701 for (i = -12; i < (BASEVIDHEIGHT/height) + 12; i++)
5702 {
5703 INT32 y = ((i*height) - (height - ((recatkdrawtimer*2)%height)));
5704 // don't draw above the screen
5705 {
5706 INT32 sy = FixedMul(y, dupz<<FRACBITS) >> FRACBITS;
5707 if (vid.height != BASEVIDHEIGHT * dupz)
5708 sy += (vid.height - (BASEVIDHEIGHT * dupz)) / 2;
5709 if ((sy+height) < 0)
5710 continue;
5711 }
5712 V_DrawFixedPatch(0, y<<FRACBITS, FRACUNIT/2, V_SNAPTOLEFT, fg, NULL);
5713 V_DrawFixedPatch(BASEVIDWIDTH<<FRACBITS, y<<FRACBITS, FRACUNIT/2, V_SNAPTORIGHT|V_FLIP, fg, NULL);
5714 // don't draw below the screen
5715 if (y > vid.height)
5716 break;
5717 }
5718
5719 // draw clock
5720 fa = (FixedAngle(((recatkdrawtimer * 4) % 360)<<FRACBITS)>>ANGLETOFINESHIFT) & FINEMASK;
5721 V_DrawSciencePatch(160<<FRACBITS, (80<<FRACBITS) + (4*FINESINE(fa)), 0, clock, FRACUNIT);
5722
5723 // Increment timer.
5724 recatkdrawtimer++;
5725 }
5726
5727 // NiGHTS Attack background.
5728 static void M_DrawNightsAttackMountains(void)
5729 {
5730 static INT32 bgscrollx;
5731 INT32 dupz = (vid.dupx < vid.dupy ? vid.dupx : vid.dupy);
5732 patch_t *background = W_CachePatchName(curbgname, PU_PATCH);
5733 INT16 w = background->width;
5734 INT32 x = FixedInt(-bgscrollx) % w;
5735 INT32 y = BASEVIDHEIGHT - (background->height * 2);
5736
5737 if (vid.height != BASEVIDHEIGHT * dupz)
5738 V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 158);
5739 V_DrawFill(0, y+50, vid.width, BASEVIDHEIGHT, V_SNAPTOLEFT|31);
5740
5741 V_DrawScaledPatch(x, y, V_SNAPTOLEFT, background);
5742 x += w;
5743 if (x < BASEVIDWIDTH)
5744 V_DrawScaledPatch(x, y, V_SNAPTOLEFT, background);
5745
5746 bgscrollx += (FRACUNIT/2);
5747 if (bgscrollx > w<<FRACBITS)
5748 bgscrollx &= 0xFFFF;
5749 }
5750
5751 // NiGHTS Attack foreground.
5752 static void M_DrawNightsAttackBackground(void)
5753 {
5754 INT32 x, y = 0;
5755 INT32 i;
5756
5757 // top
5758 patch_t *backtopfg = W_CachePatchName("NTSATKT1", PU_PATCH);
5759 patch_t *fronttopfg = W_CachePatchName("NTSATKT2", PU_PATCH);
5760 INT32 backtopwidth = backtopfg->width;
5761 //INT32 backtopheight = backtopfg->height;
5762 INT32 fronttopwidth = fronttopfg->width;
5763 //INT32 fronttopheight = fronttopfg->height;
5764
5765 // bottom
5766 patch_t *backbottomfg = W_CachePatchName("NTSATKB1", PU_PATCH);
5767 patch_t *frontbottomfg = W_CachePatchName("NTSATKB2", PU_PATCH);
5768 INT32 backbottomwidth = backbottomfg->width;
5769 INT32 backbottomheight = backbottomfg->height;
5770 INT32 frontbottomwidth = frontbottomfg->width;
5771 INT32 frontbottomheight = frontbottomfg->height;
5772
5773 // background
5774 M_DrawNightsAttackMountains();
5775
5776 // back top foreground patch
5777 x = 0-(ntsatkdrawtimer%backtopwidth);
5778 V_DrawScaledPatch(x, y, V_SNAPTOTOP|V_SNAPTOLEFT, backtopfg);
5779 for (i = 0; i < 3; i++)
5780 {
5781 x += (backtopwidth);
5782 if (x >= vid.width)
5783 break;
5784 V_DrawScaledPatch(x, y, V_SNAPTOTOP|V_SNAPTOLEFT, backtopfg);
5785 }
5786
5787 // front top foreground patch
5788 x = 0-((ntsatkdrawtimer*2)%fronttopwidth);
5789 V_DrawScaledPatch(x, y, V_SNAPTOTOP|V_SNAPTOLEFT, fronttopfg);
5790 for (i = 0; i < 3; i++)
5791 {
5792 x += (fronttopwidth);
5793 if (x >= vid.width)
5794 break;
5795 V_DrawScaledPatch(x, y, V_SNAPTOTOP|V_SNAPTOLEFT, fronttopfg);
5796 }
5797
5798 // back bottom foreground patch
5799 x = 0-(ntsatkdrawtimer%backbottomwidth);
5800 y = BASEVIDHEIGHT - backbottomheight;
5801 V_DrawScaledPatch(x, y, V_SNAPTOBOTTOM|V_SNAPTOLEFT, backbottomfg);
5802 for (i = 0; i < 3; i++)
5803 {
5804 x += (backbottomwidth);
5805 if (x >= vid.width)
5806 break;
5807 V_DrawScaledPatch(x, y, V_SNAPTOBOTTOM|V_SNAPTOLEFT, backbottomfg);
5808 }
5809
5810 // front bottom foreground patch
5811 x = 0-((ntsatkdrawtimer*2)%frontbottomwidth);
5812 y = BASEVIDHEIGHT - frontbottomheight;
5813 V_DrawScaledPatch(x, y, V_SNAPTOBOTTOM|V_SNAPTOLEFT, frontbottomfg);
5814 for (i = 0; i < 3; i++)
5815 {
5816 x += (frontbottomwidth);
5817 if (x >= vid.width)
5818 break;
5819 V_DrawScaledPatch(x, y, V_SNAPTOBOTTOM|V_SNAPTOLEFT, frontbottomfg);
5820 }
5821
5822 // Increment timer.
5823 ntsatkdrawtimer++;
5824 }
5825
5826 // NiGHTS Attack floating Super Sonic.
5827 static patch_t *ntssupersonic[2];
5828 static void M_DrawNightsAttackSuperSonic(void)
5829 {
5830 const UINT8 *colormap = R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_YELLOW, GTC_CACHE);
5831 INT32 timer = (ntsatkdrawtimer/4) % 2;
5832 angle_t fa = (FixedAngle(((ntsatkdrawtimer * 4) % 360)<<FRACBITS)>>ANGLETOFINESHIFT) & FINEMASK;
5833 V_DrawFixedPatch(235<<FRACBITS, (120<<FRACBITS) - (8*FINESINE(fa)), FRACUNIT, 0, ntssupersonic[timer], colormap);
5834 }
5835
5836 static void M_DrawLevelPlatterMenu(void)
5837 {
5838 UINT8 iter = lsrow, sizeselect = (lswide(lsrow) ? 1 : 0);
5839 INT32 y = lsbasey + lsoffs[0] - getheadingoffset(lsrow);
5840 const INT32 cursorx = (sizeselect ? 0 : (lscol*lshseperation));
5841
5842 if (currentMenu->prevMenu == &SP_TimeAttackDef)
5843 {
5844 M_SetMenuCurBackground("RECATKBG");
5845
5846 curbgxspeed = 0;
5847 curbgyspeed = 18;
5848
5849 if (curbgcolor >= 0)
5850 V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, curbgcolor);
5851 else if (!curbghide || !titlemapinaction)
5852 {
5853 F_SkyScroll(curbgxspeed, curbgyspeed, curbgname);
5854 // Draw and animate foreground
5855 if (!strncmp("RECATKBG", curbgname, 8))
5856 M_DrawRecordAttackForeground();
5857 }
5858
5859 if (curfadevalue)
5860 V_DrawFadeScreen(0xFF00, curfadevalue);
5861 }
5862
5863 if (currentMenu->prevMenu == &SP_NightsAttackDef)
5864 {
5865 M_SetMenuCurBackground("NTSATKBG");
5866
5867 if (curbgcolor >= 0)
5868 V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, curbgcolor);
5869 else if (!curbghide || !titlemapinaction)
5870 {
5871 V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 158);
5872 M_DrawNightsAttackMountains();
5873 }
5874 if (curfadevalue)
5875 V_DrawFadeScreen(0xFF00, curfadevalue);
5876 }
5877
5878 // finds row at top of the screen
5879 while (y > -8)
5880 {
5881 if (iter == 0)
5882 {
5883 if (levelselect.numrows < 3)
5884 break;
5885 iter = levelselect.numrows;
5886 }
5887 iter--;
5888 y -= lsvseperation(iter);
5889 }
5890
5891 // draw from top to bottom
5892 while (y < (vid.height/vid.dupy))
5893 {
5894 M_DrawLevelPlatterRow(iter, y);
5895 y += lsvseperation(iter);
5896 if (iter == levelselect.numrows-1)
5897 {
5898 if (levelselect.numrows < 3)
5899 break;
5900 iter = UINT8_MAX;
5901 }
5902 iter++;
5903 }
5904
5905 // draw cursor box
5906 if (levellistmode != LLM_CREATESERVER || lsrow)
5907 V_DrawSmallScaledPatch(lsbasex + cursorx + lsoffs[1], lsbasey+lsoffs[0], 0, (levselp[sizeselect][((skullAnimCounter/4) ? 1 : 0)]));
5908
5909 #if 0
5910 if (levelselect.rows[lsrow].maplist[lscol] > 0)
5911 V_DrawScaledPatch(lsbasex + cursorx-17, lsbasey+50+lsoffs[0], 0, W_CachePatchName("M_CURSOR", PU_PATCH));
5912 #endif
5913
5914 // handle movement of cursor box
5915 if (lsoffs[0] > 1 || lsoffs[0] < -1)
5916 lsoffs[0] = 2*lsoffs[0]/3;
5917 else
5918 lsoffs[0] = 0;
5919
5920 if (lsoffs[1] > 1 || lsoffs[1] < -1)
5921 lsoffs[1] = 2*lsoffs[1]/3;
5922 else
5923 lsoffs[1] = 0;
5924
5925 M_DrawMenuTitle();
5926 }
5927
5928 //
5929 // M_CanShowLevelInList
5930 //
5931 // Determines whether to show a given map in level-select lists where you don't want to see locked levels.
5932 // Set gt = -1 to ignore gametype.
5933 //
5934 boolean M_CanShowLevelInList(INT32 mapnum, INT32 gt)
5935 {
5936 return (M_CanShowLevelOnPlatter(mapnum, gt) && M_LevelAvailableOnPlatter(mapnum));
5937 }
5938
5939 static INT32 M_GetFirstLevelInList(INT32 gt)
5940 {
5941 INT32 mapnum;
5942
5943 for (mapnum = 0; mapnum < NUMMAPS; mapnum++)
5944 if (M_CanShowLevelInList(mapnum, gt))
5945 return mapnum + 1;
5946
5947 return 1;
5948 }
5949
5950 // ==================================================
5951 // MESSAGE BOX (aka: a hacked, cobbled together menu)
5952 // ==================================================
5953 static void M_DrawMessageMenu(void);
5954
5955 // Because this is just a hack-ish 'menu', I'm not putting this with the others
5956 static menuitem_t MessageMenu[] =
5957 {
5958 // TO HACK
5959 {0,NULL, NULL, NULL,0}
5960 };
5961
5962 menu_t MessageDef =
5963 {
5964 MN_SPECIAL,
5965 NULL, // title
5966 1, // # of menu items
5967 NULL, // previous menu (TO HACK)
5968 MessageMenu, // menuitem_t ->
5969 M_DrawMessageMenu, // drawing routine ->
5970 0, 0, // x, y (TO HACK)
5971 0, // lastOn, flags (TO HACK)
5972 NULL
5973 };
5974
5975
5976 void M_StartMessage(const char *string, void *routine,
5977 menumessagetype_t itemtype)
5978 {
5979 size_t max = 0, start = 0, i, strlines;
5980 static char *message = NULL;
5981 Z_Free(message);
5982 message = Z_StrDup(string);
5983 DEBFILE(message);
5984
5985 // Rudementary word wrapping.
5986 // Simple and effective. Does not handle nonuniform letter sizes, colors, etc. but who cares.
5987 strlines = 0;
5988 for (i = 0; message[i]; i++)
5989 {
5990 if (message[i] == ' ')
5991 {
5992 start = i;
5993 max += 4;
5994 }
5995 else if (message[i] == '\n')
5996 {
5997 strlines = i;
5998 start = 0;
5999 max = 0;
6000 continue;
6001 }
6002 else
6003 max += 8;
6004
6005 // Start trying to wrap if presumed length exceeds the screen width.
6006 if (max >= BASEVIDWIDTH && start > 0)
6007 {
6008 message[start] = '\n';
6009 max -= (start-strlines)*8;
6010 strlines = start;
6011 start = 0;
6012 }
6013 }
6014
6015 start = 0;
6016 max = 0;
6017
6018 M_StartControlPanel(); // can't put menuactive to true
6019
6020 if (currentMenu == &MessageDef) // Prevent recursion
6021 MessageDef.prevMenu = &MainDef;
6022 else
6023 MessageDef.prevMenu = currentMenu;
6024
6025 MessageDef.menuitems[0].text = message;
6026 MessageDef.menuitems[0].alphaKey = (UINT8)itemtype;
6027 if (!routine && itemtype != MM_NOTHING) itemtype = MM_NOTHING;
6028 switch (itemtype)
6029 {
6030 case MM_NOTHING:
6031 MessageDef.menuitems[0].status = IT_MSGHANDLER;
6032 MessageDef.menuitems[0].itemaction = M_StopMessage;
6033 break;
6034 case MM_YESNO:
6035 MessageDef.menuitems[0].status = IT_MSGHANDLER;
6036 MessageDef.menuitems[0].itemaction = routine;
6037 break;
6038 case MM_EVENTHANDLER:
6039 MessageDef.menuitems[0].status = IT_MSGHANDLER;
6040 MessageDef.menuitems[0].itemaction = routine;
6041 break;
6042 }
6043 //added : 06-02-98: now draw a textbox around the message
6044 // compute lenght max and the numbers of lines
6045 for (strlines = 0; *(message+start); strlines++)
6046 {
6047 for (i = 0;i < strlen(message+start);i++)
6048 {
6049 if (*(message+start+i) == '\n')
6050 {
6051 if (i > max)
6052 max = i;
6053 start += i;
6054 i = (size_t)-1; //added : 07-02-98 : damned!
6055 start++;
6056 break;
6057 }
6058 }
6059
6060 if (i == strlen(message+start))
6061 start += i;
6062 }
6063
6064 MessageDef.x = (INT16)((BASEVIDWIDTH - 8*max-16)/2);
6065 MessageDef.y = (INT16)((BASEVIDHEIGHT - M_StringHeight(message))/2);
6066
6067 MessageDef.lastOn = (INT16)((strlines<<8)+max);
6068
6069 //M_SetupNextMenu();
6070 currentMenu = &MessageDef;
6071 itemOn = 0;
6072 }
6073
6074 #define MAXMSGLINELEN 256
6075
6076 static void M_DrawMessageMenu(void)
6077 {
6078 INT32 y = currentMenu->y;
6079 size_t i, start = 0;
6080 INT16 max;
6081 char string[MAXMSGLINELEN];
6082 INT32 mlines;
6083 const char *msg = currentMenu->menuitems[0].text;
6084
6085 mlines = currentMenu->lastOn>>8;
6086 max = (INT16)((UINT8)(currentMenu->lastOn & 0xFF)*8);
6087
6088 // hack: draw RA background in RA menus
6089 if (gamestate == GS_TIMEATTACK)
6090 {
6091 if (curbgcolor >= 0)
6092 V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, curbgcolor);
6093 else if (!curbghide || !titlemapinaction)
6094 {
6095 if (levellistmode == LLM_NIGHTSATTACK)
6096 {
6097 V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 158);
6098 M_DrawNightsAttackMountains();
6099 }
6100 else
6101 {
6102 F_SkyScroll(curbgxspeed, curbgyspeed, curbgname);
6103 if (!strncmp("RECATKBG", curbgname, 8))
6104 M_DrawRecordAttackForeground();
6105 }
6106 }
6107 if (curfadevalue)
6108 V_DrawFadeScreen(0xFF00, curfadevalue);
6109 }
6110
6111 M_DrawTextBox(currentMenu->x, y - 8, (max+7)>>3, mlines);
6112
6113 while (*(msg+start))
6114 {
6115 size_t len = strlen(msg+start);
6116
6117 for (i = 0; i < len; i++)
6118 {
6119 if (*(msg+start+i) == '\n')
6120 {
6121 memset(string, 0, MAXMSGLINELEN);
6122 if (i >= MAXMSGLINELEN)
6123 {
6124 CONS_Printf("M_DrawMessageMenu: too long segment in %s\n", msg);
6125 return;
6126 }
6127 else
6128 {
6129 strncpy(string,msg+start, i);
6130 string[i] = '\0';
6131 start += i;
6132 i = (size_t)-1; //added : 07-02-98 : damned!
6133 start++;
6134 }
6135 break;
6136 }
6137 }
6138
6139 if (i == strlen(msg+start))
6140 {
6141 if (i >= MAXMSGLINELEN)
6142 {
6143 CONS_Printf("M_DrawMessageMenu: too long segment in %s\n", msg);
6144 return;
6145 }
6146 else
6147 {
6148 strcpy(string, msg + start);
6149 start += i;
6150 }
6151 }
6152
6153 V_DrawString((BASEVIDWIDTH - V_StringWidth(string, 0))/2,y,V_ALLOWLOWERCASE,string);
6154 y += 8; //hu_font[0]->height;
6155 }
6156 }
6157
6158 // default message handler
6159 static void M_StopMessage(INT32 choice)
6160 {
6161 (void)choice;
6162 if (menuactive)
6163 M_SetupNextMenu(MessageDef.prevMenu);
6164 }
6165
6166 // =========
6167 // IMAGEDEFS
6168 // =========
6169
6170 // Draw an Image Def. Aka, Help images.
6171 // Defines what image is used in (menuitem_t)->text.
6172 // You can even put multiple images in one menu!
6173 static void M_DrawImageDef(void)
6174 {
6175 // Grr. Need to autodetect for pic_ts.
6176 pic_t *pictest = (pic_t *)W_CacheLumpName(currentMenu->menuitems[itemOn].text,PU_CACHE);
6177 if (!pictest->zero)
6178 V_DrawScaledPic(0,0,0,W_GetNumForName(currentMenu->menuitems[itemOn].text));
6179 else
6180 {
6181 patch_t *patch = W_CachePatchName(currentMenu->menuitems[itemOn].text,PU_PATCH);
6182 if (patch->width <= BASEVIDWIDTH)
6183 V_DrawScaledPatch(0,0,0,patch);
6184 else
6185 V_DrawSmallScaledPatch(0,0,0,patch);
6186 }
6187
6188 if (currentMenu->numitems > 1)
6189 V_DrawString(0,192,V_TRANSLUCENT, va("PAGE %d of %hd", itemOn+1, currentMenu->numitems));
6190 }
6191
6192 // Handles the ImageDefs. Just a specialized function that
6193 // uses left and right movement.
6194 static void M_HandleImageDef(INT32 choice)
6195 {
6196 switch (choice)
6197 {
6198 case KEY_RIGHTARROW:
6199 if (currentMenu->numitems == 1)
6200 break;
6201
6202 S_StartSound(NULL, sfx_menu1);
6203 if (itemOn >= (INT16)(currentMenu->numitems-1))
6204 itemOn = 0;
6205 else itemOn++;
6206 break;
6207
6208 case KEY_LEFTARROW:
6209 if (currentMenu->numitems == 1)
6210 break;
6211
6212 S_StartSound(NULL, sfx_menu1);
6213 if (!itemOn)
6214 itemOn = currentMenu->numitems - 1;
6215 else itemOn--;
6216 break;
6217
6218 case KEY_ESCAPE:
6219 case KEY_ENTER:
6220 M_ClearMenus(true);
6221 break;
6222 }
6223 }
6224
6225 // ======================
6226 // MISC MAIN MENU OPTIONS
6227 // ======================
6228
6229 static void M_AddonsOptions(INT32 choice)
6230 {
6231 (void)choice;
6232 Addons_option_Onchange();
6233
6234 M_SetupNextMenu(&OP_AddonsOptionsDef);
6235 }
6236
6237 #define LOCATIONSTRING1 "Visit \x83SRB2.ORG/ADDONS\x80 to get & make addons!"
6238 //#define LOCATIONSTRING2 "Visit \x88SRB2.ORG/ADDONS\x80 to get & make addons!"
6239
6240 static void M_LoadAddonsPatches(void)
6241 {
6242 addonsp[EXT_FOLDER] = W_CachePatchName("M_FFLDR", PU_PATCH);
6243 addonsp[EXT_UP] = W_CachePatchName("M_FBACK", PU_PATCH);
6244 addonsp[EXT_NORESULTS] = W_CachePatchName("M_FNOPE", PU_PATCH);
6245 addonsp[EXT_TXT] = W_CachePatchName("M_FTXT", PU_PATCH);
6246 addonsp[EXT_CFG] = W_CachePatchName("M_FCFG", PU_PATCH);
6247 addonsp[EXT_WAD] = W_CachePatchName("M_FWAD", PU_PATCH);
6248 #ifdef USE_KART
6249 addonsp[EXT_KART] = W_CachePatchName("M_FKART", PU_PATCH);
6250 #endif
6251 addonsp[EXT_PK3] = W_CachePatchName("M_FPK3", PU_PATCH);
6252 addonsp[EXT_SOC] = W_CachePatchName("M_FSOC", PU_PATCH);
6253 addonsp[EXT_LUA] = W_CachePatchName("M_FLUA", PU_PATCH);
6254 addonsp[NUM_EXT] = W_CachePatchName("M_FUNKN", PU_PATCH);
6255 addonsp[NUM_EXT+1] = W_CachePatchName("M_FSEL", PU_PATCH);
6256 addonsp[NUM_EXT+2] = W_CachePatchName("M_FLOAD", PU_PATCH);
6257 addonsp[NUM_EXT+3] = W_CachePatchName("M_FSRCH", PU_PATCH);
6258 addonsp[NUM_EXT+4] = W_CachePatchName("M_FSAVE", PU_PATCH);
6259 }
6260
6261 static void M_Addons(INT32 choice)
6262 {
6263 const char *pathname = ".";
6264
6265 (void)choice;
6266
6267 // If M_GetGameypeColor() is ever ported from Kart, then remove this.
6268 highlightflags = V_YELLOWMAP;
6269 recommendedflags = V_GREENMAP;
6270 warningflags = V_REDMAP;
6271
6272 #if 1
6273 if (cv_addons_option.value == 0)
6274 pathname = usehome ? srb2home : srb2path;
6275 else if (cv_addons_option.value == 1)
6276 pathname = srb2home;
6277 else if (cv_addons_option.value == 2)
6278 pathname = srb2path;
6279 else
6280 #endif
6281 if (cv_addons_option.value == 3 && *cv_addons_folder.string != '\0')
6282 pathname = cv_addons_folder.string;
6283
6284 strlcpy(menupath, pathname, 1024);
6285 menupathindex[(menudepthleft = menudepth-1)] = strlen(menupath) + 1;
6286
6287 if (menupath[menupathindex[menudepthleft]-2] != PATHSEP[0])
6288 {
6289 menupath[menupathindex[menudepthleft]-1] = PATHSEP[0];
6290 menupath[menupathindex[menudepthleft]] = 0;
6291 }
6292 else
6293 --menupathindex[menudepthleft];
6294
6295 if (!preparefilemenu(false))
6296 {
6297 M_StartMessage(va("No files/folders found.\n\n%s\n\n(Press a key)\n",LOCATIONSTRING1),NULL,MM_NOTHING);
6298 // (recommendedflags == V_SKYMAP ? LOCATIONSTRING2 : LOCATIONSTRING1))
6299 return;
6300 }
6301 else
6302 dir_on[menudepthleft] = 0;
6303
6304 M_LoadAddonsPatches();
6305
6306 MISC_AddonsDef.prevMenu = currentMenu;
6307 M_SetupNextMenu(&MISC_AddonsDef);
6308 }
6309
6310 #define width 4
6311 #define vpadding 27
6312 #define h (BASEVIDHEIGHT-(2*vpadding))
6313 #define NUMCOLOURS 8 // when toast's coding it's british english hacker fucker
6314 static void M_DrawTemperature(INT32 x, fixed_t t)
6315 {
6316 INT32 y;
6317
6318 // bounds check
6319 if (t > FRACUNIT)
6320 t = FRACUNIT;
6321 /*else if (t < 0) -- not needed
6322 t = 0;*/
6323
6324 // scale
6325 if (t > 1)
6326 t = (FixedMul(h<<FRACBITS, t)>>FRACBITS);
6327
6328 // border
6329 V_DrawFill(x - 1, vpadding, 1, h, 3);
6330 V_DrawFill(x + width, vpadding, 1, h, 3);
6331 V_DrawFill(x - 1, vpadding-1, width+2, 1, 3);
6332 V_DrawFill(x - 1, vpadding+h, width+2, 1, 3);
6333
6334 // bar itself
6335 y = h;
6336 if (t)
6337 for (t = h - t; y > 0; y--)
6338 {
6339 UINT8 colours[NUMCOLOURS] = {42, 40, 58, 222, 65, 90, 97, 98};
6340 UINT8 c;
6341 if (y <= t) break;
6342 if (y+vpadding >= BASEVIDHEIGHT/2)
6343 c = 113;
6344 else
6345 c = colours[(NUMCOLOURS*(y-1))/(h/2)];
6346 V_DrawFill(x, y-1 + vpadding, width, 1, c);
6347 }
6348
6349 // fill the rest of the backing
6350 if (y)
6351 V_DrawFill(x, vpadding, width, y, 27);
6352 }
6353 #undef width
6354 #undef vpadding
6355 #undef h
6356 #undef NUMCOLOURS
6357
6358 static char *M_AddonsHeaderPath(void)
6359 {
6360 UINT32 len;
6361 static char header[1024];
6362
6363 strlcpy(header, va("%s folder%s", cv_addons_option.string, menupath+menupathindex[menudepth-1]-1), 1024);
6364 len = strlen(header);
6365 if (len > 34)
6366 {
6367 len = len-34;
6368 header[len] = header[len+1] = header[len+2] = '.';
6369 }
6370 else
6371 len = 0;
6372
6373 return header+len;
6374 }
6375
6376 #define UNEXIST S_StartSound(NULL, sfx_lose);\
6377 M_SetupNextMenu(MISC_AddonsDef.prevMenu);\
6378 M_StartMessage(va("\x82%s\x80\nThis folder no longer exists!\nAborting to main menu.\n\n(Press a key)\n", M_AddonsHeaderPath()),NULL,MM_NOTHING)
6379
6380 #define CLEARNAME Z_Free(refreshdirname);\
6381 refreshdirname = NULL
6382
6383 static void M_AddonsClearName(INT32 choice)
6384 {
6385 (void)choice;
6386 CLEARNAME;
6387 }
6388
6389 // returns whether to do message draw
6390 static boolean M_AddonsRefresh(void)
6391 {
6392 if ((refreshdirmenu & REFRESHDIR_NORMAL) && !preparefilemenu(true))
6393 {
6394 UNEXIST;
6395 return true;
6396 }
6397
6398 if (refreshdirmenu & REFRESHDIR_ADDFILE)
6399 {
6400 char *message = NULL;
6401
6402 if (refreshdirmenu & REFRESHDIR_NOTLOADED)
6403 {
6404 S_StartSound(NULL, sfx_lose);
6405 if (refreshdirmenu & REFRESHDIR_MAX)
6406 message = va("%c%s\x80\nMaximum number of add-ons reached.\nA file could not be loaded.\nIf you wish to play with this add-on, restart the game to clear existing ones.\n\n(Press a key)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), refreshdirname);
6407 else
6408 message = va("%c%s\x80\nA file was not loaded.\nCheck the console log for more information.\n\n(Press a key)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), refreshdirname);
6409 }
6410 else if (refreshdirmenu & (REFRESHDIR_WARNING|REFRESHDIR_ERROR))
6411 {
6412 S_StartSound(NULL, sfx_skid);
6413 message = va("%c%s\x80\nA file was loaded with %s.\nCheck the console log for more information.\n\n(Press a key)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), refreshdirname, ((refreshdirmenu & REFRESHDIR_ERROR) ? "errors" : "warnings"));
6414 }
6415
6416 if (message)
6417 {
6418 M_StartMessage(message,M_AddonsClearName,MM_NOTHING);
6419 return true;
6420 }
6421
6422 S_StartSound(NULL, sfx_strpst);
6423 CLEARNAME;
6424 }
6425
6426 return false;
6427 }
6428
6429 static void M_DrawAddons(void)
6430 {
6431 INT32 x, y;
6432 size_t i, m;
6433 size_t t, b; // top and bottom item #s to draw in directory
6434 const UINT8 *flashcol = NULL;
6435 UINT8 hilicol;
6436
6437 // hack - need to refresh at end of frame to handle addfile...
6438 if (refreshdirmenu & M_AddonsRefresh())
6439 {
6440 M_DrawMessageMenu();
6441 return;
6442 }
6443
6444 if (Playing())
6445 V_DrawCenteredString(BASEVIDWIDTH/2, 5, warningflags, "Adding files mid-game may cause problems.");
6446 else
6447 V_DrawCenteredString(BASEVIDWIDTH/2, 5, 0, LOCATIONSTRING1);
6448 // (recommendedflags == V_SKYMAP ? LOCATIONSTRING2 : LOCATIONSTRING1)
6449
6450 if (numwadfiles <= mainwads+1)
6451 y = 0;
6452 else if (numwadfiles >= MAX_WADFILES)
6453 y = FRACUNIT;
6454 else
6455 {
6456 x = FixedDiv(((ssize_t)(numwadfiles) - (ssize_t)(mainwads+1))<<FRACBITS, ((ssize_t)MAX_WADFILES - (ssize_t)(mainwads+1))<<FRACBITS);
6457 y = FixedDiv((((ssize_t)packetsizetally-(ssize_t)mainwadstally)<<FRACBITS), ((((ssize_t)MAXFILENEEDED*sizeof(UINT8)-(ssize_t)mainwadstally)-(5+22))<<FRACBITS)); // 5+22 = (a.ext + checksum length) is minimum addition to packet size tally
6458 if (x > y)
6459 y = x;
6460 if (y > FRACUNIT) // happens because of how we're shrinkin' it a little
6461 y = FRACUNIT;
6462 }
6463
6464 M_DrawTemperature(BASEVIDWIDTH - 19 - 5, y);
6465
6466 // DRAW MENU
6467 x = currentMenu->x;
6468 y = currentMenu->y + 1;
6469
6470 hilicol = 0; // white
6471
6472 #define boxwidth (MAXSTRINGLENGTH*8+6)
6473
6474 // draw the file path and the top white + black lines of the box
6475 V_DrawString(x-21, (y - 16) + (lsheadingheight - 12), highlightflags|V_ALLOWLOWERCASE, M_AddonsHeaderPath());
6476 V_DrawFill(x-21, (y - 16) + (lsheadingheight - 3), boxwidth, 1, hilicol);
6477 V_DrawFill(x-21, (y - 16) + (lsheadingheight - 2), boxwidth, 1, 30);
6478
6479 m = (BASEVIDHEIGHT - currentMenu->y + 2) - (y - 1);
6480 // addons menu back color
6481 V_DrawFill(x-21, y - 1, boxwidth, m, 159);
6482
6483 // The directory is too small for a scrollbar, so just draw a tall white line
6484 if (sizedirmenu <= addonmenusize)
6485 {
6486 t = 0; // first item
6487 b = sizedirmenu - 1; // last item
6488 i = 0; // "scrollbar" at "top" position
6489 }
6490 else
6491 {
6492 size_t q = m;
6493 m = (addonmenusize * m)/sizedirmenu; // height of scroll bar
6494 if (dir_on[menudepthleft] <= numaddonsshown) // all the way up
6495 {
6496 t = 0; // first item
6497 b = addonmenusize - 1; //9th item
6498 i = 0; // scrollbar at top position
6499 }
6500 else if (dir_on[menudepthleft] >= sizedirmenu - (numaddonsshown + 1)) // all the way down
6501 {
6502 t = sizedirmenu - addonmenusize; // # 9th last
6503 b = sizedirmenu - 1; // last item
6504 i = q-m; // scrollbar at bottom position
6505 }
6506 else // somewhere in the middle
6507 {
6508 t = dir_on[menudepthleft] - numaddonsshown; // 4 items above
6509 b = dir_on[menudepthleft] + numaddonsshown; // 4 items below
6510 i = (t * (q-m))/(sizedirmenu - addonmenusize); // calculate position of scrollbar
6511 }
6512 }
6513
6514 // draw the scrollbar!
6515 V_DrawFill((x-21) + boxwidth-1, (y - 1) + i, 1, m, hilicol);
6516
6517 #undef boxwidth
6518
6519 // draw up arrow that bobs up and down
6520 if (t != 0)
6521 V_DrawString(19, y+4 - (skullAnimCounter/5), highlightflags, "\x1A");
6522
6523 // make the selection box flash yellow
6524 if (skullAnimCounter < 4)
6525 flashcol = V_GetStringColormap(highlightflags);
6526
6527 // draw icons and item names
6528 for (i = t; i <= b; i++)
6529 {
6530 UINT32 flags = V_ALLOWLOWERCASE;
6531 if (y > BASEVIDHEIGHT) break;
6532 if (dirmenu[i])
6533 #define type (UINT8)(dirmenu[i][DIR_TYPE])
6534 {
6535 if (type & EXT_LOADED)
6536 {
6537 flags |= V_TRANSLUCENT;
6538 V_DrawSmallScaledPatch(x-(16+4), y, V_TRANSLUCENT, addonsp[(type & ~EXT_LOADED)]);
6539 V_DrawSmallScaledPatch(x-(16+4), y, 0, addonsp[NUM_EXT+2]);
6540 }
6541 else
6542 V_DrawSmallScaledPatch(x-(16+4), y, 0, addonsp[(type & ~EXT_LOADED)]);
6543
6544 // draw selection box for the item currently selected
6545 if ((size_t)i == dir_on[menudepthleft])
6546 {
6547 V_DrawFixedPatch((x-(16+4))<<FRACBITS, (y)<<FRACBITS, FRACUNIT/2, 0, addonsp[NUM_EXT+1], flashcol);
6548 flags = V_ALLOWLOWERCASE|highlightflags;
6549 }
6550
6551 // draw name of the item, use ... if too long
6552 #define charsonside 14
6553 if (dirmenu[i][DIR_LEN] > (charsonside*2 + 3))
6554 V_DrawString(x, y+4, flags, va("%.*s...%s", charsonside, dirmenu[i]+DIR_STRING, dirmenu[i]+DIR_STRING+dirmenu[i][DIR_LEN]-(charsonside+1)));
6555 #undef charsonside
6556 else
6557 V_DrawString(x, y+4, flags, dirmenu[i]+DIR_STRING);
6558 }
6559 #undef type
6560 y += 16;
6561 }
6562
6563 // draw down arrow that bobs down and up
6564 if (b != sizedirmenu)
6565 V_DrawString(19, y-12 + (skullAnimCounter/5), highlightflags, "\x1B");
6566
6567 // draw search box
6568 y = BASEVIDHEIGHT - currentMenu->y + 1;
6569
6570 M_DrawTextBox(x - (21 + 5), y, MAXSTRINGLENGTH, 1);
6571 if (menusearch[0])
6572 V_DrawString(x - 18, y + 8, V_ALLOWLOWERCASE, menusearch+1);
6573 else
6574 V_DrawString(x - 18, y + 8, V_ALLOWLOWERCASE|V_TRANSLUCENT, "Type to search...");
6575 if (skullAnimCounter < 4)
6576 V_DrawCharacter(x - 18 + V_StringWidth(menusearch+1, 0), y + 8,
6577 '_' | 0x80, false);
6578
6579 // draw search icon
6580 x -= (21 + 5 + 16);
6581 V_DrawSmallScaledPatch(x, y + 4, (menusearch[0] ? 0 : V_TRANSLUCENT), addonsp[NUM_EXT+3]);
6582
6583 // draw save icon
6584 x = BASEVIDWIDTH - x - 16;
6585 V_DrawSmallScaledPatch(x, y + 4, ((!modifiedgame || savemoddata) ? 0 : V_TRANSLUCENT), addonsp[NUM_EXT+4]);
6586
6587 if (modifiedgame)
6588 V_DrawSmallScaledPatch(x, y + 4, 0, addonsp[NUM_EXT+2]);
6589 }
6590
6591 static void M_AddonExec(INT32 ch)
6592 {
6593 if (ch != 'y' && ch != KEY_ENTER)
6594 return;
6595
6596 S_StartSound(NULL, sfx_zoom);
6597 COM_BufAddText(va("exec \"%s%s\"", menupath, dirmenu[dir_on[menudepthleft]]+DIR_STRING));
6598 }
6599
6600 #define len menusearch[0]
6601 static boolean M_ChangeStringAddons(INT32 choice)
6602 {
6603 if (shiftdown && choice >= 32 && choice <= 127)
6604 choice = shiftxform[choice];
6605
6606 switch (choice)
6607 {
6608 case KEY_DEL:
6609 if (len)
6610 {
6611 len = menusearch[1] = 0;
6612 return true;
6613 }
6614 break;
6615 case KEY_BACKSPACE:
6616 if (len)
6617 {
6618 menusearch[1+--len] = 0;
6619 return true;
6620 }
6621 break;
6622 default:
6623 if (choice >= 32 && choice <= 127)
6624 {
6625 if (len < MAXSTRINGLENGTH - 1)
6626 {
6627 menusearch[1+len++] = (char)choice;
6628 menusearch[1+len] = 0;
6629 return true;
6630 }
6631 }
6632 break;
6633 }
6634 return false;
6635 }
6636 #undef len
6637
6638 static void M_HandleAddons(INT32 choice)
6639 {
6640 boolean exitmenu = false; // exit to previous menu
6641
6642 if (M_ChangeStringAddons(choice))
6643 {
6644 char *tempname = NULL;
6645 if (dirmenu && dirmenu[dir_on[menudepthleft]])
6646 tempname = Z_StrDup(dirmenu[dir_on[menudepthleft]]+DIR_STRING); // don't need to I_Error if can't make - not important, just QoL
6647 #if 0 // much slower
6648 if (!preparefilemenu(true))
6649 {
6650 UNEXIST;
6651 return;
6652 }
6653 #else // streamlined
6654 searchfilemenu(tempname);
6655 #endif
6656 }
6657
6658 switch (choice)
6659 {
6660 case KEY_DOWNARROW:
6661 if (dir_on[menudepthleft] < sizedirmenu-1)
6662 dir_on[menudepthleft]++;
6663 S_StartSound(NULL, sfx_menu1);
6664 break;
6665 case KEY_UPARROW:
6666 if (dir_on[menudepthleft])
6667 dir_on[menudepthleft]--;
6668 S_StartSound(NULL, sfx_menu1);
6669 break;
6670 case KEY_PGDN:
6671 {
6672 UINT8 i;
6673 for (i = numaddonsshown; i && (dir_on[menudepthleft] < sizedirmenu-1); i--)
6674 dir_on[menudepthleft]++;
6675 }
6676 S_StartSound(NULL, sfx_menu1);
6677 break;
6678 case KEY_PGUP:
6679 {
6680 UINT8 i;
6681 for (i = numaddonsshown; i && (dir_on[menudepthleft]); i--)
6682 dir_on[menudepthleft]--;
6683 }
6684 S_StartSound(NULL, sfx_menu1);
6685 break;
6686 case KEY_ENTER:
6687 {
6688 boolean refresh = true;
6689 if (!dirmenu[dir_on[menudepthleft]])
6690 S_StartSound(NULL, sfx_lose);
6691 else
6692 {
6693 switch (dirmenu[dir_on[menudepthleft]][DIR_TYPE])
6694 {
6695 case EXT_FOLDER:
6696 strcpy(&menupath[menupathindex[menudepthleft]],dirmenu[dir_on[menudepthleft]]+DIR_STRING);
6697 if (menudepthleft)
6698 {
6699 menupathindex[--menudepthleft] = strlen(menupath);
6700 menupath[menupathindex[menudepthleft]] = 0;
6701
6702 if (!preparefilemenu(false))
6703 {
6704 S_StartSound(NULL, sfx_skid);
6705 M_StartMessage(va("%c%s\x80\nThis folder is empty.\n\n(Press a key)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), M_AddonsHeaderPath()),NULL,MM_NOTHING);
6706 menupath[menupathindex[++menudepthleft]] = 0;
6707
6708 if (!preparefilemenu(true))
6709 {
6710 UNEXIST;
6711 return;
6712 }
6713 }
6714 else
6715 {
6716 S_StartSound(NULL, sfx_menu1);
6717 dir_on[menudepthleft] = 1;
6718 }
6719 refresh = false;
6720 }
6721 else
6722 {
6723 S_StartSound(NULL, sfx_lose);
6724 M_StartMessage(va("%c%s\x80\nThis folder is too deep to navigate to!\n\n(Press a key)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), M_AddonsHeaderPath()),NULL,MM_NOTHING);
6725 menupath[menupathindex[menudepthleft]] = 0;
6726 }
6727 break;
6728 case EXT_UP:
6729 S_StartSound(NULL, sfx_menu1);
6730 menupath[menupathindex[++menudepthleft]] = 0;
6731 if (!preparefilemenu(false))
6732 {
6733 UNEXIST;
6734 return;
6735 }
6736 break;
6737 case EXT_TXT:
6738 M_StartMessage(va("%c%s\x80\nThis file may not be a console script.\nAttempt to run anyways? \n\n(Press 'Y' to confirm)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), dirmenu[dir_on[menudepthleft]]+DIR_STRING),M_AddonExec,MM_YESNO);
6739 break;
6740 case EXT_CFG:
6741 M_AddonExec(KEY_ENTER);
6742 break;
6743 case EXT_LUA:
6744 case EXT_SOC:
6745 case EXT_WAD:
6746 #ifdef USE_KART
6747 case EXT_KART:
6748 #endif
6749 case EXT_PK3:
6750 COM_BufAddText(va("addfile \"%s%s\"", menupath, dirmenu[dir_on[menudepthleft]]+DIR_STRING));
6751 break;
6752 default:
6753 S_StartSound(NULL, sfx_lose);
6754 }
6755 }
6756 if (refresh)
6757 refreshdirmenu |= REFRESHDIR_NORMAL;
6758 }
6759 break;
6760
6761 case KEY_ESCAPE:
6762 exitmenu = true;
6763 break;
6764
6765 default:
6766 break;
6767 }
6768 if (exitmenu)
6769 {
6770 closefilemenu(true);
6771
6772 // secrets disabled by addfile...
6773 MainMenu[secrets].status = (M_AnySecretUnlocked()) ? (IT_STRING | IT_CALL) : (IT_DISABLED);
6774
6775 if (currentMenu->prevMenu)
6776 M_SetupNextMenu(currentMenu->prevMenu);
6777 else
6778 M_ClearMenus(true);
6779 }
6780 }
6781
6782 static void M_PandorasBox(INT32 choice)
6783 {
6784 (void)choice;
6785 if (maptol & TOL_NIGHTS)
6786 CV_StealthSetValue(&cv_dummyrings, max(players[consoleplayer].spheres, 0));
6787 else
6788 CV_StealthSetValue(&cv_dummyrings, max(players[consoleplayer].rings, 0));
6789 if (players[consoleplayer].lives == INFLIVES)
6790 CV_StealthSet(&cv_dummylives, "Infinite");
6791 else
6792 CV_StealthSetValue(&cv_dummylives, max(players[consoleplayer].lives, 1));
6793 CV_StealthSetValue(&cv_dummycontinues, players[consoleplayer].continues);
6794 SR_PandorasBox[3].status = (continuesInSession) ? (IT_STRING | IT_CVAR) : (IT_GRAYEDOUT);
6795 SR_PandorasBox[6].status = (players[consoleplayer].charflags & SF_SUPER) ? (IT_GRAYEDOUT) : (IT_STRING | IT_CALL);
6796 SR_PandorasBox[7].status = (emeralds == ((EMERALD7)*2)-1) ? (IT_GRAYEDOUT) : (IT_STRING | IT_CALL);
6797 M_SetupNextMenu(&SR_PandoraDef);
6798 }
6799
6800 static boolean M_ExitPandorasBox(void)
6801 {
6802 if (cv_dummyrings.value != max(players[consoleplayer].rings, 0))
6803 {
6804 if (maptol & TOL_NIGHTS)
6805 COM_ImmedExecute(va("setspheres %d", cv_dummyrings.value));
6806 else
6807 COM_ImmedExecute(va("setrings %d", cv_dummyrings.value));
6808 }
6809 if (cv_dummylives.value != players[consoleplayer].lives)
6810 COM_ImmedExecute(va("setlives %d", cv_dummylives.value));
6811 if (continuesInSession && cv_dummycontinues.value != players[consoleplayer].continues)
6812 COM_ImmedExecute(va("setcontinues %d", cv_dummycontinues.value));
6813 return true;
6814 }
6815
6816 static void M_ChangeLevel(INT32 choice)
6817 {
6818 char mapname[6];
6819 (void)choice;
6820
6821 strlcpy(mapname, G_BuildMapName(cv_nextmap.value), sizeof (mapname));
6822 strlwr(mapname);
6823 mapname[5] = '\0';
6824
6825 M_ClearMenus(true);
6826 COM_BufAddText(va("map %s -gametype \"%s\"\n", mapname, cv_newgametype.string));
6827 }
6828
6829 static void M_ConfirmSpectate(INT32 choice)
6830 {
6831 (void)choice;
6832 // We allow switching to spectator even if team changing is not allowed
6833 M_ClearMenus(true);
6834 COM_ImmedExecute("changeteam spectator");
6835 }
6836
6837 static void M_ConfirmEnterGame(INT32 choice)
6838 {
6839 (void)choice;
6840 if (!cv_allowteamchange.value)
6841 {
6842 M_StartMessage(M_GetText("The server is not allowing\nteam changes at this time.\nPress a key.\n"), NULL, MM_NOTHING);
6843 return;
6844 }
6845 M_ClearMenus(true);
6846 COM_ImmedExecute("changeteam playing");
6847 }
6848
6849 static void M_ConfirmTeamScramble(INT32 choice)
6850 {
6851 (void)choice;
6852 M_ClearMenus(true);
6853
6854 switch (cv_dummyscramble.value)
6855 {
6856 case 0:
6857 COM_ImmedExecute("teamscramble 1");
6858 break;
6859 case 1:
6860 COM_ImmedExecute("teamscramble 2");
6861 break;
6862 }
6863 }
6864
6865 static void M_ConfirmTeamChange(INT32 choice)
6866 {
6867 (void)choice;
6868 if (!cv_allowteamchange.value && cv_dummyteam.value)
6869 {
6870 M_StartMessage(M_GetText("The server is not allowing\nteam changes at this time.\nPress a key.\n"), NULL, MM_NOTHING);
6871 return;
6872 }
6873
6874 M_ClearMenus(true);
6875
6876 switch (cv_dummyteam.value)
6877 {
6878 case 0:
6879 COM_ImmedExecute("changeteam spectator");
6880 break;
6881 case 1:
6882 COM_ImmedExecute("changeteam red");
6883 break;
6884 case 2:
6885 COM_ImmedExecute("changeteam blue");
6886 break;
6887 }
6888 }
6889
6890 static void M_Options(INT32 choice)
6891 {
6892 (void)choice;
6893
6894 // if the player is not admin or server, disable server options
6895 OP_MainMenu[5].status = (Playing() && !(server || IsPlayerAdmin(consoleplayer))) ? (IT_GRAYEDOUT) : (IT_STRING|IT_CALL);
6896
6897 // if the player is playing _at all_, disable the erase data options
6898 OP_DataOptionsMenu[2].status = (Playing()) ? (IT_GRAYEDOUT) : (IT_STRING|IT_SUBMENU);
6899
6900 OP_MainDef.prevMenu = currentMenu;
6901 M_SetupNextMenu(&OP_MainDef);
6902 }
6903
6904 static void M_RetryResponse(INT32 ch)
6905 {
6906 if (ch != 'y' && ch != KEY_ENTER)
6907 return;
6908
6909 if (!&players[consoleplayer] || netgame || multiplayer) // Should never happen!
6910 return;
6911
6912 M_ClearMenus(true);
6913 G_SetRetryFlag();
6914 }
6915
6916 static void M_Retry(INT32 choice)
6917 {
6918 (void)choice;
6919 if (marathonmode)
6920 {
6921 M_RetryResponse(KEY_ENTER);
6922 return;
6923 }
6924 M_StartMessage(M_GetText("Retry this act from the last starpost?\n\n(Press 'Y' to confirm)\n"),M_RetryResponse,MM_YESNO);
6925 }
6926
6927 static void M_SelectableClearMenus(INT32 choice)
6928 {
6929 (void)choice;
6930 M_ClearMenus(true);
6931 }
6932
6933 // ======
6934 // CHEATS
6935 // ======
6936
6937 static void M_UltimateCheat(INT32 choice)
6938 {
6939 (void)choice;
6940 LUAh_GameQuit(true);
6941 I_Quit();
6942 }
6943
6944 static void M_AllowSuper(INT32 choice)
6945 {
6946 (void)choice;
6947
6948 players[consoleplayer].charflags |= SF_SUPER;
6949 M_StartMessage(M_GetText("You are now capable of turning super.\nRemember to get all the emeralds!\n"),NULL,MM_NOTHING);
6950 SR_PandorasBox[6].status = IT_GRAYEDOUT;
6951
6952 G_SetGameModified(multiplayer);
6953 }
6954
6955 static void M_GetAllEmeralds(INT32 choice)
6956 {
6957 (void)choice;
6958
6959 emeralds = ((EMERALD7)*2)-1;
6960 M_StartMessage(M_GetText("You now have all 7 emeralds.\nUse them wisely.\nWith great power comes great ring drain.\n"),NULL,MM_NOTHING);
6961 SR_PandorasBox[7].status = IT_GRAYEDOUT;
6962
6963 G_SetGameModified(multiplayer);
6964 }
6965
6966 static void M_DestroyRobotsResponse(INT32 ch)
6967 {
6968 if (ch != 'y' && ch != KEY_ENTER)
6969 return;
6970
6971 // Destroy all robots
6972 P_DestroyRobots();
6973
6974 G_SetGameModified(multiplayer);
6975 }
6976
6977 static void M_DestroyRobots(INT32 choice)
6978 {
6979 (void)choice;
6980
6981 M_StartMessage(M_GetText("Do you want to destroy all\nrobots in the current level?\n\n(Press 'Y' to confirm)\n"),M_DestroyRobotsResponse,MM_YESNO);
6982 }
6983
6984 static void M_LevelSelectWarp(INT32 choice)
6985 {
6986 boolean fromloadgame = (currentMenu == &SP_LevelSelectDef);
6987
6988 (void)choice;
6989
6990 if (W_CheckNumForName(G_BuildMapName(cv_nextmap.value)) == LUMPERROR)
6991 {
6992 CONS_Alert(CONS_WARNING, "Internal game map '%s' not found\n", G_BuildMapName(cv_nextmap.value));
6993 return;
6994 }
6995
6996 startmap = (INT16)(cv_nextmap.value);
6997
6998 fromlevelselect = true;
6999
7000 if (fromloadgame)
7001 G_LoadGame((UINT32)cursaveslot, startmap);
7002 else
7003 {
7004 cursaveslot = 0;
7005 M_SetupChoosePlayer(0);
7006 }
7007 }
7008
7009 // ========
7010 // SKY ROOM
7011 // ========
7012
7013 UINT8 skyRoomMenuTranslations[MAXUNLOCKABLES];
7014
7015 static boolean checklist_cangodown; // uuuueeerggghhhh HACK
7016
7017 static void M_HandleChecklist(INT32 choice)
7018 {
7019 INT32 j;
7020 switch (choice)
7021 {
7022 case KEY_DOWNARROW:
7023 S_StartSound(NULL, sfx_menu1);
7024 if ((check_on != MAXUNLOCKABLES) && checklist_cangodown)
7025 {
7026 for (j = check_on+1; j < MAXUNLOCKABLES; j++)
7027 {
7028 if (!unlockables[j].name[0])
7029 continue;
7030 // if (unlockables[j].nochecklist)
7031 // continue;
7032 if (!unlockables[j].conditionset)
7033 continue;
7034 if (unlockables[j].conditionset > MAXCONDITIONSETS)
7035 continue;
7036 if (!unlockables[j].unlocked && unlockables[j].showconditionset && !M_Achieved(unlockables[j].showconditionset))
7037 continue;
7038 if (unlockables[j].conditionset == unlockables[check_on].conditionset)
7039 continue;
7040 break;
7041 }
7042 if (j != MAXUNLOCKABLES)
7043 check_on = j;
7044 }
7045 return;
7046
7047 case KEY_UPARROW:
7048 S_StartSound(NULL, sfx_menu1);
7049 if (check_on)
7050 {
7051 for (j = check_on-1; j > -1; j--)
7052 {
7053 if (!unlockables[j].name[0])
7054 continue;
7055 // if (unlockables[j].nochecklist)
7056 // continue;
7057 if (!unlockables[j].conditionset)
7058 continue;
7059 if (unlockables[j].conditionset > MAXCONDITIONSETS)
7060 continue;
7061 if (!unlockables[j].unlocked && unlockables[j].showconditionset && !M_Achieved(unlockables[j].showconditionset))
7062 continue;
7063 if (j && unlockables[j].conditionset == unlockables[j-1].conditionset)
7064 continue;
7065 break;
7066 }
7067 if (j != -1)
7068 check_on = j;
7069 }
7070 return;
7071
7072 case KEY_ESCAPE:
7073 if (currentMenu->prevMenu)
7074 M_SetupNextMenu(currentMenu->prevMenu);
7075 else
7076 M_ClearMenus(true);
7077 return;
7078 default:
7079 break;
7080 }
7081 }
7082
7083 #define addy(add) { y += add; if ((y - currentMenu->y) > (scrollareaheight*2)) goto finishchecklist; }
7084
7085 static void M_DrawChecklist(void)
7086 {
7087 INT32 i = check_on, j = 0, y = currentMenu->y;
7088 UINT32 condnum, previd, maxcond;
7089 condition_t *cond;
7090
7091 // draw title (or big pic)
7092 M_DrawMenuTitle();
7093
7094 if (check_on)
7095 V_DrawString(10, y-(skullAnimCounter/5), V_YELLOWMAP, "\x1A");
7096
7097 while (i < MAXUNLOCKABLES)
7098 {
7099 if (unlockables[i].name[0] == 0 //|| unlockables[i].nochecklist
7100 || !unlockables[i].conditionset || unlockables[i].conditionset > MAXCONDITIONSETS
7101 || (!unlockables[i].unlocked && unlockables[i].showconditionset && !M_Achieved(unlockables[i].showconditionset)))
7102 {
7103 i += 1;
7104 continue;
7105 }
7106
7107 V_DrawString(currentMenu->x, y, ((unlockables[i].unlocked) ? V_GREENMAP : V_TRANSLUCENT)|V_ALLOWLOWERCASE, ((unlockables[i].unlocked || !unlockables[i].nochecklist) ? unlockables[i].name : M_CreateSecretMenuOption(unlockables[i].name)));
7108
7109 for (j = i+1; j < MAXUNLOCKABLES; j++)
7110 {
7111 if (!(unlockables[j].name[0] == 0 //|| unlockables[j].nochecklist
7112 || !unlockables[j].conditionset || unlockables[j].conditionset > MAXCONDITIONSETS))
7113 break;
7114 }
7115 if ((j != MAXUNLOCKABLES) && (unlockables[i].conditionset == unlockables[j].conditionset))
7116 addy(8)
7117 else
7118 {
7119 if ((maxcond = conditionSets[unlockables[i].conditionset-1].numconditions))
7120 {
7121 cond = conditionSets[unlockables[i].conditionset-1].condition;
7122 previd = cond[0].id;
7123 addy(2);
7124
7125 if (unlockables[i].objective[0] != '/')
7126 {
7127 addy(16);
7128 V_DrawString(currentMenu->x, y-8,
7129 V_ALLOWLOWERCASE,
7130 va("\x1E %s", unlockables[i].objective));
7131 y -= 8;
7132 }
7133 else
7134 {
7135 for (condnum = 0; condnum < maxcond; condnum++)
7136 {
7137 const char *beat = "!";
7138
7139 if (cond[condnum].id != previd)
7140 {
7141 addy(8);
7142 V_DrawString(currentMenu->x + 4, y, V_YELLOWMAP, "OR");
7143 }
7144
7145 addy(8);
7146
7147 switch (cond[condnum].type)
7148 {
7149 case UC_PLAYTIME:
7150 {
7151 UINT32 hours = G_TicsToHours(cond[condnum].requirement);
7152 UINT32 minutes = G_TicsToMinutes(cond[condnum].requirement, false);
7153 UINT32 seconds = G_TicsToSeconds(cond[condnum].requirement);
7154
7155 #define getplural(field) ((field == 1) ? "" : "s")
7156 if (hours)
7157 {
7158 if (minutes)
7159 beat = va("Play the game for %d hour%s %d minute%s", hours, getplural(hours), minutes, getplural(minutes));
7160 else
7161 beat = va("Play the game for %d hour%s", hours, getplural(hours));
7162 }
7163 else
7164 {
7165 if (minutes && seconds)
7166 beat = va("Play the game for %d minute%s %d second%s", minutes, getplural(minutes), seconds, getplural(seconds));
7167 else if (minutes)
7168 beat = va("Play the game for %d minute%s", minutes, getplural(minutes));
7169 else
7170 beat = va("Play the game for %d second%s", seconds, getplural(seconds));
7171 }
7172 #undef getplural
7173 }
7174 break;
7175 case UC_MAPVISITED:
7176 case UC_MAPBEATEN:
7177 case UC_MAPALLEMERALDS:
7178 case UC_MAPULTIMATE:
7179 case UC_MAPPERFECT:
7180 {
7181 char *title = G_BuildMapTitle(cond[condnum].requirement);
7182
7183 if (title)
7184 {
7185 const char *level = ((M_MapLocked(cond[condnum].requirement) || !((mapheaderinfo[cond[condnum].requirement-1]->menuflags & LF2_NOVISITNEEDED) || (mapvisited[cond[condnum].requirement-1] & MV_MAX))) ? M_CreateSecretMenuOption(title) : title);
7186
7187 switch (cond[condnum].type)
7188 {
7189 case UC_MAPVISITED:
7190 beat = va("Visit %s", level);
7191 break;
7192 case UC_MAPALLEMERALDS:
7193 beat = va("Beat %s with all emeralds", level);
7194 break;
7195 case UC_MAPULTIMATE:
7196 beat = va("Beat %s in Ultimate mode", level);
7197 break;
7198 case UC_MAPPERFECT:
7199 beat = va("Get all rings in %s", level);
7200 break;
7201 case UC_MAPBEATEN:
7202 default:
7203 beat = va("Beat %s", level);
7204 break;
7205 }
7206 Z_Free(title);
7207 }
7208 }
7209 break;
7210 case UC_MAPSCORE:
7211 case UC_MAPTIME:
7212 case UC_MAPRINGS:
7213 {
7214 char *title = G_BuildMapTitle(cond[condnum].extrainfo1);
7215
7216 if (title)
7217 {
7218 const char *level = ((M_MapLocked(cond[condnum].extrainfo1) || !((mapheaderinfo[cond[condnum].extrainfo1-1]->menuflags & LF2_NOVISITNEEDED) || (mapvisited[cond[condnum].extrainfo1-1] & MV_MAX))) ? M_CreateSecretMenuOption(title) : title);
7219
7220 switch (cond[condnum].type)
7221 {
7222 case UC_MAPSCORE:
7223 beat = va("Get %d points in %s", cond[condnum].requirement, level);
7224 break;
7225 case UC_MAPTIME:
7226 beat = va("Beat %s in %d:%02d.%02d", level,
7227 G_TicsToMinutes(cond[condnum].requirement, true),
7228 G_TicsToSeconds(cond[condnum].requirement),
7229 G_TicsToCentiseconds(cond[condnum].requirement));
7230 break;
7231 case UC_MAPRINGS:
7232 beat = va("Get %d rings in %s", cond[condnum].requirement, level);
7233 break;
7234 default:
7235 break;
7236 }
7237 Z_Free(title);
7238 }
7239 }
7240 break;
7241 case UC_OVERALLSCORE:
7242 case UC_OVERALLTIME:
7243 case UC_OVERALLRINGS:
7244 {
7245 switch (cond[condnum].type)
7246 {
7247 case UC_OVERALLSCORE:
7248 beat = va("Get %d points over all maps", cond[condnum].requirement);
7249 break;
7250 case UC_OVERALLTIME:
7251 beat = va("Get a total time of less than %d:%02d.%02d",
7252 G_TicsToMinutes(cond[condnum].requirement, true),
7253 G_TicsToSeconds(cond[condnum].requirement),
7254 G_TicsToCentiseconds(cond[condnum].requirement));
7255 break;
7256 case UC_OVERALLRINGS:
7257 beat = va("Get %d rings over all maps", cond[condnum].requirement);
7258 break;
7259 default:
7260 break;
7261 }
7262 }
7263 break;
7264 case UC_GAMECLEAR:
7265 case UC_ALLEMERALDS:
7266 {
7267 const char *emeraldtext = ((cond[condnum].type == UC_ALLEMERALDS) ? " with all emeralds" : "");
7268 if (cond[condnum].requirement != 1)
7269 beat = va("Beat the game %d times%s",
7270 cond[condnum].requirement, emeraldtext);
7271 else
7272 beat = va("Beat the game%s",
7273 emeraldtext);
7274 }
7275 break;
7276 case UC_TOTALEMBLEMS:
7277 beat = va("Collect %s%d emblems", ((numemblems+numextraemblems == cond[condnum].requirement) ? "all " : ""), cond[condnum].requirement);
7278 break;
7279 case UC_NIGHTSTIME:
7280 case UC_NIGHTSSCORE:
7281 case UC_NIGHTSGRADE:
7282 {
7283 char *title = G_BuildMapTitle(cond[condnum].extrainfo1);
7284
7285 if (title)
7286 {
7287 const char *level = ((M_MapLocked(cond[condnum].extrainfo1) || !((mapheaderinfo[cond[condnum].extrainfo1-1]->menuflags & LF2_NOVISITNEEDED) || (mapvisited[cond[condnum].extrainfo1-1] & MV_MAX))) ? M_CreateSecretMenuOption(title) : title);
7288
7289 switch (cond[condnum].type)
7290 {
7291 case UC_NIGHTSSCORE:
7292 if (cond[condnum].extrainfo2)
7293 beat = va("Get %d points in %s, mare %d", cond[condnum].requirement, level, cond[condnum].extrainfo2);
7294 else
7295 beat = va("Get %d points in %s", cond[condnum].requirement, level);
7296 break;
7297 case UC_NIGHTSTIME:
7298 if (cond[condnum].extrainfo2)
7299 beat = va("Beat %s, mare %d in %d:%02d.%02d", level, cond[condnum].extrainfo2,
7300 G_TicsToMinutes(cond[condnum].requirement, true),
7301 G_TicsToSeconds(cond[condnum].requirement),
7302 G_TicsToCentiseconds(cond[condnum].requirement));
7303 else
7304 beat = va("Beat %s in %d:%02d.%02d",
7305 level,
7306 G_TicsToMinutes(cond[condnum].requirement, true),
7307 G_TicsToSeconds(cond[condnum].requirement),
7308 G_TicsToCentiseconds(cond[condnum].requirement));
7309 break;
7310 case UC_NIGHTSGRADE:
7311 {
7312 char grade = ('F' - (char)cond[condnum].requirement);
7313 if (grade < 'A')
7314 grade = 'A';
7315 if (cond[condnum].extrainfo2)
7316 beat = va("Get grade %c in %s, mare %d", grade, level, cond[condnum].extrainfo2);
7317 else
7318 beat = va("Get grade %c in %s", grade, level);
7319 }
7320 break;
7321 default:
7322 break;
7323 }
7324 Z_Free(title);
7325 }
7326 }
7327 break;
7328 case UC_TRIGGER:
7329 case UC_EMBLEM:
7330 case UC_CONDITIONSET:
7331 default:
7332 y -= 8; // Nope, not showing this.
7333 break;
7334 }
7335 if (beat[0] != '!')
7336 {
7337 V_DrawString(currentMenu->x, y, 0, "\x1E");
7338 V_DrawString(currentMenu->x+12, y, V_ALLOWLOWERCASE, beat);
7339 }
7340 previd = cond[condnum].id;
7341 }
7342 }
7343 }
7344 addy(12);
7345 }
7346 i = j;
7347
7348 /*V_DrawString(160, 8+(24*j), V_RETURN8, V_WordWrap(160, 292, 0, unlockables[i].objective));
7349
7350 if (unlockables[i].unlocked)
7351 V_DrawString(308, 8+(24*j), V_YELLOWMAP, "Y");
7352 else
7353 V_DrawString(308, 8+(24*j), V_YELLOWMAP, "N");*/
7354 }
7355
7356 finishchecklist:
7357 if ((checklist_cangodown = ((y - currentMenu->y) > (scrollareaheight*2)))) // haaaaaaacks.
7358 V_DrawString(10, currentMenu->y+(scrollareaheight*2)+(skullAnimCounter/5), V_YELLOWMAP, "\x1B");
7359 }
7360
7361 #define NUMHINTS 5
7362
7363 static void M_EmblemHints(INT32 choice)
7364 {
7365 INT32 i;
7366 UINT32 local = 0;
7367 emblem_t *emblem;
7368 for (i = 0; i < numemblems; i++)
7369 {
7370 emblem = &emblemlocations[i];
7371 if (emblem->level != gamemap || emblem->type > ET_SKIN)
7372 continue;
7373 if (++local > NUMHINTS*2)
7374 break;
7375 }
7376
7377 (void)choice;
7378 SR_EmblemHintMenu[0].status = (local > NUMHINTS*2) ? (IT_STRING | IT_ARROWS) : (IT_DISABLED);
7379 SR_EmblemHintMenu[1].status = (M_SecretUnlocked(SECRET_ITEMFINDER)) ? (IT_CVAR|IT_STRING) : (IT_SECRET);
7380 hintpage = 1;
7381 SR_EmblemHintDef.prevMenu = currentMenu;
7382 M_SetupNextMenu(&SR_EmblemHintDef);
7383 itemOn = 2; // always start on back.
7384 }
7385
7386 static void M_DrawEmblemHints(void)
7387 {
7388 INT32 i, j = 0, x, y, left_hints = NUMHINTS, pageflag = 0;
7389 UINT32 collected = 0, totalemblems = 0, local = 0;
7390 emblem_t *emblem;
7391 const char *hint;
7392
7393 for (i = 0; i < numemblems; i++)
7394 {
7395 emblem = &emblemlocations[i];
7396 if (emblem->level != gamemap || emblem->type > ET_SKIN)
7397 continue;
7398
7399 local++;
7400 }
7401
7402 x = (local > NUMHINTS ? 4 : 12);
7403 y = 8;
7404
7405 if (local > NUMHINTS){
7406 if (local > ((hintpage-1)*NUMHINTS*2) && local < ((hintpage)*NUMHINTS*2)){
7407 if (NUMHINTS % 2 == 1)
7408 left_hints = (local - ((hintpage-1)*NUMHINTS*2) + 1) / 2;
7409 else
7410 left_hints = (local - ((hintpage-1)*NUMHINTS*2)) / 2;
7411 }else{
7412 left_hints = NUMHINTS;
7413 }
7414 }
7415
7416 if (local > NUMHINTS*2){
7417 if (itemOn == 0){
7418 pageflag = V_YELLOWMAP;
7419 }
7420 V_DrawString(currentMenu->x + 40, currentMenu->y + 10, pageflag, va("%d of %d",hintpage, local/(NUMHINTS*2) + 1));
7421 }
7422
7423 // If there are more than 1 page's but less than 2 pages' worth of emblems on the last possible page,
7424 // put half (rounded up) of the hints on the left, and half (rounded down) on the right
7425
7426
7427 if (!local)
7428 V_DrawCenteredString(160, 48, V_YELLOWMAP, "No hidden emblems on this map.");
7429 else for (i = 0; i < numemblems; i++)
7430 {
7431 emblem = &emblemlocations[i];
7432 if (emblem->level != gamemap || emblem->type > ET_SKIN)
7433 continue;
7434
7435 totalemblems++;
7436
7437 if (totalemblems >= ((hintpage-1)*(NUMHINTS*2) + 1) && totalemblems < (hintpage*NUMHINTS*2)+1){
7438
7439 if (emblem->collected)
7440 {
7441 collected = V_GREENMAP;
7442 V_DrawMappedPatch(x, y+4, 0, W_CachePatchName(M_GetEmblemPatch(emblem, false), PU_PATCH),
7443 R_GetTranslationColormap(TC_DEFAULT, M_GetEmblemColor(emblem), GTC_CACHE));
7444 }
7445 else
7446 {
7447 collected = 0;
7448 V_DrawScaledPatch(x, y+4, 0, W_CachePatchName("NEEDIT", PU_PATCH));
7449 }
7450
7451 if (emblem->hint[0])
7452 hint = emblem->hint;
7453 else
7454 hint = M_GetText("No hint available for this emblem.");
7455 hint = V_WordWrap(40, BASEVIDWIDTH-12, 0, hint);
7456 //always draw tiny if we have more than NUMHINTS*2, visually more appealing
7457 if (local > NUMHINTS)
7458 V_DrawThinString(x+28, y, V_RETURN8|V_ALLOWLOWERCASE|collected, hint);
7459 else
7460 V_DrawString(x+28, y, V_RETURN8|V_ALLOWLOWERCASE|collected, hint);
7461
7462 y += 28;
7463
7464 // If there are more than 1 page's but less than 2 pages' worth of emblems on the last possible page,
7465 // put half (rounded up) of the hints on the left, and half (rounded down) on the right
7466
7467 if (++j == left_hints)
7468 {
7469 x = 4+(BASEVIDWIDTH/2);
7470 y = 8;
7471 }
7472 else if (j >= NUMHINTS*2)
7473 break;
7474 }
7475 }
7476
7477 M_DrawGenericMenu();
7478 }
7479
7480
7481 static void M_HandleEmblemHints(INT32 choice)
7482 {
7483 INT32 i;
7484 emblem_t *emblem;
7485 UINT32 stageemblems = 0;
7486
7487 for (i = 0; i < numemblems; i++)
7488 {
7489 emblem = &emblemlocations[i];
7490 if (emblem->level != gamemap || emblem->type > ET_SKIN)
7491 continue;
7492
7493 stageemblems++;
7494 }
7495
7496
7497 if (choice == 0){
7498 if (hintpage > 1){
7499 hintpage--;
7500 }
7501 }else{
7502 if (hintpage < ((stageemblems-1)/(NUMHINTS*2) + 1)){
7503 hintpage++;
7504 }
7505 }
7506
7507 }
7508
7509 /*static void M_DrawSkyRoom(void)
7510 {
7511 INT32 i, y = 0;
7512
7513 M_DrawGenericMenu();
7514
7515 for (i = 0; i < currentMenu->numitems; ++i)
7516 {
7517 if (currentMenu->menuitems[i].status == (IT_STRING|IT_KEYHANDLER))
7518 {
7519 y = currentMenu->menuitems[i].alphaKey;
7520 break;
7521 }
7522 }
7523 if (!y)
7524 return;
7525
7526 V_DrawRightAlignedString(BASEVIDWIDTH - currentMenu->x, currentMenu->y + y, V_YELLOWMAP, cv_soundtest.string);
7527 if (i == itemOn)
7528 {
7529 V_DrawCharacter(BASEVIDWIDTH - currentMenu->x - 10 - V_StringWidth(cv_soundtest.string, 0) - (skullAnimCounter/5), currentMenu->y + y,
7530 '\x1C' | V_YELLOWMAP, false);
7531 V_DrawCharacter(BASEVIDWIDTH - currentMenu->x + 2 + (skullAnimCounter/5), currentMenu->y + y,
7532 '\x1D' | V_YELLOWMAP, false);
7533 }
7534 if (cv_soundtest.value)
7535 V_DrawRightAlignedString(BASEVIDWIDTH - currentMenu->x, currentMenu->y + y + 8, V_YELLOWMAP, S_sfx[cv_soundtest.value].name);
7536 }*/
7537
7538 static musicdef_t *curplaying = NULL;
7539 static INT32 st_sel = 0, st_cc = 0;
7540 static tic_t st_time = 0;
7541 static patch_t* st_radio[9];
7542 static patch_t* st_launchpad[4];
7543
7544 static void M_CacheSoundTest(void)
7545 {
7546 UINT8 i;
7547 char buf[8];
7548
7549 STRBUFCPY(buf, "M_RADIOn");
7550 for (i = 0; i < 9; i++)
7551 {
7552 buf[7] = (char)('0'+i);
7553 st_radio[i] = W_CachePatchName(buf, PU_PATCH);
7554 }
7555
7556 STRBUFCPY(buf, "M_LPADn");
7557 for (i = 0; i < 4; i++)
7558 {
7559 buf[6] = (char)('0'+i);
7560 st_launchpad[i] = W_CachePatchName(buf, PU_PATCH);
7561 }
7562 }
7563
7564 static void M_SoundTest(INT32 choice)
7565 {
7566 INT32 ul = skyRoomMenuTranslations[choice-1];
7567
7568 soundtestpage = (UINT8)(unlockables[ul].variable);
7569 if (!soundtestpage)
7570 soundtestpage = 1;
7571
7572 if (!S_PrepareSoundTest())
7573 {
7574 M_StartMessage(M_GetText("No selectable tracks found.\n"),NULL,MM_NOTHING);
7575 return;
7576 }
7577
7578 M_CacheSoundTest();
7579
7580 curplaying = NULL;
7581 st_time = 0;
7582
7583 st_sel = 0;
7584
7585 st_cc = cv_closedcaptioning.value; // hack;
7586 cv_closedcaptioning.value = 1; // hack
7587
7588 M_SetupNextMenu(&SR_SoundTestDef);
7589 }
7590
7591 static void M_DrawSoundTest(void)
7592 {
7593 INT32 x, y, i;
7594 fixed_t hscale = FRACUNIT/2, vscale = FRACUNIT/2, bounce = 0;
7595 UINT8 frame[4] = {0, 0, -1, SKINCOLOR_RUBY};
7596
7597 // let's handle the ticker first. ideally we'd tick this somewhere else, BUT...
7598 if (curplaying)
7599 {
7600 if (curplaying == &soundtestsfx)
7601 {
7602 if (cv_soundtest.value)
7603 {
7604 frame[1] = (2-st_time);
7605 frame[2] = ((cv_soundtest.value - 1) % 9);
7606 frame[3] += (((cv_soundtest.value - 1) / 9) % (FIRSTSUPERCOLOR - frame[3]));
7607 if (st_time < 2)
7608 st_time++;
7609 }
7610 }
7611 else
7612 {
7613 if (curplaying->stoppingtics && st_time >= curplaying->stoppingtics)
7614 {
7615 curplaying = NULL;
7616 st_time = 0;
7617 }
7618 else
7619 {
7620 fixed_t work, bpm = curplaying->bpm;
7621 angle_t ang;
7622 //bpm = FixedDiv((60*TICRATE)<<FRACBITS, bpm); -- bake this in on load
7623
7624 work = st_time<<FRACBITS;
7625 work %= bpm;
7626
7627 if (st_time >= (FRACUNIT>>1)) // prevent overflow jump - takes about 15 minutes of loop on the same song to reach
7628 st_time = (work>>FRACBITS);
7629
7630 work = FixedDiv(work*180, bpm);
7631 frame[0] = 8-(work/(20<<FRACBITS));
7632 if (frame[0] > 8) // VERY small likelihood for the above calculation to wrap, but it turns out it IS possible lmao
7633 frame[0] = 0;
7634 ang = (FixedAngle(work)>>ANGLETOFINESHIFT) & FINEMASK;
7635 bounce = (FINESINE(ang) - FRACUNIT/2);
7636 hscale -= bounce/16;
7637 vscale += bounce/16;
7638
7639 st_time++;
7640 }
7641 }
7642 }
7643
7644 x = 90<<FRACBITS;
7645 y = (BASEVIDHEIGHT-32)<<FRACBITS;
7646
7647 V_DrawStretchyFixedPatch(x, y,
7648 hscale, vscale,
7649 0, st_radio[frame[0]], NULL);
7650
7651 V_DrawFixedPatch(x, y, FRACUNIT/2, 0, st_launchpad[0], NULL);
7652
7653 for (i = 0; i < 9; i++)
7654 {
7655 if (i == frame[2])
7656 {
7657 UINT8 *colmap = R_GetTranslationColormap(TC_RAINBOW, frame[3], GTC_CACHE);
7658 V_DrawFixedPatch(x, y + (frame[1]<<FRACBITS), FRACUNIT/2, 0, st_launchpad[frame[1]+1], colmap);
7659 }
7660 else
7661 V_DrawFixedPatch(x, y, FRACUNIT/2, 0, st_launchpad[1], NULL);
7662
7663 if ((i % 3) == 2)
7664 {
7665 x -= ((2*28) + 25)<<(FRACBITS-1);
7666 y -= ((2*7) - 11)<<(FRACBITS-1);
7667 }
7668 else
7669 {
7670 x += 28<<(FRACBITS-1);
7671 y += 7<<(FRACBITS-1);
7672 }
7673 }
7674
7675 y = (BASEVIDWIDTH-(vid.width/vid.dupx))/2;
7676
7677 V_DrawFill(y, 20, vid.width/vid.dupx, 24, 159);
7678 {
7679 static fixed_t st_scroll = -1;
7680 const char* titl;
7681 x = 16;
7682 V_DrawString(x, 10, 0, "NOW PLAYING:");
7683 if (curplaying)
7684 {
7685 if (curplaying->alttitle[0])
7686 titl = va("%s - %s - ", curplaying->title, curplaying->alttitle);
7687 else
7688 titl = va("%s - ", curplaying->title);
7689 }
7690 else
7691 titl = "None - ";
7692
7693 i = V_LevelNameWidth(titl);
7694
7695 if (++st_scroll >= i)
7696 st_scroll %= i;
7697
7698 x -= st_scroll;
7699
7700 while (x < BASEVIDWIDTH-y)
7701 x += i;
7702 while (x > y)
7703 {
7704 x -= i;
7705 V_DrawLevelTitle(x, 22, 0, titl);
7706 }
7707
7708 if (curplaying)
7709 V_DrawRightAlignedThinString(BASEVIDWIDTH-16, 46, V_ALLOWLOWERCASE, curplaying->authors);
7710 }
7711
7712 V_DrawFill(165, 60, 140, 112, 159);
7713
7714 {
7715 INT32 t, b, q, m = 112;
7716
7717 if (numsoundtestdefs <= 7)
7718 {
7719 t = 0;
7720 b = numsoundtestdefs - 1;
7721 i = 0;
7722 }
7723 else
7724 {
7725 q = m;
7726 m = (5*m)/numsoundtestdefs;
7727 if (st_sel < 3)
7728 {
7729 t = 0;
7730 b = 6;
7731 i = 0;
7732 }
7733 else if (st_sel >= numsoundtestdefs-4)
7734 {
7735 t = numsoundtestdefs - 7;
7736 b = numsoundtestdefs - 1;
7737 i = q-m;
7738 }
7739 else
7740 {
7741 t = st_sel - 3;
7742 b = st_sel + 3;
7743 i = (t * (q-m))/(numsoundtestdefs - 7);
7744 }
7745 }
7746
7747 V_DrawFill(165+140-1, 60 + i, 1, m, 0);
7748
7749 if (t != 0)
7750 V_DrawString(165+140+4, 60+4 - (skullAnimCounter/5), V_YELLOWMAP, "\x1A");
7751
7752 if (b != numsoundtestdefs - 1)
7753 V_DrawString(165+140+4, 60+112-12 + (skullAnimCounter/5), V_YELLOWMAP, "\x1B");
7754
7755 x = 169;
7756 y = 64;
7757
7758 while (t <= b)
7759 {
7760 if (t == st_sel)
7761 V_DrawFill(165, y-4, 140-1, 16, 155);
7762 if (!soundtestdefs[t]->allowed)
7763 {
7764 V_DrawString(x, y, (t == st_sel ? V_YELLOWMAP : 0)|V_ALLOWLOWERCASE, "???");
7765 }
7766 else if (soundtestdefs[t] == &soundtestsfx)
7767 {
7768 const char *sfxstr = va("SFX %s", cv_soundtest.string);
7769 V_DrawString(x, y, (t == st_sel ? V_YELLOWMAP : 0), sfxstr);
7770 if (t == st_sel)
7771 {
7772 V_DrawCharacter(x - 10 - (skullAnimCounter/5), y,
7773 '\x1C' | V_YELLOWMAP, false);
7774 V_DrawCharacter(x + 2 + V_StringWidth(sfxstr, 0) + (skullAnimCounter/5), y,
7775 '\x1D' | V_YELLOWMAP, false);
7776 }
7777
7778 if (curplaying == soundtestdefs[t])
7779 {
7780 sfxstr = (cv_soundtest.value) ? S_sfx[cv_soundtest.value].name : "N/A";
7781 i = V_StringWidth(sfxstr, 0);
7782 V_DrawFill(165+140-9-i, y-4, i+8, 16, 150);
7783 V_DrawRightAlignedString(165+140-5, y, V_YELLOWMAP, sfxstr);
7784 }
7785 }
7786 else
7787 {
7788 V_DrawString(x, y, (t == st_sel ? V_YELLOWMAP : 0)|V_ALLOWLOWERCASE, soundtestdefs[t]->title);
7789 if (curplaying == soundtestdefs[t])
7790 {
7791 V_DrawFill(165+140-9, y-4, 8, 16, 150);
7792 //V_DrawCharacter(165+140-8, y, '\x19' | V_YELLOWMAP, false);
7793 V_DrawFixedPatch((165+140-9)<<FRACBITS, (y<<FRACBITS)-(bounce*4), FRACUNIT, 0, hu_font['\x19'-HU_FONTSTART], V_GetStringColormap(V_YELLOWMAP));
7794 }
7795 }
7796 t++;
7797 y += 16;
7798 }
7799 }
7800 }
7801
7802 static void M_HandleSoundTest(INT32 choice)
7803 {
7804 boolean exitmenu = false; // exit to previous menu
7805
7806 switch (choice)
7807 {
7808 case KEY_DOWNARROW:
7809 if (st_sel++ >= numsoundtestdefs-1)
7810 st_sel = 0;
7811 {
7812 cv_closedcaptioning.value = st_cc; // hack
7813 S_StartSound(NULL, sfx_menu1);
7814 cv_closedcaptioning.value = 1; // hack
7815 }
7816 break;
7817 case KEY_UPARROW:
7818 if (!st_sel--)
7819 st_sel = numsoundtestdefs-1;
7820 {
7821 cv_closedcaptioning.value = st_cc; // hack
7822 S_StartSound(NULL, sfx_menu1);
7823 cv_closedcaptioning.value = 1; // hack
7824 }
7825 break;
7826 case KEY_PGDN:
7827 if (st_sel < numsoundtestdefs-1)
7828 {
7829 st_sel += 3;
7830 if (st_sel >= numsoundtestdefs-1)
7831 st_sel = numsoundtestdefs-1;
7832 cv_closedcaptioning.value = st_cc; // hack
7833 S_StartSound(NULL, sfx_menu1);
7834 cv_closedcaptioning.value = 1; // hack
7835 }
7836 break;
7837 case KEY_PGUP:
7838 if (st_sel)
7839 {
7840 st_sel -= 3;
7841 if (st_sel < 0)
7842 st_sel = 0;
7843 cv_closedcaptioning.value = st_cc; // hack
7844 S_StartSound(NULL, sfx_menu1);
7845 cv_closedcaptioning.value = 1; // hack
7846 }
7847 break;
7848 case KEY_BACKSPACE:
7849 if (curplaying)
7850 {
7851 S_StopSounds();
7852 S_StopMusic();
7853 curplaying = NULL;
7854 st_time = 0;
7855 cv_closedcaptioning.value = st_cc; // hack
7856 S_StartSound(NULL, sfx_skid);
7857 cv_closedcaptioning.value = 1; // hack
7858 }
7859 break;
7860 case KEY_ESCAPE:
7861 exitmenu = true;
7862 break;
7863
7864 case KEY_RIGHTARROW:
7865 if (soundtestdefs[st_sel] == &soundtestsfx && soundtestdefs[st_sel]->allowed)
7866 {
7867 S_StopSounds();
7868 S_StopMusic();
7869 curplaying = soundtestdefs[st_sel];
7870 st_time = 0;
7871 CV_AddValue(&cv_soundtest, 1);
7872 }
7873 break;
7874 case KEY_LEFTARROW:
7875 if (soundtestdefs[st_sel] == &soundtestsfx && soundtestdefs[st_sel]->allowed)
7876 {
7877 S_StopSounds();
7878 S_StopMusic();
7879 curplaying = soundtestdefs[st_sel];
7880 st_time = 0;
7881 CV_AddValue(&cv_soundtest, -1);
7882 }
7883 break;
7884 case KEY_ENTER:
7885 S_StopSounds();
7886 S_StopMusic();
7887 st_time = 0;
7888 if (soundtestdefs[st_sel]->allowed)
7889 {
7890 curplaying = soundtestdefs[st_sel];
7891 if (curplaying == &soundtestsfx)
7892 {
7893 // S_StopMusic() -- is this necessary?
7894 if (cv_soundtest.value)
7895 S_StartSound(NULL, cv_soundtest.value);
7896 }
7897 else
7898 S_ChangeMusicInternal(curplaying->name, !curplaying->stoppingtics);
7899 }
7900 else
7901 {
7902 curplaying = NULL;
7903 S_StartSound(NULL, sfx_lose);
7904 }
7905 break;
7906
7907 default:
7908 break;
7909 }
7910 if (exitmenu)
7911 {
7912 Z_Free(soundtestdefs);
7913 soundtestdefs = NULL;
7914
7915 cv_closedcaptioning.value = st_cc; // undo hack
7916
7917 if (currentMenu->prevMenu)
7918 M_SetupNextMenu(currentMenu->prevMenu);
7919 else
7920 M_ClearMenus(true);
7921 }
7922 }
7923
7924 // Entering secrets menu
7925 static void M_SecretsMenu(INT32 choice)
7926 {
7927 INT32 i, j, ul;
7928 UINT8 done[MAXUNLOCKABLES];
7929 UINT16 curheight;
7930
7931 (void)choice;
7932
7933 // Clear all before starting
7934 for (i = 1; i < MAXUNLOCKABLES+1; ++i)
7935 SR_MainMenu[i].status = IT_DISABLED;
7936
7937 memset(skyRoomMenuTranslations, 0, sizeof(skyRoomMenuTranslations));
7938 memset(done, 0, sizeof(done));
7939
7940 for (i = 1; i < MAXUNLOCKABLES+1; ++i)
7941 {
7942 curheight = UINT16_MAX;
7943 ul = -1;
7944
7945 // Autosort unlockables
7946 for (j = 0; j < MAXUNLOCKABLES; ++j)
7947 {
7948 if (!unlockables[j].height || done[j] || unlockables[j].type < 0)
7949 continue;
7950
7951 if (unlockables[j].height < curheight)
7952 {
7953 curheight = unlockables[j].height;
7954 ul = j;
7955 }
7956 }
7957 if (ul < 0)
7958 break;
7959
7960 done[ul] = true;
7961
7962 skyRoomMenuTranslations[i-1] = (UINT8)ul;
7963 SR_MainMenu[i].text = unlockables[ul].name;
7964 SR_MainMenu[i].alphaKey = (UINT16)unlockables[ul].height;
7965
7966 if (unlockables[ul].type == SECRET_HEADER)
7967 {
7968 SR_MainMenu[i].status = IT_HEADER;
7969 continue;
7970 }
7971
7972 SR_MainMenu[i].status = IT_SECRET;
7973
7974 if (unlockables[ul].unlocked)
7975 {
7976 switch (unlockables[ul].type)
7977 {
7978 case SECRET_LEVELSELECT:
7979 SR_MainMenu[i].status = IT_STRING|IT_CALL;
7980 SR_MainMenu[i].itemaction = M_CustomLevelSelect;
7981 break;
7982 case SECRET_WARP:
7983 SR_MainMenu[i].status = IT_STRING|IT_CALL;
7984 SR_MainMenu[i].itemaction = M_CustomWarp;
7985 break;
7986 case SECRET_CREDITS:
7987 SR_MainMenu[i].status = IT_STRING|IT_CALL;
7988 SR_MainMenu[i].itemaction = M_Credits;
7989 break;
7990 case SECRET_SOUNDTEST:
7991 SR_MainMenu[i].status = IT_STRING|IT_CALL;
7992 SR_MainMenu[i].itemaction = M_SoundTest;
7993 default:
7994 break;
7995 }
7996 }
7997 }
7998
7999 M_SetupNextMenu(&SR_MainDef);
8000 }
8001
8002 // ==================
8003 // NEW GAME FUNCTIONS
8004 // ==================
8005
8006 INT32 ultimate_selectable = false;
8007
8008 static void M_NewGame(void)
8009 {
8010 fromlevelselect = false;
8011
8012 startmap = spstage_start;
8013 CV_SetValue(&cv_newgametype, GT_COOP); // Graue 09-08-2004
8014
8015 M_SetupChoosePlayer(0);
8016 }
8017
8018 static void M_CustomWarp(INT32 choice)
8019 {
8020 INT32 ul = skyRoomMenuTranslations[choice-1];
8021
8022 startmap = (INT16)(unlockables[ul].variable);
8023
8024 M_SetupChoosePlayer(0);
8025 }
8026
8027 static void M_Credits(INT32 choice)
8028 {
8029 (void)choice;
8030 cursaveslot = -1;
8031 M_ClearMenus(true);
8032 F_StartCredits();
8033 }
8034
8035 static void M_CustomLevelSelect(INT32 choice)
8036 {
8037 INT32 ul = skyRoomMenuTranslations[choice-1];
8038
8039 SR_LevelSelectDef.prevMenu = currentMenu;
8040 levellistmode = LLM_LEVELSELECT;
8041 maplistoption = (UINT8)(unlockables[ul].variable);
8042
8043 if (!M_PrepareLevelPlatter(-1, true))
8044 {
8045 M_StartMessage(M_GetText("No selectable levels found.\n"),NULL,MM_NOTHING);
8046 return;
8047 }
8048
8049 M_SetupNextMenu(&SR_LevelSelectDef);
8050 }
8051
8052 // ==================
8053 // SINGLE PLAYER MENU
8054 // ==================
8055
8056 static void M_SinglePlayerMenu(INT32 choice)
8057 {
8058 (void)choice;
8059
8060
8061 // Reset the item positions, to avoid them sinking farther down every time the menu is opened if one is unavailable
8062 // Note that they're reset, not simply "not moved again", in case mid-game add-ons re-enable an option
8063 SP_MainMenu[spstartgame] .alphaKey = 76;
8064 SP_MainMenu[sprecordattack].alphaKey = 84;
8065 SP_MainMenu[spnightsmode] .alphaKey = 92;
8066 SP_MainMenu[spmarathon] .alphaKey = 100;
8067 //SP_MainMenu[sptutorial] .alphaKey = 108; // Not needed
8068 //SP_MainMenu[spstatistics].alphaKey = 116; // Not needed
8069
8070
8071 levellistmode = LLM_RECORDATTACK;
8072 if (M_GametypeHasLevels(-1))
8073 SP_MainMenu[sprecordattack].status = (M_SecretUnlocked(SECRET_RECORDATTACK)) ? IT_CALL|IT_STRING : IT_SECRET;
8074 else // If Record Attack is nonexistent in the current add-on...
8075 {
8076 SP_MainMenu[sprecordattack].status = IT_NOTHING|IT_DISABLED; // ...hide and disable the Record Attack option...
8077 SP_MainMenu[spstartgame].alphaKey += 8; // ...and lower Start Game by 8 pixels to close the gap
8078 }
8079
8080
8081 levellistmode = LLM_NIGHTSATTACK;
8082 if (M_GametypeHasLevels(-1))
8083 SP_MainMenu[spnightsmode].status = (M_SecretUnlocked(SECRET_NIGHTSMODE)) ? IT_CALL|IT_STRING : IT_SECRET;
8084 else // If NiGHTS Mode is nonexistent in the current add-on...
8085 {
8086 SP_MainMenu[spnightsmode].status = IT_NOTHING|IT_DISABLED; // ...hide and disable the NiGHTS Mode option...
8087 // ...and lower the above options' display positions by 8 pixels to close the gap
8088 SP_MainMenu[spstartgame] .alphaKey += 8;
8089 SP_MainMenu[sprecordattack].alphaKey += 8;
8090 }
8091
8092
8093 // If the FIRST stage immediately leads to the ending, or itself (which gets converted to the title screen in G_DoCompleted for marathonmode only), there's no point in having this option on the menu. You should use Record Attack in that circumstance, although if marathonnext is set this behaviour can be overridden if you make some weird mod that requires multiple playthroughs of the same map in sequence and has some in-level mechanism to break the cycle.
8094 if (mapheaderinfo[spmarathon_start-1]
8095 && !mapheaderinfo[spmarathon_start-1]->marathonnext
8096 && (mapheaderinfo[spmarathon_start-1]->nextlevel == spmarathon_start
8097 || mapheaderinfo[spmarathon_start-1]->nextlevel >= 1100))
8098 {
8099 SP_MainMenu[spmarathon].status = IT_NOTHING|IT_DISABLED; // Hide and disable the Marathon Run option...
8100 // ...and lower the above options' display positions by 8 pixels to close the gap
8101 SP_MainMenu[spstartgame] .alphaKey += 8;
8102 SP_MainMenu[sprecordattack].alphaKey += 8;
8103 SP_MainMenu[spnightsmode] .alphaKey += 8;
8104 }
8105 else // Otherwise, if Marathon Run is allowed and Record Attack is unlocked, unlock Marathon Run!
8106 SP_MainMenu[spmarathon].status = (M_SecretUnlocked(SECRET_RECORDATTACK)) ? IT_CALL|IT_STRING|IT_CALL_NOTMODIFIED : IT_SECRET;
8107
8108
8109 if (tutorialmap) // If there's a tutorial available in the current add-on...
8110 SP_MainMenu[sptutorial].status = IT_CALL | IT_STRING; // ...always unlock Tutorial
8111 else // But if there's no tutorial available in the current add-on...
8112 {
8113 SP_MainMenu[sptutorial].status = IT_NOTHING|IT_DISABLED; // ...hide and disable the Tutorial option...
8114 // ...and lower the above options' display positions by 8 pixels to close the gap
8115 SP_MainMenu[spstartgame] .alphaKey += 8;
8116 SP_MainMenu[sprecordattack].alphaKey += 8;
8117 SP_MainMenu[spnightsmode] .alphaKey += 8;
8118 SP_MainMenu[spmarathon] .alphaKey += 8;
8119 }
8120
8121
8122 M_SetupNextMenu(&SP_MainDef);
8123 }
8124
8125 static void M_LoadGameLevelSelect(INT32 choice)
8126 {
8127 (void)choice;
8128
8129 SP_LevelSelectDef.prevMenu = currentMenu;
8130 levellistmode = LLM_LEVELSELECT;
8131 maplistoption = 1+2;
8132
8133 if (!M_PrepareLevelPlatter(-1, true))
8134 {
8135 M_StartMessage(M_GetText("No selectable levels found.\n"),NULL,MM_NOTHING);
8136 return;
8137 }
8138
8139 M_SetupNextMenu(&SP_LevelSelectDef);
8140 }
8141
8142 void M_TutorialSaveControlResponse(INT32 ch)
8143 {
8144 if (ch == 'y' || ch == KEY_ENTER)
8145 {
8146 G_CopyControls(gamecontrol, gamecontroldefault[tutorialgcs], gcl_tutorial_full, num_gcl_tutorial_full);
8147 CV_Set(&cv_usemouse, cv_usemouse.defaultvalue);
8148 CV_Set(&cv_alwaysfreelook, cv_alwaysfreelook.defaultvalue);
8149 CV_Set(&cv_mousemove, cv_mousemove.defaultvalue);
8150 CV_Set(&cv_analog[0], cv_analog[0].defaultvalue);
8151 S_StartSound(NULL, sfx_itemup);
8152 }
8153 else
8154 S_StartSound(NULL, sfx_menu1);
8155 }
8156
8157 static void M_TutorialControlResponse(INT32 ch)
8158 {
8159 if (ch != KEY_ESCAPE)
8160 {
8161 G_CopyControls(gamecontroldefault[gcs_custom], gamecontrol, NULL, 0); // using gcs_custom as temp storage for old controls
8162 if (ch == 'y' || ch == KEY_ENTER)
8163 {
8164 tutorialgcs = gcs_fps;
8165 tutorialusemouse = cv_usemouse.value;
8166 tutorialfreelook = cv_alwaysfreelook.value;
8167 tutorialmousemove = cv_mousemove.value;
8168 tutorialanalog = cv_analog[0].value;
8169
8170 G_CopyControls(gamecontrol, gamecontroldefault[tutorialgcs], gcl_tutorial_full, num_gcl_tutorial_full);
8171 CV_Set(&cv_usemouse, cv_usemouse.defaultvalue);
8172 CV_Set(&cv_alwaysfreelook, cv_alwaysfreelook.defaultvalue);
8173 CV_Set(&cv_mousemove, cv_mousemove.defaultvalue);
8174 CV_Set(&cv_analog[0], cv_analog[0].defaultvalue);
8175
8176 //S_StartSound(NULL, sfx_itemup);
8177 }
8178 else
8179 {
8180 tutorialgcs = gcs_custom;
8181 S_StartSound(NULL, sfx_menu1);
8182 }
8183 M_StartTutorial(INT32_MAX);
8184 }
8185 else
8186 S_StartSound(NULL, sfx_menu1);
8187
8188 MessageDef.prevMenu = &SP_MainDef; // if FirstPrompt -> ControlsPrompt -> ESC, we would go to the main menu unless we force this
8189 }
8190
8191 // Starts up the tutorial immediately (tbh I wasn't sure where else to put this)
8192 static void M_StartTutorial(INT32 choice)
8193 {
8194 if (!tutorialmap)
8195 return; // no map to go to, don't bother
8196
8197 if (choice != INT32_MAX && G_GetControlScheme(gamecontrol, gcl_tutorial_check, num_gcl_tutorial_check) != gcs_fps)
8198 {
8199 M_StartMessage("Do you want to try the \202recommended \202movement controls\x80?\n\nWe will set them just for this tutorial.\n\nPress 'Y' or 'Enter' to confirm\nPress 'N' or any key to keep \nyour current controls.\n",M_TutorialControlResponse,MM_YESNO);
8200 return;
8201 }
8202 else if (choice != INT32_MAX)
8203 tutorialgcs = gcs_custom;
8204
8205 CV_SetValue(&cv_tutorialprompt, 0); // first-time prompt
8206
8207 tutorialmode = true; // turn on tutorial mode
8208
8209 emeralds = 0;
8210 memset(&luabanks, 0, sizeof(luabanks));
8211 M_ClearMenus(true);
8212 gamecomplete = 0;
8213 cursaveslot = 0;
8214 G_DeferedInitNew(false, G_BuildMapName(tutorialmap), 0, false, false);
8215 }
8216
8217 // ==============
8218 // LOAD GAME MENU
8219 // ==============
8220
8221 static INT32 saveSlotSelected = 1;
8222 static INT32 loadgamescroll = 0;
8223 static UINT8 loadgameoffset = 0;
8224
8225 static void M_CacheLoadGameData(void)
8226 {
8227 savselp[0] = W_CachePatchName("SAVEBACK", PU_PATCH);
8228 savselp[1] = W_CachePatchName("SAVENONE", PU_PATCH);
8229 savselp[2] = W_CachePatchName("ULTIMATE", PU_PATCH);
8230
8231 savselp[3] = W_CachePatchName("GAMEDONE", PU_PATCH);
8232 savselp[4] = W_CachePatchName("BLACXLVL", PU_PATCH);
8233 savselp[5] = W_CachePatchName("BLANKLVL", PU_PATCH);
8234 }
8235
8236 static void M_DrawLoadGameData(void)
8237 {
8238 INT32 i, prev_i = 1, savetodraw, x, y, hsep = 90;
8239 skin_t *charskin = NULL;
8240
8241 if (vid.width != BASEVIDWIDTH*vid.dupx)
8242 hsep = (hsep*vid.width)/(BASEVIDWIDTH*vid.dupx);
8243
8244 for (i = 2; prev_i; i = -(i + ((UINT32)i >> 31))) // draws from outwards in; 2, -2, 1, -1, 0
8245 {
8246 prev_i = i;
8247 savetodraw = (saveSlotSelected + i + numsaves)%numsaves;
8248 x = (BASEVIDWIDTH/2 - 42 + loadgamescroll) + (i*hsep);
8249 y = 33 + 9;
8250
8251 {
8252 INT32 diff = x - (BASEVIDWIDTH/2 - 42);
8253 if (diff < 0)
8254 diff = -diff;
8255 diff = (42 - diff)/3 - loadgameoffset;
8256 if (diff < 0)
8257 diff = 0;
8258 y -= diff;
8259 }
8260
8261 if (savetodraw == 0)
8262 {
8263 V_DrawSmallScaledPatch(x, y, 0,
8264 savselp[((ultimate_selectable) ? 2 : 1)]);
8265 x += 2;
8266 y += 1;
8267 V_DrawString(x, y,
8268 ((savetodraw == saveSlotSelected) ? V_YELLOWMAP : 0),
8269 "NO FILE");
8270 if (savetodraw == saveSlotSelected)
8271 V_DrawFill(x, y+9, 80, 1, yellowmap[3]);
8272 y += 11;
8273 V_DrawSmallScaledPatch(x, y, 0, savselp[4]);
8274 M_DrawStaticBox(x, y, V_80TRANS, 80, 50);
8275 y += 41;
8276 if (ultimate_selectable)
8277 V_DrawRightAlignedThinString(x + 79, y, V_REDMAP, "ULTIMATE.");
8278 else
8279 V_DrawRightAlignedThinString(x + 79, y, V_GRAYMAP, "DON'T SAVE!");
8280
8281 continue;
8282 }
8283
8284 savetodraw--;
8285
8286 if (savegameinfo[savetodraw].lives > 0)
8287 charskin = &skins[savegameinfo[savetodraw].skinnum];
8288
8289 // signpost background
8290 {
8291 UINT8 col;
8292 if (savegameinfo[savetodraw].lives == -666)
8293 {
8294 V_DrawSmallScaledPatch(x+2, y+64, 0, savselp[5]);
8295 }
8296 #ifdef PERFECTSAVE // disabled on request
8297 else if ((savegameinfo[savetodraw].skinnum == 1)
8298 && (savegameinfo[savetodraw].lives == 99)
8299 && (savegameinfo[savetodraw].gamemap & 8192)
8300 && (savegameinfo[savetodraw].numgameovers == 0)
8301 && (savegameinfo[savetodraw].numemeralds == ((1<<7) - 1))) // perfect save
8302 {
8303 V_DrawFill(x+6, y+64, 72, 50, 134);
8304 V_DrawFill(x+6, y+74, 72, 30, 201);
8305 V_DrawFill(x+6, y+84, 72, 10, 1);
8306 }
8307 #endif
8308 else
8309 {
8310 if (savegameinfo[savetodraw].lives == -42)
8311 col = 26;
8312 else if (savegameinfo[savetodraw].botskin == 3) // & knuckles
8313 col = 105;
8314 else if (savegameinfo[savetodraw].botskin) // tailsbot or custom
8315 col = 134;
8316 else
8317 {
8318 if (charskin->prefoppositecolor)
8319 {
8320 col = charskin->prefoppositecolor;
8321 col = skincolors[col].ramp[skincolors[skincolors[col].invcolor].invshade];
8322 }
8323 else
8324 {
8325 col = charskin->prefcolor;
8326 col = skincolors[skincolors[col].invcolor].ramp[skincolors[col].invshade];
8327 }
8328 }
8329
8330 V_DrawFill(x+6, y+64, 72, 50, col);
8331 }
8332 }
8333
8334 V_DrawSmallScaledPatch(x, y, 0, savselp[0]);
8335 x += 2;
8336 y += 1;
8337 V_DrawString(x, y,
8338 ((savetodraw == saveSlotSelected-1) ? V_YELLOWMAP : 0),
8339 va("FILE %d", savetodraw+1));
8340 if (savetodraw == saveSlotSelected-1)
8341 V_DrawFill(x, y+9, 80, 1, yellowmap[3]);
8342 y += 11;
8343
8344 // level image area
8345 {
8346 if ((savegameinfo[savetodraw].lives == -42)
8347 || (savegameinfo[savetodraw].lives == -666))
8348 {
8349 V_DrawFill(x, y, 80, 50, 31);
8350 M_DrawStaticBox(x, y, V_80TRANS, 80, 50);
8351 }
8352 else
8353 {
8354 patch_t *patch;
8355 if (savegameinfo[savetodraw].gamemap & 8192)
8356 patch = savselp[3];
8357 else
8358 {
8359 lumpnum_t lumpnum = W_CheckNumForName(va("%sP", G_BuildMapName((savegameinfo[savetodraw].gamemap) & 8191)));
8360 if (lumpnum != LUMPERROR)
8361 patch = W_CachePatchNum(lumpnum, PU_PATCH);
8362 else
8363 patch = savselp[5];
8364 }
8365 V_DrawSmallScaledPatch(x, y, 0, patch);
8366 }
8367
8368 y += 41;
8369
8370 if (savegameinfo[savetodraw].lives == -42)
8371 V_DrawRightAlignedThinString(x + 79, y, V_GRAYMAP, "NEW GAME");
8372 else if (savegameinfo[savetodraw].lives == -666)
8373 V_DrawRightAlignedThinString(x + 79, y, V_REDMAP, "CAN'T LOAD!");
8374 else if (savegameinfo[savetodraw].gamemap & 8192)
8375 V_DrawRightAlignedThinString(x + 79, y, V_GREENMAP, "CLEAR!");
8376 else
8377 V_DrawRightAlignedThinString(x + 79, y, V_YELLOWMAP, savegameinfo[savetodraw].levelname);
8378 }
8379
8380 if (savegameinfo[savetodraw].lives == -42)
8381 {
8382 if (!useContinues)
8383 V_DrawRightAlignedThinString(x + 80, y+1+60+16, V_GRAYMAP, "00000000");
8384 continue;
8385 }
8386
8387 if (savegameinfo[savetodraw].lives == -666)
8388 {
8389 if (!useContinues)
8390 V_DrawRightAlignedThinString(x + 80, y+1+60+16, V_REDMAP, "????????");
8391 continue;
8392 }
8393
8394 y += 64;
8395
8396 // tiny emeralds
8397 {
8398 INT32 j, workx = x + 6;
8399 for (j = 0; j < 7; ++j)
8400 {
8401 if (savegameinfo[savetodraw].numemeralds & (1 << j))
8402 V_DrawScaledPatch(workx, y, 0, emeraldpics[1][j]);
8403 workx += 10;
8404 }
8405 }
8406
8407 y -= 4;
8408
8409 // character heads, lives, and continues/score
8410 {
8411 spritedef_t *sprdef;
8412 spriteframe_t *sprframe;
8413 patch_t *patch;
8414 UINT8 *colormap = NULL;
8415
8416 INT32 tempx = (x+40)<<FRACBITS, flip = 0;
8417
8418 // botskin first
8419 if (savegameinfo[savetodraw].botskin)
8420 {
8421 skin_t *charbotskin = &skins[savegameinfo[savetodraw].botskin-1];
8422 sprdef = &charbotskin->sprites[SPR2_SIGN];
8423 if (!sprdef->numframes)
8424 goto skipbot;
8425 colormap = R_GetTranslationColormap(savegameinfo[savetodraw].botskin-1, charbotskin->prefcolor, GTC_CACHE);
8426 sprframe = &sprdef->spriteframes[0];
8427 patch = W_CachePatchNum(sprframe->lumppat[0], PU_PATCH);
8428
8429 V_DrawFixedPatch(
8430 tempx + (18<<FRACBITS),
8431 y<<FRACBITS,
8432 charbotskin->highresscale,
8433 0, patch, colormap);
8434
8435 tempx -= (20<<FRACBITS);
8436 //flip = V_FLIP;
8437 }
8438 skipbot:
8439 // signpost image
8440 if (!charskin) // shut up compiler
8441 goto skipsign;
8442 sprdef = &charskin->sprites[SPR2_SIGN];
8443 colormap = R_GetTranslationColormap(savegameinfo[savetodraw].skinnum, charskin->prefcolor, GTC_CACHE);
8444 if (!sprdef->numframes)
8445 goto skipsign;
8446 sprframe = &sprdef->spriteframes[0];
8447 patch = W_CachePatchNum(sprframe->lumppat[0], PU_PATCH);
8448
8449 V_DrawFixedPatch(
8450 tempx,
8451 y<<FRACBITS,
8452 charskin->highresscale,
8453 flip, patch, colormap);
8454
8455 skipsign:
8456 y += 16;
8457
8458 tempx = x;
8459 if (useContinues)
8460 {
8461 tempx += 10;
8462 if (savegameinfo[savetodraw].lives != INFLIVES
8463 && savegameinfo[savetodraw].lives > 9)
8464 tempx -= 4;
8465 }
8466
8467 if (!charskin) // shut up compiler
8468 goto skiplife;
8469
8470 // lives
8471 sprdef = &charskin->sprites[SPR2_LIFE];
8472 if (!sprdef->numframes)
8473 goto skiplife;
8474 sprframe = &sprdef->spriteframes[0];
8475 patch = W_CachePatchNum(sprframe->lumppat[0], PU_PATCH);
8476
8477 V_DrawFixedPatch(
8478 (tempx + 4)<<FRACBITS,
8479 (y + 6)<<FRACBITS,
8480 charskin->highresscale/2,
8481 0, patch, colormap);
8482 skiplife:
8483
8484 patch = W_CachePatchName("STLIVEX", PU_PATCH);
8485
8486 V_DrawScaledPatch(tempx + 9, y + 2, 0, patch);
8487 tempx += 16;
8488 if (savegameinfo[savetodraw].lives == INFLIVES)
8489 V_DrawCharacter(tempx, y + 1, '\x16', false);
8490 else
8491 V_DrawString(tempx, y, 0, va("%d", savegameinfo[savetodraw].lives));
8492
8493 if (!useContinues)
8494 {
8495 INT32 workingscorenum = savegameinfo[savetodraw].continuescore;
8496 char workingscorestr[11] = " 000000000\0";
8497 SINT8 j = 9;
8498 // Change the above two lines if MAXSCORE ever changes from 8 digits long.
8499 workingscorestr[0] = '\x86'; // done here instead of in initialiser 'cuz compiler complains
8500 if (!workingscorenum)
8501 j--; // just so ONE digit is not greyed out
8502 else
8503 {
8504 while (workingscorenum)
8505 {
8506 workingscorestr[j--] = '0' + (workingscorenum % 10);
8507 workingscorenum /= 10;
8508 }
8509 }
8510 workingscorestr[j] = (savegameinfo[savetodraw].continuescore == MAXSCORE) ? '\x83' : '\x80';
8511 V_DrawRightAlignedThinString(x + 80, y+1, 0, workingscorestr);
8512 }
8513 else
8514 {
8515 tempx = x + 47;
8516 if (savegameinfo[savetodraw].continuescore > 9)
8517 tempx -= 4;
8518
8519 // continues
8520 if (savegameinfo[savetodraw].continuescore > 0)
8521 {
8522 V_DrawSmallScaledPatch(tempx, y, 0, W_CachePatchName("CONTSAVE", PU_PATCH));
8523 V_DrawScaledPatch(tempx + 9, y + 2, 0, patch);
8524 V_DrawString(tempx + 16, y, 0, va("%d", savegameinfo[savetodraw].continuescore));
8525 }
8526 else
8527 {
8528 V_DrawSmallScaledPatch(tempx, y, 0, W_CachePatchName("CONTNONE", PU_PATCH));
8529 V_DrawScaledPatch(tempx + 9, y + 2, 0, W_CachePatchName("STNONEX", PU_PATCH));
8530 V_DrawString(tempx + 16, y, V_GRAYMAP, "0");
8531 }
8532 }
8533 }
8534 }
8535 }
8536
8537 static void M_DrawLoad(void)
8538 {
8539 M_DrawMenuTitle();
8540
8541 if (loadgamescroll > 1 || loadgamescroll < -1)
8542 loadgamescroll = 2*loadgamescroll/3;
8543 else
8544 loadgamescroll = 0;
8545
8546 if (loadgameoffset > 1)
8547 loadgameoffset = 2*loadgameoffset/3;
8548 else
8549 loadgameoffset = 0;
8550
8551 M_DrawLoadGameData();
8552 }
8553
8554 //
8555 // User wants to load this game
8556 //
8557 static void M_LoadSelect(INT32 choice)
8558 {
8559 (void)choice;
8560
8561 if (saveSlotSelected == NOSAVESLOT) //last slot is play without saving
8562 {
8563 M_NewGame();
8564 cursaveslot = 0;
8565 return;
8566 }
8567
8568 if (!FIL_ReadFileOK(va(savegamename, saveSlotSelected)))
8569 {
8570 // This slot is empty, so start a new game here.
8571 M_NewGame();
8572 }
8573 else if (savegameinfo[saveSlotSelected-1].gamemap & 8192) // Completed
8574 M_LoadGameLevelSelect(0);
8575 else
8576 G_LoadGame((UINT32)saveSlotSelected, 0);
8577
8578 cursaveslot = saveSlotSelected;
8579 }
8580
8581 #define VERSIONSIZE 16
8582 #define BADSAVE { savegameinfo[slot].lives = -666; Z_Free(savebuffer); return; }
8583 #define CHECKPOS if (save_p >= end_p) BADSAVE
8584 // Reads the save file to list lives, level, player, etc.
8585 // Tails 05-29-2003
8586 static void M_ReadSavegameInfo(UINT32 slot)
8587 {
8588 size_t length;
8589 char savename[255];
8590 UINT8 *savebuffer;
8591 UINT8 *end_p; // buffer end point, don't read past here
8592 UINT8 *save_p;
8593 INT32 fake; // Dummy variable
8594 char temp[sizeof(timeattackfolder)];
8595 char vcheck[VERSIONSIZE];
8596
8597 sprintf(savename, savegamename, slot);
8598
8599 slot--;
8600
8601 length = FIL_ReadFile(savename, &savebuffer);
8602 if (length == 0)
8603 {
8604 savegameinfo[slot].lives = -42;
8605 return;
8606 }
8607
8608 end_p = savebuffer + length;
8609
8610 // skip the description field
8611 save_p = savebuffer;
8612
8613 // Version check
8614 memset(vcheck, 0, sizeof (vcheck));
8615 sprintf(vcheck, "version %d", VERSION);
8616 if (strcmp((const char *)save_p, (const char *)vcheck)) BADSAVE
8617 save_p += VERSIONSIZE;
8618
8619 // dearchive all the modifications
8620 // P_UnArchiveMisc()
8621
8622 CHECKPOS
8623 fake = READINT16(save_p);
8624
8625 if (((fake-1) & 8191) >= NUMMAPS) BADSAVE
8626
8627 if(!mapheaderinfo[(fake-1) & 8191])
8628 savegameinfo[slot].levelname[0] = '\0';
8629 else
8630 {
8631 strlcpy(savegameinfo[slot].levelname, mapheaderinfo[(fake-1) & 8191]->lvlttl, 17+1);
8632
8633 if (strlen(mapheaderinfo[(fake-1) & 8191]->lvlttl) >= 17)
8634 strcpy(savegameinfo[slot].levelname+17-3, "...");
8635 }
8636
8637 savegameinfo[slot].gamemap = fake;
8638
8639 CHECKPOS
8640 savegameinfo[slot].numemeralds = READUINT16(save_p)-357; // emeralds
8641
8642 CHECKPOS
8643 READSTRINGN(save_p, temp, sizeof(temp)); // mod it belongs to
8644
8645 if (strcmp(temp, timeattackfolder)) BADSAVE
8646
8647 // P_UnArchivePlayer()
8648 CHECKPOS
8649 fake = READUINT16(save_p);
8650 savegameinfo[slot].skinnum = fake & ((1<<5) - 1);
8651 if (savegameinfo[slot].skinnum >= numskins
8652 || !R_SkinUsable(-1, savegameinfo[slot].skinnum))
8653 BADSAVE
8654 savegameinfo[slot].botskin = fake >> 5;
8655 if (savegameinfo[slot].botskin-1 >= numskins
8656 || !R_SkinUsable(-1, savegameinfo[slot].botskin-1))
8657 BADSAVE
8658
8659 CHECKPOS
8660 savegameinfo[slot].numgameovers = READUINT8(save_p); // numgameovers
8661 CHECKPOS
8662 savegameinfo[slot].lives = READSINT8(save_p); // lives
8663 CHECKPOS
8664 savegameinfo[slot].continuescore = READINT32(save_p); // score
8665 CHECKPOS
8666 fake = READINT32(save_p); // continues
8667 if (useContinues)
8668 savegameinfo[slot].continuescore = fake;
8669
8670 // File end marker check
8671 CHECKPOS
8672 switch (READUINT8(save_p))
8673 {
8674 case 0xb7:
8675 {
8676 UINT8 i, banksinuse;
8677 CHECKPOS
8678 banksinuse = READUINT8(save_p);
8679 CHECKPOS
8680 if (banksinuse > NUM_LUABANKS)
8681 BADSAVE
8682 for (i = 0; i < banksinuse; i++)
8683 {
8684 (void)READINT32(save_p);
8685 CHECKPOS
8686 }
8687 if (READUINT8(save_p) != 0x1d)
8688 BADSAVE
8689 }
8690 case 0x1d:
8691 break;
8692 default:
8693 BADSAVE
8694 }
8695
8696 // done
8697 Z_Free(savebuffer);
8698 }
8699 #undef CHECKPOS
8700 #undef BADSAVE
8701
8702 //
8703 // M_ReadSaveStrings
8704 // read the strings from the savegame files
8705 // and put it in savegamestrings global variable
8706 //
8707 static void M_ReadSaveStrings(void)
8708 {
8709 FILE *handle;
8710 SINT8 i;
8711 char name[256];
8712 boolean nofile[MAXSAVEGAMES-1];
8713 SINT8 tolerance = 3; // empty slots at any time
8714 UINT8 lastseen = 0;
8715
8716 loadgamescroll = 0;
8717 loadgameoffset = 14;
8718
8719 for (i = 1; (i < MAXSAVEGAMES); i++) // slot 0 is no save
8720 {
8721 snprintf(name, sizeof name, savegamename, i);
8722 name[sizeof name - 1] = '\0';
8723
8724 handle = fopen(name, "rb");
8725 if ((nofile[i-1] = (handle == NULL)))
8726 continue;
8727 fclose(handle);
8728 lastseen = i;
8729 }
8730
8731 if (savegameinfo)
8732 Z_Free(savegameinfo);
8733 savegameinfo = NULL;
8734
8735 if (lastseen < saveSlotSelected)
8736 lastseen = saveSlotSelected;
8737
8738 i = lastseen;
8739
8740 for (; (lastseen > 0 && tolerance); lastseen--)
8741 {
8742 if (nofile[lastseen-1])
8743 tolerance--;
8744 }
8745
8746 if ((i += tolerance+1) > MAXSAVEGAMES) // show 3 empty slots at minimum
8747 i = MAXSAVEGAMES;
8748
8749 numsaves = i;
8750 savegameinfo = Z_Realloc(savegameinfo, numsaves*sizeof(saveinfo_t), PU_STATIC, NULL);
8751 if (!savegameinfo)
8752 I_Error("Insufficient memory to prepare save platter");
8753
8754 for (; i > 0; i--)
8755 {
8756 if (nofile[i-1] == true)
8757 {
8758 savegameinfo[i-1].lives = -42;
8759 continue;
8760 }
8761 M_ReadSavegameInfo(i);
8762 }
8763
8764 M_CacheLoadGameData();
8765 }
8766
8767 //
8768 // User wants to delete this game
8769 //
8770 static void M_SaveGameDeleteResponse(INT32 ch)
8771 {
8772 char name[256];
8773
8774 if (ch != 'y' && ch != KEY_ENTER)
8775 return;
8776
8777 // delete savegame
8778 snprintf(name, sizeof name, savegamename, saveSlotSelected);
8779 name[sizeof name - 1] = '\0';
8780 remove(name);
8781
8782 BwehHehHe();
8783 M_ReadSaveStrings(); // reload the menu
8784 }
8785
8786 static void M_SaveGameUltimateResponse(INT32 ch)
8787 {
8788 if (ch != 'y' && ch != KEY_ENTER)
8789 return;
8790
8791 S_StartSound(NULL, sfx_menu1);
8792 M_LoadSelect(saveSlotSelected);
8793 SP_PlayerDef.prevMenu = MessageDef.prevMenu;
8794 MessageDef.prevMenu = &SP_PlayerDef;
8795 }
8796
8797 static void M_HandleLoadSave(INT32 choice)
8798 {
8799 boolean exitmenu = false; // exit to previous menu
8800
8801 switch (choice)
8802 {
8803 case KEY_RIGHTARROW:
8804 S_StartSound(NULL, sfx_s3kb7);
8805 ++saveSlotSelected;
8806 if (saveSlotSelected >= numsaves)
8807 saveSlotSelected -= numsaves;
8808 loadgamescroll = 90;
8809 break;
8810
8811 case KEY_LEFTARROW:
8812 S_StartSound(NULL, sfx_s3kb7);
8813 --saveSlotSelected;
8814 if (saveSlotSelected < 0)
8815 saveSlotSelected += numsaves;
8816 loadgamescroll = -90;
8817 break;
8818
8819 case KEY_ENTER:
8820 if (ultimate_selectable && saveSlotSelected == NOSAVESLOT)
8821 {
8822 loadgamescroll = 0;
8823 S_StartSound(NULL, sfx_skid);
8824 M_StartMessage("Are you sure you want to play\n\x85ultimate mode\x80? It isn't remotely fair,\nand you don't even get an emblem for it.\n\n(Press 'Y' to confirm)\n",M_SaveGameUltimateResponse,MM_YESNO);
8825 }
8826 else if (saveSlotSelected != NOSAVESLOT && savegameinfo[saveSlotSelected-1].lives == -42 && !(!modifiedgame || savemoddata))
8827 {
8828 loadgamescroll = 0;
8829 S_StartSound(NULL, sfx_skid);
8830 M_StartMessage(M_GetText("This cannot be done in a modified game.\n\n(Press a key)\n"), NULL, MM_NOTHING);
8831 }
8832 else if (saveSlotSelected == NOSAVESLOT || savegameinfo[saveSlotSelected-1].lives != -666) // don't allow loading of "bad saves"
8833 {
8834 loadgamescroll = 0;
8835 S_StartSound(NULL, sfx_menu1);
8836 M_LoadSelect(saveSlotSelected);
8837 }
8838 else if (!loadgameoffset)
8839 {
8840 S_StartSound(NULL, sfx_lose);
8841 loadgameoffset = 14;
8842 }
8843 break;
8844
8845 case KEY_ESCAPE:
8846 exitmenu = true;
8847 break;
8848
8849 case KEY_BACKSPACE:
8850 // Don't allow people to 'delete' "Play without Saving."
8851 // Nor allow people to 'delete' slots with no saves in them.
8852 if (saveSlotSelected != NOSAVESLOT && savegameinfo[saveSlotSelected-1].lives != -42)
8853 {
8854 loadgamescroll = 0;
8855 S_StartSound(NULL, sfx_skid);
8856 M_StartMessage(va("Are you sure you want to delete\nsave file %d?\n\n(Press 'Y' to confirm)\n", saveSlotSelected),M_SaveGameDeleteResponse,MM_YESNO);
8857 }
8858 else if (!loadgameoffset)
8859 {
8860 if (saveSlotSelected == NOSAVESLOT && ultimate_selectable)
8861 {
8862 ultimate_selectable = false;
8863 S_StartSound(NULL, sfx_strpst);
8864 }
8865 else
8866 S_StartSound(NULL, sfx_lose);
8867 loadgameoffset = 14;
8868 }
8869 break;
8870 }
8871 if (exitmenu)
8872 {
8873 // Is this a hack?
8874 charseltimer = 0;
8875 if (currentMenu->prevMenu)
8876 M_SetupNextMenu(currentMenu->prevMenu);
8877 else
8878 M_ClearMenus(true);
8879 Z_Free(savegameinfo);
8880 savegameinfo = NULL;
8881 }
8882 }
8883
8884 static void M_FirstTimeResponse(INT32 ch)
8885 {
8886 S_StartSound(NULL, sfx_menu1);
8887
8888 if (ch == KEY_ESCAPE)
8889 return;
8890
8891 if (ch != 'y' && ch != KEY_ENTER)
8892 {
8893 CV_SetValue(&cv_tutorialprompt, 0);
8894 M_ReadSaveStrings();
8895 MessageDef.prevMenu = &SP_LoadDef; // calls M_SetupNextMenu
8896 }
8897 else
8898 {
8899 M_StartTutorial(0);
8900 MessageDef.prevMenu = &MessageDef; // otherwise, the controls prompt won't fire
8901 }
8902 }
8903
8904 //
8905 // Selected from SRB2 menu
8906 //
8907 static void M_LoadGame(INT32 choice)
8908 {
8909 (void)choice;
8910
8911 if (tutorialmap && cv_tutorialprompt.value)
8912 {
8913 M_StartMessage("Do you want to \x82play a brief Tutorial\x80?\n\nWe highly recommend this because \nthe controls are slightly different \nfrom other games.\n\nPress 'Y' or 'Enter' to go\nPress 'N' or any key to skip\n",
8914 M_FirstTimeResponse, MM_YESNO);
8915 return;
8916 }
8917
8918 M_ReadSaveStrings();
8919 M_SetupNextMenu(&SP_LoadDef);
8920 }
8921
8922 //
8923 // Used by cheats to force the save menu to a specific spot.
8924 //
8925 void M_ForceSaveSlotSelected(INT32 sslot)
8926 {
8927 loadgameoffset = 14;
8928
8929 // Already there? Whatever, then!
8930 if (sslot == saveSlotSelected)
8931 return;
8932
8933 loadgamescroll = 90;
8934 if (saveSlotSelected <= numsaves/2)
8935 loadgamescroll = -loadgamescroll;
8936
8937 saveSlotSelected = sslot;
8938 }
8939
8940 // ================
8941 // CHARACTER SELECT
8942 // ================
8943
8944 static void M_CacheCharacterSelectEntry(INT32 i, INT32 skinnum)
8945 {
8946 if (!(description[i].picname[0]))
8947 {
8948 if (skins[skinnum].sprites[SPR2_XTRA].numframes > XTRA_CHARSEL)
8949 {
8950 spritedef_t *sprdef = &skins[skinnum].sprites[SPR2_XTRA];
8951 spriteframe_t *sprframe = &sprdef->spriteframes[XTRA_CHARSEL];
8952 description[i].charpic = W_CachePatchNum(sprframe->lumppat[0], PU_PATCH);
8953 }
8954 else
8955 description[i].charpic = W_CachePatchName("MISSING", PU_PATCH);
8956 }
8957 else
8958 description[i].charpic = W_CachePatchName(description[i].picname, PU_PATCH);
8959
8960 if (description[i].nametag[0])
8961 description[i].namepic = W_CachePatchName(description[i].nametag, PU_PATCH);
8962 }
8963
8964 static UINT8 M_SetupChoosePlayerDirect(INT32 choice)
8965 {
8966 INT32 skinnum;
8967 UINT8 i;
8968 UINT8 firstvalid = 255, lastvalid = 255;
8969 boolean allowed = false;
8970 char *and;
8971 (void)choice;
8972
8973 if (!mapheaderinfo[startmap-1] || mapheaderinfo[startmap-1]->forcecharacter[0] == '\0')
8974 {
8975 for (i = 0; i < MAXSKINS; i++) // Handle charsels, availability, and unlocks.
8976 {
8977 if (description[i].used) // If the character's disabled through SOC, there's nothing we can do for it.
8978 {
8979 and = strchr(description[i].skinname, '&');
8980 if (and)
8981 {
8982 char firstskin[SKINNAMESIZE+1];
8983 if (mapheaderinfo[startmap-1] && mapheaderinfo[startmap-1]->typeoflevel & TOL_NIGHTS) // skip tagteam characters for NiGHTS levels
8984 continue;
8985 strncpy(firstskin, description[i].skinname, (and - description[i].skinname));
8986 firstskin[(and - description[i].skinname)] = '\0';
8987 description[i].skinnum[0] = R_SkinAvailable(firstskin);
8988 description[i].skinnum[1] = R_SkinAvailable(and+1);
8989 }
8990 else
8991 {
8992 description[i].skinnum[0] = R_SkinAvailable(description[i].skinname);
8993 description[i].skinnum[1] = -1;
8994 }
8995 skinnum = description[i].skinnum[0];
8996 if ((skinnum != -1) && (R_SkinUsable(-1, skinnum)))
8997 {
8998 // Handling order.
8999 if (firstvalid == 255)
9000 firstvalid = i;
9001 else
9002 {
9003 description[i].prev = lastvalid;
9004 description[lastvalid].next = i;
9005 }
9006 lastvalid = i;
9007
9008 if (i == char_on)
9009 allowed = true;
9010
9011 M_CacheCharacterSelectEntry(i, skinnum);
9012 }
9013 // else -- Technically, character select icons without corresponding skins get bundled away behind this too. Sucks to be them.
9014 }
9015 }
9016 }
9017
9018 if (firstvalid == lastvalid) // We're being forced into a specific character, so might as well just skip it.
9019 {
9020 return firstvalid;
9021 }
9022
9023 // One last bit of order we can't do in the iteration above.
9024 description[firstvalid].prev = lastvalid;
9025 description[lastvalid].next = firstvalid;
9026
9027 if (!allowed)
9028 {
9029 char_on = firstvalid;
9030 if (startchar > 0 && startchar < MAXSKINS)
9031 {
9032 INT16 workchar = startchar;
9033 while (workchar--)
9034 char_on = description[char_on].next;
9035 }
9036 }
9037
9038 return MAXSKINS;
9039 }
9040
9041 static void M_SetupChoosePlayer(INT32 choice)
9042 {
9043 UINT8 skinset = M_SetupChoosePlayerDirect(choice);
9044 if (skinset != MAXSKINS)
9045 {
9046 M_ChoosePlayer(skinset);
9047 return;
9048 }
9049
9050 M_ChangeMenuMusic("_chsel", true);
9051
9052 /* the menus suck -James */
9053 if (currentMenu == &SP_LoadDef)/* from save states */
9054 {
9055 SP_PlayerDef.menuid = MTREE3(MN_SP_MAIN, MN_SP_LOAD, MN_SP_PLAYER);
9056 }
9057 else/* from Secret level select */
9058 {
9059 SP_PlayerDef.menuid = MTREE2(MN_SR_MAIN, MN_SR_PLAYER);
9060 }
9061
9062 SP_PlayerDef.prevMenu = currentMenu;
9063 M_SetupNextMenu(&SP_PlayerDef);
9064
9065 // finish scrolling the menu
9066 char_scroll = 0;
9067 charseltimer = 0;
9068
9069 Z_Free(char_notes);
9070 char_notes = V_WordWrap(0, 21*8, V_ALLOWLOWERCASE, description[char_on].notes);
9071 }
9072
9073 //
9074 // M_HandleChoosePlayerMenu
9075 //
9076 // Reacts to your key inputs. Basically a mini menu thinker.
9077 //
9078 static void M_HandleChoosePlayerMenu(INT32 choice)
9079 {
9080 boolean exitmenu = false; // exit to previous menu
9081 INT32 selectval;
9082
9083 if (keydown > 1)
9084 return;
9085
9086 switch (choice)
9087 {
9088 case KEY_DOWNARROW:
9089 if ((selectval = description[char_on].next) != char_on)
9090 {
9091 S_StartSound(NULL,sfx_s3kb7);
9092 char_on = selectval;
9093 char_scroll = -charscrollamt;
9094 Z_Free(char_notes);
9095 char_notes = V_WordWrap(0, 21*8, V_ALLOWLOWERCASE, description[char_on].notes);
9096 }
9097 else if (!char_scroll)
9098 {
9099 S_StartSound(NULL,sfx_s3kb7);
9100 char_scroll = 16*FRACUNIT;
9101 }
9102 break;
9103
9104 case KEY_UPARROW:
9105 if ((selectval = description[char_on].prev) != char_on)
9106 {
9107 S_StartSound(NULL,sfx_s3kb7);
9108 char_on = selectval;
9109 char_scroll = charscrollamt;
9110 Z_Free(char_notes);
9111 char_notes = V_WordWrap(0, 21*8, V_ALLOWLOWERCASE, description[char_on].notes);
9112 }
9113 else if (!char_scroll)
9114 {
9115 S_StartSound(NULL,sfx_s3kb7);
9116 char_scroll = -16*FRACUNIT;
9117 }
9118 break;
9119
9120 case KEY_ENTER:
9121 S_StartSound(NULL, sfx_menu1);
9122 char_scroll = 0; // finish scrolling the menu
9123 M_DrawSetupChoosePlayerMenu(); // draw the finally selected character one last time for the fadeout
9124 // Is this a hack?
9125 charseltimer = 0;
9126 M_ChoosePlayer(char_on);
9127 break;
9128
9129 case KEY_ESCAPE:
9130 exitmenu = true;
9131 break;
9132
9133 default:
9134 break;
9135 }
9136
9137 if (exitmenu)
9138 {
9139 // Is this a hack?
9140 charseltimer = 0;
9141 if (currentMenu->prevMenu)
9142 M_SetupNextMenu(currentMenu->prevMenu);
9143 else
9144 M_ClearMenus(true);
9145 }
9146 }
9147
9148 // Draw the choose player setup menu, had some fun with player anim
9149 //define CHOOSEPLAYER_DRAWHEADER
9150
9151 static void M_DrawSetupChoosePlayerMenu(void)
9152 {
9153 const INT32 my = 16;
9154
9155 skin_t *charskin = &skins[0];
9156 INT32 skinnum = 0;
9157 UINT16 col;
9158 UINT8 *colormap = NULL;
9159 INT32 prev = -1, next = -1;
9160
9161 patch_t *charbg = W_CachePatchName("CHARBG", PU_PATCH);
9162 patch_t *charfg = W_CachePatchName("CHARFG", PU_PATCH);
9163 INT16 bgheight = charbg->height;
9164 INT16 fgheight = charfg->height;
9165 INT16 bgwidth = charbg->width;
9166 INT16 fgwidth = charfg->width;
9167 INT32 x, y;
9168 INT32 w = (vid.width/vid.dupx);
9169
9170 if (abs(char_scroll) > FRACUNIT)
9171 char_scroll -= (char_scroll>>2);
9172 else // close enough.
9173 char_scroll = 0; // just be exact now.
9174
9175 // Get prev character...
9176 prev = description[char_on].prev;
9177 // If there's more than one character available...
9178 if (prev != char_on)
9179 // Let's get the next character now.
9180 next = description[char_on].next;
9181 else
9182 // No there isn't.
9183 prev = -1;
9184
9185 // Find skin number from description[]
9186 skinnum = description[char_on].skinnum[0];
9187 charskin = &skins[skinnum];
9188
9189 // Use the opposite of the character's skincolor
9190 col = description[char_on].oppositecolor;
9191 if (!col)
9192 col = skincolors[charskin->prefcolor].invcolor;
9193
9194 // Make the translation colormap
9195 colormap = R_GetTranslationColormap(TC_DEFAULT, col, GTC_CACHE);
9196
9197 // Don't render the title map
9198 hidetitlemap = true;
9199 charseltimer++;
9200
9201 // Background and borders
9202 V_DrawFill(0, 0, bgwidth, vid.height, V_SNAPTOTOP|colormap[101]);
9203 {
9204 INT32 sw = (BASEVIDWIDTH * vid.dupx);
9205 INT32 bw = (vid.width - sw) / 2;
9206 col = colormap[106];
9207 if (bw)
9208 V_DrawFill(0, 0, bw, vid.height, V_NOSCALESTART|col);
9209 }
9210
9211 y = (charseltimer%32);
9212 V_DrawMappedPatch(0, y-bgheight, V_SNAPTOTOP, charbg, colormap);
9213 V_DrawMappedPatch(0, y, V_SNAPTOTOP, charbg, colormap);
9214 V_DrawMappedPatch(0, y+bgheight, V_SNAPTOTOP, charbg, colormap);
9215 V_DrawMappedPatch(0, -y, V_SNAPTOTOP, charfg, colormap);
9216 V_DrawMappedPatch(0, -y+fgheight, V_SNAPTOTOP, charfg, colormap);
9217 V_DrawFill(fgwidth, 0, vid.width, vid.height, V_SNAPTOTOP|colormap[106]);
9218
9219 // Character pictures
9220 {
9221 x = 8;
9222 y = (my+16) - FixedInt(char_scroll);
9223 V_DrawScaledPatch(x, y, 0, description[char_on].charpic);
9224 if (prev != -1)
9225 V_DrawScaledPatch(x, y - 144, 0, description[prev].charpic);
9226 if (next != -1)
9227 V_DrawScaledPatch(x, y + 144, 0, description[next].charpic);
9228 }
9229
9230 // Character description
9231 {
9232 INT32 flags = V_ALLOWLOWERCASE|V_RETURN8;
9233 x = 146;
9234 y = my + 9;
9235 V_DrawString(x, y, flags, char_notes);
9236 }
9237
9238 // Name tags
9239 {
9240 INT32 ox, oxsh = FixedInt(FixedMul(BASEVIDWIDTH*FRACUNIT, FixedDiv(char_scroll, 128*FRACUNIT))), txsh;
9241 patch_t *curpatch = NULL, *prevpatch = NULL, *nextpatch = NULL;
9242 const char *curtext = NULL, *prevtext = NULL, *nexttext = NULL;
9243 UINT16 curtextcolor = 0, prevtextcolor = 0, nexttextcolor = 0;
9244 UINT16 curoutlinecolor = 0, prevoutlinecolor = 0, nextoutlinecolor = 0;
9245
9246 // Name tag
9247 curtext = description[char_on].displayname;
9248 curtextcolor = description[char_on].tagtextcolor;
9249 curoutlinecolor = description[char_on].tagoutlinecolor;
9250 if (curtext[0] == '\0')
9251 curpatch = description[char_on].namepic;
9252 if (!curtextcolor)
9253 curtextcolor = charskin->prefcolor;
9254 if (!curoutlinecolor)
9255 curoutlinecolor = col = skincolors[charskin->prefcolor].invcolor;
9256
9257 txsh = oxsh;
9258 ox = 8 + ((description[char_on].charpic)->width)/2;
9259 y = my + 144;
9260
9261 // cur
9262 {
9263 x = ox - txsh;
9264 if (curpatch)
9265 x -= curpatch->width / 2;
9266
9267 if (curtext[0] != '\0')
9268 {
9269 V_DrawNameTag(
9270 x, y, V_CENTERNAMETAG, FRACUNIT,
9271 R_GetTranslationColormap(TC_DEFAULT, curtextcolor, GTC_CACHE),
9272 R_GetTranslationColormap(TC_DEFAULT, curoutlinecolor, GTC_CACHE),
9273 curtext
9274 );
9275 }
9276 else if (curpatch)
9277 V_DrawScaledPatch(x, y, 0, curpatch);
9278 }
9279
9280 if (char_scroll)
9281 {
9282 // prev
9283 if ((prev != -1) && char_scroll < 0)
9284 {
9285 prevtext = description[prev].displayname;
9286 prevtextcolor = description[prev].tagtextcolor;
9287 prevoutlinecolor = description[prev].tagoutlinecolor;
9288 if (prevtext[0] == '\0')
9289 prevpatch = description[prev].namepic;
9290 charskin = &skins[description[prev].skinnum[0]];
9291 if (!prevtextcolor)
9292 prevtextcolor = charskin->prefcolor;
9293 if (!prevoutlinecolor)
9294 prevoutlinecolor = col = skincolors[charskin->prefcolor].invcolor;
9295
9296 x = (ox - txsh) - w;
9297 if (prevpatch)
9298 x -= prevpatch->width / 2;
9299
9300 if (prevtext[0] != '\0')
9301 {
9302 V_DrawNameTag(
9303 x, y, V_CENTERNAMETAG, FRACUNIT,
9304 R_GetTranslationColormap(TC_DEFAULT, prevtextcolor, GTC_CACHE),
9305 R_GetTranslationColormap(TC_DEFAULT, prevoutlinecolor, GTC_CACHE),
9306 prevtext
9307 );
9308 }
9309 else if (prevpatch)
9310 V_DrawScaledPatch(x, y, 0, prevpatch);
9311 }
9312 // next
9313 else if ((next != -1) && char_scroll > 0)
9314 {
9315 nexttext = description[next].displayname;
9316 nexttextcolor = description[next].tagtextcolor;
9317 nextoutlinecolor = description[next].tagoutlinecolor;
9318 if (nexttext[0] == '\0')
9319 nextpatch = description[next].namepic;
9320 charskin = &skins[description[next].skinnum[0]];
9321 if (!nexttextcolor)
9322 nexttextcolor = charskin->prefcolor;
9323 if (!nextoutlinecolor)
9324 nextoutlinecolor = col = skincolors[charskin->prefcolor].invcolor;
9325
9326 x = (ox - txsh) + w;
9327 if (nextpatch)
9328 x -= nextpatch->width / 2;
9329
9330 if (nexttext[0] != '\0')
9331 {
9332 V_DrawNameTag(
9333 x, y, V_CENTERNAMETAG, FRACUNIT,
9334 R_GetTranslationColormap(TC_DEFAULT, nexttextcolor, GTC_CACHE),
9335 R_GetTranslationColormap(TC_DEFAULT, nextoutlinecolor, GTC_CACHE),
9336 nexttext
9337 );
9338 }
9339 else if (nextpatch)
9340 V_DrawScaledPatch(x, y, 0, nextpatch);
9341 }
9342 }
9343 }
9344
9345 // Alternative menu header
9346 #ifdef CHOOSEPLAYER_DRAWHEADER
9347 {
9348 patch_t *header = W_CachePatchName("M_PICKP", PU_PATCH);
9349 INT32 xtitle = 146;
9350 INT32 ytitle = (35 - header->height) / 2;
9351 V_DrawFixedPatch(xtitle<<FRACBITS, ytitle<<FRACBITS, FRACUNIT/2, 0, header, NULL);
9352 }
9353 #endif // CHOOSEPLAYER_DRAWHEADER
9354
9355 M_DrawMenuTitle();
9356 }
9357
9358 // Chose the player you want to use Tails 03-02-2002
9359 static void M_ChoosePlayer(INT32 choice)
9360 {
9361 boolean ultmode = (currentMenu == &SP_MarathonDef) ? (cv_dummymarathon.value == 2) : (ultimate_selectable && SP_PlayerDef.prevMenu == &SP_LoadDef && saveSlotSelected == NOSAVESLOT);
9362 UINT8 skinnum;
9363
9364 // skip this if forcecharacter or no characters available
9365 if (choice == 255)
9366 {
9367 skinnum = botskin = 0;
9368 botingame = false;
9369 }
9370 // M_SetupChoosePlayer didn't call us directly, that means we've been properly set up.
9371 else
9372 {
9373 skinnum = description[choice].skinnum[0];
9374
9375 if ((botingame = (description[choice].skinnum[1] != -1))) {
9376 // this character has a second skin
9377 botskin = (UINT8)(description[choice].skinnum[1]+1);
9378 botcolor = skins[description[choice].skinnum[1]].prefcolor;
9379 }
9380 else
9381 botskin = botcolor = 0;
9382 }
9383
9384 M_ClearMenus(true);
9385
9386 if (!marathonmode && startmap != spstage_start)
9387 cursaveslot = 0;
9388
9389 //lastmapsaved = 0;
9390 gamecomplete = 0;
9391
9392 G_DeferedInitNew(ultmode, G_BuildMapName(startmap), skinnum, false, fromlevelselect);
9393 COM_BufAddText("dummyconsvar 1\n"); // G_DeferedInitNew doesn't do this
9394
9395 if (levelselect.rows)
9396 Z_Free(levelselect.rows);
9397 levelselect.rows = NULL;
9398
9399 if (savegameinfo)
9400 Z_Free(savegameinfo);
9401 savegameinfo = NULL;
9402 }
9403
9404 // ===============
9405 // STATISTICS MENU
9406 // ===============
9407
9408 static INT32 statsLocation;
9409 static INT32 statsMax;
9410 static INT16 statsMapList[NUMMAPS+1];
9411
9412 static void M_Statistics(INT32 choice)
9413 {
9414 INT16 i, j = 0;
9415
9416 (void)choice;
9417
9418 memset(statsMapList, 0, sizeof(statsMapList));
9419
9420 for (i = 0; i < NUMMAPS; i++)
9421 {
9422 if (!mapheaderinfo[i] || mapheaderinfo[i]->lvlttl[0] == '\0')
9423 continue;
9424
9425 if (!(mapheaderinfo[i]->typeoflevel & TOL_SP) || (mapheaderinfo[i]->menuflags & LF2_HIDEINSTATS))
9426 continue;
9427
9428 if (!(mapvisited[i] & MV_MAX))
9429 continue;
9430
9431 statsMapList[j++] = i;
9432 }
9433 statsMapList[j] = -1;
9434 statsMax = j - 11 + numextraemblems;
9435 statsLocation = 0;
9436
9437 if (statsMax < 0)
9438 statsMax = 0;
9439
9440 M_SetupNextMenu(&SP_LevelStatsDef);
9441 }
9442
9443 static void M_DrawStatsMaps(int location)
9444 {
9445 INT32 y = 80, i = -1;
9446 INT16 mnum;
9447 extraemblem_t *exemblem;
9448 boolean dotopname = true, dobottomarrow = (location < statsMax);
9449
9450 if (location)
9451 V_DrawString(10, y-(skullAnimCounter/5), V_YELLOWMAP, "\x1A");
9452
9453 while (statsMapList[++i] != -1)
9454 {
9455 if (location)
9456 {
9457 --location;
9458 continue;
9459 }
9460 else if (dotopname)
9461 {
9462 V_DrawString(20, y, V_GREENMAP, "LEVEL NAME");
9463 V_DrawString(248, y, V_GREENMAP, "EMBLEMS");
9464 y += 8;
9465 dotopname = false;
9466 }
9467
9468 mnum = statsMapList[i];
9469 M_DrawMapEmblems(mnum+1, 292, y);
9470
9471 if (mapheaderinfo[mnum]->actnum != 0)
9472 V_DrawString(20, y, V_YELLOWMAP|V_ALLOWLOWERCASE, va("%s %d", mapheaderinfo[mnum]->lvlttl, mapheaderinfo[mnum]->actnum));
9473 else
9474 V_DrawString(20, y, V_YELLOWMAP|V_ALLOWLOWERCASE, mapheaderinfo[mnum]->lvlttl);
9475
9476 y += 8;
9477
9478 if (y >= BASEVIDHEIGHT-8)
9479 goto bottomarrow;
9480 }
9481 if (dotopname && !location)
9482 {
9483 V_DrawString(20, y, V_GREENMAP, "LEVEL NAME");
9484 V_DrawString(248, y, V_GREENMAP, "EMBLEMS");
9485 y += 8;
9486 }
9487 else if (location)
9488 --location;
9489
9490 // Extra Emblems
9491 for (i = -2; i < numextraemblems; ++i)
9492 {
9493 if (i == -1)
9494 {
9495 V_DrawString(20, y, V_GREENMAP, "EXTRA EMBLEMS");
9496 if (location)
9497 {
9498 y += 8;
9499 location++;
9500 }
9501 }
9502 if (location)
9503 {
9504 --location;
9505 continue;
9506 }
9507
9508 if (i >= 0)
9509 {
9510 exemblem = &extraemblems[i];
9511
9512 if (exemblem->collected)
9513 V_DrawSmallMappedPatch(292, y, 0, W_CachePatchName(M_GetExtraEmblemPatch(exemblem, false), PU_PATCH),
9514 R_GetTranslationColormap(TC_DEFAULT, M_GetExtraEmblemColor(exemblem), GTC_CACHE));
9515 else
9516 V_DrawSmallScaledPatch(292, y, 0, W_CachePatchName("NEEDIT", PU_PATCH));
9517
9518 V_DrawString(20, y, V_YELLOWMAP|V_ALLOWLOWERCASE,
9519 (!exemblem->collected && exemblem->showconditionset && !M_Achieved(exemblem->showconditionset))
9520 ? M_CreateSecretMenuOption(exemblem->description)
9521 : exemblem->description);
9522 }
9523
9524 y += 8;
9525
9526 if (y >= BASEVIDHEIGHT-8)
9527 goto bottomarrow;
9528 }
9529 bottomarrow:
9530 if (dobottomarrow)
9531 V_DrawString(10, y-8 + (skullAnimCounter/5), V_YELLOWMAP, "\x1B");
9532 }
9533
9534 static void M_DrawLevelStats(void)
9535 {
9536 char beststr[40];
9537
9538 tic_t besttime = 0;
9539 UINT32 bestscore = 0;
9540 UINT32 bestrings = 0;
9541
9542 INT32 i;
9543 INT32 mapsunfinished = 0;
9544 boolean bestunfinished[3] = {false, false, false};
9545
9546 M_DrawMenuTitle();
9547
9548 V_DrawString(20, 24, V_YELLOWMAP, "Total Play Time:");
9549 V_DrawCenteredString(BASEVIDWIDTH/2, 32, 0, va("%i hours, %i minutes, %i seconds",
9550 G_TicsToHours(totalplaytime),
9551 G_TicsToMinutes(totalplaytime, false),
9552 G_TicsToSeconds(totalplaytime)));
9553
9554 for (i = 0; i < NUMMAPS; i++)
9555 {
9556 boolean mapunfinished = false;
9557
9558 if (!mapheaderinfo[i] || !(mapheaderinfo[i]->menuflags & LF2_RECORDATTACK))
9559 continue;
9560
9561 if (!mainrecords[i])
9562 {
9563 mapsunfinished++;
9564 bestunfinished[0] = bestunfinished[1] = bestunfinished[2] = true;
9565 continue;
9566 }
9567
9568 if (mainrecords[i]->score > 0)
9569 bestscore += mainrecords[i]->score;
9570 else
9571 mapunfinished = bestunfinished[0] = true;
9572
9573 if (mainrecords[i]->time > 0)
9574 besttime += mainrecords[i]->time;
9575 else
9576 mapunfinished = bestunfinished[1] = true;
9577
9578 if (mainrecords[i]->rings > 0)
9579 bestrings += mainrecords[i]->rings;
9580 else
9581 mapunfinished = bestunfinished[2] = true;
9582
9583 if (mapunfinished)
9584 mapsunfinished++;
9585 }
9586
9587 V_DrawString(20, 48, 0, "Combined records:");
9588
9589 if (mapsunfinished)
9590 V_DrawString(20, 56, V_REDMAP, va("(%d unfinished)", mapsunfinished));
9591 else
9592 V_DrawString(20, 56, V_GREENMAP, "(complete)");
9593
9594 V_DrawString(36, 64, 0, va("x %d/%d", M_CountEmblems(), numemblems+numextraemblems));
9595 V_DrawSmallScaledPatch(20, 64, 0, W_CachePatchName("EMBLICON", PU_PATCH));
9596
9597 sprintf(beststr, "%u", bestscore);
9598 V_DrawString(BASEVIDWIDTH/2, 48, V_YELLOWMAP, "SCORE:");
9599 V_DrawRightAlignedString(BASEVIDWIDTH-16, 48, (bestunfinished[0] ? V_REDMAP : 0), beststr);
9600
9601 sprintf(beststr, "%i:%02i:%02i.%02i", G_TicsToHours(besttime), G_TicsToMinutes(besttime, false), G_TicsToSeconds(besttime), G_TicsToCentiseconds(besttime));
9602 V_DrawString(BASEVIDWIDTH/2, 56, V_YELLOWMAP, "TIME:");
9603 V_DrawRightAlignedString(BASEVIDWIDTH-16, 56, (bestunfinished[1] ? V_REDMAP : 0), beststr);
9604
9605 sprintf(beststr, "%u", bestrings);
9606 V_DrawString(BASEVIDWIDTH/2, 64, V_YELLOWMAP, "RINGS:");
9607 V_DrawRightAlignedString(BASEVIDWIDTH-16, 64, (bestunfinished[2] ? V_REDMAP : 0), beststr);
9608
9609 M_DrawStatsMaps(statsLocation);
9610 }
9611
9612 // Handle statistics.
9613 static void M_HandleLevelStats(INT32 choice)
9614 {
9615 boolean exitmenu = false; // exit to previous menu
9616
9617 switch (choice)
9618 {
9619 case KEY_DOWNARROW:
9620 S_StartSound(NULL, sfx_menu1);
9621 if (statsLocation < statsMax)
9622 ++statsLocation;
9623 break;
9624
9625 case KEY_UPARROW:
9626 S_StartSound(NULL, sfx_menu1);
9627 if (statsLocation)
9628 --statsLocation;
9629 break;
9630
9631 case KEY_PGDN:
9632 S_StartSound(NULL, sfx_menu1);
9633 statsLocation += (statsLocation+13 >= statsMax) ? statsMax-statsLocation : 13;
9634 break;
9635
9636 case KEY_PGUP:
9637 S_StartSound(NULL, sfx_menu1);
9638 statsLocation -= (statsLocation < 13) ? statsLocation : 13;
9639 break;
9640
9641 case KEY_ESCAPE:
9642 exitmenu = true;
9643 break;
9644 }
9645 if (exitmenu)
9646 {
9647 if (currentMenu->prevMenu)
9648 M_SetupNextMenu(currentMenu->prevMenu);
9649 else
9650 M_ClearMenus(true);
9651 }
9652 }
9653
9654 // ===========
9655 // MODE ATTACK
9656 // ===========
9657
9658 // Drawing function for Time Attack
9659 void M_DrawTimeAttackMenu(void)
9660 {
9661 INT32 i, x, y, empatx, empaty, cursory = 0;
9662 UINT16 dispstatus;
9663 patch_t *PictureOfUrFace; // my WHAT
9664 patch_t *empatch;
9665
9666 M_SetMenuCurBackground("RECATKBG");
9667
9668 curbgxspeed = 0;
9669 curbgyspeed = 18;
9670
9671 M_ChangeMenuMusic("_recat", true); // Eww, but needed for when user hits escape during demo playback
9672
9673 if (curbgcolor >= 0)
9674 V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, curbgcolor);
9675 else if (!curbghide || !titlemapinaction)
9676 {
9677 F_SkyScroll(curbgxspeed, curbgyspeed, curbgname);
9678 // Draw and animate foreground
9679 if (!strncmp("RECATKBG", curbgname, 8))
9680 M_DrawRecordAttackForeground();
9681 }
9682 if (curfadevalue)
9683 V_DrawFadeScreen(0xFF00, curfadevalue);
9684
9685 M_DrawMenuTitle();
9686
9687 // draw menu (everything else goes on top of it)
9688 // Sadly we can't just use generic mode menus because we need some extra hacks
9689 x = currentMenu->x;
9690 y = currentMenu->y;
9691
9692 for (i = 0; i < currentMenu->numitems; ++i)
9693 {
9694 dispstatus = (currentMenu->menuitems[i].status & IT_DISPLAY);
9695 if (dispstatus != IT_STRING && dispstatus != IT_WHITESTRING)
9696 continue;
9697
9698 y = currentMenu->y+currentMenu->menuitems[i].alphaKey;
9699 if (i == itemOn)
9700 cursory = y;
9701
9702 V_DrawString(x, y, (dispstatus == IT_WHITESTRING) ? V_YELLOWMAP : 0 , currentMenu->menuitems[i].text);
9703
9704 // Cvar specific handling
9705 if ((currentMenu->menuitems[i].status & IT_TYPE) == IT_CVAR)
9706 {
9707 consvar_t *cv = (consvar_t *)currentMenu->menuitems[i].itemaction;
9708 INT32 soffset = 0;
9709
9710 // hack to keep the menu from overlapping the player icon
9711 if (currentMenu != &SP_TimeAttackDef)
9712 soffset = 80;
9713
9714 // Should see nothing but strings
9715 V_DrawString(BASEVIDWIDTH - x - soffset - V_StringWidth(cv->string, 0), y, V_YELLOWMAP, cv->string);
9716 if (i == itemOn)
9717 {
9718 V_DrawCharacter(BASEVIDWIDTH - x - soffset - 10 - V_StringWidth(cv->string, 0) - (skullAnimCounter/5), y,
9719 '\x1C' | V_YELLOWMAP, false);
9720 V_DrawCharacter(BASEVIDWIDTH - x - soffset + 2 + (skullAnimCounter/5), y,
9721 '\x1D' | V_YELLOWMAP, false);
9722 }
9723 }
9724 }
9725
9726 // DRAW THE SKULL CURSOR
9727 V_DrawScaledPatch(currentMenu->x - 24, cursory, 0, W_CachePatchName("M_CURSOR", PU_PATCH));
9728 V_DrawString(currentMenu->x, cursory, V_YELLOWMAP, currentMenu->menuitems[itemOn].text);
9729
9730 // Character face!
9731 {
9732 if (skins[cv_chooseskin.value-1].sprites[SPR2_XTRA].numframes > XTRA_CHARSEL)
9733 {
9734 spritedef_t *sprdef = &skins[cv_chooseskin.value-1].sprites[SPR2_XTRA];
9735 spriteframe_t *sprframe = &sprdef->spriteframes[XTRA_CHARSEL];
9736 PictureOfUrFace = W_CachePatchNum(sprframe->lumppat[0], PU_PATCH);
9737 }
9738 else
9739 PictureOfUrFace = W_CachePatchName("MISSING", PU_PATCH);
9740
9741 if (PictureOfUrFace->width >= 256)
9742 V_DrawTinyScaledPatch(224, 120, 0, PictureOfUrFace);
9743 else
9744 V_DrawSmallScaledPatch(224, 120, 0, PictureOfUrFace);
9745 }
9746
9747 // Level record list
9748 if (cv_nextmap.value)
9749 {
9750 emblem_t *em;
9751 INT32 yHeight;
9752 patch_t *PictureOfLevel;
9753 lumpnum_t lumpnum;
9754 char beststr[40];
9755 char reqscore[40], reqtime[40], reqrings[40];
9756
9757 strcpy(reqscore, "\0");
9758 strcpy(reqtime, "\0");
9759 strcpy(reqrings, "\0");
9760
9761 M_DrawLevelPlatterHeader(32-lsheadingheight/2, cv_nextmap.string, true, false);
9762
9763 // A 160x100 image of the level as entry MAPxxP
9764 lumpnum = W_CheckNumForName(va("%sP", G_BuildMapName(cv_nextmap.value)));
9765
9766 if (lumpnum != LUMPERROR)
9767 PictureOfLevel = W_CachePatchName(va("%sP", G_BuildMapName(cv_nextmap.value)), PU_PATCH);
9768 else
9769 PictureOfLevel = W_CachePatchName("BLANKLVL", PU_PATCH);
9770
9771 y = 32+lsheadingheight;
9772 V_DrawSmallScaledPatch(216, y, 0, PictureOfLevel);
9773
9774
9775 if (currentMenu == &SP_TimeAttackDef)
9776 {
9777 if (itemOn == talevel)
9778 {
9779 /* Draw arrows !! */
9780 y = y + 25 - 4;
9781 V_DrawCharacter(216 - 10 - (skullAnimCounter/5), y,
9782 '\x1C' | V_YELLOWMAP, false);
9783 V_DrawCharacter(216 + 80 + 2 + (skullAnimCounter/5), y,
9784 '\x1D' | V_YELLOWMAP, false);
9785 }
9786 // Draw press ESC to exit string on main record attack menu
9787 V_DrawString(104-72, 180, V_TRANSLUCENT, M_GetText("Press ESC to exit"));
9788 }
9789
9790 em = M_GetLevelEmblems(cv_nextmap.value);
9791 // Draw record emblems.
9792 while (em)
9793 {
9794 switch (em->type)
9795 {
9796 case ET_SCORE:
9797 yHeight = 33;
9798 sprintf(reqscore, "(%u)", em->var);
9799 break;
9800 case ET_TIME:
9801 yHeight = 53;
9802 sprintf(reqtime, "(%i:%02i.%02i)", G_TicsToMinutes((tic_t)em->var, true),
9803 G_TicsToSeconds((tic_t)em->var),
9804 G_TicsToCentiseconds((tic_t)em->var));
9805 break;
9806 case ET_RINGS:
9807 yHeight = 73;
9808 sprintf(reqrings, "(%u)", em->var);
9809 break;
9810 default:
9811 goto skipThisOne;
9812 }
9813
9814 empatch = W_CachePatchName(M_GetEmblemPatch(em, true), PU_PATCH);
9815
9816 empatx = empatch->leftoffset / 2;
9817 empaty = empatch->topoffset / 2;
9818
9819 if (em->collected)
9820 V_DrawSmallMappedPatch(104+76+empatx, yHeight+lsheadingheight/2+empaty, 0, empatch,
9821 R_GetTranslationColormap(TC_DEFAULT, M_GetEmblemColor(em), GTC_CACHE));
9822 else
9823 V_DrawSmallScaledPatch(104+76, yHeight+lsheadingheight/2, 0, W_CachePatchName("NEEDITL", PU_PATCH));
9824
9825 skipThisOne:
9826 em = M_GetLevelEmblems(-1);
9827 }
9828
9829 if (!mainrecords[cv_nextmap.value-1] || !mainrecords[cv_nextmap.value-1]->score)
9830 sprintf(beststr, "(none)");
9831 else
9832 sprintf(beststr, "%u", mainrecords[cv_nextmap.value-1]->score);
9833
9834 V_DrawString(104-72, 33+lsheadingheight/2, V_YELLOWMAP, "SCORE:");
9835 V_DrawRightAlignedString(104+64, 33+lsheadingheight/2, V_ALLOWLOWERCASE, beststr);
9836 V_DrawRightAlignedString(104+72, 43+lsheadingheight/2, V_ALLOWLOWERCASE, reqscore);
9837
9838 if (!mainrecords[cv_nextmap.value-1] || !mainrecords[cv_nextmap.value-1]->time)
9839 sprintf(beststr, "(none)");
9840 else
9841 sprintf(beststr, "%i:%02i.%02i", G_TicsToMinutes(mainrecords[cv_nextmap.value-1]->time, true),
9842 G_TicsToSeconds(mainrecords[cv_nextmap.value-1]->time),
9843 G_TicsToCentiseconds(mainrecords[cv_nextmap.value-1]->time));
9844
9845 V_DrawString(104-72, 53+lsheadingheight/2, V_YELLOWMAP, "TIME:");
9846 V_DrawRightAlignedString(104+64, 53+lsheadingheight/2, V_ALLOWLOWERCASE, beststr);
9847 V_DrawRightAlignedString(104+72, 63+lsheadingheight/2, V_ALLOWLOWERCASE, reqtime);
9848
9849 if (!mainrecords[cv_nextmap.value-1] || !mainrecords[cv_nextmap.value-1]->rings)
9850 sprintf(beststr, "(none)");
9851 else
9852 sprintf(beststr, "%hu", mainrecords[cv_nextmap.value-1]->rings);
9853
9854 V_DrawString(104-72, 73+lsheadingheight/2, V_YELLOWMAP, "RINGS:");
9855
9856 V_DrawRightAlignedString(104+64, 73+lsheadingheight/2, V_ALLOWLOWERCASE|((mapvisited[cv_nextmap.value-1] & MV_PERFECTRA) ? V_YELLOWMAP : 0), beststr);
9857
9858 V_DrawRightAlignedString(104+72, 83+lsheadingheight/2, V_ALLOWLOWERCASE, reqrings);
9859 }
9860
9861 // ALWAYS DRAW level and skin even when not on this menu!
9862 if (currentMenu != &SP_TimeAttackDef)
9863 {
9864 consvar_t *ncv;
9865
9866 x = SP_TimeAttackDef.x;
9867 y = SP_TimeAttackDef.y;
9868
9869 V_DrawString(x, y + SP_TimeAttackMenu[talevel].alphaKey, V_TRANSLUCENT, SP_TimeAttackMenu[talevel].text);
9870
9871 ncv = (consvar_t *)SP_TimeAttackMenu[taplayer].itemaction;
9872 V_DrawString(x, y + SP_TimeAttackMenu[taplayer].alphaKey, V_TRANSLUCENT, SP_TimeAttackMenu[taplayer].text);
9873 V_DrawString(BASEVIDWIDTH - x - V_StringWidth(ncv->string, 0), y + SP_TimeAttackMenu[taplayer].alphaKey, V_YELLOWMAP|V_TRANSLUCENT, ncv->string);
9874 }
9875 }
9876
9877 static void M_HandleTimeAttackLevelSelect(INT32 choice)
9878 {
9879 switch (choice)
9880 {
9881 case KEY_DOWNARROW:
9882 M_NextOpt();
9883 break;
9884 case KEY_UPARROW:
9885 M_PrevOpt();
9886 break;
9887
9888 case KEY_LEFTARROW:
9889 CV_AddValue(&cv_nextmap, -1);
9890 break;
9891 case KEY_RIGHTARROW:
9892 CV_AddValue(&cv_nextmap, 1);
9893 break;
9894
9895 case KEY_ENTER:
9896 if (levellistmode == LLM_NIGHTSATTACK)
9897 M_NightsAttackLevelSelect(0);
9898 else
9899 M_TimeAttackLevelSelect(0);
9900 break;
9901
9902 case KEY_ESCAPE:
9903 noFurtherInput = true;
9904 M_GoBack(0);
9905 return;
9906
9907 default:
9908 return;
9909 }
9910 S_StartSound(NULL, sfx_menu1);
9911 }
9912
9913 static void M_TimeAttackLevelSelect(INT32 choice)
9914 {
9915 (void)choice;
9916 SP_TimeAttackLevelSelectDef.prevMenu = currentMenu;
9917 M_SetupNextMenu(&SP_TimeAttackLevelSelectDef);
9918 }
9919
9920 // Going to Time Attack menu...
9921 static void M_TimeAttack(INT32 choice)
9922 {
9923 (void)choice;
9924
9925 SP_TimeAttackDef.prevMenu = &MainDef;
9926 levellistmode = LLM_RECORDATTACK; // Don't be dependent on cv_newgametype
9927
9928 if (!M_PrepareLevelPlatter(-1, true))
9929 {
9930 M_StartMessage(M_GetText("No record-attackable levels found.\n"),NULL,MM_NOTHING);
9931 return;
9932 }
9933
9934 M_PatchSkinNameTable();
9935
9936 G_SetGamestate(GS_TIMEATTACK); // do this before M_SetupNextMenu so that menu meta state knows that we're switching
9937 titlemapinaction = TITLEMAP_OFF; // Nope don't give us HOMs please
9938 M_SetupNextMenu(&SP_TimeAttackDef);
9939 if (!M_CanShowLevelInList(cv_nextmap.value-1, -1) && levelselect.rows[0].maplist[0])
9940 CV_SetValue(&cv_nextmap, levelselect.rows[0].maplist[0]);
9941 else
9942 Nextmap_OnChange();
9943
9944 itemOn = tastart; // "Start" is selected.
9945 }
9946
9947 // Drawing function for Nights Attack
9948 void M_DrawNightsAttackMenu(void)
9949 {
9950 INT32 i, x, y, cursory = 0;
9951 UINT16 dispstatus;
9952
9953 M_SetMenuCurBackground("NTSATKBG");
9954
9955 M_ChangeMenuMusic("_nitat", true); // Eww, but needed for when user hits escape during demo playback
9956
9957 M_DrawNightsAttackBackground();
9958 if (curfadevalue)
9959 V_DrawFadeScreen(0xFF00, curfadevalue);
9960
9961 M_DrawMenuTitle();
9962
9963 // draw menu (everything else goes on top of it)
9964 // Sadly we can't just use generic mode menus because we need some extra hacks
9965 x = currentMenu->x;
9966 y = currentMenu->y;
9967
9968 for (i = 0; i < currentMenu->numitems; ++i)
9969 {
9970 dispstatus = (currentMenu->menuitems[i].status & IT_DISPLAY);
9971 if (dispstatus != IT_STRING && dispstatus != IT_WHITESTRING)
9972 continue;
9973
9974 y = currentMenu->y+currentMenu->menuitems[i].alphaKey;
9975 if (i == itemOn)
9976 cursory = y;
9977
9978 V_DrawString(x, y, (dispstatus == IT_WHITESTRING) ? V_YELLOWMAP : 0 , currentMenu->menuitems[i].text);
9979
9980 // Cvar specific handling
9981 if ((currentMenu->menuitems[i].status & IT_TYPE) == IT_CVAR)
9982 {
9983 consvar_t *cv = (consvar_t *)currentMenu->menuitems[i].itemaction;
9984 INT32 soffset = 0;
9985
9986 // hack to keep the menu from overlapping the overall grade icon
9987 if (currentMenu != &SP_NightsAttackDef)
9988 soffset = 80;
9989
9990 // Should see nothing but strings
9991 V_DrawString(BASEVIDWIDTH - x - soffset - V_StringWidth(cv->string, 0), y, V_YELLOWMAP, cv->string);
9992 if (i == itemOn)
9993 {
9994 V_DrawCharacter(BASEVIDWIDTH - x - soffset - 10 - V_StringWidth(cv->string, 0) - (skullAnimCounter/5), y,
9995 '\x1C' | V_YELLOWMAP, false);
9996 V_DrawCharacter(BASEVIDWIDTH - x - soffset + 2 + (skullAnimCounter/5), y,
9997 '\x1D' | V_YELLOWMAP, false);
9998 }
9999 }
10000 }
10001
10002 // DRAW THE SKULL CURSOR
10003 V_DrawScaledPatch(currentMenu->x - 24, cursory, 0, W_CachePatchName("M_CURSOR", PU_PATCH));
10004 V_DrawString(currentMenu->x, cursory, V_YELLOWMAP, currentMenu->menuitems[itemOn].text);
10005
10006 // Level record list
10007 if (cv_nextmap.value)
10008 {
10009 emblem_t *em;
10010 INT32 yHeight;
10011 INT32 xpos;
10012 patch_t *PictureOfLevel;
10013 lumpnum_t lumpnum;
10014 char beststr[40];
10015
10016 //UINT8 bestoverall = G_GetBestNightsGrade(cv_nextmap.value, 0);
10017 UINT8 bestgrade = G_GetBestNightsGrade(cv_nextmap.value, cv_dummymares.value);
10018 UINT32 bestscore = G_GetBestNightsScore(cv_nextmap.value, cv_dummymares.value);
10019 tic_t besttime = G_GetBestNightsTime(cv_nextmap.value, cv_dummymares.value);
10020
10021 M_DrawLevelPlatterHeader(32-lsheadingheight/2, cv_nextmap.string, true, false);
10022
10023 // A 160x100 image of the level as entry MAPxxP
10024 lumpnum = W_CheckNumForName(va("%sP", G_BuildMapName(cv_nextmap.value)));
10025
10026 if (lumpnum != LUMPERROR)
10027 PictureOfLevel = W_CachePatchName(va("%sP", G_BuildMapName(cv_nextmap.value)), PU_PATCH);
10028 else
10029 PictureOfLevel = W_CachePatchName("BLANKLVL", PU_PATCH);
10030
10031 y = 32+lsheadingheight;
10032 V_DrawSmallScaledPatch(208, y, 0, PictureOfLevel);
10033
10034 // Draw press ESC to exit string on main nights attack menu
10035 if (currentMenu == &SP_NightsAttackDef)
10036 {
10037 if (itemOn == nalevel)
10038 {
10039 /* Draw arrows !! */
10040 y = y + 25 - 4;
10041 V_DrawCharacter(208 - 10 - (skullAnimCounter/5), y,
10042 '\x1C' | V_YELLOWMAP, false);
10043 V_DrawCharacter(208 + 80 + 2 + (skullAnimCounter/5), y,
10044 '\x1D' | V_YELLOWMAP, false);
10045 }
10046 // Draw press ESC to exit string on main record attack menu
10047 V_DrawString(104-72, 180, V_TRANSLUCENT, M_GetText("Press ESC to exit"));
10048 }
10049
10050 // Super Sonic
10051 M_DrawNightsAttackSuperSonic();
10052 //if (P_HasGrades(cv_nextmap.value, 0))
10053 // V_DrawScaledPatch(235 - (((ngradeletters[bestoverall])->width)*3)/2, 135, 0, ngradeletters[bestoverall]);
10054
10055 if (P_HasGrades(cv_nextmap.value, cv_dummymares.value))
10056 {//make bigger again
10057 V_DrawString(104 - 72, 48+lsheadingheight/2, V_YELLOWMAP, "BEST GRADE:");
10058 V_DrawSmallScaledPatch(104 + 72 - (ngradeletters[bestgrade]->width/2),
10059 48+lsheadingheight/2 + 8 - (ngradeletters[bestgrade]->height/2),
10060 0, ngradeletters[bestgrade]);
10061 }
10062
10063 if (!bestscore)
10064 sprintf(beststr, "(none)");
10065 else
10066 sprintf(beststr, "%u", bestscore);
10067
10068 V_DrawString(104 - 72, 58+lsheadingheight/2, V_YELLOWMAP, "BEST SCORE:");
10069 V_DrawRightAlignedString(104 + 72, 58+lsheadingheight/2, V_ALLOWLOWERCASE, beststr);
10070
10071 if (besttime == UINT32_MAX)
10072 sprintf(beststr, "(none)");
10073 else
10074 sprintf(beststr, "%i:%02i.%02i", G_TicsToMinutes(besttime, true),
10075 G_TicsToSeconds(besttime),
10076 G_TicsToCentiseconds(besttime));
10077
10078 V_DrawString(104 - 72, 68+lsheadingheight/2, V_YELLOWMAP, "BEST TIME:");
10079 V_DrawRightAlignedString(104 + 72, 68+lsheadingheight/2, V_ALLOWLOWERCASE, beststr);
10080
10081 if (cv_dummymares.value == 0) {
10082 // Draw record emblems.
10083 em = M_GetLevelEmblems(cv_nextmap.value);
10084 while (em)
10085 {
10086 switch (em->type)
10087 {
10088 case ET_NGRADE:
10089 xpos = 104+38;
10090 yHeight = 48;
10091 break;
10092 case ET_NTIME:
10093 xpos = 104+76;
10094 yHeight = 68;
10095 break;
10096 default:
10097 goto skipThisOne;
10098 }
10099
10100 if (em->collected)
10101 V_DrawSmallMappedPatch(xpos, yHeight+lsheadingheight/2, 0, W_CachePatchName(M_GetEmblemPatch(em, false), PU_PATCH),
10102 R_GetTranslationColormap(TC_DEFAULT, M_GetEmblemColor(em), GTC_CACHE));
10103 else
10104 V_DrawSmallScaledPatch(xpos, yHeight+lsheadingheight/2, 0, W_CachePatchName("NEEDIT", PU_PATCH));
10105
10106 skipThisOne:
10107 em = M_GetLevelEmblems(-1);
10108 }
10109 }
10110 }
10111
10112 // ALWAYS DRAW level even when not on this menu!
10113 if (currentMenu != &SP_NightsAttackDef)
10114 V_DrawString(SP_NightsAttackDef.x, SP_NightsAttackDef.y + SP_TimeAttackMenu[nalevel].alphaKey, V_TRANSLUCENT, SP_NightsAttackMenu[nalevel].text);
10115 }
10116
10117 static void M_NightsAttackLevelSelect(INT32 choice)
10118 {
10119 (void)choice;
10120 SP_NightsAttackLevelSelectDef.prevMenu = currentMenu;
10121 M_SetupNextMenu(&SP_NightsAttackLevelSelectDef);
10122 }
10123
10124 // Going to Nights Attack menu...
10125 static void M_NightsAttack(INT32 choice)
10126 {
10127 (void)choice;
10128
10129 SP_NightsAttackDef.prevMenu = &MainDef;
10130 levellistmode = LLM_NIGHTSATTACK; // Don't be dependent on cv_newgametype
10131
10132 if (!M_PrepareLevelPlatter(-1, true))
10133 {
10134 M_StartMessage(M_GetText("No NiGHTS-attackable levels found.\n"),NULL,MM_NOTHING);
10135 return;
10136 }
10137 // This is really just to make sure Sonic is the played character, just in case
10138 M_PatchSkinNameTable();
10139
10140 ntssupersonic[0] = W_CachePatchName("NTSSONC1", PU_PATCH);
10141 ntssupersonic[1] = W_CachePatchName("NTSSONC2", PU_PATCH);
10142
10143 G_SetGamestate(GS_TIMEATTACK); // do this before M_SetupNextMenu so that menu meta state knows that we're switching
10144 titlemapinaction = TITLEMAP_OFF; // Nope don't give us HOMs please
10145 M_SetupNextMenu(&SP_NightsAttackDef);
10146 if (!M_CanShowLevelInList(cv_nextmap.value-1, -1) && levelselect.rows[0].maplist[0])
10147 CV_SetValue(&cv_nextmap, levelselect.rows[0].maplist[0]);
10148 else
10149 Nextmap_OnChange();
10150
10151 itemOn = nastart; // "Start" is selected.
10152 }
10153
10154 // Player has selected the "START" from the nights attack screen
10155 static void M_ChooseNightsAttack(INT32 choice)
10156 {
10157 char *gpath;
10158 const size_t glen = strlen("replay")+1+strlen(timeattackfolder)+1+strlen("MAPXX")+1;
10159 char nameofdemo[256];
10160 (void)choice;
10161 emeralds = 0;
10162 memset(&luabanks, 0, sizeof(luabanks));
10163 M_ClearMenus(true);
10164 modeattacking = ATTACKING_NIGHTS;
10165
10166 I_mkdir(va("%s"PATHSEP"replay", srb2home), 0755);
10167 I_mkdir(va("%s"PATHSEP"replay"PATHSEP"%s", srb2home, timeattackfolder), 0755);
10168
10169 if ((gpath = malloc(glen)) == NULL)
10170 I_Error("Out of memory for replay filepath\n");
10171
10172 sprintf(gpath,"replay"PATHSEP"%s"PATHSEP"%s", timeattackfolder, G_BuildMapName(cv_nextmap.value));
10173 snprintf(nameofdemo, sizeof nameofdemo, "%s-%s-last", gpath, skins[cv_chooseskin.value-1].name);
10174
10175 if (!cv_autorecord.value)
10176 remove(va("%s"PATHSEP"%s.lmp", srb2home, nameofdemo));
10177 else
10178 G_RecordDemo(nameofdemo);
10179
10180 G_DeferedInitNew(false, G_BuildMapName(cv_nextmap.value), (UINT8)(cv_chooseskin.value-1), false, false);
10181 }
10182
10183 // Player has selected the "START" from the time attack screen
10184 static void M_ChooseTimeAttack(INT32 choice)
10185 {
10186 char *gpath;
10187 const size_t glen = strlen("replay")+1+strlen(timeattackfolder)+1+strlen("MAPXX")+1;
10188 char nameofdemo[256];
10189 (void)choice;
10190 emeralds = 0;
10191 memset(&luabanks, 0, sizeof(luabanks));
10192 M_ClearMenus(true);
10193 modeattacking = ATTACKING_RECORD;
10194
10195 I_mkdir(va("%s"PATHSEP"replay", srb2home), 0755);
10196 I_mkdir(va("%s"PATHSEP"replay"PATHSEP"%s", srb2home, timeattackfolder), 0755);
10197
10198 if ((gpath = malloc(glen)) == NULL)
10199 I_Error("Out of memory for replay filepath\n");
10200
10201 sprintf(gpath,"replay"PATHSEP"%s"PATHSEP"%s", timeattackfolder, G_BuildMapName(cv_nextmap.value));
10202 snprintf(nameofdemo, sizeof nameofdemo, "%s-%s-last", gpath, skins[cv_chooseskin.value-1].name);
10203
10204 if (!cv_autorecord.value)
10205 remove(va("%s"PATHSEP"%s.lmp", srb2home, nameofdemo));
10206 else
10207 G_RecordDemo(nameofdemo);
10208
10209 G_DeferedInitNew(false, G_BuildMapName(cv_nextmap.value), (UINT8)(cv_chooseskin.value-1), false, false);
10210 }
10211
10212 // Player has selected the "REPLAY" from the time attack screen
10213 static void M_ReplayTimeAttack(INT32 choice)
10214 {
10215 const char *which;
10216 char *demoname;
10217 M_ClearMenus(true);
10218 modeattacking = ATTACKING_RECORD; // set modeattacking before G_DoPlayDemo so the map loader knows
10219
10220 if (currentMenu == &SP_ReplayDef)
10221 {
10222 switch(choice) {
10223 default:
10224 case 0: // best score
10225 which = "score-best";
10226 break;
10227 case 1: // best time
10228 which = "time-best";
10229 break;
10230 case 2: // best rings
10231 which = "rings-best";
10232 break;
10233 case 3: // last
10234 which = "last";
10235 break;
10236 case 4: // guest
10237 // srb2/replay/main/map01-guest.lmp
10238 G_DoPlayDemo(va("%s"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s-guest.lmp", srb2home, timeattackfolder, G_BuildMapName(cv_nextmap.value)));
10239 return;
10240 }
10241 // srb2/replay/main/map01-sonic-time-best.lmp
10242 G_DoPlayDemo(va("%s"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s-%s-%s.lmp", srb2home, timeattackfolder, G_BuildMapName(cv_nextmap.value), skins[cv_chooseskin.value-1].name, which));
10243 }
10244 else if (currentMenu == &SP_NightsReplayDef)
10245 {
10246 switch(choice) {
10247 default:
10248 case 0: // best score
10249 which = "score-best";
10250 break;
10251 case 1: // best time
10252 which = "time-best";
10253 break;
10254 case 2: // last
10255 which = "last";
10256 break;
10257 case 3: // guest
10258 G_DoPlayDemo(va("%s"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s-guest.lmp", srb2home, timeattackfolder, G_BuildMapName(cv_nextmap.value)));
10259 return;
10260 }
10261
10262 demoname = va("%s"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s-%s-%s.lmp", srb2home, timeattackfolder, G_BuildMapName(cv_nextmap.value), skins[cv_chooseskin.value-1].name, which);
10263
10264 #ifdef OLDNREPLAYNAME // Check for old style named NiGHTS replay if a new style replay doesn't exist.
10265 if (!FIL_FileExists(demoname))
10266 demoname = va("%s"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s-%s.lmp", srb2home, timeattackfolder, G_BuildMapName(cv_nextmap.value), which);
10267 #endif
10268
10269 G_DoPlayDemo(demoname);
10270 }
10271 }
10272
10273 static void M_EraseGuest(INT32 choice)
10274 {
10275 const char *rguest = va("%s"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s-guest.lmp", srb2home, timeattackfolder, G_BuildMapName(cv_nextmap.value));
10276
10277 if (choice == 'y' || choice == KEY_ENTER)
10278 {
10279 if (FIL_FileExists(rguest))
10280 remove(rguest);
10281 }
10282 M_SetupNextMenu(currentMenu->prevMenu->prevMenu);
10283 Nextmap_OnChange();
10284 M_StartMessage(M_GetText("Guest replay data erased.\n"),NULL,MM_NOTHING);
10285 }
10286
10287 static void M_OverwriteGuest(const char *which)
10288 {
10289 char *rguest = Z_StrDup(va("%s"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s-guest.lmp", srb2home, timeattackfolder, G_BuildMapName(cv_nextmap.value)));
10290 UINT8 *buf;
10291 size_t len;
10292 len = FIL_ReadFile(va("%s"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s-%s-%s.lmp", srb2home, timeattackfolder, G_BuildMapName(cv_nextmap.value), skins[cv_chooseskin.value-1].name, which), &buf);
10293
10294 if (!len) {
10295 return;
10296 }
10297 if (FIL_FileExists(rguest)) {
10298 M_StopMessage(0);
10299 remove(rguest);
10300 }
10301 FIL_WriteFile(rguest, buf, len);
10302 Z_Free(rguest);
10303 if (currentMenu == &SP_NightsGuestReplayDef)
10304 M_SetupNextMenu(&SP_NightsAttackDef);
10305 else
10306 M_SetupNextMenu(&SP_TimeAttackDef);
10307 Nextmap_OnChange();
10308 M_StartMessage(M_GetText("Guest replay data saved.\n"),NULL,MM_NOTHING);
10309 }
10310
10311 static void M_OverwriteGuest_Time(INT32 choice)
10312 {
10313 (void)choice;
10314 M_OverwriteGuest("time-best");
10315 }
10316
10317 static void M_OverwriteGuest_Score(INT32 choice)
10318 {
10319 (void)choice;
10320 M_OverwriteGuest("score-best");
10321 }
10322
10323 static void M_OverwriteGuest_Rings(INT32 choice)
10324 {
10325 (void)choice;
10326 M_OverwriteGuest("rings-best");
10327 }
10328
10329 static void M_OverwriteGuest_Last(INT32 choice)
10330 {
10331 (void)choice;
10332 M_OverwriteGuest("last");
10333 }
10334
10335 static void M_SetGuestReplay(INT32 choice)
10336 {
10337 void (*which)(INT32);
10338 if (currentMenu == &SP_NightsGuestReplayDef && choice >= 2)
10339 choice++; // skip best rings
10340 switch(choice)
10341 {
10342 case 0: // best score
10343 which = M_OverwriteGuest_Score;
10344 break;
10345 case 1: // best time
10346 which = M_OverwriteGuest_Time;
10347 break;
10348 case 2: // best rings
10349 which = M_OverwriteGuest_Rings;
10350 break;
10351 case 3: // last
10352 which = M_OverwriteGuest_Last;
10353 break;
10354 case 4: // guest
10355 default:
10356 M_StartMessage(M_GetText("Are you sure you want to\ndelete the guest replay data?\n\n(Press 'Y' to confirm)\n"),M_EraseGuest,MM_YESNO);
10357 return;
10358 }
10359 if (FIL_FileExists(va("%s"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s-guest.lmp", srb2home, timeattackfolder, G_BuildMapName(cv_nextmap.value))))
10360 M_StartMessage(M_GetText("Are you sure you want to\noverwrite the guest replay data?\n\n(Press 'Y' to confirm)\n"),which,MM_YESNO);
10361 else
10362 which(0);
10363 }
10364
10365 void M_ModeAttackRetry(INT32 choice)
10366 {
10367 (void)choice;
10368 // todo -- maybe seperate this out and G_SetRetryFlag() here instead? is just calling this from the menu 100% safe?
10369 G_CheckDemoStatus(); // Cancel recording
10370 if (modeattacking == ATTACKING_RECORD)
10371 M_ChooseTimeAttack(0);
10372 else if (modeattacking == ATTACKING_NIGHTS)
10373 M_ChooseNightsAttack(0);
10374 }
10375
10376 static void M_ModeAttackEndGame(INT32 choice)
10377 {
10378 (void)choice;
10379 G_CheckDemoStatus(); // Cancel recording
10380
10381 if (gamestate == GS_LEVEL || gamestate == GS_INTERMISSION)
10382 Command_ExitGame_f();
10383
10384 M_StartControlPanel();
10385 switch(modeattacking)
10386 {
10387 default:
10388 case ATTACKING_RECORD:
10389 currentMenu = &SP_TimeAttackDef;
10390 wipetypepost = menupres[MN_SP_TIMEATTACK].enterwipe;
10391 break;
10392 case ATTACKING_NIGHTS:
10393 currentMenu = &SP_NightsAttackDef;
10394 wipetypepost = menupres[MN_SP_NIGHTSATTACK].enterwipe;
10395 break;
10396 }
10397 itemOn = currentMenu->lastOn;
10398 G_SetGamestate(GS_TIMEATTACK);
10399 modeattacking = ATTACKING_NONE;
10400 M_ChangeMenuMusic("_title", true);
10401 Nextmap_OnChange();
10402 }
10403
10404 static void M_MarathonLiveEventBackup(INT32 choice)
10405 {
10406 if (choice == 'y' || choice == KEY_ENTER)
10407 {
10408 marathonmode = MA_INIT;
10409 G_LoadGame(MARATHONSLOT, 0);
10410 cursaveslot = MARATHONSLOT;
10411 if (!(marathonmode & MA_RUNNING))
10412 marathonmode = 0;
10413 return;
10414 }
10415
10416 M_StopMessage(0);
10417 stopstopmessage = true;
10418
10419 if (choice == KEY_DEL)
10420 {
10421 if (FIL_FileExists(liveeventbackup)) // just in case someone deleted it while we weren't looking.
10422 remove(liveeventbackup);
10423 BwehHehHe();
10424 M_StartMessage("Live event backup erased.\n",M_Marathon,MM_NOTHING);
10425 return;
10426 }
10427
10428 M_Marathon(-1);
10429 }
10430
10431 // Going to Marathon menu...
10432 static void M_Marathon(INT32 choice)
10433 {
10434 UINT8 skinset;
10435 INT32 mapnum = 0;
10436
10437 if (choice != -1 && FIL_FileExists(liveeventbackup))
10438 {
10439 M_StartMessage(\
10440 "\x82Live event backup detected.\n\x80\
10441 Do you want to resurrect the last run?\n\
10442 (Fs in chat if we crashed on stream.)\n\
10443 \n\
10444 Press 'Y' or 'Enter' to resume,\n\
10445 'Del' to delete, or any other\n\
10446 key to continue to Marathon Run.",M_MarathonLiveEventBackup,MM_YESNO);
10447 return;
10448 }
10449
10450 fromlevelselect = false;
10451
10452 startmap = spmarathon_start;
10453 CV_SetValue(&cv_newgametype, GT_COOP); // Graue 09-08-2004
10454
10455 skinset = M_SetupChoosePlayerDirect(-1);
10456
10457 SP_MarathonMenu[marathonplayer].status = (skinset == MAXSKINS) ? IT_KEYHANDLER|IT_STRING : IT_NOTHING|IT_DISABLED;
10458
10459 while (mapnum < NUMMAPS)
10460 {
10461 if (mapheaderinfo[mapnum])
10462 {
10463 if (mapheaderinfo[mapnum]->cutscenenum || mapheaderinfo[mapnum]->precutscenenum)
10464 break;
10465 }
10466 mapnum++;
10467 }
10468
10469 SP_MarathonMenu[marathoncutscenes].status = (mapnum < NUMMAPS) ? IT_CVAR|IT_STRING : IT_NOTHING|IT_DISABLED;
10470
10471 M_ChangeMenuMusic("spec8", true);
10472
10473 SP_MarathonDef.prevMenu = &MainDef;
10474 G_SetGamestate(GS_TIMEATTACK); // do this before M_SetupNextMenu so that menu meta state knows that we're switching
10475 titlemapinaction = TITLEMAP_OFF; // Nope don't give us HOMs please
10476 M_SetupNextMenu(&SP_MarathonDef);
10477 itemOn = marathonstart; // "Start" is selected.
10478 recatkdrawtimer = 50-8;
10479 char_scroll = 0;
10480 }
10481
10482 static void M_HandleMarathonChoosePlayer(INT32 choice)
10483 {
10484 INT32 selectval;
10485
10486 if (keydown > 1)
10487 return;
10488
10489 switch (choice)
10490 {
10491 case KEY_DOWNARROW:
10492 M_NextOpt();
10493 break;
10494 case KEY_UPARROW:
10495 M_PrevOpt();
10496 break;
10497
10498 case KEY_LEFTARROW:
10499 if ((selectval = description[char_on].prev) == char_on)
10500 return;
10501 char_on = selectval;
10502 break;
10503 case KEY_RIGHTARROW:
10504 if ((selectval = description[char_on].next) == char_on)
10505 return;
10506 char_on = selectval;
10507 break;
10508
10509 case KEY_ESCAPE:
10510 noFurtherInput = true;
10511 M_GoBack(0);
10512 return;
10513
10514 default:
10515 return;
10516 }
10517 S_StartSound(NULL, sfx_menu1);
10518 }
10519
10520 static void M_StartMarathon(INT32 choice)
10521 {
10522 (void)choice;
10523 marathontime = 0;
10524 marathonmode = MA_RUNNING|MA_INIT;
10525 cursaveslot = (cv_dummymarathon.value == 1) ? MARATHONSLOT : 0;
10526 if (!cv_dummycutscenes.value)
10527 marathonmode |= MA_NOCUTSCENES;
10528 if (cv_dummyloadless.value)
10529 marathonmode |= MA_INGAME;
10530 M_ChoosePlayer(char_on);
10531 }
10532
10533 // Drawing function for Marathon menu
10534 void M_DrawMarathon(void)
10535 {
10536 INT32 i, x, y, cursory = 0, cnt, soffset = 0, w;
10537 UINT16 dispstatus;
10538 consvar_t *cv;
10539 const char *cvstring;
10540 char *work;
10541 angle_t fa;
10542 INT32 dupz = (vid.dupx < vid.dupy ? vid.dupx : vid.dupy), xspan = (vid.width/dupz), yspan = (vid.height/dupz), diffx = (xspan - BASEVIDWIDTH)/2, diffy = (yspan - BASEVIDHEIGHT)/2, maxy = BASEVIDHEIGHT + diffy;
10543
10544 curbgxspeed = 0;
10545 curbgyspeed = 18;
10546
10547 M_ChangeMenuMusic("spec8", true); // Eww, but needed for when user hits escape during demo playback
10548
10549 V_DrawFill(-diffx, -diffy, diffx+(BASEVIDWIDTH-190)/2, yspan, 158);
10550 V_DrawFill((BASEVIDWIDTH-190)/2, -diffy, 190, yspan, 31);
10551 V_DrawFill((BASEVIDWIDTH+190)/2, -diffy, diffx+(BASEVIDWIDTH-190)/2, yspan, 158);
10552 //M_DrawRecordAttackForeground();
10553 if (curfadevalue)
10554 V_DrawFadeScreen(0xFF00, curfadevalue);
10555
10556 x = (((BASEVIDWIDTH-82)/2)+11)<<FRACBITS;
10557 y = (((BASEVIDHEIGHT-82)/2)+12-10)<<FRACBITS;
10558
10559 cnt = (36*(recatkdrawtimer<<FRACBITS))/TICRATE;
10560 fa = (FixedAngle(cnt)>>ANGLETOFINESHIFT) & FINEMASK;
10561 y -= (10*FINECOSINE(fa));
10562
10563 recatkdrawtimer++;
10564
10565 soffset = cnt = (recatkdrawtimer%50);
10566 if (!useBlackRock)
10567 {
10568 if (cnt > 8)
10569 cnt = 8;
10570 V_DrawFixedPatch(x+(6<<FRACBITS), y, FRACUNIT/2, (cnt&~1)<<(V_ALPHASHIFT-1), W_CachePatchName("RECCLOCK", PU_PATCH), NULL);
10571 }
10572 else if (cnt > 8)
10573 {
10574 cnt = 8;
10575 V_DrawFixedPatch(x, y, FRACUNIT, V_TRANSLUCENT, W_CachePatchName("ENDEGRK5", PU_PATCH), NULL);
10576 }
10577 else
10578 {
10579 V_DrawFixedPatch(x, y, FRACUNIT, cnt<<V_ALPHASHIFT, W_CachePatchName("ROID0000", PU_PATCH), NULL);
10580 V_DrawFixedPatch(x, y, FRACUNIT, V_TRANSLUCENT, W_CachePatchName("ENDEGRK5", PU_PATCH), NULL);
10581 V_DrawFixedPatch(x, y, FRACUNIT, cnt<<V_ALPHASHIFT, W_CachePatchName("ENDEGRK0", PU_PATCH), NULL);
10582 }
10583
10584 {
10585 UINT8 col;
10586 i = 0;
10587 w = (((8-cnt)+1)/3)+1;
10588 w *= w;
10589 cursory = 0;
10590 while (i < cnt)
10591 {
10592 i++;
10593 col = 158+((cnt-i)/3);
10594 if (col >= 160)
10595 col = 253;
10596 V_DrawFill(((BASEVIDWIDTH-190)/2)-cursory-w, -diffy, w, yspan, col);
10597 V_DrawFill(((BASEVIDWIDTH+190)/2)+cursory, -diffy, w, yspan, col);
10598 cursory += w;
10599 w *= 2;
10600 }
10601 }
10602
10603 w = char_scroll + (((8-cnt)*(8-cnt))<<(FRACBITS-5));
10604 if (soffset == 50-1)
10605 w += FRACUNIT/2;
10606
10607 {
10608 patch_t *fg = W_CachePatchName("RECATKFG", PU_PATCH);
10609 INT32 trans = V_60TRANS+((cnt&~3)<<(V_ALPHASHIFT-2));
10610 INT32 height = fg->height / 2;
10611 char patchname[7] = "CEMGx0";
10612
10613 dupz = (w*7)/6; //(w*42*120)/(360*6); -- I don't know why this works but I'm not going to complain.
10614 dupz = ((dupz>>FRACBITS) % height);
10615 y = height/2;
10616 while (y+dupz >= -diffy)
10617 y -= height;
10618 while (y-2-dupz < maxy)
10619 {
10620 V_DrawFixedPatch(((BASEVIDWIDTH-190)<<(FRACBITS-1)), (y-2-dupz)<<FRACBITS, FRACUNIT/2, trans, fg, NULL);
10621 V_DrawFixedPatch(((BASEVIDWIDTH+190)<<(FRACBITS-1)), (y+dupz)<<FRACBITS, FRACUNIT/2, trans|V_FLIP, fg, NULL);
10622 y += height;
10623 }
10624
10625 trans = V_40TRANS+((cnt&~1)<<(V_ALPHASHIFT-1));
10626
10627 for (i = 0; i < 7; ++i)
10628 {
10629 fa = (FixedAngle(w)>>ANGLETOFINESHIFT) & FINEMASK;
10630 x = (BASEVIDWIDTH<<(FRACBITS-1)) + (60*FINESINE(fa));
10631 y = ((BASEVIDHEIGHT+16-20)<<(FRACBITS-1)) - (60*FINECOSINE(fa));
10632 w += (360<<FRACBITS)/7;
10633
10634 patchname[4] = 'A'+(char)i;
10635 V_DrawFixedPatch(x, y, FRACUNIT, trans, W_CachePatchName(patchname, PU_PATCH), NULL);
10636 }
10637
10638 height = 18; // prevents the need for the next line
10639 //dupz = (w*height)/18;
10640 dupz = ((w>>FRACBITS) % height);
10641 y = dupz+(height/4);
10642 x = 105+dupz;
10643 while (y >= -diffy)
10644 {
10645 x -= height;
10646 y -= height;
10647 }
10648 while (y-dupz < maxy && x < (xspan/2))
10649 {
10650 V_DrawFill((BASEVIDWIDTH/2)-x-height, -diffy, height, diffy+y+height, 153);
10651 V_DrawFill((BASEVIDWIDTH/2)+x, (maxy-y)-height, height, height+y, 153);
10652 y += height;
10653 x += height;
10654 }
10655 }
10656
10657 if (!soffset)
10658 {
10659 char_scroll += (360<<FRACBITS)/42; // like a clock, ticking at 42bpm!
10660 if (char_scroll >= 360<<FRACBITS)
10661 char_scroll -= 360<<FRACBITS;
10662 if (recatkdrawtimer > (10*TICRATE))
10663 recatkdrawtimer -= (10*TICRATE);
10664 }
10665
10666 M_DrawMenuTitle();
10667
10668 // draw menu (everything else goes on top of it)
10669 // Sadly we can't just use generic mode menus because we need some extra hacks
10670 x = currentMenu->x;
10671 y = currentMenu->y;
10672
10673 dispstatus = (currentMenu->menuitems[marathonplayer].status & IT_DISPLAY);
10674 if (dispstatus == IT_STRING || dispstatus == IT_WHITESTRING)
10675 {
10676 soffset = 68;
10677 if (description[char_on].charpic->width >= 256)
10678 V_DrawTinyScaledPatch(224, 120, 0, description[char_on].charpic);
10679 else
10680 V_DrawSmallScaledPatch(224, 120, 0, description[char_on].charpic);
10681 }
10682 else
10683 soffset = 0;
10684
10685 for (i = 0; i < currentMenu->numitems; ++i)
10686 {
10687 dispstatus = (currentMenu->menuitems[i].status & IT_DISPLAY);
10688 if (dispstatus != IT_STRING && dispstatus != IT_WHITESTRING)
10689 continue;
10690
10691 y = currentMenu->y+currentMenu->menuitems[i].alphaKey;
10692 if (i == itemOn)
10693 cursory = y;
10694
10695 V_DrawString(x, y, (dispstatus == IT_WHITESTRING) ? V_YELLOWMAP : 0 , currentMenu->menuitems[i].text);
10696
10697 cv = NULL;
10698 cvstring = NULL;
10699 work = NULL;
10700 if ((currentMenu->menuitems[i].status & IT_TYPE) == IT_CVAR)
10701 {
10702 cv = (consvar_t *)currentMenu->menuitems[i].itemaction;
10703 cvstring = cv->string;
10704 }
10705 else if (i == marathonplayer)
10706 {
10707 if (description[char_on].displayname[0])
10708 {
10709 work = Z_StrDup(description[char_on].displayname);
10710 cnt = 0;
10711 while (work[cnt])
10712 {
10713 if (work[cnt] == '\n')
10714 work[cnt] = ' ';
10715 cnt++;
10716 }
10717 cvstring = work;
10718 }
10719 else
10720 cvstring = description[char_on].skinname;
10721 }
10722
10723 // Cvar specific handling
10724 if (cvstring)
10725 {
10726 INT32 flags = V_YELLOWMAP;
10727 if (cv == &cv_dummymarathon && cv->value == 2) // ultimate_selectable
10728 flags = V_REDMAP;
10729
10730 // Should see nothing but strings
10731 if (cv == &cv_dummymarathon && cv->value == 1)
10732 {
10733 w = V_ThinStringWidth(cvstring, 0);
10734 V_DrawThinString(BASEVIDWIDTH - x - soffset - w, y+1, flags, cvstring);
10735 }
10736 else
10737 {
10738 w = V_StringWidth(cvstring, 0);
10739 V_DrawString(BASEVIDWIDTH - x - soffset - w, y, flags, cvstring);
10740 }
10741 if (i == itemOn)
10742 {
10743 V_DrawCharacter(BASEVIDWIDTH - x - soffset - 10 - w - (skullAnimCounter/5), y,
10744 '\x1C' | V_YELLOWMAP, false);
10745 V_DrawCharacter(BASEVIDWIDTH - x - soffset + 2 + (skullAnimCounter/5), y,
10746 '\x1D' | V_YELLOWMAP, false);
10747 }
10748 if (work)
10749 Z_Free(work);
10750 }
10751 }
10752
10753 // DRAW THE SKULL CURSOR
10754 V_DrawScaledPatch(currentMenu->x - 24, cursory, 0, W_CachePatchName("M_CURSOR", PU_PATCH));
10755 V_DrawString(currentMenu->x, cursory, V_YELLOWMAP, currentMenu->menuitems[itemOn].text);
10756
10757 // Draw press ESC to exit string on main record attack menu
10758 V_DrawString(104-72, 180, V_TRANSLUCENT, M_GetText("Press ESC to exit"));
10759 }
10760
10761 // ========
10762 // END GAME
10763 // ========
10764
10765 static void M_ExitGameResponse(INT32 ch)
10766 {
10767 if (ch != 'y' && ch != KEY_ENTER)
10768 return;
10769
10770 //Command_ExitGame_f();
10771 G_SetExitGameFlag();
10772 M_ClearMenus(true);
10773 }
10774
10775 static void M_EndGame(INT32 choice)
10776 {
10777 (void)choice;
10778 if (demoplayback || demorecording)
10779 return;
10780
10781 if (!Playing())
10782 return;
10783
10784 M_StartMessage(M_GetText("Are you sure you want to end the game?\n\n(Press 'Y' to confirm)\n"), M_ExitGameResponse, MM_YESNO);
10785 }
10786
10787 //===========================================================================
10788 // Connect Menu
10789 //===========================================================================
10790
10791 #define SERVERHEADERHEIGHT 44
10792 #define SERVERLINEHEIGHT 12
10793
10794 #define S_LINEY(n) currentMenu->y + SERVERHEADERHEIGHT + (n * SERVERLINEHEIGHT)
10795
10796 #ifndef NONET
10797 static UINT32 localservercount;
10798
10799 static void M_HandleServerPage(INT32 choice)
10800 {
10801 boolean exitmenu = false; // exit to previous menu
10802
10803 switch (choice)
10804 {
10805 case KEY_DOWNARROW:
10806 M_NextOpt();
10807 S_StartSound(NULL, sfx_menu1);
10808 break;
10809 case KEY_UPARROW:
10810 M_PrevOpt();
10811 S_StartSound(NULL, sfx_menu1);
10812 break;
10813 case KEY_BACKSPACE:
10814 case KEY_ESCAPE:
10815 exitmenu = true;
10816 break;
10817
10818 case KEY_ENTER:
10819 case KEY_RIGHTARROW:
10820 S_StartSound(NULL, sfx_menu1);
10821 if ((serverlistpage + 1) * SERVERS_PER_PAGE < serverlistcount)
10822 serverlistpage++;
10823 break;
10824 case KEY_LEFTARROW:
10825 S_StartSound(NULL, sfx_menu1);
10826 if (serverlistpage > 0)
10827 serverlistpage--;
10828 break;
10829
10830 default:
10831 break;
10832 }
10833 if (exitmenu)
10834 {
10835 if (currentMenu->prevMenu)
10836 M_SetupNextMenu(currentMenu->prevMenu);
10837 else
10838 M_ClearMenus(true);
10839 }
10840 }
10841
10842 static void M_Connect(INT32 choice)
10843 {
10844 // do not call menuexitfunc
10845 M_ClearMenus(false);
10846
10847 COM_BufAddText(va("connect node %d\n", serverlist[choice-FIRSTSERVERLINE + serverlistpage * SERVERS_PER_PAGE].node));
10848 }
10849
10850 static void M_Refresh(INT32 choice)
10851 {
10852 (void)choice;
10853
10854 // Display a little "please wait" message.
10855 M_DrawTextBox(52, BASEVIDHEIGHT/2-10, 25, 3);
10856 V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT/2, 0, "Searching for servers...");
10857 V_DrawCenteredString(BASEVIDWIDTH/2, (BASEVIDHEIGHT/2)+12, 0, "Please wait.");
10858 I_OsPolling();
10859 I_UpdateNoBlit();
10860 if (rendermode == render_soft)
10861 I_FinishUpdate(); // page flip or blit buffer
10862
10863 // note: this is the one case where 0 is a valid room number
10864 // because it corresponds to "All"
10865 CL_UpdateServerList(!(ms_RoomId < 0), ms_RoomId);
10866
10867 // first page of servers
10868 serverlistpage = 0;
10869 }
10870
10871 static INT32 menuRoomIndex = 0;
10872
10873 static void M_DrawRoomMenu(void)
10874 {
10875 static int frame = -12;
10876 int dot_frame;
10877 char text[4];
10878
10879 const char *rmotd;
10880 const char *waiting_message;
10881
10882 int dots;
10883
10884 if (m_waiting_mode)
10885 {
10886 dot_frame = frame / 4;
10887 dots = dot_frame + 3;
10888
10889 strcpy(text, " ");
10890
10891 if (dots > 0)
10892 {
10893 if (dot_frame < 0)
10894 dot_frame = 0;
10895
10896 strncpy(&text[dot_frame], "...", min(dots, 3 - dot_frame));
10897 }
10898
10899 if (++frame == 12)
10900 frame = -12;
10901
10902 currentMenu->menuitems[0].text = text;
10903 }
10904
10905 // use generic drawer for cursor, items and title
10906 M_DrawGenericMenu();
10907
10908 V_DrawString(currentMenu->x - 16, currentMenu->y, V_YELLOWMAP, M_GetText("Select a room"));
10909
10910 if (m_waiting_mode == M_NOT_WAITING)
10911 {
10912 M_DrawTextBox(144, 24, 20, 20);
10913
10914 if (itemOn == 0)
10915 rmotd = M_GetText("Don't connect to the Master Server.");
10916 else
10917 rmotd = room_list[itemOn-1].motd;
10918
10919 rmotd = V_WordWrap(0, 20*8, 0, rmotd);
10920 V_DrawString(144+8, 32, V_ALLOWLOWERCASE|V_RETURN8, rmotd);
10921 }
10922
10923 if (m_waiting_mode)
10924 {
10925 // Display a little "please wait" message.
10926 M_DrawTextBox(52, BASEVIDHEIGHT/2-10, 25, 3);
10927 if (m_waiting_mode == M_WAITING_VERSION)
10928 waiting_message = "Checking for updates...";
10929 else
10930 waiting_message = "Fetching room info...";
10931 V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT/2, 0, waiting_message);
10932 V_DrawCenteredString(BASEVIDWIDTH/2, (BASEVIDHEIGHT/2)+12, 0, "Please wait.");
10933 }
10934 }
10935
10936 static void M_DrawConnectMenu(void)
10937 {
10938 UINT16 i;
10939 char *gt;
10940 INT32 numPages = (serverlistcount+(SERVERS_PER_PAGE-1))/SERVERS_PER_PAGE;
10941
10942 for (i = FIRSTSERVERLINE; i < min(localservercount, SERVERS_PER_PAGE)+FIRSTSERVERLINE; i++)
10943 MP_ConnectMenu[i].status = IT_STRING | IT_SPACE;
10944
10945 if (!numPages)
10946 numPages = 1;
10947
10948 // Room name
10949 if (ms_RoomId < 0)
10950 V_DrawRightAlignedString(BASEVIDWIDTH - currentMenu->x, currentMenu->y + MP_ConnectMenu[mp_connect_room].alphaKey,
10951 V_YELLOWMAP, (itemOn == mp_connect_room) ? "<Select to change>" : "<Unlisted Mode>");
10952 else
10953 V_DrawRightAlignedString(BASEVIDWIDTH - currentMenu->x, currentMenu->y + MP_ConnectMenu[mp_connect_room].alphaKey,
10954 V_YELLOWMAP, room_list[menuRoomIndex].name);
10955
10956 // Page num
10957 V_DrawRightAlignedString(BASEVIDWIDTH - currentMenu->x, currentMenu->y + MP_ConnectMenu[mp_connect_page].alphaKey,
10958 V_YELLOWMAP, va("%u of %d", serverlistpage+1, numPages));
10959
10960 // Horizontal line!
10961 V_DrawFill(1, currentMenu->y+40, 318, 1, 0);
10962
10963 if (serverlistcount <= 0)
10964 V_DrawString(currentMenu->x,currentMenu->y+SERVERHEADERHEIGHT, 0, "No servers found");
10965 else
10966 for (i = 0; i < min(serverlistcount - serverlistpage * SERVERS_PER_PAGE, SERVERS_PER_PAGE); i++)
10967 {
10968 INT32 slindex = i + serverlistpage * SERVERS_PER_PAGE;
10969 UINT32 globalflags = (serverlist[slindex].info.refusereason ? V_TRANSLUCENT : 0)
10970 |((itemOn == FIRSTSERVERLINE+i) ? V_YELLOWMAP : 0)|V_ALLOWLOWERCASE;
10971
10972 V_DrawString(currentMenu->x, S_LINEY(i), globalflags, serverlist[slindex].info.servername);
10973
10974 // Don't use color flags intentionally, the global yellow color will auto override the text color code
10975 if (serverlist[slindex].info.modifiedgame)
10976 V_DrawSmallString(currentMenu->x+202, S_LINEY(i)+8, globalflags, "\x85" "Mod");
10977 if (serverlist[slindex].info.cheatsenabled)
10978 V_DrawSmallString(currentMenu->x+222, S_LINEY(i)+8, globalflags, "\x83" "Cheats");
10979
10980 V_DrawSmallString(currentMenu->x, S_LINEY(i)+8, globalflags,
10981 va("Ping: %u", (UINT32)LONG(serverlist[slindex].info.time)));
10982
10983 gt = serverlist[slindex].info.gametypename;
10984
10985 V_DrawSmallString(currentMenu->x+46,S_LINEY(i)+8, globalflags,
10986 va("Players: %02d/%02d", serverlist[slindex].info.numberofplayer, serverlist[slindex].info.maxplayer));
10987
10988 if (strlen(gt) > 11)
10989 gt = va("Gametype: %.11s...", gt);
10990 else
10991 gt = va("Gametype: %s", gt);
10992
10993 V_DrawSmallString(currentMenu->x+112, S_LINEY(i)+8, globalflags, gt);
10994
10995 MP_ConnectMenu[i+FIRSTSERVERLINE].status = IT_STRING | IT_CALL;
10996 }
10997
10998 localservercount = serverlistcount;
10999
11000 M_DrawGenericMenu();
11001
11002 if (m_waiting_mode)
11003 {
11004 // Display a little "please wait" message.
11005 M_DrawTextBox(52, BASEVIDHEIGHT/2-10, 25, 3);
11006 V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT/2, 0, "Searching for servers...");
11007 V_DrawCenteredString(BASEVIDWIDTH/2, (BASEVIDHEIGHT/2)+12, 0, "Please wait.");
11008 }
11009 }
11010
11011 static boolean M_CancelConnect(void)
11012 {
11013 D_CloseConnection();
11014 return true;
11015 }
11016
11017 // Ascending order, not descending.
11018 // The casts are safe as long as the caller doesn't do anything stupid.
11019 #define SERVER_LIST_ENTRY_COMPARATOR(key) \
11020 static int ServerListEntryComparator_##key(const void *entry1, const void *entry2) \
11021 { \
11022 const serverelem_t *sa = (const serverelem_t*)entry1, *sb = (const serverelem_t*)entry2; \
11023 if (sa->info.key != sb->info.key) \
11024 return sa->info.key - sb->info.key; \
11025 return strcmp(sa->info.servername, sb->info.servername); \
11026 }
11027
11028 // This does descending instead of ascending.
11029 #define SERVER_LIST_ENTRY_COMPARATOR_REVERSE(key) \
11030 static int ServerListEntryComparator_##key##_reverse(const void *entry1, const void *entry2) \
11031 { \
11032 const serverelem_t *sa = (const serverelem_t*)entry1, *sb = (const serverelem_t*)entry2; \
11033 if (sb->info.key != sa->info.key) \
11034 return sb->info.key - sa->info.key; \
11035 return strcmp(sb->info.servername, sa->info.servername); \
11036 }
11037
11038 SERVER_LIST_ENTRY_COMPARATOR(time)
11039 SERVER_LIST_ENTRY_COMPARATOR(numberofplayer)
11040 SERVER_LIST_ENTRY_COMPARATOR_REVERSE(numberofplayer)
11041 SERVER_LIST_ENTRY_COMPARATOR_REVERSE(maxplayer)
11042
11043 static int ServerListEntryComparator_gametypename(const void *entry1, const void *entry2)
11044 {
11045 const serverelem_t *sa = (const serverelem_t*)entry1, *sb = (const serverelem_t*)entry2;
11046 int c;
11047 if (( c = strcasecmp(sa->info.gametypename, sb->info.gametypename) ))
11048 return c;
11049 return strcmp(sa->info.servername, sb->info.servername); \
11050 }
11051
11052 // Special one for modified state.
11053 static int ServerListEntryComparator_modified(const void *entry1, const void *entry2)
11054 {
11055 const serverelem_t *sa = (const serverelem_t*)entry1, *sb = (const serverelem_t*)entry2;
11056
11057 // Modified acts as 2 points, cheats act as one point.
11058 int modstate_a = (sa->info.cheatsenabled ? 1 : 0) | (sa->info.modifiedgame ? 2 : 0);
11059 int modstate_b = (sb->info.cheatsenabled ? 1 : 0) | (sb->info.modifiedgame ? 2 : 0);
11060
11061 if (modstate_a != modstate_b)
11062 return modstate_a - modstate_b;
11063
11064 // Default to strcmp.
11065 return strcmp(sa->info.servername, sb->info.servername);
11066 }
11067 #endif
11068
11069 void M_SortServerList(void)
11070 {
11071 #ifndef NONET
11072 switch(cv_serversort.value)
11073 {
11074 case 0: // Ping.
11075 qsort(serverlist, serverlistcount, sizeof(serverelem_t), ServerListEntryComparator_time);
11076 break;
11077 case 1: // Modified state.
11078 qsort(serverlist, serverlistcount, sizeof(serverelem_t), ServerListEntryComparator_modified);
11079 break;
11080 case 2: // Most players.
11081 qsort(serverlist, serverlistcount, sizeof(serverelem_t), ServerListEntryComparator_numberofplayer_reverse);
11082 break;
11083 case 3: // Least players.
11084 qsort(serverlist, serverlistcount, sizeof(serverelem_t), ServerListEntryComparator_numberofplayer);
11085 break;
11086 case 4: // Max players.
11087 qsort(serverlist, serverlistcount, sizeof(serverelem_t), ServerListEntryComparator_maxplayer_reverse);
11088 break;
11089 case 5: // Gametype.
11090 qsort(serverlist, serverlistcount, sizeof(serverelem_t), ServerListEntryComparator_gametypename);
11091 break;
11092 }
11093 #endif
11094 }
11095
11096 #ifndef NONET
11097 #ifdef UPDATE_ALERT
11098 static boolean M_CheckMODVersion(int id)
11099 {
11100 char updatestring[500];
11101 const char *updatecheck = GetMODVersion(id);
11102 if(updatecheck)
11103 {
11104 sprintf(updatestring, UPDATE_ALERT_STRING, VERSIONSTRING, updatecheck);
11105 M_StartMessage(updatestring, NULL, MM_NOTHING);
11106 return false;
11107 } else
11108 return true;
11109 }
11110 #endif/*UPDATE_ALERT*/
11111
11112 #if defined (MASTERSERVER) && defined (HAVE_THREADS)
11113 static void
11114 Check_new_version_thread (int *id)
11115 {
11116 int hosting;
11117 int okay;
11118
11119 okay = 0;
11120
11121 #ifdef UPDATE_ALERT
11122 if (M_CheckMODVersion(*id))
11123 #endif
11124 {
11125 I_lock_mutex(&ms_QueryId_mutex);
11126 {
11127 okay = ( *id == ms_QueryId );
11128 }
11129 I_unlock_mutex(ms_QueryId_mutex);
11130
11131 if (okay)
11132 {
11133 I_lock_mutex(&m_menu_mutex);
11134 {
11135 m_waiting_mode = M_WAITING_ROOMS;
11136 hosting = ( currentMenu->prevMenu == &MP_ServerDef );
11137 }
11138 I_unlock_mutex(m_menu_mutex);
11139
11140 GetRoomsList(hosting, *id);
11141 }
11142 }
11143 #ifdef UPDATE_ALERT
11144 else
11145 {
11146 I_lock_mutex(&ms_QueryId_mutex);
11147 {
11148 okay = ( *id == ms_QueryId );
11149 }
11150 I_unlock_mutex(ms_QueryId_mutex);
11151 }
11152 #endif
11153
11154 if (okay)
11155 {
11156 I_lock_mutex(&m_menu_mutex);
11157 {
11158 if (m_waiting_mode)
11159 {
11160 m_waiting_mode = M_NOT_WAITING;
11161 MP_RoomMenu[0].text = "<Offline Mode>";
11162 }
11163 }
11164 I_unlock_mutex(m_menu_mutex);
11165 }
11166
11167 free(id);
11168 }
11169 #endif/*defined (MASTERSERVER) && defined (HAVE_THREADS)*/
11170
11171 static void M_ConnectMenu(INT32 choice)
11172 {
11173 (void)choice;
11174 // modified game check: no longer handled
11175 // we don't request a restart unless the filelist differs
11176
11177 // first page of servers
11178 serverlistpage = 0;
11179 if (ms_RoomId < 0)
11180 {
11181 M_RoomMenu(0); // Select a room instead of staring at an empty list
11182 // This prevents us from returning to the modified game alert.
11183 currentMenu->prevMenu = &MP_MainDef;
11184 }
11185 else
11186 M_SetupNextMenu(&MP_ConnectDef);
11187 itemOn = 0;
11188 M_Refresh(0);
11189 }
11190
11191 static void M_ConnectMenuModChecks(INT32 choice)
11192 {
11193 (void)choice;
11194 // okay never mind we want to COMMUNICATE to the player pre-emptively instead of letting them try and then get confused when it doesn't work
11195
11196 if (modifiedgame)
11197 {
11198 M_StartMessage(M_GetText("You have add-ons loaded.\nYou won't be able to join netgames!\n\nTo play online, restart the game\nand don't load any addons.\nSRB2 will automatically add\neverything you need when you join.\n\n(Press a key)\n"),M_ConnectMenu,MM_EVENTHANDLER);
11199 return;
11200 }
11201
11202 M_ConnectMenu(-1);
11203 }
11204
11205 UINT32 roomIds[NUM_LIST_ROOMS];
11206
11207 static void M_RoomMenu(INT32 choice)
11208 {
11209 INT32 i;
11210 #if defined (MASTERSERVER) && defined (HAVE_THREADS)
11211 int *id;
11212 #endif
11213
11214 (void)choice;
11215
11216 // Display a little "please wait" message.
11217 M_DrawTextBox(52, BASEVIDHEIGHT/2-10, 25, 3);
11218 V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT/2, 0, "Fetching room info...");
11219 V_DrawCenteredString(BASEVIDWIDTH/2, (BASEVIDHEIGHT/2)+12, 0, "Please wait.");
11220 I_OsPolling();
11221 I_UpdateNoBlit();
11222 if (rendermode == render_soft)
11223 I_FinishUpdate(); // page flip or blit buffer
11224
11225 for (i = 1; i < NUM_LIST_ROOMS+1; ++i)
11226 MP_RoomMenu[i].status = IT_DISABLED;
11227 memset(roomIds, 0, sizeof(roomIds));
11228
11229 MP_RoomDef.prevMenu = currentMenu;
11230 M_SetupNextMenu(&MP_RoomDef);
11231
11232 #ifdef MASTERSERVER
11233 #ifdef HAVE_THREADS
11234 #ifdef UPDATE_ALERT
11235 m_waiting_mode = M_WAITING_VERSION;
11236 #else/*UPDATE_ALERT*/
11237 m_waiting_mode = M_WAITING_ROOMS;
11238 #endif/*UPDATE_ALERT*/
11239
11240 MP_RoomMenu[0].text = "";
11241
11242 id = malloc(sizeof *id);
11243
11244 I_lock_mutex(&ms_QueryId_mutex);
11245 {
11246 *id = ms_QueryId;
11247 }
11248 I_unlock_mutex(ms_QueryId_mutex);
11249
11250 I_spawn_thread("check-new-version",
11251 (I_thread_fn)Check_new_version_thread, id);
11252 #else/*HAVE_THREADS*/
11253 #ifdef UPDATE_ALERT
11254 if (M_CheckMODVersion(0))
11255 #endif/*UPDATE_ALERT*/
11256 {
11257 GetRoomsList(currentMenu->prevMenu == &MP_ServerDef, 0);
11258 }
11259 #endif/*HAVE_THREADS*/
11260 #endif/*MASTERSERVER*/
11261 }
11262
11263 static void M_ChooseRoom(INT32 choice)
11264 {
11265 #if defined (MASTERSERVER) && defined (HAVE_THREADS)
11266 I_lock_mutex(&ms_QueryId_mutex);
11267 {
11268 ms_QueryId++;
11269 }
11270 I_unlock_mutex(ms_QueryId_mutex);
11271 #endif
11272
11273 if (choice == 0)
11274 ms_RoomId = -1;
11275 else
11276 {
11277 ms_RoomId = roomIds[choice-1];
11278 menuRoomIndex = choice - 1;
11279 }
11280
11281 serverlistpage = 0;
11282 /*
11283 We were on the Multiplayer menu? That means that we must have been trying to
11284 view the server browser, but we hadn't selected a room yet. So we need to go
11285 to the browser next, not back there.
11286 */
11287 if (currentMenu->prevMenu == &MP_MainDef)
11288 M_SetupNextMenu(&MP_ConnectDef);
11289 else
11290 M_SetupNextMenu(currentMenu->prevMenu);
11291
11292 if (currentMenu == &MP_ConnectDef)
11293 M_Refresh(0);
11294 }
11295 #endif //NONET
11296
11297 //===========================================================================
11298 // Start Server Menu
11299 //===========================================================================
11300
11301 static void M_StartServer(INT32 choice)
11302 {
11303 boolean StartSplitScreenGame = (currentMenu == &MP_SplitServerDef);
11304
11305 (void)choice;
11306 if (!StartSplitScreenGame)
11307 netgame = true;
11308
11309 multiplayer = true;
11310
11311 // Still need to reset devmode
11312 cv_debug = 0;
11313
11314 if (demoplayback)
11315 G_StopDemo();
11316 if (metalrecording)
11317 G_StopMetalDemo();
11318
11319 if (!StartSplitScreenGame)
11320 {
11321 D_MapChange(cv_nextmap.value, cv_newgametype.value, false, 1, 1, false, false);
11322 COM_BufAddText("dummyconsvar 1\n");
11323 }
11324 else // split screen
11325 {
11326 paused = false;
11327 SV_StartSinglePlayerServer();
11328 if (!splitscreen)
11329 {
11330 splitscreen = true;
11331 SplitScreen_OnChange();
11332 }
11333 D_MapChange(cv_nextmap.value, cv_newgametype.value, false, 1, 1, false, false);
11334 }
11335
11336 M_ClearMenus(true);
11337 }
11338
11339 static void M_DrawServerMenu(void)
11340 {
11341 M_DrawGenericMenu();
11342
11343 #ifndef NONET
11344 // Room name
11345 if (currentMenu == &MP_ServerDef)
11346 {
11347 M_DrawLevelPlatterHeader(currentMenu->y - lsheadingheight/2, "Server settings", true, false);
11348 if (ms_RoomId < 0)
11349 V_DrawRightAlignedString(BASEVIDWIDTH - currentMenu->x, currentMenu->y + MP_ServerMenu[mp_server_room].alphaKey,
11350 V_YELLOWMAP, (itemOn == mp_server_room) ? "<Select to change>" : "<Unlisted Mode>");
11351 else
11352 V_DrawRightAlignedString(BASEVIDWIDTH - currentMenu->x, currentMenu->y + MP_ServerMenu[mp_server_room].alphaKey,
11353 V_YELLOWMAP, room_list[menuRoomIndex].name);
11354 }
11355 #endif
11356
11357 if (cv_nextmap.value)
11358 {
11359 #ifndef NONET
11360 #define imgheight MP_ServerMenu[mp_server_levelgt].alphaKey
11361 #else
11362 #define imgheight 100
11363 #endif
11364 patch_t *PictureOfLevel;
11365 lumpnum_t lumpnum;
11366 char headerstr[40];
11367
11368 sprintf(headerstr, "%s - %s", cv_newgametype.string, cv_nextmap.string);
11369
11370 M_DrawLevelPlatterHeader(currentMenu->y + imgheight - 10 - lsheadingheight/2, (const char *)headerstr, true, false);
11371
11372 // A 160x100 image of the level as entry MAPxxP
11373 lumpnum = W_CheckNumForName(va("%sP", G_BuildMapName(cv_nextmap.value)));
11374
11375 if (lumpnum != LUMPERROR)
11376 PictureOfLevel = W_CachePatchName(va("%sP", G_BuildMapName(cv_nextmap.value)), PU_PATCH);
11377 else
11378 PictureOfLevel = W_CachePatchName("BLANKLVL", PU_PATCH);
11379
11380 V_DrawSmallScaledPatch(319 - (currentMenu->x + (PictureOfLevel->width / 2)), currentMenu->y + imgheight, 0, PictureOfLevel);
11381 }
11382 }
11383
11384 static void M_MapChange(INT32 choice)
11385 {
11386 (void)choice;
11387
11388 MISC_ChangeLevelDef.prevMenu = currentMenu;
11389 levellistmode = LLM_CREATESERVER;
11390
11391 if (Playing() && !(M_CanShowLevelOnPlatter(cv_nextmap.value-1, cv_newgametype.value)) && (M_CanShowLevelOnPlatter(gamemap-1, cv_newgametype.value)))
11392 CV_SetValue(&cv_nextmap, gamemap);
11393
11394 if (!M_PrepareLevelPlatter(cv_newgametype.value, (currentMenu == &MPauseDef)))
11395 {
11396 M_StartMessage(M_GetText("No selectable levels found.\n"),NULL,MM_NOTHING);
11397 return;
11398 }
11399
11400 M_SetupNextMenu(&MISC_ChangeLevelDef);
11401 }
11402
11403 static void M_StartSplitServerMenu(INT32 choice)
11404 {
11405 (void)choice;
11406 levellistmode = LLM_CREATESERVER;
11407 Newgametype_OnChange();
11408 M_SetupNextMenu(&MP_SplitServerDef);
11409 }
11410
11411 static void M_ServerOptions(INT32 choice)
11412 {
11413 (void)choice;
11414
11415 #ifndef NONET
11416 if ((splitscreen && !netgame) || currentMenu == &MP_SplitServerDef)
11417 {
11418 OP_ServerOptionsMenu[ 1].status = IT_GRAYEDOUT; // Server name
11419 OP_ServerOptionsMenu[ 2].status = IT_GRAYEDOUT; // Max players
11420 OP_ServerOptionsMenu[ 3].status = IT_GRAYEDOUT; // Allow add-on downloading
11421 OP_ServerOptionsMenu[ 4].status = IT_GRAYEDOUT; // Allow players to join
11422 OP_ServerOptionsMenu[36].status = IT_GRAYEDOUT; // Master server
11423 OP_ServerOptionsMenu[37].status = IT_GRAYEDOUT; // Minimum delay between joins
11424 OP_ServerOptionsMenu[38].status = IT_GRAYEDOUT; // Attempts to resynchronise
11425 }
11426 else
11427 {
11428 OP_ServerOptionsMenu[ 1].status = IT_STRING | IT_CVAR | IT_CV_STRING;
11429 OP_ServerOptionsMenu[ 2].status = IT_STRING | IT_CVAR;
11430 OP_ServerOptionsMenu[ 3].status = IT_STRING | IT_CVAR;
11431 OP_ServerOptionsMenu[ 4].status = IT_STRING | IT_CVAR;
11432 OP_ServerOptionsMenu[36].status = (netgame
11433 ? IT_GRAYEDOUT
11434 : (IT_STRING | IT_CVAR | IT_CV_STRING));
11435 OP_ServerOptionsMenu[37].status = IT_STRING | IT_CVAR;
11436 OP_ServerOptionsMenu[38].status = IT_STRING | IT_CVAR;
11437 }
11438 #endif
11439
11440 /* Disable fading because of different menu head. */
11441 if (currentMenu == &OP_MainDef)/* from Options menu */
11442 OP_ServerOptionsDef.menuid = MTREE2(MN_OP_MAIN, MN_OP_SERVER);
11443 else/* from Multiplayer menu */
11444 OP_ServerOptionsDef.menuid = MTREE2(MN_MP_MAIN, MN_MP_SERVER_OPTIONS);
11445
11446 OP_ServerOptionsDef.prevMenu = currentMenu;
11447 M_SetupNextMenu(&OP_ServerOptionsDef);
11448 }
11449
11450 #ifndef NONET
11451 static void M_StartServerMenu(INT32 choice)
11452 {
11453 (void)choice;
11454 ms_RoomId = -1;
11455 levellistmode = LLM_CREATESERVER;
11456 Newgametype_OnChange();
11457 M_SetupNextMenu(&MP_ServerDef);
11458 itemOn = 1;
11459 }
11460
11461 // ==============
11462 // CONNECT VIA IP
11463 // ==============
11464
11465 static char setupm_ip[28];
11466
11467 // Draw the funky Connect IP menu. Tails 11-19-2002
11468 // So much work for such a little thing!
11469 static void M_DrawMPMainMenu(void)
11470 {
11471 INT32 x = currentMenu->x;
11472 INT32 y = currentMenu->y;
11473
11474 // use generic drawer for cursor, items and title
11475 M_DrawGenericMenu();
11476
11477 V_DrawRightAlignedString(BASEVIDWIDTH-x, y+66,
11478 ((itemOn == 4) ? V_YELLOWMAP : 0), va("(2-%d players)", MAXPLAYERS));
11479
11480 V_DrawRightAlignedString(BASEVIDWIDTH-x, y+76,
11481 ((itemOn == 5) ? V_YELLOWMAP : 0), "(2 players)");
11482
11483 V_DrawRightAlignedString(BASEVIDWIDTH-x, y+116,
11484 ((itemOn == 8) ? V_YELLOWMAP : 0), "(splitscreen)");
11485
11486 y += 22;
11487
11488 V_DrawFill(x+5, y+4+5, /*16*8 + 6,*/ BASEVIDWIDTH - 2*(x+5), 8+6, 159);
11489
11490 // draw name string
11491 V_DrawString(x+8,y+12, V_ALLOWLOWERCASE, setupm_ip);
11492
11493 // draw text cursor for name
11494 if (itemOn == 2 //0
11495 && skullAnimCounter < 4) //blink cursor
11496 V_DrawCharacter(x+8+V_StringWidth(setupm_ip, V_ALLOWLOWERCASE),y+12,'_',false);
11497 }
11498
11499 // Tails 11-19-2002
11500 static void M_ConnectIP(INT32 choice)
11501 {
11502 (void)choice;
11503
11504 if (*setupm_ip == 0)
11505 {
11506 M_StartMessage("You must specify an IP address.\n", NULL, MM_NOTHING);
11507 return;
11508 }
11509
11510 M_ClearMenus(true);
11511
11512 COM_BufAddText(va("connect \"%s\"\n", setupm_ip));
11513
11514 // A little "please wait" message.
11515 M_DrawTextBox(56, BASEVIDHEIGHT/2-12, 24, 2);
11516 V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT/2, 0, "Connecting to server...");
11517 I_OsPolling();
11518 I_UpdateNoBlit();
11519 if (rendermode == render_soft)
11520 I_FinishUpdate(); // page flip or blit buffer
11521 }
11522
11523 // Tails 11-19-2002
11524 static void M_HandleConnectIP(INT32 choice)
11525 {
11526 size_t l;
11527 boolean exitmenu = false; // exit to previous menu and send name change
11528
11529 switch (choice)
11530 {
11531 case KEY_DOWNARROW:
11532 M_NextOpt();
11533 S_StartSound(NULL,sfx_menu1); // Tails
11534 break;
11535
11536 case KEY_UPARROW:
11537 M_PrevOpt();
11538 S_StartSound(NULL,sfx_menu1); // Tails
11539 break;
11540
11541 case KEY_ENTER:
11542 S_StartSound(NULL,sfx_menu1); // Tails
11543 M_ConnectIP(1);
11544 break;
11545
11546 case KEY_ESCAPE:
11547 exitmenu = true;
11548 break;
11549
11550 case KEY_BACKSPACE:
11551 if ((l = strlen(setupm_ip)) != 0)
11552 {
11553 S_StartSound(NULL,sfx_menu1); // Tails
11554 setupm_ip[l-1] = 0;
11555 }
11556 break;
11557
11558 case KEY_DEL:
11559 if (setupm_ip[0] && !shiftdown) // Shift+Delete is used for something else.
11560 {
11561 S_StartSound(NULL,sfx_menu1); // Tails
11562 setupm_ip[0] = 0;
11563 }
11564 if (!shiftdown) // Shift+Delete is used for something else.
11565 break;
11566
11567 /* FALLTHRU */
11568 default:
11569 l = strlen(setupm_ip);
11570
11571 if ( ctrldown ) {
11572 switch (choice) {
11573 case 'v':
11574 case 'V': // ctrl+v, pasting
11575 {
11576 const char *paste = I_ClipboardPaste();
11577
11578 if (paste != NULL) {
11579 strncat(setupm_ip, paste, 28-1 - l); // Concat the ip field with clipboard
11580 if (strlen(paste) != 0) // Don't play sound if nothing was pasted
11581 S_StartSound(NULL,sfx_menu1); // Tails
11582 }
11583
11584 break;
11585 }
11586 case KEY_INS:
11587 case 'c':
11588 case 'C': // ctrl+c, ctrl+insert, copying
11589 I_ClipboardCopy(setupm_ip, l);
11590 S_StartSound(NULL,sfx_menu1); // Tails
11591 break;
11592
11593 case 'x':
11594 case 'X': // ctrl+x, cutting
11595 I_ClipboardCopy(setupm_ip, l);
11596 S_StartSound(NULL,sfx_menu1); // Tails
11597 setupm_ip[0] = 0;
11598 break;
11599
11600 default: // otherwise do nothing
11601 break;
11602 }
11603 break; // don't check for typed keys
11604 }
11605
11606 if ( shiftdown ) {
11607 switch (choice) {
11608 case KEY_INS: // shift+insert, pasting
11609 {
11610 const char *paste = I_ClipboardPaste();
11611
11612 if (paste != NULL) {
11613 strncat(setupm_ip, paste, 28-1 - l); // Concat the ip field with clipboard
11614 if (strlen(paste) != 0) // Don't play sound if nothing was pasted
11615 S_StartSound(NULL,sfx_menu1); // Tails
11616 }
11617
11618 break;
11619 }
11620 case KEY_DEL: // shift+delete, cutting
11621 I_ClipboardCopy(setupm_ip, l);
11622 S_StartSound(NULL,sfx_menu1); // Tails
11623 setupm_ip[0] = 0;
11624 break;
11625 default: // otherwise do nothing.
11626 break;
11627 }
11628 }
11629
11630 if (l >= 28-1)
11631 break;
11632
11633 // Rudimentary number and period enforcing - also allows letters so hostnames can be used instead
11634 if ((choice >= '-' && choice <= ':') || (choice >= 'A' && choice <= 'Z') || (choice >= 'a' && choice <= 'z'))
11635 {
11636 S_StartSound(NULL,sfx_menu1); // Tails
11637 setupm_ip[l] = (char)choice;
11638 setupm_ip[l+1] = 0;
11639 }
11640 else if (choice >= 199 && choice <= 211 && choice != 202 && choice != 206) //numpad too!
11641 {
11642 char keypad_translation[] = {'7','8','9','-','4','5','6','+','1','2','3','0','.'};
11643 choice = keypad_translation[choice - 199];
11644 S_StartSound(NULL,sfx_menu1); // Tails
11645 setupm_ip[l] = (char)choice;
11646 setupm_ip[l+1] = 0;
11647 }
11648
11649 break;
11650 }
11651
11652 if (exitmenu)
11653 {
11654 currentMenu->lastOn = itemOn;
11655 if (currentMenu->prevMenu)
11656 M_SetupNextMenu (currentMenu->prevMenu);
11657 else
11658 M_ClearMenus(true);
11659 }
11660 }
11661 #endif //!NONET
11662
11663 // ========================
11664 // MULTIPLAYER PLAYER SETUP
11665 // ========================
11666 // Tails 03-02-2002
11667
11668 static UINT8 multi_tics;
11669 static UINT8 multi_frame;
11670 static UINT8 multi_spr2;
11671
11672 // this is set before entering the MultiPlayer setup menu,
11673 // for either player 1 or 2
11674 static char setupm_name[MAXPLAYERNAME+1];
11675 static player_t *setupm_player;
11676 static consvar_t *setupm_cvskin;
11677 static consvar_t *setupm_cvcolor;
11678 static consvar_t *setupm_cvname;
11679 static consvar_t *setupm_cvdefaultskin;
11680 static consvar_t *setupm_cvdefaultcolor;
11681 static INT32 setupm_fakeskin;
11682 static menucolor_t *setupm_fakecolor;
11683
11684 static void M_DrawSetupMultiPlayerMenu(void)
11685 {
11686 INT32 x, y, cursory = 0, flags = 0;
11687 spritedef_t *sprdef;
11688 spriteframe_t *sprframe;
11689 patch_t *patch;
11690 UINT8 *colormap;
11691
11692 x = MP_PlayerSetupDef.x;
11693 y = MP_PlayerSetupDef.y;
11694
11695 // use generic drawer for cursor, items and title
11696 //M_DrawGenericMenu();
11697
11698 // draw title (or big pic)
11699 M_DrawMenuTitle();
11700
11701 M_DrawLevelPlatterHeader(y - (lsheadingheight - 12), "Name", true, false);
11702 if (itemOn == 0)
11703 cursory = y;
11704 y += 11;
11705
11706 // draw name string
11707 V_DrawFill(x, y, 282/*(MAXPLAYERNAME+1)*8+6*/, 14, 159);
11708 V_DrawString(x + 8, y + 3, V_ALLOWLOWERCASE, setupm_name);
11709 if (skullAnimCounter < 4 && itemOn == 0)
11710 V_DrawCharacter(x + 8 + V_StringWidth(setupm_name, V_ALLOWLOWERCASE), y + 3,
11711 '_' | 0x80, false);
11712
11713 y += 20;
11714
11715 M_DrawLevelPlatterHeader(y - (lsheadingheight - 12), "Character", true, false);
11716 if (itemOn == 1)
11717 cursory = y;
11718
11719 // draw skin string
11720 V_DrawRightAlignedString(BASEVIDWIDTH - x, y,
11721 ((MP_PlayerSetupMenu[1].status & IT_TYPE) == IT_SPACE ? V_TRANSLUCENT : 0)|(itemOn == 1 ? V_YELLOWMAP : 0)|V_ALLOWLOWERCASE,
11722 skins[setupm_fakeskin].realname);
11723
11724 if (itemOn == 1 && (MP_PlayerSetupMenu[1].status & IT_TYPE) != IT_SPACE)
11725 {
11726 V_DrawCharacter(BASEVIDWIDTH - x - 10 - V_StringWidth(skins[setupm_fakeskin].realname, V_ALLOWLOWERCASE) - (skullAnimCounter/5), y,
11727 '\x1C' | V_YELLOWMAP, false);
11728 V_DrawCharacter(BASEVIDWIDTH - x + 2 + (skullAnimCounter/5), y,
11729 '\x1D' | V_YELLOWMAP, false);
11730 }
11731
11732 x = BASEVIDWIDTH/2;
11733 y += 11;
11734
11735 // anim the player in the box
11736 if (--multi_tics <= 0)
11737 {
11738 multi_frame++;
11739 multi_tics = 4;
11740 }
11741
11742 #define charw 74
11743
11744 // draw box around character
11745 V_DrawFill(x-(charw/2), y, charw, 84, 159);
11746
11747 sprdef = &skins[setupm_fakeskin].sprites[multi_spr2];
11748
11749 if (!setupm_fakecolor->color || !sprdef->numframes) // should never happen but hey, who knows
11750 goto faildraw;
11751
11752 // ok, draw player sprite for sure now
11753 colormap = R_GetTranslationColormap(setupm_fakeskin, setupm_fakecolor->color, GTC_CACHE);
11754
11755 if (multi_frame >= sprdef->numframes)
11756 multi_frame = 0;
11757
11758 sprframe = &sprdef->spriteframes[multi_frame];
11759 patch = W_CachePatchNum(sprframe->lumppat[0], PU_PATCH);
11760 if (sprframe->flip & 1) // Only for first sprite
11761 flags |= V_FLIP; // This sprite is left/right flipped!
11762
11763 #define chary (y+64)
11764
11765 V_DrawFixedPatch(
11766 x<<FRACBITS,
11767 chary<<FRACBITS,
11768 FixedDiv(skins[setupm_fakeskin].highresscale, skins[setupm_fakeskin].shieldscale),
11769 flags, patch, colormap);
11770
11771 goto colordraw;
11772
11773 faildraw:
11774 sprdef = &sprites[SPR_UNKN];
11775 if (!sprdef->numframes) // No frames ??
11776 return; // Can't render!
11777
11778 sprframe = &sprdef->spriteframes[0];
11779 patch = W_CachePatchNum(sprframe->lumppat[0], PU_PATCH);
11780 if (sprframe->flip & 1) // Only for first sprite
11781 flags |= V_FLIP; // This sprite is left/right flipped!
11782
11783 V_DrawScaledPatch(x, chary, flags, patch);
11784
11785 #undef chary
11786
11787 colordraw:
11788 x = MP_PlayerSetupDef.x;
11789 y += 75;
11790
11791 M_DrawLevelPlatterHeader(y - (lsheadingheight - 12), "Color", true, false);
11792 if (itemOn == 2)
11793 cursory = y;
11794
11795 // draw color string
11796 V_DrawRightAlignedString(BASEVIDWIDTH - x, y,
11797 ((MP_PlayerSetupMenu[2].status & IT_TYPE) == IT_SPACE ? V_TRANSLUCENT : 0)|(itemOn == 2 ? V_YELLOWMAP : 0)|V_ALLOWLOWERCASE,
11798 skincolors[setupm_fakecolor->color].name);
11799
11800 if (itemOn == 2 && (MP_PlayerSetupMenu[2].status & IT_TYPE) != IT_SPACE)
11801 {
11802 V_DrawCharacter(BASEVIDWIDTH - x - 10 - V_StringWidth(skincolors[setupm_fakecolor->color].name, V_ALLOWLOWERCASE) - (skullAnimCounter/5), y,
11803 '\x1C' | V_YELLOWMAP, false);
11804 V_DrawCharacter(BASEVIDWIDTH - x + 2 + (skullAnimCounter/5), y,
11805 '\x1D' | V_YELLOWMAP, false);
11806 }
11807
11808 y += 11;
11809
11810 #define indexwidth 8
11811 {
11812 const INT32 numcolors = (282-charw)/(2*indexwidth); // Number of colors per side
11813 INT32 w = indexwidth; // Width of a singular color block
11814 menucolor_t *mc = setupm_fakecolor->prev; // Last accessed color
11815 UINT8 h;
11816 INT16 i;
11817
11818 // Draw color in the middle
11819 x += numcolors*w;
11820 for (h = 0; h < 16; h++)
11821 V_DrawFill(x, y+h, charw, 1, skincolors[setupm_fakecolor->color].ramp[h]);
11822
11823 //Draw colors from middle to left
11824 for (i=0; i<numcolors; i++) {
11825 x -= w;
11826 // Find accessible color before this one
11827 while (!skincolors[mc->color].accessible)
11828 mc = mc->prev;
11829 for (h = 0; h < 16; h++)
11830 V_DrawFill(x, y+h, w, 1, skincolors[mc->color].ramp[h]);
11831 mc = mc->prev;
11832 }
11833
11834 // Draw colors from middle to right
11835 mc = setupm_fakecolor->next;
11836 x += numcolors*w + charw;
11837 for (i=0; i<numcolors; i++) {
11838 // Find accessible color after this one
11839 while (!skincolors[mc->color].accessible)
11840 mc = mc->next;
11841 for (h = 0; h < 16; h++)
11842 V_DrawFill(x, y+h, w, 1, skincolors[mc->color].ramp[h]);
11843 x += w;
11844 mc = mc->next;
11845 }
11846 }
11847 #undef charw
11848 #undef indexwidth
11849
11850 x = MP_PlayerSetupDef.x;
11851 y += 20;
11852
11853 V_DrawString(x, y,
11854 ((R_SkinAvailable(setupm_cvdefaultskin->string) != setupm_fakeskin
11855 || setupm_cvdefaultcolor->value != setupm_fakecolor->color)
11856 ? 0
11857 : V_TRANSLUCENT)
11858 | ((itemOn == 3) ? V_YELLOWMAP : 0),
11859 "Save as default");
11860 if (itemOn == 3)
11861 cursory = y;
11862
11863 V_DrawScaledPatch(x - 17, cursory, 0,
11864 W_CachePatchName("M_CURSOR", PU_PATCH));
11865 }
11866
11867 // Handle 1P/2P MP Setup
11868 static void M_HandleSetupMultiPlayer(INT32 choice)
11869 {
11870 size_t l;
11871 INT32 prev_setupm_fakeskin;
11872 boolean exitmenu = false; // exit to previous menu and send name change
11873
11874 switch (choice)
11875 {
11876 case KEY_DOWNARROW:
11877 M_NextOpt();
11878 S_StartSound(NULL,sfx_menu1); // Tails
11879 break;
11880
11881 case KEY_UPARROW:
11882 M_PrevOpt();
11883 S_StartSound(NULL,sfx_menu1); // Tails
11884 break;
11885
11886 case KEY_LEFTARROW:
11887 if (itemOn == 1) //player skin
11888 {
11889 S_StartSound(NULL,sfx_menu1); // Tails
11890 prev_setupm_fakeskin = setupm_fakeskin;
11891 do
11892 {
11893 setupm_fakeskin--;
11894 if (setupm_fakeskin < 0)
11895 setupm_fakeskin = numskins-1;
11896 }
11897 while ((prev_setupm_fakeskin != setupm_fakeskin) && !(R_SkinUsable(-1, setupm_fakeskin)));
11898 multi_spr2 = P_GetSkinSprite2(&skins[setupm_fakeskin], SPR2_WALK, NULL);
11899 }
11900 else if (itemOn == 2) // player color
11901 {
11902 S_StartSound(NULL,sfx_menu1); // Tails
11903 setupm_fakecolor = setupm_fakecolor->prev;
11904 }
11905 break;
11906
11907 case KEY_ENTER:
11908 if (itemOn == 3
11909 && (R_SkinAvailable(setupm_cvdefaultskin->string) != setupm_fakeskin
11910 || setupm_cvdefaultcolor->value != setupm_fakecolor->color))
11911 {
11912 S_StartSound(NULL,sfx_strpst);
11913 // you know what? always putting these in the buffer won't hurt anything.
11914 COM_BufAddText (va("%s \"%s\"\n",setupm_cvdefaultskin->name,skins[setupm_fakeskin].name));
11915 COM_BufAddText (va("%s %d\n",setupm_cvdefaultcolor->name,setupm_fakecolor->color));
11916 break;
11917 }
11918 /* FALLTHRU */
11919 case KEY_RIGHTARROW:
11920 if (itemOn == 1) //player skin
11921 {
11922 S_StartSound(NULL,sfx_menu1); // Tails
11923 prev_setupm_fakeskin = setupm_fakeskin;
11924 do
11925 {
11926 setupm_fakeskin++;
11927 if (setupm_fakeskin > numskins-1)
11928 setupm_fakeskin = 0;
11929 }
11930 while ((prev_setupm_fakeskin != setupm_fakeskin) && !(R_SkinUsable(-1, setupm_fakeskin)));
11931 multi_spr2 = P_GetSkinSprite2(&skins[setupm_fakeskin], SPR2_WALK, NULL);
11932 }
11933 else if (itemOn == 2) // player color
11934 {
11935 S_StartSound(NULL,sfx_menu1); // Tails
11936 setupm_fakecolor = setupm_fakecolor->next;
11937 }
11938 break;
11939
11940 case KEY_ESCAPE:
11941 exitmenu = true;
11942 break;
11943
11944 case KEY_BACKSPACE:
11945 if (itemOn == 0 && (l = strlen(setupm_name))!=0)
11946 {
11947 S_StartSound(NULL,sfx_menu1); // Tails
11948 setupm_name[l-1] = 0;
11949 }
11950 else if (itemOn == 2)
11951 {
11952 UINT16 col = skins[setupm_fakeskin].prefcolor;
11953 if ((setupm_fakecolor->color != col) && skincolors[col].accessible)
11954 {
11955 S_StartSound(NULL,sfx_menu1); // Tails
11956 for (setupm_fakecolor=menucolorhead;;setupm_fakecolor=setupm_fakecolor->next)
11957 if (setupm_fakecolor->color == col || setupm_fakecolor == menucolortail)
11958 break;
11959 }
11960 }
11961 break;
11962 break;
11963
11964 case KEY_DEL:
11965 if (itemOn == 0 && (l = strlen(setupm_name))!=0)
11966 {
11967 S_StartSound(NULL,sfx_menu1); // Tails
11968 setupm_name[0] = 0;
11969 }
11970 break;
11971
11972 default:
11973 if (itemOn != 0 || choice < 32 || choice > 127)
11974 break;
11975 S_StartSound(NULL,sfx_menu1); // Tails
11976 l = strlen(setupm_name);
11977 if (l < MAXPLAYERNAME)
11978 {
11979 setupm_name[l] = (char)choice;
11980 setupm_name[l+1] = 0;
11981 }
11982 break;
11983 }
11984
11985 // check color
11986 if (itemOn == 2 && !skincolors[setupm_fakecolor->color].accessible) {
11987 if (choice == KEY_LEFTARROW)
11988 while (!skincolors[setupm_fakecolor->color].accessible)
11989 setupm_fakecolor = setupm_fakecolor->prev;
11990 else if (choice == KEY_RIGHTARROW || choice == KEY_ENTER)
11991 while (!skincolors[setupm_fakecolor->color].accessible)
11992 setupm_fakecolor = setupm_fakecolor->next;
11993 }
11994
11995 if (exitmenu)
11996 {
11997 if (currentMenu->prevMenu)
11998 M_SetupNextMenu (currentMenu->prevMenu);
11999 else
12000 M_ClearMenus(true);
12001 }
12002 }
12003
12004 // start the multiplayer setup menu
12005 static void M_SetupMultiPlayer(INT32 choice)
12006 {
12007 (void)choice;
12008
12009 multi_frame = 0;
12010 multi_tics = 4;
12011 strcpy(setupm_name, cv_playername.string);
12012
12013 // set for player 1
12014 setupm_player = &players[consoleplayer];
12015 setupm_cvskin = &cv_skin;
12016 setupm_cvcolor = &cv_playercolor;
12017 setupm_cvname = &cv_playername;
12018 setupm_cvdefaultskin = &cv_defaultskin;
12019 setupm_cvdefaultcolor = &cv_defaultplayercolor;
12020
12021 // For whatever reason this doesn't work right if you just use ->value
12022 setupm_fakeskin = R_SkinAvailable(setupm_cvskin->string);
12023 if (setupm_fakeskin == -1)
12024 setupm_fakeskin = 0;
12025
12026 for (setupm_fakecolor=menucolorhead;;setupm_fakecolor=setupm_fakecolor->next)
12027 if (setupm_fakecolor->color == setupm_cvcolor->value || setupm_fakecolor == menucolortail)
12028 break;
12029
12030 // disable skin changes if we can't actually change skins
12031 if (!CanChangeSkin(consoleplayer))
12032 MP_PlayerSetupMenu[1].status = (IT_GRAYEDOUT);
12033 else
12034 MP_PlayerSetupMenu[1].status = (IT_KEYHANDLER|IT_STRING);
12035
12036 // ditto with colour
12037 if (Playing() && G_GametypeHasTeams())
12038 MP_PlayerSetupMenu[2].status = (IT_GRAYEDOUT);
12039 else
12040 MP_PlayerSetupMenu[2].status = (IT_KEYHANDLER|IT_STRING);
12041
12042 multi_spr2 = P_GetSkinSprite2(&skins[setupm_fakeskin], SPR2_WALK, NULL);
12043
12044 MP_PlayerSetupDef.prevMenu = currentMenu;
12045 M_SetupNextMenu(&MP_PlayerSetupDef);
12046 }
12047
12048 // start the multiplayer setup menu, for secondary player (splitscreen mode)
12049 static void M_SetupMultiPlayer2(INT32 choice)
12050 {
12051 (void)choice;
12052
12053 multi_frame = 0;
12054 multi_tics = 4;
12055 strcpy (setupm_name, cv_playername2.string);
12056
12057 // set for splitscreen secondary player
12058 setupm_player = &players[secondarydisplayplayer];
12059 setupm_cvskin = &cv_skin2;
12060 setupm_cvcolor = &cv_playercolor2;
12061 setupm_cvname = &cv_playername2;
12062 setupm_cvdefaultskin = &cv_defaultskin2;
12063 setupm_cvdefaultcolor = &cv_defaultplayercolor2;
12064
12065 // For whatever reason this doesn't work right if you just use ->value
12066 setupm_fakeskin = R_SkinAvailable(setupm_cvskin->string);
12067 if (setupm_fakeskin == -1)
12068 setupm_fakeskin = 0;
12069
12070 for (setupm_fakecolor=menucolorhead;;setupm_fakecolor=setupm_fakecolor->next)
12071 if (setupm_fakecolor->color == setupm_cvcolor->value || setupm_fakecolor == menucolortail)
12072 break;
12073
12074 // disable skin changes if we can't actually change skins
12075 if (splitscreen && !CanChangeSkin(secondarydisplayplayer))
12076 MP_PlayerSetupMenu[1].status = (IT_GRAYEDOUT);
12077 else
12078 MP_PlayerSetupMenu[1].status = (IT_KEYHANDLER | IT_STRING);
12079
12080 // ditto with colour
12081 if (Playing() && G_GametypeHasTeams())
12082 MP_PlayerSetupMenu[2].status = (IT_GRAYEDOUT);
12083 else
12084 MP_PlayerSetupMenu[2].status = (IT_KEYHANDLER|IT_STRING);
12085
12086 multi_spr2 = P_GetSkinSprite2(&skins[setupm_fakeskin], SPR2_WALK, NULL);
12087
12088 MP_PlayerSetupDef.prevMenu = currentMenu;
12089 M_SetupNextMenu(&MP_PlayerSetupDef);
12090 }
12091
12092 static boolean M_QuitMultiPlayerMenu(void)
12093 {
12094 size_t l;
12095 // send name if changed
12096 if (strcmp(setupm_name, setupm_cvname->string))
12097 {
12098 // remove trailing whitespaces
12099 for (l= strlen(setupm_name)-1;
12100 (signed)l >= 0 && setupm_name[l] ==' '; l--)
12101 setupm_name[l] =0;
12102 COM_BufAddText (va("%s \"%s\"\n",setupm_cvname->name,setupm_name));
12103 }
12104 COM_BufAddText (va("%s \"%s\"\n",setupm_cvskin->name,skins[setupm_fakeskin].name));
12105 // send color if changed
12106 if (setupm_fakecolor->color != setupm_cvcolor->value)
12107 COM_BufAddText (va("%s %d\n",setupm_cvcolor->name,setupm_fakecolor->color));
12108 return true;
12109 }
12110
12111 void M_AddMenuColor(UINT16 color) {
12112 menucolor_t *c;
12113
12114 if (color >= numskincolors) {
12115 CONS_Printf("M_AddMenuColor: color %d does not exist.",color);
12116 return;
12117 }
12118
12119 c = (menucolor_t *)malloc(sizeof(menucolor_t));
12120 c->color = color;
12121 if (menucolorhead == NULL) {
12122 c->next = c;
12123 c->prev = c;
12124 menucolorhead = c;
12125 menucolortail = c;
12126 } else {
12127 c->next = menucolorhead;
12128 c->prev = menucolortail;
12129 menucolortail->next = c;
12130 menucolorhead->prev = c;
12131 menucolortail = c;
12132 }
12133 }
12134
12135 void M_MoveColorBefore(UINT16 color, UINT16 targ) {
12136 menucolor_t *look, *c = NULL, *t = NULL;
12137
12138 if (color == targ)
12139 return;
12140 if (color >= numskincolors) {
12141 CONS_Printf("M_MoveColorBefore: color %d does not exist.",color);
12142 return;
12143 }
12144 if (targ >= numskincolors) {
12145 CONS_Printf("M_MoveColorBefore: target color %d does not exist.",targ);
12146 return;
12147 }
12148
12149 for (look=menucolorhead;;look=look->next) {
12150 if (look->color == color)
12151 c = look;
12152 else if (look->color == targ)
12153 t = look;
12154 if (c != NULL && t != NULL)
12155 break;
12156 if (look==menucolortail)
12157 return;
12158 }
12159
12160 if (c == t->prev)
12161 return;
12162
12163 if (t==menucolorhead)
12164 menucolorhead = c;
12165 if (c==menucolortail)
12166 menucolortail = c->prev;
12167
12168 c->prev->next = c->next;
12169 c->next->prev = c->prev;
12170
12171 c->prev = t->prev;
12172 c->next = t;
12173 t->prev->next = c;
12174 t->prev = c;
12175 }
12176
12177 void M_MoveColorAfter(UINT16 color, UINT16 targ) {
12178 menucolor_t *look, *c = NULL, *t = NULL;
12179
12180 if (color == targ)
12181 return;
12182 if (color >= numskincolors) {
12183 CONS_Printf("M_MoveColorAfter: color %d does not exist.\n",color);
12184 return;
12185 }
12186 if (targ >= numskincolors) {
12187 CONS_Printf("M_MoveColorAfter: target color %d does not exist.\n",targ);
12188 return;
12189 }
12190
12191 for (look=menucolorhead;;look=look->next) {
12192 if (look->color == color)
12193 c = look;
12194 else if (look->color == targ)
12195 t = look;
12196 if (c != NULL && t != NULL)
12197 break;
12198 if (look==menucolortail)
12199 return;
12200 }
12201
12202 if (t == c->prev)
12203 return;
12204
12205 if (t==menucolortail)
12206 menucolortail = c;
12207 else if (c==menucolortail)
12208 menucolortail = c->prev;
12209
12210 c->prev->next = c->next;
12211 c->next->prev = c->prev;
12212
12213 c->next = t->next;
12214 c->prev = t;
12215 t->next->prev = c;
12216 t->next = c;
12217 }
12218
12219 UINT16 M_GetColorBefore(UINT16 color) {
12220 menucolor_t *look;
12221
12222 if (color >= numskincolors) {
12223 CONS_Printf("M_GetColorBefore: color %d does not exist.\n",color);
12224 return 0;
12225 }
12226
12227 for (look=menucolorhead;;look=look->next) {
12228 if (look->color == color)
12229 return look->prev->color;
12230 if (look==menucolortail)
12231 return 0;
12232 }
12233 }
12234
12235 UINT16 M_GetColorAfter(UINT16 color) {
12236 menucolor_t *look;
12237
12238 if (color >= numskincolors) {
12239 CONS_Printf("M_GetColorAfter: color %d does not exist.\n",color);
12240 return 0;
12241 }
12242
12243 for (look=menucolorhead;;look=look->next) {
12244 if (look->color == color)
12245 return look->next->color;
12246 if (look==menucolortail)
12247 return 0;
12248 }
12249 }
12250
12251 void M_InitPlayerSetupColors(void) {
12252 UINT8 i;
12253 numskincolors = SKINCOLOR_FIRSTFREESLOT;
12254 menucolorhead = menucolortail = NULL;
12255 for (i=0; i<numskincolors; i++)
12256 M_AddMenuColor(i);
12257 }
12258
12259 void M_FreePlayerSetupColors(void) {
12260 menucolor_t *look = menucolorhead, *tmp;
12261
12262 if (menucolorhead==NULL)
12263 return;
12264
12265 while (true) {
12266 if (look != menucolortail) {
12267 tmp = look;
12268 look = look->next;
12269 free(tmp);
12270 } else {
12271 free(look);
12272 return;
12273 }
12274 }
12275
12276 menucolorhead = menucolortail = NULL;
12277 }
12278
12279 // =================
12280 // DATA OPTIONS MENU
12281 // =================
12282 static UINT8 erasecontext = 0;
12283
12284 static void M_EraseDataResponse(INT32 ch)
12285 {
12286 if (ch != 'y' && ch != KEY_ENTER)
12287 return;
12288
12289 // Delete the data
12290 if (erasecontext != 1)
12291 G_ClearRecords();
12292 if (erasecontext != 0)
12293 M_ClearSecrets();
12294 if (erasecontext == 2)
12295 {
12296 totalplaytime = 0;
12297 F_StartIntro();
12298 }
12299 BwehHehHe();
12300 M_ClearMenus(true);
12301 }
12302
12303 static void M_EraseData(INT32 choice)
12304 {
12305 const char *eschoice, *esstr = M_GetText("Are you sure you want to erase\n%s?\n\n(Press 'Y' to confirm)\n");
12306
12307 erasecontext = (UINT8)choice;
12308
12309 if (choice == 0)
12310 eschoice = M_GetText("Record Attack data");
12311 else if (choice == 1)
12312 eschoice = M_GetText("Extras data");
12313 else
12314 eschoice = M_GetText("ALL game data");
12315
12316 M_StartMessage(va(esstr, eschoice),M_EraseDataResponse,MM_YESNO);
12317 }
12318
12319 static void M_ScreenshotOptions(INT32 choice)
12320 {
12321 (void)choice;
12322 Screenshot_option_Onchange();
12323 Moviemode_mode_Onchange();
12324
12325 M_SetupScreenshotMenu();
12326 M_SetupNextMenu(&OP_ScreenshotOptionsDef);
12327 }
12328
12329 static void M_SetupScreenshotMenu(void)
12330 {
12331 menuitem_t *item = &OP_ScreenshotOptionsMenu[op_screenshot_colorprofile];
12332
12333 #ifdef HWRENDER
12334 // Hide some options based on render mode
12335 if (rendermode == render_opengl)
12336 {
12337 item->status = IT_GRAYEDOUT;
12338 if ((currentMenu == &OP_ScreenshotOptionsDef) && (itemOn == op_screenshot_colorprofile)) // Can't select that
12339 itemOn = op_screenshot_storagelocation;
12340 }
12341 else
12342 #endif
12343 item->status = (IT_STRING | IT_CVAR);
12344 }
12345
12346 // =============
12347 // JOYSTICK MENU
12348 // =============
12349
12350 // Start the controls menu, setting it up for either the console player,
12351 // or the secondary splitscreen player
12352
12353 static void M_DrawJoystick(void)
12354 {
12355 INT32 i, compareval2, compareval;
12356
12357 // draw title (or big pic)
12358 M_DrawMenuTitle();
12359
12360 for (i = 0; i <= MAX_JOYSTICKS; i++) // See MAX_JOYSTICKS
12361 {
12362 M_DrawTextBox(OP_JoystickSetDef.x-8, OP_JoystickSetDef.y+LINEHEIGHT*i-12, 28, 1);
12363 //M_DrawSaveLoadBorder(OP_JoystickSetDef.x+4, OP_JoystickSetDef.y+1+LINEHEIGHT*i);
12364
12365 #ifdef JOYSTICK_HOTPLUG
12366 if (atoi(cv_usejoystick2.string) > I_NumJoys())
12367 compareval2 = atoi(cv_usejoystick2.string);
12368 else
12369 compareval2 = cv_usejoystick2.value;
12370
12371 if (atoi(cv_usejoystick.string) > I_NumJoys())
12372 compareval = atoi(cv_usejoystick.string);
12373 else
12374 compareval = cv_usejoystick.value;
12375 #else
12376 compareval2 = cv_usejoystick2.value;
12377 compareval = cv_usejoystick.value;
12378 #endif
12379
12380 if ((setupcontrols_secondaryplayer && (i == compareval2))
12381 || (!setupcontrols_secondaryplayer && (i == compareval)))
12382 V_DrawString(OP_JoystickSetDef.x, OP_JoystickSetDef.y+LINEHEIGHT*i-4,V_GREENMAP,joystickInfo[i]);
12383 else
12384 V_DrawString(OP_JoystickSetDef.x, OP_JoystickSetDef.y+LINEHEIGHT*i-4,0,joystickInfo[i]);
12385
12386 if (i == itemOn)
12387 {
12388 V_DrawScaledPatch(currentMenu->x - 24, OP_JoystickSetDef.y+LINEHEIGHT*i-4, 0,
12389 W_CachePatchName("M_CURSOR", PU_PATCH));
12390 }
12391 }
12392 }
12393
12394 void M_SetupJoystickMenu(INT32 choice)
12395 {
12396 INT32 i = 0;
12397 const char *joyNA = "Unavailable";
12398 INT32 n = I_NumJoys();
12399 (void)choice;
12400
12401 strcpy(joystickInfo[i], "None");
12402
12403 for (i = 1; i <= MAX_JOYSTICKS; i++)
12404 {
12405 if (i <= n && (I_GetJoyName(i)) != NULL)
12406 strncpy(joystickInfo[i], I_GetJoyName(i), 28);
12407 else
12408 strcpy(joystickInfo[i], joyNA);
12409
12410 #ifdef JOYSTICK_HOTPLUG
12411 // We use cv_usejoystick.string as the USER-SET var
12412 // and cv_usejoystick.value as the INTERNAL var
12413 //
12414 // In practice, if cv_usejoystick.string == 0, this overrides
12415 // cv_usejoystick.value and always disables
12416 //
12417 // Update cv_usejoystick.string here so that the user can
12418 // properly change this value.
12419 if (i == cv_usejoystick.value)
12420 CV_SetValue(&cv_usejoystick, i);
12421 if (i == cv_usejoystick2.value)
12422 CV_SetValue(&cv_usejoystick2, i);
12423 #endif
12424 }
12425
12426 M_SetupNextMenu(&OP_JoystickSetDef);
12427 }
12428
12429 static void M_Setup1PJoystickMenu(INT32 choice)
12430 {
12431 setupcontrols_secondaryplayer = false;
12432 OP_JoystickSetDef.prevMenu = &OP_Joystick1Def;
12433 OP_JoystickSetDef.menuid &= ~(((1 << MENUBITS) - 1) << MENUBITS);
12434 OP_JoystickSetDef.menuid &= ~(((1 << MENUBITS) - 1) << (MENUBITS*2));
12435 OP_JoystickSetDef.menuid |= MN_OP_P1CONTROLS << MENUBITS;
12436 OP_JoystickSetDef.menuid |= MN_OP_P1JOYSTICK << (MENUBITS*2);
12437 M_SetupJoystickMenu(choice);
12438 }
12439
12440 static void M_Setup2PJoystickMenu(INT32 choice)
12441 {
12442 setupcontrols_secondaryplayer = true;
12443 OP_JoystickSetDef.prevMenu = &OP_Joystick2Def;
12444 OP_JoystickSetDef.menuid &= ~(((1 << MENUBITS) - 1) << MENUBITS);
12445 OP_JoystickSetDef.menuid &= ~(((1 << MENUBITS) - 1) << (MENUBITS*2));
12446 OP_JoystickSetDef.menuid |= MN_OP_P2CONTROLS << MENUBITS;
12447 OP_JoystickSetDef.menuid |= MN_OP_P2JOYSTICK << (MENUBITS*2);
12448 M_SetupJoystickMenu(choice);
12449 }
12450
12451 static void M_AssignJoystick(INT32 choice)
12452 {
12453 #ifdef JOYSTICK_HOTPLUG
12454 INT32 oldchoice, oldstringchoice;
12455 INT32 numjoys = I_NumJoys();
12456
12457 if (setupcontrols_secondaryplayer)
12458 {
12459 oldchoice = oldstringchoice = atoi(cv_usejoystick2.string) > numjoys ? atoi(cv_usejoystick2.string) : cv_usejoystick2.value;
12460 CV_SetValue(&cv_usejoystick2, choice);
12461
12462 // Just in case last-minute changes were made to cv_usejoystick.value,
12463 // update the string too
12464 // But don't do this if we're intentionally setting higher than numjoys
12465 if (choice <= numjoys)
12466 {
12467 CV_SetValue(&cv_usejoystick2, cv_usejoystick2.value);
12468
12469 // reset this so the comparison is valid
12470 if (oldchoice > numjoys)
12471 oldchoice = cv_usejoystick2.value;
12472
12473 if (oldchoice != choice)
12474 {
12475 if (choice && oldstringchoice > numjoys) // if we did not select "None", we likely selected a used device
12476 CV_SetValue(&cv_usejoystick2, (oldstringchoice > numjoys ? oldstringchoice : oldchoice));
12477
12478 if (oldstringchoice ==
12479 (atoi(cv_usejoystick2.string) > numjoys ? atoi(cv_usejoystick2.string) : cv_usejoystick2.value))
12480 M_StartMessage("This gamepad is used by another\n"
12481 "player. Reset the gamepad\n"
12482 "for that player first.\n\n"
12483 "(Press a key)\n", NULL, MM_NOTHING);
12484 }
12485 }
12486 }
12487 else
12488 {
12489 oldchoice = oldstringchoice = atoi(cv_usejoystick.string) > numjoys ? atoi(cv_usejoystick.string) : cv_usejoystick.value;
12490 CV_SetValue(&cv_usejoystick, choice);
12491
12492 // Just in case last-minute changes were made to cv_usejoystick.value,
12493 // update the string too
12494 // But don't do this if we're intentionally setting higher than numjoys
12495 if (choice <= numjoys)
12496 {
12497 CV_SetValue(&cv_usejoystick, cv_usejoystick.value);
12498
12499 // reset this so the comparison is valid
12500 if (oldchoice > numjoys)
12501 oldchoice = cv_usejoystick.value;
12502
12503 if (oldchoice != choice)
12504 {
12505 if (choice && oldstringchoice > numjoys) // if we did not select "None", we likely selected a used device
12506 CV_SetValue(&cv_usejoystick, (oldstringchoice > numjoys ? oldstringchoice : oldchoice));
12507
12508 if (oldstringchoice ==
12509 (atoi(cv_usejoystick.string) > numjoys ? atoi(cv_usejoystick.string) : cv_usejoystick.value))
12510 M_StartMessage("This gamepad is used by another\n"
12511 "player. Reset the gamepad\n"
12512 "for that player first.\n\n"
12513 "(Press a key)\n", NULL, MM_NOTHING);
12514 }
12515 }
12516 }
12517 #else
12518 if (setupcontrols_secondaryplayer)
12519 CV_SetValue(&cv_usejoystick2, choice);
12520 else
12521 CV_SetValue(&cv_usejoystick, choice);
12522 #endif
12523 }
12524
12525 // =============
12526 // CONTROLS MENU
12527 // =============
12528
12529 static void M_Setup1PControlsMenu(INT32 choice)
12530 {
12531 (void)choice;
12532 setupcontrols_secondaryplayer = false;
12533 setupcontrols = gamecontrol; // was called from main Options (for console player, then)
12534 currentMenu->lastOn = itemOn;
12535
12536 // Unhide the nine non-P2 controls and their headers
12537 //OP_ChangeControlsMenu[18+0].status = IT_HEADER;
12538 //OP_ChangeControlsMenu[18+1].status = IT_SPACE;
12539 // ...
12540 OP_ChangeControlsMenu[18+2].status = IT_CALL|IT_STRING2;
12541 OP_ChangeControlsMenu[18+3].status = IT_CALL|IT_STRING2;
12542 OP_ChangeControlsMenu[18+4].status = IT_CALL|IT_STRING2;
12543 OP_ChangeControlsMenu[18+5].status = IT_CALL|IT_STRING2;
12544 OP_ChangeControlsMenu[18+6].status = IT_CALL|IT_STRING2;
12545 //OP_ChangeControlsMenu[18+7].status = IT_CALL|IT_STRING2;
12546 OP_ChangeControlsMenu[18+8].status = IT_CALL|IT_STRING2;
12547 // ...
12548 OP_ChangeControlsMenu[27+0].status = IT_HEADER;
12549 OP_ChangeControlsMenu[27+1].status = IT_SPACE;
12550 // ...
12551 OP_ChangeControlsMenu[27+2].status = IT_CALL|IT_STRING2;
12552 OP_ChangeControlsMenu[27+3].status = IT_CALL|IT_STRING2;
12553
12554 OP_ChangeControlsDef.prevMenu = &OP_P1ControlsDef;
12555 OP_ChangeControlsDef.menuid &= ~(((1 << MENUBITS) - 1) << MENUBITS); // remove second level
12556 OP_ChangeControlsDef.menuid |= MN_OP_P1CONTROLS << MENUBITS; // combine second level
12557 M_SetupNextMenu(&OP_ChangeControlsDef);
12558 }
12559
12560 static void M_Setup2PControlsMenu(INT32 choice)
12561 {
12562 (void)choice;
12563 setupcontrols_secondaryplayer = true;
12564 setupcontrols = gamecontrolbis;
12565 currentMenu->lastOn = itemOn;
12566
12567 // Hide the nine non-P2 controls and their headers
12568 //OP_ChangeControlsMenu[18+0].status = IT_GRAYEDOUT2;
12569 //OP_ChangeControlsMenu[18+1].status = IT_GRAYEDOUT2;
12570 // ...
12571 OP_ChangeControlsMenu[18+2].status = IT_GRAYEDOUT2;
12572 OP_ChangeControlsMenu[18+3].status = IT_GRAYEDOUT2;
12573 OP_ChangeControlsMenu[18+4].status = IT_GRAYEDOUT2;
12574 OP_ChangeControlsMenu[18+5].status = IT_GRAYEDOUT2;
12575 OP_ChangeControlsMenu[18+6].status = IT_GRAYEDOUT2;
12576 //OP_ChangeControlsMenu[18+7].status = IT_GRAYEDOUT2;
12577 OP_ChangeControlsMenu[18+8].status = IT_GRAYEDOUT2;
12578 // ...
12579 OP_ChangeControlsMenu[27+0].status = IT_GRAYEDOUT2;
12580 OP_ChangeControlsMenu[27+1].status = IT_GRAYEDOUT2;
12581 // ...
12582 OP_ChangeControlsMenu[27+2].status = IT_GRAYEDOUT2;
12583 OP_ChangeControlsMenu[27+3].status = IT_GRAYEDOUT2;
12584
12585 OP_ChangeControlsDef.prevMenu = &OP_P2ControlsDef;
12586 OP_ChangeControlsDef.menuid &= ~(((1 << MENUBITS) - 1) << MENUBITS); // remove second level
12587 OP_ChangeControlsDef.menuid |= MN_OP_P2CONTROLS << MENUBITS; // combine second level
12588 M_SetupNextMenu(&OP_ChangeControlsDef);
12589 }
12590
12591 #define controlheight 18
12592
12593 // Draws the Customise Controls menu
12594 static void M_DrawControl(void)
12595 {
12596 char tmp[50];
12597 INT32 x, y, i, max, cursory = 0, iter;
12598 INT32 keys[2];
12599
12600 x = currentMenu->x;
12601 y = currentMenu->y;
12602
12603 /*i = itemOn - (controlheight/2);
12604 if (i < 0)
12605 i = 0;
12606 */
12607
12608 iter = (controlheight/2);
12609 for (i = itemOn; ((iter || currentMenu->menuitems[i].status == IT_GRAYEDOUT2) && i > 0); i--)
12610 {
12611 if (currentMenu->menuitems[i].status != IT_GRAYEDOUT2)
12612 iter--;
12613 }
12614 if (currentMenu->menuitems[i].status == IT_GRAYEDOUT2)
12615 i--;
12616
12617 iter += (controlheight/2);
12618 for (max = itemOn; (iter && max < currentMenu->numitems); max++)
12619 {
12620 if (currentMenu->menuitems[max].status != IT_GRAYEDOUT2)
12621 iter--;
12622 }
12623
12624 if (iter)
12625 {
12626 iter += (controlheight/2);
12627 for (i = itemOn; ((iter || currentMenu->menuitems[i].status == IT_GRAYEDOUT2) && i > 0); i--)
12628 {
12629 if (currentMenu->menuitems[i].status != IT_GRAYEDOUT2)
12630 iter--;
12631 }
12632 }
12633
12634 /*max = i + controlheight;
12635 if (max > currentMenu->numitems)
12636 {
12637 max = currentMenu->numitems;
12638 if (max < controlheight)
12639 i = 0;
12640 else
12641 i = max - controlheight;
12642 }*/
12643
12644 // draw title (or big pic)
12645 M_DrawMenuTitle();
12646
12647 if (tutorialmode && tutorialgcs)
12648 {
12649 if ((gametic / TICRATE) % 2)
12650 M_CentreText(30, "\202EXIT THE TUTORIAL TO CHANGE THE CONTROLS");
12651 else
12652 M_CentreText(30, "EXIT THE TUTORIAL TO CHANGE THE CONTROLS");
12653 }
12654 else
12655 M_CentreText(30,
12656 (setupcontrols_secondaryplayer ? "SET CONTROLS FOR SECONDARY PLAYER" :
12657 "PRESS ENTER TO CHANGE, BACKSPACE TO CLEAR"));
12658
12659 if (i)
12660 V_DrawString(currentMenu->x - 16, y-(skullAnimCounter/5), V_YELLOWMAP, "\x1A"); // up arrow
12661 if (max != currentMenu->numitems)
12662 V_DrawString(currentMenu->x - 16, y+(SMALLLINEHEIGHT*(controlheight-1))+(skullAnimCounter/5), V_YELLOWMAP, "\x1B"); // down arrow
12663
12664 for (; i < max; i++)
12665 {
12666 if (currentMenu->menuitems[i].status == IT_GRAYEDOUT2)
12667 continue;
12668
12669 if (i == itemOn)
12670 cursory = y;
12671
12672 if (currentMenu->menuitems[i].status == IT_CONTROL)
12673 {
12674 V_DrawString(x, y, ((i == itemOn) ? V_YELLOWMAP : 0), currentMenu->menuitems[i].text);
12675 keys[0] = setupcontrols[currentMenu->menuitems[i].alphaKey][0];
12676 keys[1] = setupcontrols[currentMenu->menuitems[i].alphaKey][1];
12677
12678 tmp[0] ='\0';
12679 if (keys[0] == KEY_NULL && keys[1] == KEY_NULL)
12680 {
12681 strcpy(tmp, "---");
12682 }
12683 else
12684 {
12685 if (keys[0] != KEY_NULL)
12686 strcat (tmp, G_KeynumToString (keys[0]));
12687
12688 if (keys[0] != KEY_NULL && keys[1] != KEY_NULL)
12689 strcat(tmp," or ");
12690
12691 if (keys[1] != KEY_NULL)
12692 strcat (tmp, G_KeynumToString (keys[1]));
12693
12694
12695 }
12696 V_DrawRightAlignedString(BASEVIDWIDTH-currentMenu->x, y, V_YELLOWMAP, tmp);
12697 }
12698 /*else if (currentMenu->menuitems[i].status == IT_GRAYEDOUT2)
12699 V_DrawString(x, y, V_TRANSLUCENT, currentMenu->menuitems[i].text);*/
12700 else if ((currentMenu->menuitems[i].status == IT_HEADER) && (i != max-1))
12701 M_DrawLevelPlatterHeader(y, currentMenu->menuitems[i].text, true, false);
12702
12703 y += SMALLLINEHEIGHT;
12704 }
12705
12706 V_DrawScaledPatch(currentMenu->x - 20, cursory, 0,
12707 W_CachePatchName("M_CURSOR", PU_PATCH));
12708 }
12709
12710 #undef controlbuffer
12711
12712 static INT32 controltochange;
12713 static char controltochangetext[33];
12714
12715 static void M_ChangecontrolResponse(event_t *ev)
12716 {
12717 INT32 control;
12718 INT32 found;
12719 INT32 ch = ev->data1;
12720
12721 // ESCAPE cancels; dummy out PAUSE
12722 if (ch != KEY_ESCAPE && ch != KEY_PAUSE)
12723 {
12724
12725 switch (ev->type)
12726 {
12727 // ignore mouse/joy movements, just get buttons
12728 case ev_mouse:
12729 case ev_mouse2:
12730 case ev_joystick:
12731 case ev_joystick2:
12732 ch = KEY_NULL; // no key
12733 break;
12734
12735 // keypad arrows are converted for the menu in cursor arrows
12736 // so use the event instead of ch
12737 case ev_keydown:
12738 ch = ev->data1;
12739 break;
12740
12741 default:
12742 break;
12743 }
12744
12745 control = controltochange;
12746
12747 // check if we already entered this key
12748 found = -1;
12749 if (setupcontrols[control][0] ==ch)
12750 found = 0;
12751 else if (setupcontrols[control][1] ==ch)
12752 found = 1;
12753 if (found >= 0)
12754 {
12755 // replace mouse and joy clicks by double clicks
12756 if (ch >= KEY_MOUSE1 && ch <= KEY_MOUSE1+MOUSEBUTTONS)
12757 setupcontrols[control][found] = ch-KEY_MOUSE1+KEY_DBLMOUSE1;
12758 else if (ch >= KEY_JOY1 && ch <= KEY_JOY1+JOYBUTTONS)
12759 setupcontrols[control][found] = ch-KEY_JOY1+KEY_DBLJOY1;
12760 else if (ch >= KEY_2MOUSE1 && ch <= KEY_2MOUSE1+MOUSEBUTTONS)
12761 setupcontrols[control][found] = ch-KEY_2MOUSE1+KEY_DBL2MOUSE1;
12762 else if (ch >= KEY_2JOY1 && ch <= KEY_2JOY1+JOYBUTTONS)
12763 setupcontrols[control][found] = ch-KEY_2JOY1+KEY_DBL2JOY1;
12764 }
12765 else
12766 {
12767 // check if change key1 or key2, or replace the two by the new
12768 found = 0;
12769 if (setupcontrols[control][0] == KEY_NULL)
12770 found++;
12771 if (setupcontrols[control][1] == KEY_NULL)
12772 found++;
12773 if (found == 2)
12774 {
12775 found = 0;
12776 setupcontrols[control][1] = KEY_NULL; //replace key 1,clear key2
12777 }
12778 (void)G_CheckDoubleUsage(ch, true);
12779 setupcontrols[control][found] = ch;
12780 }
12781 S_StartSound(NULL, sfx_strpst);
12782 }
12783 else if (ch == KEY_PAUSE)
12784 {
12785 // This buffer assumes a 125-character message plus a 32-character control name (per controltochangetext buffer size)
12786 static char tmp[158];
12787 menu_t *prev = currentMenu->prevMenu;
12788
12789 if (controltochange == gc_pause)
12790 sprintf(tmp, M_GetText("The \x82Pause Key \x80is enabled, but \nit cannot be used to retry runs \nduring Record Attack. \n\nHit another key for\n%s\nESC for Cancel"),
12791 controltochangetext);
12792 else
12793 sprintf(tmp, M_GetText("The \x82Pause Key \x80is enabled, but \nit is not configurable. \n\nHit another key for\n%s\nESC for Cancel"),
12794 controltochangetext);
12795
12796 M_StartMessage(tmp, M_ChangecontrolResponse, MM_EVENTHANDLER);
12797 currentMenu->prevMenu = prev;
12798
12799 S_StartSound(NULL, sfx_s3k42);
12800 return;
12801 }
12802 else
12803 S_StartSound(NULL, sfx_skid);
12804
12805 M_StopMessage(0);
12806 }
12807
12808 static void M_ChangeControl(INT32 choice)
12809 {
12810 // This buffer assumes a 35-character message (per below) plus a max control name limit of 32 chars (per controltochangetext)
12811 // If you change the below message, then change the size of this buffer!
12812 static char tmp[68];
12813
12814 if (tutorialmode && tutorialgcs) // don't allow control changes if temp control override is active
12815 return;
12816
12817 controltochange = currentMenu->menuitems[choice].alphaKey;
12818 sprintf(tmp, M_GetText("Hit the new key for\n%s\nESC for Cancel"),
12819 currentMenu->menuitems[choice].text);
12820 strlcpy(controltochangetext, currentMenu->menuitems[choice].text, 33);
12821
12822 M_StartMessage(tmp, M_ChangecontrolResponse, MM_EVENTHANDLER);
12823 }
12824
12825 static void M_Setup1PPlaystyleMenu(INT32 choice)
12826 {
12827 (void)choice;
12828
12829 playstyle_activeplayer = 0;
12830 OP_PlaystyleDef.prevMenu = &OP_P1ControlsDef;
12831 M_SetupNextMenu(&OP_PlaystyleDef);
12832 }
12833
12834 static void M_Setup2PPlaystyleMenu(INT32 choice)
12835 {
12836 (void)choice;
12837
12838 playstyle_activeplayer = 1;
12839 OP_PlaystyleDef.prevMenu = &OP_P2ControlsDef;
12840 M_SetupNextMenu(&OP_PlaystyleDef);
12841 }
12842
12843 static void M_DrawPlaystyleMenu(void)
12844 {
12845 size_t i;
12846
12847 for (i = 0; i < 4; i++)
12848 {
12849 if (i != 3)
12850 V_DrawCenteredString((i+1)*BASEVIDWIDTH/4, 20, (i == playstyle_currentchoice) ? V_YELLOWMAP : 0, PlaystyleNames[i]);
12851
12852 if (i == playstyle_currentchoice)
12853 {
12854 V_DrawScaledPatch((i+1)*BASEVIDWIDTH/4 - 8, 10, 0, W_CachePatchName("M_CURSOR", PU_CACHE));
12855 V_DrawString(30, 50, V_ALLOWLOWERCASE, PlaystyleDesc[i]);
12856 }
12857 }
12858 }
12859
12860 static void M_HandlePlaystyleMenu(INT32 choice)
12861 {
12862 switch (choice)
12863 {
12864 case KEY_ESCAPE:
12865 case KEY_BACKSPACE:
12866 M_SetupNextMenu(currentMenu->prevMenu);
12867 break;
12868
12869 case KEY_ENTER:
12870 S_StartSound(NULL, sfx_menu1);
12871 CV_SetValue((playstyle_activeplayer ? &cv_directionchar[1] : &cv_directionchar[0]), playstyle_currentchoice ? 1 : 0);
12872 CV_SetValue((playstyle_activeplayer ? &cv_useranalog[1] : &cv_useranalog[0]), playstyle_currentchoice/2);
12873
12874 if (playstyle_activeplayer)
12875 CV_UpdateCam2Dist();
12876 else
12877 CV_UpdateCamDist();
12878
12879 M_SetupNextMenu(currentMenu->prevMenu);
12880 break;
12881
12882 case KEY_LEFTARROW:
12883 S_StartSound(NULL, sfx_menu1);
12884 playstyle_currentchoice = (playstyle_currentchoice+2)%3;
12885 break;
12886
12887 case KEY_RIGHTARROW:
12888 S_StartSound(NULL, sfx_menu1);
12889 playstyle_currentchoice = (playstyle_currentchoice+1)%3;
12890 break;
12891 }
12892 }
12893
12894 static void M_DrawCameraOptionsMenu(void)
12895 {
12896 M_DrawGenericScrollMenu();
12897
12898 if (gamestate == GS_LEVEL && (paused || P_AutoPause()))
12899 {
12900 if (currentMenu == &OP_Camera2OptionsDef && splitscreen && camera2.chase)
12901 P_MoveChaseCamera(&players[secondarydisplayplayer], &camera2, false);
12902 if (currentMenu == &OP_CameraOptionsDef && camera.chase)
12903 P_MoveChaseCamera(&players[displayplayer], &camera, false);
12904 }
12905 }
12906
12907 // ===============
12908 // VIDEO MODE MENU
12909 // ===============
12910
12911 //added : 30-01-98:
12912 #define MAXCOLUMNMODES 12 //max modes displayed in one column
12913 #define MAXMODEDESCS (MAXCOLUMNMODES*3)
12914
12915 static modedesc_t modedescs[MAXMODEDESCS];
12916
12917 static void M_VideoModeMenu(INT32 choice)
12918 {
12919 INT32 i, j, vdup, nummodes, width, height;
12920 const char *desc;
12921
12922 (void)choice;
12923
12924 memset(modedescs, 0, sizeof(modedescs));
12925
12926 #if (defined (__unix__) && !defined (MSDOS)) || defined (UNIXCOMMON) || defined (HAVE_SDL)
12927 VID_PrepareModeList(); // FIXME: hack
12928 #endif
12929 vidm_nummodes = 0;
12930 vidm_selected = 0;
12931 nummodes = VID_NumModes();
12932
12933 #ifdef _WINDOWS
12934 // clean that later: skip windowed mode 0, video modes menu only shows FULL SCREEN modes
12935 if (nummodes <= NUMSPECIALMODES)
12936 i = 0; // unless we have nothing
12937 else
12938 i = NUMSPECIALMODES;
12939 #else
12940 // DOS does not skip mode 0, because mode 0 is ALWAYS present
12941 i = 0;
12942 #endif
12943 for (; i < nummodes && vidm_nummodes < MAXMODEDESCS; i++)
12944 {
12945 desc = VID_GetModeName(i);
12946 if (desc)
12947 {
12948 vdup = 0;
12949
12950 // when a resolution exists both under VGA and VESA, keep the
12951 // VESA mode, which is always a higher modenum
12952 for (j = 0; j < vidm_nummodes; j++)
12953 {
12954 if (!strcmp(modedescs[j].desc, desc))
12955 {
12956 // mode(0): 320x200 is always standard VGA, not vesa
12957 if (modedescs[j].modenum)
12958 {
12959 modedescs[j].modenum = i;
12960 vdup = 1;
12961
12962 if (i == vid.modenum)
12963 vidm_selected = j;
12964 }
12965 else
12966 vdup = 1;
12967
12968 break;
12969 }
12970 }
12971
12972 if (!vdup)
12973 {
12974 modedescs[vidm_nummodes].modenum = i;
12975 modedescs[vidm_nummodes].desc = desc;
12976
12977 if (i == vid.modenum)
12978 vidm_selected = vidm_nummodes;
12979
12980 // Pull out the width and height
12981 sscanf(desc, "%u%*c%u", &width, &height);
12982
12983 // Show multiples of 320x200 as green.
12984 if (SCR_IsAspectCorrect(width, height))
12985 modedescs[vidm_nummodes].goodratio = 1;
12986
12987 vidm_nummodes++;
12988 }
12989 }
12990 }
12991
12992 vidm_column_size = (vidm_nummodes+2) / 3;
12993
12994 M_SetupNextMenu(&OP_VideoModeDef);
12995 }
12996
12997 static void M_DrawMainVideoMenu(void)
12998 {
12999 M_DrawGenericScrollMenu();
13000 if (itemOn < 8) // where it starts to go offscreen; change this number if you change the layout of the video menu
13001 {
13002 INT32 y = currentMenu->y+currentMenu->menuitems[1].alphaKey*2;
13003 if (itemOn == 7)
13004 y -= 10;
13005 V_DrawRightAlignedString(BASEVIDWIDTH - currentMenu->x, y,
13006 (SCR_IsAspectCorrect(vid.width, vid.height) ? V_GREENMAP : V_YELLOWMAP),
13007 va("%dx%d", vid.width, vid.height));
13008 }
13009 }
13010
13011 // Draw the video modes list, a-la-Quake
13012 static void M_DrawVideoMode(void)
13013 {
13014 INT32 i, j, row, col;
13015
13016 // draw title
13017 M_DrawMenuTitle();
13018
13019 V_DrawCenteredString(BASEVIDWIDTH/2, OP_VideoModeDef.y,
13020 V_YELLOWMAP, "Choose mode, reselect to change default");
13021
13022 row = 41;
13023 col = OP_VideoModeDef.y + 14;
13024 for (i = 0; i < vidm_nummodes; i++)
13025 {
13026 if (i == vidm_selected)
13027 V_DrawString(row, col, V_YELLOWMAP, modedescs[i].desc);
13028 // Show multiples of 320x200 as green.
13029 else
13030 V_DrawString(row, col, (modedescs[i].goodratio) ? V_GREENMAP : 0, modedescs[i].desc);
13031
13032 col += 8;
13033 if ((i % vidm_column_size) == (vidm_column_size-1))
13034 {
13035 row += 7*13;
13036 col = OP_VideoModeDef.y + 14;
13037 }
13038 }
13039
13040 if (vidm_testingmode > 0)
13041 {
13042 INT32 testtime = (vidm_testingmode/TICRATE) + 1;
13043
13044 M_CentreText(OP_VideoModeDef.y + 116,
13045 va("Previewing mode %c%dx%d",
13046 (SCR_IsAspectCorrect(vid.width, vid.height)) ? 0x83 : 0x80,
13047 vid.width, vid.height));
13048 M_CentreText(OP_VideoModeDef.y + 138,
13049 "Press ENTER again to keep this mode");
13050 M_CentreText(OP_VideoModeDef.y + 150,
13051 va("Wait %d second%s", testtime, (testtime > 1) ? "s" : ""));
13052 M_CentreText(OP_VideoModeDef.y + 158,
13053 "or press ESC to return");
13054
13055 }
13056 else
13057 {
13058 M_CentreText(OP_VideoModeDef.y + 116,
13059 va("Current mode is %c%dx%d",
13060 (SCR_IsAspectCorrect(vid.width, vid.height)) ? 0x83 : 0x80,
13061 vid.width, vid.height));
13062 M_CentreText(OP_VideoModeDef.y + 124,
13063 va("Default mode is %c%dx%d",
13064 (SCR_IsAspectCorrect(cv_scr_width.value, cv_scr_height.value)) ? 0x83 : 0x80,
13065 cv_scr_width.value, cv_scr_height.value));
13066
13067 V_DrawCenteredString(BASEVIDWIDTH/2, OP_VideoModeDef.y + 138,
13068 V_GREENMAP, "Green modes are recommended.");
13069 V_DrawCenteredString(BASEVIDWIDTH/2, OP_VideoModeDef.y + 146,
13070 V_YELLOWMAP, "Other modes may have visual errors.");
13071 V_DrawCenteredString(BASEVIDWIDTH/2, OP_VideoModeDef.y + 158,
13072 V_YELLOWMAP, "Larger modes may have performance issues.");
13073 }
13074
13075 // Draw the cursor for the VidMode menu
13076 i = 41 - 10 + ((vidm_selected / vidm_column_size)*7*13);
13077 j = OP_VideoModeDef.y + 14 + ((vidm_selected % vidm_column_size)*8);
13078
13079 V_DrawScaledPatch(i - 8, j, 0,
13080 W_CachePatchName("M_CURSOR", PU_PATCH));
13081 }
13082
13083 // Just M_DrawGenericScrollMenu but showing a backing behind the headers.
13084 static void M_DrawColorMenu(void)
13085 {
13086 INT32 x, y, i, max, tempcentery, cursory = 0;
13087
13088 // DRAW MENU
13089 x = currentMenu->x;
13090 y = currentMenu->y;
13091
13092 V_DrawFill(19 , y-4, 47, 1, 35);
13093 V_DrawFill(19+( 47), y-4, 47, 1, 73);
13094 V_DrawFill(19+(2*47), y-4, 47, 1, 112);
13095 V_DrawFill(19+(3*47), y-4, 47, 1, 255);
13096 V_DrawFill(19+(4*47), y-4, 47, 1, 152);
13097 V_DrawFill(19+(5*47), y-4, 46, 1, 181);
13098
13099 V_DrawFill(300, y-4, 1, 1, 26);
13100 V_DrawFill( 19, y-3, 282, 1, 26);
13101
13102 if ((currentMenu->menuitems[itemOn].alphaKey*2 - currentMenu->menuitems[0].alphaKey*2) <= scrollareaheight)
13103 tempcentery = currentMenu->y - currentMenu->menuitems[0].alphaKey*2;
13104 else if ((currentMenu->menuitems[currentMenu->numitems-1].alphaKey*2 - currentMenu->menuitems[itemOn].alphaKey*2) <= scrollareaheight)
13105 tempcentery = currentMenu->y - currentMenu->menuitems[currentMenu->numitems-1].alphaKey*2 + 2*scrollareaheight;
13106 else
13107 tempcentery = currentMenu->y - currentMenu->menuitems[itemOn].alphaKey*2 + scrollareaheight;
13108
13109 for (i = 0; i < currentMenu->numitems; i++)
13110 {
13111 if (currentMenu->menuitems[i].status != IT_DISABLED && currentMenu->menuitems[i].alphaKey*2 + tempcentery >= currentMenu->y)
13112 break;
13113 }
13114
13115 for (max = currentMenu->numitems; max > 0; max--)
13116 {
13117 if (currentMenu->menuitems[max].status != IT_DISABLED && currentMenu->menuitems[max-1].alphaKey*2 + tempcentery <= (currentMenu->y + 2*scrollareaheight))
13118 break;
13119 }
13120
13121 if (i)
13122 V_DrawString(currentMenu->x - 20, currentMenu->y - (skullAnimCounter/5), V_YELLOWMAP, "\x1A"); // up arrow
13123 if (max != currentMenu->numitems)
13124 V_DrawString(currentMenu->x - 20, currentMenu->y + 2*scrollareaheight + (skullAnimCounter/5), V_YELLOWMAP, "\x1B"); // down arrow
13125
13126 // draw title (or big pic)
13127 M_DrawMenuTitle();
13128
13129 for (; i < max; i++)
13130 {
13131 y = currentMenu->menuitems[i].alphaKey*2 + tempcentery;
13132 if (i == itemOn)
13133 cursory = y;
13134 switch (currentMenu->menuitems[i].status & IT_DISPLAY)
13135 {
13136 case IT_PATCH:
13137 case IT_DYBIGSPACE:
13138 case IT_BIGSLIDER:
13139 case IT_STRING2:
13140 case IT_DYLITLSPACE:
13141 case IT_GRAYPATCH:
13142 case IT_TRANSTEXT2:
13143 // unsupported
13144 break;
13145 case IT_NOTHING:
13146 break;
13147 case IT_STRING:
13148 case IT_WHITESTRING:
13149 if (i != itemOn && (currentMenu->menuitems[i].status & IT_DISPLAY)==IT_STRING)
13150 V_DrawString(x, y, 0, currentMenu->menuitems[i].text);
13151 else
13152 V_DrawString(x, y, V_YELLOWMAP, currentMenu->menuitems[i].text);
13153
13154 // Cvar specific handling
13155 switch (currentMenu->menuitems[i].status & IT_TYPE)
13156 case IT_CVAR:
13157 {
13158 consvar_t *cv = (consvar_t *)currentMenu->menuitems[i].itemaction;
13159 switch (currentMenu->menuitems[i].status & IT_CVARTYPE)
13160 {
13161 case IT_CV_SLIDER:
13162 M_DrawSlider(x, y, cv, (i == itemOn));
13163 case IT_CV_NOPRINT: // color use this
13164 case IT_CV_INVISSLIDER: // monitor toggles use this
13165 break;
13166 case IT_CV_STRING:
13167 if (y + 12 > (currentMenu->y + 2*scrollareaheight))
13168 break;
13169 M_DrawTextBox(x, y + 4, MAXSTRINGLENGTH, 1);
13170 V_DrawString(x + 8, y + 12, V_ALLOWLOWERCASE, cv->string);
13171 if (skullAnimCounter < 4 && i == itemOn)
13172 V_DrawCharacter(x + 8 + V_StringWidth(cv->string, 0), y + 12,
13173 '_' | 0x80, false);
13174 y += 16;
13175 break;
13176 default:
13177 V_DrawRightAlignedString(BASEVIDWIDTH - x, y,
13178 ((cv->flags & CV_CHEAT) && !CV_IsSetToDefault(cv) ? V_REDMAP : V_YELLOWMAP), cv->string);
13179 if (i == itemOn)
13180 {
13181 V_DrawCharacter(BASEVIDWIDTH - x - 10 - V_StringWidth(cv->string, 0) - (skullAnimCounter/5), y,
13182 '\x1C' | V_YELLOWMAP, false);
13183 V_DrawCharacter(BASEVIDWIDTH - x + 2 + (skullAnimCounter/5), y,
13184 '\x1D' | V_YELLOWMAP, false);
13185 }
13186 break;
13187 }
13188 break;
13189 }
13190 break;
13191 case IT_TRANSTEXT:
13192 V_DrawString(x, y, V_TRANSLUCENT, currentMenu->menuitems[i].text);
13193 break;
13194 case IT_QUESTIONMARKS:
13195 V_DrawString(x, y, V_TRANSLUCENT|V_OLDSPACING, M_CreateSecretMenuOption(currentMenu->menuitems[i].text));
13196 break;
13197 case IT_HEADERTEXT:
13198 //V_DrawString(x-16, y, V_YELLOWMAP, currentMenu->menuitems[i].text);
13199 V_DrawFill(19, y, 281, 9, currentMenu->menuitems[i+1].alphaKey);
13200 V_DrawFill(300, y, 1, 9, 26);
13201 M_DrawLevelPlatterHeader(y - (lsheadingheight - 12), currentMenu->menuitems[i].text, false, false);
13202 break;
13203 }
13204 }
13205
13206 // DRAW THE SKULL CURSOR
13207 V_DrawScaledPatch(currentMenu->x - 24, cursory, 0,
13208 W_CachePatchName("M_CURSOR", PU_PATCH));
13209 }
13210
13211 // special menuitem key handler for video mode list
13212 static void M_HandleVideoMode(INT32 ch)
13213 {
13214 if (vidm_testingmode > 0) switch (ch)
13215 {
13216 // change back to the previous mode quickly
13217 case KEY_ESCAPE:
13218 setmodeneeded = vidm_previousmode + 1;
13219 vidm_testingmode = 0;
13220 break;
13221
13222 case KEY_ENTER:
13223 S_StartSound(NULL, sfx_menu1);
13224 vidm_testingmode = 0; // stop testing
13225 }
13226
13227 else switch (ch)
13228 {
13229 case KEY_DOWNARROW:
13230 S_StartSound(NULL, sfx_menu1);
13231 if (++vidm_selected >= vidm_nummodes)
13232 vidm_selected = 0;
13233 break;
13234
13235 case KEY_UPARROW:
13236 S_StartSound(NULL, sfx_menu1);
13237 if (--vidm_selected < 0)
13238 vidm_selected = vidm_nummodes - 1;
13239 break;
13240
13241 case KEY_LEFTARROW:
13242 S_StartSound(NULL, sfx_menu1);
13243 vidm_selected -= vidm_column_size;
13244 if (vidm_selected < 0)
13245 vidm_selected = (vidm_column_size*3) + vidm_selected;
13246 if (vidm_selected >= vidm_nummodes)
13247 vidm_selected = vidm_nummodes - 1;
13248 break;
13249
13250 case KEY_RIGHTARROW:
13251 S_StartSound(NULL, sfx_menu1);
13252 vidm_selected += vidm_column_size;
13253 if (vidm_selected >= (vidm_column_size*3))
13254 vidm_selected %= vidm_column_size;
13255 if (vidm_selected >= vidm_nummodes)
13256 vidm_selected = vidm_nummodes - 1;
13257 break;
13258
13259 case KEY_ENTER:
13260 S_StartSound(NULL, sfx_menu1);
13261 if (vid.modenum == modedescs[vidm_selected].modenum)
13262 SCR_SetDefaultMode();
13263 else
13264 {
13265 vidm_testingmode = 15*TICRATE;
13266 vidm_previousmode = vid.modenum;
13267 if (!setmodeneeded) // in case the previous setmode was not finished
13268 setmodeneeded = modedescs[vidm_selected].modenum + 1;
13269 }
13270 break;
13271
13272 case KEY_ESCAPE: // this one same as M_Responder
13273 if (currentMenu->prevMenu)
13274 M_SetupNextMenu(currentMenu->prevMenu);
13275 else
13276 M_ClearMenus(true);
13277 break;
13278
13279 default:
13280 break;
13281 }
13282 }
13283
13284 static void M_DrawScreenshotMenu(void)
13285 {
13286 M_DrawGenericScrollMenu();
13287 #ifdef HWRENDER
13288 if ((rendermode == render_opengl) && (itemOn < 7)) // where it starts to go offscreen; change this number if you change the layout of the screenshot menu
13289 {
13290 INT32 y = currentMenu->y+currentMenu->menuitems[op_screenshot_colorprofile].alphaKey*2;
13291 if (itemOn == 6)
13292 y -= 10;
13293 V_DrawRightAlignedString(BASEVIDWIDTH - currentMenu->x, y, V_REDMAP, "Yes");
13294 }
13295 #endif
13296 }
13297
13298 // ===============
13299 // Monitor Toggles
13300 // ===============
13301 static void M_DrawMonitorToggles(void)
13302 {
13303 INT32 i, y;
13304 INT32 sum = 0;
13305 consvar_t *cv;
13306 boolean cheating = false;
13307
13308 M_DrawGenericMenu();
13309
13310 // Assumes all are cvar type.
13311 for (i = 0; i < currentMenu->numitems; ++i)
13312 {
13313 if (!(currentMenu->menuitems[i].status & IT_CVAR) || !(cv = (consvar_t *)currentMenu->menuitems[i].itemaction))
13314 continue;
13315 sum += cv->value;
13316
13317 if (!CV_IsSetToDefault(cv))
13318 cheating = true;
13319 }
13320
13321 for (i = 0; i < currentMenu->numitems; ++i)
13322 {
13323 if (!(currentMenu->menuitems[i].status & IT_CVAR) || !(cv = (consvar_t *)currentMenu->menuitems[i].itemaction))
13324 continue;
13325 y = currentMenu->y + currentMenu->menuitems[i].alphaKey;
13326
13327 M_DrawSlider(currentMenu->x + 20, y, cv, (i == itemOn));
13328
13329 if (!cv->value)
13330 V_DrawRightAlignedString(312, y, V_OLDSPACING|((i == itemOn) ? V_YELLOWMAP : 0), "None");
13331 else
13332 V_DrawRightAlignedString(312, y, V_OLDSPACING|((i == itemOn) ? V_YELLOWMAP : 0), va("%3d%%", (cv->value*100)/sum));
13333 }
13334
13335 if (cheating)
13336 V_DrawCenteredString(BASEVIDWIDTH/2, currentMenu->y, V_REDMAP, "* MODIFIED, CHEATS ENABLED *");
13337 }
13338
13339 // =========
13340 // Quit Game
13341 // =========
13342 static INT32 quitsounds[] =
13343 {
13344 // holy shit we're changing things up!
13345 sfx_itemup, // Tails 11-09-99
13346 sfx_jump, // Tails 11-09-99
13347 sfx_skid, // Inu 04-03-13
13348 sfx_spring, // Tails 11-09-99
13349 sfx_pop,
13350 sfx_spdpad, // Inu 04-03-13
13351 sfx_wdjump, // Inu 04-03-13
13352 sfx_mswarp, // Inu 04-03-13
13353 sfx_splash, // Tails 11-09-99
13354 sfx_floush, // Tails 11-09-99
13355 sfx_gloop, // Tails 11-09-99
13356 sfx_s3k66, // Inu 04-03-13
13357 sfx_s3k6a, // Inu 04-03-13
13358 sfx_s3k73, // Inu 04-03-13
13359 sfx_chchng // Tails 11-09-99
13360 };
13361
13362 void M_QuitResponse(INT32 ch)
13363 {
13364 tic_t ptime;
13365 INT32 mrand;
13366
13367 if (ch != 'y' && ch != KEY_ENTER)
13368 return;
13369 LUAh_GameQuit(true);
13370 if (!(netgame || cv_debug))
13371 {
13372 S_ResetCaptions();
13373 marathonmode = 0;
13374
13375 mrand = M_RandomKey(sizeof(quitsounds)/sizeof(INT32));
13376 if (quitsounds[mrand]) S_StartSound(NULL, quitsounds[mrand]);
13377
13378 //added : 12-02-98: do that instead of I_WaitVbl which does not work
13379 ptime = I_GetTime() + NEWTICRATE*2; // Shortened the quit time, used to be 2 seconds Tails 03-26-2001
13380 while (ptime > I_GetTime())
13381 {
13382 V_DrawScaledPatch(0, 0, 0, W_CachePatchName("GAMEQUIT", PU_PATCH)); // Demo 3 Quit Screen Tails 06-16-2001
13383 I_FinishUpdate(); // Update the screen with the image Tails 06-19-2001
13384 I_Sleep();
13385 }
13386 }
13387 I_Quit();
13388 }
13389
13390 static void M_QuitSRB2(INT32 choice)
13391 {
13392 // We pick index 0 which is language sensitive, or one at random,
13393 // between 1 and maximum number.
13394 (void)choice;
13395 M_StartMessage(quitmsg[M_RandomKey(NUM_QUITMESSAGES)], M_QuitResponse, MM_YESNO);
13396 }
13397