From b26cd786ec4deca1b50b51b543261b22ec738663 Mon Sep 17 00:00:00 2001 From: James R Date: Mon, 9 Jan 2023 20:13:59 -0800 Subject: [PATCH] Move all specialized code out of k_menufunc.c Adds new files: - menus/extras-statistics.c - menus/play-online-room-select.c - menus/transient/cup-select.c - menus/transient/explosions.c - menus/transient/gametype.c - menus/transient/message-box.c - menus/transient/virtual-keyboard.c --- src/k_menu.h | 20 + src/k_menufunc.c | 6801 +------------------- src/menus/CMakeLists.txt | 2 + src/menus/extras-1.c | 102 + src/menus/extras-addons.c | 332 + src/menus/extras-challenges.c | 562 ++ src/menus/extras-replay-hut.c | 230 + src/menus/extras-statistics.c | 99 + src/menus/main-1.c | 78 + src/menus/options-1.c | 181 + src/menus/options-data-addons.c | 7 + src/menus/options-data-erase-1.c | 42 + src/menus/options-data-erase-profile.c | 83 + src/menus/options-data-screenshots.c | 40 + src/menus/options-gameplay-item-toggles.c | 120 + src/menus/options-profiles-1.c | 193 + src/menus/options-profiles-edit-1.c | 143 + src/menus/options-profiles-edit-controls.c | 338 + src/menus/options-video-modes.c | 168 + src/menus/play-char-select.c | 1460 +++++ src/menus/play-local-1.c | 46 + src/menus/play-local-race-1.c | 20 + src/menus/play-local-race-difficulty.c | 63 + src/menus/play-local-race-time-attack.c | 94 + src/menus/play-online-1.c | 52 + src/menus/play-online-host.c | 71 + src/menus/play-online-join-ip.c | 67 + src/menus/play-online-room-select.c | 66 + src/menus/play-online-server-browser.c | 485 +- src/menus/transient/CMakeLists.txt | 5 + src/menus/transient/cup-select.c | 195 + src/menus/transient/explosions.c | 63 + src/menus/transient/gametype.c | 34 + src/menus/transient/level-select.c | 547 +- src/menus/transient/manual.c | 50 + src/menus/transient/message-box.c | 200 + src/menus/transient/pause-game.c | 285 + src/menus/transient/pause-replay.c | 156 + src/menus/transient/virtual-keyboard.c | 226 + 39 files changed, 6889 insertions(+), 6837 deletions(-) create mode 100644 src/menus/extras-statistics.c create mode 100644 src/menus/play-online-room-select.c create mode 100644 src/menus/transient/cup-select.c create mode 100644 src/menus/transient/explosions.c create mode 100644 src/menus/transient/gametype.c create mode 100644 src/menus/transient/message-box.c create mode 100644 src/menus/transient/virtual-keyboard.c diff --git a/src/k_menu.h b/src/k_menu.h index f7a9c9725..52319ee18 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -556,6 +556,7 @@ extern boolean menuwipe; extern consvar_t cv_showfocuslost; extern consvar_t cv_chooseskin, cv_serversort, cv_menujam_update; +extern consvar_t cv_autorecord; void M_SetMenuDelay(UINT8 i); @@ -569,6 +570,17 @@ void M_MapMenuControls(event_t *ev); boolean M_Responder(event_t *ev); boolean M_MenuButtonPressed(UINT8 pid, UINT32 bt); boolean M_MenuButtonHeld(UINT8 pid, UINT32 bt); + +boolean M_ChangeStringCvar(INT32 choice); + +boolean M_NextOpt(void); +boolean M_PrevOpt(void); + +boolean M_MenuConfirmPressed(UINT8 pid); +boolean M_MenuBackPressed(UINT8 pid); +boolean M_MenuExtraPressed(UINT8 pid); +boolean M_MenuExtraHeld(UINT8 pid); + void M_StartControlPanel(void); void M_ClearMenus(boolean callexitmenufunc); void M_SelectableClearMenus(INT32 choice); @@ -577,6 +589,8 @@ void M_GoBack(INT32 choice); void M_Ticker(void); void M_Init(void); +void M_MenuTypingInput(INT32 key); + extern menu_t MessageDef; void M_StartMessage(const char *string, void *routine, menumessagetype_t itemtype); void M_StopMessage(INT32 choice); @@ -679,6 +693,7 @@ extern consvar_t *setup_playercvars[MAXSPLITSCREENPLAYERS][SPLITCV_MAX]; void M_CharacterSelectInit(void); void M_CharacterSelect(INT32 choice); +void M_SetupReadyExplosions(boolean charsel, UINT16 basex, UINT16 basey, UINT16 color); boolean M_CharacterSelectHandler(INT32 choice); void M_CharacterSelectTick(void); boolean M_CharacterSelectQuit(void); @@ -723,6 +738,8 @@ boolean M_CanShowLevelInList(INT16 mapnum, levelsearch_t *levelsearch); UINT16 M_CountLevelsToShowInList(levelsearch_t *levelsearch); UINT16 M_GetFirstLevelInList(UINT8 *i, levelsearch_t *levelsearch); UINT16 M_GetNextLevelInList(UINT16 mapnum, UINT8 *i, levelsearch_t *levelsearch); +void M_LevelSelectScrollDest(void); +boolean M_LevelListFromGametype(INT16 gt); void M_LevelSelectInit(INT32 choice); void M_CupSelectHandler(INT32 choice); @@ -730,6 +747,8 @@ void M_CupSelectTick(void); void M_LevelSelectHandler(INT32 choice); void M_LevelSelectTick(void); +void M_LevelSelected(INT16 add); + // dummy consvars for GP & match race setup extern consvar_t cv_dummygpdifficulty; extern consvar_t cv_dummykartspeed; @@ -916,6 +935,7 @@ void M_CheckProfileData(INT32 choice); // check if we have profiles. // profile selection menu void M_ProfileSelectInit(INT32 choice); +void M_FirstPickProfile(INT32 c); void M_HandleProfileSelect(INT32 ch); // profile edition diff --git a/src/k_menufunc.c b/src/k_menufunc.c index 321dfcb31..ba2d02ce5 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -9,65 +9,12 @@ #include "doomdef.h" #include "d_main.h" -#include "d_netcmd.h" #include "console.h" -#include "r_local.h" #include "hu_stuff.h" -#include "g_game.h" -#include "g_input.h" -#include "m_argv.h" - -// Data. -#include "sounds.h" #include "s_sound.h" -#include "i_system.h" - -// Addfile -#include "filesrch.h" - #include "v_video.h" -#include "i_video.h" -#include "keys.h" -#include "z_zone.h" -#include "w_wad.h" -#include "p_local.h" -#include "p_setup.h" #include "f_finale.h" - -#ifdef HWRENDER -#include "hardware/hw_main.h" -#endif - -#include "d_net.h" -#include "mserv.h" #include "m_misc.h" -#include "m_anigif.h" -#include "byteptr.h" -#include "st_stuff.h" -#include "i_sound.h" -#include "i_time.h" -#include "k_kart.h" -#include "k_follower.h" -#include "d_player.h" // KITEM_ constants -#include "doomstat.h" // MAXSPLITSCREENPLAYERS -#include "k_grandprix.h" // MAXSPLITSCREENPLAYERS - -#include "i_joy.h" // for joystick menu controls - -// Condition Sets -#include "m_cond.h" - -// And just some randomness for the exits. -#include "m_random.h" - -#include "r_skins.h" - -#if defined(HAVE_SDL) -#include "SDL.h" -#if SDL_VERSION_ATLEAST(2,0,0) -#include "sdl/sdlmain.h" // JOYSTICK_HOTPLUG -#endif -#endif #ifdef PC_DOS #include // for snprintf @@ -98,30 +45,6 @@ struct menutransition_s menutransition; // Menu transition properties INT32 menuKey = -1; // keyboard key pressed for menu menucmd_t menucmd[MAXSPLITSCREENPLAYERS]; -// message prompt struct -struct menumessage_s menumessage; - -// Typing "sub"-menu -struct menutyping_s menutyping; - -// keyboard layouts -INT16 virtualKeyboard[5][13] = { - - {'1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', 0}, - {'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', 0}, - {'a', 's', 'd', 'f', 'g', 'h', 'i', 'j', 'k', 'l', ';', '\'', '\\'}, - {'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/', 0, 0, 0}, - {KEY_SPACE, KEY_RSHIFT, KEY_BACKSPACE, KEY_CAPSLOCK, KEY_ENTER, 0, 0, 0, 0, 0, 0, 0, 0} -}; - -INT16 shift_virtualKeyboard[5][13] = { - - {'!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+', 0}, - {'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '{', '}', 0}, - {'A', 'S', 'D', 'F', 'G', 'H', 'I', 'J', 'K', 'L', ':', '\"', '|'}, - {'Z', 'X', 'C', 'V', 'B', 'N', 'M', '<', '>', '?', 0, 0, 0}, - {KEY_SPACE, KEY_RSHIFT, KEY_BACKSPACE, KEY_CAPSLOCK, KEY_ENTER, 0, 0, 0, 0, 0, 0, 0, 0} -}; // finish wipes between screens boolean menuwipe = false; @@ -135,114 +58,37 @@ static boolean noFurtherInput = false; // ========================================================================== // Consvar onchange functions -static void Dummymenuplayer_OnChange(void); static void Dummystaff_OnChange(void); consvar_t cv_showfocuslost = CVAR_INIT ("showfocuslost", "Yes", CV_SAVE, CV_YesNo, NULL); -static CV_PossibleValue_t skins_cons_t[MAXSKINS+1] = {{1, DEFAULTSKIN}}; -consvar_t cv_chooseskin = CVAR_INIT ("chooseskin", DEFAULTSKIN, CV_HIDDEN, skins_cons_t, NULL); - consvar_t cv_menujam_update = CVAR_INIT ("menujam_update", "Off", CV_SAVE, CV_OnOff, NULL); static CV_PossibleValue_t menujam_cons_t[] = {{0, "menu"}, {1, "menu2"}, {2, "menu3"}, {0, NULL}}; static consvar_t cv_menujam = CVAR_INIT ("menujam", "0", CV_SAVE, menujam_cons_t, NULL); -static CV_PossibleValue_t serversort_cons_t[] = { - {0,"Ping"}, - {1,"AVG. Power Level"}, - {2,"Most Players"}, - {3,"Least Players"}, - {4,"Max Player Slots"}, - {5,"Gametype"}, - {0,NULL} -}; -consvar_t cv_serversort = CVAR_INIT ("serversort", "Ping", CV_CALL, serversort_cons_t, M_SortServerList); - // first time memory consvar_t cv_tutorialprompt = CVAR_INIT ("tutorialprompt", "On", CV_SAVE, CV_OnOff, NULL); -// autorecord demos for time attack -static consvar_t cv_autorecord = CVAR_INIT ("autorecord", "Yes", 0, CV_YesNo, NULL); - -CV_PossibleValue_t ghost_cons_t[] = {{0, "Hide"}, {1, "Show Character"}, {2, "Show All"}, {0, NULL}}; -CV_PossibleValue_t ghost2_cons_t[] = {{0, "Hide"}, {1, "Show"}, {0, NULL}}; - -consvar_t cv_ghost_besttime = CVAR_INIT ("ghost_besttime", "Show All", CV_SAVE, ghost_cons_t, NULL); -consvar_t cv_ghost_bestlap = CVAR_INIT ("ghost_bestlap", "Show All", CV_SAVE, ghost_cons_t, NULL); -consvar_t cv_ghost_last = CVAR_INIT ("ghost_last", "Show All", CV_SAVE, ghost_cons_t, NULL); -consvar_t cv_ghost_guest = CVAR_INIT ("ghost_guest", "Show", CV_SAVE, ghost2_cons_t, NULL); -consvar_t cv_ghost_staff = CVAR_INIT ("ghost_staff", "Show", CV_SAVE, ghost2_cons_t, NULL); - //Console variables used solely in the menu system. //todo: add a way to use non-console variables in the menu // or make these consvars legitimate like color or skin. -static void Splitplayers_OnChange(void); -CV_PossibleValue_t splitplayers_cons_t[] = {{1, "One"}, {2, "Two"}, {3, "Three"}, {4, "Four"}, {0, NULL}}; -consvar_t cv_splitplayers = CVAR_INIT ("splitplayers", "One", CV_CALL, splitplayers_cons_t, Splitplayers_OnChange); -static CV_PossibleValue_t dummymenuplayer_cons_t[] = {{0, "NOPE"}, {1, "P1"}, {2, "P2"}, {3, "P3"}, {4, "P4"}, {0, NULL}}; static CV_PossibleValue_t dummyteam_cons_t[] = {{0, "Spectator"}, {1, "Red"}, {2, "Blue"}, {0, NULL}}; static CV_PossibleValue_t dummyspectate_cons_t[] = {{0, "Spectator"}, {1, "Playing"}, {0, NULL}}; static CV_PossibleValue_t dummyscramble_cons_t[] = {{0, "Random"}, {1, "Points"}, {0, NULL}}; static CV_PossibleValue_t dummystaff_cons_t[] = {{0, "MIN"}, {100, "MAX"}, {0, NULL}}; -//static consvar_t cv_dummymenuplayer = CVAR_INIT ("dummymenuplayer", "P1", CV_HIDDEN|CV_CALL, dummymenuplayer_cons_t, Dummymenuplayer_OnChange); static consvar_t cv_dummyteam = CVAR_INIT ("dummyteam", "Spectator", CV_HIDDEN, dummyteam_cons_t, NULL); //static cv_dummyspectate = CVAR_INITconsvar_t ("dummyspectate", "Spectator", CV_HIDDEN, dummyspectate_cons_t, NULL); static consvar_t cv_dummyscramble = CVAR_INIT ("dummyscramble", "Random", CV_HIDDEN, dummyscramble_cons_t, NULL); static consvar_t cv_dummystaff = CVAR_INIT ("dummystaff", "0", CV_HIDDEN|CV_CALL, dummystaff_cons_t, Dummystaff_OnChange); -consvar_t cv_dummyip = CVAR_INIT ("dummyip", "", CV_HIDDEN, NULL, NULL); -consvar_t cv_dummymenuplayer = CVAR_INIT ("dummymenuplayer", "P1", CV_HIDDEN|CV_CALL, dummymenuplayer_cons_t, Dummymenuplayer_OnChange); consvar_t cv_dummyspectate = CVAR_INIT ("dummyspectate", "Spectator", CV_HIDDEN, dummyspectate_cons_t, NULL); -INT16 menugametype = GT_RACE; - -consvar_t cv_dummyprofilename = CVAR_INIT ("dummyprofilename", "", CV_HIDDEN, NULL, NULL); -consvar_t cv_dummyprofileplayername = CVAR_INIT ("dummyprofileplayername", "", CV_HIDDEN, NULL, NULL); -consvar_t cv_dummyprofilekickstart = CVAR_INIT ("dummyprofilekickstart", "Off", CV_HIDDEN, CV_OnOff, NULL); - -consvar_t cv_dummygpdifficulty = CVAR_INIT ("dummygpdifficulty", "Normal", CV_HIDDEN, gpdifficulty_cons_t, NULL); -consvar_t cv_dummykartspeed = CVAR_INIT ("dummykartspeed", "Normal", CV_HIDDEN, dummykartspeed_cons_t, NULL); -consvar_t cv_dummygpencore = CVAR_INIT ("dummygpencore", "Off", CV_HIDDEN, CV_OnOff, NULL); - -static void M_UpdateAddonsSearch(void); -consvar_t cv_dummyaddonsearch = CVAR_INIT ("dummyaddonsearch", "", CV_HIDDEN|CV_CALL|CV_NOINIT, NULL, M_UpdateAddonsSearch); - -static CV_PossibleValue_t dummymatchbots_cons_t[] = { - {0, "Off"}, - {1, "Lv.1"}, - {2, "Lv.2"}, - {3, "Lv.3"}, - {4, "Lv.4"}, - {5, "Lv.5"}, - {6, "Lv.6"}, - {7, "Lv.7"}, - {8, "Lv.8"}, - {9, "Lv.9"}, - {10, "Lv.10"}, - {11, "Lv.11"}, - {12, "Lv.12"}, - {13, "Lv.MAX"}, - {0, NULL} -}; -consvar_t cv_dummymatchbots = CVAR_INIT ("dummymatchbots", "Off", CV_HIDDEN, dummymatchbots_cons_t, NULL); - -// for server fetch threads... -M_waiting_mode_t m_waiting_mode = M_NOT_WAITING; - // ========================================================================== // CVAR ONCHANGE EVENTS GO HERE // ========================================================================== // (there's only a couple anyway) -static void Dummymenuplayer_OnChange(void) -{ - if (cv_dummymenuplayer.value < 1) - CV_StealthSetValue(&cv_dummymenuplayer, splitscreen+1); - else if (cv_dummymenuplayer.value > splitscreen+1) - CV_StealthSetValue(&cv_dummymenuplayer, 1); -} - static void Dummystaff_OnChange(void) { #ifdef STAFFGHOSTS @@ -280,357 +126,11 @@ static void Dummystaff_OnChange(void) #endif //#ifdef STAFFGHOSTS } -void Screenshot_option_Onchange(void) -{ - // Screenshot opt is at #3, 0 based array obv. - OPTIONS_DataScreenshot[2].status = - (cv_screenshot_option.value == 3 ? IT_CVAR|IT_STRING|IT_CV_STRING : IT_DISABLED); - -} - -void Moviemode_mode_Onchange(void) -{ -#if 0 - INT32 i, cstart, cend; - for (i = op_screenshot_gif_start; i <= op_screenshot_apng_end; ++i) - OP_ScreenshotOptionsMenu[i].status = IT_DISABLED; - - switch (cv_moviemode.value) - { - case MM_GIF: - cstart = op_screenshot_gif_start; - cend = op_screenshot_gif_end; - break; - case MM_APNG: - cstart = op_screenshot_apng_start; - cend = op_screenshot_apng_end; - break; - default: - return; - } - for (i = cstart; i <= cend; ++i) - OP_ScreenshotOptionsMenu[i].status = IT_STRING|IT_CVAR; -#endif -} - -void Moviemode_option_Onchange(void) -{ - // opt 7 in a 0 based array, you get the idea... - OPTIONS_DataScreenshot[6].status = - (cv_movie_option.value == 3 ? IT_CVAR|IT_STRING|IT_CV_STRING : IT_DISABLED); -} - -void Addons_option_Onchange(void) -{ - // Option 2 will always be the textbar. - // (keep in mind this is a 0 indexed array and the first element is a header...) - OPTIONS_DataAddon[2].status = - (cv_addons_option.value == 3 ? IT_CVAR|IT_STRING|IT_CV_STRING : IT_DISABLED); -} - -static void M_EraseDataResponse(INT32 ch) -{ - if (ch == MA_NO) - return; - - S_StartSound(NULL, sfx_itrole); // bweh heh heh - - // Delete the data - if (optionsmenu.erasecontext == 2) - { - // SRB2Kart: This actually needs to be done FIRST, so that you don't immediately regain playtime/matches secrets - gamedata->totalplaytime = 0; - gamedata->matchesplayed = 0; - } - if (optionsmenu.erasecontext != 1) - G_ClearRecords(); - if (optionsmenu.erasecontext != 0) - M_ClearSecrets(); - - F_StartIntro(); - M_ClearMenus(true); -} - -void M_EraseData(INT32 choice) -{ - const char *eschoice, *esstr = M_GetText("Are you sure you want to erase\n%s?\n\nPress (A) to confirm or (B) to cancel\n"); - - optionsmenu.erasecontext = (UINT8)choice; - - if (choice == 0) - eschoice = M_GetText("Time Attack data"); - else if (choice == 1) - eschoice = M_GetText("Secrets data"); - else - eschoice = M_GetText("ALL game data"); - - M_StartMessage(va(esstr, eschoice), FUNCPTRCAST(M_EraseDataResponse), MM_YESNO); -} - // ========================================================================= // BASIC MENU HANDLING // ========================================================================= -UINT16 nummenucolors = 0; - -void M_AddMenuColor(UINT16 color) { - menucolor_t *c; - - if (color >= numskincolors) { - CONS_Printf("M_AddMenuColor: color %d does not exist.",color); - return; - } - - // SRB2Kart: I do not understand vanilla doesn't need this but WE do???!?!??! - if (!skincolors[color].accessible) { - return; - } - - c = (menucolor_t *)malloc(sizeof(menucolor_t)); - c->color = color; - if (menucolorhead == NULL) { - c->next = c; - c->prev = c; - menucolorhead = c; - menucolortail = c; - } else { - c->next = menucolorhead; - c->prev = menucolortail; - menucolortail->next = c; - menucolorhead->prev = c; - menucolortail = c; - } - - nummenucolors++; -} - -void M_MoveColorBefore(UINT16 color, UINT16 targ) { - menucolor_t *look, *c = NULL, *t = NULL; - - if (color == targ) - return; - if (color >= numskincolors) { - CONS_Printf("M_MoveColorBefore: color %d does not exist.",color); - return; - } - if (targ >= numskincolors) { - CONS_Printf("M_MoveColorBefore: target color %d does not exist.",targ); - return; - } - - for (look=menucolorhead;;look=look->next) { - if (look->color == color) - c = look; - else if (look->color == targ) - t = look; - if (c != NULL && t != NULL) - break; - if (look==menucolortail) - return; - } - - if (c == t->prev) - return; - - if (t==menucolorhead) - menucolorhead = c; - if (c==menucolortail) - menucolortail = c->prev; - - c->prev->next = c->next; - c->next->prev = c->prev; - - c->prev = t->prev; - c->next = t; - t->prev->next = c; - t->prev = c; -} - -void M_MoveColorAfter(UINT16 color, UINT16 targ) { - menucolor_t *look, *c = NULL, *t = NULL; - - if (color == targ) - return; - if (color >= numskincolors) { - CONS_Printf("M_MoveColorAfter: color %d does not exist.\n",color); - return; - } - if (targ >= numskincolors) { - CONS_Printf("M_MoveColorAfter: target color %d does not exist.\n",targ); - return; - } - - for (look=menucolorhead;;look=look->next) { - if (look->color == color) - c = look; - else if (look->color == targ) - t = look; - if (c != NULL && t != NULL) - break; - if (look==menucolortail) - return; - } - - if (t == c->prev) - return; - - if (t==menucolortail) - menucolortail = c; - else if (c==menucolortail) - menucolortail = c->prev; - - c->prev->next = c->next; - c->next->prev = c->prev; - - c->next = t->next; - c->prev = t; - t->next->prev = c; - t->next = c; -} - -UINT16 M_GetColorBefore(UINT16 color, UINT16 amount, boolean follower) -{ - menucolor_t *look = NULL; - - for (; amount > 0; amount--) - { - if (follower == true) - { - if (color == FOLLOWERCOLOR_OPPOSITE) - { - look = menucolortail; - color = menucolortail->color; - continue; - } - - if (color == FOLLOWERCOLOR_MATCH) - { - look = NULL; - color = FOLLOWERCOLOR_OPPOSITE; - continue; - } - - if (color == menucolorhead->color) - { - look = NULL; - color = FOLLOWERCOLOR_MATCH; - continue; - } - } - - if (color == 0 || color >= numskincolors) - { - CONS_Printf("M_GetColorBefore: color %d does not exist.\n",color); - return 0; - } - - if (look == NULL) - { - for (look = menucolorhead;; look = look->next) - { - if (look->color == color) - { - break; - } - if (look == menucolortail) - { - return 0; - } - } - } - - look = look->prev; - color = look->color; - } - return color; -} - -UINT16 M_GetColorAfter(UINT16 color, UINT16 amount, boolean follower) -{ - menucolor_t *look = NULL; - - for (; amount > 0; amount--) - { - if (follower == true) - { - if (color == menucolortail->color) - { - look = NULL; - color = FOLLOWERCOLOR_OPPOSITE; - continue; - } - - if (color == FOLLOWERCOLOR_OPPOSITE) - { - look = NULL; - color = FOLLOWERCOLOR_MATCH; - continue; - } - - if (color == FOLLOWERCOLOR_MATCH) - { - look = menucolorhead; - color = menucolorhead->color; - continue; - } - } - - if (color == 0 || color >= numskincolors) - { - CONS_Printf("M_GetColorAfter: color %d does not exist.\n",color); - return 0; - } - - if (look == NULL) - { - for (look = menucolorhead;; look = look->next) - { - if (look->color == color) - { - break; - } - if (look == menucolortail) - { - return 0; - } - } - } - - look = look->next; - color = look->color; - } - return color; -} - -void M_InitPlayerSetupColors(void) { - UINT8 i; - numskincolors = SKINCOLOR_FIRSTFREESLOT; - menucolorhead = menucolortail = NULL; - for (i=0; inext; - free(tmp); - } else { - free(look); - return; - } - } - - menucolorhead = menucolortail = NULL; -} - static void M_ChangeCvar(INT32 choice) { consvar_t *cv = currentMenu->menuitems[itemOn].itemaction.cvar; @@ -669,55 +169,7 @@ static void M_ChangeCvar(INT32 choice) } } -static boolean M_ChangeStringCvar(INT32 choice) -{ - consvar_t *cv = currentMenu->menuitems[itemOn].itemaction.cvar; - char buf[MAXSTRINGLENGTH]; - size_t len; - - if (shiftdown && choice >= 32 && choice <= 127) - choice = shiftxform[choice]; - - switch (choice) - { - case KEY_BACKSPACE: - len = strlen(cv->string); - if (len > 0) - { - S_StartSound(NULL, sfx_s3k5b); // Tails - M_Memcpy(buf, cv->string, len); - buf[len-1] = 0; - CV_Set(cv, buf); - } - return true; - case KEY_DEL: - if (cv->string[0]) - { - S_StartSound(NULL, sfx_s3k5b); // Tails - CV_Set(cv, ""); - } - return true; - default: - if (choice >= 32 && choice <= 127) - { - len = strlen(cv->string); - if (len < MAXSTRINGLENGTH - 1) - { - S_StartSound(NULL, sfx_s3k5b); // Tails - M_Memcpy(buf, cv->string, len); - buf[len++] = (char)choice; - buf[len] = 0; - CV_Set(cv, buf); - } - return true; - } - break; - } - - return false; -} - -static boolean M_NextOpt(void) +boolean M_NextOpt(void) { INT16 oldItemOn = itemOn; // prevent infinite loop @@ -744,7 +196,7 @@ static boolean M_NextOpt(void) return true; } -static boolean M_PrevOpt(void) +boolean M_PrevOpt(void) { INT16 oldItemOn = itemOn; // prevent infinite loop @@ -1214,7 +666,7 @@ boolean M_MenuButtonHeld(UINT8 pid, UINT32 bt) } // Returns true if we press the confirmation button -static boolean M_MenuConfirmPressed(UINT8 pid) +boolean M_MenuConfirmPressed(UINT8 pid) { return M_MenuButtonPressed(pid, MBT_A); } @@ -1225,7 +677,7 @@ static boolean M_MenuConfirmPressed(UINT8 pid) }*/ // Returns true if we press the Cancel button -static boolean M_MenuBackPressed(UINT8 pid) +boolean M_MenuBackPressed(UINT8 pid) { return (M_MenuButtonPressed(pid, MBT_B) || M_MenuButtonPressed(pid, MBT_X)); } @@ -1236,167 +688,17 @@ static boolean M_MenuBackPressed(UINT8 pid) }*/ // Retrurns true if we press the tertiary option button (C) -static boolean M_MenuExtraPressed(UINT8 pid) +boolean M_MenuExtraPressed(UINT8 pid) { return M_MenuButtonPressed(pid, MBT_C); } -static boolean M_MenuExtraHeld(UINT8 pid) +boolean M_MenuExtraHeld(UINT8 pid) { return M_MenuButtonHeld(pid, MBT_C); } -// Updates the x coordinate of the keybord so prevent it from going in weird places -static void M_UpdateKeyboardX(void) -{ - // 0s are only at the rightmost edges of the keyboard table, so just go backwards until we get something. - while (!virtualKeyboard[menutyping.keyboardy][menutyping.keyboardx]) - menutyping.keyboardx--; -} - -static boolean M_IsTypingKey(INT32 key) -{ - return key == KEY_BACKSPACE || key == KEY_ENTER || - key == KEY_ESCAPE || key == KEY_DEL || isprint(key); -} - -static void M_MenuTypingInput(INT32 key) -{ - - const UINT8 pid = 0; - - // Fade-in - - if (menutyping.menutypingclose) // closing - { - menutyping.menutypingfade--; - if (!menutyping.menutypingfade) - menutyping.active = false; - - return; // prevent inputs while closing the menu. - } - else // opening - { - menutyping.menutypingfade++; - if (menutyping.menutypingfade > 9) // Don't fade all the way, but have it VERY strong to be readable - menutyping.menutypingfade = 9; - else if (menutyping.menutypingfade < 9) - return; // Don't allow typing until it's fully opened. - } - - // Determine when to check for keyboard inputs or controller inputs using menuKey, which is the key passed here as argument. - if (!menutyping.keyboardtyping) // controller inputs - { - // we pressed a keyboard input that's not any of our buttons - if (M_IsTypingKey(key) && menucmd[pid].dpad_lr == 0 && menucmd[pid].dpad_ud == 0 - && !(menucmd[pid].buttons & MBT_A) - && !(menucmd[pid].buttons & MBT_B) - && !(menucmd[pid].buttons & MBT_C) - && !(menucmd[pid].buttons & MBT_X) - && !(menucmd[pid].buttons & MBT_Y) - && !(menucmd[pid].buttons & MBT_Z)) - { - menutyping.keyboardtyping = true; - } - } - else // Keyboard inputs. - { - // On the flipside, if we're pressing any keyboard input, switch to controller inputs. - if (!M_IsTypingKey(key) && ( - M_MenuButtonPressed(pid, MBT_A) - || M_MenuButtonPressed(pid, MBT_B) - || M_MenuButtonPressed(pid, MBT_C) - || M_MenuButtonPressed(pid, MBT_X) - || M_MenuButtonPressed(pid, MBT_Y) - || M_MenuButtonPressed(pid, MBT_Z) - || menucmd[pid].dpad_lr != 0 - || menucmd[pid].dpad_ud != 0 - )) - { - menutyping.keyboardtyping = false; - return; - } - - // OTHERWISE, process keyboard inputs for typing! - if (key == KEY_ENTER || key == KEY_ESCAPE) - { - menutyping.menutypingclose = true; // close menu. - return; - } - - } - - if (menucmd[pid].delay == 0 && !menutyping.keyboardtyping) // We must check for this here because we bypass the normal delay check to allow for normal keyboard inputs - { - if (menucmd[pid].dpad_ud > 0) // down - { - menutyping.keyboardy++; - if (menutyping.keyboardy > 4) - menutyping.keyboardy = 0; - - M_UpdateKeyboardX(); - M_SetMenuDelay(pid); - S_StartSound(NULL, sfx_s3k5b); - } - else if (menucmd[pid].dpad_ud < 0) // up - { - menutyping.keyboardy--; - if (menutyping.keyboardy < 0) - menutyping.keyboardy = 4; - - M_UpdateKeyboardX(); - M_SetMenuDelay(pid); - S_StartSound(NULL, sfx_s3k5b); - } - else if (menucmd[pid].dpad_lr > 0) // right - { - menutyping.keyboardx++; - if (!virtualKeyboard[menutyping.keyboardy][menutyping.keyboardx]) - menutyping.keyboardx = 0; - - M_SetMenuDelay(pid); - S_StartSound(NULL, sfx_s3k5b); - } - else if (menucmd[pid].dpad_lr < 0) // left - { - menutyping.keyboardx--; - if (menutyping.keyboardx < 0) - { - menutyping.keyboardx = 12; - M_UpdateKeyboardX(); - } - M_SetMenuDelay(pid); - S_StartSound(NULL, sfx_s3k5b); - } - else if (M_MenuConfirmPressed(pid)) - { - // Add the character. First though, check what we're pressing.... - INT16 c = virtualKeyboard[menutyping.keyboardy][menutyping.keyboardx]; - if (menutyping.keyboardshift ^ menutyping.keyboardcapslock) - c = shift_virtualKeyboard[menutyping.keyboardy][menutyping.keyboardx]; - - if (c == KEY_RSHIFT) - menutyping.keyboardshift = !menutyping.keyboardshift; - else if (c == KEY_CAPSLOCK) - menutyping.keyboardcapslock = !menutyping.keyboardcapslock; - else if (c == KEY_ENTER) - { - menutyping.menutypingclose = true; // close menu. - return; - } - else - { - M_ChangeStringCvar((INT32)c); // Write! - menutyping.keyboardshift = false; // undo shift if it had been pressed - } - - M_SetMenuDelay(pid); - S_StartSound(NULL, sfx_s3k5b); - } - } -} - static void M_HandleMenuInput(void) { void (*routine)(INT32 choice); // for some casting problem @@ -1741,6094 +1043,3 @@ void M_Init(void) M_UpdateMenuBGImage(true); } - -// ================================================== -// MESSAGE BOX (aka: a hacked, cobbled together menu) -// ================================================== -// Because this is just a "fake" menu, these definitions are not with the others -static menuitem_t MessageMenu[] = -{ - // TO HACK - {0, NULL, NULL, NULL, {NULL}, 0, 0} -}; - -menu_t MessageDef = -{ - 1, // # of menu items - NULL, // previous menu (TO HACK) - 0, // lastOn, flags (TO HACK) - MessageMenu, // menuitem_t -> - 0, 0, // x, y (TO HACK) - 0, 0, // extra1, extra2 - 0, 0, // transition tics - NULL, // drawing routine -> - NULL, // ticker routine - NULL, // init routine - NULL, // quit routine - NULL // input routine -}; - -// -// M_StringHeight -// -// Find string height from hu_font chars -// -static inline size_t M_StringHeight(const char *string) -{ - size_t h = 8, i; - - for (i = 0; i < strlen(string); i++) - if (string[i] == '\n') - h += 8; - - return h; -} - -// default message handler -void M_StartMessage(const char *string, void *routine, menumessagetype_t itemtype) -{ - const UINT8 pid = 0; - size_t max = 0, start = 0, strlines = 0, i; - static char *message = NULL; - Z_Free(message); - message = Z_StrDup(string); - DEBFILE(message); - - // Rudementary word wrapping. - // Simple and effective. Does not handle nonuniform letter sizes, etc. but who cares. - for (i = 0; message[i]; i++) - { - if (message[i] == ' ') - { - start = i; - max += 4; - } - else if (message[i] == '\n') - { - strlines = i; - start = 0; - max = 0; - continue; - } - else if (message[i] & 0x80) - continue; - else - max += 8; - - // Start trying to wrap if presumed length exceeds the screen width. - if (max >= BASEVIDWIDTH && start > 0) - { - message[start] = '\n'; - max -= (start-strlines)*8; - strlines = start; - start = 0; - } - } - - strncpy(menumessage.message, string, MAXMENUMESSAGE); - menumessage.flags = itemtype; - *(void**)&menumessage.routine = routine; - menumessage.fadetimer = (gamestate == GS_WAITINGPLAYERS) ? 9 : 1; - menumessage.active = true; - - start = 0; - max = 0; - - if (!routine || menumessage.flags == MM_NOTHING) - { - menumessage.flags = MM_NOTHING; - menumessage.routine = M_StopMessage; - } - - // event routine - if (menumessage.flags == MM_EVENTHANDLER) - { - *(void**)&menumessage.eroutine = routine; - menumessage.routine = NULL; - } - - //added : 06-02-98: now draw a textbox around the message - // compute lenght max and the numbers of lines - for (strlines = 0; *(message+start); strlines++) - { - for (i = 0;i < strlen(message+start);i++) - { - if (*(message+start+i) == '\n') - { - if (i > max) - max = i; - start += i; - i = (size_t)-1; //added : 07-02-98 : damned! - start++; - break; - } - } - - if (i == strlen(message+start)) - { - start += i; - if (i > max) - max = i; - } - } - - menumessage.x = (INT16)((BASEVIDWIDTH - 8*max-16)/2); - menumessage.y = (INT16)((BASEVIDHEIGHT - M_StringHeight(message))/2); - - menumessage.m = (INT16)((strlines<<8)+max); - - M_SetMenuDelay(pid); // Set menu delay to avoid setting off any of the handlers. -} - -void M_StopMessage(INT32 choice) -{ - const char pid = 0; - (void) choice; - - menumessage.active = false; - M_SetMenuDelay(pid); -} - -// regular handler for MM_NOTHING and MM_YESNO -void M_HandleMenuMessage(void) -{ - const UINT8 pid = 0; - boolean btok = M_MenuConfirmPressed(pid); - boolean btnok = M_MenuBackPressed(pid); - - if (menumessage.fadetimer < 9) - menumessage.fadetimer++; - - switch (menumessage.flags) - { - // Send 1 to the routine if we're pressing A/B/X/Y - case MM_NOTHING: - { - // send 1 if any button is pressed, 0 otherwise. - if (btok || btnok) - menumessage.routine(0); - - break; - } - // Send 1 to the routine if we're pressing A/X, 2 if B/Y, 0 otherwise. - case MM_YESNO: - { - INT32 answer = MA_NONE; - if (btok) - answer = MA_YES; - else if (btnok) - answer = MA_NO; - - // send 1 if btok is pressed, 2 if nok is pressed, 0 otherwise. - if (answer) - { - menumessage.routine(answer); - M_StopMessage(0); - } - - break; - } - // MM_EVENTHANDLER: In M_Responder to allow full event compat. - default: - break; - } - - // if we detect any keypress, don't forget to set the menu delay regardless. - if (btok || btnok) - M_SetMenuDelay(pid); -} - -// ========= -// IMAGEDEFS -// ========= - -// Handles the ImageDefs. Just a specialized function that -// uses left and right movement. -void M_HandleImageDef(INT32 choice) -{ - const UINT8 pid = 0; - boolean exitmenu = false; - (void) choice; - - if (menucmd[pid].dpad_lr > 0) - { - if (itemOn >= (INT16)(currentMenu->numitems-1)) - return; - S_StartSound(NULL, sfx_s3k5b); - M_SetMenuDelay(pid); - itemOn++; - } - else if (menucmd[pid].dpad_lr < 0) - { - if (!itemOn) - return; - S_StartSound(NULL, sfx_s3k5b); - M_SetMenuDelay(pid); - itemOn--; - } - else if (M_MenuConfirmPressed(pid) || M_MenuButtonPressed(pid, MBT_X) || M_MenuButtonPressed(pid, MBT_Y)) - { - exitmenu = true; - M_SetMenuDelay(pid); - } - - - if (exitmenu) - { - if (currentMenu->prevMenu) - M_SetupNextMenu(currentMenu->prevMenu, false); - else - M_ClearMenus(true); - } -} - -// ========= -// MAIN MENU -// ========= - -// Quit Game -static INT32 quitsounds[] = -{ - // holy shit we're changing things up! - // srb2kart: you ain't seen nothing yet - sfx_kc2e, - sfx_kc2f, - sfx_cdfm01, - sfx_ddash, - sfx_s3ka2, - sfx_s3k49, - sfx_slip, - sfx_tossed, - sfx_s3k7b, - sfx_itrolf, - sfx_itrole, - sfx_cdpcm9, - sfx_s3k4e, - sfx_s259, - sfx_3db06, - sfx_s3k3a, - sfx_peel, - sfx_cdfm28, - sfx_s3k96, - sfx_s3kc0s, - sfx_cdfm39, - sfx_hogbom, - sfx_kc5a, - sfx_kc46, - sfx_s3k92, - sfx_s3k42, - sfx_kpogos, - sfx_screec -}; - -void M_QuitResponse(INT32 ch) -{ - tic_t ptime; - INT32 mrand; - - if (ch == MA_YES) - { - if (!(netgame || cht_debug)) - { - mrand = M_RandomKey(sizeof(quitsounds) / sizeof(INT32)); - if (quitsounds[mrand]) - S_StartSound(NULL, quitsounds[mrand]); - - //added : 12-02-98: do that instead of I_WaitVbl which does not work - ptime = I_GetTime() + NEWTICRATE*2; // Shortened the quit time, used to be 2 seconds Tails 03-26-2001 - while (ptime > I_GetTime()) - { - V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31); - V_DrawSmallScaledPatch(0, 0, 0, W_CachePatchName("GAMEQUIT", PU_CACHE)); // Demo 3 Quit Screen Tails 06-16-2001 - I_FinishUpdate(); // Update the screen with the image Tails 06-19-2001 - I_Sleep(cv_sleep.value); - I_UpdateTime(cv_timescale.value); - } - } - I_Quit(); - } -} - -void M_QuitSRB2(INT32 choice) -{ - // We pick index 0 which is language sensitive, or one at random, - // between 1 and maximum number. - (void)choice; - M_StartMessage("Are you sure you want to quit playing?\n\nPress (A) to confirm or (B) to cancel", FUNCPTRCAST(M_QuitResponse), MM_YESNO); -} - -// ========= -// PLAY MENU -// ========= - -// Character Select! -// @TODO: Splitscreen handling when profiles are added into the game. ...I probably won't be the one to handle this however. -Lat' - -struct setup_chargrid_s setup_chargrid[9][9]; -setup_player_t setup_player[MAXSPLITSCREENPLAYERS]; -struct setup_explosions_s setup_explosions[CSEXPLOSIONS]; - -UINT8 setup_followercategories[MAXFOLLOWERCATEGORIES][2]; -UINT8 setup_numfollowercategories; - -UINT8 setup_numplayers = 0; // This variable is very important, it was extended to determine how many players exist in ALL menus. -tic_t setup_animcounter = 0; - -UINT8 setup_page = 0; -UINT8 setup_maxpage = 0; // For charsel page to identify alts easier... - -// sets up the grid pos for the skin used by the profile. -static void M_SetupProfileGridPos(setup_player_t *p) -{ - profile_t *pr = PR_GetProfile(p->profilen); - INT32 i = R_SkinAvailable(pr->skinname); - INT32 alt = 0; // Hey it's my character's name! - - // While we're here, read follower values. - p->followern = K_FollowerAvailable(pr->follower); - - if (p->followern < 0 || p->followern >= numfollowers || followers[p->followern].category >= numfollowercategories || !K_FollowerUsable(p->followern)) - p->followercategory = p->followern = -1; - else - p->followercategory = followers[p->followern].category; - - p->followercolor = pr->followercolor; - - if (!R_SkinUsable(g_localplayers[0], i, false)) - { - i = GetSkinNumClosestToStats(skins[i].kartspeed, skins[i].kartweight, skins[i].flags, false); - } - - // Now position the grid for skin - p->gridx = skins[i].kartspeed-1; - p->gridy = skins[i].kartweight-1; - - // Now this put our cursor on the good alt - while (alt < setup_chargrid[p->gridx][p->gridy].numskins && setup_chargrid[p->gridx][p->gridy].skinlist[alt] != i) - alt++; - - p->clonenum = alt; - p->color = PR_GetProfileColor(pr); -} - -static void M_SetupMidGameGridPos(setup_player_t *p, UINT8 num) -{ - INT32 i = R_SkinAvailable(cv_skin[num].zstring); - INT32 alt = 0; // Hey it's my character's name! - - // While we're here, read follower values. - p->followern = cv_follower[num].value; - p->followercolor = cv_followercolor[num].value; - - if (p->followern < 0 || p->followern >= numfollowers || followers[p->followern].category >= numfollowercategories || !K_FollowerUsable(p->followern)) - p->followercategory = p->followern = -1; - else - p->followercategory = followers[p->followern].category; - - // Now position the grid for skin - p->gridx = skins[i].kartspeed-1; - p->gridy = skins[i].kartweight-1; - - // Now this put our cursor on the good alt - while (alt < setup_chargrid[p->gridx][p->gridy].numskins && setup_chargrid[p->gridx][p->gridy].skinlist[alt] != i) - alt++; - - p->clonenum = alt; - p->color = cv_playercolor[num].value; - return; // we're done here -} - - -void M_CharacterSelectInit(void) -{ - UINT8 i, j; - setup_maxpage = 0; - - // While we're editing profiles, don't unset the devices for p1 - if (gamestate == GS_MENU) - { - for (i = 0; i < MAXSPLITSCREENPLAYERS; i++) - { - // Un-set devices for other players. - if (i != 0 || optionsmenu.profile) - { - CV_SetValue(&cv_usejoystick[i], -1); - CONS_Printf("M_CharacterSelectInit: Device for %d set to %d\n", i, -1); - } - } - } - - memset(setup_chargrid, -1, sizeof(setup_chargrid)); - for (i = 0; i < 9; i++) - { - for (j = 0; j < 9; j++) - setup_chargrid[i][j].numskins = 0; - } - - memset(setup_player, 0, sizeof(setup_player)); - setup_numplayers = 0; - - memset(setup_explosions, 0, sizeof(setup_explosions)); - setup_animcounter = 0; - - for (i = 0; i < MAXSPLITSCREENPLAYERS; i++) - { - // Default to no follower / match colour. - setup_player[i].followern = -1; - setup_player[i].followercategory = -1; - setup_player[i].followercolor = FOLLOWERCOLOR_MATCH; - - // Set default selected profile to the last used profile for each player: - // (Make sure we don't overshoot it somehow if we deleted profiles or whatnot) - setup_player[i].profilen = min(cv_lastprofile[i].value, PR_GetNumProfiles()); - } - - for (i = 0; i < numskins; i++) - { - UINT8 x = skins[i].kartspeed-1; - UINT8 y = skins[i].kartweight-1; - - if (!R_SkinUsable(g_localplayers[0], i, false)) - continue; - - if (setup_chargrid[x][y].numskins >= MAXCLONES) - CONS_Alert(CONS_ERROR, "Max character alts reached for %d,%d\n", x+1, y+1); - else - { - setup_chargrid[x][y].skinlist[setup_chargrid[x][y].numskins] = i; - setup_chargrid[x][y].numskins++; - - setup_maxpage = max(setup_maxpage, setup_chargrid[x][y].numskins-1); - } - - for (j = 0; j < MAXSPLITSCREENPLAYERS; j++) - { - if (!strcmp(cv_skin[j].string, skins[i].name)) - { - setup_player[j].gridx = x; - setup_player[j].gridy = y; - setup_player[j].color = skins[i].prefcolor; - - // If we're on prpfile select, skip straight to CSSTEP_CHARS - // do the same if we're midgame, but make sure to consider splitscreen properly. - if ((optionsmenu.profile && j == 0) || (gamestate != GS_MENU && j <= splitscreen)) - { - if (optionsmenu.profile) // In menu, setting up profile character/follower - { - setup_player[j].profilen = optionsmenu.profilen; - PR_ApplyProfileLight(setup_player[j].profilen, 0); - M_SetupProfileGridPos(&setup_player[j]); - } - else // gamestate != GS_MENU, in that case, assume this is whatever profile we chose to play with. - { - setup_player[j].profilen = cv_lastprofile[j].value; - M_SetupMidGameGridPos(&setup_player[j], j); - } - - // Don't reapply the profile here, it was already applied. - setup_player[j].mdepth = CSSTEP_CHARS; - } - } - } - } - - setup_numfollowercategories = 0; - for (i = 0; i < numfollowercategories; i++) - { - if (followercategories[i].numincategory == 0) - continue; - - setup_followercategories[setup_numfollowercategories][0] = 0; - - for (j = 0; j < numfollowers; j++) - { - if (followers[j].category != i) - continue; - - if (!K_FollowerUsable(j)) - continue; - - setup_followercategories[setup_numfollowercategories][0]++; - setup_followercategories[setup_numfollowercategories][1] = i; - } - - if (!setup_followercategories[setup_numfollowercategories][0]) - continue; - - setup_numfollowercategories++; - } - - setup_page = 0; -} - -void M_CharacterSelect(INT32 choice) -{ - (void)choice; - PLAY_CharSelectDef.prevMenu = currentMenu; - M_SetupNextMenu(&PLAY_CharSelectDef, false); -} - -static void M_SetupReadyExplosions(boolean charsel, UINT16 basex, UINT16 basey, UINT16 color) -{ - UINT8 i, j; - UINT8 e = 0; - UINT16 maxx = (charsel ? 9 : gamedata->challengegridwidth); - UINT16 maxy = (charsel ? 9 : CHALLENGEGRIDHEIGHT); - - while (setup_explosions[e].tics) - { - e++; - if (e == CSEXPLOSIONS) - return; - } - - for (i = 0; i < 3; i++) - { - UINT8 t = 5 + (i*2); - UINT8 offset = (i+1); - - for (j = 0; j < 4; j++) - { - INT16 x = basex, y = basey; - - switch (j) - { - case 0: x += offset; break; - case 1: x -= offset; break; - case 2: y += offset; break; - case 3: y -= offset; break; - } - - if ((y < 0 || y >= maxy)) - continue; - - if (charsel || !challengegridloops) - { - if (x < 0 || x >= maxx) - continue; - } - - setup_explosions[e].tics = t; - setup_explosions[e].color = color; - setup_explosions[e].x = x; - setup_explosions[e].y = y; - - while (setup_explosions[e].tics) - { - e++; - if (e == CSEXPLOSIONS) - return; - } - } - } -} - - -// Gets the selected follower's state for a given setup player. -static void M_GetFollowerState(setup_player_t *p) -{ - - p->follower_state = &states[followers[p->followern].followstate]; - - if (p->follower_state->frame & FF_ANIMATE) - p->follower_tics = p->follower_state->var2; // support for FF_ANIMATE - else - p->follower_tics = p->follower_state->tics; - - p->follower_frame = p->follower_state->frame & FF_FRAMEMASK; -} - -static boolean M_DeviceAvailable(INT32 deviceID, UINT8 numPlayers) -{ - INT32 i; - - if (numPlayers == 0) - { - // All of them are available! - return true; - } - - for (i = 0; i < numPlayers; i++) - { - if (cv_usejoystick[i].value == deviceID) - { - // This one's already being used. - return false; - } - } - - // This device is good to go. - return true; -} - -static boolean M_HandlePressStart(setup_player_t *p, UINT8 num) -{ - INT32 i, j; - - if (optionsmenu.profile) - return false; // Don't allow for the possibility of SOMEHOW another player joining in. - - // Detect B press first ... this means P1 can actually exit out of the menu. - if (M_MenuBackPressed(num)) - { - M_SetMenuDelay(num); - - if (num == 0) - { - // We're done here. - memset(setup_player, 0, sizeof(setup_player)); // Reset this to avoid funky things with profile display. - M_GoBack(0); - return true; - } - - // Don't allow this press to ever count as "start". - return false; - } - - if (num != setup_numplayers) - { - // Only detect devices for the last player. - return false; - } - - // Now detect new devices trying to join. - for (i = 0; i < MAXDEVICES; i++) - { - if (deviceResponding[i] != true) - { - // No buttons are being pushed. - continue; - } - - if (M_DeviceAvailable(i, setup_numplayers) == true) - { - // Available!! Let's use this one!! - - // if P1 is setting up using keyboard (device 0), save their last used device. - // this is to allow them to retain controller usage when they play alone. - // Because let's face it, when you test mods, you're often lazy to grab your controller for menuing :) - if (!i && !num) - { - setup_player[num].ponedevice = cv_usejoystick[num].value; - } - else if (num) - { - // For any player past player 1, set controls to default profile controls, otherwise it's generally awful to do any menuing... - memcpy(&gamecontrol[num], gamecontroldefault, sizeof(gamecontroldefault)); - } - - - CV_SetValue(&cv_usejoystick[num], i); - CONS_Printf("M_HandlePressStart: Device for %d set to %d\n", num, i); - - for (j = num+1; j < MAXSPLITSCREENPLAYERS; j++) - { - // Un-set devices for other players. - CV_SetValue(&cv_usejoystick[j], -1); - CONS_Printf("M_HandlePressStart: Device for %d set to %d\n", j, -1); - } - - //setup_numplayers++; - p->mdepth = CSSTEP_PROFILE; - S_StartSound(NULL, sfx_s3k65); - - // Prevent quick presses for multiple players - for (j = 0; j < MAXSPLITSCREENPLAYERS; j++) - { - setup_player[j].delay = MENUDELAYTIME; - M_SetMenuDelay(j); - menucmd[j].buttonsHeld |= MBT_X; - } - - memset(deviceResponding, false, sizeof(deviceResponding)); - return true; - } - } - - return false; -} - -static boolean M_HandleCSelectProfile(setup_player_t *p, UINT8 num) -{ - const UINT8 maxp = PR_GetNumProfiles() -1; - UINT8 realnum = num; // Used for profile when using splitdevice. - UINT8 i; - - if (cv_splitdevice.value) - num = 0; - - if (menucmd[num].dpad_ud > 0) - { - p->profilen++; - if (p->profilen > maxp) - p->profilen = 0; - - S_StartSound(NULL, sfx_s3k5b); - M_SetMenuDelay(num); - } - else if (menucmd[num].dpad_ud < 0) - { - if (p->profilen == 0) - p->profilen = maxp; - else - p->profilen--; - - S_StartSound(NULL, sfx_s3k5b); - M_SetMenuDelay(num); - } - else if (M_MenuBackPressed(num)) - { - if (num == setup_numplayers-1) - { - - p->mdepth = CSSTEP_NONE; - S_StartSound(NULL, sfx_s3k5b); - - // Prevent quick presses for multiple players - for (i = 0; i < MAXSPLITSCREENPLAYERS; i++) - { - setup_player[i].delay = MENUDELAYTIME; - M_SetMenuDelay(i); - menucmd[i].buttonsHeld |= MBT_X; - } - - if (num > 0) - { - CV_StealthSetValue(&cv_usejoystick[num], -1); - CONS_Printf("M_HandleCSelectProfile: Device for %d set to %d\n", num, -1); - } - - return true; - } - else - { - S_StartSound(NULL, sfx_s3kb2); - } - - M_SetMenuDelay(num); - } - else if (M_MenuConfirmPressed(num)) - { - SINT8 belongsTo = -1; - - if (p->profilen != PROFILE_GUEST) - { - for (i = 0; i < setup_numplayers; i++) - { - if (setup_player[i].mdepth > CSSTEP_PROFILE - && setup_player[i].profilen == p->profilen) - { - belongsTo = i; - break; - } - } - } - - if (belongsTo != -1 && belongsTo != num) - { - S_StartSound(NULL, sfx_s3k7b); - M_SetMenuDelay(num); - return false; - } - - // Apply the profile. - PR_ApplyProfile(p->profilen, realnum); // Otherwise P1 would inherit the last player's profile in splitdevice and that's not what we want... - M_SetupProfileGridPos(p); - - p->changeselect = 0; - - if (p->profilen == PROFILE_GUEST) - { - // Guest profile, always ask for options. - p->mdepth = CSSTEP_CHARS; - } - else - { - p->mdepth = CSSTEP_ASKCHANGES; - } - - S_StartSound(NULL, sfx_s3k63); - } - else if (M_MenuExtraPressed(num)) - { - UINT8 yourprofile = min(cv_lastprofile[realnum].value, PR_GetNumProfiles()); - if (p->profilen == yourprofile) - p->profilen = PROFILE_GUEST; - else - p->profilen = yourprofile; - S_StartSound(NULL, sfx_s3k7b); //sfx_s3kc3s - M_SetMenuDelay(num); - } - - return false; - -} - - -static void M_HandleCharAskChange(setup_player_t *p, UINT8 num) -{ - - if (cv_splitdevice.value) - num = 0; - - // there's only 2 options so lol - if (menucmd[num].dpad_ud != 0) - { - p->changeselect = (p->changeselect == 0) ? 1 : 0; - - S_StartSound(NULL, sfx_s3k5b); - M_SetMenuDelay(num); - } - else if (M_MenuBackPressed(num)) - { - p->changeselect = 0; - p->mdepth = CSSTEP_PROFILE; - - S_StartSound(NULL, sfx_s3k5b); - M_SetMenuDelay(num); - } - else if (M_MenuConfirmPressed(num)) - { - if (!p->changeselect) - { - // no changes - M_GetFollowerState(p); - p->mdepth = CSSTEP_READY; - p->delay = TICRATE; - - S_StartSound(NULL, sfx_s3k4e); - M_SetupReadyExplosions(true, p->gridx, p->gridy, p->color); - } - else - { - // changes - p->mdepth = CSSTEP_CHARS; - S_StartSound(NULL, sfx_s3k63); - } - - M_SetMenuDelay(num); - } -} - -static boolean M_HandleCharacterGrid(setup_player_t *p, UINT8 num) -{ - UINT8 numclones; - INT32 skin; - - if (cv_splitdevice.value) - num = 0; - - if (menucmd[num].dpad_ud > 0) - { - p->gridy++; - if (p->gridy > 8) - p->gridy = 0; - S_StartSound(NULL, sfx_s3k5b); - M_SetMenuDelay(num); - } - else if (menucmd[num].dpad_ud < 0) - { - p->gridy--; - if (p->gridy < 0) - p->gridy = 8; - S_StartSound(NULL, sfx_s3k5b); - M_SetMenuDelay(num); - } - - if (menucmd[num].dpad_lr > 0) - { - p->gridx++; - if (p->gridx > 8) - p->gridx = 0; - S_StartSound(NULL, sfx_s3k5b); - M_SetMenuDelay(num); - } - else if (menucmd[num].dpad_lr < 0) - { - p->gridx--; - if (p->gridx < 0) - p->gridx = 8; - S_StartSound(NULL, sfx_s3k5b); - M_SetMenuDelay(num); - } - else if (M_MenuExtraPressed(num)) - { - p->gridx /= 3; - p->gridx = (3*p->gridx) + 1; - p->gridy /= 3; - p->gridy = (3*p->gridy) + 1; - S_StartSound(NULL, sfx_s3k7b); //sfx_s3kc3s - M_SetMenuDelay(num); - } - - // try to set the clone num to the page # if possible. - p->clonenum = setup_page; - - // Process this after possible pad movement, - // this makes sure we don't have a weird ghost hover on a character with no clones. - numclones = setup_chargrid[p->gridx][p->gridy].numskins; - - if (p->clonenum >= numclones) - p->clonenum = 0; - - if (M_MenuConfirmPressed(num) /*|| M_MenuButtonPressed(num, MBT_START)*/) - { - skin = setup_chargrid[p->gridx][p->gridy].skinlist[setup_page]; - if (setup_page >= setup_chargrid[p->gridx][p->gridy].numskins || skin == -1) - { - S_StartSound(NULL, sfx_s3k7b); //sfx_s3kb2 - } - else - { - if (setup_page+1 == setup_chargrid[p->gridx][p->gridy].numskins) - p->mdepth = CSSTEP_COLORS; // Skip clones menu if there are none on this page. - else - p->mdepth = CSSTEP_ALTS; - - S_StartSound(NULL, sfx_s3k63); - } - - M_SetMenuDelay(num); - } - else if (M_MenuBackPressed(num)) - { - // for profiles / gameplay, exit out of the menu instantly, - // we don't want to go to the input detection menu. - if (optionsmenu.profile || gamestate != GS_MENU) - { - memset(setup_player, 0, sizeof(setup_player)); // Reset setup_player otherwise it does some VERY funky things. - M_SetMenuDelay(0); - M_GoBack(0); - return true; - } - else // in main menu - { - p->mdepth = CSSTEP_PROFILE; - S_StartSound(NULL, sfx_s3k5b); - } - M_SetMenuDelay(num); - } - - if (num == 0 && setup_numplayers == 1 && setup_maxpage) // ONLY one player. - { - if (M_MenuButtonPressed(num, MBT_L)) - { - if (setup_page == 0) - setup_page = setup_maxpage; - else - setup_page--; - - S_StartSound(NULL, sfx_s3k63); - M_SetMenuDelay(num); - } - else if (M_MenuButtonPressed(num, MBT_R)) - { - if (setup_page == setup_maxpage) - setup_page = 0; - else - setup_page++; - - S_StartSound(NULL, sfx_s3k63); - M_SetMenuDelay(num); - } - } - - return false; -} - -static void M_HandleCharRotate(setup_player_t *p, UINT8 num) -{ - UINT8 numclones = setup_chargrid[p->gridx][p->gridy].numskins; - - if (cv_splitdevice.value) - num = 0; - - if (menucmd[num].dpad_lr > 0) - { - p->clonenum++; - if (p->clonenum >= numclones) - p->clonenum = 0; - p->rotate = CSROTATETICS; - p->delay = CSROTATETICS; - S_StartSound(NULL, sfx_s3kc3s); - } - else if (menucmd[num].dpad_lr < 0) - { - p->clonenum--; - if (p->clonenum < 0) - p->clonenum = numclones-1; - p->rotate = -CSROTATETICS; - p->delay = CSROTATETICS; - S_StartSound(NULL, sfx_s3kc3s); - } - - if (M_MenuConfirmPressed(num) /*|| M_MenuButtonPressed(num, MBT_START)*/) - { - p->mdepth = CSSTEP_COLORS; - S_StartSound(NULL, sfx_s3k63); - M_SetMenuDelay(num); - } - else if (M_MenuBackPressed(num)) - { - p->mdepth = CSSTEP_CHARS; - S_StartSound(NULL, sfx_s3k5b); - M_SetMenuDelay(num); - } - else if (M_MenuExtraPressed(num)) - { - p->clonenum = 0; - p->rotate = CSROTATETICS; - p->hitlag = true; - S_StartSound(NULL, sfx_s3k7b); //sfx_s3kc3s - M_SetMenuDelay(num); - } -} - -static void M_HandleColorRotate(setup_player_t *p, UINT8 num) -{ - if (cv_splitdevice.value) - num = 0; - - if (menucmd[num].dpad_lr > 0) - { - p->color = M_GetColorAfter(p->color, 1, false); - p->rotate = CSROTATETICS; - M_SetMenuDelay(num); //CSROTATETICS - S_StartSound(NULL, sfx_s3k5b); //sfx_s3kc3s - } - else if (menucmd[num].dpad_lr < 0) - { - p->color = M_GetColorBefore(p->color, 1, false); - p->rotate = -CSROTATETICS; - M_SetMenuDelay(num); //CSROTATETICS - S_StartSound(NULL, sfx_s3k5b); //sfx_s3kc3s - } - - if (M_MenuConfirmPressed(num) /*|| M_MenuButtonPressed(num, MBT_START)*/) - { - p->mdepth = CSSTEP_FOLLOWERCATEGORY; - S_StartSound(NULL, sfx_s3k63); - M_SetMenuDelay(num); - } - else if (M_MenuBackPressed(num)) - { - if (setup_chargrid[p->gridx][p->gridy].numskins == 1) - { - p->mdepth = CSSTEP_CHARS; // Skip clones menu - } - else - { - p->mdepth = CSSTEP_ALTS; - } - S_StartSound(NULL, sfx_s3k5b); - M_SetMenuDelay(num); - } - else if (M_MenuExtraPressed(num)) - { - if (p->skin >= 0) - { - p->color = skins[p->skin].prefcolor; - p->rotate = CSROTATETICS; - p->hitlag = true; - S_StartSound(NULL, sfx_s3k7b); //sfx_s3kc3s - M_SetMenuDelay(num); - } - } -} - -static void M_AnimateFollower(setup_player_t *p) -{ - if (--p->follower_tics <= 0) - { - - // FF_ANIMATE; cycle through FRAMES and get back afterwards. This will be prominent amongst followers hence why it's being supported here. - if (p->follower_state->frame & FF_ANIMATE) - { - p->follower_frame++; - p->follower_tics = p->follower_state->var2; - if (p->follower_frame > (p->follower_state->frame & FF_FRAMEMASK) + p->follower_state->var1) // that's how it works, right? - p->follower_frame = p->follower_state->frame & FF_FRAMEMASK; - } - else - { - if (p->follower_state->nextstate != S_NULL) - p->follower_state = &states[p->follower_state->nextstate]; - p->follower_tics = p->follower_state->tics; - /*if (p->follower_tics == -1) - p->follower_tics = 15; // er, what?*/ - // get spritedef: - p->follower_frame = p->follower_state->frame & FF_FRAMEMASK; - } - } - - p->follower_timer++; -} - -static void M_HandleFollowerCategoryRotate(setup_player_t *p, UINT8 num) -{ - if (cv_splitdevice.value) - num = 0; - - if (menucmd[num].dpad_lr > 0) - { - p->followercategory++; - if (p->followercategory >= setup_numfollowercategories) - p->followercategory = -1; - - p->rotate = CSROTATETICS; - p->delay = CSROTATETICS; - S_StartSound(NULL, sfx_s3kc3s); - } - else if (menucmd[num].dpad_lr < 0) - { - p->followercategory--; - if (p->followercategory < -1) - p->followercategory = setup_numfollowercategories-1; - - p->rotate = -CSROTATETICS; - p->delay = CSROTATETICS; - S_StartSound(NULL, sfx_s3kc3s); - } - - if (M_MenuConfirmPressed(num) /*|| M_MenuButtonPressed(num, MBT_START)*/) - { - if (p->followercategory < 0) - { - p->followern = -1; - p->mdepth = CSSTEP_READY; - p->delay = TICRATE; - M_SetupReadyExplosions(true, p->gridx, p->gridy, p->color); - S_StartSound(NULL, sfx_s3k4e); - } - else - { - if (p->followern < 0 || followers[p->followern].category != p->followercategory) - { - p->followern = 0; - while (p->followern < numfollowers - && (followers[p->followern].category != setup_followercategories[p->followercategory][1] - || !K_FollowerUsable(p->followern))) - p->followern++; - } - - if (p->followern >= numfollowers) - { - p->followern = -1; - S_StartSound(NULL, sfx_s3kb2); - } - else - { - M_GetFollowerState(p); - p->mdepth = CSSTEP_FOLLOWER; - S_StartSound(NULL, sfx_s3k63); - } - } - - M_SetMenuDelay(num); - } - else if (M_MenuBackPressed(num)) - { - p->mdepth = CSSTEP_COLORS; - S_StartSound(NULL, sfx_s3k5b); - M_SetMenuDelay(num); - } - else if (M_MenuExtraPressed(num)) - { - if (p->followercategory >= 0 || p->followern < 0 || p->followern >= numfollowers || followers[p->followern].category >= numfollowercategories) - p->followercategory = -1; - else - p->followercategory = followers[p->followern].category; - p->rotate = CSROTATETICS; - p->hitlag = true; - S_StartSound(NULL, sfx_s3k7b); //sfx_s3kc3s - M_SetMenuDelay(num); - } -} - -static void M_HandleFollowerRotate(setup_player_t *p, UINT8 num) -{ - INT16 startfollowern = p->followern; - - if (cv_splitdevice.value) - num = 0; - - if (menucmd[num].dpad_lr > 0) - { - do - { - p->followern++; - if (p->followern >= numfollowers) - p->followern = 0; - if (p->followern == startfollowern) - break; - } - while (followers[p->followern].category != setup_followercategories[p->followercategory][1] || !K_FollowerUsable(p->followern)); - - M_GetFollowerState(p); - - p->rotate = CSROTATETICS; - p->delay = CSROTATETICS; - S_StartSound(NULL, sfx_s3kc3s); - } - else if (menucmd[num].dpad_lr < 0) - { - do - { - p->followern--; - if (p->followern < 0) - p->followern = numfollowers-1; - if (p->followern == startfollowern) - break; - } - while (followers[p->followern].category != setup_followercategories[p->followercategory][1] || !K_FollowerUsable(p->followern)); - - M_GetFollowerState(p); - - p->rotate = -CSROTATETICS; - p->delay = CSROTATETICS; - S_StartSound(NULL, sfx_s3kc3s); - } - - if (M_MenuConfirmPressed(num) /*|| M_MenuButtonPressed(num, MBT_START)*/) - { - if (p->followern > -1) - { - p->mdepth = CSSTEP_FOLLOWERCOLORS; - S_StartSound(NULL, sfx_s3k63); - } - else - { - p->mdepth = CSSTEP_READY; - p->delay = TICRATE; - M_SetupReadyExplosions(true, p->gridx, p->gridy, p->color); - S_StartSound(NULL, sfx_s3k4e); - } - - M_SetMenuDelay(num); - } - else if (M_MenuBackPressed(num)) - { - p->mdepth = CSSTEP_FOLLOWERCATEGORY; - S_StartSound(NULL, sfx_s3k5b); - M_SetMenuDelay(num); - } - else if (M_MenuExtraPressed(num)) - { - p->mdepth = CSSTEP_FOLLOWERCATEGORY; - p->followercategory = -1; - p->rotate = CSROTATETICS; - p->hitlag = true; - S_StartSound(NULL, sfx_s3k7b); //sfx_s3kc3s - M_SetMenuDelay(num); - } -} - -static void M_HandleFollowerColorRotate(setup_player_t *p, UINT8 num) -{ - if (cv_splitdevice.value) - num = 0; - - M_AnimateFollower(p); - - if (menucmd[num].dpad_lr > 0) - { - p->followercolor = M_GetColorAfter(p->followercolor, 1, true); - p->rotate = CSROTATETICS; - M_SetMenuDelay(num); //CSROTATETICS - S_StartSound(NULL, sfx_s3k5b); //sfx_s3kc3s - } - else if (menucmd[num].dpad_lr < 0) - { - p->followercolor = M_GetColorBefore(p->followercolor, 1, true); - p->rotate = -CSROTATETICS; - M_SetMenuDelay(num); //CSROTATETICS - S_StartSound(NULL, sfx_s3k5b); //sfx_s3kc3s - } - - if (M_MenuConfirmPressed(num) /*|| M_MenuButtonPressed(num, MBT_START)*/) - { - p->mdepth = CSSTEP_READY; - p->delay = TICRATE; - M_SetupReadyExplosions(true, p->gridx, p->gridy, p->color); - S_StartSound(NULL, sfx_s3k4e); - M_SetMenuDelay(num); - } - else if (M_MenuBackPressed(num)) - { - M_GetFollowerState(p); - p->mdepth = CSSTEP_FOLLOWER; - S_StartSound(NULL, sfx_s3k5b); - M_SetMenuDelay(num); - } - else if (M_MenuExtraPressed(num)) - { - if (p->followercolor == FOLLOWERCOLOR_MATCH) - p->followercolor = FOLLOWERCOLOR_OPPOSITE; - else if (p->followercolor == followers[p->followern].defaultcolor) - p->followercolor = FOLLOWERCOLOR_MATCH; - else - p->followercolor = followers[p->followern].defaultcolor; - p->rotate = CSROTATETICS; - p->hitlag = true; - S_StartSound(NULL, sfx_s3k7b); //sfx_s3kc3s - M_SetMenuDelay(num); - } -} - -boolean M_CharacterSelectHandler(INT32 choice) -{ - INT32 i; - - (void)choice; - - for (i = MAXSPLITSCREENPLAYERS-1; i >= 0; i--) - { - setup_player_t *p = &setup_player[i]; - boolean playersChanged = false; - - if (p->delay == 0 && menucmd[i].delay == 0) - { - if (!optionsmenu.profile) - { - // If splitdevice is true, only do the last non-ready setups. - if (cv_splitdevice.value) - { - // Previous setup isn't ready, go there. - // In any case, do setup 0 first. - if (i > 0 && setup_player[i-1].mdepth < CSSTEP_READY) - continue; - } - } - - switch (p->mdepth) - { - case CSSTEP_NONE: // Enter Game - if (gamestate == GS_MENU) // do NOT handle that outside of GS_MENU. - playersChanged = M_HandlePressStart(p, i); - break; - case CSSTEP_PROFILE: - playersChanged = M_HandleCSelectProfile(p, i); - break; - case CSSTEP_ASKCHANGES: - M_HandleCharAskChange(p, i); - break; - case CSSTEP_CHARS: // Character Select grid - M_HandleCharacterGrid(p, i); - break; - case CSSTEP_ALTS: // Select clone - M_HandleCharRotate(p, i); - break; - case CSSTEP_COLORS: // Select color - M_HandleColorRotate(p, i); - break; - case CSSTEP_FOLLOWERCATEGORY: - M_HandleFollowerCategoryRotate(p, i); - break; - case CSSTEP_FOLLOWER: - M_HandleFollowerRotate(p, i); - break; - case CSSTEP_FOLLOWERCOLORS: - M_HandleFollowerColorRotate(p, i); - break; - case CSSTEP_READY: - default: // Unready - if (M_MenuBackPressed(i)) - { - p->mdepth = CSSTEP_COLORS; - S_StartSound(NULL, sfx_s3k5b); - M_SetMenuDelay(i); - } - break; - } - } - - // Just makes it easier to access later - p->skin = setup_chargrid[p->gridx][p->gridy].skinlist[p->clonenum]; - - // Keep profile colour. - /*if (p->mdepth < CSSTEP_COLORS) - { - p->color = skins[p->skin].prefcolor; - - }*/ - - if (playersChanged == true) - { - setup_page = 0; // reset that. - break; - } - } - - // Setup new numplayers - setup_numplayers = 0; - for (i = 0; i < MAXSPLITSCREENPLAYERS; i++) - { - if (setup_player[i].mdepth == CSSTEP_NONE) - break; - - setup_numplayers = i+1; - } - - return true; -} - -// Apply character skin and colour changes while ingame (we just call the skin / color commands.) -// ...Will this cause command buffer issues? -Lat' -static void M_MPConfirmCharacterSelection(void) -{ - UINT8 i; - INT16 col; - - for (i = 0; i < splitscreen +1; i++) - { - // colour - // (convert the number that's saved to a string we can use) - col = setup_player[i].color; - CV_StealthSetValue(&cv_playercolor[i], col); - - // follower - if (setup_player[i].followern < 0) - CV_StealthSet(&cv_follower[i], "None"); - else - CV_StealthSet(&cv_follower[i], followers[setup_player[i].followern].name); - - // finally, call the skin[x] console command. - // This will call SendNameAndColor which will synch everything we sent here and apply the changes! - - CV_StealthSet(&cv_skin[i], skins[setup_player[i].skin].name); - - // ...actually, let's do this last - Skin_OnChange has some return-early occasions - // follower color - CV_SetValue(&cv_followercolor[i], setup_player[i].followercolor); - - } - M_ClearMenus(true); -} - -void M_CharacterSelectTick(void) -{ - UINT8 i; - boolean setupnext = true; - - setup_animcounter++; - - for (i = 0; i < MAXSPLITSCREENPLAYERS; i++) - { - if (setup_player[i].delay) - setup_player[i].delay--; - - if (setup_player[i].rotate > 0) - setup_player[i].rotate--; - else if (setup_player[i].rotate < 0) - setup_player[i].rotate++; - else - setup_player[i].hitlag = false; - - if (i >= setup_numplayers) - continue; - - if (setup_player[i].mdepth < CSSTEP_READY || setup_player[i].delay > 0) - { - // Someone's not ready yet. - setupnext = false; - } - } - - for (i = 0; i < CSEXPLOSIONS; i++) - { - if (setup_explosions[i].tics > 0) - setup_explosions[i].tics--; - } - - if (setupnext && setup_numplayers > 0) - { - // Selecting from the menu - if (gamestate == GS_MENU) - { - // in a profile; update the selected profile and then go back to the profile menu. - if (optionsmenu.profile) - { - // save player - strcpy(optionsmenu.profile->skinname, skins[setup_player[0].skin].name); - optionsmenu.profile->color = setup_player[0].color; - - // save follower - strcpy(optionsmenu.profile->follower, followers[setup_player[0].followern].name); - optionsmenu.profile->followercolor = setup_player[0].followercolor; - - // reset setup_player - memset(setup_player, 0, sizeof(setup_player)); - setup_numplayers = 0; - - M_GoBack(0); - return; - } - else // in a normal menu, stealthset the cvars and then go to the play menu. - { - for (i = 0; i < setup_numplayers; i++) - { - CV_StealthSet(&cv_skin[i], skins[setup_player[i].skin].name); - CV_StealthSetValue(&cv_playercolor[i], setup_player[i].color); - - if (setup_player[i].followern < 0) - CV_StealthSet(&cv_follower[i], "None"); - else - CV_StealthSet(&cv_follower[i], followers[setup_player[i].followern].name); - CV_StealthSetValue(&cv_followercolor[i], setup_player[i].followercolor); - } - - CV_StealthSetValue(&cv_splitplayers, setup_numplayers); - - // P1 is alone, set their old device just in case. - if (setup_numplayers < 2 && setup_player[0].ponedevice) - { - CV_StealthSetValue(&cv_usejoystick[0], setup_player[0].ponedevice); - } - - M_SetupNextMenu(&PLAY_MainDef, false); - } - } - else // In a game - { - // 23/05/2022: Since there's already restrictskinchange, just allow this to happen regardless. - M_MPConfirmCharacterSelection(); - } - } -} - -boolean M_CharacterSelectQuit(void) -{ - return true; -} - -void M_SetupGametypeMenu(INT32 choice) -{ - (void)choice; - - PLAY_GamemodesDef.prevMenu = currentMenu; - - // Battle and Capsules (and Special) disabled - PLAY_GamemodesMenu[1].status = IT_DISABLED; - PLAY_GamemodesMenu[2].status = IT_DISABLED; - PLAY_GamemodesMenu[3].status = IT_DISABLED; - - if (cv_splitplayers.value > 1) - { - // Re-add Battle - PLAY_GamemodesMenu[1].status = IT_STRING | IT_CALL; - } - else - { - boolean anyunlocked = false; - - if (M_SecretUnlocked(SECRET_BREAKTHECAPSULES, true)) - { - // Re-add Capsules - PLAY_GamemodesMenu[2].status = IT_STRING | IT_CALL; - anyunlocked = true; - } - - if (M_SecretUnlocked(SECRET_SPECIALATTACK, true)) - { - // Re-add Special - PLAY_GamemodesMenu[3].status = IT_STRING | IT_CALL; - anyunlocked = true; - } - - if (!anyunlocked) - { - // Only one non-Back entry, let's skip straight to Race. - M_SetupRaceMenu(-1); - return; - } - } - - M_SetupNextMenu(&PLAY_GamemodesDef, false); -} - -void M_SetupRaceMenu(INT32 choice) -{ - (void)choice; - - PLAY_RaceGamemodesDef.prevMenu = currentMenu; - - // Time Attack disabled - PLAY_RaceGamemodesMenu[2].status = IT_DISABLED; - - // Time Attack is 1P only - if (cv_splitplayers.value <= 1 - && M_SecretUnlocked(SECRET_TIMEATTACK, true)) - { - PLAY_RaceGamemodesMenu[2].status = IT_STRING | IT_CALL; - } - - M_SetupNextMenu(&PLAY_RaceGamemodesDef, false); -} - -// DIFFICULTY SELECT - -void M_SetupDifficultySelect(INT32 choice) -{ - // check what we picked. - choice = currentMenu->menuitems[itemOn].mvar1; - - // setup the difficulty menu and then remove choices depending on choice - PLAY_RaceDifficultyDef.prevMenu = currentMenu; - - PLAY_RaceDifficulty[drace_gpdifficulty].status = IT_DISABLED; - PLAY_RaceDifficulty[drace_mrkartspeed].status = IT_DISABLED; - PLAY_RaceDifficulty[drace_mrcpu].status = IT_DISABLED; - PLAY_RaceDifficulty[drace_mrracers].status = IT_DISABLED; - PLAY_RaceDifficulty[drace_encore].status = IT_DISABLED; - PLAY_RaceDifficulty[drace_cupselect].status = IT_DISABLED; - PLAY_RaceDifficulty[drace_mapselect].status = IT_DISABLED; - - if (choice) // Match Race - { - PLAY_RaceDifficulty[drace_mrkartspeed].status = IT_STRING|IT_CVAR; // Kart Speed - PLAY_RaceDifficulty[drace_mrcpu].status = IT_STRING2|IT_CVAR; // CPUs on/off - PLAY_RaceDifficulty[drace_mrracers].status = IT_STRING2|IT_CVAR; // CPU amount - PLAY_RaceDifficulty[drace_mapselect].status = IT_STRING|IT_CALL; // Level Select (Match Race) - PLAY_RaceDifficultyDef.lastOn = drace_mapselect; // Select map select by default. - } - else // GP - { - PLAY_RaceDifficulty[drace_gpdifficulty].status = IT_STRING|IT_CVAR; // Difficulty - PLAY_RaceDifficulty[drace_cupselect].status = IT_STRING|IT_CALL; // Level Select (GP) - PLAY_RaceDifficultyDef.lastOn = drace_cupselect; // Select cup select by default. - } - - if (M_SecretUnlocked(SECRET_ENCORE, false)) - { - PLAY_RaceDifficulty[drace_encore].status = IT_STRING2|IT_CVAR; // Encore on/off - } - - M_SetupNextMenu(&PLAY_RaceDifficultyDef, false); -} - -// LEVEL SELECT - -// -// M_CanShowLevelInList -// -// Determines whether to show a given map in the various level-select lists. -// -boolean M_CanShowLevelInList(INT16 mapnum, levelsearch_t *levelsearch) -{ - if (!levelsearch) - return false; - - if (mapnum >= nummapheaders) - return false; - - // Does the map exist? - if (!mapheaderinfo[mapnum]) - return false; - - // Does the map have a name? - if (!mapheaderinfo[mapnum]->lvlttl[0]) - return false; - - // Does the map have a LUMP? - if (mapheaderinfo[mapnum]->lumpnum == LUMPERROR) - return false; - - // Check for TOL - if (!(mapheaderinfo[mapnum]->typeoflevel & levelsearch->typeoflevel)) - return false; - - // Should the map be hidden? - if (mapheaderinfo[mapnum]->menuflags & LF2_HIDEINMENU) - return false; - - // I don't know why, but some may have exceptions. - if (levelsearch->timeattack && (mapheaderinfo[mapnum]->menuflags & LF2_NOTIMEATTACK)) - return false; - - // Don't permit cup when no cup requested (also no dupes in time attack) - if (levelsearch->cupmode - && (levelsearch->timeattack || !levelsearch->cup) - && mapheaderinfo[mapnum]->cup != levelsearch->cup) - return false; - - // Finally, the most complex check: does the map have lock conditions? - if (levelsearch->checklocked) - { - // Check for completion - if ((mapheaderinfo[mapnum]->menuflags & LF2_FINISHNEEDED) - && !(mapheaderinfo[mapnum]->mapvisited & MV_BEATEN)) - return false; - - // Check for unlock - if (M_MapLocked(mapnum+1)) - return false; - } - - // Survived our checks. - return true; -} - -UINT16 M_CountLevelsToShowInList(levelsearch_t *levelsearch) -{ - INT16 i, count = 0; - - if (!levelsearch) - return false; - - if (levelsearch->cup) - { - if (levelsearch->checklocked && M_CupLocked(levelsearch->cup)) - return 0; - - for (i = 0; i < CUPCACHE_MAX; i++) - { - if (!M_CanShowLevelInList(levelsearch->cup->cachedlevels[i], levelsearch)) - continue; - count++; - } - - return count; - } - - for (i = 0; i < nummapheaders; i++) - if (M_CanShowLevelInList(i, levelsearch)) - count++; - - return count; -} - -UINT16 M_GetFirstLevelInList(UINT8 *i, levelsearch_t *levelsearch) -{ - INT16 mapnum = NEXTMAP_INVALID; - - if (!levelsearch) - return false; - - if (levelsearch->cup) - { - if (levelsearch->checklocked && M_CupLocked(levelsearch->cup)) - { - *i = CUPCACHE_MAX; - return NEXTMAP_INVALID; - } - - *i = 0; - mapnum = NEXTMAP_INVALID; - for (; *i < CUPCACHE_MAX; (*i)++) - { - if (!M_CanShowLevelInList(levelsearch->cup->cachedlevels[*i], levelsearch)) - continue; - mapnum = levelsearch->cup->cachedlevels[*i]; - break; - } - } - else - { - for (mapnum = 0; mapnum < nummapheaders; mapnum++) - if (M_CanShowLevelInList(mapnum, levelsearch)) - break; - } - - return mapnum; -} - -UINT16 M_GetNextLevelInList(UINT16 mapnum, UINT8 *i, levelsearch_t *levelsearch) -{ - if (!levelsearch) - return false; - - if (levelsearch->cup) - { - mapnum = NEXTMAP_INVALID; - (*i)++; - for (; *i < CUPCACHE_MAX; (*i)++) - { - if (!M_CanShowLevelInList(levelsearch->cup->cachedlevels[*i], levelsearch)) - continue; - mapnum = levelsearch->cup->cachedlevels[*i]; - break; - } - } - else - { - mapnum++; - while (!M_CanShowLevelInList(mapnum, levelsearch) && mapnum < nummapheaders) - mapnum++; - } - - return mapnum; -} - -struct cupgrid_s cupgrid; -struct levellist_s levellist; - -static void M_LevelSelectScrollDest(void) -{ - UINT16 m = M_CountLevelsToShowInList(&levellist.levelsearch)-1; - - levellist.dest = (6*levellist.cursor); - - if (levellist.dest < 3) - levellist.dest = 3; - - if (levellist.dest > (6*m)-3) - levellist.dest = (6*m)-3; -} - -// Builds the level list we'll be using from the gametype we're choosing and send us to the apropriate menu. -static boolean M_LevelListFromGametype(INT16 gt) -{ - static boolean first = true; - UINT8 temp = 0; - - if (first || gt != levellist.newgametype || levellist.guessgt != MAXGAMETYPES) - { - if (first) - { - cupgrid.cappages = 0; - cupgrid.builtgrid = NULL; - } - - levellist.newgametype = gt; - - levellist.levelsearch.typeoflevel = G_TOLFlag(gt); - if (levellist.levelsearch.timeattack == true && gt == GT_SPECIAL) - { - // Sneak in an extra. - levellist.levelsearch.typeoflevel |= G_TOLFlag(GT_VERSUS); - levellist.guessgt = gt; - } - else - { - levellist.guessgt = MAXGAMETYPES; - } - - levellist.levelsearch.cupmode = (!(gametypes[gt]->rules & GTR_NOCUPSELECT)); - levellist.levelsearch.cup = NULL; - - first = false; - } - - // Obviously go to Cup Select in gametypes that have cups. - // Use a really long level select in gametypes that don't use cups. - - if (levellist.levelsearch.cupmode) - { - levelsearch_t templevelsearch = levellist.levelsearch; // full copy - size_t currentid = 0, highestunlockedid = 0; - const size_t pagelen = sizeof(cupheader_t*) * (CUPMENU_COLUMNS * CUPMENU_ROWS); - boolean foundany = false; - - templevelsearch.cup = kartcupheaders; - -#if 0 - // Make sure there's valid cups before going to this menu. -- rip sweet prince - if (templevelsearch.cup == NULL) - I_Error("Can you really call this a racing game, I didn't recieve any Cups on my pillow or anything"); -#endif - - if (cupgrid.cappages == 0) - { - cupgrid.cappages = 2; - cupgrid.builtgrid = Z_Calloc( - cupgrid.cappages * pagelen, - PU_STATIC, - NULL); - - if (!cupgrid.builtgrid) - { - I_Error("M_LevelListFromGametype: Not enough memory to allocate builtgrid"); - } - } - memset(cupgrid.builtgrid, 0, cupgrid.cappages * pagelen); - - while (templevelsearch.cup) - { - templevelsearch.checklocked = false; - if (!M_CountLevelsToShowInList(&templevelsearch)) - { - // No valid maps, skip. - templevelsearch.cup = templevelsearch.cup->next; - continue; - } - - foundany = true; - - if ((currentid * sizeof(cupheader_t*)) >= cupgrid.cappages * pagelen) - { - // Double the size of the buffer, and clear the other stuff. - const size_t firstlen = cupgrid.cappages * pagelen; - cupgrid.builtgrid = Z_Realloc(cupgrid.builtgrid, - firstlen * 2, - PU_STATIC, NULL); - - if (!cupgrid.builtgrid) - { - I_Error("M_LevelListFromGametype: Not enough memory to reallocate builtgrid"); - } - - memset(cupgrid.builtgrid + firstlen, 0, firstlen); - cupgrid.cappages *= 2; - } - - cupgrid.builtgrid[currentid] = templevelsearch.cup; - - templevelsearch.checklocked = true; - if (M_GetFirstLevelInList(&temp, &templevelsearch) != NEXTMAP_INVALID) - { - highestunlockedid = currentid; - if (Playing() && mapheaderinfo[gamemap-1] && mapheaderinfo[gamemap-1]->cup == templevelsearch.cup) - { - cupgrid.x = currentid % CUPMENU_COLUMNS; - cupgrid.y = (currentid / CUPMENU_COLUMNS) % CUPMENU_ROWS; - cupgrid.pageno = currentid / (CUPMENU_COLUMNS * CUPMENU_ROWS); - } - } - - currentid++; - templevelsearch.cup = templevelsearch.cup->next; - } - - if (foundany == false) - { - return false; - } - - cupgrid.numpages = (highestunlockedid / (CUPMENU_COLUMNS * CUPMENU_ROWS)) + 1; - if (cupgrid.pageno >= cupgrid.numpages) - { - cupgrid.pageno = 0; - } - - PLAY_CupSelectDef.prevMenu = currentMenu; - PLAY_LevelSelectDef.prevMenu = &PLAY_CupSelectDef; - M_SetupNextMenu(&PLAY_CupSelectDef, false); - - return true; - } - - // Okay, just a list of maps then. - - if (M_GetFirstLevelInList(&temp, &levellist.levelsearch) == NEXTMAP_INVALID) - { - return false; - } - - // Reset position properly if you go back & forth between gametypes - if (levellist.levelsearch.cup) - { - levellist.cursor = 0; - levellist.levelsearch.cup = NULL; - } - - M_LevelSelectScrollDest(); - levellist.y = levellist.dest; - - PLAY_LevelSelectDef.prevMenu = currentMenu; - M_SetupNextMenu(&PLAY_LevelSelectDef, false); - - return true; -} - -// Init level select for use in local play using the last choice we made. -// For the online MP version used to START HOSTING A GAME, see M_MPSetupNetgameMapSelect() -// (We still use this one midgame) - -void M_LevelSelectInit(INT32 choice) -{ - INT32 gt = currentMenu->menuitems[itemOn].mvar2; - - (void)choice; - - // Make sure this is reset as we'll only be using this function for offline games! - levellist.netgame = false; - levellist.levelsearch.checklocked = true; - - switch (currentMenu->menuitems[itemOn].mvar1) - { - case 0: - cupgrid.grandprix = false; - levellist.levelsearch.timeattack = false; - break; - case 1: - cupgrid.grandprix = false; - levellist.levelsearch.timeattack = true; - break; - case 2: - cupgrid.grandprix = true; - levellist.levelsearch.timeattack = false; - break; - default: - CONS_Alert(CONS_WARNING, "Bad level select init\n"); - return; - } - - if (gt == -1) - { - gt = menugametype; - } - - if (!M_LevelListFromGametype(gt)) - { - S_StartSound(NULL, sfx_s3kb2); - M_StartMessage(va("No levels available for\n%s Mode!\n\nPress (B)\n", gametypes[gt]->name), NULL, MM_NOTHING); - } -} - -static void M_LevelSelected(INT16 add) -{ - UINT8 i = 0; - INT16 map = M_GetFirstLevelInList(&i, &levellist.levelsearch); - - while (add > 0) - { - map = M_GetNextLevelInList(map, &i, &levellist.levelsearch); - - if (map >= nummapheaders) - { - break; - } - - add--; - } - - if (map >= nummapheaders) - { - // This shouldn't happen - return; - } - - levellist.choosemap = map; - - if (levellist.levelsearch.timeattack) - { - S_StartSound(NULL, sfx_s3k63); - - if (levellist.guessgt != MAXGAMETYPES) - levellist.newgametype = G_GuessGametypeByTOL(levellist.levelsearch.typeoflevel); - - PLAY_TimeAttackDef.lastOn = ta_start; - PLAY_TimeAttackDef.prevMenu = currentMenu; - M_SetupNextMenu(&PLAY_TimeAttackDef, false); - } - else - { - if (gamestate == GS_MENU) - { - UINT8 ssplayers = cv_splitplayers.value-1; - - netgame = false; - multiplayer = true; - - strncpy(connectedservername, cv_servername.string, MAXSERVERNAME); - - // Still need to reset devmode - cht_debug = 0; - - if (demo.playback) - G_StopDemo(); - if (metalrecording) - G_StopMetalDemo(); - - /*if (levellist.choosemap == 0) - levellist.choosemap = G_RandMap(G_TOLFlag(levellist.newgametype), -1, 0, 0, false, NULL);*/ - - if (cv_maxconnections.value < ssplayers+1) - CV_SetValue(&cv_maxconnections, ssplayers+1); - - if (splitscreen != ssplayers) - { - splitscreen = ssplayers; - SplitScreen_OnChange(); - } - - S_StartSound(NULL, sfx_s3k63); - - paused = false; - - // Early fadeout to let the sound finish playing - F_WipeStartScreen(); - V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31); - F_WipeEndScreen(); - F_RunWipe(wipedefs[wipe_level_toblack], false, "FADEMAP0", false, false); - - SV_StartSinglePlayerServer(levellist.newgametype, levellist.netgame); - - CV_StealthSet(&cv_kartbot, cv_dummymatchbots.string); - CV_StealthSet(&cv_kartencore, (cv_dummygpencore.value == 1) ? "On" : "Auto"); - CV_StealthSet(&cv_kartspeed, (cv_dummykartspeed.value == KARTSPEED_NORMAL) ? "Auto" : cv_dummykartspeed.string); - - D_MapChange(levellist.choosemap+1, levellist.newgametype, (cv_kartencore.value == 1), 1, 1, false, false); - } - else - { - // directly do the map change - D_MapChange(levellist.choosemap+1, levellist.newgametype, (cv_kartencore.value == 1), 1, 1, false, false); - } - - M_ClearMenus(true); - } -} - -void M_CupSelectHandler(INT32 choice) -{ - const UINT8 pid = 0; - - (void)choice; - - if (menucmd[pid].dpad_lr > 0) - { - cupgrid.x++; - if (cupgrid.x >= CUPMENU_COLUMNS) - { - cupgrid.x = 0; - cupgrid.pageno++; - if (cupgrid.pageno >= cupgrid.numpages) - cupgrid.pageno = 0; - } - S_StartSound(NULL, sfx_s3k5b); - M_SetMenuDelay(pid); - } - else if (menucmd[pid].dpad_lr < 0) - { - cupgrid.x--; - if (cupgrid.x < 0) - { - cupgrid.x = CUPMENU_COLUMNS-1; - if (cupgrid.pageno == 0) - cupgrid.pageno = cupgrid.numpages-1; - else - cupgrid.pageno--; - } - S_StartSound(NULL, sfx_s3k5b); - M_SetMenuDelay(pid); - } - - if (menucmd[pid].dpad_ud > 0) - { - cupgrid.y++; - if (cupgrid.y >= CUPMENU_ROWS) - cupgrid.y = 0; - S_StartSound(NULL, sfx_s3k5b); - M_SetMenuDelay(pid); - } - else if (menucmd[pid].dpad_ud < 0) - { - cupgrid.y--; - if (cupgrid.y < 0) - cupgrid.y = CUPMENU_ROWS-1; - S_StartSound(NULL, sfx_s3k5b); - M_SetMenuDelay(pid); - } - - if (M_MenuConfirmPressed(pid) /*|| M_MenuButtonPressed(pid, MBT_START)*/) - { - INT16 count; - cupheader_t *newcup = cupgrid.builtgrid[CUPMENU_CURSORID]; - cupheader_t *oldcup = levellist.levelsearch.cup; - - M_SetMenuDelay(pid); - - levellist.levelsearch.cup = newcup; - count = M_CountLevelsToShowInList(&levellist.levelsearch); - - if ((!newcup) - || (count <= 0) - || (cupgrid.grandprix == true && newcup->cachedlevels[0] == NEXTMAP_INVALID)) - { - S_StartSound(NULL, sfx_s3kb2); - return; - } - - if (cupgrid.grandprix == true) - { - INT32 levelNum; - UINT8 ssplayers = cv_splitplayers.value-1; - - S_StartSound(NULL, sfx_s3k63); - - // Early fadeout to let the sound finish playing - F_WipeStartScreen(); - V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31); - F_WipeEndScreen(); - F_RunWipe(wipedefs[wipe_level_toblack], false, "FADEMAP0", false, false); - - memset(&grandprixinfo, 0, sizeof(struct grandprixinfo)); - - if (cv_maxconnections.value < ssplayers+1) - CV_SetValue(&cv_maxconnections, ssplayers+1); - - if (splitscreen != ssplayers) - { - splitscreen = ssplayers; - SplitScreen_OnChange(); - } - - // read our dummy cvars - - grandprixinfo.gamespeed = min(KARTSPEED_HARD, cv_dummygpdifficulty.value); - grandprixinfo.masterbots = (cv_dummygpdifficulty.value == 3); - grandprixinfo.encore = (boolean)cv_dummygpencore.value; - - grandprixinfo.cup = newcup; - - grandprixinfo.gp = true; - grandprixinfo.roundnum = 1; - grandprixinfo.initalize = true; - - paused = false; - - // Don't restart the server if we're already in a game lol - if (gamestate == GS_MENU) - { - SV_StartSinglePlayerServer(levellist.newgametype, levellist.netgame); - } - - levelNum = grandprixinfo.cup->cachedlevels[0]; - - D_MapChange( - levelNum + 1, - GT_RACE, - grandprixinfo.encore, - true, - 1, - false, - false - ); - - M_ClearMenus(true); - } - else if (count == 1) - { - PLAY_TimeAttackDef.transitionID = currentMenu->transitionID+1; - M_LevelSelected(0); - } - else - { - // Keep cursor position if you select the same cup again, reset if it's a different cup - if (oldcup != newcup || levellist.cursor >= count) - { - levellist.cursor = 0; - } - - M_LevelSelectScrollDest(); - levellist.y = levellist.dest; - - M_SetupNextMenu(&PLAY_LevelSelectDef, false); - S_StartSound(NULL, sfx_s3k63); - } - } - else if (M_MenuBackPressed(pid)) - { - M_SetMenuDelay(pid); - - if (currentMenu->prevMenu) - M_SetupNextMenu(currentMenu->prevMenu, false); - else - M_ClearMenus(true); - } -} - -void M_CupSelectTick(void) -{ - cupgrid.previewanim++; -} - -void M_LevelSelectHandler(INT32 choice) -{ - INT16 maxlevels = M_CountLevelsToShowInList(&levellist.levelsearch); - const UINT8 pid = 0; - - (void)choice; - - if (levellist.y != levellist.dest) - { - return; - } - - if (menucmd[pid].dpad_ud > 0) - { - levellist.cursor++; - if (levellist.cursor >= maxlevels) - levellist.cursor = 0; - S_StartSound(NULL, sfx_s3k5b); - M_SetMenuDelay(pid); - } - else if (menucmd[pid].dpad_ud < 0) - { - levellist.cursor--; - if (levellist.cursor < 0) - levellist.cursor = maxlevels-1; - S_StartSound(NULL, sfx_s3k5b); - M_SetMenuDelay(pid); - } - - M_LevelSelectScrollDest(); - - if (M_MenuConfirmPressed(pid) /*|| M_MenuButtonPressed(pid, MBT_START)*/) - { - M_SetMenuDelay(pid); - - PLAY_TimeAttackDef.transitionID = currentMenu->transitionID; - M_LevelSelected(levellist.cursor); - } - else if (M_MenuBackPressed(pid)) - { - M_SetMenuDelay(pid); - - if (currentMenu->prevMenu) - M_SetupNextMenu(currentMenu->prevMenu, false); - else - M_ClearMenus(true); - } -} - -void M_LevelSelectTick(void) -{ - - INT16 dist = levellist.dest - levellist.y; - - if (abs(dist) == 1) // cheating to avoid off by 1 errors with divisions. - levellist.y = levellist.dest; - else - levellist.y += dist/2; -} - - -// time attack stuff... -void M_HandleStaffReplay(INT32 choice) -{ - // @TODO: - (void) choice; -} - -void M_ReplayTimeAttack(INT32 choice) -{ - // @TODO: - (void) choice; -} - -void M_SetGuestReplay(INT32 choice) -{ - // @TODO: - (void) choice; -} - -void M_StartTimeAttack(INT32 choice) -{ - char *gpath; - char nameofdemo[256]; - - (void)choice; - - modeattacking = ATTACKING_TIME; - - if ((gametypes[levellist.newgametype]->rules & GTR_CIRCUIT) - && (mapheaderinfo[levellist.choosemap]->numlaps != 1)) - { - modeattacking |= ATTACKING_LAP; - } - - // Still need to reset devmode - cht_debug = 0; - emeralds = 0; - - if (demo.playback) - G_StopDemo(); - if (metalrecording) - G_StopMetalDemo(); - - splitscreen = 0; - SplitScreen_OnChange(); - - S_StartSound(NULL, sfx_s3k63); - - paused = false; - - // Early fadeout to let the sound finish playing - F_WipeStartScreen(); - V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31); - F_WipeEndScreen(); - F_RunWipe(wipedefs[wipe_level_toblack], false, "FADEMAP0", false, false); - - SV_StartSinglePlayerServer(levellist.newgametype, false); - - gpath = va("%s"PATHSEP"media"PATHSEP"replay"PATHSEP"%s", - srb2home, timeattackfolder); - M_MkdirEach(gpath, M_PathParts(gpath) - 3, 0755); - - strcat(gpath, PATHSEP); - strcat(gpath, G_BuildMapName(levellist.choosemap+1)); - - snprintf(nameofdemo, sizeof nameofdemo, "%s-%s-last", gpath, cv_skin[0].string); - - if (!cv_autorecord.value) - remove(va("%s"PATHSEP"%s.lmp", srb2home, nameofdemo)); - else - G_RecordDemo(nameofdemo); - - M_ClearMenus(true); - D_MapChange(levellist.choosemap+1, levellist.newgametype, (cv_dummygpencore.value == 1), 1, 1, false, false); -} - - -struct mpmenu_s mpmenu; - -// MULTIPLAYER OPTION SELECT - -// Use this as a quit routine within the HOST GAME and JOIN BY IP "sub" menus -boolean M_MPResetOpts(void) -{ - UINT8 i = 0; - - for (; i < 3; i++) - mpmenu.modewinextend[i][0] = 0; // Undo this - - return true; -} - -void M_MPOptSelectInit(INT32 choice) -{ - INT16 arrcpy[3][3] = {{0,68,0}, {0,12,0}, {0,74,0}}; - UINT8 i = 0, j = 0; // To copy the array into the struct - const UINT32 forbidden = GTR_FORBIDMP; - - (void)choice; - - mpmenu.modechoice = 0; - mpmenu.ticker = 0; - - for (; i < 3; i++) - for (j = 0; j < 3; j++) - mpmenu.modewinextend[i][j] = arrcpy[i][j]; // I miss Lua already - - // Guarantee menugametype is good - M_NextMenuGametype(forbidden); - M_PrevMenuGametype(forbidden); - - M_SetupNextMenu(&PLAY_MP_OptSelectDef, false); -} - -void M_MPOptSelectTick(void) -{ - UINT8 i = 0; - - // 3 Because we have 3 options in the menu - for (; i < 3; i++) - { - if (mpmenu.modewinextend[i][0]) - mpmenu.modewinextend[i][2] += 8; - else - mpmenu.modewinextend[i][2] -= 8; - - mpmenu.modewinextend[i][2] = min(mpmenu.modewinextend[i][1], max(0, mpmenu.modewinextend[i][2])); - //CONS_Printf("%d - %d,%d,%d\n", i, mpmenu.modewinextend[i][0], mpmenu.modewinextend[i][1], mpmenu.modewinextend[i][2]); - } -} - - -// MULTIPLAYER HOST -void M_MPHostInit(INT32 choice) -{ - - (void)choice; - mpmenu.modewinextend[0][0] = 1; - M_SetupNextMenu(&PLAY_MP_HostDef, true); - itemOn = mhost_go; -} - -void M_NextMenuGametype(UINT32 forbidden) -{ - const INT16 currentmenugametype = menugametype; - do - { - menugametype++; - if (menugametype >= numgametypes) - menugametype = 0; - - if (!(gametypes[menugametype]->rules & forbidden)) - break; - } while (menugametype != currentmenugametype); -} - -void M_PrevMenuGametype(UINT32 forbidden) -{ - const INT16 currentmenugametype = menugametype; - do - { - if (menugametype == 0) - menugametype = numgametypes; - menugametype--; - - if (!(gametypes[menugametype]->rules & forbidden)) - break; - } while (menugametype != currentmenugametype); -} - -void M_HandleHostMenuGametype(INT32 choice) -{ - const UINT8 pid = 0; - const UINT32 forbidden = GTR_FORBIDMP; - - (void)choice; - - if (M_MenuBackPressed(pid)) - { - M_GoBack(0); - M_SetMenuDelay(pid); - return; - } - else if (menucmd[pid].dpad_lr > 0 || M_MenuConfirmPressed(pid)) - { - M_NextMenuGametype(forbidden); - S_StartSound(NULL, sfx_s3k5b); - M_SetMenuDelay(pid); - } - else if (menucmd[pid].dpad_lr < 0) - { - M_PrevMenuGametype(forbidden); - S_StartSound(NULL, sfx_s3k5b); - M_SetMenuDelay(pid); - } - - if (menucmd[pid].dpad_ud > 0) - { - M_NextOpt(); - S_StartSound(NULL, sfx_s3k5b); - M_SetMenuDelay(pid); - } - else if (menucmd[pid].dpad_ud < 0) - { - M_PrevOpt(); - S_StartSound(NULL, sfx_s3k5b); - M_SetMenuDelay(pid); - } -} - -void M_MPSetupNetgameMapSelect(INT32 choice) -{ - (void)choice; - - // Yep, we'll be starting a netgame. - levellist.netgame = true; - // Make sure we reset those - levellist.levelsearch.timeattack = false; - levellist.levelsearch.checklocked = true; - cupgrid.grandprix = false; - - // okay this is REALLY stupid but this fixes the host menu re-folding on itself when we go back. - mpmenu.modewinextend[0][0] = 1; - - if (!M_LevelListFromGametype(menugametype)) - { - S_StartSound(NULL, sfx_s3kb2); - M_StartMessage(va("No levels available for\n%s Mode!\n\nPress (B)\n", gametypes[menugametype]->name), NULL, MM_NOTHING); - } -} - -// MULTIPLAYER JOIN BY IP -void M_MPJoinIPInit(INT32 choice) -{ - - (void)choice; - mpmenu.modewinextend[2][0] = 1; - M_SetupNextMenu(&PLAY_MP_JoinIPDef, true); -} - -// Attempts to join a given IP from the menu. -void M_JoinIP(const char *ipa) -{ - if (*(ipa) == '\0') // Jack shit - { - M_StartMessage("Please specify an address.\n", NULL, MM_NOTHING); - return; - } - - COM_BufAddText(va("connect \"%s\"\n", ipa)); - - // A little "please wait" message. - M_DrawTextBox(56, BASEVIDHEIGHT/2-12, 24, 2); - V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT/2, 0, "Connecting to server..."); - I_OsPolling(); - I_UpdateNoBlit(); - if (rendermode == render_soft) - I_FinishUpdate(); // page flip or blit buffer -} - -boolean M_JoinIPInputs(INT32 ch) -{ - - const UINT8 pid = 0; - (void) ch; - - if (itemOn == 1) // connect field - { - // enter: connect - if (M_MenuConfirmPressed(pid)) - { - M_JoinIP(cv_dummyip.string); - M_SetMenuDelay(pid); - return true; - } - } - else if (currentMenu->numitems - itemOn <= NUMLOGIP && M_MenuConfirmPressed(pid)) // On one of the last 3 options for IP rejoining - { - UINT8 index = NUMLOGIP - (currentMenu->numitems - itemOn); - M_SetMenuDelay(pid); - - // Is there an address at this part of the table? - if (*joinedIPlist[index][0]) - M_JoinIP(joinedIPlist[index][0]); - else - S_StartSound(NULL, sfx_lose); - - return true; // eat input. - } - - return false; -} - -// MULTIPLAYER ROOM SELECT MENU - -void M_MPRoomSelect(INT32 choice) -{ - const UINT8 pid = 0; - (void) choice; - - if (menucmd[pid].dpad_lr) - { - mpmenu.room = (!mpmenu.room) ? 1 : 0; - S_StartSound(NULL, sfx_s3k5b); - M_SetMenuDelay(pid); - } - else if (M_MenuBackPressed(pid)) - { - M_GoBack(0); - M_SetMenuDelay(pid); - } - - else if (M_MenuConfirmPressed(pid)) - { - M_ServersMenu(0); - M_SetMenuDelay(pid); - } -} - -void M_MPRoomSelectTick(void) -{ - mpmenu.ticker++; -} - -void M_MPRoomSelectInit(INT32 choice) -{ - (void)choice; - mpmenu.room = 0; - mpmenu.ticker = 0; - mpmenu.servernum = 0; - mpmenu.scrolln = 0; - mpmenu.slide = 0; - - M_SetupNextMenu(&PLAY_MP_RoomSelectDef, false); -} - -// MULTIPLAYER ROOM FETCH / REFRESH THREADS - -// depending on mpmenu.room, either allows only unmodded servers or modded ones. Remove others from the list. -// we do this by iterating backwards. -static void M_CleanServerList(void) -{ - UINT8 i = serverlistcount; - - while (i) - { - - if (serverlist[i].info.modifiedgame != mpmenu.room) - { - // move everything after this index 1 slot down... - if (i != serverlistcount) - memcpy(&serverlist[i], &serverlist[i+1], sizeof(serverelem_t)*(serverlistcount-i)); - - serverlistcount--; - } - - i--; - } -} - -void -M_SetWaitingMode (int mode) -{ -#ifdef HAVE_THREADS - I_lock_mutex(&k_menu_mutex); -#endif - { - m_waiting_mode = mode; - } -#ifdef HAVE_THREADS - I_unlock_mutex(k_menu_mutex); -#endif -} - -int -M_GetWaitingMode (void) -{ - int mode; - -#ifdef HAVE_THREADS - I_lock_mutex(&k_menu_mutex); -#endif - { - mode = m_waiting_mode; - } -#ifdef HAVE_THREADS - I_unlock_mutex(k_menu_mutex); -#endif - - return mode; -} - -#ifdef MASTERSERVER -#ifdef HAVE_THREADS -void -Spawn_masterserver_thread (const char *name, void (*thread)(int*)) -{ - int *id = malloc(sizeof *id); - - I_lock_mutex(&ms_QueryId_mutex); - { - *id = ms_QueryId; - } - I_unlock_mutex(ms_QueryId_mutex); - - I_spawn_thread(name, (I_thread_fn)thread, id); -} - -int -Same_instance (int id) -{ - int okay; - - I_lock_mutex(&ms_QueryId_mutex); - { - okay = ( id == ms_QueryId ); - } - I_unlock_mutex(ms_QueryId_mutex); - - return okay; -} -#endif/*HAVE_THREADS*/ - -void -Fetch_servers_thread (int *id) -{ - msg_server_t * server_list; - - (void)id; - - M_SetWaitingMode(M_WAITING_SERVERS); - -#ifdef HAVE_THREADS - server_list = GetShortServersList(*id); -#else - server_list = GetShortServersList(0); -#endif - - if (server_list) - { -#ifdef HAVE_THREADS - if (Same_instance(*id)) -#endif - { - M_SetWaitingMode(M_NOT_WAITING); - -#ifdef HAVE_THREADS - I_lock_mutex(&ms_ServerList_mutex); - { - ms_ServerList = server_list; - } - I_unlock_mutex(ms_ServerList_mutex); -#else - CL_QueryServerList(server_list); - free(server_list); -#endif - } -#ifdef HAVE_THREADS - else - { - free(server_list); - } -#endif - } - -#ifdef HAVE_THREADS - free(id); -#endif -} -#endif/*MASTERSERVER*/ - -// updates serverlist -void M_RefreshServers(INT32 choice) -{ - (void)choice; - - // Display a little "please wait" message. - M_DrawTextBox(52, BASEVIDHEIGHT/2-10, 25, 3); - V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT/2, 0, "Searching for servers..."); - V_DrawCenteredString(BASEVIDWIDTH/2, (BASEVIDHEIGHT/2)+12, 0, "Please wait."); - I_OsPolling(); - I_UpdateNoBlit(); - if (rendermode == render_soft) - I_FinishUpdate(); // page flip or blit buffer - -#ifdef MASTERSERVER -#ifdef HAVE_THREADS - Spawn_masterserver_thread("fetch-servers", Fetch_servers_thread); -#else/*HAVE_THREADS*/ - Fetch_servers_thread(NULL); -#endif/*HAVE_THREADS*/ -#else/*MASTERSERVER*/ - CL_UpdateServerList(); -#endif/*MASTERSERVER*/ - -#ifdef SERVERLISTDEBUG - M_ServerListFillDebug(); -#endif - M_CleanServerList(); - M_SortServerList(); - -} - -#ifdef UPDATE_ALERT -static void M_CheckMODVersion(int id) -{ - char updatestring[500]; - const char *updatecheck = GetMODVersion(id); - if(updatecheck) - { - sprintf(updatestring, UPDATE_ALERT_STRING, VERSIONSTRING, updatecheck); -#ifdef HAVE_THREADS - I_lock_mutex(&k_menu_mutex); -#endif - M_StartMessage(updatestring, NULL, MM_NOTHING); -#ifdef HAVE_THREADS - I_unlock_mutex(k_menu_mutex); -#endif - } -} -#endif/*UPDATE_ALERT*/ - -#if defined (UPDATE_ALERT) && defined (HAVE_THREADS) -static void -Check_new_version_thread (int *id) -{ - M_SetWaitingMode(M_WAITING_VERSION); - - M_CheckMODVersion(*id); - - if (Same_instance(*id)) - { - Fetch_servers_thread(id); - } - else - { - free(id); - } -} -#endif/*defined (UPDATE_ALERT) && defined (HAVE_THREADS)*/ - - -// Initializes serverlist when entering the menu... -void M_ServersMenu(INT32 choice) -{ - (void)choice; - // modified game check: no longer handled - // we don't request a restart unless the filelist differs - - mpmenu.servernum = 0; - mpmenu.scrolln = 0; - mpmenu.slide = 0; - - M_SetupNextMenu(&PLAY_MP_ServerBrowserDef, false); - itemOn = 0; - -#if defined (MASTERSERVER) && defined (HAVE_THREADS) - I_lock_mutex(&ms_QueryId_mutex); - { - ms_QueryId++; - } - I_unlock_mutex(ms_QueryId_mutex); - - I_lock_mutex(&ms_ServerList_mutex); - { - if (ms_ServerList) - { - free(ms_ServerList); - ms_ServerList = NULL; - } - } - I_unlock_mutex(ms_ServerList_mutex); - -#ifdef UPDATE_ALERT - Spawn_masterserver_thread("check-new-version", Check_new_version_thread); -#else/*UPDATE_ALERT*/ - Spawn_masterserver_thread("fetch-servers", Fetch_servers_thread); -#endif/*UPDATE_ALERT*/ -#else/*defined (MASTERSERVER) && defined (HAVE_THREADS)*/ -#ifdef UPDATE_ALERT - M_CheckMODVersion(0); -#endif/*UPDATE_ALERT*/ - M_RefreshServers(0); -#endif/*defined (MASTERSERVER) && defined (HAVE_THREADS)*/ - -#ifdef SERVERLISTDEBUG - M_ServerListFillDebug(); -#endif - - M_CleanServerList(); - M_SortServerList(); -} - -#ifdef SERVERLISTDEBUG - -// Fill serverlist with a bunch of garbage to make our life easier in debugging -void M_ServerListFillDebug(void) -{ - UINT8 i = 0; - - serverlistcount = 10; - memset(serverlist, 0, sizeof(serverlist)); // zero out the array for convenience... - - for (i = 0; i < serverlistcount; i++) - { - // We don't really care about the server node for this, let's just fill in the info so that we have a visual... - serverlist[i].info.numberofplayer = min(i, 8); - serverlist[i].info.maxplayer = 8; - - serverlist[i].info.avgpwrlv = P_RandomRange(PR_UNDEFINED, 500, 1500); - serverlist[i].info.time = P_RandomRange(PR_UNDEFINED, 1, 8); // ping - - strcpy(serverlist[i].info.servername, va("Serv %d", i+1)); - - strcpy(serverlist[i].info.gametypename, i & 1 ? "Race" : "Battle"); - - P_RandomRange(PR_UNDEFINED, 0, 5); // change results... - serverlist[i].info.kartvars = P_RandomRange(PR_UNDEFINED, 0, 3) & SV_SPEEDMASK; - - serverlist[i].info.modifiedgame = P_RandomRange(PR_UNDEFINED, 0, 1); - - CONS_Printf("Serv %d | %d...\n", i, serverlist[i].info.modifiedgame); - } -} - -#endif // SERVERLISTDEBUG - -// Ascending order, not descending. -// The casts are safe as long as the caller doesn't do anything stupid. -#define SERVER_LIST_ENTRY_COMPARATOR(key) \ -static int ServerListEntryComparator_##key(const void *entry1, const void *entry2) \ -{ \ - const serverelem_t *sa = (const serverelem_t*)entry1, *sb = (const serverelem_t*)entry2; \ - if (sa->info.key != sb->info.key) \ - return sa->info.key - sb->info.key; \ - return strcmp(sa->info.servername, sb->info.servername); \ -} - -// This does descending instead of ascending. -#define SERVER_LIST_ENTRY_COMPARATOR_REVERSE(key) \ -static int ServerListEntryComparator_##key##_reverse(const void *entry1, const void *entry2) \ -{ \ - const serverelem_t *sa = (const serverelem_t*)entry1, *sb = (const serverelem_t*)entry2; \ - if (sb->info.key != sa->info.key) \ - return sb->info.key - sa->info.key; \ - return strcmp(sb->info.servername, sa->info.servername); \ -} - -SERVER_LIST_ENTRY_COMPARATOR(time) -SERVER_LIST_ENTRY_COMPARATOR(numberofplayer) -SERVER_LIST_ENTRY_COMPARATOR_REVERSE(numberofplayer) -SERVER_LIST_ENTRY_COMPARATOR_REVERSE(maxplayer) -SERVER_LIST_ENTRY_COMPARATOR(avgpwrlv) - - -static int ServerListEntryComparator_gametypename(const void *entry1, const void *entry2) -{ - const serverelem_t *sa = (const serverelem_t*)entry1, *sb = (const serverelem_t*)entry2; - int c; - if (( c = strcasecmp(sa->info.gametypename, sb->info.gametypename) )) - return c; - return strcmp(sa->info.servername, sb->info.servername); \ -} - -void M_SortServerList(void) -{ - switch(cv_serversort.value) - { - case 0: // Ping. - qsort(serverlist, serverlistcount, sizeof(serverelem_t), ServerListEntryComparator_time); - break; - case 1: // AVG. Power Level - qsort(serverlist, serverlistcount, sizeof(serverelem_t), ServerListEntryComparator_avgpwrlv); - break; - case 2: // Most players. - qsort(serverlist, serverlistcount, sizeof(serverelem_t), ServerListEntryComparator_numberofplayer_reverse); - break; - case 3: // Least players. - qsort(serverlist, serverlistcount, sizeof(serverelem_t), ServerListEntryComparator_numberofplayer); - break; - case 4: // Max players. - qsort(serverlist, serverlistcount, sizeof(serverelem_t), ServerListEntryComparator_maxplayer_reverse); - break; - case 5: // Gametype. - qsort(serverlist, serverlistcount, sizeof(serverelem_t), ServerListEntryComparator_gametypename); - break; - } -} - - -// Server browser inputs & ticker -void M_MPServerBrowserTick(void) -{ - mpmenu.slide /= 2; -} - -// Input handler for server browser. -boolean M_ServerBrowserInputs(INT32 ch) -{ - UINT8 pid = 0; - UINT8 maxscroll = serverlistcount-(SERVERSPERPAGE/2); - (void) ch; - - if (!itemOn && menucmd[pid].dpad_ud < 0) - { - M_PrevOpt(); // go to itemOn 2 - if (serverlistcount) - { - UINT8 prevscroll = mpmenu.scrolln; - - mpmenu.servernum = serverlistcount; - mpmenu.scrolln = maxscroll; - mpmenu.slide = SERVERSPACE * (prevscroll - mpmenu.scrolln); - } - else - { - itemOn = 1; // Sike! If there are no servers, go to refresh instead. - } - - return true; // overwrite behaviour. - } - else if (itemOn == 2) // server browser itself... - { - // we have to manually do that here. - if (M_MenuBackPressed(pid)) - { - M_GoBack(0); - M_SetMenuDelay(pid); - } - - else if (menucmd[pid].dpad_ud > 0) // down - { - if (mpmenu.servernum >= serverlistcount-1) - { - UINT8 prevscroll = mpmenu.scrolln; - mpmenu.servernum = 0; - mpmenu.scrolln = 0; - mpmenu.slide = SERVERSPACE * (prevscroll - mpmenu.scrolln); - - M_NextOpt(); // Go back to the top of the real menu. - } - else - { - mpmenu.servernum++; - if (mpmenu.scrolln < maxscroll && mpmenu.servernum > SERVERSPERPAGE/2) - { - mpmenu.scrolln++; - mpmenu.slide += SERVERSPACE; - } - } - S_StartSound(NULL, sfx_s3k5b); - M_SetMenuDelay(pid); - - } - else if (menucmd[pid].dpad_ud < 0) - { - - if (!mpmenu.servernum) - { - M_PrevOpt(); - } - else - { - if (mpmenu.servernum <= serverlistcount-(SERVERSPERPAGE/2) && mpmenu.scrolln) - { - mpmenu.scrolln--; - mpmenu.slide -= SERVERSPACE; - } - - mpmenu.servernum--; - } - S_StartSound(NULL, sfx_s3k5b); - M_SetMenuDelay(pid); - - } - return true; // Overwrite behaviour. - } - return false; // use normal behaviour. -} - - -// Options menu: -struct optionsmenu_s optionsmenu; - -void M_ResetOptions(void) -{ - optionsmenu.ticker = 0; - optionsmenu.offset = 0; - - optionsmenu.optx = 0; - optionsmenu.opty = 0; - optionsmenu.toptx = 0; - optionsmenu.topty = 0; - - // BG setup: - optionsmenu.currcolour = OPTIONS_MainDef.extra1; - optionsmenu.lastcolour = 0; - optionsmenu.fade = 0; - - // For profiles: - memset(setup_player, 0, sizeof(setup_player)); - optionsmenu.profile = NULL; -} - -void M_InitOptions(INT32 choice) -{ - (void)choice; - - OPTIONS_MainDef.menuitems[mopt_gameplay].status = IT_STRING | IT_TRANSTEXT; - OPTIONS_MainDef.menuitems[mopt_server].status = IT_STRING | IT_TRANSTEXT; - - // enable gameplay & server options under the right circumstances. - if (gamestate == GS_MENU - || ((server || IsPlayerAdmin(consoleplayer)) && K_CanChangeRules(false))) - { - OPTIONS_MainDef.menuitems[mopt_gameplay].status = IT_STRING | IT_SUBMENU; - OPTIONS_MainDef.menuitems[mopt_server].status = IT_STRING | IT_SUBMENU; - OPTIONS_GameplayDef.menuitems[gopt_encore].status = - (M_SecretUnlocked(SECRET_ENCORE, false) ? (IT_STRING | IT_CVAR) : IT_DISABLED); - } - - OPTIONS_DataDef.menuitems[dopt_erase].status = (gamestate == GS_MENU - ? (IT_STRING | IT_SUBMENU) - : (IT_TRANSTEXT2 | IT_SPACE)); - - M_ResetOptions(); - - // So that pause doesn't go to the main menu... - OPTIONS_MainDef.prevMenu = currentMenu; - - // This will disable or enable the textboxes of the affected menus before we get to them. - Screenshot_option_Onchange(); - Moviemode_mode_Onchange(); - Moviemode_option_Onchange(); - Addons_option_Onchange(); - - M_SetupNextMenu(&OPTIONS_MainDef, false); -} - -// Prepares changing the colour of the background -void M_OptionsChangeBGColour(INT16 newcolour) -{ - optionsmenu.fade = 10; - optionsmenu.lastcolour = optionsmenu.currcolour; - optionsmenu.currcolour = newcolour; -} - -boolean M_OptionsQuit(void) -{ - optionsmenu.toptx = 140-1; - optionsmenu.topty = 70+1; - - // Reset button behaviour because profile menu is different, since of course it is. - if (optionsmenu.resetprofilemenu) - { - optionsmenu.profilemenu = false; - optionsmenu.profile = NULL; - optionsmenu.resetprofilemenu = false; - } - - return true; // Always allow quitting, duh. -} - -void M_OptionsTick(void) -{ - optionsmenu.offset /= 2; - optionsmenu.ticker++; - - optionsmenu.optx += (optionsmenu.toptx - optionsmenu.optx)/2; - optionsmenu.opty += (optionsmenu.topty - optionsmenu.opty)/2; - - if (abs(optionsmenu.optx - optionsmenu.opty) < 2) - { - optionsmenu.optx = optionsmenu.toptx; - optionsmenu.opty = optionsmenu.topty; // Avoid awkward 1 px errors. - } - - // Move the button for cool animations - if (currentMenu == &OPTIONS_MainDef) - { - M_OptionsQuit(); // ...So now this is used here. - } - else if (optionsmenu.profile == NULL) // Not currently editing a profile (otherwise we're using these variables for other purposes....) - { - // I don't like this, it looks like shit but it needs to be done.......... - if (optionsmenu.profilemenu) - { - optionsmenu.toptx = 420; - optionsmenu.topty = 70+1; - } - else if (currentMenu == &OPTIONS_GameplayItemsDef) - { - optionsmenu.toptx = -160; // off the side of the screen - optionsmenu.topty = 50; - } - else - { - optionsmenu.toptx = 160; - optionsmenu.topty = 50; - } - } - - // Handle the background stuff: - if (optionsmenu.fade) - optionsmenu.fade--; - - // change the colour if we aren't matching the current menu colour - if (optionsmenu.currcolour != currentMenu->extra1) - M_OptionsChangeBGColour(currentMenu->extra1); - - // And one last giggle... - if (shitsfree) - shitsfree--; -} - -boolean M_OptionsInputs(INT32 ch) -{ - - const UINT8 pid = 0; - (void)ch; - - if (menucmd[pid].dpad_ud > 0) - { - M_SetMenuDelay(pid); - optionsmenu.offset += 48; - M_NextOpt(); - S_StartSound(NULL, sfx_s3k5b); - - if (itemOn == 0) - optionsmenu.offset -= currentMenu->numitems*48; - - - return true; - } - else if (menucmd[pid].dpad_ud < 0) - { - M_SetMenuDelay(pid); - optionsmenu.offset -= 48; - M_PrevOpt(); - S_StartSound(NULL, sfx_s3k5b); - - if (itemOn == currentMenu->numitems-1) - optionsmenu.offset += currentMenu->numitems*48; - - - return true; - } - else if (M_MenuConfirmPressed(pid)) - { - - if (currentMenu->menuitems[itemOn].status & IT_TRANSTEXT) - return true; // No. - - optionsmenu.optx = 140; - optionsmenu.opty = 70; // Default position for the currently selected option. - return false; // Don't eat. - } - return false; -} - -void M_ProfileSelectInit(INT32 choice) -{ - (void)choice; - optionsmenu.profilemenu = true; - - M_SetupNextMenu(&OPTIONS_ProfilesDef, false); -} - -// setup video mode menu -void M_VideoModeMenu(INT32 choice) -{ - INT32 i, j, vdup, nummodes; - UINT32 width, height; - const char *desc; - - (void)choice; - - memset(optionsmenu.modedescs, 0, sizeof(optionsmenu.modedescs)); - -#if (defined (__unix__) && !defined (MSDOS)) || defined (UNIXCOMMON) || defined (HAVE_SDL) - VID_PrepareModeList(); // FIXME: hack -#endif - optionsmenu.vidm_nummodes = 0; - optionsmenu.vidm_selected = 0; - nummodes = VID_NumModes(); - - // DOS does not skip mode 0, because mode 0 is ALWAYS present - i = 0; - for (; i < nummodes && optionsmenu.vidm_nummodes < MAXMODEDESCS; i++) - { - desc = VID_GetModeName(i); - if (desc) - { - vdup = 0; - - // when a resolution exists both under VGA and VESA, keep the - // VESA mode, which is always a higher modenum - for (j = 0; j < optionsmenu.vidm_nummodes; j++) - { - if (!strcmp(optionsmenu.modedescs[j].desc, desc)) - { - // mode(0): 320x200 is always standard VGA, not vesa - if (optionsmenu.modedescs[j].modenum) - { - optionsmenu.modedescs[j].modenum = i; - vdup = 1; - - if (i == vid.modenum) - optionsmenu.vidm_selected = j; - } - else - vdup = 1; - - break; - } - } - - if (!vdup) - { - optionsmenu.modedescs[optionsmenu.vidm_nummodes].modenum = i; - optionsmenu.modedescs[optionsmenu.vidm_nummodes].desc = desc; - - if (i == vid.modenum) - optionsmenu.vidm_selected = optionsmenu.vidm_nummodes; - - // Pull out the width and height - sscanf(desc, "%u%*c%u", &width, &height); - - // Show multiples of 320x200 as green. - if (SCR_IsAspectCorrect(width, height)) - optionsmenu.modedescs[optionsmenu.vidm_nummodes].goodratio = 1; - - optionsmenu.vidm_nummodes++; - } - } - } - - optionsmenu.vidm_column_size = (optionsmenu.vidm_nummodes+2) / 3; - - M_SetupNextMenu(&OPTIONS_VideoModesDef, false); -} - -// Select the current profile for menu use and go to maindef. -static void M_FirstPickProfile(INT32 c) -{ - if (c == MA_YES) - { - M_ResetOptions(); // Reset all options variables otherwise things are gonna go reaaal bad lol. - optionsmenu.profile = NULL; // Make sure to get rid of that, too. - - PR_ApplyProfile(optionsmenu.profilen, 0); - M_SetupNextMenu(M_InterruptMenuWithChallenges(&MainDef), false); - - // Tell the game this is the last profile we picked. - CV_StealthSetValue(&cv_ttlprofilen, optionsmenu.profilen); - - // Save em! - PR_SaveProfiles(); - return; - } -} - -// Start menu edition. Call this with MA_YES if not used with a textbox. -static void M_StartEditProfile(INT32 c) -{ - - const INT32 maxp = PR_GetNumProfiles(); - - if (c == MA_YES) - { - if (optionsmenu.profilen == maxp) - PR_InitNewProfile(); // initialize the new profile. - - optionsmenu.profile = PR_GetProfile(optionsmenu.profilen); - // copy this profile's controls into optionsmenu so that we can edit controls without changing them directly. - // we do this so that we don't edit a profile's controls in real-time and end up doing really weird shit. - memcpy(&optionsmenu.tempcontrols, optionsmenu.profile->controls, sizeof(gamecontroldefault)); - - // This is now used to move the card we've selected. - optionsmenu.optx = 160; - optionsmenu.opty = 35; - optionsmenu.toptx = 130/2; - optionsmenu.topty = 0; - - // setup cvars - if (optionsmenu.profile->version) - { - CV_StealthSet(&cv_dummyprofilename, optionsmenu.profile->profilename); - CV_StealthSet(&cv_dummyprofileplayername, optionsmenu.profile->playername); - CV_StealthSetValue(&cv_dummyprofilekickstart, optionsmenu.profile->kickstartaccel); - } - else - { - CV_StealthSet(&cv_dummyprofilename, ""); - CV_StealthSet(&cv_dummyprofileplayername, ""); - CV_StealthSetValue(&cv_dummyprofilekickstart, 0); // off - } - - // Setup greyout and stuff. - OPTIONS_EditProfile[popt_profilename].status = IT_STRING | IT_CVAR | IT_CV_STRING; - OPTIONS_EditProfile[popt_profilepname].status = IT_STRING | IT_CVAR | IT_CV_STRING; - OPTIONS_EditProfile[popt_char].status = IT_STRING | IT_CALL; - - if (gamestate != GS_MENU) // If we're modifying things mid game, transtext some of those! - { - OPTIONS_EditProfile[popt_profilename].status |= IT_TRANSTEXT; - OPTIONS_EditProfile[popt_profilepname].status |= IT_TRANSTEXT; - OPTIONS_EditProfile[popt_char].status |= IT_TRANSTEXT; - } - - M_SetupNextMenu(&OPTIONS_EditProfileDef, false); - return; - } -} - -void M_HandleProfileSelect(INT32 ch) -{ - const UINT8 pid = 0; - INT32 maxp = PR_GetNumProfiles(); - boolean creatable = (maxp < MAXPROFILES); - (void) ch; - - if (!creatable) - { - maxp = MAXPROFILES; - } - - if (menucmd[pid].dpad_lr > 0) - { - optionsmenu.profilen++; - optionsmenu.offset += (128 + 128/8); - - if (optionsmenu.profilen > maxp) - { - optionsmenu.profilen = 0; - optionsmenu.offset -= (128 + 128/8)*(maxp+1); - } - - S_StartSound(NULL, sfx_s3k5b); - M_SetMenuDelay(pid); - - } - else if (menucmd[pid].dpad_lr < 0) - { - optionsmenu.profilen--; - optionsmenu.offset -= (128 + 128/8); - - if (optionsmenu.profilen < 0) - { - optionsmenu.profilen = maxp; - optionsmenu.offset += (128 + 128/8)*(maxp+1); - } - - S_StartSound(NULL, sfx_s3k5b); - M_SetMenuDelay(pid); - } - - else if (M_MenuConfirmPressed(pid)) - { - - // Boot profile setup has already been done. - if (cv_currprofile.value > -1) - { - - if (optionsmenu.profilen == 0) // Guest profile, you can't edit that one! - { - S_StartSound(NULL, sfx_s3k7b); - M_StartMessage(M_GetText("The Guest profile cannot be edited.\nCreate a new profile instead."), NULL, MM_NOTHING); - M_SetMenuDelay(pid); - return; - } - else if (creatable && optionsmenu.profilen == maxp && gamestate != GS_MENU) - { - S_StartSound(NULL, sfx_s3k7b); - M_StartMessage(M_GetText("Cannot create a new profile\nmid-game. Return to the\ntitle screen first."), NULL, MM_NOTHING); - M_SetMenuDelay(pid); - return; - } - - S_StartSound(NULL, sfx_s3k5b); - M_StartEditProfile(MA_YES); - } - else - { - // We're on the profile selection screen. - if (creatable && optionsmenu.profilen == maxp) - { - M_StartEditProfile(MA_YES); - M_SetMenuDelay(pid); - return; - } - else - { -#if 0 - if (optionsmenu.profilen == 0) - { - M_StartMessage(M_GetText("Are you sure you wish\nto use the Guest Profile?\nThis profile cannot be customised.\nIt is recommended to create\na new Profile instead.\n\nPress (A) to confirm or (B) to cancel"), FUNCPTRCAST(M_FirstPickProfile), MM_YESNO); - return; - } -#endif - - M_FirstPickProfile(MA_YES); - M_SetMenuDelay(pid); - return; - } - } - } - - else if (M_MenuBackPressed(pid)) - { - optionsmenu.resetprofilemenu = true; - M_GoBack(0); - M_SetMenuDelay(pid); - } - - if (menutransition.tics == 0 && optionsmenu.resetprofile) - { - optionsmenu.profile = NULL; // Make sure to reset that when transitions are done.' - optionsmenu.resetprofile = false; - } -} - -// Returns true if the profile can be saved, false otherwise. Also starts messages if necessary. -static boolean M_ProfileEditEnd(const UINT8 pid) -{ - UINT8 i; - - // Guest profile, you can't edit that one! - if (optionsmenu.profilen == 0) - { - S_StartSound(NULL, sfx_s3k7b); - M_StartMessage(M_GetText("Guest profile cannot be edited.\nCreate a new profile instead."), NULL, MM_NOTHING); - M_SetMenuDelay(pid); - return false; - } - - // check if some profiles have the same name - for (i = 0; i < PR_GetNumProfiles(); i++) - { - profile_t *check = PR_GetProfile(i); - - // For obvious reasons don't check if our name is the same as our name.... - if (check != optionsmenu.profile) - { - if (!(strcmp(optionsmenu.profile->profilename, check->profilename))) - { - S_StartSound(NULL, sfx_s3k7b); - M_StartMessage(M_GetText("Another profile uses the same name.\nThis must be changed to be able to save."), NULL, MM_NOTHING); - M_SetMenuDelay(pid); - return false; - } - } - } - - return true; -} - -static void M_ProfileEditExit(void) -{ - optionsmenu.toptx = 160; - optionsmenu.topty = 35; - optionsmenu.resetprofile = true; // Reset profile after the transition is done. - - PR_SaveProfiles(); // save profiles after we do that. -} - -// For profile edit, just make sure going back resets the card to its position, the rest is taken care of automatically. -boolean M_ProfileEditInputs(INT32 ch) -{ - - (void) ch; - const UINT8 pid = 0; - - if (M_MenuBackPressed(pid)) - { - if (M_ProfileEditEnd(pid)) - { - M_ProfileEditExit(); - if (cv_currprofile.value == -1) - M_SetupNextMenu(&MAIN_ProfilesDef, false); - else - M_GoBack(0); - M_SetMenuDelay(pid); - } - return true; - } - else if (M_MenuConfirmPressed(pid)) - { - if (currentMenu->menuitems[itemOn].status & IT_TRANSTEXT) - return true; // No. - } - - return false; -} - -// Handle some actions in profile editing -void M_HandleProfileEdit(void) -{ - // Always copy the profile name and player name in the profile. - if (optionsmenu.profile) - { - // Copy the first 6 chars for profile name - if (strlen(cv_dummyprofilename.string)) - { - char *s; - // convert dummyprofilename to uppercase - strncpy(optionsmenu.profile->profilename, cv_dummyprofilename.string, PROFILENAMELEN); - s = optionsmenu.profile->profilename; - while (*s) - { - *s = toupper(*s); - s++; - } - } - - if (strlen(cv_dummyprofileplayername.string)) - strncpy(optionsmenu.profile->playername, cv_dummyprofileplayername.string, MAXPLAYERNAME); - } - - M_OptionsTick(); // Has to be afterwards because this can unset optionsmenu.profile -} - -// Confirm Profile edi via button. -void M_ConfirmProfile(INT32 choice) -{ - const UINT8 pid = 0; - (void) choice; - - if (M_ProfileEditEnd(pid)) - { - if (cv_currprofile.value > -1) - { - M_ProfileEditExit(); - M_GoBack(0); - M_SetMenuDelay(pid); - } - else - { - M_StartMessage(M_GetText("Are you sure you wish to\nselect this profile?\n\nPress (A) to confirm or (B) to cancel"), FUNCPTRCAST(M_FirstPickProfile), MM_YESNO); - M_SetMenuDelay(pid); - } - } - return; -} - -// special menuitem key handler for video mode list -void M_HandleVideoModes(INT32 ch) -{ - - const UINT8 pid = 0; - (void)ch; - - if (optionsmenu.vidm_testingmode > 0) - { - // change back to the previous mode quickly - if (M_MenuBackPressed(pid)) - { - setmodeneeded = optionsmenu.vidm_previousmode + 1; - optionsmenu.vidm_testingmode = 0; - } - else if (M_MenuConfirmPressed(pid)) - { - S_StartSound(NULL, sfx_s3k5b); - optionsmenu.vidm_testingmode = 0; // stop testing - } - } - - else - { - if (menucmd[pid].dpad_ud > 0) - { - S_StartSound(NULL, sfx_s3k5b); - if (++optionsmenu.vidm_selected >= optionsmenu.vidm_nummodes) - optionsmenu.vidm_selected = 0; - - M_SetMenuDelay(pid); - } - - else if (menucmd[pid].dpad_ud < 0) - { - S_StartSound(NULL, sfx_s3k5b); - if (--optionsmenu.vidm_selected < 0) - optionsmenu.vidm_selected = optionsmenu.vidm_nummodes - 1; - - M_SetMenuDelay(pid); - } - - else if (menucmd[pid].dpad_lr < 0) - { - S_StartSound(NULL, sfx_s3k5b); - optionsmenu.vidm_selected -= optionsmenu.vidm_column_size; - if (optionsmenu.vidm_selected < 0) - optionsmenu.vidm_selected = (optionsmenu.vidm_column_size*3) + optionsmenu.vidm_selected; - if (optionsmenu.vidm_selected >= optionsmenu.vidm_nummodes) - optionsmenu.vidm_selected = optionsmenu.vidm_nummodes - 1; - - M_SetMenuDelay(pid); - } - - else if (menucmd[pid].dpad_lr > 0) - { - S_StartSound(NULL, sfx_s3k5b); - optionsmenu.vidm_selected += optionsmenu.vidm_column_size; - if (optionsmenu.vidm_selected >= (optionsmenu.vidm_column_size*3)) - optionsmenu.vidm_selected %= optionsmenu.vidm_column_size; - if (optionsmenu.vidm_selected >= optionsmenu.vidm_nummodes) - optionsmenu.vidm_selected = optionsmenu.vidm_nummodes - 1; - - M_SetMenuDelay(pid); - } - - else if (M_MenuConfirmPressed(pid)) - { - M_SetMenuDelay(pid); - S_StartSound(NULL, sfx_s3k5b); - if (vid.modenum == optionsmenu.modedescs[optionsmenu.vidm_selected].modenum) - SCR_SetDefaultMode(); - else - { - optionsmenu.vidm_testingmode = 15*TICRATE; - optionsmenu.vidm_previousmode = vid.modenum; - if (!setmodeneeded) // in case the previous setmode was not finished - setmodeneeded = optionsmenu.modedescs[optionsmenu.vidm_selected].modenum + 1; - } - } - - else if (M_MenuBackPressed(pid)) - { - M_SetMenuDelay(pid); - if (currentMenu->prevMenu) - M_SetupNextMenu(currentMenu->prevMenu, false); - else - M_ClearMenus(true); - } - } -} - -// sets whatever device has had its key pressed to the active device. -// 20/05/22: Commented out for now but not deleted as it might still find some use in the future? -/* -static void SetDeviceOnPress(void) -{ - UINT8 i; - - for (i=0; i < MAXDEVICES; i++) - { - if (deviceResponding[i]) - { - CV_SetValue(&cv_usejoystick[0], i); // Force-set this joystick as the current joystick we're using for P1 (which is the only one controlling menus) - CONS_Printf("SetDeviceOnPress: Device for %d set to %d\n", 0, i); - return; - } - } -} -*/ - - -// Prompt a device selection window (just tap any button on the device you want) -void M_ProfileDeviceSelect(INT32 choice) -{ - (void)choice; - - // While we're here, setup the incoming controls menu to reset the scroll & bind status: - optionsmenu.controlscroll = 0; - optionsmenu.bindcontrol = 0; - optionsmenu.bindtimer = 0; - - optionsmenu.lastkey = 0; - optionsmenu.keyheldfor = 0; - - optionsmenu.contx = optionsmenu.tcontx = controlleroffsets[gc_a][0]; - optionsmenu.conty = optionsmenu.tconty = controlleroffsets[gc_a][1]; - - M_SetupNextMenu(&OPTIONS_ProfileControlsDef, false); // Don't set device here anymore. -} - -void M_HandleProfileControls(void) -{ - UINT8 maxscroll = currentMenu->numitems - 5; - M_OptionsTick(); - - optionsmenu.contx += (optionsmenu.tcontx - optionsmenu.contx)/2; - optionsmenu.conty += (optionsmenu.tconty - optionsmenu.conty)/2; - - if (abs(optionsmenu.contx - optionsmenu.tcontx) < 2 && abs(optionsmenu.conty - optionsmenu.tconty) < 2) - { - optionsmenu.contx = optionsmenu.tcontx; - optionsmenu.conty = optionsmenu.tconty; // Avoid awkward 1 px errors. - } - - optionsmenu.controlscroll = itemOn - 3; // very barebones scrolling, but it works just fine for our purpose. - if (optionsmenu.controlscroll > maxscroll) - optionsmenu.controlscroll = maxscroll; - - if (optionsmenu.controlscroll < 0) - optionsmenu.controlscroll = 0; - - // bindings, cancel if timer is depleted. - if (optionsmenu.bindcontrol) - { - optionsmenu.bindtimer--; - if (!optionsmenu.bindtimer) - { - optionsmenu.bindcontrol = 0; // we've gone past the max, just stop. - } - - } -} - -void M_ProfileTryController(INT32 choice) -{ - (void)choice; - - optionsmenu.trycontroller = TICRATE*5; - - // Apply these controls right now on P1's end. - memcpy(&gamecontrol[0], optionsmenu.tempcontrols, sizeof(gamecontroldefault)); -} - -static void M_ProfileControlSaveResponse(INT32 choice) -{ - if (choice == MA_YES) - { - SINT8 belongsto = PR_ProfileUsedBy(optionsmenu.profile); - // Save the profile - optionsmenu.profile->kickstartaccel = cv_dummyprofilekickstart.value; - memcpy(&optionsmenu.profile->controls, optionsmenu.tempcontrols, sizeof(gamecontroldefault)); - - // If this profile is in-use by anyone, apply the changes immediately upon exiting. - // Don't apply the profile itself as that would lead to issues mid-game. - if (belongsto > -1 && belongsto < MAXSPLITSCREENPLAYERS) - { - memcpy(&gamecontrol[belongsto], optionsmenu.tempcontrols, sizeof(gamecontroldefault)); - CV_StealthSetValue(&cv_kickstartaccel[belongsto], cv_dummyprofilekickstart.value); - } - - M_GoBack(0); - } -} - -void M_ProfileControlsConfirm(INT32 choice) -{ - (void)choice; - - //M_StartMessage(M_GetText("Exiting will save the control changes\nfor this Profile.\nIs this okay?\n\nPress (A) to confirm or (B) to cancel"), FUNCPTRCAST(M_ProfileControlSaveResponse), MM_YESNO); - // TODO: Add a graphic for controls saving, instead of obnoxious prompt. - - M_ProfileControlSaveResponse(MA_YES); - - optionsmenu.profile->kickstartaccel = cv_dummyprofilekickstart.value; // Make sure to save kickstart accel. - - // Reapply player 1's real profile. - if (cv_currprofile.value > -1) - { - PR_ApplyProfile(cv_lastprofile[0].value, 0); - } -} - -boolean M_ProfileControlsInputs(INT32 ch) -{ - const UINT8 pid = 0; - (void)ch; - - // By default, accept all inputs. - if (optionsmenu.trycontroller) - { - if (menucmd[pid].dpad_ud || menucmd[pid].dpad_lr || menucmd[pid].buttons) - { - optionsmenu.trycontroller = 5*TICRATE; - } - else - { - optionsmenu.trycontroller--; - } - - if (optionsmenu.trycontroller == 0) - { - // Reset controls to that of the current profile. - profile_t *cpr = PR_GetProfile(cv_currprofile.value); - if (cpr == NULL) - cpr = PR_GetProfile(0); // Creating a profile at boot, revert to guest profile - memcpy(&gamecontrol[0], cpr->controls, sizeof(gamecontroldefault)); - } - - return true; - } - - if (optionsmenu.bindcontrol) - return true; // Eat all inputs there. We'll use a stupid hack in M_Responder instead. - - //SetDeviceOnPress(); // Update device constantly so that we don't stay stuck with otpions saying a device is unavailable just because we're mapping multiple devices... - - if (M_MenuExtraPressed(pid)) - { - // check if we're on a valid menu option... - if (currentMenu->menuitems[itemOn].mvar1) - { - // clear controls for that key - INT32 i; - - for (i = 0; i < MAXINPUTMAPPING; i++) - optionsmenu.tempcontrols[currentMenu->menuitems[itemOn].mvar1][i] = KEY_NULL; - - S_StartSound(NULL, sfx_s3k66); - } - M_SetMenuDelay(pid); - return true; - } - else if (M_MenuBackPressed(pid)) - { - M_ProfileControlsConfirm(0); - M_SetMenuDelay(pid); - return true; - } - - return false; -} - -void M_ProfileSetControl(INT32 ch) -{ - INT32 controln = currentMenu->menuitems[itemOn].mvar1; - UINT8 i; - (void) ch; - - optionsmenu.bindcontrol = 1; // Default to control #1 - - for (i = 0; i < MAXINPUTMAPPING; i++) - { - if (optionsmenu.tempcontrols[controln][i] == KEY_NULL) - { - optionsmenu.bindcontrol = i+1; - break; - } - } - - // If we could find a null key to map into, map there. - // Otherwise, this will stay at 1 which means we'll overwrite the first bound control. - - optionsmenu.bindtimer = TICRATE*5; -} - -// Map the event to the profile. - -#define KEYHOLDFOR 1 -void M_MapProfileControl(event_t *ev) -{ - INT32 c = 0; - UINT8 n = optionsmenu.bindcontrol-1; // # of input to bind - INT32 controln = currentMenu->menuitems[itemOn].mvar1; // gc_ - UINT8 where = n; // By default, we'll save the bind where we're supposed to map. - INT32 i; - - //SetDeviceOnPress(); // Update cv_usejoystick - - // Only consider keydown and joystick events to make sure we ignore ev_mouse and other events - // See also G_MapEventsToControls - switch (ev->type) - { - case ev_keydown: - if (ev->data1 < NUMINPUTS) - { - c = ev->data1; - } -#ifdef PARANOIA - else - { - CONS_Debug(DBG_GAMELOGIC, "Bad downkey input %d\n", ev->data1); - } -#endif - break; - case ev_joystick: - if (ev->data1 >= JOYAXES) - { -#ifdef PARANOIA - CONS_Debug(DBG_GAMELOGIC, "Bad joystick axis event %d\n", ev->data1); -#endif - return; - } - else - { - INT32 deadzone = deadzone = (JOYAXISRANGE * cv_deadzone[0].value) / FRACUNIT; // TODO how properly account for different deadzone cvars for different devices - boolean responsivelr = ((ev->data2 != INT32_MAX) && (abs(ev->data2) >= deadzone)); - boolean responsiveud = ((ev->data3 != INT32_MAX) && (abs(ev->data3) >= deadzone)); - - i = ev->data1; - - if (i >= JOYANALOGS) - { - // The trigger axes are handled specially. - i -= JOYANALOGS; - - if (responsivelr) - { - c = KEY_AXIS1 + (JOYANALOGS * 4) + (i * 2); - } - else if (responsiveud) - { - c = KEY_AXIS1 + (JOYANALOGS * 4) + (i * 2) + 1; - } - } - else - { - // Actual analog sticks - - // Only consider unambiguous assignment. - if (responsivelr == responsiveud) - return; - - if (responsivelr) - { - if (ev->data2 < 0) - { - // Left - c = KEY_AXIS1 + (i * 4); - } - else - { - // Right - c = KEY_AXIS1 + (i * 4) + 1; - } - } - else //if (responsiveud) - { - if (ev->data3 < 0) - { - // Up - c = KEY_AXIS1 + (i * 4) + 2; - } - else - { - // Down - c = KEY_AXIS1 + (i * 4) + 3; - } - } - } - } - break; - default: - return; - } - - // safety result - if (!c) - return; - - // Set menu delay regardless of what we're doing to avoid stupid stuff. - M_SetMenuDelay(0); - - // Check if this particular key (c) is already bound in any slot. - // If that's the case, simply do nothing. - for (i = 0; i < MAXINPUTMAPPING; i++) - { - if (optionsmenu.tempcontrols[controln][i] == c) - { - optionsmenu.bindcontrol = 0; - return; - } - } - - // With the way we do things, there cannot be instances of 'gaps' within the controls, so we don't need to pretend like we need to handle that. - // Unless of course you tamper with the cfg file, but then it's *your* fault, not mine. - - optionsmenu.tempcontrols[controln][where] = c; - optionsmenu.bindcontrol = 0; // not binding anymore - - // If possible, reapply the profile... - // 19/05/22: Actually, no, don't do that, it just fucks everything up in too many cases. - - /* - if (gamestate == GS_MENU) // In menu? Apply this to P1, no questions asked. - { - // Apply the profile's properties to player 1 but keep the last profile cv to p1's ACTUAL profile to revert once we exit. - UINT8 lastp = cv_lastprofile[0].value; - PR_ApplyProfile(PR_GetProfileNum(optionsmenu.profile), 0); - CV_StealthSetValue(&cv_lastprofile[0], lastp); - } - else // != GS_MENU - { - // ONLY apply the profile if it's in use by anything currently. - UINT8 pnum = PR_GetProfileNum(optionsmenu.profile); - for (i = 0; i < MAXSPLITSCREENPLAYERS; i++) - { - if (cv_lastprofile[i].value == pnum) - { - PR_ApplyProfile(pnum, i); - break; - } - } - } - */ -} -#undef KEYHOLDFOR - -void M_HandleItemToggles(INT32 choice) -{ - const INT32 width = 8, height = 4; - INT32 column = itemOn/height, row = itemOn%height; - INT16 next; - UINT8 i; - boolean exitmenu = false; - const UINT8 pid = 0; - - (void) choice; - - if (menucmd[pid].dpad_lr > 0) - { - S_StartSound(NULL, sfx_s3k5b); - column++; - if (((column*height)+row) >= currentMenu->numitems) - column = 0; - next = min(((column*height)+row), currentMenu->numitems-1); - itemOn = next; - - M_SetMenuDelay(pid); - } - - else if (menucmd[pid].dpad_lr < 0) - { - S_StartSound(NULL, sfx_s3k5b); - column--; - if (column < 0) - column = width-1; - if (((column*height)+row) >= currentMenu->numitems) - column--; - next = max(((column*height)+row), 0); - if (next >= currentMenu->numitems) - next = currentMenu->numitems-1; - itemOn = next; - - M_SetMenuDelay(pid); - } - - else if (menucmd[pid].dpad_ud > 0) - { - S_StartSound(NULL, sfx_s3k5b); - row = (row+1) % height; - if (((column*height)+row) >= currentMenu->numitems) - row = 0; - next = min(((column*height)+row), currentMenu->numitems-1); - itemOn = next; - - M_SetMenuDelay(pid); - } - - else if (menucmd[pid].dpad_ud < 0) - { - S_StartSound(NULL, sfx_s3k5b); - row = (row-1) % height; - if (row < 0) - row = height-1; - if (((column*height)+row) >= currentMenu->numitems) - row--; - next = max(((column*height)+row), 0); - if (next >= currentMenu->numitems) - next = currentMenu->numitems-1; - itemOn = next; - - M_SetMenuDelay(pid); - } - - else if (M_MenuConfirmPressed(pid)) - { - M_SetMenuDelay(pid); - if (currentMenu->menuitems[itemOn].mvar1 == 255) - { - //S_StartSound(NULL, sfx_s26d); - if (!shitsfree) - { - shitsfree = TICRATE; - S_StartSound(NULL, sfx_itfree); - } - } - else - if (currentMenu->menuitems[itemOn].mvar1 == 0) - { - INT32 v = cv_items[0].value; - S_StartSound(NULL, sfx_s1b4); - for (i = 0; i < NUMKARTRESULTS-1; i++) - { - if (cv_items[i].value == v) - CV_AddValue(&cv_items[i], 1); - } - } - else - { - if (currentMenu->menuitems[itemOn].mvar2) - { - S_StartSound(NULL, currentMenu->menuitems[itemOn].mvar2); - } - else - { - S_StartSound(NULL, sfx_s1ba); - } - CV_AddValue(&cv_items[currentMenu->menuitems[itemOn].mvar1-1], 1); - } - } - - else if (M_MenuBackPressed(pid)) - { - M_SetMenuDelay(pid); - exitmenu = true; - } - - if (exitmenu) - { - if (currentMenu->prevMenu) - M_SetupNextMenu(currentMenu->prevMenu, false); - else - M_ClearMenus(true); - } -} - -// Check if we have any profile loaded. -void M_CheckProfileData(INT32 choice) -{ - UINT8 np = PR_GetNumProfiles(); - (void) choice; - - if (np < 2) - { - S_StartSound(NULL, sfx_s3k7b); - M_StartMessage("There are no custom profiles.\n\nPress (B)", NULL, MM_NOTHING); - return; - } - - optionsmenu.eraseprofilen = 1; - M_SetupNextMenu(&OPTIONS_DataProfileEraseDef, false); -} - -static void M_EraseProfileResponse(INT32 choice) -{ - if (choice == MA_YES) - { - S_StartSound(NULL, sfx_itrole); // bweh heh heh - - PR_DeleteProfile(optionsmenu.eraseprofilen); - - // Did we bust our current profile..!? - if (cv_currprofile.value == -1) - { - F_StartIntro(); - M_ClearMenus(true); - } - else if (optionsmenu.eraseprofilen > PR_GetNumProfiles()-1) - { - optionsmenu.eraseprofilen--; - } - } -} - -void M_HandleProfileErase(INT32 choice) -{ - const UINT8 pid = 0; - const UINT8 np = PR_GetNumProfiles()-1; - (void) choice; - - if (menucmd[pid].dpad_ud > 0) - { - S_StartSound(NULL, sfx_s3k5b); - optionsmenu.eraseprofilen++; - - if (optionsmenu.eraseprofilen > np) - optionsmenu.eraseprofilen = 1; - - M_SetMenuDelay(pid); - } - else if (menucmd[pid].dpad_ud < 0) - { - S_StartSound(NULL, sfx_s3k5b); - - if (optionsmenu.eraseprofilen == 1) - optionsmenu.eraseprofilen = np; - else - optionsmenu.eraseprofilen--; - - M_SetMenuDelay(pid); - } - else if (M_MenuBackPressed(pid)) - { - M_GoBack(0); - M_SetMenuDelay(pid); - } - else if (M_MenuConfirmPressed(pid)) - { - if (optionsmenu.eraseprofilen == cv_currprofile.value) - M_StartMessage("Your ""\x85""current profile""\x80"" will be erased.\nAre you sure you want to proceed?\nDeleting this profile will also\nreturn you to the title screen.\n\nPress (A) to confirm or (B) to cancel", FUNCPTRCAST(M_EraseProfileResponse), MM_YESNO); - else - M_StartMessage("This profile will be erased.\nAre you sure you want to proceed?\n\nPress (A) to confirm or (B) to cancel", FUNCPTRCAST(M_EraseProfileResponse), MM_YESNO); - - M_SetMenuDelay(pid); - } -} - -// Extras menu; -// this is copypasted from the options menu but all of these are different functions in case we ever want it to look more unique - -struct extrasmenu_s extrasmenu; - -void M_InitExtras(INT32 choice) -{ - (void)choice; - - extrasmenu.ticker = 0; - extrasmenu.offset = 0; - - extrasmenu.extx = 0; - extrasmenu.exty = 0; - extrasmenu.textx = 0; - extrasmenu.texty = 0; - - M_SetupNextMenu(&EXTRAS_MainDef, false); -} - -// For statistics, will maybe remain unused for a while -boolean M_ExtrasQuit(void) -{ - extrasmenu.textx = 140-1; - extrasmenu.texty = 70+1; - - return true; // Always allow quitting, duh. -} - -void M_ExtrasTick(void) -{ - extrasmenu.offset /= 2; - extrasmenu.ticker++; - - extrasmenu.extx += (extrasmenu.textx - extrasmenu.extx)/2; - extrasmenu.exty += (extrasmenu.texty - extrasmenu.exty)/2; - - if (abs(extrasmenu.extx - extrasmenu.exty) < 2) - { - extrasmenu.extx = extrasmenu.textx; - extrasmenu.exty = extrasmenu.texty; // Avoid awkward 1 px errors. - } - - // Move the button for cool animations - if (currentMenu == &EXTRAS_MainDef) - { - M_ExtrasQuit(); // reset the options button. - } - else - { - extrasmenu.textx = 160; - extrasmenu.texty = 50; - } -} - -boolean M_ExtrasInputs(INT32 ch) -{ - - const UINT8 pid = 0; - (void) ch; - - if (menucmd[pid].dpad_ud > 0) - { - extrasmenu.offset += 48; - M_NextOpt(); - S_StartSound(NULL, sfx_s3k5b); - - if (itemOn == 0) - extrasmenu.offset -= currentMenu->numitems*48; - - M_SetMenuDelay(pid); - return true; - } - - else if (menucmd[pid].dpad_ud < 0) - { - extrasmenu.offset -= 48; - M_PrevOpt(); - S_StartSound(NULL, sfx_s3k5b); - - if (itemOn == currentMenu->numitems-1) - extrasmenu.offset += currentMenu->numitems*48; - - M_SetMenuDelay(pid); - return true; - } - - else if (M_MenuConfirmPressed(pid)) - { - - if (currentMenu->menuitems[itemOn].status & IT_TRANSTEXT) - return true; // No. - - extrasmenu.extx = 140; - extrasmenu.exty = 70; // Default position for the currently selected option. - - M_SetMenuDelay(pid); - return false; // Don't eat. - } - return false; -} - -// ===================== -// PAUSE / IN-GAME MENUS -// ===================== -void M_EndModeAttackRun(void) -{ - G_CheckDemoStatus(); // Cancel recording - - if (gamestate == GS_LEVEL || gamestate == GS_INTERMISSION) - Command_ExitGame_f(); - - M_StartControlPanel(); - - currentMenu = &PLAY_TimeAttackDef; - itemOn = currentMenu->lastOn; - - G_SetGamestate(GS_MENU); - S_ChangeMusicInternal("menu", true); - - modeattacking = ATTACKING_NONE; -} - -struct pausemenu_s pausemenu; - -// Pause menu! -void M_OpenPauseMenu(void) -{ - INT32 i = 0; - - currentMenu = &PAUSE_MainDef; - - // Ready the variables - pausemenu.ticker = 0; - - pausemenu.offset = 0; - pausemenu.openoffset = 256; - pausemenu.closing = false; - - currentMenu->lastOn = mpause_continue; // Make sure we select "RESUME GAME" by default - - // Now the hilarious balancing act of deciding what options should be enabled and which ones shouldn't be! - // By default, disable anything sensitive: - - PAUSE_Main[mpause_addons].status = IT_DISABLED; - PAUSE_Main[mpause_changegametype].status = IT_DISABLED; - PAUSE_Main[mpause_switchmap].status = IT_DISABLED; - PAUSE_Main[mpause_restartmap].status = IT_DISABLED; - PAUSE_Main[mpause_tryagain].status = IT_DISABLED; -#ifdef HAVE_DISCORDRPC - PAUSE_Main[mpause_discordrequests].status = IT_DISABLED; -#endif - - PAUSE_Main[mpause_spectate].status = IT_DISABLED; - PAUSE_Main[mpause_entergame].status = IT_DISABLED; - PAUSE_Main[mpause_canceljoin].status = IT_DISABLED; - PAUSE_Main[mpause_spectatemenu].status = IT_DISABLED; - PAUSE_Main[mpause_psetup].status = IT_DISABLED; - - Dummymenuplayer_OnChange(); // Make sure the consvar is within bounds of the amount of splitscreen players we have. - - if (K_CanChangeRules(false)) - { - PAUSE_Main[mpause_psetup].status = IT_STRING | IT_CALL; - - if (server || IsPlayerAdmin(consoleplayer)) - { - PAUSE_Main[mpause_changegametype].status = IT_STRING | IT_KEYHANDLER; - menugametype = gametype; - - PAUSE_Main[mpause_switchmap].status = IT_STRING | IT_CALL; - PAUSE_Main[mpause_restartmap].status = IT_STRING | IT_CALL; - PAUSE_Main[mpause_addons].status = IT_STRING | IT_CALL; - } - } - else if (!netgame && !demo.playback) - { - boolean retryallowed = (modeattacking != ATTACKING_NONE); - if (G_GametypeUsesLives()) - { - for (i = 0; i <= splitscreen; i++) - { - if (players[g_localplayers[i]].lives <= 1) - continue; - retryallowed = true; - break; - } - } - - if (retryallowed) - { - PAUSE_Main[mpause_tryagain].status = IT_STRING | IT_CALL; - } - } - - if (G_GametypeHasSpectators()) - { - if (splitscreen) - PAUSE_Main[mpause_spectatemenu].status = IT_STRING|IT_SUBMENU; - else - { - if (!players[consoleplayer].spectator) - PAUSE_Main[mpause_spectate].status = IT_STRING | IT_CALL; - else if (players[consoleplayer].pflags & PF_WANTSTOJOIN) - PAUSE_Main[mpause_canceljoin].status = IT_STRING | IT_CALL; - else - PAUSE_Main[mpause_entergame].status = IT_STRING | IT_CALL; - } - } -} - -void M_QuitPauseMenu(INT32 choice) -{ - (void)choice; - // M_PauseTick actually handles the quitting when it's been long enough. - pausemenu.closing = true; - pausemenu.openoffset = 4; -} - -void M_PauseTick(void) -{ - pausemenu.offset /= 2; - pausemenu.ticker++; - - if (pausemenu.closing) - { - pausemenu.openoffset *= 2; - if (pausemenu.openoffset > 255) - M_ClearMenus(true); - - } - else - pausemenu.openoffset /= 2; -} - -boolean M_PauseInputs(INT32 ch) -{ - - const UINT8 pid = 0; - (void) ch; - - if (pausemenu.closing) - return true; // Don't allow inputs. - - if (menucmd[pid].dpad_ud < 0) - { - M_SetMenuDelay(pid); - pausemenu.offset -= 50; // Each item is spaced by 50 px - S_StartSound(NULL, sfx_s3k5b); - M_PrevOpt(); - return true; - } - - else if (menucmd[pid].dpad_ud > 0) - { - pausemenu.offset += 50; // Each item is spaced by 50 px - S_StartSound(NULL, sfx_s3k5b); - M_NextOpt(); - M_SetMenuDelay(pid); - return true; - } - - else if (M_MenuBackPressed(pid) || M_MenuButtonPressed(pid, MBT_START)) - { - M_QuitPauseMenu(-1); - M_SetMenuDelay(pid); - return true; - } - return false; -} - -// Change gametype -void M_HandlePauseMenuGametype(INT32 choice) -{ - const UINT8 pid = 0; - const UINT32 forbidden = GTR_FORBIDMP; - - (void)choice; - - if (M_MenuConfirmPressed(pid)) - { - if (menugametype != gametype) - { - M_ClearMenus(true); - COM_ImmedExecute(va("randommap -gt %s", gametypes[menugametype]->name)); - return; - } - - M_SetMenuDelay(pid); - S_StartSound(NULL, sfx_s3k7b); - } - else if (M_MenuExtraPressed(pid)) - { - menugametype = gametype; - M_SetMenuDelay(pid); - S_StartSound(NULL, sfx_s3k7b); - } - else if (menucmd[pid].dpad_lr > 0) - { - M_NextMenuGametype(forbidden); - S_StartSound(NULL, sfx_s3k5b); - M_SetMenuDelay(pid); - } - else if (menucmd[pid].dpad_lr < 0) - { - M_PrevMenuGametype(forbidden); - S_StartSound(NULL, sfx_s3k5b); - M_SetMenuDelay(pid); - } -} - -// Restart map -void M_RestartMap(INT32 choice) -{ - (void)choice; - M_ClearMenus(false); - COM_ImmedExecute("restartlevel"); -} - -// Try again -void M_TryAgain(INT32 choice) -{ - (void)choice; - if (demo.playback) - return; - - if (netgame || !Playing()) // Should never happen! - return; - - M_ClearMenus(false); - - if (modeattacking != ATTACKING_NONE) - { - G_CheckDemoStatus(); // Cancel recording - M_StartTimeAttack(-1); - } - else - { - G_SetRetryFlag(); - } -} - -// Pause spectate / join functions -void M_ConfirmSpectate(INT32 choice) -{ - (void)choice; - // We allow switching to spectator even if team changing is not allowed - M_QuitPauseMenu(-1); - COM_ImmedExecute("changeteam spectator"); -} - -void M_ConfirmEnterGame(INT32 choice) -{ - (void)choice; - if (!cv_allowteamchange.value) - { - M_StartMessage(M_GetText("The server is not allowing\nteam changes at this time.\n\nPress (B)\n"), NULL, MM_NOTHING); - return; - } - M_QuitPauseMenu(-1); - COM_ImmedExecute("changeteam playing"); -} - -static void M_ExitGameResponse(INT32 ch) -{ - if (ch != MA_YES) - return; - - if (modeattacking) - { - M_EndModeAttackRun(); - } - else - { - G_SetExitGameFlag(); - M_ClearMenus(true); - } -} - -void M_EndGame(INT32 choice) -{ - (void)choice; - if (demo.playback) - return; - - if (!Playing()) - return; - - M_StartMessage(M_GetText("Are you sure you want to\nreturn to the menu?\nPress (A) to confirm or (B) to cancel\n"), FUNCPTRCAST(M_ExitGameResponse), MM_YESNO); -} - - -// Replay Playback Menu -void M_SetPlaybackMenuPointer(void) -{ - itemOn = playback_pause; -} - -void M_PlaybackRewind(INT32 choice) -{ - static tic_t lastconfirmtime; - - (void)choice; - - if (!demo.rewinding) - { - if (paused) - { - G_ConfirmRewind(leveltime-1); - paused = true; - S_PauseAudio(); - } - else - demo.rewinding = paused = true; - } - else if (lastconfirmtime + TICRATE/2 < I_GetTime()) - { - lastconfirmtime = I_GetTime(); - G_ConfirmRewind(leveltime); - } - - CV_SetValue(&cv_playbackspeed, 1); -} - -void M_PlaybackPause(INT32 choice) -{ - (void)choice; - - paused = !paused; - - if (demo.rewinding) - { - G_ConfirmRewind(leveltime); - paused = true; - S_PauseAudio(); - } - else if (paused) - S_PauseAudio(); - else - S_ResumeAudio(); - - CV_SetValue(&cv_playbackspeed, 1); -} - -void M_PlaybackFastForward(INT32 choice) -{ - (void)choice; - - if (demo.rewinding) - { - G_ConfirmRewind(leveltime); - paused = false; - S_ResumeAudio(); - } - CV_SetValue(&cv_playbackspeed, cv_playbackspeed.value == 1 ? 4 : 1); -} - -void M_PlaybackAdvance(INT32 choice) -{ - (void)choice; - - paused = false; - TryRunTics(1); - paused = true; -} - -void M_PlaybackSetViews(INT32 choice) -{ - if (choice > 0) - { - if (splitscreen < 3) - G_AdjustView(splitscreen + 2, 0, true); - } - else if (splitscreen) - { - splitscreen--; - R_ExecuteSetViewSize(); - } -} - -void M_PlaybackAdjustView(INT32 choice) -{ - G_AdjustView(itemOn - playback_viewcount, (choice > 0) ? 1 : -1, true); -} - -// this one's rather tricky -void M_PlaybackToggleFreecam(INT32 choice) -{ - (void)choice; - M_ClearMenus(true); - - // remove splitscreen: - splitscreen = 0; - R_ExecuteSetViewSize(); - - P_InitCameraCmd(); // init camera controls - if (!demo.freecam) // toggle on - { - demo.freecam = true; - democam.cam = &camera[0]; // this is rather useful - } - else // toggle off - { - demo.freecam = false; - // reset democam vars: - democam.cam = NULL; - //democam.turnheld = false; - democam.keyboardlook = false; // reset only these. localangle / aiming gets set before the cam does anything anyway - } -} - -void M_PlaybackQuit(INT32 choice) -{ - (void)choice; - G_StopDemo(); - - if (demo.inreplayhut) - M_ReplayHut(choice); - else if (modeattacking) - M_EndModeAttackRun(); - else - D_StartTitle(); -} - -void M_PrepReplayList(void) -{ - size_t i; - - if (extrasmenu.demolist) - Z_Free(extrasmenu.demolist); - - extrasmenu.demolist = Z_Calloc(sizeof(menudemo_t) * sizedirmenu, PU_STATIC, NULL); - - for (i = 0; i < sizedirmenu; i++) - { - if (dirmenu[i][DIR_TYPE] == EXT_UP) - { - extrasmenu.demolist[i].type = MD_SUBDIR; - sprintf(extrasmenu.demolist[i].title, "UP"); - } - else if (dirmenu[i][DIR_TYPE] == EXT_FOLDER) - { - extrasmenu.demolist[i].type = MD_SUBDIR; - strncpy(extrasmenu.demolist[i].title, dirmenu[i] + DIR_STRING, 64); - } - else - { - extrasmenu.demolist[i].type = MD_NOTLOADED; - snprintf(extrasmenu.demolist[i].filepath, sizeof extrasmenu.demolist[i].filepath, - // 255 = UINT8 limit. dirmenu entries are restricted to this length (see DIR_LEN). - "%s%.255s", menupath, dirmenu[i] + DIR_STRING); - sprintf(extrasmenu.demolist[i].title, "....."); - } - } -} - - -void M_ReplayHut(INT32 choice) -{ - (void)choice; - - extrasmenu.replayScrollTitle = 0; - extrasmenu.replayScrollDelay = TICRATE; - extrasmenu.replayScrollDir = 1; - - if (!demo.inreplayhut) - { - snprintf(menupath, 1024, "%s"PATHSEP"media"PATHSEP"replay"PATHSEP"online"PATHSEP, srb2home); - menupathindex[(menudepthleft = menudepth-1)] = strlen(menupath); - } - if (!preparefilemenu(false, true)) - { - M_StartMessage("No replays found.\n\nPress (B)\n", NULL, MM_NOTHING); - return; - } - else if (!demo.inreplayhut) - dir_on[menudepthleft] = 0; - demo.inreplayhut = true; - - extrasmenu.replayScrollTitle = 0; extrasmenu.replayScrollDelay = TICRATE; extrasmenu.replayScrollDir = 1; - - M_PrepReplayList(); - - menuactive = true; - M_SetupNextMenu(&EXTRAS_ReplayHutDef, false); - //G_SetGamestate(GS_TIMEATTACK); - //titlemapinaction = TITLEMAP_OFF; // Nope don't give us HOMs please - - demo.rewinding = false; - CL_ClearRewinds(); - - //S_ChangeMusicInternal("replst", true); -} - -// key handler -void M_HandleReplayHutList(INT32 choice) -{ - - const UINT8 pid = 0; - (void) choice; - - if (menucmd[pid].dpad_ud < 0) - { - if (dir_on[menudepthleft]) - dir_on[menudepthleft]--; - else - return; - //M_PrevOpt(); - - S_StartSound(NULL, sfx_s3k5b); - M_SetMenuDelay(pid); - extrasmenu.replayScrollTitle = 0; extrasmenu.replayScrollDelay = TICRATE; extrasmenu.replayScrollDir = 1; - } - - else if (menucmd[pid].dpad_ud > 0) - { - if (dir_on[menudepthleft] < sizedirmenu-1) - dir_on[menudepthleft]++; - else - return; - //itemOn = 0; // Not M_NextOpt because that would take us to the extra dummy item - - S_StartSound(NULL, sfx_s3k5b); - M_SetMenuDelay(pid); - extrasmenu.replayScrollTitle = 0; extrasmenu.replayScrollDelay = TICRATE; extrasmenu.replayScrollDir = 1; - } - - else if (M_MenuBackPressed(pid)) - { - M_SetMenuDelay(pid); - M_QuitReplayHut(); - } - - else if (M_MenuConfirmPressed(pid)) - { - M_SetMenuDelay(pid); - switch (dirmenu[dir_on[menudepthleft]][DIR_TYPE]) - { - case EXT_FOLDER: - strcpy(&menupath[menupathindex[menudepthleft]],dirmenu[dir_on[menudepthleft]]+DIR_STRING); - if (menudepthleft) - { - menupathindex[--menudepthleft] = strlen(menupath); - menupath[menupathindex[menudepthleft]] = 0; - - if (!preparefilemenu(false, true)) - { - S_StartSound(NULL, sfx_s224); - M_StartMessage(va("%c%s\x80\nThis folder is empty.\n\nPress (B)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), M_AddonsHeaderPath()),NULL,MM_NOTHING); - menupath[menupathindex[++menudepthleft]] = 0; - - if (!preparefilemenu(true, true)) - { - M_QuitReplayHut(); - return; - } - } - else - { - S_StartSound(NULL, sfx_s3k5b); - dir_on[menudepthleft] = 1; - M_PrepReplayList(); - } - } - else - { - S_StartSound(NULL, sfx_s26d); - M_StartMessage(va("%c%s\x80\nThis folder is too deep to navigate to!\n\nPress (B)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), M_AddonsHeaderPath()),NULL,MM_NOTHING); - menupath[menupathindex[menudepthleft]] = 0; - } - break; - case EXT_UP: - S_StartSound(NULL, sfx_s3k5b); - menupath[menupathindex[++menudepthleft]] = 0; - if (!preparefilemenu(false, true)) - { - M_QuitReplayHut(); - return; - } - M_PrepReplayList(); - break; - default: - // We can't just use M_SetupNextMenu because that'll run ReplayDef's quitroutine and boot us back to the title screen! - currentMenu->lastOn = itemOn; - currentMenu = &EXTRAS_ReplayStartDef; - - extrasmenu.replayScrollTitle = 0; extrasmenu.replayScrollDelay = TICRATE; extrasmenu.replayScrollDir = 1; - - switch (extrasmenu.demolist[dir_on[menudepthleft]].addonstatus) - { - case DFILE_ERROR_CANNOTLOAD: - // Only show "Watch Replay Without Addons" - EXTRAS_ReplayStart[0].status = IT_DISABLED; - EXTRAS_ReplayStart[1].status = IT_CALL|IT_STRING; - //EXTRAS_ReplayStart[1].alphaKey = 0; - EXTRAS_ReplayStart[2].status = IT_DISABLED; - itemOn = 1; - break; - - case DFILE_ERROR_NOTLOADED: - case DFILE_ERROR_INCOMPLETEOUTOFORDER: - // Show "Load Addons and Watch Replay" and "Watch Replay Without Addons" - EXTRAS_ReplayStart[0].status = IT_CALL|IT_STRING; - EXTRAS_ReplayStart[1].status = IT_CALL|IT_STRING; - //EXTRAS_ReplayStart[1].alphaKey = 10; - EXTRAS_ReplayStart[2].status = IT_DISABLED; - itemOn = 0; - break; - - case DFILE_ERROR_EXTRAFILES: - case DFILE_ERROR_OUTOFORDER: - default: - // Show "Watch Replay" - EXTRAS_ReplayStart[0].status = IT_DISABLED; - EXTRAS_ReplayStart[1].status = IT_DISABLED; - EXTRAS_ReplayStart[2].status = IT_CALL|IT_STRING; - //EXTRAS_ReplayStart[2].alphaKey = 0; - itemOn = 2; - break; - } - } - } -} - -boolean M_QuitReplayHut(void) -{ - // D_StartTitle does its own wipe, since GS_TIMEATTACK is now a complete gamestate. - menuactive = false; - D_StartTitle(); - - if (extrasmenu.demolist) - Z_Free(extrasmenu.demolist); - extrasmenu.demolist = NULL; - - demo.inreplayhut = false; - - return true; -} - -void M_HutStartReplay(INT32 choice) -{ - (void)choice; - - M_ClearMenus(false); - demo.loadfiles = (itemOn == 0); - demo.ignorefiles = (itemOn != 0); - - G_DoPlayDemo(extrasmenu.demolist[dir_on[menudepthleft]].filepath); -} - - -static void Splitplayers_OnChange(void) -{ -#if 0 - if (cv_splitplayers.value < setupm_pselect) - setupm_pselect = 1; -#endif -} - -// Misc menus - -// Addons menu: (Merely copypasted, original code by toaster) - -void M_Addons(INT32 choice) -{ - const char *pathname = "."; - - (void)choice; - -#if 1 - if (cv_addons_option.value == 0) - pathname = addonsdir; - else if (cv_addons_option.value == 1) - pathname = srb2home; - else if (cv_addons_option.value == 2) - pathname = srb2path; - else -#endif - if (cv_addons_option.value == 3 && *cv_addons_folder.string != '\0') - pathname = cv_addons_folder.string; - - strlcpy(menupath, pathname, 1024); - menupathindex[(menudepthleft = menudepth-1)] = strlen(menupath) + 1; - - if (menupath[menupathindex[menudepthleft]-2] != PATHSEP[0]) - { - menupath[menupathindex[menudepthleft]-1] = PATHSEP[0]; - menupath[menupathindex[menudepthleft]] = 0; - } - else - --menupathindex[menudepthleft]; - - if (!preparefilemenu(false, false)) - { - M_StartMessage(va("No files/folders found.\n\n%s\n\nPress (B)\n", LOCATIONSTRING1),NULL,MM_NOTHING); - return; - } - else - dir_on[menudepthleft] = 0; - - MISC_AddonsDef.lastOn = 0; // Always start on search - - MISC_AddonsDef.prevMenu = currentMenu; - M_SetupNextMenu(&MISC_AddonsDef, false); -} - - -char *M_AddonsHeaderPath(void) -{ - UINT32 len; - static char header[1024]; - - strlcpy(header, va("%s folder%s", cv_addons_option.string, menupath+menupathindex[menudepth-1]-1), 1024); - len = strlen(header); - if (len > 34) - { - len = len-34; - header[len] = header[len+1] = header[len+2] = '.'; - } - else - len = 0; - - return header+len; -} - -#define UNEXIST S_StartSound(NULL, sfx_s26d);\ - M_SetupNextMenu(MISC_AddonsDef.prevMenu, false);\ - M_StartMessage(va("\x82%s\x80\nThis folder no longer exists!\nAborting to main menu.\n\nPress (B)\n", M_AddonsHeaderPath()),NULL,MM_NOTHING) - -#define CLEARNAME Z_Free(refreshdirname);\ - refreshdirname = NULL - -static boolean prevmajormods = false; - -static void M_AddonsClearName(INT32 choice) -{ - if (!majormods || prevmajormods) - { - CLEARNAME; - } - M_StopMessage(choice); -} - -// Handles messages for addon errors. -void M_AddonsRefresh(void) -{ - if ((refreshdirmenu & REFRESHDIR_NORMAL) && !preparefilemenu(true, false)) - { - UNEXIST; - if (refreshdirname) - { - CLEARNAME; - } - return;// true; - } - -#ifdef DEVELOP - prevmajormods = majormods; -#else - if (!majormods && prevmajormods) - prevmajormods = false; -#endif - - if ((refreshdirmenu & REFRESHDIR_ADDFILE) || (majormods && !prevmajormods)) - { - char *message = NULL; - - if (refreshdirmenu & REFRESHDIR_NOTLOADED) - { - S_StartSound(NULL, sfx_s26d); - if (refreshdirmenu & REFRESHDIR_MAX) - message = va("%c%s\x80\nMaximum number of addons reached.\nA file could not be loaded.\nIf you wish to play with this addon, restart the game to clear existing ones.\n\nPress (B)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), refreshdirname); - else - message = va("%c%s\x80\nA file was not loaded.\nCheck the console log for more info.\n\nPress (B)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), refreshdirname); - } - else if (refreshdirmenu & (REFRESHDIR_WARNING|REFRESHDIR_ERROR)) - { - S_StartSound(NULL, sfx_s224); - message = va("%c%s\x80\nA file was loaded with %s.\nCheck the console log for more info.\n\nPress (B)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), refreshdirname, ((refreshdirmenu & REFRESHDIR_ERROR) ? "errors" : "warnings")); - } - else if (majormods && !prevmajormods) - { - S_StartSound(NULL, sfx_s221); - message = va("%c%s\x80\nYou've loaded a gameplay-modifying addon.\n\nRecord Attack has been disabled, but you\ncan still play alone in local Multiplayer.\n\nIf you wish to play Record Attack mode, restart the game to disable loaded addons.\n\nPress (B)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), refreshdirname); - prevmajormods = majormods; - } - - if (message) - { - M_StartMessage(message,FUNCPTRCAST(M_AddonsClearName),MM_YESNO); - return;// true; - } - - S_StartSound(NULL, sfx_s221); - CLEARNAME; - } - - return;// false; -} - -static void M_AddonExec(INT32 ch) -{ - if (ch == MA_YES) - { - S_StartSound(NULL, sfx_zoom); - COM_BufAddText(va("exec \"%s%s\"", menupath, dirmenu[dir_on[menudepthleft]]+DIR_STRING)); - } -} - -static void M_UpdateAddonsSearch(void) -{ - menusearch[0] = strlen(cv_dummyaddonsearch.string); - strlcpy(menusearch+1, cv_dummyaddonsearch.string, MAXSTRINGLENGTH); - if (!cv_addons_search_case.value) - strupr(menusearch+1); - -#if 0 // much slower - if (!preparefilemenu(true, false)) - { - UNEXIST; - return; - } -#else // streamlined - searchfilemenu(NULL); -#endif -} - -void M_HandleAddons(INT32 choice) -{ - const UINT8 pid = 0; - boolean exitmenu = false; // exit to previous menu - - (void) choice; - - if (menucmd[pid].dpad_ud > 0) - { - if (dir_on[menudepthleft] < sizedirmenu-1) - { - dir_on[menudepthleft]++; - S_StartSound(NULL, sfx_s3k5b); - } - else if (M_NextOpt()) - { - S_StartSound(NULL, sfx_s3k5b); - } - M_SetMenuDelay(pid); - } - else if (menucmd[pid].dpad_ud < 0) - { - if (dir_on[menudepthleft]) - { - dir_on[menudepthleft]--; - S_StartSound(NULL, sfx_s3k5b); - } - else if (M_PrevOpt()) - { - S_StartSound(NULL, sfx_s3k5b); - } - M_SetMenuDelay(pid); - } - - else if (M_MenuButtonPressed(pid, MBT_L)) - { - UINT8 i; - for (i = numaddonsshown; i && (dir_on[menudepthleft] < sizedirmenu-1); i--) - dir_on[menudepthleft]++; - - S_StartSound(NULL, sfx_s3k5b); - M_SetMenuDelay(pid); - } - - else if (M_MenuButtonPressed(pid, MBT_R)) - { - UINT8 i; - for (i = numaddonsshown; i && (dir_on[menudepthleft]); i--) - dir_on[menudepthleft]--; - - S_StartSound(NULL, sfx_s3k5b); - M_SetMenuDelay(pid); - } - - else if (M_MenuConfirmPressed(pid)) - { - boolean refresh = true; - M_SetMenuDelay(pid); - - if (!dirmenu[dir_on[menudepthleft]]) - S_StartSound(NULL, sfx_s26d); - else - { - switch (dirmenu[dir_on[menudepthleft]][DIR_TYPE]) - { - case EXT_FOLDER: - strcpy(&menupath[menupathindex[menudepthleft]],dirmenu[dir_on[menudepthleft]]+DIR_STRING); - if (menudepthleft) - { - menupathindex[--menudepthleft] = strlen(menupath); - menupath[menupathindex[menudepthleft]] = 0; - - if (!preparefilemenu(false, false)) - { - S_StartSound(NULL, sfx_s224); - M_StartMessage(va("%c%s\x80\nThis folder is empty.\n\nPress (B)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), M_AddonsHeaderPath()),NULL,MM_NOTHING); - menupath[menupathindex[++menudepthleft]] = 0; - - if (!preparefilemenu(true, false)) - { - UNEXIST; - return; - } - } - else - { - S_StartSound(NULL, sfx_s3k5b); - dir_on[menudepthleft] = 1; - } - refresh = false; - } - else - { - S_StartSound(NULL, sfx_s26d); - M_StartMessage(va("%c%s\x80\nThis folder is too deep to navigate to!\n\nPress (B)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), M_AddonsHeaderPath()),NULL,MM_NOTHING); - menupath[menupathindex[menudepthleft]] = 0; - } - break; - - case EXT_UP: - S_StartSound(NULL, sfx_s3k5b); - menupath[menupathindex[++menudepthleft]] = 0; - if (!preparefilemenu(false, false)) - { - UNEXIST; - return; - } - break; - - case EXT_TXT: - M_StartMessage(va("%c%s\x80\nThis file may not be a console script.\nAttempt to run anyways? \n\nPress (A) to confirm or (B) to cancel\n\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), dirmenu[dir_on[menudepthleft]]+DIR_STRING),FUNCPTRCAST(M_AddonExec),MM_YESNO); - break; - - case EXT_CFG: - M_StartMessage(va("%c%s\x80\nThis file may modify your settings.\nAttempt to run anyways? \n\nPress (A) to confirm or (B) to cancel\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), dirmenu[dir_on[menudepthleft]]+DIR_STRING),FUNCPTRCAST(M_AddonExec),MM_YESNO); - break; - - case EXT_LUA: - case EXT_SOC: - case EXT_WAD: -#ifdef USE_KART - case EXT_KART: -#endif - case EXT_PK3: - COM_BufAddText(va("addfile \"%s%s\"", menupath, dirmenu[dir_on[menudepthleft]]+DIR_STRING)); - break; - - default: - S_StartSound(NULL, sfx_s26d); - } - - if (refresh) - refreshdirmenu |= REFRESHDIR_NORMAL; - } - } - else if (M_MenuBackPressed(pid)) - { - exitmenu = true; - M_SetMenuDelay(pid); - } - - - if (exitmenu) - { - closefilemenu(true); - - // Secret menu! - //MainMenu[secrets].status = (M_AnySecretUnlocked()) ? (IT_STRING | IT_CALL) : (IT_DISABLED); - - if (currentMenu->prevMenu) - M_SetupNextMenu(currentMenu->prevMenu, false); - else - M_ClearMenus(true); - - M_SetMenuDelay(pid); - } -} - -// Opening manual -void M_Manual(INT32 choice) -{ - (void)choice; - - MISC_ManualDef.prevMenu = (choice == INT32_MAX ? NULL : currentMenu); - M_SetupNextMenu(&MISC_ManualDef, true); -} - -// Challenges menu - -struct challengesmenu_s challengesmenu; - -menu_t *M_InterruptMenuWithChallenges(menu_t *desiredmenu) -{ - UINT8 i; - - M_UpdateUnlockablesAndExtraEmblems(false); - - if ((challengesmenu.pending = challengesmenu.requestnew = (M_GetNextAchievedUnlock() < MAXUNLOCKABLES))) - { - MISC_ChallengesDef.prevMenu = desiredmenu; - } - - if (challengesmenu.pending || desiredmenu == NULL) - { - challengesmenu.currentunlock = MAXUNLOCKABLES; - challengesmenu.unlockcondition = NULL; - - M_PopulateChallengeGrid(); - if (gamedata->challengegrid) - challengesmenu.extradata = M_ChallengeGridExtraData(); - - memset(setup_explosions, 0, sizeof(setup_explosions)); - memset(&challengesmenu.unlockcount, 0, sizeof(challengesmenu.unlockcount)); - for (i = 0; i < MAXUNLOCKABLES; i++) - { - if (!unlockables[i].conditionset) - { - continue; - } - - challengesmenu.unlockcount[CC_TOTAL]++; - - if (!gamedata->unlocked[i]) - { - continue; - } - - challengesmenu.unlockcount[CC_UNLOCKED]++; - } - - return &MISC_ChallengesDef; - } - - return desiredmenu; -} - -static void M_ChallengesAutoFocus(UINT8 unlockid, boolean fresh) -{ - UINT8 i; - SINT8 work; - - if (unlockid >= MAXUNLOCKABLES) - return; - - challengesmenu.currentunlock = unlockid; - challengesmenu.unlockcondition = M_BuildConditionSetString(challengesmenu.currentunlock); - challengesmenu.unlockanim = 0; - - if (gamedata->challengegrid == NULL || challengesmenu.extradata == NULL) - return; - - for (i = 0; i < (CHALLENGEGRIDHEIGHT * gamedata->challengegridwidth); i++) - { - if (gamedata->challengegrid[i] != unlockid) - { - // Not what we're looking for. - continue; - } - - if (challengesmenu.extradata[i] & CHE_CONNECTEDLEFT) - { - // no need to check for CHE_CONNECTEDUP in linear iteration - continue; - } - - // Helper calculation for non-fresh scrolling. - work = (challengesmenu.col + challengesmenu.focusx); - - challengesmenu.col = challengesmenu.hilix = i/CHALLENGEGRIDHEIGHT; - challengesmenu.row = challengesmenu.hiliy = i%CHALLENGEGRIDHEIGHT; - - if (fresh) - { - // We're just entering the menu. Immediately jump to the desired position... - challengesmenu.focusx = 0; - // ...and since the menu is even-width, randomly select whether it's left or right of center. - if (!unlockables[unlockid].majorunlock - && M_RandomChance(FRACUNIT/2)) - challengesmenu.focusx--; - } - else - { - // We're jumping between multiple unlocks in sequence. Get the difference (looped from -range/2 < work <= range/2). - work -= challengesmenu.col; - if (work <= -gamedata->challengegridwidth/2) - work += gamedata->challengegridwidth; - else if (work >= gamedata->challengegridwidth/2) - work -= gamedata->challengegridwidth; - - if (work > 0) - { - // We only need to scroll as far as the rightward edge. - if (unlockables[unlockid].majorunlock) - { - work--; - challengesmenu.col++; - if (challengesmenu.col >= gamedata->challengegridwidth) - challengesmenu.col = 0; - } - - // Offset right, scroll left? - if (work > LEFTUNLOCKSCROLL) - { - work -= LEFTUNLOCKSCROLL; - challengesmenu.focusx = LEFTUNLOCKSCROLL; - } - else - { - challengesmenu.focusx = work; - work = 0; - } - } - else if (work < 0) - { - // Offset left, scroll right? - if (work < -RIGHTUNLOCKSCROLL) - { - challengesmenu.focusx = -RIGHTUNLOCKSCROLL; - work += RIGHTUNLOCKSCROLL; - } - else - { - challengesmenu.focusx = work; - work = 0; - } - } - else - { - // We're right where we want to be. - challengesmenu.focusx = 0; - } - - // And put the pixel-based scrolling in play, too. - challengesmenu.offset = -work*16; - } - - break; - } -} - -void M_Challenges(INT32 choice) -{ - UINT8 i; - (void)choice; - - M_InterruptMenuWithChallenges(NULL); - MISC_ChallengesDef.prevMenu = currentMenu; - - if (gamedata->challengegrid != NULL && !challengesmenu.pending) - { - UINT8 selection[MAXUNLOCKABLES]; - UINT8 numunlocks = 0; - - // Get a random available unlockable. - for (i = 0; i < MAXUNLOCKABLES; i++) - { - if (!unlockables[i].conditionset) - { - continue; - } - - if (!gamedata->unlocked[i]) - { - continue; - } - - selection[numunlocks++] = i; - } - - if (!numunlocks) - { - // ...OK, get a random unlockable. - for (i = 0; i < MAXUNLOCKABLES; i++) - { - if (!unlockables[i].conditionset) - { - continue; - } - - selection[numunlocks++] = i; - } - } - - M_ChallengesAutoFocus(selection[M_RandomKey(numunlocks)], true); - } - - M_SetupNextMenu(&MISC_ChallengesDef, false); -} - -void M_ChallengesTick(void) -{ - const UINT8 pid = 0; - UINT8 i, newunlock = MAXUNLOCKABLES; - boolean fresh = (challengesmenu.currentunlock >= MAXUNLOCKABLES); - - // Ticking - challengesmenu.ticker++; - challengesmenu.offset /= 2; - for (i = 0; i < CSEXPLOSIONS; i++) - { - if (setup_explosions[i].tics > 0) - setup_explosions[i].tics--; - } - if (challengesmenu.unlockcount[CC_ANIM] > 0) - challengesmenu.unlockcount[CC_ANIM]--; - M_CupSelectTick(); - - if (challengesmenu.pending) - { - // Pending mode. - - if (challengesmenu.requestnew) - { - // The menu apparatus is requesting a new unlock. - challengesmenu.requestnew = false; - if ((newunlock = M_GetNextAchievedUnlock()) < MAXUNLOCKABLES) - { - // We got one! - M_ChallengesAutoFocus(newunlock, fresh); - } - else - { - // All done! Let's save the unlocks we've busted open. - challengesmenu.pending = false; - G_SaveGameData(); - } - } - else if (challengesmenu.fade < 5) - { - // Fade increase. - challengesmenu.fade++; - } - else - { - // Unlock sequence. - tic_t nexttime = M_MenuExtraHeld(pid) ? (UNLOCKTIME*2) : MAXUNLOCKTIME; - - if (++challengesmenu.unlockanim >= nexttime) - { - challengesmenu.requestnew = true; - } - - if (challengesmenu.currentunlock < MAXUNLOCKABLES - && challengesmenu.unlockanim == UNLOCKTIME) - { - // Unlock animation... also tied directly to the actual unlock! - gamedata->unlocked[challengesmenu.currentunlock] = true; - M_UpdateUnlockablesAndExtraEmblems(true); - - // Update shown description just in case..? - challengesmenu.unlockcondition = M_BuildConditionSetString(challengesmenu.currentunlock); - - challengesmenu.unlockcount[CC_TALLY]++; - challengesmenu.unlockcount[CC_ANIM]++; - - Z_Free(challengesmenu.extradata); - if ((challengesmenu.extradata = M_ChallengeGridExtraData())) - { - unlockable_t *ref = &unlockables[challengesmenu.currentunlock]; - UINT16 bombcolor = SKINCOLOR_NONE; - - if (ref->color != SKINCOLOR_NONE && ref->color < numskincolors) - { - bombcolor = ref->color; - } - else switch (ref->type) - { - case SECRET_SKIN: - { - INT32 skin = M_UnlockableSkinNum(ref); - if (skin != -1) - { - bombcolor = skins[skin].prefcolor; - } - break; - } - case SECRET_FOLLOWER: - { - INT32 skin = M_UnlockableFollowerNum(ref); - if (skin != -1) - { - bombcolor = K_GetEffectiveFollowerColor(followers[skin].defaultcolor, cv_playercolor[0].value); - } - break; - } - default: - break; - } - - if (bombcolor == SKINCOLOR_NONE) - { - bombcolor = cv_playercolor[0].value; - } - - i = (ref->majorunlock && M_RandomChance(FRACUNIT/2)) ? 1 : 0; - M_SetupReadyExplosions(false, challengesmenu.hilix, challengesmenu.hiliy+i, bombcolor); - if (ref->majorunlock) - { - M_SetupReadyExplosions(false, challengesmenu.hilix+1, challengesmenu.hiliy+(1-i), bombcolor); - } - - S_StartSound(NULL, sfx_s3k4e); - } - } - } - } - else - { - - // Tick down the tally. (currently not visible) - /*if ((challengesmenu.ticker & 1) - && challengesmenu.unlockcount[CC_TALLY] > 0) - { - challengesmenu.unlockcount[CC_TALLY]--; - challengesmenu.unlockcount[CC_UNLOCKED]++; - }*/ - - if (challengesmenu.fade > 0) - { - // Fade decrease. - challengesmenu.fade--; - } - } -} - -boolean M_ChallengesInputs(INT32 ch) -{ - const UINT8 pid = 0; - UINT8 i; - const boolean start = M_MenuButtonPressed(pid, MBT_START); - const boolean move = (menucmd[pid].dpad_ud != 0 || menucmd[pid].dpad_lr != 0); - (void) ch; - - if (challengesmenu.fade) - { - ; - } -#ifdef DEVELOP - else if (M_MenuExtraPressed(pid) && challengesmenu.extradata) // debugging - { - if (challengesmenu.currentunlock < MAXUNLOCKABLES) - { - Z_Free(gamedata->challengegrid); - gamedata->challengegrid = NULL; - gamedata->challengegridwidth = 0; - M_PopulateChallengeGrid(); - Z_Free(challengesmenu.extradata); - challengesmenu.extradata = M_ChallengeGridExtraData(); - - M_ChallengesAutoFocus(challengesmenu.currentunlock, true); - - challengesmenu.pending = true; - } - return true; - } -#endif - else - { - if (M_MenuBackPressed(pid) || start) - { - M_GoBack(0); - M_SetMenuDelay(pid); - - Z_Free(challengesmenu.extradata); - challengesmenu.extradata = NULL; - - challengesmenu.unlockcondition = NULL; - - return true; - } - - if (challengesmenu.extradata != NULL && move) - { - // Determine movement around the grid - // For right/down movement, we can pre-determine the number of steps based on extradata. - // For left/up movement, we can't - we have to be ready to iterate twice, and break early if we don't run into a large tile. - - if (menucmd[pid].dpad_ud > 0) - { - i = 2; - while (i > 0) - { - if (challengesmenu.row < CHALLENGEGRIDHEIGHT-1) - { - challengesmenu.row++; - } - else - { - challengesmenu.row = 0; - } - if (!(challengesmenu.extradata[ - (challengesmenu.col * CHALLENGEGRIDHEIGHT) - + challengesmenu.row] - & CHE_CONNECTEDUP)) - { - break; - } - i--; - } - S_StartSound(NULL, sfx_s3k5b); - M_SetMenuDelay(pid); - } - else if (menucmd[pid].dpad_ud < 0) - { - i = (challengesmenu.extradata[ - (challengesmenu.col * CHALLENGEGRIDHEIGHT) - + challengesmenu.row] - & CHE_CONNECTEDUP) ? 2 : 1; - while (i > 0) - { - if (challengesmenu.row > 0) - { - challengesmenu.row--; - } - else - { - challengesmenu.row = CHALLENGEGRIDHEIGHT-1; - } - i--; - } - S_StartSound(NULL, sfx_s3k5b); - M_SetMenuDelay(pid); - } - - if (menucmd[pid].dpad_lr > 0) - { - i = 2; - while (i > 0) - { - // Slide the focus counter to movement, if we can. - if (challengesmenu.focusx > -RIGHTUNLOCKSCROLL) - { - challengesmenu.focusx--; - } - - // Step the actual column right. - if (challengesmenu.col < gamedata->challengegridwidth-1) - { - challengesmenu.col++; - } - else - { - challengesmenu.col = 0; - } - - if (!(challengesmenu.extradata[ - (challengesmenu.col * CHALLENGEGRIDHEIGHT) - + challengesmenu.row] - & CHE_CONNECTEDLEFT)) - { - break; - } - - i--; - } - S_StartSound(NULL, sfx_s3k5b); - M_SetMenuDelay(pid); - } - else if (menucmd[pid].dpad_lr < 0) - { - i = (challengesmenu.extradata[ - (challengesmenu.col * CHALLENGEGRIDHEIGHT) - + challengesmenu.row] - & CHE_CONNECTEDLEFT) ? 2 : 1; - while (i > 0) - { - // Slide the focus counter to movement, if we can. - if (challengesmenu.focusx < LEFTUNLOCKSCROLL) - { - challengesmenu.focusx++; - } - - // Step the actual column left. - if (challengesmenu.col > 0) - { - challengesmenu.col--; - } - else - { - challengesmenu.col = gamedata->challengegridwidth-1; - } - - i--; - } - S_StartSound(NULL, sfx_s3k5b); - M_SetMenuDelay(pid); - } - - // After movement has been determined, figure out the current selection. - i = (challengesmenu.col * CHALLENGEGRIDHEIGHT) + challengesmenu.row; - challengesmenu.currentunlock = (gamedata->challengegrid[i]); - challengesmenu.unlockcondition = M_BuildConditionSetString(challengesmenu.currentunlock); - - challengesmenu.hilix = challengesmenu.col; - challengesmenu.hiliy = challengesmenu.row; - - if (challengesmenu.currentunlock < MAXUNLOCKABLES - && unlockables[challengesmenu.currentunlock].majorunlock) - { - // Adjust highlight coordinates up/to the left for large tiles. - - if (challengesmenu.hiliy > 0 && (challengesmenu.extradata[i] & CHE_CONNECTEDUP)) - { - challengesmenu.hiliy--; - } - - if ((challengesmenu.extradata[i] & CHE_CONNECTEDLEFT)) - { - if (challengesmenu.hilix > 0) - { - challengesmenu.hilix--; - } - else - { - challengesmenu.hilix = gamedata->challengegridwidth-1; - } - } - } - - return true; - } - - if (M_MenuConfirmPressed(pid) - && challengesmenu.currentunlock < MAXUNLOCKABLES - && gamedata->unlocked[challengesmenu.currentunlock]) - { - switch (unlockables[challengesmenu.currentunlock].type) - { - case SECRET_ALTTITLE: - CV_AddValue(&cv_alttitle, 1); - S_StartSound(NULL, sfx_s3kc3s); - M_SetMenuDelay(pid); - break; - default: - break; - } - - return true; - } - } - - return true; -} - -// Statistics menu - -struct statisticsmenu_s statisticsmenu; - -void M_Statistics(INT32 choice) -{ - UINT16 i = 0; - - (void)choice; - - statisticsmenu.maplist = Z_Malloc(sizeof(UINT16) * nummapheaders, PU_STATIC, NULL); - statisticsmenu.nummaps = 0; - - for (i = 0; i < nummapheaders; i++) - { - if (!mapheaderinfo[i]) - continue; - - // Check for no visibility + legacy box - if (mapheaderinfo[i]->menuflags & (LF2_NOTIMEATTACK|LF2_HIDEINSTATS|LF2_HIDEINMENU)) - continue; - - // Check for completion - if ((mapheaderinfo[i]->menuflags & LF2_FINISHNEEDED) - && !(mapheaderinfo[i]->mapvisited & MV_BEATEN)) - continue; - - // Check for unlock - if (M_MapLocked(i+1)) - continue; - - statisticsmenu.maplist[statisticsmenu.nummaps++] = i; - } - statisticsmenu.maplist[statisticsmenu.nummaps] = NEXTMAP_INVALID; - statisticsmenu.maxscroll = (statisticsmenu.nummaps + M_CountMedals(true, true) + 2) - 10; - statisticsmenu.location = 0; - - if (statisticsmenu.maxscroll < 0) - { - statisticsmenu.maxscroll = 0; - } - - MISC_StatisticsDef.prevMenu = currentMenu; - M_SetupNextMenu(&MISC_StatisticsDef, false); -} - -boolean M_StatisticsInputs(INT32 ch) -{ - const UINT8 pid = 0; - - (void)ch; - - if (M_MenuBackPressed(pid)) - { - M_GoBack(0); - M_SetMenuDelay(pid); - - Z_Free(statisticsmenu.maplist); - statisticsmenu.maplist = NULL; - - return true; - } - - if (M_MenuExtraPressed(pid)) - { - if (statisticsmenu.location > 0) - { - statisticsmenu.location = 0; - S_StartSound(NULL, sfx_s3k5b); - M_SetMenuDelay(pid); - } - } - else if (menucmd[pid].dpad_ud > 0) - { - if (statisticsmenu.location < statisticsmenu.maxscroll) - { - statisticsmenu.location++; - S_StartSound(NULL, sfx_s3k5b); - M_SetMenuDelay(pid); - } - } - else if (menucmd[pid].dpad_ud < 0) - { - if (statisticsmenu.location > 0) - { - statisticsmenu.location--; - S_StartSound(NULL, sfx_s3k5b); - M_SetMenuDelay(pid); - } - } - - return true; -} diff --git a/src/menus/CMakeLists.txt b/src/menus/CMakeLists.txt index 43bc71147..cb350244d 100644 --- a/src/menus/CMakeLists.txt +++ b/src/menus/CMakeLists.txt @@ -3,6 +3,7 @@ target_sources(SRB2SDL2 PRIVATE extras-addons.c extras-challenges.c extras-replay-hut.c + extras-statistics.c main-1.c main-profile-select.c options-1.c @@ -35,6 +36,7 @@ target_sources(SRB2SDL2 PRIVATE play-online-1.c play-online-host.c play-online-join-ip.c + play-online-room-select.c play-online-server-browser.c ) diff --git a/src/menus/extras-1.c b/src/menus/extras-1.c index e6af1edf3..457ca348d 100644 --- a/src/menus/extras-1.c +++ b/src/menus/extras-1.c @@ -2,6 +2,7 @@ /// \brief Extras Menu #include "../k_menu.h" +#include "../s_sound.h" menuitem_t EXTRAS_Main[] = { @@ -35,3 +36,104 @@ menu_t EXTRAS_MainDef = { M_ExtrasInputs }; +// Extras menu; +// this is copypasted from the options menu but all of these are different functions in case we ever want it to look more unique + +struct extrasmenu_s extrasmenu; + +void M_InitExtras(INT32 choice) +{ + (void)choice; + + extrasmenu.ticker = 0; + extrasmenu.offset = 0; + + extrasmenu.extx = 0; + extrasmenu.exty = 0; + extrasmenu.textx = 0; + extrasmenu.texty = 0; + + M_SetupNextMenu(&EXTRAS_MainDef, false); +} + +// For statistics, will maybe remain unused for a while +boolean M_ExtrasQuit(void) +{ + extrasmenu.textx = 140-1; + extrasmenu.texty = 70+1; + + return true; // Always allow quitting, duh. +} + +void M_ExtrasTick(void) +{ + extrasmenu.offset /= 2; + extrasmenu.ticker++; + + extrasmenu.extx += (extrasmenu.textx - extrasmenu.extx)/2; + extrasmenu.exty += (extrasmenu.texty - extrasmenu.exty)/2; + + if (abs(extrasmenu.extx - extrasmenu.exty) < 2) + { + extrasmenu.extx = extrasmenu.textx; + extrasmenu.exty = extrasmenu.texty; // Avoid awkward 1 px errors. + } + + // Move the button for cool animations + if (currentMenu == &EXTRAS_MainDef) + { + M_ExtrasQuit(); // reset the options button. + } + else + { + extrasmenu.textx = 160; + extrasmenu.texty = 50; + } +} + +boolean M_ExtrasInputs(INT32 ch) +{ + + const UINT8 pid = 0; + (void) ch; + + if (menucmd[pid].dpad_ud > 0) + { + extrasmenu.offset += 48; + M_NextOpt(); + S_StartSound(NULL, sfx_s3k5b); + + if (itemOn == 0) + extrasmenu.offset -= currentMenu->numitems*48; + + M_SetMenuDelay(pid); + return true; + } + + else if (menucmd[pid].dpad_ud < 0) + { + extrasmenu.offset -= 48; + M_PrevOpt(); + S_StartSound(NULL, sfx_s3k5b); + + if (itemOn == currentMenu->numitems-1) + extrasmenu.offset += currentMenu->numitems*48; + + M_SetMenuDelay(pid); + return true; + } + + else if (M_MenuConfirmPressed(pid)) + { + + if (currentMenu->menuitems[itemOn].status & IT_TRANSTEXT) + return true; // No. + + extrasmenu.extx = 140; + extrasmenu.exty = 70; // Default position for the currently selected option. + + M_SetMenuDelay(pid); + return false; // Don't eat. + } + return false; +} diff --git a/src/menus/extras-addons.c b/src/menus/extras-addons.c index b010861d3..e980d131d 100644 --- a/src/menus/extras-addons.c +++ b/src/menus/extras-addons.c @@ -2,6 +2,11 @@ /// \brief Addons menu! #include "../k_menu.h" +#include "../filesrch.h" // Addfile +#include "../d_main.h" +#include "../z_zone.h" +#include "../s_sound.h" +#include "../v_video.h" menuitem_t MISC_AddonsMenu[] = { @@ -25,3 +30,330 @@ menu_t MISC_AddonsDef = { NULL, NULL }; + +// Addons menu: (Merely copypasted, original code by toaster) + +static void M_UpdateAddonsSearch(void); +consvar_t cv_dummyaddonsearch = CVAR_INIT ("dummyaddonsearch", "", CV_HIDDEN|CV_CALL|CV_NOINIT, NULL, M_UpdateAddonsSearch); + +void M_Addons(INT32 choice) +{ + const char *pathname = "."; + + (void)choice; + +#if 1 + if (cv_addons_option.value == 0) + pathname = addonsdir; + else if (cv_addons_option.value == 1) + pathname = srb2home; + else if (cv_addons_option.value == 2) + pathname = srb2path; + else +#endif + if (cv_addons_option.value == 3 && *cv_addons_folder.string != '\0') + pathname = cv_addons_folder.string; + + strlcpy(menupath, pathname, 1024); + menupathindex[(menudepthleft = menudepth-1)] = strlen(menupath) + 1; + + if (menupath[menupathindex[menudepthleft]-2] != PATHSEP[0]) + { + menupath[menupathindex[menudepthleft]-1] = PATHSEP[0]; + menupath[menupathindex[menudepthleft]] = 0; + } + else + --menupathindex[menudepthleft]; + + if (!preparefilemenu(false, false)) + { + M_StartMessage(va("No files/folders found.\n\n%s\n\nPress (B)\n", LOCATIONSTRING1),NULL,MM_NOTHING); + return; + } + else + dir_on[menudepthleft] = 0; + + MISC_AddonsDef.lastOn = 0; // Always start on search + + MISC_AddonsDef.prevMenu = currentMenu; + M_SetupNextMenu(&MISC_AddonsDef, false); +} + + +char *M_AddonsHeaderPath(void) +{ + UINT32 len; + static char header[1024]; + + strlcpy(header, va("%s folder%s", cv_addons_option.string, menupath+menupathindex[menudepth-1]-1), 1024); + len = strlen(header); + if (len > 34) + { + len = len-34; + header[len] = header[len+1] = header[len+2] = '.'; + } + else + len = 0; + + return header+len; +} + +#define UNEXIST S_StartSound(NULL, sfx_s26d);\ + M_SetupNextMenu(MISC_AddonsDef.prevMenu, false);\ + M_StartMessage(va("\x82%s\x80\nThis folder no longer exists!\nAborting to main menu.\n\nPress (B)\n", M_AddonsHeaderPath()),NULL,MM_NOTHING) + +#define CLEARNAME Z_Free(refreshdirname);\ + refreshdirname = NULL + +static boolean prevmajormods = false; + +static void M_AddonsClearName(INT32 choice) +{ + if (!majormods || prevmajormods) + { + CLEARNAME; + } + M_StopMessage(choice); +} + +// Handles messages for addon errors. +void M_AddonsRefresh(void) +{ + if ((refreshdirmenu & REFRESHDIR_NORMAL) && !preparefilemenu(true, false)) + { + UNEXIST; + if (refreshdirname) + { + CLEARNAME; + } + return;// true; + } + +#ifdef DEVELOP + prevmajormods = majormods; +#else + if (!majormods && prevmajormods) + prevmajormods = false; +#endif + + if ((refreshdirmenu & REFRESHDIR_ADDFILE) || (majormods && !prevmajormods)) + { + char *message = NULL; + + if (refreshdirmenu & REFRESHDIR_NOTLOADED) + { + S_StartSound(NULL, sfx_s26d); + if (refreshdirmenu & REFRESHDIR_MAX) + message = va("%c%s\x80\nMaximum number of addons reached.\nA file could not be loaded.\nIf you wish to play with this addon, restart the game to clear existing ones.\n\nPress (B)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), refreshdirname); + else + message = va("%c%s\x80\nA file was not loaded.\nCheck the console log for more info.\n\nPress (B)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), refreshdirname); + } + else if (refreshdirmenu & (REFRESHDIR_WARNING|REFRESHDIR_ERROR)) + { + S_StartSound(NULL, sfx_s224); + message = va("%c%s\x80\nA file was loaded with %s.\nCheck the console log for more info.\n\nPress (B)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), refreshdirname, ((refreshdirmenu & REFRESHDIR_ERROR) ? "errors" : "warnings")); + } + else if (majormods && !prevmajormods) + { + S_StartSound(NULL, sfx_s221); + message = va("%c%s\x80\nYou've loaded a gameplay-modifying addon.\n\nRecord Attack has been disabled, but you\ncan still play alone in local Multiplayer.\n\nIf you wish to play Record Attack mode, restart the game to disable loaded addons.\n\nPress (B)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), refreshdirname); + prevmajormods = majormods; + } + + if (message) + { + M_StartMessage(message,FUNCPTRCAST(M_AddonsClearName),MM_YESNO); + return;// true; + } + + S_StartSound(NULL, sfx_s221); + CLEARNAME; + } + + return;// false; +} + +static void M_AddonExec(INT32 ch) +{ + if (ch == MA_YES) + { + S_StartSound(NULL, sfx_zoom); + COM_BufAddText(va("exec \"%s%s\"", menupath, dirmenu[dir_on[menudepthleft]]+DIR_STRING)); + } +} + +static void M_UpdateAddonsSearch(void) +{ + menusearch[0] = strlen(cv_dummyaddonsearch.string); + strlcpy(menusearch+1, cv_dummyaddonsearch.string, MAXSTRINGLENGTH); + if (!cv_addons_search_case.value) + strupr(menusearch+1); + +#if 0 // much slower + if (!preparefilemenu(true, false)) + { + UNEXIST; + return; + } +#else // streamlined + searchfilemenu(NULL); +#endif +} + +void M_HandleAddons(INT32 choice) +{ + const UINT8 pid = 0; + boolean exitmenu = false; // exit to previous menu + + (void) choice; + + if (menucmd[pid].dpad_ud > 0) + { + if (dir_on[menudepthleft] < sizedirmenu-1) + { + dir_on[menudepthleft]++; + S_StartSound(NULL, sfx_s3k5b); + } + else if (M_NextOpt()) + { + S_StartSound(NULL, sfx_s3k5b); + } + M_SetMenuDelay(pid); + } + else if (menucmd[pid].dpad_ud < 0) + { + if (dir_on[menudepthleft]) + { + dir_on[menudepthleft]--; + S_StartSound(NULL, sfx_s3k5b); + } + else if (M_PrevOpt()) + { + S_StartSound(NULL, sfx_s3k5b); + } + M_SetMenuDelay(pid); + } + + else if (M_MenuButtonPressed(pid, MBT_L)) + { + UINT8 i; + for (i = numaddonsshown; i && (dir_on[menudepthleft] < sizedirmenu-1); i--) + dir_on[menudepthleft]++; + + S_StartSound(NULL, sfx_s3k5b); + M_SetMenuDelay(pid); + } + + else if (M_MenuButtonPressed(pid, MBT_R)) + { + UINT8 i; + for (i = numaddonsshown; i && (dir_on[menudepthleft]); i--) + dir_on[menudepthleft]--; + + S_StartSound(NULL, sfx_s3k5b); + M_SetMenuDelay(pid); + } + + else if (M_MenuConfirmPressed(pid)) + { + boolean refresh = true; + M_SetMenuDelay(pid); + + if (!dirmenu[dir_on[menudepthleft]]) + S_StartSound(NULL, sfx_s26d); + else + { + switch (dirmenu[dir_on[menudepthleft]][DIR_TYPE]) + { + case EXT_FOLDER: + strcpy(&menupath[menupathindex[menudepthleft]],dirmenu[dir_on[menudepthleft]]+DIR_STRING); + if (menudepthleft) + { + menupathindex[--menudepthleft] = strlen(menupath); + menupath[menupathindex[menudepthleft]] = 0; + + if (!preparefilemenu(false, false)) + { + S_StartSound(NULL, sfx_s224); + M_StartMessage(va("%c%s\x80\nThis folder is empty.\n\nPress (B)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), M_AddonsHeaderPath()),NULL,MM_NOTHING); + menupath[menupathindex[++menudepthleft]] = 0; + + if (!preparefilemenu(true, false)) + { + UNEXIST; + return; + } + } + else + { + S_StartSound(NULL, sfx_s3k5b); + dir_on[menudepthleft] = 1; + } + refresh = false; + } + else + { + S_StartSound(NULL, sfx_s26d); + M_StartMessage(va("%c%s\x80\nThis folder is too deep to navigate to!\n\nPress (B)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), M_AddonsHeaderPath()),NULL,MM_NOTHING); + menupath[menupathindex[menudepthleft]] = 0; + } + break; + + case EXT_UP: + S_StartSound(NULL, sfx_s3k5b); + menupath[menupathindex[++menudepthleft]] = 0; + if (!preparefilemenu(false, false)) + { + UNEXIST; + return; + } + break; + + case EXT_TXT: + M_StartMessage(va("%c%s\x80\nThis file may not be a console script.\nAttempt to run anyways? \n\nPress (A) to confirm or (B) to cancel\n\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), dirmenu[dir_on[menudepthleft]]+DIR_STRING),FUNCPTRCAST(M_AddonExec),MM_YESNO); + break; + + case EXT_CFG: + M_StartMessage(va("%c%s\x80\nThis file may modify your settings.\nAttempt to run anyways? \n\nPress (A) to confirm or (B) to cancel\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), dirmenu[dir_on[menudepthleft]]+DIR_STRING),FUNCPTRCAST(M_AddonExec),MM_YESNO); + break; + + case EXT_LUA: + case EXT_SOC: + case EXT_WAD: +#ifdef USE_KART + case EXT_KART: +#endif + case EXT_PK3: + COM_BufAddText(va("addfile \"%s%s\"", menupath, dirmenu[dir_on[menudepthleft]]+DIR_STRING)); + break; + + default: + S_StartSound(NULL, sfx_s26d); + } + + if (refresh) + refreshdirmenu |= REFRESHDIR_NORMAL; + } + } + else if (M_MenuBackPressed(pid)) + { + exitmenu = true; + M_SetMenuDelay(pid); + } + + + if (exitmenu) + { + closefilemenu(true); + + // Secret menu! + //MainMenu[secrets].status = (M_AnySecretUnlocked()) ? (IT_STRING | IT_CALL) : (IT_DISABLED); + + if (currentMenu->prevMenu) + M_SetupNextMenu(currentMenu->prevMenu, false); + else + M_ClearMenus(true); + + M_SetMenuDelay(pid); + } +} diff --git a/src/menus/extras-challenges.c b/src/menus/extras-challenges.c index 15c955d0e..ca4c1268e 100644 --- a/src/menus/extras-challenges.c +++ b/src/menus/extras-challenges.c @@ -2,6 +2,11 @@ /// \brief Challenges. #include "../k_menu.h" +#include "../m_cond.h" // Condition Sets +#include "../m_random.h" // And just some randomness for the exits. +#include "../z_zone.h" +#include "../r_skins.h" +#include "../s_sound.h" menuitem_t MISC_ChallengesStatsDummyMenu[] = { @@ -23,6 +28,8 @@ menu_t MISC_ChallengesDef = { M_ChallengesInputs, }; +// This must be defined here so it can take sizeof +// MISC_ChallengesStatsDummyMenu :V menu_t MISC_StatisticsDef = { sizeof (MISC_ChallengesStatsDummyMenu)/sizeof (menuitem_t), &MainDef, @@ -37,3 +44,558 @@ menu_t MISC_StatisticsDef = { NULL, M_StatisticsInputs, }; + +struct challengesmenu_s challengesmenu; + +menu_t *M_InterruptMenuWithChallenges(menu_t *desiredmenu) +{ + UINT8 i; + + M_UpdateUnlockablesAndExtraEmblems(false); + + if ((challengesmenu.pending = challengesmenu.requestnew = (M_GetNextAchievedUnlock() < MAXUNLOCKABLES))) + { + MISC_ChallengesDef.prevMenu = desiredmenu; + } + + if (challengesmenu.pending || desiredmenu == NULL) + { + challengesmenu.currentunlock = MAXUNLOCKABLES; + challengesmenu.unlockcondition = NULL; + + M_PopulateChallengeGrid(); + if (gamedata->challengegrid) + challengesmenu.extradata = M_ChallengeGridExtraData(); + + memset(setup_explosions, 0, sizeof(setup_explosions)); + memset(&challengesmenu.unlockcount, 0, sizeof(challengesmenu.unlockcount)); + for (i = 0; i < MAXUNLOCKABLES; i++) + { + if (!unlockables[i].conditionset) + { + continue; + } + + challengesmenu.unlockcount[CC_TOTAL]++; + + if (!gamedata->unlocked[i]) + { + continue; + } + + challengesmenu.unlockcount[CC_UNLOCKED]++; + } + + return &MISC_ChallengesDef; + } + + return desiredmenu; +} + +static void M_ChallengesAutoFocus(UINT8 unlockid, boolean fresh) +{ + UINT8 i; + SINT8 work; + + if (unlockid >= MAXUNLOCKABLES) + return; + + challengesmenu.currentunlock = unlockid; + challengesmenu.unlockcondition = M_BuildConditionSetString(challengesmenu.currentunlock); + challengesmenu.unlockanim = 0; + + if (gamedata->challengegrid == NULL || challengesmenu.extradata == NULL) + return; + + for (i = 0; i < (CHALLENGEGRIDHEIGHT * gamedata->challengegridwidth); i++) + { + if (gamedata->challengegrid[i] != unlockid) + { + // Not what we're looking for. + continue; + } + + if (challengesmenu.extradata[i] & CHE_CONNECTEDLEFT) + { + // no need to check for CHE_CONNECTEDUP in linear iteration + continue; + } + + // Helper calculation for non-fresh scrolling. + work = (challengesmenu.col + challengesmenu.focusx); + + challengesmenu.col = challengesmenu.hilix = i/CHALLENGEGRIDHEIGHT; + challengesmenu.row = challengesmenu.hiliy = i%CHALLENGEGRIDHEIGHT; + + if (fresh) + { + // We're just entering the menu. Immediately jump to the desired position... + challengesmenu.focusx = 0; + // ...and since the menu is even-width, randomly select whether it's left or right of center. + if (!unlockables[unlockid].majorunlock + && M_RandomChance(FRACUNIT/2)) + challengesmenu.focusx--; + } + else + { + // We're jumping between multiple unlocks in sequence. Get the difference (looped from -range/2 < work <= range/2). + work -= challengesmenu.col; + if (work <= -gamedata->challengegridwidth/2) + work += gamedata->challengegridwidth; + else if (work >= gamedata->challengegridwidth/2) + work -= gamedata->challengegridwidth; + + if (work > 0) + { + // We only need to scroll as far as the rightward edge. + if (unlockables[unlockid].majorunlock) + { + work--; + challengesmenu.col++; + if (challengesmenu.col >= gamedata->challengegridwidth) + challengesmenu.col = 0; + } + + // Offset right, scroll left? + if (work > LEFTUNLOCKSCROLL) + { + work -= LEFTUNLOCKSCROLL; + challengesmenu.focusx = LEFTUNLOCKSCROLL; + } + else + { + challengesmenu.focusx = work; + work = 0; + } + } + else if (work < 0) + { + // Offset left, scroll right? + if (work < -RIGHTUNLOCKSCROLL) + { + challengesmenu.focusx = -RIGHTUNLOCKSCROLL; + work += RIGHTUNLOCKSCROLL; + } + else + { + challengesmenu.focusx = work; + work = 0; + } + } + else + { + // We're right where we want to be. + challengesmenu.focusx = 0; + } + + // And put the pixel-based scrolling in play, too. + challengesmenu.offset = -work*16; + } + + break; + } +} + +void M_Challenges(INT32 choice) +{ + UINT8 i; + (void)choice; + + M_InterruptMenuWithChallenges(NULL); + MISC_ChallengesDef.prevMenu = currentMenu; + + if (gamedata->challengegrid != NULL && !challengesmenu.pending) + { + UINT8 selection[MAXUNLOCKABLES]; + UINT8 numunlocks = 0; + + // Get a random available unlockable. + for (i = 0; i < MAXUNLOCKABLES; i++) + { + if (!unlockables[i].conditionset) + { + continue; + } + + if (!gamedata->unlocked[i]) + { + continue; + } + + selection[numunlocks++] = i; + } + + if (!numunlocks) + { + // ...OK, get a random unlockable. + for (i = 0; i < MAXUNLOCKABLES; i++) + { + if (!unlockables[i].conditionset) + { + continue; + } + + selection[numunlocks++] = i; + } + } + + M_ChallengesAutoFocus(selection[M_RandomKey(numunlocks)], true); + } + + M_SetupNextMenu(&MISC_ChallengesDef, false); +} + +void M_ChallengesTick(void) +{ + const UINT8 pid = 0; + UINT8 i, newunlock = MAXUNLOCKABLES; + boolean fresh = (challengesmenu.currentunlock >= MAXUNLOCKABLES); + + // Ticking + challengesmenu.ticker++; + challengesmenu.offset /= 2; + for (i = 0; i < CSEXPLOSIONS; i++) + { + if (setup_explosions[i].tics > 0) + setup_explosions[i].tics--; + } + if (challengesmenu.unlockcount[CC_ANIM] > 0) + challengesmenu.unlockcount[CC_ANIM]--; + M_CupSelectTick(); + + if (challengesmenu.pending) + { + // Pending mode. + + if (challengesmenu.requestnew) + { + // The menu apparatus is requesting a new unlock. + challengesmenu.requestnew = false; + if ((newunlock = M_GetNextAchievedUnlock()) < MAXUNLOCKABLES) + { + // We got one! + M_ChallengesAutoFocus(newunlock, fresh); + } + else + { + // All done! Let's save the unlocks we've busted open. + challengesmenu.pending = false; + G_SaveGameData(); + } + } + else if (challengesmenu.fade < 5) + { + // Fade increase. + challengesmenu.fade++; + } + else + { + // Unlock sequence. + tic_t nexttime = M_MenuExtraHeld(pid) ? (UNLOCKTIME*2) : MAXUNLOCKTIME; + + if (++challengesmenu.unlockanim >= nexttime) + { + challengesmenu.requestnew = true; + } + + if (challengesmenu.currentunlock < MAXUNLOCKABLES + && challengesmenu.unlockanim == UNLOCKTIME) + { + // Unlock animation... also tied directly to the actual unlock! + gamedata->unlocked[challengesmenu.currentunlock] = true; + M_UpdateUnlockablesAndExtraEmblems(true); + + // Update shown description just in case..? + challengesmenu.unlockcondition = M_BuildConditionSetString(challengesmenu.currentunlock); + + challengesmenu.unlockcount[CC_TALLY]++; + challengesmenu.unlockcount[CC_ANIM]++; + + Z_Free(challengesmenu.extradata); + if ((challengesmenu.extradata = M_ChallengeGridExtraData())) + { + unlockable_t *ref = &unlockables[challengesmenu.currentunlock]; + UINT16 bombcolor = SKINCOLOR_NONE; + + if (ref->color != SKINCOLOR_NONE && ref->color < numskincolors) + { + bombcolor = ref->color; + } + else switch (ref->type) + { + case SECRET_SKIN: + { + INT32 skin = M_UnlockableSkinNum(ref); + if (skin != -1) + { + bombcolor = skins[skin].prefcolor; + } + break; + } + case SECRET_FOLLOWER: + { + INT32 skin = M_UnlockableFollowerNum(ref); + if (skin != -1) + { + bombcolor = K_GetEffectiveFollowerColor(followers[skin].defaultcolor, cv_playercolor[0].value); + } + break; + } + default: + break; + } + + if (bombcolor == SKINCOLOR_NONE) + { + bombcolor = cv_playercolor[0].value; + } + + i = (ref->majorunlock && M_RandomChance(FRACUNIT/2)) ? 1 : 0; + M_SetupReadyExplosions(false, challengesmenu.hilix, challengesmenu.hiliy+i, bombcolor); + if (ref->majorunlock) + { + M_SetupReadyExplosions(false, challengesmenu.hilix+1, challengesmenu.hiliy+(1-i), bombcolor); + } + + S_StartSound(NULL, sfx_s3k4e); + } + } + } + } + else + { + + // Tick down the tally. (currently not visible) + /*if ((challengesmenu.ticker & 1) + && challengesmenu.unlockcount[CC_TALLY] > 0) + { + challengesmenu.unlockcount[CC_TALLY]--; + challengesmenu.unlockcount[CC_UNLOCKED]++; + }*/ + + if (challengesmenu.fade > 0) + { + // Fade decrease. + challengesmenu.fade--; + } + } +} + +boolean M_ChallengesInputs(INT32 ch) +{ + const UINT8 pid = 0; + UINT8 i; + const boolean start = M_MenuButtonPressed(pid, MBT_START); + const boolean move = (menucmd[pid].dpad_ud != 0 || menucmd[pid].dpad_lr != 0); + (void) ch; + + if (challengesmenu.fade) + { + ; + } +#ifdef DEVELOP + else if (M_MenuExtraPressed(pid) && challengesmenu.extradata) // debugging + { + if (challengesmenu.currentunlock < MAXUNLOCKABLES) + { + Z_Free(gamedata->challengegrid); + gamedata->challengegrid = NULL; + gamedata->challengegridwidth = 0; + M_PopulateChallengeGrid(); + Z_Free(challengesmenu.extradata); + challengesmenu.extradata = M_ChallengeGridExtraData(); + + M_ChallengesAutoFocus(challengesmenu.currentunlock, true); + + challengesmenu.pending = true; + } + return true; + } +#endif + else + { + if (M_MenuBackPressed(pid) || start) + { + M_GoBack(0); + M_SetMenuDelay(pid); + + Z_Free(challengesmenu.extradata); + challengesmenu.extradata = NULL; + + challengesmenu.unlockcondition = NULL; + + return true; + } + + if (challengesmenu.extradata != NULL && move) + { + // Determine movement around the grid + // For right/down movement, we can pre-determine the number of steps based on extradata. + // For left/up movement, we can't - we have to be ready to iterate twice, and break early if we don't run into a large tile. + + if (menucmd[pid].dpad_ud > 0) + { + i = 2; + while (i > 0) + { + if (challengesmenu.row < CHALLENGEGRIDHEIGHT-1) + { + challengesmenu.row++; + } + else + { + challengesmenu.row = 0; + } + if (!(challengesmenu.extradata[ + (challengesmenu.col * CHALLENGEGRIDHEIGHT) + + challengesmenu.row] + & CHE_CONNECTEDUP)) + { + break; + } + i--; + } + S_StartSound(NULL, sfx_s3k5b); + M_SetMenuDelay(pid); + } + else if (menucmd[pid].dpad_ud < 0) + { + i = (challengesmenu.extradata[ + (challengesmenu.col * CHALLENGEGRIDHEIGHT) + + challengesmenu.row] + & CHE_CONNECTEDUP) ? 2 : 1; + while (i > 0) + { + if (challengesmenu.row > 0) + { + challengesmenu.row--; + } + else + { + challengesmenu.row = CHALLENGEGRIDHEIGHT-1; + } + i--; + } + S_StartSound(NULL, sfx_s3k5b); + M_SetMenuDelay(pid); + } + + if (menucmd[pid].dpad_lr > 0) + { + i = 2; + while (i > 0) + { + // Slide the focus counter to movement, if we can. + if (challengesmenu.focusx > -RIGHTUNLOCKSCROLL) + { + challengesmenu.focusx--; + } + + // Step the actual column right. + if (challengesmenu.col < gamedata->challengegridwidth-1) + { + challengesmenu.col++; + } + else + { + challengesmenu.col = 0; + } + + if (!(challengesmenu.extradata[ + (challengesmenu.col * CHALLENGEGRIDHEIGHT) + + challengesmenu.row] + & CHE_CONNECTEDLEFT)) + { + break; + } + + i--; + } + S_StartSound(NULL, sfx_s3k5b); + M_SetMenuDelay(pid); + } + else if (menucmd[pid].dpad_lr < 0) + { + i = (challengesmenu.extradata[ + (challengesmenu.col * CHALLENGEGRIDHEIGHT) + + challengesmenu.row] + & CHE_CONNECTEDLEFT) ? 2 : 1; + while (i > 0) + { + // Slide the focus counter to movement, if we can. + if (challengesmenu.focusx < LEFTUNLOCKSCROLL) + { + challengesmenu.focusx++; + } + + // Step the actual column left. + if (challengesmenu.col > 0) + { + challengesmenu.col--; + } + else + { + challengesmenu.col = gamedata->challengegridwidth-1; + } + + i--; + } + S_StartSound(NULL, sfx_s3k5b); + M_SetMenuDelay(pid); + } + + // After movement has been determined, figure out the current selection. + i = (challengesmenu.col * CHALLENGEGRIDHEIGHT) + challengesmenu.row; + challengesmenu.currentunlock = (gamedata->challengegrid[i]); + challengesmenu.unlockcondition = M_BuildConditionSetString(challengesmenu.currentunlock); + + challengesmenu.hilix = challengesmenu.col; + challengesmenu.hiliy = challengesmenu.row; + + if (challengesmenu.currentunlock < MAXUNLOCKABLES + && unlockables[challengesmenu.currentunlock].majorunlock) + { + // Adjust highlight coordinates up/to the left for large tiles. + + if (challengesmenu.hiliy > 0 && (challengesmenu.extradata[i] & CHE_CONNECTEDUP)) + { + challengesmenu.hiliy--; + } + + if ((challengesmenu.extradata[i] & CHE_CONNECTEDLEFT)) + { + if (challengesmenu.hilix > 0) + { + challengesmenu.hilix--; + } + else + { + challengesmenu.hilix = gamedata->challengegridwidth-1; + } + } + } + + return true; + } + + if (M_MenuConfirmPressed(pid) + && challengesmenu.currentunlock < MAXUNLOCKABLES + && gamedata->unlocked[challengesmenu.currentunlock]) + { + switch (unlockables[challengesmenu.currentunlock].type) + { + case SECRET_ALTTITLE: + CV_AddValue(&cv_alttitle, 1); + S_StartSound(NULL, sfx_s3kc3s); + M_SetMenuDelay(pid); + break; + default: + break; + } + + return true; + } + } + + return true; +} diff --git a/src/menus/extras-replay-hut.c b/src/menus/extras-replay-hut.c index 3ba3f7e20..db9e3b0dd 100644 --- a/src/menus/extras-replay-hut.c +++ b/src/menus/extras-replay-hut.c @@ -2,6 +2,11 @@ /// \brief Extras Menu: Replay Hut #include "../k_menu.h" +#include "../filesrch.h" // Addfile +#include "../d_main.h" +#include "../s_sound.h" +#include "../v_video.h" +#include "../z_zone.h" // extras menu: replay hut menuitem_t EXTRAS_ReplayHut[] = @@ -60,3 +65,228 @@ menu_t EXTRAS_ReplayStartDef = NULL, NULL }; + +void M_PrepReplayList(void) +{ + size_t i; + + if (extrasmenu.demolist) + Z_Free(extrasmenu.demolist); + + extrasmenu.demolist = Z_Calloc(sizeof(menudemo_t) * sizedirmenu, PU_STATIC, NULL); + + for (i = 0; i < sizedirmenu; i++) + { + if (dirmenu[i][DIR_TYPE] == EXT_UP) + { + extrasmenu.demolist[i].type = MD_SUBDIR; + sprintf(extrasmenu.demolist[i].title, "UP"); + } + else if (dirmenu[i][DIR_TYPE] == EXT_FOLDER) + { + extrasmenu.demolist[i].type = MD_SUBDIR; + strncpy(extrasmenu.demolist[i].title, dirmenu[i] + DIR_STRING, 64); + } + else + { + extrasmenu.demolist[i].type = MD_NOTLOADED; + snprintf(extrasmenu.demolist[i].filepath, sizeof extrasmenu.demolist[i].filepath, + // 255 = UINT8 limit. dirmenu entries are restricted to this length (see DIR_LEN). + "%s%.255s", menupath, dirmenu[i] + DIR_STRING); + sprintf(extrasmenu.demolist[i].title, "....."); + } + } +} + +void M_ReplayHut(INT32 choice) +{ + (void)choice; + + extrasmenu.replayScrollTitle = 0; + extrasmenu.replayScrollDelay = TICRATE; + extrasmenu.replayScrollDir = 1; + + if (!demo.inreplayhut) + { + snprintf(menupath, 1024, "%s"PATHSEP"media"PATHSEP"replay"PATHSEP"online"PATHSEP, srb2home); + menupathindex[(menudepthleft = menudepth-1)] = strlen(menupath); + } + if (!preparefilemenu(false, true)) + { + M_StartMessage("No replays found.\n\nPress (B)\n", NULL, MM_NOTHING); + return; + } + else if (!demo.inreplayhut) + dir_on[menudepthleft] = 0; + demo.inreplayhut = true; + + extrasmenu.replayScrollTitle = 0; extrasmenu.replayScrollDelay = TICRATE; extrasmenu.replayScrollDir = 1; + + M_PrepReplayList(); + + menuactive = true; + M_SetupNextMenu(&EXTRAS_ReplayHutDef, false); + //G_SetGamestate(GS_TIMEATTACK); + //titlemapinaction = TITLEMAP_OFF; // Nope don't give us HOMs please + + demo.rewinding = false; + CL_ClearRewinds(); + + //S_ChangeMusicInternal("replst", true); +} + +// key handler +void M_HandleReplayHutList(INT32 choice) +{ + + const UINT8 pid = 0; + (void) choice; + + if (menucmd[pid].dpad_ud < 0) + { + if (dir_on[menudepthleft]) + dir_on[menudepthleft]--; + else + return; + //M_PrevOpt(); + + S_StartSound(NULL, sfx_s3k5b); + M_SetMenuDelay(pid); + extrasmenu.replayScrollTitle = 0; extrasmenu.replayScrollDelay = TICRATE; extrasmenu.replayScrollDir = 1; + } + + else if (menucmd[pid].dpad_ud > 0) + { + if (dir_on[menudepthleft] < sizedirmenu-1) + dir_on[menudepthleft]++; + else + return; + //itemOn = 0; // Not M_NextOpt because that would take us to the extra dummy item + + S_StartSound(NULL, sfx_s3k5b); + M_SetMenuDelay(pid); + extrasmenu.replayScrollTitle = 0; extrasmenu.replayScrollDelay = TICRATE; extrasmenu.replayScrollDir = 1; + } + + else if (M_MenuBackPressed(pid)) + { + M_SetMenuDelay(pid); + M_QuitReplayHut(); + } + + else if (M_MenuConfirmPressed(pid)) + { + M_SetMenuDelay(pid); + switch (dirmenu[dir_on[menudepthleft]][DIR_TYPE]) + { + case EXT_FOLDER: + strcpy(&menupath[menupathindex[menudepthleft]],dirmenu[dir_on[menudepthleft]]+DIR_STRING); + if (menudepthleft) + { + menupathindex[--menudepthleft] = strlen(menupath); + menupath[menupathindex[menudepthleft]] = 0; + + if (!preparefilemenu(false, true)) + { + S_StartSound(NULL, sfx_s224); + M_StartMessage(va("%c%s\x80\nThis folder is empty.\n\nPress (B)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), M_AddonsHeaderPath()),NULL,MM_NOTHING); + menupath[menupathindex[++menudepthleft]] = 0; + + if (!preparefilemenu(true, true)) + { + M_QuitReplayHut(); + return; + } + } + else + { + S_StartSound(NULL, sfx_s3k5b); + dir_on[menudepthleft] = 1; + M_PrepReplayList(); + } + } + else + { + S_StartSound(NULL, sfx_s26d); + M_StartMessage(va("%c%s\x80\nThis folder is too deep to navigate to!\n\nPress (B)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), M_AddonsHeaderPath()),NULL,MM_NOTHING); + menupath[menupathindex[menudepthleft]] = 0; + } + break; + case EXT_UP: + S_StartSound(NULL, sfx_s3k5b); + menupath[menupathindex[++menudepthleft]] = 0; + if (!preparefilemenu(false, true)) + { + M_QuitReplayHut(); + return; + } + M_PrepReplayList(); + break; + default: + // We can't just use M_SetupNextMenu because that'll run ReplayDef's quitroutine and boot us back to the title screen! + currentMenu->lastOn = itemOn; + currentMenu = &EXTRAS_ReplayStartDef; + + extrasmenu.replayScrollTitle = 0; extrasmenu.replayScrollDelay = TICRATE; extrasmenu.replayScrollDir = 1; + + switch (extrasmenu.demolist[dir_on[menudepthleft]].addonstatus) + { + case DFILE_ERROR_CANNOTLOAD: + // Only show "Watch Replay Without Addons" + EXTRAS_ReplayStart[0].status = IT_DISABLED; + EXTRAS_ReplayStart[1].status = IT_CALL|IT_STRING; + //EXTRAS_ReplayStart[1].alphaKey = 0; + EXTRAS_ReplayStart[2].status = IT_DISABLED; + itemOn = 1; + break; + + case DFILE_ERROR_NOTLOADED: + case DFILE_ERROR_INCOMPLETEOUTOFORDER: + // Show "Load Addons and Watch Replay" and "Watch Replay Without Addons" + EXTRAS_ReplayStart[0].status = IT_CALL|IT_STRING; + EXTRAS_ReplayStart[1].status = IT_CALL|IT_STRING; + //EXTRAS_ReplayStart[1].alphaKey = 10; + EXTRAS_ReplayStart[2].status = IT_DISABLED; + itemOn = 0; + break; + + case DFILE_ERROR_EXTRAFILES: + case DFILE_ERROR_OUTOFORDER: + default: + // Show "Watch Replay" + EXTRAS_ReplayStart[0].status = IT_DISABLED; + EXTRAS_ReplayStart[1].status = IT_DISABLED; + EXTRAS_ReplayStart[2].status = IT_CALL|IT_STRING; + //EXTRAS_ReplayStart[2].alphaKey = 0; + itemOn = 2; + break; + } + } + } +} + +boolean M_QuitReplayHut(void) +{ + // D_StartTitle does its own wipe, since GS_TIMEATTACK is now a complete gamestate. + menuactive = false; + D_StartTitle(); + + if (extrasmenu.demolist) + Z_Free(extrasmenu.demolist); + extrasmenu.demolist = NULL; + + demo.inreplayhut = false; + + return true; +} + +void M_HutStartReplay(INT32 choice) +{ + (void)choice; + + M_ClearMenus(false); + demo.loadfiles = (itemOn == 0); + demo.ignorefiles = (itemOn != 0); + + G_DoPlayDemo(extrasmenu.demolist[dir_on[menudepthleft]].filepath); +} diff --git a/src/menus/extras-statistics.c b/src/menus/extras-statistics.c new file mode 100644 index 000000000..97f4b3192 --- /dev/null +++ b/src/menus/extras-statistics.c @@ -0,0 +1,99 @@ +/// \file menus/extras-challenges.c +/// \brief Statistics menu + +#include "../k_menu.h" +#include "../z_zone.h" +#include "../m_cond.h" // Condition Sets +#include "../s_sound.h" + +struct statisticsmenu_s statisticsmenu; + +void M_Statistics(INT32 choice) +{ + UINT16 i = 0; + + (void)choice; + + statisticsmenu.maplist = Z_Malloc(sizeof(UINT16) * nummapheaders, PU_STATIC, NULL); + statisticsmenu.nummaps = 0; + + for (i = 0; i < nummapheaders; i++) + { + if (!mapheaderinfo[i]) + continue; + + // Check for no visibility + legacy box + if (mapheaderinfo[i]->menuflags & (LF2_NOTIMEATTACK|LF2_HIDEINSTATS|LF2_HIDEINMENU)) + continue; + + // Check for completion + if ((mapheaderinfo[i]->menuflags & LF2_FINISHNEEDED) + && !(mapheaderinfo[i]->mapvisited & MV_BEATEN)) + continue; + + // Check for unlock + if (M_MapLocked(i+1)) + continue; + + statisticsmenu.maplist[statisticsmenu.nummaps++] = i; + } + statisticsmenu.maplist[statisticsmenu.nummaps] = NEXTMAP_INVALID; + statisticsmenu.maxscroll = (statisticsmenu.nummaps + M_CountMedals(true, true) + 2) - 10; + statisticsmenu.location = 0; + + if (statisticsmenu.maxscroll < 0) + { + statisticsmenu.maxscroll = 0; + } + + MISC_StatisticsDef.prevMenu = currentMenu; + M_SetupNextMenu(&MISC_StatisticsDef, false); +} + +boolean M_StatisticsInputs(INT32 ch) +{ + const UINT8 pid = 0; + + (void)ch; + + if (M_MenuBackPressed(pid)) + { + M_GoBack(0); + M_SetMenuDelay(pid); + + Z_Free(statisticsmenu.maplist); + statisticsmenu.maplist = NULL; + + return true; + } + + if (M_MenuExtraPressed(pid)) + { + if (statisticsmenu.location > 0) + { + statisticsmenu.location = 0; + S_StartSound(NULL, sfx_s3k5b); + M_SetMenuDelay(pid); + } + } + else if (menucmd[pid].dpad_ud > 0) + { + if (statisticsmenu.location < statisticsmenu.maxscroll) + { + statisticsmenu.location++; + S_StartSound(NULL, sfx_s3k5b); + M_SetMenuDelay(pid); + } + } + else if (menucmd[pid].dpad_ud < 0) + { + if (statisticsmenu.location > 0) + { + statisticsmenu.location--; + S_StartSound(NULL, sfx_s3k5b); + M_SetMenuDelay(pid); + } + } + + return true; +} diff --git a/src/menus/main-1.c b/src/menus/main-1.c index ea337e3f3..aa441b572 100644 --- a/src/menus/main-1.c +++ b/src/menus/main-1.c @@ -11,6 +11,13 @@ // point to submenus. #include "../k_menu.h" +#include "../m_random.h" +#include "../s_sound.h" +#include "../i_time.h" +#include "../v_video.h" +#include "../z_zone.h" +#include "../i_video.h" // I_FinishUpdate +#include "../i_system.h" // I_Sleep menuitem_t MainMenu[] = { @@ -32,3 +39,74 @@ menuitem_t MainMenu[] = }; menu_t MainDef = KARTGAMEMODEMENU(MainMenu, NULL); + +// Quit Game +static INT32 quitsounds[] = +{ + // holy shit we're changing things up! + // srb2kart: you ain't seen nothing yet + sfx_kc2e, + sfx_kc2f, + sfx_cdfm01, + sfx_ddash, + sfx_s3ka2, + sfx_s3k49, + sfx_slip, + sfx_tossed, + sfx_s3k7b, + sfx_itrolf, + sfx_itrole, + sfx_cdpcm9, + sfx_s3k4e, + sfx_s259, + sfx_3db06, + sfx_s3k3a, + sfx_peel, + sfx_cdfm28, + sfx_s3k96, + sfx_s3kc0s, + sfx_cdfm39, + sfx_hogbom, + sfx_kc5a, + sfx_kc46, + sfx_s3k92, + sfx_s3k42, + sfx_kpogos, + sfx_screec +}; + +void M_QuitSRB2(INT32 choice) +{ + // We pick index 0 which is language sensitive, or one at random, + // between 1 and maximum number. + (void)choice; + M_StartMessage("Are you sure you want to quit playing?\n\nPress (A) to confirm or (B) to cancel", FUNCPTRCAST(M_QuitResponse), MM_YESNO); +} + +void M_QuitResponse(INT32 ch) +{ + tic_t ptime; + INT32 mrand; + + if (ch == MA_YES) + { + if (!(netgame || cht_debug)) + { + mrand = M_RandomKey(sizeof(quitsounds) / sizeof(INT32)); + if (quitsounds[mrand]) + S_StartSound(NULL, quitsounds[mrand]); + + //added : 12-02-98: do that instead of I_WaitVbl which does not work + ptime = I_GetTime() + NEWTICRATE*2; // Shortened the quit time, used to be 2 seconds Tails 03-26-2001 + while (ptime > I_GetTime()) + { + V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31); + V_DrawSmallScaledPatch(0, 0, 0, W_CachePatchName("GAMEQUIT", PU_CACHE)); // Demo 3 Quit Screen Tails 06-16-2001 + I_FinishUpdate(); // Update the screen with the image Tails 06-19-2001 + I_Sleep(cv_sleep.value); + I_UpdateTime(cv_timescale.value); + } + } + I_Quit(); + } +} diff --git a/src/menus/options-1.c b/src/menus/options-1.c index 6c6d12374..fd35da23d 100644 --- a/src/menus/options-1.c +++ b/src/menus/options-1.c @@ -2,6 +2,9 @@ /// \brief Options Menu #include "../k_menu.h" +#include "../k_grandprix.h" // K_CanChangeRules +#include "../m_cond.h" // Condition Sets +#include "../s_sound.h" // options menu -- see mopt_e menuitem_t OPTIONS_Main[] = @@ -47,3 +50,181 @@ menu_t OPTIONS_MainDef = { NULL, M_OptionsInputs }; + +struct optionsmenu_s optionsmenu; + +void M_ResetOptions(void) +{ + optionsmenu.ticker = 0; + optionsmenu.offset = 0; + + optionsmenu.optx = 0; + optionsmenu.opty = 0; + optionsmenu.toptx = 0; + optionsmenu.topty = 0; + + // BG setup: + optionsmenu.currcolour = OPTIONS_MainDef.extra1; + optionsmenu.lastcolour = 0; + optionsmenu.fade = 0; + + // For profiles: + memset(setup_player, 0, sizeof(setup_player)); + optionsmenu.profile = NULL; +} + +void M_InitOptions(INT32 choice) +{ + (void)choice; + + OPTIONS_MainDef.menuitems[mopt_gameplay].status = IT_STRING | IT_TRANSTEXT; + OPTIONS_MainDef.menuitems[mopt_server].status = IT_STRING | IT_TRANSTEXT; + + // enable gameplay & server options under the right circumstances. + if (gamestate == GS_MENU + || ((server || IsPlayerAdmin(consoleplayer)) && K_CanChangeRules(false))) + { + OPTIONS_MainDef.menuitems[mopt_gameplay].status = IT_STRING | IT_SUBMENU; + OPTIONS_MainDef.menuitems[mopt_server].status = IT_STRING | IT_SUBMENU; + OPTIONS_GameplayDef.menuitems[gopt_encore].status = + (M_SecretUnlocked(SECRET_ENCORE, false) ? (IT_STRING | IT_CVAR) : IT_DISABLED); + } + + OPTIONS_DataDef.menuitems[dopt_erase].status = (gamestate == GS_MENU + ? (IT_STRING | IT_SUBMENU) + : (IT_TRANSTEXT2 | IT_SPACE)); + + M_ResetOptions(); + + // So that pause doesn't go to the main menu... + OPTIONS_MainDef.prevMenu = currentMenu; + + // This will disable or enable the textboxes of the affected menus before we get to them. + Screenshot_option_Onchange(); + Moviemode_mode_Onchange(); + Moviemode_option_Onchange(); + Addons_option_Onchange(); + + M_SetupNextMenu(&OPTIONS_MainDef, false); +} + +// Prepares changing the colour of the background +void M_OptionsChangeBGColour(INT16 newcolour) +{ + optionsmenu.fade = 10; + optionsmenu.lastcolour = optionsmenu.currcolour; + optionsmenu.currcolour = newcolour; +} + +boolean M_OptionsQuit(void) +{ + optionsmenu.toptx = 140-1; + optionsmenu.topty = 70+1; + + // Reset button behaviour because profile menu is different, since of course it is. + if (optionsmenu.resetprofilemenu) + { + optionsmenu.profilemenu = false; + optionsmenu.profile = NULL; + optionsmenu.resetprofilemenu = false; + } + + return true; // Always allow quitting, duh. +} + +void M_OptionsTick(void) +{ + optionsmenu.offset /= 2; + optionsmenu.ticker++; + + optionsmenu.optx += (optionsmenu.toptx - optionsmenu.optx)/2; + optionsmenu.opty += (optionsmenu.topty - optionsmenu.opty)/2; + + if (abs(optionsmenu.optx - optionsmenu.opty) < 2) + { + optionsmenu.optx = optionsmenu.toptx; + optionsmenu.opty = optionsmenu.topty; // Avoid awkward 1 px errors. + } + + // Move the button for cool animations + if (currentMenu == &OPTIONS_MainDef) + { + M_OptionsQuit(); // ...So now this is used here. + } + else if (optionsmenu.profile == NULL) // Not currently editing a profile (otherwise we're using these variables for other purposes....) + { + // I don't like this, it looks like shit but it needs to be done.......... + if (optionsmenu.profilemenu) + { + optionsmenu.toptx = 420; + optionsmenu.topty = 70+1; + } + else if (currentMenu == &OPTIONS_GameplayItemsDef) + { + optionsmenu.toptx = -160; // off the side of the screen + optionsmenu.topty = 50; + } + else + { + optionsmenu.toptx = 160; + optionsmenu.topty = 50; + } + } + + // Handle the background stuff: + if (optionsmenu.fade) + optionsmenu.fade--; + + // change the colour if we aren't matching the current menu colour + if (optionsmenu.currcolour != currentMenu->extra1) + M_OptionsChangeBGColour(currentMenu->extra1); + + // And one last giggle... + if (shitsfree) + shitsfree--; +} + +boolean M_OptionsInputs(INT32 ch) +{ + + const UINT8 pid = 0; + (void)ch; + + if (menucmd[pid].dpad_ud > 0) + { + M_SetMenuDelay(pid); + optionsmenu.offset += 48; + M_NextOpt(); + S_StartSound(NULL, sfx_s3k5b); + + if (itemOn == 0) + optionsmenu.offset -= currentMenu->numitems*48; + + + return true; + } + else if (menucmd[pid].dpad_ud < 0) + { + M_SetMenuDelay(pid); + optionsmenu.offset -= 48; + M_PrevOpt(); + S_StartSound(NULL, sfx_s3k5b); + + if (itemOn == currentMenu->numitems-1) + optionsmenu.offset += currentMenu->numitems*48; + + + return true; + } + else if (M_MenuConfirmPressed(pid)) + { + + if (currentMenu->menuitems[itemOn].status & IT_TRANSTEXT) + return true; // No. + + optionsmenu.optx = 140; + optionsmenu.opty = 70; // Default position for the currently selected option. + return false; // Don't eat. + } + return false; +} diff --git a/src/menus/options-data-addons.c b/src/menus/options-data-addons.c index 8b6cce96a..1af384e97 100644 --- a/src/menus/options-data-addons.c +++ b/src/menus/options-data-addons.c @@ -51,3 +51,10 @@ menu_t OPTIONS_DataAddonDef = { NULL, }; +void Addons_option_Onchange(void) +{ + // Option 2 will always be the textbar. + // (keep in mind this is a 0 indexed array and the first element is a header...) + OPTIONS_DataAddon[2].status = + (cv_addons_option.value == 3 ? IT_CVAR|IT_STRING|IT_CV_STRING : IT_DISABLED); +} diff --git a/src/menus/options-data-erase-1.c b/src/menus/options-data-erase-1.c index 8be6deff0..a27e1f559 100644 --- a/src/menus/options-data-erase-1.c +++ b/src/menus/options-data-erase-1.c @@ -2,6 +2,9 @@ /// \brief Erase Data Menu #include "../k_menu.h" +#include "../s_sound.h" +#include "../m_cond.h" // Condition Sets +#include "../f_finale.h" menuitem_t OPTIONS_DataErase[] = { @@ -40,3 +43,42 @@ menu_t OPTIONS_DataEraseDef = { NULL, NULL, }; + +static void M_EraseDataResponse(INT32 ch) +{ + if (ch == MA_NO) + return; + + S_StartSound(NULL, sfx_itrole); // bweh heh heh + + // Delete the data + if (optionsmenu.erasecontext == 2) + { + // SRB2Kart: This actually needs to be done FIRST, so that you don't immediately regain playtime/matches secrets + gamedata->totalplaytime = 0; + gamedata->matchesplayed = 0; + } + if (optionsmenu.erasecontext != 1) + G_ClearRecords(); + if (optionsmenu.erasecontext != 0) + M_ClearSecrets(); + + F_StartIntro(); + M_ClearMenus(true); +} + +void M_EraseData(INT32 choice) +{ + const char *eschoice, *esstr = M_GetText("Are you sure you want to erase\n%s?\n\nPress (A) to confirm or (B) to cancel\n"); + + optionsmenu.erasecontext = (UINT8)choice; + + if (choice == 0) + eschoice = M_GetText("Time Attack data"); + else if (choice == 1) + eschoice = M_GetText("Secrets data"); + else + eschoice = M_GetText("ALL game data"); + + M_StartMessage(va(esstr, eschoice), FUNCPTRCAST(M_EraseDataResponse), MM_YESNO); +} diff --git a/src/menus/options-data-erase-profile.c b/src/menus/options-data-erase-profile.c index 87e4e95cc..69afc1d20 100644 --- a/src/menus/options-data-erase-profile.c +++ b/src/menus/options-data-erase-profile.c @@ -2,6 +2,8 @@ /// \brief Erase Profile Menu #include "../k_menu.h" +#include "../s_sound.h" +#include "../f_finale.h" menuitem_t OPTIONS_DataProfileErase[] = { @@ -22,3 +24,84 @@ menu_t OPTIONS_DataProfileEraseDef = { NULL, NULL }; + +// Check if we have any profile loaded. +void M_CheckProfileData(INT32 choice) +{ + UINT8 np = PR_GetNumProfiles(); + (void) choice; + + if (np < 2) + { + S_StartSound(NULL, sfx_s3k7b); + M_StartMessage("There are no custom profiles.\n\nPress (B)", NULL, MM_NOTHING); + return; + } + + optionsmenu.eraseprofilen = 1; + M_SetupNextMenu(&OPTIONS_DataProfileEraseDef, false); +} + +static void M_EraseProfileResponse(INT32 choice) +{ + if (choice == MA_YES) + { + S_StartSound(NULL, sfx_itrole); // bweh heh heh + + PR_DeleteProfile(optionsmenu.eraseprofilen); + + // Did we bust our current profile..!? + if (cv_currprofile.value == -1) + { + F_StartIntro(); + M_ClearMenus(true); + } + else if (optionsmenu.eraseprofilen > PR_GetNumProfiles()-1) + { + optionsmenu.eraseprofilen--; + } + } +} + +void M_HandleProfileErase(INT32 choice) +{ + const UINT8 pid = 0; + const UINT8 np = PR_GetNumProfiles()-1; + (void) choice; + + if (menucmd[pid].dpad_ud > 0) + { + S_StartSound(NULL, sfx_s3k5b); + optionsmenu.eraseprofilen++; + + if (optionsmenu.eraseprofilen > np) + optionsmenu.eraseprofilen = 1; + + M_SetMenuDelay(pid); + } + else if (menucmd[pid].dpad_ud < 0) + { + S_StartSound(NULL, sfx_s3k5b); + + if (optionsmenu.eraseprofilen == 1) + optionsmenu.eraseprofilen = np; + else + optionsmenu.eraseprofilen--; + + M_SetMenuDelay(pid); + } + else if (M_MenuBackPressed(pid)) + { + M_GoBack(0); + M_SetMenuDelay(pid); + } + else if (M_MenuConfirmPressed(pid)) + { + if (optionsmenu.eraseprofilen == cv_currprofile.value) + M_StartMessage("Your ""\x85""current profile""\x80"" will be erased.\nAre you sure you want to proceed?\nDeleting this profile will also\nreturn you to the title screen.\n\nPress (A) to confirm or (B) to cancel", FUNCPTRCAST(M_EraseProfileResponse), MM_YESNO); + else + M_StartMessage("This profile will be erased.\nAre you sure you want to proceed?\n\nPress (A) to confirm or (B) to cancel", FUNCPTRCAST(M_EraseProfileResponse), MM_YESNO); + + M_SetMenuDelay(pid); + } +} diff --git a/src/menus/options-data-screenshots.c b/src/menus/options-data-screenshots.c index a24dea49c..dcd50f99d 100644 --- a/src/menus/options-data-screenshots.c +++ b/src/menus/options-data-screenshots.c @@ -44,3 +44,43 @@ menu_t OPTIONS_DataScreenshotDef = { NULL, NULL, }; + +void Screenshot_option_Onchange(void) +{ + // Screenshot opt is at #3, 0 based array obv. + OPTIONS_DataScreenshot[2].status = + (cv_screenshot_option.value == 3 ? IT_CVAR|IT_STRING|IT_CV_STRING : IT_DISABLED); + +} + +void Moviemode_mode_Onchange(void) +{ +#if 0 + INT32 i, cstart, cend; + for (i = op_screenshot_gif_start; i <= op_screenshot_apng_end; ++i) + OP_ScreenshotOptionsMenu[i].status = IT_DISABLED; + + switch (cv_moviemode.value) + { + case MM_GIF: + cstart = op_screenshot_gif_start; + cend = op_screenshot_gif_end; + break; + case MM_APNG: + cstart = op_screenshot_apng_start; + cend = op_screenshot_apng_end; + break; + default: + return; + } + for (i = cstart; i <= cend; ++i) + OP_ScreenshotOptionsMenu[i].status = IT_STRING|IT_CVAR; +#endif +} + +void Moviemode_option_Onchange(void) +{ + // opt 7 in a 0 based array, you get the idea... + OPTIONS_DataScreenshot[6].status = + (cv_movie_option.value == 3 ? IT_CVAR|IT_STRING|IT_CV_STRING : IT_DISABLED); +} diff --git a/src/menus/options-gameplay-item-toggles.c b/src/menus/options-gameplay-item-toggles.c index 3479d5998..4c6e76080 100644 --- a/src/menus/options-gameplay-item-toggles.c +++ b/src/menus/options-gameplay-item-toggles.c @@ -2,6 +2,7 @@ /// \brief Random Item Toggles #include "../k_menu.h" +#include "../s_sound.h" menuitem_t OPTIONS_GameplayItems[] = { @@ -60,3 +61,122 @@ menu_t OPTIONS_GameplayItemsDef = { NULL, NULL, }; + +void M_HandleItemToggles(INT32 choice) +{ + const INT32 width = 8, height = 4; + INT32 column = itemOn/height, row = itemOn%height; + INT16 next; + UINT8 i; + boolean exitmenu = false; + const UINT8 pid = 0; + + (void) choice; + + if (menucmd[pid].dpad_lr > 0) + { + S_StartSound(NULL, sfx_s3k5b); + column++; + if (((column*height)+row) >= currentMenu->numitems) + column = 0; + next = min(((column*height)+row), currentMenu->numitems-1); + itemOn = next; + + M_SetMenuDelay(pid); + } + + else if (menucmd[pid].dpad_lr < 0) + { + S_StartSound(NULL, sfx_s3k5b); + column--; + if (column < 0) + column = width-1; + if (((column*height)+row) >= currentMenu->numitems) + column--; + next = max(((column*height)+row), 0); + if (next >= currentMenu->numitems) + next = currentMenu->numitems-1; + itemOn = next; + + M_SetMenuDelay(pid); + } + + else if (menucmd[pid].dpad_ud > 0) + { + S_StartSound(NULL, sfx_s3k5b); + row = (row+1) % height; + if (((column*height)+row) >= currentMenu->numitems) + row = 0; + next = min(((column*height)+row), currentMenu->numitems-1); + itemOn = next; + + M_SetMenuDelay(pid); + } + + else if (menucmd[pid].dpad_ud < 0) + { + S_StartSound(NULL, sfx_s3k5b); + row = (row-1) % height; + if (row < 0) + row = height-1; + if (((column*height)+row) >= currentMenu->numitems) + row--; + next = max(((column*height)+row), 0); + if (next >= currentMenu->numitems) + next = currentMenu->numitems-1; + itemOn = next; + + M_SetMenuDelay(pid); + } + + else if (M_MenuConfirmPressed(pid)) + { + M_SetMenuDelay(pid); + if (currentMenu->menuitems[itemOn].mvar1 == 255) + { + //S_StartSound(NULL, sfx_s26d); + if (!shitsfree) + { + shitsfree = TICRATE; + S_StartSound(NULL, sfx_itfree); + } + } + else + if (currentMenu->menuitems[itemOn].mvar1 == 0) + { + INT32 v = cv_items[0].value; + S_StartSound(NULL, sfx_s1b4); + for (i = 0; i < NUMKARTRESULTS-1; i++) + { + if (cv_items[i].value == v) + CV_AddValue(&cv_items[i], 1); + } + } + else + { + if (currentMenu->menuitems[itemOn].mvar2) + { + S_StartSound(NULL, currentMenu->menuitems[itemOn].mvar2); + } + else + { + S_StartSound(NULL, sfx_s1ba); + } + CV_AddValue(&cv_items[currentMenu->menuitems[itemOn].mvar1-1], 1); + } + } + + else if (M_MenuBackPressed(pid)) + { + M_SetMenuDelay(pid); + exitmenu = true; + } + + if (exitmenu) + { + if (currentMenu->prevMenu) + M_SetupNextMenu(currentMenu->prevMenu, false); + else + M_ClearMenus(true); + } +} diff --git a/src/menus/options-profiles-1.c b/src/menus/options-profiles-1.c index a61eb1ef5..bc0fc280c 100644 --- a/src/menus/options-profiles-1.c +++ b/src/menus/options-profiles-1.c @@ -2,6 +2,7 @@ /// \brief Profiles Menu #include "../k_menu.h" +#include "../s_sound.h" // profile select menuitem_t OPTIONS_Profiles[] = { @@ -23,3 +24,195 @@ menu_t OPTIONS_ProfilesDef = { NULL, NULL, }; + +consvar_t cv_dummyprofilename = CVAR_INIT ("dummyprofilename", "", CV_HIDDEN, NULL, NULL); +consvar_t cv_dummyprofileplayername = CVAR_INIT ("dummyprofileplayername", "", CV_HIDDEN, NULL, NULL); +consvar_t cv_dummyprofilekickstart = CVAR_INIT ("dummyprofilekickstart", "Off", CV_HIDDEN, CV_OnOff, NULL); + +void M_ProfileSelectInit(INT32 choice) +{ + (void)choice; + optionsmenu.profilemenu = true; + + M_SetupNextMenu(&OPTIONS_ProfilesDef, false); +} + +// Select the current profile for menu use and go to maindef. +void M_FirstPickProfile(INT32 c) +{ + if (c == MA_YES) + { + M_ResetOptions(); // Reset all options variables otherwise things are gonna go reaaal bad lol. + optionsmenu.profile = NULL; // Make sure to get rid of that, too. + + PR_ApplyProfile(optionsmenu.profilen, 0); + M_SetupNextMenu(M_InterruptMenuWithChallenges(&MainDef), false); + + // Tell the game this is the last profile we picked. + CV_StealthSetValue(&cv_ttlprofilen, optionsmenu.profilen); + + // Save em! + PR_SaveProfiles(); + return; + } +} + +// Start menu edition. Call this with MA_YES if not used with a textbox. +static void M_StartEditProfile(INT32 c) +{ + + const INT32 maxp = PR_GetNumProfiles(); + + if (c == MA_YES) + { + if (optionsmenu.profilen == maxp) + PR_InitNewProfile(); // initialize the new profile. + + optionsmenu.profile = PR_GetProfile(optionsmenu.profilen); + // copy this profile's controls into optionsmenu so that we can edit controls without changing them directly. + // we do this so that we don't edit a profile's controls in real-time and end up doing really weird shit. + memcpy(&optionsmenu.tempcontrols, optionsmenu.profile->controls, sizeof(gamecontroldefault)); + + // This is now used to move the card we've selected. + optionsmenu.optx = 160; + optionsmenu.opty = 35; + optionsmenu.toptx = 130/2; + optionsmenu.topty = 0; + + // setup cvars + if (optionsmenu.profile->version) + { + CV_StealthSet(&cv_dummyprofilename, optionsmenu.profile->profilename); + CV_StealthSet(&cv_dummyprofileplayername, optionsmenu.profile->playername); + CV_StealthSetValue(&cv_dummyprofilekickstart, optionsmenu.profile->kickstartaccel); + } + else + { + CV_StealthSet(&cv_dummyprofilename, ""); + CV_StealthSet(&cv_dummyprofileplayername, ""); + CV_StealthSetValue(&cv_dummyprofilekickstart, 0); // off + } + + // Setup greyout and stuff. + OPTIONS_EditProfile[popt_profilename].status = IT_STRING | IT_CVAR | IT_CV_STRING; + OPTIONS_EditProfile[popt_profilepname].status = IT_STRING | IT_CVAR | IT_CV_STRING; + OPTIONS_EditProfile[popt_char].status = IT_STRING | IT_CALL; + + if (gamestate != GS_MENU) // If we're modifying things mid game, transtext some of those! + { + OPTIONS_EditProfile[popt_profilename].status |= IT_TRANSTEXT; + OPTIONS_EditProfile[popt_profilepname].status |= IT_TRANSTEXT; + OPTIONS_EditProfile[popt_char].status |= IT_TRANSTEXT; + } + + M_SetupNextMenu(&OPTIONS_EditProfileDef, false); + return; + } +} + +void M_HandleProfileSelect(INT32 ch) +{ + const UINT8 pid = 0; + INT32 maxp = PR_GetNumProfiles(); + boolean creatable = (maxp < MAXPROFILES); + (void) ch; + + if (!creatable) + { + maxp = MAXPROFILES; + } + + if (menucmd[pid].dpad_lr > 0) + { + optionsmenu.profilen++; + optionsmenu.offset += (128 + 128/8); + + if (optionsmenu.profilen > maxp) + { + optionsmenu.profilen = 0; + optionsmenu.offset -= (128 + 128/8)*(maxp+1); + } + + S_StartSound(NULL, sfx_s3k5b); + M_SetMenuDelay(pid); + + } + else if (menucmd[pid].dpad_lr < 0) + { + optionsmenu.profilen--; + optionsmenu.offset -= (128 + 128/8); + + if (optionsmenu.profilen < 0) + { + optionsmenu.profilen = maxp; + optionsmenu.offset += (128 + 128/8)*(maxp+1); + } + + S_StartSound(NULL, sfx_s3k5b); + M_SetMenuDelay(pid); + } + + else if (M_MenuConfirmPressed(pid)) + { + + // Boot profile setup has already been done. + if (cv_currprofile.value > -1) + { + + if (optionsmenu.profilen == 0) // Guest profile, you can't edit that one! + { + S_StartSound(NULL, sfx_s3k7b); + M_StartMessage(M_GetText("The Guest profile cannot be edited.\nCreate a new profile instead."), NULL, MM_NOTHING); + M_SetMenuDelay(pid); + return; + } + else if (creatable && optionsmenu.profilen == maxp && gamestate != GS_MENU) + { + S_StartSound(NULL, sfx_s3k7b); + M_StartMessage(M_GetText("Cannot create a new profile\nmid-game. Return to the\ntitle screen first."), NULL, MM_NOTHING); + M_SetMenuDelay(pid); + return; + } + + S_StartSound(NULL, sfx_s3k5b); + M_StartEditProfile(MA_YES); + } + else + { + // We're on the profile selection screen. + if (creatable && optionsmenu.profilen == maxp) + { + M_StartEditProfile(MA_YES); + M_SetMenuDelay(pid); + return; + } + else + { +#if 0 + if (optionsmenu.profilen == 0) + { + M_StartMessage(M_GetText("Are you sure you wish\nto use the Guest Profile?\nThis profile cannot be customised.\nIt is recommended to create\na new Profile instead.\n\nPress (A) to confirm or (B) to cancel"), FUNCPTRCAST(M_FirstPickProfile), MM_YESNO); + return; + } +#endif + + M_FirstPickProfile(MA_YES); + M_SetMenuDelay(pid); + return; + } + } + } + + else if (M_MenuBackPressed(pid)) + { + optionsmenu.resetprofilemenu = true; + M_GoBack(0); + M_SetMenuDelay(pid); + } + + if (menutransition.tics == 0 && optionsmenu.resetprofile) + { + optionsmenu.profile = NULL; // Make sure to reset that when transitions are done.' + optionsmenu.resetprofile = false; + } +} diff --git a/src/menus/options-profiles-edit-1.c b/src/menus/options-profiles-edit-1.c index 2e9f91c17..061acbf6a 100644 --- a/src/menus/options-profiles-edit-1.c +++ b/src/menus/options-profiles-edit-1.c @@ -2,6 +2,7 @@ /// \brief Profile Editor #include "../k_menu.h" +#include "../s_sound.h" menuitem_t OPTIONS_EditProfile[] = { {IT_STRING | IT_CVAR | IT_CV_STRING, "Profile Name", "6-character long name to identify this Profile.", @@ -35,3 +36,145 @@ menu_t OPTIONS_EditProfileDef = { NULL, M_ProfileEditInputs, }; + +// Returns true if the profile can be saved, false otherwise. Also starts messages if necessary. +static boolean M_ProfileEditEnd(const UINT8 pid) +{ + UINT8 i; + + // Guest profile, you can't edit that one! + if (optionsmenu.profilen == 0) + { + S_StartSound(NULL, sfx_s3k7b); + M_StartMessage(M_GetText("Guest profile cannot be edited.\nCreate a new profile instead."), NULL, MM_NOTHING); + M_SetMenuDelay(pid); + return false; + } + + // check if some profiles have the same name + for (i = 0; i < PR_GetNumProfiles(); i++) + { + profile_t *check = PR_GetProfile(i); + + // For obvious reasons don't check if our name is the same as our name.... + if (check != optionsmenu.profile) + { + if (!(strcmp(optionsmenu.profile->profilename, check->profilename))) + { + S_StartSound(NULL, sfx_s3k7b); + M_StartMessage(M_GetText("Another profile uses the same name.\nThis must be changed to be able to save."), NULL, MM_NOTHING); + M_SetMenuDelay(pid); + return false; + } + } + } + + return true; +} + +static void M_ProfileEditExit(void) +{ + optionsmenu.toptx = 160; + optionsmenu.topty = 35; + optionsmenu.resetprofile = true; // Reset profile after the transition is done. + + PR_SaveProfiles(); // save profiles after we do that. +} + +// For profile edit, just make sure going back resets the card to its position, the rest is taken care of automatically. +boolean M_ProfileEditInputs(INT32 ch) +{ + + (void) ch; + const UINT8 pid = 0; + + if (M_MenuBackPressed(pid)) + { + if (M_ProfileEditEnd(pid)) + { + M_ProfileEditExit(); + if (cv_currprofile.value == -1) + M_SetupNextMenu(&MAIN_ProfilesDef, false); + else + M_GoBack(0); + M_SetMenuDelay(pid); + } + return true; + } + else if (M_MenuConfirmPressed(pid)) + { + if (currentMenu->menuitems[itemOn].status & IT_TRANSTEXT) + return true; // No. + } + + return false; +} + +// Handle some actions in profile editing +void M_HandleProfileEdit(void) +{ + // Always copy the profile name and player name in the profile. + if (optionsmenu.profile) + { + // Copy the first 6 chars for profile name + if (strlen(cv_dummyprofilename.string)) + { + char *s; + // convert dummyprofilename to uppercase + strncpy(optionsmenu.profile->profilename, cv_dummyprofilename.string, PROFILENAMELEN); + s = optionsmenu.profile->profilename; + while (*s) + { + *s = toupper(*s); + s++; + } + } + + if (strlen(cv_dummyprofileplayername.string)) + strncpy(optionsmenu.profile->playername, cv_dummyprofileplayername.string, MAXPLAYERNAME); + } + + M_OptionsTick(); // Has to be afterwards because this can unset optionsmenu.profile +} + +// Confirm Profile edi via button. +void M_ConfirmProfile(INT32 choice) +{ + const UINT8 pid = 0; + (void) choice; + + if (M_ProfileEditEnd(pid)) + { + if (cv_currprofile.value > -1) + { + M_ProfileEditExit(); + M_GoBack(0); + M_SetMenuDelay(pid); + } + else + { + M_StartMessage(M_GetText("Are you sure you wish to\nselect this profile?\n\nPress (A) to confirm or (B) to cancel"), FUNCPTRCAST(M_FirstPickProfile), MM_YESNO); + M_SetMenuDelay(pid); + } + } + return; +} + +// Prompt a device selection window (just tap any button on the device you want) +void M_ProfileDeviceSelect(INT32 choice) +{ + (void)choice; + + // While we're here, setup the incoming controls menu to reset the scroll & bind status: + optionsmenu.controlscroll = 0; + optionsmenu.bindcontrol = 0; + optionsmenu.bindtimer = 0; + + optionsmenu.lastkey = 0; + optionsmenu.keyheldfor = 0; + + optionsmenu.contx = optionsmenu.tcontx = controlleroffsets[gc_a][0]; + optionsmenu.conty = optionsmenu.tconty = controlleroffsets[gc_a][1]; + + M_SetupNextMenu(&OPTIONS_ProfileControlsDef, false); // Don't set device here anymore. +} diff --git a/src/menus/options-profiles-edit-controls.c b/src/menus/options-profiles-edit-controls.c index 774bcdfa4..d1ac713f2 100644 --- a/src/menus/options-profiles-edit-controls.c +++ b/src/menus/options-profiles-edit-controls.c @@ -2,6 +2,8 @@ /// \brief Profile Controls Editor #include "../k_menu.h" +#include "../s_sound.h" +#include "../i_joy.h" // for joystick menu controls menuitem_t OPTIONS_ProfileControls[] = { @@ -108,3 +110,339 @@ menu_t OPTIONS_ProfileControlsDef = { NULL, M_ProfileControlsInputs, }; + +// sets whatever device has had its key pressed to the active device. +// 20/05/22: Commented out for now but not deleted as it might still find some use in the future? +/* +static void SetDeviceOnPress(void) +{ + UINT8 i; + + for (i=0; i < MAXDEVICES; i++) + { + if (deviceResponding[i]) + { + CV_SetValue(&cv_usejoystick[0], i); // Force-set this joystick as the current joystick we're using for P1 (which is the only one controlling menus) + CONS_Printf("SetDeviceOnPress: Device for %d set to %d\n", 0, i); + return; + } + } +} +*/ + +void M_HandleProfileControls(void) +{ + UINT8 maxscroll = currentMenu->numitems - 5; + M_OptionsTick(); + + optionsmenu.contx += (optionsmenu.tcontx - optionsmenu.contx)/2; + optionsmenu.conty += (optionsmenu.tconty - optionsmenu.conty)/2; + + if (abs(optionsmenu.contx - optionsmenu.tcontx) < 2 && abs(optionsmenu.conty - optionsmenu.tconty) < 2) + { + optionsmenu.contx = optionsmenu.tcontx; + optionsmenu.conty = optionsmenu.tconty; // Avoid awkward 1 px errors. + } + + optionsmenu.controlscroll = itemOn - 3; // very barebones scrolling, but it works just fine for our purpose. + if (optionsmenu.controlscroll > maxscroll) + optionsmenu.controlscroll = maxscroll; + + if (optionsmenu.controlscroll < 0) + optionsmenu.controlscroll = 0; + + // bindings, cancel if timer is depleted. + if (optionsmenu.bindcontrol) + { + optionsmenu.bindtimer--; + if (!optionsmenu.bindtimer) + { + optionsmenu.bindcontrol = 0; // we've gone past the max, just stop. + } + + } +} + +void M_ProfileTryController(INT32 choice) +{ + (void)choice; + + optionsmenu.trycontroller = TICRATE*5; + + // Apply these controls right now on P1's end. + memcpy(&gamecontrol[0], optionsmenu.tempcontrols, sizeof(gamecontroldefault)); +} + +static void M_ProfileControlSaveResponse(INT32 choice) +{ + if (choice == MA_YES) + { + SINT8 belongsto = PR_ProfileUsedBy(optionsmenu.profile); + // Save the profile + optionsmenu.profile->kickstartaccel = cv_dummyprofilekickstart.value; + memcpy(&optionsmenu.profile->controls, optionsmenu.tempcontrols, sizeof(gamecontroldefault)); + + // If this profile is in-use by anyone, apply the changes immediately upon exiting. + // Don't apply the profile itself as that would lead to issues mid-game. + if (belongsto > -1 && belongsto < MAXSPLITSCREENPLAYERS) + { + memcpy(&gamecontrol[belongsto], optionsmenu.tempcontrols, sizeof(gamecontroldefault)); + CV_StealthSetValue(&cv_kickstartaccel[belongsto], cv_dummyprofilekickstart.value); + } + + M_GoBack(0); + } +} + +void M_ProfileControlsConfirm(INT32 choice) +{ + (void)choice; + + //M_StartMessage(M_GetText("Exiting will save the control changes\nfor this Profile.\nIs this okay?\n\nPress (A) to confirm or (B) to cancel"), FUNCPTRCAST(M_ProfileControlSaveResponse), MM_YESNO); + // TODO: Add a graphic for controls saving, instead of obnoxious prompt. + + M_ProfileControlSaveResponse(MA_YES); + + optionsmenu.profile->kickstartaccel = cv_dummyprofilekickstart.value; // Make sure to save kickstart accel. + + // Reapply player 1's real profile. + if (cv_currprofile.value > -1) + { + PR_ApplyProfile(cv_lastprofile[0].value, 0); + } +} + +boolean M_ProfileControlsInputs(INT32 ch) +{ + const UINT8 pid = 0; + (void)ch; + + // By default, accept all inputs. + if (optionsmenu.trycontroller) + { + if (menucmd[pid].dpad_ud || menucmd[pid].dpad_lr || menucmd[pid].buttons) + { + optionsmenu.trycontroller = 5*TICRATE; + } + else + { + optionsmenu.trycontroller--; + } + + if (optionsmenu.trycontroller == 0) + { + // Reset controls to that of the current profile. + profile_t *cpr = PR_GetProfile(cv_currprofile.value); + if (cpr == NULL) + cpr = PR_GetProfile(0); // Creating a profile at boot, revert to guest profile + memcpy(&gamecontrol[0], cpr->controls, sizeof(gamecontroldefault)); + } + + return true; + } + + if (optionsmenu.bindcontrol) + return true; // Eat all inputs there. We'll use a stupid hack in M_Responder instead. + + //SetDeviceOnPress(); // Update device constantly so that we don't stay stuck with otpions saying a device is unavailable just because we're mapping multiple devices... + + if (M_MenuExtraPressed(pid)) + { + // check if we're on a valid menu option... + if (currentMenu->menuitems[itemOn].mvar1) + { + // clear controls for that key + INT32 i; + + for (i = 0; i < MAXINPUTMAPPING; i++) + optionsmenu.tempcontrols[currentMenu->menuitems[itemOn].mvar1][i] = KEY_NULL; + + S_StartSound(NULL, sfx_s3k66); + } + M_SetMenuDelay(pid); + return true; + } + else if (M_MenuBackPressed(pid)) + { + M_ProfileControlsConfirm(0); + M_SetMenuDelay(pid); + return true; + } + + return false; +} + +void M_ProfileSetControl(INT32 ch) +{ + INT32 controln = currentMenu->menuitems[itemOn].mvar1; + UINT8 i; + (void) ch; + + optionsmenu.bindcontrol = 1; // Default to control #1 + + for (i = 0; i < MAXINPUTMAPPING; i++) + { + if (optionsmenu.tempcontrols[controln][i] == KEY_NULL) + { + optionsmenu.bindcontrol = i+1; + break; + } + } + + // If we could find a null key to map into, map there. + // Otherwise, this will stay at 1 which means we'll overwrite the first bound control. + + optionsmenu.bindtimer = TICRATE*5; +} + +// Map the event to the profile. + +#define KEYHOLDFOR 1 +void M_MapProfileControl(event_t *ev) +{ + INT32 c = 0; + UINT8 n = optionsmenu.bindcontrol-1; // # of input to bind + INT32 controln = currentMenu->menuitems[itemOn].mvar1; // gc_ + UINT8 where = n; // By default, we'll save the bind where we're supposed to map. + INT32 i; + + //SetDeviceOnPress(); // Update cv_usejoystick + + // Only consider keydown and joystick events to make sure we ignore ev_mouse and other events + // See also G_MapEventsToControls + switch (ev->type) + { + case ev_keydown: + if (ev->data1 < NUMINPUTS) + { + c = ev->data1; + } +#ifdef PARANOIA + else + { + CONS_Debug(DBG_GAMELOGIC, "Bad downkey input %d\n", ev->data1); + } +#endif + break; + case ev_joystick: + if (ev->data1 >= JOYAXES) + { +#ifdef PARANOIA + CONS_Debug(DBG_GAMELOGIC, "Bad joystick axis event %d\n", ev->data1); +#endif + return; + } + else + { + INT32 deadzone = deadzone = (JOYAXISRANGE * cv_deadzone[0].value) / FRACUNIT; // TODO how properly account for different deadzone cvars for different devices + boolean responsivelr = ((ev->data2 != INT32_MAX) && (abs(ev->data2) >= deadzone)); + boolean responsiveud = ((ev->data3 != INT32_MAX) && (abs(ev->data3) >= deadzone)); + + i = ev->data1; + + if (i >= JOYANALOGS) + { + // The trigger axes are handled specially. + i -= JOYANALOGS; + + if (responsivelr) + { + c = KEY_AXIS1 + (JOYANALOGS * 4) + (i * 2); + } + else if (responsiveud) + { + c = KEY_AXIS1 + (JOYANALOGS * 4) + (i * 2) + 1; + } + } + else + { + // Actual analog sticks + + // Only consider unambiguous assignment. + if (responsivelr == responsiveud) + return; + + if (responsivelr) + { + if (ev->data2 < 0) + { + // Left + c = KEY_AXIS1 + (i * 4); + } + else + { + // Right + c = KEY_AXIS1 + (i * 4) + 1; + } + } + else //if (responsiveud) + { + if (ev->data3 < 0) + { + // Up + c = KEY_AXIS1 + (i * 4) + 2; + } + else + { + // Down + c = KEY_AXIS1 + (i * 4) + 3; + } + } + } + } + break; + default: + return; + } + + // safety result + if (!c) + return; + + // Set menu delay regardless of what we're doing to avoid stupid stuff. + M_SetMenuDelay(0); + + // Check if this particular key (c) is already bound in any slot. + // If that's the case, simply do nothing. + for (i = 0; i < MAXINPUTMAPPING; i++) + { + if (optionsmenu.tempcontrols[controln][i] == c) + { + optionsmenu.bindcontrol = 0; + return; + } + } + + // With the way we do things, there cannot be instances of 'gaps' within the controls, so we don't need to pretend like we need to handle that. + // Unless of course you tamper with the cfg file, but then it's *your* fault, not mine. + + optionsmenu.tempcontrols[controln][where] = c; + optionsmenu.bindcontrol = 0; // not binding anymore + + // If possible, reapply the profile... + // 19/05/22: Actually, no, don't do that, it just fucks everything up in too many cases. + + /* + if (gamestate == GS_MENU) // In menu? Apply this to P1, no questions asked. + { + // Apply the profile's properties to player 1 but keep the last profile cv to p1's ACTUAL profile to revert once we exit. + UINT8 lastp = cv_lastprofile[0].value; + PR_ApplyProfile(PR_GetProfileNum(optionsmenu.profile), 0); + CV_StealthSetValue(&cv_lastprofile[0], lastp); + } + else // != GS_MENU + { + // ONLY apply the profile if it's in use by anything currently. + UINT8 pnum = PR_GetProfileNum(optionsmenu.profile); + for (i = 0; i < MAXSPLITSCREENPLAYERS; i++) + { + if (cv_lastprofile[i].value == pnum) + { + PR_ApplyProfile(pnum, i); + break; + } + } + } + */ +} +#undef KEYHOLDFOR diff --git a/src/menus/options-video-modes.c b/src/menus/options-video-modes.c index 160820abf..85f2cd70b 100644 --- a/src/menus/options-video-modes.c +++ b/src/menus/options-video-modes.c @@ -2,6 +2,8 @@ /// \brief Video modes (resolutions) #include "../k_menu.h" +#include "../i_video.h" +#include "../s_sound.h" menuitem_t OPTIONS_VideoModes[] = { @@ -24,3 +26,169 @@ menu_t OPTIONS_VideoModesDef = { NULL, NULL, }; + +// setup video mode menu +void M_VideoModeMenu(INT32 choice) +{ + INT32 i, j, vdup, nummodes; + UINT32 width, height; + const char *desc; + + (void)choice; + + memset(optionsmenu.modedescs, 0, sizeof(optionsmenu.modedescs)); + +#if (defined (__unix__) && !defined (MSDOS)) || defined (UNIXCOMMON) || defined (HAVE_SDL) + VID_PrepareModeList(); // FIXME: hack +#endif + optionsmenu.vidm_nummodes = 0; + optionsmenu.vidm_selected = 0; + nummodes = VID_NumModes(); + + // DOS does not skip mode 0, because mode 0 is ALWAYS present + i = 0; + for (; i < nummodes && optionsmenu.vidm_nummodes < MAXMODEDESCS; i++) + { + desc = VID_GetModeName(i); + if (desc) + { + vdup = 0; + + // when a resolution exists both under VGA and VESA, keep the + // VESA mode, which is always a higher modenum + for (j = 0; j < optionsmenu.vidm_nummodes; j++) + { + if (!strcmp(optionsmenu.modedescs[j].desc, desc)) + { + // mode(0): 320x200 is always standard VGA, not vesa + if (optionsmenu.modedescs[j].modenum) + { + optionsmenu.modedescs[j].modenum = i; + vdup = 1; + + if (i == vid.modenum) + optionsmenu.vidm_selected = j; + } + else + vdup = 1; + + break; + } + } + + if (!vdup) + { + optionsmenu.modedescs[optionsmenu.vidm_nummodes].modenum = i; + optionsmenu.modedescs[optionsmenu.vidm_nummodes].desc = desc; + + if (i == vid.modenum) + optionsmenu.vidm_selected = optionsmenu.vidm_nummodes; + + // Pull out the width and height + sscanf(desc, "%u%*c%u", &width, &height); + + // Show multiples of 320x200 as green. + if (SCR_IsAspectCorrect(width, height)) + optionsmenu.modedescs[optionsmenu.vidm_nummodes].goodratio = 1; + + optionsmenu.vidm_nummodes++; + } + } + } + + optionsmenu.vidm_column_size = (optionsmenu.vidm_nummodes+2) / 3; + + M_SetupNextMenu(&OPTIONS_VideoModesDef, false); +} + +// special menuitem key handler for video mode list +void M_HandleVideoModes(INT32 ch) +{ + + const UINT8 pid = 0; + (void)ch; + + if (optionsmenu.vidm_testingmode > 0) + { + // change back to the previous mode quickly + if (M_MenuBackPressed(pid)) + { + setmodeneeded = optionsmenu.vidm_previousmode + 1; + optionsmenu.vidm_testingmode = 0; + } + else if (M_MenuConfirmPressed(pid)) + { + S_StartSound(NULL, sfx_s3k5b); + optionsmenu.vidm_testingmode = 0; // stop testing + } + } + + else + { + if (menucmd[pid].dpad_ud > 0) + { + S_StartSound(NULL, sfx_s3k5b); + if (++optionsmenu.vidm_selected >= optionsmenu.vidm_nummodes) + optionsmenu.vidm_selected = 0; + + M_SetMenuDelay(pid); + } + + else if (menucmd[pid].dpad_ud < 0) + { + S_StartSound(NULL, sfx_s3k5b); + if (--optionsmenu.vidm_selected < 0) + optionsmenu.vidm_selected = optionsmenu.vidm_nummodes - 1; + + M_SetMenuDelay(pid); + } + + else if (menucmd[pid].dpad_lr < 0) + { + S_StartSound(NULL, sfx_s3k5b); + optionsmenu.vidm_selected -= optionsmenu.vidm_column_size; + if (optionsmenu.vidm_selected < 0) + optionsmenu.vidm_selected = (optionsmenu.vidm_column_size*3) + optionsmenu.vidm_selected; + if (optionsmenu.vidm_selected >= optionsmenu.vidm_nummodes) + optionsmenu.vidm_selected = optionsmenu.vidm_nummodes - 1; + + M_SetMenuDelay(pid); + } + + else if (menucmd[pid].dpad_lr > 0) + { + S_StartSound(NULL, sfx_s3k5b); + optionsmenu.vidm_selected += optionsmenu.vidm_column_size; + if (optionsmenu.vidm_selected >= (optionsmenu.vidm_column_size*3)) + optionsmenu.vidm_selected %= optionsmenu.vidm_column_size; + if (optionsmenu.vidm_selected >= optionsmenu.vidm_nummodes) + optionsmenu.vidm_selected = optionsmenu.vidm_nummodes - 1; + + M_SetMenuDelay(pid); + } + + else if (M_MenuConfirmPressed(pid)) + { + M_SetMenuDelay(pid); + S_StartSound(NULL, sfx_s3k5b); + if (vid.modenum == optionsmenu.modedescs[optionsmenu.vidm_selected].modenum) + SCR_SetDefaultMode(); + else + { + optionsmenu.vidm_testingmode = 15*TICRATE; + optionsmenu.vidm_previousmode = vid.modenum; + if (!setmodeneeded) // in case the previous setmode was not finished + setmodeneeded = optionsmenu.modedescs[optionsmenu.vidm_selected].modenum + 1; + } + } + + else if (M_MenuBackPressed(pid)) + { + M_SetMenuDelay(pid); + if (currentMenu->prevMenu) + M_SetupNextMenu(currentMenu->prevMenu, false); + else + M_ClearMenus(true); + } + } +} diff --git a/src/menus/play-char-select.c b/src/menus/play-char-select.c index cf3306cca..1116dfa06 100644 --- a/src/menus/play-char-select.c +++ b/src/menus/play-char-select.c @@ -2,6 +2,8 @@ /// \brief Character Select #include "../k_menu.h" +#include "../r_skins.h" +#include "../s_sound.h" menuitem_t PLAY_CharSelect[] = { @@ -22,3 +24,1461 @@ menu_t PLAY_CharSelectDef = { M_CharacterSelectQuit, M_CharacterSelectHandler }; + +static CV_PossibleValue_t skins_cons_t[MAXSKINS+1] = {{1, DEFAULTSKIN}}; +consvar_t cv_chooseskin = CVAR_INIT ("chooseskin", DEFAULTSKIN, CV_HIDDEN, skins_cons_t, NULL); + +static void Splitplayers_OnChange(void); +CV_PossibleValue_t splitplayers_cons_t[] = {{1, "One"}, {2, "Two"}, {3, "Three"}, {4, "Four"}, {0, NULL}}; +consvar_t cv_splitplayers = CVAR_INIT ("splitplayers", "One", CV_CALL, splitplayers_cons_t, Splitplayers_OnChange); + +UINT16 nummenucolors = 0; + +// Character Select! +// @TODO: Splitscreen handling when profiles are added into the game. ...I probably won't be the one to handle this however. -Lat' + +struct setup_chargrid_s setup_chargrid[9][9]; +setup_player_t setup_player[MAXSPLITSCREENPLAYERS]; + +UINT8 setup_followercategories[MAXFOLLOWERCATEGORIES][2]; +UINT8 setup_numfollowercategories; + +UINT8 setup_numplayers = 0; // This variable is very important, it was extended to determine how many players exist in ALL menus. +tic_t setup_animcounter = 0; + +UINT8 setup_page = 0; +UINT8 setup_maxpage = 0; // For charsel page to identify alts easier... + +void M_AddMenuColor(UINT16 color) { + menucolor_t *c; + + if (color >= numskincolors) { + CONS_Printf("M_AddMenuColor: color %d does not exist.",color); + return; + } + + // SRB2Kart: I do not understand vanilla doesn't need this but WE do???!?!??! + if (!skincolors[color].accessible) { + return; + } + + c = (menucolor_t *)malloc(sizeof(menucolor_t)); + c->color = color; + if (menucolorhead == NULL) { + c->next = c; + c->prev = c; + menucolorhead = c; + menucolortail = c; + } else { + c->next = menucolorhead; + c->prev = menucolortail; + menucolortail->next = c; + menucolorhead->prev = c; + menucolortail = c; + } + + nummenucolors++; +} + +void M_MoveColorBefore(UINT16 color, UINT16 targ) { + menucolor_t *look, *c = NULL, *t = NULL; + + if (color == targ) + return; + if (color >= numskincolors) { + CONS_Printf("M_MoveColorBefore: color %d does not exist.",color); + return; + } + if (targ >= numskincolors) { + CONS_Printf("M_MoveColorBefore: target color %d does not exist.",targ); + return; + } + + for (look=menucolorhead;;look=look->next) { + if (look->color == color) + c = look; + else if (look->color == targ) + t = look; + if (c != NULL && t != NULL) + break; + if (look==menucolortail) + return; + } + + if (c == t->prev) + return; + + if (t==menucolorhead) + menucolorhead = c; + if (c==menucolortail) + menucolortail = c->prev; + + c->prev->next = c->next; + c->next->prev = c->prev; + + c->prev = t->prev; + c->next = t; + t->prev->next = c; + t->prev = c; +} + +void M_MoveColorAfter(UINT16 color, UINT16 targ) { + menucolor_t *look, *c = NULL, *t = NULL; + + if (color == targ) + return; + if (color >= numskincolors) { + CONS_Printf("M_MoveColorAfter: color %d does not exist.\n",color); + return; + } + if (targ >= numskincolors) { + CONS_Printf("M_MoveColorAfter: target color %d does not exist.\n",targ); + return; + } + + for (look=menucolorhead;;look=look->next) { + if (look->color == color) + c = look; + else if (look->color == targ) + t = look; + if (c != NULL && t != NULL) + break; + if (look==menucolortail) + return; + } + + if (t == c->prev) + return; + + if (t==menucolortail) + menucolortail = c; + else if (c==menucolortail) + menucolortail = c->prev; + + c->prev->next = c->next; + c->next->prev = c->prev; + + c->next = t->next; + c->prev = t; + t->next->prev = c; + t->next = c; +} + +UINT16 M_GetColorBefore(UINT16 color, UINT16 amount, boolean follower) +{ + menucolor_t *look = NULL; + + for (; amount > 0; amount--) + { + if (follower == true) + { + if (color == FOLLOWERCOLOR_OPPOSITE) + { + look = menucolortail; + color = menucolortail->color; + continue; + } + + if (color == FOLLOWERCOLOR_MATCH) + { + look = NULL; + color = FOLLOWERCOLOR_OPPOSITE; + continue; + } + + if (color == menucolorhead->color) + { + look = NULL; + color = FOLLOWERCOLOR_MATCH; + continue; + } + } + + if (color == 0 || color >= numskincolors) + { + CONS_Printf("M_GetColorBefore: color %d does not exist.\n",color); + return 0; + } + + if (look == NULL) + { + for (look = menucolorhead;; look = look->next) + { + if (look->color == color) + { + break; + } + if (look == menucolortail) + { + return 0; + } + } + } + + look = look->prev; + color = look->color; + } + return color; +} + +UINT16 M_GetColorAfter(UINT16 color, UINT16 amount, boolean follower) +{ + menucolor_t *look = NULL; + + for (; amount > 0; amount--) + { + if (follower == true) + { + if (color == menucolortail->color) + { + look = NULL; + color = FOLLOWERCOLOR_OPPOSITE; + continue; + } + + if (color == FOLLOWERCOLOR_OPPOSITE) + { + look = NULL; + color = FOLLOWERCOLOR_MATCH; + continue; + } + + if (color == FOLLOWERCOLOR_MATCH) + { + look = menucolorhead; + color = menucolorhead->color; + continue; + } + } + + if (color == 0 || color >= numskincolors) + { + CONS_Printf("M_GetColorAfter: color %d does not exist.\n",color); + return 0; + } + + if (look == NULL) + { + for (look = menucolorhead;; look = look->next) + { + if (look->color == color) + { + break; + } + if (look == menucolortail) + { + return 0; + } + } + } + + look = look->next; + color = look->color; + } + return color; +} + +void M_InitPlayerSetupColors(void) { + UINT8 i; + numskincolors = SKINCOLOR_FIRSTFREESLOT; + menucolorhead = menucolortail = NULL; + for (i=0; inext; + free(tmp); + } else { + free(look); + return; + } + } + + menucolorhead = menucolortail = NULL; +} + +// sets up the grid pos for the skin used by the profile. +static void M_SetupProfileGridPos(setup_player_t *p) +{ + profile_t *pr = PR_GetProfile(p->profilen); + INT32 i = R_SkinAvailable(pr->skinname); + INT32 alt = 0; // Hey it's my character's name! + + // While we're here, read follower values. + p->followern = K_FollowerAvailable(pr->follower); + + if (p->followern < 0 || p->followern >= numfollowers || followers[p->followern].category >= numfollowercategories || !K_FollowerUsable(p->followern)) + p->followercategory = p->followern = -1; + else + p->followercategory = followers[p->followern].category; + + p->followercolor = pr->followercolor; + + if (!R_SkinUsable(g_localplayers[0], i, false)) + { + i = GetSkinNumClosestToStats(skins[i].kartspeed, skins[i].kartweight, skins[i].flags, false); + } + + // Now position the grid for skin + p->gridx = skins[i].kartspeed-1; + p->gridy = skins[i].kartweight-1; + + // Now this put our cursor on the good alt + while (alt < setup_chargrid[p->gridx][p->gridy].numskins && setup_chargrid[p->gridx][p->gridy].skinlist[alt] != i) + alt++; + + p->clonenum = alt; + p->color = PR_GetProfileColor(pr); +} + +static void M_SetupMidGameGridPos(setup_player_t *p, UINT8 num) +{ + INT32 i = R_SkinAvailable(cv_skin[num].zstring); + INT32 alt = 0; // Hey it's my character's name! + + // While we're here, read follower values. + p->followern = cv_follower[num].value; + p->followercolor = cv_followercolor[num].value; + + if (p->followern < 0 || p->followern >= numfollowers || followers[p->followern].category >= numfollowercategories || !K_FollowerUsable(p->followern)) + p->followercategory = p->followern = -1; + else + p->followercategory = followers[p->followern].category; + + // Now position the grid for skin + p->gridx = skins[i].kartspeed-1; + p->gridy = skins[i].kartweight-1; + + // Now this put our cursor on the good alt + while (alt < setup_chargrid[p->gridx][p->gridy].numskins && setup_chargrid[p->gridx][p->gridy].skinlist[alt] != i) + alt++; + + p->clonenum = alt; + p->color = cv_playercolor[num].value; + return; // we're done here +} + + +void M_CharacterSelectInit(void) +{ + UINT8 i, j; + setup_maxpage = 0; + + // While we're editing profiles, don't unset the devices for p1 + if (gamestate == GS_MENU) + { + for (i = 0; i < MAXSPLITSCREENPLAYERS; i++) + { + // Un-set devices for other players. + if (i != 0 || optionsmenu.profile) + { + CV_SetValue(&cv_usejoystick[i], -1); + CONS_Printf("M_CharacterSelectInit: Device for %d set to %d\n", i, -1); + } + } + } + + memset(setup_chargrid, -1, sizeof(setup_chargrid)); + for (i = 0; i < 9; i++) + { + for (j = 0; j < 9; j++) + setup_chargrid[i][j].numskins = 0; + } + + memset(setup_player, 0, sizeof(setup_player)); + setup_numplayers = 0; + + memset(setup_explosions, 0, sizeof(setup_explosions)); + setup_animcounter = 0; + + for (i = 0; i < MAXSPLITSCREENPLAYERS; i++) + { + // Default to no follower / match colour. + setup_player[i].followern = -1; + setup_player[i].followercategory = -1; + setup_player[i].followercolor = FOLLOWERCOLOR_MATCH; + + // Set default selected profile to the last used profile for each player: + // (Make sure we don't overshoot it somehow if we deleted profiles or whatnot) + setup_player[i].profilen = min(cv_lastprofile[i].value, PR_GetNumProfiles()); + } + + for (i = 0; i < numskins; i++) + { + UINT8 x = skins[i].kartspeed-1; + UINT8 y = skins[i].kartweight-1; + + if (!R_SkinUsable(g_localplayers[0], i, false)) + continue; + + if (setup_chargrid[x][y].numskins >= MAXCLONES) + CONS_Alert(CONS_ERROR, "Max character alts reached for %d,%d\n", x+1, y+1); + else + { + setup_chargrid[x][y].skinlist[setup_chargrid[x][y].numskins] = i; + setup_chargrid[x][y].numskins++; + + setup_maxpage = max(setup_maxpage, setup_chargrid[x][y].numskins-1); + } + + for (j = 0; j < MAXSPLITSCREENPLAYERS; j++) + { + if (!strcmp(cv_skin[j].string, skins[i].name)) + { + setup_player[j].gridx = x; + setup_player[j].gridy = y; + setup_player[j].color = skins[i].prefcolor; + + // If we're on prpfile select, skip straight to CSSTEP_CHARS + // do the same if we're midgame, but make sure to consider splitscreen properly. + if ((optionsmenu.profile && j == 0) || (gamestate != GS_MENU && j <= splitscreen)) + { + if (optionsmenu.profile) // In menu, setting up profile character/follower + { + setup_player[j].profilen = optionsmenu.profilen; + PR_ApplyProfileLight(setup_player[j].profilen, 0); + M_SetupProfileGridPos(&setup_player[j]); + } + else // gamestate != GS_MENU, in that case, assume this is whatever profile we chose to play with. + { + setup_player[j].profilen = cv_lastprofile[j].value; + M_SetupMidGameGridPos(&setup_player[j], j); + } + + // Don't reapply the profile here, it was already applied. + setup_player[j].mdepth = CSSTEP_CHARS; + } + } + } + } + + setup_numfollowercategories = 0; + for (i = 0; i < numfollowercategories; i++) + { + if (followercategories[i].numincategory == 0) + continue; + + setup_followercategories[setup_numfollowercategories][0] = 0; + + for (j = 0; j < numfollowers; j++) + { + if (followers[j].category != i) + continue; + + if (!K_FollowerUsable(j)) + continue; + + setup_followercategories[setup_numfollowercategories][0]++; + setup_followercategories[setup_numfollowercategories][1] = i; + } + + if (!setup_followercategories[setup_numfollowercategories][0]) + continue; + + setup_numfollowercategories++; + } + + setup_page = 0; +} + +void M_CharacterSelect(INT32 choice) +{ + (void)choice; + PLAY_CharSelectDef.prevMenu = currentMenu; + M_SetupNextMenu(&PLAY_CharSelectDef, false); +} + + +// Gets the selected follower's state for a given setup player. +static void M_GetFollowerState(setup_player_t *p) +{ + + p->follower_state = &states[followers[p->followern].followstate]; + + if (p->follower_state->frame & FF_ANIMATE) + p->follower_tics = p->follower_state->var2; // support for FF_ANIMATE + else + p->follower_tics = p->follower_state->tics; + + p->follower_frame = p->follower_state->frame & FF_FRAMEMASK; +} + +static boolean M_DeviceAvailable(INT32 deviceID, UINT8 numPlayers) +{ + INT32 i; + + if (numPlayers == 0) + { + // All of them are available! + return true; + } + + for (i = 0; i < numPlayers; i++) + { + if (cv_usejoystick[i].value == deviceID) + { + // This one's already being used. + return false; + } + } + + // This device is good to go. + return true; +} + +static boolean M_HandlePressStart(setup_player_t *p, UINT8 num) +{ + INT32 i, j; + + if (optionsmenu.profile) + return false; // Don't allow for the possibility of SOMEHOW another player joining in. + + // Detect B press first ... this means P1 can actually exit out of the menu. + if (M_MenuBackPressed(num)) + { + M_SetMenuDelay(num); + + if (num == 0) + { + // We're done here. + memset(setup_player, 0, sizeof(setup_player)); // Reset this to avoid funky things with profile display. + M_GoBack(0); + return true; + } + + // Don't allow this press to ever count as "start". + return false; + } + + if (num != setup_numplayers) + { + // Only detect devices for the last player. + return false; + } + + // Now detect new devices trying to join. + for (i = 0; i < MAXDEVICES; i++) + { + if (deviceResponding[i] != true) + { + // No buttons are being pushed. + continue; + } + + if (M_DeviceAvailable(i, setup_numplayers) == true) + { + // Available!! Let's use this one!! + + // if P1 is setting up using keyboard (device 0), save their last used device. + // this is to allow them to retain controller usage when they play alone. + // Because let's face it, when you test mods, you're often lazy to grab your controller for menuing :) + if (!i && !num) + { + setup_player[num].ponedevice = cv_usejoystick[num].value; + } + else if (num) + { + // For any player past player 1, set controls to default profile controls, otherwise it's generally awful to do any menuing... + memcpy(&gamecontrol[num], gamecontroldefault, sizeof(gamecontroldefault)); + } + + + CV_SetValue(&cv_usejoystick[num], i); + CONS_Printf("M_HandlePressStart: Device for %d set to %d\n", num, i); + + for (j = num+1; j < MAXSPLITSCREENPLAYERS; j++) + { + // Un-set devices for other players. + CV_SetValue(&cv_usejoystick[j], -1); + CONS_Printf("M_HandlePressStart: Device for %d set to %d\n", j, -1); + } + + //setup_numplayers++; + p->mdepth = CSSTEP_PROFILE; + S_StartSound(NULL, sfx_s3k65); + + // Prevent quick presses for multiple players + for (j = 0; j < MAXSPLITSCREENPLAYERS; j++) + { + setup_player[j].delay = MENUDELAYTIME; + M_SetMenuDelay(j); + menucmd[j].buttonsHeld |= MBT_X; + } + + memset(deviceResponding, false, sizeof(deviceResponding)); + return true; + } + } + + return false; +} + +static boolean M_HandleCSelectProfile(setup_player_t *p, UINT8 num) +{ + const UINT8 maxp = PR_GetNumProfiles() -1; + UINT8 realnum = num; // Used for profile when using splitdevice. + UINT8 i; + + if (cv_splitdevice.value) + num = 0; + + if (menucmd[num].dpad_ud > 0) + { + p->profilen++; + if (p->profilen > maxp) + p->profilen = 0; + + S_StartSound(NULL, sfx_s3k5b); + M_SetMenuDelay(num); + } + else if (menucmd[num].dpad_ud < 0) + { + if (p->profilen == 0) + p->profilen = maxp; + else + p->profilen--; + + S_StartSound(NULL, sfx_s3k5b); + M_SetMenuDelay(num); + } + else if (M_MenuBackPressed(num)) + { + if (num == setup_numplayers-1) + { + + p->mdepth = CSSTEP_NONE; + S_StartSound(NULL, sfx_s3k5b); + + // Prevent quick presses for multiple players + for (i = 0; i < MAXSPLITSCREENPLAYERS; i++) + { + setup_player[i].delay = MENUDELAYTIME; + M_SetMenuDelay(i); + menucmd[i].buttonsHeld |= MBT_X; + } + + if (num > 0) + { + CV_StealthSetValue(&cv_usejoystick[num], -1); + CONS_Printf("M_HandleCSelectProfile: Device for %d set to %d\n", num, -1); + } + + return true; + } + else + { + S_StartSound(NULL, sfx_s3kb2); + } + + M_SetMenuDelay(num); + } + else if (M_MenuConfirmPressed(num)) + { + SINT8 belongsTo = -1; + + if (p->profilen != PROFILE_GUEST) + { + for (i = 0; i < setup_numplayers; i++) + { + if (setup_player[i].mdepth > CSSTEP_PROFILE + && setup_player[i].profilen == p->profilen) + { + belongsTo = i; + break; + } + } + } + + if (belongsTo != -1 && belongsTo != num) + { + S_StartSound(NULL, sfx_s3k7b); + M_SetMenuDelay(num); + return false; + } + + // Apply the profile. + PR_ApplyProfile(p->profilen, realnum); // Otherwise P1 would inherit the last player's profile in splitdevice and that's not what we want... + M_SetupProfileGridPos(p); + + p->changeselect = 0; + + if (p->profilen == PROFILE_GUEST) + { + // Guest profile, always ask for options. + p->mdepth = CSSTEP_CHARS; + } + else + { + p->mdepth = CSSTEP_ASKCHANGES; + } + + S_StartSound(NULL, sfx_s3k63); + } + else if (M_MenuExtraPressed(num)) + { + UINT8 yourprofile = min(cv_lastprofile[realnum].value, PR_GetNumProfiles()); + if (p->profilen == yourprofile) + p->profilen = PROFILE_GUEST; + else + p->profilen = yourprofile; + S_StartSound(NULL, sfx_s3k7b); //sfx_s3kc3s + M_SetMenuDelay(num); + } + + return false; + +} + + +static void M_HandleCharAskChange(setup_player_t *p, UINT8 num) +{ + + if (cv_splitdevice.value) + num = 0; + + // there's only 2 options so lol + if (menucmd[num].dpad_ud != 0) + { + p->changeselect = (p->changeselect == 0) ? 1 : 0; + + S_StartSound(NULL, sfx_s3k5b); + M_SetMenuDelay(num); + } + else if (M_MenuBackPressed(num)) + { + p->changeselect = 0; + p->mdepth = CSSTEP_PROFILE; + + S_StartSound(NULL, sfx_s3k5b); + M_SetMenuDelay(num); + } + else if (M_MenuConfirmPressed(num)) + { + if (!p->changeselect) + { + // no changes + M_GetFollowerState(p); + p->mdepth = CSSTEP_READY; + p->delay = TICRATE; + + S_StartSound(NULL, sfx_s3k4e); + M_SetupReadyExplosions(true, p->gridx, p->gridy, p->color); + } + else + { + // changes + p->mdepth = CSSTEP_CHARS; + S_StartSound(NULL, sfx_s3k63); + } + + M_SetMenuDelay(num); + } +} + +static boolean M_HandleCharacterGrid(setup_player_t *p, UINT8 num) +{ + UINT8 numclones; + INT32 skin; + + if (cv_splitdevice.value) + num = 0; + + if (menucmd[num].dpad_ud > 0) + { + p->gridy++; + if (p->gridy > 8) + p->gridy = 0; + S_StartSound(NULL, sfx_s3k5b); + M_SetMenuDelay(num); + } + else if (menucmd[num].dpad_ud < 0) + { + p->gridy--; + if (p->gridy < 0) + p->gridy = 8; + S_StartSound(NULL, sfx_s3k5b); + M_SetMenuDelay(num); + } + + if (menucmd[num].dpad_lr > 0) + { + p->gridx++; + if (p->gridx > 8) + p->gridx = 0; + S_StartSound(NULL, sfx_s3k5b); + M_SetMenuDelay(num); + } + else if (menucmd[num].dpad_lr < 0) + { + p->gridx--; + if (p->gridx < 0) + p->gridx = 8; + S_StartSound(NULL, sfx_s3k5b); + M_SetMenuDelay(num); + } + else if (M_MenuExtraPressed(num)) + { + p->gridx /= 3; + p->gridx = (3*p->gridx) + 1; + p->gridy /= 3; + p->gridy = (3*p->gridy) + 1; + S_StartSound(NULL, sfx_s3k7b); //sfx_s3kc3s + M_SetMenuDelay(num); + } + + // try to set the clone num to the page # if possible. + p->clonenum = setup_page; + + // Process this after possible pad movement, + // this makes sure we don't have a weird ghost hover on a character with no clones. + numclones = setup_chargrid[p->gridx][p->gridy].numskins; + + if (p->clonenum >= numclones) + p->clonenum = 0; + + if (M_MenuConfirmPressed(num) /*|| M_MenuButtonPressed(num, MBT_START)*/) + { + skin = setup_chargrid[p->gridx][p->gridy].skinlist[setup_page]; + if (setup_page >= setup_chargrid[p->gridx][p->gridy].numskins || skin == -1) + { + S_StartSound(NULL, sfx_s3k7b); //sfx_s3kb2 + } + else + { + if (setup_page+1 == setup_chargrid[p->gridx][p->gridy].numskins) + p->mdepth = CSSTEP_COLORS; // Skip clones menu if there are none on this page. + else + p->mdepth = CSSTEP_ALTS; + + S_StartSound(NULL, sfx_s3k63); + } + + M_SetMenuDelay(num); + } + else if (M_MenuBackPressed(num)) + { + // for profiles / gameplay, exit out of the menu instantly, + // we don't want to go to the input detection menu. + if (optionsmenu.profile || gamestate != GS_MENU) + { + memset(setup_player, 0, sizeof(setup_player)); // Reset setup_player otherwise it does some VERY funky things. + M_SetMenuDelay(0); + M_GoBack(0); + return true; + } + else // in main menu + { + p->mdepth = CSSTEP_PROFILE; + S_StartSound(NULL, sfx_s3k5b); + } + M_SetMenuDelay(num); + } + + if (num == 0 && setup_numplayers == 1 && setup_maxpage) // ONLY one player. + { + if (M_MenuButtonPressed(num, MBT_L)) + { + if (setup_page == 0) + setup_page = setup_maxpage; + else + setup_page--; + + S_StartSound(NULL, sfx_s3k63); + M_SetMenuDelay(num); + } + else if (M_MenuButtonPressed(num, MBT_R)) + { + if (setup_page == setup_maxpage) + setup_page = 0; + else + setup_page++; + + S_StartSound(NULL, sfx_s3k63); + M_SetMenuDelay(num); + } + } + + return false; +} + +static void M_HandleCharRotate(setup_player_t *p, UINT8 num) +{ + UINT8 numclones = setup_chargrid[p->gridx][p->gridy].numskins; + + if (cv_splitdevice.value) + num = 0; + + if (menucmd[num].dpad_lr > 0) + { + p->clonenum++; + if (p->clonenum >= numclones) + p->clonenum = 0; + p->rotate = CSROTATETICS; + p->delay = CSROTATETICS; + S_StartSound(NULL, sfx_s3kc3s); + } + else if (menucmd[num].dpad_lr < 0) + { + p->clonenum--; + if (p->clonenum < 0) + p->clonenum = numclones-1; + p->rotate = -CSROTATETICS; + p->delay = CSROTATETICS; + S_StartSound(NULL, sfx_s3kc3s); + } + + if (M_MenuConfirmPressed(num) /*|| M_MenuButtonPressed(num, MBT_START)*/) + { + p->mdepth = CSSTEP_COLORS; + S_StartSound(NULL, sfx_s3k63); + M_SetMenuDelay(num); + } + else if (M_MenuBackPressed(num)) + { + p->mdepth = CSSTEP_CHARS; + S_StartSound(NULL, sfx_s3k5b); + M_SetMenuDelay(num); + } + else if (M_MenuExtraPressed(num)) + { + p->clonenum = 0; + p->rotate = CSROTATETICS; + p->hitlag = true; + S_StartSound(NULL, sfx_s3k7b); //sfx_s3kc3s + M_SetMenuDelay(num); + } +} + +static void M_HandleColorRotate(setup_player_t *p, UINT8 num) +{ + if (cv_splitdevice.value) + num = 0; + + if (menucmd[num].dpad_lr > 0) + { + p->color = M_GetColorAfter(p->color, 1, false); + p->rotate = CSROTATETICS; + M_SetMenuDelay(num); //CSROTATETICS + S_StartSound(NULL, sfx_s3k5b); //sfx_s3kc3s + } + else if (menucmd[num].dpad_lr < 0) + { + p->color = M_GetColorBefore(p->color, 1, false); + p->rotate = -CSROTATETICS; + M_SetMenuDelay(num); //CSROTATETICS + S_StartSound(NULL, sfx_s3k5b); //sfx_s3kc3s + } + + if (M_MenuConfirmPressed(num) /*|| M_MenuButtonPressed(num, MBT_START)*/) + { + p->mdepth = CSSTEP_FOLLOWERCATEGORY; + S_StartSound(NULL, sfx_s3k63); + M_SetMenuDelay(num); + } + else if (M_MenuBackPressed(num)) + { + if (setup_chargrid[p->gridx][p->gridy].numskins == 1) + { + p->mdepth = CSSTEP_CHARS; // Skip clones menu + } + else + { + p->mdepth = CSSTEP_ALTS; + } + S_StartSound(NULL, sfx_s3k5b); + M_SetMenuDelay(num); + } + else if (M_MenuExtraPressed(num)) + { + if (p->skin >= 0) + { + p->color = skins[p->skin].prefcolor; + p->rotate = CSROTATETICS; + p->hitlag = true; + S_StartSound(NULL, sfx_s3k7b); //sfx_s3kc3s + M_SetMenuDelay(num); + } + } +} + +static void M_AnimateFollower(setup_player_t *p) +{ + if (--p->follower_tics <= 0) + { + + // FF_ANIMATE; cycle through FRAMES and get back afterwards. This will be prominent amongst followers hence why it's being supported here. + if (p->follower_state->frame & FF_ANIMATE) + { + p->follower_frame++; + p->follower_tics = p->follower_state->var2; + if (p->follower_frame > (p->follower_state->frame & FF_FRAMEMASK) + p->follower_state->var1) // that's how it works, right? + p->follower_frame = p->follower_state->frame & FF_FRAMEMASK; + } + else + { + if (p->follower_state->nextstate != S_NULL) + p->follower_state = &states[p->follower_state->nextstate]; + p->follower_tics = p->follower_state->tics; + /*if (p->follower_tics == -1) + p->follower_tics = 15; // er, what?*/ + // get spritedef: + p->follower_frame = p->follower_state->frame & FF_FRAMEMASK; + } + } + + p->follower_timer++; +} + +static void M_HandleFollowerCategoryRotate(setup_player_t *p, UINT8 num) +{ + if (cv_splitdevice.value) + num = 0; + + if (menucmd[num].dpad_lr > 0) + { + p->followercategory++; + if (p->followercategory >= setup_numfollowercategories) + p->followercategory = -1; + + p->rotate = CSROTATETICS; + p->delay = CSROTATETICS; + S_StartSound(NULL, sfx_s3kc3s); + } + else if (menucmd[num].dpad_lr < 0) + { + p->followercategory--; + if (p->followercategory < -1) + p->followercategory = setup_numfollowercategories-1; + + p->rotate = -CSROTATETICS; + p->delay = CSROTATETICS; + S_StartSound(NULL, sfx_s3kc3s); + } + + if (M_MenuConfirmPressed(num) /*|| M_MenuButtonPressed(num, MBT_START)*/) + { + if (p->followercategory < 0) + { + p->followern = -1; + p->mdepth = CSSTEP_READY; + p->delay = TICRATE; + M_SetupReadyExplosions(true, p->gridx, p->gridy, p->color); + S_StartSound(NULL, sfx_s3k4e); + } + else + { + if (p->followern < 0 || followers[p->followern].category != p->followercategory) + { + p->followern = 0; + while (p->followern < numfollowers + && (followers[p->followern].category != setup_followercategories[p->followercategory][1] + || !K_FollowerUsable(p->followern))) + p->followern++; + } + + if (p->followern >= numfollowers) + { + p->followern = -1; + S_StartSound(NULL, sfx_s3kb2); + } + else + { + M_GetFollowerState(p); + p->mdepth = CSSTEP_FOLLOWER; + S_StartSound(NULL, sfx_s3k63); + } + } + + M_SetMenuDelay(num); + } + else if (M_MenuBackPressed(num)) + { + p->mdepth = CSSTEP_COLORS; + S_StartSound(NULL, sfx_s3k5b); + M_SetMenuDelay(num); + } + else if (M_MenuExtraPressed(num)) + { + if (p->followercategory >= 0 || p->followern < 0 || p->followern >= numfollowers || followers[p->followern].category >= numfollowercategories) + p->followercategory = -1; + else + p->followercategory = followers[p->followern].category; + p->rotate = CSROTATETICS; + p->hitlag = true; + S_StartSound(NULL, sfx_s3k7b); //sfx_s3kc3s + M_SetMenuDelay(num); + } +} + +static void M_HandleFollowerRotate(setup_player_t *p, UINT8 num) +{ + INT16 startfollowern = p->followern; + + if (cv_splitdevice.value) + num = 0; + + if (menucmd[num].dpad_lr > 0) + { + do + { + p->followern++; + if (p->followern >= numfollowers) + p->followern = 0; + if (p->followern == startfollowern) + break; + } + while (followers[p->followern].category != setup_followercategories[p->followercategory][1] || !K_FollowerUsable(p->followern)); + + M_GetFollowerState(p); + + p->rotate = CSROTATETICS; + p->delay = CSROTATETICS; + S_StartSound(NULL, sfx_s3kc3s); + } + else if (menucmd[num].dpad_lr < 0) + { + do + { + p->followern--; + if (p->followern < 0) + p->followern = numfollowers-1; + if (p->followern == startfollowern) + break; + } + while (followers[p->followern].category != setup_followercategories[p->followercategory][1] || !K_FollowerUsable(p->followern)); + + M_GetFollowerState(p); + + p->rotate = -CSROTATETICS; + p->delay = CSROTATETICS; + S_StartSound(NULL, sfx_s3kc3s); + } + + if (M_MenuConfirmPressed(num) /*|| M_MenuButtonPressed(num, MBT_START)*/) + { + if (p->followern > -1) + { + p->mdepth = CSSTEP_FOLLOWERCOLORS; + S_StartSound(NULL, sfx_s3k63); + } + else + { + p->mdepth = CSSTEP_READY; + p->delay = TICRATE; + M_SetupReadyExplosions(true, p->gridx, p->gridy, p->color); + S_StartSound(NULL, sfx_s3k4e); + } + + M_SetMenuDelay(num); + } + else if (M_MenuBackPressed(num)) + { + p->mdepth = CSSTEP_FOLLOWERCATEGORY; + S_StartSound(NULL, sfx_s3k5b); + M_SetMenuDelay(num); + } + else if (M_MenuExtraPressed(num)) + { + p->mdepth = CSSTEP_FOLLOWERCATEGORY; + p->followercategory = -1; + p->rotate = CSROTATETICS; + p->hitlag = true; + S_StartSound(NULL, sfx_s3k7b); //sfx_s3kc3s + M_SetMenuDelay(num); + } +} + +static void M_HandleFollowerColorRotate(setup_player_t *p, UINT8 num) +{ + if (cv_splitdevice.value) + num = 0; + + M_AnimateFollower(p); + + if (menucmd[num].dpad_lr > 0) + { + p->followercolor = M_GetColorAfter(p->followercolor, 1, true); + p->rotate = CSROTATETICS; + M_SetMenuDelay(num); //CSROTATETICS + S_StartSound(NULL, sfx_s3k5b); //sfx_s3kc3s + } + else if (menucmd[num].dpad_lr < 0) + { + p->followercolor = M_GetColorBefore(p->followercolor, 1, true); + p->rotate = -CSROTATETICS; + M_SetMenuDelay(num); //CSROTATETICS + S_StartSound(NULL, sfx_s3k5b); //sfx_s3kc3s + } + + if (M_MenuConfirmPressed(num) /*|| M_MenuButtonPressed(num, MBT_START)*/) + { + p->mdepth = CSSTEP_READY; + p->delay = TICRATE; + M_SetupReadyExplosions(true, p->gridx, p->gridy, p->color); + S_StartSound(NULL, sfx_s3k4e); + M_SetMenuDelay(num); + } + else if (M_MenuBackPressed(num)) + { + M_GetFollowerState(p); + p->mdepth = CSSTEP_FOLLOWER; + S_StartSound(NULL, sfx_s3k5b); + M_SetMenuDelay(num); + } + else if (M_MenuExtraPressed(num)) + { + if (p->followercolor == FOLLOWERCOLOR_MATCH) + p->followercolor = FOLLOWERCOLOR_OPPOSITE; + else if (p->followercolor == followers[p->followern].defaultcolor) + p->followercolor = FOLLOWERCOLOR_MATCH; + else + p->followercolor = followers[p->followern].defaultcolor; + p->rotate = CSROTATETICS; + p->hitlag = true; + S_StartSound(NULL, sfx_s3k7b); //sfx_s3kc3s + M_SetMenuDelay(num); + } +} + +boolean M_CharacterSelectHandler(INT32 choice) +{ + INT32 i; + + (void)choice; + + for (i = MAXSPLITSCREENPLAYERS-1; i >= 0; i--) + { + setup_player_t *p = &setup_player[i]; + boolean playersChanged = false; + + if (p->delay == 0 && menucmd[i].delay == 0) + { + if (!optionsmenu.profile) + { + // If splitdevice is true, only do the last non-ready setups. + if (cv_splitdevice.value) + { + // Previous setup isn't ready, go there. + // In any case, do setup 0 first. + if (i > 0 && setup_player[i-1].mdepth < CSSTEP_READY) + continue; + } + } + + switch (p->mdepth) + { + case CSSTEP_NONE: // Enter Game + if (gamestate == GS_MENU) // do NOT handle that outside of GS_MENU. + playersChanged = M_HandlePressStart(p, i); + break; + case CSSTEP_PROFILE: + playersChanged = M_HandleCSelectProfile(p, i); + break; + case CSSTEP_ASKCHANGES: + M_HandleCharAskChange(p, i); + break; + case CSSTEP_CHARS: // Character Select grid + M_HandleCharacterGrid(p, i); + break; + case CSSTEP_ALTS: // Select clone + M_HandleCharRotate(p, i); + break; + case CSSTEP_COLORS: // Select color + M_HandleColorRotate(p, i); + break; + case CSSTEP_FOLLOWERCATEGORY: + M_HandleFollowerCategoryRotate(p, i); + break; + case CSSTEP_FOLLOWER: + M_HandleFollowerRotate(p, i); + break; + case CSSTEP_FOLLOWERCOLORS: + M_HandleFollowerColorRotate(p, i); + break; + case CSSTEP_READY: + default: // Unready + if (M_MenuBackPressed(i)) + { + p->mdepth = CSSTEP_COLORS; + S_StartSound(NULL, sfx_s3k5b); + M_SetMenuDelay(i); + } + break; + } + } + + // Just makes it easier to access later + p->skin = setup_chargrid[p->gridx][p->gridy].skinlist[p->clonenum]; + + // Keep profile colour. + /*if (p->mdepth < CSSTEP_COLORS) + { + p->color = skins[p->skin].prefcolor; + + }*/ + + if (playersChanged == true) + { + setup_page = 0; // reset that. + break; + } + } + + // Setup new numplayers + setup_numplayers = 0; + for (i = 0; i < MAXSPLITSCREENPLAYERS; i++) + { + if (setup_player[i].mdepth == CSSTEP_NONE) + break; + + setup_numplayers = i+1; + } + + return true; +} + +// Apply character skin and colour changes while ingame (we just call the skin / color commands.) +// ...Will this cause command buffer issues? -Lat' +static void M_MPConfirmCharacterSelection(void) +{ + UINT8 i; + INT16 col; + + for (i = 0; i < splitscreen +1; i++) + { + // colour + // (convert the number that's saved to a string we can use) + col = setup_player[i].color; + CV_StealthSetValue(&cv_playercolor[i], col); + + // follower + if (setup_player[i].followern < 0) + CV_StealthSet(&cv_follower[i], "None"); + else + CV_StealthSet(&cv_follower[i], followers[setup_player[i].followern].name); + + // finally, call the skin[x] console command. + // This will call SendNameAndColor which will synch everything we sent here and apply the changes! + + CV_StealthSet(&cv_skin[i], skins[setup_player[i].skin].name); + + // ...actually, let's do this last - Skin_OnChange has some return-early occasions + // follower color + CV_SetValue(&cv_followercolor[i], setup_player[i].followercolor); + + } + M_ClearMenus(true); +} + +void M_CharacterSelectTick(void) +{ + UINT8 i; + boolean setupnext = true; + + setup_animcounter++; + + for (i = 0; i < MAXSPLITSCREENPLAYERS; i++) + { + if (setup_player[i].delay) + setup_player[i].delay--; + + if (setup_player[i].rotate > 0) + setup_player[i].rotate--; + else if (setup_player[i].rotate < 0) + setup_player[i].rotate++; + else + setup_player[i].hitlag = false; + + if (i >= setup_numplayers) + continue; + + if (setup_player[i].mdepth < CSSTEP_READY || setup_player[i].delay > 0) + { + // Someone's not ready yet. + setupnext = false; + } + } + + for (i = 0; i < CSEXPLOSIONS; i++) + { + if (setup_explosions[i].tics > 0) + setup_explosions[i].tics--; + } + + if (setupnext && setup_numplayers > 0) + { + // Selecting from the menu + if (gamestate == GS_MENU) + { + // in a profile; update the selected profile and then go back to the profile menu. + if (optionsmenu.profile) + { + // save player + strcpy(optionsmenu.profile->skinname, skins[setup_player[0].skin].name); + optionsmenu.profile->color = setup_player[0].color; + + // save follower + strcpy(optionsmenu.profile->follower, followers[setup_player[0].followern].name); + optionsmenu.profile->followercolor = setup_player[0].followercolor; + + // reset setup_player + memset(setup_player, 0, sizeof(setup_player)); + setup_numplayers = 0; + + M_GoBack(0); + return; + } + else // in a normal menu, stealthset the cvars and then go to the play menu. + { + for (i = 0; i < setup_numplayers; i++) + { + CV_StealthSet(&cv_skin[i], skins[setup_player[i].skin].name); + CV_StealthSetValue(&cv_playercolor[i], setup_player[i].color); + + if (setup_player[i].followern < 0) + CV_StealthSet(&cv_follower[i], "None"); + else + CV_StealthSet(&cv_follower[i], followers[setup_player[i].followern].name); + CV_StealthSetValue(&cv_followercolor[i], setup_player[i].followercolor); + } + + CV_StealthSetValue(&cv_splitplayers, setup_numplayers); + + // P1 is alone, set their old device just in case. + if (setup_numplayers < 2 && setup_player[0].ponedevice) + { + CV_StealthSetValue(&cv_usejoystick[0], setup_player[0].ponedevice); + } + + M_SetupNextMenu(&PLAY_MainDef, false); + } + } + else // In a game + { + // 23/05/2022: Since there's already restrictskinchange, just allow this to happen regardless. + M_MPConfirmCharacterSelection(); + } + } +} + +boolean M_CharacterSelectQuit(void) +{ + return true; +} + +static void Splitplayers_OnChange(void) +{ +#if 0 + if (cv_splitplayers.value < setupm_pselect) + setupm_pselect = 1; +#endif +} diff --git a/src/menus/play-local-1.c b/src/menus/play-local-1.c index 6267f91b6..bea8a1686 100644 --- a/src/menus/play-local-1.c +++ b/src/menus/play-local-1.c @@ -2,6 +2,7 @@ /// \brief Local Play, gamemode selection menu #include "../k_menu.h" +#include "../m_cond.h" // Condition Sets menuitem_t PLAY_GamemodesMenu[] = { @@ -21,3 +22,48 @@ menuitem_t PLAY_GamemodesMenu[] = }; menu_t PLAY_GamemodesDef = KARTGAMEMODEMENU(PLAY_GamemodesMenu, &PLAY_MainDef); + +void M_SetupGametypeMenu(INT32 choice) +{ + (void)choice; + + PLAY_GamemodesDef.prevMenu = currentMenu; + + // Battle and Capsules (and Special) disabled + PLAY_GamemodesMenu[1].status = IT_DISABLED; + PLAY_GamemodesMenu[2].status = IT_DISABLED; + PLAY_GamemodesMenu[3].status = IT_DISABLED; + + if (cv_splitplayers.value > 1) + { + // Re-add Battle + PLAY_GamemodesMenu[1].status = IT_STRING | IT_CALL; + } + else + { + boolean anyunlocked = false; + + if (M_SecretUnlocked(SECRET_BREAKTHECAPSULES, true)) + { + // Re-add Capsules + PLAY_GamemodesMenu[2].status = IT_STRING | IT_CALL; + anyunlocked = true; + } + + if (M_SecretUnlocked(SECRET_SPECIALATTACK, true)) + { + // Re-add Special + PLAY_GamemodesMenu[3].status = IT_STRING | IT_CALL; + anyunlocked = true; + } + + if (!anyunlocked) + { + // Only one non-Back entry, let's skip straight to Race. + M_SetupRaceMenu(-1); + return; + } + } + + M_SetupNextMenu(&PLAY_GamemodesDef, false); +} diff --git a/src/menus/play-local-race-1.c b/src/menus/play-local-race-1.c index 84faa4a8f..92a092db6 100644 --- a/src/menus/play-local-race-1.c +++ b/src/menus/play-local-race-1.c @@ -2,6 +2,7 @@ /// \brief Race Mode Menu #include "../k_menu.h" +#include "../m_cond.h" // Condition Sets menuitem_t PLAY_RaceGamemodesMenu[] = { @@ -18,3 +19,22 @@ menuitem_t PLAY_RaceGamemodesMenu[] = }; menu_t PLAY_RaceGamemodesDef = KARTGAMEMODEMENU(PLAY_RaceGamemodesMenu, &PLAY_GamemodesDef); + +void M_SetupRaceMenu(INT32 choice) +{ + (void)choice; + + PLAY_RaceGamemodesDef.prevMenu = currentMenu; + + // Time Attack disabled + PLAY_RaceGamemodesMenu[2].status = IT_DISABLED; + + // Time Attack is 1P only + if (cv_splitplayers.value <= 1 + && M_SecretUnlocked(SECRET_TIMEATTACK, true)) + { + PLAY_RaceGamemodesMenu[2].status = IT_STRING | IT_CALL; + } + + M_SetupNextMenu(&PLAY_RaceGamemodesDef, false); +} diff --git a/src/menus/play-local-race-difficulty.c b/src/menus/play-local-race-difficulty.c index a55ede90b..49dec06cb 100644 --- a/src/menus/play-local-race-difficulty.c +++ b/src/menus/play-local-race-difficulty.c @@ -2,6 +2,7 @@ /// \brief difficulty selection -- see drace_e #include "../k_menu.h" +#include "../m_cond.h" // Condition Sets menuitem_t PLAY_RaceDifficulty[] = { @@ -45,3 +46,65 @@ menu_t PLAY_RaceDifficultyDef = { NULL, NULL }; + +consvar_t cv_dummygpdifficulty = CVAR_INIT ("dummygpdifficulty", "Normal", CV_HIDDEN, gpdifficulty_cons_t, NULL); +consvar_t cv_dummykartspeed = CVAR_INIT ("dummykartspeed", "Normal", CV_HIDDEN, dummykartspeed_cons_t, NULL); +consvar_t cv_dummygpencore = CVAR_INIT ("dummygpencore", "Off", CV_HIDDEN, CV_OnOff, NULL); + +static CV_PossibleValue_t dummymatchbots_cons_t[] = { + {0, "Off"}, + {1, "Lv.1"}, + {2, "Lv.2"}, + {3, "Lv.3"}, + {4, "Lv.4"}, + {5, "Lv.5"}, + {6, "Lv.6"}, + {7, "Lv.7"}, + {8, "Lv.8"}, + {9, "Lv.9"}, + {10, "Lv.10"}, + {11, "Lv.11"}, + {12, "Lv.12"}, + {13, "Lv.MAX"}, + {0, NULL} +}; +consvar_t cv_dummymatchbots = CVAR_INIT ("dummymatchbots", "Off", CV_HIDDEN, dummymatchbots_cons_t, NULL); + +void M_SetupDifficultySelect(INT32 choice) +{ + // check what we picked. + choice = currentMenu->menuitems[itemOn].mvar1; + + // setup the difficulty menu and then remove choices depending on choice + PLAY_RaceDifficultyDef.prevMenu = currentMenu; + + PLAY_RaceDifficulty[drace_gpdifficulty].status = IT_DISABLED; + PLAY_RaceDifficulty[drace_mrkartspeed].status = IT_DISABLED; + PLAY_RaceDifficulty[drace_mrcpu].status = IT_DISABLED; + PLAY_RaceDifficulty[drace_mrracers].status = IT_DISABLED; + PLAY_RaceDifficulty[drace_encore].status = IT_DISABLED; + PLAY_RaceDifficulty[drace_cupselect].status = IT_DISABLED; + PLAY_RaceDifficulty[drace_mapselect].status = IT_DISABLED; + + if (choice) // Match Race + { + PLAY_RaceDifficulty[drace_mrkartspeed].status = IT_STRING|IT_CVAR; // Kart Speed + PLAY_RaceDifficulty[drace_mrcpu].status = IT_STRING2|IT_CVAR; // CPUs on/off + PLAY_RaceDifficulty[drace_mrracers].status = IT_STRING2|IT_CVAR; // CPU amount + PLAY_RaceDifficulty[drace_mapselect].status = IT_STRING|IT_CALL; // Level Select (Match Race) + PLAY_RaceDifficultyDef.lastOn = drace_mapselect; // Select map select by default. + } + else // GP + { + PLAY_RaceDifficulty[drace_gpdifficulty].status = IT_STRING|IT_CVAR; // Difficulty + PLAY_RaceDifficulty[drace_cupselect].status = IT_STRING|IT_CALL; // Level Select (GP) + PLAY_RaceDifficultyDef.lastOn = drace_cupselect; // Select cup select by default. + } + + if (M_SecretUnlocked(SECRET_ENCORE, false)) + { + PLAY_RaceDifficulty[drace_encore].status = IT_STRING2|IT_CVAR; // Encore on/off + } + + M_SetupNextMenu(&PLAY_RaceDifficultyDef, false); +} diff --git a/src/menus/play-local-race-time-attack.c b/src/menus/play-local-race-time-attack.c index 488a1b42d..6ed559773 100644 --- a/src/menus/play-local-race-time-attack.c +++ b/src/menus/play-local-race-time-attack.c @@ -2,6 +2,12 @@ /// \brief Race Time Attack Menu #include "../k_menu.h" +#include "../r_local.h" // SplitScreen_OnChange +#include "../s_sound.h" +#include "../f_finale.h" // F_WipeStartScreen +#include "../v_video.h" +#include "../d_main.h" // srb2home +#include "../m_misc.h" // M_MkdirEach // see ta_e menuitem_t PLAY_TimeAttack[] = @@ -114,3 +120,91 @@ menu_t PLAY_TAGhostsDef = { NULL, NULL }; + +// autorecord demos for time attack +consvar_t cv_autorecord = CVAR_INIT ("autorecord", "Yes", 0, CV_YesNo, NULL); + +CV_PossibleValue_t ghost_cons_t[] = {{0, "Hide"}, {1, "Show Character"}, {2, "Show All"}, {0, NULL}}; +CV_PossibleValue_t ghost2_cons_t[] = {{0, "Hide"}, {1, "Show"}, {0, NULL}}; + +consvar_t cv_ghost_besttime = CVAR_INIT ("ghost_besttime", "Show All", CV_SAVE, ghost_cons_t, NULL); +consvar_t cv_ghost_bestlap = CVAR_INIT ("ghost_bestlap", "Show All", CV_SAVE, ghost_cons_t, NULL); +consvar_t cv_ghost_last = CVAR_INIT ("ghost_last", "Show All", CV_SAVE, ghost_cons_t, NULL); +consvar_t cv_ghost_guest = CVAR_INIT ("ghost_guest", "Show", CV_SAVE, ghost2_cons_t, NULL); +consvar_t cv_ghost_staff = CVAR_INIT ("ghost_staff", "Show", CV_SAVE, ghost2_cons_t, NULL); + +// time attack stuff... +void M_HandleStaffReplay(INT32 choice) +{ + // @TODO: + (void) choice; +} + +void M_ReplayTimeAttack(INT32 choice) +{ + // @TODO: + (void) choice; +} + +void M_SetGuestReplay(INT32 choice) +{ + // @TODO: + (void) choice; +} + +void M_StartTimeAttack(INT32 choice) +{ + char *gpath; + char nameofdemo[256]; + + (void)choice; + + modeattacking = ATTACKING_TIME; + + if ((gametypes[levellist.newgametype]->rules & GTR_CIRCUIT) + && (mapheaderinfo[levellist.choosemap]->numlaps != 1)) + { + modeattacking |= ATTACKING_LAP; + } + + // Still need to reset devmode + cht_debug = 0; + emeralds = 0; + + if (demo.playback) + G_StopDemo(); + if (metalrecording) + G_StopMetalDemo(); + + splitscreen = 0; + SplitScreen_OnChange(); + + S_StartSound(NULL, sfx_s3k63); + + paused = false; + + // Early fadeout to let the sound finish playing + F_WipeStartScreen(); + V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31); + F_WipeEndScreen(); + F_RunWipe(wipedefs[wipe_level_toblack], false, "FADEMAP0", false, false); + + SV_StartSinglePlayerServer(levellist.newgametype, false); + + gpath = va("%s"PATHSEP"media"PATHSEP"replay"PATHSEP"%s", + srb2home, timeattackfolder); + M_MkdirEach(gpath, M_PathParts(gpath) - 3, 0755); + + strcat(gpath, PATHSEP); + strcat(gpath, G_BuildMapName(levellist.choosemap+1)); + + snprintf(nameofdemo, sizeof nameofdemo, "%s-%s-last", gpath, cv_skin[0].string); + + if (!cv_autorecord.value) + remove(va("%s"PATHSEP"%s.lmp", srb2home, nameofdemo)); + else + G_RecordDemo(nameofdemo); + + M_ClearMenus(true); + D_MapChange(levellist.choosemap+1, levellist.newgametype, (cv_dummygpencore.value == 1), 1, 1, false, false); +} diff --git a/src/menus/play-online-1.c b/src/menus/play-online-1.c index 18c8c0568..32ab3ca27 100644 --- a/src/menus/play-online-1.c +++ b/src/menus/play-online-1.c @@ -30,3 +30,55 @@ menu_t PLAY_MP_OptSelectDef = { NULL, NULL }; + +struct mpmenu_s mpmenu; + +// Use this as a quit routine within the HOST GAME and JOIN BY IP "sub" menus +boolean M_MPResetOpts(void) +{ + UINT8 i = 0; + + for (; i < 3; i++) + mpmenu.modewinextend[i][0] = 0; // Undo this + + return true; +} + +void M_MPOptSelectInit(INT32 choice) +{ + INT16 arrcpy[3][3] = {{0,68,0}, {0,12,0}, {0,74,0}}; + UINT8 i = 0, j = 0; // To copy the array into the struct + const UINT32 forbidden = GTR_FORBIDMP; + + (void)choice; + + mpmenu.modechoice = 0; + mpmenu.ticker = 0; + + for (; i < 3; i++) + for (j = 0; j < 3; j++) + mpmenu.modewinextend[i][j] = arrcpy[i][j]; // I miss Lua already + + // Guarantee menugametype is good + M_NextMenuGametype(forbidden); + M_PrevMenuGametype(forbidden); + + M_SetupNextMenu(&PLAY_MP_OptSelectDef, false); +} + +void M_MPOptSelectTick(void) +{ + UINT8 i = 0; + + // 3 Because we have 3 options in the menu + for (; i < 3; i++) + { + if (mpmenu.modewinextend[i][0]) + mpmenu.modewinextend[i][2] += 8; + else + mpmenu.modewinextend[i][2] -= 8; + + mpmenu.modewinextend[i][2] = min(mpmenu.modewinextend[i][1], max(0, mpmenu.modewinextend[i][2])); + //CONS_Printf("%d - %d,%d,%d\n", i, mpmenu.modewinextend[i][0], mpmenu.modewinextend[i][1], mpmenu.modewinextend[i][2]); + } +} diff --git a/src/menus/play-online-host.c b/src/menus/play-online-host.c index 0a0faea74..8e6a2bdd8 100644 --- a/src/menus/play-online-host.c +++ b/src/menus/play-online-host.c @@ -2,6 +2,7 @@ /// \brief MULTIPLAYER HOST SCREEN -- see mhost_e #include "../k_menu.h" +#include "../s_sound.h" // MULTIPLAYER HOST SCREEN -- see mhost_e menuitem_t PLAY_MP_Host[] = @@ -39,3 +40,73 @@ menu_t PLAY_MP_HostDef = { M_MPResetOpts, NULL }; + +void M_MPHostInit(INT32 choice) +{ + + (void)choice; + mpmenu.modewinextend[0][0] = 1; + M_SetupNextMenu(&PLAY_MP_HostDef, true); + itemOn = mhost_go; +} + +void M_HandleHostMenuGametype(INT32 choice) +{ + const UINT8 pid = 0; + const UINT32 forbidden = GTR_FORBIDMP; + + (void)choice; + + if (M_MenuBackPressed(pid)) + { + M_GoBack(0); + M_SetMenuDelay(pid); + return; + } + else if (menucmd[pid].dpad_lr > 0 || M_MenuConfirmPressed(pid)) + { + M_NextMenuGametype(forbidden); + S_StartSound(NULL, sfx_s3k5b); + M_SetMenuDelay(pid); + } + else if (menucmd[pid].dpad_lr < 0) + { + M_PrevMenuGametype(forbidden); + S_StartSound(NULL, sfx_s3k5b); + M_SetMenuDelay(pid); + } + + if (menucmd[pid].dpad_ud > 0) + { + M_NextOpt(); + S_StartSound(NULL, sfx_s3k5b); + M_SetMenuDelay(pid); + } + else if (menucmd[pid].dpad_ud < 0) + { + M_PrevOpt(); + S_StartSound(NULL, sfx_s3k5b); + M_SetMenuDelay(pid); + } +} + +void M_MPSetupNetgameMapSelect(INT32 choice) +{ + (void)choice; + + // Yep, we'll be starting a netgame. + levellist.netgame = true; + // Make sure we reset those + levellist.levelsearch.timeattack = false; + levellist.levelsearch.checklocked = true; + cupgrid.grandprix = false; + + // okay this is REALLY stupid but this fixes the host menu re-folding on itself when we go back. + mpmenu.modewinextend[0][0] = 1; + + if (!M_LevelListFromGametype(menugametype)) + { + S_StartSound(NULL, sfx_s3kb2); + M_StartMessage(va("No levels available for\n%s Mode!\n\nPress (B)\n", gametypes[menugametype]->name), NULL, MM_NOTHING); + } +} diff --git a/src/menus/play-online-join-ip.c b/src/menus/play-online-join-ip.c index 398756eb9..ee084e2e6 100644 --- a/src/menus/play-online-join-ip.c +++ b/src/menus/play-online-join-ip.c @@ -2,6 +2,10 @@ /// \brief MULTIPLAYER JOIN BY IP #include "../k_menu.h" +#include "../v_video.h" +#include "../i_system.h" // I_OsPolling +#include "../i_video.h" // I_UpdateNoBlit +#include "../m_misc.h" // NUMLOGIP menuitem_t PLAY_MP_JoinIP[] = { @@ -41,3 +45,66 @@ menu_t PLAY_MP_JoinIPDef = { M_MPResetOpts, M_JoinIPInputs }; + +consvar_t cv_dummyip = CVAR_INIT ("dummyip", "", CV_HIDDEN, NULL, NULL); + +void M_MPJoinIPInit(INT32 choice) +{ + + (void)choice; + mpmenu.modewinextend[2][0] = 1; + M_SetupNextMenu(&PLAY_MP_JoinIPDef, true); +} + +// Attempts to join a given IP from the menu. +void M_JoinIP(const char *ipa) +{ + if (*(ipa) == '\0') // Jack shit + { + M_StartMessage("Please specify an address.\n", NULL, MM_NOTHING); + return; + } + + COM_BufAddText(va("connect \"%s\"\n", ipa)); + + // A little "please wait" message. + M_DrawTextBox(56, BASEVIDHEIGHT/2-12, 24, 2); + V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT/2, 0, "Connecting to server..."); + I_OsPolling(); + I_UpdateNoBlit(); + if (rendermode == render_soft) + I_FinishUpdate(); // page flip or blit buffer +} + +boolean M_JoinIPInputs(INT32 ch) +{ + + const UINT8 pid = 0; + (void) ch; + + if (itemOn == 1) // connect field + { + // enter: connect + if (M_MenuConfirmPressed(pid)) + { + M_JoinIP(cv_dummyip.string); + M_SetMenuDelay(pid); + return true; + } + } + else if (currentMenu->numitems - itemOn <= NUMLOGIP && M_MenuConfirmPressed(pid)) // On one of the last 3 options for IP rejoining + { + UINT8 index = NUMLOGIP - (currentMenu->numitems - itemOn); + M_SetMenuDelay(pid); + + // Is there an address at this part of the table? + if (*joinedIPlist[index][0]) + M_JoinIP(joinedIPlist[index][0]); + else + S_StartSound(NULL, sfx_lose); + + return true; // eat input. + } + + return false; +} diff --git a/src/menus/play-online-room-select.c b/src/menus/play-online-room-select.c new file mode 100644 index 000000000..d9f0fdd49 --- /dev/null +++ b/src/menus/play-online-room-select.c @@ -0,0 +1,66 @@ +/// \file menus/play-online-room-select.c +/// \brief MULTIPLAYER ROOM SELECT MENU + +#include "../k_menu.h" +#include "../s_sound.h" + +menuitem_t PLAY_MP_RoomSelect[] = +{ + {IT_NOTHING | IT_KEYHANDLER, NULL, NULL, NULL, {.routine = M_MPRoomSelect}, 0, 0}, +}; + +menu_t PLAY_MP_RoomSelectDef = { + sizeof (PLAY_MP_RoomSelect) / sizeof (menuitem_t), + &PLAY_MP_OptSelectDef, + 0, + PLAY_MP_RoomSelect, + 0, 0, + 0, 0, + 0, 0, + M_DrawMPRoomSelect, + M_MPRoomSelectTick, + NULL, + NULL, + NULL +}; + +void M_MPRoomSelect(INT32 choice) +{ + const UINT8 pid = 0; + (void) choice; + + if (menucmd[pid].dpad_lr) + { + mpmenu.room = (!mpmenu.room) ? 1 : 0; + S_StartSound(NULL, sfx_s3k5b); + M_SetMenuDelay(pid); + } + else if (M_MenuBackPressed(pid)) + { + M_GoBack(0); + M_SetMenuDelay(pid); + } + + else if (M_MenuConfirmPressed(pid)) + { + M_ServersMenu(0); + M_SetMenuDelay(pid); + } +} + +void M_MPRoomSelectTick(void) +{ + mpmenu.ticker++; +} + +void M_MPRoomSelectInit(INT32 choice) +{ + (void)choice; + mpmenu.room = 0; + mpmenu.ticker = 0; + mpmenu.servernum = 0; + mpmenu.scrolln = 0; + mpmenu.slide = 0; + + M_SetupNextMenu(&PLAY_MP_RoomSelectDef, false); +} diff --git a/src/menus/play-online-server-browser.c b/src/menus/play-online-server-browser.c index 6549340a7..5aebbd771 100644 --- a/src/menus/play-online-server-browser.c +++ b/src/menus/play-online-server-browser.c @@ -1,30 +1,15 @@ /// \file menus/play-online-server-browser.c -/// \brief Online server, CORE / MODDED +/// \brief MULTIPLAYER ROOM FETCH / REFRESH THREADS #include "../k_menu.h" +#include "../v_video.h" +#include "../i_system.h" // I_OsPolling +#include "../i_video.h" // I_UpdateNoBlit -// MULTIPLAYER ROOM SELECT (CORE / MODDED) -menuitem_t PLAY_MP_RoomSelect[] = -{ - {IT_NOTHING | IT_KEYHANDLER, NULL, NULL, NULL, {.routine = M_MPRoomSelect}, 0, 0}, -}; +#ifdef SERVERLISTDEBUG +#include "../m_random.h" +#endif -menu_t PLAY_MP_RoomSelectDef = { - sizeof (PLAY_MP_RoomSelect) / sizeof (menuitem_t), - &PLAY_MP_OptSelectDef, - 0, - PLAY_MP_RoomSelect, - 0, 0, - 0, 0, - 0, 0, - M_DrawMPRoomSelect, - M_MPRoomSelectTick, - NULL, - NULL, - NULL -}; - -// SERVER BROWSER menuitem_t PLAY_MP_ServerBrowser[] = { @@ -51,3 +36,459 @@ menu_t PLAY_MP_ServerBrowserDef = { NULL, M_ServerBrowserInputs }; + +static CV_PossibleValue_t serversort_cons_t[] = { + {0,"Ping"}, + {1,"AVG. Power Level"}, + {2,"Most Players"}, + {3,"Least Players"}, + {4,"Max Player Slots"}, + {5,"Gametype"}, + {0,NULL} +}; +consvar_t cv_serversort = CVAR_INIT ("serversort", "Ping", CV_CALL, serversort_cons_t, M_SortServerList); + +// for server fetch threads... +M_waiting_mode_t m_waiting_mode = M_NOT_WAITING; + +// depending on mpmenu.room, either allows only unmodded servers or modded ones. Remove others from the list. +// we do this by iterating backwards. +static void M_CleanServerList(void) +{ + UINT8 i = serverlistcount; + + while (i) + { + + if (serverlist[i].info.modifiedgame != mpmenu.room) + { + // move everything after this index 1 slot down... + if (i != serverlistcount) + memcpy(&serverlist[i], &serverlist[i+1], sizeof(serverelem_t)*(serverlistcount-i)); + + serverlistcount--; + } + + i--; + } +} + +void +M_SetWaitingMode (int mode) +{ +#ifdef HAVE_THREADS + I_lock_mutex(&k_menu_mutex); +#endif + { + m_waiting_mode = mode; + } +#ifdef HAVE_THREADS + I_unlock_mutex(k_menu_mutex); +#endif +} + +int +M_GetWaitingMode (void) +{ + int mode; + +#ifdef HAVE_THREADS + I_lock_mutex(&k_menu_mutex); +#endif + { + mode = m_waiting_mode; + } +#ifdef HAVE_THREADS + I_unlock_mutex(k_menu_mutex); +#endif + + return mode; +} + +#ifdef MASTERSERVER +#ifdef HAVE_THREADS +void +Spawn_masterserver_thread (const char *name, void (*thread)(int*)) +{ + int *id = malloc(sizeof *id); + + I_lock_mutex(&ms_QueryId_mutex); + { + *id = ms_QueryId; + } + I_unlock_mutex(ms_QueryId_mutex); + + I_spawn_thread(name, (I_thread_fn)thread, id); +} + +int +Same_instance (int id) +{ + int okay; + + I_lock_mutex(&ms_QueryId_mutex); + { + okay = ( id == ms_QueryId ); + } + I_unlock_mutex(ms_QueryId_mutex); + + return okay; +} +#endif/*HAVE_THREADS*/ + +void +Fetch_servers_thread (int *id) +{ + msg_server_t * server_list; + + (void)id; + + M_SetWaitingMode(M_WAITING_SERVERS); + +#ifdef HAVE_THREADS + server_list = GetShortServersList(*id); +#else + server_list = GetShortServersList(0); +#endif + + if (server_list) + { +#ifdef HAVE_THREADS + if (Same_instance(*id)) +#endif + { + M_SetWaitingMode(M_NOT_WAITING); + +#ifdef HAVE_THREADS + I_lock_mutex(&ms_ServerList_mutex); + { + ms_ServerList = server_list; + } + I_unlock_mutex(ms_ServerList_mutex); +#else + CL_QueryServerList(server_list); + free(server_list); +#endif + } +#ifdef HAVE_THREADS + else + { + free(server_list); + } +#endif + } + +#ifdef HAVE_THREADS + free(id); +#endif +} +#endif/*MASTERSERVER*/ + +// updates serverlist +void M_RefreshServers(INT32 choice) +{ + (void)choice; + + // Display a little "please wait" message. + M_DrawTextBox(52, BASEVIDHEIGHT/2-10, 25, 3); + V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT/2, 0, "Searching for servers..."); + V_DrawCenteredString(BASEVIDWIDTH/2, (BASEVIDHEIGHT/2)+12, 0, "Please wait."); + I_OsPolling(); + I_UpdateNoBlit(); + if (rendermode == render_soft) + I_FinishUpdate(); // page flip or blit buffer + +#ifdef MASTERSERVER +#ifdef HAVE_THREADS + Spawn_masterserver_thread("fetch-servers", Fetch_servers_thread); +#else/*HAVE_THREADS*/ + Fetch_servers_thread(NULL); +#endif/*HAVE_THREADS*/ +#else/*MASTERSERVER*/ + CL_UpdateServerList(); +#endif/*MASTERSERVER*/ + +#ifdef SERVERLISTDEBUG + M_ServerListFillDebug(); +#endif + M_CleanServerList(); + M_SortServerList(); + +} + +#ifdef UPDATE_ALERT +static void M_CheckMODVersion(int id) +{ + char updatestring[500]; + const char *updatecheck = GetMODVersion(id); + if(updatecheck) + { + sprintf(updatestring, UPDATE_ALERT_STRING, VERSIONSTRING, updatecheck); +#ifdef HAVE_THREADS + I_lock_mutex(&k_menu_mutex); +#endif + M_StartMessage(updatestring, NULL, MM_NOTHING); +#ifdef HAVE_THREADS + I_unlock_mutex(k_menu_mutex); +#endif + } +} +#endif/*UPDATE_ALERT*/ + +#if defined (UPDATE_ALERT) && defined (HAVE_THREADS) +static void +Check_new_version_thread (int *id) +{ + M_SetWaitingMode(M_WAITING_VERSION); + + M_CheckMODVersion(*id); + + if (Same_instance(*id)) + { + Fetch_servers_thread(id); + } + else + { + free(id); + } +} +#endif/*defined (UPDATE_ALERT) && defined (HAVE_THREADS)*/ + + +// Initializes serverlist when entering the menu... +void M_ServersMenu(INT32 choice) +{ + (void)choice; + // modified game check: no longer handled + // we don't request a restart unless the filelist differs + + mpmenu.servernum = 0; + mpmenu.scrolln = 0; + mpmenu.slide = 0; + + M_SetupNextMenu(&PLAY_MP_ServerBrowserDef, false); + itemOn = 0; + +#if defined (MASTERSERVER) && defined (HAVE_THREADS) + I_lock_mutex(&ms_QueryId_mutex); + { + ms_QueryId++; + } + I_unlock_mutex(ms_QueryId_mutex); + + I_lock_mutex(&ms_ServerList_mutex); + { + if (ms_ServerList) + { + free(ms_ServerList); + ms_ServerList = NULL; + } + } + I_unlock_mutex(ms_ServerList_mutex); + +#ifdef UPDATE_ALERT + Spawn_masterserver_thread("check-new-version", Check_new_version_thread); +#else/*UPDATE_ALERT*/ + Spawn_masterserver_thread("fetch-servers", Fetch_servers_thread); +#endif/*UPDATE_ALERT*/ +#else/*defined (MASTERSERVER) && defined (HAVE_THREADS)*/ +#ifdef UPDATE_ALERT + M_CheckMODVersion(0); +#endif/*UPDATE_ALERT*/ + M_RefreshServers(0); +#endif/*defined (MASTERSERVER) && defined (HAVE_THREADS)*/ + +#ifdef SERVERLISTDEBUG + M_ServerListFillDebug(); +#endif + + M_CleanServerList(); + M_SortServerList(); +} + +#ifdef SERVERLISTDEBUG + +// Fill serverlist with a bunch of garbage to make our life easier in debugging +void M_ServerListFillDebug(void) +{ + UINT8 i = 0; + + serverlistcount = 10; + memset(serverlist, 0, sizeof(serverlist)); // zero out the array for convenience... + + for (i = 0; i < serverlistcount; i++) + { + // We don't really care about the server node for this, let's just fill in the info so that we have a visual... + serverlist[i].info.numberofplayer = min(i, 8); + serverlist[i].info.maxplayer = 8; + + serverlist[i].info.avgpwrlv = P_RandomRange(PR_UNDEFINED, 500, 1500); + serverlist[i].info.time = P_RandomRange(PR_UNDEFINED, 1, 8); // ping + + strcpy(serverlist[i].info.servername, va("Serv %d", i+1)); + + strcpy(serverlist[i].info.gametypename, i & 1 ? "Race" : "Battle"); + + P_RandomRange(PR_UNDEFINED, 0, 5); // change results... + serverlist[i].info.kartvars = P_RandomRange(PR_UNDEFINED, 0, 3) & SV_SPEEDMASK; + + serverlist[i].info.modifiedgame = P_RandomRange(PR_UNDEFINED, 0, 1); + + CONS_Printf("Serv %d | %d...\n", i, serverlist[i].info.modifiedgame); + } +} + +#endif // SERVERLISTDEBUG + +// Ascending order, not descending. +// The casts are safe as long as the caller doesn't do anything stupid. +#define SERVER_LIST_ENTRY_COMPARATOR(key) \ +static int ServerListEntryComparator_##key(const void *entry1, const void *entry2) \ +{ \ + const serverelem_t *sa = (const serverelem_t*)entry1, *sb = (const serverelem_t*)entry2; \ + if (sa->info.key != sb->info.key) \ + return sa->info.key - sb->info.key; \ + return strcmp(sa->info.servername, sb->info.servername); \ +} + +// This does descending instead of ascending. +#define SERVER_LIST_ENTRY_COMPARATOR_REVERSE(key) \ +static int ServerListEntryComparator_##key##_reverse(const void *entry1, const void *entry2) \ +{ \ + const serverelem_t *sa = (const serverelem_t*)entry1, *sb = (const serverelem_t*)entry2; \ + if (sb->info.key != sa->info.key) \ + return sb->info.key - sa->info.key; \ + return strcmp(sb->info.servername, sa->info.servername); \ +} + +SERVER_LIST_ENTRY_COMPARATOR(time) +SERVER_LIST_ENTRY_COMPARATOR(numberofplayer) +SERVER_LIST_ENTRY_COMPARATOR_REVERSE(numberofplayer) +SERVER_LIST_ENTRY_COMPARATOR_REVERSE(maxplayer) +SERVER_LIST_ENTRY_COMPARATOR(avgpwrlv) + + +static int ServerListEntryComparator_gametypename(const void *entry1, const void *entry2) +{ + const serverelem_t *sa = (const serverelem_t*)entry1, *sb = (const serverelem_t*)entry2; + int c; + if (( c = strcasecmp(sa->info.gametypename, sb->info.gametypename) )) + return c; + return strcmp(sa->info.servername, sb->info.servername); \ +} + +void M_SortServerList(void) +{ + switch(cv_serversort.value) + { + case 0: // Ping. + qsort(serverlist, serverlistcount, sizeof(serverelem_t), ServerListEntryComparator_time); + break; + case 1: // AVG. Power Level + qsort(serverlist, serverlistcount, sizeof(serverelem_t), ServerListEntryComparator_avgpwrlv); + break; + case 2: // Most players. + qsort(serverlist, serverlistcount, sizeof(serverelem_t), ServerListEntryComparator_numberofplayer_reverse); + break; + case 3: // Least players. + qsort(serverlist, serverlistcount, sizeof(serverelem_t), ServerListEntryComparator_numberofplayer); + break; + case 4: // Max players. + qsort(serverlist, serverlistcount, sizeof(serverelem_t), ServerListEntryComparator_maxplayer_reverse); + break; + case 5: // Gametype. + qsort(serverlist, serverlistcount, sizeof(serverelem_t), ServerListEntryComparator_gametypename); + break; + } +} + + +// Server browser inputs & ticker +void M_MPServerBrowserTick(void) +{ + mpmenu.slide /= 2; +} + +// Input handler for server browser. +boolean M_ServerBrowserInputs(INT32 ch) +{ + UINT8 pid = 0; + UINT8 maxscroll = serverlistcount-(SERVERSPERPAGE/2); + (void) ch; + + if (!itemOn && menucmd[pid].dpad_ud < 0) + { + M_PrevOpt(); // go to itemOn 2 + if (serverlistcount) + { + UINT8 prevscroll = mpmenu.scrolln; + + mpmenu.servernum = serverlistcount; + mpmenu.scrolln = maxscroll; + mpmenu.slide = SERVERSPACE * (prevscroll - mpmenu.scrolln); + } + else + { + itemOn = 1; // Sike! If there are no servers, go to refresh instead. + } + + return true; // overwrite behaviour. + } + else if (itemOn == 2) // server browser itself... + { + // we have to manually do that here. + if (M_MenuBackPressed(pid)) + { + M_GoBack(0); + M_SetMenuDelay(pid); + } + + else if (menucmd[pid].dpad_ud > 0) // down + { + if (mpmenu.servernum >= serverlistcount-1) + { + UINT8 prevscroll = mpmenu.scrolln; + mpmenu.servernum = 0; + mpmenu.scrolln = 0; + mpmenu.slide = SERVERSPACE * (prevscroll - mpmenu.scrolln); + + M_NextOpt(); // Go back to the top of the real menu. + } + else + { + mpmenu.servernum++; + if (mpmenu.scrolln < maxscroll && mpmenu.servernum > SERVERSPERPAGE/2) + { + mpmenu.scrolln++; + mpmenu.slide += SERVERSPACE; + } + } + S_StartSound(NULL, sfx_s3k5b); + M_SetMenuDelay(pid); + + } + else if (menucmd[pid].dpad_ud < 0) + { + + if (!mpmenu.servernum) + { + M_PrevOpt(); + } + else + { + if (mpmenu.servernum <= serverlistcount-(SERVERSPERPAGE/2) && mpmenu.scrolln) + { + mpmenu.scrolln--; + mpmenu.slide -= SERVERSPACE; + } + + mpmenu.servernum--; + } + S_StartSound(NULL, sfx_s3k5b); + M_SetMenuDelay(pid); + + } + return true; // Overwrite behaviour. + } + return false; // use normal behaviour. +} diff --git a/src/menus/transient/CMakeLists.txt b/src/menus/transient/CMakeLists.txt index 305c82829..bea478d5f 100644 --- a/src/menus/transient/CMakeLists.txt +++ b/src/menus/transient/CMakeLists.txt @@ -1,6 +1,11 @@ target_sources(SRB2SDL2 PRIVATE + cup-select.c + explosions.c level-select.c + gametype.c manual.c + message-box.c pause-game.c pause-replay.c + virtual-keyboard.c ) diff --git a/src/menus/transient/cup-select.c b/src/menus/transient/cup-select.c new file mode 100644 index 000000000..b65f1ea3d --- /dev/null +++ b/src/menus/transient/cup-select.c @@ -0,0 +1,195 @@ +/// \file menus/transient/cup-select.c +/// \brief Cup Select + +#include "../../k_menu.h" +#include "../../s_sound.h" +#include "../../f_finale.h" // F_WipeStartScreen +#include "../../v_video.h" +#include "../../k_grandprix.h" +#include "../../r_local.h" // SplitScreen_OnChange + +menuitem_t PLAY_CupSelect[] = +{ + {IT_NOTHING | IT_KEYHANDLER, NULL, NULL, NULL, {.routine = M_CupSelectHandler}, 0, 0}, +}; + +menu_t PLAY_CupSelectDef = { + sizeof(PLAY_CupSelect) / sizeof(menuitem_t), + &PLAY_RaceGamemodesDef, + 0, + PLAY_CupSelect, + 0, 0, + 0, 0, + 2, 5, + M_DrawCupSelect, + M_CupSelectTick, + NULL, + NULL, + NULL +}; + +struct cupgrid_s cupgrid; + +void M_CupSelectHandler(INT32 choice) +{ + const UINT8 pid = 0; + + (void)choice; + + if (menucmd[pid].dpad_lr > 0) + { + cupgrid.x++; + if (cupgrid.x >= CUPMENU_COLUMNS) + { + cupgrid.x = 0; + cupgrid.pageno++; + if (cupgrid.pageno >= cupgrid.numpages) + cupgrid.pageno = 0; + } + S_StartSound(NULL, sfx_s3k5b); + M_SetMenuDelay(pid); + } + else if (menucmd[pid].dpad_lr < 0) + { + cupgrid.x--; + if (cupgrid.x < 0) + { + cupgrid.x = CUPMENU_COLUMNS-1; + if (cupgrid.pageno == 0) + cupgrid.pageno = cupgrid.numpages-1; + else + cupgrid.pageno--; + } + S_StartSound(NULL, sfx_s3k5b); + M_SetMenuDelay(pid); + } + + if (menucmd[pid].dpad_ud > 0) + { + cupgrid.y++; + if (cupgrid.y >= CUPMENU_ROWS) + cupgrid.y = 0; + S_StartSound(NULL, sfx_s3k5b); + M_SetMenuDelay(pid); + } + else if (menucmd[pid].dpad_ud < 0) + { + cupgrid.y--; + if (cupgrid.y < 0) + cupgrid.y = CUPMENU_ROWS-1; + S_StartSound(NULL, sfx_s3k5b); + M_SetMenuDelay(pid); + } + + if (M_MenuConfirmPressed(pid) /*|| M_MenuButtonPressed(pid, MBT_START)*/) + { + INT16 count; + cupheader_t *newcup = cupgrid.builtgrid[CUPMENU_CURSORID]; + cupheader_t *oldcup = levellist.levelsearch.cup; + + M_SetMenuDelay(pid); + + levellist.levelsearch.cup = newcup; + count = M_CountLevelsToShowInList(&levellist.levelsearch); + + if ((!newcup) + || (count <= 0) + || (cupgrid.grandprix == true && newcup->cachedlevels[0] == NEXTMAP_INVALID)) + { + S_StartSound(NULL, sfx_s3kb2); + return; + } + + if (cupgrid.grandprix == true) + { + INT32 levelNum; + UINT8 ssplayers = cv_splitplayers.value-1; + + S_StartSound(NULL, sfx_s3k63); + + // Early fadeout to let the sound finish playing + F_WipeStartScreen(); + V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31); + F_WipeEndScreen(); + F_RunWipe(wipedefs[wipe_level_toblack], false, "FADEMAP0", false, false); + + memset(&grandprixinfo, 0, sizeof(struct grandprixinfo)); + + if (cv_maxconnections.value < ssplayers+1) + CV_SetValue(&cv_maxconnections, ssplayers+1); + + if (splitscreen != ssplayers) + { + splitscreen = ssplayers; + SplitScreen_OnChange(); + } + + // read our dummy cvars + + grandprixinfo.gamespeed = min(KARTSPEED_HARD, cv_dummygpdifficulty.value); + grandprixinfo.masterbots = (cv_dummygpdifficulty.value == 3); + grandprixinfo.encore = (boolean)cv_dummygpencore.value; + + grandprixinfo.cup = newcup; + + grandprixinfo.gp = true; + grandprixinfo.roundnum = 1; + grandprixinfo.initalize = true; + + paused = false; + + // Don't restart the server if we're already in a game lol + if (gamestate == GS_MENU) + { + SV_StartSinglePlayerServer(levellist.newgametype, levellist.netgame); + } + + levelNum = grandprixinfo.cup->cachedlevels[0]; + + D_MapChange( + levelNum + 1, + GT_RACE, + grandprixinfo.encore, + true, + 1, + false, + false + ); + + M_ClearMenus(true); + } + else if (count == 1) + { + PLAY_TimeAttackDef.transitionID = currentMenu->transitionID+1; + M_LevelSelected(0); + } + else + { + // Keep cursor position if you select the same cup again, reset if it's a different cup + if (oldcup != newcup || levellist.cursor >= count) + { + levellist.cursor = 0; + } + + M_LevelSelectScrollDest(); + levellist.y = levellist.dest; + + M_SetupNextMenu(&PLAY_LevelSelectDef, false); + S_StartSound(NULL, sfx_s3k63); + } + } + else if (M_MenuBackPressed(pid)) + { + M_SetMenuDelay(pid); + + if (currentMenu->prevMenu) + M_SetupNextMenu(currentMenu->prevMenu, false); + else + M_ClearMenus(true); + } +} + +void M_CupSelectTick(void) +{ + cupgrid.previewanim++; +} diff --git a/src/menus/transient/explosions.c b/src/menus/transient/explosions.c new file mode 100644 index 000000000..3c5c868ed --- /dev/null +++ b/src/menus/transient/explosions.c @@ -0,0 +1,63 @@ +/// \file menus/transient/explosions.c +/// \brief Explosions used on the character select grid and +/// challenges grid. + +#include "../../k_menu.h" +#include "../../m_cond.h" // Condition Sets + +struct setup_explosions_s setup_explosions[CSEXPLOSIONS]; + +void M_SetupReadyExplosions(boolean charsel, UINT16 basex, UINT16 basey, UINT16 color) +{ + UINT8 i, j; + UINT8 e = 0; + UINT16 maxx = (charsel ? 9 : gamedata->challengegridwidth); + UINT16 maxy = (charsel ? 9 : CHALLENGEGRIDHEIGHT); + + while (setup_explosions[e].tics) + { + e++; + if (e == CSEXPLOSIONS) + return; + } + + for (i = 0; i < 3; i++) + { + UINT8 t = 5 + (i*2); + UINT8 offset = (i+1); + + for (j = 0; j < 4; j++) + { + INT16 x = basex, y = basey; + + switch (j) + { + case 0: x += offset; break; + case 1: x -= offset; break; + case 2: y += offset; break; + case 3: y -= offset; break; + } + + if ((y < 0 || y >= maxy)) + continue; + + if (charsel || !challengegridloops) + { + if (x < 0 || x >= maxx) + continue; + } + + setup_explosions[e].tics = t; + setup_explosions[e].color = color; + setup_explosions[e].x = x; + setup_explosions[e].y = y; + + while (setup_explosions[e].tics) + { + e++; + if (e == CSEXPLOSIONS) + return; + } + } + } +} diff --git a/src/menus/transient/gametype.c b/src/menus/transient/gametype.c new file mode 100644 index 000000000..7bb0ba425 --- /dev/null +++ b/src/menus/transient/gametype.c @@ -0,0 +1,34 @@ +/// \file menus/transient/gametype.c +/// \brief Gametype selection + +#include "../../k_menu.h" + +INT16 menugametype = GT_RACE; + +void M_NextMenuGametype(UINT32 forbidden) +{ + const INT16 currentmenugametype = menugametype; + do + { + menugametype++; + if (menugametype >= numgametypes) + menugametype = 0; + + if (!(gametypes[menugametype]->rules & forbidden)) + break; + } while (menugametype != currentmenugametype); +} + +void M_PrevMenuGametype(UINT32 forbidden) +{ + const INT16 currentmenugametype = menugametype; + do + { + if (menugametype == 0) + menugametype = numgametypes; + menugametype--; + + if (!(gametypes[menugametype]->rules & forbidden)) + break; + } while (menugametype != currentmenugametype); +} diff --git a/src/menus/transient/level-select.c b/src/menus/transient/level-select.c index ab124b8e5..9480e762f 100644 --- a/src/menus/transient/level-select.c +++ b/src/menus/transient/level-select.c @@ -2,26 +2,12 @@ /// \brief Level Select #include "../../k_menu.h" - -menuitem_t PLAY_CupSelect[] = -{ - {IT_NOTHING | IT_KEYHANDLER, NULL, NULL, NULL, {.routine = M_CupSelectHandler}, 0, 0}, -}; - -menu_t PLAY_CupSelectDef = { - sizeof(PLAY_CupSelect) / sizeof(menuitem_t), - &PLAY_RaceGamemodesDef, - 0, - PLAY_CupSelect, - 0, 0, - 0, 0, - 2, 5, - M_DrawCupSelect, - M_CupSelectTick, - NULL, - NULL, - NULL -}; +#include "../../m_cond.h" // Condition Sets +#include "../../z_zone.h" +#include "../../s_sound.h" +#include "../../r_local.h" // SplitScreen_OnChange +#include "../../f_finale.h" // F_WipeStartScreen +#include "../../v_video.h" menuitem_t PLAY_LevelSelect[] = { @@ -42,3 +28,524 @@ menu_t PLAY_LevelSelectDef = { NULL, NULL }; + +struct levellist_s levellist; + +// +// M_CanShowLevelInList +// +// Determines whether to show a given map in the various level-select lists. +// +boolean M_CanShowLevelInList(INT16 mapnum, levelsearch_t *levelsearch) +{ + if (!levelsearch) + return false; + + if (mapnum >= nummapheaders) + return false; + + // Does the map exist? + if (!mapheaderinfo[mapnum]) + return false; + + // Does the map have a name? + if (!mapheaderinfo[mapnum]->lvlttl[0]) + return false; + + // Does the map have a LUMP? + if (mapheaderinfo[mapnum]->lumpnum == LUMPERROR) + return false; + + // Check for TOL + if (!(mapheaderinfo[mapnum]->typeoflevel & levelsearch->typeoflevel)) + return false; + + // Should the map be hidden? + if (mapheaderinfo[mapnum]->menuflags & LF2_HIDEINMENU) + return false; + + // I don't know why, but some may have exceptions. + if (levelsearch->timeattack && (mapheaderinfo[mapnum]->menuflags & LF2_NOTIMEATTACK)) + return false; + + // Don't permit cup when no cup requested (also no dupes in time attack) + if (levelsearch->cupmode + && (levelsearch->timeattack || !levelsearch->cup) + && mapheaderinfo[mapnum]->cup != levelsearch->cup) + return false; + + // Finally, the most complex check: does the map have lock conditions? + if (levelsearch->checklocked) + { + // Check for completion + if ((mapheaderinfo[mapnum]->menuflags & LF2_FINISHNEEDED) + && !(mapheaderinfo[mapnum]->mapvisited & MV_BEATEN)) + return false; + + // Check for unlock + if (M_MapLocked(mapnum+1)) + return false; + } + + // Survived our checks. + return true; +} + +UINT16 M_CountLevelsToShowInList(levelsearch_t *levelsearch) +{ + INT16 i, count = 0; + + if (!levelsearch) + return false; + + if (levelsearch->cup) + { + if (levelsearch->checklocked && M_CupLocked(levelsearch->cup)) + return 0; + + for (i = 0; i < CUPCACHE_MAX; i++) + { + if (!M_CanShowLevelInList(levelsearch->cup->cachedlevels[i], levelsearch)) + continue; + count++; + } + + return count; + } + + for (i = 0; i < nummapheaders; i++) + if (M_CanShowLevelInList(i, levelsearch)) + count++; + + return count; +} + +UINT16 M_GetFirstLevelInList(UINT8 *i, levelsearch_t *levelsearch) +{ + INT16 mapnum = NEXTMAP_INVALID; + + if (!levelsearch) + return false; + + if (levelsearch->cup) + { + if (levelsearch->checklocked && M_CupLocked(levelsearch->cup)) + { + *i = CUPCACHE_MAX; + return NEXTMAP_INVALID; + } + + *i = 0; + mapnum = NEXTMAP_INVALID; + for (; *i < CUPCACHE_MAX; (*i)++) + { + if (!M_CanShowLevelInList(levelsearch->cup->cachedlevels[*i], levelsearch)) + continue; + mapnum = levelsearch->cup->cachedlevels[*i]; + break; + } + } + else + { + for (mapnum = 0; mapnum < nummapheaders; mapnum++) + if (M_CanShowLevelInList(mapnum, levelsearch)) + break; + } + + return mapnum; +} + +UINT16 M_GetNextLevelInList(UINT16 mapnum, UINT8 *i, levelsearch_t *levelsearch) +{ + if (!levelsearch) + return false; + + if (levelsearch->cup) + { + mapnum = NEXTMAP_INVALID; + (*i)++; + for (; *i < CUPCACHE_MAX; (*i)++) + { + if (!M_CanShowLevelInList(levelsearch->cup->cachedlevels[*i], levelsearch)) + continue; + mapnum = levelsearch->cup->cachedlevels[*i]; + break; + } + } + else + { + mapnum++; + while (!M_CanShowLevelInList(mapnum, levelsearch) && mapnum < nummapheaders) + mapnum++; + } + + return mapnum; +} + +void M_LevelSelectScrollDest(void) +{ + UINT16 m = M_CountLevelsToShowInList(&levellist.levelsearch)-1; + + levellist.dest = (6*levellist.cursor); + + if (levellist.dest < 3) + levellist.dest = 3; + + if (levellist.dest > (6*m)-3) + levellist.dest = (6*m)-3; +} + +// Builds the level list we'll be using from the gametype we're choosing and send us to the apropriate menu. +boolean M_LevelListFromGametype(INT16 gt) +{ + static boolean first = true; + UINT8 temp = 0; + + if (first || gt != levellist.newgametype || levellist.guessgt != MAXGAMETYPES) + { + if (first) + { + cupgrid.cappages = 0; + cupgrid.builtgrid = NULL; + } + + levellist.newgametype = gt; + + levellist.levelsearch.typeoflevel = G_TOLFlag(gt); + if (levellist.levelsearch.timeattack == true && gt == GT_SPECIAL) + { + // Sneak in an extra. + levellist.levelsearch.typeoflevel |= G_TOLFlag(GT_VERSUS); + levellist.guessgt = gt; + } + else + { + levellist.guessgt = MAXGAMETYPES; + } + + levellist.levelsearch.cupmode = (!(gametypes[gt]->rules & GTR_NOCUPSELECT)); + levellist.levelsearch.cup = NULL; + + first = false; + } + + // Obviously go to Cup Select in gametypes that have cups. + // Use a really long level select in gametypes that don't use cups. + + if (levellist.levelsearch.cupmode) + { + levelsearch_t templevelsearch = levellist.levelsearch; // full copy + size_t currentid = 0, highestunlockedid = 0; + const size_t pagelen = sizeof(cupheader_t*) * (CUPMENU_COLUMNS * CUPMENU_ROWS); + boolean foundany = false; + + templevelsearch.cup = kartcupheaders; + +#if 0 + // Make sure there's valid cups before going to this menu. -- rip sweet prince + if (templevelsearch.cup == NULL) + I_Error("Can you really call this a racing game, I didn't recieve any Cups on my pillow or anything"); +#endif + + if (cupgrid.cappages == 0) + { + cupgrid.cappages = 2; + cupgrid.builtgrid = Z_Calloc( + cupgrid.cappages * pagelen, + PU_STATIC, + NULL); + + if (!cupgrid.builtgrid) + { + I_Error("M_LevelListFromGametype: Not enough memory to allocate builtgrid"); + } + } + memset(cupgrid.builtgrid, 0, cupgrid.cappages * pagelen); + + while (templevelsearch.cup) + { + templevelsearch.checklocked = false; + if (!M_CountLevelsToShowInList(&templevelsearch)) + { + // No valid maps, skip. + templevelsearch.cup = templevelsearch.cup->next; + continue; + } + + foundany = true; + + if ((currentid * sizeof(cupheader_t*)) >= cupgrid.cappages * pagelen) + { + // Double the size of the buffer, and clear the other stuff. + const size_t firstlen = cupgrid.cappages * pagelen; + cupgrid.builtgrid = Z_Realloc(cupgrid.builtgrid, + firstlen * 2, + PU_STATIC, NULL); + + if (!cupgrid.builtgrid) + { + I_Error("M_LevelListFromGametype: Not enough memory to reallocate builtgrid"); + } + + memset(cupgrid.builtgrid + firstlen, 0, firstlen); + cupgrid.cappages *= 2; + } + + cupgrid.builtgrid[currentid] = templevelsearch.cup; + + templevelsearch.checklocked = true; + if (M_GetFirstLevelInList(&temp, &templevelsearch) != NEXTMAP_INVALID) + { + highestunlockedid = currentid; + if (Playing() && mapheaderinfo[gamemap-1] && mapheaderinfo[gamemap-1]->cup == templevelsearch.cup) + { + cupgrid.x = currentid % CUPMENU_COLUMNS; + cupgrid.y = (currentid / CUPMENU_COLUMNS) % CUPMENU_ROWS; + cupgrid.pageno = currentid / (CUPMENU_COLUMNS * CUPMENU_ROWS); + } + } + + currentid++; + templevelsearch.cup = templevelsearch.cup->next; + } + + if (foundany == false) + { + return false; + } + + cupgrid.numpages = (highestunlockedid / (CUPMENU_COLUMNS * CUPMENU_ROWS)) + 1; + if (cupgrid.pageno >= cupgrid.numpages) + { + cupgrid.pageno = 0; + } + + PLAY_CupSelectDef.prevMenu = currentMenu; + PLAY_LevelSelectDef.prevMenu = &PLAY_CupSelectDef; + M_SetupNextMenu(&PLAY_CupSelectDef, false); + + return true; + } + + // Okay, just a list of maps then. + + if (M_GetFirstLevelInList(&temp, &levellist.levelsearch) == NEXTMAP_INVALID) + { + return false; + } + + // Reset position properly if you go back & forth between gametypes + if (levellist.levelsearch.cup) + { + levellist.cursor = 0; + levellist.levelsearch.cup = NULL; + } + + M_LevelSelectScrollDest(); + levellist.y = levellist.dest; + + PLAY_LevelSelectDef.prevMenu = currentMenu; + M_SetupNextMenu(&PLAY_LevelSelectDef, false); + + return true; +} + +// Init level select for use in local play using the last choice we made. +// For the online MP version used to START HOSTING A GAME, see M_MPSetupNetgameMapSelect() +// (We still use this one midgame) + +void M_LevelSelectInit(INT32 choice) +{ + INT32 gt = currentMenu->menuitems[itemOn].mvar2; + + (void)choice; + + // Make sure this is reset as we'll only be using this function for offline games! + levellist.netgame = false; + levellist.levelsearch.checklocked = true; + + switch (currentMenu->menuitems[itemOn].mvar1) + { + case 0: + cupgrid.grandprix = false; + levellist.levelsearch.timeattack = false; + break; + case 1: + cupgrid.grandprix = false; + levellist.levelsearch.timeattack = true; + break; + case 2: + cupgrid.grandprix = true; + levellist.levelsearch.timeattack = false; + break; + default: + CONS_Alert(CONS_WARNING, "Bad level select init\n"); + return; + } + + if (gt == -1) + { + gt = menugametype; + } + + if (!M_LevelListFromGametype(gt)) + { + S_StartSound(NULL, sfx_s3kb2); + M_StartMessage(va("No levels available for\n%s Mode!\n\nPress (B)\n", gametypes[gt]->name), NULL, MM_NOTHING); + } +} + +void M_LevelSelected(INT16 add) +{ + UINT8 i = 0; + INT16 map = M_GetFirstLevelInList(&i, &levellist.levelsearch); + + while (add > 0) + { + map = M_GetNextLevelInList(map, &i, &levellist.levelsearch); + + if (map >= nummapheaders) + { + break; + } + + add--; + } + + if (map >= nummapheaders) + { + // This shouldn't happen + return; + } + + levellist.choosemap = map; + + if (levellist.levelsearch.timeattack) + { + S_StartSound(NULL, sfx_s3k63); + + if (levellist.guessgt != MAXGAMETYPES) + levellist.newgametype = G_GuessGametypeByTOL(levellist.levelsearch.typeoflevel); + + PLAY_TimeAttackDef.lastOn = ta_start; + PLAY_TimeAttackDef.prevMenu = currentMenu; + M_SetupNextMenu(&PLAY_TimeAttackDef, false); + } + else + { + if (gamestate == GS_MENU) + { + UINT8 ssplayers = cv_splitplayers.value-1; + + netgame = false; + multiplayer = true; + + strncpy(connectedservername, cv_servername.string, MAXSERVERNAME); + + // Still need to reset devmode + cht_debug = 0; + + if (demo.playback) + G_StopDemo(); + if (metalrecording) + G_StopMetalDemo(); + + /*if (levellist.choosemap == 0) + levellist.choosemap = G_RandMap(G_TOLFlag(levellist.newgametype), -1, 0, 0, false, NULL);*/ + + if (cv_maxconnections.value < ssplayers+1) + CV_SetValue(&cv_maxconnections, ssplayers+1); + + if (splitscreen != ssplayers) + { + splitscreen = ssplayers; + SplitScreen_OnChange(); + } + + S_StartSound(NULL, sfx_s3k63); + + paused = false; + + // Early fadeout to let the sound finish playing + F_WipeStartScreen(); + V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31); + F_WipeEndScreen(); + F_RunWipe(wipedefs[wipe_level_toblack], false, "FADEMAP0", false, false); + + SV_StartSinglePlayerServer(levellist.newgametype, levellist.netgame); + + CV_StealthSet(&cv_kartbot, cv_dummymatchbots.string); + CV_StealthSet(&cv_kartencore, (cv_dummygpencore.value == 1) ? "On" : "Auto"); + CV_StealthSet(&cv_kartspeed, (cv_dummykartspeed.value == KARTSPEED_NORMAL) ? "Auto" : cv_dummykartspeed.string); + + D_MapChange(levellist.choosemap+1, levellist.newgametype, (cv_kartencore.value == 1), 1, 1, false, false); + } + else + { + // directly do the map change + D_MapChange(levellist.choosemap+1, levellist.newgametype, (cv_kartencore.value == 1), 1, 1, false, false); + } + + M_ClearMenus(true); + } +} + +void M_LevelSelectHandler(INT32 choice) +{ + INT16 maxlevels = M_CountLevelsToShowInList(&levellist.levelsearch); + const UINT8 pid = 0; + + (void)choice; + + if (levellist.y != levellist.dest) + { + return; + } + + if (menucmd[pid].dpad_ud > 0) + { + levellist.cursor++; + if (levellist.cursor >= maxlevels) + levellist.cursor = 0; + S_StartSound(NULL, sfx_s3k5b); + M_SetMenuDelay(pid); + } + else if (menucmd[pid].dpad_ud < 0) + { + levellist.cursor--; + if (levellist.cursor < 0) + levellist.cursor = maxlevels-1; + S_StartSound(NULL, sfx_s3k5b); + M_SetMenuDelay(pid); + } + + M_LevelSelectScrollDest(); + + if (M_MenuConfirmPressed(pid) /*|| M_MenuButtonPressed(pid, MBT_START)*/) + { + M_SetMenuDelay(pid); + + PLAY_TimeAttackDef.transitionID = currentMenu->transitionID; + M_LevelSelected(levellist.cursor); + } + else if (M_MenuBackPressed(pid)) + { + M_SetMenuDelay(pid); + + if (currentMenu->prevMenu) + M_SetupNextMenu(currentMenu->prevMenu, false); + else + M_ClearMenus(true); + } +} + +void M_LevelSelectTick(void) +{ + + INT16 dist = levellist.dest - levellist.y; + + if (abs(dist) == 1) // cheating to avoid off by 1 errors with divisions. + levellist.y = levellist.dest; + else + levellist.y += dist/2; +} diff --git a/src/menus/transient/manual.c b/src/menus/transient/manual.c index 2ba3c32a9..e6ca008eb 100644 --- a/src/menus/transient/manual.c +++ b/src/menus/transient/manual.c @@ -2,6 +2,7 @@ /// \brief Manual #include "../../k_menu.h" +#include "../../s_sound.h" menuitem_t MISC_Manual[] = { {IT_NOTHING | IT_KEYHANDLER, "MANUAL00", NULL, NULL, {.routine = M_HandleImageDef}, 0, 0}, @@ -21,3 +22,52 @@ menuitem_t MISC_Manual[] = { }; menu_t MISC_ManualDef = IMAGEDEF(MISC_Manual); + +// Handles the ImageDefs. Just a specialized function that +// uses left and right movement. +void M_HandleImageDef(INT32 choice) +{ + const UINT8 pid = 0; + boolean exitmenu = false; + (void) choice; + + if (menucmd[pid].dpad_lr > 0) + { + if (itemOn >= (INT16)(currentMenu->numitems-1)) + return; + S_StartSound(NULL, sfx_s3k5b); + M_SetMenuDelay(pid); + itemOn++; + } + else if (menucmd[pid].dpad_lr < 0) + { + if (!itemOn) + return; + S_StartSound(NULL, sfx_s3k5b); + M_SetMenuDelay(pid); + itemOn--; + } + else if (M_MenuConfirmPressed(pid) || M_MenuButtonPressed(pid, MBT_X) || M_MenuButtonPressed(pid, MBT_Y)) + { + exitmenu = true; + M_SetMenuDelay(pid); + } + + + if (exitmenu) + { + if (currentMenu->prevMenu) + M_SetupNextMenu(currentMenu->prevMenu, false); + else + M_ClearMenus(true); + } +} + +// Opening manual +void M_Manual(INT32 choice) +{ + (void)choice; + + MISC_ManualDef.prevMenu = (choice == INT32_MAX ? NULL : currentMenu); + M_SetupNextMenu(&MISC_ManualDef, true); +} diff --git a/src/menus/transient/message-box.c b/src/menus/transient/message-box.c new file mode 100644 index 000000000..1f3c0b47a --- /dev/null +++ b/src/menus/transient/message-box.c @@ -0,0 +1,200 @@ +// \file menus/transient/message-box.c +// \brief MESSAGE BOX (aka: a hacked, cobbled together menu) + +#include "../../k_menu.h" +#include "../../z_zone.h" + +static menuitem_t MessageMenu[] = +{ + // TO HACK + {0, NULL, NULL, NULL, {NULL}, 0, 0} +}; + +menu_t MessageDef = +{ + 1, // # of menu items + NULL, // previous menu (TO HACK) + 0, // lastOn, flags (TO HACK) + MessageMenu, // menuitem_t -> + 0, 0, // x, y (TO HACK) + 0, 0, // extra1, extra2 + 0, 0, // transition tics + NULL, // drawing routine -> + NULL, // ticker routine + NULL, // init routine + NULL, // quit routine + NULL // input routine +}; + +// message prompt struct +struct menumessage_s menumessage; + +// +// M_StringHeight +// +// Find string height from hu_font chars +// +static inline size_t M_StringHeight(const char *string) +{ + size_t h = 8, i; + + for (i = 0; i < strlen(string); i++) + if (string[i] == '\n') + h += 8; + + return h; +} + +// default message handler +void M_StartMessage(const char *string, void *routine, menumessagetype_t itemtype) +{ + const UINT8 pid = 0; + size_t max = 0, start = 0, strlines = 0, i; + static char *message = NULL; + Z_Free(message); + message = Z_StrDup(string); + DEBFILE(message); + + // Rudementary word wrapping. + // Simple and effective. Does not handle nonuniform letter sizes, etc. but who cares. + for (i = 0; message[i]; i++) + { + if (message[i] == ' ') + { + start = i; + max += 4; + } + else if (message[i] == '\n') + { + strlines = i; + start = 0; + max = 0; + continue; + } + else if (message[i] & 0x80) + continue; + else + max += 8; + + // Start trying to wrap if presumed length exceeds the screen width. + if (max >= BASEVIDWIDTH && start > 0) + { + message[start] = '\n'; + max -= (start-strlines)*8; + strlines = start; + start = 0; + } + } + + strncpy(menumessage.message, string, MAXMENUMESSAGE); + menumessage.flags = itemtype; + *(void**)&menumessage.routine = routine; + menumessage.fadetimer = (gamestate == GS_WAITINGPLAYERS) ? 9 : 1; + menumessage.active = true; + + start = 0; + max = 0; + + if (!routine || menumessage.flags == MM_NOTHING) + { + menumessage.flags = MM_NOTHING; + menumessage.routine = M_StopMessage; + } + + // event routine + if (menumessage.flags == MM_EVENTHANDLER) + { + *(void**)&menumessage.eroutine = routine; + menumessage.routine = NULL; + } + + //added : 06-02-98: now draw a textbox around the message + // compute lenght max and the numbers of lines + for (strlines = 0; *(message+start); strlines++) + { + for (i = 0;i < strlen(message+start);i++) + { + if (*(message+start+i) == '\n') + { + if (i > max) + max = i; + start += i; + i = (size_t)-1; //added : 07-02-98 : damned! + start++; + break; + } + } + + if (i == strlen(message+start)) + { + start += i; + if (i > max) + max = i; + } + } + + menumessage.x = (INT16)((BASEVIDWIDTH - 8*max-16)/2); + menumessage.y = (INT16)((BASEVIDHEIGHT - M_StringHeight(message))/2); + + menumessage.m = (INT16)((strlines<<8)+max); + + M_SetMenuDelay(pid); // Set menu delay to avoid setting off any of the handlers. +} + +void M_StopMessage(INT32 choice) +{ + const char pid = 0; + (void) choice; + + menumessage.active = false; + M_SetMenuDelay(pid); +} + +// regular handler for MM_NOTHING and MM_YESNO +void M_HandleMenuMessage(void) +{ + const UINT8 pid = 0; + boolean btok = M_MenuConfirmPressed(pid); + boolean btnok = M_MenuBackPressed(pid); + + if (menumessage.fadetimer < 9) + menumessage.fadetimer++; + + switch (menumessage.flags) + { + // Send 1 to the routine if we're pressing A/B/X/Y + case MM_NOTHING: + { + // send 1 if any button is pressed, 0 otherwise. + if (btok || btnok) + menumessage.routine(0); + + break; + } + // Send 1 to the routine if we're pressing A/X, 2 if B/Y, 0 otherwise. + case MM_YESNO: + { + INT32 answer = MA_NONE; + if (btok) + answer = MA_YES; + else if (btnok) + answer = MA_NO; + + // send 1 if btok is pressed, 2 if nok is pressed, 0 otherwise. + if (answer) + { + menumessage.routine(answer); + M_StopMessage(0); + } + + break; + } + // MM_EVENTHANDLER: In M_Responder to allow full event compat. + default: + break; + } + + // if we detect any keypress, don't forget to set the menu delay regardless. + if (btok || btnok) + M_SetMenuDelay(pid); +} diff --git a/src/menus/transient/pause-game.c b/src/menus/transient/pause-game.c index 5a29da812..5619a739e 100644 --- a/src/menus/transient/pause-game.c +++ b/src/menus/transient/pause-game.c @@ -2,6 +2,8 @@ /// \brief In-game/pause menus #include "../../k_menu.h" +#include "../../k_grandprix.h" // K_CanChangeRules +#include "../../s_sound.h" // ESC pause menu // Since there's no descriptions to each item, we'll use the descriptions as the names of the patches we want to draw for each option :) @@ -68,3 +70,286 @@ menu_t PAUSE_MainDef = { NULL, M_PauseInputs }; + +static void Dummymenuplayer_OnChange(void); + +static CV_PossibleValue_t dummymenuplayer_cons_t[] = {{0, "NOPE"}, {1, "P1"}, {2, "P2"}, {3, "P3"}, {4, "P4"}, {0, NULL}}; + +//static consvar_t cv_dummymenuplayer = CVAR_INIT ("dummymenuplayer", "P1", CV_HIDDEN|CV_CALL, dummymenuplayer_cons_t, Dummymenuplayer_OnChange); +consvar_t cv_dummymenuplayer = CVAR_INIT ("dummymenuplayer", "P1", CV_HIDDEN|CV_CALL, dummymenuplayer_cons_t, Dummymenuplayer_OnChange); + +struct pausemenu_s pausemenu; + +static void Dummymenuplayer_OnChange(void) +{ + if (cv_dummymenuplayer.value < 1) + CV_StealthSetValue(&cv_dummymenuplayer, splitscreen+1); + else if (cv_dummymenuplayer.value > splitscreen+1) + CV_StealthSetValue(&cv_dummymenuplayer, 1); +} + +// Pause menu! +void M_OpenPauseMenu(void) +{ + INT32 i = 0; + + currentMenu = &PAUSE_MainDef; + + // Ready the variables + pausemenu.ticker = 0; + + pausemenu.offset = 0; + pausemenu.openoffset = 256; + pausemenu.closing = false; + + currentMenu->lastOn = mpause_continue; // Make sure we select "RESUME GAME" by default + + // Now the hilarious balancing act of deciding what options should be enabled and which ones shouldn't be! + // By default, disable anything sensitive: + + PAUSE_Main[mpause_addons].status = IT_DISABLED; + PAUSE_Main[mpause_changegametype].status = IT_DISABLED; + PAUSE_Main[mpause_switchmap].status = IT_DISABLED; + PAUSE_Main[mpause_restartmap].status = IT_DISABLED; + PAUSE_Main[mpause_tryagain].status = IT_DISABLED; +#ifdef HAVE_DISCORDRPC + PAUSE_Main[mpause_discordrequests].status = IT_DISABLED; +#endif + + PAUSE_Main[mpause_spectate].status = IT_DISABLED; + PAUSE_Main[mpause_entergame].status = IT_DISABLED; + PAUSE_Main[mpause_canceljoin].status = IT_DISABLED; + PAUSE_Main[mpause_spectatemenu].status = IT_DISABLED; + PAUSE_Main[mpause_psetup].status = IT_DISABLED; + + Dummymenuplayer_OnChange(); // Make sure the consvar is within bounds of the amount of splitscreen players we have. + + if (K_CanChangeRules(false)) + { + PAUSE_Main[mpause_psetup].status = IT_STRING | IT_CALL; + + if (server || IsPlayerAdmin(consoleplayer)) + { + PAUSE_Main[mpause_changegametype].status = IT_STRING | IT_KEYHANDLER; + menugametype = gametype; + + PAUSE_Main[mpause_switchmap].status = IT_STRING | IT_CALL; + PAUSE_Main[mpause_restartmap].status = IT_STRING | IT_CALL; + PAUSE_Main[mpause_addons].status = IT_STRING | IT_CALL; + } + } + else if (!netgame && !demo.playback) + { + boolean retryallowed = (modeattacking != ATTACKING_NONE); + if (G_GametypeUsesLives()) + { + for (i = 0; i <= splitscreen; i++) + { + if (players[g_localplayers[i]].lives <= 1) + continue; + retryallowed = true; + break; + } + } + + if (retryallowed) + { + PAUSE_Main[mpause_tryagain].status = IT_STRING | IT_CALL; + } + } + + if (G_GametypeHasSpectators()) + { + if (splitscreen) + PAUSE_Main[mpause_spectatemenu].status = IT_STRING|IT_SUBMENU; + else + { + if (!players[consoleplayer].spectator) + PAUSE_Main[mpause_spectate].status = IT_STRING | IT_CALL; + else if (players[consoleplayer].pflags & PF_WANTSTOJOIN) + PAUSE_Main[mpause_canceljoin].status = IT_STRING | IT_CALL; + else + PAUSE_Main[mpause_entergame].status = IT_STRING | IT_CALL; + } + } +} + +void M_QuitPauseMenu(INT32 choice) +{ + (void)choice; + // M_PauseTick actually handles the quitting when it's been long enough. + pausemenu.closing = true; + pausemenu.openoffset = 4; +} + +void M_PauseTick(void) +{ + pausemenu.offset /= 2; + pausemenu.ticker++; + + if (pausemenu.closing) + { + pausemenu.openoffset *= 2; + if (pausemenu.openoffset > 255) + M_ClearMenus(true); + + } + else + pausemenu.openoffset /= 2; +} + +boolean M_PauseInputs(INT32 ch) +{ + + const UINT8 pid = 0; + (void) ch; + + if (pausemenu.closing) + return true; // Don't allow inputs. + + if (menucmd[pid].dpad_ud < 0) + { + M_SetMenuDelay(pid); + pausemenu.offset -= 50; // Each item is spaced by 50 px + S_StartSound(NULL, sfx_s3k5b); + M_PrevOpt(); + return true; + } + + else if (menucmd[pid].dpad_ud > 0) + { + pausemenu.offset += 50; // Each item is spaced by 50 px + S_StartSound(NULL, sfx_s3k5b); + M_NextOpt(); + M_SetMenuDelay(pid); + return true; + } + + else if (M_MenuBackPressed(pid) || M_MenuButtonPressed(pid, MBT_START)) + { + M_QuitPauseMenu(-1); + M_SetMenuDelay(pid); + return true; + } + return false; +} + +// Change gametype +void M_HandlePauseMenuGametype(INT32 choice) +{ + const UINT8 pid = 0; + const UINT32 forbidden = GTR_FORBIDMP; + + (void)choice; + + if (M_MenuConfirmPressed(pid)) + { + if (menugametype != gametype) + { + M_ClearMenus(true); + COM_ImmedExecute(va("randommap -gt %s", gametypes[menugametype]->name)); + return; + } + + M_SetMenuDelay(pid); + S_StartSound(NULL, sfx_s3k7b); + } + else if (M_MenuExtraPressed(pid)) + { + menugametype = gametype; + M_SetMenuDelay(pid); + S_StartSound(NULL, sfx_s3k7b); + } + else if (menucmd[pid].dpad_lr > 0) + { + M_NextMenuGametype(forbidden); + S_StartSound(NULL, sfx_s3k5b); + M_SetMenuDelay(pid); + } + else if (menucmd[pid].dpad_lr < 0) + { + M_PrevMenuGametype(forbidden); + S_StartSound(NULL, sfx_s3k5b); + M_SetMenuDelay(pid); + } +} + +// Restart map +void M_RestartMap(INT32 choice) +{ + (void)choice; + M_ClearMenus(false); + COM_ImmedExecute("restartlevel"); +} + +// Try again +void M_TryAgain(INT32 choice) +{ + (void)choice; + if (demo.playback) + return; + + if (netgame || !Playing()) // Should never happen! + return; + + M_ClearMenus(false); + + if (modeattacking != ATTACKING_NONE) + { + G_CheckDemoStatus(); // Cancel recording + M_StartTimeAttack(-1); + } + else + { + G_SetRetryFlag(); + } +} + +// Pause spectate / join functions +void M_ConfirmSpectate(INT32 choice) +{ + (void)choice; + // We allow switching to spectator even if team changing is not allowed + M_QuitPauseMenu(-1); + COM_ImmedExecute("changeteam spectator"); +} + +void M_ConfirmEnterGame(INT32 choice) +{ + (void)choice; + if (!cv_allowteamchange.value) + { + M_StartMessage(M_GetText("The server is not allowing\nteam changes at this time.\n\nPress (B)\n"), NULL, MM_NOTHING); + return; + } + M_QuitPauseMenu(-1); + COM_ImmedExecute("changeteam playing"); +} + +static void M_ExitGameResponse(INT32 ch) +{ + if (ch != MA_YES) + return; + + if (modeattacking) + { + M_EndModeAttackRun(); + } + else + { + G_SetExitGameFlag(); + M_ClearMenus(true); + } +} + +void M_EndGame(INT32 choice) +{ + (void)choice; + if (demo.playback) + return; + + if (!Playing()) + return; + + M_StartMessage(M_GetText("Are you sure you want to\nreturn to the menu?\nPress (A) to confirm or (B) to cancel\n"), FUNCPTRCAST(M_ExitGameResponse), MM_YESNO); +} diff --git a/src/menus/transient/pause-replay.c b/src/menus/transient/pause-replay.c index 06b0dd594..e16bba8e7 100644 --- a/src/menus/transient/pause-replay.c +++ b/src/menus/transient/pause-replay.c @@ -2,6 +2,12 @@ /// \brief Replay popup menu #include "../../k_menu.h" +#include "../../s_sound.h" +#include "../../p_tick.h" // leveltime +#include "../../i_time.h" +#include "../../r_main.h" // R_ExecuteSetViewSize +#include "../../p_local.h" // P_InitCameraCmd +#include "../../d_main.h" // D_StartTitle menuitem_t PAUSE_PlaybackMenu[] = { @@ -38,3 +44,153 @@ menu_t PAUSE_PlaybackMenuDef = { NULL, NULL }; + +void M_EndModeAttackRun(void) +{ + G_CheckDemoStatus(); // Cancel recording + + if (gamestate == GS_LEVEL || gamestate == GS_INTERMISSION) + Command_ExitGame_f(); + + M_StartControlPanel(); + + currentMenu = &PLAY_TimeAttackDef; + itemOn = currentMenu->lastOn; + + G_SetGamestate(GS_MENU); + S_ChangeMusicInternal("menu", true); + + modeattacking = ATTACKING_NONE; +} + +// Replay Playback Menu +void M_SetPlaybackMenuPointer(void) +{ + itemOn = playback_pause; +} + +void M_PlaybackRewind(INT32 choice) +{ + static tic_t lastconfirmtime; + + (void)choice; + + if (!demo.rewinding) + { + if (paused) + { + G_ConfirmRewind(leveltime-1); + paused = true; + S_PauseAudio(); + } + else + demo.rewinding = paused = true; + } + else if (lastconfirmtime + TICRATE/2 < I_GetTime()) + { + lastconfirmtime = I_GetTime(); + G_ConfirmRewind(leveltime); + } + + CV_SetValue(&cv_playbackspeed, 1); +} + +void M_PlaybackPause(INT32 choice) +{ + (void)choice; + + paused = !paused; + + if (demo.rewinding) + { + G_ConfirmRewind(leveltime); + paused = true; + S_PauseAudio(); + } + else if (paused) + S_PauseAudio(); + else + S_ResumeAudio(); + + CV_SetValue(&cv_playbackspeed, 1); +} + +void M_PlaybackFastForward(INT32 choice) +{ + (void)choice; + + if (demo.rewinding) + { + G_ConfirmRewind(leveltime); + paused = false; + S_ResumeAudio(); + } + CV_SetValue(&cv_playbackspeed, cv_playbackspeed.value == 1 ? 4 : 1); +} + +void M_PlaybackAdvance(INT32 choice) +{ + (void)choice; + + paused = false; + TryRunTics(1); + paused = true; +} + +void M_PlaybackSetViews(INT32 choice) +{ + if (choice > 0) + { + if (splitscreen < 3) + G_AdjustView(splitscreen + 2, 0, true); + } + else if (splitscreen) + { + splitscreen--; + R_ExecuteSetViewSize(); + } +} + +void M_PlaybackAdjustView(INT32 choice) +{ + G_AdjustView(itemOn - playback_viewcount, (choice > 0) ? 1 : -1, true); +} + +// this one's rather tricky +void M_PlaybackToggleFreecam(INT32 choice) +{ + (void)choice; + M_ClearMenus(true); + + // remove splitscreen: + splitscreen = 0; + R_ExecuteSetViewSize(); + + P_InitCameraCmd(); // init camera controls + if (!demo.freecam) // toggle on + { + demo.freecam = true; + democam.cam = &camera[0]; // this is rather useful + } + else // toggle off + { + demo.freecam = false; + // reset democam vars: + democam.cam = NULL; + //democam.turnheld = false; + democam.keyboardlook = false; // reset only these. localangle / aiming gets set before the cam does anything anyway + } +} + +void M_PlaybackQuit(INT32 choice) +{ + (void)choice; + G_StopDemo(); + + if (demo.inreplayhut) + M_ReplayHut(choice); + else if (modeattacking) + M_EndModeAttackRun(); + else + D_StartTitle(); +} diff --git a/src/menus/transient/virtual-keyboard.c b/src/menus/transient/virtual-keyboard.c new file mode 100644 index 000000000..3211538a3 --- /dev/null +++ b/src/menus/transient/virtual-keyboard.c @@ -0,0 +1,226 @@ +/// \file menus/transient/virtual-keyboard.c +/// \brief Keyboard input + +#include "../../k_menu.h" +#include "../../s_sound.h" +#include "../../hu_stuff.h" // shiftxform + +// Typing "sub"-menu +struct menutyping_s menutyping; + +// keyboard layouts +INT16 virtualKeyboard[5][13] = { + + {'1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', 0}, + {'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', 0}, + {'a', 's', 'd', 'f', 'g', 'h', 'i', 'j', 'k', 'l', ';', '\'', '\\'}, + {'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/', 0, 0, 0}, + {KEY_SPACE, KEY_RSHIFT, KEY_BACKSPACE, KEY_CAPSLOCK, KEY_ENTER, 0, 0, 0, 0, 0, 0, 0, 0} +}; + +INT16 shift_virtualKeyboard[5][13] = { + + {'!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+', 0}, + {'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '{', '}', 0}, + {'A', 'S', 'D', 'F', 'G', 'H', 'I', 'J', 'K', 'L', ':', '\"', '|'}, + {'Z', 'X', 'C', 'V', 'B', 'N', 'M', '<', '>', '?', 0, 0, 0}, + {KEY_SPACE, KEY_RSHIFT, KEY_BACKSPACE, KEY_CAPSLOCK, KEY_ENTER, 0, 0, 0, 0, 0, 0, 0, 0} +}; + +boolean M_ChangeStringCvar(INT32 choice) +{ + consvar_t *cv = currentMenu->menuitems[itemOn].itemaction.cvar; + char buf[MAXSTRINGLENGTH]; + size_t len; + + if (shiftdown && choice >= 32 && choice <= 127) + choice = shiftxform[choice]; + + switch (choice) + { + case KEY_BACKSPACE: + len = strlen(cv->string); + if (len > 0) + { + S_StartSound(NULL, sfx_s3k5b); // Tails + M_Memcpy(buf, cv->string, len); + buf[len-1] = 0; + CV_Set(cv, buf); + } + return true; + case KEY_DEL: + if (cv->string[0]) + { + S_StartSound(NULL, sfx_s3k5b); // Tails + CV_Set(cv, ""); + } + return true; + default: + if (choice >= 32 && choice <= 127) + { + len = strlen(cv->string); + if (len < MAXSTRINGLENGTH - 1) + { + S_StartSound(NULL, sfx_s3k5b); // Tails + M_Memcpy(buf, cv->string, len); + buf[len++] = (char)choice; + buf[len] = 0; + CV_Set(cv, buf); + } + return true; + } + break; + } + + return false; +} + +// Updates the x coordinate of the keybord so prevent it from going in weird places +static void M_UpdateKeyboardX(void) +{ + // 0s are only at the rightmost edges of the keyboard table, so just go backwards until we get something. + while (!virtualKeyboard[menutyping.keyboardy][menutyping.keyboardx]) + menutyping.keyboardx--; +} + +static boolean M_IsTypingKey(INT32 key) +{ + return key == KEY_BACKSPACE || key == KEY_ENTER || + key == KEY_ESCAPE || key == KEY_DEL || isprint(key); +} + +void M_MenuTypingInput(INT32 key) +{ + + const UINT8 pid = 0; + + // Fade-in + + if (menutyping.menutypingclose) // closing + { + menutyping.menutypingfade--; + if (!menutyping.menutypingfade) + menutyping.active = false; + + return; // prevent inputs while closing the menu. + } + else // opening + { + menutyping.menutypingfade++; + if (menutyping.menutypingfade > 9) // Don't fade all the way, but have it VERY strong to be readable + menutyping.menutypingfade = 9; + else if (menutyping.menutypingfade < 9) + return; // Don't allow typing until it's fully opened. + } + + // Determine when to check for keyboard inputs or controller inputs using menuKey, which is the key passed here as argument. + if (!menutyping.keyboardtyping) // controller inputs + { + // we pressed a keyboard input that's not any of our buttons + if (M_IsTypingKey(key) && menucmd[pid].dpad_lr == 0 && menucmd[pid].dpad_ud == 0 + && !(menucmd[pid].buttons & MBT_A) + && !(menucmd[pid].buttons & MBT_B) + && !(menucmd[pid].buttons & MBT_C) + && !(menucmd[pid].buttons & MBT_X) + && !(menucmd[pid].buttons & MBT_Y) + && !(menucmd[pid].buttons & MBT_Z)) + { + menutyping.keyboardtyping = true; + } + } + else // Keyboard inputs. + { + // On the flipside, if we're pressing any keyboard input, switch to controller inputs. + if (!M_IsTypingKey(key) && ( + M_MenuButtonPressed(pid, MBT_A) + || M_MenuButtonPressed(pid, MBT_B) + || M_MenuButtonPressed(pid, MBT_C) + || M_MenuButtonPressed(pid, MBT_X) + || M_MenuButtonPressed(pid, MBT_Y) + || M_MenuButtonPressed(pid, MBT_Z) + || menucmd[pid].dpad_lr != 0 + || menucmd[pid].dpad_ud != 0 + )) + { + menutyping.keyboardtyping = false; + return; + } + + // OTHERWISE, process keyboard inputs for typing! + if (key == KEY_ENTER || key == KEY_ESCAPE) + { + menutyping.menutypingclose = true; // close menu. + return; + } + + } + + if (menucmd[pid].delay == 0 && !menutyping.keyboardtyping) // We must check for this here because we bypass the normal delay check to allow for normal keyboard inputs + { + if (menucmd[pid].dpad_ud > 0) // down + { + menutyping.keyboardy++; + if (menutyping.keyboardy > 4) + menutyping.keyboardy = 0; + + M_UpdateKeyboardX(); + M_SetMenuDelay(pid); + S_StartSound(NULL, sfx_s3k5b); + } + else if (menucmd[pid].dpad_ud < 0) // up + { + menutyping.keyboardy--; + if (menutyping.keyboardy < 0) + menutyping.keyboardy = 4; + + M_UpdateKeyboardX(); + M_SetMenuDelay(pid); + S_StartSound(NULL, sfx_s3k5b); + } + else if (menucmd[pid].dpad_lr > 0) // right + { + menutyping.keyboardx++; + if (!virtualKeyboard[menutyping.keyboardy][menutyping.keyboardx]) + menutyping.keyboardx = 0; + + M_SetMenuDelay(pid); + S_StartSound(NULL, sfx_s3k5b); + } + else if (menucmd[pid].dpad_lr < 0) // left + { + menutyping.keyboardx--; + if (menutyping.keyboardx < 0) + { + menutyping.keyboardx = 12; + M_UpdateKeyboardX(); + } + M_SetMenuDelay(pid); + S_StartSound(NULL, sfx_s3k5b); + } + else if (M_MenuConfirmPressed(pid)) + { + // Add the character. First though, check what we're pressing.... + INT16 c = virtualKeyboard[menutyping.keyboardy][menutyping.keyboardx]; + if (menutyping.keyboardshift ^ menutyping.keyboardcapslock) + c = shift_virtualKeyboard[menutyping.keyboardy][menutyping.keyboardx]; + + if (c == KEY_RSHIFT) + menutyping.keyboardshift = !menutyping.keyboardshift; + else if (c == KEY_CAPSLOCK) + menutyping.keyboardcapslock = !menutyping.keyboardcapslock; + else if (c == KEY_ENTER) + { + menutyping.menutypingclose = true; // close menu. + return; + } + else + { + M_ChangeStringCvar((INT32)c); // Write! + menutyping.keyboardshift = false; // undo shift if it had been pressed + } + + M_SetMenuDelay(pid); + S_StartSound(NULL, sfx_s3k5b); + } + } +}