diff --git a/src/Sourcefile b/src/Sourcefile index c1e29e119..986b75f74 100644 --- a/src/Sourcefile +++ b/src/Sourcefile @@ -31,7 +31,6 @@ m_cheat.c m_cond.c m_easing.c m_fixed.c -m_menu.c m_misc.c m_perfstats.c m_random.c @@ -115,7 +114,12 @@ k_botsearch.c k_grandprix.c k_boss.c k_hud.c +k_menudef.c +k_menufunc.c +k_menudraw.c k_terrain.c k_brightmap.c +k_terrain.c k_director.c k_follower.c +k_profiles.c diff --git a/src/am_map.c b/src/am_map.c index 53a7480a5..04806de94 100644 --- a/src/am_map.c +++ b/src/am_map.c @@ -462,7 +462,7 @@ boolean AM_Responder(event_t *ev) { //faB: prevent alt-tab in win32 version to activate automap just before // minimizing the app; doesn't do any harm to the DOS version - if (!gamekeydown[KEY_LALT] && !gamekeydown[KEY_RALT]) + if (!gamekeydown[0][KEY_LALT] && !gamekeydown[0][KEY_RALT]) { bigstate = 0; //added : 24-01-98 : toggle off large view AM_Start(); diff --git a/src/command.c b/src/command.c index c71fece57..1439463c1 100644 --- a/src/command.c +++ b/src/command.c @@ -21,7 +21,7 @@ #include "command.h" #include "console.h" #include "z_zone.h" -#include "m_menu.h" +#include "k_menu.h" #include "m_misc.h" #include "m_fixed.h" #include "m_argv.h" @@ -35,6 +35,7 @@ #include "lua_script.h" #include "d_netfil.h" // findfile #include "r_data.h" // Color_cons_t +#include "r_skins.h" //======== // protos. @@ -81,10 +82,23 @@ CV_PossibleValue_t kartspeed_cons_t[] = { {KARTSPEED_HARD, "Hard"}, {0, NULL} }; +CV_PossibleValue_t dummykartspeed_cons_t[] = { + {KARTSPEED_EASY, "Easy"}, + {KARTSPEED_NORMAL, "Normal"}, + {KARTSPEED_HARD, "Hard"}, + {0, NULL} +}; +CV_PossibleValue_t gpdifficulty_cons_t[] = { + {KARTSPEED_EASY, "Easy"}, + {KARTSPEED_NORMAL, "Normal"}, + {KARTSPEED_HARD, "Hard"}, + {KARTGP_MASTER, "Master"}, + {0, NULL} +}; // Filter consvars by EXECVERSION // First implementation is 2 (1.0.2), so earlier configs default at 1 (1.0.0) -// Also set CV_HIDEN during runtime, after config is loaded +// Also set CV_HIDDEN during runtime, after config is loaded static boolean execversion_enabled = false; consvar_t cv_execversion = CVAR_INIT ("execversion","1",CV_CALL,CV_Unsigned, CV_EnforceExecVersion); @@ -1255,7 +1269,7 @@ void CV_RegisterVar(consvar_t *variable) } // link the variable in - if (!(variable->flags & CV_HIDEN)) + if (!(variable->flags & CV_HIDDEN)) { variable->next = consvar_vars; consvar_vars = variable; @@ -1537,8 +1551,7 @@ finish: // landing point for possiblevalue failures badinput: - if (var != &cv_nextmap) // Suppress errors for cv_nextmap - CONS_Printf(M_GetText("\"%s\" is not a possible value for \"%s\"\n"), valstr, var->name); + CONS_Printf(M_GetText("\"%s\" is not a possible value for \"%s\"\n"), valstr, var->name); // default value not valid... ?! if (var->defaultvalue == valstr) @@ -1804,6 +1817,15 @@ static void CV_SetCVar(consvar_t *var, const char *value, boolean stealth) return; } + if (var == &cv_kartspeed && !M_SecretUnlocked(SECRET_HARDSPEED)) + { + if (!stricmp(value, "Hard") || atoi(value) >= KARTSPEED_HARD) + { + CONS_Printf(M_GetText("You haven't unlocked this yet!\n")); + return; + } + } + if (var == &cv_forceskin) { INT32 skin = R_SkinAvailable(value); @@ -1939,6 +1961,7 @@ void CV_AddValue(consvar_t *var, INT32 increment) if (var->PossibleValue) { + /* if (var == &cv_nextmap) { // Special case for the nextmap variable, used only directly from the menu @@ -1972,9 +1995,11 @@ void CV_AddValue(consvar_t *var, INT32 increment) return; } } + else + */ #define MINVAL 0 #define MAXVAL 1 - else if (var->PossibleValue[MINVAL].strvalue && !strcmp(var->PossibleValue[MINVAL].strvalue, "MIN")) + if (var->PossibleValue[MINVAL].strvalue && !strcmp(var->PossibleValue[MINVAL].strvalue, "MIN")) { #ifdef PARANOIA if (!var->PossibleValue[MAXVAL].strvalue) @@ -2079,9 +2104,16 @@ void CV_AddValue(consvar_t *var, INT32 increment) return; } } - else if (var == &cv_kartspeed) + else if (var->PossibleValue == kartspeed_cons_t + || var->PossibleValue == dummykartspeed_cons_t + || var->PossibleValue == gpdifficulty_cons_t) { - max = (M_SecretUnlocked(SECRET_HARDSPEED) ? 3 : 2); + if (!M_SecretUnlocked(SECRET_HARDSPEED)) + { + max = KARTSPEED_NORMAL+1; + if (var->PossibleValue == kartspeed_cons_t) + max++; // Accommodate KARTSPEED_AUTO + } } #ifdef PARANOIA if (currentindice == -1) @@ -2122,58 +2154,10 @@ static void CV_EnforceExecVersion(void) CV_StealthSetValue(&cv_execversion, EXECVERSION); } -static boolean CV_FilterJoyAxisVars(consvar_t *v, const char *valstr) -{ -#if 1 - // We don't have changed axis defaults yet - (void)v; - (void)valstr; -#else - UINT8 i; - - // If ALL axis settings are previous defaults, set them to the new defaults - // EXECVERSION < 26 (2.1.21) - - for (i = 0; i < 4; i++) - { - if (joyaxis_default[i]) - { - if (!stricmp(v->name, "joyaxis_fire")) - { - if (joyaxis_count[i] > 7) return false; - else if (joyaxis_count[i] == 7) return true; - - if (!stricmp(valstr, "None")) joyaxis_count[i]++; - else joyaxis_default[i] = false; - } - // reset all axis settings to defaults - if (joyaxis_count[i] == 7) - { - switch (i) - { - default: - COM_BufInsertText(va("%s \"%s\"\n", cv_turnaxis[0].name, cv_turnaxis[0].defaultvalue)); - COM_BufInsertText(va("%s \"%s\"\n", cv_moveaxis[0].name, cv_moveaxis[0].defaultvalue)); - COM_BufInsertText(va("%s \"%s\"\n", cv_brakeaxis[0].name, cv_brakeaxis[0].defaultvalue)); - COM_BufInsertText(va("%s \"%s\"\n", cv_aimaxis[0].name, cv_aimaxis[0].defaultvalue)); - COM_BufInsertText(va("%s \"%s\"\n", cv_lookaxis[0].name, cv_lookaxis[0].defaultvalue)); - COM_BufInsertText(va("%s \"%s\"\n", cv_fireaxis[0].name, cv_fireaxis[0].defaultvalue)); - COM_BufInsertText(va("%s \"%s\"\n", cv_driftaxis[0].name, cv_driftaxis[0].defaultvalue)); - break; - } - joyaxis_count[i]++; - return false; - } - } - } -#endif - - // we haven't reached our counts yet, or we're not default - return true; -} - static boolean CV_FilterVarByVersion(consvar_t *v, const char *valstr) { + (void)valstr; + // True means allow the CV change, False means block it // We only care about CV_SAVE because this filters the user's config files @@ -2191,11 +2175,6 @@ static boolean CV_FilterVarByVersion(consvar_t *v, const char *valstr) || !stricmp(v->name, "mousemove2")) return false; #endif - - // axis defaults were changed to be friendly to 360 controllers - // if ALL axis settings are defaults, then change them to new values - if (!CV_FilterJoyAxisVars(v, valstr)) - return false; } return true; diff --git a/src/command.h b/src/command.h index a9558626b..876dce67f 100644 --- a/src/command.h +++ b/src/command.h @@ -120,7 +120,7 @@ typedef enum CV_SHOWMODIF = 128, // say something when modified CV_SHOWMODIFONETIME = 256, // same but will be reset to 0 when modified, set in toggle CV_NOSHOWHELP = 512, // Don't show variable in the HELP list Tails 08-13-2002 - CV_HIDEN = 1024, // variable is not part of the cvar list so cannot be accessed by the console + CV_HIDDEN = 1024, // variable is not part of the cvar list so cannot be accessed by the console // can only be set when we have the pointer to it // used on menus CV_CHEAT = 2048, // Don't let this be used in multiplayer unless cheats are on. @@ -175,7 +175,7 @@ extern CV_PossibleValue_t CV_Natural[]; #define KARTSPEED_NORMAL 1 #define KARTSPEED_HARD 2 #define KARTGP_MASTER 3 // Not a speed setting, gives the hardest speed with maxed out bots -extern CV_PossibleValue_t kartspeed_cons_t[]; +extern CV_PossibleValue_t kartspeed_cons_t[], dummykartspeed_cons_t[], gpdifficulty_cons_t[]; extern consvar_t cv_execversion; diff --git a/src/console.c b/src/console.c index c0e5d1877..7cbcb85a9 100644 --- a/src/console.c +++ b/src/console.c @@ -31,7 +31,7 @@ #include "i_system.h" #include "i_threads.h" #include "d_main.h" -#include "m_menu.h" +#include "k_menu.h" #include "filesrch.h" #include "m_misc.h" @@ -154,28 +154,6 @@ static CV_PossibleValue_t backcolor_cons_t[] = {{0, "White"}, {1, "Black"}, { {0, NULL}}; consvar_t cons_backcolor = CVAR_INIT ("con_backcolor", "Black", CV_CALL|CV_SAVE, backcolor_cons_t, CONS_backcolor_Change); -static CV_PossibleValue_t menuhighlight_cons_t[] = -{ - {0, "Game type"}, - {V_YELLOWMAP, "Always yellow"}, - {V_PURPLEMAP, "Always purple"}, - {V_GREENMAP, "Always green"}, - {V_BLUEMAP, "Always blue"}, - {V_REDMAP, "Always red"}, - {V_GRAYMAP, "Always gray"}, - {V_ORANGEMAP, "Always orange"}, - {V_SKYMAP, "Always sky-blue"}, - {V_GOLDMAP, "Always gold"}, - {V_LAVENDERMAP, "Always lavender"}, - {V_AQUAMAP, "Always aqua-green"}, - {V_MAGENTAMAP, "Always magenta"}, - {V_PINKMAP, "Always pink"}, - {V_BROWNMAP, "Always brown"}, - {V_TANMAP, "Always tan"}, - {0, NULL} -}; -consvar_t cons_menuhighlight = CVAR_INIT ("menuhighlight", "Game type", CV_SAVE, menuhighlight_cons_t, NULL); - static void CON_Print(char *msg); // @@ -466,7 +444,6 @@ void CON_Init(void) CV_RegisterVar(&cons_height); CV_RegisterVar(&cons_backpic); CV_RegisterVar(&cons_backcolor); - CV_RegisterVar(&cons_menuhighlight); COM_AddCommand("bind", CONS_Bind_f); } else @@ -938,7 +915,7 @@ boolean CON_Responder(event_t *ev) if (modeattacking || metalrecording || marathonmode) return false; - if (ev->data1 >= KEY_MOUSE1) // See also: HUD_Responder + if (ev->data1 >= NUMKEYS) // See also: HUD_Responder { INT32 i; for (i = 0; i < num_gamecontrols; i++) diff --git a/src/console.h b/src/console.h index c7a459330..192596ab0 100644 --- a/src/console.h +++ b/src/console.h @@ -60,7 +60,7 @@ extern INT32 con_clearlines; // lines of top of screen to refresh extern boolean con_hudupdate; // hud messages have changed, need refresh extern UINT32 con_scalefactor; // console text scale factor -extern consvar_t cons_backcolor, cons_menuhighlight; +extern consvar_t cons_backcolor; extern UINT8 *yellowmap, *purplemap, *greenmap, *bluemap, *graymap, *redmap, *orangemap,\ *skymap, *goldmap, *lavendermap, *aquamap, *magentamap, *pinkmap, *brownmap, *tanmap; diff --git a/src/d_clisrv.c b/src/d_clisrv.c index 00207d1bb..352c28c01 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -27,7 +27,7 @@ #include "hu_stuff.h" #include "keys.h" #include "g_input.h" // JOY1 -#include "m_menu.h" +#include "k_menu.h" #include "console.h" #include "d_netfil.h" #include "byteptr.h" @@ -903,13 +903,13 @@ static void SV_SendServerInfo(INT32 node, tic_t servertime) netbuffer->u.serverinfo.leveltime = (tic_t)LONG(leveltime); netbuffer->u.serverinfo.numberofplayer = (UINT8)D_NumPlayers(); - netbuffer->u.serverinfo.maxplayer = (UINT8)(min((dedicated ? MAXPLAYERS-1 : MAXPLAYERS), cv_maxplayers.value)); + netbuffer->u.serverinfo.maxplayer = (UINT8)(min((dedicated ? MAXPLAYERS-1 : MAXPLAYERS), cv_maxconnections.value)); if (!node) netbuffer->u.serverinfo.refusereason = 0; else if (!cv_allownewplayer.value) netbuffer->u.serverinfo.refusereason = 1; - else if (D_NumPlayers() >= cv_maxplayers.value) + else if (D_NumPlayers() >= cv_maxconnections.value) netbuffer->u.serverinfo.refusereason = 2; else netbuffer->u.serverinfo.refusereason = 0; @@ -1073,7 +1073,7 @@ static boolean SV_SendServerConfig(INT32 node) netbuffer->u.servercfg.gametype = (UINT8)gametype; netbuffer->u.servercfg.modifiedgame = (UINT8)modifiedgame; - netbuffer->u.servercfg.maxplayer = (UINT8)(min((dedicated ? MAXPLAYERS-1 : MAXPLAYERS), cv_maxplayers.value)); + netbuffer->u.servercfg.maxplayer = (UINT8)(min((dedicated ? MAXPLAYERS-1 : MAXPLAYERS), cv_maxconnections.value)); netbuffer->u.servercfg.allownewplayer = cv_allownewplayer.value; netbuffer->u.servercfg.discordinvites = (boolean)cv_discordinvites.value; @@ -1486,7 +1486,7 @@ static void M_ConfirmConnect(event_t *ev) #ifndef NONET if (ev->type == ev_keydown) { - if (ev->data1 == ' ' || ev->data1 == 'y' || ev->data1 == KEY_ENTER || ev->data1 == gamecontrol[0][gc_accelerate][0] || ev->data1 == gamecontrol[0][gc_accelerate][1]) + if (G_PlayerInputDown(0, gc_a, 1)) { if (totalfilesrequestednum > 0) { @@ -1509,7 +1509,7 @@ static void M_ConfirmConnect(event_t *ev) M_ClearMenus(true); } - else if (ev->data1 == 'n' || ev->data1 == KEY_ESCAPE|| ev->data1 == gamecontrol[0][gc_brake][0] || ev->data1 == gamecontrol[0][gc_brake][1]) + else if (G_PlayerInputDown(0, gc_x, 1)) { cl_mode = CL_ABORTED; M_ClearMenus(true); @@ -1910,6 +1910,8 @@ static boolean CL_ServerConnectionTicker(const char *tmpsave, tic_t *oldtic, tic { I_OsPolling(); + memset(deviceResponding, false, sizeof (deviceResponding)); + if (cl_mode == CL_CONFIRMCONNECT) { D_ProcessEvents(); //needed for menu system to receive inputs @@ -1920,7 +1922,7 @@ static boolean CL_ServerConnectionTicker(const char *tmpsave, tic_t *oldtic, tic G_MapEventsToControls(&events[eventtail]); } - if ((gamekeydown[KEY_ESCAPE] || gamekeydown[KEY_JOY1+1]) || cl_mode == CL_ABORTED) + if (G_PlayerInputDown(0, gc_x, 1) || cl_mode == CL_ABORTED) { CONS_Printf(M_GetText("Network game synchronization aborted.\n")); // M_StartMessage(M_GetText("Network game synchronization aborted.\n\nPress ESC\n"), NULL, MM_NOTHING); @@ -1928,7 +1930,8 @@ static boolean CL_ServerConnectionTicker(const char *tmpsave, tic_t *oldtic, tic D_QuitNetGame(); CL_Reset(); D_StartTitle(); - memset(gamekeydown, 0, NUMKEYS); + memset(gamekeydown, 0, sizeof (gamekeydown)); + memset(deviceResponding, false, sizeof (deviceResponding)); return false; } @@ -1951,11 +1954,11 @@ static boolean CL_ServerConnectionTicker(const char *tmpsave, tic_t *oldtic, tic } CL_DrawConnectionStatus(); #ifdef HAVE_THREADS - I_lock_mutex(&m_menu_mutex); + I_lock_mutex(&k_menu_mutex); #endif M_Drawer(); //Needed for drawing messageboxes on the connection screen #ifdef HAVE_THREADS - I_unlock_mutex(m_menu_mutex); + I_unlock_mutex(k_menu_mutex); #endif I_UpdateNoVsync(); // page flip or blit buffer if (moviemode) @@ -2011,6 +2014,8 @@ static void CL_ConnectToServer(void) CONS_Printf(M_GetText("Contacting the server...\n")); } + if (cv_currprofile.value == -1) + PR_ApplyProfilePretend(cv_ttlprofilen.value, 0); if (gamestate == GS_INTERMISSION) Y_EndIntermission(); // clean up intermission graphics etc if (gamestate == GS_VOTING) @@ -2018,6 +2023,8 @@ static void CL_ConnectToServer(void) DEBFILE(va("waiting %d nodes\n", doomcom->numnodes)); G_SetGamestate(GS_WAITINGPLAYERS); + if (wipegamestate == GS_MENU) + M_ClearMenus(true); wipegamestate = GS_WAITINGPLAYERS; ClearAdminPlayers(); @@ -2033,11 +2040,14 @@ static void CL_ConnectToServer(void) if (i != -1) { char *gametypestr = serverlist[i].info.gametypename; - CONS_Printf(M_GetText("Connecting to: %s\n"), serverlist[i].info.servername); + + CON_LogMessage(va(M_GetText("Connecting to: %s\n"), serverlist[i].info.servername)); + gametypestr[sizeof serverlist[i].info.gametypename - 1] = '\0'; - CONS_Printf(M_GetText("Gametype: %s\n"), gametypestr); - CONS_Printf(M_GetText("Version: %d.%d\n"), - serverlist[i].info.version, serverlist[i].info.subversion); + CON_LogMessage(va(M_GetText("Gametype: %s\n"), gametypestr)); + + CON_LogMessage(va(M_GetText("Version: %d.%d\n"), + serverlist[i].info.version, serverlist[i].info.subversion)); } SL_ClearServerList(servernode); #endif @@ -2069,6 +2079,16 @@ static void CL_ConnectToServer(void) DEBFILE(va("Synchronisation Finished\n")); displayplayers[0] = consoleplayer; + + // At this point we've succesfully joined the server, if we joined by IP (ie: a valid joinedIP string), save it! + // @TODO: Save the proper server name, right now it doesn't seem like we can consistently retrieve it from the serverlist....? + // It works... sometimes but not always which is weird. + + if (*joinedIP && strlen(joinedIP)) // false if we have "" which is \0 + M_AddToJoinedIPs(joinedIP, netbuffer->u.serverinfo.servername); + + strcpy(joinedIP, ""); // And empty this for good measure regardless of whether or not we actually used it. + } #ifndef NONET @@ -2237,6 +2257,10 @@ static void Command_ReloadBan(void) //recheck ban.txt static void Command_connect(void) { + + // By default, clear the saved address that we'd save after succesfully joining just to be sure: + strcpy(joinedIP, ""); + if (COM_Argc() < 2 || *COM_Argv(1) == 0) { CONS_Printf(M_GetText( @@ -2252,6 +2276,33 @@ static void Command_connect(void) CONS_Printf(M_GetText("You cannot connect while in a game. End this game first.\n")); return; } + else if (cv_currprofile.value == 0) + { + CONS_Printf(M_GetText("You cannot connect while using the Guest Profile. Use a Custom Profile to play Online.\n")); + return; + } + else if (cv_currprofile.value == -1) + { + // No profile set, we're attempting to connect from the title screen. (Discord RPC / connect command) + // Automatically apply the last profiles for every potential split player. + // Make sure Player 1's Profile ISN'T the guest profile even if we do that. + + UINT8 i; + + CONS_Printf(M_GetText("No Profile set, attempting to use last used Profiles...\n")); + + for (i = 0; i < cv_splitplayers.value; i++) + { + if (cv_lastprofile[i].value || i > 0) + PR_ApplyProfile(cv_lastprofile[i].value, i); + else + { + CONS_Printf(M_GetText("Player 1's last used Profile is the Guest Profile, which cannot be used to play Online.\n")); + return; + } + } + CONS_Printf(M_GetText("Profiles have been automatically set according to the last used Profiles.\n")); + } // modified game check: no longer handled // we don't request a restart unless the filelist differs @@ -2292,6 +2343,10 @@ static void Command_connect(void) servernode = I_NetMakeNodewPort(COM_Argv(1), COM_Argv(2)); else // address only, or address:port servernode = I_NetMakeNode(COM_Argv(1)); + + // Last IPs joined: + // Keep the address we typed in memory so that we can save it if we *succesfully* join the server + strcpy(joinedIP, COM_Argv(1)); } else { @@ -2310,6 +2365,7 @@ static void Command_connect(void) SplitScreen_OnChange(); } + M_ClearMenus(true); CL_ConnectToServer(); } #endif @@ -3042,7 +3098,7 @@ consvar_t cv_joinnextround = CVAR_INIT ("joinnextround", "Off", CV_NETVAR, CV_On #endif static CV_PossibleValue_t maxplayers_cons_t[] = {{2, "MIN"}, {MAXPLAYERS, "MAX"}, {0, NULL}}; -consvar_t cv_maxplayers = CVAR_INIT ("maxplayers", "8", CV_SAVE|CV_CALL, maxplayers_cons_t, Joinable_OnChange); +consvar_t cv_maxconnections = CVAR_INIT ("maxconnections", "16", CV_SAVE|CV_CALL, maxplayers_cons_t, Joinable_OnChange); static CV_PossibleValue_t joindelay_cons_t[] = {{1, "MIN"}, {3600, "MAX"}, {0, "Off"}, {0, NULL}}; consvar_t cv_joindelay = CVAR_INIT ("joindelay", "10", CV_SAVE|CV_NETVAR, joindelay_cons_t, NULL); @@ -3078,7 +3134,7 @@ static void Joinable_OnChange(void) if (!server) return; - maxplayer = (UINT8)(min((dedicated ? MAXPLAYERS-1 : MAXPLAYERS), cv_maxplayers.value)); + maxplayer = (UINT8)(min((dedicated ? MAXPLAYERS-1 : MAXPLAYERS), cv_maxconnections.value)); WRITEUINT8(p, maxplayer); WRITEUINT8(p, cv_allownewplayer.value); @@ -3470,6 +3526,7 @@ static void Got_AddBot(UINT8 **p, INT32 playernum) players[newplayernum].splitscreenindex = 0; players[newplayernum].bot = true; players[newplayernum].botvars.difficulty = difficulty; + players[newplayernum].lives = 9; players[newplayernum].skincolor = skins[skinnum].prefcolor; sprintf(player_names[newplayernum], "%s", skins[skinnum].realname); @@ -3614,7 +3671,7 @@ boolean SV_SpawnServer(void) if (!serverrunning) { - CONS_Printf(M_GetText("Starting Server....\n")); + CON_LogMessage(M_GetText("Starting Server....\n")); serverrunning = true; SV_ResetServer(); SV_GenContext(); @@ -3670,6 +3727,7 @@ void SV_StartSinglePlayerServer(void) server = true; netgame = false; multiplayer = false; + strcpy(joinedIP, ""); // Make sure to empty this so that we don't save garbage when we start our own game. (because yes we use this for netgames too....) if ((modeattacking == ATTACKING_CAPSULES) || (bossinfo.boss == true)) { @@ -3728,7 +3786,7 @@ static void HandleConnect(SINT8 node) // Sal: Dedicated mode is INCREDIBLY hacked together. // If a server filled out, then it'd overwrite the host and turn everyone into weird husks..... // It's too much effort to legimately fix right now. Just prevent it from reaching that state. - UINT8 maxplayers = min((dedicated ? MAXPLAYERS-1 : MAXPLAYERS), cv_maxplayers.value); + UINT8 maxplayers = min((dedicated ? MAXPLAYERS-1 : MAXPLAYERS), cv_maxconnections.value); if (bannednode && bannednode[node]) SV_SendRefuse(node, M_GetText("You have been banned\nfrom the server.")); @@ -5583,11 +5641,11 @@ void NetUpdate(void) { resptime = nowtime; #ifdef HAVE_THREADS - I_lock_mutex(&m_menu_mutex); + I_lock_mutex(&k_menu_mutex); #endif M_Ticker(); #ifdef HAVE_THREADS - I_unlock_mutex(m_menu_mutex); + I_unlock_mutex(k_menu_mutex); #endif CON_Ticker(); } diff --git a/src/d_clisrv.h b/src/d_clisrv.h index 9fa7ac37d..e10e51626 100644 --- a/src/d_clisrv.h +++ b/src/d_clisrv.h @@ -445,7 +445,7 @@ extern tic_t servermaxping; extern boolean server_lagless; -extern consvar_t cv_netticbuffer, cv_allownewplayer, cv_maxplayers, cv_joindelay; +extern consvar_t cv_netticbuffer, cv_allownewplayer, cv_maxconnections, cv_joindelay; extern consvar_t cv_resynchattempts, cv_blamecfail; extern consvar_t cv_maxsend, cv_noticedownload, cv_downloadspeed; diff --git a/src/d_event.h b/src/d_event.h index c69796573..5a8c915a3 100644 --- a/src/d_event.h +++ b/src/d_event.h @@ -25,9 +25,6 @@ typedef enum ev_console, ev_mouse, ev_joystick, - ev_joystick2, - ev_joystick3, - ev_joystick4, } evtype_t; // Event structure. @@ -37,6 +34,7 @@ typedef struct INT32 data1; // keys / mouse/joystick buttons INT32 data2; // mouse/joystick x move INT32 data3; // mouse/joystick y move + INT32 device; // which player's device it belongs to } event_t; // diff --git a/src/d_main.c b/src/d_main.c index 9b8e8d7f0..d7092a901 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -44,7 +44,7 @@ #include "i_threads.h" #include "i_video.h" #include "m_argv.h" -#include "m_menu.h" +#include "k_menu.h" #include "m_misc.h" #include "p_setup.h" #include "p_saveg.h" @@ -184,7 +184,9 @@ void D_ProcessEvents(void) event_t *ev; boolean eaten; + boolean menuresponse = false; + memset(deviceResponding, false, sizeof (deviceResponding)); for (; eventtail != eventhead; eventtail = (eventtail+1) & (MAXEVENTS-1)) { ev = &events[eventtail]; @@ -205,25 +207,6 @@ void D_ProcessEvents(void) continue; } - // Menu input -#ifdef HAVE_THREADS - I_lock_mutex(&m_menu_mutex); -#endif - { - eaten = M_Responder(ev); - } -#ifdef HAVE_THREADS - I_unlock_mutex(m_menu_mutex); -#endif - - if (eaten) - continue; // menu ate the event - - // Demo input: - if (demo.playback) - if (M_DemoResponder(ev)) - continue; // demo ate the event - // console input #ifdef HAVE_THREADS I_lock_mutex(&con_mutex); @@ -241,8 +224,37 @@ void D_ProcessEvents(void) continue; // ate the event } + // Menu input + menuresponse = true; +#ifdef HAVE_THREADS + I_lock_mutex(&k_menu_mutex); +#endif + { + eaten = M_Responder(ev); + } +#ifdef HAVE_THREADS + I_unlock_mutex(k_menu_mutex); +#endif + + if (eaten) + continue; // menu ate the event + + // Demo input: + /* + if (demo.playback) + if (M_DemoResponder(ev)) + continue; // demo ate the event + */ + + G_Responder(ev); } + + // Reset menu controls when no event is processed + if (!menuresponse) + { + M_MapMenuControls(NULL); + } } // @@ -321,7 +333,7 @@ static void D_Display(void) // set for all later wipedefindex = gamestate; // wipe_xxx_toblack if (gamestate == GS_TITLESCREEN && wipegamestate != GS_INTRO) - wipedefindex = wipe_timeattack_toblack; + wipedefindex = wipe_titlescreen_toblack; if (wipetypepre < 0 || !F_WipeExists(wipetypepre)) wipetypepre = wipedefs[wipedefindex]; @@ -335,7 +347,7 @@ static void D_Display(void) F_WipeStartScreen(); F_WipeColorFill(31); F_WipeEndScreen(); - F_RunWipe(wipetypepre, gamestate != GS_TIMEATTACK, "FADEMAP0", false, false); + F_RunWipe(wipetypepre, gamestate != GS_MENU, "FADEMAP0", false, false); } if (gamestate != GS_LEVEL && rendermode != render_none) @@ -348,7 +360,7 @@ static void D_Display(void) } else //dedicated servers { - F_RunWipe(wipedefs[wipedefindex], gamestate != GS_TIMEATTACK, "FADEMAP0", false, false); + F_RunWipe(wipedefs[wipedefindex], gamestate != GS_MENU, "FADEMAP0", false, false); wipegamestate = gamestate; } @@ -388,7 +400,7 @@ static void D_Display(void) HU_Drawer(); break; - case GS_TIMEATTACK: + case GS_MENU: break; case GS_INTRO: @@ -541,9 +553,8 @@ static void D_Display(void) if (rendermode == render_soft) { VID_BlitLinearScreen(screens[0], screens[1], vid.width*vid.bpp, vid.height, vid.width*vid.bpp, vid.rowbytes); - Y_ConsiderScreenBuffer(); - usebuffer = true; } + lastdraw = false; } @@ -595,11 +606,11 @@ static void D_Display(void) vid.recalc = 0; #ifdef HAVE_THREADS - I_lock_mutex(&m_menu_mutex); + I_lock_mutex(&k_menu_mutex); #endif M_Drawer(); // menu is drawn even on top of everything #ifdef HAVE_THREADS - I_unlock_mutex(m_menu_mutex); + I_unlock_mutex(k_menu_mutex); #endif // focus lost moved to M_Drawer @@ -623,7 +634,7 @@ static void D_Display(void) { F_WipeEndScreen(); - F_RunWipe(wipedefs[wipedefindex], gamestate != GS_TIMEATTACK && gamestate != GS_TITLESCREEN, "FADEMAP0", true, false); + F_RunWipe(wipedefs[wipedefindex], gamestate != GS_MENU && gamestate != GS_TITLESCREEN, "FADEMAP0", true, false); } // reset counters so timedemo doesn't count the wipe duration @@ -990,25 +1001,13 @@ void D_StartTitle(void) G_SetGametype(GT_RACE); // SRB2kart paused = false; advancedemo = false; - F_InitMenuPresValues(); F_StartTitleScreen(); - currentMenu = &MainDef; // reset the current menu ID - // Reset the palette if (rendermode != render_none) V_SetPaletteLump("PLAYPAL"); // The title screen is obviously not a tutorial! (Unless I'm mistaken) - /* - if (tutorialmode && tutorialgcs) - { - G_CopyControls(gamecontrol[0], gamecontroldefault[0][gcs_custom], gcl_full, num_gcl_full); // using gcs_custom as temp storage - M_StartMessage("Do you want to \x82save the recommended \x82movement controls?\x80\n\nPress 'Y' or 'Enter' to confirm\nPress 'N' or any key to keep \nyour current controls", - M_TutorialSaveControlResponse, MM_YESNO); - } - */ - tutorialmode = false; } @@ -1270,6 +1269,9 @@ void D_SRB2Main(void) strcpy(savegamename, SAVEGAMENAME"%u.ssg"); strcpy(liveeventbackup, "live"SAVEGAMENAME".bkp"); // intentionally not ending with .ssg + // Init the joined IP table for quick rejoining of past games. + M_InitJoinedIPArray(); + { const char *userhome = D_Home(); //Alam: path to home @@ -1342,6 +1344,8 @@ void D_SRB2Main(void) } } + M_LoadJoinedIPs(); // load joined ips + // Create addons dir snprintf(addonsdir, sizeof addonsdir, "%s%s%s", srb2home, PATHSEP, "addons"); I_mkdir(addonsdir, 0755); @@ -1398,13 +1402,6 @@ void D_SRB2Main(void) // adapt tables to SRB2's needs, including extra slots for dehacked file support P_PatchInfoTables(); - // initiate menu metadata before SOCcing them - M_InitMenuPresTables(); - - // init title screen display params - if (M_GetUrlProtocolArg() || M_CheckParm("-connect")) - F_InitMenuPresValues(); - //---------------------------------------------------- READY TIME // we need to check for dedicated before initialization of some subsystems @@ -1415,10 +1412,6 @@ void D_SRB2Main(void) // Make backups of some SOCcable tables. P_BackupTables(); - // Setup character tables - // Have to be done here before files are loaded - M_InitCharacterTables(); - // load wad, including the main wad file CONS_Printf("W_InitMultipleFiles(): Adding IWAD and main PWADs.\n"); W_InitMultipleFiles(startupiwads, false); @@ -1565,6 +1558,9 @@ void D_SRB2Main(void) //--------------------------------------------------------- CONFIG.CFG M_FirstLoadConfig(); // WARNING : this do a "COM_BufExecute()" + // Load Profiles now that default controls have been defined + PR_LoadProfiles(); // load control profiles + M_Init(); #if (defined (__unix__) && !defined (MSDOS)) || defined (UNIXCOMMON) || defined (HAVE_SDL) @@ -1717,9 +1713,9 @@ void D_SRB2Main(void) // user settings come before "+" parameters. if (dedicated) - COM_ImmedExecute(va("exec \"%s"PATHSEP"kartserv.cfg\"\n", srb2home)); + COM_ImmedExecute(va("exec \"%s"PATHSEP"ringserv.cfg\"\n", srb2home)); else - COM_ImmedExecute(va("exec \"%s"PATHSEP"kartexec.cfg\" -noerror\n", srb2home)); + COM_ImmedExecute(va("exec \"%s"PATHSEP"ringexec.cfg\" -noerror\n", srb2home)); if (!autostart) M_PushSpecialParameters(); // push all "+" parameters at the command buffer @@ -1785,6 +1781,10 @@ void D_SRB2Main(void) CV_ClearChangedFlags(); + // Has to be done before anything else so skin, color, etc in command buffer has an affect. + // ttlprofilen used because it's roughly equivalent in functionality - a QoL aid for quickly getting from startup to action + PR_ApplyProfile(cv_ttlprofilen.value, 0); + // Do this here so if you run SRB2 with eg +timelimit 5, the time limit counts // as having been modified for the first game. M_PushSpecialParameters(); // push all "+" parameter at the command buffer @@ -1886,7 +1886,6 @@ void D_SRB2Main(void) } else if (M_CheckParm("-skipintro")) { - F_InitMenuPresValues(); F_StartTitleScreen(); } else diff --git a/src/d_main.h b/src/d_main.h index 81de0634d..f01f9227f 100644 --- a/src/d_main.h +++ b/src/d_main.h @@ -14,6 +14,7 @@ #ifndef __D_MAIN__ #define __D_MAIN__ +#include "k_profiles.h" // PR_LoadProfiles() #include "d_event.h" #include "w_wad.h" // for MAX_WADFILES diff --git a/src/d_netcmd.c b/src/d_netcmd.c index c90129081..76034f927 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -21,7 +21,7 @@ #include "g_game.h" #include "hu_stuff.h" #include "g_input.h" -#include "m_menu.h" +#include "k_menu.h" #include "r_local.h" #include "r_skins.h" #include "p_local.h" @@ -231,7 +231,7 @@ static CV_PossibleValue_t joyport_cons_t[] = {{1, "/dev/js0"}, {2, "/dev/js1"}, {4, "/dev/js3"}, {0, NULL}}; #else // accept whatever value - it is in fact the joystick device number -#define usejoystick_cons_t NULL +static CV_PossibleValue_t usejoystick_cons_t[] = {{-1, "MIN"}, {MAXGAMEPADS, "MAX"}, {0, NULL}}; #endif static CV_PossibleValue_t teamscramble_cons_t[] = {{0, "Off"}, {1, "Random"}, {2, "Points"}, {0, NULL}}; @@ -249,11 +249,11 @@ static consvar_t cv_fishcake = CVAR_INIT ("fishcake", "Off", CV_CALL|CV_NOSHOWHE #endif static consvar_t cv_dummyconsvar = CVAR_INIT ("dummyconsvar", "Off", CV_CALL|CV_NOSHOWHELP, CV_OnOff, DummyConsvar_OnChange); -consvar_t cv_restrictskinchange = CVAR_INIT ("restrictskinchange", "No", CV_NETVAR|CV_CHEAT, CV_YesNo, NULL); +consvar_t cv_restrictskinchange = CVAR_INIT ("restrictskinchange", "Yes", CV_NETVAR|CV_CHEAT, CV_YesNo, NULL); consvar_t cv_allowteamchange = CVAR_INIT ("allowteamchange", "Yes", CV_NETVAR, CV_YesNo, NULL); -static CV_PossibleValue_t ingamecap_cons_t[] = {{0, "MIN"}, {MAXPLAYERS-1, "MAX"}, {0, NULL}}; -consvar_t cv_ingamecap = CVAR_INIT ("ingamecap", "0", CV_NETVAR, ingamecap_cons_t, NULL); +static CV_PossibleValue_t maxplayers_cons_t[] = {{1, "MIN"}, {MAXPLAYERS, "MAX"}, {0, NULL}}; +consvar_t cv_maxplayers = CVAR_INIT ("maxplayers", "8", CV_NETVAR, maxplayers_cons_t, NULL); consvar_t cv_startinglives = CVAR_INIT ("startinglives", "3", CV_NETVAR|CV_CHEAT|CV_NOSHOWHELP, startingliveslimit_cons_t, NULL); @@ -301,6 +301,28 @@ consvar_t cv_followercolor[MAXSPLITSCREENPLAYERS] = { CVAR_INIT ("followercolor4", "1", CV_SAVE|CV_CALL|CV_NOINIT, Followercolor_cons_t, Followercolor4_OnChange) }; +// last selected profile, unaccessible cvar only set internally but is saved. +// It's used to know what profile to autoload you to when you get into the character setup. + +static CV_PossibleValue_t lastprofile_cons_t[] = {{-1, "MIN"}, {MAXPROFILES, "MAX"}, {0, NULL}}; + +consvar_t cv_lastprofile[MAXSPLITSCREENPLAYERS] = { + CVAR_INIT ("lastprofile", "0", CV_SAVE|CV_HIDDEN, lastprofile_cons_t, NULL), + CVAR_INIT ("lastprofile2", "0", CV_SAVE|CV_HIDDEN, lastprofile_cons_t, NULL), + CVAR_INIT ("lastprofile3", "0", CV_SAVE|CV_HIDDEN, lastprofile_cons_t, NULL), + CVAR_INIT ("lastprofile4", "0", CV_SAVE|CV_HIDDEN, lastprofile_cons_t, NULL), +}; + +// currently loaded profile for P1 menuing. +// You choose this profile when starting the game, this will also set lastprofile[0] +consvar_t cv_currprofile = CVAR_INIT ("currprofile", "-1", CV_HIDDEN, lastprofile_cons_t, NULL); + +// This one is used exclusively for the titlescreen +consvar_t cv_ttlprofilen = CVAR_INIT ("ttlprofilen", "0", CV_SAVE, lastprofile_cons_t, NULL); + +// Cvar for using splitscreen with 1 device. +consvar_t cv_splitdevice = CVAR_INIT ("splitdevice", "Off", CV_SAVE, CV_OnOff, NULL); + consvar_t cv_skipmapcheck = CVAR_INIT ("skipmapcheck", "Off", CV_SAVE, CV_OnOff, NULL); INT32 cv_debug; @@ -308,10 +330,10 @@ INT32 cv_debug; consvar_t cv_usemouse = CVAR_INIT ("use_mouse", "Off", CV_SAVE|CV_CALL,usemouse_cons_t, I_StartupMouse); consvar_t cv_usejoystick[MAXSPLITSCREENPLAYERS] = { - CVAR_INIT ("use_gamepad", "1", CV_SAVE|CV_CALL, usejoystick_cons_t, I_InitJoystick1), - CVAR_INIT ("use_gamepad2", "2", CV_SAVE|CV_CALL, usejoystick_cons_t, I_InitJoystick2), - CVAR_INIT ("use_joystick3", "3", CV_SAVE|CV_CALL, usejoystick_cons_t, I_InitJoystick3), - CVAR_INIT ("use_joystick4", "4", CV_SAVE|CV_CALL, usejoystick_cons_t, I_InitJoystick4) + CVAR_INIT ("use_device", "1", CV_SAVE|CV_CALL, usejoystick_cons_t, I_InitJoystick1), + CVAR_INIT ("use_device2", "2", CV_SAVE|CV_CALL, usejoystick_cons_t, I_InitJoystick2), + CVAR_INIT ("use_device3", "3", CV_SAVE|CV_CALL, usejoystick_cons_t, I_InitJoystick3), + CVAR_INIT ("use_device4", "4", CV_SAVE|CV_CALL, usejoystick_cons_t, I_InitJoystick4) }; #if (defined (LJOYSTICK) || defined (HAVE_SDL)) @@ -590,23 +612,17 @@ const char *netxcmdnames[MAXNETXCMD - 1] = void D_RegisterServerCommands(void) { INT32 i; + Forceskin_cons_t[0].value = -1; Forceskin_cons_t[0].strvalue = "Off"; - for (i = 0; i < NUMGAMETYPES; i++) - { - gametype_cons_t[i].value = i; - gametype_cons_t[i].strvalue = Gametype_Names[i]; - } - gametype_cons_t[NUMGAMETYPES].value = 0; - gametype_cons_t[NUMGAMETYPES].strvalue = NULL; - // Set the values to 0/NULL, it will be overwritten later when a skin is assigned to the slot. for (i = 1; i < MAXSKINS; i++) { Forceskin_cons_t[i].value = 0; Forceskin_cons_t[i].strvalue = NULL; } + RegisterNetXCmd(XD_NAMEANDCOLOR, Got_NameAndColor); RegisterNetXCmd(XD_WEAPONPREF, Got_WeaponPref); RegisterNetXCmd(XD_POWERLEVEL, Got_PowerLevel); @@ -721,11 +737,11 @@ void D_RegisterServerCommands(void) CV_RegisterVar(&cv_allowexitlevel); CV_RegisterVar(&cv_restrictskinchange); CV_RegisterVar(&cv_allowteamchange); - CV_RegisterVar(&cv_ingamecap); + CV_RegisterVar(&cv_maxplayers); CV_RegisterVar(&cv_respawntime); // d_clisrv - CV_RegisterVar(&cv_maxplayers); + CV_RegisterVar(&cv_maxconnections); CV_RegisterVar(&cv_joindelay); CV_RegisterVar(&cv_resynchattempts); CV_RegisterVar(&cv_maxsend); @@ -876,8 +892,13 @@ void D_RegisterClientCommands(void) CV_RegisterVar(&cv_skin[i]); CV_RegisterVar(&cv_follower[i]); CV_RegisterVar(&cv_followercolor[i]); + CV_RegisterVar(&cv_lastprofile[i]); } + CV_RegisterVar(&cv_currprofile); + CV_RegisterVar(&cv_ttlprofilen); + CV_RegisterVar(&cv_splitdevice); + // preferred number of players CV_RegisterVar(&cv_splitplayers); @@ -931,7 +952,7 @@ void D_RegisterClientCommands(void) CV_RegisterVar(&cv_bsaturation); CV_RegisterVar(&cv_msaturation); - // m_menu.c + // k_menu.c //CV_RegisterVar(&cv_compactscoreboard); CV_RegisterVar(&cv_chatheight); CV_RegisterVar(&cv_chatwidth); @@ -950,15 +971,7 @@ void D_RegisterClientCommands(void) { CV_RegisterVar(&cv_kickstartaccel[i]); CV_RegisterVar(&cv_shrinkme[i]); - CV_RegisterVar(&cv_turnaxis[i]); - CV_RegisterVar(&cv_moveaxis[i]); - CV_RegisterVar(&cv_brakeaxis[i]); - CV_RegisterVar(&cv_aimaxis[i]); - CV_RegisterVar(&cv_lookaxis[i]); - CV_RegisterVar(&cv_fireaxis[i]); - CV_RegisterVar(&cv_driftaxis[i]); CV_RegisterVar(&cv_deadzone[i]); - CV_RegisterVar(&cv_digitaldeadzone[i]); } // filesrch.c @@ -1310,6 +1323,8 @@ UINT8 CanChangeSkin(INT32 playernum) // Server has skin change restrictions. if (cv_restrictskinchange.value) { + UINT8 i; + // Can change skin during initial countdown. if (leveltime < starttime) return true; @@ -1318,8 +1333,22 @@ UINT8 CanChangeSkin(INT32 playernum) if (players[playernum].spectator || players[playernum].playerstate == PST_DEAD || players[playernum].playerstate == PST_REBORN) return true; - return false; + // Check for freeeplay + for (i = 0; i < MAXPLAYERS; i++) + { + if (i == consoleplayer) + continue; + if (playeringame[i] && !players[i].spectator && gamestate == GS_LEVEL) + return false; // Not freeplay! + } + + // if we've gotten here, then it's freeplay, and switching anytime is fair game. + return true; } + // if restrictskinchange is off and we're trying to change skins, don't allow changing skins while moving after the race has started. + else if (gamestate == GS_LEVEL && leveltime >= starttime) + return (!P_PlayerMoving(playernum)); + return true; } @@ -1864,21 +1893,23 @@ static void Got_LeaveParty(UINT8 **cp,INT32 playernum) void D_SendPlayerConfig(UINT8 n) { + const profile_t *pr = PR_GetProfile(cv_lastprofile[n].value); + UINT8 buf[4]; UINT8 *p = buf; SendNameAndColor(n); SendWeaponPref(n); - if (n == 0) + if (pr != NULL) { // Send it over - WRITEUINT16(p, vspowerlevel[PWRLV_RACE]); - WRITEUINT16(p, vspowerlevel[PWRLV_BATTLE]); + WRITEUINT16(p, pr->powerlevels[PWRLV_RACE]); + WRITEUINT16(p, pr->powerlevels[PWRLV_BATTLE]); } else { - // Splitscreen players have invalid powerlevel + // Guest players have no power level WRITEUINT16(p, 0); WRITEUINT16(p, 0); } @@ -2372,10 +2403,12 @@ void D_MapChange(INT32 mapnum, INT32 newgametype, boolean pencoremode, boolean r // The supplied data are assumed to be good. I_Assert(delay >= 0 && delay <= 2); + /* if (mapnum != -1) { CV_SetValue(&cv_nextmap, mapnum); } + */ CONS_Debug(DBG_GAMELOGIC, "Map change: mapnum=%d gametype=%d pencoremode=%d resetplayers=%d delay=%d skipprecutscene=%d\n", mapnum, newgametype, pencoremode, resetplayers, delay, skipprecutscene); @@ -2838,10 +2871,6 @@ static void Command_Map_f(void) return; } - if (tutorialmode && tutorialgcs) - { - G_CopyControls(gamecontrol[0], gamecontroldefault[0][gcs_custom], gcl_full, num_gcl_full); // using gcs_custom as temp storage - } tutorialmode = false; // warping takes us out of tutorial mode D_MapChange(newmapnum, newgametype, newencoremode, newresetplayers, 0, false, fromlevelselect); @@ -2964,7 +2993,7 @@ static void Command_Pause(void) } else if (modeattacking) // in time attack, pausing restarts the map { - M_ModeAttackRetry(0); // directly call from m_menu; + //M_ModeAttackRetry(0); // directly call from m_menu; return; } @@ -4550,6 +4579,7 @@ void D_GameTypeChanged(INT32 lastgametype) if (oldgt && newgt) CONS_Printf(M_GetText("Gametype was changed from %s to %s\n"), oldgt, newgt); } + // Only do the following as the server, not as remote admin. // There will always be a server, and this only needs to be done once. if (server && (multiplayer || netgame)) @@ -5022,7 +5052,7 @@ void Command_Retry_f(void) } else if (grandprixinfo.gp == false && bossinfo.boss == false) { - CONS_Printf(M_GetText("This only works in Grand Prix or Mission Mode.\n")); + CONS_Printf(M_GetText("This only works in singleplayer games.\n")); } else { @@ -5482,7 +5512,7 @@ static void Followercolor4_OnChange(void) } } -/** Sends a skin change for the console player, unless that player is moving. +/** Sends a skin change for the console player, unless that player is moving. Also forces them to spectate if the change is done during gameplay * \sa cv_skin, Skin2_OnChange, Color_OnChange * \author Graue */ @@ -5498,7 +5528,7 @@ static void Skin_OnChange(void) return; } - if (CanChangeSkin(consoleplayer) && !P_PlayerMoving(consoleplayer)) + if (CanChangeSkin(consoleplayer)) SendNameAndColor(0); else { @@ -5508,7 +5538,7 @@ static void Skin_OnChange(void) } /** Sends a skin change for the secondary splitscreen player, unless that - * player is moving. + * player is moving. Forces spectate the player if the change is done during gameplay. * \sa cv_skin2, Skin_OnChange, Color2_OnChange * \author Graue */ @@ -5517,7 +5547,7 @@ static void Skin2_OnChange(void) if (!Playing() || !splitscreen) return; // do whatever you want - if (CanChangeSkin(g_localplayers[1]) && !P_PlayerMoving(g_localplayers[1])) + if (CanChangeSkin(g_localplayers[1])) SendNameAndColor(1); else { @@ -5531,7 +5561,7 @@ static void Skin3_OnChange(void) if (!Playing() || splitscreen < 2) return; // do whatever you want - if (CanChangeSkin(g_localplayers[2]) && !P_PlayerMoving(g_localplayers[2])) + if (CanChangeSkin(g_localplayers[2])) SendNameAndColor(2); else { @@ -5545,7 +5575,7 @@ static void Skin4_OnChange(void) if (!Playing() || splitscreen < 3) return; // do whatever you want - if (CanChangeSkin(g_localplayers[3]) && !P_PlayerMoving(g_localplayers[3])) + if (CanChangeSkin(g_localplayers[3])) SendNameAndColor(3); else { @@ -5776,13 +5806,6 @@ static void KartFrantic_OnChange(void) static void KartSpeed_OnChange(void) { - if (!M_SecretUnlocked(SECRET_HARDSPEED) && cv_kartspeed.value == KARTSPEED_HARD) - { - CONS_Printf(M_GetText("You haven't earned this yet.\n")); - CV_StealthSet(&cv_kartspeed, cv_kartspeed.defaultvalue); - return; - } - if (K_CanChangeRules() == false) { return; diff --git a/src/d_netcmd.h b/src/d_netcmd.h index e84342795..aa4f1a80e 100644 --- a/src/d_netcmd.h +++ b/src/d_netcmd.h @@ -23,6 +23,19 @@ extern consvar_t cv_playercolor[MAXSPLITSCREENPLAYERS]; extern consvar_t cv_skin[MAXSPLITSCREENPLAYERS]; extern consvar_t cv_follower[MAXSPLITSCREENPLAYERS]; extern consvar_t cv_followercolor[MAXSPLITSCREENPLAYERS]; +extern consvar_t cv_lastprofile[MAXSPLITSCREENPLAYERS]; + +// current profile loaded. +// Used to know how to make the options menu behave among other things. +extern consvar_t cv_currprofile; + +// This is used to save the last profile you used on the title screen. +// that way you can mash n all... +extern consvar_t cv_ttlprofilen; + +// CVar that allows starting as many splitscreens as you want with one device +// Intended for use with testing +extern consvar_t cv_splitdevice; // preferred number of players extern consvar_t cv_splitplayers; @@ -56,7 +69,7 @@ extern consvar_t cv_runscripts; extern consvar_t cv_mute; extern consvar_t cv_pause; -extern consvar_t cv_restrictskinchange, cv_allowteamchange, cv_ingamecap, cv_respawntime; +extern consvar_t cv_restrictskinchange, cv_allowteamchange, cv_maxplayers, cv_respawntime; // SRB2kart items extern consvar_t cv_superring, cv_sneaker, cv_rocketsneaker, cv_invincibility, cv_banana; @@ -85,7 +98,7 @@ extern consvar_t cv_kartusepwrlv; extern consvar_t cv_votetime; -extern consvar_t cv_kartdebugitem, cv_kartdebugamount, cv_kartallowgiveitem, cv_kartdebugdistribution, cv_kartdebughuddrop; +extern consvar_t cv_kartdebugitem, cv_kartdebugamount, cv_kartallowgiveitem, cv_kartdebugshrink, cv_kartdebugdistribution, cv_kartdebughuddrop; extern consvar_t cv_kartdebugcheckpoint, cv_kartdebugnodes, cv_kartdebugcolorize, cv_kartdebugdirector; extern consvar_t cv_kartdebugwaypoints, cv_kartdebugbotpredict; diff --git a/src/d_netfil.c b/src/d_netfil.c index 84ead407a..ca2ee3886 100644 --- a/src/d_netfil.c +++ b/src/d_netfil.c @@ -54,7 +54,7 @@ #include "byteptr.h" #include "p_setup.h" #include "m_misc.h" -#include "m_menu.h" +#include "k_menu.h" #include "md5.h" #include "filesrch.h" diff --git a/src/d_player.h b/src/d_player.h index 85eec2e3e..91989279c 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -443,6 +443,8 @@ typedef struct player_s fixed_t spindashspeed; // Spindash release speed UINT8 spindashboost; // Spindash release boost timer + fixed_t fastfall; // Fast fall momentum + UINT8 numboosts; // Count of how many boosts are being stacked, for after image spawning fixed_t boostpower; // Base boost value, for offroad fixed_t speedboost; // Boost value smoothing for max speed @@ -504,8 +506,8 @@ typedef struct player_s SINT8 lastjawztarget; // (-1 to 15) - Last person you target with jawz, for playing the target switch sfx UINT8 jawztargetdelay; // (0 to 5) - Delay for Jawz target switching, to make it less twitchy - UINT8 confirmInflictor; // Player ID that dealt damage to you - UINT8 confirmInflictorDelay; // Delay before playing the sound + UINT8 confirmVictim; // Player ID that you dealt damage to + UINT8 confirmVictimDelay; // Delay before playing the sound UINT8 trickpanel; // Trick panel state UINT8 tricktime; // Increases while you're tricking. You can't input any trick until it's reached a certain threshold diff --git a/src/d_ticcmd.h b/src/d_ticcmd.h index 4994cc206..4cf1e7c97 100644 --- a/src/d_ticcmd.h +++ b/src/d_ticcmd.h @@ -33,13 +33,14 @@ typedef enum BT_LOOKBACK = 1<<5, // Look Backward BT_EBRAKEMASK = (BT_ACCELERATE|BT_BRAKE), + BT_SPINDASHMASK = (BT_ACCELERATE|BT_BRAKE|BT_DRIFT), // free: 1<<6 to 1<<12 // Lua garbage - BT_CUSTOM1 = 1<<13, - BT_CUSTOM2 = 1<<14, - BT_CUSTOM3 = 1<<15, + BT_LUAA = 1<<13, + BT_LUAB = 1<<14, + BT_LUAC = 1<<15, } buttoncode_t; // The data sampled per tick (single player) diff --git a/src/deh_lua.c b/src/deh_lua.c index f4c3bcf41..2084872e8 100644 --- a/src/deh_lua.c +++ b/src/deh_lua.c @@ -13,7 +13,7 @@ #include "g_game.h" #include "s_sound.h" #include "z_zone.h" -#include "m_menu.h" +#include "k_menu.h" #include "m_misc.h" #include "p_local.h" #include "st_stuff.h" @@ -486,16 +486,6 @@ static inline int lib_getenum(lua_State *L) if (mathlib) return luaL_error(L, "NiGHTS grade '%s' could not be found.\n", word); return 0; } - else if (fastncmp("MN_",word,3)) { - p = word+3; - for (i = 0; i < NUMMENUTYPES; i++) - if (fastcmp(p, MENUTYPES_LIST[i])) { - lua_pushinteger(L, i); - return 1; - } - if (mathlib) return luaL_error(L, "menutype '%s' could not be found.\n", word); - return 0; - } else if (fastncmp("PRECIP_",word,7)) { p = word+7; for (i = 0; i < MAXPRECIP; i++) diff --git a/src/deh_soc.c b/src/deh_soc.c index 8617ad3bb..dd0fbf5eb 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -20,7 +20,7 @@ #include "z_zone.h" #include "w_wad.h" #include "y_inter.h" -#include "m_menu.h" +#include "k_menu.h" #include "m_misc.h" #include "f_finale.h" #include "st_stuff.h" @@ -164,200 +164,6 @@ void clear_levels(void) P_AllocMapHeader(gamemap-1); } -static boolean findFreeSlot(INT32 *num) -{ - // Send the character select entry to a free slot. - while (*num < MAXSKINS && (description[*num].used)) - *num = *num+1; - - // No more free slots. :( - if (*num >= MAXSKINS) - return false; - - // Redesign your logo. (See M_DrawSetupChoosePlayerMenu in m_menu.c...) - description[*num].picname[0] = '\0'; - description[*num].nametag[0] = '\0'; - description[*num].displayname[0] = '\0'; - description[*num].oppositecolor = SKINCOLOR_NONE; - description[*num].tagtextcolor = SKINCOLOR_NONE; - description[*num].tagoutlinecolor = SKINCOLOR_NONE; - - // Found one! ^_^ - return (description[*num].used = true); -} - -// Reads a player. -// For modifying the character select screen -void readPlayer(MYFILE *f, INT32 num) -{ - char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL); - char *word; - char *word2; - char *displayname = ZZ_Alloc(MAXLINELEN+1); - INT32 i; - boolean slotfound = false; - - #define SLOTFOUND \ - if (!slotfound && (slotfound = findFreeSlot(&num)) == false) \ - goto done; - - displayname[MAXLINELEN] = '\0'; - - do - { - if (myfgets(s, MAXLINELEN, f)) - { - if (s[0] == '\n') - break; - - for (i = 0; i < MAXLINELEN-3; i++) - { - char *tmp; - if (s[i] == '=') - { - tmp = &s[i+2]; - strncpy(displayname, tmp, SKINNAMESIZE); - break; - } - } - - word = strtok(s, " "); - if (word) - strupr(word); - else - break; - - if (fastcmp(word, "PLAYERTEXT")) - { - char *playertext = NULL; - - SLOTFOUND - - for (i = 0; i < MAXLINELEN-3; i++) - { - if (s[i] == '=') - { - playertext = &s[i+2]; - break; - } - } - if (playertext) - { - strcpy(description[num].notes, playertext); - strcat(description[num].notes, myhashfgets(playertext, sizeof (description[num].notes), f)); - } - else - strcpy(description[num].notes, ""); - - // For some reason, cutting the string did not work above. Most likely due to strcpy or strcat... - // It works down here, though. - { - INT32 numline = 0; - for (i = 0; (size_t)i < sizeof(description[num].notes)-1; i++) - { - if (numline < 20 && description[num].notes[i] == '\n') - numline++; - - if (numline >= 20 || description[num].notes[i] == '\0' || description[num].notes[i] == '#') - break; - } - } - description[num].notes[strlen(description[num].notes)-1] = '\0'; - description[num].notes[i] = '\0'; - continue; - } - - word2 = strtok(NULL, " = "); - if (word2) - strupr(word2); - else - break; - - if (word2[strlen(word2)-1] == '\n') - word2[strlen(word2)-1] = '\0'; - i = atoi(word2); - - if (fastcmp(word, "PICNAME")) - { - SLOTFOUND - strncpy(description[num].picname, word2, 8); - } - // new character select - else if (fastcmp(word, "DISPLAYNAME")) - { - SLOTFOUND - // replace '#' with line breaks - // (also remove any '\n') - { - char *cur = NULL; - - // remove '\n' - cur = strchr(displayname, '\n'); - if (cur) - *cur = '\0'; - - // turn '#' into '\n' - cur = strchr(displayname, '#'); - while (cur) - { - *cur = '\n'; - cur = strchr(cur, '#'); - } - } - // copy final string - strncpy(description[num].displayname, displayname, SKINNAMESIZE); - } - else if (fastcmp(word, "OPPOSITECOLOR") || fastcmp(word, "OPPOSITECOLOUR")) - { - SLOTFOUND - description[num].oppositecolor = (UINT16)get_number(word2); - } - else if (fastcmp(word, "NAMETAG") || fastcmp(word, "TAGNAME")) - { - SLOTFOUND - strncpy(description[num].nametag, word2, 8); - } - else if (fastcmp(word, "TAGTEXTCOLOR") || fastcmp(word, "TAGTEXTCOLOUR")) - { - SLOTFOUND - description[num].tagtextcolor = (UINT16)get_number(word2); - } - else if (fastcmp(word, "TAGOUTLINECOLOR") || fastcmp(word, "TAGOUTLINECOLOUR")) - { - SLOTFOUND - description[num].tagoutlinecolor = (UINT16)get_number(word2); - } - else if (fastcmp(word, "STATUS")) - { - /* - You MAY disable previous entries if you so desire... - But try to enable something that's already enabled and you will be sent to a free slot. - - Because of this, you are allowed to edit any previous entries you like, but only if you - signal that you are purposely doing so by disabling and then reenabling the slot. - */ - if (i && !slotfound && (slotfound = findFreeSlot(&num)) == false) - goto done; - - description[num].used = (!!i); - } - else if (fastcmp(word, "SKINNAME")) - { - // Send to free slot. - SLOTFOUND - strlcpy(description[num].skinname, word2, sizeof description[num].skinname); - strlwr(description[num].skinname); - } - else - deh_warning("readPlayer %d: unknown word '%s'", num, word); - } - } while (!myfeof(f)); // finish when the line is empty - #undef SLOTFOUND -done: - Z_Free(displayname); - Z_Free(s); -} - // TODO: Figure out how to do undolines for this.... // TODO: Warnings for running out of freeslots void readfreeslots(MYFILE *f) @@ -1113,7 +919,6 @@ void readsprite2(MYFILE *f, INT32 num) Z_Free(s); } -// copypasted from readPlayer :] void readgametype(MYFILE *f, char *gtname) { char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL); @@ -2258,194 +2063,6 @@ void readtextprompt(MYFILE *f, INT32 num) Z_Free(s); } -void readmenu(MYFILE *f, INT32 num) -{ - char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL); - char *word = s; - char *word2; - char *tmp; - INT32 value; - - do - { - if (myfgets(s, MAXLINELEN, f)) - { - if (s[0] == '\n') - break; - - // First remove trailing newline, if there is one - tmp = strchr(s, '\n'); - if (tmp) - *tmp = '\0'; - - tmp = strchr(s, '#'); - if (tmp) - *tmp = '\0'; - if (s == tmp) - continue; // Skip comment lines, but don't break. - - // Get the part before the " = " - tmp = strchr(s, '='); - if (tmp) - *(tmp-1) = '\0'; - else - break; - strupr(word); - - // Now get the part after - word2 = (tmp += 2); - strupr(word2); - - value = atoi(word2); // used for numerical settings - - if (fastcmp(word, "BACKGROUNDNAME")) - { - strncpy(menupres[num].bgname, word2, 8); - titlechanged = true; - } - else if (fastcmp(word, "HIDEBACKGROUND")) - { - menupres[num].bghide = (boolean)(value || word2[0] == 'T' || word2[0] == 'Y'); - titlechanged = true; - } - else if (fastcmp(word, "BACKGROUNDCOLOR")) - { - menupres[num].bgcolor = get_number(word2); - titlechanged = true; - } - else if (fastcmp(word, "HIDETITLEPICS") || fastcmp(word, "HIDEPICS") || fastcmp(word, "TITLEPICSHIDE")) - { - // true by default, except MM_MAIN - menupres[num].hidetitlepics = (boolean)(value || word2[0] == 'T' || word2[0] == 'Y'); - titlechanged = true; - } - else if (fastcmp(word, "TITLEPICSMODE")) - { - if (fastcmp(word2, "USER")) - menupres[num].ttmode = TTMODE_USER; - else if (fastcmp(word2, "HIDE") || fastcmp(word2, "HIDDEN") || fastcmp(word2, "NONE")) - { - menupres[num].ttmode = TTMODE_USER; - menupres[num].ttname[0] = 0; - menupres[num].hidetitlepics = true; - } - else if (fastcmp(word2, "RINGRACERS")) - menupres[num].ttmode = TTMODE_RINGRACERS; - else if (fastcmp(word2, "OLD")) - menupres[num].ttmode = TTMODE_OLD; - titlechanged = true; - } - else if (fastcmp(word, "TITLEPICSSCALE")) - { - // Don't handle Alacroix special case here; see Maincfg section. - menupres[num].ttscale = max(1, min(8, (UINT8)get_number(word2))); - titlechanged = true; - } - else if (fastcmp(word, "TITLEPICSNAME")) - { - strncpy(menupres[num].ttname, word2, 9); - titlechanged = true; - } - else if (fastcmp(word, "TITLEPICSX")) - { - menupres[num].ttx = (INT16)get_number(word2); - titlechanged = true; - } - else if (fastcmp(word, "TITLEPICSY")) - { - menupres[num].tty = (INT16)get_number(word2); - titlechanged = true; - } - else if (fastcmp(word, "TITLEPICSLOOP")) - { - menupres[num].ttloop = (INT16)get_number(word2); - titlechanged = true; - } - else if (fastcmp(word, "TITLEPICSTICS")) - { - menupres[num].tttics = (UINT16)get_number(word2); - titlechanged = true; - } - else if (fastcmp(word, "TITLESCROLLSPEED") || fastcmp(word, "TITLESCROLLXSPEED") - || fastcmp(word, "SCROLLSPEED") || fastcmp(word, "SCROLLXSPEED")) - { - menupres[num].titlescrollxspeed = get_number(word2); - titlechanged = true; - } - else if (fastcmp(word, "TITLESCROLLYSPEED") || fastcmp(word, "SCROLLYSPEED")) - { - menupres[num].titlescrollyspeed = get_number(word2); - titlechanged = true; - } - else if (fastcmp(word, "MUSIC")) - { - strncpy(menupres[num].musname, word2, 7); - menupres[num].musname[6] = 0; - titlechanged = true; - } - else if (fastcmp(word, "MUSICTRACK")) - { - menupres[num].mustrack = ((UINT16)value - 1); - titlechanged = true; - } - else if (fastcmp(word, "MUSICLOOP")) - { - // true by default except MM_MAIN - menupres[num].muslooping = (value || word2[0] == 'T' || word2[0] == 'Y'); - titlechanged = true; - } - else if (fastcmp(word, "NOMUSIC")) - { - menupres[num].musstop = (value || word2[0] == 'T' || word2[0] == 'Y'); - titlechanged = true; - } - else if (fastcmp(word, "IGNOREMUSIC")) - { - menupres[num].musignore = (value || word2[0] == 'T' || word2[0] == 'Y'); - titlechanged = true; - } - else if (fastcmp(word, "FADESTRENGTH")) - { - // one-based, <= 0 means use default value. 1-32 - menupres[num].fadestrength = get_number(word2)-1; - titlechanged = true; - } - else if (fastcmp(word, "NOENTERBUBBLE")) - { - menupres[num].enterbubble = !(value || word2[0] == 'T' || word2[0] == 'Y'); - titlechanged = true; - } - else if (fastcmp(word, "NOEXITBUBBLE")) - { - menupres[num].exitbubble = !(value || word2[0] == 'T' || word2[0] == 'Y'); - titlechanged = true; - } - else if (fastcmp(word, "ENTERTAG")) - { - menupres[num].entertag = get_number(word2); - titlechanged = true; - } - else if (fastcmp(word, "EXITTAG")) - { - menupres[num].exittag = get_number(word2); - titlechanged = true; - } - else if (fastcmp(word, "ENTERWIPE")) - { - menupres[num].enterwipe = get_number(word2); - titlechanged = true; - } - else if (fastcmp(word, "EXITWIPE")) - { - menupres[num].exitwipe = get_number(word2); - titlechanged = true; - } - } - } while (!myfeof(f)); // finish when the line is empty - - Z_Free(s); -} - void readframe(MYFILE *f, INT32 num) { char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL); @@ -3658,13 +3275,13 @@ void readwipes(MYFILE *f) else if (fastcmp(pword, "FINAL")) wipeoffset = wipe_titlescreen_final; } - else if (fastncmp(word, "TIMEATTACK_", 11)) + else if (fastncmp(word, "MENU_", 11)) { pword = word + 11; if (fastcmp(pword, "TOBLACK")) - wipeoffset = wipe_timeattack_toblack; + wipeoffset = wipe_menu_toblack; else if (fastcmp(pword, "FINAL")) - wipeoffset = wipe_timeattack_final; + wipeoffset = wipe_menu_final; } else if (fastncmp(word, "CREDITS_", 8)) { @@ -3869,6 +3486,7 @@ void readfollower(MYFILE *f) followers[numfollowers].bobamp = 4*FRACUNIT; followers[numfollowers].hitconfirmtime = TICRATE; followers[numfollowers].defaultcolor = SKINCOLOR_GREEN; + strcpy(followers[numfollowers].icon, "M_NORANK"); do { @@ -3902,6 +3520,11 @@ void readfollower(MYFILE *f) strcpy(followers[numfollowers].name, word2); nameset = true; } + else if (fastcmp(word, "ICON")) + { + strcpy(followers[numfollowers].icon, word2); + nameset = true; + } else if (fastcmp(word, "MODE")) { if (word2) @@ -4268,20 +3891,6 @@ sfxenum_t get_sfx(const char *word) return sfx_None; } -menutype_t get_menutype(const char *word) -{ // Returns the value of MN_ enumerations - menutype_t i; - if (*word >= '0' && *word <= '9') - return atoi(word); - if (fastncmp("MN_",word,3)) - word += 3; // take off the MN_ - for (i = 0; i < NUMMENUTYPES; i++) - if (fastcmp(word, MENUTYPES_LIST[i])) - return i; - deh_warning("Couldn't find menutype named 'MN_%s'",word); - return MN_NONE; -} - /*static INT16 get_gametype(const char *word) { // Returns the value of GT_ enumerations INT16 i; diff --git a/src/deh_soc.h b/src/deh_soc.h index 7381e548c..d19b67a1a 100644 --- a/src/deh_soc.h +++ b/src/deh_soc.h @@ -21,7 +21,7 @@ #include "m_argv.h" #include "z_zone.h" #include "w_wad.h" -#include "m_menu.h" +#include "k_menu.h" #include "m_misc.h" #include "f_finale.h" #include "st_stuff.h" @@ -52,7 +52,6 @@ statenum_t get_state(const char *word); spritenum_t get_sprite(const char *word); playersprite_t get_sprite2(const char *word); sfxenum_t get_sfx(const char *word); -menutype_t get_menutype(const char *word); //INT16 get_gametype(const char *word); //powertype_t get_power(const char *word); skincolornum_t get_skincolor(const char *word); @@ -79,7 +78,6 @@ void readlight(MYFILE *f, INT32 num); void readskincolor(MYFILE *f, INT32 num); void readthing(MYFILE *f, INT32 num); void readfreeslots(MYFILE *f); -void readPlayer(MYFILE *f, INT32 num); void clear_levels(void); void clear_conditionsets(void); diff --git a/src/deh_tables.c b/src/deh_tables.c index 47a6ca515..d58550367 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -13,7 +13,7 @@ #include "doomdef.h" // Constants #include "s_sound.h" // Sound constants #include "info.h" // Mobj, state, sprite, etc constants -#include "m_menu.h" // Menu constants +#include "k_menu.h" // Menu constants #include "y_inter.h" // Intermission constants #include "p_local.h" // some more constants #include "r_draw.h" // Colormap constants @@ -6011,101 +6011,6 @@ const char *const HUDITEMS_LIST[] = { "POWERUPS" }; -const char *const MENUTYPES_LIST[] = { - "NONE", - - "MAIN", - - // Single Player - "SP_MAIN", - - "SP_LOAD", - "SP_PLAYER", - - "SP_LEVELSELECT", - "SP_LEVELSTATS", - - "SP_TIMEATTACK", - "SP_TIMEATTACK_LEVELSELECT", - "SP_GUESTREPLAY", - "SP_REPLAY", - "SP_GHOST", - - "SP_NIGHTSATTACK", - "SP_NIGHTS_LEVELSELECT", - "SP_NIGHTS_GUESTREPLAY", - "SP_NIGHTS_REPLAY", - "SP_NIGHTS_GHOST", - - // Multiplayer - "MP_MAIN", - "MP_SPLITSCREEN", // SplitServer - "MP_SERVER", - "MP_CONNECT", - "MP_ROOM", - "MP_PLAYERSETUP", // MP_PlayerSetupDef shared with SPLITSCREEN if #defined NONET - "MP_SERVER_OPTIONS", - - // Options - "OP_MAIN", - - "OP_P1CONTROLS", - "OP_CHANGECONTROLS", // OP_ChangeControlsDef shared with P2 - "OP_P1MOUSE", - "OP_P1JOYSTICK", - "OP_JOYSTICKSET", // OP_JoystickSetDef shared with P2 - "OP_P1CAMERA", - - "OP_P2CONTROLS", - "OP_P2MOUSE", - "OP_P2JOYSTICK", - "OP_P2CAMERA", - - "OP_PLAYSTYLE", - - "OP_VIDEO", - "OP_VIDEOMODE", - "OP_COLOR", - "OP_OPENGL", - "OP_OPENGL_LIGHTING", - - "OP_SOUND", - - "OP_SERVER", - "OP_MONITORTOGGLE", - - "OP_DATA", - "OP_ADDONS", - "OP_SCREENSHOTS", - "OP_ERASEDATA", - - // Extras - "SR_MAIN", - "SR_PANDORA", - "SR_LEVELSELECT", - "SR_UNLOCKCHECKLIST", - "SR_EMBLEMHINT", - "SR_PLAYER", - "SR_SOUNDTEST", - - // Addons (Part of MISC, but let's make it our own) - "AD_MAIN", - - // MISC - // "MESSAGE", - // "SPAUSE", - - // "MPAUSE", - // "SCRAMBLETEAM", - // "CHANGETEAM", - // "CHANGELEVEL", - - // "MAPAUSE", - // "HELP", - - "SPECIAL" -}; - struct int_const_s const INT_CONST[] = { // If a mod removes some variables here, // please leave the names in-tact and just set @@ -6574,9 +6479,9 @@ struct int_const_s const INT_CONST[] = { {"BT_DRIFT",BT_DRIFT}, {"BT_BRAKE",BT_BRAKE}, {"BT_ATTACK",BT_ATTACK}, - {"BT_CUSTOM1",BT_CUSTOM1}, // Lua customizable - {"BT_CUSTOM2",BT_CUSTOM2}, // Lua customizable - {"BT_CUSTOM3",BT_CUSTOM3}, // Lua customizable + {"BT_LUAA",BT_LUAA}, // Lua customizable + {"BT_LUAB",BT_LUAB}, // Lua customizable + {"BT_LUAC",BT_LUAC}, // Lua customizable // Lua command registration flags {"COM_ADMIN",COM_ADMIN}, @@ -6598,8 +6503,7 @@ struct int_const_s const INT_CONST[] = { {"CV_SHOWMODIF",CV_SHOWMODIF}, {"CV_SHOWMODIFONETIME",CV_SHOWMODIFONETIME}, {"CV_NOSHOWHELP",CV_NOSHOWHELP}, - {"CV_HIDEN",CV_HIDEN}, - {"CV_HIDDEN",CV_HIDEN}, + {"CV_HIDDEN",CV_HIDDEN}, {"CV_CHEAT",CV_CHEAT}, {"CV_NOLUA",CV_NOLUA}, @@ -6707,7 +6611,7 @@ struct int_const_s const INT_CONST[] = { {"GS_INTERMISSION",GS_INTERMISSION}, {"GS_CONTINUING",GS_CONTINUING}, {"GS_TITLESCREEN",GS_TITLESCREEN}, - {"GS_TIMEATTACK",GS_TIMEATTACK}, + {"GS_MENU",GS_MENU}, {"GS_CREDITS",GS_CREDITS}, {"GS_EVALUATION",GS_EVALUATION}, {"GS_GAMEEND",GS_GAMEEND}, diff --git a/src/deh_tables.h b/src/deh_tables.h index 3c5d64830..294e6452a 100644 --- a/src/deh_tables.h +++ b/src/deh_tables.h @@ -69,7 +69,6 @@ extern const char *COLOR_ENUMS[]; extern const char *const POWERS_LIST[]; extern const char *const KARTHUD_LIST[]; extern const char *const HUDITEMS_LIST[]; -extern const char *const MENUTYPES_LIST[]; extern struct int_const_s const INT_CONST[]; diff --git a/src/dehacked.c b/src/dehacked.c index b813106c4..d9a2084f1 100644 --- a/src/dehacked.c +++ b/src/dehacked.c @@ -11,6 +11,7 @@ /// \brief Load dehacked file and change tables and text #include "doomdef.h" + #include "m_cond.h" #include "deh_soc.h" #include "deh_tables.h" @@ -245,18 +246,7 @@ static void DEH_LoadDehackedFile(MYFILE *f, boolean mainfile) else i = 0; - if (fastcmp(word, "CHARACTER")) - { - if (i >= 0 && i < 32) - readPlayer(f, i); - else - { - deh_warning("Character %d out of range (0 - 31)", i); - ignorelines(f); - } - continue; - } - else if (fastcmp(word, "EMBLEM")) + if (fastcmp(word, "EMBLEM")) { if (!mainfile && !gamedataadded) { @@ -495,19 +485,6 @@ static void DEH_LoadDehackedFile(MYFILE *f, boolean mainfile) ignorelines(f); } } - else if (fastcmp(word, "MENU")) - { - if (i == 0 && word2[0] != '0') // If word2 isn't a number - i = get_menutype(word2); // find a huditem by name - if (i >= 1 && i < NUMMENUTYPES) - readmenu(f, i); - else - { - // zero-based, but let's start at 1 - deh_warning("Menu number %d out of range (1 - %d)", i, NUMMENUTYPES-1); - ignorelines(f); - } - } else if (fastcmp(word, "UNLOCKABLE")) { if (!mainfile && !gamedataadded) @@ -564,6 +541,7 @@ static void DEH_LoadDehackedFile(MYFILE *f, boolean mainfile) { cup = Z_Calloc(sizeof (cupheader_t), PU_STATIC, NULL); cup->id = numkartcupheaders; + cup->unlockrequired = -1; deh_strlcpy(cup->name, word2, sizeof(cup->name), va("Cup header %s: name", word2)); if (prev != NULL) diff --git a/src/discord.c b/src/discord.c index 444988af6..e178dbe2c 100644 --- a/src/discord.c +++ b/src/discord.c @@ -20,7 +20,7 @@ #include "i_net.h" #include "g_game.h" #include "p_tick.h" -#include "m_menu.h" // gametype_cons_t +#include "k_menu.h" // gametype_cons_t #include "r_things.h" // skins #include "mserv.h" // cv_advertise #include "z_zone.h" @@ -267,7 +267,7 @@ static void DRPC_HandleJoinRequest(const DiscordUser *requestUser) else { discordRequestList = newRequest; - M_RefreshPauseMenu(); + //M_RefreshPauseMenu(); } // Made it to the end, request was valid, so play the request sound :) diff --git a/src/doomdef.h b/src/doomdef.h index f36e780e6..08c8fb137 100644 --- a/src/doomdef.h +++ b/src/doomdef.h @@ -162,7 +162,7 @@ extern char logfilename[1024]; // Comment out this line to completely disable update alerts (recommended for testing, but not for release) #ifndef BETAVERSION -#define UPDATE_ALERT +//#define UPDATE_ALERT #endif // The string used in the alert that pops up in the event of an update being available. @@ -205,8 +205,10 @@ extern char logfilename[1024]; #define PLAYERSMASK (MAXPLAYERS-1) #define MAXPLAYERNAME 21 #define MAXSPLITSCREENPLAYERS 4 // Max number of players on a single computer +#define MAXGAMEPADS (MAXSPLITSCREENPLAYERS * 2) // Number of gamepads we'll be allowing #define MAXSKINS UINT8_MAX +#define SKINNAMESIZE 16 // Moved from r_skins.h as including that particular header causes issues later down the line. #define COLORRAMPSIZE 16 #define MAXCOLORNAME 32 @@ -225,6 +227,10 @@ typedef struct skincolor_s boolean accessible; // Accessible by the color command + setup menu } skincolor_t; +#define FOLLOWERCOLOR_MATCH UINT16_MAX +#define FOLLOWERCOLOR_OPPOSITE (UINT16_MAX-1) +UINT16 K_GetEffectiveFollowerColor(UINT16 followercolor, UINT16 playercolor); + typedef enum { SKINCOLOR_NONE = 0, diff --git a/src/doomstat.h b/src/doomstat.h index 3ed2cfa5d..66e21ccfa 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -195,7 +195,6 @@ extern INT16 bootmap; //bootmap for loading a map on startup extern INT16 tutorialmap; // map to load for tutorial extern boolean tutorialmode; // are we in a tutorial right now? -extern INT32 tutorialgcs; // which control scheme is loaded? extern boolean looptitle; diff --git a/src/f_finale.c b/src/f_finale.c index a84d9a45f..6e5f4b7f7 100644 --- a/src/f_finale.c +++ b/src/f_finale.c @@ -27,7 +27,6 @@ #include "z_zone.h" #include "i_system.h" #include "i_threads.h" -#include "m_menu.h" #include "dehacked.h" #include "g_input.h" #include "console.h" @@ -42,6 +41,9 @@ #include "lua_hud.h" +// SRB2Kart +#include "k_menu.h" + // Stage of animation: // 0 = text, 1 = art screen INT32 finalecount; @@ -53,7 +55,6 @@ static INT32 timetonext; // Delay between screen changes static tic_t animtimer; // Used for some animation timings static tic_t credbgtimer; // Credits background -static INT16 skullAnimCounter; // Prompts: Chevron animation static tic_t stoptimer; @@ -65,7 +66,7 @@ mobj_t *titlemapcameraref = NULL; // menu presentation state char curbgname[9]; SINT8 curfadevalue; -INT32 curbgcolor; +INT32 curbgcolor = -1; // Please stop assaulting my eyes. INT32 curbgxspeed; INT32 curbgyspeed; boolean curbghide; @@ -427,11 +428,11 @@ void F_IntroTicker(void) I_OsPolling(); I_UpdateNoBlit(); #ifdef HAVE_THREADS - I_lock_mutex(&m_menu_mutex); + I_lock_mutex(&k_menu_mutex); #endif M_Drawer(); // menu is drawn even on top of wipes #ifdef HAVE_THREADS - I_unlock_mutex(m_menu_mutex); + I_unlock_mutex(k_menu_mutex); #endif I_FinishUpdate(); // Update the screen with the image Tails 06-19-2001 @@ -1682,11 +1683,9 @@ void F_GameEndTicker(void) // TITLE SCREEN // ============== -void F_InitMenuPresValues(void) +static void F_InitMenuPresValues(void) { menuanimtimer = 0; - prevMenuId = 0; - activeMenuId = MainDef.menuid; // Set defaults for presentation values strncpy(curbgname, "TITLESKY", 9); @@ -1705,11 +1704,6 @@ void F_InitMenuPresValues(void) curttloop = ttloop; curtttics = tttics; - // Find current presentation values - //M_SetMenuCurBackground((gamestate == GS_TIMEATTACK) ? "RECATTBG" : "TITLESKY"); - //M_SetMenuCurFadeValue(16); - //M_SetMenuCurTitlePics(); - LUA_HUD_DestroyDrawList(luahuddrawlist_title); luahuddrawlist_title = LUA_HUD_CreateDrawList(); } @@ -1842,11 +1836,13 @@ static void F_CacheTitleScreen(void) void F_StartTitleScreen(void) { + setup_numplayers = 0; + if (gamestate != GS_TITLESCREEN && gamestate != GS_WAITINGPLAYERS) { ttuser_count = 0; finalecount = 0; - wipetypepost = menupres[MN_MAIN].enterwipe; + wipetypepost = 0; } else wipegamestate = GS_TITLESCREEN; @@ -1898,10 +1894,6 @@ void F_StartTitleScreen(void) camera[0].chase = true; camera[0].height = 0; - // Run enter linedef exec for MN_MAIN, since this is where we start - if (menupres[MN_MAIN].entertag) - P_LinedefExecute(menupres[MN_MAIN].entertag, players[displayplayers[0]].mo, NULL); - wipegamestate = prevwipegamestate; } else @@ -1920,6 +1912,7 @@ void F_StartTitleScreen(void) demoDelayLeft = demoDelayTime; demoIdleLeft = demoIdleTime; + F_InitMenuPresValues(); F_CacheTitleScreen(); } @@ -1936,6 +1929,8 @@ void F_TitleScreenDrawer(void) V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, curbgcolor); else if (!curbghide || !titlemapinaction || gamestate == GS_WAITINGPLAYERS) F_SkyScroll(curbgxspeed, curbgyspeed, curbgname); + else + V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31); // Don't draw outside of the title screen, or if the patch isn't there. if (gamestate != GS_TITLESCREEN && gamestate != GS_WAITINGPLAYERS) @@ -2115,18 +2110,10 @@ luahook: LUA_HUD_DrawList(luahuddrawlist_title); } -// separate animation timer for backgrounds, since we also count -// during GS_TIMEATTACK -void F_MenuPresTicker(boolean run) -{ - if (run) - menuanimtimer++; -} - // (no longer) De-Demo'd Title Screen void F_TitleScreenTicker(boolean run) { - F_MenuPresTicker(true); // title sky + menuanimtimer++; // title sky if (run) { @@ -2135,10 +2122,7 @@ void F_TitleScreenTicker(boolean run) if (finalecount == 1) { // Now start the music - if (menupres[MN_MAIN].musname[0]) - S_ChangeMusic(menupres[MN_MAIN].musname, menupres[MN_MAIN].mustrack, menupres[MN_MAIN].muslooping); - else - S_ChangeMusicInternal("_title", looptitle); + S_ChangeMusicInternal("_title", looptitle); } } @@ -2853,12 +2837,13 @@ void F_StartTextPrompt(INT32 promptnum, INT32 pagenum, mobj_t *mo, UINT16 postex static boolean F_GetTextPromptTutorialTag(char *tag, INT32 length) { - INT32 gcs = gcs_custom; + INT32 gcs = 0; boolean suffixed = true; if (!tag || !tag[0] || !tutorialmode) return false; + /* if (!strncmp(tag, "TAA", 3)) // Accelerate gcs = G_GetControlScheme(gamecontrol[0], gcl_accelerate, num_gcl_accelerate); else if (!strncmp(tag, "TAB", 3)) // Brake @@ -2873,14 +2858,10 @@ static boolean F_GetTextPromptTutorialTag(char *tag, INT32 length) gcs = G_GetControlScheme(gamecontrol[0], gcl_item, num_gcl_item); else gcs = G_GetControlScheme(gamecontrol[0], gcl_full, num_gcl_full); + */ switch (gcs) { - case gcs_kart: - // strncat(tag, "KART", length); - suffixed = false; - break; - default: strncat(tag, "CUSTOM", length); break; diff --git a/src/f_finale.h b/src/f_finale.h index 82b800f17..d3ed08d78 100644 --- a/src/f_finale.h +++ b/src/f_finale.h @@ -129,9 +129,6 @@ extern UINT16 curtttics; #define TITLEBACKGROUNDACTIVE (curfadevalue >= 0 || curbgname[0]) -void F_InitMenuPresValues(void); -void F_MenuPresTicker(boolean run); - // // WIPE // @@ -163,7 +160,7 @@ enum wipe_voting_toblack, wipe_continuing_toblack, wipe_titlescreen_toblack, - wipe_timeattack_toblack, + wipe_menu_toblack, wipe_credits_toblack, wipe_evaluation_toblack, wipe_gameend_toblack, @@ -181,7 +178,7 @@ enum wipe_voting_final, wipe_continuing_final, wipe_titlescreen_final, - wipe_timeattack_final, + wipe_menu_final, wipe_credits_final, wipe_evaluation_final, wipe_gameend_final, diff --git a/src/f_wipe.c b/src/f_wipe.c index ca4f7be50..b15c4b171 100644 --- a/src/f_wipe.c +++ b/src/f_wipe.c @@ -26,7 +26,6 @@ #include "i_time.h" #include "i_system.h" #include "i_threads.h" -#include "m_menu.h" #include "console.h" #include "d_main.h" #include "m_misc.h" // movie mode @@ -43,6 +42,9 @@ #define NOWIPE // do not enable wipe image post processing for ARM, SH and MIPS CPUs #endif +// SRB2Kart +#include "k_menu.h" + typedef struct fademask_s { UINT8* mask; UINT16 width, height; @@ -58,7 +60,7 @@ UINT8 wipedefs[NUMWIPEDEFS] = { 0, // wipe_voting_toblack, 0, // wipe_continuing_toblack 0, // wipe_titlescreen_toblack - 0, // wipe_timeattack_toblack + 1, // wipe_menu_toblack 99, // wipe_credits_toblack 0, // wipe_evaluation_toblack 0, // wipe_gameend_toblack @@ -74,7 +76,7 @@ UINT8 wipedefs[NUMWIPEDEFS] = { 0, // wipe_voting_final 0, // wipe_continuing_final 0, // wipe_titlescreen_final - 0, // wipe_timeattack_final + 1, // wipe_menu_final 99, // wipe_credits_final 0, // wipe_evaluation_final 0, // wipe_gameend_final @@ -509,11 +511,11 @@ void F_RunWipe(UINT8 wipetype, boolean drawMenu, const char *colormap, boolean r if (drawMenu) { #ifdef HAVE_THREADS - I_lock_mutex(&m_menu_mutex); + I_lock_mutex(&k_menu_mutex); #endif M_Drawer(); // menu is drawn even on top of wipes #ifdef HAVE_THREADS - I_unlock_mutex(m_menu_mutex); + I_unlock_mutex(k_menu_mutex); #endif } diff --git a/src/filesrch.c b/src/filesrch.c index 1d8d7bb6b..9db81ad37 100644 --- a/src/filesrch.c +++ b/src/filesrch.c @@ -32,7 +32,7 @@ #include "d_netfil.h" #include "m_misc.h" #include "z_zone.h" -#include "m_menu.h" // Addons_option_Onchange +#include "k_menu.h" // Addons_option_Onchange #if (defined (_WIN32) && !defined (_WIN32_WCE)) && defined (_MSC_VER) && !defined (_XBOX) @@ -561,15 +561,15 @@ char exttable[NUM_EXT_TABLE][7] = { // maximum extension length (currently 4) pl char filenamebuf[MAX_WADFILES][MAX_WADPATH]; -static boolean filemenucmp(char *haystack, char *needle) +static boolean filemenucmp(char *haystack) { static char localhaystack[128]; strlcpy(localhaystack, haystack, 128); if (!cv_addons_search_case.value) strupr(localhaystack); if (cv_addons_search_type.value) - return (strstr(localhaystack, needle) != 0); - return (!strncmp(localhaystack, needle, menusearch[0])); + return (strstr(localhaystack, menusearch+1) != 0); + return (!strncmp(localhaystack, menusearch+1, menusearch[0])); } void closefilemenu(boolean validsize) @@ -616,7 +616,6 @@ void closefilemenu(boolean validsize) void searchfilemenu(char *tempname) { size_t i, first; - char localmenusearch[MAXSTRINGLENGTH] = ""; if (dirmenu) { @@ -662,14 +661,10 @@ void searchfilemenu(char *tempname) return; } - strcpy(localmenusearch, menusearch+1); - if (!cv_addons_search_case.value) - strupr(localmenusearch); - sizedirmenu = 0; for (i = first; i < sizecoredirmenu; i++) { - if (filemenucmp(coredirmenu[i]+DIR_STRING, localmenusearch)) + if (filemenucmp(coredirmenu[i]+DIR_STRING)) sizedirmenu++; } @@ -691,7 +686,7 @@ void searchfilemenu(char *tempname) sizedirmenu = 0; for (i = first; i < sizecoredirmenu; i++) { - if (filemenucmp(coredirmenu[i]+DIR_STRING, localmenusearch)) + if (filemenucmp(coredirmenu[i]+DIR_STRING)) { if (tempname && !strcmp(coredirmenu[i]+DIR_STRING, tempname)) { @@ -724,7 +719,10 @@ boolean preparefilemenu(boolean samedepth, boolean replayhut) tempname = Z_StrDup(dirmenu[dir_on[menudepthleft]]+DIR_STRING); // don't need to I_Error if can't make - not important, just QoL } else + { menusearch[0] = menusearch[1] = 0; // clear search + CV_StealthSet(&cv_dummyaddonsearch, ""); + } if (!(dirhandle = opendir(menupath))) // get directory { diff --git a/src/filesrch.h b/src/filesrch.h index fb5fc0edd..ae824a5ae 100644 --- a/src/filesrch.h +++ b/src/filesrch.h @@ -6,7 +6,7 @@ #include "doomdef.h" #include "d_netfil.h" -#include "m_menu.h" // MAXSTRINGLENGTH +#include "k_menu.h" // MAXSTRINGLENGTH extern consvar_t cv_addons_option, cv_addons_folder, cv_addons_md5, cv_addons_showall, cv_addons_search_case, cv_addons_search_type; diff --git a/src/g_demo.c b/src/g_demo.c index 24ee6cd4e..1e2d5e2f3 100644 --- a/src/g_demo.c +++ b/src/g_demo.c @@ -26,7 +26,7 @@ #include "g_game.h" #include "g_demo.h" #include "m_misc.h" -#include "m_menu.h" +#include "k_menu.h" #include "m_argv.h" #include "hu_stuff.h" #include "z_zone.h" @@ -3888,7 +3888,7 @@ boolean G_DemoTitleResponder(event_t *ev) return true; } - if (ch == KEY_ENTER || ch >= KEY_MOUSE1) + if (ch == KEY_ENTER || ch >= NUMKEYS) { demo.savemode = DSM_WILLSAVE; return true; diff --git a/src/g_game.c b/src/g_game.c index bbc2f0293..2a4078cd4 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -33,7 +33,7 @@ #include "g_demo.h" #include "m_cheat.h" #include "m_misc.h" -#include "m_menu.h" +#include "k_menu.h" #include "m_argv.h" #include "hu_stuff.h" #include "st_stuff.h" @@ -75,8 +75,8 @@ JoyType_t Joystick[MAXSPLITSCREENPLAYERS]; #define SAVEGAMESIZE (1024) // SRB2kart -char gamedatafilename[64] = "kartdata.dat"; -char timeattackfolder[64] = "kart"; +char gamedatafilename[64] = "ringdata.dat"; +char timeattackfolder[64] = "ringracers"; char customversionstring[32] = "\0"; static void G_DoCompleted(void); @@ -160,7 +160,6 @@ INT16 bootmap; //bootmap for loading a map on startup INT16 tutorialmap = 0; // map to load for tutorial boolean tutorialmode = false; // are we in a tutorial right now? -INT32 tutorialgcs = gcs_custom; // which control scheme is loaded? boolean looptitle = true; @@ -353,22 +352,6 @@ static void weaponPrefChange2(void); static void weaponPrefChange3(void); static void weaponPrefChange4(void); -static CV_PossibleValue_t joyaxis_cons_t[] = {{0, "None"}, -{1, "X-Axis"}, {2, "Y-Axis"}, {-1, "X-Axis-"}, {-2, "Y-Axis-"}, -#if JOYAXISSET > 1 -{3, "Z-Axis"}, {4, "X-Rudder"}, {-3, "Z-Axis-"}, {-4, "X-Rudder-"}, -#endif -#if JOYAXISSET > 2 -{5, "Y-Rudder"}, {6, "Z-Rudder"}, {-5, "Y-Rudder-"}, {-6, "Z-Rudder-"}, -#endif -#if JOYAXISSET > 3 -{7, "U-Axis"}, {8, "V-Axis"}, {-7, "U-Axis-"}, {-8, "V-Axis-"}, -#endif - {0, NULL}}; -#if JOYAXISSET > 4 -"More Axis Sets" -#endif - // don't mind me putting these here, I was lazy to figure out where else I could put those without blowing up the compiler. // chat timer thingy @@ -425,68 +408,12 @@ consvar_t cv_shrinkme[MAXSPLITSCREENPLAYERS] = { CVAR_INIT ("shrinkme4", "Off", CV_CALL, CV_OnOff, weaponPrefChange4) }; -consvar_t cv_turnaxis[MAXSPLITSCREENPLAYERS] = { - CVAR_INIT ("joyaxis_turn", "X-Axis", CV_SAVE, joyaxis_cons_t, NULL), - CVAR_INIT ("joyaxis2_turn", "X-Axis", CV_SAVE, joyaxis_cons_t, NULL), - CVAR_INIT ("joyaxis3_turn", "X-Axis", CV_SAVE, joyaxis_cons_t, NULL), - CVAR_INIT ("joyaxis4_turn", "X-Axis", CV_SAVE, joyaxis_cons_t, NULL) -}; - -consvar_t cv_moveaxis[MAXSPLITSCREENPLAYERS] = { - CVAR_INIT ("joyaxis_move", "None", CV_SAVE, joyaxis_cons_t, NULL), - CVAR_INIT ("joyaxis_move2", "None", CV_SAVE, joyaxis_cons_t, NULL), - CVAR_INIT ("joyaxis_move3", "None", CV_SAVE, joyaxis_cons_t, NULL), - CVAR_INIT ("joyaxis_move4", "None", CV_SAVE, joyaxis_cons_t, NULL) -}; - -consvar_t cv_brakeaxis[MAXSPLITSCREENPLAYERS] = { - CVAR_INIT ("joyaxis_brake", "None", CV_SAVE, joyaxis_cons_t, NULL), - CVAR_INIT ("joyaxis2_brake", "None", CV_SAVE, joyaxis_cons_t, NULL), - CVAR_INIT ("joyaxis3_brake", "None", CV_SAVE, joyaxis_cons_t, NULL), - CVAR_INIT ("joyaxis4_brake", "None", CV_SAVE, joyaxis_cons_t, NULL) -}; - -consvar_t cv_aimaxis[MAXSPLITSCREENPLAYERS] = { - CVAR_INIT ("joyaxis_aim", "Y-Axis", CV_SAVE, joyaxis_cons_t, NULL), - CVAR_INIT ("joyaxis2_aim", "Y-Axis", CV_SAVE, joyaxis_cons_t, NULL), - CVAR_INIT ("joyaxis3_aim", "Y-Axis", CV_SAVE, joyaxis_cons_t, NULL), - CVAR_INIT ("joyaxis4_aim", "Y-Axis", CV_SAVE, joyaxis_cons_t, NULL) -}; - -consvar_t cv_lookaxis[MAXSPLITSCREENPLAYERS] = { - CVAR_INIT ("joyaxis_look", "None", CV_SAVE, joyaxis_cons_t, NULL), - CVAR_INIT ("joyaxis2_look", "None", CV_SAVE, joyaxis_cons_t, NULL), - CVAR_INIT ("joyaxis3_look", "None", CV_SAVE, joyaxis_cons_t, NULL), - CVAR_INIT ("joyaxis4_look", "None", CV_SAVE, joyaxis_cons_t, NULL) -}; - -consvar_t cv_fireaxis[MAXSPLITSCREENPLAYERS] = { - CVAR_INIT ("joyaxis_fire", "Z-Axis", CV_SAVE, joyaxis_cons_t, NULL), - CVAR_INIT ("joyaxis_fire2", "Z-Axis", CV_SAVE, joyaxis_cons_t, NULL), - CVAR_INIT ("joyaxis_fire3", "Z-Axis", CV_SAVE, joyaxis_cons_t, NULL), - CVAR_INIT ("joyaxis_fire4", "Z-Axis", CV_SAVE, joyaxis_cons_t, NULL) -}; - -consvar_t cv_driftaxis[MAXSPLITSCREENPLAYERS] = { - CVAR_INIT ("joyaxis_drift", "Z-Rudder", CV_SAVE, joyaxis_cons_t, NULL), - CVAR_INIT ("joyaxis2_drift", "Z-Rudder", CV_SAVE, joyaxis_cons_t, NULL), - CVAR_INIT ("joyaxis3_drift", "Z-Rudder", CV_SAVE, joyaxis_cons_t, NULL), - CVAR_INIT ("joyaxis4_drift", "Z-Rudder", CV_SAVE, joyaxis_cons_t, NULL) -}; - static CV_PossibleValue_t zerotoone_cons_t[] = {{0, "MIN"}, {FRACUNIT, "MAX"}, {0, NULL}}; consvar_t cv_deadzone[MAXSPLITSCREENPLAYERS] = { - CVAR_INIT ("joy_deadzone", "0.125", CV_FLOAT|CV_SAVE, zerotoone_cons_t, NULL), - CVAR_INIT ("joy2_deadzone", "0.125", CV_FLOAT|CV_SAVE, zerotoone_cons_t, NULL), - CVAR_INIT ("joy3_deadzone", "0.125", CV_FLOAT|CV_SAVE, zerotoone_cons_t, NULL), - CVAR_INIT ("joy4_deadzone", "0.125", CV_FLOAT|CV_SAVE, zerotoone_cons_t, NULL) -}; - -consvar_t cv_digitaldeadzone[MAXSPLITSCREENPLAYERS] = { - CVAR_INIT ("joy_digdeadzone", "0.25", CV_FLOAT|CV_SAVE, zerotoone_cons_t, NULL), - CVAR_INIT ("joy2_digdeadzone", "0.25", CV_FLOAT|CV_SAVE, zerotoone_cons_t, NULL), - CVAR_INIT ("joy3_digdeadzone", "0.25", CV_FLOAT|CV_SAVE, zerotoone_cons_t, NULL), - CVAR_INIT ("joy4_digdeadzone", "0.25", CV_FLOAT|CV_SAVE, zerotoone_cons_t, NULL) + CVAR_INIT ("deadzone", "0.25", CV_FLOAT|CV_SAVE, zerotoone_cons_t, NULL), + CVAR_INIT ("deadzone2", "0.25", CV_FLOAT|CV_SAVE, zerotoone_cons_t, NULL), + CVAR_INIT ("deadzone3", "0.25", CV_FLOAT|CV_SAVE, zerotoone_cons_t, NULL), + CVAR_INIT ("deadzone4", "0.25", CV_FLOAT|CV_SAVE, zerotoone_cons_t, NULL) }; // now automatically allocated in D_RegisterClientCommands @@ -672,9 +599,10 @@ const char *G_BuildMapName(INT32 map) { static char mapname[10] = "MAPXX"; // internal map name (wad resource name) - I_Assert(map >= 0); + I_Assert(map > 0); I_Assert(map <= NUMMAPS); +#if 0 if (map == 0) // hack??? { if (gamestate == GS_TITLESCREEN) @@ -685,6 +613,7 @@ const char *G_BuildMapName(INT32 map) map = prevmap; map = G_RandMap(G_TOLFlag(cv_newgametype.value), map, 0, 0, false, NULL)+1; } +#endif if (map < 100) sprintf(&mapname[3], "%.2d", map); @@ -736,74 +665,161 @@ INT16 G_SoftwareClipAimingPitch(INT32 *aiming) return (INT16)((*aiming)>>16); } -INT32 PlayerJoyAxis(UINT8 player, axis_input_e axissel) +// Default controls for keyboard. These are hardcoded and cannot be changed. +static INT32 keyboardMenuDefaults[][2] = { + {gc_a, KEY_ENTER}, + {gc_c, KEY_BACKSPACE}, + {gc_x, KEY_ESCAPE}, + {gc_left, KEY_LEFTARROW}, + {gc_right, KEY_RIGHTARROW}, + {gc_up, KEY_UPARROW}, + {gc_down, KEY_DOWNARROW}, + + // special control + {gc_start, KEY_ESCAPE}, + // 8 total controls* +}; + +#define KEYBOARDDEFAULTSSPLIT 7 + + +INT32 G_PlayerInputAnalog(UINT8 p, INT32 gc, UINT8 menuPlayers) { - INT32 retaxis; - INT32 axisval; - boolean flp = false; + INT32 deviceID; + INT32 i, j; + INT32 deadzone = 0; + boolean trydefaults = true; + boolean tryingotherID = false; + INT32 *controltable = &(gamecontrol[p][gc][0]); - //find what axis to get - switch (axissel) + if (p >= MAXSPLITSCREENPLAYERS) { - case AXISTURN: - axisval = cv_turnaxis[player-1].value; - break; - case AXISMOVE: - axisval = cv_moveaxis[player-1].value; - break; - case AXISBRAKE: - axisval = cv_brakeaxis[player-1].value; - break; - case AXISAIM: - axisval = cv_aimaxis[player-1].value; - break; - case AXISLOOK: - axisval = cv_lookaxis[player-1].value; - break; - case AXISFIRE: - axisval = cv_fireaxis[player-1].value; - break; - case AXISDRIFT: - axisval = cv_driftaxis[player-1].value; - break; - default: - return 0; - } - - if (axisval < 0) //odd -axises - { - axisval = -axisval; - flp = true; - } - if (axisval > JOYAXISSET*2 || axisval == 0) //not there in array or None +#ifdef PARANOIA + CONS_Debug(DBG_GAMELOGIC, "G_PlayerInputAnalog: Invalid player ID %d\n", p); +#endif return 0; - - if (axisval%2) - { - axisval /= 2; - retaxis = joyxmove[player-1][axisval]; - } - else - { - axisval--; - axisval /= 2; - retaxis = joyymove[player-1][axisval]; } - if (retaxis < (-JOYAXISRANGE)) - retaxis = -JOYAXISRANGE; - if (retaxis > (+JOYAXISRANGE)) - retaxis = +JOYAXISRANGE; + deadzone = (JOYAXISRANGE * cv_deadzone[p].value) / FRACUNIT; - if (!Joystick[player-1].bGamepadStyle && axissel >= AXISDIGITAL) + deviceID = cv_usejoystick[p].value; + +retrygetcontrol: + for (i = 0; i < MAXINPUTMAPPING; i++) { - const INT32 jdeadzone = ((JOYAXISRANGE-1) * cv_digitaldeadzone[player-1].value) >> FRACBITS; - if (-jdeadzone < retaxis && retaxis < jdeadzone) - return 0; + INT32 key = controltable[i]; + INT32 menukey = KEY_NULL; + INT32 value = 0; + boolean processinput = true; + + + // for menus, keyboards have defaults! + if (deviceID == 0) + { + + // In menus, check indexes 0 through 5 (everything besides gc_start) + // Outside of menus, only consider the hardcoded input for gc_start at index 6 + + INT32 maxj = menuactive ? KEYBOARDDEFAULTSSPLIT : KEYBOARDDEFAULTSSPLIT+1; + j = (!menuactive) ? KEYBOARDDEFAULTSSPLIT : 0; + + for (; j < maxj; j++) // check keyboardMenuDefaults + { + // the gc we're looking for + if (gc == keyboardMenuDefaults[j][0]) + { + menukey = keyboardMenuDefaults[j][1]; + break; + } + + // The key is mapped to *something else*...? + // Then don't process that as it would conflict with our hardcoded inputs. + else if (key == keyboardMenuDefaults[j][1]) + { + processinput = false; + break; + } + } + } + + // Invalid key number. + if (!G_KeyIsAvailable(key, deviceID) && !G_KeyIsAvailable(menukey, deviceID)) + { + continue; + } + + if (processinput) + { + // It's possible to access this control right now, so let's disable the default control backup for later. + trydefaults = false; + + value = gamekeydown[deviceID][key]; + if (menukey && gamekeydown[deviceID][menukey]) + value = gamekeydown[deviceID][menukey]; + + if (value >= deadzone) + { + return value; + } + } } - if (flp) retaxis = -retaxis; //flip it around - return retaxis; + // If you're on controller, try your keyboard-based binds as an immediate backup. + // Do not do this if there are more than 1 local player. + if (p == 0 && deviceID > 0 && !tryingotherID && menuPlayers < 2 && !splitscreen) + { + deviceID = 0; + goto retrygetcontrol; + } + + if (menuPlayers == 0) + { + return 0; + } + + // We don't want menus to become unnavigable if people unbind + // all of their controls, so we do several things in this scenario. + // First: try other controllers. + + if (!tryingotherID) + { + deviceID = MAXDEVICES; + tryingotherID = true; + } +loweringid: + deviceID--; + if (deviceID > 0) + { + for (i = 0; i < menuPlayers; i++) + { + if (deviceID != cv_usejoystick[i].value) + continue; + // Controller taken? Try again... + goto loweringid; + } + goto retrygetcontrol; + } + + if (trydefaults && G_KeyBindIsNecessary(gc)) + { + // If we still haven't found anything and the keybind is necessary, + // try it all again but with default binds. + trydefaults = false; + controltable = &(gamecontroldefault[gc][0]); + tryingotherID = false; + deviceID = cv_usejoystick[p].value; + goto retrygetcontrol; + + } + + return 0; +} + +#undef KEYBOARDDEFAULTSSPLIT + +boolean G_PlayerInputDown(UINT8 p, INT32 gc, UINT8 menuPlayers) +{ + return (G_PlayerInputAnalog(p, gc, menuPlayers) != 0); } // Take a magnitude of two axes, and adjust it to take out the deadzone @@ -950,27 +966,15 @@ static void G_DoAnglePrediction(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer, p void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer) { const UINT8 forplayer = ssplayer-1; - - const INT32 lookaxis = cv_lookaxis[forplayer].value; - const boolean invertmouse = cv_invertmouse.value; - const boolean analogjoystickmove = cv_usejoystick[forplayer].value && !Joystick[forplayer].bGamepadStyle; - const boolean gamepadjoystickmove = cv_usejoystick[forplayer].value && Joystick[forplayer].bGamepadStyle; - const boolean usejoystick = (analogjoystickmove || gamepadjoystickmove); - - static boolean keyboard_look[MAXSPLITSCREENPLAYERS]; // true if lookup/down using keyboard - static boolean resetdown[MAXSPLITSCREENPLAYERS]; // don't cam reset every frame - - INT32 forward, axis; + INT32 forward; joystickvector2_t joystickvector; - boolean turnleft, turnright; - player_t *player = &players[g_localplayers[forplayer]]; - camera_t *thiscam = &camera[forplayer]; - boolean *kbl = &keyboard_look[forplayer]; - boolean *rd = &resetdown[forplayer]; - const boolean mouseaiming = player->spectator; + //camera_t *thiscam = &camera[forplayer]; + //boolean *kbl = &keyboard_look[forplayer]; + //boolean *rd = &resetdown[forplayer]; + //const boolean mouseaiming = player->spectator; if (demo.playback) return; @@ -999,16 +1003,27 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer) return; } + cmd->flags = 0; + + if (menuactive || chat_on || CON_Ready()) + { + cmd->flags |= TICCMD_TYPING; + + if (hu_keystrokes) + { + cmd->flags |= TICCMD_KEYSTROKE; + } + + goto aftercmdinput; + } + if (K_PlayerUsesBotMovement(player)) { // Bot ticcmd is generated by K_BuildBotTiccmd return; } - turnright = PlayerInputDown(ssplayer, gc_turnright); - turnleft = PlayerInputDown(ssplayer, gc_turnleft); - - joystickvector.xaxis = PlayerJoyAxis(ssplayer, AXISTURN); + joystickvector.xaxis = G_PlayerInputAnalog(forplayer, gc_right, 0) - G_PlayerInputAnalog(forplayer, gc_left, 0); joystickvector.yaxis = 0; G_HandleAxisDeadZone(forplayer, &joystickvector); @@ -1016,143 +1031,99 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer) // use it for aiming to throw items forward/backward and the vote screen // This mean that the turn axis will still be gradient but up/down will be 0 // until the stick is pushed far enough - joystickvector.yaxis = PlayerJoyAxis(ssplayer, AXISAIM); + joystickvector.yaxis = G_PlayerInputAnalog(forplayer, gc_down, 0) - G_PlayerInputAnalog(forplayer, gc_up, 0); if (encoremode) { - turnright ^= turnleft; // swap these using three XORs - turnleft ^= turnright; - turnright ^= turnleft; joystickvector.xaxis = -joystickvector.xaxis; } - if (gamepadjoystickmove && joystickvector.xaxis != 0) - { - turnright = turnright || (joystickvector.xaxis > 0); - turnleft = turnleft || (joystickvector.xaxis < 0); - } forward = 0; - cmd->turning = 0; - // let movement keys cancel each other out - if (turnright && !(turnleft)) - { - cmd->turning -= KART_FULLTURN; - } - else if (turnleft && !(turnright)) - { - cmd->turning += KART_FULLTURN; - } - - if (analogjoystickmove && joystickvector.xaxis != 0) + if (joystickvector.xaxis != 0) { cmd->turning -= (joystickvector.xaxis * KART_FULLTURN) >> 10; } - // Specator mouse turning - if (player->spectator) - { - cmd->turning -= (mousex * 8) * (encoremode ? -1 : 1); - } - if (player->spectator || objectplacing) // SRB2Kart: spectators need special controls { - axis = PlayerJoyAxis(ssplayer, AXISMOVE); - if (PlayerInputDown(ssplayer, gc_accelerate) || (usejoystick && axis > 0)) + if (G_PlayerInputDown(forplayer, gc_accel, 0)) + { cmd->buttons |= BT_ACCELERATE; - axis = PlayerJoyAxis(ssplayer, AXISBRAKE); - if (PlayerInputDown(ssplayer, gc_brake) || (usejoystick && axis > 0)) + } + + if (G_PlayerInputDown(forplayer, gc_brake, 0)) + { cmd->buttons |= BT_BRAKE; - axis = PlayerJoyAxis(ssplayer, AXISAIM); - if (PlayerInputDown(ssplayer, gc_aimforward) || (usejoystick && axis < 0)) + } + + if (joystickvector.yaxis < 0) + { forward += MAXPLMOVE; - if (PlayerInputDown(ssplayer, gc_aimbackward) || (usejoystick && axis > 0)) + } + + if (joystickvector.yaxis > 0) + { forward -= MAXPLMOVE; + } } else { // forward with key or button // SRB2kart - we use an accel/brake instead of forward/backward. - axis = PlayerJoyAxis(ssplayer, AXISMOVE); - if (PlayerInputDown(ssplayer, gc_accelerate) || (gamepadjoystickmove && axis > 0)) + INT32 value = G_PlayerInputAnalog(forplayer, gc_accel, 0); + if (value != 0) { cmd->buttons |= BT_ACCELERATE; - forward = MAXPLMOVE; // 50 - } - else if (analogjoystickmove && axis > 0) - { - cmd->buttons |= BT_ACCELERATE; - // JOYAXISRANGE is supposed to be 1023 (divide by 1024) - forward += ((axis * MAXPLMOVE) >> 10); + forward += ((value * MAXPLMOVE) >> 10); } - axis = PlayerJoyAxis(ssplayer, AXISBRAKE); - if (PlayerInputDown(ssplayer, gc_brake) || (gamepadjoystickmove && axis > 0)) + value = G_PlayerInputAnalog(forplayer, gc_brake, 0); + if (value != 0) { cmd->buttons |= BT_BRAKE; - if (cmd->buttons & BT_ACCELERATE || cmd->forwardmove <= 0) - forward -= MAXPLMOVE; - } - else if (analogjoystickmove && axis > 0) - { - cmd->buttons |= BT_BRAKE; - // JOYAXISRANGE is supposed to be 1023 (divide by 1024) - if (cmd->buttons & BT_ACCELERATE || cmd->forwardmove <= 0) - forward -= ((axis * MAXPLMOVE) >> 10); + forward -= ((value * MAXPLMOVE) >> 10); } // But forward/backward IS used for aiming. - if (PlayerInputDown(ssplayer, gc_aimforward)) - cmd->throwdir += KART_FULLTURN; - if (PlayerInputDown(ssplayer, gc_aimbackward)) - cmd->throwdir -= KART_FULLTURN; - - if (analogjoystickmove && joystickvector.yaxis != 0) + if (joystickvector.yaxis != 0) { cmd->throwdir -= (joystickvector.yaxis * KART_FULLTURN) >> 10; } } - // fire with any button/key - axis = PlayerJoyAxis(ssplayer, AXISFIRE); - if (PlayerInputDown(ssplayer, gc_fire) || (usejoystick && axis > 0)) - cmd->buttons |= BT_ATTACK; - - // drift with any button/key - axis = PlayerJoyAxis(ssplayer, AXISDRIFT); - if (PlayerInputDown(ssplayer, gc_drift) || (usejoystick && axis > 0)) - cmd->buttons |= BT_DRIFT; - - // Spindash with any button/key - // Simply holds all of the inputs for you. - axis = PlayerJoyAxis(ssplayer, AXISSPINDASH); - if (PlayerInputDown(ssplayer, gc_spindash) || (usejoystick && axis > 0)) - cmd->buttons |= (BT_ACCELERATE|BT_BRAKE|BT_DRIFT); - - // rear view with any button/key - axis = PlayerJoyAxis(ssplayer, AXISLOOKBACK); - if (PlayerInputDown(ssplayer, gc_lookback) || (usejoystick && axis > 0)) - cmd->buttons |= BT_LOOKBACK; - - // Lua scriptable buttons - if (PlayerInputDown(ssplayer, gc_custom1)) - cmd->buttons |= BT_CUSTOM1; - if (PlayerInputDown(ssplayer, gc_custom2)) - cmd->buttons |= BT_CUSTOM2; - if (PlayerInputDown(ssplayer, gc_custom3)) - cmd->buttons |= BT_CUSTOM3; - - // Reset camera - if (PlayerInputDown(ssplayer, gc_camreset)) + // drift + if (G_PlayerInputDown(forplayer, gc_drift, 0)) { - if (thiscam->chase && *rd == false) - P_ResetCamera(player, thiscam); - *rd = true; + cmd->buttons |= BT_DRIFT; } - else - *rd = false; + + // C + if (G_PlayerInputDown(forplayer, gc_spindash, 0)) + { + forward = 0; + cmd->buttons |= BT_SPINDASHMASK; + } + + // fire + if (G_PlayerInputDown(forplayer, gc_item, 0)) + { + cmd->buttons |= BT_ATTACK; + } + + // rear view + if (G_PlayerInputDown(forplayer, gc_lookback, 0)) + { + cmd->buttons |= BT_LOOKBACK; + } + + // lua buttons a thru c + if (G_PlayerInputDown(forplayer, gc_luaa, 0)) { cmd->buttons |= BT_LUAA; } + if (G_PlayerInputDown(forplayer, gc_luab, 0)) { cmd->buttons |= BT_LUAB; } + if (G_PlayerInputDown(forplayer, gc_luac, 0)) { cmd->buttons |= BT_LUAC; } // spectator aiming shit, ahhhh... + /* { INT32 player_invert = invertmouse ? -1 : 1; INT32 screen_invert = @@ -1160,15 +1131,6 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer) && (!thiscam->chase)) //because chasecam's not inverted ? -1 : 1; // set to -1 or 1 to multiply - // mouse look stuff (mouse look is not the same as mouse aim) - if (mouseaiming && player->spectator) - { - *kbl = false; - - // looking up/down - cmd->aiming += (mlooky<<19)*player_invert*screen_invert; - } - axis = PlayerJoyAxis(ssplayer, AXISLOOK); if (analogjoystickmove && axis != 0 && lookaxis && player->spectator) cmd->aiming += (axis<<16) * screen_invert; @@ -1194,22 +1156,11 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer) if (PlayerInputDown(ssplayer, gc_centerview)) // No need to put a spectator limit on this one though :V cmd->aiming = 0; } - - mousex = mousey = mlooky = 0; + */ cmd->forwardmove += (SINT8)forward; - cmd->flags = 0; - - if (chat_on || CON_Ready()) - { - cmd->flags |= TICCMD_TYPING; - - if (hu_keystrokes) - { - cmd->flags |= TICCMD_KEYSTROKE; - } - } +aftercmdinput: /* Lua: Allow this hook to overwrite ticcmd. We check if we're actually in a level because for some reason this Hook would run in menus and on the titlescreen otherwise. @@ -1304,7 +1255,7 @@ static void weaponPrefChange4(void) // void G_DoLoadLevel(boolean resetplayer) { - INT32 i, j; + INT32 i; // Make sure objectplace is OFF when you first start the level! OP_ResetObjectplace(); @@ -1314,6 +1265,8 @@ void G_DoLoadLevel(boolean resetplayer) if (wipegamestate == GS_LEVEL) wipegamestate = -1; // force a wipe + if (cv_currprofile.value == -1) + PR_ApplyProfilePretend(cv_ttlprofilen.value, 0); if (gamestate == GS_INTERMISSION) Y_EndIntermission(); if (gamestate == GS_VOTING) @@ -1335,6 +1288,8 @@ void G_DoLoadLevel(boolean resetplayer) titlemapinaction = TITLEMAP_OFF; G_SetGamestate(GS_LEVEL); + if (wipegamestate == GS_MENU) + M_ClearMenus(true); I_UpdateMouseGrab(); for (i = 0; i < MAXPLAYERS; i++) @@ -1364,12 +1319,7 @@ void G_DoLoadLevel(boolean resetplayer) // clear cmd building stuff memset(gamekeydown, 0, sizeof (gamekeydown)); - for (i = 0;i < JOYAXISSET; i++) - { - for (j = 0; j < MAXSPLITSCREENPLAYERS; j++) - joyxmove[j][i] = joyymove[j][i] = 0; - } - mousex = mousey = 0; + memset(deviceResponding, false, sizeof (deviceResponding)); // clear hud messages remains (usually from game startup) CON_ClearHUD(); @@ -1468,7 +1418,7 @@ static INT32 camtoggledelay[MAXSPLITSCREENPLAYERS]; // boolean G_Responder(event_t *ev) { - UINT8 i; + //INT32 i; // any other key pops up menu if in demos if (gameaction == ga_nothing && !demo.quitafterplaying && @@ -1563,7 +1513,7 @@ boolean G_Responder(event_t *ev) // allow spy mode changes even during the demo if (gamestate == GS_LEVEL && ev->type == ev_keydown - && (ev->data1 == KEY_F12 || ev->data1 == gamecontrol[0][gc_viewpoint][0] || ev->data1 == gamecontrol[0][gc_viewpoint][1])) + && (ev->data1 == KEY_F12 /*|| ev->data1 == gamecontrol[0][gc_viewpoint][0] || ev->data1 == gamecontrol[0][gc_viewpoint][1]*/)) { if (!demo.playback && (r_splitscreen || !netgame)) g_localplayers[0] = consoleplayer; @@ -1581,6 +1531,7 @@ boolean G_Responder(event_t *ev) if (gamestate == GS_LEVEL && ev->type == ev_keydown && multiplayer && demo.playback && !demo.freecam) { + /* if (ev->data1 == gamecontrol[1][gc_viewpoint][0] || ev->data1 == gamecontrol[1][gc_viewpoint][1]) { G_AdjustView(2, 1, true); @@ -1596,12 +1547,13 @@ boolean G_Responder(event_t *ev) G_AdjustView(4, 1, true); return true; } + */ // Allow pausing if ( - ev->data1 == gamecontrol[0][gc_pause][0] - || ev->data1 == gamecontrol[0][gc_pause][1] - || ev->data1 == KEY_PAUSE + //ev->data1 == gamecontrol[0][gc_pause][0] + //|| ev->data1 == gamecontrol[0][gc_pause][1] + ev->data1 == KEY_PAUSE ) { paused = !paused; @@ -1635,9 +1587,9 @@ boolean G_Responder(event_t *ev) switch (ev->type) { case ev_keydown: - if (ev->data1 == gamecontrol[0][gc_pause][0] - || ev->data1 == gamecontrol[0][gc_pause][1] - || ev->data1 == KEY_PAUSE) + if (//ev->data1 == gamecontrol[0][gc_pause][0] + //|| ev->data1 == gamecontrol[0][gc_pause][1] + ev->data1 == KEY_PAUSE) { if (modeattacking && !demo.playback && (gamestate == GS_LEVEL)) { @@ -1667,6 +1619,7 @@ boolean G_Responder(event_t *ev) } } + /* for (i = 0; i < MAXSPLITSCREENPLAYERS; i++) { if (ev->data1 == gamecontrol[i][gc_camtoggle][0] @@ -1679,6 +1632,7 @@ boolean G_Responder(event_t *ev) } } } + */ return true; @@ -1691,15 +1645,6 @@ boolean G_Responder(event_t *ev) case ev_joystick: return true; // eat events - case ev_joystick2: - return true; // eat events - - case ev_joystick3: - return true; // eat events - - case ev_joystick4: - return true; // eat events - default: break; } @@ -2044,8 +1989,7 @@ void G_Ticker(boolean run) HU_Ticker(); break; - case GS_TIMEATTACK: - F_MenuPresTicker(run); + case GS_MENU: break; case GS_INTRO: @@ -3323,12 +3267,11 @@ INT16 G_SometimesGetDifferentGametype(void) // G_GetGametypeColor // // Pretty and consistent ^u^ -// See also M_GetGametypeColor. +// See also M_GetGametypeColor (if that still exists). // UINT8 G_GetGametypeColor(INT16 gt) { - if (modeattacking // == ATTACKING_TIME - || gamestate == GS_TIMEATTACK) + if (modeattacking) // == ATTACKING_RECORD return orangemap[0]; if (gt == GT_BATTLE) @@ -3802,8 +3745,6 @@ demointermission: // See also F_EndCutscene, the only other place which handles intra-map/ending transitions void G_AfterIntermission(void) { - Y_CleanupScreenBuffer(); - if (modeattacking) { M_EndModeAttackRun(); @@ -3820,9 +3761,11 @@ void G_AfterIntermission(void) { G_StopDemo(); +#if 0 if (demo.inreplayhut) M_ReplayHut(0); else +#endif D_StartTitle(); return; @@ -4044,9 +3987,6 @@ void G_LoadGameData(void) totalplaytime = 0; // total play time (separate from all) matchesplayed = 0; // SRB2Kart: matches played & finished - for (i = 0; i < PWRLV_NUMTYPES; i++) // SRB2Kart: online rank system - vspowerlevel[i] = PWRLVRECORD_START; - if (M_CheckParm("-nodata")) return; // Don't load. @@ -4076,19 +4016,12 @@ void G_LoadGameData(void) Z_Free(savebuffer); save_p = NULL; - I_Error("Game data is from another version of SRB2.\nDelete %s(maybe in %s) and try again.", gamedatafilename, gdfolder); + I_Error("Game data is from another version of SRB2.\nDelete %s (maybe in %s) and try again.", gamedatafilename, gdfolder); } totalplaytime = READUINT32(save_p); matchesplayed = READUINT32(save_p); - for (i = 0; i < PWRLV_NUMTYPES; i++) - { - vspowerlevel[i] = READUINT16(save_p); - if (vspowerlevel[i] < PWRLVRECORD_MIN || vspowerlevel[i] > PWRLVRECORD_MAX) - goto datacorrupt; - } - modded = READUINT8(save_p); // Aha! Someone's been screwing with the save file! @@ -4205,9 +4138,6 @@ void G_SaveGameData(void) WRITEUINT32(save_p, totalplaytime); WRITEUINT32(save_p, matchesplayed); - for (i = 0; i < PWRLV_NUMTYPES; i++) - WRITEUINT16(save_p, vspowerlevel[i]); - WRITEUINT8(save_p, (UINT8)savemoddata); // TODO put another cipher on these things? meh, I don't care... @@ -4271,6 +4201,9 @@ void G_SaveGameData(void) FIL_WriteFile(va(pandf, srb2home, gamedatafilename), savebuffer, length); free(savebuffer); save_p = savebuffer = NULL; + + // Also save profiles here. + PR_SaveProfiles(); } #define VERSIONSIZE 16 @@ -4589,8 +4522,6 @@ void G_InitNew(UINT8 pencoremode, const char *mapname, boolean resetplayer, bool (void)FLS; - Y_CleanupScreenBuffer(); - if (paused) { paused = false; diff --git a/src/g_game.h b/src/g_game.h index 52c7c7a0d..a6406932a 100644 --- a/src/g_game.h +++ b/src/g_game.h @@ -65,7 +65,6 @@ extern consvar_t cv_lookaxis[MAXSPLITSCREENPLAYERS]; extern consvar_t cv_fireaxis[MAXSPLITSCREENPLAYERS]; extern consvar_t cv_driftaxis[MAXSPLITSCREENPLAYERS]; extern consvar_t cv_deadzone[MAXSPLITSCREENPLAYERS]; -extern consvar_t cv_digitaldeadzone[MAXSPLITSCREENPLAYERS]; extern consvar_t cv_ghost_besttime, cv_ghost_bestlap, cv_ghost_last, cv_ghost_guest, cv_ghost_staff; @@ -96,26 +95,6 @@ ticcmd_t *G_MoveTiccmd(ticcmd_t* dest, const ticcmd_t* src, const size_t n); INT32 G_ClipAimingPitch(INT32 *aiming); INT16 G_SoftwareClipAimingPitch(INT32 *aiming); -typedef enum -{ - AXISNONE = 0, - - AXISTURN, - AXISMOVE, - AXISBRAKE, - AXISLOOK, - - AXISDIGITAL, // axes below this use digital deadzone - - AXISFIRE = AXISDIGITAL, - AXISDRIFT, - AXISSPINDASH, - AXISLOOKBACK, - AXISAIM, -} axis_input_e; - -INT32 PlayerJoyAxis(UINT8 player, axis_input_e axissel); - extern angle_t localangle[MAXSPLITSCREENPLAYERS]; extern INT32 localaiming[MAXSPLITSCREENPLAYERS]; // should be an angle_t but signed extern INT32 localsteering[MAXSPLITSCREENPLAYERS]; @@ -123,6 +102,9 @@ extern INT32 localdelta[MAXSPLITSCREENPLAYERS]; extern INT32 localstoredeltas[MAXSPLITSCREENPLAYERS][TICCMD_LATENCYMASK + 1]; extern UINT8 localtic; +INT32 G_PlayerInputAnalog(UINT8 p, INT32 gc, UINT8 menuPlayers); +boolean G_PlayerInputDown(UINT8 p, INT32 gc, UINT8 menuPlayers); + // // GAME // diff --git a/src/g_input.c b/src/g_input.c index 72c7033fa..ead8cab27 100644 --- a/src/g_input.c +++ b/src/g_input.c @@ -18,6 +18,7 @@ #include "hu_stuff.h" // need HUFONT start & end #include "d_net.h" #include "console.h" +#include "i_joy.h" // JOYAXISRANGE #define MAXMOUSESENSITIVITY 100 // sensitivity steps @@ -31,32 +32,29 @@ consvar_t cv_mouseysens = CVAR_INIT ("mouseysens", "20", CV_SAVE, mousesens_cons consvar_t cv_mouseysens2 = CVAR_INIT ("mouseysens2", "20", CV_SAVE, mousesens_cons_t, NULL); consvar_t cv_controlperkey = CVAR_INIT ("controlperkey", "One", CV_SAVE, onecontrolperkey_cons_t, NULL); -INT32 mousex, mousey; -INT32 mlooky; // like mousey but with a custom sensitivity for mlook - -// joystick values are repeated -INT32 joyxmove[MAXSPLITSCREENPLAYERS][JOYAXISSET], joyymove[MAXSPLITSCREENPLAYERS][JOYAXISSET]; - -// current state of the keys: true if pushed -UINT8 gamekeydown[NUMINPUTS]; +// current state of the keys +// FRACUNIT for fully pressed, 0 for not pressed +INT32 gamekeydown[MAXDEVICES][NUMINPUTS]; +boolean deviceResponding[MAXDEVICES]; // two key codes (or virtual key) per game control -INT32 gamecontrol[MAXSPLITSCREENPLAYERS][num_gamecontrols][2]; -INT32 gamecontroldefault[MAXSPLITSCREENPLAYERS][num_gamecontrolschemes][num_gamecontrols][2]; // default control storage, use 0 (gcs_custom) for memory retention +INT32 gamecontrol[MAXSPLITSCREENPLAYERS][num_gamecontrols][MAXINPUTMAPPING]; +INT32 gamecontroldefault[num_gamecontrols][MAXINPUTMAPPING]; // default control storage // lists of GC codes for selective operation -const INT32 gcl_accelerate[num_gcl_accelerate] = { gc_accelerate }; +/* +const INT32 gcl_accelerate[num_gcl_accelerate] = { gc_a }; -const INT32 gcl_brake[num_gcl_brake] = { gc_brake }; +const INT32 gcl_brake[num_gcl_brake] = { gc_b }; -const INT32 gcl_drift[num_gcl_drift] = { gc_drift }; +const INT32 gcl_drift[num_gcl_drift] = { gc_c }; const INT32 gcl_spindash[num_gcl_spindash] = { - gc_accelerate, gc_drift, gc_brake, gc_spindash + gc_a, gc_b, gc_c, gc_abc }; const INT32 gcl_movement[num_gcl_movement] = { - gc_accelerate, gc_drift, gc_brake, gc_spindash, gc_turnleft, gc_turnright + gc_a, gc_b, gc_c, gc_abc, gc_left, gc_right }; const INT32 gcl_item[num_gcl_item] = { @@ -64,22 +62,26 @@ const INT32 gcl_item[num_gcl_item] = { }; const INT32 gcl_full[num_gcl_full] = { - gc_accelerate, gc_drift, gc_brake, gc_spindash, gc_turnleft, gc_turnright, + gc_a, gc_drift, gc_b, gc_spindash, gc_turnleft, gc_turnright, gc_fire, gc_aimforward, gc_aimbackward, gc_lookback }; +*/ -typedef struct +INT32 G_GetDevicePlayer(INT32 deviceID) { - UINT8 time; - UINT8 state; - UINT8 clicks; -} dclick_t; -static dclick_t mousedclicks[MOUSEBUTTONS]; -static dclick_t joydclicks[MAXSPLITSCREENPLAYERS][JOYBUTTONS + JOYHATS*4]; + INT32 i; -// protos -static UINT8 G_CheckDoubleClick(UINT8 state, dclick_t *dt); + for (i = 0; i < MAXSPLITSCREENPLAYERS; i++) + { + if (deviceID == cv_usejoystick[i].value) + { + return i; + } + } + + return -1; +} // // Remaps the inputs to game controls. @@ -91,137 +93,151 @@ static UINT8 G_CheckDoubleClick(UINT8 state, dclick_t *dt); void G_MapEventsToControls(event_t *ev) { INT32 i; - UINT8 flag; + + if (ev->device >= 0 && ev->device < MAXDEVICES) + { + switch (ev->type) + { + case ev_keydown: + //case ev_keyup: + //case ev_mouse: + //case ev_joystick: + deviceResponding[ev->device] = true; + break; + + default: + break; + } + } + else + { + return; + } switch (ev->type) { case ev_keydown: if (ev->data1 < NUMINPUTS) - gamekeydown[ev->data1] = 1; + { + gamekeydown[ev->device][ev->data1] = JOYAXISRANGE; + } #ifdef PARANOIA else { - CONS_Debug(DBG_GAMELOGIC, "Bad downkey input %d\n",ev->data1); + CONS_Debug(DBG_GAMELOGIC, "Bad downkey input %d\n", ev->data1); } - #endif break; case ev_keyup: if (ev->data1 < NUMINPUTS) - gamekeydown[ev->data1] = 0; + { + gamekeydown[ev->device][ev->data1] = 0; + } #ifdef PARANOIA else { - CONS_Debug(DBG_GAMELOGIC, "Bad upkey input %d\n",ev->data1); + CONS_Debug(DBG_GAMELOGIC, "Bad upkey input %d\n", ev->data1); } #endif break; case ev_mouse: // buttons are virtual keys - if (menuactive || CON_Ready() || chat_on) - break; - mousex = (INT32)(ev->data2*((cv_mousesens.value*cv_mousesens.value)/110.0f + 0.1f)); - mousey = (INT32)(ev->data3*((cv_mousesens.value*cv_mousesens.value)/110.0f + 0.1f)); - mlooky = (INT32)(ev->data3*((cv_mouseysens.value*cv_mousesens.value)/110.0f + 0.1f)); + // X axis + if (ev->data2 < 0) + { + // Left + gamekeydown[ev->device][KEY_MOUSEMOVE + 2] = abs(ev->data2); + gamekeydown[ev->device][KEY_MOUSEMOVE + 3] = 0; + } + else + { + // Right + gamekeydown[ev->device][KEY_MOUSEMOVE + 2] = 0; + gamekeydown[ev->device][KEY_MOUSEMOVE + 3] = abs(ev->data2); + } + + // Y axis + if (ev->data3 < 0) + { + // Up + gamekeydown[ev->device][KEY_MOUSEMOVE] = abs(ev->data3); + gamekeydown[ev->device][KEY_MOUSEMOVE + 1] = 0; + } + else + { + // Down + gamekeydown[ev->device][KEY_MOUSEMOVE] = 0; + gamekeydown[ev->device][KEY_MOUSEMOVE + 1] = abs(ev->data3); + } break; case ev_joystick: // buttons are virtual keys - i = ev->data1; - if (i >= JOYAXISSET || menuactive || CON_Ready() || chat_on) + if (ev->data1 >= JOYAXISSETS) + { +#ifdef PARANOIA + CONS_Debug(DBG_GAMELOGIC, "Bad joystick axis event %d\n", ev->data1); +#endif break; - if (ev->data2 != INT32_MAX) joyxmove[0][i] = ev->data2; - if (ev->data3 != INT32_MAX) joyymove[0][i] = ev->data3; - break; + } - case ev_joystick2: // buttons are virtual keys i = ev->data1; - if (i >= JOYAXISSET || menuactive) - break; - if (ev->data2 != INT32_MAX) joyxmove[1][i] = ev->data2; - if (ev->data3 != INT32_MAX) joyymove[1][i] = ev->data3; - break; - case ev_joystick3: - i = ev->data1; - if (i >= JOYAXISSET) - break; - if (ev->data2 != INT32_MAX) joyxmove[2][i] = ev->data2; - if (ev->data3 != INT32_MAX) joyymove[2][i] = ev->data3; - break; + if (i >= JOYANALOGS) + { + // The trigger axes are handled specially. + i -= JOYANALOGS; - case ev_joystick4: - i = ev->data1; - if (i >= JOYAXISSET) - break; - if (ev->data2 != INT32_MAX) joyxmove[3][i] = ev->data2; - if (ev->data3 != INT32_MAX) joyymove[3][i] = ev->data3; + if (ev->data2 != INT32_MAX) + { + gamekeydown[ev->device][KEY_AXIS1 + (JOYANALOGS * 4) + (i * 2)] = max(0, ev->data2); + } + + if (ev->data3 != INT32_MAX) + { + gamekeydown[ev->device][KEY_AXIS1 + (JOYANALOGS * 4) + (i * 2) + 1] = max(0, ev->data3); + } + } + else + { + // Actual analog sticks + if (ev->data2 != INT32_MAX) + { + if (ev->data2 < 0) + { + // Left + gamekeydown[ev->device][KEY_AXIS1 + (i * 4)] = abs(ev->data2); + gamekeydown[ev->device][KEY_AXIS1 + (i * 4) + 1] = 0; + } + else + { + // Right + gamekeydown[ev->device][KEY_AXIS1 + (i * 4)] = 0; + gamekeydown[ev->device][KEY_AXIS1 + (i * 4) + 1] = abs(ev->data2); + } + } + + if (ev->data3 != INT32_MAX) + { + if (ev->data3 < 0) + { + // Up + gamekeydown[ev->device][KEY_AXIS1 + (i * 4) + 2] = abs(ev->data3); + gamekeydown[ev->device][KEY_AXIS1 + (i * 4) + 3] = 0; + } + else + { + // Down + gamekeydown[ev->device][KEY_AXIS1 + (i * 4) + 2] = 0; + gamekeydown[ev->device][KEY_AXIS1 + (i * 4) + 3] = abs(ev->data3); + } + } + } break; default: break; } - - // ALWAYS check for mouse & joystick double-clicks even if no mouse event - for (i = 0; i < MOUSEBUTTONS; i++) - { - flag = G_CheckDoubleClick(gamekeydown[KEY_MOUSE1+i], &mousedclicks[i]); - gamekeydown[KEY_DBLMOUSE1+i] = flag; - } - - for (i = 0; i < JOYBUTTONS + JOYHATS*4; i++) - { - flag = G_CheckDoubleClick(gamekeydown[KEY_JOY1+i], &joydclicks[0][i]); - gamekeydown[KEY_DBLJOY1+i] = flag; - } - - for (i = 0; i < JOYBUTTONS + JOYHATS*4; i++) - { - flag = G_CheckDoubleClick(gamekeydown[KEY_2JOY1+i], &joydclicks[1][i]); - gamekeydown[KEY_DBL2JOY1+i] = flag; - } - - for (i = 0; i < JOYBUTTONS + JOYHATS*4; i++) - { - flag = G_CheckDoubleClick(gamekeydown[KEY_3JOY1+i], &joydclicks[2][i]); - gamekeydown[KEY_DBL3JOY1+i] = flag; - } - - for (i = 0; i < JOYBUTTONS + JOYHATS*4; i++) - { - flag = G_CheckDoubleClick(gamekeydown[KEY_4JOY1+i], &joydclicks[3][i]); - gamekeydown[KEY_DBL4JOY1+i] = flag; - } -} - -// -// General double-click detection routine for any kind of input. -// -static UINT8 G_CheckDoubleClick(UINT8 state, dclick_t *dt) -{ - if (state != dt->state && dt->time > 1) - { - dt->state = state; - if (state) - dt->clicks++; - if (dt->clicks == 2) - { - dt->clicks = 0; - return true; - } - else - dt->time = 0; - } - else - { - dt->time++; - if (dt->time > 20) - { - dt->clicks = 0; - dt->state = 0; - } - } - return false; } typedef struct @@ -239,12 +255,12 @@ static keyname_t keynames[] = {KEY_ESCAPE, "ESCAPE"}, {KEY_BACKSPACE, "BACKSPACE"}, - {KEY_NUMLOCK, "NUMLOCK"}, - {KEY_SCROLLLOCK, "SCROLLLOCK"}, + {KEY_NUMLOCK, "NUM LOCK"}, + {KEY_SCROLLLOCK, "SCROLL LOCK"}, // bill gates keys - {KEY_LEFTWIN, "LEFTWIN"}, - {KEY_RIGHTWIN, "RIGHTWIN"}, + {KEY_LEFTWIN, "LWINDOWS"}, + {KEY_RIGHTWIN, "RWINDOWS"}, {KEY_MENU, "MENU"}, {KEY_LSHIFT, "LSHIFT"}, @@ -276,14 +292,14 @@ static keyname_t keynames[] = // extended keys (not keypad) {KEY_HOME, "HOME"}, {KEY_UPARROW, "UP ARROW"}, - {KEY_PGUP, "PGUP"}, + {KEY_PGUP, "PAGE UP"}, {KEY_LEFTARROW, "LEFT ARROW"}, {KEY_RIGHTARROW, "RIGHT ARROW"}, {KEY_END, "END"}, {KEY_DOWNARROW, "DOWN ARROW"}, - {KEY_PGDN, "PGDN"}, - {KEY_INS, "INS"}, - {KEY_DEL, "DEL"}, + {KEY_PGDN, "PAGE DOWN"}, + {KEY_INS, "INSERT"}, + {KEY_DEL, "DELETE"}, // other keys {KEY_F1, "F1"}, @@ -312,482 +328,142 @@ static keyname_t keynames[] = {KEY_MOUSE1+5,"MOUSE6"}, {KEY_MOUSE1+6,"MOUSE7"}, {KEY_MOUSE1+7,"MOUSE8"}, - {KEY_MOUSEWHEELUP, "Wheel 1 UP"}, - {KEY_MOUSEWHEELDOWN, "Wheel 1 Down"}, - {KEY_2MOUSEWHEELUP, "Wheel 2 UP"}, - {KEY_2MOUSEWHEELDOWN, "Wheel 2 Down"}, + {KEY_MOUSEMOVE+0,"Mouse Up"}, + {KEY_MOUSEMOVE+1,"Mouse Down"}, + {KEY_MOUSEMOVE+2,"Mouse Left"}, + {KEY_MOUSEMOVE+3,"Mouse Right"}, + {KEY_MOUSEWHEELUP, "Wheel Up"}, + {KEY_MOUSEWHEELDOWN, "Wheel Down"}, - {KEY_JOY1+0, "JOY1"}, - {KEY_JOY1+1, "JOY2"}, - {KEY_JOY1+2, "JOY3"}, - {KEY_JOY1+3, "JOY4"}, - {KEY_JOY1+4, "JOY5"}, - {KEY_JOY1+5, "JOY6"}, - {KEY_JOY1+6, "JOY7"}, - {KEY_JOY1+7, "JOY8"}, - {KEY_JOY1+8, "JOY9"}, -#if !defined (NOMOREJOYBTN_1S) - // we use up to 32 buttons in DirectInput - {KEY_JOY1+9, "JOY10"}, - {KEY_JOY1+10, "JOY11"}, - {KEY_JOY1+11, "JOY12"}, - {KEY_JOY1+12, "JOY13"}, - {KEY_JOY1+13, "JOY14"}, - {KEY_JOY1+14, "JOY15"}, - {KEY_JOY1+15, "JOY16"}, - {KEY_JOY1+16, "JOY17"}, - {KEY_JOY1+17, "JOY18"}, - {KEY_JOY1+18, "JOY19"}, - {KEY_JOY1+19, "JOY20"}, - {KEY_JOY1+20, "JOY21"}, - {KEY_JOY1+21, "JOY22"}, - {KEY_JOY1+22, "JOY23"}, - {KEY_JOY1+23, "JOY24"}, - {KEY_JOY1+24, "JOY25"}, - {KEY_JOY1+25, "JOY26"}, - {KEY_JOY1+26, "JOY27"}, - {KEY_JOY1+27, "JOY28"}, - {KEY_JOY1+28, "JOY29"}, - {KEY_JOY1+29, "JOY30"}, - {KEY_JOY1+30, "JOY31"}, - {KEY_JOY1+31, "JOY32"}, -#endif - // the DOS version uses Allegro's joystick support - {KEY_HAT1+0, "HATUP"}, - {KEY_HAT1+1, "HATDOWN"}, - {KEY_HAT1+2, "HATLEFT"}, - {KEY_HAT1+3, "HATRIGHT"}, - {KEY_HAT1+4, "HATUP2"}, - {KEY_HAT1+5, "HATDOWN2"}, - {KEY_HAT1+6, "HATLEFT2"}, - {KEY_HAT1+7, "HATRIGHT2"}, - {KEY_HAT1+8, "HATUP3"}, - {KEY_HAT1+9, "HATDOWN3"}, - {KEY_HAT1+10, "HATLEFT3"}, - {KEY_HAT1+11, "HATRIGHT3"}, - {KEY_HAT1+12, "HATUP4"}, - {KEY_HAT1+13, "HATDOWN4"}, - {KEY_HAT1+14, "HATLEFT4"}, - {KEY_HAT1+15, "HATRIGHT4"}, - - {KEY_DBLMOUSE1+0, "DBLMOUSE1"}, - {KEY_DBLMOUSE1+1, "DBLMOUSE2"}, - {KEY_DBLMOUSE1+2, "DBLMOUSE3"}, - {KEY_DBLMOUSE1+3, "DBLMOUSE4"}, - {KEY_DBLMOUSE1+4, "DBLMOUSE5"}, - {KEY_DBLMOUSE1+5, "DBLMOUSE6"}, - {KEY_DBLMOUSE1+6, "DBLMOUSE7"}, - {KEY_DBLMOUSE1+7, "DBLMOUSE8"}, - - {KEY_DBLJOY1+0, "DBLJOY1"}, - {KEY_DBLJOY1+1, "DBLJOY2"}, - {KEY_DBLJOY1+2, "DBLJOY3"}, - {KEY_DBLJOY1+3, "DBLJOY4"}, - {KEY_DBLJOY1+4, "DBLJOY5"}, - {KEY_DBLJOY1+5, "DBLJOY6"}, - {KEY_DBLJOY1+6, "DBLJOY7"}, - {KEY_DBLJOY1+7, "DBLJOY8"}, -#if !defined (NOMOREJOYBTN_1DBL) - {KEY_DBLJOY1+8, "DBLJOY9"}, - {KEY_DBLJOY1+9, "DBLJOY10"}, - {KEY_DBLJOY1+10, "DBLJOY11"}, - {KEY_DBLJOY1+11, "DBLJOY12"}, - {KEY_DBLJOY1+12, "DBLJOY13"}, - {KEY_DBLJOY1+13, "DBLJOY14"}, - {KEY_DBLJOY1+14, "DBLJOY15"}, - {KEY_DBLJOY1+15, "DBLJOY16"}, - {KEY_DBLJOY1+16, "DBLJOY17"}, - {KEY_DBLJOY1+17, "DBLJOY18"}, - {KEY_DBLJOY1+18, "DBLJOY19"}, - {KEY_DBLJOY1+19, "DBLJOY20"}, - {KEY_DBLJOY1+20, "DBLJOY21"}, - {KEY_DBLJOY1+21, "DBLJOY22"}, - {KEY_DBLJOY1+22, "DBLJOY23"}, - {KEY_DBLJOY1+23, "DBLJOY24"}, - {KEY_DBLJOY1+24, "DBLJOY25"}, - {KEY_DBLJOY1+25, "DBLJOY26"}, - {KEY_DBLJOY1+26, "DBLJOY27"}, - {KEY_DBLJOY1+27, "DBLJOY28"}, - {KEY_DBLJOY1+28, "DBLJOY29"}, - {KEY_DBLJOY1+29, "DBLJOY30"}, - {KEY_DBLJOY1+30, "DBLJOY31"}, - {KEY_DBLJOY1+31, "DBLJOY32"}, -#endif - {KEY_DBLHAT1+0, "DBLHATUP"}, - {KEY_DBLHAT1+1, "DBLHATDOWN"}, - {KEY_DBLHAT1+2, "DBLHATLEFT"}, - {KEY_DBLHAT1+3, "DBLHATRIGHT"}, - {KEY_DBLHAT1+4, "DBLHATUP2"}, - {KEY_DBLHAT1+5, "DBLHATDOWN2"}, - {KEY_DBLHAT1+6, "DBLHATLEFT2"}, - {KEY_DBLHAT1+7, "DBLHATRIGHT2"}, - {KEY_DBLHAT1+8, "DBLHATUP3"}, - {KEY_DBLHAT1+9, "DBLHATDOWN3"}, - {KEY_DBLHAT1+10, "DBLHATLEFT3"}, - {KEY_DBLHAT1+11, "DBLHATRIGHT3"}, - {KEY_DBLHAT1+12, "DBLHATUP4"}, - {KEY_DBLHAT1+13, "DBLHATDOWN4"}, - {KEY_DBLHAT1+14, "DBLHATLEFT4"}, - {KEY_DBLHAT1+15, "DBLHATRIGHT4"}, - - {KEY_2JOY1+0, "SEC_JOY1"}, - {KEY_2JOY1+1, "SEC_JOY2"}, - {KEY_2JOY1+2, "SEC_JOY3"}, - {KEY_2JOY1+3, "SEC_JOY4"}, - {KEY_2JOY1+4, "SEC_JOY5"}, - {KEY_2JOY1+5, "SEC_JOY6"}, - {KEY_2JOY1+6, "SEC_JOY7"}, - {KEY_2JOY1+7, "SEC_JOY8"}, -#if !defined (NOMOREJOYBTN_2S) - // we use up to 32 buttons in DirectInput - {KEY_2JOY1+8, "SEC_JOY9"}, - {KEY_2JOY1+9, "SEC_JOY10"}, - {KEY_2JOY1+10, "SEC_JOY11"}, - {KEY_2JOY1+11, "SEC_JOY12"}, - {KEY_2JOY1+12, "SEC_JOY13"}, - {KEY_2JOY1+13, "SEC_JOY14"}, - {KEY_2JOY1+14, "SEC_JOY15"}, - {KEY_2JOY1+15, "SEC_JOY16"}, - {KEY_2JOY1+16, "SEC_JOY17"}, - {KEY_2JOY1+17, "SEC_JOY18"}, - {KEY_2JOY1+18, "SEC_JOY19"}, - {KEY_2JOY1+19, "SEC_JOY20"}, - {KEY_2JOY1+20, "SEC_JOY21"}, - {KEY_2JOY1+21, "SEC_JOY22"}, - {KEY_2JOY1+22, "SEC_JOY23"}, - {KEY_2JOY1+23, "SEC_JOY24"}, - {KEY_2JOY1+24, "SEC_JOY25"}, - {KEY_2JOY1+25, "SEC_JOY26"}, - {KEY_2JOY1+26, "SEC_JOY27"}, - {KEY_2JOY1+27, "SEC_JOY28"}, - {KEY_2JOY1+28, "SEC_JOY29"}, - {KEY_2JOY1+29, "SEC_JOY30"}, - {KEY_2JOY1+30, "SEC_JOY31"}, - {KEY_2JOY1+31, "SEC_JOY32"}, -#endif - // the DOS version uses Allegro's joystick support - {KEY_2HAT1+0, "SEC_HATUP"}, - {KEY_2HAT1+1, "SEC_HATDOWN"}, - {KEY_2HAT1+2, "SEC_HATLEFT"}, - {KEY_2HAT1+3, "SEC_HATRIGHT"}, - {KEY_2HAT1+4, "SEC_HATUP2"}, - {KEY_2HAT1+5, "SEC_HATDOWN2"}, - {KEY_2HAT1+6, "SEC_HATLEFT2"}, - {KEY_2HAT1+7, "SEC_HATRIGHT2"}, - {KEY_2HAT1+8, "SEC_HATUP3"}, - {KEY_2HAT1+9, "SEC_HATDOWN3"}, - {KEY_2HAT1+10, "SEC_HATLEFT3"}, - {KEY_2HAT1+11, "SEC_HATRIGHT3"}, - {KEY_2HAT1+12, "SEC_HATUP4"}, - {KEY_2HAT1+13, "SEC_HATDOWN4"}, - {KEY_2HAT1+14, "SEC_HATLEFT4"}, - {KEY_2HAT1+15, "SEC_HATRIGHT4"}, - - {KEY_DBL2JOY1+0, "DBLSEC_JOY1"}, - {KEY_DBL2JOY1+1, "DBLSEC_JOY2"}, - {KEY_DBL2JOY1+2, "DBLSEC_JOY3"}, - {KEY_DBL2JOY1+3, "DBLSEC_JOY4"}, - {KEY_DBL2JOY1+4, "DBLSEC_JOY5"}, - {KEY_DBL2JOY1+5, "DBLSEC_JOY6"}, - {KEY_DBL2JOY1+6, "DBLSEC_JOY7"}, - {KEY_DBL2JOY1+7, "DBLSEC_JOY8"}, -#if !defined (NOMOREJOYBTN_2DBL) - {KEY_DBL2JOY1+8, "DBLSEC_JOY9"}, - {KEY_DBL2JOY1+9, "DBLSEC_JOY10"}, - {KEY_DBL2JOY1+10, "DBLSEC_JOY11"}, - {KEY_DBL2JOY1+11, "DBLSEC_JOY12"}, - {KEY_DBL2JOY1+12, "DBLSEC_JOY13"}, - {KEY_DBL2JOY1+13, "DBLSEC_JOY14"}, - {KEY_DBL2JOY1+14, "DBLSEC_JOY15"}, - {KEY_DBL2JOY1+15, "DBLSEC_JOY16"}, - {KEY_DBL2JOY1+16, "DBLSEC_JOY17"}, - {KEY_DBL2JOY1+17, "DBLSEC_JOY18"}, - {KEY_DBL2JOY1+18, "DBLSEC_JOY19"}, - {KEY_DBL2JOY1+19, "DBLSEC_JOY20"}, - {KEY_DBL2JOY1+20, "DBLSEC_JOY21"}, - {KEY_DBL2JOY1+21, "DBLSEC_JOY22"}, - {KEY_DBL2JOY1+22, "DBLSEC_JOY23"}, - {KEY_DBL2JOY1+23, "DBLSEC_JOY24"}, - {KEY_DBL2JOY1+24, "DBLSEC_JOY25"}, - {KEY_DBL2JOY1+25, "DBLSEC_JOY26"}, - {KEY_DBL2JOY1+26, "DBLSEC_JOY27"}, - {KEY_DBL2JOY1+27, "DBLSEC_JOY28"}, - {KEY_DBL2JOY1+28, "DBLSEC_JOY29"}, - {KEY_DBL2JOY1+29, "DBLSEC_JOY30"}, - {KEY_DBL2JOY1+30, "DBLSEC_JOY31"}, - {KEY_DBL2JOY1+31, "DBLSEC_JOY32"}, -#endif - {KEY_DBL2HAT1+0, "DBLSEC_HATUP"}, - {KEY_DBL2HAT1+1, "DBLSEC_HATDOWN"}, - {KEY_DBL2HAT1+2, "DBLSEC_HATLEFT"}, - {KEY_DBL2HAT1+3, "DBLSEC_HATRIGHT"}, - {KEY_DBL2HAT1+4, "DBLSEC_HATUP2"}, - {KEY_DBL2HAT1+5, "DBLSEC_HATDOWN2"}, - {KEY_DBL2HAT1+6, "DBLSEC_HATLEFT2"}, - {KEY_DBL2HAT1+7, "DBLSEC_HATRIGHT2"}, - {KEY_DBL2HAT1+8, "DBLSEC_HATUP3"}, - {KEY_DBL2HAT1+9, "DBLSEC_HATDOWN3"}, - {KEY_DBL2HAT1+10, "DBLSEC_HATLEFT3"}, - {KEY_DBL2HAT1+11, "DBLSEC_HATRIGHT3"}, - {KEY_DBL2HAT1+12, "DBLSEC_HATUP4"}, - {KEY_DBL2HAT1+13, "DBLSEC_HATDOWN4"}, - {KEY_DBL2HAT1+14, "DBLSEC_HATLEFT4"}, - {KEY_DBL2HAT1+15, "DBLSEC_HATRIGHT4"}, - - - {KEY_3JOY1+0, "TRD_JOY1"}, - {KEY_3JOY1+1, "TRD_JOY2"}, - {KEY_3JOY1+2, "TRD_JOY3"}, - {KEY_3JOY1+3, "TRD_JOY4"}, - {KEY_3JOY1+4, "TRD_JOY5"}, - {KEY_3JOY1+5, "TRD_JOY6"}, - {KEY_3JOY1+6, "TRD_JOY7"}, - {KEY_3JOY1+7, "TRD_JOY8"}, - {KEY_3JOY1+8, "TRD_JOY9"}, - {KEY_3JOY1+9, "TRD_JOY10"}, - {KEY_3JOY1+10, "TRD_JOY11"}, - {KEY_3JOY1+11, "TRD_JOY12"}, - {KEY_3JOY1+12, "TRD_JOY13"}, - {KEY_3JOY1+13, "TRD_JOY14"}, - {KEY_3JOY1+14, "TRD_JOY15"}, - {KEY_3JOY1+15, "TRD_JOY16"}, - {KEY_3JOY1+16, "TRD_JOY17"}, - {KEY_3JOY1+17, "TRD_JOY18"}, - {KEY_3JOY1+18, "TRD_JOY19"}, - {KEY_3JOY1+19, "TRD_JOY20"}, - {KEY_3JOY1+20, "TRD_JOY21"}, - {KEY_3JOY1+21, "TRD_JOY22"}, - {KEY_3JOY1+22, "TRD_JOY23"}, - {KEY_3JOY1+23, "TRD_JOY24"}, - {KEY_3JOY1+24, "TRD_JOY25"}, - {KEY_3JOY1+25, "TRD_JOY26"}, - {KEY_3JOY1+26, "TRD_JOY27"}, - {KEY_3JOY1+27, "TRD_JOY28"}, - {KEY_3JOY1+28, "TRD_JOY29"}, - {KEY_3JOY1+29, "TRD_JOY30"}, - {KEY_3JOY1+30, "TRD_JOY31"}, - {KEY_3JOY1+31, "TRD_JOY32"}, - - {KEY_DBL3JOY1+0, "DBLTRD_JOY1"}, - {KEY_DBL3JOY1+1, "DBLTRD_JOY2"}, - {KEY_DBL3JOY1+2, "DBLTRD_JOY3"}, - {KEY_DBL3JOY1+3, "DBLTRD_JOY4"}, - {KEY_DBL3JOY1+4, "DBLTRD_JOY5"}, - {KEY_DBL3JOY1+5, "DBLTRD_JOY6"}, - {KEY_DBL3JOY1+6, "DBLTRD_JOY7"}, - {KEY_DBL3JOY1+7, "DBLTRD_JOY8"}, - {KEY_DBL3JOY1+8, "DBLTRD_JOY9"}, - {KEY_DBL3JOY1+9, "DBLTRD_JOY10"}, - {KEY_DBL3JOY1+10, "DBLTRD_JOY11"}, - {KEY_DBL3JOY1+11, "DBLTRD_JOY12"}, - {KEY_DBL3JOY1+12, "DBLTRD_JOY13"}, - {KEY_DBL3JOY1+13, "DBLTRD_JOY14"}, - {KEY_DBL3JOY1+14, "DBLTRD_JOY15"}, - {KEY_DBL3JOY1+15, "DBLTRD_JOY16"}, - {KEY_DBL3JOY1+16, "DBLTRD_JOY17"}, - {KEY_DBL3JOY1+17, "DBLTRD_JOY18"}, - {KEY_DBL3JOY1+18, "DBLTRD_JOY19"}, - {KEY_DBL3JOY1+19, "DBLTRD_JOY20"}, - {KEY_DBL3JOY1+20, "DBLTRD_JOY21"}, - {KEY_DBL3JOY1+21, "DBLTRD_JOY22"}, - {KEY_DBL3JOY1+22, "DBLTRD_JOY23"}, - {KEY_DBL3JOY1+23, "DBLTRD_JOY24"}, - {KEY_DBL3JOY1+24, "DBLTRD_JOY25"}, - {KEY_DBL3JOY1+25, "DBLTRD_JOY26"}, - {KEY_DBL3JOY1+26, "DBLTRD_JOY27"}, - {KEY_DBL3JOY1+27, "DBLTRD_JOY28"}, - {KEY_DBL3JOY1+28, "DBLTRD_JOY29"}, - {KEY_DBL3JOY1+29, "DBLTRD_JOY30"}, - {KEY_DBL3JOY1+30, "DBLTRD_JOY31"}, - {KEY_DBL3JOY1+31, "DBLTRD_JOY32"}, - - {KEY_3HAT1+0, "TRD_HATUP"}, - {KEY_3HAT1+1, "TRD_HATDOWN"}, - {KEY_3HAT1+2, "TRD_HATLEFT"}, - {KEY_3HAT1+3, "TRD_HATRIGHT"}, - {KEY_3HAT1+4, "TRD_HATUP2"}, - {KEY_3HAT1+5, "TRD_HATDOWN2"}, - {KEY_3HAT1+6, "TRD_HATLEFT2"}, - {KEY_3HAT1+7, "TRD_HATRIGHT2"}, - {KEY_3HAT1+8, "TRD_HATUP3"}, - {KEY_3HAT1+9, "TRD_HATDOWN3"}, - {KEY_3HAT1+10, "TRD_HATLEFT3"}, - {KEY_3HAT1+11, "TRD_HATRIGHT3"}, - {KEY_3HAT1+12, "TRD_HATUP4"}, - {KEY_3HAT1+13, "TRD_HATDOWN4"}, - {KEY_3HAT1+14, "TRD_HATLEFT4"}, - {KEY_3HAT1+15, "TRD_HATRIGHT4"}, - - {KEY_DBL3HAT1+0, "DBLTRD_HATUP"}, - {KEY_DBL3HAT1+1, "DBLTRD_HATDOWN"}, - {KEY_DBL3HAT1+2, "DBLTRD_HATLEFT"}, - {KEY_DBL3HAT1+3, "DBLTRD_HATRIGHT"}, - {KEY_DBL3HAT1+4, "DBLTRD_HATUP2"}, - {KEY_DBL3HAT1+5, "DBLTRD_HATDOWN2"}, - {KEY_DBL3HAT1+6, "DBLTRD_HATLEFT2"}, - {KEY_DBL3HAT1+7, "DBLTRD_HATRIGHT2"}, - {KEY_DBL3HAT1+8, "DBLTRD_HATUP3"}, - {KEY_DBL3HAT1+9, "DBLTRD_HATDOWN3"}, - {KEY_DBL3HAT1+10, "DBLTRD_HATLEFT3"}, - {KEY_DBL3HAT1+11, "DBLTRD_HATRIGHT3"}, - {KEY_DBL3HAT1+12, "DBLTRD_HATUP4"}, - {KEY_DBL3HAT1+13, "DBLTRD_HATDOWN4"}, - {KEY_DBL3HAT1+14, "DBLTRD_HATLEFT4"}, - {KEY_DBL3HAT1+15, "DBLTRD_HATRIGHT4"}, - - {KEY_4JOY1+0, "FOR_JOY1"}, - {KEY_4JOY1+1, "FOR_JOY2"}, - {KEY_4JOY1+2, "FOR_JOY3"}, - {KEY_4JOY1+3, "FOR_JOY4"}, - {KEY_4JOY1+4, "FOR_JOY5"}, - {KEY_4JOY1+5, "FOR_JOY6"}, - {KEY_4JOY1+6, "FOR_JOY7"}, - {KEY_4JOY1+7, "FOR_JOY8"}, - {KEY_4JOY1+8, "FOR_JOY9"}, - {KEY_4JOY1+9, "FOR_JOY10"}, - {KEY_4JOY1+10, "FOR_JOY11"}, - {KEY_4JOY1+11, "FOR_JOY12"}, - {KEY_4JOY1+12, "FOR_JOY13"}, - {KEY_4JOY1+13, "FOR_JOY14"}, - {KEY_4JOY1+14, "FOR_JOY15"}, - {KEY_4JOY1+15, "FOR_JOY16"}, - {KEY_4JOY1+16, "FOR_JOY17"}, - {KEY_4JOY1+17, "FOR_JOY18"}, - {KEY_4JOY1+18, "FOR_JOY19"}, - {KEY_4JOY1+19, "FOR_JOY20"}, - {KEY_4JOY1+20, "FOR_JOY21"}, - {KEY_4JOY1+21, "FOR_JOY22"}, - {KEY_4JOY1+22, "FOR_JOY23"}, - {KEY_4JOY1+23, "FOR_JOY24"}, - {KEY_4JOY1+24, "FOR_JOY25"}, - {KEY_4JOY1+25, "FOR_JOY26"}, - {KEY_4JOY1+26, "FOR_JOY27"}, - {KEY_4JOY1+27, "FOR_JOY28"}, - {KEY_4JOY1+28, "FOR_JOY29"}, - {KEY_4JOY1+29, "FOR_JOY30"}, - {KEY_4JOY1+30, "FOR_JOY31"}, - {KEY_4JOY1+31, "FOR_JOY32"}, - - {KEY_DBL4JOY1+0, "DBLFOR_JOY1"}, - {KEY_DBL4JOY1+1, "DBLFOR_JOY2"}, - {KEY_DBL4JOY1+2, "DBLFOR_JOY3"}, - {KEY_DBL4JOY1+3, "DBLFOR_JOY4"}, - {KEY_DBL4JOY1+4, "DBLFOR_JOY5"}, - {KEY_DBL4JOY1+5, "DBLFOR_JOY6"}, - {KEY_DBL4JOY1+6, "DBLFOR_JOY7"}, - {KEY_DBL4JOY1+7, "DBLFOR_JOY8"}, - {KEY_DBL4JOY1+8, "DBLFOR_JOY9"}, - {KEY_DBL4JOY1+9, "DBLFOR_JOY10"}, - {KEY_DBL4JOY1+10, "DBLFOR_JOY11"}, - {KEY_DBL4JOY1+11, "DBLFOR_JOY12"}, - {KEY_DBL4JOY1+12, "DBLFOR_JOY13"}, - {KEY_DBL4JOY1+13, "DBLFOR_JOY14"}, - {KEY_DBL4JOY1+14, "DBLFOR_JOY15"}, - {KEY_DBL4JOY1+15, "DBLFOR_JOY16"}, - {KEY_DBL4JOY1+16, "DBLFOR_JOY17"}, - {KEY_DBL4JOY1+17, "DBLFOR_JOY18"}, - {KEY_DBL4JOY1+18, "DBLFOR_JOY19"}, - {KEY_DBL4JOY1+19, "DBLFOR_JOY20"}, - {KEY_DBL4JOY1+20, "DBLFOR_JOY21"}, - {KEY_DBL4JOY1+21, "DBLFOR_JOY22"}, - {KEY_DBL4JOY1+22, "DBLFOR_JOY23"}, - {KEY_DBL4JOY1+23, "DBLFOR_JOY24"}, - {KEY_DBL4JOY1+24, "DBLFOR_JOY25"}, - {KEY_DBL4JOY1+25, "DBLFOR_JOY26"}, - {KEY_DBL4JOY1+26, "DBLFOR_JOY27"}, - {KEY_DBL4JOY1+27, "DBLFOR_JOY28"}, - {KEY_DBL4JOY1+28, "DBLFOR_JOY29"}, - {KEY_DBL4JOY1+29, "DBLFOR_JOY30"}, - {KEY_DBL4JOY1+30, "DBLFOR_JOY31"}, - {KEY_DBL4JOY1+31, "DBLFOR_JOY32"}, - - {KEY_4HAT1+0, "FOR_HATUP"}, - {KEY_4HAT1+1, "FOR_HATDOWN"}, - {KEY_4HAT1+2, "FOR_HATLEFT"}, - {KEY_4HAT1+3, "FOR_HATRIGHT"}, - {KEY_4HAT1+4, "FOR_HATUP2"}, - {KEY_4HAT1+5, "FOR_HATDOWN2"}, - {KEY_4HAT1+6, "FOR_HATLEFT2"}, - {KEY_4HAT1+7, "FOR_HATRIGHT2"}, - {KEY_4HAT1+8, "FOR_HATUP3"}, - {KEY_4HAT1+9, "FOR_HATDOWN3"}, - {KEY_4HAT1+10, "FOR_HATLEFT3"}, - {KEY_4HAT1+11, "FOR_HATRIGHT3"}, - {KEY_4HAT1+12, "FOR_HATUP4"}, - {KEY_4HAT1+13, "FOR_HATDOWN4"}, - {KEY_4HAT1+14, "FOR_HATLEFT4"}, - {KEY_4HAT1+15, "FOR_HATRIGHT4"}, - - {KEY_DBL4HAT1+0, "DBLFOR_HATUP"}, - {KEY_DBL4HAT1+1, "DBLFOR_HATDOWN"}, - {KEY_DBL4HAT1+2, "DBLFOR_HATLEFT"}, - {KEY_DBL4HAT1+3, "DBLFOR_HATRIGHT"}, - {KEY_DBL4HAT1+4, "DBLFOR_HATUP2"}, - {KEY_DBL4HAT1+5, "DBLFOR_HATDOWN2"}, - {KEY_DBL4HAT1+6, "DBLFOR_HATLEFT2"}, - {KEY_DBL4HAT1+7, "DBLFOR_HATRIGHT2"}, - {KEY_DBL4HAT1+8, "DBLFOR_HATUP3"}, - {KEY_DBL4HAT1+9, "DBLFOR_HATDOWN3"}, - {KEY_DBL4HAT1+10, "DBLFOR_HATLEFT3"}, - {KEY_DBL4HAT1+11, "DBLFOR_HATRIGHT3"}, - {KEY_DBL4HAT1+12, "DBLFOR_HATUP4"}, - {KEY_DBL4HAT1+13, "DBLFOR_HATDOWN4"}, - {KEY_DBL4HAT1+14, "DBLFOR_HATLEFT4"}, - {KEY_DBL4HAT1+15, "DBLFOR_HATRIGHT4"}, + {KEY_JOY1+0, "A BUTTON"}, + {KEY_JOY1+1, "B BUTTON"}, + {KEY_JOY1+2, "X BUTTON"}, + {KEY_JOY1+3, "Y BUTTON"}, + {KEY_JOY1+4, "BACK BUTTON"}, + {KEY_JOY1+5, "GUIDE BUTTON"}, + {KEY_JOY1+6, "START BUTTON"}, + {KEY_JOY1+7, "L-STICK CLICK"}, + {KEY_JOY1+8, "R-STICK CLICK"}, + {KEY_JOY1+9, "L BUMPER"}, + {KEY_JOY1+10, "R BUMPER"}, + {KEY_JOY1+11, "D-PAD UP"}, + {KEY_JOY1+12, "D-PAD DOWN"}, + {KEY_JOY1+13, "D-PAD LEFT"}, + {KEY_JOY1+14, "D-PAD RIGHT"}, + {KEY_JOY1+15, "MISC. BUTTON"}, + {KEY_JOY1+16, "PADDLE1 BUTTON"}, + {KEY_JOY1+17, "PADDLE2 BUTTON"}, + {KEY_JOY1+18, "PADDLE3 BUTTON"}, + {KEY_JOY1+19, "PADDLE4 BUTTON"}, + {KEY_JOY1+20, "TOUCHPAD"}, + {KEY_AXIS1+0, "L-STICK LEFT"}, + {KEY_AXIS1+1, "L-STICK RIGHT"}, + {KEY_AXIS1+2, "L-STICK UP"}, + {KEY_AXIS1+3, "L-STICK DOWN"}, + {KEY_AXIS1+4, "R-STICK LEFT"}, + {KEY_AXIS1+5, "R-STICK RIGHT"}, + {KEY_AXIS1+6, "R-STICK UP"}, + {KEY_AXIS1+7, "R-STICK DOWN"}, + {KEY_AXIS1+8, "L TRIGGER"}, + {KEY_AXIS1+9, "R TRIGGER"}, }; static const char *gamecontrolname[num_gamecontrols] = { - "nothing", // a key/button mapped to gc_null has no effect - "aimforward", - "aimbackward", - "turnleft", - "turnright", - "accelerate", - "drift", - "brake", - "spindash", - "fire", - "lookback", - "camreset", - "camtoggle", - "spectate", - "lookup", - "lookdown", - "centerview", - "talkkey", - "teamtalkkey", - "scores", + "null", // a key/button mapped to gc_null has no effect + "up", + "down", + "left", + "right", + "a", + "b", + "c", + "x", + "y", + "z", + "l", + "r", + "start", + "abc", + "luaa", + "luab", + "luac", "console", - "pause", - "systemmenu", + "talk", + "teamtalk", "screenshot", "recordgif", - "viewpoint", - "custom1", - "custom2", - "custom3", }; #define NUMKEYNAMES (sizeof (keynames)/sizeof (keyname_t)) +// If keybind is necessary to navigate menus, it's on this list. +boolean G_KeyBindIsNecessary(INT32 gc) +{ + switch (gc) + { + case gc_a: + case gc_b: + case gc_up: + case gc_down: + case gc_left: + case gc_right: + case gc_start: + return true; + default: + return false; + } + return false; +} + +// Returns false if a key is deemed unreachable for this device. +boolean G_KeyIsAvailable(INT32 key, INT32 deviceID) +{ + // Invalid key number. + if (key <= 0 || key >= NUMINPUTS) + { + return false; + } + + // Valid controller-specific virtual key, but no controller attached for player. + if (key >= KEY_JOY1 && key < JOYINPUTEND && deviceID <= 0) + { + return false; + } + + // Valid mouse-specific virtual key, but no mouse attached for player. TODO HOW TO DETECT ACTIVE MOUSE CONNECTION + /* + if (key >= KEY_MOUSE1 && key < MOUSEINPUTEND && ????????) + { + return false; + } + */ + + return true; +} + // // Detach any keys associated to the given game control // - pass the pointer to the gamecontrol table for the player being edited -void G_ClearControlKeys(INT32 (*setupcontrols)[2], INT32 control) +void G_ClearControlKeys(INT32 (*setupcontrols)[MAXINPUTMAPPING], INT32 control) { - setupcontrols[control][0] = KEY_NULL; - setupcontrols[control][1] = KEY_NULL; + INT32 i; + for (i = 0; i < MAXINPUTMAPPING; i++) + { + setupcontrols[control][i] = KEY_NULL; + } } void G_ClearAllControlKeys(void) { INT32 i, j; - for (i = 0; i < num_gamecontrols; i++) + for (j = 0; j < MAXSPLITSCREENPLAYERS; j++) { - for (j = 0; j < MAXSPLITSCREENPLAYERS; j++) + for (i = 0; i < num_gamecontrols; i++) + { G_ClearControlKeys(gamecontrol[j], i); + } } } @@ -844,121 +520,79 @@ INT32 G_KeyStringtoNum(const char *keystr) void G_DefineDefaultControls(void) { - INT32 i; - + // These defaults are bad & temporary. // Keyboard controls - gamecontroldefault[0][gcs_kart][gc_aimforward ][0] = KEY_UPARROW; - gamecontroldefault[0][gcs_kart][gc_aimbackward][0] = KEY_DOWNARROW; - gamecontroldefault[0][gcs_kart][gc_turnleft ][0] = KEY_LEFTARROW; - gamecontroldefault[0][gcs_kart][gc_turnright ][0] = KEY_RIGHTARROW; - gamecontroldefault[0][gcs_kart][gc_accelerate ][0] = 'a'; - gamecontroldefault[0][gcs_kart][gc_drift ][0] = 's'; - gamecontroldefault[0][gcs_kart][gc_brake ][0] = 'd'; - gamecontroldefault[0][gcs_kart][gc_fire ][0] = KEY_SPACE; - gamecontroldefault[0][gcs_kart][gc_lookback ][0] = KEY_LSHIFT; + gamecontroldefault[gc_up ][0] = KEY_UPARROW; + gamecontroldefault[gc_down ][0] = KEY_DOWNARROW; + gamecontroldefault[gc_left ][0] = KEY_LEFTARROW; + gamecontroldefault[gc_right][0] = KEY_RIGHTARROW; + gamecontroldefault[gc_a ][0] = 'z'; + gamecontroldefault[gc_b ][0] = 'x'; + gamecontroldefault[gc_c ][0] = 'c'; + gamecontroldefault[gc_x ][0] = 'a'; + gamecontroldefault[gc_y ][0] = 's'; + gamecontroldefault[gc_z ][0] = 'd'; + gamecontroldefault[gc_l ][0] = 'q'; + gamecontroldefault[gc_r ][0] = 'e'; + gamecontroldefault[gc_start][0] = KEY_ESCAPE; // * - gamecontroldefault[0][gcs_kart][gc_pause ][0] = KEY_PAUSE; - gamecontroldefault[0][gcs_kart][gc_console ][0] = KEY_CONSOLE; - gamecontroldefault[0][gcs_kart][gc_screenshot ][0] = KEY_F8; - gamecontroldefault[0][gcs_kart][gc_recordgif ][0] = KEY_F9; - gamecontroldefault[0][gcs_kart][gc_viewpoint ][0] = KEY_F12; - gamecontroldefault[0][gcs_kart][gc_talkkey ][0] = 't'; - //gamecontroldefault[0][gcs_kart][gc_teamkey ][0] = 'y'; - gamecontroldefault[0][gcs_kart][gc_scores ][0] = KEY_TAB; - gamecontroldefault[0][gcs_kart][gc_spectate ][0] = '\''; - gamecontroldefault[0][gcs_kart][gc_lookup ][0] = KEY_PGUP; - gamecontroldefault[0][gcs_kart][gc_lookdown ][0] = KEY_PGDN; - gamecontroldefault[0][gcs_kart][gc_centerview ][0] = KEY_END; - gamecontroldefault[0][gcs_kart][gc_camreset ][0] = KEY_HOME; - gamecontroldefault[0][gcs_kart][gc_camtoggle ][0] = KEY_BACKSPACE; + // Gamepad controls + gamecontroldefault[gc_up ][1] = KEY_HAT1+0; // D-Pad Up + gamecontroldefault[gc_down ][1] = KEY_HAT1+1; // D-Pad Down + gamecontroldefault[gc_left ][1] = KEY_HAT1+2; // D-Pad Left + gamecontroldefault[gc_right][1] = KEY_HAT1+3; // D-Pad Right + gamecontroldefault[gc_a ][1] = KEY_JOY1+0; // A + gamecontroldefault[gc_b ][1] = KEY_JOY1+2; // X + gamecontroldefault[gc_c ][1] = KEY_JOY1+3; // Y + gamecontroldefault[gc_x ][1] = KEY_JOY1+1; // B + gamecontroldefault[gc_y ][1] = KEY_JOY1+6; + gamecontroldefault[gc_z ][1] = KEY_JOY1+8; + gamecontroldefault[gc_l ][1] = KEY_JOY1+4; // LB + gamecontroldefault[gc_r ][1] = KEY_JOY1+5; // RB + gamecontroldefault[gc_start][1] = KEY_JOY1+7; // Start - for (i = gcs_custom+1; i < num_gamecontrolschemes; i++) // skip gcs_custom - { - // Gamepad controls -- same for all schemes - gamecontroldefault[0][i][gc_accelerate ][1] = KEY_JOY1+0; // A - gamecontroldefault[0][i][gc_lookback ][1] = KEY_JOY1+2; // X - gamecontroldefault[0][i][gc_brake ][1] = KEY_JOY1+1; // B - gamecontroldefault[0][i][gc_fire ][1] = KEY_JOY1+4; // LB - gamecontroldefault[0][i][gc_drift ][1] = KEY_JOY1+5; // RB - - gamecontroldefault[0][i][gc_viewpoint ][1] = KEY_JOY1+3; // Y - gamecontroldefault[0][i][gc_pause ][1] = KEY_JOY1+6; // Back - gamecontroldefault[0][i][gc_systemmenu ][0] = KEY_JOY1+7; // Start - gamecontroldefault[0][i][gc_talkkey ][1] = KEY_HAT1+1; // D-Pad Down - gamecontroldefault[0][i][gc_scores ][1] = KEY_HAT1+0; // D-Pad Up - - gamecontroldefault[1][i][gc_accelerate ][0] = KEY_2JOY1+0; // A - gamecontroldefault[1][i][gc_lookback ][0] = KEY_2JOY1+2; // X - gamecontroldefault[1][i][gc_brake ][0] = KEY_2JOY1+1; // B - gamecontroldefault[1][i][gc_fire ][0] = KEY_2JOY1+4; // LB - gamecontroldefault[1][i][gc_drift ][0] = KEY_2JOY1+5; // RB - - gamecontroldefault[2][i][gc_accelerate ][0] = KEY_3JOY1+0; // A - gamecontroldefault[2][i][gc_lookback ][0] = KEY_3JOY1+2; // X - gamecontroldefault[2][i][gc_brake ][0] = KEY_3JOY1+1; // B - gamecontroldefault[2][i][gc_fire ][0] = KEY_3JOY1+4; // LB - gamecontroldefault[2][i][gc_drift ][0] = KEY_3JOY1+5; // RB - - gamecontroldefault[3][i][gc_accelerate ][0] = KEY_3JOY1+0; // A - gamecontroldefault[3][i][gc_lookback ][0] = KEY_3JOY1+2; // X - gamecontroldefault[3][i][gc_brake ][0] = KEY_3JOY1+1; // B - gamecontroldefault[3][i][gc_fire ][0] = KEY_3JOY1+4; // LB - gamecontroldefault[3][i][gc_drift ][0] = KEY_3JOY1+5; // RB - } + gamecontroldefault[gc_up ][2] = KEY_AXIS1+2; // Axis Y- + gamecontroldefault[gc_down ][2] = KEY_AXIS1+3; // Axis Y+ + gamecontroldefault[gc_left ][2] = KEY_AXIS1+0; // Axis X- + gamecontroldefault[gc_right][2] = KEY_AXIS1+1; // Axis X+ } -INT32 G_GetControlScheme(INT32 (*fromcontrols)[2], const INT32 *gclist, INT32 gclen) +void G_CopyControls(INT32 (*setupcontrols)[MAXINPUTMAPPING], INT32 (*fromcontrols)[MAXINPUTMAPPING], const INT32 *gclist, INT32 gclen) { INT32 i, j, gc; - boolean skipscheme; - - for (i = 1; i < num_gamecontrolschemes; i++) // skip gcs_custom (0) - { - skipscheme = false; - for (j = 0; j < (gclist && gclen ? gclen : num_gamecontrols); j++) - { - gc = (gclist && gclen) ? gclist[j] : j; - if (((fromcontrols[gc][0] && gamecontroldefault[0][i][gc][0]) ? fromcontrols[gc][0] != gamecontroldefault[0][i][gc][0] : true) && - ((fromcontrols[gc][0] && gamecontroldefault[0][i][gc][1]) ? fromcontrols[gc][0] != gamecontroldefault[0][i][gc][1] : true) && - ((fromcontrols[gc][1] && gamecontroldefault[0][i][gc][0]) ? fromcontrols[gc][1] != gamecontroldefault[0][i][gc][0] : true) && - ((fromcontrols[gc][1] && gamecontroldefault[0][i][gc][1]) ? fromcontrols[gc][1] != gamecontroldefault[0][i][gc][1] : true)) - { - skipscheme = true; - break; - } - } - if (!skipscheme) - return i; - } - - return gcs_custom; -} - -void G_CopyControls(INT32 (*setupcontrols)[2], INT32 (*fromcontrols)[2], const INT32 *gclist, INT32 gclen) -{ - INT32 i, gc; for (i = 0; i < (gclist && gclen ? gclen : num_gamecontrols); i++) { gc = (gclist && gclen) ? gclist[i] : i; - setupcontrols[gc][0] = fromcontrols[gc][0]; - setupcontrols[gc][1] = fromcontrols[gc][1]; + + for (j = 0; j < MAXINPUTMAPPING; j++) + { + setupcontrols[gc][j] = fromcontrols[gc][j]; + } } } -void G_SaveKeySetting(FILE *f, INT32 (*fromcontrolsa)[2], INT32 (*fromcontrolsb)[2], INT32 (*fromcontrolsc)[2], INT32 (*fromcontrolsd)[2]) +void G_SaveKeySetting(FILE *f, INT32 (*fromcontrolsa)[MAXINPUTMAPPING], INT32 (*fromcontrolsb)[MAXINPUTMAPPING], INT32 (*fromcontrolsc)[MAXINPUTMAPPING], INT32 (*fromcontrolsd)[MAXINPUTMAPPING]) { - INT32 i; + INT32 i, j; + // TODO: would be nice to get rid of this code duplication for (i = 1; i < num_gamecontrols; i++) { - fprintf(f, "setcontrol \"%s\" \"%s\"", gamecontrolname[i], - G_KeynumToString(fromcontrolsa[i][0])); + fprintf(f, "setcontrol \"%s\" \"%s\"", gamecontrolname[i], G_KeynumToString(fromcontrolsa[i][0])); - if (fromcontrolsa[i][1]) - fprintf(f, " \"%s\"\n", G_KeynumToString(fromcontrolsa[i][1])); - else - fprintf(f, "\n"); + for (j = 1; j < MAXINPUTMAPPING+1; j++) + { + if (j < MAXINPUTMAPPING && fromcontrolsa[i][j]) + { + fprintf(f, " \"%s\"", G_KeynumToString(fromcontrolsa[i][j])); + } + else + { + fprintf(f, "\n"); + break; + } + } } for (i = 1; i < num_gamecontrols; i++) @@ -966,10 +600,18 @@ void G_SaveKeySetting(FILE *f, INT32 (*fromcontrolsa)[2], INT32 (*fromcontrolsb) fprintf(f, "setcontrol2 \"%s\" \"%s\"", gamecontrolname[i], G_KeynumToString(fromcontrolsb[i][0])); - if (fromcontrolsb[i][1]) - fprintf(f, " \"%s\"\n", G_KeynumToString(fromcontrolsb[i][1])); - else - fprintf(f, "\n"); + for (j = 1; j < MAXINPUTMAPPING+1; j++) + { + if (j < MAXINPUTMAPPING && fromcontrolsb[i][j]) + { + fprintf(f, " \"%s\"", G_KeynumToString(fromcontrolsb[i][j])); + } + else + { + fprintf(f, "\n"); + break; + } + } } for (i = 1; i < num_gamecontrols; i++) @@ -977,10 +619,18 @@ void G_SaveKeySetting(FILE *f, INT32 (*fromcontrolsa)[2], INT32 (*fromcontrolsb) fprintf(f, "setcontrol3 \"%s\" \"%s\"", gamecontrolname[i], G_KeynumToString(fromcontrolsc[i][0])); - if (fromcontrolsc[i][1]) - fprintf(f, " \"%s\"\n", G_KeynumToString(fromcontrolsc[i][1])); - else - fprintf(f, "\n"); + for (j = 1; j < MAXINPUTMAPPING+1; j++) + { + if (j < MAXINPUTMAPPING && fromcontrolsc[i][j]) + { + fprintf(f, " \"%s\"", G_KeynumToString(fromcontrolsc[i][j])); + } + else + { + fprintf(f, "\n"); + break; + } + } } for (i = 1; i < num_gamecontrols; i++) @@ -988,28 +638,38 @@ void G_SaveKeySetting(FILE *f, INT32 (*fromcontrolsa)[2], INT32 (*fromcontrolsb) fprintf(f, "setcontrol4 \"%s\" \"%s\"", gamecontrolname[i], G_KeynumToString(fromcontrolsd[i][0])); - if (fromcontrolsd[i][1]) - fprintf(f, " \"%s\"\n", G_KeynumToString(fromcontrolsd[i][1])); - else - fprintf(f, "\n"); + for (j = 1; j < MAXINPUTMAPPING+1; j++) + { + if (j < MAXINPUTMAPPING && fromcontrolsd[i][j]) + { + fprintf(f, " \"%s\"", G_KeynumToString(fromcontrolsd[i][j])); + } + else + { + fprintf(f, "\n"); + break; + } + } } } -INT32 G_CheckDoubleUsage(INT32 keynum, boolean modify) +INT32 G_CheckDoubleUsage(INT32 keynum, INT32 playernum, boolean modify) { INT32 result = gc_null; + if (cv_controlperkey.value == 1) { - INT32 i, j, k; + INT32 i, j; for (i = 0; i < num_gamecontrols; i++) { - for (j = 0; j < 2; j++) + for (j = 0; j < MAXINPUTMAPPING; j++) { - for (k = 0; k < MAXSPLITSCREENPLAYERS; k++) + if (gamecontrol[playernum][i][j] == keynum) { - if (gamecontrol[k][i][j] == keynum) { - result = i; - if (modify) gamecontrol[k][i][j] = KEY_NULL; + result = i; + if (modify) + { + gamecontrol[playernum][i][j] = KEY_NULL; } } @@ -1018,191 +678,68 @@ INT32 G_CheckDoubleUsage(INT32 keynum, boolean modify) } } } + return result; } -static INT32 G_FilterKeyByVersion(INT32 numctrl, INT32 keyidx, INT32 player, INT32 *keynum1, INT32 *keynum2, boolean *nestedoverride) -{ - // Special case: ignore KEY_PAUSE because it's hardcoded - if (keyidx == 0 && *keynum1 == KEY_PAUSE) - { - if (*keynum2 != KEY_PAUSE) - { - *keynum1 = *keynum2; // shift down keynum2 and continue - *keynum2 = 0; - } - else - return -1; // skip setting control - } - else if (keyidx == 1 && *keynum2 == KEY_PAUSE) - return -1; // skip setting control - -#if 1 - // We don't have changed control defaults yet - (void)numctrl; - (void)player; - (void)nestedoverride; -#else - if (GETMAJOREXECVERSION(cv_execversion.value) < 27 && ( // v2.1.22 - numctrl == gc_weaponnext || numctrl == gc_weaponprev || numctrl == gc_tossflag || - numctrl == gc_spin || numctrl == gc_camreset || numctrl == gc_jump || - numctrl == gc_pause || numctrl == gc_systemmenu || numctrl == gc_camtoggle || - numctrl == gc_screenshot || numctrl == gc_talkkey || numctrl == gc_scores || - numctrl == gc_centerview - )) - { - INT32 keynum = 0, existingctrl = 0; - INT32 defaultkey; - boolean defaultoverride = false; - - // get the default gamecontrol - defaultkey = gamecontrol[player][numctrl][0]; - - // Assign joypad button defaults if there is an open slot. - // At this point, gamecontrol should have the default controls - // (unless LOADCONFIG is being run) - // - // If the player runs SETCONTROL in-game, this block should not be reached - // because EXECVERSION is locked onto the latest version. - if (keyidx == 0 && !*keynum1) - { - if (*keynum2) // push keynum2 down; this is an edge case - { - *keynum1 = *keynum2; - *keynum2 = 0; - keynum = *keynum1; - } - else - { - keynum = defaultkey; - defaultoverride = true; - } - } - else if (keyidx == 1 && (!*keynum2 || (!*keynum1 && *keynum2))) // last one is the same edge case as above - { - keynum = defaultkey; - defaultoverride = true; - } - else // default to the specified keynum - keynum = (keyidx == 1 ? *keynum2 : *keynum1); - - // Did our last call override keynum2? - if (*nestedoverride) - { - defaultoverride = true; - *nestedoverride = false; - } - - // Fill keynum2 with the default control - if (keyidx == 0 && !*keynum2) - { - *keynum2 = defaultkey; - // Tell the next call that this is an override - *nestedoverride = true; - - // if keynum2 already matches keynum1, we probably recursed - // so unset it - if (*keynum1 == *keynum2) - { - *keynum2 = 0; - *nestedoverride = false; - } - } - - // check if the key is being used somewhere else before passing it - // pass it through if it's the same numctrl. This is an edge case -- when using - // LOADCONFIG, gamecontrol is not reset with default. - // - // Also, only check if we're actually overriding, to preserve behavior where - // config'd keys overwrite default keys. - if (defaultoverride) - existingctrl = G_CheckDoubleUsage(keynum, false); - - if (keynum && (!existingctrl || existingctrl == numctrl)) - return keynum; - else if (keyidx == 0 && *keynum2) - { - // try it again and push down keynum2 - *keynum1 = *keynum2; - *keynum2 = 0; - return G_FilterKeyByVersion(numctrl, keyidx, player, keynum1, keynum2, nestedoverride); - // recursion *should* be safe because we only assign keynum2 to a joy default - // and then clear it if we find that keynum1 already has the joy default. - } - else - return 0; - } -#endif - - // All's good, so pass the keynum as-is - if (keyidx == 1) - return *keynum2; - else //if (keyidx == 0) - return *keynum1; -} - -static void setcontrol(INT32 (*gc)[2]) +static void setcontrol(UINT8 player) { INT32 numctrl; const char *namectrl; - INT32 keynum, keynum1, keynum2; - INT32 player; - boolean nestedoverride = false; - - if ((void*)gc == (void*)&gamecontrol[3]) - player = 3; - else if ((void*)gc == (void*)&gamecontrol[2]) - player = 2; - else if ((void*)gc == (void*)&gamecontrol[1]) - player = 1; - else - player = 0; + INT32 keynum; + INT32 inputMap = 0; + INT32 i; namectrl = COM_Argv(1); - for (numctrl = 0; numctrl < num_gamecontrols && stricmp(namectrl, gamecontrolname[numctrl]); + for (numctrl = 0; + numctrl < num_gamecontrols && stricmp(namectrl, gamecontrolname[numctrl]); numctrl++) - ; + { ; } + if (numctrl == num_gamecontrols) { CONS_Printf(M_GetText("Control '%s' unknown\n"), namectrl); return; } - keynum1 = G_KeyStringtoNum(COM_Argv(2)); - keynum2 = G_KeyStringtoNum(COM_Argv(3)); - keynum = G_FilterKeyByVersion(numctrl, 0, player, &keynum1, &keynum2, &nestedoverride); - if (keynum >= 0) + for (i = 0; i < MAXINPUTMAPPING; i++) { - (void)G_CheckDoubleUsage(keynum, true); + keynum = G_KeyStringtoNum(COM_Argv(inputMap + 2)); - // if keynum was rejected, try it again with keynum2 - if (!keynum && keynum2) - { - keynum1 = keynum2; // push down keynum2 - keynum2 = 0; - keynum = G_FilterKeyByVersion(numctrl, 0, player, &keynum1, &keynum2, &nestedoverride); - if (keynum >= 0) - (void)G_CheckDoubleUsage(keynum, true); - } - } - - if (keynum >= 0) - gc[numctrl][0] = keynum; - - if (keynum2) - { - keynum = G_FilterKeyByVersion(numctrl, 1, player, &keynum1, &keynum2, &nestedoverride); if (keynum >= 0) { - if (keynum != gc[numctrl][0]) - gc[numctrl][1] = keynum; - else - gc[numctrl][1] = 0; + (void)G_CheckDoubleUsage(keynum, player, true); + + // if keynum was rejected, try it again with the next key. + while (keynum == 0) + { + inputMap++; + if (inputMap >= MAXINPUTMAPPING) + { + break; + } + + keynum = G_KeyStringtoNum(COM_Argv(inputMap + 2)); + + if (keynum >= 0) + { + (void)G_CheckDoubleUsage(keynum, player, true); + } + } + } + + if (keynum >= 0) + { + gamecontrol[player][numctrl][i] = keynum; + } + + inputMap++; + if (inputMap >= MAXINPUTMAPPING) + { + break; } } - else - gc[numctrl][1] = 0; } void Command_Setcontrol_f(void) @@ -1211,13 +748,13 @@ void Command_Setcontrol_f(void) na = (INT32)COM_Argc(); - if (na != 3 && na != 4) + if (na < 3 || na > MAXINPUTMAPPING+2) { - CONS_Printf(M_GetText("setcontrol [<2nd keyname>]: set controls for player 1\n")); + CONS_Printf(M_GetText("setcontrol [] [] []: set controls for player 1\n")); return; } - setcontrol(gamecontrol[0]); + setcontrol(0); } void Command_Setcontrol2_f(void) @@ -1226,13 +763,13 @@ void Command_Setcontrol2_f(void) na = (INT32)COM_Argc(); - if (na != 3 && na != 4) + if (na < 3 || na > MAXINPUTMAPPING+2) { - CONS_Printf(M_GetText("setcontrol2 [<2nd keyname>]: set controls for player 2\n")); + CONS_Printf(M_GetText("setcontrol2 [] [] []: set controls for player 2\n")); return; } - setcontrol(gamecontrol[1]); + setcontrol(1); } void Command_Setcontrol3_f(void) @@ -1241,13 +778,13 @@ void Command_Setcontrol3_f(void) na = (INT32)COM_Argc(); - if (na != 3 && na != 4) + if (na < 3 || na > MAXINPUTMAPPING+2) { - CONS_Printf(M_GetText("setcontrol3 [<2nd keyname>]: set controls for player 3\n")); + CONS_Printf(M_GetText("setcontrol3 [] [] []: set controls for player 3\n")); return; } - setcontrol(gamecontrol[2]); + setcontrol(2); } void Command_Setcontrol4_f(void) @@ -1256,11 +793,11 @@ void Command_Setcontrol4_f(void) na = (INT32)COM_Argc(); - if (na != 3 && na != 4) + if (na < 3 || na > MAXINPUTMAPPING+2) { - CONS_Printf(M_GetText("setcontrol4 [<2nd keyname>]: set controls for player 4\n")); + CONS_Printf(M_GetText("setcontrol4 [] [] []: set controls for player 4\n")); return; } - setcontrol(gamecontrol[3]); + setcontrol(3); } diff --git a/src/g_input.h b/src/g_input.h index 57941d6c1..14aa5aba3 100644 --- a/src/g_input.h +++ b/src/g_input.h @@ -23,110 +23,93 @@ #define NUMKEYS 256 #define MOUSEBUTTONS 8 -#define JOYBUTTONS 32 // 32 buttons -#define JOYHATS 4 // 4 hats -#define JOYAXISSET 4 // 4 Sets of 2 axises + +#define JOYBUTTONS 21 // 21 buttons, to match SDL_GameControllerButton +#define JOYANALOGS 2 // 2 sets of analog stick axes, with positive and negative each +#define JOYTRIGGERS 1 // 1 set of trigger axes, positive only +#define JOYAXISSETS (JOYANALOGS + JOYTRIGGERS) +#define JOYAXES ((4 * JOYANALOGS) + (2 * JOYTRIGGERS)) + +#define MAXINPUTMAPPING 4 // // mouse and joystick buttons are handled as 'virtual' keys // typedef enum { - KEY_MOUSE1 = NUMKEYS, - KEY_JOY1 = KEY_MOUSE1 + MOUSEBUTTONS, - KEY_HAT1 = KEY_JOY1 + JOYBUTTONS, + KEY_JOY1 = NUMKEYS, + KEY_HAT1 = KEY_JOY1 + 11, // macro for SDL_CONTROLLER_BUTTON_DPAD_UP + KEY_AXIS1 = KEY_JOY1 + JOYBUTTONS, + JOYINPUTEND = KEY_AXIS1 + JOYAXES, - KEY_DBLMOUSE1 =KEY_HAT1 + JOYHATS*4, // double clicks - KEY_DBLJOY1 = KEY_DBLMOUSE1 + MOUSEBUTTONS, - KEY_DBLHAT1 = KEY_DBLJOY1 + JOYBUTTONS, - - KEY_2MOUSE1 = KEY_DBLHAT1 + JOYHATS*4, - KEY_2JOY1 = KEY_2MOUSE1 + MOUSEBUTTONS, - KEY_2HAT1 = KEY_2JOY1 + JOYBUTTONS, - - KEY_DBL2MOUSE1 = KEY_2HAT1 + JOYHATS*4, - KEY_DBL2JOY1 = KEY_DBL2MOUSE1 + MOUSEBUTTONS, - KEY_DBL2HAT1 = KEY_DBL2JOY1 + JOYBUTTONS, - - KEY_3JOY1 = KEY_DBL2HAT1 + JOYHATS*4, - KEY_3HAT1 = KEY_3JOY1 + JOYBUTTONS, - - KEY_DBL3JOY1 = KEY_3HAT1 + JOYHATS*4, - KEY_DBL3HAT1 = KEY_DBL3JOY1 + JOYBUTTONS, - - KEY_4JOY1 = KEY_DBL3HAT1 + JOYHATS*4, - KEY_4HAT1 = KEY_4JOY1 + JOYBUTTONS, - - KEY_DBL4JOY1 = KEY_4HAT1 + JOYHATS*4, - KEY_DBL4HAT1 = KEY_DBL4JOY1 + JOYBUTTONS, - - KEY_MOUSEWHEELUP = KEY_DBL4HAT1 + JOYHATS*4, + KEY_MOUSE1 = JOYINPUTEND, + KEY_MOUSEMOVE = KEY_MOUSE1 + MOUSEBUTTONS, + KEY_MOUSEWHEELUP = KEY_MOUSEMOVE + 4, KEY_MOUSEWHEELDOWN = KEY_MOUSEWHEELUP + 1, - KEY_2MOUSEWHEELUP = KEY_MOUSEWHEELDOWN + 1, - KEY_2MOUSEWHEELDOWN = KEY_2MOUSEWHEELUP + 1, + MOUSEINPUTEND = KEY_MOUSEWHEELDOWN + 1, - NUMINPUTS = KEY_2MOUSEWHEELDOWN + 1, + NUMINPUTS = MOUSEINPUTEND, } key_input_e; typedef enum { gc_null = 0, // a key/button mapped to gc_null has no effect - gc_aimforward, - gc_aimbackward, - gc_turnleft, - gc_turnright, - gc_accelerate, - gc_drift, - gc_brake, - gc_spindash, - gc_fire, - gc_lookback, - gc_camreset, - gc_camtoggle, - gc_spectate, - gc_lookup, - gc_lookdown, - gc_centerview, - gc_talkkey, - gc_teamkey, - gc_scores, + + // The actual gamepad + gc_up, + gc_down, + gc_left, + gc_right, + gc_a, + gc_b, + gc_c, + gc_x, + gc_y, + gc_z, + gc_l, + gc_r, + gc_start, + + // special keys + gc_abc, + gc_luaa, + gc_luab, + gc_luac, gc_console, - gc_pause, - gc_systemmenu, + gc_talk, + gc_teamtalk, gc_screenshot, gc_recordgif, - gc_viewpoint, - gc_custom1, // Lua scriptable - gc_custom2, // Lua scriptable - gc_custom3, // Lua scriptable - num_gamecontrols -} gamecontrols_e; -typedef enum -{ - gcs_custom, - gcs_kart, // Kart doesn't really need this code, like, at all? But I don't feel like removing it. - num_gamecontrolschemes -} gamecontrolschemes_e; + num_gamecontrols, + + // alias gameplay controls + gc_accel = gc_a, + gc_brake = gc_x, + gc_drift = gc_r, + + gc_item = gc_l, + gc_spindash = gc_c, + + gc_lookback = gc_b, +} gamecontrols_e; // mouse values are used once extern consvar_t cv_mousesens, cv_mouseysens; extern consvar_t cv_mousesens2, cv_mouseysens2; extern consvar_t cv_controlperkey; -extern INT32 mousex, mousey; -extern INT32 mlooky; //mousey with mlookSensitivity +// current state of the keys: JOYAXISRANGE or 0 when boolean. +// Or anything inbetween for analog values +#define MAXDEVICES (MAXGAMEPADS + 1) // Gamepads + keyboard & mouse +extern INT32 gamekeydown[MAXDEVICES][NUMINPUTS]; +extern boolean deviceResponding[MAXDEVICES]; -extern INT32 joyxmove[MAXSPLITSCREENPLAYERS][JOYAXISSET], joyymove[MAXSPLITSCREENPLAYERS][JOYAXISSET]; - -// current state of the keys: true if pushed -extern UINT8 gamekeydown[NUMINPUTS]; - -// two key codes (or virtual key) per game control -extern INT32 gamecontrol[MAXSPLITSCREENPLAYERS][num_gamecontrols][2]; -extern INT32 gamecontroldefault[MAXSPLITSCREENPLAYERS][num_gamecontrolschemes][num_gamecontrols][2]; // default control storage, use 0 (gcs_custom) for memory retention -#define PlayerInputDown(p, gc) (gamekeydown[gamecontrol[p-1][gc][0]] || gamekeydown[gamecontrol[p-1][gc][1]]) +// several key codes (or virtual key) per game control +extern INT32 gamecontrol[MAXSPLITSCREENPLAYERS][num_gamecontrols][MAXINPUTMAPPING]; +extern INT32 gamecontroldefault[num_gamecontrols][MAXINPUTMAPPING]; // default control storage +/* #define num_gcl_accelerate 1 #define num_gcl_brake 1 #define num_gcl_drift 1 @@ -142,10 +125,13 @@ extern const INT32 gcl_spindash[num_gcl_spindash]; extern const INT32 gcl_movement[num_gcl_movement]; extern const INT32 gcl_item[num_gcl_item]; extern const INT32 gcl_full[num_gcl_full]; +*/ // peace to my little coder fingers! // check a gamecontrol being active or not +INT32 G_GetDevicePlayer(INT32 deviceID); + // remaps the input event to a game control. void G_MapEventsToControls(event_t *ev); @@ -153,17 +139,20 @@ void G_MapEventsToControls(event_t *ev); const char *G_KeynumToString(INT32 keynum); INT32 G_KeyStringtoNum(const char *keystr); +boolean G_KeyBindIsNecessary(INT32 gc); +boolean G_KeyIsAvailable(INT32 key, INT32 deviceID); + // detach any keys associated to the given game control -void G_ClearControlKeys(INT32 (*setupcontrols)[2], INT32 control); +void G_ClearControlKeys(INT32 (*setupcontrols)[MAXINPUTMAPPING], INT32 control); void G_ClearAllControlKeys(void); void Command_Setcontrol_f(void); void Command_Setcontrol2_f(void); void Command_Setcontrol3_f(void); void Command_Setcontrol4_f(void); void G_DefineDefaultControls(void); -INT32 G_GetControlScheme(INT32 (*fromcontrols)[2], const INT32 *gclist, INT32 gclen); -void G_CopyControls(INT32 (*setupcontrols)[2], INT32 (*fromcontrols)[2], const INT32 *gclist, INT32 gclen); -void G_SaveKeySetting(FILE *f, INT32 (*fromcontrolsa)[2], INT32 (*fromcontrolsb)[2], INT32 (*fromcontrolsc)[2], INT32 (*fromcontrolsd)[2]); -INT32 G_CheckDoubleUsage(INT32 keynum, boolean modify); +INT32 G_GetControlScheme(INT32 (*fromcontrols)[MAXINPUTMAPPING], const INT32 *gclist, INT32 gclen); +void G_CopyControls(INT32 (*setupcontrols)[MAXINPUTMAPPING], INT32 (*fromcontrols)[MAXINPUTMAPPING], const INT32 *gclist, INT32 gclen); +void G_SaveKeySetting(FILE *f, INT32 (*fromcontrolsa)[MAXINPUTMAPPING], INT32 (*fromcontrolsb)[MAXINPUTMAPPING], INT32 (*fromcontrolsc)[MAXINPUTMAPPING], INT32 (*fromcontrolsd)[MAXINPUTMAPPING]); +INT32 G_CheckDoubleUsage(INT32 keynum, INT32 playernum, boolean modify); #endif diff --git a/src/g_state.h b/src/g_state.h index 3aa4a3da7..80d816732 100644 --- a/src/g_state.h +++ b/src/g_state.h @@ -27,7 +27,7 @@ typedef enum GS_CONTINUING, // continue screen GS_TITLESCREEN, // title screen - GS_TIMEATTACK, // time attack menu + GS_MENU, // SRB2Kart: menu-only (previously was GS_TIMEATTACK) GS_CREDITS, // credit sequence GS_EVALUATION, // Evaluation at the end of a game. diff --git a/src/hardware/hw_bsp.c b/src/hardware/hw_bsp.c index df0b7113e..9202341a4 100644 --- a/src/hardware/hw_bsp.c +++ b/src/hardware/hw_bsp.c @@ -17,7 +17,7 @@ #include "../z_zone.h" #include "../console.h" #include "../v_video.h" -#include "../m_menu.h" +#include "../k_menu.h" #include "../i_system.h" #include "../m_argv.h" #include "../i_video.h" diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c index 01e900c38..a819171b4 100644 --- a/src/hardware/hw_main.c +++ b/src/hardware/hw_main.c @@ -6603,6 +6603,7 @@ void HWR_AddCommands(void) CV_RegisterVar(&cv_glshearing); CV_RegisterVar(&cv_glshaders); CV_RegisterVar(&cv_glallowshaders); + CV_RegisterVar(&cv_glanisotropicmode); CV_RegisterVar(&cv_glfiltermode); CV_RegisterVar(&cv_glsolvetjoin); @@ -6618,7 +6619,7 @@ void HWR_AddSessionCommands(void) { if (gl_sessioncommandsadded) return; - CV_RegisterVar(&cv_glanisotropicmode); + // Kept in case we ever need this again. gl_sessioncommandsadded = true; } diff --git a/src/http-mserv.c b/src/http-mserv.c index 2def3cf8d..b5e3ba6c7 100644 --- a/src/http-mserv.c +++ b/src/http-mserv.c @@ -23,7 +23,7 @@ Documentation available here. #include "command.h" #include "console.h" #include "m_argv.h" -#include "m_menu.h" +#include "k_menu.h" #include "mserv.h" #include "i_tcp.h"/* for current_port */ #include "i_threads.h" diff --git a/src/hu_stuff.c b/src/hu_stuff.c index f4f52f5cf..19e016ae7 100644 --- a/src/hu_stuff.c +++ b/src/hu_stuff.c @@ -16,7 +16,7 @@ #include "hu_stuff.h" #include "font.h" -#include "m_menu.h" // gametype_cons_t +#include "k_menu.h" // gametype_cons_t #include "m_cond.h" // emblems #include "m_misc.h" // word jumping @@ -237,15 +237,13 @@ void HU_Init(void) DIG (3); ADIM (HU); - PR ("STCFN"); REG; PR ("TNYFN"); REG; - ADIM (KART); - PR ("MKFNT"); + PR ("FILEF"); REG; ADIM (LT); @@ -279,6 +277,26 @@ void HU_Init(void) PR ("PINGN"); REG; + PR ("PRFN"); + REG; + + DIG (3); + + ADIM (KART); + PR ("MKFNT"); + REG; + + ADIM (LT); + PR ("GAMEM"); + REG; + + ADIM (LT); + PR ("THIFN"); + REG; + + PR ("TLWFN"); + REG; + #undef REG #undef DIG #undef PR @@ -976,10 +994,12 @@ void HU_Ticker(void) hu_tick++; hu_tick &= 7; // currently only to blink chat input cursor + /* if (PlayerInputDown(1, gc_scores)) hu_showscores = !chat_on; else hu_showscores = false; + */ hu_keystrokes = false; @@ -1195,16 +1215,24 @@ boolean HU_Responder(event_t *ev) // (Unless if you're sharing a keyboard, since you probably establish when you start chatting that you have dibs on it...) // (Ahhh, the good ol days when I was a kid who couldn't afford an extra USB controller...) - if (ev->data1 >= KEY_MOUSE1) + if (ev->data1 >= NUMKEYS) { - INT32 i; + INT32 i, j; for (i = 0; i < num_gamecontrols; i++) { - if (gamecontrol[0][i][0] == ev->data1 || gamecontrol[0][i][1] == ev->data1) + for (j = 0; j < MAXINPUTMAPPING; j++) + { + if (gamecontrol[0][i][j] == ev->data1) + break; + } + + if (j < MAXINPUTMAPPING) + { break; + } } - if (i == num_gamecontrols) + if (i == num_gamecontrols && j == MAXINPUTMAPPING) return false; } @@ -1212,7 +1240,7 @@ boolean HU_Responder(event_t *ev) if (!chat_on) { // enter chat mode - if ((ev->data1 == gamecontrol[0][gc_talkkey][0] || ev->data1 == gamecontrol[0][gc_talkkey][1]) + if ((ev->data1 == gamecontrol[0][gc_talk][0] || ev->data1 == gamecontrol[0][gc_talk][1]) && netgame && !OLD_MUTE) // check for old chat mute, still let the players open the chat incase they want to scroll otherwise. { chat_on = true; @@ -1222,7 +1250,7 @@ boolean HU_Responder(event_t *ev) typelines = 1; return true; } - if ((ev->data1 == gamecontrol[0][gc_teamkey][0] || ev->data1 == gamecontrol[0][gc_teamkey][1]) + if ((ev->data1 == gamecontrol[0][gc_teamtalk][0] || ev->data1 == gamecontrol[0][gc_teamtalk][1]) && netgame && !OLD_MUTE) { chat_on = true; @@ -1246,9 +1274,9 @@ boolean HU_Responder(event_t *ev) return true; // Ignore non-keyboard keys, except when the talk key is bound - if (ev->data1 >= KEY_MOUSE1 - && (ev->data1 != gamecontrol[0][gc_talkkey][0] - && ev->data1 != gamecontrol[0][gc_talkkey][1])) + if (ev->data1 >= NUMKEYS + /*&& (ev->data1 != gamecontrol[0][gc_talkkey][0] + && ev->data1 != gamecontrol[0][gc_talkkey][1])*/) return false; c = CON_ShiftChar(c); @@ -1310,9 +1338,9 @@ boolean HU_Responder(event_t *ev) I_UpdateMouseGrab(); } else if (c == KEY_ESCAPE - || ((c == gamecontrol[0][gc_talkkey][0] || c == gamecontrol[0][gc_talkkey][1] + /*|| ((c == gamecontrol[0][gc_talkkey][0] || c == gamecontrol[0][gc_talkkey][1] || c == gamecontrol[0][gc_teamkey][0] || c == gamecontrol[0][gc_teamkey][1]) - && c >= KEY_MOUSE1)) // If it's not a keyboard key, then the chat button is used as a toggle. + && c >= NUMKEYS)*/) // If it's not a keyboard key, then the chat button is used as a toggle. { chat_on = false; c_input = 0; // reset input cursor @@ -1579,7 +1607,7 @@ static void HU_drawChatLog(INT32 offset) INT32 x = chatx+2, y, dx = 0, dy = 0; UINT32 i = 0; INT32 chat_topy, chat_bottomy; - INT32 highlight = HU_GetHighlightColor(); + INT32 highlight = V_YELLOWMAP; boolean atbottom = false; // make sure that our scroll position isn't "illegal"; @@ -2374,9 +2402,7 @@ static void HU_DrawRankings(void) V_DrawFadeScreen(0xFF00, 16); // A little more readable, and prevents cheating the fades under other circumstances. - if (cons_menuhighlight.value) - hilicol = cons_menuhighlight.value; - else if (modeattacking) + if (modeattacking) hilicol = V_ORANGEMAP; else hilicol = ((gametype == GT_RACE) ? V_SKYMAP : V_REDMAP); @@ -2385,7 +2411,7 @@ static void HU_DrawRankings(void) if (modeattacking) V_DrawString(4, 188, hilicol|V_SNAPTOBOTTOM|V_SNAPTOLEFT, "Record Attack"); else - V_DrawString(4, 188, hilicol|V_SNAPTOBOTTOM|V_SNAPTOLEFT, gametype_cons_t[gametype].strvalue); + V_DrawString(4, 188, hilicol|V_SNAPTOBOTTOM|V_SNAPTOLEFT, Gametype_Names[gametype]); if ((gametyperules & (GTR_TIMELIMIT|GTR_POINTLIMIT)) && !bossinfo.boss) { diff --git a/src/hu_stuff.h b/src/hu_stuff.h index d81dba2a6..cc9959467 100644 --- a/src/hu_stuff.h +++ b/src/hu_stuff.h @@ -32,6 +32,11 @@ #define KART_FONTEND 'Z' #define KART_FONTSIZE (KART_FONTEND - KART_FONTSTART + 1) + +#define AZ_FONTSTART 'A' // the first font character +#define AZ_FONTEND 'Z' + +#define AZ_FONTSIZE (AZ_FONTEND - AZ_FONTSTART + 1) // // Level title font @@ -49,7 +54,7 @@ enum { X (HU), X (TINY), - X (KART), + X (FILE), X (LT), X (CRED), @@ -60,6 +65,12 @@ enum X (TALLNUM), X (NIGHTSNUM), X (PINGNUM), + X (PROFNUM), + + X (KART), + X (GM), + X (LSHI), + X (LSLOW), }; #undef X diff --git a/src/k_bot.c b/src/k_bot.c index e64089aef..3fbb5278d 100644 --- a/src/k_bot.c +++ b/src/k_bot.c @@ -110,7 +110,7 @@ boolean K_AddBot(UINT8 skin, UINT8 difficulty, UINT8 *p) void K_UpdateMatchRaceBots(void) { const UINT8 difficulty = cv_kartbot.value; - UINT8 pmax = min((dedicated ? MAXPLAYERS-1 : MAXPLAYERS), cv_maxplayers.value); + UINT8 pmax = min((dedicated ? MAXPLAYERS-1 : MAXPLAYERS), cv_maxconnections.value); UINT8 numplayers = 0; UINT8 numbots = 0; UINT8 numwaiting = 0; @@ -136,9 +136,9 @@ void K_UpdateMatchRaceBots(void) } } - if (cv_ingamecap.value > 0) + if (cv_maxplayers.value > 0) { - pmax = min(pmax, cv_ingamecap.value); + pmax = min(pmax, cv_maxplayers.value); } for (i = 0; i < MAXPLAYERS; i++) diff --git a/src/k_collide.c b/src/k_collide.c index 128364cfc..a23801ccc 100644 --- a/src/k_collide.c +++ b/src/k_collide.c @@ -44,7 +44,7 @@ boolean K_OrbinautJawzCollide(mobj_t *t1, mobj_t *t2) if ((t1->threshold > 0 && t2->hitlag > 0) || (t2->threshold > 0 && t1->hitlag > 0)) return true; - if (((t1->target == t2) || (!(t2->flags & (MF_ENEMY|MF_BOSS)) && (t1->target == t2->target))) && (t1->threshold > 0 || (t2->type != MT_PLAYER && t2->threshold > 0))) + if (((t1->target == t2) || (!(t2->flags & (MF_ENEMY|MF_BOSS)) && (t1->target == t2->target))) && ((t1->threshold > 0 && t2->type == MT_PLAYER) || (t2->type != MT_PLAYER && t2->threshold > 0))) return true; if (t1->health <= 0 || t2->health <= 0) diff --git a/src/k_follower.c b/src/k_follower.c index 2a5f92a05..17be8c749 100644 --- a/src/k_follower.c +++ b/src/k_follower.c @@ -175,6 +175,23 @@ static void K_SetFollowerState(mobj_t *f, statenum_t state) } } +/*-------------------------------------------------- + UINT16 K_GetEffectiveFollowerColor(UINT16 followercolor, UINT16 playercolor) + + See header file for description. +--------------------------------------------------*/ +UINT16 K_GetEffectiveFollowerColor(UINT16 followercolor, UINT16 playercolor) +{ + if (followercolor < numskincolors) // bog standard + return followercolor; + + if (followercolor == FOLLOWERCOLOR_OPPOSITE) // "Opposite" + return skincolors[playercolor].invcolor; + + //if (followercolor == FOLLOWERCOLOR_MATCH) -- "Match" + return playercolor; +} + /*-------------------------------------------------- static void K_UpdateFollowerState(mobj_t *f, statenum_t state, followerstate_t type) @@ -302,24 +319,7 @@ void K_HandleFollower(player_t *player) } // Set follower colour - switch (player->followercolor) - { - case FOLLOWERCOLOR_MATCH: // "Match" - color = player->skincolor; - break; - - case FOLLOWERCOLOR_OPPOSITE: // "Opposite" - color = skincolors[player->skincolor].invcolor; - break; - - default: - color = player->followercolor; - if (color == 0 || color > MAXSKINCOLORS+2) // Make sure this isn't garbage - { - color = player->skincolor; // "Match" as fallback. - } - break; - } + color = K_GetEffectiveFollowerColor(player->followercolor, player->skincolor); if (player->follower == NULL) // follower doesn't exist / isn't valid { diff --git a/src/k_follower.h b/src/k_follower.h index 0466bdfac..f9a16c971 100644 --- a/src/k_follower.h +++ b/src/k_follower.h @@ -15,7 +15,6 @@ #include "doomdef.h" #include "doomstat.h" -#include "r_skins.h" #define FOLLOWERCOLOR_MATCH UINT16_MAX #define FOLLOWERCOLOR_OPPOSITE (UINT16_MAX-1) @@ -48,6 +47,7 @@ typedef struct follower_s { char skinname[SKINNAMESIZE+1]; // Skin Name. This is what to refer to when asking the commands anything. char name[SKINNAMESIZE+1]; // Name. This is used for the menus. We'll just follow the same rules as skins for this. + char icon[8+1]; // Lump names are only 8 characters. (+1 for \0) skincolornum_t defaultcolor; // default color for menus. followermode_t mode; // Follower behavior modifier. @@ -136,6 +136,23 @@ boolean K_SetFollowerByName(INT32 playernum, const char *skinname); void K_SetFollowerByNum(INT32 playernum, INT32 skinnum); +/*-------------------------------------------------- + UINT16 K_GetEffectiveFollowerColor(UINT16 followercolor, UINT16 playercolor) + + Updates a player's follower pointer, and does + its positioning and animations. + + Input Arguments:- + followercolor - The raw color setting for the follower + playercolor - The player's associated colour, for reference + + Return:- + The resultant skincolor enum for the follower +--------------------------------------------------*/ + +UINT16 K_GetEffectiveFollowerColor(UINT16 followercolor, UINT16 playercolor); + + /*-------------------------------------------------- void K_HandleFollower(player_t *player) diff --git a/src/k_hud.c b/src/k_hud.c index cd4ce30ab..9d9c4a418 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -662,6 +662,8 @@ const char *K_GetItemPatch(UINT8 item, boolean tiny) return (tiny ? "K_ISMINE" : "K_ITMINE"); case KITEM_LANDMINE: return (tiny ? "K_ISLNDM" : "K_ITLNDM"); + case KITEM_DROPTARGET: + return (tiny ? "K_ISDTRG" : "K_ITDTRG"); case KITEM_BALLHOG: return (tiny ? "K_ISBHOG" : "K_ITBHOG"); case KITEM_SPB: @@ -4469,7 +4471,7 @@ void K_drawKartFreePlay(void) if (((leveltime-lt_endtime) % TICRATE) < TICRATE/2) return; - V_DrawKartString((BASEVIDWIDTH - (LAPS_X+1)) - (12*9), // mirror the laps thingy + V_DrawKartString((BASEVIDWIDTH - (LAPS_X+1)) - 72, // mirror the laps thingy LAPS_Y+3, V_HUDTRANS|V_SLIDEIN|V_SNAPTOBOTTOM|V_SNAPTORIGHT, "FREE PLAY"); } diff --git a/src/k_kart.c b/src/k_kart.c index 189b946cd..ab8f04da9 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -2824,91 +2824,107 @@ void K_PlayOvertakeSound(mobj_t *source) K_RegularVoiceTimers(source->player); } -void K_PlayPainSound(mobj_t *source) +void K_PlayPainSound(mobj_t *source, mobj_t *other) { sfxenum_t pick = P_RandomKey(2); // Gotta roll the RNG every time this is called for sync reasons + sfxenum_t sfx_id = ((skin_t *)source->skin)->soundsid[S_sfx[sfx_khurt1 + pick].skinsound]; + boolean alwaysHear = false; + + if (other != NULL && P_MobjWasRemoved(other) == false && other->player != NULL) + { + alwaysHear = P_IsDisplayPlayer(other->player); + } + if (cv_kartvoices.value) - S_StartSound(source, sfx_khurt1 + pick); + { + S_StartSound(alwaysHear ? NULL : source, sfx_id); + } K_RegularVoiceTimers(source->player); } -void K_PlayHitEmSound(mobj_t *source, mobj_t *victim) +void K_PlayHitEmSound(mobj_t *source, mobj_t *other) { - const boolean victimIsLocal = (victim != NULL && P_IsDisplayPlayer(victim->player) == true); + sfxenum_t sfx_id = ((skin_t *)source->skin)->soundsid[S_sfx[sfx_khitem].skinsound]; + boolean alwaysHear = false; - if (source->player->follower) + if (other != NULL && P_MobjWasRemoved(other) == false && other->player != NULL) { - follower_t fl = followers[source->player->followerskin]; - source->player->follower->movecount = fl.hitconfirmtime; // movecount is used to play the hitconfirm animation for followers. + alwaysHear = P_IsDisplayPlayer(other->player); } if (cv_kartvoices.value) { - if (victimIsLocal == false) - { - S_StartSound(source, sfx_khitem); - } - } - else - { - S_StartSound(source, sfx_s1c9); // The only lost gameplay functionality with voices disabled + S_StartSound(alwaysHear ? NULL : source, sfx_id); } K_RegularVoiceTimers(source->player); +} - if (victim != NULL && victim->player != NULL) +void K_TryHurtSoundExchange(mobj_t *victim, mobj_t *attacker) +{ + if (victim == NULL || P_MobjWasRemoved(victim) == true || victim->player == NULL) { - victim->player->confirmInflictor = source->player - players; - victim->player->confirmInflictorDelay = TICRATE/2; + return; + } + + // In a perfect world we could move this here, but there's + // a few niche situations where we want a pain sound from + // the victim, but no confirm sound from the attacker. + // (ex: DMG_STING) + + //K_PlayPainSound(victim, attacker); + + if (attacker == NULL || P_MobjWasRemoved(attacker) == true || attacker->player == NULL) + { + return; + } + + attacker->player->confirmVictim = (victim->player - players); + attacker->player->confirmVictimDelay = TICRATE/2; + + if (attacker->player->follower != NULL) + { + const follower_t *fl = &followers[attacker->player->followerskin]; + attacker->player->follower->movecount = fl->hitconfirmtime; // movecount is used to play the hitconfirm animation for followers. } } void K_PlayPowerGloatSound(mobj_t *source) { if (cv_kartvoices.value) + { S_StartSound(source, sfx_kgloat); + } K_RegularVoiceTimers(source->player); } static void K_HandleDelayedHitByEm(player_t *player) { - if (player->confirmInflictorDelay == 0) + if (player->confirmVictimDelay == 0) { return; } - player->confirmInflictorDelay--; + player->confirmVictimDelay--; - if (player->confirmInflictorDelay == 0 - && P_IsDisplayPlayer(player) == true - && cv_kartvoices.value) + if (player->confirmVictimDelay == 0) { - player_t *inflictor = NULL; + mobj_t *victim = NULL; - if (player->confirmInflictor >= MAXPLAYERS) + if (player->confirmVictim < MAXPLAYERS && playeringame[player->confirmVictim]) { - return; + player_t *victimPlayer = &players[player->confirmVictim]; + + if (victimPlayer != NULL && victimPlayer->spectator == false) + { + victim = victimPlayer->mo; + } } - if (!playeringame[player->confirmInflictor]) - { - return; - } - - inflictor = &players[player->confirmInflictor]; - if (inflictor == NULL || inflictor->spectator) - { - return; - } - - if (inflictor->mo != NULL && P_MobjWasRemoved(inflictor->mo) == false) - { - sfxenum_t sfx_id = ((skin_t *)inflictor->mo->skin)->soundsid[S_sfx[sfx_khitem].skinsound]; - S_StartSound(NULL, sfx_id); - } + K_PlayHitEmSound(player->mo, victim); } } @@ -4281,8 +4297,8 @@ UINT16 K_DriftSparkColor(player_t *player, INT32 charge) if (charge < 0) { - // Stage 0: Yellow - color = SKINCOLOR_GOLD; + // Stage 0: Grey + color = SKINCOLOR_SILVER; } else if (charge >= dsfour) { @@ -4299,7 +4315,7 @@ UINT16 K_DriftSparkColor(player_t *player, INT32 charge) } else if (charge >= dsthree) { - // Stage 3: Purple + // Stage 3: Blue if (charge <= dsthree+(16*3)) { // transition 1 @@ -4308,19 +4324,6 @@ UINT16 K_DriftSparkColor(player_t *player, INT32 charge) else if (charge <= dsthree+(32*3)) { // transition 2 - color = SKINCOLOR_MOONSET; - } - else - { - color = SKINCOLOR_PURPLE; - } - } - else if (charge >= dstwo) - { - // Stage 2: Blue - if (charge <= dstwo+(32*3)) - { - // transition color = SKINCOLOR_NOVA; } else @@ -4328,10 +4331,10 @@ UINT16 K_DriftSparkColor(player_t *player, INT32 charge) color = SKINCOLOR_SAPPHIRE; } } - else if (charge >= dsone) + else if (charge >= dstwo) { - // Stage 1: Red - if (charge <= dsone+(32*3)) + // Stage 2: Red + if (charge <= dstwo+(32*3)) { // transition color = SKINCOLOR_TANGERINE; @@ -4341,6 +4344,19 @@ UINT16 K_DriftSparkColor(player_t *player, INT32 charge) color = SKINCOLOR_KETCHUP; } } + else if (charge >= dsone) + { + // Stage 1: Yellow + if (charge <= dsone+(32*3)) + { + // transition + color = SKINCOLOR_TAN; + } + else + { + color = SKINCOLOR_GOLD; + } + } return color; } @@ -8527,9 +8543,9 @@ INT32 K_GetKartDriftSparkValueForStage(player_t *player, UINT8 stage) } /* -Stage 1: red sparks -Stage 2: blue sparks -Stage 3: purple sparks +Stage 1: yellow sparks +Stage 2: red sparks +Stage 3: blue sparks Stage 4: big large rainbow sparks Stage 0: air failsafe */ @@ -8545,26 +8561,22 @@ void K_SpawnDriftBoostExplosion(player_t *player, int stage) switch (stage) { case 1: - overlay->color = SKINCOLOR_KETCHUP; overlay->fuse = 16; break; case 2: - overlay->color = SKINCOLOR_SAPPHIRE; + overlay->fuse = 32; - S_StartSound(player->mo, sfx_kc5b); break; case 3: - overlay->color = SKINCOLOR_PURPLE; overlay->fuse = 48; S_StartSound(player->mo, sfx_kc5b); break; case 4: - overlay->color = SKINCOLOR_SILVER; overlay->fuse = 120; S_StartSound(player->mo, sfx_kc5b); @@ -8572,7 +8584,6 @@ void K_SpawnDriftBoostExplosion(player_t *player, int stage) break; case 0: - overlay->color = SKINCOLOR_SILVER; overlay->fuse = 16; break; } @@ -8607,7 +8618,7 @@ static void K_KartDrift(player_t *player, boolean onground) if (player->driftcharge < 0) { - // Stage 0: Yellow sparks + // Stage 0: Grey sparks if (!onground) P_Thrust(player->mo, pushdir, player->speed / 8); @@ -8616,7 +8627,7 @@ static void K_KartDrift(player_t *player, boolean onground) } else if (player->driftcharge >= dsone && player->driftcharge < dstwo) { - // Stage 1: Red sparks + // Stage 1: Yellow sparks if (!onground) P_Thrust(player->mo, pushdir, player->speed / 4); @@ -8627,7 +8638,7 @@ static void K_KartDrift(player_t *player, boolean onground) } else if (player->driftcharge < dsthree) { - // Stage 2: Blue sparks + // Stage 2: Red sparks if (!onground) P_Thrust(player->mo, pushdir, player->speed / 3); @@ -8638,7 +8649,7 @@ static void K_KartDrift(player_t *player, boolean onground) } else if (player->driftcharge < dsfour) { - // Stage 3: Purple sparks + // Stage 3: Blue sparks if (!onground) P_Thrust(player->mo, pushdir, ( 5 * player->speed ) / 12); @@ -9025,13 +9036,17 @@ static INT32 K_FlameShieldMax(player_t *player) boolean K_PlayerEBrake(player_t *player) { + if (player->fastfall != 0) + { + return true; + } + return (K_GetKartButtons(player) & BT_EBRAKEMASK) == BT_EBRAKEMASK - && P_IsObjectOnGround(player->mo) == true - && player->drift == 0 - && player->spinouttimer == 0 - && player->justbumped == 0 - && player->spindashboost == 0 - && player->nocontrol == 0; + && player->drift == 0 + && P_PlayerInPain(player) == false + && player->justbumped == 0 + && player->spindashboost == 0 + && player->nocontrol == 0; } SINT8 K_Sliptiding(player_t *player) @@ -9053,9 +9068,8 @@ void K_KartEbrakeVisuals(player_t *p) mobj_t *spdl; fixed_t sx, sy; - if (K_PlayerEBrake(p)) + if (K_PlayerEBrake(p) == true) { - if (p->ebrakefor % 20 == 0) { wave = P_SpawnMobj(p->mo->x, p->mo->y, p->mo->z, MT_SOFTLANDING); @@ -9092,7 +9106,6 @@ void K_KartEbrakeVisuals(player_t *p) K_FlipFromObject(p->mo->hprev, p->mo); } - if (!p->spindash) { // Spawn downwards fastline @@ -9233,6 +9246,7 @@ static void K_KartSpindashWind(mobj_t *parent) static void K_KartSpindash(player_t *player) { + const boolean onGround = P_IsObjectOnGround(player->mo); const INT16 MAXCHARGETIME = K_GetSpindashChargeTime(player); UINT16 buttons = K_GetKartButtons(player); boolean spawnWind = (leveltime % 2 == 0); @@ -9296,6 +9310,44 @@ static void K_KartSpindash(player_t *player) return; } + // Handle fast falling behaviors first. + if (onGround == false) + { + // Update fastfall. + player->fastfall = player->mo->momz; + player->spindash = 0; + return; + } + else if (player->fastfall != 0) + { + // Handle fastfall bounce. + const fixed_t maxBounce = player->mo->scale * 10; + const fixed_t minBounce = player->mo->scale; + fixed_t bounce = 2 * abs(player->fastfall) / 3; + + if (bounce > maxBounce) + { + bounce = maxBounce; + } + else + { + // Lose speed on bad bounce. + player->mo->momx /= 2; + player->mo->momy /= 2; + + if (bounce < minBounce) + { + bounce = minBounce; + } + } + + S_StartSound(player->mo, sfx_ffbonc); + player->mo->momz = bounce * P_MobjFlip(player->mo); + + player->fastfall = 0; + return; + } + if (player->speed == 0 && player->steering != 0 && leveltime % 8 == 0) { // Rubber burn turn sfx @@ -10477,7 +10529,7 @@ void K_CheckSpectateStatus(void) if (!players[i].spectator) { numingame++; - if (cv_ingamecap.value && numingame >= cv_ingamecap.value) // DON'T allow if you've hit the in-game player cap + if (cv_maxplayers.value && numingame >= cv_maxplayers.value) // DON'T allow if you've hit the in-game player cap return; if (gamestate != GS_LEVEL) // Allow if you're not in a level continue; @@ -10502,7 +10554,7 @@ void K_CheckSpectateStatus(void) return; // Organize by spectate wait timer - if (cv_ingamecap.value) + if (cv_maxplayers.value) { UINT8 oldrespawnlist[MAXPLAYERS]; memcpy(oldrespawnlist, respawnlist, numjoiners); @@ -10529,7 +10581,7 @@ void K_CheckSpectateStatus(void) // Finally, we can de-spectate everyone! for (i = 0; i < numjoiners; i++) { - if (cv_ingamecap.value && numingame+i >= cv_ingamecap.value) // Hit the in-game player cap while adding people? + if (cv_maxplayers.value && numingame+i >= cv_maxplayers.value) // Hit the in-game player cap while adding people? break; //CONS_Printf("player %s is joining on tic %d\n", player_names[respawnlist[i]], leveltime); P_SpectatorJoinGame(&players[respawnlist[i]]); diff --git a/src/k_kart.h b/src/k_kart.h index 00c36ea76..bab501200 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -154,8 +154,9 @@ void K_HandleDirectionalInfluence(player_t *player); void K_PlayAttackTaunt(mobj_t *source); void K_PlayBoostTaunt(mobj_t *source); void K_PlayOvertakeSound(mobj_t *source); -void K_PlayPainSound(mobj_t *source); -void K_PlayHitEmSound(mobj_t *source, mobj_t *victim); +void K_PlayPainSound(mobj_t *source, mobj_t *other); +void K_PlayHitEmSound(mobj_t *source, mobj_t *other); +void K_TryHurtSoundExchange(mobj_t *victim, mobj_t *attacker); void K_PlayPowerGloatSound(mobj_t *source); fixed_t K_ItemScaleForPlayer(player_t *player); diff --git a/src/k_menu.h b/src/k_menu.h new file mode 100644 index 000000000..415c54284 --- /dev/null +++ b/src/k_menu.h @@ -0,0 +1,1113 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 1993-1996 by id Software, Inc. +// Copyright (C) 1998-2000 by DooM Legacy Team. +// Copyright (C) 2011-2016 by Matthew "Inuyasha" Walsh. +// Copyright (C) 1999-2018 by Sonic Team Junior. +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- +/// \file k_menu.h +/// \brief Menu widget stuff, selection and such + +#ifndef __K_MENU__ +#define __K_MENU__ + +#include "d_event.h" +#include "command.h" +#include "doomstat.h" // MAXSPLITSCREENPLAYERS +#include "g_demo.h" //menudemo_t +#include "k_profiles.h" // profile data & functions +#include "g_input.h" // gc_ +#include "i_threads.h" +#include "mserv.h" + +#define SERVERLISTDEBUG + +// flags for items in the menu +// menu handle (what we do when key is pressed +#define IT_TYPE 14 // (2+4+8) +#define IT_CALL 0 // call the function +#define IT_ARROWS 2 // call function with 0 for left arrow and 1 for right arrow in param +#define IT_KEYHANDLER 4 // call with the key in param +#define IT_SUBMENU 6 // go to sub menu +#define IT_CVAR 8 // handle as a cvar +#define IT_SPACE 10 // no handling +#define IT_MSGHANDLER 12 // same as key but with event and sometime can handle y/n key (special for message + +#define IT_DISPLAY (48+64+128) // 16+32+64+128 +#define IT_NOTHING 0 // space +#define IT_PATCH 16 // a patch or a string with big font +#define IT_STRING 32 // little string (spaced with 10) +#define IT_WHITESTRING 48 // little string in white +#define IT_DYBIGSPACE 64 // same as noting +#define IT_DYLITLSPACE (16+64) // little space +#define IT_STRING2 (32+64) // a simple string +#define IT_GRAYPATCH (16+32+64) // grayed patch or big font string +#define IT_BIGSLIDER 128 // volume sound use this +#define IT_TRANSTEXT (16+128) // Transparent text +#define IT_TRANSTEXT2 (32+128) // used for control names +#define IT_HEADERTEXT (48+128) // Non-selectable header option, displays in yellow offset to the left a little +#define IT_QUESTIONMARKS (64+128) // Displays as question marks, used for secrets +#define IT_CENTER 256 // if IT_PATCH, center it on screen + +//consvar specific +#define IT_CVARTYPE (512+1024+2048) +#define IT_CV_NORMAL 0 +#define IT_CV_SLIDER 512 +#define IT_CV_STRING 1024 +#define IT_CV_NOPRINT 1536 +#define IT_CV_NOMOD 2048 +#define IT_CV_INVISSLIDER 2560 +#define IT_CV_PASSWORD 3072 + +//call/submenu specific +// There used to be a lot more here but ... +// A lot of them became redundant with the advent of the Pause menu, so they were removed +#define IT_CALLTYPE (512+1024) +#define IT_CALL_NORMAL 0 +#define IT_CALL_NOTMODIFIED 512 + +// in INT16 for some common use +#define IT_BIGSPACE (IT_SPACE +IT_DYBIGSPACE) +#define IT_LITLSPACE (IT_SPACE +IT_DYLITLSPACE) +#define IT_CONTROL (IT_STRING2+IT_CALL) +#define IT_CVARMAX (IT_CVAR +IT_CV_NOMOD) +#define IT_DISABLED (IT_SPACE +IT_GRAYPATCH) +#define IT_GRAYEDOUT (IT_SPACE +IT_TRANSTEXT) +#define IT_GRAYEDOUT2 (IT_SPACE +IT_TRANSTEXT2) +#define IT_HEADER (IT_SPACE +IT_HEADERTEXT) +#define IT_SECRET (IT_SPACE +IT_QUESTIONMARKS) + +#define MAXSTRINGLENGTH 32 + +#ifdef HAVE_THREADS +extern I_mutex k_menu_mutex; +#endif + +// for server threads etc. +typedef enum +{ + M_NOT_WAITING, + + M_WAITING_VERSION, + M_WAITING_SERVERS, +} +M_waiting_mode_t; + +extern M_waiting_mode_t m_waiting_mode; + +typedef union +{ + struct menu_s *submenu; // IT_SUBMENU + consvar_t *cvar; // IT_CVAR + void (*routine)(INT32 choice); // IT_CALL, IT_KEYHANDLER, IT_ARROWS +} itemaction_t; + +// Player Setup menu colors linked list +typedef struct menucolor_s { + struct menucolor_s *next; + struct menucolor_s *prev; + UINT16 color; +} menucolor_t; + +extern menucolor_t *menucolorhead, *menucolortail; + +extern CV_PossibleValue_t gametype_cons_t[]; + +// +// MENU TYPEDEFS +// + +typedef struct menuitem_s +{ + UINT16 status; // show IT_xxx + + const char *text; // option title + const char *tooltip; // description of option used by K_MenuTooltips + const char *patch; // image of option used by K_MenuPreviews + + itemaction_t itemaction; + + // extra variables + INT32 mvar1; + INT32 mvar2; +} menuitem_t; + +typedef struct menu_s +{ + INT16 numitems; // # of menu items + struct menu_s *prevMenu; // previous menu + + INT16 lastOn; // last item user was on in menu + menuitem_t *menuitems; // menu items + + INT16 x, y; // x, y of menu + INT16 extra1, extra2; // Can be whatever really! Options menu uses extra1 for bg colour. + + INT16 transitionID; // only transition if IDs match + INT16 transitionTics; // tics for transitions out + + void (*drawroutine)(void); // draw routine + void (*tickroutine)(void); // ticker routine + void (*initroutine)(void); // called when starting a new menu + boolean (*quitroutine)(void); // called before quit a menu return true if we can + boolean (*inputroutine)(INT32); // if set, called every frame in the input handler. Returning true overwrites normal input handling. +} menu_t; + +typedef enum +{ + MM_NOTHING = 0, // is just displayed until the user do someting + MM_YESNO, // routine is called with only 'y' or 'n' in param + MM_EVENTHANDLER // the same of above but without 'y' or 'n' restriction + // and routine is void routine(event_t *) (ex: set control) +} menumessagetype_t; + +// =========== +// PROTOTYPING +// =========== + +// K_MENUDEF.C +extern menuitem_t MainMenu[]; +extern menu_t MainDef; + +typedef enum +{ + play = 0, + extra, + options, + quitkart +} main_e; + +extern menuitem_t PLAY_CharSelect[]; +extern menu_t PLAY_CharSelectDef; + +extern menuitem_t PLAY_MainMenu[]; +extern menu_t PLAY_MainDef; + +extern menuitem_t PLAY_GamemodesMenu[]; +extern menu_t PLAY_GamemodesDef; + +extern menuitem_t PLAY_RaceGamemodesMenu[]; +extern menu_t PLAY_RaceGamemodesDef; + +typedef enum +{ + drace_gpdifficulty = 0, + drace_mrkartspeed, + drace_mrcpu, + drace_mrracers, + drace_encore, + drace_boxend, + drace_cupselect = drace_boxend, + drace_mapselect, + drace_back +} drace_e; + +extern menuitem_t PLAY_RaceDifficulty[]; +extern menu_t PLAY_RaceDifficultyDef; + +extern menuitem_t PLAY_CupSelect[]; +extern menu_t PLAY_CupSelectDef; + +extern menuitem_t PLAY_LevelSelect[]; +extern menu_t PLAY_LevelSelectDef; + +extern menuitem_t PLAY_TimeAttack[]; +extern menu_t PLAY_TimeAttackDef; + +extern menuitem_t PLAY_TAReplay[]; +extern menu_t PLAY_TAReplayDef; + +extern menuitem_t PLAY_TAReplayGuest[]; +extern menu_t PLAY_TAReplayGuestDef; + +extern menuitem_t PLAY_TAGhosts[]; +extern menu_t PLAY_TAGhostsDef; + +extern menuitem_t PLAY_MP_OptSelect[]; +extern menu_t PLAY_MP_OptSelectDef; + +typedef enum +{ + mhost_sname = 0, + mhost_public, + mhost_maxp, + mhost_gametype, + mhost_go, +} mhost_e; + +extern menuitem_t PLAY_MP_Host[]; +extern menu_t PLAY_MP_HostDef; + +extern menuitem_t PLAY_MP_JoinIP[]; +extern menu_t PLAY_MP_JoinIPDef; + +extern menuitem_t PLAY_MP_RoomSelect[]; +extern menu_t PLAY_MP_RoomSelectDef; + +extern menuitem_t PLAY_MP_ServerBrowser[]; +extern menu_t PLAY_MP_ServerBrowserDef; + +extern menuitem_t PLAY_BattleGamemodesMenu[]; +extern menu_t PLAY_BattleGamemodesDef; + +// OPTIONS +extern menuitem_t OPTIONS_Main[]; +extern menu_t OPTIONS_MainDef; + +// We'll need this since we're gonna have to dynamically enable and disable options depending on which state we're in. +typedef enum +{ + mopt_profiles = 0, + mopt_video, + mopt_sound, + mopt_hud, + mopt_gameplay, + mopt_server, + mopt_data, + mopt_manual, +} mopt_e; + +typedef enum +{ + dopt_screenshot = 0, + dopt_addon, + dopt_replay, +#ifdef HAVE_DISCORDRPC + dopt_discord, +#endif + dopt_spacer, + dopt_erase, +} dopt_e; + +extern menuitem_t OPTIONS_Profiles[]; +extern menu_t OPTIONS_ProfilesDef; + +// Separate menu to avoid spaghetti code etc. +extern menuitem_t MAIN_Profiles[]; +extern menu_t MAIN_ProfilesDef; + +typedef enum +{ + popt_profilename = 0, + popt_profilepname, + popt_char, + popt_controls, + popt_confirm, +} popt_e; + +extern menuitem_t OPTIONS_EditProfile[]; +extern menu_t OPTIONS_EditProfileDef; + +extern menuitem_t OPTIONS_ProfileControls[]; +extern menu_t OPTIONS_ProfileControlsDef; + +extern menuitem_t OPTIONS_Video[]; +extern menu_t OPTIONS_VideoDef; + +extern menuitem_t OPTIONS_VideoModes[]; +extern menu_t OPTIONS_VideoModesDef; + +#ifdef HWRENDER +extern menuitem_t OPTIONS_VideoOGL[]; +extern menu_t OPTIONS_VideoOGLDef; +#endif + +extern menuitem_t OPTIONS_Sound[]; +extern menu_t OPTIONS_SoundDef; + +extern menuitem_t OPTIONS_HUD[]; +extern menu_t OPTIONS_HUDDef; + +extern menuitem_t OPTIONS_HUDOnline[]; +extern menu_t OPTIONS_HUDOnlineDef; + +typedef enum +{ + gopt_gamespeed = 0, + gopt_baselapcount, + gopt_frantic, + gopt_encore, + gopt_exitcountdown, + gopt_spacer1, + gopt_timelimit, + gopt_startingbumpers, + gopt_karmacomeback, + gopt_spacer2, + gopt_itemtoggles +} gopt_e; + +extern menuitem_t OPTIONS_Gameplay[]; +extern menu_t OPTIONS_GameplayDef; + +extern menuitem_t OPTIONS_GameplayItems[]; +extern menu_t OPTIONS_GameplayItemsDef; + +extern menuitem_t OPTIONS_Server[]; +extern menu_t OPTIONS_ServerDef; + +#ifndef NONET +extern menuitem_t OPTIONS_ServerAdvanced[]; +extern menu_t OPTIONS_ServerAdvancedDef; +#endif + +extern menuitem_t OPTIONS_Data[]; +extern menu_t OPTIONS_DataDef; + +extern menuitem_t OPTIONS_DataScreenshot[]; +extern menu_t OPTIONS_DataScreenshotDef; + +extern menuitem_t OPTIONS_DataAddon[]; +extern menu_t OPTIONS_DataAddonDef; + +extern menuitem_t OPTIONS_DataReplay[]; +extern menu_t OPTIONS_DataReplayDef; + +#ifdef HAVE_DISCORDRPC +extern menuitem_t OPTIONS_DataDiscord[]; +extern menu_t OPTIONS_DataDiscordDef; +#endif + +extern menuitem_t OPTIONS_DataErase[]; +extern menu_t OPTIONS_DataEraseDef; + +extern menuitem_t OPTIONS_DataProfileErase[]; +extern menu_t OPTIONS_DataProfileEraseDef; + +// EXTRAS +extern menuitem_t EXTRAS_Main[]; +extern menu_t EXTRAS_MainDef; + +extern menuitem_t EXTRAS_ReplayHut[]; +extern menu_t EXTRAS_ReplayHutDef; + +extern menuitem_t EXTRAS_ReplayStart[]; +extern menu_t EXTRAS_ReplayStartDef; + +// PAUSE +extern menuitem_t PAUSE_Main[]; +extern menu_t PAUSE_MainDef; + +extern menuitem_t MISC_Addons[]; +extern menu_t MISC_AddonsDef; + +// MANUAL +extern menuitem_t MISC_Manual[]; +extern menu_t MISC_ManualDef; + +// We'll need this since we're gonna have to dynamically enable and disable options depending on which state we're in. +typedef enum +{ + mpause_addons = 0, + mpause_switchmap, +#ifdef HAVE_DISCORDRPC + mpause_discordrequests, +#endif + + mpause_continue, + mpause_spectate, + mpause_entergame, + mpause_canceljoin, + mpause_spectatemenu, + mpause_psetup, + mpause_options, + + mpause_title, +} mpause_e; + +extern menuitem_t PAUSE_GamemodesMenu[]; +extern menu_t PAUSE_GamemodesDef; + +extern menuitem_t PAUSE_PlaybackMenu[]; +extern menu_t PAUSE_PlaybackMenuDef; + +typedef enum +{ + playback_hide, + playback_rewind, + playback_pause, + playback_fastforward, + playback_backframe, + playback_resume, + playback_advanceframe, + playback_viewcount, + playback_view1, + playback_view2, + playback_view3, + playback_view4, + playback_quit +} playback_e; + +// K_MENUFUNC.C + +// Moviemode menu updating +void Moviemode_option_Onchange(void); + +extern menu_t *currentMenu; +extern char dummystaffname[22]; + +extern INT16 itemOn; // menu item skull is on, Hack by Tails 09-18-2002 +extern INT16 skullAnimCounter; // skull animation counter + +extern INT32 menuKey; // keyboard key pressed for menu + +extern INT16 virtualKeyboard[5][13]; +extern INT16 shift_virtualKeyboard[5][13]; + +extern struct menutyping_s +{ + boolean active; // Active + boolean menutypingclose; // Closing + boolean keyboardtyping; // If true, all keystrokes are treated as typing (ignores MBT_A etc). This is unset if you try moving the cursor on the virtual keyboard or use your controller + SINT8 menutypingfade; // fade in and out + + SINT8 keyboardx; + SINT8 keyboardy; + boolean keyboardcapslock; + boolean keyboardshift; + +} menutyping; +// While typing, we'll have a fade strongly darken the screen to overlay the typing menu instead + +typedef enum +{ + MA_NONE = 0, + MA_YES, + MA_NO +} manswer_e; + +#define MAXMENUMESSAGE 256 +extern struct menumessage_s +{ + boolean active; + INT32 flags; // MM_ + char message[MAXMENUMESSAGE]; // message to display + + SINT8 fadetimer; // opening + INT32 x; + INT32 y; + INT32 m; + + void (*routine)(INT32 choice); // Normal routine + void (*eroutine)(event_t *ev); // Event routine (MM_EVENTHANDLER) +} menumessage; + +void M_HandleMenuMessage(void); + +#define MENUDELAYTIME 7 +#define MENUMINDELAY 2 + +typedef enum +{ + MBT_A = 1, + MBT_B = 1<<1, + MBT_C = 1<<2, + MBT_X = 1<<3, + MBT_Y = 1<<4, + MBT_Z = 1<<5, + MBT_L = 1<<6, + MBT_R = 1<<7, + MBT_START = 1<<8 +} menuButtonCode_t; + +typedef struct menucmd_s +{ + SINT8 dpad_ud; // up / down dpad + SINT8 dpad_lr; // left / right + UINT32 buttons; // buttons + UINT32 buttonsHeld; // prev frame's buttons + UINT16 delay; // menu wait + UINT32 delayCount; // num times ya did menu wait (to make the wait shorter each time) +} menucmd_t; + +extern menucmd_t menucmd[MAXSPLITSCREENPLAYERS]; + +extern struct menutransition_s { + INT16 tics; + INT16 dest; + struct menu_s *startmenu; + struct menu_s *endmenu; + boolean in; +} menutransition; + +extern boolean menuwipe; + +extern consvar_t cv_showfocuslost; +extern consvar_t cv_chooseskin, cv_serversort; + +void M_SetMenuDelay(UINT8 i); + +void Moviemode_mode_Onchange(void); +void Screenshot_option_Onchange(void); +void Addons_option_Onchange(void); + +void M_SortServerList(void); + +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); +void M_StartControlPanel(void); +void M_ClearMenus(boolean callexitmenufunc); +void M_SelectableClearMenus(INT32 choice); +void M_SetupNextMenu(menu_t *menudef, boolean nofade); +void M_GoBack(INT32 choice); +void M_Ticker(void); +void M_Init(void); + +extern menu_t MessageDef; +void M_StartMessage(const char *string, void *routine, menumessagetype_t itemtype); +void M_StopMessage(INT32 choice); + +void M_QuitResponse(INT32 ch); +void M_QuitSRB2(INT32 choice); + +extern UINT16 nummenucolors; +void M_AddMenuColor(UINT16 color); +void M_MoveColorBefore(UINT16 color, UINT16 targ); +void M_MoveColorAfter(UINT16 color, UINT16 targ); +UINT16 M_GetColorBefore(UINT16 color, UINT16 amount, boolean follower); +UINT16 M_GetColorAfter(UINT16 color, UINT16 amount, boolean follower); +void M_InitPlayerSetupColors(void); +void M_FreePlayerSetupColors(void); + +// If you want to waste a bunch of memory for a limit no one will hit, feel free to boost this to MAXSKINS :P +// I figure this will be enough clone characters to fit onto one grid space. +#define MAXCLONES MAXSKINS/8 + +extern struct setup_chargrid_s { + INT16 skinlist[MAXCLONES]; + UINT8 numskins; +} setup_chargrid[9][9]; + +typedef enum +{ + CSSTEP_NONE = 0, + CSSTEP_PROFILE, + CSSTEP_ASKCHANGES, + CSSTEP_CHARS, + CSSTEP_ALTS, + CSSTEP_COLORS, + CSSTEP_FOLLOWER, + CSSTEP_FOLLOWERCOLORS, + CSSTEP_READY +} setup_mdepth_t; + +typedef struct setup_player_s +{ + SINT8 gridx, gridy; + UINT8 profilen; + INT16 skin; + SINT8 clonenum; + SINT8 rotate; + UINT8 delay; + UINT16 color; + UINT8 mdepth; + + // Hack, save player 1's original device even if they init charsel with keyboard. + // If they play ALONE, allow them to retain that original device, otherwise, ignore this. + // We can allow them to retain the device with no consequence as when P1 is alone, they have exclusive keyboard fallback options. + UINT8 ponedevice; + + UINT8 changeselect; + + INT32 followern; + UINT16 followercolor; + tic_t follower_tics; + tic_t follower_timer; + UINT8 follower_frame; + state_t *follower_state; +} setup_player_t; + +extern setup_player_t setup_player[MAXSPLITSCREENPLAYERS]; + +extern UINT8 setup_numplayers; +extern tic_t setup_animcounter; + +// for charsel pages. +extern UINT8 setup_page; +extern UINT8 setup_maxpage; + +#define CSROTATETICS 6 + +// The selection spawns 3 explosions in 4 directions, and there's 4 players -- 3 * 4 * 4 = 48 +#define CSEXPLOSIONS 48 + +extern struct setup_explosions_s { + UINT8 x, y; + UINT8 tics; + UINT8 color; +} setup_explosions[CSEXPLOSIONS]; + +typedef enum +{ + SPLITCV_SKIN = 0, + SPLITCV_COLOR, + SPLITCV_NAME, + SPLITCV_MAX +} splitscreencvars_t; +extern consvar_t *setup_playercvars[MAXSPLITSCREENPLAYERS][SPLITCV_MAX]; + +void M_CharacterSelectInit(void); +void M_CharacterSelect(INT32 choice); +boolean M_CharacterSelectHandler(INT32 choice); +void M_CharacterSelectTick(void); +boolean M_CharacterSelectQuit(void); + +void M_SetupGametypeMenu(INT32 choice); +void M_SetupRaceMenu(INT32 choice); + +#define CUPMENU_COLUMNS 7 +#define CUPMENU_ROWS 2 +#define CUPMENU_CURSORID (cupgrid.x + (cupgrid.y * CUPMENU_COLUMNS) + (cupgrid.pageno * (CUPMENU_COLUMNS * CUPMENU_ROWS))) + +extern struct cupgrid_s { + SINT8 x, y; + SINT8 pageno; + UINT8 numpages; + tic_t previewanim; + boolean grandprix; // Setup grand prix server after picking + boolean netgame; // Start the game in an actual server +} cupgrid; + +extern struct levellist_s { + SINT8 cursor; + UINT16 y; + UINT16 dest; + cupheader_t *selectedcup; + INT16 choosemap; + UINT8 newgametype; + boolean timeattack; // Setup time attack menu after picking + boolean netgame; // Start the game in an actual server +} levellist; + +boolean M_CanShowLevelInList(INT16 mapnum, UINT8 gt); +INT16 M_CountLevelsToShowInList(UINT8 gt); +INT16 M_GetFirstLevelInList(UINT8 gt); + +void M_LevelSelectInit(INT32 choice); +void M_CupSelectHandler(INT32 choice); +void M_CupSelectTick(void); +void M_LevelSelectHandler(INT32 choice); +void M_LevelSelectTick(void); + +// dummy consvars for GP & match race setup +extern consvar_t cv_dummygpdifficulty; +extern consvar_t cv_dummykartspeed; +extern consvar_t cv_dummygpencore; +extern consvar_t cv_dummymatchbots; + +void M_SetupDifficultySelect(INT32 choice); +void M_DifficultySelectInputs(INT32 choice); + +// Multiplayer menu stuff + +// Keep track of multiplayer menu related data +// We'll add more stuff here as we need em... + +#define SERVERSPERPAGE 8 +#define SERVERSPACE 18 + +extern struct mpmenu_s { + UINT8 modechoice; + INT16 modewinextend[3][3]; // Used to "extend" the options in the mode select screen. + // format for each option: {extended?, max extension, # lines extended} + // See M_OptSelectTick, it'll make more sense there. Sorry if this is a bit of a mess! + + UINT8 room; + tic_t ticker; + + UINT8 servernum; + UINT8 scrolln; + // max scrolln is always going to be serverlistcount-4 as we can display 8 servers at any time and we start scrolling at half. + + INT16 slide; + +} mpmenu; + +// Time Attack +void M_StartTimeAttack(INT32 choice); +void M_ReplayTimeAttack(INT32 choice); +void M_HandleStaffReplay(INT32 choice); +void M_SetGuestReplay(INT32 choice); + +// MP selection +void M_MPOptSelect(INT32 choice); +void M_MPOptSelectInit(INT32 choice); +void M_MPOptSelectTick(void); +boolean M_MPResetOpts(void); +extern consvar_t cv_dummygametype; // lazy hack to allow us to select the GT on the server host submenu +extern consvar_t cv_dummyip; // I HAVE + // HAVE YOUR IP ADDRESS (This just the hack Cvar we'll type into and then it apends itself to "connect" in the console for IP join) + +// MP Host +void M_MPHostInit(INT32 choice); +void M_MPSetupNetgameMapSelect(INT32 choice); + +// MP join by IP +void M_MPJoinIPInit(INT32 choice); +boolean M_JoinIPInputs(INT32 ch); +void M_JoinIP(const char *ipa); + +// Server browser room selection +void M_MPRoomSelect(INT32 choice); +void M_MPRoomSelectTick(void); +void M_MPRoomSelectInit(INT32 choice); + +// Server browser hell with threads... +void M_SetWaitingMode(int mode); +int M_GetWaitingMode(void); + +void M_MPServerBrowserTick(void); +boolean M_ServerBrowserInputs(INT32 ch); + +#ifdef MASTERSERVER +#ifdef HAVE_THREADS + +void Spawn_masterserver_thread (const char *name, void (*thread)(int*)); +int Same_instance (int id); + +#endif /*HAVE_THREADS*/ + +void Fetch_servers_thread (int *id); + +#endif /*MASTERSERVER*/ + +void M_RefreshServers(INT32 choice); +void M_ServersMenu(INT32 choice); + +// for debugging purposes... +#ifdef SERVERLISTDEBUG +void M_ServerListFillDebug(void); +#endif + +// Options menu: + +// mode descriptions for video mode menu +typedef struct +{ + INT32 modenum; // video mode number in the vidmodes list + const char *desc; // XXXxYYY + UINT8 goodratio; // aspect correct if 1 +} modedesc_t; + + +#define MAXCOLUMNMODES 12 //max modes displayed in one column +#define MAXMODEDESCS (MAXCOLUMNMODES*3) +// Keep track of some options properties +extern struct optionsmenu_s { + + tic_t ticker; // How long the menu's been open for + INT16 offset; // To make the icons move smoothly when we transition! + + tic_t buttflash; // Button flashing before transitionning to the new submenu. + + // For moving the button when we get into a submenu. it's smooth and cool! (normal x/y and target x/y.) + // this is only used during menu transitions. + + // For profiles specifically, this moves the card around since we don't have the rest of the menu displayed in that case. + INT16 optx; + INT16 opty; + INT16 toptx; + INT16 topty; + + // profile garbage + boolean profilemenu; // In profile menu. (Used to know when to get the "PROFILE SETUP" button away.... + boolean resetprofilemenu; // Reset button behaviour when exiting + SINT8 profilen; // # of the selected profile. + + boolean resetprofile; // After going back from the edit menu, this tells the profile select menu to kill the profile data after the transition. + profile_t *profile; // Pointer to the profile we're editing + + INT32 tempcontrols[num_gamecontrols][MAXINPUTMAPPING]; + // Temporary buffer where we're gonna store game controls. + // This is only applied to the profile when you exit out of the controls menu. + + INT16 controlscroll; // scrolling for the control menu.... + UINT8 bindcontrol; // 0: not binding, 1: binding control #1, 2: binding control #2 + INT16 bindtimer; // Timer until binding is cancelled (5s) + + INT16 trycontroller; // Starts at 3*TICRATE, holding B lowers this, when at 0, cancel controller try mode. + + // Used for horrible axis shenanigans + INT32 lastkey; + tic_t keyheldfor; + + // controller coords... + // Works the same as (t)opt + INT16 contx; + INT16 conty; + INT16 tcontx; + INT16 tconty; + + // for video mode testing: + INT32 vidm_testingmode; + INT32 vidm_previousmode; + INT32 vidm_selected; + INT32 vidm_nummodes; + INT32 vidm_column_size; + + modedesc_t modedescs[MAXMODEDESCS]; + + UINT8 erasecontext; + + UINT8 eraseprofilen; + + // background: + INT16 currcolour; + INT16 lastcolour; + tic_t fade; +} optionsmenu; + +extern INT16 controlleroffsets[][2]; + +extern consvar_t cv_dummyprofilename; +extern consvar_t cv_dummyprofileplayername; +extern consvar_t cv_dummyprofilekickstart; + +void M_ResetOptions(void); +void M_InitOptions(INT32 choice); // necessary for multiplayer since there's some options we won't want to access +void M_OptionsTick(void); +boolean M_OptionsInputs(INT32 ch); +boolean M_OptionsQuit(void); // resets buttons when you quit the options. +void M_OptionsChangeBGColour(INT16 newcolour); // changes the background colour for options + +void M_HandleItemToggles(INT32 choice); // For item toggling +void M_EraseData(INT32 choice); // For data erasing +void M_CheckProfileData(INT32 choice); // check if we have profiles. + +// profile selection menu +void M_ProfileSelectInit(INT32 choice); +void M_HandleProfileSelect(INT32 ch); + +// profile edition +void M_HandleProfileEdit(void); +void M_ProfileDeviceSelect(INT32 choice); +void M_ConfirmProfile(INT32 choice); +boolean M_ProfileEditInputs(INT32 ch); + +void M_HandleProfileControls(void); +boolean M_ProfileControlsInputs(INT32 ch); +void M_ProfileSetControl(INT32 ch); + +void M_MapProfileControl(event_t *ev); +void M_ProfileTryController(INT32 choice); +void M_ProfileControlsConfirm(INT32 choice); + +// video modes menu (resolution) +void M_VideoModeMenu(INT32 choice); +void M_HandleVideoModes(INT32 ch); + +// data stuff +void M_HandleProfileErase(INT32 choice); + + +// Extras menu: +#define DF_ENCORE 0x40 + +extern struct extrasmenu_s { + + tic_t ticker; // How long the menu's been open for + INT16 offset; // To make the icons move smoothly when we transition! + + // For moving the button when we get into a submenu. it's smooth and cool! (normal x/y and target x/y.) + // this is only used during menu transitions. (and will probably remain unused until we get the statistics menu + INT16 extx; + INT16 exty; + INT16 textx; + INT16 texty; + + + // The replay vars...... oh no...... + menudemo_t *demolist; + + INT16 replayScrollTitle; + SINT8 replayScrollDelay; + SINT8 replayScrollDir; + + + +} extrasmenu; + +void M_InitExtras(INT32 choice); // init for the struct +void M_ExtrasTick(void); +boolean M_ExtrasInputs(INT32 ch); +boolean M_ExtrasQuit(void); // resets buttons when you quit + +// Extras: Replay Hut +void M_HandleReplayHutList(INT32 choice); +boolean M_QuitReplayHut(void); +void M_HutStartReplay(INT32 choice); +void M_PrepReplayList(void); + + +// Pause menu: + +// Keep track of some pause menu data for visual goodness. +extern struct pausemenu_s { + + tic_t ticker; // How long the menu's been open for + INT16 offset; // To make the icons move smoothly when we transition! + + INT16 openoffset; // Used when you open / close the menu to slide everything in. + boolean closing; // When this is set, the open offset goes backwards to close the menu smoothly. +} pausemenu; + +void M_OpenPauseMenu(void); +void M_QuitPauseMenu(INT32 choice); +boolean M_PauseInputs(INT32 ch); +void M_PauseTick(void); + +extern consvar_t cv_dummymenuplayer; +extern consvar_t cv_dummyspectator; + +// Bunch of funny functions for the pause menu...~ +void M_ConfirmSpectate(INT32 choice); // Spectate confirm when you're alone +void M_ConfirmEnterGame(INT32 choice); // Enter game confirm when you're alone +void M_ConfirmSpectateChange(INT32 choice); // Splitscreen spectate/play menu func +void M_EndGame(INT32 choice); // Quitting to title + +// Replay Playback + +extern tic_t playback_last_menu_interaction_leveltime; + +void M_EndModeAttackRun(void); +void M_SetPlaybackMenuPointer(void); +void M_PlaybackRewind(INT32 choice); +void M_PlaybackPause(INT32 choice); +void M_PlaybackFastForward(INT32 choice); +void M_PlaybackAdvance(INT32 choice); +void M_PlaybackSetViews(INT32 choice); +void M_PlaybackAdjustView(INT32 choice); +void M_PlaybackToggleFreecam(INT32 choice); +void M_PlaybackQuit(INT32 choice); + +void M_ReplayHut(INT32 choice); + +// Misc menus: +#define numaddonsshown 4 +void M_Addons(INT32 choice); +void M_AddonsRefresh(void); +void M_HandleAddons(INT32 choice); +char *M_AddonsHeaderPath(void); +extern consvar_t cv_dummyaddonsearch; + +void M_Manual(INT32 choice); +void M_HandleImageDef(INT32 choice); + +// K_MENUDRAW.C + +// flags for text highlights +#define highlightflags V_AQUAMAP +#define recommendedflags V_GREENMAP +#define warningflags V_GRAYMAP + +void M_UpdateMenuBGImage(boolean forceReset); +void M_DrawMenuBackground(void); +void M_DrawMenuForeground(void); +void M_Drawer(void); +void M_DrawGenericMenu(void); +void M_DrawKartGamemodeMenu(void); +void M_DrawTextBox(INT32 x, INT32 y, INT32 width, INT32 boxlines); +void M_DrawMessageMenu(void); +void M_DrawImageDef(void); + +void M_DrawCharacterSelect(void); + +void M_DrawCupSelect(void); +void M_DrawLevelSelect(void); +void M_DrawTimeAttack(void); + +void M_DrawRaceDifficulty(void); + +// Multiplayer menu stuff +void M_DrawMPOptSelect(void); +void M_DrawMPHost(void); +void M_DrawMPJoinIP(void); +void M_DrawMPRoomSelect(void); +void M_DrawMPServerBrowser(void); + +// Pause menu: +void M_DrawPause(void); + +// Replay Playback +void M_DrawPlaybackMenu(void); + +// Options menus: +void M_DrawOptionsMovingButton(void); // for sick transitions... +void M_DrawOptions(void); +void M_DrawGenericOptions(void); +void M_DrawProfileSelect(void); +void M_DrawEditProfile(void); +void M_DrawProfileControls(void); +void M_DrawVideoModes(void); +void M_DrawItemToggles(void); +void M_DrawProfileErase(void); +extern tic_t shitsfree; + +// Extras menu: +void M_DrawExtrasMovingButton(void); +void M_DrawExtras(void); +void M_DrawReplayHut(void); +void M_DrawReplayStartMenu(void); +void M_DrawReplayHutReplayInfo(void); + +// Misc menus: +#define LOCATIONSTRING1 "Visit \x83SRB2.ORG/MODS\x80 to get & make addons!" +#define LOCATIONSTRING2 "Visit \x88SRB2.ORG/MODS\x80 to get & make addons!" +void M_DrawAddons(void); + +// These defines make it a little easier to make menus +#define DEFAULTMENUSTYLE(source, prev, x, y)\ +{\ + sizeof(source) / sizeof(menuitem_t),\ + prev,\ + 0,\ + source,\ + x, y,\ + 0, 0,\ + M_DrawGenericMenu,\ + NULL,\ + NULL,\ + NULL,\ + NULL\ +} + + +#define KARTGAMEMODEMENU(source, prev)\ +{\ + sizeof(source) / sizeof(menuitem_t),\ + prev,\ + 0,\ + source,\ + 0, 0,\ + 0, 0, \ + 1, 5,\ + M_DrawKartGamemodeMenu,\ + NULL,\ + NULL,\ + NULL,\ + NULL\ +} + +#define IMAGEDEF(source)\ +{\ + sizeof(source) / sizeof(menuitem_t),\ + NULL,\ + 0,\ + source,\ + 0, 0,\ + 0, 0, \ + 1, 5,\ + M_DrawImageDef,\ + NULL,\ + NULL,\ + NULL,\ + NULL\ +} + + +#endif //__K_MENU__ diff --git a/src/k_menudef.c b/src/k_menudef.c new file mode 100644 index 000000000..ed1e33c42 --- /dev/null +++ b/src/k_menudef.c @@ -0,0 +1,1747 @@ +/// \file k_menudef.c +/// \brief SRB2Kart menu definitions + +#include "k_menu.h" +#include "screen.h" // BASEVIDWIDTH +#include "r_main.h" // cv_skybox +#include "v_video.h" // cv_globalgamma +#include "hardware/hw_main.h" // gl consvars +#include "s_sound.h" // sounds consvars +#include "g_game.h" // cv_chatnotifications +#include "console.h" // console cvars +#include "filesrch.h" // addons cvars +#include "m_misc.h" // screenshot cvars +#include "r_fps.h" // fps cvars +#include "discord.h" // discord rpc cvars + +// ========================================================================== +// ORGANIZATION START. +// ========================================================================== +// Note: Never should we be jumping from one category of menu options to another +// without first going to the Main Menu. +// Note: Ignore the above if you're working with the Pause menu. +// Note: (Prefix)_MainMenu should be the target of all Main Menu options that +// point to submenus. + +// --------- +// Main Menu +// --------- +menuitem_t MainMenu[] = +{ + {IT_STRING | IT_CALL, "Play", + "Cut to the chase and start the race!", NULL, + {.routine = M_CharacterSelect}, 0, 0}, + + {IT_STRING | IT_CALL, "Extras", + "Check out some bonus features.", "MENUI001", + {.routine = M_InitExtras}, 0, 0}, + + {IT_STRING, "Options", + "Configure your controls, settings, and preferences.", NULL, + {.routine = M_InitOptions}, 0, 0}, + + {IT_STRING | IT_CALL, "Quit", + "Exit \"Dr. Robotnik's Ring Racers\".", NULL, + {.routine = M_QuitSRB2}, 0, 0}, +}; + +menu_t MainDef = KARTGAMEMODEMENU(MainMenu, NULL); + +// --------- +// Play Menu +// --------- + +menuitem_t PLAY_CharSelect[] = +{ + {IT_NOTHING, NULL, NULL, NULL, {NULL}, 0, 0}, +}; + +menu_t PLAY_CharSelectDef = { + sizeof (PLAY_CharSelect) / sizeof (menuitem_t), + &MainDef, + 0, + PLAY_CharSelect, + 0, 0, + 0, 0, + 0, 0, + M_DrawCharacterSelect, + M_CharacterSelectTick, + M_CharacterSelectInit, + M_CharacterSelectQuit, + M_CharacterSelectHandler +}; + +menuitem_t PLAY_MainMenu[] = +{ + {IT_STRING | IT_CALL, "Local Play", "Play only on this computer.", + NULL, {.routine = M_SetupGametypeMenu}, 0, 0}, + + {IT_STRING | IT_CALL, "Online", "Connect to other computers.", + NULL, {.routine = M_MPOptSelectInit}, /*M_MPRoomSelectInit,*/ 0, 0}, + + {IT_STRING | IT_CALL, "Back", NULL, NULL, {.routine = M_GoBack}, 0, 0}, +}; + +menu_t PLAY_MainDef = KARTGAMEMODEMENU(PLAY_MainMenu, &PLAY_CharSelectDef); + +menuitem_t PLAY_GamemodesMenu[] = +{ + {IT_STRING | IT_CALL, "Race", "A contest to see who's the fastest of them all!", + NULL, {.routine = M_SetupRaceMenu}, 0, 0}, + + {IT_STRING | IT_CALL, "Battle", "It's last hedgehog standing in this free-for-all!", + "MENIMG00", {.routine = M_LevelSelectInit}, 0, GT_BATTLE}, + + {IT_STRING | IT_CALL, "Capsules", "Bust up all of the capsules in record time!", + NULL, {.routine = M_LevelSelectInit}, 1, GT_BATTLE}, + + {IT_STRING | IT_CALL, "Back", NULL, NULL, {.routine = M_GoBack}, 0, 0}, +}; + +menu_t PLAY_GamemodesDef = KARTGAMEMODEMENU(PLAY_GamemodesMenu, &PLAY_MainDef); + +// RACE + +menuitem_t PLAY_RaceGamemodesMenu[] = +{ + {IT_STRING | IT_CALL, "Grand Prix", "Compete for the best rank over five races!", + NULL, {.routine = M_SetupDifficultySelect}, 0, 0}, + + {IT_STRING | IT_CALL, "Match Race", "Play by your own rules in a specialized, single race!", + "MENIMG01", {.routine = M_SetupDifficultySelect}, 1, 0}, + + {IT_STRING | IT_CALL, "Time Attack", "Record your best time on any track!", + NULL, {.routine = M_LevelSelectInit}, 1, GT_RACE}, + + {IT_STRING | IT_CALL, "Back", NULL, NULL, {.routine = M_GoBack}, 0, 0}, +}; + +menu_t PLAY_RaceGamemodesDef = KARTGAMEMODEMENU(PLAY_RaceGamemodesMenu, &PLAY_GamemodesDef); + + +// difficulty selection -- see drace_e +menuitem_t PLAY_RaceDifficulty[] = +{ + // For GP + {IT_STRING | IT_CVAR, "Difficulty", "Select the game difficulty", + NULL, {.cvar = &cv_dummygpdifficulty}, 0, 0}, + + // Match Race + {IT_STRING | IT_CVAR, "Difficulty", "Select the game speed", + NULL, {.cvar = &cv_dummykartspeed}, 0, 0}, + + // DISABLE THAT OPTION OUTSIDE OF MATCH RACE + {IT_STRING2 | IT_CVAR, "CPU", "Set the difficulty of CPU players.", + NULL, {.cvar = &cv_dummymatchbots}, 0, 0}, + {IT_STRING2 | IT_CVAR, "Racers", "Sets the number of racers, including players and CPU.", + NULL, {.cvar = &cv_maxplayers}, 0, 0}, + + {IT_STRING2 | IT_CVAR, "Encore", "Enable or disable Encore mode", + NULL, {.cvar = &cv_dummygpencore}, 0, 0}, + + // For GP + {IT_STRING | IT_CALL, "Cup Select", "Go on and select a cup!", NULL, {.routine = M_LevelSelectInit}, 2, GT_RACE}, + + // Match Race + {IT_STRING | IT_CALL, "Map Select", "Go on and select a race track!", NULL, {.routine = M_LevelSelectInit}, 0, GT_RACE}, + + {IT_STRING | IT_CALL, "Back", NULL, NULL, {.routine = M_GoBack}, 0, 0}, +}; + +menu_t PLAY_RaceDifficultyDef = { + sizeof(PLAY_RaceDifficulty) / sizeof(menuitem_t), + &PLAY_RaceGamemodesDef, + 0, + PLAY_RaceDifficulty, + 0, 0, + 0, 0, + 1, 5, + M_DrawRaceDifficulty, + NULL, + NULL, + NULL, + NULL +}; + + +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 +}; + +menuitem_t PLAY_LevelSelect[] = +{ + {IT_NOTHING | IT_KEYHANDLER, NULL, NULL, NULL, {.routine = M_LevelSelectHandler}, 0, 0}, +}; + +menu_t PLAY_LevelSelectDef = { + sizeof(PLAY_LevelSelect) / sizeof(menuitem_t), + &PLAY_CupSelectDef, + 0, + PLAY_LevelSelect, + 0, 0, + 0, 0, + 2, 5, + M_DrawLevelSelect, + M_LevelSelectTick, + NULL, + NULL, + NULL +}; + +menuitem_t PLAY_TimeAttack[] = +{ + {IT_STRING | IT_SUBMENU, "Replay...", NULL, NULL, {.submenu = &PLAY_TAReplayDef}, 0, 0}, + {IT_STRING | IT_SUBMENU, "Guest...", NULL, NULL, {.submenu = &PLAY_TAReplayGuestDef}, 0, 0}, + {IT_STRING | IT_SUBMENU, "Ghosts...", NULL, NULL, {.submenu = &PLAY_TAGhostsDef}, 0, 0}, + {IT_HEADERTEXT|IT_HEADER, "", NULL, NULL, {NULL}, 0, 0}, + {IT_STRING | IT_CALL, "Start", NULL, NULL, {.routine = M_StartTimeAttack}, 0, 0}, +}; + +menu_t PLAY_TimeAttackDef = { + sizeof(PLAY_TimeAttack) / sizeof(menuitem_t), + &PLAY_LevelSelectDef, + 0, + PLAY_TimeAttack, + 0, 0, + 0, 0, + 2, 5, + M_DrawTimeAttack, + NULL, + NULL, + NULL, + NULL +}; + + +menuitem_t PLAY_TAReplay[] = +{ + {IT_STRING | IT_CALL, "Replay Best Time", NULL, NULL, {.routine = M_ReplayTimeAttack}, 0, 0}, + {IT_STRING | IT_CALL, "Replay Best Lap", NULL, NULL, {.routine = M_ReplayTimeAttack}, 0, 0}, + {IT_HEADERTEXT|IT_HEADER, "", NULL, NULL, {NULL}, 0, 0}, + {IT_STRING | IT_CALL, "Replay Last", NULL, NULL, {.routine = M_ReplayTimeAttack}, 0, 0}, + {IT_STRING | IT_CALL, "Replay Guest", NULL, NULL, {.routine = M_ReplayTimeAttack}, 0, 0}, + {IT_STRING | IT_CALL, "Replay Staff", NULL, NULL, {.routine = M_HandleStaffReplay}, 0, 0}, + {IT_HEADERTEXT|IT_HEADER, "", NULL, NULL, {NULL}, 0, 0}, + + {IT_STRING | IT_SUBMENU, "Back", NULL, NULL, {.submenu = &PLAY_TimeAttackDef}, 0, 0}, +}; + +menu_t PLAY_TAReplayDef = { + sizeof(PLAY_TAReplay) / sizeof(menuitem_t), + &PLAY_TimeAttackDef, + 0, + PLAY_TAReplay, + 0, 0, + 0, 0, + 2, 5, + M_DrawTimeAttack, + NULL, + NULL, + NULL, + NULL +}; + +menuitem_t PLAY_TAReplayGuest[] = +{ + {IT_HEADERTEXT|IT_HEADER, "Save as guest...", NULL, NULL, {NULL}, 0, 0}, + + {IT_STRING | IT_CALL, "Best Time", NULL, NULL, {.routine = M_SetGuestReplay}, 0, 0}, + {IT_STRING | IT_CALL, "Best Lap", NULL, NULL, {.routine = M_SetGuestReplay}, 0, 0}, + {IT_STRING | IT_CALL, "Last Run", NULL, NULL, {.routine = M_SetGuestReplay}, 0, 0}, + + {IT_HEADERTEXT|IT_HEADER, "", NULL, NULL, {NULL}, 0, 0}, + {IT_STRING | IT_CALL, "Delete Guest", NULL, NULL, {.routine = M_SetGuestReplay}, 0, 0}, + + {IT_HEADERTEXT|IT_HEADER, "", NULL, NULL, {NULL}, 0, 0}, + {IT_STRING | IT_SUBMENU, "Back", NULL, NULL, {.submenu = &PLAY_TimeAttackDef}, 0, 0}, + +}; + +menu_t PLAY_TAReplayGuestDef = { + sizeof(PLAY_TAReplayGuest) / sizeof(menuitem_t), + &PLAY_TimeAttackDef, + 0, + PLAY_TAReplayGuest, + 0, 0, + 0, 0, + 2, 5, + M_DrawTimeAttack, + NULL, + NULL, + NULL, + NULL +}; + +menuitem_t PLAY_TAGhosts[] = +{ + {IT_STRING | IT_CVAR, "Best Time", NULL, NULL, {.cvar = &cv_ghost_besttime}, 0, 0}, + {IT_STRING | IT_CVAR, "Best Lap", NULL, NULL, {.cvar = &cv_ghost_bestlap}, 0, 0}, + {IT_STRING | IT_CVAR, "Last", NULL, NULL, {.cvar = &cv_ghost_last}, 0, 0}, + {IT_DISABLED, "Guest", NULL, NULL, {.cvar = &cv_ghost_guest}, 0, 0}, + {IT_DISABLED, "Staff", NULL, NULL, {.cvar = &cv_ghost_staff}, 0, 0}, + + {IT_HEADERTEXT|IT_HEADER, "", NULL, NULL, {NULL}, 0, 0}, + {IT_STRING | IT_SUBMENU, "Back", NULL, NULL, {.submenu = &PLAY_TimeAttackDef}, 0, 0}, +}; + +menu_t PLAY_TAGhostsDef = { + sizeof(PLAY_TAGhosts) / sizeof(menuitem_t), + &PLAY_TimeAttackDef, + 0, + PLAY_TAGhosts, + 0, 0, + 0, 0, + 2, 5, + M_DrawTimeAttack, + NULL, + NULL, + NULL, + NULL +}; + +// MULTIPLAYER OPTION SELECT +menuitem_t PLAY_MP_OptSelect[] = +{ + //{IT_NOTHING | IT_KEYHANDLER, NULL, NULL, NULL, M_MPOptSelect, 0, 0}, + {IT_STRING | IT_CALL, "Host Game", "Start your own online game!", + NULL, {.routine = M_MPHostInit}, 0, 0}, + + {IT_STRING | IT_CALL, "Server Browser", "Search for game servers to play in.", + NULL, {.routine = M_MPRoomSelectInit}, 0, 0}, + + {IT_STRING | IT_CALL, "Join by IP", "Join an online game by its IP address.", + NULL, {.routine = M_MPJoinIPInit}, 0, 0}, +}; + +menu_t PLAY_MP_OptSelectDef = { + sizeof (PLAY_MP_OptSelect) / sizeof (menuitem_t), + &PLAY_MainDef, + 0, + PLAY_MP_OptSelect, + 0, 0, + 0, 0, + -1, 1, + M_DrawMPOptSelect, + M_MPOptSelectTick, + NULL, + NULL, + NULL +}; + +// MULTIPLAYER HOST SCREEN -- see mhost_e +menuitem_t PLAY_MP_Host[] = +{ + //{IT_NOTHING | IT_KEYHANDLER, NULL, NULL, NULL, M_MPOptSelect, 0, 0}, + + {IT_STRING | IT_CVAR | IT_CV_STRING, "Server Name", "Display name for your game online. Other players will see this.", + NULL, {.cvar = &cv_servername}, 0, 0}, + + {IT_STRING | IT_CVAR, "Public Server", "Display or not your game in the Server Browser for other players.", + NULL, {.cvar = &cv_advertise}, 0, 0}, + + {IT_STRING | IT_CVAR, "Max. Players", "Set how many players can play at once. Others will spectate.", + NULL, {.cvar = &cv_maxplayers}, 0, 0}, + + {IT_STRING | IT_CVAR, "Gamemode", "Are we racing? Or perhaps battling?", + NULL, {.cvar = &cv_dummygametype}, 0, 0}, + + {IT_STRING | IT_CALL, "GO", "Select a map with the currently selected gamemode", + NULL, {.routine = M_MPSetupNetgameMapSelect}, 0, 0}, + +}; + +menu_t PLAY_MP_HostDef = { + sizeof (PLAY_MP_Host) / sizeof (menuitem_t), + &PLAY_MP_OptSelectDef, + 0, + PLAY_MP_Host, + 0, 0, + 0, 0, + -1, 1, // 1 frame transition.... This is really just because I don't want the black fade when we press esc, hehe + M_DrawMPHost, + M_MPOptSelectTick, // This handles the unfolding options + NULL, + M_MPResetOpts, + NULL +}; + +// MULTIPLAYER JOIN BY IP +menuitem_t PLAY_MP_JoinIP[] = +{ + //{IT_NOTHING | IT_KEYHANDLER, NULL, NULL, NULL, M_MPOptSelect, 0, 0}, + + {IT_STRING | IT_CVAR | IT_CV_STRING, "IP: ", "Type the IPv4 address of the server.", + NULL, {.cvar = &cv_dummyip}, 0, 0}, + + {IT_STRING, "CONNECT ", "Attempt to connect to the server you entered the IP for.", + NULL, {NULL}, 0, 0}, + + {IT_STRING | IT_SPACE, "LAST IPs JOINED:", "Kanade best waifu :)", + NULL, {NULL}, 0, 0}, + + {IT_STRING, "servip1", "The last 3 IPs you've succesfully joined are displayed here.", + NULL, {NULL}, 0, 0}, + + {IT_STRING, "servip2", "The last 3 IPs you've succesfully joined are displayed here.", + NULL, {NULL}, 0, 0}, + + {IT_STRING, "servip3", "The last 3 IPs you've succesfully joined are displayed here.", + NULL, {NULL}, 0, 0}, + +}; + +menu_t PLAY_MP_JoinIPDef = { + sizeof (PLAY_MP_JoinIP) / sizeof (menuitem_t), + &PLAY_MP_OptSelectDef, + 0, + PLAY_MP_JoinIP, + 0, 0, + 0, 0, + -1, 1, // 1 frame transition.... This is really just because I don't want the black fade when we press esc, hehe + M_DrawMPJoinIP, + M_MPOptSelectTick, // This handles the unfolding options + NULL, + M_MPResetOpts, + M_JoinIPInputs +}; + +// MULTIPLAYER ROOM SELECT (CORE / MODDED) +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 +}; + +// SERVER BROWSER +menuitem_t PLAY_MP_ServerBrowser[] = +{ + + {IT_STRING | IT_CVAR, "SORT BY", NULL, // tooltip MUST be null. + NULL, {.cvar = &cv_serversort}, 0, 0}, + + {IT_STRING, "REFRESH", NULL, + NULL, {NULL}, 0, 0}, + + {IT_NOTHING, NULL, NULL, NULL, {NULL}, 0, 0}, +}; + +menu_t PLAY_MP_ServerBrowserDef = { + sizeof (PLAY_MP_ServerBrowser) / sizeof (menuitem_t), + &PLAY_MP_RoomSelectDef, + 0, + PLAY_MP_ServerBrowser, + 32, 36, + 0, 0, + 0, 0, + M_DrawMPServerBrowser, + M_MPServerBrowserTick, + NULL, + NULL, + M_ServerBrowserInputs +}; + +// options menu -- see mopt_e +menuitem_t OPTIONS_Main[] = +{ + + {IT_STRING | IT_CALL, "Profile Setup", "Remap keys & buttons to your likings.", + NULL, {.routine = M_ProfileSelectInit}, 0, 0}, + + {IT_STRING | IT_SUBMENU, "Video Options", "Change video settings such as the resolution.", + NULL, {.submenu = &OPTIONS_VideoDef}, 0, 0}, + + {IT_STRING | IT_SUBMENU, "Sound Options", "Adjust various sound settings such as the volume.", + NULL, {.submenu = &OPTIONS_SoundDef}, 0, 0}, + + {IT_STRING | IT_SUBMENU, "HUD Options", "Options related to the Heads-Up Display.", + NULL, {.submenu = &OPTIONS_HUDDef}, 0, 0}, + + {IT_STRING | IT_SUBMENU, "Gameplay Options", "Change various game related options", + NULL, {.submenu = &OPTIONS_GameplayDef}, 0, 0}, + + {IT_STRING | IT_SUBMENU, "Server Options", "Change various specific options for your game server.", + NULL, {.submenu = &OPTIONS_ServerDef}, 0, 0}, + + {IT_STRING | IT_SUBMENU, "Data Options", "Miscellaneous data options such as the screenshot format.", + NULL, {.submenu = &OPTIONS_DataDef}, 0, 0}, + + {IT_STRING | IT_CALL, "Tricks & Secrets", "Those who bother reading a game manual always get the edge over those who don't!", + NULL, {.routine = M_Manual}, 0, 0}, +}; + +// For options menu, the 'extra1' field will determine the background colour to use for... the background! (What a concept!) +menu_t OPTIONS_MainDef = { + sizeof (OPTIONS_Main) / sizeof (menuitem_t), + &MainDef, + 0, + OPTIONS_Main, + 0, 0, + SKINCOLOR_SLATE, 0, + 2, 5, + M_DrawOptions, + M_OptionsTick, + NULL, + NULL, + M_OptionsInputs +}; + +// profiles menu +// profile select +menuitem_t OPTIONS_Profiles[] = { + {IT_KEYHANDLER | IT_NOTHING, NULL, "Select a Profile.", + NULL, {.routine = M_HandleProfileSelect}, 0, 0}, // dummy menuitem for the control func +}; + +menu_t OPTIONS_ProfilesDef = { + sizeof (OPTIONS_Profiles) / sizeof (menuitem_t), + &OPTIONS_MainDef, + 0, + OPTIONS_Profiles, + 32, 80, + SKINCOLOR_ULTRAMARINE, 0, + 2, 5, + M_DrawProfileSelect, + M_OptionsTick, + NULL, + NULL, + NULL, +}; + +// Duplicate for main profile select. +menuitem_t MAIN_Profiles[] = { + {IT_KEYHANDLER | IT_NOTHING, NULL, "Select a profile to use or create a new Profile.", + NULL, {.routine = M_HandleProfileSelect}, 0, 0}, // dummy menuitem for the control func +}; + +menu_t MAIN_ProfilesDef = { + sizeof (MAIN_Profiles) / sizeof (menuitem_t), + NULL, + 0, + MAIN_Profiles, + 32, 80, + SKINCOLOR_ULTRAMARINE, 0, + 2, 5, + M_DrawProfileSelect, + M_OptionsTick, + NULL, + NULL, + NULL, +}; + + +menuitem_t OPTIONS_EditProfile[] = { + {IT_STRING | IT_CVAR | IT_CV_STRING, "Profile Name", "6-character long name to identify this Profile.", + NULL, {.cvar = &cv_dummyprofilename}, 0, 0}, + + {IT_STRING | IT_CVAR | IT_CV_STRING, "Player Name", "Name displayed online when using this Profile.", + NULL, {.cvar = &cv_dummyprofileplayername}, 0, 0}, + + {IT_STRING | IT_CALL, "Character", "Default character and color for this Profile.", + NULL, {.routine = M_CharacterSelect}, 0, 0}, + + {IT_STRING | IT_CALL, "Controls", "Select the button mappings for this Profile.", + NULL, {.routine = M_ProfileDeviceSelect}, 0, 0}, + + {IT_STRING | IT_CALL, "Confirm", "Confirm changes.", + NULL, {.routine = M_ConfirmProfile}, 0, 0}, + +}; + +menu_t OPTIONS_EditProfileDef = { + sizeof (OPTIONS_EditProfile) / sizeof (menuitem_t), + &OPTIONS_ProfilesDef, + 0, + OPTIONS_EditProfile, + 32, 80, + SKINCOLOR_ULTRAMARINE, 0, + 2, 5, + M_DrawEditProfile, + M_HandleProfileEdit, + NULL, + NULL, + M_ProfileEditInputs, +}; + +menuitem_t OPTIONS_ProfileControls[] = { + + {IT_HEADER, "MAIN CONTROLS", "That's the stuff on the controller!!", + NULL, {NULL}, 0, 0}, + + {IT_CONTROL, "A", "Accelerate / Confirm", + "PR_BTA", {.routine = M_ProfileSetControl}, gc_a, 0}, + + {IT_CONTROL, "B", "Look backwards / Back", + "PR_BTB", {.routine = M_ProfileSetControl}, gc_b, 0}, + + {IT_CONTROL, "C", "Spindash / Extra", + "PR_BTC", {.routine = M_ProfileSetControl}, gc_c, 0}, + + {IT_CONTROL, "X", "Brake / Back", + "PR_BTX", {.routine = M_ProfileSetControl}, gc_x, 0}, + + // @TODO What does this do??? + {IT_CONTROL, "Y", "N/A ?", + "PR_BTY", {.routine = M_ProfileSetControl}, gc_y, 0}, + + {IT_CONTROL, "Z", "N/A ?", + "PR_BTZ", {.routine = M_ProfileSetControl}, gc_z, 0}, + + {IT_CONTROL, "L", "Use item", + "PR_BTL", {.routine = M_ProfileSetControl}, gc_l, 0}, + + {IT_CONTROL, "R", "Drift", + "PR_BTR", {.routine = M_ProfileSetControl}, gc_r, 0}, + + {IT_CONTROL, "Turn Left", "Turn left", + "PR_PADL", {.routine = M_ProfileSetControl}, gc_left, 0}, + + {IT_CONTROL, "Turn Right", "Turn right", + "PR_PADR", {.routine = M_ProfileSetControl}, gc_right, 0}, + + {IT_CONTROL, "Aim Forward", "Aim forwards", + "PR_PADU", {.routine = M_ProfileSetControl}, gc_up, 0}, + + {IT_CONTROL, "Aim Backwards", "Aim backwards", + "PR_PADD", {.routine = M_ProfileSetControl}, gc_down, 0}, + + {IT_CONTROL, "Start", "Open pause menu", + "PR_BTS", {.routine = M_ProfileSetControl}, gc_start, 0}, + + {IT_HEADER, "OPTIONAL CONTROLS", "Take a screenshot, chat...", + NULL, {NULL}, 0, 0}, + + {IT_CONTROL, "SCREENSHOT", "Also usable with F8 on Keyboard.", + NULL, {.routine = M_ProfileSetControl}, gc_screenshot, 0}, + + {IT_CONTROL, "GIF CAPTURE", "Also usable with F9 on Keyboard.", + NULL, {.routine = M_ProfileSetControl}, gc_recordgif, 0}, + + {IT_CONTROL, "OPEN CHAT", "Opens chatbox in online games.", + NULL, {.routine = M_ProfileSetControl}, gc_talk, 0}, + + {IT_CONTROL, "OPEN TEAM CHAT", "Do we even have team gamemodes?", + NULL, {.routine = M_ProfileSetControl}, gc_teamtalk, 0}, + + {IT_CONTROL, "OPEN CONSOLE", "Opens the developer options console.", + NULL, {.routine = M_ProfileSetControl}, gc_console, 0}, + + {IT_CONTROL, "LUA/A", "May be used by add-ons.", + NULL, {.routine = M_ProfileSetControl}, gc_luaa, 0}, + + {IT_CONTROL, "LUA/B", "May be used by add-ons.", + NULL, {.routine = M_ProfileSetControl}, gc_luab, 0}, + + {IT_CONTROL, "LUA/C", "May be used by add-ons.", + NULL, {.routine = M_ProfileSetControl}, gc_luac, 0}, + + {IT_HEADER, "TOGGLES", "For per-player commands", + NULL, {NULL}, 0, 0}, + + {IT_CONTROL | IT_CVAR, "KICKSTART ACCEL", "Hold A to auto-accel. Tap it to cancel.", + NULL, {.cvar = &cv_dummyprofilekickstart}, 0, 0}, + + {IT_HEADER, "EXTRA", "", + NULL, {NULL}, 0, 0}, + + {IT_STRING | IT_CALL, "TRY MAPPINGS", "Test your controls.", + NULL, {.routine = M_ProfileTryController}, 0, 0}, + + {IT_STRING | IT_CALL, "CONFIRM", "Go back to profile setup.", + NULL, {.routine = M_ProfileControlsConfirm}, 0, 0}, +}; + + + +menu_t OPTIONS_ProfileControlsDef = { + sizeof (OPTIONS_ProfileControls) / sizeof (menuitem_t), + &OPTIONS_EditProfileDef, + 0, + OPTIONS_ProfileControls, + 32, 80, + SKINCOLOR_ULTRAMARINE, 0, + 3, 5, + M_DrawProfileControls, + M_HandleProfileControls, + NULL, + NULL, + M_ProfileControlsInputs, +}; + +// video options menu... +// options menu +menuitem_t OPTIONS_Video[] = +{ + + {IT_STRING | IT_CALL, "Set Resolution...", "Change the screen resolution for the game.", + NULL, {.routine = M_VideoModeMenu}, 0, 0}, + +// A check to see if you're not running on a fucking antique potato powered stone i guess??????? + +#if (defined (__unix__) && !defined (MSDOS)) || defined (UNIXCOMMON) || defined (HAVE_SDL) + {IT_STRING | IT_CVAR, "Fullscreen", "Set whether you want to use fullscreen or windowed mode.", + NULL, {.cvar = &cv_fullscreen}, 0, 0}, +#endif + + {IT_NOTHING|IT_SPACE, NULL, "Kanade best waifu! I promise!", + NULL, {NULL}, 0, 0}, + + // Everytime I see a screenshot at max gamma I die inside + {IT_STRING | IT_CVAR | IT_CV_SLIDER, "Gamma", "Adjusts the overall brightness of the game.", + NULL, {.cvar = &cv_globalgamma}, 0, 0}, + + {IT_STRING | IT_CVAR, "FPS Cap", "Handles the refresh rate of the game (does not affect gamelogic).", + NULL, {.cvar = &cv_fpscap}, 0, 0}, + + {IT_STRING | IT_CVAR, "Enable Skyboxes", "Turning this off will improve performance at the detriment of visuals for many maps.", + NULL, {.cvar = &cv_skybox}, 0, 0}, + + {IT_STRING | IT_CVAR, "Draw Distance", "How far objects can be drawn. Lower values may improve performance at the cost of visibility.", + NULL, {.cvar = &cv_drawdist}, 0, 0}, + + {IT_STRING | IT_CVAR, "Weather Draw Distance", "Affects how far weather visuals can be drawn. Lower values improve performance.", + NULL, {.cvar = &cv_drawdist_precip}, 0, 0}, + + {IT_STRING | IT_CVAR, "Show FPS", "Displays the game framerate at the lower right corner of the screen.", + NULL, {.cvar = &cv_ticrate}, 0, 0}, + + {IT_NOTHING|IT_SPACE, NULL, "Kanade best waifu! I promise!", + NULL, {NULL}, 0, 0}, + +#ifdef HWRENDER + {IT_STRING | IT_SUBMENU, "Hardware Options...", "For usage and configuration of the OpenGL renderer.", + NULL, {.submenu = &OPTIONS_VideoOGLDef}, 0, 0}, +#endif + +}; + +menu_t OPTIONS_VideoDef = { + sizeof (OPTIONS_Video) / sizeof (menuitem_t), + &OPTIONS_MainDef, + 0, + OPTIONS_Video, + 32, 80, + SKINCOLOR_PLAGUE, 0, + 2, 5, + M_DrawGenericOptions, + M_OptionsTick, + NULL, + NULL, + NULL, +}; + +menuitem_t OPTIONS_VideoModes[] = { + + {IT_KEYHANDLER | IT_NOTHING, NULL, "Select a resolution.", + NULL, {.routine = M_HandleVideoModes}, 0, 0}, // dummy menuitem for the control func + +}; + +menu_t OPTIONS_VideoModesDef = { + sizeof (OPTIONS_VideoModes) / sizeof (menuitem_t), + &OPTIONS_VideoDef, + 0, + OPTIONS_VideoModes, + 48, 80, + SKINCOLOR_PLAGUE, 0, + 2, 5, + M_DrawVideoModes, + M_OptionsTick, + NULL, + NULL, + NULL, +}; + +#ifdef HWRENDER +menuitem_t OPTIONS_VideoOGL[] = +{ + + {IT_STRING | IT_CVAR, "Renderer", "Change renderers between Software and OpenGL", + NULL, {.cvar = &cv_renderer}, 0, 0}, + + {IT_SPACE | IT_NOTHING, NULL, NULL, + NULL, {NULL}, 0, 0}, + + {IT_HEADER, "OPTIONS BELOW ARE OPENGL ONLY!", "Watch people get confused anyway!!", + NULL, {NULL}, 0, 0}, + + {IT_STRING | IT_CVAR, "3D Models", "Use 3D models instead of sprites when applicable.", + NULL, {.cvar = &cv_glmodels}, 0, 0}, + + {IT_STRING | IT_CVAR, "Shaders", "Use GLSL Shaders. Turning them off increases performance at the expanse of visual quality.", + NULL, {.cvar = &cv_glshaders}, 0, 0}, + + {IT_SPACE | IT_NOTHING, NULL, NULL, + NULL, {NULL}, 0, 0}, + + {IT_STRING | IT_CVAR, "Texture Quality", "Texture depth. Higher values are recommended.", + NULL, {.cvar = &cv_scr_depth}, 0, 0}, + + {IT_STRING | IT_CVAR, "Texture Filter", "Texture Filter. Nearest is recommended.", + NULL, {.cvar = &cv_glfiltermode}, 0, 0}, + + {IT_STRING | IT_CVAR, "Anisotropic", "Lower values will improve performance at a minor quality loss.", + NULL, {.cvar = &cv_glanisotropicmode}, 0, 0}, + + {IT_SPACE | IT_NOTHING, NULL, NULL, + NULL, {NULL}, 0, 0}, + + {IT_STRING | IT_CVAR, "Sprite Billboarding", "Adjusts sprites when viewed from above or below to not make them appear flat.", + NULL, {.cvar = &cv_glspritebillboarding}, 0, 0}, + + {IT_STRING | IT_CVAR, "Software Perspective", "Emulates Software shearing when looking up or down. Not recommended.", + NULL, {.cvar = &cv_glshearing}, 0, 0}, +}; + +menu_t OPTIONS_VideoOGLDef = { + sizeof (OPTIONS_VideoOGL) / sizeof (menuitem_t), + &OPTIONS_VideoDef, + 0, + OPTIONS_VideoOGL, + 32, 80, + SKINCOLOR_PLAGUE, 0, + 2, 5, + M_DrawGenericOptions, + M_OptionsTick, + NULL, + NULL, + NULL, +}; +#endif + +menuitem_t OPTIONS_Sound[] = +{ + + {IT_STRING | IT_CVAR, "SFX", "Enable or disable sound effect playback.", + NULL, {.cvar = &cv_gamesounds}, 0, 0}, + + {IT_STRING | IT_CVAR | IT_CV_SLIDER, "SFX Volume", "Adjust the volume of sound effects.", + NULL, {.cvar = &cv_soundvolume}, 0, 0}, + + {IT_STRING | IT_CVAR, "Music", "Enable or disable music playback.", + NULL, {.cvar = &cv_gamedigimusic}, 0, 0}, + + {IT_STRING | IT_CVAR | IT_CV_SLIDER, "Music Volume", "Adjust the volume of music playback.", + NULL, {.cvar = &cv_digmusicvolume}, 0, 0}, + + {IT_SPACE | IT_NOTHING, NULL, NULL, + NULL, {NULL}, 0, 0}, + + {IT_STRING | IT_CVAR, "Reverse L/R Channels", "Reverse left & right channels for Stereo playback.", + NULL, {.cvar = &stereoreverse}, 0, 0}, + + {IT_STRING | IT_CVAR, "Surround", "Enables or disable Surround sound playback.", + NULL, {.cvar = &surround}, 0, 0}, + + {IT_SPACE | IT_NOTHING, NULL, NULL, + NULL, {NULL}, 0, 0}, + + {IT_STRING | IT_CVAR, "Chat Notifications", "Set when to play notification sounds when chat messages are received.", + NULL, {.cvar = &cv_chatnotifications}, 0, 0}, + + {IT_STRING | IT_CVAR, "Character Voices", "Set how often to play character voices in game.", + NULL, {.cvar = &cv_kartvoices}, 0, 0}, + + {IT_STRING | IT_CVAR, "Powerup Warning", "Set how to warn you from other player's powerups such as Invincibility.", + NULL, {.cvar = &cv_kartinvinsfx}, 0, 0}, + + {IT_SPACE | IT_NOTHING, NULL, NULL, + NULL, {NULL}, 0, 0}, + + {IT_STRING | IT_CVAR, "Play Music While Unfocused", "Keeps playing music even if the game is not the active window.", + NULL, {.cvar = &cv_playmusicifunfocused}, 0, 0}, + + {IT_STRING | IT_CVAR, "Play SFX While Unfocused", "Keeps playing sound effects even if the game is not the active window.", + NULL, {.cvar = &cv_playsoundifunfocused}, 0, 0}, + + // @TODO: Sound test (there's currently no space on this menu, might be better to throw it in extras?) +}; + +menu_t OPTIONS_SoundDef = { + sizeof (OPTIONS_Sound) / sizeof (menuitem_t), + &OPTIONS_MainDef, + 0, + OPTIONS_Sound, + 48, 80, + SKINCOLOR_THUNDER, 0, + 2, 5, + M_DrawGenericOptions, + M_OptionsTick, + NULL, + NULL, + NULL, +}; + +menuitem_t OPTIONS_HUD[] = +{ + + {IT_STRING | IT_CVAR, "Show HUD (F3)", "Toggles HUD display. Great for taking screenshots!", + NULL, {.cvar = &cv_showhud}, 0, 0}, + + {IT_STRING | IT_CVAR | IT_CV_SLIDER, "HUD Opacity", "Non opaque values may have performance impacts in software mode.", + NULL, {.cvar = &cv_translucenthud}, 0, 0}, + + {IT_SPACE | IT_NOTHING, NULL, NULL, + NULL, {NULL}, 0, 0}, + + {IT_STRING | IT_CVAR | IT_CV_SLIDER, "Minimap Opacity", "Changes the opacity of the minimap.", + NULL, {.cvar = &cv_kartminimap}, 0, 0}, + + {IT_STRING | IT_CVAR, "Speedometer", "Choose to what speed unit to display or toggle off the speedometer.", + NULL, {.cvar = &cv_kartspeedometer}, 0, 0}, + + {IT_STRING | IT_CVAR, "Display \"CHECK\"", "Displays an icon when a player is tailing you.", + NULL, {.cvar = &cv_kartcheck}, 0, 0}, + + {IT_SPACE | IT_NOTHING, NULL, NULL, + NULL, {NULL}, 0, 0}, + + {IT_STRING | IT_CVAR, "Console Text Size", "Size of the text within the console.", + NULL, {.cvar = &cv_constextsize}, 0, 0}, + + // we spell words properly here. + {IT_STRING | IT_CVAR, "Console Tint", "Change the background colour of the console.", + NULL, {.cvar = &cons_backcolor}, 0, 0}, + + {IT_STRING | IT_CVAR, "Show \"FOCUS LOST\"", "Displays \"FOCUS LOST\" when the game window isn't the active window.", + NULL, {.cvar = &cv_showfocuslost}, 0, 0}, + + {IT_SPACE | IT_NOTHING, NULL, NULL, + NULL, {NULL}, 0, 0}, + + {IT_STRING | IT_SUBMENU, "Online HUD Options...", "HUD options related to the online chat box and other features.", + NULL, {.submenu = &OPTIONS_HUDOnlineDef}, 0, 0}, +}; + +menu_t OPTIONS_HUDDef = { + sizeof (OPTIONS_HUD) / sizeof (menuitem_t), + &OPTIONS_MainDef, + 0, + OPTIONS_HUD, + 48, 80, + SKINCOLOR_SUNSLAM, 0, + 2, 5, + M_DrawGenericOptions, + M_OptionsTick, + NULL, + NULL, + NULL, +}; + +menuitem_t OPTIONS_HUDOnline[] = +{ + + {IT_STRING | IT_CVAR, "Chat Mode", "Choose whether to display chat in its own window or the console.", + NULL, {.cvar = &cv_consolechat}, 0, 0}, + + {IT_SPACE | IT_NOTHING, NULL, NULL, + NULL, {NULL}, 0, 0}, + + {IT_STRING | IT_CVAR, "Chat Box Tint", "Changes the background colour of the chat box.", + NULL, {.cvar = &cv_chatbacktint}, 0, 0}, + + {IT_STRING | IT_CVAR | IT_CV_SLIDER, "Chat Box Width", "Change the width of the Chat Box", + NULL, {.cvar = &cv_chatwidth}, 0, 0}, + + {IT_STRING | IT_CVAR | IT_CV_SLIDER, "Chat Box Height", "Change the height of the Chat Box", + NULL, {.cvar = &cv_chatheight}, 0, 0}, + + {IT_SPACE | IT_NOTHING, NULL, NULL, + NULL, {NULL}, 0, 0}, + + {IT_STRING | IT_CVAR, "Message Fadeout Time", "How long chat messages stay displayed with the chat closed.", + NULL, {.cvar = &cv_chattime}, 0, 0}, + + {IT_STRING | IT_CVAR, "Spam Protection", "Prevents too many message from a single player from being displayed.", + NULL, {.cvar = &cv_chatspamprotection}, 0, 0}, + + {IT_SPACE | IT_NOTHING, NULL, NULL, + NULL, {NULL}, 0, 0}, + + {IT_STRING | IT_CVAR, "Local Ping Display", "In netgames, displays your ping at the lower right corner of the screen.", + NULL, {.cvar = &cv_showping}, 0, 0}, + +}; + +menu_t OPTIONS_HUDOnlineDef = { + sizeof (OPTIONS_HUDOnline) / sizeof (menuitem_t), + &OPTIONS_HUDDef, + 0, + OPTIONS_HUDOnline, + 48, 80, + SKINCOLOR_SUNSLAM, 0, + 2, 5, + M_DrawGenericOptions, + M_OptionsTick, + NULL, + NULL, + NULL, +}; + +// Gameplay options -- see gopt_e +menuitem_t OPTIONS_Gameplay[] = +{ + + {IT_STRING | IT_CVAR, "Game Speed", "Change Game Speed for the next map.", + NULL, {.cvar = &cv_kartspeed}, 0, 0}, + + {IT_STRING | IT_CVAR, "Base Lap Count", "Change how many laps must be completed per race.", + NULL, {.cvar = &cv_numlaps}, 0, 0}, + + {IT_STRING | IT_CVAR, "Frantic Items", "Make item odds crazier with more powerful items!", + NULL, {.cvar = &cv_kartfrantic}, 0, 0}, + + {IT_STRING | IT_CVAR, "Encore Mode", "Forces Encore Mode on for the next map.", + NULL, {.cvar = &cv_kartencore}, 0, 0}, + + {IT_STRING | IT_CVAR, "Exit Countdown", "How long players have to finish after 1st place finishes.", + NULL, {.cvar = &cv_countdowntime}, 0, 0}, + + {IT_SPACE | IT_NOTHING, NULL, NULL, + NULL, {NULL}, 0, 0}, + + {IT_STRING | IT_CVAR, "Time Limit", "Change the time limit for Battle rounds.", + NULL, {.cvar = &cv_timelimit}, 0, 0}, + + {IT_STRING | IT_CVAR, "Starting Bumpers", "Change how many bumpers player start with in Battle.", + NULL, {.cvar = &cv_kartbumpers}, 0, 0}, + + {IT_STRING | IT_CVAR, "Karma Comeback", "Enable Karma Comeback in Battle mode.", + NULL, {.cvar = &cv_kartcomeback}, 0, 0}, + + {IT_SPACE | IT_NOTHING, NULL, NULL, + NULL, {NULL}, 0, 0}, + + {IT_STRING | IT_SUBMENU, "Random Item Toggles...", "Change which items to enable for your games.", + NULL, {.submenu = &OPTIONS_GameplayItemsDef}, 0, 0}, + +}; + +menu_t OPTIONS_GameplayDef = { + sizeof (OPTIONS_Gameplay) / sizeof (menuitem_t), + &OPTIONS_MainDef, + 0, + OPTIONS_Gameplay, + 48, 80, + SKINCOLOR_SCARLET, 0, + 2, 5, + M_DrawGenericOptions, + M_OptionsTick, + NULL, + NULL, + NULL, +}; + +menuitem_t OPTIONS_GameplayItems[] = +{ + // Mostly handled by the drawing function. + {IT_KEYHANDLER | IT_NOTHING, NULL, "Super Rings", NULL, {.routine = M_HandleItemToggles}, KITEM_SUPERRING, 0}, + {IT_KEYHANDLER | IT_NOTHING, NULL, "Self-Propelled Bombs", NULL, {.routine = M_HandleItemToggles}, KITEM_SPB, 0}, + {IT_KEYHANDLER | IT_NOTHING, NULL, "Eggman Marks", NULL, {.routine = M_HandleItemToggles}, KITEM_EGGMAN, 0}, + {IT_KEYHANDLER | IT_NOTHING, NULL, "Toggle All", NULL, {.routine = M_HandleItemToggles}, 0, 0}, + + {IT_KEYHANDLER | IT_NOTHING, NULL, "Sneakers", NULL, {.routine = M_HandleItemToggles}, KITEM_SNEAKER, 0}, + {IT_KEYHANDLER | IT_NOTHING, NULL, "Sneakers x2", NULL, {.routine = M_HandleItemToggles}, KRITEM_DUALSNEAKER, 0}, + {IT_KEYHANDLER | IT_NOTHING, NULL, "Sneakers x3", NULL, {.routine = M_HandleItemToggles}, KRITEM_TRIPLESNEAKER, 0}, + {IT_KEYHANDLER | IT_NOTHING, NULL, "Rocket Sneakers", NULL, {.routine = M_HandleItemToggles}, KITEM_ROCKETSNEAKER, 0}, + + {IT_KEYHANDLER | IT_NOTHING, NULL, "Bananas", NULL, {.routine = M_HandleItemToggles}, KITEM_BANANA, 0}, + {IT_KEYHANDLER | IT_NOTHING, NULL, "Bananas x3", NULL, {.routine = M_HandleItemToggles}, KRITEM_TRIPLEBANANA, 0}, + {IT_KEYHANDLER | IT_NOTHING, NULL, "Bananas x10", NULL, {.routine = M_HandleItemToggles}, KRITEM_TENFOLDBANANA, 0}, + {IT_KEYHANDLER | IT_NOTHING, NULL, "Proximity Mines", NULL, {.routine = M_HandleItemToggles}, KITEM_MINE, 0}, + + {IT_KEYHANDLER | IT_NOTHING, NULL, "Orbinauts", NULL, {.routine = M_HandleItemToggles}, KITEM_ORBINAUT, 0}, + {IT_KEYHANDLER | IT_NOTHING, NULL, "Orbinauts x3", NULL, {.routine = M_HandleItemToggles}, KRITEM_TRIPLEORBINAUT, 0}, + {IT_KEYHANDLER | IT_NOTHING, NULL, "Orbinauts x4", NULL, {.routine = M_HandleItemToggles}, KRITEM_QUADORBINAUT, 0}, + {IT_KEYHANDLER | IT_NOTHING, NULL, "Land Mines", NULL, {.routine = M_HandleItemToggles}, KITEM_LANDMINE, 0}, + + {IT_KEYHANDLER | IT_NOTHING, NULL, "Jawz", NULL, {.routine = M_HandleItemToggles}, KITEM_JAWZ, 0}, + {IT_KEYHANDLER | IT_NOTHING, NULL, "Jawz x2", NULL, {.routine = M_HandleItemToggles}, KRITEM_DUALJAWZ, 0}, + {IT_KEYHANDLER | IT_NOTHING, NULL, "Ballhogs", NULL, {.routine = M_HandleItemToggles}, KITEM_BALLHOG, 0}, + {IT_KEYHANDLER | IT_NOTHING, NULL, "Drop Targets", NULL, {.routine = M_HandleItemToggles}, KITEM_DROPTARGET, sfx_s258}, + + {IT_KEYHANDLER | IT_NOTHING, NULL, "Lightning Shields", NULL, {.routine = M_HandleItemToggles}, KITEM_LIGHTNINGSHIELD, 0}, + {IT_KEYHANDLER | IT_NOTHING, NULL, "Bubble Shields", NULL, {.routine = M_HandleItemToggles}, KITEM_BUBBLESHIELD, 0}, + {IT_KEYHANDLER | IT_NOTHING, NULL, "Flame Shields", NULL, {.routine = M_HandleItemToggles}, KITEM_FLAMESHIELD, 0}, + {IT_KEYHANDLER | IT_NOTHING, NULL, "Hyudoros", NULL, {.routine = M_HandleItemToggles}, KITEM_HYUDORO, 0}, + + {IT_KEYHANDLER | IT_NOTHING, NULL, "Invinciblity", NULL, {.routine = M_HandleItemToggles}, KITEM_INVINCIBILITY, 0}, + {IT_KEYHANDLER | IT_NOTHING, NULL, "Grow", NULL, {.routine = M_HandleItemToggles}, KITEM_GROW, 0}, + {IT_KEYHANDLER | IT_NOTHING, NULL, "Shrink", NULL, {.routine = M_HandleItemToggles}, KITEM_SHRINK, 0}, + {IT_KEYHANDLER | IT_NOTHING, NULL, NULL, NULL, {.routine = M_HandleItemToggles}, 255, 0}, + + {IT_KEYHANDLER | IT_NOTHING, NULL, "Pogo Springs", NULL, {.routine = M_HandleItemToggles}, KITEM_POGOSPRING, 0}, + {IT_KEYHANDLER | IT_NOTHING, NULL, "Kitchen Sinks", NULL, {.routine = M_HandleItemToggles}, KITEM_KITCHENSINK, 0}, + {IT_KEYHANDLER | IT_NOTHING, NULL, NULL, NULL, {.routine = M_HandleItemToggles}, 255, 0}, + {IT_KEYHANDLER | IT_NOTHING, NULL, NULL, NULL, {.routine = M_HandleItemToggles}, 255, 0} +}; + +menu_t OPTIONS_GameplayItemsDef = { + sizeof (OPTIONS_GameplayItems) / sizeof (menuitem_t), + &OPTIONS_GameplayDef, + 0, + OPTIONS_GameplayItems, + 14, 40, + SKINCOLOR_SCARLET, 0, + 2, 5, + M_DrawItemToggles, + M_OptionsTick, + NULL, + NULL, + NULL, +}; + +menuitem_t OPTIONS_Server[] = +{ + + {IT_STRING | IT_CVAR | IT_CV_STRING, "Server Name", "Change the name of your server.", + NULL, {.cvar = &cv_servername}, 0, 0}, + + {IT_STRING | IT_CVAR, "Intermission", "Set how long to stay on the result screen.", + NULL, {.cvar = &cv_inttime}, 0, 0}, + + {IT_STRING | IT_CVAR, "Map Progression", "Set how the next map is chosen.", + NULL, {.cvar = &cv_advancemap}, 0, 0}, + + {IT_STRING | IT_CVAR, "Vote Timer", "Set how long players have to vote.", + NULL, {.cvar = &cv_votetime}, 0, 0}, + + {IT_STRING | IT_CVAR, "Vote Mode Change", "Set how often voting proposes a different gamemode.", + NULL, {.cvar = &cv_kartvoterulechanges}, 0, 0}, + +#ifndef NONET + + {IT_SPACE | IT_NOTHING, NULL, NULL, + NULL, {NULL}, 0, 0}, + + {IT_STRING | IT_CVAR, "Maximum Players", "How many players can play at once.", + NULL, {.cvar = &cv_maxplayers}, 0, 0}, + + {IT_STRING | IT_CVAR, "Maximum Connections", "How many players & spectators can connect to the server.", + NULL, {.cvar = &cv_maxconnections}, 0, 0}, + + {IT_STRING | IT_CVAR, "Allow Joining", "Sets whether players can connect to your server.", + NULL, {.cvar = &cv_allownewplayer}, 0, 0}, + + {IT_STRING | IT_CVAR, "Allow Downloads", "Allows joiners to download missing files from you.", + NULL, {.cvar = &cv_downloading}, 0, 0}, + + {IT_STRING | IT_CVAR, "Pause Permissions", "Sets who can pause the game.", + NULL, {.cvar = &cv_pause}, 0, 0}, + + {IT_STRING | IT_CVAR, "Mute Chat", "Prevents non-admins from sending chat messages.", + NULL, {.cvar = &cv_mute}, 0, 0}, + + {IT_SPACE | IT_NOTHING, NULL, NULL, + NULL, {NULL}, 0, 0}, + + {IT_STRING | IT_SUBMENU, "Advanced...", "Advanced options. Be careful when messing with these!", + NULL, {.submenu = &OPTIONS_ServerAdvancedDef}, 0, 0}, + +#endif +}; + +menu_t OPTIONS_ServerDef = { + sizeof (OPTIONS_Server) / sizeof (menuitem_t), + &OPTIONS_MainDef, + 0, + OPTIONS_Server, + 48, 70, // This menu here is slightly higher because there's a lot of options... + SKINCOLOR_VIOLET, 0, + 2, 5, + M_DrawGenericOptions, + M_OptionsTick, + NULL, + NULL, + NULL, +}; + +#ifndef NONET +menuitem_t OPTIONS_ServerAdvanced[] = +{ + + {IT_STRING | IT_CVAR | IT_CV_STRING, "Server Browser Address", "Default is \'https://ms.kartkrew.org/ms/api\'", + NULL, {.cvar = &cv_masterserver}, 0, 0}, + + {IT_STRING | IT_CVAR, "Resynch. Attempts", "How many times to attempt sending data to desynchronized players.", + NULL, {.cvar = &cv_resynchattempts}, 0, 0}, + + {IT_STRING | IT_CVAR, "Ping Limit (ms)", "Players above the ping limit will get kicked from the server.", + NULL, {.cvar = &cv_maxping}, 0, 0}, + + {IT_STRING | IT_CVAR, "Ping Timeout (s)", "Players must be above the ping limit for this long before being kicked.", + NULL, {.cvar = &cv_pingtimeout}, 0, 0}, + + {IT_STRING | IT_CVAR, "Connection Timeout (tics)", "Players not giving any netowrk activity for this long are kicked.", + NULL, {.cvar = &cv_nettimeout}, 0, 0}, + + {IT_STRING | IT_CVAR, "Join Timeout (tics)", "Players taking too long to join are kicked.", + NULL, {.cvar = &cv_jointimeout}, 0, 0}, + + {IT_SPACE | IT_NOTHING, NULL, NULL, + NULL, {NULL}, 0, 0}, + + {IT_STRING | IT_CVAR, "Max File Transfer", "Maximum size of the files that can be downloaded from joining clients. (KB)", + NULL, {.cvar = &cv_maxsend}, 0, 0}, + + {IT_STRING | IT_CVAR, "File Transfer Speed", "File transfer packet rate. Larger values send more data.", + NULL, {.cvar = &cv_downloadspeed}, 0, 0}, + + {IT_SPACE | IT_NOTHING, NULL, NULL, + NULL, {NULL}, 0, 0}, + + {IT_STRING | IT_CVAR, "Log Joiner IPs", "Shows the IP of connecting players.", + NULL, {.cvar = &cv_showjoinaddress}, 0, 0}, + + {IT_STRING | IT_CVAR, "Log Resynch", "Shows which players need resynchronization.", + NULL, {.cvar = &cv_blamecfail}, 0, 0}, + + {IT_STRING | IT_CVAR, "Log Transfers", "Shows when clients are downloading files from you.", + NULL, {.cvar = &cv_noticedownload}, 0, 0}, +}; + +menu_t OPTIONS_ServerAdvancedDef = { + sizeof (OPTIONS_ServerAdvanced) / sizeof (menuitem_t), + &OPTIONS_ServerDef, + 0, + OPTIONS_ServerAdvanced, + 48, 70, // This menu here is slightly higher because there's a lot of options... + SKINCOLOR_VIOLET, 0, + 2, 5, + M_DrawGenericOptions, + M_OptionsTick, + NULL, + NULL, + NULL, +}; +#endif + +// data options menu -- see dopt_e +menuitem_t OPTIONS_Data[] = +{ + + {IT_STRING | IT_SUBMENU, "Screenshot Options...", "Set options relative to screenshot and GIF capture.", + NULL, {.submenu = &OPTIONS_DataScreenshotDef}, 0, 0}, + + {IT_STRING | IT_SUBMENU, "Addon Options...", "Set options relative to the addons menu.", + NULL, {.submenu = &OPTIONS_DataAddonDef}, 0, 0}, + + {IT_STRING | IT_SUBMENU, "Replay Options...", "Set options relative to replays.", + NULL, {.submenu = &OPTIONS_DataReplayDef}, 0, 0}, + +#ifdef HAVE_DISCORDRPC + {IT_STRING | IT_SUBMENU, "Discord Options...", "Set options relative to Discord Rich Presence.", + NULL, {.submenu = &OPTIONS_DataDiscordDef}, 0, 0}, +#endif + + {IT_SPACE | IT_NOTHING, NULL, NULL, + NULL, {NULL}, 0, 0}, + + {IT_STRING | IT_SUBMENU, "\x85""Erase Data...", "Erase specific data. Be careful, what's deleted is gone forever!", + NULL, {.submenu = &OPTIONS_DataEraseDef}, 0, 0}, + +}; + +menu_t OPTIONS_DataDef = { + sizeof (OPTIONS_Data) / sizeof (menuitem_t), + &OPTIONS_MainDef, + 0, + OPTIONS_Data, + 48, 80, + SKINCOLOR_BLUEBERRY, 0, + 2, 5, + M_DrawGenericOptions, + M_OptionsTick, + NULL, + NULL, + NULL, +}; + +menuitem_t OPTIONS_DataAddon[] = +{ + + {IT_HEADER, "MENU", NULL, + NULL, {NULL}, 0, 0}, + + {IT_STRING | IT_CVAR, "Location", "Where to start searching addons from in the menu.", + NULL, {.cvar = &cv_addons_option}, 0, 0}, + + {IT_STRING | IT_CVAR | IT_CV_STRING, "Custom Folder", "Specify which folder to start searching from if the location is set to custom.", + NULL, {.cvar = &cv_addons_folder}, 24, 0}, + + {IT_STRING | IT_CVAR, "Identify Addons via", "Set whether to consider the extension or contents of a file.", + NULL, {.cvar = &cv_addons_md5}, 0, 0}, + + {IT_STRING | IT_CVAR, "Show Unsupported Files", "Sets whether non-addon files should be shown.", + NULL, {.cvar = &cv_addons_showall}, 0, 0}, + + {IT_SPACE | IT_NOTHING, NULL, NULL, + NULL, {NULL}, 0, 0}, + + {IT_HEADER, "SEARCH", NULL, + NULL, {NULL}, 0, 0}, + + {IT_STRING | IT_CVAR, "Matching", "Set where to check for the text pattern when looking up addons via name.", + NULL, {.cvar = &cv_addons_search_type}, 0, 0}, + + {IT_STRING | IT_CVAR, "Case Sensitivity", "Set whether to consider the case when searching for addons..", + NULL, {.cvar = &cv_addons_search_case}, 0, 0}, + +}; + +menu_t OPTIONS_DataAddonDef = { + sizeof (OPTIONS_DataAddon) / sizeof (menuitem_t), + &OPTIONS_DataDef, + 0, + OPTIONS_DataAddon, + 48, 80, + SKINCOLOR_BLUEBERRY, 0, + 2, 5, + M_DrawGenericOptions, + M_OptionsTick, + NULL, + NULL, + NULL, +}; + +menuitem_t OPTIONS_DataScreenshot[] = +{ + + {IT_HEADER, "SCREENSHOTS (F8)", NULL, + NULL, {NULL}, 0, 0}, + + {IT_STRING | IT_CVAR, "Storage Location", "Sets where to store screenshots.", + NULL, {.cvar = &cv_screenshot_option}, 0, 0}, + + {IT_STRING | IT_CVAR | IT_CV_STRING, "Custom Folder", "Specify which folder to save screenshots in.", + NULL, {.cvar = &cv_screenshot_folder}, 24, 0}, + + {IT_SPACE | IT_NOTHING, NULL, NULL, + NULL, {NULL}, 0, 0}, + + {IT_HEADER, "GIF RECORDING (F9)", NULL, + NULL, {NULL}, 0, 0}, + + {IT_STRING | IT_CVAR, "Storage Location", "Sets where to store GIFs", + NULL, {.cvar = &cv_movie_option}, 0, 0}, + + {IT_STRING | IT_CVAR | IT_CV_STRING, "Custom Folder", "Specify which folder to save GIFs in.", + NULL, {.cvar = &cv_movie_folder}, 24, 0}, + +}; + +menu_t OPTIONS_DataScreenshotDef = { + sizeof (OPTIONS_DataScreenshot) / sizeof (menuitem_t), + &OPTIONS_DataDef, + 0, + OPTIONS_DataScreenshot, + 48, 80, + SKINCOLOR_BLUEBERRY, 0, + 2, 5, + M_DrawGenericOptions, + M_OptionsTick, + NULL, + NULL, + NULL, +}; + +menuitem_t OPTIONS_DataReplay[] = +{ + {IT_STRING | IT_CVAR, "Record Replays", "Select when to save replays.", + NULL, {.cvar = &cv_recordmultiplayerdemos}, 0, 0}, + + {IT_STRING | IT_CVAR, "Synch. Check Interval", "How often to check for synchronization while playing back a replay.", + NULL, {.cvar = &cv_netdemosyncquality}, 0, 0}, +}; + +menu_t OPTIONS_DataReplayDef = { + sizeof (OPTIONS_DataReplay) / sizeof (menuitem_t), + &OPTIONS_DataDef, + 0, + OPTIONS_DataReplay, + 48, 80, + SKINCOLOR_BLUEBERRY, 0, + 2, 5, + M_DrawGenericOptions, + M_OptionsTick, + NULL, + NULL, + NULL, +}; + +#ifdef HAVE_DISCORDRPC +menuitem_t OPTIONS_DataDiscord[] = +{ + {IT_STRING | IT_CVAR, "Rich Presence", "Allow Discord to display game info on your status.", + NULL, {.cvar = &cv_discordrp}, 0, 0}, + + {IT_SPACE | IT_NOTHING, NULL, NULL, + NULL, {NULL}, 0, 0}, + + {IT_HEADER, "RICH PRESENCE SETTINGS", NULL, + NULL, {NULL}, 0, 0}, + + {IT_STRING | IT_CVAR, "Streamer Mode", "Prevents the logging of some account information such as your tag in the console.", + NULL, {.cvar = &cv_discordstreamer}, 0, 0}, + + {IT_STRING | IT_CVAR, "Allow Ask to Join", "Allow other people to request joining your game from Discord.", + NULL, {.cvar = &cv_discordasks}, 0, 0}, + + {IT_STRING | IT_CVAR, "Allow Invites", "Set who is allowed to generate Discord invites to your game.", + NULL, {.cvar = &cv_discordinvites}, 0, 0}, + +}; + +menu_t OPTIONS_DataDiscordDef = { + sizeof (OPTIONS_DataDiscord) / sizeof (menuitem_t), + &OPTIONS_DataDef, + 0, + OPTIONS_DataDiscord, + 48, 80, + SKINCOLOR_BLUEBERRY, 0, + 2, 5, + M_DrawGenericOptions, + M_OptionsTick, + NULL, + NULL, + NULL, +}; +#endif + + +menuitem_t OPTIONS_DataErase[] = +{ + + {IT_STRING | IT_CALL, "Erase Time Attack Data", "Be careful! What's deleted is gone forever!", + NULL, {.routine = M_EraseData}, 0, 0}, + + {IT_STRING | IT_CALL, "Erase Unlockable Data", "Be careful! What's deleted is gone forever!", + NULL, {.routine = M_EraseData}, 0, 0}, + + {IT_SPACE | IT_NOTHING, NULL, NULL, + NULL, {NULL}, 0, 0}, + + {IT_STRING | IT_CALL, "Erase Profile Data...", "Select a Profile to erase.", + NULL, {.routine = M_CheckProfileData}, 0, 0}, + + {IT_SPACE | IT_NOTHING, NULL, NULL, + NULL, {NULL}, 0, 0}, + + {IT_STRING | IT_CALL, "\x85\x45rase all Data", "Be careful! What's deleted is gone forever!", + NULL, {.routine = M_EraseData}, 0, 0}, + +}; + +menu_t OPTIONS_DataEraseDef = { + sizeof (OPTIONS_DataErase) / sizeof (menuitem_t), + &OPTIONS_DataDef, + 0, + OPTIONS_DataErase, + 48, 80, + SKINCOLOR_BLUEBERRY, 0, + 2, 5, + M_DrawGenericOptions, + M_OptionsTick, + NULL, + NULL, + NULL, +}; + +menuitem_t OPTIONS_DataProfileErase[] = +{ + {IT_NOTHING | IT_KEYHANDLER, NULL, NULL, NULL, {.routine = M_HandleProfileErase}, 0, 0}, +}; + +menu_t OPTIONS_DataProfileEraseDef = { + sizeof (OPTIONS_DataProfileErase) / sizeof (menuitem_t), + &OPTIONS_DataEraseDef, + 0, + OPTIONS_DataProfileErase, + 48, 80, + SKINCOLOR_BLUEBERRY, 0, + 2, 5, + M_DrawProfileErase, + M_OptionsTick, + NULL, + NULL, + NULL +}; + +// extras menu +menuitem_t EXTRAS_Main[] = +{ + + {IT_STRING | IT_CALL, "Addons", "Add files to customize your experience.", + NULL, {.routine = M_Addons}, 0, 0}, + + {IT_STRING | IT_CALL, "Replay Hut", "Play the replays you've saved throughout your many races & battles!", + NULL, {.routine = M_ReplayHut}, 0, 0}, + + {IT_STRING | IT_CALL, "Statistics", "Look back on some of your greatest achievements such as your playtime and wins!", + NULL, {NULL}, 0, 0}, + + {IT_STRING | IT_TRANSTEXT, "Extras Checklist", "View the requirement for some of the secret content you can unlock!", + NULL, {NULL}, 0, 0}, +}; + +// the extras menu essentially reuses the options menu stuff +menu_t EXTRAS_MainDef = { + sizeof (EXTRAS_Main) / sizeof (menuitem_t), + &MainDef, + 0, + EXTRAS_Main, + 0, 0, + 0, 0, + 2, 5, + M_DrawExtras, + M_ExtrasTick, + NULL, + NULL, + M_ExtrasInputs +}; + +// extras menu: replay hut +menuitem_t EXTRAS_ReplayHut[] = +{ + {IT_KEYHANDLER|IT_NOTHING, "", "", // Dummy menuitem for the replay list + NULL, {.routine = M_HandleReplayHutList}, 0, 0}, + + {IT_NOTHING, "", "", // Dummy for handling wrapping to the top of the menu.. + NULL, {NULL}, 0, 0}, +}; + +menu_t EXTRAS_ReplayHutDef = +{ + sizeof (EXTRAS_ReplayHut)/sizeof (menuitem_t), + &EXTRAS_MainDef, + 0, + EXTRAS_ReplayHut, + 30, 80, + 0, 0, + 0, 0, + M_DrawReplayHut, + NULL, + NULL, + M_QuitReplayHut, + NULL +}; + +menuitem_t EXTRAS_ReplayStart[] = +{ + {IT_CALL |IT_STRING, "Load Addons and Watch", NULL, + NULL, {.routine = M_HutStartReplay}, 0, 0}, + + {IT_CALL |IT_STRING, "Load Without Addons", NULL, + NULL, {.routine = M_HutStartReplay}, 10, 0}, + + {IT_CALL |IT_STRING, "Watch Replay", NULL, + NULL, {.routine = M_HutStartReplay}, 10, 0}, + + {IT_SUBMENU |IT_STRING, "Go Back", NULL, + NULL, {.submenu = &EXTRAS_ReplayHutDef}, 30, 0}, +}; + + +menu_t EXTRAS_ReplayStartDef = +{ + sizeof (EXTRAS_ReplayStart)/sizeof (menuitem_t), + &EXTRAS_ReplayHutDef, + 0, + EXTRAS_ReplayStart, + 27, 80, + 0, 0, + 0, 0, + M_DrawReplayStartMenu, + NULL, + NULL, + NULL, + NULL +}; + +// ------------------- +// In-game/pause menus +// ------------------- + +// 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 :) + +menuitem_t PAUSE_Main[] = +{ + + {IT_STRING | IT_CALL, "ADDONS", "M_ICOADD", + NULL, {.routine = M_Addons}, 0, 0}, + + {IT_STRING | IT_SUBMENU, "CHANGE MAP", "M_ICOMAP", + NULL, {.submenu = &PAUSE_GamemodesDef}, 0, 0}, + +#ifdef HAVE_DISCORDRPC + {IT_STRING | IT_CALL, "DISCORD REQUESTS", "M_ICODIS", + NULL, {NULL}, 0, 0}, +#endif + + {IT_STRING | IT_CALL, "RESUME GAME", "M_ICOUNP", + NULL, {.routine = M_QuitPauseMenu}, 0, 0}, + + {IT_STRING | IT_CALL, "SPECTATE", "M_ICOSPC", + NULL, {.routine = M_ConfirmSpectate}, 0, 0}, + + {IT_STRING | IT_CALL, "ENTER GAME", "M_ICOENT", + NULL, {.routine = M_ConfirmEnterGame}, 0, 0}, + + {IT_STRING | IT_CALL, "CANCEL JOIN", "M_ICOSPC", + NULL, {.routine = M_ConfirmSpectate}, 0, 0}, + + {IT_STRING | IT_SUBMENU, "JOIN OR SPECTATE", "M_ICOENT", + NULL, {NULL}, 0, 0}, + + {IT_STRING | IT_CALL, "PLAYER SETUP", "M_ICOCHR", + NULL, {.routine = M_CharacterSelect}, 0, 0}, + + {IT_STRING | IT_CALL, "OPTIONS", "M_ICOOPT", + NULL, {.routine = M_InitOptions}, 0, 0}, + + {IT_STRING | IT_CALL, "EXIT GAME", "M_ICOEXT", + NULL, {.routine = M_EndGame}, 0, 0}, +}; + +menu_t PAUSE_MainDef = { + sizeof (PAUSE_Main) / sizeof (menuitem_t), + NULL, + 0, + PAUSE_Main, + 0, 0, + 0, 0, + 1, 10, // For transition with some menus! + M_DrawPause, + M_PauseTick, + NULL, + NULL, + M_PauseInputs +}; + +// PAUSE : Map switching gametype selection (In case you want to pick from battle / race...) +menuitem_t PAUSE_GamemodesMenu[] = +{ + {IT_STRING | IT_CALL, "Race", "Select which gamemode to choose a new map from.", + NULL, {.routine = M_LevelSelectInit}, 0, GT_RACE}, + + {IT_STRING | IT_CALL, "Battle", "Select which gamemode to choose a new map from.", + NULL, {.routine = M_LevelSelectInit}, 0, GT_BATTLE}, + + {IT_STRING | IT_CALL, "Back", NULL, NULL, {.routine = M_GoBack}, 0, 0}, +}; + +menu_t PAUSE_GamemodesDef = KARTGAMEMODEMENU(PAUSE_GamemodesMenu, &PAUSE_MainDef); + +// Replay popup menu +menuitem_t PAUSE_PlaybackMenu[] = +{ + {IT_CALL | IT_STRING, "Hide Menu (Esc)", NULL, "M_PHIDE", {.routine = M_SelectableClearMenus}, 0, 0}, + + {IT_CALL | IT_STRING, "Rewind ([)", NULL, "M_PREW", {.routine = M_PlaybackRewind}, 20, 0}, + {IT_CALL | IT_STRING, "Pause (\\)", NULL, "M_PPAUSE", {.routine = M_PlaybackPause}, 36, 0}, + {IT_CALL | IT_STRING, "Fast-Forward (])", NULL, "M_PFFWD", {.routine = M_PlaybackFastForward}, 52, 0}, + {IT_CALL | IT_STRING, "Backup Frame ([)", NULL, "M_PSTEPB", {.routine = M_PlaybackRewind}, 20, 0}, + {IT_CALL | IT_STRING, "Resume", NULL, "M_PRESUM", {.routine = M_PlaybackPause}, 36, 0}, + {IT_CALL | IT_STRING, "Advance Frame (])", NULL, "M_PFADV", {.routine = M_PlaybackAdvance}, 52, 0}, + + {IT_ARROWS | IT_STRING, "View Count (- and =)", NULL, "M_PVIEWS", {.routine = M_PlaybackSetViews}, 72, 0}, + {IT_ARROWS | IT_STRING, "Viewpoint (1)", NULL, "M_PNVIEW", {.routine = M_PlaybackAdjustView}, 88, 0}, + {IT_ARROWS | IT_STRING, "Viewpoint 2 (2)", NULL, "M_PNVIEW", {.routine = M_PlaybackAdjustView}, 104, 0}, + {IT_ARROWS | IT_STRING, "Viewpoint 3 (3)", NULL, "M_PNVIEW", {.routine = M_PlaybackAdjustView}, 120, 0}, + {IT_ARROWS | IT_STRING, "Viewpoint 4 (4)", NULL, "M_PNVIEW", {.routine = M_PlaybackAdjustView}, 136, 0}, + + {IT_CALL | IT_STRING, "Toggle Free Camera (')", NULL, "M_PVIEWS", {.routine = M_PlaybackToggleFreecam}, 156, 0}, + {IT_CALL | IT_STRING, "Stop Playback", NULL, "M_PEXIT", {.routine = M_PlaybackQuit}, 172, 0}, +}; + +menu_t PAUSE_PlaybackMenuDef = { + sizeof (PAUSE_PlaybackMenu) / sizeof (menuitem_t), + NULL, + 0, + PAUSE_PlaybackMenu, + BASEVIDWIDTH/2 - 88, 2, + 0, 0, + 0, 0, + M_DrawPlaybackMenu, + NULL, + NULL, + NULL, + NULL +}; + + +// Other misc menus: + +// Manual +menuitem_t MISC_Manual[] = { + {IT_NOTHING | IT_KEYHANDLER, "MANUAL00", NULL, NULL, {.routine = M_HandleImageDef}, 0, 0}, + {IT_NOTHING | IT_KEYHANDLER, "MANUAL01", NULL, NULL, {.routine = M_HandleImageDef}, 1, 0}, + {IT_NOTHING | IT_KEYHANDLER, "MANUAL02", NULL, NULL, {.routine = M_HandleImageDef}, 1, 0}, + {IT_NOTHING | IT_KEYHANDLER, "MANUAL03", NULL, NULL, {.routine = M_HandleImageDef}, 1, 0}, + {IT_NOTHING | IT_KEYHANDLER, "MANUAL04", NULL, NULL, {.routine = M_HandleImageDef}, 1, 0}, + {IT_NOTHING | IT_KEYHANDLER, "MANUAL05", NULL, NULL, {.routine = M_HandleImageDef}, 1, 0}, + {IT_NOTHING | IT_KEYHANDLER, "MANUAL06", NULL, NULL, {.routine = M_HandleImageDef}, 1, 0}, + {IT_NOTHING | IT_KEYHANDLER, "MANUAL07", NULL, NULL, {.routine = M_HandleImageDef}, 1, 0}, + {IT_NOTHING | IT_KEYHANDLER, "MANUAL08", NULL, NULL, {.routine = M_HandleImageDef}, 1, 0}, + {IT_NOTHING | IT_KEYHANDLER, "MANUAL09", NULL, NULL, {.routine = M_HandleImageDef}, 1, 0}, + {IT_NOTHING | IT_KEYHANDLER, "MANUAL10", NULL, NULL, {.routine = M_HandleImageDef}, 1, 0}, + {IT_NOTHING | IT_KEYHANDLER, "MANUAL11", NULL, NULL, {.routine = M_HandleImageDef}, 1, 0}, + {IT_NOTHING | IT_KEYHANDLER, "MANUAL12", NULL, NULL, {.routine = M_HandleImageDef}, 1, 0}, + {IT_NOTHING | IT_KEYHANDLER, "MANUAL99", NULL, NULL, {.routine = M_HandleImageDef}, 0, 0}, +}; + +menu_t MISC_ManualDef = IMAGEDEF(MISC_Manual); + +// Addons menu! +menuitem_t MISC_AddonsMenu[] = +{ + {IT_STRING | IT_CVAR | IT_CV_STRING, NULL, NULL, + NULL, {.cvar = &cv_dummyaddonsearch}, 0, 0}, + {IT_KEYHANDLER | IT_NOTHING, NULL, NULL, + NULL, {.routine = M_HandleAddons}, 0, 0}, // dummy menuitem for the control func +}; + +menu_t MISC_AddonsDef = { + sizeof (MISC_AddonsMenu)/sizeof (menuitem_t), + NULL, + 0, + MISC_AddonsMenu, + 50, 28, + 0, 0, + 0, 0, + M_DrawAddons, + M_AddonsRefresh, + NULL, + NULL, + NULL +}; diff --git a/src/k_menudraw.c b/src/k_menudraw.c new file mode 100644 index 000000000..76a001544 --- /dev/null +++ b/src/k_menudraw.c @@ -0,0 +1,4377 @@ +/// \file k_menudraw.c +/// \brief SRB2Kart's menu drawer functions + +#ifdef __GNUC__ +#include +#endif + +#include "k_menu.h" + +#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 "k_kart.h" +#include "k_hud.h" +#include "k_follower.h" +#include "d_player.h" // KITEM_ constants +#include "doomstat.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" + +#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 +int snprintf(char *str, size_t n, const char *fmt, ...); +//int vsnprintf(char *str, size_t n, const char *fmt, va_list ap); +#endif + +#define SKULLXOFF -32 +#define LINEHEIGHT 16 +#define STRINGHEIGHT 8 +#define FONTBHEIGHT 20 +#define SMALLLINEHEIGHT 8 +#define SLIDER_RANGE 10 +#define SLIDER_WIDTH (8*SLIDER_RANGE+6) +#define SERVERS_PER_PAGE 11 + + +// horizontally centered text +static void M_CentreText(INT32 xoffs, INT32 y, const char *string) +{ + INT32 x; + //added : 02-02-98 : centre on 320, because V_DrawString centers on vid.width... + x = ((BASEVIDWIDTH - V_StringWidth(string, V_OLDSPACING))>>1) + xoffs; + V_DrawString(x,y,V_OLDSPACING,string); +} + + +// A smaller 'Thermo', with range given as percents (0-100) +static void M_DrawSlider(INT32 x, INT32 y, const consvar_t *cv, boolean ontop) +{ + INT32 i; + INT32 range; + patch_t *p; + + for (i = 0; cv->PossibleValue[i+1].strvalue; i++); + + x = BASEVIDWIDTH - x - SLIDER_WIDTH; + + if (ontop) + { + V_DrawCharacter(x - 16 - (skullAnimCounter/5), y, + '\x1C' | highlightflags, false); // left arrow + V_DrawCharacter(x+(SLIDER_RANGE*8) + 8 + (skullAnimCounter/5), y, + '\x1D' | highlightflags, false); // right arrow + } + + if ((range = atoi(cv->defaultvalue)) != cv->value) + { + range = ((range - cv->PossibleValue[0].value) * 100 / + (cv->PossibleValue[1].value - cv->PossibleValue[0].value)); + + if (range < 0) + range = 0; + if (range > 100) + range = 100; + + // draw the default + p = W_CachePatchName("M_SLIDEC", PU_CACHE); + V_DrawScaledPatch(x - 4 + (((SLIDER_RANGE)*8 + 4)*range)/100, y, 0, p); + } + + V_DrawScaledPatch(x - 8, y, 0, W_CachePatchName("M_SLIDEL", PU_CACHE)); + + p = W_CachePatchName("M_SLIDEM", PU_CACHE); + for (i = 0; i < SLIDER_RANGE; i++) + V_DrawScaledPatch (x+i*8, y, 0,p); + + p = W_CachePatchName("M_SLIDER", PU_CACHE); + V_DrawScaledPatch(x+SLIDER_RANGE*8, y, 0, p); + + range = ((cv->value - cv->PossibleValue[0].value) * 100 / + (cv->PossibleValue[1].value - cv->PossibleValue[0].value)); + + if (range < 0) + range = 0; + if (range > 100) + range = 100; + + // draw the slider cursor + p = W_CachePatchName("M_SLIDEC", PU_CACHE); + V_DrawScaledPatch(x - 4 + (((SLIDER_RANGE)*8 + 4)*range)/100, y, 0, p); +} + + +static patch_t *addonsp[NUM_EXT+5]; + +static fixed_t bgText1Scroll = 0; +static fixed_t bgText2Scroll = 0; +static fixed_t bgImageScroll = 0; +static char bgImageName[9]; + +#define MENUBG_TEXTSCROLL 6 +#define MENUBG_IMAGESCROLL 32 + +void M_UpdateMenuBGImage(boolean forceReset) +{ + char oldName[9]; + + memcpy(oldName, bgImageName, 9); + + if (currentMenu->menuitems[itemOn].patch) + { + sprintf(bgImageName, "%s", currentMenu->menuitems[itemOn].patch); + } + else + { + sprintf(bgImageName, "MENUI000"); + } + + if (forceReset == false && strcmp(bgImageName, oldName)) + { + bgImageScroll = (BASEVIDWIDTH / 2)*FRACUNIT; + } +} + +void M_DrawMenuBackground(void) +{ + patch_t *text1 = W_CachePatchName("MENUBGT1", PU_CACHE); + patch_t *text2 = W_CachePatchName("MENUBGT2", PU_CACHE); + + fixed_t text1loop = SHORT(text1->height)*FRACUNIT; + fixed_t text2loop = SHORT(text2->width)*FRACUNIT; + + V_DrawFixedPatch(0, 0, FRACUNIT, 0, W_CachePatchName("MENUBG4", PU_CACHE), NULL); + + V_DrawFixedPatch(-bgImageScroll, 0, FRACUNIT, 0, W_CachePatchName("MENUBG1", PU_CACHE), NULL); + V_DrawFixedPatch(-bgImageScroll, 0, FRACUNIT, 0, W_CachePatchName(bgImageName, PU_CACHE), NULL); + + V_DrawFixedPatch(0, (BASEVIDHEIGHT + 16) * FRACUNIT, FRACUNIT, V_SUBTRACT, W_CachePatchName("MENUBG2", PU_CACHE), NULL); + + V_DrawFixedPatch(8 * FRACUNIT, -bgText1Scroll, + FRACUNIT, V_SUBTRACT, text1, NULL); + V_DrawFixedPatch(8 * FRACUNIT, -bgText1Scroll + text1loop, + FRACUNIT, V_SUBTRACT, text1, NULL); + + bgText1Scroll += (MENUBG_TEXTSCROLL*renderdeltatics); + while (bgText1Scroll > text1loop) + bgText1Scroll -= text1loop; + + V_DrawFixedPatch(-bgText2Scroll, (BASEVIDHEIGHT-8) * FRACUNIT, + FRACUNIT, V_ADD, text2, NULL); + V_DrawFixedPatch(-bgText2Scroll + text2loop, (BASEVIDHEIGHT-8) * FRACUNIT, + FRACUNIT, V_ADD, text2, NULL); + + bgText2Scroll += (MENUBG_TEXTSCROLL*renderdeltatics); + while (bgText2Scroll > text2loop) + bgText2Scroll -= text2loop; + + if (bgImageScroll > 0) + { + bgImageScroll -= (MENUBG_IMAGESCROLL*renderdeltatics); + if (bgImageScroll < 0) + { + bgImageScroll = 0; + } + } +} + +static void M_DrawMenuParty(void) +{ + const INT32 PLATTER_WIDTH = 19; + const INT32 PLATTER_STAGGER = 6; + const INT32 PLATTER_OFFSET = (PLATTER_WIDTH - PLATTER_STAGGER); + + patch_t *small = W_CachePatchName("MENUPLRA", PU_CACHE); + patch_t *large = W_CachePatchName("MENUPLRB", PU_CACHE); + + INT32 x, y; + INT32 skin; + UINT16 color; + UINT8 *colormap; + + if (setup_numplayers == 0 || currentMenu == &PLAY_CharSelectDef) + { + return; + } + + x = 2; + y = BASEVIDHEIGHT - small->height - 2; + + switch (setup_numplayers) + { + case 1: + { + x -= 8; + V_DrawScaledPatch(x, y, 0, small); + + skin = R_SkinAvailable(cv_skin[0].string); + color = cv_playercolor[0].value; + colormap = R_GetTranslationColormap(skin, color, GTC_MENUCACHE); + + V_DrawMappedPatch(x + 22, y + 8, 0, faceprefix[skin][FACE_MINIMAP], colormap); + break; + } + case 2: + { + x -= 8; + V_DrawScaledPatch(x, y, 0, small); + V_DrawScaledPatch(x + PLATTER_OFFSET, y - PLATTER_STAGGER, 0, small); + + skin = R_SkinAvailable(cv_skin[1].string); + color = cv_playercolor[1].value; + colormap = R_GetTranslationColormap(skin, color, GTC_MENUCACHE); + + V_DrawMappedPatch(x + PLATTER_OFFSET + 22, y - PLATTER_STAGGER + 8, 0, faceprefix[skin][FACE_MINIMAP], colormap); + + skin = R_SkinAvailable(cv_skin[0].string); + color = cv_playercolor[0].value; + colormap = R_GetTranslationColormap(skin, color, GTC_MENUCACHE); + + V_DrawMappedPatch(x + 22, y + 8, 0, faceprefix[skin][FACE_MINIMAP], colormap); + break; + } + case 3: + { + V_DrawScaledPatch(x, y, 0, large); + V_DrawScaledPatch(x + PLATTER_OFFSET, y - PLATTER_STAGGER, 0, small); + + skin = R_SkinAvailable(cv_skin[1].string); + color = cv_playercolor[1].value; + colormap = R_GetTranslationColormap(skin, color, GTC_MENUCACHE); + + V_DrawMappedPatch(x + PLATTER_OFFSET + 22, y - PLATTER_STAGGER + 8, 0, faceprefix[skin][FACE_MINIMAP], colormap); + + skin = R_SkinAvailable(cv_skin[0].string); + color = cv_playercolor[0].value; + colormap = R_GetTranslationColormap(skin, color, GTC_MENUCACHE); + + V_DrawMappedPatch(x + 12, y - 2, 0, faceprefix[skin][FACE_MINIMAP], colormap); + + skin = R_SkinAvailable(cv_skin[2].string); + color = cv_playercolor[2].value; + colormap = R_GetTranslationColormap(skin, color, GTC_MENUCACHE); + + V_DrawMappedPatch(x + 22, y + 8, 0, faceprefix[skin][FACE_MINIMAP], colormap); + break; + } + case 4: + { + V_DrawScaledPatch(x, y, 0, large); + V_DrawScaledPatch(x + PLATTER_OFFSET, y - PLATTER_STAGGER, 0, large); + + skin = R_SkinAvailable(cv_skin[1].string); + color = cv_playercolor[1].value; + colormap = R_GetTranslationColormap(skin, color, GTC_MENUCACHE); + + V_DrawMappedPatch(x + PLATTER_OFFSET + 12, y - PLATTER_STAGGER - 2, 0, faceprefix[skin][FACE_MINIMAP], colormap); + + skin = R_SkinAvailable(cv_skin[0].string); + color = cv_playercolor[0].value; + colormap = R_GetTranslationColormap(skin, color, GTC_MENUCACHE); + + V_DrawMappedPatch(x + 12, y - 2, 0, faceprefix[skin][FACE_MINIMAP], colormap); + + skin = R_SkinAvailable(cv_skin[3].string); + color = cv_playercolor[3].value; + colormap = R_GetTranslationColormap(skin, color, GTC_MENUCACHE); + + V_DrawMappedPatch(x + PLATTER_OFFSET + 22, y - PLATTER_STAGGER + 8, 0, faceprefix[skin][FACE_MINIMAP], colormap); + + skin = R_SkinAvailable(cv_skin[2].string); + color = cv_playercolor[2].value; + colormap = R_GetTranslationColormap(skin, color, GTC_MENUCACHE); + + V_DrawMappedPatch(x + 22, y + 8, 0, faceprefix[skin][FACE_MINIMAP], colormap); + break; + } + default: + { + return; + } + } + + x += PLATTER_WIDTH; + y += small->height; + V_DrawScaledPatch(x + 16, y - 12, 0, W_CachePatchName(va("OPPRNK0%d", setup_numplayers % 10), PU_CACHE)); +} + +void M_DrawMenuForeground(void) +{ + if (gamestate == GS_MENU) + { + M_DrawMenuParty(); + } + + // draw non-green resolution border + if ((vid.width % BASEVIDWIDTH != 0) || (vid.height % BASEVIDHEIGHT != 0)) + { + V_DrawFixedPatch(0, 0, FRACUNIT, 0, W_CachePatchName("WEIRDRES", PU_CACHE), NULL); + } +} + +// Draws the typing submenu +static void M_DrawMenuTyping(void) +{ + + INT32 i, j; + + INT32 x = 60; + INT32 y = 100 + (9-menutyping.menutypingfade)*8; + INT32 tflag = (9 - menutyping.menutypingfade)<menuitems[itemOn].itemaction.cvar; + + char buf[8]; // We write there to use drawstring for convenience. + + V_DrawFadeScreen(31, menutyping.menutypingfade); + + // Draw the string we're editing at the top. + V_DrawString(x, y-48 + 12, V_ALLOWLOWERCASE|tflag, cv->string); + if (skullAnimCounter < 4) + V_DrawCharacter(x + V_StringWidth(cv->string, 0), y - 35, '_' | 0x80, false); + + // Some contextual stuff + if (menutyping.keyboardtyping) + V_DrawThinString(10, 175, V_ALLOWLOWERCASE|tflag|V_GRAYMAP, "Type using your keyboard. Press Enter to confirm & exit.\nUse your controller or any directional input to use the Virtual Keyboard.\n"); + else + V_DrawThinString(10, 175, V_ALLOWLOWERCASE|tflag|V_GRAYMAP, "Type using the Virtual Keyboard. Use the \'OK\' button to confirm & exit.\nPress any keyboard key not bound to a control to use it."); + + + // Now the keyboard itself + for (i=0; i < 5; i++) + { + for (j=0; j < 13; j++) + { + INT32 mflag = 0; + INT16 c = virtualKeyboard[i][j]; + if (menutyping.keyboardshift ^ menutyping.keyboardcapslock) + c = shift_virtualKeyboard[i][j]; + + + if (c == KEY_BACKSPACE) + strcpy(buf, "DEL"); + + else if (c == KEY_RSHIFT) + strcpy(buf, "SHIFT"); + + else if (c == KEY_CAPSLOCK) + strcpy(buf, "CAPS"); + + else if (c == KEY_ENTER) + strcpy(buf, "OK"); + + else if (c == KEY_SPACE) + strcpy(buf, "SPACE"); + + else + { + buf[0] = c; + buf[1] = '\0'; + } + + // highlight: + if (menutyping.keyboardx == j && menutyping.keyboardy == i && !menutyping.keyboardtyping) + mflag |= highlightflags; + else if (menutyping.keyboardtyping) + mflag |= V_TRANSLUCENT; // grey it out if we can't use it. + + V_DrawString(x, y, V_ALLOWLOWERCASE|tflag|mflag, buf); + + x += V_StringWidth(buf, 0)+8; + } + x = 60; + y += 12; + } +} + +// Draw the message popup submenu +static void M_DrawMenuMessage(void) +{ + + INT32 y = menumessage.y + (9-menumessage.fadetimer)*20; + size_t i, start = 0; + INT16 max; + char string[MAXMENUMESSAGE]; + INT32 mlines; + const char *msg = menumessage.message; + + mlines = menumessage.m>>8; + max = (INT16)((UINT8)(menumessage.m & 0xFF)*8); + + V_DrawFadeScreen(31, menumessage.fadetimer); + M_DrawTextBox(menumessage.x, y - 8, (max+7)>>3, mlines); + + while (*(msg+start)) + { + size_t len = strlen(msg+start); + + for (i = 0; i < len; i++) + { + if (*(msg+start+i) == '\n') + { + memset(string, 0, MAXMENUMESSAGE); + if (i >= MAXMENUMESSAGE) + { + CONS_Printf("M_DrawMenuMessage: too long segment in %s\n", msg); + return; + } + else + { + strncpy(string,msg+start, i); + string[i] = '\0'; + start += i; + i = (size_t)-1; //added : 07-02-98 : damned! + start++; + } + break; + } + } + + if (i == strlen(msg+start)) + { + if (i >= MAXMENUMESSAGE) + { + CONS_Printf("M_DrawMenuMessage: too long segment in %s\n", msg); + return; + } + else + { + strcpy(string, msg + start); + start += i; + } + } + + V_DrawString((BASEVIDWIDTH - V_StringWidth(string, 0))/2, y, V_ALLOWLOWERCASE, string); + y += 8; + } +} + +// +// M_Drawer +// Called after the view has been rendered, +// but before it has been blitted. +// + +void M_Drawer(void) +{ + if (currentMenu == &MessageDef) + menuactive = true; + + if (menuwipe) + F_WipeStartScreen(); + + if (menuactive) + { + if (gamestate == GS_MENU) + { + M_DrawMenuBackground(); + } + else if (!WipeInAction && currentMenu != &PAUSE_PlaybackMenuDef) + { + if (rendermode == render_opengl) // OGL can't handle what SW is doing so let's fake it; + V_DrawFadeScreen(122, 3); // palette index aproximation... + else // Software can keep its unique fade + V_DrawCustomFadeScreen("FADEMAP0", 4); // now that's more readable with a faded background (yeah like Quake...) + } + + if (currentMenu->drawroutine) + currentMenu->drawroutine(); // call current menu Draw routine + + M_DrawMenuForeground(); + + // Draw version down in corner + // ... but only in the MAIN MENU. I'm a picky bastard. + if (currentMenu == &MainDef) + { + if (customversionstring[0] != '\0') + { + V_DrawThinString(vid.dupx, vid.height - 20*vid.dupy, V_NOSCALESTART|V_TRANSLUCENT, "Mod version:"); + V_DrawThinString(vid.dupx, vid.height - 10*vid.dupy, V_NOSCALESTART|V_TRANSLUCENT|V_ALLOWLOWERCASE, customversionstring); + } + else + { +#ifdef DEVELOP // Development -- show revision / branch info + V_DrawThinString(vid.dupx, vid.height - 20*vid.dupy, V_NOSCALESTART|V_TRANSLUCENT|V_ALLOWLOWERCASE, compbranch); + V_DrawThinString(vid.dupx, vid.height - 10*vid.dupy, V_NOSCALESTART|V_TRANSLUCENT|V_ALLOWLOWERCASE, comprevision); +#else // Regular build + V_DrawThinString(vid.dupx, vid.height - 10*vid.dupy, V_NOSCALESTART|V_TRANSLUCENT|V_ALLOWLOWERCASE, va("%s", VERSIONSTRING)); +#endif + } + + + } + + // Draw message overlay when needed + if (menumessage.active) + M_DrawMenuMessage(); + + // Draw typing overlay when needed, above all other menu elements. + if (menutyping.active) + M_DrawMenuTyping(); + } + + if (menuwipe) + { + F_WipeEndScreen(); + F_RunWipe(wipedefs[wipe_menu_final], false, "FADEMAP0", true, false); + menuwipe = false; + } + + // focus lost notification goes on top of everything, even the former everything + if (window_notinfocus && cv_showfocuslost.value) + { + M_DrawTextBox((BASEVIDWIDTH/2) - (60), (BASEVIDHEIGHT/2) - (16), 13, 2); + if (gamestate == GS_LEVEL && (P_AutoPause() || paused)) + V_DrawCenteredString(BASEVIDWIDTH/2, (BASEVIDHEIGHT/2) - (4), highlightflags, "Game Paused"); + else + V_DrawCenteredString(BASEVIDWIDTH/2, (BASEVIDHEIGHT/2) - (4), highlightflags, "Focus Lost"); + } +} + +// ========================================================================== +// GENERIC MENUS +// ========================================================================== + +// +// M_DrawMenuTooltips +// +// Draw a banner across the top of the screen, with a description of the current option displayed +// +static void M_DrawMenuTooltips(void) +{ + if (currentMenu->menuitems[itemOn].tooltip != NULL) + { + V_DrawFixedPatch(0, 0, FRACUNIT, 0, W_CachePatchName("MENUHINT", PU_CACHE), NULL); + V_DrawCenteredThinString(BASEVIDWIDTH/2, 12, V_ALLOWLOWERCASE|V_6WIDTHSPACE, currentMenu->menuitems[itemOn].tooltip); + } +} + +// Converts a string into question marks. +// Used for the secrets menu, to hide yet-to-be-unlocked stuff. +static const char *M_CreateSecretMenuOption(const char *str) +{ + static char qbuf[32]; + int i; + + for (i = 0; i < 31; ++i) + { + if (!str[i]) + { + qbuf[i] = '\0'; + return qbuf; + } + else if (str[i] != ' ') + qbuf[i] = '?'; + else + qbuf[i] = ' '; + } + + qbuf[31] = '\0'; + return qbuf; +} + +// +// M_DrawGenericMenu +// +// Default, generic text-based list menu, used for Options +// +void M_DrawGenericMenu(void) +{ + INT32 x = currentMenu->x, y = currentMenu->y, w, i, cursory = 0; + + M_DrawMenuTooltips(); + + for (i = 0; i < currentMenu->numitems; i++) + { + if (i == itemOn) + cursory = y; + + switch (currentMenu->menuitems[i].status & IT_DISPLAY) + { + case IT_PATCH: + if (currentMenu->menuitems[i].patch && currentMenu->menuitems[i].patch[0]) + { + if (currentMenu->menuitems[i].status & IT_CENTER) + { + patch_t *p; + p = W_CachePatchName(currentMenu->menuitems[i].patch, PU_CACHE); + V_DrawScaledPatch((BASEVIDWIDTH - SHORT(p->width))/2, y, 0, p); + } + else + { + V_DrawScaledPatch(x, y, 0, + W_CachePatchName(currentMenu->menuitems[i].patch, PU_CACHE)); + } + } + /* FALLTHRU */ + case IT_NOTHING: + case IT_DYBIGSPACE: + y = currentMenu->y + currentMenu->menuitems[i].mvar1;//+= LINEHEIGHT; + break; +#if 0 + case IT_BIGSLIDER: + M_DrawThermo(x, y, currentMenu->menuitems[i].itemaction.cvar); + y += LINEHEIGHT; + break; +#endif + case IT_STRING: + case IT_WHITESTRING: + if (currentMenu->menuitems[i].mvar1) + y = currentMenu->y+currentMenu->menuitems[i].mvar1; + if (i == itemOn) + cursory = y; + + if ((currentMenu->menuitems[i].status & IT_DISPLAY)==IT_STRING) + V_DrawString(x, y, 0, currentMenu->menuitems[i].text); + else + V_DrawString(x, y, highlightflags, currentMenu->menuitems[i].text); + + // Cvar specific handling + switch (currentMenu->menuitems[i].status & IT_TYPE) + case IT_CVAR: + { + consvar_t *cv = currentMenu->menuitems[i].itemaction.cvar; + switch (currentMenu->menuitems[i].status & IT_CVARTYPE) + { +#if 0 + case IT_CV_SLIDER: + M_DrawSlider(x, y, cv, (i == itemOn)); + case IT_CV_NOPRINT: // color use this + case IT_CV_INVISSLIDER: // monitor toggles use this + break; +#endif + case IT_CV_STRING: + M_DrawTextBox(x, y + 4, MAXSTRINGLENGTH, 1); + V_DrawString(x + 8, y + 12, V_ALLOWLOWERCASE, cv->string); + if (skullAnimCounter < 4 && i == itemOn) + V_DrawCharacter(x + 8 + V_StringWidth(cv->string, 0), y + 12, + '_' | 0x80, false); + y += 16; + break; + default: + w = V_StringWidth(cv->string, 0); + V_DrawString(BASEVIDWIDTH - x - w, y, + ((cv->flags & CV_CHEAT) && !CV_IsSetToDefault(cv) ? warningflags : highlightflags), cv->string); + if (i == itemOn) + { + V_DrawCharacter(BASEVIDWIDTH - x - 10 - w - (skullAnimCounter/5), y, + '\x1C' | highlightflags, false); // left arrow + V_DrawCharacter(BASEVIDWIDTH - x + 2 + (skullAnimCounter/5), y, + '\x1D' | highlightflags, false); // right arrow + } + break; + } + break; + } + y += STRINGHEIGHT; + break; + case IT_STRING2: + V_DrawString(x, y, 0, currentMenu->menuitems[i].text); + /* FALLTHRU */ + case IT_DYLITLSPACE: + y += SMALLLINEHEIGHT; + break; + case IT_GRAYPATCH: + if (currentMenu->menuitems[i].patch && currentMenu->menuitems[i].patch[0]) + V_DrawMappedPatch(x, y, 0, + W_CachePatchName(currentMenu->menuitems[i].patch,PU_CACHE), graymap); + y += LINEHEIGHT; + break; + case IT_TRANSTEXT: + if (currentMenu->menuitems[i].mvar1) + y = currentMenu->y+currentMenu->menuitems[i].mvar1; + /* FALLTHRU */ + case IT_TRANSTEXT2: + V_DrawString(x, y, V_TRANSLUCENT, currentMenu->menuitems[i].text); + y += SMALLLINEHEIGHT; + break; + case IT_QUESTIONMARKS: + if (currentMenu->menuitems[i].mvar1) + y = currentMenu->y+currentMenu->menuitems[i].mvar1; + + V_DrawString(x, y, V_TRANSLUCENT|V_OLDSPACING, M_CreateSecretMenuOption(currentMenu->menuitems[i].text)); + y += SMALLLINEHEIGHT; + break; + case IT_HEADERTEXT: // draws 16 pixels to the left, in yellow text + if (currentMenu->menuitems[i].mvar1) + y = currentMenu->y+currentMenu->menuitems[i].mvar1; + + V_DrawString(x-16, y, highlightflags, currentMenu->menuitems[i].text); + y += SMALLLINEHEIGHT; + break; + } + } + + if ((currentMenu->menuitems[itemOn].status & IT_DISPLAY) == 0) + cursory = 300; // Put the cursor off screen if we can't even display that option and we're on it, it makes no sense otherwise... + + // DRAW THE SKULL CURSOR + if (((currentMenu->menuitems[itemOn].status & IT_DISPLAY) == IT_PATCH) + || ((currentMenu->menuitems[itemOn].status & IT_DISPLAY) == IT_NOTHING)) + { + V_DrawScaledPatch(currentMenu->x + SKULLXOFF, cursory - 5, 0, + W_CachePatchName("M_CURSOR", PU_CACHE)); + } + else + { + V_DrawScaledPatch(currentMenu->x - 24, cursory, 0, + W_CachePatchName("M_CURSOR", PU_CACHE)); + V_DrawString(currentMenu->x, cursory, highlightflags, currentMenu->menuitems[itemOn].text); + } +} + +#define GM_STARTX 128 +#define GM_STARTY 80 +#define GM_XOFFSET 17 +#define GM_YOFFSET 34 + +// +// M_DrawKartGamemodeMenu +// +// Huge gamemode-selection list for main menu +// +void M_DrawKartGamemodeMenu(void) +{ + UINT8 n = 0; + INT32 i, x, y; + + for (i = 0; i < currentMenu->numitems; i++) + { + if (currentMenu->menuitems[i].status == IT_DISABLED) + { + continue; + } + + n++; + } + + n--; + x = GM_STARTX - ((GM_XOFFSET / 2) * (n-1)); + y = GM_STARTY - ((GM_YOFFSET / 2) * (n-1)); + + M_DrawMenuTooltips(); + + if (menutransition.tics) + { + x += 48 * menutransition.tics; + } + + for (i = 0; i < currentMenu->numitems; i++) + { + if (currentMenu->menuitems[i].status == IT_DISABLED) + { + continue; + } + + if (i >= currentMenu->numitems-1) + { + x = GM_STARTX + (GM_XOFFSET * 5 / 2); + y = GM_STARTY + (GM_YOFFSET * 5 / 2); + + if (menutransition.tics) + { + x += 48 * menutransition.tics; + } + } + + switch (currentMenu->menuitems[i].status & IT_DISPLAY) + { + case IT_STRING: + { + UINT8 *colormap = NULL; + + if (i == itemOn) + { + colormap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_PLAGUE, GTC_CACHE); + } + else + { + colormap = R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_MOSS, GTC_CACHE); + } + + V_DrawFixedPatch(x*FRACUNIT, y*FRACUNIT, FRACUNIT, 0, W_CachePatchName("MENUPLTR", PU_CACHE), colormap); + V_DrawGamemodeString(x + 16, y - 3, V_ALLOWLOWERCASE, colormap, currentMenu->menuitems[i].text); + } + break; + } + + x += GM_XOFFSET; + y += GM_YOFFSET; + } +} + +#define MAXMSGLINELEN 256 + +// +// Draw a textbox, like Quake does, because sometimes it's difficult +// to read the text with all the stuff in the background... +// +void M_DrawTextBox(INT32 x, INT32 y, INT32 width, INT32 boxlines) +{ + // Solid color textbox. + V_DrawFill(x+5, y+5, width*8+6, boxlines*8+6, 159); + //V_DrawFill(x+8, y+8, width*8, boxlines*8, 31); +} + +// +// M_DrawMessageMenu +// +// Generic message prompt +// +void M_DrawMessageMenu(void) +{ + INT32 y = currentMenu->y; + size_t i, start = 0; + INT16 max; + char string[MAXMENUMESSAGE]; + INT32 mlines; + const char *msg = currentMenu->menuitems[0].text; + + mlines = currentMenu->lastOn>>8; + max = (INT16)((UINT8)(currentMenu->lastOn & 0xFF)*8); + + M_DrawTextBox(currentMenu->x, y - 8, (max+7)>>3, mlines); + + while (*(msg+start)) + { + size_t len = strlen(msg+start); + + for (i = 0; i < len; i++) + { + if (*(msg+start+i) == '\n') + { + memset(string, 0, MAXMENUMESSAGE); + if (i >= MAXMENUMESSAGE) + { + CONS_Printf("M_DrawMessageMenu: too long segment in %s\n", msg); + return; + } + else + { + strncpy(string,msg+start, i); + string[i] = '\0'; + start += i; + i = (size_t)-1; //added : 07-02-98 : damned! + start++; + } + break; + } + } + + if (i == strlen(msg+start)) + { + if (i >= MAXMENUMESSAGE) + { + CONS_Printf("M_DrawMessageMenu: too long segment in %s\n", msg); + return; + } + else + { + strcpy(string, msg + start); + start += i; + } + } + + V_DrawString((BASEVIDWIDTH - V_StringWidth(string, 0))/2,y,V_ALLOWLOWERCASE,string); + y += 8; //SHORT(hu_font[0]->height); + } +} + +// Draw an Image Def. Aka, Help images. +// Defines what image is used in (menuitem_t)->patch. +// You can even put multiple images in one menu! +void M_DrawImageDef(void) +{ + patch_t *patch = W_CachePatchName(currentMenu->menuitems[itemOn].text, PU_CACHE); + + if (patch->width <= BASEVIDWIDTH) + { + V_DrawScaledPatch(0, 0, 0, patch); + } + else + { + V_DrawSmallScaledPatch(0, 0, 0, patch); + } + + if (currentMenu->menuitems[itemOn].mvar1) + { + V_DrawString(2,BASEVIDHEIGHT-10, V_YELLOWMAP, va("%d", (itemOn<<1)-1)); // intentionally not highlightflags, unlike below + V_DrawRightAlignedString(BASEVIDWIDTH-2,BASEVIDHEIGHT-10, V_YELLOWMAP, va("%d", itemOn<<1)); // ditto + } + else + { + INT32 x = BASEVIDWIDTH>>1, y = (BASEVIDHEIGHT>>1) - 4; + x += (itemOn ? 1 : -1)*((BASEVIDWIDTH>>2) + 10); + V_DrawCenteredString(x, y-10, highlightflags, "USE ARROW KEYS"); + V_DrawCharacter(x - 10 - (skullAnimCounter/5), y, + '\x1C' | highlightflags, false); // left arrow + V_DrawCharacter(x + 2 + (skullAnimCounter/5), y, + '\x1D' | highlightflags, false); // right arrow + V_DrawCenteredString(x, y+10, highlightflags, "TO LEAF THROUGH"); + } +} + +// +// PLAY MENUS +// + +static void M_DrawCharSelectCircle(setup_player_t *p, INT16 x, INT16 y) +{ + angle_t angamt = ANGLE_MAX; + UINT16 i, numoptions = 0; + UINT16 l = 0, r = 0; + + switch (p->mdepth) + { + case CSSTEP_ALTS: + numoptions = setup_chargrid[p->gridx][p->gridy].numskins; + break; + case CSSTEP_COLORS: + numoptions = nummenucolors; + break; + case CSSTEP_FOLLOWER: + numoptions = numfollowers+1; + break; + case CSSTEP_FOLLOWERCOLORS: + numoptions = nummenucolors+2; + break; + default: + return; + } + + if (numoptions == 0) + { + return; + } + + angamt /= numoptions; + + for (i = 0; i < numoptions; i++) + { + fixed_t cx = x << FRACBITS, cy = y << FRACBITS; + boolean subtract = (i & 1); + angle_t ang = ((i+1)/2) * angamt; + patch_t *patch = NULL; + UINT8 *colormap = NULL; + fixed_t radius = 28<mdepth) + { + case CSSTEP_ALTS: + { + INT16 skin; + + n = (p->clonenum) + numoptions/2; + if (subtract) + n -= ((i+1)/2); + else + n += ((i+1)/2); + n %= numoptions; + + skin = setup_chargrid[p->gridx][p->gridy].skinlist[n]; + patch = faceprefix[skin][FACE_RANK]; + colormap = R_GetTranslationColormap(skin, skins[skin].prefcolor, GTC_MENUCACHE); + radius = 24<width) << FRACBITS) >> 1; + cy -= (SHORT(patch->height) << FRACBITS) >> 1; + break; + } + + case CSSTEP_COLORS: + { + INT16 diff; + + if (i == 0) + { + n = l = r = M_GetColorBefore(p->color, numoptions/2, false); + } + else if (subtract) + { + n = l = M_GetColorBefore(l, 1, false); + } + else + { + n = r = M_GetColorAfter(r, 1, false); + } + + colormap = R_GetTranslationColormap(TC_DEFAULT, n, GTC_MENUCACHE); + + diff = (numoptions - i)/2; // only 0 when i == numoptions-1 + + if (diff == 0) + patch = W_CachePatchName("COLORSP2", PU_CACHE); + else if (abs(diff) < 25) + patch = W_CachePatchName("COLORSP1", PU_CACHE); + else + patch = W_CachePatchName("COLORSP0", PU_CACHE); + + cx -= (SHORT(patch->width) << FRACBITS) >> 1; + break; + } + + case CSSTEP_FOLLOWER: + { + follower_t *fl = NULL; + + n = (p->followern + 1) + numoptions/2; + if (subtract) + n -= ((i+1)/2); + else + n += ((i+1)/2); + n %= numoptions; + + if (n == 0) + { + patch = W_CachePatchName("K_NOBLNS", PU_CACHE); + } + else + { + fl = &followers[n - 1]; + patch = W_CachePatchName(fl->icon, PU_CACHE); + + colormap = R_GetTranslationColormap(TC_DEFAULT, + K_GetEffectiveFollowerColor(p->followercolor, p->color), + GTC_MENUCACHE + ); + } + + radius = 24<width) << FRACBITS) >> 1; + cy -= (SHORT(patch->height) << FRACBITS) >> 1; + break; + } + + case CSSTEP_FOLLOWERCOLORS: + { + INT16 diff; + UINT16 col; + + if (i == 0) + { + n = l = r = M_GetColorBefore(p->followercolor, numoptions/2, true); + } + else if (subtract) + { + n = l = M_GetColorBefore(l, 1, true); + } + else + { + n = r = M_GetColorAfter(r, 1, true); + } + + col = K_GetEffectiveFollowerColor(n, p->color); + + colormap = R_GetTranslationColormap(TC_DEFAULT, col, GTC_MENUCACHE); + + diff = (numoptions - i)/2; // only 0 when i == numoptions-1 + + if (diff == 0) + patch = W_CachePatchName("COLORSP2", PU_CACHE); + else if (abs(diff) < 25) + patch = W_CachePatchName("COLORSP1", PU_CACHE); + else + patch = W_CachePatchName("COLORSP0", PU_CACHE); + + cx -= (SHORT(patch->width) << FRACBITS) >> 1; + break; + } + + default: + break; + } + + if (subtract) + ang = (signed)(ANGLE_90 - ang); + else + ang = ANGLE_90 + ang; + + if (numoptions & 1) + ang = (signed)(ang - (angamt/2)); + + if (p->rotate) + ang = (signed)(ang + ((angamt / CSROTATETICS) * p->rotate)); + + cx += FixedMul(radius, FINECOSINE(ang >> ANGLETOFINESHIFT)); + cy -= FixedMul(radius, FINESINE(ang >> ANGLETOFINESHIFT)) / 3; + + V_DrawFixedPatch(cx, cy, FRACUNIT, 0, patch, colormap); + if (p->mdepth == CSSTEP_ALTS && n != p->clonenum) + V_DrawFixedPatch(cx, cy, FRACUNIT, V_TRANSLUCENT, W_CachePatchName("ICONDARK", PU_CACHE), NULL); + } +} + +// returns false if the character couldn't be rendered +static boolean M_DrawCharacterSprite(INT16 x, INT16 y, INT16 skin, INT32 addflags, UINT8 *colormap) +{ + UINT8 spr; + spritedef_t *sprdef; + spriteframe_t *sprframe; + patch_t *sprpatch; + + UINT32 flags = 0; + UINT32 frame; + + spr = P_GetSkinSprite2(&skins[skin], SPR2_FSTN, NULL); + sprdef = &skins[skin].sprites[spr]; + + if (!sprdef->numframes) // No frames ?? + return false; // Can't render! + + frame = states[S_KART_FAST].frame & FF_FRAMEMASK; + if (frame >= sprdef->numframes) // Walking animation missing + frame = 0; // Try to use standing frame + + sprframe = &sprdef->spriteframes[frame]; + sprpatch = W_CachePatchNum(sprframe->lumppat[1], PU_CACHE); + + if (sprframe->flip & 1) // Only for first sprite + flags |= V_FLIP; // This sprite is left/right flipped! + + if (skins[skin].flags & SF_HIRES) + { + V_DrawFixedPatch(x<followern; + else + followernum = num; + + // Don't draw if we're outta bounds. + if (followernum < 0 || followernum >= numfollowers) + return false; + + fl = followers[followernum]; + + if (p != NULL) + { + usestate = p->follower_state; + useframe = p->follower_frame; + } + else + { + usestate = &states[followers[followernum].followstate]; + useframe = usestate->frame & FF_FRAMEMASK; + } + + sprdef = &sprites[usestate->sprite]; + + // draw the follower + + if (useframe >= sprdef->numframes) + useframe = 0; // frame doesn't exist, we went beyond it... what? + + sprframe = &sprdef->spriteframes[useframe]; + patch = W_CachePatchNum(sprframe->lumppat[1], PU_CACHE); + + if (sprframe->flip & 2) // Only for first sprite + { + if (addflags & V_FLIP) + addflags &= ~V_FLIP; + else + addflags |= V_FLIP; // This sprite is left/right flipped! + } + + fixed_t sine = 0; + + if (p != NULL) + { + sine = FixedMul(fl.bobamp, FINESINE(((FixedMul(4 * M_TAU_FIXED, fl.bobspeed) * p->follower_timer)>>ANGLETOFINESHIFT) & FINEMASK)); + color = K_GetEffectiveFollowerColor(p->followercolor, p->color); + } + + colormap = R_GetTranslationColormap(TC_DEFAULT, color, GTC_MENUCACHE); + V_DrawFixedPatch((x)*FRACUNIT, ((y-12)*FRACUNIT) + sine - fl.zoffs, fl.scale, addflags, patch, colormap); + + return true; +} + +static void M_DrawCharSelectSprite(UINT8 num, INT16 x, INT16 y) +{ + setup_player_t *p = &setup_player[num]; + UINT8 cnum = p->clonenum; + + // for p1 alone don't try to preview things on pages that don't exist lol. + if (p->mdepth == CSSTEP_CHARS && setup_numplayers == 1) + cnum = setup_page; + + INT16 skin = setup_chargrid[p->gridx][p->gridy].skinlist[cnum]; + UINT8 color = p->color; + UINT8 *colormap = R_GetTranslationColormap(skin, color, GTC_MENUCACHE); + INT32 flags = 0; + + // Flip for left-side players + if (!(num & 1)) + flags ^= V_FLIP; + + if (skin >= 0) + M_DrawCharacterSprite(x, y, skin, flags, colormap); +} + +static void M_DrawCharSelectPreview(UINT8 num) +{ + INT16 x = 11, y = 5; + char letter = 'A' + num; + setup_player_t *p = &setup_player[num]; + + if (num & 1) + x += 233; + + if (num > 1) + y += 99; + + V_DrawScaledPatch(x, y+6, V_TRANSLUCENT, W_CachePatchName("PREVBACK", PU_CACHE)); + + if (p->mdepth >= CSSTEP_CHARS) + { + M_DrawCharSelectSprite(num, x+32, y+75); + + if (p->mdepth >= CSSTEP_FOLLOWER) + { + M_DrawFollowerSprite(x+16, y+75, -1, !(num & 1) ? V_FLIP : 0, 0, p); + } + + M_DrawCharSelectCircle(p, x+32, y+64); + } + + if ((setup_animcounter/10) & 1 && gamestate == GS_MENU) // Not drawn outside of GS_MENU. + { + if (p->mdepth == CSSTEP_NONE && num == setup_numplayers) + { + V_DrawScaledPatch(x+1, y+36, 0, W_CachePatchName("4PSTART", PU_CACHE)); + } + else if (p->mdepth >= CSSTEP_READY) + { + V_DrawScaledPatch(x+1, y+36, 0, W_CachePatchName("4PREADY", PU_CACHE)); + } + } + + V_DrawScaledPatch(x+9, y+2, 0, W_CachePatchName("FILEBACK", PU_CACHE)); + V_DrawScaledPatch(x, y+2, 0, W_CachePatchName(va("CHARSEL%c", letter), PU_CACHE)); + if (p->mdepth > CSSTEP_PROFILE) + { + profile_t *pr = PR_GetProfile(p->profilen); + V_DrawCenteredFileString(x+16+18, y+2, 0, pr->profilename); + } + else + { + V_DrawFileString(x+16, y+2, 0, "PLAYER"); + } + + // Profile selection + if (p->mdepth == CSSTEP_PROFILE) + { + INT16 px = x+12; + INT16 py = y+48 - p->profilen*12; + UINT8 maxp = PR_GetNumProfiles(); + + UINT8 i = 0; + UINT8 j; + + for (i = 0; i < maxp; i++) + { + profile_t *pr = PR_GetProfile(i); + INT16 dist = abs(p->profilen - i); + INT32 notSelectable = 0; + SINT8 belongsTo = -1; + + if (i != PROFILE_GUEST) + { + for (j = 0; j < setup_numplayers; j++) + { + if (setup_player[j].mdepth > CSSTEP_PROFILE + && setup_player[j].profilen == i) + { + belongsTo = j; + break; + } + } + } + + if (belongsTo != -1 && belongsTo != num) + { + notSelectable |= V_TRANSLUCENT; + } + + if (dist > 2) + { + py += 12; + continue; + } + + if (dist == 2) + { + V_DrawCenteredFileString(px+26, py, notSelectable, pr->version ? pr->profilename : "NEW"); + V_DrawScaledPatch(px, py, V_TRANSLUCENT, W_CachePatchName("FILEBACK", PU_CACHE)); + } + else + { + V_DrawScaledPatch(px, py, 0, W_CachePatchName("FILEBACK", PU_CACHE)); + + if (i != p->profilen || ((setup_animcounter/10) & 1)) + { + V_DrawCenteredFileString(px+26, py, notSelectable, pr->version ? pr->profilename : "NEW"); + } + } + py += 12; + } + + } + // "Changes?" + else if (p->mdepth == CSSTEP_ASKCHANGES) + { + UINT8 i; + char choices[2][9] = {"All good", "Change"}; + INT32 xpos = x+8; + INT32 ypos = y+38; + + V_DrawFileString(xpos, ypos, 0, "READY?"); + + for (i = 0; i < 2; i++) + { + UINT8 cy = ypos+16 + (i*10); + + if (p->changeselect == i) + V_DrawScaledPatch(xpos, cy, 0, W_CachePatchName("M_CURSOR", PU_CACHE)); + + V_DrawThinString(xpos+16, cy, (p->changeselect == i ? highlightflags : 0)|V_6WIDTHSPACE, choices[i]); + } + } +} + +static void M_DrawCharSelectExplosions(void) +{ + UINT8 i; + + for (i = 0; i < CSEXPLOSIONS; i++) + { + INT16 quadx, quady; + UINT8 *colormap; + UINT8 frame; + + if (setup_explosions[i].tics == 0 || setup_explosions[i].tics > 5) + continue; + + frame = 6 - setup_explosions[i].tics; + + quadx = 4 * (setup_explosions[i].x / 3); + quady = 4 * (setup_explosions[i].y / 3); + + colormap = R_GetTranslationColormap(TC_DEFAULT, setup_explosions[i].color, GTC_MENUCACHE); + + V_DrawMappedPatch( + 82 + (setup_explosions[i].x*16) + quadx - 6, + 22 + (setup_explosions[i].y*16) + quady - 6, + 0, W_CachePatchName(va("CHCNFRM%d", frame), PU_CACHE), + colormap + ); + } +} + +#define IDLELEN 8 +#define SELECTLEN (8 + IDLELEN + 7 + IDLELEN) + +static void M_DrawCharSelectCursor(UINT8 num) +{ + static const char *idleframes[IDLELEN] = { + "CHHOV1", "CHHOV1", "CHHOV1", "CHHOV2", "CHHOV1", "CHHOV3", "CHHOV1", "CHHOV2" + }; + static const char *selectframesa[SELECTLEN] = { + "CHHOV1", "CHPIKA1", "CHHOV2", "CHPIKA2", "CHHOV3", "CHPIKA3", "CHHOV2", "CHPIKA4", + "CHHOV1", "CHHOV1", "CHHOV1", "CHHOV2", "CHHOV1", "CHHOV3", "CHHOV1", "CHHOV2", + "CHPIKA5", "CHHOV2", "CHPIKA6", "CHHOV3", "CHPIKA7", "CHHOV2", "CHPIKA8", + "CHHOV1", "CHHOV1", "CHHOV1", "CHHOV2", "CHHOV1", "CHHOV3", "CHHOV1", "CHHOV2" + }; + static const char *selectframesb[SELECTLEN] = { + NULL, "CHPIKB1", NULL, "CHPIKB2", NULL, "CHPIKB3", NULL, "CHPIKB4", + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + "CHPIKB5", NULL, "CHPIKB6", NULL, "CHPIKB7", NULL, "CHPIKB8", + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL + }; + + setup_player_t *p = &setup_player[num]; + char letter = 'A' + num; + UINT8 *colormap; + INT16 x, y; + INT16 quadx, quady; + + quadx = 4 * (p->gridx / 3); + quady = 4 * (p->gridy / 3); + + x = 82 + (p->gridx*16) + quadx - 13; + y = 22 + (p->gridy*16) + quady - 12; + + // profiles skew the graphic to the right slightly + if (optionsmenu.profile) + x += 64; + + colormap = R_GetTranslationColormap(TC_DEFAULT, (p->color != SKINCOLOR_NONE ? p->color : SKINCOLOR_GREY), GTC_MENUCACHE); + + if (p->mdepth >= CSSTEP_READY) + { + V_DrawMappedPatch(x, y, 0, W_CachePatchName("CHCNFRM0", PU_CACHE), colormap); + } + else if (p->mdepth > CSSTEP_CHARS) + { + V_DrawMappedPatch(x, y, 0, W_CachePatchName(selectframesa[setup_animcounter % SELECTLEN], PU_CACHE), colormap); + if (selectframesb[(setup_animcounter-1) % SELECTLEN] != NULL) + V_DrawMappedPatch(x, y, V_TRANSLUCENT, W_CachePatchName(selectframesb[(setup_animcounter-1) % SELECTLEN], PU_CACHE), colormap); + } + else + { + V_DrawMappedPatch(x, y, 0, W_CachePatchName(idleframes[setup_animcounter % IDLELEN], PU_CACHE), colormap); + } + + if (p->mdepth < CSSTEP_READY) + V_DrawMappedPatch(x, y, 0, W_CachePatchName(va("CSELH%c", letter), PU_CACHE), colormap); +} + +#undef IDLE +#undef IDLELEN +#undef SELECTLEN + +// Draw character profile card. +// Moved here because in the case of profile edition this is drawn in the charsel menu. +static void M_DrawProfileCard(INT32 x, INT32 y, boolean greyedout, profile_t *p) +{ + setup_player_t *sp = &setup_player[0]; // When editing profile character, we'll always be checking for what P1 is doing. + patch_t *card = W_CachePatchName("PR_CARD", PU_CACHE); + patch_t *cardbot = W_CachePatchName("PR_CARDB", PU_CACHE); + patch_t *pwrlv = W_CachePatchName("PR_PWR", PU_CACHE); + UINT8 *colormap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_BLACK, GTC_CACHE); + INT32 skinnum = -1; + INT32 powerlevel = -1; + + char pname[PROFILENAMELEN+1] = "NEW"; + + if (p != NULL && p->version) + { + colormap = R_GetTranslationColormap(TC_DEFAULT, PR_GetProfileColor(p), GTC_CACHE); + strcpy(pname, p->profilename); + skinnum = R_SkinAvailable(p->skinname); + powerlevel = p->powerlevels[0]; // Only display race power level. + } + + // check setup_player for colormap for the card. + // we'll need to check again for drawing afterwards unfortunately. + if (sp->mdepth >= CSSTEP_CHARS) + colormap = R_GetTranslationColormap(skinnum, sp->color, GTC_MENUCACHE); + + // Card + V_DrawFixedPatch(x*FRACUNIT, y*FRACUNIT, FRACUNIT, greyedout ? V_TRANSLUCENT : 0, card, colormap); + + if (greyedout) + return; // only used for profiles we can't select. + + // Draw pwlv if we can + if (powerlevel > -1) + { + V_DrawFixedPatch((x+30)*FRACUNIT, (y+84)*FRACUNIT, FRACUNIT, 0, pwrlv, colormap); + V_DrawCenteredKartString(x+30, y+87, 0, va("%d\n", powerlevel)); + } + + // check what setup_player is doing in priority. + if (sp->mdepth >= CSSTEP_CHARS) + { + + skinnum = setup_chargrid[sp->gridx][sp->gridy].skinlist[sp->clonenum]; + + if (skinnum >= 0) + { + if (M_DrawCharacterSprite(x-22, y+119, skinnum, V_FLIP, colormap)) + V_DrawMappedPatch(x+14, y+66, 0, faceprefix[skinnum][FACE_RANK], colormap); + + if (sp->mdepth >= CSSTEP_FOLLOWER) + { + if (M_DrawFollowerSprite(x-44 +12, y+119, 0, V_FLIP, 0, sp)) + { + UINT16 col = K_GetEffectiveFollowerColor(sp->followercolor, sp->color);; + patch_t *ico = W_CachePatchName(followers[sp->followern].icon, PU_CACHE); + UINT8 *fcolormap; + + fcolormap = R_GetTranslationColormap(TC_DEFAULT, col, GTC_MENUCACHE); + V_DrawMappedPatch(x+14+18, y+66, 0, ico, fcolormap); + } + } + } + + M_DrawCharSelectCircle(sp, x-22, y+104); + } + else if (skinnum > -1) // otherwise, read from profile. + { + UINT16 col = K_GetEffectiveFollowerColor(p->followercolor, p->color);; + UINT8 fln = K_FollowerAvailable(p->follower); + + if (M_DrawCharacterSprite(x-22, y+119, skinnum, V_FLIP, colormap)) + V_DrawMappedPatch(x+14, y+66, 0, faceprefix[skinnum][FACE_RANK], colormap); + + if (M_DrawFollowerSprite(x-44 +12, y+119, fln, V_FLIP, col, NULL)) + { + patch_t *ico = W_CachePatchName(followers[fln].icon, PU_CACHE); + UINT8 *fcolormap = R_GetTranslationColormap(TC_DEFAULT, col, GTC_MENUCACHE); + V_DrawMappedPatch(x+14+18, y+66, 0, ico, fcolormap); + } + } + + V_DrawCenteredGamemodeString(x, y+24, 0, 0, pname); + + // Card bottom to overlay the skin preview + V_DrawFixedPatch(x*FRACUNIT, y*FRACUNIT, FRACUNIT, 0, cardbot, colormap); + + // Profile number & player name + + if (p != NULL) + { + V_DrawProfileNum(x + 37 + 10, y + 131, 0, PR_GetProfileNum(p)); + V_DrawCenteredThinString(x, y + 151, V_GRAYMAP|V_6WIDTHSPACE, p->playername); + } +} + +void M_DrawCharacterSelect(void) +{ + UINT8 i, j, k; + UINT8 priority = 0; + INT16 quadx, quady; + INT16 skin; + INT32 basex = optionsmenu.profile != NULL ? 64 : 0; + + if (setup_numplayers > 0) + { + priority = setup_animcounter % setup_numplayers; + } + + // We have to loop twice -- first time to draw the drop shadows, a second time to draw the icons. + for (i = 0; i < 9; i++) + { + for (j = 0; j < 9; j++) + { + skin = setup_chargrid[i][j].skinlist[setup_page]; + quadx = 4 * (i / 3); + quady = 4 * (j / 3); + + // Here's a quick little cheat to save on drawing time! + // Don't draw a shadow if it'll get covered by another icon + if ((i % 3 < 2) && (j % 3 < 2)) + { + if ((setup_chargrid[i+1][j].skinlist[setup_page] != -1) + && (setup_chargrid[i][j+1].skinlist[setup_page] != -1) + && (setup_chargrid[i+1][j+1].skinlist[setup_page] != -1)) + continue; + } + + if (skin != -1) + V_DrawScaledPatch(basex+ 82 + (i*16) + quadx + 1, 22 + (j*16) + quady + 1, 0, W_CachePatchName("ICONBACK", PU_CACHE)); + } + } + + // Draw this inbetween. These drop shadows should be covered by the stat graph, but the icons shouldn't. + V_DrawScaledPatch(basex+ 3, 2, 0, W_CachePatchName((optionsmenu.profile ? "PR_STGRPH" : "STATGRPH"), PU_CACHE)); + + // Draw the icons now + for (i = 0; i < 9; i++) + { + for (j = 0; j < 9; j++) + { + for (k = 0; k < setup_numplayers; k++) + { + if (setup_player[k].gridx == i && setup_player[k].gridy == j) + break; // k == setup_numplayers means no one has it selected + } + + skin = setup_chargrid[i][j].skinlist[setup_page]; + quadx = 4 * (i / 3); + quady = 4 * (j / 3); + + if (skin != -1) + { + UINT8 *colormap; + + if (k == setup_numplayers) + colormap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_GREY, GTC_MENUCACHE); + else + colormap = R_GetTranslationColormap(skin, skins[skin].prefcolor, GTC_MENUCACHE); + + V_DrawMappedPatch(basex + 82 + (i*16) + quadx, 22 + (j*16) + quady, 0, faceprefix[skin][FACE_RANK], colormap); + + // draw dot if there are more alts behind there! + if (setup_page+1 < setup_chargrid[i][j].numskins) + V_DrawScaledPatch(basex + 82 + (i*16) + quadx, 22 + (j*16) + quady + 11, 0, W_CachePatchName("ALTSDOT", PU_CACHE)); + } + } + } + + // Explosions when you've made your final selection + M_DrawCharSelectExplosions(); + + for (i = 0; i < MAXSPLITSCREENPLAYERS; i++) + { + // Draw a preview for each player + if (optionsmenu.profile == NULL) + { + M_DrawCharSelectPreview(i); + } + else if (i == 0) + { + M_DrawProfileCard(optionsmenu.optx, optionsmenu.opty, false, optionsmenu.profile); + } + + if (i >= setup_numplayers) + continue; + + // Draw the cursors + if (i != priority) + M_DrawCharSelectCursor(i); + } + + if (setup_numplayers > 0) + { + // Draw the priority player over the other ones + M_DrawCharSelectCursor(priority); + } +} + +// DIFFICULTY SELECT +// This is a mix of K_DrawKartGamemodeMenu and the generic menu drawer depending on what we need. +// This is only ever used here (I hope because this is starting to pile up on hacks to look like the old m_menu.c lol...) + +void M_DrawRaceDifficulty(void) +{ + patch_t *box = W_CachePatchName("M_DBOX", PU_CACHE); + + INT32 i; + INT32 x = 120; + INT32 y = 48; + + M_DrawMenuTooltips(); + + // Draw the box for difficulty... + V_DrawFixedPatch((111 + 48*menutransition.tics)*FRACUNIT, 33*FRACUNIT, FRACUNIT, 0, box, NULL); + + if (menutransition.tics) + { + x += 48 * menutransition.tics; + } + + for (i = 0; i < currentMenu->numitems; i++) + { + if (i >= drace_boxend) + { + x = GM_STARTX + (GM_XOFFSET * 5 / 2); + y = GM_STARTY + (GM_YOFFSET * 5 / 2); + + if (i < currentMenu->numitems-1) + { + x -= GM_XOFFSET; + y -= GM_YOFFSET; + } + + + if (menutransition.tics) + { + x += 48 * menutransition.tics; + } + } + + switch (currentMenu->menuitems[i].status & IT_DISPLAY) + { + // This is HACKY...... + + case IT_STRING2: + { + + INT32 f = (i == itemOn) ? highlightflags : 0; + + V_DrawString(140 + 48*menutransition.tics, y, f, currentMenu->menuitems[i].text); + + if (currentMenu->menuitems[i].status & IT_CVAR) + { + // implicitely we'll only take care of normal cvars + INT32 cx = 260 + 48*menutransition.tics; + consvar_t *cv = currentMenu->menuitems[i].itemaction.cvar; + + V_DrawCenteredString(cx, y, f, cv->string); + + if (i == itemOn) + { + + INT32 w = V_StringWidth(cv->string, 0)/2; + + V_DrawCharacter(cx - 10 - w - (skullAnimCounter/5), y, '\x1C' | highlightflags, false); // left arrow + V_DrawCharacter(cx + w + 2 + (skullAnimCounter/5), y, '\x1D' | highlightflags, false); // right arrow + } + } + + y += 10; + + break; + } + + case IT_STRING: + { + + UINT8 *colormap = NULL; + + if (i == itemOn) + { + colormap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_PLAGUE, GTC_CACHE); + } + else + { + colormap = R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_MOSS, GTC_CACHE); + } + + + if (currentMenu->menuitems[i].status & IT_CVAR) + { + + INT32 fx = (x - 48*menutransition.tics); + INT32 centx = fx + (320-fx)/2 + (menutransition.tics*48); // undo the menutransition movement to redo it here otherwise the text won't move at the same speed lole. + + // implicitely we'll only take care of normal consvars + consvar_t *cv = currentMenu->menuitems[i].itemaction.cvar; + + V_DrawFixedPatch(x*FRACUNIT, y*FRACUNIT, FRACUNIT, 0, W_CachePatchName("MENUSHRT", PU_CACHE), colormap); + V_DrawCenteredGamemodeString(centx, y - 3, V_ALLOWLOWERCASE, colormap, cv->string); + + if (i == itemOn) + { + patch_t *arr_r = W_CachePatchName("GM_ARRL", PU_CACHE); + patch_t *arr_l = W_CachePatchName("GM_ARRR", PU_CACHE); + + V_DrawFixedPatch((centx-54 - arr_r->width - (skullAnimCounter/5))*FRACUNIT, (y-3)*FRACUNIT, FRACUNIT, 0, arr_r, colormap); + V_DrawFixedPatch((centx+54 + (skullAnimCounter/5))*FRACUNIT, (y-3)*FRACUNIT, FRACUNIT, 0, arr_l, colormap); + } + + } + else // not a cvar + { + V_DrawFixedPatch(x*FRACUNIT, y*FRACUNIT, FRACUNIT, 0, W_CachePatchName("MENUPLTR", PU_CACHE), colormap); + V_DrawGamemodeString(x + 16, y - 3, V_ALLOWLOWERCASE, colormap, currentMenu->menuitems[i].text); + } + x += GM_XOFFSET; + y += GM_YOFFSET; + + break; + } + } + } +} + +// LEVEL SELECT + +static void M_DrawCupPreview(INT16 y, cupheader_t *cup) +{ + UINT8 i; + const INT16 pad = ((vid.width/vid.dupx) - BASEVIDWIDTH)/2; + INT16 x = -(cupgrid.previewanim % 82) - pad; + + V_DrawFill(0, y, BASEVIDWIDTH, 54, 31); + + if (cup && (cup->unlockrequired == -1 || unlockables[cup->unlockrequired].unlocked)) + { + i = (cupgrid.previewanim / 82) % cup->numlevels; + while (x < BASEVIDWIDTH + pad) + { + lumpnum_t lumpnum; + patch_t *PictureOfLevel; + + lumpnum = W_CheckNumForName(va("%sP", G_BuildMapName(cup->levellist[i]+1))); + if (lumpnum != LUMPERROR) + PictureOfLevel = W_CachePatchNum(lumpnum, PU_CACHE); + else + PictureOfLevel = W_CachePatchName("BLANKLVL", PU_CACHE); + + V_DrawSmallScaledPatch(x + 1, y+2, 0, PictureOfLevel); + i = (i+1) % cup->numlevels; + x += 82; + } + } + else + { + patch_t *st = W_CachePatchName(va("PREVST0%d", (cupgrid.previewanim % 4) + 1), PU_CACHE); + while (x < BASEVIDWIDTH) + { + V_DrawScaledPatch(x+1, y+2, 0, st); + x += 82; + } + } +} + +static void M_DrawCupTitle(INT16 y, cupheader_t *cup) +{ + V_DrawScaledPatch(0, y, 0, W_CachePatchName("MENUHINT", PU_CACHE)); + + if (cup) + { + boolean unlocked = (cup->unlockrequired == -1 || unlockables[cup->unlockrequired].unlocked); + UINT8 *colormap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_GREY, GTC_MENUCACHE); + patch_t *icon = W_CachePatchName(cup->icon, PU_CACHE); + const char *str = (unlocked ? va("%s Cup", cup->name) : "???"); + INT16 offset = V_LSTitleLowStringWidth(str, 0) / 2; + + V_DrawLSTitleLowString(BASEVIDWIDTH/2 - offset, y+6, 0, str); + + if (unlocked) + { + V_DrawMappedPatch(BASEVIDWIDTH/2 - offset - 24, y+5, 0, icon, colormap); + V_DrawMappedPatch(BASEVIDWIDTH/2 + offset + 3, y+5, 0, icon, colormap); + } + } + else + { + if (currentMenu == &PLAY_LevelSelectDef) + V_DrawCenteredLSTitleLowString(BASEVIDWIDTH/2, y+6, 0, va("%s Mode", Gametype_Names[levellist.newgametype])); + } +} + +void M_DrawCupSelect(void) +{ + UINT8 i, j; + cupheader_t *cup = kartcupheaders; + + while (cup) + { + if (cup->id == CUPMENU_CURSORID) + break; + cup = cup->next; + } + + for (i = 0; i < CUPMENU_COLUMNS; i++) + { + for (j = 0; j < CUPMENU_ROWS; j++) + { + UINT8 id = (i + (j * CUPMENU_COLUMNS)) + (cupgrid.pageno * (CUPMENU_COLUMNS * CUPMENU_ROWS)); + cupheader_t *iconcup = kartcupheaders; + patch_t *patch = NULL; + INT16 x, y; + INT16 icony = 7; + + while (iconcup) + { + if (iconcup->id == id) + break; + iconcup = iconcup->next; + } + + if (!iconcup) + break; + + /*if (iconcup->emeraldnum == 0) + patch = W_CachePatchName("CUPMON3A", PU_CACHE); + else*/ if (iconcup->emeraldnum > 7) + { + patch = W_CachePatchName("CUPMON2A", PU_CACHE); + icony = 5; + } + else + patch = W_CachePatchName("CUPMON1A", PU_CACHE); + + x = 14 + (i*42); + y = 20 + (j*44) - (30*menutransition.tics); + + V_DrawScaledPatch(x, y, 0, patch); + + if (iconcup->unlockrequired != -1 && !unlockables[iconcup->unlockrequired].unlocked) + { + patch_t *st = W_CachePatchName(va("ICONST0%d", (cupgrid.previewanim % 4) + 1), PU_CACHE); + V_DrawScaledPatch(x + 8, y + icony, 0, st); + } + else + { + V_DrawScaledPatch(x + 8, y + icony, 0, W_CachePatchName(iconcup->icon, PU_CACHE)); + V_DrawScaledPatch(x + 8, y + icony, 0, W_CachePatchName("CUPBOX", PU_CACHE)); + } + } + } + + V_DrawScaledPatch(14 + (cupgrid.x*42) - 4, + 20 + (cupgrid.y*44) - 1 - (24*menutransition.tics), + 0, W_CachePatchName("CUPCURS", PU_CACHE) + ); + + M_DrawCupPreview(146 + (24*menutransition.tics), cup); + M_DrawCupTitle(120 - (24*menutransition.tics), cup); +} + +static void M_DrawHighLowLevelTitle(INT16 x, INT16 y, INT16 map) +{ + char word1[22]; + char word2[22]; + UINT8 word1len = 0; + UINT8 word2len = 0; + INT16 x2 = x; + UINT8 i; + + if (!mapheaderinfo[map] || !mapheaderinfo[map]->lvlttl[0]) + return; + + if (mapheaderinfo[map]->zonttl[0]) + { + boolean one = true; + boolean two = true; + + for (i = 0; i < 22; i++) + { + if (!one && !two) + break; + + if (mapheaderinfo[map]->lvlttl[i] && one) + { + word1[word1len] = mapheaderinfo[map]->lvlttl[i]; + word1len++; + } + else + one = false; + + if (mapheaderinfo[map]->zonttl[i] && two) + { + word2[word2len] = mapheaderinfo[map]->zonttl[i]; + word2len++; + } + else + two = false; + } + } + else + { + boolean donewithone = false; + + for (i = 0; i < 22; i++) + { + if (!mapheaderinfo[map]->lvlttl[i]) + break; + + if (mapheaderinfo[map]->lvlttl[i] == ' ') + { + if (!donewithone) + { + donewithone = true; + continue; + } + } + + if (donewithone) + { + word2[word2len] = mapheaderinfo[map]->lvlttl[i]; + word2len++; + } + else + { + word1[word1len] = mapheaderinfo[map]->lvlttl[i]; + word1len++; + } + } + } + + if (mapheaderinfo[map]->actnum) + { + word2[word2len] = ' '; + word2len++; + + word2[word2len] = '0' + mapheaderinfo[map]->actnum; + word2len++; + } + + word1[word1len] = '\0'; + word2[word2len] = '\0'; + + { + char addlen[3]; + + for (i = 0; i < 2; i++) + { + if (!word1[i]) + break; + + addlen[i] = word1[i]; + } + + addlen[i] = '\0'; + x2 += V_LSTitleLowStringWidth(addlen, 0); + } + + + if (word1len) + V_DrawLSTitleHighString(x, y, 0, word1); + if (word2len) + V_DrawLSTitleLowString(x2, y+28, 0, word2); +} + +static void M_DrawLevelSelectBlock(INT16 x, INT16 y, INT16 map, boolean redblink, boolean greyscale) +{ + lumpnum_t lumpnum; + patch_t *PictureOfLevel; + UINT8 *colormap = NULL; + + if (greyscale) + colormap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_GREY, GTC_MENUCACHE); + + lumpnum = W_CheckNumForName(va("%sP", G_BuildMapName(map+1))); + if (lumpnum != LUMPERROR) + PictureOfLevel = W_CachePatchNum(lumpnum, PU_CACHE); + else + PictureOfLevel = W_CachePatchName("BLANKLVL", PU_CACHE); + + if (redblink) + V_DrawScaledPatch(3+x, y, 0, W_CachePatchName("LVLSEL2", PU_CACHE)); + else + V_DrawScaledPatch(3+x, y, 0, W_CachePatchName("LVLSEL", PU_CACHE)); + + V_DrawSmallMappedPatch(9+x, y+6, 0, PictureOfLevel, colormap); + M_DrawHighLowLevelTitle(98+x, y+8, map); +} + +void M_DrawLevelSelect(void) +{ + INT16 i; + INT16 start = M_GetFirstLevelInList(levellist.newgametype); + INT16 map = start; + INT16 t = (64*menutransition.tics), tay = 0; + INT16 y = 80 - (12 * levellist.y); + boolean tatransition = ((menutransition.startmenu == &PLAY_TimeAttackDef || menutransition.endmenu == &PLAY_TimeAttackDef) && menutransition.tics); + + if (tatransition) + { + t = -t; + tay = t/2; + } + + for (i = 0; i < M_CountLevelsToShowInList(levellist.newgametype); i++) + { + INT16 lvlx = t, lvly = y; + + while (!M_CanShowLevelInList(map, levellist.newgametype) && map < NUMMAPS) + map++; + + if (map >= NUMMAPS) + break; + + if (i == levellist.cursor && tatransition) + { + lvlx = 0; + lvly = max(2, y+tay); + } + + M_DrawLevelSelectBlock(lvlx, lvly, map, + (i == levellist.cursor && (((skullAnimCounter / 4) & 1) || tatransition)), + (i != levellist.cursor) + ); + + y += 72; + map++; + } + + M_DrawCupTitle(tay, levellist.selectedcup); +} + +void M_DrawTimeAttack(void) +{ + INT16 map = levellist.choosemap; + INT16 t = (48*menutransition.tics); + INT16 leftedge = 149+t+16; + INT16 rightedge = 149+t+155; + INT16 opty = 140; + INT32 w; + lumpnum_t lumpnum; + UINT8 i; + consvar_t *cv; + + M_DrawLevelSelectBlock(0, 2, map, true, false); + + //V_DrawFill(24-t, 82, 100, 100, 36); // size test + + V_DrawScaledPatch(149+t, 70, 0, W_CachePatchName("BESTTIME", PU_CACHE)); + + if (currentMenu == &PLAY_TimeAttackDef) + { + lumpnum = W_CheckNumForName(va("%sR", G_BuildMapName(map+1))); + if (lumpnum != LUMPERROR) + V_DrawScaledPatch(24-t, 82, 0, W_CachePatchNum(lumpnum, PU_CACHE)); + + V_DrawRightAlignedString(rightedge-12, 82, highlightflags, "BEST LAP:"); + K_drawKartTimestamp(0, 162+t, 88, 0, 2); + + V_DrawRightAlignedString(rightedge-12, 112, highlightflags, "BEST TIME:"); + K_drawKartTimestamp(0, 162+t, 118, map, 1); + } + else + opty = 80; + + for (i = 0; i < currentMenu->numitems; i++) + { + UINT32 f = (i == itemOn) ? highlightflags : 0; + + switch (currentMenu->menuitems[i].status & IT_DISPLAY) + { + + case IT_HEADERTEXT: + + V_DrawString(leftedge, opty, highlightflags, currentMenu->menuitems[i].text); + opty += 10; + break; + + case IT_STRING: + + if (i >= currentMenu->numitems-1) + V_DrawRightAlignedString(rightedge, opty, f, currentMenu->menuitems[i].text); + else + V_DrawString(leftedge, opty, f, currentMenu->menuitems[i].text); + opty += 10; + + // Cvar specific handling + + if (currentMenu->menuitems[i].status & IT_CVAR) + { + cv = currentMenu->menuitems[i].itemaction.cvar; + + w = V_StringWidth(cv->string, 0); + V_DrawString(leftedge, opty, f, cv->string); + if (i == itemOn) + { + V_DrawCharacter(leftedge - 10 - (skullAnimCounter/5), opty, '\x1C' | f, false); // left arrow + V_DrawCharacter(leftedge + w + 2+ (skullAnimCounter/5), opty, '\x1D' | f, false); // right arrow + } + opty += 10; + } + + break; + case IT_SPACE: + opty += 4; + break; + } + } +} + +// This draws the options of a given menu in a fashion specific to the multiplayer option select screen (host game / server browser etc) +// First argument is the menu to draw the options from, the 2nd one is a table that contains which option is to be "extendded" +// Format for each option: {extended? (only used by the ticker), max extension (likewise), # of lines to extend} + +// NOTE: This is pretty rigid and only intended for use with the multiplayer options menu which has *3* choices. + +static void M_MPOptDrawer(menu_t *m, INT16 extend[3][3]) +{ + // This is a copypaste of the generic gamemode menu code with a few changes. + // TODO: Allow specific options to "expand" into smaller ones. + + patch_t *buttback = W_CachePatchName("M_PLAT2", PU_CACHE); + INT32 i, x = 132, y = 32; // Dirty magic numbers for now but they work out. + + for (i = 0; i < m->numitems; i++) + { + + switch (m->menuitems[i].status & IT_DISPLAY) + { + case IT_STRING: + { + UINT8 *colormap = NULL; + INT16 j = 0; + + if ((currentMenu == m && i == itemOn) || extend[i][0]) // Selected / unfolded + { + colormap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_PLAGUE, GTC_CACHE); + } + else + { + colormap = R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_MOSS, GTC_CACHE); + } + + if (extend[i][2]) + { + for (j=0; j < extend[i][2]/2; j++) + { + // Draw rectangles that look like the current selected item starting from the top of the actual selection graphic and going up to where it's supposed to go. + // With colour 169 (that's the index of the shade of black the plague colourization gives us. ...No I don't like using a magic number either. + V_DrawFill(x + (extend[i][2]/2) - j - (buttback->width/2), (y + extend[i][2]) - (2*j), 225, 2, 169); + } + } + V_DrawFixedPatch((x + (extend[i][2]/2)) *FRACUNIT, (y + extend[i][2])*FRACUNIT, FRACUNIT, 0, buttback, colormap); + V_DrawCenteredGamemodeString(x, y - 3, V_ALLOWLOWERCASE, colormap, m->menuitems[i].text); + } + break; + } + + x += GM_XOFFSET; + y += GM_YOFFSET + extend[i][2]; + } +} + +// Draws the EGGA CHANNEL background. +static void M_DrawEggaChannel(void) +{ + patch_t *background = W_CachePatchName("M_EGGACH", PU_CACHE); + + V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 25); + V_DrawFixedPatch(160<numitems; i++) + { + if (i == currentMenu->numitems-1) + { + xp = 202; + yp = 100; + + UINT8 *colormap = R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_MOSS, GTC_CACHE); + if (i == itemOn) + colormap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_PLAGUE, GTC_CACHE); + + // Ideally we'd calculate this but it's not worth it for a 1-off menu probably..... + V_DrawFixedPatch(xp<width/2), yp -3, V_ALLOWLOWERCASE, colormap, currentMenu->menuitems[i].text); + } + else + { + switch (currentMenu->menuitems[i].status & IT_DISPLAY) + { + case IT_TRANSTEXT2: + { + V_DrawThinString(xp, yp, V_ALLOWLOWERCASE|V_6WIDTHSPACE|V_TRANSLUCENT, currentMenu->menuitems[i].text); + xp += 5; + yp += 11; + break; + } + case IT_STRING: + { + V_DrawThinString(xp, yp, V_ALLOWLOWERCASE|V_6WIDTHSPACE | (i == itemOn ? highlightflags : 0), currentMenu->menuitems[i].text); + + // Cvar specific handling + switch (currentMenu->menuitems[i].status & IT_TYPE) + { + case IT_CVAR: + { + consvar_t *cv = currentMenu->menuitems[i].itemaction.cvar; + switch (currentMenu->menuitems[i].status & IT_CVARTYPE) + { + case IT_CV_STRING: + V_DrawThinString(xp + 96, yp, V_ALLOWLOWERCASE|V_6WIDTHSPACE, cv->string); + if (skullAnimCounter < 4 && i == itemOn) + V_DrawString(xp + 94 + V_ThinStringWidth(cv->string, V_6WIDTHSPACE), yp+1, 0, "_"); + + break; + + default: + w = V_ThinStringWidth(cv->string, V_6WIDTHSPACE); + V_DrawThinString(xp + 138 - w, yp, ((cv->flags & CV_CHEAT) && !CV_IsSetToDefault(cv) ? warningflags : highlightflags)|V_6WIDTHSPACE, cv->string); + if (i == itemOn) + { + V_DrawCharacter(xp + 138 - 10 - w - (skullAnimCounter/5), yp, '\x1C' | highlightflags, false); // left arrow + V_DrawCharacter(xp + 138 + 2 + (skullAnimCounter/5), yp, '\x1D' | highlightflags, false); // right arrow + } + break; + } + break; + } + } + + xp += 5; + yp += 11; + + break; + } + break; + } + + } + + } +} + +// Multiplayer mode option select: JOIN BY IP +// Once again we'll copypaste 1 bit of the generic menu handler we used for hosting but we only need it for IT_CV_STRING since that's all we got :) +// (I don't like duplicating code but I rather this than some horrible all-in-one function with too many options...) +void M_DrawMPJoinIP(void) +{ + + //patch_t *minibutt = W_CachePatchName("M_SBUTT", PU_CACHE); + // There is no such things as mini butts, only thick thighs to rest your head on. + //patch_t *minigo = W_CachePatchName("M_SGO", PU_CACHE); + patch_t *typebar = W_CachePatchName("M_TYPEB", PU_CACHE); + + //UINT8 *colormap = NULL; + UINT8 *colormapc = NULL; + + INT32 xp = 73, yp = 133, i = 0; // Starting position for the text drawing. + M_DrawMPOptSelect(); // Draw the Multiplayer option select menu first + + + // Now draw our host options... + for (i = 0; i < currentMenu->numitems; i++) + { + + switch (currentMenu->menuitems[i].status & IT_DISPLAY) + { + case IT_STRING: + { + + char str[MAXSTRINGLENGTH]; + strcpy(str, currentMenu->menuitems[i].text); + + // The last 3 options of this menu are to be the joined IP addresses... + if (currentMenu->numitems - i <= NUMLOGIP) + { + UINT8 index = NUMLOGIP - (currentMenu->numitems - i); + if (strlen(joinedIPlist[index][1])) // Try drawing server name + strcpy(str, joinedIPlist[index][1]); + else if (strlen(joinedIPlist[index][0])) // If that fails, get the address + strcpy(str, joinedIPlist[index][0]); + else + strcpy(str, "---"); // If that fails too then there's nothing! + } + + V_DrawString(xp, yp, V_ALLOWLOWERCASE | ((i == itemOn || currentMenu->menuitems[i].status & IT_SPACE) ? highlightflags : 0), str); + + // Cvar specific handling + switch (currentMenu->menuitems[i].status & IT_TYPE) + { + case IT_CVAR: + { + consvar_t *cv = currentMenu->menuitems[i].itemaction.cvar; + switch (currentMenu->menuitems[i].status & IT_CVARTYPE) + { + case IT_CV_STRING: + + //colormap = R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_MOSS, GTC_CACHE); + colormapc = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_PLAGUE, GTC_CACHE); + + V_DrawFixedPatch((xp + 20)<string); + if (skullAnimCounter < 4 && i == itemOn) + V_DrawCharacter(xp + 24 + V_ThinStringWidth(cv->string, 0), yp, '_' | 0x80, false); + + /*// On this specific menu the only time we'll ever see this is for the connect by IP typefield. + // Draw the small GO button here (and the text which is a separate graphic) + V_DrawFixedPatch((xp + 20 + typebar->width -4)<width -4 + (minibutt->width/2))<height)) + soffy)*FRACUNIT , FRACUNIT, V_ADD, drawp, NULL); + soffy += scrollp[mpmenu.room]->height; + } + + + // Draw buttons: + V_DrawFixedPatch(160<height)*FRACUNIT; + fixed_t text2loop = SHORT(text2->width)*FRACUNIT; + + const UINT8 startx = 18; + const UINT8 basey = 56; + const INT32 starty = basey - 18*mpmenu.scrolln + mpmenu.slide; + INT32 ypos = 0; + UINT8 i; + + // background stuff + V_DrawFixedPatch(0, 0, FRACUNIT, 0, W_CachePatchName("BG_MPS3", PU_CACHE), NULL); + + V_DrawFixedPatch(0, (BASEVIDHEIGHT + 16) * FRACUNIT, FRACUNIT, V_TRANSLUCENT, W_CachePatchName("MENUBG2", PU_CACHE), NULL); + + V_DrawFixedPatch(-bgText2Scroll, (BASEVIDHEIGHT-8) * FRACUNIT, + FRACUNIT, V_TRANSLUCENT, text2, NULL); + V_DrawFixedPatch(-bgText2Scroll + text2loop, (BASEVIDHEIGHT-8) * FRACUNIT, + FRACUNIT, V_TRANSLUCENT, text2, NULL); + + V_DrawFixedPatch(8 * FRACUNIT, -bgText1Scroll, + FRACUNIT, V_TRANSLUCENT, text1, NULL); + V_DrawFixedPatch(8 * FRACUNIT, -bgText1Scroll + text1loop, + FRACUNIT, V_TRANSLUCENT, text1, NULL); + + // bgTextScroll is already handled by the menu background. + + // the actual server list. + for (i = 0; i < serverlistcount; i++) + { + + boolean racegt = strcmp(serverlist[i].info.gametypename, "Race") == 0; + INT32 transflag = 0; + INT32 basetransflag = 0; + + if (serverlist[i].info.numberofplayer >= serverlist[i].info.maxplayer) + basetransflag = 5; + + /*if (i < mpmenu.servernum) + // if slide > 0, then we went DOWN in the list and have to fade that server out. + if (mpmenu.slide > 0) + transflag = basetransflag + (18-mpmenu.slide)/2; + else if (mpmenu.slide < 0) + transflag = 10 - basetransflag - (18-mpmenu.slide)/2;*/ + + transflag = basetransflag; + + if (transflag >= 0 && transflag < 10) + { + transflag = transflag << V_ALPHASHIFT; // shift the translucency flag. + + if (itemOn == 2 && mpmenu.servernum == i) + V_DrawFixedPatch(startx*FRACUNIT, (starty + ypos)*FRACUNIT, FRACUNIT, transflag, racegt ? racehs : batlhs, NULL); + else + V_DrawFixedPatch(startx*FRACUNIT, (starty + ypos)*FRACUNIT, FRACUNIT, transflag, racegt ? raceh : batlh, NULL); + + // Server name: + V_DrawString(startx+11, starty + ypos + 6, V_ALLOWLOWERCASE|transflag, serverlist[i].info.servername); + + // Ping: + V_DrawThinString(startx + 191, starty + ypos + 7, V_6WIDTHSPACE|transflag, va("%03d", serverlist[i].info.time)); + + // Playercount + V_DrawThinString(startx + 214, starty + ypos + 7, V_6WIDTHSPACE|transflag, va("%02d/%02d", serverlist[i].info.numberofplayer, serverlist[i].info.maxplayer)); + + // Power Level + V_DrawThinString(startx + 248, starty + ypos, V_6WIDTHSPACE|transflag, va("%04d PLv", serverlist[i].info.avgpwrlv)); + + // game speed if applicable: + if (racegt) + { + UINT8 speed = serverlist[i].info.kartvars & SV_SPEEDMASK; + patch_t *pp = W_CachePatchName(va("M_SDIFF%d", speed), PU_CACHE); + + V_DrawFixedPatch((startx + 251)*FRACUNIT, (starty + ypos + 9)*FRACUNIT, FRACUNIT, transflag, pp, NULL); + } + } + ypos += SERVERSPACE; + } + + // Draw genericmenu ontop! + V_DrawFill(0, 0, 320, 52, 31); + V_DrawFill(0, 53, 320, 1, 31); + V_DrawFill(0, 55, 320, 1, 31); + + V_DrawCenteredGamemodeString(160, 2, V_ALLOWLOWERCASE, 0, "Server Browser"); + + // normal menu options + M_DrawGenericMenu(); + +} + +// OPTIONS MENU + +// Draws the cogs and also the options background! +static void M_DrawOptionsCogs(void) +{ + // the background isn't drawn outside of being in the main menu state. + if (gamestate == GS_MENU) + { + patch_t *back[3] = {W_CachePatchName("OPT_BG1", PU_CACHE), W_CachePatchName("OPT_BG2", PU_CACHE), W_CachePatchName("OPT_BG3", PU_CACHE)}; + INT32 tflag = 0; + UINT8 *c; + UINT8 *c2; // colormap for the one we're changing + + if (optionsmenu.fade) + { + c2 = R_GetTranslationColormap(TC_DEFAULT, optionsmenu.lastcolour, GTC_CACHE); + V_DrawFixedPatch(0, 0, FRACUNIT, 0, back[(optionsmenu.ticker/10) %3], c2); + + // prepare fade flag: + tflag = min(V_90TRANS, (optionsmenu.fade)<numitems; i++) + { + INT32 py = y - (itemOn*48); + INT32 px = x - menutransition.tics*64; + INT32 tflag = 0; + + if (i == itemOn) + c = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_PLAGUE, GTC_CACHE); + else + c = R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_BLACK, GTC_CACHE); + + if (currentMenu->menuitems[i].status & IT_TRANSTEXT) + tflag = V_TRANSLUCENT; + + if (!(menutransition.tics && i == itemOn)) + { + V_DrawFixedPatch(px*FRACUNIT, py*FRACUNIT, FRACUNIT, 0, buttback, c); + V_DrawCenteredGamemodeString(px-3, py - 16, V_ALLOWLOWERCASE|tflag, (i == itemOn ? c : NULL), currentMenu->menuitems[i].text); + } + + y += 48; + x += 48; + } + + M_DrawMenuTooltips(); + + if (menutransition.tics) + M_DrawOptionsMovingButton(); + +} + +void M_DrawGenericOptions(void) +{ + INT32 x = currentMenu->x - menutransition.tics*48, y = currentMenu->y, w, i, cursory = 0; + + M_DrawOptionsCogs(); + M_DrawMenuTooltips(); + M_DrawOptionsMovingButton(); + + for (i = 0; i < currentMenu->numitems; i++) + { + if (i == itemOn) + cursory = y; + switch (currentMenu->menuitems[i].status & IT_DISPLAY) + { + case IT_PATCH: + if (currentMenu->menuitems[i].patch && currentMenu->menuitems[i].patch[0]) + { + if (currentMenu->menuitems[i].status & IT_CENTER) + { + patch_t *p; + p = W_CachePatchName(currentMenu->menuitems[i].patch, PU_CACHE); + V_DrawScaledPatch((BASEVIDWIDTH - SHORT(p->width))/2, y, 0, p); + } + else + { + V_DrawScaledPatch(x, y, 0, + W_CachePatchName(currentMenu->menuitems[i].patch, PU_CACHE)); + } + } + /* FALLTHRU */ + case IT_NOTHING: + case IT_DYBIGSPACE: + y += SMALLLINEHEIGHT; + break; +#if 0 + case IT_BIGSLIDER: + M_DrawThermo(x, y, currentMenu->menuitems[i].itemaction.cvar); + y += LINEHEIGHT; + break; +#endif + case IT_STRING: + case IT_WHITESTRING: + if (i == itemOn) + cursory = y; + + if ((currentMenu->menuitems[i].status & IT_DISPLAY)==IT_STRING) + V_DrawString(x, y, 0, currentMenu->menuitems[i].text); + else + V_DrawString(x, y, highlightflags, currentMenu->menuitems[i].text); + + // Cvar specific handling + switch (currentMenu->menuitems[i].status & IT_TYPE) + case IT_CVAR: + { + consvar_t *cv = currentMenu->menuitems[i].itemaction.cvar; + switch (currentMenu->menuitems[i].status & IT_CVARTYPE) + { + case IT_CV_SLIDER: + M_DrawSlider(x, y, cv, (i == itemOn)); + case IT_CV_NOPRINT: // color use this + case IT_CV_INVISSLIDER: // monitor toggles use this + break; + case IT_CV_STRING: + M_DrawTextBox(x, y + 4, MAXSTRINGLENGTH, 1); + V_DrawString(x + 8, y + 12, V_ALLOWLOWERCASE, cv->string); + if (skullAnimCounter < 4 && i == itemOn) + V_DrawCharacter(x + 8 + V_StringWidth(cv->string, 0), y + 12, + '_' | 0x80, false); + y += 16; + break; + default: + w = V_StringWidth(cv->string, 0); + V_DrawString(BASEVIDWIDTH - x - w, y, + ((cv->flags & CV_CHEAT) && !CV_IsSetToDefault(cv) ? warningflags : highlightflags), cv->string); + if (i == itemOn) + { + V_DrawCharacter(BASEVIDWIDTH - x - 10 - w - (skullAnimCounter/5), y, + '\x1C' | highlightflags, false); // left arrow + V_DrawCharacter(BASEVIDWIDTH - x + 2 + (skullAnimCounter/5), y, + '\x1D' | highlightflags, false); // right arrow + } + break; + } + break; + } + y += STRINGHEIGHT; + break; + case IT_STRING2: + V_DrawString(x, y, 0, currentMenu->menuitems[i].text); + /* FALLTHRU */ + case IT_DYLITLSPACE: + case IT_SPACE: + y += (currentMenu->menuitems[i].mvar1 ? currentMenu->menuitems[i].mvar1 : SMALLLINEHEIGHT); + break; + case IT_GRAYPATCH: + if (currentMenu->menuitems[i].patch && currentMenu->menuitems[i].patch[0]) + V_DrawMappedPatch(x, y, 0, + W_CachePatchName(currentMenu->menuitems[i].patch,PU_CACHE), graymap); + y += (currentMenu->menuitems[i].mvar1 ? currentMenu->menuitems[i].mvar1 : SMALLLINEHEIGHT); + break; + case IT_TRANSTEXT: + if (currentMenu->menuitems[i].mvar1) + y = currentMenu->y+currentMenu->menuitems[i].mvar1; + /* FALLTHRU */ + case IT_TRANSTEXT2: + V_DrawString(x, y, V_TRANSLUCENT, currentMenu->menuitems[i].text); + y += SMALLLINEHEIGHT; + break; + case IT_QUESTIONMARKS: + if (currentMenu->menuitems[i].mvar1) + y = currentMenu->y+currentMenu->menuitems[i].mvar1; + + V_DrawString(x, y, V_TRANSLUCENT|V_OLDSPACING, M_CreateSecretMenuOption(currentMenu->menuitems[i].text)); + y += SMALLLINEHEIGHT; + break; + case IT_HEADERTEXT: // draws 16 pixels to the left, in yellow text + if (currentMenu->menuitems[i].mvar1) + y = currentMenu->y+currentMenu->menuitems[i].mvar1; + + V_DrawString(x-16, y, highlightflags, currentMenu->menuitems[i].text); + y += SMALLLINEHEIGHT; + break; + } + } + + // DRAW THE SKULL CURSOR + if (((currentMenu->menuitems[itemOn].status & IT_DISPLAY) == IT_PATCH) + || ((currentMenu->menuitems[itemOn].status & IT_DISPLAY) == IT_NOTHING)) + { + V_DrawScaledPatch(x + SKULLXOFF, cursory - 5, 0, + W_CachePatchName("M_CURSOR", PU_CACHE)); + } + else + { + V_DrawScaledPatch(x - 24, cursory, 0, + W_CachePatchName("M_CURSOR", PU_CACHE)); + V_DrawString(x, cursory, highlightflags, currentMenu->menuitems[itemOn].text); + } +} + +// *Heavily* simplified version of the generic options menu, cattered only towards erasing profiles. +void M_DrawProfileErase(void) +{ + INT32 x = currentMenu->x - menutransition.tics*48, y = currentMenu->y-SMALLLINEHEIGHT, i, cursory = 0; + UINT8 np = PR_GetNumProfiles(); + + M_DrawOptionsCogs(); + M_DrawMenuTooltips(); + M_DrawOptionsMovingButton(); + + for (i = 1; i < np; i++) + { + + profile_t *pr = PR_GetProfile(i); + + if (i == optionsmenu.eraseprofilen) + { + cursory = y; + V_DrawScaledPatch(x - 24, cursory, 0, W_CachePatchName("M_CURSOR", PU_CACHE)); + } + + V_DrawString(x, y, + (i == optionsmenu.eraseprofilen ? highlightflags : 0)|V_ALLOWLOWERCASE, + va("%sPRF%03d - %s (%s)", + (cv_currprofile.value == i) ? "[In use] " : "", + i, pr->profilename, pr->playername)); + y += SMALLLINEHEIGHT; + } +} + +// Draws profile selection +void M_DrawProfileSelect(void) +{ + INT32 i; + const INT32 maxp = PR_GetNumProfiles(); + INT32 x = 160 - optionsmenu.profilen*(128 + 128/8) + optionsmenu.offset; + INT32 y = 35 + menutransition.tics*32; + + M_DrawOptionsCogs(); + M_DrawMenuTooltips(); + + // This shouldn't be drawn when a profile is selected as optx/opty are used to move the card. + if (optionsmenu.profile == NULL && menutransition.tics) + M_DrawOptionsMovingButton(); + + for (i=0; i < MAXPROFILES+1; i++) // +1 because the default profile does not count + { + profile_t *p = PR_GetProfile(i); + + // don't draw the card in this specific scenario + if (!(menutransition.tics && optionsmenu.profile != NULL && optionsmenu.profilen == i)) + M_DrawProfileCard(x, y, i > maxp, p); + + x += 128 + 128/8; + } + + // needs to be drawn since it happens on the transition + if (optionsmenu.profile != NULL) + M_DrawProfileCard(optionsmenu.optx, optionsmenu.opty, false, optionsmenu.profile); + + +} + +// Profile edition menu +void M_DrawEditProfile(void) +{ + + INT32 y = 34; + INT32 x = 145; + INT32 i; + + M_DrawOptionsCogs(); + + // Tooltip + // The text is slightly shifted hence why we don't just use M_DrawMenuTooltips() + V_DrawFixedPatch(0, 0, FRACUNIT, 0, W_CachePatchName("MENUHINT", PU_CACHE), NULL); + if (currentMenu->menuitems[itemOn].tooltip != NULL) + { + V_DrawCenteredThinString(224, 12, V_ALLOWLOWERCASE|V_6WIDTHSPACE, currentMenu->menuitems[itemOn].tooltip); + } + + // Draw the menu options... + for (i = 0; i < currentMenu->numitems; i++) + { + + UINT8 *colormap = NULL; + INT32 tflag = (currentMenu->menuitems[i].status & IT_TRANSTEXT) ? V_TRANSLUCENT : 0; + + if (i == itemOn) + colormap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_PLAGUE, GTC_CACHE); + + // Background + V_DrawFill(0, y, 400 - (menutransition.tics*64), 24, itemOn == i ? 169 : 30); // 169 is the plague colourization + // Text + V_DrawGamemodeString(x + (menutransition.tics*32), y - 6, V_ALLOWLOWERCASE|tflag, colormap, currentMenu->menuitems[i].text); + + // Cvar specific handling + /*switch (currentMenu->menuitems[i].status & IT_TYPE) + { + case IT_CVAR: + { + consvar_t *cv = currentMenu->menuitems[i].itemaction.cvar; + switch (currentMenu->menuitems[i].status & IT_CVARTYPE) + { + case IT_CV_STRING: + V_DrawFill(0, y+24, 400 - (menutransition.tics*64), 16, itemOn == i ? 169 : 30); // 169 is the plague colourization + V_DrawString(x + 8, y + 29, V_ALLOWLOWERCASE, cv->string); + if (skullAnimCounter < 4 && i == itemOn) + V_DrawCharacter(x + 8 + V_StringWidth(cv->string, 0), y + 29, '_' | 0x80, false); + y += 16; + } + } + }*/ + + y += 34; + } + + // Finally, draw the card ontop + if (optionsmenu.profile != NULL) + { + M_DrawProfileCard(optionsmenu.optx, optionsmenu.opty, false, optionsmenu.profile); + } +} + +// Controller offsets to center on each button. +INT16 controlleroffsets[][2] = { + {0, 0}, // gc_none + {70, 112}, // gc_up + {70, 112}, // gc_down + {70, 112}, // gc_left + {70, 112}, // gc_right + {208, 200}, // gc_a + {237, 181}, // gc_b + {267, 166}, // gc_c + {191, 164}, // gc_x + {215, 149}, // gc_y + {242, 137}, // gc_z + {55, 102}, // gc_l + {253, 102}, // gc_r + {149, 187}, // gc_start +}; + +// Controller patches for button presses. +// {patch if not pressed, patch if pressed} +// if NULL, draws nothing. +// reminder that lumpnames can only be 8 chars at most. (+1 for \0) + +char controllerpresspatch[9][2][9] = { + {"", "BTP_A"}, // MBT_A + {"", "BTP_B"}, // MBT_B + {"", "BTP_C"}, // MBT_C + {"", "BTP_X"}, // MBT_X + {"", "BTP_Y"}, // MBT_Y + {"", "BTP_Z"}, // MBT_Z + {"BTNP_L", "BTP_L"},// MBT_L + {"BTNP_R", "BTP_R"},// MBT_R + {"", "BTP_ST"} // MBT_START +}; + + +// the control stuff. +// Dear god. +void M_DrawProfileControls(void) +{ + const UINT8 spacing = 34; + INT32 y = 16 - (optionsmenu.controlscroll*spacing); + INT32 x = 8; + INT32 i, j, k; + const UINT8 pid = 0; + + M_DrawOptionsCogs(); + + V_DrawScaledPatch(BASEVIDWIDTH*2/3 - optionsmenu.contx, BASEVIDHEIGHT/2 -optionsmenu.conty, 0, W_CachePatchName("PR_CONT", PU_CACHE)); + + // Draw button presses... + // @TODO: Dpad when we get the sprites for it. + + for (i = 0; i < 9; i++) + { + INT32 bt = 1<menuitems[itemOn].tooltip != NULL) + { + V_DrawCenteredThinString(229, 12, V_ALLOWLOWERCASE|V_6WIDTHSPACE, currentMenu->menuitems[itemOn].tooltip); + } + + V_DrawFill(0, 0, 138, 200, 31); // Black border + + // Draw the menu options... + for (i = 0; i < currentMenu->numitems; i++) + { + char buf[256]; + INT32 keys[MAXINPUTMAPPING]; + + // cursor + if (i == itemOn) + { + for (j=0; j < 24; j++) + V_DrawFill(0, (y)+j, 128+j, 1, 73); + } + + switch (currentMenu->menuitems[i].status & IT_DISPLAY) + { + case IT_HEADERTEXT: + V_DrawFill(0, y+17, 124, 1, 0); // underline + V_DrawString(x, y+8, 0, currentMenu->menuitems[i].text); + y += spacing; + break; + + case IT_STRING: + V_DrawString(x, y+1, (i == itemOn ? highlightflags : 0), currentMenu->menuitems[i].text); + y += spacing; + break; + + case IT_STRING2: + { + boolean drawnpatch = false; + + if (currentMenu->menuitems[i].patch) + { + V_DrawScaledPatch(x+12, y+12, 0, W_CachePatchName(currentMenu->menuitems[i].patch, PU_CACHE)); + drawnpatch = true; + } + else + V_DrawString(x, y+1, (i == itemOn ? highlightflags : 0), currentMenu->menuitems[i].text); + + if (currentMenu->menuitems[i].status & IT_CVAR) // not the proper way to check but this menu only has normal onoff cvars. + { + INT32 w; + consvar_t *cv = currentMenu->menuitems[i].itemaction.cvar; + + w = V_StringWidth(cv->string, 0); + V_DrawString(x + 12, y + 12, ((cv->flags & CV_CHEAT) && !CV_IsSetToDefault(cv) ? warningflags : highlightflags), cv->string); + if (i == itemOn) + { + V_DrawCharacter(x - (skullAnimCounter/5), y+12, '\x1C' | highlightflags, false); // left arrow + V_DrawCharacter(x + 12 + w + 2 + (skullAnimCounter/5) , y+12, '\x1D' | highlightflags, false); // right arrow + } + } + else if (currentMenu->menuitems[i].status & IT_CONTROL) + { + UINT32 vflags = V_6WIDTHSPACE; + INT32 gc = currentMenu->menuitems[i].mvar1; + + UINT8 available = 0, set = 0; + + // Get userbound controls... + for (k = 0; k < MAXINPUTMAPPING; k++) + { + keys[k] = optionsmenu.tempcontrols[gc][k]; + if (keys[k] == KEY_NULL) + continue; + set++; + if (!G_KeyIsAvailable(keys[k], cv_usejoystick[0].value)) + continue; + available++; + }; + + buf[0] = '\0'; + + + // Cool as is this, this doesn't actually help show accurate info because of how some players would set inputs with keyboard and controller at once in a volatile way... + // @TODO: Address that mess, somehow? + + // Can't reach any of them? + /*if (available == 0) + { + if (((3*optionsmenu.ticker)/(2*TICRATE)) & 1) // 1.5 seconds + { + vflags |= V_ORANGEMAP; +#ifdef SHOWCONTROLDEFAULT + if (G_KeyBindIsNecessary(gc)) + { + // Get the defaults for essential keys. + // Went through all the trouble of making this look cool, + // then realised defaulting should only apply to menus. + // Too much opportunity for confusion if kept. + for (k = 0; k < MAXINPUTMAPPING; k++) + { + keys[k] = gamecontroldefault[gc][k]; + if (keys[k] == KEY_NULL) + continue; + available++; + } + set = available; + } + else if (set) +#else + if (!set) + { + if (!G_KeyBindIsNecessary(gc)) + vflags = V_REDMAP|V_6WIDTHSPACE; + } + else +#endif + { + strcpy(buf, "CURRENTLY UNAVAILABLE"); + } + } + else + { + vflags |= V_REDMAP; + } + }*/ + + if (buf[0]) + ; + else if (!set) + strcpy(buf, "\x85NOT BOUND"); + else + { + for (k = 0; k < MAXINPUTMAPPING; k++) + { + if (keys[k] == KEY_NULL) + continue; + + if (k > 0) + strcat(buf," / "); + + if (k == 2 && drawnpatch) // hacky... + strcat(buf, "\n"); + + strcat(buf, G_KeynumToString (keys[k])); + } + } + + // don't shift the text if we didn't draw a patch. + V_DrawThinString(x + (drawnpatch ? 32 : 0), y + (drawnpatch ? 2 : 12), vflags, buf); + + // controller dest coords: + if (itemOn == i && gc > 0 && gc <= gc_start) + { + optionsmenu.tcontx = controlleroffsets[gc][0]; + optionsmenu.tconty = controlleroffsets[gc][1]; + } + } + + + y += spacing; + break; + } + } + } + + // Overlay for control binding + if (optionsmenu.bindcontrol) + { + INT16 reversetimer = TICRATE*5 - optionsmenu.bindtimer; + INT32 fade = reversetimer; + INT32 ypos; + + if (fade > 9) + fade = 9; + + ypos = (BASEVIDHEIGHT/2) - 4 +16*(9 - fade); + V_DrawFadeScreen(31, fade); + + M_DrawTextBox((BASEVIDWIDTH/2) - (120), ypos - 12, 30, 4); + + V_DrawCenteredString(BASEVIDWIDTH/2, ypos, 0, va("Press key #%d for control", optionsmenu.bindcontrol)); + V_DrawCenteredString(BASEVIDWIDTH/2, ypos +10, 0, va("\"%s\"", currentMenu->menuitems[itemOn].text)); + V_DrawCenteredString(BASEVIDWIDTH/2, ypos +20, highlightflags, va("(WAIT %d SECONDS TO SKIP)", optionsmenu.bindtimer/TICRATE)); + } +} + +// Draw the video modes list, a-la-Quake +void M_DrawVideoModes(void) +{ + INT32 i, j, row, col; + + M_DrawOptionsCogs(); + M_DrawMenuTooltips(); + M_DrawOptionsMovingButton(); + + V_DrawCenteredString(BASEVIDWIDTH/2 + menutransition.tics*64, currentMenu->y, + highlightflags, "Choose mode, reselect to change default"); + + row = 41 + menutransition.tics*64; + col = currentMenu->y + 14; + for (i = 0; i < optionsmenu.vidm_nummodes; i++) + { + if (i == optionsmenu.vidm_selected) + V_DrawString(row, col, highlightflags, optionsmenu.modedescs[i].desc); + // Show multiples of 320x200 as green. + else + V_DrawString(row, col, (optionsmenu.modedescs[i].goodratio) ? recommendedflags : 0, optionsmenu.modedescs[i].desc); + + col += 8; + if ((i % optionsmenu.vidm_column_size) == (optionsmenu.vidm_column_size-1)) + { + row += 7*13; + col = currentMenu->y + 14; + } + } + + if (optionsmenu.vidm_testingmode > 0) + { + INT32 testtime = (optionsmenu.vidm_testingmode/TICRATE) + 1; + + M_CentreText(menutransition.tics*64, currentMenu->y + 75, + va("Previewing mode %c%dx%d", + (SCR_IsAspectCorrect(vid.width, vid.height)) ? 0x83 : 0x80, + vid.width, vid.height)); + M_CentreText(menutransition.tics*64, currentMenu->y + 75+8, + "Press ENTER again to keep this mode"); + M_CentreText(menutransition.tics*64, currentMenu->y + 75+16, + va("Wait %d second%s", testtime, (testtime > 1) ? "s" : "")); + M_CentreText(menutransition.tics*64, currentMenu->y + 75+24, + "or press ESC to return"); + + } + else + { + M_CentreText(menutransition.tics*64, currentMenu->y + 75, + va("Current mode is %c%dx%d", + (SCR_IsAspectCorrect(vid.width, vid.height)) ? 0x83 : 0x80, + vid.width, vid.height)); + M_CentreText(menutransition.tics*64, currentMenu->y + 75+8, + va("Default mode is %c%dx%d", + (SCR_IsAspectCorrect(cv_scr_width.value, cv_scr_height.value)) ? 0x83 : 0x80, + cv_scr_width.value, cv_scr_height.value)); + + V_DrawCenteredString(BASEVIDWIDTH/2 + menutransition.tics*64, currentMenu->y + 75+16, + recommendedflags, "Marked modes are recommended."); + V_DrawCenteredString(BASEVIDWIDTH/2 + menutransition.tics*64, currentMenu->y + 75+24, + highlightflags, "Other modes may have visual errors."); + V_DrawCenteredString(BASEVIDWIDTH/2 + menutransition.tics*64, currentMenu->y + 75+32, + highlightflags, "Larger modes may have performance issues."); + } + + // Draw the cursor for the VidMode menu + i = 41 - 10 + ((optionsmenu.vidm_selected / optionsmenu.vidm_column_size)*7*13) + menutransition.tics*64; + j = currentMenu->y + 14 + ((optionsmenu.vidm_selected % optionsmenu.vidm_column_size)*8); + + V_DrawScaledPatch(i - 8, j, 0, + W_CachePatchName("M_CURSOR", PU_CACHE)); +} + +// Gameplay Item Tggles: +tic_t shitsfree = 0; + +void M_DrawItemToggles(void) +{ + const INT32 edges = 8; + const INT32 height = 4; + const INT32 spacing = 35; + const INT32 column = itemOn/height; + //const INT32 row = itemOn%height; + INT32 leftdraw, rightdraw, totaldraw; + INT32 x = currentMenu->x + menutransition.tics*64, y = currentMenu->y; + INT32 onx = 0, ony = 0; + consvar_t *cv; + INT32 i, translucent, drawnum; + + M_DrawOptionsCogs(); + M_DrawMenuTooltips(); + M_DrawOptionsMovingButton(); + + // Find the available space around column + leftdraw = rightdraw = column; + totaldraw = 0; + for (i = 0; (totaldraw < edges*2 && i < edges*4); i++) + { + if (rightdraw+1 < (currentMenu->numitems/height)+1) + { + rightdraw++; + totaldraw++; + } + if (leftdraw-1 >= 0) + { + leftdraw--; + totaldraw++; + } + } + + for (i = leftdraw; i <= rightdraw; i++) + { + INT32 j; + + for (j = 0; j < height; j++) + { + const INT32 thisitem = (i*height)+j; + + if (thisitem >= currentMenu->numitems) + break; + + if (thisitem == itemOn) + { + onx = x; + ony = y; + y += spacing; + continue; + } + + if (currentMenu->menuitems[thisitem].mvar1 == 0) + { + V_DrawScaledPatch(x, y, 0, W_CachePatchName("K_ISBG", PU_CACHE)); + V_DrawScaledPatch(x, y, 0, W_CachePatchName("K_ISTOGL", PU_CACHE)); + y += spacing; + continue; + } + + if (currentMenu->menuitems[thisitem].mvar1 == 255) + { + V_DrawScaledPatch(x, y, 0, W_CachePatchName("K_ISBGD", PU_CACHE)); + y += spacing; + continue; + } + + cv = KartItemCVars[currentMenu->menuitems[thisitem].mvar1-1]; + translucent = (cv->value ? 0 : V_TRANSLUCENT); + + switch (currentMenu->menuitems[thisitem].mvar1) + { + case KRITEM_DUALSNEAKER: + case KRITEM_DUALJAWZ: + drawnum = 2; + break; + case KRITEM_TRIPLESNEAKER: + case KRITEM_TRIPLEBANANA: + case KRITEM_TRIPLEORBINAUT: + drawnum = 3; + break; + case KRITEM_QUADORBINAUT: + drawnum = 4; + break; + case KRITEM_TENFOLDBANANA: + drawnum = 10; + break; + default: + drawnum = 0; + break; + } + + if (cv->value) + V_DrawScaledPatch(x, y, 0, W_CachePatchName("K_ISBG", PU_CACHE)); + else + V_DrawScaledPatch(x, y, 0, W_CachePatchName("K_ISBGD", PU_CACHE)); + + if (drawnum != 0) + { + V_DrawScaledPatch(x, y, 0, W_CachePatchName("K_ISMUL", PU_CACHE)); + V_DrawScaledPatch(x, y, translucent, W_CachePatchName(K_GetItemPatch(currentMenu->menuitems[thisitem].mvar1, true), PU_CACHE)); + V_DrawString(x+24, y+31, V_ALLOWLOWERCASE|translucent, va("x%d", drawnum)); + } + else + V_DrawScaledPatch(x, y, translucent, W_CachePatchName(K_GetItemPatch(currentMenu->menuitems[thisitem].mvar1, true), PU_CACHE)); + + y += spacing; + } + + x += spacing; + y = currentMenu->y; + } + + { + if (currentMenu->menuitems[itemOn].mvar1 == 0) + { + V_DrawScaledPatch(onx-1, ony-2, 0, W_CachePatchName("K_ITBG", PU_CACHE)); + V_DrawScaledPatch(onx-1, ony-2, 0, W_CachePatchName("K_ITTOGL", PU_CACHE)); + } + else if (currentMenu->menuitems[itemOn].mvar1 == 255) + { + V_DrawScaledPatch(onx-1, ony-2, 0, W_CachePatchName("K_ITBGD", PU_CACHE)); + if (shitsfree) + { + INT32 trans = V_TRANSLUCENT; + if (shitsfree-1 > TICRATE-5) + trans = ((10-TICRATE)+shitsfree-1)<menuitems[itemOn].mvar1-1]; + translucent = (cv->value ? 0 : V_TRANSLUCENT); + + switch (currentMenu->menuitems[itemOn].mvar1) + { + case KRITEM_DUALSNEAKER: + case KRITEM_DUALJAWZ: + drawnum = 2; + break; + case KRITEM_TRIPLESNEAKER: + case KRITEM_TRIPLEBANANA: + drawnum = 3; + break; + case KRITEM_TENFOLDBANANA: + drawnum = 10; + break; + default: + drawnum = 0; + break; + } + + if (cv->value) + V_DrawScaledPatch(onx-1, ony-2, 0, W_CachePatchName("K_ITBG", PU_CACHE)); + else + V_DrawScaledPatch(onx-1, ony-2, 0, W_CachePatchName("K_ITBGD", PU_CACHE)); + + if (drawnum != 0) + { + V_DrawScaledPatch(onx-1, ony-2, 0, W_CachePatchName("K_ITMUL", PU_CACHE)); + V_DrawScaledPatch(onx-1, ony-2, translucent, W_CachePatchName(K_GetItemPatch(currentMenu->menuitems[itemOn].mvar1, false), PU_CACHE)); + V_DrawScaledPatch(onx+27, ony+39, translucent, W_CachePatchName("K_ITX", PU_CACHE)); + V_DrawKartString(onx+37, ony+34, translucent, va("%d", drawnum)); + } + else + V_DrawScaledPatch(onx-1, ony-2, translucent, W_CachePatchName(K_GetItemPatch(currentMenu->menuitems[itemOn].mvar1, false), PU_CACHE)); + } + } +} + + +// EXTRAS: +// Copypasted from options but separate either way in case we want it to look more unique later down the line. +void M_DrawExtrasMovingButton(void) +{ + patch_t *butt = W_CachePatchName("OPT_BUTT", PU_CACHE); + UINT8 *c = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_PLAGUE, GTC_CACHE); + + V_DrawFixedPatch((extrasmenu.extx)*FRACUNIT, (extrasmenu.exty)*FRACUNIT, FRACUNIT, 0, butt, c); + V_DrawCenteredGamemodeString((extrasmenu.extx)-3, (extrasmenu.exty) - 16, V_ALLOWLOWERCASE, c, EXTRAS_MainDef.menuitems[EXTRAS_MainDef.lastOn].text); +} + +void M_DrawExtras(void) +{ + UINT8 i; + INT32 x = 140 - (48*itemOn) + extrasmenu.offset; + INT32 y = 70 + extrasmenu.offset; + patch_t *buttback = W_CachePatchName("OPT_BUTT", PU_CACHE); + patch_t *bg = W_CachePatchName("M_XTRABG", PU_CACHE); + + UINT8 *c = NULL; + + V_DrawFixedPatch(0, 0, FRACUNIT, 0, bg, NULL); + + for (i=0; i < currentMenu->numitems; i++) + { + INT32 py = y - (itemOn*48); + INT32 px = x - menutransition.tics*64; + INT32 tflag = 0; + + if (i == itemOn) + c = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_PLAGUE, GTC_CACHE); + else + c = R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_BLACK, GTC_CACHE); + + if (currentMenu->menuitems[i].status & IT_TRANSTEXT) + tflag = V_TRANSLUCENT; + + if (!(menutransition.tics && i == itemOn)) + { + V_DrawFixedPatch(px*FRACUNIT, py*FRACUNIT, FRACUNIT, 0, buttback, c); + V_DrawCenteredGamemodeString(px-3, py - 16, V_ALLOWLOWERCASE|tflag, (i == itemOn ? c : NULL), currentMenu->menuitems[i].text); + } + + y += 48; + x += 48; + } + + M_DrawMenuTooltips(); + + if (menutransition.tics) + M_DrawExtrasMovingButton(); + +} + +// +// INGAME / PAUSE MENUS +// + +// PAUSE + +// PAUSE MAIN MENU +void M_DrawPause(void) +{ + + SINT8 i; + SINT8 itemsdrawn = 0; + SINT8 countdown = 0; + INT16 ypos = -50; // Draw 3 items from selected item (y=100 - 3 items spaced by 50 px each... you get the idea.) + INT16 dypos; + + INT16 offset = menutransition.tics ? floor(pow(2, (double)menutransition.tics)) : pausemenu.openoffset; + INT16 arrxpos = 150 + 2*offset; // To draw the background arrow. + + INT16 j = 0; + char word1[MAXSTRINGLENGTH]; + INT16 word1len = 0; + char word2[MAXSTRINGLENGTH]; + INT16 word2len = 0; + boolean sok = false; + + patch_t *pausebg = W_CachePatchName("M_STRIPU", PU_CACHE); + patch_t *vertbg = W_CachePatchName("M_STRIPV", PU_CACHE); + patch_t *pausetext = W_CachePatchName("M_PAUSET", PU_CACHE); + + patch_t *arrstart = W_CachePatchName("M_PTIP", PU_CACHE); + patch_t *arrfill = W_CachePatchName("M_PFILL", PU_CACHE); + + //V_DrawFadeScreen(0xFF00, 16); + + // "PAUSED" + V_DrawFixedPatch(-offset*FRACUNIT, 0, FRACUNIT, V_ADD, pausebg, NULL); + V_DrawFixedPatch(-offset*FRACUNIT, 0, FRACUNIT, 0, pausetext, NULL); + + // Vertical Strip: + V_DrawFixedPatch((230 + offset)<numitems-1; + + while (currentMenu->menuitems[i].status == IT_DISABLED) + { + i--; + + if (i < 0) + i = currentMenu->numitems-1; + } + } + + // Aaaaand now we can start drawing! + // Reminder that we set the patches of the options to the description since we're not using that. I'm smart, I know... + + // Draw the background arrow + V_DrawFixedPatch(arrxpos<width) < BASEVIDWIDTH) + { + V_DrawFixedPatch(arrxpos<width; + } + + while (itemsdrawn < 7) + { + + switch (currentMenu->menuitems[i].status & IT_DISPLAY) + { + case IT_STRING: + { + + char iconame[9]; // 8 chars + \0 + patch_t *pp; + + if (i == itemOn) + { + strcpy(iconame, currentMenu->menuitems[i].tooltip); + iconame[7] = '2'; // Yes this is a stupid hack. Replace the last character with a 2 when we're selecting this graphic. + + pp = W_CachePatchName(iconame, PU_CACHE); + } + else + pp = W_CachePatchName(currentMenu->menuitems[i].tooltip, PU_CACHE); + + // 294 - 261 = 33 + // We need to move 33 px in 50 tics which means we move 33/50 = 0.66 px every tic = 2/3 of the offset. + // trust me i graduated highschool!!!! + + // Multiply by -1 or 1 depending on whether we're below or above 100 px. + // This double ternary is awful, yes. + + dypos = ypos + pausemenu.offset; + V_DrawFixedPatch( ((i == itemOn ? (294 - pausemenu.offset*2/3 * (dypos > 100 ? 1 : -1)) : 261) + offset) << FRACBITS, (dypos)*FRACUNIT, FRACUNIT, 0, pp, NULL); + + ypos += 50; + itemsdrawn++; // We drew that! + break; + } + } + + + i++; // Regardless of whether we drew or not, go to the next item in the menu. + if (i > currentMenu->numitems) + { + i = 0; + while (!(currentMenu->menuitems[i].status & IT_DISPLAY)) + i++; + + } + } + + // Draw the string! + // ...but first get what we need to get. + while (currentMenu->menuitems[itemOn].text[j] && j < MAXSTRINGLENGTH) + { + char c = currentMenu->menuitems[itemOn].text[j]; + + if (c == ' ' && !sok) + { + sok = true; + j++; + continue; // We don't care about this :moyai: + } + + if (sok) + { + word2[word2len] = c; + word2len++; + } + else + { + word1[word1len] = c; + word1len++; + } + + j++; + } + + word1[word1len] = '\0'; + word2[word2len] = '\0'; + + // If there's no 2nd word, take this opportunity to center this line of text. + if (word1len) + V_DrawCenteredLSTitleHighString(220 + offset*2, 75 + (!word2len ? 10 : 0), 0, word1); + + if (word2len) + V_DrawCenteredLSTitleLowString(220 + offset*2, 103, 0, word2); +} + +tic_t playback_last_menu_interaction_leveltime = 0; + +void M_DrawPlaybackMenu(void) +{ + INT16 i; + patch_t *icon = NULL; + UINT8 *activemap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_GOLD, GTC_MENUCACHE); + UINT32 transmap = max(0, (INT32)(leveltime - playback_last_menu_interaction_leveltime - 4*TICRATE)) / 5; + transmap = min(8, transmap) << V_ALPHASHIFT; + + if (leveltime - playback_last_menu_interaction_leveltime >= 6*TICRATE) + playback_last_menu_interaction_leveltime = leveltime - 6*TICRATE; + + // Toggle items + if (paused && !demo.rewinding) + { + PAUSE_PlaybackMenu[playback_pause].status = PAUSE_PlaybackMenu[playback_fastforward].status = PAUSE_PlaybackMenu[playback_rewind].status = IT_DISABLED; + PAUSE_PlaybackMenu[playback_resume].status = PAUSE_PlaybackMenu[playback_advanceframe].status = PAUSE_PlaybackMenu[playback_backframe].status = IT_CALL|IT_STRING; + + if (itemOn >= playback_rewind && itemOn <= playback_fastforward) + itemOn += playback_backframe - playback_rewind; + } + else + { + PAUSE_PlaybackMenu[playback_pause].status = PAUSE_PlaybackMenu[playback_fastforward].status = PAUSE_PlaybackMenu[playback_rewind].status = IT_CALL|IT_STRING; + PAUSE_PlaybackMenu[playback_resume].status = PAUSE_PlaybackMenu[playback_advanceframe].status = PAUSE_PlaybackMenu[playback_backframe].status = IT_DISABLED; + + if (itemOn >= playback_backframe && itemOn <= playback_advanceframe) + itemOn -= playback_backframe - playback_rewind; + } + + if (modeattacking) + { + for (i = playback_viewcount; i <= playback_view4; i++) + PAUSE_PlaybackMenu[i].status = IT_DISABLED; + + //PAUSE_PlaybackMenu[playback_moreoptions].mvar1 = 72; + //PAUSE_PlaybackMenu[playback_quit].mvar1 = 88; + PAUSE_PlaybackMenu[playback_quit].mvar1 = 72; + + //currentMenu->x = BASEVIDWIDTH/2 - 52; + currentMenu->x = BASEVIDWIDTH/2 - 44; + } + else + { + PAUSE_PlaybackMenu[playback_viewcount].status = IT_ARROWS|IT_STRING; + + for (i = 0; i <= splitscreen; i++) + PAUSE_PlaybackMenu[playback_view1+i].status = IT_ARROWS|IT_STRING; + for (i = splitscreen+1; i < 4; i++) + PAUSE_PlaybackMenu[playback_view1+i].status = IT_DISABLED; + + //PAUSE_PlaybackMenu[playback_moreoptions].mvar1 = 156; + //PAUSE_PlaybackMenu[playback_quit].mvar1 = 172; + PAUSE_PlaybackMenu[playback_quit].mvar1 = 156; + + //currentMenu->x = BASEVIDWIDTH/2 - 94; + currentMenu->x = BASEVIDWIDTH/2 - 88; + } + + // wip + //M_DrawTextBox(currentMenu->x-68, currentMenu->y-7, 15, 15); + //M_DrawCenteredMenu(); + + for (i = 0; i < currentMenu->numitems; i++) + { + UINT8 *inactivemap = NULL; + + if (i >= playback_view1 && i <= playback_view4) + { + if (modeattacking) continue; + + if (splitscreen >= i - playback_view1) + { + INT32 ply = displayplayers[i - playback_view1]; + + icon = faceprefix[players[ply].skin][FACE_RANK]; + if (i != itemOn) + inactivemap = R_GetTranslationColormap(players[ply].skin, players[ply].skincolor, GTC_MENUCACHE); + } + else if (currentMenu->menuitems[i].patch && W_CheckNumForName(currentMenu->menuitems[i].patch) != LUMPERROR) + icon = W_CachePatchName(currentMenu->menuitems[i].patch, PU_CACHE); + } + else if (currentMenu->menuitems[i].status == IT_DISABLED) + continue; + else if (currentMenu->menuitems[i].patch && W_CheckNumForName(currentMenu->menuitems[i].patch) != LUMPERROR) + icon = W_CachePatchName(currentMenu->menuitems[i].patch, PU_CACHE); + + if ((i == playback_fastforward && cv_playbackspeed.value > 1) || (i == playback_rewind && demo.rewinding)) + V_DrawMappedPatch(currentMenu->x + currentMenu->menuitems[i].mvar1, currentMenu->y, V_SNAPTOTOP, icon, R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_JAWZ, GTC_MENUCACHE)); + else + V_DrawMappedPatch(currentMenu->x + currentMenu->menuitems[i].mvar1, currentMenu->y, V_SNAPTOTOP, icon, (i == itemOn) ? activemap : inactivemap); + + if (i == itemOn) + { + V_DrawCharacter(currentMenu->x + currentMenu->menuitems[i].mvar1 + 4, currentMenu->y + 14, + '\x1A' | V_SNAPTOTOP|highlightflags, false); + + V_DrawCenteredString(BASEVIDWIDTH/2, currentMenu->y + 18, V_SNAPTOTOP|V_ALLOWLOWERCASE, currentMenu->menuitems[i].text); + + if ((currentMenu->menuitems[i].status & IT_TYPE) == IT_ARROWS) + { + char *str; + + if (!(i == playback_viewcount && splitscreen == 3)) + V_DrawCharacter(BASEVIDWIDTH/2 - 4, currentMenu->y + 28 - (skullAnimCounter/5), + '\x1A' | V_SNAPTOTOP|highlightflags, false); // up arrow + + if (!(i == playback_viewcount && splitscreen == 0)) + V_DrawCharacter(BASEVIDWIDTH/2 - 4, currentMenu->y + 48 + (skullAnimCounter/5), + '\x1B' | V_SNAPTOTOP|highlightflags, false); // down arrow + + switch (i) + { + case playback_viewcount: + str = va("%d", splitscreen+1); + break; + + case playback_view1: + case playback_view2: + case playback_view3: + case playback_view4: + str = player_names[displayplayers[i - playback_view1]]; // 0 to 3 + break; + + default: // shouldn't ever be reached but whatever + continue; + } + + V_DrawCenteredString(BASEVIDWIDTH/2, currentMenu->y + 38, V_SNAPTOTOP|V_ALLOWLOWERCASE|highlightflags, str); + } + } + } +} + + +// replay hut... +// ...dear lord this is messy, but Ima be real I ain't fixing this. + +#define SCALEDVIEWWIDTH (vid.width/vid.dupx) +#define SCALEDVIEWHEIGHT (vid.height/vid.dupy) +void M_DrawReplayHutReplayInfo(void) +{ + lumpnum_t lumpnum; + patch_t *patch; + UINT8 *colormap; + INT32 x, y, w, h; + + switch (extrasmenu.demolist[dir_on[menudepthleft]].type) + { + case MD_NOTLOADED: + V_DrawCenteredString(160, 40, V_SNAPTOTOP, "Loading replay information..."); + break; + + case MD_INVALID: + V_DrawCenteredString(160, 40, V_SNAPTOTOP|warningflags, "This replay cannot be played."); + break; + + case MD_SUBDIR: + break; // Can't think of anything to draw here right now + + case MD_OUTDATED: + V_DrawThinString(17, 64, V_SNAPTOTOP|V_ALLOWLOWERCASE|V_TRANSLUCENT|highlightflags, "Recorded on an outdated version."); + /* FALLTHRU */ + default: + // Draw level stuff + x = 15; y = 15; + + // A 160x100 image of the level as entry MAPxxP + //CONS_Printf("%d %s\n", extrasmenu.demolist[dir_on[menudepthleft]].map, G_BuildMapName(extrasmenu.demolist[dir_on[menudepthleft]].map)); + lumpnum = W_CheckNumForName(va("%sP", G_BuildMapName(extrasmenu.demolist[dir_on[menudepthleft]].map))); + if (lumpnum != LUMPERROR) + patch = W_CachePatchNum(lumpnum, PU_CACHE); + else + patch = W_CachePatchName("M_NOLVL", PU_CACHE); + + if (!(extrasmenu.demolist[dir_on[menudepthleft]].kartspeed & DF_ENCORE)) + V_DrawSmallScaledPatch(x, y, V_SNAPTOTOP, patch); + else + { + w = SHORT(patch->width); + h = SHORT(patch->height); + V_DrawSmallScaledPatch(x+(w>>1), y, V_SNAPTOTOP|V_FLIP, patch); + + { + static angle_t rubyfloattime = 0; + const fixed_t rubyheight = FINESINE(rubyfloattime>>ANGLETOFINESHIFT); + V_DrawFixedPatch((x+(w>>2))<>2))<width), y+20, V_SNAPTOTOP, patch, colormap); + + break; + } +} + +void M_DrawReplayHut(void) +{ + INT32 x, y, cursory = 0; + INT16 i; + INT16 replaylistitem = currentMenu->numitems-2; + boolean processed_one_this_frame = false; + + static UINT16 replayhutmenuy = 0; + + M_DrawEggaChannel(); + + // Draw menu choices + x = currentMenu->x; + y = currentMenu->y; + + if (itemOn > replaylistitem) + { + itemOn = replaylistitem; + dir_on[menudepthleft] = sizedirmenu-1; + extrasmenu.replayScrollTitle = 0; extrasmenu.replayScrollDelay = TICRATE; extrasmenu.replayScrollDir = 1; + } + else if (itemOn < replaylistitem) + { + dir_on[menudepthleft] = 0; + extrasmenu.replayScrollTitle = 0; extrasmenu.replayScrollDelay = TICRATE; extrasmenu.replayScrollDir = 1; + } + + if (itemOn == replaylistitem) + { + INT32 maxy; + // Scroll menu items if needed + cursory = y + currentMenu->menuitems[replaylistitem].mvar1 + dir_on[menudepthleft]*10; + maxy = y + currentMenu->menuitems[replaylistitem].mvar1 + sizedirmenu*10; + + if (cursory > maxy - 20) + cursory = maxy - 20; + + if (cursory - replayhutmenuy > SCALEDVIEWHEIGHT-50) + replayhutmenuy += (cursory-SCALEDVIEWHEIGHT-replayhutmenuy + 51)/2; + else if (cursory - replayhutmenuy < 110) + replayhutmenuy += (max(0, cursory-110)-replayhutmenuy - 1)/2; + } + else + replayhutmenuy /= 2; + + y -= replayhutmenuy; + + // Draw static menu items + for (i = 0; i < replaylistitem; i++) + { + INT32 localy = y + currentMenu->menuitems[i].mvar1; + + if (localy < 65) + continue; + + if (i == itemOn) + cursory = localy; + + if ((currentMenu->menuitems[i].status & IT_DISPLAY)==IT_STRING) + V_DrawString(x, localy, V_SNAPTOTOP|V_SNAPTOLEFT, currentMenu->menuitems[i].text); + else + V_DrawString(x, localy, V_SNAPTOTOP|V_SNAPTOLEFT|highlightflags, currentMenu->menuitems[i].text); + } + + y += currentMenu->menuitems[replaylistitem].mvar1; + + for (i = 0; i < (INT16)sizedirmenu; i++) + { + INT32 localy = y+i*10; + INT32 localx = x; + + if (localy < 65) + continue; + if (localy >= SCALEDVIEWHEIGHT) + break; + + if (extrasmenu.demolist[i].type == MD_NOTLOADED && !processed_one_this_frame) + { + processed_one_this_frame = true; + G_LoadDemoInfo(&extrasmenu.demolist[i]); + } + + if (extrasmenu.demolist[i].type == MD_SUBDIR) + { + localx += 8; + V_DrawScaledPatch(x - 4, localy, V_SNAPTOTOP|V_SNAPTOLEFT, W_CachePatchName(dirmenu[i][DIR_TYPE] == EXT_UP ? "M_RBACK" : "M_RFLDR", PU_CACHE)); + } + + if (itemOn == replaylistitem && i == (INT16)dir_on[menudepthleft]) + { + cursory = localy; + + if (extrasmenu.replayScrollDelay) + extrasmenu.replayScrollDelay--; + else if (extrasmenu.replayScrollDir > 0) + { + if (extrasmenu.replayScrollTitle < (V_StringWidth(extrasmenu.demolist[i].title, 0) - (SCALEDVIEWWIDTH - (x<<1)))<<1) + extrasmenu.replayScrollTitle++; + else + { + extrasmenu.replayScrollDelay = TICRATE; + extrasmenu.replayScrollDir = -1; + } + } + else + { + if (extrasmenu.replayScrollTitle > 0) + extrasmenu.replayScrollTitle--; + else + { + extrasmenu.replayScrollDelay = TICRATE; + extrasmenu.replayScrollDir = 1; + } + } + + V_DrawString(localx - (extrasmenu.replayScrollTitle>>1), localy, V_SNAPTOTOP|V_SNAPTOLEFT|highlightflags|V_ALLOWLOWERCASE, extrasmenu.demolist[i].title); + } + else + V_DrawString(localx, localy, V_SNAPTOTOP|V_SNAPTOLEFT|V_ALLOWLOWERCASE, extrasmenu.demolist[i].title); + } + + // Draw scrollbar + y = sizedirmenu*10 + currentMenu->menuitems[replaylistitem].mvar1 + 30; + if (y > SCALEDVIEWHEIGHT-80) + { + V_DrawFill(BASEVIDWIDTH-4, 75, 4, SCALEDVIEWHEIGHT-80, V_SNAPTOTOP|V_SNAPTORIGHT|159); + V_DrawFill(BASEVIDWIDTH-3, 76 + (SCALEDVIEWHEIGHT-80) * replayhutmenuy / y, 2, (((SCALEDVIEWHEIGHT-80) * (SCALEDVIEWHEIGHT-80))-1) / y - 1, V_SNAPTOTOP|V_SNAPTORIGHT|149); + } + + // Draw the cursor + V_DrawScaledPatch(currentMenu->x - 24, cursory, V_SNAPTOTOP|V_SNAPTOLEFT, + W_CachePatchName("M_CURSOR", PU_CACHE)); + V_DrawString(currentMenu->x, cursory, V_SNAPTOTOP|V_SNAPTOLEFT|highlightflags, currentMenu->menuitems[itemOn].text); + + // Now draw some replay info! + V_DrawFill(10, 10, 300, 60, V_SNAPTOTOP|159); + + if (itemOn == replaylistitem) + { + M_DrawReplayHutReplayInfo(); + } +} + +void M_DrawReplayStartMenu(void) +{ + const char *warning; + UINT8 i; + + M_DrawEggaChannel(); + M_DrawGenericMenu(); + +#define STARTY 62-(extrasmenu.replayScrollTitle>>1) + // Draw rankings beyond first + for (i = 1; i < MAXPLAYERS && extrasmenu.demolist[dir_on[menudepthleft]].standings[i].ranking; i++) + { + patch_t *patch; + UINT8 *colormap; + + V_DrawRightAlignedString(BASEVIDWIDTH-100, STARTY + i*20, V_SNAPTOTOP|highlightflags, va("%2d", extrasmenu.demolist[dir_on[menudepthleft]].standings[i].ranking)); + V_DrawThinString(BASEVIDWIDTH-96, STARTY + i*20, V_SNAPTOTOP|V_ALLOWLOWERCASE, extrasmenu.demolist[dir_on[menudepthleft]].standings[i].name); + + if (extrasmenu.demolist[dir_on[menudepthleft]].standings[i].timeorscore == UINT32_MAX-1) + V_DrawThinString(BASEVIDWIDTH-92, STARTY + i*20 + 9, V_SNAPTOTOP, "NO CONTEST"); + else if (extrasmenu.demolist[dir_on[menudepthleft]].gametype == GT_RACE) + V_DrawRightAlignedString(BASEVIDWIDTH-40, STARTY + i*20 + 9, V_SNAPTOTOP, va("%d'%02d\"%02d", + G_TicsToMinutes(extrasmenu.demolist[dir_on[menudepthleft]].standings[i].timeorscore, true), + G_TicsToSeconds(extrasmenu.demolist[dir_on[menudepthleft]].standings[i].timeorscore), + G_TicsToCentiseconds(extrasmenu.demolist[dir_on[menudepthleft]].standings[i].timeorscore) + )); + else + V_DrawString(BASEVIDWIDTH-92, STARTY + i*20 + 9, V_SNAPTOTOP, va("%d", extrasmenu.demolist[dir_on[menudepthleft]].standings[i].timeorscore)); + + // Character face! + + // Lat: 08/06/2020: For some reason missing skins have their value set to 255 (don't even ask me why I didn't write this) + // and for an even STRANGER reason this passes the first check below, so we're going to make sure that the skin here ISN'T 255 before we do anything stupid. + + if (extrasmenu.demolist[dir_on[menudepthleft]].standings[i].skin != 0xFF) + { + patch = faceprefix[extrasmenu.demolist[dir_on[menudepthleft]].standings[i].skin][FACE_RANK]; + colormap = R_GetTranslationColormap( + extrasmenu.demolist[dir_on[menudepthleft]].standings[i].skin, + extrasmenu.demolist[dir_on[menudepthleft]].standings[i].color, + GTC_MENUCACHE); + } + else + { + patch = W_CachePatchName("M_NORANK", PU_CACHE); + colormap = R_GetTranslationColormap( + TC_RAINBOW, + extrasmenu.demolist[dir_on[menudepthleft]].standings[i].color, + GTC_MENUCACHE); + } + + V_DrawMappedPatch(BASEVIDWIDTH-5 - SHORT(patch->width), STARTY + i*20, V_SNAPTOTOP, patch, colormap); + } +#undef STARTY + + // Handle scrolling rankings + if (extrasmenu.replayScrollDelay) + extrasmenu.replayScrollDelay--; + else if (extrasmenu.replayScrollDir > 0) + { + if (extrasmenu.replayScrollTitle < (i*20 - SCALEDVIEWHEIGHT + 100)<<1) + extrasmenu.replayScrollTitle++; + else + { + extrasmenu.replayScrollDelay = TICRATE; + extrasmenu.replayScrollDir = -1; + } + } + else + { + if (extrasmenu.replayScrollTitle > 0) + extrasmenu.replayScrollTitle--; + else + { + extrasmenu.replayScrollDelay = TICRATE; + extrasmenu.replayScrollDir = 1; + } + } + + V_DrawFill(10, 10, 300, 60, V_SNAPTOTOP|159); + M_DrawReplayHutReplayInfo(); + + V_DrawString(10, 72, V_SNAPTOTOP|highlightflags|V_ALLOWLOWERCASE, extrasmenu.demolist[dir_on[menudepthleft]].title); + + // Draw a warning prompt if needed + switch (extrasmenu.demolist[dir_on[menudepthleft]].addonstatus) + { + case DFILE_ERROR_CANNOTLOAD: + warning = "Some addons in this replay cannot be loaded.\nYou can watch anyway, but desyncs may occur."; + break; + + case DFILE_ERROR_NOTLOADED: + case DFILE_ERROR_INCOMPLETEOUTOFORDER: + warning = "Loading addons will mark your game as modified, and Record Attack may be unavailable.\nYou can watch without loading addons, but desyncs may occur."; + break; + + case DFILE_ERROR_EXTRAFILES: + warning = "You have addons loaded that were not present in this replay.\nYou can watch anyway, but desyncs may occur."; + break; + + case DFILE_ERROR_OUTOFORDER: + warning = "You have this replay's addons loaded, but they are out of order.\nYou can watch anyway, but desyncs may occur."; + break; + + default: + return; + } + + V_DrawSmallString(4, BASEVIDHEIGHT-14, V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_ALLOWLOWERCASE, warning); +} + +// Draw misc menus: + +// Addons + +#define lsheadingheight 16 + +// Just do this here instead. +static void M_CacheAddonPatches(void) +{ + addonsp[EXT_FOLDER] = W_CachePatchName("M_FFLDR", PU_STATIC); + addonsp[EXT_UP] = W_CachePatchName("M_FBACK", PU_STATIC); + addonsp[EXT_NORESULTS] = W_CachePatchName("M_FNOPE", PU_STATIC); + addonsp[EXT_TXT] = W_CachePatchName("M_FTXT", PU_STATIC); + addonsp[EXT_CFG] = W_CachePatchName("M_FCFG", PU_STATIC); + addonsp[EXT_WAD] = W_CachePatchName("M_FWAD", PU_STATIC); + #ifdef USE_KART + addonsp[EXT_KART] = W_CachePatchName("M_FKART", PU_STATIC); + #endif + addonsp[EXT_PK3] = W_CachePatchName("M_FPK3", PU_STATIC); + addonsp[EXT_SOC] = W_CachePatchName("M_FSOC", PU_STATIC); + addonsp[EXT_LUA] = W_CachePatchName("M_FLUA", PU_STATIC); + addonsp[NUM_EXT] = W_CachePatchName("M_FUNKN", PU_STATIC); + addonsp[NUM_EXT+1] = W_CachePatchName("M_FSEL", PU_STATIC); + addonsp[NUM_EXT+2] = W_CachePatchName("M_FLOAD", PU_STATIC); + addonsp[NUM_EXT+3] = W_CachePatchName("M_FSRCH", PU_STATIC); + addonsp[NUM_EXT+4] = W_CachePatchName("M_FSAVE", PU_STATIC); +} + +#define addonsseperation 16 + +void M_DrawAddons(void) +{ + INT32 x, y; + ssize_t i, m; + const UINT8 *flashcol = NULL; + UINT8 hilicol; + + M_CacheAddonPatches(); + + // hack: If we're calling this from GS_MENU, that means we're in the extras menu! + // so draw the apropriate background + if (gamestate == GS_MENU) + { + patch_t *bg = W_CachePatchName("M_XTRABG", PU_CACHE); + V_DrawFixedPatch(0, 0, FRACUNIT, 0, bg, NULL); + } + + if (Playing()) + V_DrawCenteredString(BASEVIDWIDTH/2, 5, warningflags, "Adding files mid-game may cause problems."); + else + V_DrawCenteredString(BASEVIDWIDTH/2, 5, 0, (recommendedflags == V_SKYMAP ? LOCATIONSTRING2 : LOCATIONSTRING1)); + + // DRAW MENU + x = currentMenu->x; + y = currentMenu->y + 1; + + hilicol = V_GetStringColormap(highlightflags)[0]; + + y -= 16; + + V_DrawString(x-21, y + (lsheadingheight - 12), highlightflags|V_ALLOWLOWERCASE, M_AddonsHeaderPath()); + V_DrawFill(x-21, y + (lsheadingheight - 3), MAXSTRINGLENGTH*8+6, 1, hilicol); + //V_DrawFill(x-21, y + (lsheadingheight - 2), MAXSTRINGLENGTH*8+6, 1, 30); + + y += 10; + + M_DrawTextBox(x - (21 + 5), y, MAXSTRINGLENGTH, 1); + { + const char *str = (menusearch[0] ? cv_dummyaddonsearch.string : "Search..."); + INT32 tflag = (menusearch[0] ? 0 : V_TRANSLUCENT); + INT32 xoffs = 0; + if (itemOn == 0) + { + xoffs += 8; + V_DrawString(x + (skullAnimCounter/5) - 20, y+8, highlightflags, "\x1D"); + } + V_DrawString(x + xoffs - 18, y+8, V_ALLOWLOWERCASE|tflag, str); + } + + V_DrawSmallScaledPatch(x - (21 + 5 + 16), y+4, (menusearch[0] ? 0 : V_TRANSLUCENT), addonsp[NUM_EXT+3]); + + y += 21; + + m = (addonsseperation*(2*numaddonsshown + 1)) + 1 + 2*(16-addonsseperation); + V_DrawFill(x - 21, y - 1, MAXSTRINGLENGTH*8+6, m, 159); + + // scrollbar! + if (sizedirmenu <= (2*numaddonsshown + 1)) + i = 0; + else + { + ssize_t q = m; + m = ((2*numaddonsshown + 1) * m)/sizedirmenu; + if (dir_on[menudepthleft] <= numaddonsshown) // all the way up + i = 0; + else if (sizedirmenu <= (dir_on[menudepthleft] + numaddonsshown + 1)) // all the way down + i = q-m; + else + i = ((dir_on[menudepthleft] - numaddonsshown) * (q-m))/(sizedirmenu - (2*numaddonsshown + 1)); + } + + V_DrawFill(x + MAXSTRINGLENGTH*8+5 - 21, (y - 1) + i, 1, m, hilicol); + + // get bottom... + m = dir_on[menudepthleft] + numaddonsshown + 1; + if (m > (ssize_t)sizedirmenu) + m = sizedirmenu; + + // then compute top and adjust bottom if needed! + if (m < (2*numaddonsshown + 1)) + { + m = min(sizedirmenu, 2*numaddonsshown + 1); + i = 0; + } + else + i = m - (2*numaddonsshown + 1); + + if (i != 0) + V_DrawString(19, y+4 - (skullAnimCounter/5), highlightflags, "\x1A"); + + if (skullAnimCounter < 4) + flashcol = V_GetStringColormap(highlightflags); + + y -= (16-addonsseperation); + + for (; i < m; i++) + { + UINT32 flags = V_ALLOWLOWERCASE; + if (y > BASEVIDHEIGHT) break; + if (dirmenu[i]) +#define type (UINT8)(dirmenu[i][DIR_TYPE]) + { + if (type & EXT_LOADED) + { + flags |= V_TRANSLUCENT; + V_DrawSmallScaledPatch(x-(16+4), y, V_TRANSLUCENT, addonsp[(type & ~EXT_LOADED)]); + V_DrawSmallScaledPatch(x-(16+4), y, 0, addonsp[NUM_EXT+2]); + } + else + V_DrawSmallScaledPatch(x-(16+4), y, 0, addonsp[(type & ~EXT_LOADED)]); + + if (itemOn == 1 && (size_t)i == dir_on[menudepthleft]) + { + V_DrawFixedPatch((x-(16+4))< (charsonside*2 + 3)) + V_DrawString(x, y+4, flags, va("%.*s...%s", charsonside, dirmenu[i]+DIR_STRING, dirmenu[i]+DIR_STRING+dirmenu[i][DIR_LEN]-(charsonside+1))); +#undef charsonside + else + V_DrawString(x, y+4, flags, dirmenu[i]+DIR_STRING); + } +#undef type + y += addonsseperation; + } + + if (m != (ssize_t)sizedirmenu) + V_DrawString(19, y-12 + (skullAnimCounter/5), highlightflags, "\x1B"); + + if (m < (2*numaddonsshown + 1)) + { + y += ((2*numaddonsshown + 1)-m)*addonsseperation; + } + + y -= 2; + + V_DrawSmallScaledPatch(x, y, ((!majormods) ? 0 : V_TRANSLUCENT), addonsp[NUM_EXT+4]); + if (modifiedgame) + V_DrawSmallScaledPatch(x, y, 0, addonsp[NUM_EXT+2]); + + m = numwadfiles-(mainwads+2+1); + + V_DrawCenteredString(BASEVIDWIDTH/2, y+4, (majormods ? highlightflags : V_TRANSLUCENT), va("%ld ADD-ON%s LOADED", (long)m, (m == 1) ? "" : "S")); //+2 for music, sounds, +1 for main.kart +} + +#undef addonsseperation diff --git a/src/k_menufunc.c b/src/k_menufunc.c new file mode 100644 index 000000000..753a9b767 --- /dev/null +++ b/src/k_menufunc.c @@ -0,0 +1,6594 @@ +/// \file k_menufunc.c +/// \brief SRB2Kart's menu functions + +#ifdef __GNUC__ +#include +#endif + +#include "k_menu.h" + +#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 +int snprintf(char *str, size_t n, const char *fmt, ...); +//int vsnprintf(char *str, size_t n, const char *fmt, va_list ap); +#endif + +// ========================================================================== +// GLOBAL VARIABLES +// ========================================================================== + +#ifdef HAVE_THREADS +I_mutex k_menu_mutex; +#endif + +boolean menuactive = false; +boolean fromlevelselect = false; + +// current menudef +menu_t *currentMenu = &MAIN_ProfilesDef; + +char dummystaffname[22]; + +INT16 itemOn = 0; // menu item skull is on, Hack by Tails 09-18-2002 +INT16 skullAnimCounter = 8; // skull animation counter +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; + +// lock out further input in a tic when important buttons are pressed +// (in other words -- stop bullshit happening by mashing buttons in fades) +static boolean noFurtherInput = false; + +// ========================================================================== +// CONSOLE VARIABLES AND THEIR POSSIBLE VALUES GO HERE. +// ========================================================================== + +// 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); + +// This gametype list is integral for many different reasons. +// When you add gametypes here, don't forget to update them in dehacked.c and doomstat.h! +CV_PossibleValue_t gametype_cons_t[NUMGAMETYPES+1]; + +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 CV_PossibleValue_t dummygametype_cons_t[] = {{0, "Race"}, {1, "Battle"}, {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_dummygametype = CVAR_INIT ("dummygametype", "Race", CV_HIDDEN, dummygametype_cons_t, NULL); +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); + +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) +{ +#if 0 + lumpnum_t l; + + dummystaffname[0] = '\0'; + + if ((l = W_CheckNumForName(va("%sS01",G_BuildMapName(cv_nextmap.value)))) == LUMPERROR) + { + CV_StealthSetValue(&cv_dummystaff, 0); + return; + } + else + { + char *temp = dummystaffname; + UINT8 numstaff = 1; + while (numstaff < 99 && (l = W_CheckNumForName(va("%sS%02u",G_BuildMapName(cv_nextmap.value),numstaff+1))) != LUMPERROR) + numstaff++; + + if (cv_dummystaff.value < 1) + CV_StealthSetValue(&cv_dummystaff, numstaff); + else if (cv_dummystaff.value > numstaff) + CV_StealthSetValue(&cv_dummystaff, 1); + + if ((l = W_CheckNumForName(va("%sS%02u",G_BuildMapName(cv_nextmap.value), cv_dummystaff.value))) == LUMPERROR) + return; // shouldn't happen but might as well check... + + G_UpdateStaffGhostName(l); + + while (*temp) + temp++; + + sprintf(temp, " - %d", cv_dummystaff.value); + } +#endif +} + +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 + totalplaytime = 0; + 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\n(Press A to confirm)\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; + + // Backspace sets values to default value + if (choice == -1) + { + CV_Set(cv, cv->defaultvalue); + return; + } + + choice = (choice<<1) - 1; + + if (((currentMenu->menuitems[itemOn].status & IT_CVARTYPE) == IT_CV_SLIDER) + || ((currentMenu->menuitems[itemOn].status & IT_CVARTYPE) == IT_CV_INVISSLIDER) + || ((currentMenu->menuitems[itemOn].status & IT_CVARTYPE) == IT_CV_NOMOD)) + { + CV_SetValue(cv, cv->value+choice); + } + else if (cv->flags & CV_FLOAT) + { + char s[20]; + sprintf(s, "%f", FIXED_TO_FLOAT(cv->value) + (choice) * (1.0f / 16.0f)); + CV_Set(cv, s); + } + else + { +#ifndef NONET + if (cv == &cv_nettimeout || cv == &cv_jointimeout) + choice *= (TICRATE/7); + else if (cv == &cv_maxsend) + choice *= 512; + else if (cv == &cv_maxping) + choice *= 50; +#endif + + CV_AddValue(cv, 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) +{ + INT16 oldItemOn = itemOn; // prevent infinite loop + + if ((currentMenu->menuitems[itemOn].status & IT_CVARTYPE) == IT_CV_PASSWORD) + (currentMenu->menuitems[itemOn].itemaction.cvar)->value = 0; + + do + { + if (itemOn + 1 > currentMenu->numitems - 1) + { + // Prevent looparound here + // If you're going to add any extra exceptions, DON'T. + // Add a "don't loop" flag to the menu_t struct instead. + if (currentMenu == &MISC_AddonsDef) + return false; + itemOn = 0; + } + else + itemOn++; + } while (oldItemOn != itemOn && (currentMenu->menuitems[itemOn].status & IT_TYPE) == IT_SPACE); + + M_UpdateMenuBGImage(false); + + return true; +} + +static boolean M_PrevOpt(void) +{ + INT16 oldItemOn = itemOn; // prevent infinite loop + + if ((currentMenu->menuitems[itemOn].status & IT_CVARTYPE) == IT_CV_PASSWORD) + (currentMenu->menuitems[itemOn].itemaction.cvar)->value = 0; + + do + { + if (!itemOn) + { + // Prevent looparound here + // If you're going to add any extra exceptions, DON'T. + // Add a "don't loop" flag to the menu_t struct instead. + if (currentMenu == &MISC_AddonsDef) + return false; + itemOn = currentMenu->numitems - 1; + } + else + itemOn--; + } while (oldItemOn != itemOn && (currentMenu->menuitems[itemOn].status & IT_TYPE) == IT_SPACE); + + M_UpdateMenuBGImage(false); + + return true; +} + +// +// M_Responder +// +boolean M_Responder(event_t *ev) +{ + menuKey = -1; + + if (dedicated || (demo.playback && demo.title) + || gamestate == GS_INTRO || gamestate == GS_CUTSCENE || gamestate == GS_GAMEEND + || gamestate == GS_CREDITS || gamestate == GS_EVALUATION) + { + return false; + } + + if (noFurtherInput) + { + // Ignore input after enter/escape/other buttons + // (but still allow shift keyup so caps doesn't get stuck) + return false; + } + + if (ev->type == ev_keydown && ev->data1 < NUMKEYS) + { + // Record keyboard presses + menuKey = ev->data1; + } + + M_MapMenuControls(ev); + + // Profiles: Control mapping. + // We take the WHOLE EVENT for convenience. + if (optionsmenu.bindcontrol) + { + M_MapProfileControl(ev); + return true; // eat events. + } + + // event handler for MM_EVENTHANDLER + if (menumessage.active && menumessage.flags == MM_EVENTHANDLER && menumessage.routine) + { + CONS_Printf("MM_EVENTHANDLER...\n"); + menumessage.eroutine(ev); // What a terrible hack... + return true; + } + + // Handle menu handling in-game. + if (menuactive == false) + { + noFurtherInput = true; + +#if 0 + // The Fx keys. + switch (menuKey) + { + case KEY_F1: // Help key + Command_Manual_f(); + return true; + + case KEY_F2: // Empty + return true; + + case KEY_F3: // Toggle HUD + CV_SetValue(&cv_showhud, !cv_showhud.value); + return true; + + case KEY_F4: // Sound Volume + if (modeattacking) + return true; + M_StartControlPanel(); + M_Options(0); + currentMenu = &OP_SoundOptionsDef; + itemOn = 0; + return true; + + case KEY_F5: // Video Mode + if (modeattacking) + return true; + M_StartControlPanel(); + M_Options(0); + M_VideoModeMenu(0); + return true; + + case KEY_F6: // Empty + return true; + + case KEY_F7: // Options + if (modeattacking) + return true; + M_StartControlPanel(); + M_Options(0); + M_SetupNextMenu(&OP_MainDef, false); + return true; + + // Screenshots on F8 now handled elsewhere + // Same with Moviemode on F9 + + case KEY_F10: // Quit SRB2 + M_QuitSRB2(0); + return true; + + case KEY_F11: // Fullscreen + CV_AddValue(&cv_fullscreen, 1); + return true; + + // Spymode on F12 handled in game logic + } +#endif + + if (CON_Ready() == false && G_PlayerInputDown(0, gc_start, splitscreen + 1) == true) + { + if (chat_on) + { + HU_clearChatChars(); + chat_on = false; + } + else + { + M_StartControlPanel(); + } + + return true; + } + + noFurtherInput = false; // turns out we didn't care + return false; + } + + // Typing for CV_IT_STRING + if (menutyping.active && !menutyping.menutypingclose && menutyping.keyboardtyping) + { + M_ChangeStringCvar(menuKey); + } + + // We're in the menu itself now. + // M_Ticker will take care of the rest. + return true; +} + +// +// M_StartControlPanel +// +void M_StartControlPanel(void) +{ + INT32 i; + + memset(gamekeydown, 0, sizeof (gamekeydown)); + memset(menucmd, 0, sizeof (menucmd)); + for (i = 0; i < MAXSPLITSCREENPLAYERS; i++) + { + menucmd[i].delay = MENUDELAYTIME; + } + + // No instantly skipping the titlescreen. + // (We can change this timer later when extra animation is added.) + if (gamestate == GS_TITLESCREEN && finalecount < 1) + return; + + // intro might call this repeatedly + if (menuactive) + { + CON_ToggleOff(); // move away console + return; + } + + if (gamestate == GS_TITLESCREEN) // Set up menu state + { + G_SetGamestate(GS_MENU); + + gameaction = ga_nothing; + paused = false; + CON_ToggleOff(); + + S_ChangeMusicInternal("menu", true); + } + + menuactive = true; + + if (!Playing()) + { + if (cv_currprofile.value == -1) // Only ask once per session. + { + // Make sure the profile data is ready now since we need to select a profile. + M_ResetOptions(); + + // we need to do this before setting ApplyProfile otherwise funky things are going to happen. + currentMenu = &MAIN_ProfilesDef; + optionsmenu.profilen = cv_ttlprofilen.value; + + // options don't need initializing here. + PR_ApplyProfile(0, 0); // apply guest profile to player 0 by default. + // this is to garantee that the controls aren't fucked up. + + // make sure we don't overstep that. + if (optionsmenu.profilen > PR_GetNumProfiles()) + optionsmenu.profilen = PR_GetNumProfiles(); + else if (optionsmenu.profilen < 0) + optionsmenu.profilen = 0; + + currentMenu->lastOn = 0; + + CV_StealthSetValue(&cv_currprofile, -1); // Make sure to reset that as it is set by PR_ApplyProfile which we kind of hack together to force it. + } + else + { + currentMenu = &MainDef; + } + } + else + { + if (demo.playback) + { + currentMenu = &PAUSE_PlaybackMenuDef; + } + else + { + M_OpenPauseMenu(); + } + } + + itemOn = currentMenu->lastOn; + + CON_ToggleOff(); // move away console +} + +// +// M_ClearMenus +// +void M_ClearMenus(boolean callexitmenufunc) +{ + if (!menuactive) + return; + + if (currentMenu->quitroutine && callexitmenufunc && !currentMenu->quitroutine()) + return; // we can't quit this menu (also used to set parameter from the menu) + +#ifndef DC // Save the config file. I'm sick of crashing the game later and losing all my changes! + COM_BufAddText(va("saveconfig \"%s\" -silent\n", configfile)); +#endif //Alam: But not on the Dreamcast's VMUs + + if (currentMenu == &MessageDef) // Oh sod off! + currentMenu = &MainDef; // Not like it matters + + if (gamestate == GS_MENU) // Back to title screen + D_StartTitle(); + + menutyping.active = false; + menumessage.active = false; + + menuactive = false; +} + +void M_SelectableClearMenus(INT32 choice) +{ + (void)choice; + M_ClearMenus(true); +} + +// +// M_SetupNextMenu +// +void M_SetupNextMenu(menu_t *menudef, boolean notransition) +{ + INT16 i; + + if (!notransition) + { + if (currentMenu->transitionID == menudef->transitionID + && currentMenu->transitionTics) + { + menutransition.startmenu = currentMenu; + menutransition.endmenu = menudef; + + menutransition.tics = 0; + menutransition.dest = currentMenu->transitionTics; + menutransition.in = false; + return; // Don't change menu yet, the transition will call this again + } + else if (gamestate == GS_MENU) + { + menuwipe = true; + F_WipeStartScreen(); + V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31); + F_WipeEndScreen(); + F_RunWipe(wipedefs[wipe_menu_toblack], false, "FADEMAP0", false, false); + } + } + + if (currentMenu->quitroutine) + { + // If you're going from a menu to itself, why are you running the quitroutine? You're not quitting it! -SH + if (currentMenu != menudef && !currentMenu->quitroutine()) + return; // we can't quit this menu (also used to set parameter from the menu) + } + + if (menudef->initroutine != NULL +#if 0 + && currentMenu != menudef // Unsure if we need this... +#endif + ) + { + // Moving to a new menu, reinitialize. + menudef->initroutine(); + } + + currentMenu = menudef; + itemOn = currentMenu->lastOn; + + // in case of... + if (itemOn >= currentMenu->numitems) + itemOn = currentMenu->numitems - 1; + + // the curent item can be disabled, + // this code go up until an enabled item found + if ((currentMenu->menuitems[itemOn].status & IT_TYPE) == IT_SPACE) + { + for (i = 0; i < currentMenu->numitems; i++) + { + if ((currentMenu->menuitems[i].status & IT_TYPE) != IT_SPACE) + { + itemOn = i; + break; + } + } + } + + M_UpdateMenuBGImage(false); +} + +void M_GoBack(INT32 choice) +{ + (void)choice; + + noFurtherInput = true; + currentMenu->lastOn = itemOn; + + if (currentMenu->prevMenu) + { + //If we entered the game search menu, but didn't enter a game, + //make sure the game doesn't still think we're in a netgame. + if (!Playing() && netgame && multiplayer) + { + netgame = false; + multiplayer = false; + } + + M_SetupNextMenu(currentMenu->prevMenu, false); + } + else + M_ClearMenus(true); + + S_StartSound(NULL, sfx_s3k5b); +} + +// +// M_Ticker +// +void M_SetMenuDelay(UINT8 i) +{ + menucmd[i].delayCount++; + if (menucmd[i].delayCount < 1) + { + // Shouldn't happen, but for safety. + menucmd[i].delayCount = 1; + } + + menucmd[i].delay = (MENUDELAYTIME / menucmd[i].delayCount); + if (menucmd[i].delay < 1) + { + menucmd[i].delay = 1; + } +} + +static void M_UpdateMenuCMD(UINT8 i) +{ + UINT8 mp = max(1, setup_numplayers); + + menucmd[i].dpad_ud = 0; + menucmd[i].dpad_lr = 0; + + menucmd[i].buttonsHeld = menucmd[i].buttons; + menucmd[i].buttons = 0; + + if (G_PlayerInputDown(i, gc_up, mp)) { menucmd[i].dpad_ud--; } + if (G_PlayerInputDown(i, gc_down, mp)) { menucmd[i].dpad_ud++; } + + if (G_PlayerInputDown(i, gc_left, mp)) { menucmd[i].dpad_lr--; } + if (G_PlayerInputDown(i, gc_right, mp)) { menucmd[i].dpad_lr++; } + + if (G_PlayerInputDown(i, gc_a, mp)) { menucmd[i].buttons |= MBT_A; } + if (G_PlayerInputDown(i, gc_b, mp)) { menucmd[i].buttons |= MBT_B; } + if (G_PlayerInputDown(i, gc_c, mp)) { menucmd[i].buttons |= MBT_C; } + if (G_PlayerInputDown(i, gc_x, mp)) { menucmd[i].buttons |= MBT_X; } + if (G_PlayerInputDown(i, gc_y, mp)) { menucmd[i].buttons |= MBT_Y; } + if (G_PlayerInputDown(i, gc_z, mp)) { menucmd[i].buttons |= MBT_Z; } + if (G_PlayerInputDown(i, gc_l, mp)) { menucmd[i].buttons |= MBT_L; } + if (G_PlayerInputDown(i, gc_r, mp)) { menucmd[i].buttons |= MBT_R; } + if (G_PlayerInputDown(i, gc_start, mp)) { menucmd[i].buttons |= MBT_START; } + + if (menucmd[i].dpad_ud == 0 && menucmd[i].dpad_lr == 0 && menucmd[i].buttons == 0) + { + // Reset delay count with no buttons. + menucmd[i].delay = min(menucmd[i].delay, MENUMINDELAY); + menucmd[i].delayCount = 0; + } +} + +void M_MapMenuControls(event_t *ev) +{ + INT32 i; + + if (ev) + { + // update keys current state + G_MapEventsToControls(ev); + } + + // Update menu CMD + for (i = 0; i < MAXSPLITSCREENPLAYERS; i++) + { + M_UpdateMenuCMD(i); + } +} + +boolean M_MenuButtonPressed(UINT8 pid, UINT32 bt) +{ + if (menucmd[pid].buttonsHeld & bt) + { + return false; + } + + return (menucmd[pid].buttons & bt); +} + +boolean M_MenuButtonHeld(UINT8 pid, UINT32 bt) +{ + return (menucmd[pid].buttons & bt); +} + +// Returns true if we press the confirmation button +static boolean M_MenuConfirmPressed(UINT8 pid) +{ + return M_MenuButtonPressed(pid, MBT_A); +} + +// Returns true if we press the Cancel button +static boolean M_MenuBackPressed(UINT8 pid) +{ + return (M_MenuButtonPressed(pid, MBT_B) || M_MenuButtonPressed(pid, MBT_X)); +} + +// Retrurns true if we press the tertiary option button (C) +static boolean M_MenuExtraPressed(UINT8 pid) +{ + return M_MenuButtonPressed(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 + UINT8 pid = 0; // todo: Add ability for any splitscreen player to bring up the menu. + SINT8 lr = 0, ud = 0; + + if (menuactive == false) + { + // We're not in the menu. + return; + } + + if (menumessage.active) + { + M_HandleMenuMessage(); + return; + } + + // Typing for CV_IT_STRING + if (menutyping.active) + { + M_MenuTypingInput(menuKey); + return; + } + + if (menucmd[pid].delay > 0) + { + return; + } + + // Handle menu-specific input handling. If this returns true, we skip regular input handling. + if (currentMenu->inputroutine) + { + if (currentMenu->inputroutine(menuKey)) + { + return; + } + } + + routine = currentMenu->menuitems[itemOn].itemaction.routine; + + // Handle menuitems which need a specific key handling + if (routine && (currentMenu->menuitems[itemOn].status & IT_TYPE) == IT_KEYHANDLER) + { + routine(-1); + return; + } + + // BP: one of the more big hack i have never made + if (routine && (currentMenu->menuitems[itemOn].status & IT_TYPE) == IT_CVAR) + { + if ((currentMenu->menuitems[itemOn].status & IT_CVARTYPE) == IT_CV_STRING) + { + // Routine is null either way + routine = NULL; + + // If we're hovering over a IT_CV_STRING option, pressing A/X opens the typing submenu + if (M_MenuConfirmPressed(pid)) + { + menutyping.keyboardtyping = menuKey != 0 ? true : false; // If we entered this menu by pressing a menu Key, default to keyboard typing, otherwise use controller. + menutyping.active = true; + menutyping.menutypingclose = false; + return; + } + + } + else + { + routine = M_ChangeCvar; + } + } + + lr = menucmd[pid].dpad_lr; + ud = menucmd[pid].dpad_ud; + + // LR does nothing in the default menu, just remap as dpad. + if (menucmd[pid].buttons & MBT_L) { lr--; } + if (menucmd[pid].buttons & MBT_R) { lr++; } + + // Keys usable within menu + if (ud > 0) + { + if (M_NextOpt()) + S_StartSound(NULL, sfx_s3k5b); + M_SetMenuDelay(pid); + return; + } + else if (ud < 0) + { + if (M_PrevOpt()) + S_StartSound(NULL, sfx_s3k5b); + M_SetMenuDelay(pid); + return; + } + else if (lr < 0) + { + if (routine && ((currentMenu->menuitems[itemOn].status & IT_TYPE) == IT_ARROWS + || (currentMenu->menuitems[itemOn].status & IT_TYPE) == IT_CVAR)) + { + S_StartSound(NULL, sfx_s3k5b); + routine(0); + M_SetMenuDelay(pid); + } + + return; + } + else if (lr > 0) + { + if (routine && ((currentMenu->menuitems[itemOn].status & IT_TYPE) == IT_ARROWS + || (currentMenu->menuitems[itemOn].status & IT_TYPE) == IT_CVAR)) + { + S_StartSound(NULL, sfx_s3k5b); + routine(1); + M_SetMenuDelay(pid); + } + + return; + } + else if (M_MenuConfirmPressed(pid) /*|| M_MenuButtonPressed(pid, MBT_START)*/) + { + noFurtherInput = true; + currentMenu->lastOn = itemOn; + + if (routine) + { + S_StartSound(NULL, sfx_s3k5b); + + if (((currentMenu->menuitems[itemOn].status & IT_TYPE) == IT_CALL + || (currentMenu->menuitems[itemOn].status & IT_TYPE) == IT_SUBMENU) + && (currentMenu->menuitems[itemOn].status & IT_CALLTYPE)) + { + if (((currentMenu->menuitems[itemOn].status & IT_CALLTYPE) & IT_CALL_NOTMODIFIED) && majormods) + { + M_StartMessage(M_GetText("This cannot be done with complex addons\nor in a cheated game.\n\n(Press a key)\n"), NULL, MM_NOTHING); + return; + } + } + + switch (currentMenu->menuitems[itemOn].status & IT_TYPE) + { + case IT_CVAR: + case IT_ARROWS: + routine(1); // right arrow + break; + case IT_CALL: + routine(itemOn); + break; + case IT_SUBMENU: + currentMenu->lastOn = itemOn; + M_SetupNextMenu((menu_t *)currentMenu->menuitems[itemOn].itemaction.submenu, false); + break; + } + } + + M_SetMenuDelay(pid); + return; + } + else if (M_MenuBackPressed(pid)) + { + M_GoBack(0); + M_SetMenuDelay(pid); + return; + } + else if (M_MenuExtraPressed(pid)) + { + if (routine && ((currentMenu->menuitems[itemOn].status & IT_TYPE) == IT_ARROWS + || (currentMenu->menuitems[itemOn].status & IT_TYPE) == IT_CVAR)) + { + consvar_t *cv = currentMenu->menuitems[itemOn].itemaction.cvar; + + // Make these CVar options? + if (cv == &cv_chooseskin + || cv == &cv_dummystaff + /* + || cv == &cv_nextmap + || cv == &cv_newgametype + */ + ) + { + return; + } + + S_StartSound(NULL, sfx_s3k5b); + + routine(-1); + M_SetMenuDelay(pid); + return; + } + + return; + } + + return; +} + +void M_Ticker(void) +{ + INT32 i; + + if (menutransition.tics != 0 || menutransition.dest != 0) + { + noFurtherInput = true; + + if (menutransition.tics < menutransition.dest) + menutransition.tics++; + else if (menutransition.tics > menutransition.dest) + menutransition.tics--; + + // If dest is non-zero, we've started transition and want to switch menus + // If dest is zero, we're mid-transition and want to end it + if (menutransition.tics == menutransition.dest + && menutransition.endmenu != NULL + && currentMenu != menutransition.endmenu + ) + { + if (menutransition.startmenu->transitionID == menutransition.endmenu->transitionID + && menutransition.endmenu->transitionTics) + { + menutransition.tics = menutransition.endmenu->transitionTics; + menutransition.dest = 0; + menutransition.in = true; + } + else if (gamestate == GS_MENU) + { + memset(&menutransition, 0, sizeof(menutransition)); + + menuwipe = true; + F_WipeStartScreen(); + V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31); + F_WipeEndScreen(); + F_RunWipe(wipedefs[wipe_menu_toblack], false, "FADEMAP0", false, false); + } + + M_SetupNextMenu(menutransition.endmenu, true); + } + } + else + { + if (menuwipe) + { + // try not to let people input during the fadeout + noFurtherInput = true; + } + else + { + // reset input trigger + noFurtherInput = false; + } + } + + for (i = 0; i < MAXSPLITSCREENPLAYERS; i++) + { + if (menucmd[i].delay > 0) + { + menucmd[i].delay--; + } + } + + if (noFurtherInput == false) + { + M_HandleMenuInput(); + } + + if (currentMenu->tickroutine) + { + currentMenu->tickroutine(); + } + + if (dedicated) + { + return; + } + + if (--skullAnimCounter <= 0) + skullAnimCounter = 8; + +#if 0 + if (currentMenu == &PAUSE_PlaybackMenuDef) + { + if (playback_enterheld > 0) + playback_enterheld--; + } + else + playback_enterheld = 0; + + //added : 30-01-98 : test mode for five seconds + if (vidm_testingmode > 0) + { + // restore the previous video mode + if (--vidm_testingmode == 0) + setmodeneeded = vidm_previousmode + 1; + } +#endif +} + +// +// M_Init +// +void M_Init(void) +{ +#if 0 + CV_RegisterVar(&cv_nextmap); +#endif + CV_RegisterVar(&cv_chooseskin); + CV_RegisterVar(&cv_autorecord); + + if (dedicated) + return; + + //COM_AddCommand("manual", Command_Manual_f); + + // Menu hacks + CV_RegisterVar(&cv_dummymenuplayer); + CV_RegisterVar(&cv_dummyteam); + CV_RegisterVar(&cv_dummyspectate); + CV_RegisterVar(&cv_dummyscramble); + CV_RegisterVar(&cv_dummystaff); + CV_RegisterVar(&cv_dummygametype); + CV_RegisterVar(&cv_dummyip); + + CV_RegisterVar(&cv_dummyprofilename); + CV_RegisterVar(&cv_dummyprofileplayername); + CV_RegisterVar(&cv_dummyprofilekickstart); + + CV_RegisterVar(&cv_dummygpdifficulty); + CV_RegisterVar(&cv_dummykartspeed); + CV_RegisterVar(&cv_dummygpencore); + CV_RegisterVar(&cv_dummymatchbots); + + CV_RegisterVar(&cv_dummyaddonsearch); + + M_UpdateMenuBGImage(true); + +#if 0 +#ifdef HWRENDER + // Permanently hide some options based on render mode + if (rendermode == render_soft) + OP_VideoOptionsMenu[op_video_ogl].status = + OP_VideoOptionsMenu[op_video_kartman].status = + OP_VideoOptionsMenu[op_video_md2] .status = IT_DISABLED; +#endif +#endif + +#ifndef NONET + CV_RegisterVar(&cv_serversort); +#endif +} + +// ================================================== +// 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, i, strlines; + 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, colors, etc. but who cares. + strlines = 0; + 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 + 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 = 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); + + menumessage.fadetimer++; + + if (menumessage.fadetimer > 9) + menumessage.fadetimer = 9; + + + 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 || cv_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\n(Press A to exit)", 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[48]; + +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; + + // While we're here, read follower values. + p->followern = K_FollowerAvailable(pr->follower); + p->followercolor = pr->followercolor; + + // Now position the grid for skin + for (i = 0; i < numskins; i++) + { + if (!(strcmp(pr->skinname, skins[i].name))) + { + INT32 alt = 0; // Hey it's my character's name! + p->gridx = skins[i].kartspeed-1; + p->gridy = skins[i].kartweight-1; + + // Now this put our cursor on the good alt + while (setup_chargrid[p->gridx][p->gridy].skinlist[alt] != i) + alt++; + + p->clonenum = alt; + p->color = PR_GetProfileColor(pr); + return; // we're done here + } + } +} + +static void M_SetupMidGameGridPos(setup_player_t *p, UINT8 num) +{ + INT32 i; + + // While we're here, read follower values. + p->followern = cv_follower[num].value; + p->followercolor = cv_followercolor[num].value; + + // Now position the grid for skin + for (i = 0; i < numskins; i++) + { + if (!(strcmp(cv_skin[num].zstring, skins[i].name))) + { + INT32 alt = 0; // Hey it's my character's name! + p->gridx = skins[i].kartspeed-1; + p->gridy = skins[i].kartweight-1; + + // Now this put our cursor on the good alt + while (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].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 (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_page = 0; +} + +void M_CharacterSelect(INT32 choice) +{ + (void)choice; + PLAY_CharSelectDef.prevMenu = currentMenu; + M_SetupNextMenu(&PLAY_CharSelectDef, false); +} + +static void M_SetupReadyExplosions(setup_player_t *p) +{ + UINT8 i, j; + UINT8 e = 0; + + 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++) + { + SINT8 x = p->gridx, y = p->gridy; + + switch (j) + { + case 0: x += offset; break; + case 1: x -= offset; break; + case 2: y += offset; break; + case 3: y -= offset; break; + } + + if ((x < 0 || x > 8) || (y < 0 || y > 8)) + continue; + + setup_explosions[e].tics = t; + setup_explosions[e].color = p->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); + } + + 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(p); + } + 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); + } + + // 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); + } +} + +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_FOLLOWER; + M_GetFollowerState(p); + 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); + } +} + +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_HandleFollowerRotate(setup_player_t *p, UINT8 num) +{ + if (cv_splitdevice.value) + num = 0; + + if (menucmd[num].dpad_lr > 0) + { + p->followern++; + if (p->followern >= numfollowers) + p->followern = -1; + + M_GetFollowerState(p); + + p->rotate = CSROTATETICS; + p->delay = CSROTATETICS; + S_StartSound(NULL, sfx_s3kc3s); + } + else if (menucmd[num].dpad_lr < 0) + { + p->followern--; + if (p->followern < -1) + p->followern = numfollowers-1; + + 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(p); + S_StartSound(NULL, sfx_s3k4e); + } + + M_SetMenuDelay(num); + } + else if (M_MenuBackPressed(num)) + { + p->mdepth = CSSTEP_COLORS; + S_StartSound(NULL, sfx_s3k5b); + 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(p); + S_StartSound(NULL, sfx_s3k4e); + M_SetMenuDelay(num); + } + else if (M_MenuBackPressed(num)) + { + p->mdepth = CSSTEP_FOLLOWER; + S_StartSound(NULL, sfx_s3k5b); + 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_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; + + char commandnames[][MAXSTRINGLENGTH] = { "skin ", "skin2 ", "skin3 ", "skin4 "}; + // ^ laziness 100 (we append a space directly so that we don't have to do it later too!!!!) + + for (i = 0; i < splitscreen +1; i++) + { + char cmd[MAXSTRINGLENGTH]; + + // 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 + CV_StealthSetValue(&cv_follower[i], setup_player[i].followern); + + // follower color + CV_StealthSetValue(&cv_followercolor[i], setup_player[i].followercolor); + + // finally, call the skin[x] console command. + // This will call SendNameAndColor which will synch everything we sent here and apply the changes! + + // This is a hack to make sure we call Skin[x]_OnChange afterwards + CV_StealthSetValue(&cv_skin[i], -1); + + strcpy(cmd, commandnames[i]); + strcat(cmd, skins[setup_player[i].skin].name); + COM_ImmedExecute(cmd); + + } + 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++; + + 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].skinname); + 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); + + CV_StealthSetValue(&cv_follower[i], setup_player[i].followern); + 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; + + if (cv_splitplayers.value <= 1) + { + // Remove Battle, add Capsules + PLAY_GamemodesMenu[1].status = IT_DISABLED; + PLAY_GamemodesMenu[2].status = IT_STRING | IT_CALL; + } + else + { + // Add Battle, remove Capsules + PLAY_GamemodesMenu[1].status = IT_STRING | IT_CALL; + PLAY_GamemodesMenu[2].status = IT_DISABLED; + } + + M_SetupNextMenu(&PLAY_GamemodesDef, false); +} + +void M_SetupRaceMenu(INT32 choice) +{ + (void)choice; + + PLAY_RaceGamemodesDef.prevMenu = currentMenu; + + // Time Attack is 1P only + if (cv_splitplayers.value <= 1) + { + PLAY_RaceGamemodesMenu[2].status = IT_STRING | IT_CALL; + } + else + { + PLAY_RaceGamemodesMenu[2].status = IT_DISABLED; + } + + 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)) + { + 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. +// Set gt = -1 to ignore gametype. +// +boolean M_CanShowLevelInList(INT16 mapnum, UINT8 gt) +{ + // Does the map exist? + if (!mapheaderinfo[mapnum]) + return false; + + // Does the map have a name? + if (!mapheaderinfo[mapnum]->lvlttl[0]) + return false; + + if (M_MapLocked(mapnum+1)) + return false; // not unlocked + + // Should the map be hidden? + if (mapheaderinfo[mapnum]->menuflags & LF2_HIDEINMENU /*&& mapnum+1 != gamemap*/) + return false; + + // I don't know why, but some may have exceptions. + if (levellist.timeattack && (mapheaderinfo[mapnum]->menuflags & LF2_NOTIMEATTACK)) + return false; + + if (gt == GT_BATTLE && (mapheaderinfo[mapnum]->typeoflevel & TOL_BATTLE)) + return true; + + if (gt == GT_RACE && (mapheaderinfo[mapnum]->typeoflevel & TOL_RACE)) + { + if (levellist.selectedcup && levellist.selectedcup->numlevels) + { + UINT8 i; + + for (i = 0; i < levellist.selectedcup->numlevels; i++) + { + if (mapnum == levellist.selectedcup->levellist[i]) + break; + } + + if (i == levellist.selectedcup->numlevels) + return false; + } + + return true; + } + + // Hmm? Couldn't decide? + return false; +} + +INT16 M_CountLevelsToShowInList(UINT8 gt) +{ + INT16 mapnum, count = 0; + + for (mapnum = 0; mapnum < NUMMAPS; mapnum++) + if (M_CanShowLevelInList(mapnum, gt)) + count++; + + return count; +} + +INT16 M_GetFirstLevelInList(UINT8 gt) +{ + INT16 mapnum; + + for (mapnum = 0; mapnum < NUMMAPS; mapnum++) + if (M_CanShowLevelInList(mapnum, gt)) + return mapnum; + + return 0; +} + +struct cupgrid_s cupgrid; +struct levellist_s levellist; + +static void M_LevelSelectScrollDest(void) +{ + UINT16 m = M_CountLevelsToShowInList(levellist.newgametype)-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 void M_LevelListFromGametype(INT16 gt) +{ + levellist.newgametype = gt; + PLAY_CupSelectDef.prevMenu = currentMenu; + + // 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.newgametype == GT_RACE) + { + cupheader_t *cup = kartcupheaders; + UINT8 highestid = 0; + + // Make sure there's valid cups before going to this menu. + if (cup == NULL) + I_Error("Can you really call this a racing game, I didn't recieve any Cups on my pillow or anything"); + + while (cup) + { + if (cup->unlockrequired == -1 || unlockables[cup->unlockrequired].unlocked) + highestid = cup->id; + cup = cup->next; + } + + cupgrid.numpages = (highestid / (CUPMENU_COLUMNS * CUPMENU_ROWS)) + 1; + + PLAY_LevelSelectDef.prevMenu = &PLAY_CupSelectDef; + M_SetupNextMenu(&PLAY_CupSelectDef, false); + + return; + } + + // Reset position properly if you go back & forth between gametypes + if (levellist.selectedcup) + { + levellist.cursor = 0; + levellist.selectedcup = NULL; + } + + M_LevelSelectScrollDest(); + levellist.y = levellist.dest; + + PLAY_LevelSelectDef.prevMenu = currentMenu; + M_SetupNextMenu(&PLAY_LevelSelectDef, false); + +} + +// 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) +{ + (void)choice; + + levellist.netgame = false; // Make sure this is reset as we'll only be using this function for offline games! + cupgrid.netgame = false; // Ditto + + switch (currentMenu->menuitems[itemOn].mvar1) + { + case 0: + cupgrid.grandprix = false; + levellist.timeattack = false; + break; + case 1: + cupgrid.grandprix = false; + levellist.timeattack = true; + break; + case 2: + cupgrid.grandprix = true; + levellist.timeattack = false; + break; + default: + CONS_Alert(CONS_WARNING, "Bad level select init\n"); + return; + } + + levellist.newgametype = currentMenu->menuitems[itemOn].mvar2; + + M_LevelListFromGametype(levellist.newgametype); +} + +void M_CupSelectHandler(INT32 choice) +{ + cupheader_t *newcup = kartcupheaders; + const UINT8 pid = 0; + + (void)choice; + + while (newcup) + { + if (newcup->id == CUPMENU_CURSORID) + break; + newcup = newcup->next; + } + + 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; + cupgrid.pageno--; + if (cupgrid.pageno < 0) + cupgrid.pageno = cupgrid.numpages-1; + } + 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)*/) + { + M_SetMenuDelay(pid); + + if ((!newcup) || (newcup && newcup->unlockrequired != -1 && !unlockables[newcup->unlockrequired].unlocked)) + { + S_StartSound(NULL, sfx_s3kb2); + return; + } + + if (cupgrid.grandprix == true) + { + + 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(); + multiplayer = true; // yeah, SV_StartSinglePlayerServer clobbers this... + netgame = levellist.netgame; // ^ ditto. + } + + D_MapChange( + grandprixinfo.cup->levellist[0] + 1, + GT_RACE, + grandprixinfo.encore, + true, + 1, + false, + false + ); + + M_ClearMenus(true); + } + else + { + // Keep cursor position if you select the same cup again, reset if it's a different cup + if (!levellist.selectedcup || newcup->id != levellist.selectedcup->id) + { + levellist.cursor = 0; + levellist.selectedcup = newcup; + } + + 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 start = M_GetFirstLevelInList(levellist.newgametype); + INT16 maxlevels = M_CountLevelsToShowInList(levellist.newgametype); + 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)*/) + { + INT16 map = start; + INT16 add = levellist.cursor; + + M_SetMenuDelay(pid); + + while (add > 0) + { + map++; + + while (!M_CanShowLevelInList(map, levellist.newgametype) && map < NUMMAPS) + map++; + + if (map >= NUMMAPS) + break; + + add--; + } + + if (map >= NUMMAPS) + { + // This shouldn't happen + return; + } + + levellist.choosemap = map; + + if (levellist.timeattack) + { + M_SetupNextMenu(&PLAY_TimeAttackDef, false); + S_StartSound(NULL, sfx_s3k63); + } + 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 + cv_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(); + multiplayer = true; // yeah, SV_StartSinglePlayerServer clobbers this... + netgame = levellist.netgame; // ^ ditto. + + 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); + } + } + 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; + const size_t glen = strlen("media")+1+strlen("replay")+1+strlen(timeattackfolder)+1+strlen("MAPXX")+1; + char nameofdemo[256]; + + (void)choice; + + switch (levellist.newgametype) + { + case GT_BATTLE: + modeattacking = ATTACKING_CAPSULES; + break; + default: + modeattacking = ATTACKING_TIME; + break; + } + + // Still need to reset devmode + cv_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(); + + gpath = va("%s"PATHSEP"media"PATHSEP"replay"PATHSEP"%s", + srb2home, timeattackfolder); + M_MkdirEach(gpath, M_PathParts(gpath) - 3, 0755); + + if ((gpath = malloc(glen)) == NULL) + I_Error("Out of memory for replay filepath\n"); + + sprintf(gpath,"media"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s", timeattackfolder, 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 + + (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 + + 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_MPSetupNetgameMapSelect(INT32 choice) +{ + + INT16 gt = GT_RACE; + (void)choice; + + levellist.netgame = true; // Yep, we'll be starting a netgame. + cupgrid.netgame = true; // Ditto + levellist.timeattack = false; // Make sure we reset those + cupgrid.grandprix = false; // Ditto + + // In case we ever want to add new gamemodes there somehow, have at it! + switch (cv_dummygametype.value) + { + case 1: // Battle + { + gt = GT_BATTLE; + break; + } + + default: + { + gt = GT_RACE; + break; + } + } + + // okay this is REALLY stupid but this fixes the host menu re-folding on itself when we go back. + mpmenu.modewinextend[0][0] = 1; + + M_LevelListFromGametype(gt); // Setup the level select. + // (This will also automatically send us to the apropriate menu) +} + +// 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)); + M_ClearMenus(true); + + // 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 (strlen(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(); + +} + +#ifndef NONET +#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(500, 1500); + serverlist[i].info.time = P_RandomRange(16, 500); // ping + + strcpy(serverlist[i].info.servername, va("Serv %d", i+1)); + + strcpy(serverlist[i].info.gametypename, i & 1 ? "Race" : "Battle"); + + P_RandomRange(0, 5); // change results... + serverlist[i].info.kartvars = P_RandomRange(0, 3) & SV_SPEEDMASK; + + serverlist[i].info.modifiedgame = P_RandomRange(0, 1); + + CONS_Printf("Serv %d | %d...\n", i, serverlist[i].info.modifiedgame); + } +} + +#endif // SERVERLISTDEBUG + +#endif //NONET + +// 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) +{ +#ifndef NONET + 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; + } +#endif +} + + +// 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())) + { + 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) ? (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(); + +#ifdef _WINDOWS + // clean that later: skip windowed mode 0, video modes menu only shows FULL SCREEN modes + if (nummodes <= NUMSPECIALMODES) + i = 0; // unless we have nothing + else + i = NUMSPECIALMODES; +#else + // DOS does not skip mode 0, because mode 0 is ALWAYS present + i = 0; +#endif + 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(&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\n(Press A to confirm)"), 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\n(Press A to confirm)"), 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\n(Press A to confirm)"), 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_sneaker.value; + S_StartSound(NULL, sfx_s1b4); + for (i = 0; i < NUMKARTRESULTS-1; i++) + { + if (KartItemCVars[i]->value == v) + CV_AddValue(KartItemCVars[i], 1); + } + } + else + { + if (currentMenu->menuitems[itemOn].mvar2) + { + S_StartSound(NULL, currentMenu->menuitems[itemOn].mvar2); + } + else + { + S_StartSound(NULL, sfx_s1ba); + } + CV_AddValue(KartItemCVars[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\n(Press any button)", 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\n(Press A to confirm)", FUNCPTRCAST(M_EraseProfileResponse), MM_YESNO); + else + M_StartMessage("This profile will be erased.\nAre you sure you want to proceed?\n\n(Press A to confirm)", 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) +{ + 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_switchmap].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()) + { + PAUSE_Main[mpause_psetup].status = IT_STRING | IT_CALL; + + if (server || IsPlayerAdmin(consoleplayer)) + { + PAUSE_Main[mpause_switchmap].status = IT_STRING | IT_SUBMENU; + PAUSE_Main[mpause_addons].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; + + 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; +} + +// 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.\nPress a key.\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 return\nto the title screen?\n(Press A to confirm)\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, 255, "%s%s", 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\n(Press a key)\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\n(Press a key)\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\n(Press a key)\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 = usehome ? srb2home : srb2path; + 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\n(Press a key)\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\n(Press a key)\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\n(Press a key)\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\n(Press a key)\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\n(Press a key)\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\n(Press a key)\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\n(Press a key)\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\n(Press a key)\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\n(Press A to confirm)\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\n(Press A to confirm)\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); +} diff --git a/src/k_profiles.c b/src/k_profiles.c new file mode 100644 index 000000000..5f50d189f --- /dev/null +++ b/src/k_profiles.c @@ -0,0 +1,539 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 1998-2000 by DooM Legacy Team. +// Copyright (C) 1999-2020 by Sonic Team Junior. +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- +/// \file k_profiles.c +/// \brief implements methods for profiles etc. + +#include "d_main.h" // pandf +#include "byteptr.h" // READ/WRITE macros +#include "p_saveg.h" // save_p +#include "m_misc.h" //FIL_WriteFile() +#include "k_profiles.h" +#include "z_zone.h" +#include "r_skins.h" + +// List of all the profiles. +static profile_t *profilesList[MAXPROFILES+1]; // +1 because we're gonna add a default "GUEST' profile. +static UINT8 numprofiles = 0; // # of loaded profiles + +INT32 PR_GetNumProfiles(void) +{ + return numprofiles; +} + +profile_t* PR_MakeProfile( + const char *prname, + const char *pname, + const char *sname, const UINT16 col, + const char *fname, const UINT16 fcol, + INT32 controlarray[num_gamecontrols][MAXINPUTMAPPING], + boolean guest) +{ + profile_t *new = Z_Malloc(sizeof(profile_t), PU_STATIC, NULL); + UINT8 i; + + new->version = PROFILEVER; + + strcpy(new->profilename, prname); + new->profilename[sizeof new->profilename - 1] = '\0'; + + strcpy(new->skinname, sname); + strcpy(new->playername, pname); + new->color = col; + + strcpy(new->follower, fname); + new->followercolor = fcol; + new->kickstartaccel = false; + + // Copy from gamecontrol directly as we'll be setting controls up directly in the profile. + memcpy(new->controls, controlarray, sizeof(new->controls)); + + // Init both power levels + for (i = 0; i < PWRLV_NUMTYPES; i++) + { + new->powerlevels[i] = (guest ? 0 : PWRLVRECORD_START); + } + + return new; +} + +profile_t* PR_MakeProfileFromPlayer(const char *prname, const char *pname, const char *sname, const UINT16 col, const char *fname, UINT16 fcol, UINT8 pnum) +{ + // Generate profile using the player's gamecontrol, as we set them directly when making profiles from menus. + profile_t *new = PR_MakeProfile(prname, pname, sname, col, fname, fcol, gamecontrol[pnum], false); + + // Player bound cvars: + new->kickstartaccel = cv_kickstartaccel[pnum].value; + + return new; +} + +boolean PR_AddProfile(profile_t *p) +{ + if (numprofiles < MAXPROFILES+1) + { + profilesList[numprofiles] = p; + numprofiles++; + + CONS_Printf("Profile '%s' added\n", p->profilename); + + return true; + } + else + return false; +} + +profile_t* PR_GetProfile(INT32 num) +{ + if (num < numprofiles) + return profilesList[num]; + else + return NULL; +} + +boolean PR_DeleteProfile(INT32 num) +{ + UINT8 i; + profile_t* sacrifice; + + if (num <= 0 || num > numprofiles) + { + return false; + } + + sacrifice = profilesList[num]; + + // If we're deleting inbetween profiles, move everything. + if (num < numprofiles) + { + for (i = num; i < numprofiles-1; i++) + { + profilesList[i] = profilesList[i+1]; + } + + // Make sure to move cv_lastprofile (and title/current profile) values as well! + for (i = 0; i < MAXSPLITSCREENPLAYERS+2; i++) + { + consvar_t *cv; + + if (i < MAXSPLITSCREENPLAYERS) + cv = &cv_lastprofile[i]; + else if (i == MAXSPLITSCREENPLAYERS) + cv = &cv_ttlprofilen; + else + cv = &cv_currprofile; + + if (cv->value < num) + { + // Not affected. + continue; + } + + if (cv->value > num) + { + // Shift our lastprofile number down to match the new order. + CV_StealthSetValue(cv, cv->value-1); + continue; + } + + if (cv != &cv_currprofile) + { + // There's no hope for it. If we were on the deleted profile, default back to guest. + CV_StealthSetValue(cv, PROFILE_GUEST); + continue; + } + + // Oh boy, now we're really in for it. + CV_StealthSetValue(cv, -1); + } + } + + // In any case, delete the last profile as well. + profilesList[numprofiles] = NULL; + numprofiles--; + + PR_SaveProfiles(); + + // Finally, clear up our memory! + Z_Free(sacrifice); + + return true; +} + +void PR_InitNewProfile(void) +{ + char pname[PROFILENAMELEN+1] = "PRF"; + profile_t *dprofile; + UINT8 usenum = numprofiles-1; + UINT8 i; + boolean nameok = false; + + pname[4] = '\0'; + + // When deleting profile, it's possible to do some pretty wacko stuff that would lead a new fresh profile to share the same name as another profile we have never changed the name of. + // This could become an infinite loop if MAXPROFILES >= 26. + while (!nameok) + { + pname[3] = 'A'+usenum; + + for (i = 0; i < numprofiles; i++) + { + profile_t *pr = PR_GetProfile(i); + if (!strcmp(pr->profilename, pname)) + { + usenum++; + if (pname[3] == 'Z') + usenum = 0; + + break; + } + + // if we got here, then it means the name is okay! + if (i == numprofiles-1) + nameok = true; + } + } + + dprofile = PR_MakeProfile( + pname, + PROFILEDEFAULTPNAME, + PROFILEDEFAULTSKIN, PROFILEDEFAULTCOLOR, + PROFILEDEFAULTFOLLOWER, PROFILEDEFAULTFOLLOWERCOLOR, + gamecontroldefault, + false + ); + PR_AddProfile(dprofile); +} + +static UINT8 *savebuffer; + +void PR_SaveProfiles(void) +{ + size_t length = 0; + const size_t headerlen = strlen(PROFILEHEADER); + UINT8 i, j, k; + + save_p = savebuffer = (UINT8 *)malloc(sizeof(UINT32) + (numprofiles * sizeof(profile_t))); + if (!save_p) + { + I_Error("No more free memory for saving profiles\n"); + return; + } + + // Add header. + WRITESTRINGN(save_p, PROFILEHEADER, headerlen); + WRITEUINT8(save_p, PROFILEVER); + WRITEUINT8(save_p, numprofiles); + + for (i = 1; i < numprofiles; i++) + { + // Names. + WRITESTRINGN(save_p, profilesList[i]->profilename, PROFILENAMELEN); + WRITESTRINGN(save_p, profilesList[i]->playername, MAXPLAYERNAME); + + // Character and colour. + WRITESTRINGN(save_p, profilesList[i]->skinname, SKINNAMESIZE); + WRITEUINT16(save_p, profilesList[i]->color); + + // Follower and colour. + WRITESTRINGN(save_p, profilesList[i]->follower, SKINNAMESIZE); + WRITEUINT16(save_p, profilesList[i]->followercolor); + + // PWR. + for (j = 0; j < PWRLV_NUMTYPES; j++) + { + WRITEUINT16(save_p, profilesList[i]->powerlevels[j]); + } + + // Consvars. + WRITEUINT8(save_p, profilesList[i]->kickstartaccel); + + // Controls. + for (j = 0; j < num_gamecontrols; j++) + { + for (k = 0; k < MAXINPUTMAPPING; k++) + { + WRITEINT32(save_p, profilesList[i]->controls[j][k]); + } + } + } + + length = save_p - savebuffer; + + if (!FIL_WriteFile(va(pandf, srb2home, PROFILESFILE), savebuffer, length)) + { + free(savebuffer); + I_Error("Couldn't save profiles. Are you out of Disk space / playing in a protected folder?"); + } + free(savebuffer); + save_p = savebuffer = NULL; +} + +void PR_LoadProfiles(void) +{ + size_t length = 0; + const size_t headerlen = strlen(PROFILEHEADER); + UINT8 i, j, k, version; + profile_t *dprofile = PR_MakeProfile( + PROFILEDEFAULTNAME, + PROFILEDEFAULTPNAME, + PROFILEDEFAULTSKIN, PROFILEDEFAULTCOLOR, + PROFILEDEFAULTFOLLOWER, PROFILEDEFAULTFOLLOWERCOLOR, + gamecontroldefault, + true + ); + + length = FIL_ReadFile(va(pandf, srb2home, PROFILESFILE), &savebuffer); + if (!length) + { + // No profiles. Add the default one. + PR_AddProfile(dprofile); + return; + } + + save_p = savebuffer; + + if (strncmp(PROFILEHEADER, (const char *)savebuffer, headerlen)) + { + const char *gdfolder = "the Ring Racers folder"; + if (strcmp(srb2home,".")) + gdfolder = srb2home; + + Z_Free(savebuffer); + save_p = NULL; + I_Error("Not a valid Profile file.\nDelete %s (maybe in %s) and try again.", PROFILESFILE, gdfolder); + } + save_p += headerlen; + + version = READUINT8(save_p); + if (version > PROFILEVER) + { + Z_Free(savebuffer); + save_p = NULL; + I_Error("Existing %s is from the future! (expected %d, got %d)", PROFILESFILE, PROFILEVER, version); + } + + numprofiles = READUINT8(save_p); + if (numprofiles > MAXPROFILES) + numprofiles = MAXPROFILES; + + for (i = 1; i < numprofiles; i++) + { + profilesList[i] = Z_Malloc(sizeof(profile_t), PU_STATIC, NULL); + + // Version. + profilesList[i]->version = version; + + // Names. + READSTRINGN(save_p, profilesList[i]->profilename, PROFILENAMELEN); + READSTRINGN(save_p, profilesList[i]->playername, MAXPLAYERNAME); + + // Character and colour. + READSTRINGN(save_p, profilesList[i]->skinname, SKINNAMESIZE); + profilesList[i]->color = READUINT16(save_p); + + if (profilesList[i]->color == SKINCOLOR_NONE) + { + ; // Valid, even outside the bounds + } + else if (profilesList[i]->color >= numskincolors + || skincolors[profilesList[i]->color].accessible == false) + { + profilesList[i]->color = PROFILEDEFAULTCOLOR; + } + + // Follower and colour. + READSTRINGN(save_p, profilesList[i]->follower, SKINNAMESIZE); + profilesList[i]->followercolor = READUINT16(save_p); + + if (profilesList[i]->followercolor == FOLLOWERCOLOR_MATCH + || profilesList[i]->followercolor == FOLLOWERCOLOR_OPPOSITE) + { + ; // Valid, even outside the bounds + } + else if (profilesList[i]->followercolor >= numskincolors + || profilesList[i]->followercolor == SKINCOLOR_NONE + || skincolors[profilesList[i]->followercolor].accessible == false) + { + profilesList[i]->followercolor = PROFILEDEFAULTFOLLOWERCOLOR; + } + + // PWR. + for (j = 0; j < PWRLV_NUMTYPES; j++) + { + profilesList[i]->powerlevels[j] = READUINT16(save_p); + if (profilesList[i]->powerlevels[j] < PWRLVRECORD_MIN + || profilesList[i]->powerlevels[j] > PWRLVRECORD_MAX) + { + // invalid, reset + profilesList[i]->powerlevels[j] = PWRLVRECORD_START; + } + } + + // Consvars. + profilesList[i]->kickstartaccel = (boolean)READUINT8(save_p); + + // Controls. + for (j = 0; j < num_gamecontrols; j++) + { + for (k = 0; k < MAXINPUTMAPPING; k++) + { + profilesList[i]->controls[j][k] = READINT32(save_p); + } + } + } + + // Add the the default profile directly to avoid letting anyone tamper with it. + profilesList[PROFILE_GUEST] = dprofile; +} + +skincolornum_t PR_GetProfileColor(profile_t *p) +{ + if (p->color == SKINCOLOR_NONE) + { + // Get skin's prefcolor. + INT32 foundskin = R_SkinAvailable(p->skinname); + if (foundskin == -1) + { + // Return random default value + return SKINCOLOR_RED; + } + + return skins[foundskin].prefcolor; + } + + // Get exact color. + return p->color; +} + +static void PR_ApplyProfile_Appearance(profile_t *p, UINT8 playernum) +{ + CV_StealthSet(&cv_skin[playernum], p->skinname); + CV_StealthSetValue(&cv_playercolor[playernum], PR_GetProfileColor(p)); + CV_StealthSet(&cv_playername[playernum], p->playername); + + // Followers + CV_StealthSet(&cv_follower[playernum], p->follower); + CV_StealthSetValue(&cv_followercolor[playernum], p->followercolor); +} + +static void PR_ApplyProfile_Settings(profile_t *p, UINT8 playernum) +{ + // toggles + CV_StealthSetValue(&cv_kickstartaccel[playernum], p->kickstartaccel); + + // set controls... + memcpy(&gamecontrol[playernum], p->controls, sizeof(gamecontroldefault)); +} + +static void PR_ApplyProfile_Memory(UINT8 profilenum, UINT8 playernum) +{ + // set memory cvar + CV_StealthSetValue(&cv_lastprofile[playernum], profilenum); + + // If we're doing this on P1, also change current profile. + if (playernum == 0) + { + CV_StealthSetValue(&cv_currprofile, profilenum); + } +} + +void PR_ApplyProfile(UINT8 profilenum, UINT8 playernum) +{ + profile_t *p = PR_GetProfile(profilenum); + + // this CAN happen!! + if (p == NULL) + { + CONS_Printf("Profile '%d' could not be loaded as it does not exist. Guest Profile will be loaded instead.\n", profilenum); + profilenum = 0; // make sure to set this so that the cvar is set properly. + p = PR_GetProfile(profilenum); + } + + PR_ApplyProfile_Appearance(p, playernum); + PR_ApplyProfile_Settings(p, playernum); + PR_ApplyProfile_Memory(profilenum, playernum); +} + +void PR_ApplyProfileLight(UINT8 profilenum, UINT8 playernum) +{ + profile_t *p = PR_GetProfile(profilenum); + + // this CAN happen!! + if (p == NULL) + { + // no need to be as loud... + profilenum = 0; // make sure to set this so that the cvar is set properly. + p = PR_GetProfile(profilenum); + } + + PR_ApplyProfile_Appearance(p, playernum); +} + +void PR_ApplyProfilePretend(UINT8 profilenum, UINT8 playernum) +{ + profile_t *p = PR_GetProfile(profilenum); + + // this CAN happen!! + if (p == NULL) + { + CONS_Printf("Profile '%d' could not be loaded as it does not exist. Guest Profile will be loaded instead.\n", profilenum); + profilenum = 0; // make sure to set this so that the cvar is set properly. + p = PR_GetProfile(profilenum); + } + + PR_ApplyProfile_Memory(profilenum, playernum); +} + +UINT8 PR_GetProfileNum(profile_t *p) +{ + UINT8 i; + for (i = 0; i < MAXPROFILES+1; i++) + { + profile_t *comp = PR_GetProfile(i); + if (comp == p) + return i; + } + return 0; +} + +SINT8 PR_ProfileUsedBy(profile_t *p) +{ + UINT8 i; + UINT8 prn = PR_GetProfileNum(p); + + for (i = 0; i < MAXSPLITSCREENPLAYERS; i++) + { + if (prn == cv_lastprofile[i].value) + return i; + } + + return -1; +} + +profile_t *PR_GetPlayerProfile(player_t *player) +{ + const UINT8 playerNum = (player - players); + UINT8 i; + + if (demo.playback) + { + return NULL; + } + + for (i = 0; i <= splitscreen; i++) + { + if (playerNum == g_localplayers[i]) + { + return PR_GetProfile(cv_lastprofile[i].value); + } + } + + return NULL; +} diff --git a/src/k_profiles.h b/src/k_profiles.h new file mode 100644 index 000000000..0a254bef5 --- /dev/null +++ b/src/k_profiles.h @@ -0,0 +1,155 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 1993-1996 by id Software, Inc. +// Copyright (C) 1998-2000 by DooM Legacy Team. +// Copyright (C) 2011-2016 by Matthew "Inuyasha" Walsh. +// Copyright (C) 1999-2018 by Sonic Team Junior. +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- +/// \file k_profiles.h +/// \brief Control profiles definition + +#ifndef __PROFILES_H__ +#define __PROFILES_H__ + +#include "doomdef.h" // MAXPLAYERNAME +//#include "r_skins.h" // SKINNAMESIZE // This cuases stupid issues. +#include "g_input.h" // Input related stuff +#include "string.h" // strcpy etc +#include "g_game.h" // game CVs +#include "k_follower.h" // followers + +// We have to redefine this because somehow including r_skins.h causes a redefinition of node_t since that's used for both net nodes and BSP nodes too...... +// And honestly I don't wanna refactor that. +#define SKINNAMESIZE 16 + +#define PROFILENAMELEN 6 +#define PROFILEVER 1 +#define MAXPROFILES 16 +#define PROFILESFILE "ringprofiles.prf" +#define PROFILE_GUEST 0 + +#define PROFILEDEFAULTNAME "GUEST" +#define PROFILEDEFAULTPNAME "Guest" +#define PROFILEDEFAULTSKIN "eggman" +#define PROFILEDEFAULTCOLOR SKINCOLOR_NONE +#define PROFILEDEFAULTFOLLOWER "none" +#define PROFILEDEFAULTFOLLOWERCOLOR FOLLOWERCOLOR_MATCH + +#define PROFILEHEADER "Doctor Robotnik's Ring Racers Profiles" + +// Man I wish I had more than 16 friends!! + +// profile_t definition (WIP) +// If you edit, see PR_SaveProfiles and PR_LoadProfiles +typedef struct profile_s +{ + + // Versionning + UINT8 version; // Version of the profile, this can be useful for backwards compatibility reading if we ever update the profile structure/format after release. + // A version of 0 can easily be checked to identify an unitialized profile. + + // Profile header + char profilename[PROFILENAMELEN+1]; // Profile name (not to be confused with player name) + + // Player data + char playername[MAXPLAYERNAME+1]; // Player name + char skinname[SKINNAMESIZE+1]; // Default Skin + UINT16 color; // Default player coloUr. ...But for consistency we'll name it color. + char follower[SKINNAMESIZE+1]; // Follower + UINT16 followercolor; // Follower color + + UINT16 powerlevels[PWRLV_NUMTYPES]; // PWRLV for each gametype. + + // Player-specific consvars. + // @TODO: List all of those + boolean kickstartaccel; // cv_kickstartaccel + + // Finally, control data itself + INT32 controls[num_gamecontrols][MAXINPUTMAPPING]; // Lists of all the controls, defined the same way as default inputs in g_input.c +} profile_t; + + +// Functions + +// returns how many profiles there are +INT32 PR_GetNumProfiles(void); + +// PR_MakeProfile +// Makes a profile from the supplied profile name, player name, colour, follower, followercolour and controls. +// The consvar values are left untouched. +profile_t* PR_MakeProfile( + const char *prname, + const char *pname, + const char *sname, const UINT16 col, + const char *fname, const UINT16 fcol, + INT32 controlarray[num_gamecontrols][MAXINPUTMAPPING], + boolean guest +); + +// PR_MakeProfileFromPlayer +// Makes a profile_t from the supplied profile name, player name, colour, follower and followercolour. +// The last argument is a player number to read cvars from; as for convenience, cvars will be set directly when making a profile (since loading another one will overwrite them, this will be inconsequential) +profile_t* PR_MakeProfileFromPlayer(const char *prname, const char *pname, const char *sname, const UINT16 col, const char *fname, UINT16 fcol, UINT8 pnum); + +// PR_AddProfile(profile_t p) +// Adds a profile to profilesList and increments numprofiles. +// Returns true if succesful, false if not. +boolean PR_AddProfile(profile_t *p); + +// PR_GetProfile(INT32 num) +// Returns a pointer to the profile you're asking for or NULL if the profile is uninitialized. +profile_t* PR_GetProfile(INT32 num); + +// PR_DeleteProfile(INT32 n) +// Deletes the specified profile. n cannot be 0. Returns false if the profile couldn't be deleted, true otherwise. +// This will also move every profile back accordingly to ensure the table has no empty profiles inbetween two valid profiles. +boolean PR_DeleteProfile(INT32 n); + +// PR_InitNewProfile(void) +// Initializes the first new profile +void PR_InitNewProfile(void); + +// PR_SaveProfiles(void) +// Saves all the profiles in profiles.cfg +// This does not save profilesList[0] since that's always going to be the default profile. +void PR_SaveProfiles(void); + +// PR_LoadProfiles(void) +// Loads all the profiles saved in profiles.cfg. +// This also loads +void PR_LoadProfiles(void); + +// PR_GetProfileColor(profile_t *p) +// Returns the profile's color, or the skin's prefcolor if set to none. +skincolornum_t PR_GetProfileColor(profile_t *p); + +// PR_ApplyProfile(UINT8 profilenum, UINT8 playernum) +// Applies the given profile's settings to the given player. +void PR_ApplyProfile(UINT8 profilenum, UINT8 playernum); + +// PR_ApplyProfileLight(UINT8 profilenum, UINT8 playernum) +// Similar to PR_ApplyProfile but only applies skin and follower values. +// Controls, kickstartaccel and "current profile" data is *not* modified. +void PR_ApplyProfileLight(UINT8 profilenum, UINT8 playernum); + +// PR_ApplyProfilePretend(UINT8 profilenum, UINT8 playernum) +// ONLY modifies "current profile" data. +// Exists because any other option inteferes with rapid testing. +void PR_ApplyProfilePretend(UINT8 profilenum, UINT8 playernum); + +// PR_GetProfileNum(profile_t *p) +// Gets the profile's index # in profilesList +UINT8 PR_GetProfileNum(profile_t *p); + +// PR_ProfileUsedBy(profile_t *p) +// Returns the player # this profile is used by (if any) +// If the profile belongs to no player, then this returns -1 +SINT8 PR_ProfileUsedBy(profile_t *p); + +profile_t *PR_GetPlayerProfile(player_t *player); + +#endif diff --git a/src/k_pwrlv.c b/src/k_pwrlv.c index cd3ee1827..d5cc7e2ab 100644 --- a/src/k_pwrlv.c +++ b/src/k_pwrlv.c @@ -17,10 +17,7 @@ #include "p_tick.h" // leveltime #include "k_grandprix.h" #include "k_boss.h" - -// Online rankings for the main gametypes. -// This array is saved to the gamedata. -UINT16 vspowerlevel[PWRLV_NUMTYPES]; +#include "k_profiles.h" // Client-sided calculations done for Power Levels. // This is done so that clients will never be able to hack someone else's score over the server. @@ -370,6 +367,7 @@ INT16 K_FinalPowerIncrement(player_t *player, INT16 yourPower, INT16 baseInc) // Get at least one point. inc = 1; } +#if 0 else { // You trade points in 1v1s, @@ -388,6 +386,7 @@ INT16 K_FinalPowerIncrement(player_t *player, INT16 yourPower, INT16 baseInc) } } } +#endif } if (yourPower + inc > PWRLVRECORD_MAX) @@ -416,14 +415,16 @@ void K_CashInPowerLevels(void) { if (playeringame[i] == true && powerType != PWRLV_DISABLED) { + profile_t *pr = PR_GetPlayerProfile(&players[i]); INT16 inc = K_FinalPowerIncrement(&players[i], clientpowerlevels[i][powerType], clientPowerAdd[i]); + clientpowerlevels[i][powerType] += inc; //CONS_Printf("%s: %d -> %d (%d)\n", player_names[i], clientpowerlevels[i][powerType] - inc, clientpowerlevels[i][powerType], inc); - if (!demo.playback && i == consoleplayer && inc != 0) + if (pr != NULL && inc != 0) { - vspowerlevel[powerType] = clientpowerlevels[i][powerType]; + pr->powerlevels[powerType] = clientpowerlevels[i][powerType]; if (M_UpdateUnlockablesAndExtraEmblems()) { @@ -567,6 +568,7 @@ void K_SetPowerLevelScrambles(SINT8 powertype) void K_PlayerForfeit(UINT8 playerNum, boolean pointLoss) { + profile_t *pr; UINT8 p = 0; SINT8 powerType = PWRLV_DISABLED; @@ -631,17 +633,22 @@ void K_PlayerForfeit(UINT8 playerNum, boolean pointLoss) K_UpdatePowerLevelsOnFailure(&players[playerNum]); inc = K_FinalPowerIncrement(&players[playerNum], yourPower, clientPowerAdd[playerNum]); - if (inc >= 0) + if (inc == 0) { - // Don't record no change or increases. + // No change return; } - // pointLoss isn't set for stuff like sync-outs, - // which shouldn't be so harsh on the victim! - if (!demo.playback && pointLoss == true && playerNum == consoleplayer) + if (inc < 0 && pointLoss == false) { - vspowerlevel[powerType] = yourPower + inc; + // Don't record point losses for sync-out / crashes. + return; + } + + pr = PR_GetPlayerProfile(&players[playerNum]); + if (pr != NULL) + { + pr->powerlevels[powerType] = yourPower + inc; if (M_UpdateUnlockablesAndExtraEmblems()) { diff --git a/src/k_pwrlv.h b/src/k_pwrlv.h index 9e5645c2f..0dff84774 100644 --- a/src/k_pwrlv.h +++ b/src/k_pwrlv.h @@ -31,7 +31,6 @@ typedef enum extern SINT8 speedscramble; extern SINT8 encorescramble; -extern UINT16 vspowerlevel[PWRLV_NUMTYPES]; extern UINT16 clientpowerlevels[MAXPLAYERS][PWRLV_NUMTYPES]; extern INT16 clientPowerAdd[MAXPLAYERS]; extern UINT8 spectateGriefed; diff --git a/src/lua_baselib.c b/src/lua_baselib.c index 301665e45..190a7f701 100644 --- a/src/lua_baselib.c +++ b/src/lua_baselib.c @@ -22,7 +22,6 @@ #include "m_random.h" #include "s_sound.h" #include "g_game.h" -#include "m_menu.h" #include "y_inter.h" #include "hu_stuff.h" // HU_AddChatText #include "console.h" @@ -33,7 +32,7 @@ #include "k_color.h" #include "k_hud.h" #include "d_netcmd.h" // IsPlayerAdmin -#include "m_menu.h" // Player Setup menu color stuff +#include "k_menu.h" // Player Setup menu color stuff #include "m_misc.h" // M_MapNumber #include "p_spec.h" // P_StartQuake #include "i_system.h" // I_GetPreciseTime, I_GetPrecisePrecision @@ -356,14 +355,26 @@ static int lib_pMoveColorAfter(lua_State *L) static int lib_pGetColorBefore(lua_State *L) { UINT16 color = (UINT16)luaL_checkinteger(L, 1); - lua_pushinteger(L, M_GetColorBefore(color)); + UINT16 amount = (UINT16)luaL_checkinteger(L, 2); + boolean follower = lua_optboolean(L, 3); + lua_pushinteger(L, M_GetColorBefore(color, amount, follower)); return 1; } static int lib_pGetColorAfter(lua_State *L) { UINT16 color = (UINT16)luaL_checkinteger(L, 1); - lua_pushinteger(L, M_GetColorAfter(color)); + UINT16 amount = (UINT16)luaL_checkinteger(L, 2); + boolean follower = lua_optboolean(L, 3); + lua_pushinteger(L, M_GetColorAfter(color, amount, follower)); + return 1; +} + +static int lib_pGetEffectiveFollowerColor(lua_State *L) +{ + UINT16 followercolor = (UINT16)luaL_checkinteger(L, 1); + UINT16 playercolor = (UINT16)luaL_checkinteger(L, 2); + lua_pushinteger(L, K_GetEffectiveFollowerColor(followercolor, playercolor)); return 1; } @@ -3339,23 +3350,37 @@ static int lib_kOvertakeSound(lua_State *L) static int lib_kPainSound(lua_State *L) { mobj_t *mobj = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ)); + mobj_t *other = NULL; NOHUD if (!mobj->player) return luaL_error(L, "K_PlayPainSound: mobj_t isn't a player object."); //Nothing bad would happen if we let it run the func, but telling why it ain't doing anything is helpful. - K_PlayPainSound(mobj); + if (!lua_isnone(L, 2) && lua_isuserdata(L, 2)) + other = *((mobj_t **)luaL_checkudata(L, 2, META_MOBJ)); + K_PlayPainSound(mobj, other); return 0; } static int lib_kHitEmSound(lua_State *L) { mobj_t *mobj = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ)); - mobj_t *victim = NULL; + mobj_t *other = NULL; NOHUD if (!mobj->player) return luaL_error(L, "K_PlayHitEmSound: mobj_t isn't a player object."); //Nothing bad would happen if we let it run the func, but telling why it ain't doing anything is helpful. if (!lua_isnone(L, 2) && lua_isuserdata(L, 2)) - victim = *((mobj_t **)luaL_checkudata(L, 2, META_MOBJ)); - K_PlayHitEmSound(mobj, victim); + other = *((mobj_t **)luaL_checkudata(L, 2, META_MOBJ)); + K_PlayHitEmSound(mobj, other); + return 0; +} + +static int lib_kTryHurtSoundExchange(lua_State *L) +{ + mobj_t *victim = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ)); + mobj_t *attacker = *((mobj_t **)luaL_checkudata(L, 2, META_MOBJ)); + NOHUD + if (!victim->player) + return luaL_error(L, "K_TryHurtSoundExchange: mobj_t isn't a player object."); //Nothing bad would happen if we let it run the func, but telling why it ain't doing anything is helpful. + K_TryHurtSoundExchange(victim, attacker); return 0; } @@ -3896,6 +3921,7 @@ static luaL_Reg lib[] = { {"P_ReturnThrustX",lib_pReturnThrustX}, {"P_ReturnThrustY",lib_pReturnThrustY}, {"P_NukeEnemies",lib_pNukeEnemies}, + {"K_GetEffectiveFollowerColor",lib_pGetEffectiveFollowerColor}, // p_map {"P_CheckPosition",lib_pCheckPosition}, @@ -4037,6 +4063,7 @@ static luaL_Reg lib[] = { {"K_PlayLossSound", lib_kLossSound}, {"K_PlayPainSound", lib_kPainSound}, {"K_PlayHitEmSound", lib_kHitEmSound}, + {"K_TryHurtSoundExchange", lib_kTryHurtSoundExchange}, {"K_IsPlayerLosing",lib_kIsPlayerLosing}, {"K_IsPlayerWanted",lib_kIsPlayerWanted}, {"K_KartBouncing",lib_kKartBouncing}, diff --git a/src/lua_playerlib.c b/src/lua_playerlib.c index 1d5f6bee7..7078b2258 100644 --- a/src/lua_playerlib.c +++ b/src/lua_playerlib.c @@ -274,6 +274,8 @@ static int player_get(lua_State *L) lua_pushinteger(L, plr->spindashspeed); else if (fastcmp(field,"spindashboost")) lua_pushinteger(L, plr->spindashboost); + else if (fastcmp(field,"fastfall")) + lua_pushfixed(L, plr->fastfall); else if (fastcmp(field,"numboosts")) lua_pushinteger(L, plr->numboosts); else if (fastcmp(field,"boostpower")) @@ -360,10 +362,10 @@ static int player_get(lua_State *L) lua_pushinteger(L, plr->lastjawztarget); else if (fastcmp(field,"jawztargetdelay")) lua_pushinteger(L, plr->jawztargetdelay); - else if (fastcmp(field,"confirmInflictor")) - lua_pushinteger(L, plr->confirmInflictor); - else if (fastcmp(field,"confirmInflictorDelay")) - lua_pushinteger(L, plr->confirmInflictorDelay); + else if (fastcmp(field,"confirmVictim")) + lua_pushinteger(L, plr->confirmVictim); + else if (fastcmp(field,"confirmVictimDelay")) + lua_pushinteger(L, plr->confirmVictimDelay); else if (fastcmp(field,"glanceDir")) lua_pushinteger(L, plr->glanceDir); else if (fastcmp(field,"trickpanel")) @@ -632,6 +634,8 @@ static int player_set(lua_State *L) plr->spindashspeed = luaL_checkinteger(L, 3); else if (fastcmp(field,"spindashboost")) plr->spindashboost = luaL_checkinteger(L, 3); + else if (fastcmp(field,"fastfall")) + plr->fastfall = luaL_checkfixed(L, 3); else if (fastcmp(field,"numboosts")) plr->numboosts = luaL_checkinteger(L, 3); else if (fastcmp(field,"boostpower")) @@ -718,10 +722,10 @@ static int player_set(lua_State *L) plr->lastjawztarget = luaL_checkinteger(L, 3); else if (fastcmp(field,"jawztargetdelay")) plr->jawztargetdelay = luaL_checkinteger(L, 3); - else if (fastcmp(field,"confirmInflictor")) - plr->confirmInflictor = luaL_checkinteger(L, 3); - else if (fastcmp(field,"confirmInflictorDelay")) - plr->confirmInflictorDelay = luaL_checkinteger(L, 3); + else if (fastcmp(field,"confirmVictim")) + plr->confirmVictim = luaL_checkinteger(L, 3); + else if (fastcmp(field,"confirmVictimDelay")) + plr->confirmVictimDelay = luaL_checkinteger(L, 3); else if (fastcmp(field,"glanceDir")) plr->glanceDir = luaL_checkinteger(L, 3); else if (fastcmp(field,"trickpanel")) diff --git a/src/m_cheat.c b/src/m_cheat.c index ce925e931..5eddbce4a 100644 --- a/src/m_cheat.c +++ b/src/m_cheat.c @@ -22,7 +22,7 @@ #include "d_net.h" #include "m_cheat.h" -#include "m_menu.h" +#include "k_menu.h" #include "m_random.h" #include "m_misc.h" @@ -58,20 +58,6 @@ typedef struct // ========================================================================== // Cheat responders -/*static UINT8 cheatf_ultimate(void) -{ - if (menuactive && (currentMenu != &MainDef && currentMenu != &SP_LoadDef)) - return 0; // Only on the main menu, or the save select! - - BwehHehHe(); - ultimate_selectable = (!ultimate_selectable); - - // If on the save select, move to what is now Ultimate Mode! - if (currentMenu == &SP_LoadDef) - M_ForceSaveSlotSelected(NOSAVESLOT); - return 1; -}*/ - static UINT8 cheatf_warp(void) { UINT8 i; @@ -83,7 +69,7 @@ static UINT8 cheatf_warp(void) if (menuactive && currentMenu != &MainDef) return 0; // Only on the main menu! - // Temporarily unlock EVERYTHING. + // Unlock EVERYTHING. for (i = 0; i < MAXUNLOCKABLES; i++) { if (!unlockables[i].conditionset) @@ -134,18 +120,6 @@ static UINT8 cheatf_devmode(void) } #endif -/*static cheatseq_t cheat_ultimate = { - 0, cheatf_ultimate, - { SCRAMBLE('u'), SCRAMBLE('l'), SCRAMBLE('t'), SCRAMBLE('i'), SCRAMBLE('m'), SCRAMBLE('a'), SCRAMBLE('t'), SCRAMBLE('e'), 0xff } -};*/ - -/*static cheatseq_t cheat_ultimate_joy = { - 0, cheatf_ultimate, - { SCRAMBLE(KEY_UPARROW), SCRAMBLE(KEY_UPARROW), SCRAMBLE(KEY_DOWNARROW), SCRAMBLE(KEY_DOWNARROW), - SCRAMBLE(KEY_LEFTARROW), SCRAMBLE(KEY_RIGHTARROW), SCRAMBLE(KEY_LEFTARROW), SCRAMBLE(KEY_RIGHTARROW), - SCRAMBLE(KEY_ENTER), 0xff } -};*/ - static cheatseq_t cheat_warp = { 0, cheatf_warp, //{ SCRAMBLE('r'), SCRAMBLE('e'), SCRAMBLE('d'), SCRAMBLE('x'), SCRAMBLE('v'), SCRAMBLE('i'), 0xff } @@ -253,8 +227,6 @@ boolean cht_Responder(event_t *ev) else ch = (UINT8)ev->data1; - //ret += cht_CheckCheat(&cheat_ultimate, (char)ch); - //ret += cht_CheckCheat(&cheat_ultimate_joy, (char)ch); ret += cht_CheckCheat(&cheat_warp, (char)ch); ret += cht_CheckCheat(&cheat_warp_joy, (char)ch); #ifdef DEVELOP diff --git a/src/m_cond.c b/src/m_cond.c index ceff3c167..0760275f2 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -20,7 +20,9 @@ #include "g_game.h" // record info #include "r_skins.h" // numskins #include "r_draw.h" // R_GetColorByName + #include "k_pwrlv.h" +#include "k_profiles.h" // Map triggers for linedef executors // 32 triggers, one bit each @@ -108,7 +110,20 @@ UINT8 M_CheckCondition(condition_t *cn) case UC_MATCHESPLAYED: // Requires any level completed >= x times return (matchesplayed >= (unsigned)cn->requirement); case UC_POWERLEVEL: // Requires power level >= x on a certain gametype - return (vspowerlevel[cn->extrainfo1] >= (unsigned)cn->requirement); + { + UINT8 i; + for (i = PROFILE_GUEST; i < PR_GetNumProfiles(); i++) + { + profile_t *p = PR_GetProfile(i); + + if (p->powerlevels[cn->extrainfo1] >= (unsigned)cn->requirement) + { + return true; + } + } + + return false; + } case UC_GAMECLEAR: // Requires game beaten >= x times return (timesBeaten >= (unsigned)cn->requirement); case UC_OVERALLTIME: // Requires overall time <= x @@ -359,10 +374,14 @@ UINT8 M_SecretUnlocked(INT32 type) { INT32 i; -#if 1 if (dedicated) return true; -#endif + +#if 0 + (void)type; + (void)i; + return false; // for quick testing +#else #ifdef DEVELOP #define CHADYES true @@ -378,6 +397,7 @@ UINT8 M_SecretUnlocked(INT32 type) return CHADYES; #undef CHADYES +#endif //if 0 } UINT8 M_MapLocked(INT32 mapnum) diff --git a/src/m_menu.c b/src/m_menu.c deleted file mode 100644 index 58a40176e..000000000 --- a/src/m_menu.c +++ /dev/null @@ -1,11586 +0,0 @@ -// SONIC ROBO BLAST 2 -//----------------------------------------------------------------------------- -// Copyright (C) 1993-1996 by id Software, Inc. -// Copyright (C) 1998-2000 by DooM Legacy Team. -// Copyright (C) 2011-2016 by Matthew "Inuyasha" Walsh. -// Copyright (C) 1999-2018 by Sonic Team Junior. -// -// This program is free software distributed under the -// terms of the GNU General Public License, version 2. -// See the 'LICENSE' file for more details. -//----------------------------------------------------------------------------- -/// \file m_menu.c -/// \brief XMOD's extremely revamped menu system. - -#ifdef __GNUC__ -#include -#endif - -#include "m_menu.h" - -#include "doomdef.h" -#include "d_main.h" -#include "d_netcmd.h" -#include "console.h" -#include "r_fps.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_time.h" -#include "i_system.h" -#include "i_threads.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 "k_hud.h" // SRB2kart -#include "k_kart.h" // KartItemCVars -#include "k_pwrlv.h" -#include "d_player.h" // KITEM_ constants -#include "k_color.h" -#include "k_grandprix.h" -#include "k_follower.h" -#include "r_fps.h" - -#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" - -#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 -int snprintf(char *str, size_t n, const char *fmt, ...); -//int vsnprintf(char *str, size_t n, const char *fmt, va_list ap); -#endif - -#ifdef HAVE_DISCORDRPC -//#include "discord_rpc.h" -#include "discord.h" -#endif - -#define SKULLXOFF -32 -#define LINEHEIGHT 16 -#define STRINGHEIGHT 8 -#define FONTBHEIGHT 20 -#define SMALLLINEHEIGHT 8 -#define SLIDER_RANGE 10 -#define SLIDER_WIDTH (8*SLIDER_RANGE+6) -#define SERVERS_PER_PAGE 11 - -#if defined (NONET) || defined (TESTERS) -#define NOMENUHOST -#endif - -typedef enum -{ - QUITMSG = 0, - QUITMSG1, - QUITMSG2, - QUITMSG3, - QUITMSG4, - QUITMSG5, - QUITMSG6, - QUITMSG7, - - QUIT2MSG, - QUIT2MSG1, - QUIT2MSG2, - QUIT2MSG3, - QUIT2MSG4, - QUIT2MSG5, - QUIT2MSG6, - - QUIT3MSG, - QUIT3MSG1, - QUIT3MSG2, - QUIT3MSG3, - QUIT3MSG4, - QUIT3MSG5, - QUIT3MSG6, - NUM_QUITMESSAGES -} text_enum; - -#ifdef HAVE_THREADS -I_mutex m_menu_mutex; -#endif - -M_waiting_mode_t m_waiting_mode = M_NOT_WAITING; - -const char *quitmsg[NUM_QUITMESSAGES]; - -// Stuff for customizing the player select screen Tails 09-22-2003 -description_t description[MAXSKINS]; - -//static char *char_notes = NULL; -//static fixed_t char_scroll = 0; - -boolean menuactive = false; -boolean fromlevelselect = false; - -typedef enum -{ - LLM_CREATESERVER, - LLM_LEVELSELECT, - LLM_TIMEATTACK, - LLM_BREAKTHECAPSULES, - LLM_BOSS -} levellist_mode_t; - -levellist_mode_t levellistmode = LLM_CREATESERVER; -UINT8 maplistoption = 0; - -static char joystickInfo[8][29]; -#ifndef NONET -static UINT32 serverlistpage; -#endif - -//static saveinfo_t savegameinfo[MAXSAVEGAMES]; // Extra info about the save games. - -INT16 startmap; // Mario, NiGHTS, or just a plain old normal game? - -static INT16 itemOn = 1; // menu item skull is on, Hack by Tails 09-18-2002 -static INT16 skullAnimCounter = 10; // skull animation counter -static tic_t followertimer = 0; // Used for smooth follower floating - -static UINT8 setupcontrolplayer; -static INT32 (*setupcontrols)[2]; // pointer to the gamecontrols of the player being edited - -// shhh... what am I doing... nooooo! -static INT32 vidm_testingmode = 0; -static INT32 vidm_previousmode; -static INT32 vidm_selected = 0; -static INT32 vidm_nummodes; -static INT32 vidm_column_size; - -// -// PROTOTYPES -// - -static void M_StopMessage(INT32 choice); - -#ifndef NONET -static void M_HandleServerPage(INT32 choice); -#endif - -// Prototyping is fun, innit? -// ========================================================================== -// NEEDED FUNCTION PROTOTYPES GO HERE -// ========================================================================== - -void M_SetWaitingMode(int mode); -int M_GetWaitingMode(void); - -// the haxor message menu -menu_t MessageDef; - -#ifdef HAVE_DISCORDRPC -menu_t MISC_DiscordRequestsDef; -static void M_HandleDiscordRequests(INT32 choice); -static void M_DrawDiscordRequests(void); -#endif - -menu_t SPauseDef; - -#define lsheadingheight 16 - -// Sky Room -//static void M_CustomLevelSelect(INT32 choice); -//static void M_CustomWarp(INT32 choice); -FUNCNORETURN static ATTRNORETURN void M_UltimateCheat(INT32 choice); -//static void M_LoadGameLevelSelect(INT32 choice); -static void M_GetAllEmeralds(INT32 choice); -static void M_DestroyRobots(INT32 choice); -//static void M_LevelSelectWarp(INT32 choice); -static void M_Credits(INT32 choice); -static void M_PandorasBox(INT32 choice); -static void M_EmblemHints(INT32 choice); -static char *M_GetConditionString(condition_t cond); -menu_t SR_MainDef, SR_UnlockChecklistDef; - -// Misc. Main Menu -#ifndef TESTERS -static void M_SinglePlayerMenu(INT32 choice); -#endif -static void M_Options(INT32 choice); -static void M_Manual(INT32 choice); -static void M_SelectableClearMenus(INT32 choice); -static void M_Retry(INT32 choice); -static void M_EndGame(INT32 choice); -static void M_MapChange(INT32 choice); -static void M_ChangeLevel(INT32 choice); -static void M_ConfirmSpectate(INT32 choice); -static void M_ConfirmEnterGame(INT32 choice); -static void M_ConfirmTeamScramble(INT32 choice); -static void M_ConfirmTeamChange(INT32 choice); -static void M_ConfirmSpectateChange(INT32 choice); -//static void M_SecretsMenu(INT32 choice); -//static void M_SetupChoosePlayer(INT32 choice); -static void M_QuitSRB2(INT32 choice); -menu_t SP_MainDef, MP_MainDef, OP_MainDef; -menu_t MISC_ScrambleTeamDef, MISC_ChangeTeamDef, MISC_ChangeSpectateDef; - -// Single Player -static void M_GrandPrixTemp(INT32 choice); -static void M_StartGrandPrix(INT32 choice); -static void M_TimeAttack(INT32 choice); -static boolean M_QuitTimeAttackMenu(void); -static void M_BreakTheCapsules(INT32 choice); -static void M_Statistics(INT32 choice); -static void M_HandleStaffReplay(INT32 choice); -static void M_ReplayTimeAttack(INT32 choice); -static void M_ChooseTimeAttack(INT32 choice); -//static void M_ChooseNightsAttack(INT32 choice); -static void M_ModeAttackEndGame(INT32 choice); -static void M_SetGuestReplay(INT32 choice); -//static void M_ChoosePlayer(INT32 choice); -menu_t SP_LevelStatsDef; -static menu_t SP_GrandPrixTempDef; -static menu_t SP_TimeAttackDef, SP_ReplayDef, SP_GuestReplayDef, SP_GhostDef; -//static menu_t SP_NightsAttackDef, SP_NightsReplayDef, SP_NightsGuestReplayDef, SP_NightsGhostDef; - -// Multiplayer -#ifndef NONET -#ifndef TESTERS -static void M_StartServerMenu(INT32 choice); -#endif -static void M_ConnectMenu(INT32 choice); -static void M_ConnectMenuModChecks(INT32 choice); -static void M_Refresh(INT32 choice); -static void M_Connect(INT32 choice); -#endif -#ifndef TESTERS -static void M_StartOfflineServerMenu(INT32 choice); -#endif -static void M_StartServer(INT32 choice); -static void M_SetupMultiPlayer(INT32 choice); -static void M_SetupMultiPlayer2(INT32 choice); -static void M_SetupMultiPlayer3(INT32 choice); -static void M_SetupMultiPlayer4(INT32 choice); -static void M_SetupMultiHandler(INT32 choice); - -// Options -// Split into multiple parts due to size -// Controls -menu_t OP_ControlsDef, OP_AllControlsDef; -menu_t OP_MouseOptionsDef; -menu_t OP_Joystick1Def, OP_Joystick2Def, OP_Joystick3Def, OP_Joystick4Def; -static void M_VideoModeMenu(INT32 choice); -static void M_Setup1PControlsMenu(INT32 choice); -static void M_Setup2PControlsMenu(INT32 choice); -static void M_Setup3PControlsMenu(INT32 choice); -static void M_Setup4PControlsMenu(INT32 choice); - -static void M_Setup1PJoystickMenu(INT32 choice); -static void M_Setup2PJoystickMenu(INT32 choice); -static void M_Setup3PJoystickMenu(INT32 choice); -static void M_Setup4PJoystickMenu(INT32 choice); - -static void M_AssignJoystick(INT32 choice); -static void M_ChangeControl(INT32 choice); -static void M_ResetControls(INT32 choice); - -// Video & Sound -menu_t OP_VideoOptionsDef, OP_VideoModeDef; -#ifdef HWRENDER -static void M_OpenGLOptionsMenu(INT32 choice); -menu_t OP_OpenGLOptionsDef; -#endif -menu_t OP_SoundOptionsDef; -//static void M_RestartAudio(void); - -//Misc -menu_t OP_DataOptionsDef, OP_ScreenshotOptionsDef, OP_EraseDataDef; -#ifdef HAVE_DISCORDRPC -menu_t OP_DiscordOptionsDef; -#endif -menu_t OP_HUDOptionsDef, OP_ChatOptionsDef; -menu_t OP_GameOptionsDef, OP_ServerOptionsDef; -#ifndef NONET -menu_t OP_AdvServerOptionsDef; -#endif -//menu_t OP_NetgameOptionsDef, OP_GametypeOptionsDef; -menu_t OP_MonitorToggleDef; -static void M_ScreenshotOptions(INT32 choice); -static void M_EraseData(INT32 choice); - -static void M_Addons(INT32 choice); -static void M_AddonsOptions(INT32 choice); -static patch_t *addonsp[NUM_EXT+5]; - -#define numaddonsshown 4 - -// Replay hut -menu_t MISC_ReplayHutDef; -menu_t MISC_ReplayOptionsDef; -static void M_HandleReplayHutList(INT32 choice); -static void M_DrawReplayHut(void); -static void M_DrawReplayStartMenu(void); -static boolean M_QuitReplayHut(void); -static void M_HutStartReplay(INT32 choice); - -static void M_DrawPlaybackMenu(void); -static void M_PlaybackRewind(INT32 choice); -static void M_PlaybackPause(INT32 choice); -static void M_PlaybackFastForward(INT32 choice); -static void M_PlaybackAdvance(INT32 choice); -static void M_PlaybackSetViews(INT32 choice); -static void M_PlaybackAdjustView(INT32 choice); -static void M_PlaybackToggleFreecam(INT32 choice); -static void M_PlaybackQuit(INT32 choice); - -static UINT8 playback_enterheld = 0; // horrid hack to prevent holding the button from being extremely fucked - -// Drawing functions -static void M_DrawGenericMenu(void); -static void M_DrawGenericBackgroundMenu(void); -static void M_DrawCenteredMenu(void); -static void M_DrawAddons(void); -static void M_DrawSkyRoom(void); -static void M_DrawChecklist(void); -static void M_DrawEmblemHints(void); -static void M_DrawPauseMenu(void); -static void M_DrawLevelSelectOnly(boolean leftfade, boolean rightfade); -static void M_DrawServerMenu(void); -static void M_DrawImageDef(void); -//static void M_DrawLoad(void); -static void M_DrawLevelStats(void); -static void M_DrawTimeAttackMenu(void); -//static void M_DrawNightsAttackMenu(void); -//static void M_DrawSetupChoosePlayerMenu(void); -static void M_DrawControl(void); -static void M_DrawVideoMenu(void); -static void M_DrawHUDOptions(void); -static void M_DrawVideoMode(void); -static void M_DrawMonitorToggles(void); -static void M_DrawMPMainMenu(void); -#ifndef NONET -static void M_DrawConnectMenu(void); -#endif -static void M_DrawJoystick(void); -static void M_DrawSetupMultiPlayerMenu(void); - -// Handling functions -#ifndef NONET -static boolean M_CancelConnect(void); -#endif -static boolean M_ExitPandorasBox(void); -static boolean M_QuitMultiPlayerMenu(void); -static void M_HandleAddons(INT32 choice); -static void M_HandleSoundTest(INT32 choice); -static void M_HandleImageDef(INT32 choice); -//static void M_HandleLoadSave(INT32 choice); -static void M_HandleLevelStats(INT32 choice); -#ifndef NONET -static void M_HandleConnectIP(INT32 choice); -#endif -static void M_HandleSetupMultiPlayer(INT32 choice); -static void M_HandleVideoMode(INT32 choice); -static void M_HandleMonitorToggles(INT32 choice); - -// Consvar onchange functions -static void Newgametype_OnChange(void); -static void Dummymenuplayer_OnChange(void); -//static void Dummymares_OnChange(void); -static void Dummystaff_OnChange(void); - -// ========================================================================== -// CONSOLE VARIABLES AND THEIR POSSIBLE VALUES GO HERE. -// ========================================================================== - -consvar_t cv_showfocuslost = CVAR_INIT ("showfocuslost", "Yes", CV_SAVE, CV_YesNo, NULL); - -static CV_PossibleValue_t map_cons_t[] = { - {0,"MIN"}, - {NUMMAPS, "MAX"}, - {0, NULL} -}; -consvar_t cv_nextmap = CVAR_INIT ("nextmap", "1", CV_HIDEN|CV_CALL, map_cons_t, Nextmap_OnChange); - -static CV_PossibleValue_t skins_cons_t[MAXSKINS+1] = {{1, DEFAULTSKIN}}; -consvar_t cv_chooseskin = CVAR_INIT ("chooseskin", DEFAULTSKIN, CV_HIDEN|CV_CALL, skins_cons_t, Nextmap_OnChange); - -// This gametype list is integral for many different reasons. -// When you add gametypes here, don't forget to update them in dehacked.c and doomstat.h! -CV_PossibleValue_t gametype_cons_t[NUMGAMETYPES+1]; - -consvar_t cv_newgametype = CVAR_INIT ("newgametype", "Race", CV_HIDEN|CV_CALL, gametype_cons_t, Newgametype_OnChange); - -static CV_PossibleValue_t serversort_cons_t[] = { - {0,"Ping"}, - {1,"Modified State"}, - {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 ringlimit_cons_t[] = {{-20, "MIN"}, {20, "MAX"}, {0, NULL}}; -static CV_PossibleValue_t liveslimit_cons_t[] = {{-1, "MIN"}, {9, "MAX"}, {0, NULL}}; -/*static CV_PossibleValue_t dummymares_cons_t[] = { - {-1, "END"}, {0,"Overall"}, {1,"Mare 1"}, {2,"Mare 2"}, {3,"Mare 3"}, {4,"Mare 4"}, {5,"Mare 5"}, {6,"Mare 6"}, {7,"Mare 7"}, {8,"Mare 8"}, {0,NULL} -};*/ -static CV_PossibleValue_t dummystaff_cons_t[] = {{0, "MIN"}, {100, "MAX"}, {0, NULL}}; - -static consvar_t cv_dummymenuplayer = CVAR_INIT ("dummymenuplayer", "P1", CV_HIDEN|CV_CALL, dummymenuplayer_cons_t, Dummymenuplayer_OnChange); -static consvar_t cv_dummyteam = CVAR_INIT ("dummyteam", "Spectator", CV_HIDEN, dummyteam_cons_t, NULL); -static consvar_t cv_dummyspectate = CVAR_INIT ("dummyspectate", "Spectator", CV_HIDEN, dummyspectate_cons_t, NULL); -static consvar_t cv_dummyscramble = CVAR_INIT ("dummyscramble", "Random", CV_HIDEN, dummyscramble_cons_t, NULL); -static consvar_t cv_dummyrings = CVAR_INIT ("dummyrings", "0", CV_HIDEN, ringlimit_cons_t, NULL); -static consvar_t cv_dummylives = CVAR_INIT ("dummylives", "0", CV_HIDEN, liveslimit_cons_t, NULL); -static consvar_t cv_dummystaff = CVAR_INIT ("dummystaff", "0", CV_HIDEN|CV_CALL, dummystaff_cons_t, Dummystaff_OnChange); - -static CV_PossibleValue_t dummygpdifficulty_cons_t[] = {{0, "Easy"}, {1, "Normal"}, {2, "Hard"}, {3, "Master"}, {0, NULL}}; -static CV_PossibleValue_t dummygpcup_cons_t[50] = {{1, "TEMP"}}; // A REALLY BIG NUMBER, SINCE THIS IS TEMP UNTIL NEW MENUS - -static consvar_t cv_dummygpdifficulty = CVAR_INIT ("dummygpdifficulty", "Normal", CV_HIDEN, dummygpdifficulty_cons_t, NULL); -static consvar_t cv_dummygpencore = CVAR_INIT ("dummygpencore", "Off", CV_HIDEN, CV_OnOff, NULL); -static consvar_t cv_dummygpcup = CVAR_INIT ("dummygpcup", "TEMP", CV_HIDEN, dummygpcup_cons_t, NULL); - -// ========================================================================== -// ORGANIZATION START. -// ========================================================================== -// Note: Never should we be jumping from one category of menu options to another -// without first going to the Main Menu. -// Note: Ignore the above if you're working with the Pause menu. -// Note: (Prefix)_MainMenu should be the target of all Main Menu options that -// point to submenus. - -// --------- -// Main Menu -// --------- -static menuitem_t MainMenu[] = -{ - {IT_SUBMENU|IT_STRING, NULL, "Extras", {.submenu = &SR_MainDef}, 76}, -#ifdef TESTERS - {IT_GRAYEDOUT, NULL, "1 Player", {NULL}, 84}, -#else - {IT_CALL |IT_STRING, NULL, "1 Player", {.routine = M_SinglePlayerMenu}, 84}, -#endif - {IT_SUBMENU|IT_STRING, NULL, "Multiplayer", {.submenu = &MP_MainDef}, 92}, - {IT_CALL |IT_STRING, NULL, "Options", {.routine = M_Options}, 100}, - /* I don't think is useful at all... */ - {IT_CALL |IT_STRING, NULL, "Addons", {.routine = M_Addons}, 108}, - {IT_CALL |IT_STRING, NULL, "Quit Game", {.routine = M_QuitSRB2}, 116}, -}; - -typedef enum -{ - secrets = 0, - singleplr, - multiplr, - options, - addons, - quitdoom -} main_e; - -static menuitem_t MISC_AddonsMenu[] = -{ - {IT_KEYHANDLER | IT_NOTHING, NULL, "", {.routine = M_HandleAddons}, 0}, // dummy menuitem for the control func -}; - -static menuitem_t MISC_ReplayHutMenu[] = -{ - {IT_KEYHANDLER|IT_NOTHING, NULL, "", {.routine = M_HandleReplayHutList}, 0}, // Dummy menuitem for the replay list - {IT_NOTHING, NULL, "", {NULL}, 0}, // Dummy for handling wrapping to the top of the menu.. -}; - -static menuitem_t MISC_ReplayStartMenu[] = -{ - {IT_CALL |IT_STRING, NULL, "Load Addons and Watch", {.routine = M_HutStartReplay}, 0}, - {IT_CALL |IT_STRING, NULL, "Watch Without Addons", {.routine = M_HutStartReplay}, 10}, - {IT_CALL |IT_STRING, NULL, "Watch Replay", {.routine = M_HutStartReplay}, 10}, - {IT_SUBMENU |IT_STRING, NULL, "Back", {.submenu = &MISC_ReplayHutDef}, 30}, -}; - -static menuitem_t MISC_ReplayOptionsMenu[] = -{ - {IT_CVAR|IT_STRING, NULL, "Record Replays", {.cvar = &cv_recordmultiplayerdemos}, 0}, - {IT_CVAR|IT_STRING, NULL, "Sync Check Interval", {.cvar = &cv_netdemosyncquality}, 10}, -}; - -static tic_t playback_last_menu_interaction_leveltime = 0; -static menuitem_t PlaybackMenu[] = -{ - {IT_CALL | IT_STRING, "M_PHIDE", "Hide Menu (Esc)", {.routine = M_SelectableClearMenus}, 0}, - - {IT_CALL | IT_STRING, "M_PREW", "Rewind ([)", {.routine = M_PlaybackRewind}, 20}, - {IT_CALL | IT_STRING, "M_PPAUSE", "Pause (\\)", {.routine = M_PlaybackPause}, 36}, - {IT_CALL | IT_STRING, "M_PFFWD", "Fast-Forward (])", {.routine = M_PlaybackFastForward}, 52}, - {IT_CALL | IT_STRING, "M_PSTEPB", "Backup Frame ([)", {.routine = M_PlaybackRewind}, 20}, - {IT_CALL | IT_STRING, "M_PRESUM", "Resume", {.routine = M_PlaybackPause}, 36}, - {IT_CALL | IT_STRING, "M_PFADV", "Advance Frame (])", {.routine = M_PlaybackAdvance}, 52}, - - {IT_ARROWS | IT_STRING, "M_PVIEWS", "View Count (- and =)", {.routine = M_PlaybackSetViews}, 72}, - {IT_ARROWS | IT_STRING, "M_PNVIEW", "Viewpoint (1)", {.routine = M_PlaybackAdjustView}, 88}, - {IT_ARROWS | IT_STRING, "M_PNVIEW", "Viewpoint 2 (2)", {.routine = M_PlaybackAdjustView}, 104}, - {IT_ARROWS | IT_STRING, "M_PNVIEW", "Viewpoint 3 (3)", {.routine = M_PlaybackAdjustView}, 120}, - {IT_ARROWS | IT_STRING, "M_PNVIEW", "Viewpoint 4 (4)", {.routine = M_PlaybackAdjustView}, 136}, - - {IT_CALL | IT_STRING, "M_PVIEWS", "Toggle Free Camera (')", {.routine = M_PlaybackToggleFreecam}, 156}, - {IT_CALL | IT_STRING, "M_PEXIT", "Stop Playback", {.routine = M_PlaybackQuit}, 172}, -}; -typedef enum -{ - playback_hide, - playback_rewind, - playback_pause, - playback_fastforward, - playback_backframe, - playback_resume, - playback_advanceframe, - playback_viewcount, - playback_view1, - playback_view2, - playback_view3, - playback_view4, - playback_freecamera, - //playback_moreoptions, - playback_quit -} playback_e; - -// --------------------------------- -// Pause Menu Mode Attacking Edition -// --------------------------------- -static menuitem_t MAPauseMenu[] = -{ - {IT_CALL | IT_STRING, NULL, "Continue", {.routine = M_SelectableClearMenus},48}, - {IT_CALL | IT_STRING, NULL, "Retry", {.routine = M_ModeAttackRetry}, 56}, - {IT_CALL | IT_STRING, NULL, "Abort", {.routine = M_ModeAttackEndGame}, 64}, -}; - -typedef enum -{ - mapause_continue, - mapause_retry, - mapause_abort -} mapause_e; - -// --------------------- -// Pause Menu MP Edition -// --------------------- -static menuitem_t MPauseMenu[] = -{ - {IT_STRING | IT_CALL, NULL, "Addons...", {.routine = M_Addons}, 8}, - {IT_STRING | IT_SUBMENU, NULL, "Scramble Teams...", {.submenu = &MISC_ScrambleTeamDef}, 16}, - {IT_STRING | IT_CALL, NULL, "Switch Map..." , {.routine = M_MapChange}, 24}, - -#ifdef HAVE_DISCORDRPC - {IT_STRING | IT_SUBMENU, NULL, "Ask To Join Requests...", {.submenu = &MISC_DiscordRequestsDef}, 24}, -#endif - - {IT_CALL | IT_STRING, NULL, "Continue", {.routine = M_SelectableClearMenus}, 40}, - {IT_CALL | IT_STRING, NULL, "P1 Setup...", {.routine = M_SetupMultiPlayer}, 48}, // splitscreen - {IT_CALL | IT_STRING, NULL, "P2 Setup...", {.routine = M_SetupMultiPlayer2}, 56}, // splitscreen - {IT_CALL | IT_STRING, NULL, "P3 Setup...", {.routine = M_SetupMultiPlayer3}, 64}, // splitscreen - {IT_CALL | IT_STRING, NULL, "P4 Setup...", {.routine = M_SetupMultiPlayer4}, 72}, // splitscreen - - {IT_STRING | IT_CALL, NULL, "Spectate", {.routine = M_ConfirmSpectate}, 48}, // alone - {IT_STRING | IT_CALL, NULL, "Enter Game", {.routine = M_ConfirmEnterGame}, 48}, // alone - {IT_STRING | IT_CALL, NULL, "Cancel Join", {.routine = M_ConfirmSpectate}, 48}, // alone - {IT_STRING | IT_SUBMENU, NULL, "Switch Team...", {.submenu = &MISC_ChangeTeamDef}, 48}, - {IT_STRING | IT_SUBMENU, NULL, "Enter/Spectate...", {.submenu = &MISC_ChangeSpectateDef}, 48}, - {IT_CALL | IT_STRING, NULL, "Player Setup...", {.routine = M_SetupMultiPlayer}, 56}, // alone - {IT_CALL | IT_STRING, NULL, "Options", {.routine = M_Options}, 64}, - - {IT_CALL | IT_STRING, NULL, "Return to Title", {.routine = M_EndGame}, 80}, - {IT_CALL | IT_STRING, NULL, "Quit Game", {.routine = M_QuitSRB2}, 88}, -}; - -typedef enum -{ - mpause_addons = 0, - mpause_scramble, - mpause_switchmap, -#ifdef HAVE_DISCORDRPC - mpause_discordrequests, -#endif - - mpause_continue, - mpause_psetupsplit, - mpause_psetupsplit2, - mpause_psetupsplit3, - mpause_psetupsplit4, - - mpause_spectate, - mpause_entergame, - mpause_canceljoin, - mpause_switchteam, - mpause_switchspectate, - mpause_psetup, - mpause_options, - - mpause_title, - mpause_quit -} mpause_e; - -// --------------------- -// Pause Menu SP Edition -// --------------------- -static menuitem_t SPauseMenu[] = -{ - // Pandora's Box will be shifted up if both options are available - {IT_CALL | IT_STRING, NULL, "Pandora's Box...", {.routine = M_PandorasBox}, 16}, - {IT_CALL | IT_STRING, NULL, "Medal Hints...", {.routine = M_EmblemHints}, 24}, - //{IT_CALL | IT_STRING, NULL, "Level Select...", M_LoadGameLevelSelect, 32}, - - {IT_CALL | IT_STRING, NULL, "Continue", {.routine = M_SelectableClearMenus},48}, - {IT_CALL | IT_STRING, NULL, "Retry", {.routine = M_Retry}, 56}, - {IT_CALL | IT_STRING, NULL, "Options", {.routine = M_Options}, 64}, - - {IT_CALL | IT_STRING, NULL, "Return to Title", {.routine = M_EndGame}, 80}, - {IT_CALL | IT_STRING, NULL, "Quit Game", {.routine = M_QuitSRB2}, 88}, -}; - -typedef enum -{ - spause_pandora = 0, - spause_hints, - //spause_levelselect, - - spause_continue, - spause_retry, - spause_options, - spause_title, - spause_quit -} spause_e; - -#ifdef HAVE_DISCORDRPC -static menuitem_t MISC_DiscordRequestsMenu[] = -{ - {IT_KEYHANDLER|IT_NOTHING, NULL, "", {.routine = M_HandleDiscordRequests}, 0}, -}; -#endif - -// ----------------- -// Misc menu options -// ----------------- -// Prefix: MISC_ -static menuitem_t MISC_ScrambleTeamMenu[] = -{ - {IT_STRING|IT_CVAR, NULL, "Scramble Method", {.cvar = &cv_dummyscramble}, 30}, - {IT_WHITESTRING|IT_CALL, NULL, "Confirm", {.routine = M_ConfirmTeamScramble}, 90}, -}; - -static menuitem_t MISC_ChangeTeamMenu[] = -{ - {IT_STRING|IT_CVAR, NULL, "Player", {.cvar = &cv_dummymenuplayer}, 30}, - {IT_STRING|IT_CVAR, NULL, "Team", {.cvar = &cv_dummyteam}, 40}, - {IT_WHITESTRING|IT_CALL, NULL, "Confirm", {.routine = M_ConfirmTeamChange}, 90}, -}; - -static menuitem_t MISC_ChangeSpectateMenu[] = -{ - {IT_STRING|IT_CVAR, NULL, "Player", {.cvar = &cv_dummymenuplayer}, 30}, - {IT_STRING|IT_CVAR, NULL, "Status", {.cvar = &cv_dummyspectate}, 40}, - {IT_WHITESTRING|IT_CALL, NULL, "Confirm", {.routine = M_ConfirmSpectateChange}, 90}, -}; - -static menuitem_t MISC_ChangeLevelMenu[] = -{ - {IT_STRING|IT_CVAR, NULL, "Game Type", {.cvar = &cv_newgametype}, 68}, - {IT_STRING|IT_CVAR, NULL, "Level", {.cvar = &cv_nextmap}, 78}, - {IT_WHITESTRING|IT_CALL, NULL, "Change Level", {.routine = M_ChangeLevel}, 130}, -}; - -static menuitem_t MISC_HelpMenu[] = -{ - {IT_KEYHANDLER | IT_NOTHING, NULL, "MANUAL00", {.routine = M_HandleImageDef}, 0}, - {IT_KEYHANDLER | IT_NOTHING, NULL, "MANUAL01", {.routine = M_HandleImageDef}, 1}, - {IT_KEYHANDLER | IT_NOTHING, NULL, "MANUAL02", {.routine = M_HandleImageDef}, 1}, - {IT_KEYHANDLER | IT_NOTHING, NULL, "MANUAL03", {.routine = M_HandleImageDef}, 1}, - {IT_KEYHANDLER | IT_NOTHING, NULL, "MANUAL04", {.routine = M_HandleImageDef}, 1}, - {IT_KEYHANDLER | IT_NOTHING, NULL, "MANUAL05", {.routine = M_HandleImageDef}, 1}, - {IT_KEYHANDLER | IT_NOTHING, NULL, "MANUAL06", {.routine = M_HandleImageDef}, 1}, - {IT_KEYHANDLER | IT_NOTHING, NULL, "MANUAL07", {.routine = M_HandleImageDef}, 1}, - {IT_KEYHANDLER | IT_NOTHING, NULL, "MANUAL08", {.routine = M_HandleImageDef}, 1}, - {IT_KEYHANDLER | IT_NOTHING, NULL, "MANUAL09", {.routine = M_HandleImageDef}, 1}, - {IT_KEYHANDLER | IT_NOTHING, NULL, "MANUAL10", {.routine = M_HandleImageDef}, 1}, - {IT_KEYHANDLER | IT_NOTHING, NULL, "MANUAL11", {.routine = M_HandleImageDef}, 1}, - {IT_KEYHANDLER | IT_NOTHING, NULL, "MANUAL12", {.routine = M_HandleImageDef}, 1}, - {IT_KEYHANDLER | IT_NOTHING, NULL, "MANUAL99", {.routine = M_HandleImageDef}, 0}, -}; - -// -------------------------------- -// Sky Room and all of its submenus -// -------------------------------- -// Prefix: SR_ - -// Pause Menu Pandora's Box Options -static menuitem_t SR_PandorasBox[] = -{ - {IT_STRING | IT_CVAR, NULL, "Rings", {.cvar = &cv_dummyrings}, 20}, - {IT_STRING | IT_CVAR, NULL, "Lives", {.cvar = &cv_dummylives}, 30}, - - {IT_STRING | IT_CVAR, NULL, "Gravity", {.cvar = &cv_gravity}, 60}, - - {IT_STRING | IT_CALL, NULL, "Get All Emeralds", {.routine = M_GetAllEmeralds}, 90}, - {IT_STRING | IT_CALL, NULL, "Destroy All Robots", {.routine = M_DestroyRobots}, 100}, - - {IT_STRING | IT_CALL, NULL, "Ultimate Cheat", {.routine = M_UltimateCheat}, 130}, -}; - -// Sky Room Custom Unlocks -static menuitem_t SR_MainMenu[] = -{ -#ifndef TESTERS - {IT_STRING|IT_SUBMENU, NULL, "Unlockables", {.submenu = &SR_UnlockChecklistDef}, 100}, -#endif - {IT_CALL|IT_STRING|IT_CALL_NOTMODIFIED, NULL, "Statistics", {.routine = M_Statistics}, 108}, - {IT_CALL|IT_STRING, NULL, "Replay Hut", {.routine = M_ReplayHut}, 116}, - {IT_DISABLED, NULL, "", {NULL}, 0}, // Custom1 - {IT_DISABLED, NULL, "", {NULL}, 0}, // Custom2 - {IT_DISABLED, NULL, "", {NULL}, 0}, // Custom3 - {IT_DISABLED, NULL, "", {NULL}, 0}, // Custom4 - {IT_DISABLED, NULL, "", {NULL}, 0}, // Custom5 - {IT_DISABLED, NULL, "", {NULL}, 0}, // Custom6 - {IT_DISABLED, NULL, "", {NULL}, 0}, // Custom7 - {IT_DISABLED, NULL, "", {NULL}, 0}, // Custom8 - {IT_DISABLED, NULL, "", {NULL}, 0}, // Custom9 - {IT_DISABLED, NULL, "", {NULL}, 0}, // Custom10 - {IT_DISABLED, NULL, "", {NULL}, 0}, // Custom11 - {IT_DISABLED, NULL, "", {NULL}, 0}, // Custom12 - {IT_DISABLED, NULL, "", {NULL}, 0}, // Custom13 - {IT_DISABLED, NULL, "", {NULL}, 0}, // Custom14 - {IT_DISABLED, NULL, "", {NULL}, 0}, // Custom15 - {IT_DISABLED, NULL, "", {NULL}, 0}, // Custom16 - {IT_DISABLED, NULL, "", {NULL}, 0}, // Custom17 - {IT_DISABLED, NULL, "", {NULL}, 0}, // Custom18 - {IT_DISABLED, NULL, "", {NULL}, 0}, // Custom19 - {IT_DISABLED, NULL, "", {NULL}, 0}, // Custom20 - {IT_DISABLED, NULL, "", {NULL}, 0}, // Custom21 - {IT_DISABLED, NULL, "", {NULL}, 0}, // Custom22 - {IT_DISABLED, NULL, "", {NULL}, 0}, // Custom23 - {IT_DISABLED, NULL, "", {NULL}, 0}, // Custom24 - {IT_DISABLED, NULL, "", {NULL}, 0}, // Custom25 - {IT_DISABLED, NULL, "", {NULL}, 0}, // Custom26 - {IT_DISABLED, NULL, "", {NULL}, 0}, // Custom27 - {IT_DISABLED, NULL, "", {NULL}, 0}, // Custom28 - {IT_DISABLED, NULL, "", {NULL}, 0}, // Custom29 - {IT_DISABLED, NULL, "", {NULL}, 0}, // Custom30 - {IT_DISABLED, NULL, "", {NULL}, 0}, // Custom31 - {IT_DISABLED, NULL, "", {NULL}, 0}, // Custom32 - -}; - -/*static menuitem_t SR_LevelSelectMenu[] = -{ - {IT_STRING|IT_CVAR, NULL, "Level", &cv_nextmap, 78}, - {IT_WHITESTRING|IT_CALL, NULL, "Start", M_LevelSelectWarp, 130}, -};*/ - -static menuitem_t SR_UnlockChecklistMenu[] = -{ - {IT_SUBMENU | IT_STRING, NULL, "NEXT", {.submenu = &MainDef}, 192}, -}; - -static menuitem_t SR_EmblemHintMenu[] = -{ - {IT_STRING|IT_CVAR, NULL, "Medal Radar", {.cvar = &cv_itemfinder}, 10}, - {IT_WHITESTRING|IT_SUBMENU, NULL, "Back", {.submenu = &SPauseDef}, 20} -}; - -// -------------------------------- -// 1 Player and all of its submenus -// -------------------------------- -// Prefix: SP_ - -// Single Player Main -static menuitem_t SP_MainMenu[] = -{ - {IT_STRING|IT_CALL, NULL, "Grand Prix", {.routine = M_GrandPrixTemp}, 92}, - {IT_SECRET, NULL, "Time Attack", {.routine = M_TimeAttack}, 100}, - {IT_SECRET, NULL, "Break the Capsules", {.routine = M_BreakTheCapsules}, 108}, -}; - -enum -{ - spgrandprix, - sptimeattack, - spbreakthecapsules, -}; - -// Single Player Load Game -static menuitem_t SP_GrandPrixPlaceholderMenu[] = -{ - {IT_STRING|IT_CVAR, NULL, "Character", {.cvar = &cv_chooseskin}, 10}, - {IT_STRING|IT_CVAR, NULL, "Color", {.cvar = &cv_playercolor[0]}, 20}, - - {IT_STRING|IT_CVAR, NULL, "Difficulty", {.cvar = &cv_dummygpdifficulty}, 40}, - {IT_STRING|IT_CVAR, NULL, "Encore Mode", {.cvar = &cv_dummygpencore}, 50}, - - {IT_STRING|IT_CVAR, NULL, "Cup", {.cvar = &cv_dummygpcup}, 70}, - {IT_STRING|IT_CALL, NULL, "Start", {.routine = M_StartGrandPrix}, 80}, -}; - -// Single Player Time Attack -static menuitem_t SP_TimeAttackMenu[] = -{ - {IT_STRING|IT_CVAR|IT_CV_STRING, NULL, "Name", {.cvar = &cv_playername[0]}, 0}, - {IT_STRING|IT_CVAR, NULL, "Character", {.cvar = &cv_chooseskin}, 13}, - {IT_STRING|IT_CVAR, NULL, "Color", {.cvar = &cv_playercolor[0]}, 26}, - {IT_STRING|IT_CVAR, NULL, "Level", {.cvar = &cv_nextmap}, 78}, - - {IT_DISABLED, NULL, "Guest...", {.submenu = &SP_GuestReplayDef}, 98}, - {IT_DISABLED, NULL, "Replay...", {.submenu = &SP_ReplayDef}, 108}, - {IT_WHITESTRING|IT_SUBMENU, NULL, "Ghosts...", {.submenu = &SP_GhostDef}, 118}, - {IT_WHITESTRING|IT_CALL|IT_CALL_NOTMODIFIED, NULL, "Start", {.routine = M_ChooseTimeAttack}, 130}, -}; - -enum -{ - taname, - taplayer, - tacolor, - talevel, - - taguest, - tareplay, - taghost, - tastart -}; - -static menuitem_t SP_ReplayMenu[] = -{ - {IT_WHITESTRING|IT_CALL, NULL, "Replay Best Time", {.routine = M_ReplayTimeAttack}, 90}, - {IT_WHITESTRING|IT_CALL, NULL, "Replay Best Lap", {.routine = M_ReplayTimeAttack}, 98}, - - {IT_WHITESTRING|IT_CALL, NULL, "Replay Last", {.routine = M_ReplayTimeAttack}, 106}, - {IT_WHITESTRING|IT_CALL, NULL, "Replay Guest", {.routine = M_ReplayTimeAttack}, 114}, - {IT_WHITESTRING|IT_KEYHANDLER, NULL, "Replay Staff",{.routine = M_HandleStaffReplay},122}, - - {IT_WHITESTRING|IT_SUBMENU, NULL, "Back", {.submenu = &SP_TimeAttackDef}, 130} -}; - -/*static menuitem_t SP_NightsReplayMenu[] = -{ - {IT_WHITESTRING|IT_CALL, NULL, "Replay Best Score", M_ReplayTimeAttack, 0}, - {IT_WHITESTRING|IT_CALL, NULL, "Replay Best Time", M_ReplayTimeAttack,16}, - - {IT_WHITESTRING|IT_CALL, NULL, "Replay Last", M_ReplayTimeAttack,21}, - {IT_WHITESTRING|IT_CALL, NULL, "Replay Guest", M_ReplayTimeAttack,29}, - {IT_WHITESTRING|IT_KEYHANDLER, NULL, "Replay Staff",M_HandleStaffReplay,37}, - - {IT_WHITESTRING|IT_SUBMENU, NULL, "Back", &SP_NightsAttackDef, 50} -};*/ - -static menuitem_t SP_GuestReplayMenu[] = -{ - {IT_WHITESTRING|IT_CALL, NULL, "Save Best Time as Guest", {.routine = M_SetGuestReplay}, 94}, - {IT_WHITESTRING|IT_CALL, NULL, "Save Best Lap as Guest", {.routine = M_SetGuestReplay},102}, - {IT_WHITESTRING|IT_CALL, NULL, "Save Last as Guest", {.routine = M_SetGuestReplay},110}, - - {IT_WHITESTRING|IT_CALL, NULL, "Delete Guest Replay", {.routine = M_SetGuestReplay},120}, - - {IT_WHITESTRING|IT_SUBMENU, NULL, "Back", {.submenu = &SP_TimeAttackDef}, 130} -}; - -/*static menuitem_t SP_NightsGuestReplayMenu[] = -{ - {IT_WHITESTRING|IT_CALL, NULL, "Save Best Score as Guest", M_SetGuestReplay, 8}, - {IT_WHITESTRING|IT_CALL, NULL, "Save Best Time as Guest", M_SetGuestReplay,16}, - {IT_WHITESTRING|IT_CALL, NULL, "Save Last as Guest", M_SetGuestReplay,24}, - - {IT_WHITESTRING|IT_CALL, NULL, "Delete Guest Replay", M_SetGuestReplay,37}, - - {IT_WHITESTRING|IT_SUBMENU, NULL, "Back", &SP_NightsAttackDef, 50} -};*/ - -static menuitem_t SP_GhostMenu[] = -{ - {IT_STRING|IT_CVAR, NULL, "Best Time", {.cvar = &cv_ghost_besttime}, 88}, - {IT_STRING|IT_CVAR, NULL, "Best Lap", {.cvar = &cv_ghost_bestlap}, 96}, - {IT_STRING|IT_CVAR, NULL, "Last", {.cvar = &cv_ghost_last}, 104}, - {IT_DISABLED, NULL, "Guest", {.cvar = &cv_ghost_guest}, 112}, - {IT_DISABLED, NULL, "Staff Attack", {.cvar = &cv_ghost_staff}, 120}, - - {IT_WHITESTRING|IT_SUBMENU, NULL, "Back", {.submenu = &SP_TimeAttackDef}, 130} -}; - -/*static menuitem_t SP_NightsGhostMenu[] = -{ - {IT_STRING|IT_CVAR, NULL, "Best Score", &cv_ghost_bestscore, 0}, - {IT_STRING|IT_CVAR, NULL, "Best Time", &cv_ghost_besttime, 8}, - {IT_STRING|IT_CVAR, NULL, "Last", &cv_ghost_last, 16}, - - {IT_STRING|IT_CVAR, NULL, "Guest", &cv_ghost_guest, 29}, - {IT_STRING|IT_CVAR, NULL, "Staff Attack",&cv_ghost_staff, 37}, - - {IT_WHITESTRING|IT_SUBMENU, NULL, "Back", &SP_NightsAttackDef, 50} -};*/ - -// Single Player Nights Attack -/*static menuitem_t SP_NightsAttackMenu[] = -{ - {IT_STRING|IT_CVAR, NULL, "Level", &cv_nextmap, 44}, - {IT_STRING|IT_CVAR, NULL, "Show Records For", &cv_dummymares, 54}, - - {IT_DISABLED, NULL, "Guest Option...", &SP_NightsGuestReplayDef, 108}, - {IT_DISABLED, NULL, "Replay...", &SP_NightsReplayDef, 118}, - {IT_DISABLED, NULL, "Ghosts...", &SP_NightsGhostDef, 128}, - {IT_WHITESTRING|IT_CALL|IT_CALL_NOTMODIFIED, NULL, "Start", M_ChooseNightsAttack, 138}, -};*/ - -enum -{ - nalevel, - narecords, - - naguest, - nareplay, - naghost, - nastart -}; - -// Statistics -static menuitem_t SP_LevelStatsMenu[] = -{ - {IT_KEYHANDLER | IT_NOTHING, NULL, "", {.routine = M_HandleLevelStats}, '\0'}, // dummy menuitem for the control func -}; - -// A rare case. -// External files modify this menu, so we can't call it static. -// And I'm too lazy to go through and rename it everywhere. ARRGH! -#define M_ChoosePlayer NULL -menuitem_t PlayerMenu[MAXSKINS]; - -// ----------------------------------- -// Multiplayer and all of its submenus -// ----------------------------------- -// Prefix: MP_ - -static menuitem_t MP_MainMenu[] = -{ - {IT_HEADER, NULL, "Players", {NULL}, 0}, - {IT_STRING|IT_CVAR, NULL, "Number of local players", {.cvar = &cv_splitplayers}, 10}, - - {IT_STRING|IT_KEYHANDLER,NULL, "Player setup...", {.routine = M_SetupMultiHandler}, 18}, - - {IT_HEADER, NULL, "Host a game", {NULL}, 100-24}, -#ifndef NOMENUHOST - {IT_STRING|IT_CALL, NULL, "Internet/LAN...", {.routine = M_StartServerMenu}, 110-24}, -#else - {IT_GRAYEDOUT, NULL, "Internet/LAN...", {NULL}, 110-24}, -#endif -#ifdef TESTERS - {IT_GRAYEDOUT, NULL, "Offline...", {NULL}, 118-24}, -#else - {IT_STRING|IT_CALL, NULL, "Offline...", {.routine = M_StartOfflineServerMenu}, 118-24}, -#endif - - {IT_HEADER, NULL, "Join a game", {NULL}, 132-24}, -#ifndef NONET - {IT_STRING|IT_CALL, NULL, "Internet server browser...",{.routine = M_ConnectMenuModChecks}, 142-24}, - {IT_STRING|IT_KEYHANDLER, NULL, "Specify IPv4 address:", {.routine = M_HandleConnectIP}, 150-24}, -#else - {IT_GRAYEDOUT, NULL, "Internet server browser...", {NULL}, 142-24}, - {IT_GRAYEDOUT, NULL, "Specify IPv4 address:", {NULL}, 150-24}, -#endif - //{IT_HEADER, NULL, "Player setup", NULL, 80}, - //{IT_STRING|IT_CALL, NULL, "Name, character, color...", M_SetupMultiPlayer, 90}, -}; - -#ifndef NONET - -static menuitem_t MP_ServerMenu[] = -{ - {IT_STRING|IT_CVAR, NULL, "Max. Player Count", {.cvar = &cv_maxplayers}, 10}, - {IT_STRING|IT_CVAR, NULL, "Advertise", {.cvar = &cv_advertise}, 20}, - {IT_STRING|IT_CVAR|IT_CV_STRING, NULL, "Server Name", {.cvar = &cv_servername}, 30}, - - {IT_STRING|IT_CVAR, NULL, "Game Type", {.cvar = &cv_newgametype}, 68}, - {IT_STRING|IT_CVAR, NULL, "Level", {.cvar = &cv_nextmap}, 78}, - - {IT_WHITESTRING|IT_CALL, NULL, "Start", {.routine = M_StartServer}, 130}, -}; - -#endif - -// Separated offline and normal servers. -static menuitem_t MP_OfflineServerMenu[] = -{ - {IT_STRING|IT_CVAR, NULL, "Game Type", {.cvar = &cv_newgametype}, 68}, - {IT_STRING|IT_CVAR, NULL, "Level", {.cvar = &cv_nextmap}, 78}, - - {IT_WHITESTRING|IT_CALL, NULL, "Start", {.routine = M_StartServer}, 130}, -}; - -static menuitem_t MP_PlayerSetupMenu[] = -{ - {IT_KEYHANDLER | IT_STRING, NULL, "Name", {.routine = M_HandleSetupMultiPlayer}, 0}, - {IT_KEYHANDLER | IT_STRING, NULL, "Character", {.routine = M_HandleSetupMultiPlayer}, 16}, // Tails 01-18-2001 - {IT_KEYHANDLER | IT_STRING, NULL, "Follower", {.routine = M_HandleSetupMultiPlayer}, 26}, - {IT_KEYHANDLER | IT_STRING, NULL, "Color", {.routine = M_HandleSetupMultiPlayer}, 152}, -}; - -#ifndef NONET -static menuitem_t MP_ConnectMenu[] = -{ - {IT_STRING | IT_CVAR, NULL, "Sort By", {.cvar = &cv_serversort}, 4}, - {IT_STRING | IT_KEYHANDLER, NULL, "Page", {.routine = M_HandleServerPage}, 12}, - {IT_STRING | IT_CALL, NULL, "Refresh", {.routine = M_Refresh}, 20}, - - {IT_STRING | IT_SPACE, NULL, "", {.routine = M_Connect}, 36}, - {IT_STRING | IT_SPACE, NULL, "", {.routine = M_Connect}, 48}, - {IT_STRING | IT_SPACE, NULL, "", {.routine = M_Connect}, 60}, - {IT_STRING | IT_SPACE, NULL, "", {.routine = M_Connect}, 72}, - {IT_STRING | IT_SPACE, NULL, "", {.routine = M_Connect}, 84}, - {IT_STRING | IT_SPACE, NULL, "", {.routine = M_Connect}, 96}, - {IT_STRING | IT_SPACE, NULL, "", {.routine = M_Connect}, 108}, - {IT_STRING | IT_SPACE, NULL, "", {.routine = M_Connect}, 120}, - {IT_STRING | IT_SPACE, NULL, "", {.routine = M_Connect}, 132}, - {IT_STRING | IT_SPACE, NULL, "", {.routine = M_Connect}, 144}, - {IT_STRING | IT_SPACE, NULL, "", {.routine = M_Connect}, 156}, -}; - -enum -{ - mp_connect_sort, - mp_connect_page, - mp_connect_refresh, - FIRSTSERVERLINE -}; -#endif - -// ------------------------------------ -// Options and most (?) of its submenus -// ------------------------------------ -// Prefix: OP_ -static menuitem_t OP_MainMenu[] = -{ - {IT_SUBMENU|IT_STRING, NULL, "Control Setup...", {.submenu = &OP_ControlsDef}, 10}, - - {IT_SUBMENU|IT_STRING, NULL, "Video Options...", {.submenu = &OP_VideoOptionsDef}, 30}, - {IT_SUBMENU|IT_STRING, NULL, "Sound Options...", {.submenu = &OP_SoundOptionsDef}, 40}, - - {IT_SUBMENU|IT_STRING, NULL, "HUD Options...", {.submenu = &OP_HUDOptionsDef}, 60}, - {IT_SUBMENU|IT_STRING, NULL, "Gameplay Options...", {.submenu = &OP_GameOptionsDef}, 70}, - {IT_SUBMENU|IT_STRING, NULL, "Server Options...", {.submenu = &OP_ServerOptionsDef}, 80}, - - {IT_SUBMENU|IT_STRING, NULL, "Data Options...", {.submenu = &OP_DataOptionsDef}, 100}, - - {IT_CALL|IT_STRING, NULL, "Tricks & Secrets (F1)", {.routine = M_Manual}, 120}, - {IT_CALL|IT_STRING, NULL, "Play Credits", {.routine = M_Credits}, 130}, -}; - -static menuitem_t OP_ControlsMenu[] = -{ - {IT_CALL | IT_STRING, NULL, "Player 1 Controls...", {.routine = M_Setup1PControlsMenu}, 10}, - {IT_CALL | IT_STRING, NULL, "Player 2 Controls...", {.routine = M_Setup2PControlsMenu}, 20}, - - {IT_CALL | IT_STRING, NULL, "Player 3 Controls...", {.routine = M_Setup3PControlsMenu}, 30}, - {IT_CALL | IT_STRING, NULL, "Player 4 Controls...", {.routine = M_Setup4PControlsMenu}, 40}, - - {IT_STRING | IT_CVAR, NULL, "Controls per key", {.cvar = &cv_controlperkey}, 60}, -}; - -static menuitem_t OP_AllControlsMenu[] = -{ - {IT_SUBMENU|IT_STRING, NULL, "Gamepad Options...", {.submenu = &OP_Joystick1Def}, 0}, - {IT_CALL|IT_STRING, NULL, "Reset to defaults", {.routine = M_ResetControls}, 8}, - //{IT_SPACE, NULL, NULL, NULL, 0}, - {IT_HEADER, NULL, "Gameplay Controls", {NULL}, 0}, - {IT_SPACE, NULL, NULL, {NULL}, 0}, - {IT_CONTROL, NULL, "Accelerate", {.routine = M_ChangeControl}, gc_accelerate }, - {IT_CONTROL, NULL, "Turn Left", {.routine = M_ChangeControl}, gc_turnleft }, - {IT_CONTROL, NULL, "Turn Right", {.routine = M_ChangeControl}, gc_turnright }, - {IT_CONTROL, NULL, "Drift", {.routine = M_ChangeControl}, gc_drift }, - {IT_CONTROL, NULL, "Brake", {.routine = M_ChangeControl}, gc_brake }, - {IT_CONTROL, NULL, "Spindash", {.routine = M_ChangeControl}, gc_spindash }, - {IT_CONTROL, NULL, "Use/Throw Item", {.routine = M_ChangeControl}, gc_fire }, - {IT_CONTROL, NULL, "Aim Forward", {.routine = M_ChangeControl}, gc_aimforward }, - {IT_CONTROL, NULL, "Aim Backward", {.routine = M_ChangeControl}, gc_aimbackward}, - {IT_CONTROL, NULL, "Look Backward", {.routine = M_ChangeControl}, gc_lookback }, - {IT_HEADER, NULL, "Miscelleanous Controls", {NULL}, 0}, - {IT_SPACE, NULL, NULL, {NULL}, 0}, - {IT_CONTROL, NULL, "Chat", {.routine = M_ChangeControl}, gc_talkkey }, - //{IT_CONTROL, NULL, "Team Chat", M_ChangeControl, gc_teamkey }, - {IT_CONTROL, NULL, "Show Rankings", {.routine = M_ChangeControl}, gc_scores }, - {IT_CONTROL, NULL, "Change Viewpoint", {.routine = M_ChangeControl}, gc_viewpoint }, - {IT_CONTROL, NULL, "Reset Camera", {.routine = M_ChangeControl}, gc_camreset }, - {IT_CONTROL, NULL, "Toggle First-Person", {.routine = M_ChangeControl}, gc_camtoggle }, - {IT_CONTROL, NULL, "Pause", {.routine = M_ChangeControl}, gc_pause }, - {IT_CONTROL, NULL, "Screenshot", {.routine = M_ChangeControl}, gc_screenshot }, - {IT_CONTROL, NULL, "Toggle GIF Recording", {.routine = M_ChangeControl}, gc_recordgif }, - {IT_CONTROL, NULL, "Open/Close Menu (ESC)", {.routine = M_ChangeControl}, gc_systemmenu }, - {IT_CONTROL, NULL, "Developer Console", {.routine = M_ChangeControl}, gc_console }, - {IT_HEADER, NULL, "Spectator Controls", {NULL}, 0}, - {IT_SPACE, NULL, NULL, {NULL}, 0}, - {IT_CONTROL, NULL, "Become Spectator", {.routine = M_ChangeControl}, gc_spectate }, - {IT_CONTROL, NULL, "Look Up", {.routine = M_ChangeControl}, gc_lookup }, - {IT_CONTROL, NULL, "Look Down", {.routine = M_ChangeControl}, gc_lookdown }, - {IT_CONTROL, NULL, "Center View", {.routine = M_ChangeControl}, gc_centerview }, - {IT_HEADER, NULL, "Custom Lua Actions", {NULL}, 0}, - {IT_SPACE, NULL, NULL, {NULL}, 0}, - {IT_CONTROL, NULL, "Custom Action 1", {.routine = M_ChangeControl}, gc_custom1 }, - {IT_CONTROL, NULL, "Custom Action 2", {.routine = M_ChangeControl}, gc_custom2 }, - {IT_CONTROL, NULL, "Custom Action 3", {.routine = M_ChangeControl}, gc_custom3 }, -}; - -static menuitem_t OP_Joystick1Menu[] = -{ - {IT_STRING | IT_CALL, NULL, "Select Gamepad..." , {.routine = M_Setup1PJoystickMenu}, 10}, - {IT_STRING | IT_CVAR, NULL, "Aim Forward/Back" , {.cvar = &cv_aimaxis[0]} , 30}, - {IT_STRING | IT_CVAR, NULL, "Turn Left/Right" , {.cvar = &cv_turnaxis[0]} , 40}, - {IT_STRING | IT_CVAR, NULL, "Accelerate" , {.cvar = &cv_moveaxis[0]} , 50}, - {IT_STRING | IT_CVAR, NULL, "Brake" , {.cvar = &cv_brakeaxis[0]} , 60}, - {IT_STRING | IT_CVAR, NULL, "Drift" , {.cvar = &cv_driftaxis[0]} , 70}, - {IT_STRING | IT_CVAR, NULL, "Use Item" , {.cvar = &cv_fireaxis[0]} , 80}, - {IT_STRING | IT_CVAR, NULL, "Look Up/Down" , {.cvar = &cv_lookaxis[0]} , 90}, -}; - -static menuitem_t OP_Joystick2Menu[] = -{ - {IT_STRING | IT_CALL, NULL, "Select Gamepad..." , {.routine = M_Setup2PJoystickMenu}, 10}, - {IT_STRING | IT_CVAR, NULL, "Aim Forward/Back" , {.cvar = &cv_aimaxis[1]} , 30}, - {IT_STRING | IT_CVAR, NULL, "Turn Left/Right" , {.cvar = &cv_turnaxis[1]} , 40}, - {IT_STRING | IT_CVAR, NULL, "Accelerate" , {.cvar = &cv_moveaxis[1]} , 50}, - {IT_STRING | IT_CVAR, NULL, "Brake" , {.cvar = &cv_brakeaxis[1]} , 60}, - {IT_STRING | IT_CVAR, NULL, "Drift" , {.cvar = &cv_driftaxis[1]} , 70}, - {IT_STRING | IT_CVAR, NULL, "Use Item" , {.cvar = &cv_fireaxis[1]} , 80}, - {IT_STRING | IT_CVAR, NULL, "Look Up/Down" , {.cvar = &cv_lookaxis[1]} , 90}, -}; - -static menuitem_t OP_Joystick3Menu[] = -{ - {IT_STRING | IT_CALL, NULL, "Select Gamepad..." , {.routine = M_Setup3PJoystickMenu}, 10}, - {IT_STRING | IT_CVAR, NULL, "Aim Forward/Back" , {.cvar = &cv_aimaxis[2]} , 30}, - {IT_STRING | IT_CVAR, NULL, "Turn Left/Right" , {.cvar = &cv_turnaxis[2]} , 40}, - {IT_STRING | IT_CVAR, NULL, "Accelerate" , {.cvar = &cv_moveaxis[2]} , 50}, - {IT_STRING | IT_CVAR, NULL, "Brake" , {.cvar = &cv_brakeaxis[2]} , 60}, - {IT_STRING | IT_CVAR, NULL, "Drift" , {.cvar = &cv_driftaxis[2]} , 70}, - {IT_STRING | IT_CVAR, NULL, "Use Item" , {.cvar = &cv_fireaxis[2]} , 80}, - {IT_STRING | IT_CVAR, NULL, "Look Up/Down" , {.cvar = &cv_lookaxis[2]} , 90}, -}; - -static menuitem_t OP_Joystick4Menu[] = -{ - {IT_STRING | IT_CALL, NULL, "Select Gamepad..." , {.routine = M_Setup4PJoystickMenu}, 10}, - {IT_STRING | IT_CVAR, NULL, "Aim Forward/Back" , {.cvar = &cv_aimaxis[3]} , 30}, - {IT_STRING | IT_CVAR, NULL, "Turn Left/Right" , {.cvar = &cv_turnaxis[3]} , 40}, - {IT_STRING | IT_CVAR, NULL, "Accelerate" , {.cvar = &cv_moveaxis[3]} , 50}, - {IT_STRING | IT_CVAR, NULL, "Brake" , {.cvar = &cv_brakeaxis[3]} , 60}, - {IT_STRING | IT_CVAR, NULL, "Drift" , {.cvar = &cv_driftaxis[3]} , 70}, - {IT_STRING | IT_CVAR, NULL, "Use Item" , {.cvar = &cv_fireaxis[3]} , 80}, - {IT_STRING | IT_CVAR, NULL, "Look Up/Down" , {.cvar = &cv_lookaxis[3]} , 90}, -}; - -static menuitem_t OP_JoystickSetMenu[] = -{ - {IT_CALL | IT_NOTHING, "None", NULL, {.routine = M_AssignJoystick}, LINEHEIGHT+5}, - {IT_CALL | IT_NOTHING, "", NULL, {.routine = M_AssignJoystick}, (LINEHEIGHT*2)+5}, - {IT_CALL | IT_NOTHING, "", NULL, {.routine = M_AssignJoystick}, (LINEHEIGHT*3)+5}, - {IT_CALL | IT_NOTHING, "", NULL, {.routine = M_AssignJoystick}, (LINEHEIGHT*4)+5}, - {IT_CALL | IT_NOTHING, "", NULL, {.routine = M_AssignJoystick}, (LINEHEIGHT*5)+5}, - {IT_CALL | IT_NOTHING, "", NULL, {.routine = M_AssignJoystick}, (LINEHEIGHT*6)+5}, - {IT_CALL | IT_NOTHING, "", NULL, {.routine = M_AssignJoystick}, (LINEHEIGHT*7)+5}, - {IT_CALL | IT_NOTHING, "", NULL, {.routine = M_AssignJoystick}, (LINEHEIGHT*8)+5}, -}; - -/* -static menuitem_t OP_MouseOptionsMenu[] = -{ - {IT_STRING | IT_CVAR, NULL, "Use Mouse", &cv_usemouse, 10}, - - - {IT_STRING | IT_CVAR, NULL, "First-Person MouseLook", &cv_alwaysfreelook, 30}, - {IT_STRING | IT_CVAR, NULL, "Third-Person MouseLook", &cv_chasefreelook, 40}, - {IT_STRING | IT_CVAR, NULL, "Mouse Move", &cv_mousemove, 50}, - {IT_STRING | IT_CVAR, NULL, "Invert Mouse", &cv_invertmouse, 60}, - {IT_STRING | IT_CVAR | IT_CV_SLIDER, - NULL, "Mouse X Speed", &cv_mousesens, 70}, - {IT_STRING | IT_CVAR | IT_CV_SLIDER, - NULL, "Mouse Y Speed", &cv_mouseysens, 80}, -}; -*/ - -static menuitem_t OP_VideoOptionsMenu[] = -{ - {IT_STRING | IT_CALL, NULL, "Set Resolution...", {.routine = M_VideoModeMenu}, 10}, -#if (defined (__unix__) && !defined (MSDOS)) || defined (UNIXCOMMON) || defined (HAVE_SDL) - {IT_STRING|IT_CVAR, NULL, "Fullscreen", {.cvar = &cv_fullscreen}, 20}, -#endif -#ifdef HWRENDER - {IT_STRING | IT_CVAR, NULL, "Renderer", {.cvar = &cv_renderer}, 30}, -#else - {IT_TRANSTEXT | IT_PAIR, "Renderer", "Software", {.cvar = &cv_renderer}, 30}, -#endif - {IT_STRING | IT_CVAR | IT_CV_SLIDER, - NULL, "Gamma", {.cvar = &cv_globalgamma}, 50}, - - {IT_STRING | IT_CVAR, NULL, "Show FPS", {.cvar = &cv_ticrate}, 60}, - {IT_STRING | IT_CVAR, NULL, "FPS Cap", {.cvar = &cv_fpscap}, 70}, - - {IT_STRING | IT_CVAR, NULL, "Draw Distance", {.cvar = &cv_drawdist}, 90}, - {IT_STRING | IT_CVAR, NULL, "Weather Draw Distance", {.cvar = &cv_drawdist_precip}, 100}, - {IT_STRING | IT_CVAR, NULL, "Skyboxes", {.cvar = &cv_skybox}, 110}, - -#ifdef HWRENDER - {IT_CALL | IT_STRING, NULL, "OpenGL Options...", {.routine = M_OpenGLOptionsMenu}, 140}, -#endif -}; - -enum -{ - op_video_res = 0, -#if (defined (__unix__) && !defined (MSDOS)) || defined (UNIXCOMMON) || defined (HAVE_SDL) - op_video_fullscreen, -#endif - op_video_gamma, - op_video_dd, - op_video_wdd, - //op_video_wd, - op_video_skybox, - op_video_fps, - op_video_vsync, -#ifdef HWRENDER - op_video_ogl, -#endif -}; - -static menuitem_t OP_VideoModeMenu[] = -{ - {IT_KEYHANDLER | IT_NOTHING, NULL, "", {.routine = M_HandleVideoMode}, '\0'}, // dummy menuitem for the control func -}; - -#ifdef HWRENDER -static menuitem_t OP_OpenGLOptionsMenu[] = -{ - {IT_STRING | IT_CVAR, NULL, "3D Models", {.cvar = &cv_glmodels}, 10}, - {IT_STRING|IT_CVAR, NULL, "Shaders", {.cvar = &cv_glshaders}, 20}, - - {IT_STRING|IT_CVAR, NULL, "Texture Quality", {.cvar = &cv_scr_depth}, 40}, - {IT_STRING|IT_CVAR, NULL, "Texture Filter", {.cvar = &cv_glfiltermode}, 50}, - {IT_STRING|IT_CVAR, NULL, "Anisotropic", {.cvar = &cv_glanisotropicmode}, 60}, - - {IT_STRING|IT_CVAR, NULL, "Sprite Billboarding", {.cvar = &cv_glspritebillboarding}, 80}, - {IT_STRING|IT_CVAR, NULL, "Software Perspective", {.cvar = &cv_glshearing}, 90}, -}; -#endif - -static menuitem_t OP_SoundOptionsMenu[] = -{ - {IT_STRING|IT_CVAR, NULL, "SFX", {.cvar = &cv_gamesounds}, 10}, - {IT_STRING|IT_CVAR|IT_CV_SLIDER, - NULL, "SFX Volume", {.cvar = &cv_soundvolume}, 18}, - - {IT_STRING|IT_CVAR, NULL, "Music", {.cvar = &cv_gamedigimusic}, 30}, - {IT_STRING|IT_CVAR|IT_CV_SLIDER, - NULL, "Music Volume", {.cvar = &cv_digmusicvolume}, 38}, - - //{IT_STRING|IT_CALL, NULL, "Restart Audio System", M_RestartAudio, 50}, - - {IT_STRING|IT_CVAR, NULL, "Reverse L/R Channels", {.cvar = &stereoreverse}, 50}, - {IT_STRING|IT_CVAR, NULL, "Surround Sound", {.cvar = &surround}, 60}, - - {IT_STRING|IT_CVAR, NULL, "Chat Notifications", {.cvar = &cv_chatnotifications}, 75}, - {IT_STRING|IT_CVAR, NULL, "Character voices", {.cvar = &cv_kartvoices}, 85}, - {IT_STRING|IT_CVAR, NULL, "Powerup Warning", {.cvar = &cv_kartinvinsfx}, 95}, - - {IT_KEYHANDLER|IT_STRING, NULL, "Sound Test", {.routine = M_HandleSoundTest}, 110}, - - {IT_STRING|IT_CVAR, NULL, "Play Music While Unfocused", {.cvar = &cv_playmusicifunfocused}, 125}, - {IT_STRING|IT_CVAR, NULL, "Play SFX While Unfocused", {.cvar = &cv_playsoundifunfocused}, 135}, -}; - -static menuitem_t OP_DataOptionsMenu[] = -{ - - {IT_STRING | IT_CALL, NULL, "Screenshot Options...", {.routine = M_ScreenshotOptions}, 10}, - {IT_STRING | IT_CALL, NULL, "Addon Options...", {.routine = M_AddonsOptions}, 20}, - {IT_STRING | IT_SUBMENU, NULL, "Replay Options...", {.submenu = &MISC_ReplayOptionsDef}, 30}, -#ifdef HAVE_DISCORDRPC - {IT_STRING | IT_SUBMENU, NULL, "Discord Options...", {.submenu = &OP_DiscordOptionsDef}, 40}, - - {IT_STRING | IT_SUBMENU, NULL, "Erase Data...", {.submenu = &OP_EraseDataDef}, 60}, -#else - {IT_STRING | IT_SUBMENU, NULL, "Erase Data...", {.submenu = &OP_EraseDataDef}, 50}, -#endif -}; - -static menuitem_t OP_ScreenshotOptionsMenu[] = -{ - {IT_STRING|IT_CVAR, NULL, "Storage Location", {.cvar = &cv_screenshot_option}, 10}, - {IT_STRING|IT_CVAR|IT_CV_STRING, NULL, "Custom Folder", {.cvar = &cv_screenshot_folder}, 20}, - - {IT_HEADER, NULL, "Screenshots (F8)", {NULL}, 50}, - {IT_STRING|IT_CVAR, NULL, "Memory Level", {.cvar = &cv_zlib_memory}, 60}, - {IT_STRING|IT_CVAR, NULL, "Compression Level", {.cvar = &cv_zlib_level}, 70}, - {IT_STRING|IT_CVAR, NULL, "Strategy", {.cvar = &cv_zlib_strategy}, 80}, - {IT_STRING|IT_CVAR, NULL, "Window Size", {.cvar = &cv_zlib_window_bits}, 90}, - - {IT_HEADER, NULL, "Movie Mode (F9)", {NULL}, 105}, - {IT_STRING|IT_CVAR, NULL, "Capture Mode", {.cvar = &cv_moviemode}, 115}, - - {IT_STRING|IT_CVAR, NULL, "Region Optimizing", {.cvar = &cv_gif_optimize}, 125}, - {IT_STRING|IT_CVAR, NULL, "Downscaling", {.cvar = &cv_gif_downscale}, 135}, - - {IT_STRING|IT_CVAR, NULL, "Memory Level", {.cvar = &cv_zlib_memorya}, 125}, - {IT_STRING|IT_CVAR, NULL, "Compression Level", {.cvar = &cv_zlib_levela}, 135}, - {IT_STRING|IT_CVAR, NULL, "Strategy", {.cvar = &cv_zlib_strategya}, 145}, - {IT_STRING|IT_CVAR, NULL, "Window Size", {.cvar = &cv_zlib_window_bitsa}, 155}, -}; - -enum -{ - op_screenshot_folder = 1, - op_screenshot_capture = 8, - op_screenshot_gif_start = 9, - op_screenshot_gif_end = 10, - op_screenshot_apng_start = 11, - op_screenshot_apng_end = 14, -}; - -static menuitem_t OP_EraseDataMenu[] = -{ - {IT_STRING | IT_CALL, NULL, "Erase Record Data", {.routine = M_EraseData}, 10}, - {IT_STRING | IT_CALL, NULL, "Erase Unlockable Data", {.routine = M_EraseData}, 20}, - - {IT_STRING | IT_CALL, NULL, "\x85" "Erase ALL Data", {.routine = M_EraseData}, 40}, -}; - -static menuitem_t OP_AddonsOptionsMenu[] = -{ - {IT_HEADER, NULL, "Menu", {NULL}, 0}, - {IT_STRING|IT_CVAR, NULL, "Location", {.cvar = &cv_addons_option}, 10}, - {IT_STRING|IT_CVAR|IT_CV_STRING, NULL, "Custom Folder", {.cvar = &cv_addons_folder}, 20}, - {IT_STRING|IT_CVAR, NULL, "Identify addons via", {.cvar = &cv_addons_md5}, 48}, - {IT_STRING|IT_CVAR, NULL, "Show unsupported file types", {.cvar = &cv_addons_showall}, 58}, - - {IT_HEADER, NULL, "Search", {NULL}, 76}, - {IT_STRING|IT_CVAR, NULL, "Matching", {.cvar = &cv_addons_search_type}, 86}, - {IT_STRING|IT_CVAR, NULL, "Case-sensitive", {.cvar = &cv_addons_search_case}, 96}, -}; - -enum -{ - op_addons_folder = 2, -}; - -#ifdef HAVE_DISCORDRPC -static menuitem_t OP_DiscordOptionsMenu[] = -{ - {IT_STRING | IT_CVAR, NULL, "Rich Presence", {.cvar = &cv_discordrp}, 10}, - - {IT_HEADER, NULL, "Rich Presence Settings", {NULL}, 30}, - {IT_STRING | IT_CVAR, NULL, "Streamer Mode", {.cvar = &cv_discordstreamer}, 40}, - - {IT_STRING | IT_CVAR, NULL, "Allow Ask To Join", {.cvar = &cv_discordasks}, 60}, - {IT_STRING | IT_CVAR, NULL, "Allow Invites", {.cvar = &cv_discordinvites}, 70}, -}; -#endif - -static menuitem_t OP_HUDOptionsMenu[] = -{ - {IT_STRING | IT_CVAR, NULL, "Show HUD (F3)", {.cvar = &cv_showhud}, 20}, - {IT_STRING | IT_CVAR | IT_CV_SLIDER, - NULL, "HUD Visibility", {.cvar = &cv_translucenthud}, 30}, - - {IT_STRING | IT_SUBMENU, NULL, "Online HUD options...", {.submenu = &OP_ChatOptionsDef}, 45}, - {IT_STRING | IT_CVAR, NULL, "Background Glass", {.cvar = &cons_backcolor}, 55}, - - {IT_STRING | IT_CVAR | IT_CV_SLIDER, - NULL, "Minimap Visibility", {.cvar = &cv_kartminimap}, 70}, - {IT_STRING | IT_CVAR, NULL, "Speedometer Display", {.cvar = &cv_kartspeedometer}, 80}, - {IT_STRING | IT_CVAR, NULL, "Show \"CHECK\"", {.cvar = &cv_kartcheck}, 90}, - - {IT_STRING | IT_CVAR, NULL, "Menu Highlights", {.cvar = &cons_menuhighlight}, 105}, - // highlight info - (GOOD HIGHLIGHT, WARNING HIGHLIGHT) - 105 (see M_DrawHUDOptions) - - {IT_STRING | IT_CVAR, NULL, "Console Text Size", {.cvar = &cv_constextsize}, 130}, - - {IT_STRING | IT_CVAR, NULL, "Show \"FOCUS LOST\"", {.cvar = &cv_showfocuslost}, 145}, -}; - -// Ok it's still called chatoptions but we'll put ping display in here to be clean -static menuitem_t OP_ChatOptionsMenu[] = -{ - // will ANYONE who doesn't know how to use the console want to touch this one? - {IT_STRING | IT_CVAR, NULL, "Chat Mode", {.cvar = &cv_consolechat}, 10}, // nonetheless... - - {IT_STRING | IT_CVAR | IT_CV_SLIDER, - NULL, "Chat Box Width", {.cvar = &cv_chatwidth}, 25}, - {IT_STRING | IT_CVAR | IT_CV_SLIDER, - NULL, "Chat Box Height", {.cvar = &cv_chatheight}, 35}, - - {IT_STRING | IT_CVAR, NULL, "Chat Background Tint", {.cvar = &cv_chatbacktint}, 50}, - {IT_STRING | IT_CVAR, NULL, "Message Fadeout Time", {.cvar = &cv_chattime}, 60}, - {IT_STRING | IT_CVAR, NULL, "Spam Protection", {.cvar = &cv_chatspamprotection}, 70}, - - {IT_STRING | IT_CVAR, NULL, "Local ping display", {.cvar = &cv_showping}, 90}, // shows ping next to framerate if we want to. -}; - -static menuitem_t OP_GameOptionsMenu[] = -{ - {IT_STRING | IT_SUBMENU, NULL, "Random Item Toggles...", {.submenu = &OP_MonitorToggleDef}, 10}, - - {IT_STRING | IT_CVAR, NULL, "Game Speed", {.cvar = &cv_kartspeed}, 30}, - {IT_STRING | IT_CVAR, NULL, "Frantic Items", {.cvar = &cv_kartfrantic}, 40}, - {IT_SECRET, NULL, "Encore Mode", {.cvar = &cv_kartencore}, 50}, - - {IT_STRING | IT_CVAR, NULL, "Number of Laps", {.cvar = &cv_numlaps}, 70}, - {IT_STRING | IT_CVAR, NULL, "Exit Countdown Timer", {.cvar = &cv_countdowntime}, 80}, - - {IT_STRING | IT_CVAR, NULL, "Time Limit", {.cvar = &cv_timelimit}, 100}, - {IT_STRING | IT_CVAR, NULL, "Starting Bumpers", {.cvar = &cv_kartbumpers}, 110}, - {IT_STRING | IT_CVAR, NULL, "Karma Comeback", {.cvar = &cv_kartcomeback}, 120}, - - {IT_STRING | IT_CVAR, NULL, "Track Power Levels", {.cvar = &cv_kartusepwrlv}, 140}, -}; - -static menuitem_t OP_ServerOptionsMenu[] = -{ -#ifndef NONET - {IT_STRING | IT_CVAR | IT_CV_STRING, - NULL, "Server Name", {.cvar = &cv_servername}, 10}, -#endif - - {IT_STRING | IT_CVAR, NULL, "Intermission Timer", {.cvar = &cv_inttime}, 40}, - {IT_STRING | IT_CVAR, NULL, "Map Progression", {.cvar = &cv_advancemap}, 50}, - {IT_STRING | IT_CVAR, NULL, "Voting Timer", {.cvar = &cv_votetime}, 60}, - {IT_STRING | IT_CVAR, NULL, "Voting Rule Changes", {.cvar = &cv_kartvoterulechanges}, 70}, - -#ifndef NONET - {IT_STRING | IT_CVAR, NULL, "Max. Player Count", {.cvar = &cv_maxplayers}, 90}, - {IT_STRING | IT_CVAR, NULL, "Allow Players to Join", {.cvar = &cv_allownewplayer}, 100}, - {IT_STRING | IT_CVAR, NULL, "Allow Addon Downloading", {.cvar = &cv_downloading}, 110}, - {IT_STRING | IT_CVAR, NULL, "Pause Permission", {.cvar = &cv_pause}, 120}, - {IT_STRING | IT_CVAR, NULL, "Mute All Chat", {.cvar = &cv_mute}, 130}, - - {IT_SUBMENU|IT_STRING, NULL, "Advanced Options...", {.submenu = &OP_AdvServerOptionsDef}, 150}, -#endif -}; - -#ifndef NONET -static menuitem_t OP_AdvServerOptionsMenu[] = -{ - {IT_STRING | IT_CVAR | IT_CV_STRING, - NULL, "Server Browser Address", {.cvar = &cv_masterserver}, 10}, - - {IT_STRING | IT_CVAR, NULL, "Attempts to resynchronise", {.cvar = &cv_resynchattempts}, 40}, - {IT_STRING | IT_CVAR, NULL, "Ping limit (ms)", {.cvar = &cv_maxping}, 50}, - {IT_STRING | IT_CVAR, NULL, "Ping timeout (s)", {.cvar = &cv_pingtimeout}, 60}, - {IT_STRING | IT_CVAR, NULL, "Connection timeout (tics)", {.cvar = &cv_nettimeout}, 70}, - {IT_STRING | IT_CVAR, NULL, "Join timeout (tics)", {.cvar = &cv_jointimeout}, 80}, - - {IT_STRING | IT_CVAR, NULL, "Max. file transfer send (KB)", {.cvar = &cv_maxsend}, 100}, - {IT_STRING | IT_CVAR, NULL, "File transfer packet rate", {.cvar = &cv_downloadspeed}, 110}, - - {IT_STRING | IT_CVAR, NULL, "Log join addresses", {.cvar = &cv_showjoinaddress}, 130}, - {IT_STRING | IT_CVAR, NULL, "Log resyncs", {.cvar = &cv_blamecfail}, 140}, - {IT_STRING | IT_CVAR, NULL, "Log file transfers", {.cvar = &cv_noticedownload}, 150}, -}; -#endif - -/*static menuitem_t OP_NetgameOptionsMenu[] = -{ - {IT_STRING | IT_CVAR, NULL, "Time Limit", &cv_timelimit, 10}, - {IT_STRING | IT_CVAR, NULL, "Point Limit", &cv_pointlimit, 18}, - - {IT_STRING | IT_CVAR, NULL, "Frantic Items", &cv_kartfrantic, 34}, - - {IT_STRING | IT_CVAR, NULL, "Item Respawn", &cv_itemrespawn, 50}, - {IT_STRING | IT_CVAR, NULL, "Item Respawn Delay", &cv_itemrespawntime, 58}, - - {IT_STRING | IT_CVAR, NULL, "Player Respawn Delay", &cv_respawntime, 74}, - - {IT_STRING | IT_CVAR, NULL, "Force Skin #", &cv_forceskin, 90}, - {IT_STRING | IT_CVAR, NULL, "Restrict Skin Changes", &cv_restrictskinchange, 98}, - - //{IT_STRING | IT_CVAR, NULL, "Autobalance Teams", &cv_autobalance, 114}, - //{IT_STRING | IT_CVAR, NULL, "Scramble Teams on Map Change", &cv_scrambleonchange, 122}, -};*/ - -/*static menuitem_t OP_GametypeOptionsMenu[] = -{ - {IT_HEADER, NULL, "RACE", NULL, 2}, - {IT_STRING | IT_CVAR, NULL, "Game Speed", &cv_kartspeed, 10}, - {IT_STRING | IT_CVAR, NULL, "Encore Mode", &cv_kartencore, 18}, - {IT_STRING | IT_CVAR, NULL, "Number of Laps", &cv_numlaps, 26}, - {IT_STRING | IT_CVAR, NULL, "Use Map Lap Counts", &cv_usemapnumlaps, 34}, - - {IT_HEADER, NULL, "BATTLE", NULL, 50}, - {IT_STRING | IT_CVAR, NULL, "Starting Bumpers", &cv_kartbumpers, 58}, - {IT_STRING | IT_CVAR, NULL, "Karma Comeback", &cv_kartcomeback, 66}, -};*/ - -//#define ITEMTOGGLEBOTTOMRIGHT - -static menuitem_t OP_MonitorToggleMenu[] = -{ - // Mostly handled by the drawing function. - // Instead of using this for dumb monitors, lets use the new item bools we have :V - {IT_KEYHANDLER | IT_NOTHING, NULL, "Sneakers", {.routine = M_HandleMonitorToggles}, KITEM_SNEAKER}, - {IT_KEYHANDLER | IT_NOTHING, NULL, "Sneakers x3", {.routine = M_HandleMonitorToggles}, KRITEM_TRIPLESNEAKER}, - {IT_KEYHANDLER | IT_NOTHING, NULL, "Rocket Sneakers", {.routine = M_HandleMonitorToggles}, KITEM_ROCKETSNEAKER}, - {IT_KEYHANDLER | IT_NOTHING, NULL, "Toggle All", {.routine = M_HandleMonitorToggles}, 0}, - {IT_KEYHANDLER | IT_NOTHING, NULL, "Bananas", {.routine = M_HandleMonitorToggles}, KITEM_BANANA}, - {IT_KEYHANDLER | IT_NOTHING, NULL, "Bananas x3", {.routine = M_HandleMonitorToggles}, KRITEM_TRIPLEBANANA}, - {IT_KEYHANDLER | IT_NOTHING, NULL, "Bananas x10", {.routine = M_HandleMonitorToggles}, KRITEM_TENFOLDBANANA}, - {IT_KEYHANDLER | IT_NOTHING, NULL, "Eggman Monitors", {.routine = M_HandleMonitorToggles}, KITEM_EGGMAN}, - {IT_KEYHANDLER | IT_NOTHING, NULL, "Orbinauts", {.routine = M_HandleMonitorToggles}, KITEM_ORBINAUT}, - {IT_KEYHANDLER | IT_NOTHING, NULL, "Orbinauts x3", {.routine = M_HandleMonitorToggles}, KRITEM_TRIPLEORBINAUT}, - {IT_KEYHANDLER | IT_NOTHING, NULL, "Orbinauts x4", {.routine = M_HandleMonitorToggles}, KRITEM_QUADORBINAUT}, - {IT_KEYHANDLER | IT_NOTHING, NULL, "Mines", {.routine = M_HandleMonitorToggles}, KITEM_MINE}, - {IT_KEYHANDLER | IT_NOTHING, NULL, "Jawz", {.routine = M_HandleMonitorToggles}, KITEM_JAWZ}, - {IT_KEYHANDLER | IT_NOTHING, NULL, "Jawz x2", {.routine = M_HandleMonitorToggles}, KRITEM_DUALJAWZ}, - {IT_KEYHANDLER | IT_NOTHING, NULL, "Ballhogs", {.routine = M_HandleMonitorToggles}, KITEM_BALLHOG}, - {IT_KEYHANDLER | IT_NOTHING, NULL, "Self-Propelled Bombs", {.routine = M_HandleMonitorToggles}, KITEM_SPB}, - {IT_KEYHANDLER | IT_NOTHING, NULL, "Invinciblity", {.routine = M_HandleMonitorToggles}, KITEM_INVINCIBILITY}, - {IT_KEYHANDLER | IT_NOTHING, NULL, "Grow", {.routine = M_HandleMonitorToggles}, KITEM_GROW}, - {IT_KEYHANDLER | IT_NOTHING, NULL, "Shrink", {.routine = M_HandleMonitorToggles}, KITEM_SHRINK}, - {IT_KEYHANDLER | IT_NOTHING, NULL, "Lightning Shields", {.routine = M_HandleMonitorToggles}, KITEM_LIGHTNINGSHIELD}, - {IT_KEYHANDLER | IT_NOTHING, NULL, "Hyudoros", {.routine = M_HandleMonitorToggles}, KITEM_HYUDORO}, - {IT_KEYHANDLER | IT_NOTHING, NULL, "Pogo Springs", {.routine = M_HandleMonitorToggles}, KITEM_POGOSPRING}, - {IT_KEYHANDLER | IT_NOTHING, NULL, "Super Rings", {.routine = M_HandleMonitorToggles}, KITEM_SUPERRING}, - {IT_KEYHANDLER | IT_NOTHING, NULL, "Kitchen Sinks", {.routine = M_HandleMonitorToggles}, KITEM_KITCHENSINK}, -#ifdef ITEMTOGGLEBOTTOMRIGHT - {IT_KEYHANDLER | IT_NOTHING, NULL, "---", {.routine = M_HandleMonitorToggles}, 255}, -#endif -}; - -// ========================================================================== -// ALL MENU DEFINITIONS GO HERE -// ========================================================================== - -// Main Menu and related -menu_t MainDef = CENTERMENUSTYLE(MN_NONE, NULL, MainMenu, NULL, 72); - -menu_t MISC_AddonsDef = -{ - MN_NONE, - NULL, - sizeof (MISC_AddonsMenu)/sizeof (menuitem_t), - &OP_DataOptionsDef, - MISC_AddonsMenu, - M_DrawAddons, - 50, 28, - 0, - NULL -}; - -menu_t MISC_ReplayHutDef = -{ - MN_NONE, - NULL, - sizeof (MISC_ReplayHutMenu)/sizeof (menuitem_t), - NULL, - MISC_ReplayHutMenu, - M_DrawReplayHut, - 30, 80, - 0, - M_QuitReplayHut -}; - -menu_t MISC_ReplayOptionsDef = -{ - MN_NONE, - "M_REPOPT", - sizeof (MISC_ReplayOptionsMenu)/sizeof (menuitem_t), - &OP_DataOptionsDef, - MISC_ReplayOptionsMenu, - M_DrawGenericMenu, - 27, 40, - 0, - NULL -}; - -menu_t MISC_ReplayStartDef = -{ - MN_NONE, - NULL, - sizeof (MISC_ReplayStartMenu)/sizeof (menuitem_t), - &MISC_ReplayHutDef, - MISC_ReplayStartMenu, - M_DrawReplayStartMenu, - 30, 90, - 0, - NULL -}; - -menu_t PlaybackMenuDef = { - MN_NONE, - NULL, - sizeof (PlaybackMenu)/sizeof (menuitem_t), - NULL, - PlaybackMenu, - M_DrawPlaybackMenu, - //BASEVIDWIDTH/2 - 94, 2, - BASEVIDWIDTH/2 - 88, 2, - 0, - NULL -}; - -menu_t MAPauseDef = PAUSEMENUSTYLE(MAPauseMenu, 40, 72); -menu_t SPauseDef = PAUSEMENUSTYLE(SPauseMenu, 40, 72); -menu_t MPauseDef = PAUSEMENUSTYLE(MPauseMenu, 40, 72); - -#ifdef HAVE_DISCORDRPC -menu_t MISC_DiscordRequestsDef = { - MN_NONE, - NULL, - sizeof (MISC_DiscordRequestsMenu)/sizeof (menuitem_t), - &MPauseDef, - MISC_DiscordRequestsMenu, - M_DrawDiscordRequests, - 0, 0, - 0, - NULL -}; -#endif - -// Misc Main Menu -menu_t MISC_ScrambleTeamDef = DEFAULTMENUSTYLE(MN_NONE, NULL, MISC_ScrambleTeamMenu, &MPauseDef, 27, 40); -menu_t MISC_ChangeTeamDef = DEFAULTMENUSTYLE(MN_NONE, NULL, MISC_ChangeTeamMenu, &MPauseDef, 27, 40); -menu_t MISC_ChangeSpectateDef = DEFAULTMENUSTYLE(MN_NONE, NULL, MISC_ChangeSpectateMenu, &MPauseDef, 27, 40); -menu_t MISC_ChangeLevelDef = MAPICONMENUSTYLE(NULL, MISC_ChangeLevelMenu, &MPauseDef); -menu_t MISC_HelpDef = IMAGEDEF(MISC_HelpMenu); - -// -// M_GetGametypeColor -// -// Pretty and consistent ^u^ -// See also G_GetGametypeColor. -// - -static INT32 highlightflags, recommendedflags, warningflags; - -inline static void M_GetGametypeColor(void) -{ - INT16 gt; - - warningflags = V_REDMAP; - recommendedflags = V_GREENMAP; - - if (cons_menuhighlight.value) - { - highlightflags = cons_menuhighlight.value; - if (highlightflags == V_REDMAP) - { - warningflags = V_ORANGEMAP; - return; - } - if (highlightflags == V_GREENMAP) - { - recommendedflags = V_SKYMAP; - return; - } - return; - } - - warningflags = V_REDMAP; - recommendedflags = V_GREENMAP; - - if (modeattacking // == ATTACKING_TIME - || gamestate == GS_TIMEATTACK) - { - highlightflags = V_ORANGEMAP; - return; - } - - if (currentMenu->drawroutine == M_DrawServerMenu) - gt = cv_newgametype.value; - else if (!Playing()) - { - highlightflags = V_YELLOWMAP; - return; - } - else - gt = gametype; - - if (gt == GT_BATTLE || levellistmode == LLM_BOSS) - { - highlightflags = V_REDMAP; - warningflags = V_ORANGEMAP; - return; - } - if (gt == GT_RACE) - { - highlightflags = V_SKYMAP; - return; - } - - highlightflags = V_YELLOWMAP; // FALLBACK -} - -// excuse me but I'm extremely lazy: -INT32 HU_GetHighlightColor(void) -{ - M_GetGametypeColor(); // update flag colour reguardless of the menu being opened or not. - return highlightflags; -} - -// Sky Room -menu_t SR_PandoraDef = -{ - MN_NONE, - "M_PANDRA", - sizeof (SR_PandorasBox)/sizeof (menuitem_t), - &SPauseDef, - SR_PandorasBox, - M_DrawGenericMenu, - 60, 40, - 0, - M_ExitPandorasBox -}; -menu_t SR_MainDef = CENTERMENUSTYLE(MN_NONE, NULL, SR_MainMenu, &MainDef, 72); - -//menu_t SR_LevelSelectDef = MAPICONMENUSTYLE(NULL, SR_LevelSelectMenu, &SR_MainDef); - -menu_t SR_UnlockChecklistDef = -{ - MN_NONE, - NULL, - 1, - &SR_MainDef, - SR_UnlockChecklistMenu, - M_DrawChecklist, - 280, 185, - 0, - NULL -}; -menu_t SR_EmblemHintDef = -{ - MN_NONE, - NULL, - sizeof (SR_EmblemHintMenu)/sizeof (menuitem_t), - &SPauseDef, - SR_EmblemHintMenu, - M_DrawEmblemHints, - 60, 150, - 0, - NULL -}; - -// Single Player -menu_t SP_MainDef = CENTERMENUSTYLE(MN_NONE, NULL, SP_MainMenu, &MainDef, 72); -/*menu_t SP_LoadDef = -{ - MN_NONE, - "M_PICKG", - 1, - &SP_MainDef, - SP_LoadGameMenu, - M_DrawLoad, - 68, 46, - 0, - NULL -}; -menu_t SP_LevelSelectDef = MAPICONMENUSTYLE(NULL, SP_LevelSelectMenu, &SP_LoadDef);*/ - -menu_t SP_LevelStatsDef = -{ - MN_NONE, - "M_STATS", - 1, - &SR_MainDef, - SP_LevelStatsMenu, - M_DrawLevelStats, - 280, 185, - 0, - NULL -}; - -static menu_t SP_GrandPrixTempDef = DEFAULTMENUSTYLE(MN_NONE, NULL, SP_GrandPrixPlaceholderMenu, &MainDef, 60, 30); - -static menu_t SP_TimeAttackDef = -{ - MN_NONE, - "M_ATTACK", - sizeof (SP_TimeAttackMenu)/sizeof (menuitem_t), - &MainDef, // Doesn't matter. - SP_TimeAttackMenu, - M_DrawTimeAttackMenu, - 34, 40, - 0, - M_QuitTimeAttackMenu -}; -static menu_t SP_ReplayDef = -{ - MN_NONE, - "M_ATTACK", - sizeof(SP_ReplayMenu)/sizeof(menuitem_t), - &SP_TimeAttackDef, - SP_ReplayMenu, - M_DrawTimeAttackMenu, - 34, 40, - 0, - NULL -}; -static menu_t SP_GuestReplayDef = -{ - MN_NONE, - "M_ATTACK", - sizeof(SP_GuestReplayMenu)/sizeof(menuitem_t), - &SP_TimeAttackDef, - SP_GuestReplayMenu, - M_DrawTimeAttackMenu, - 34, 40, - 0, - NULL -}; -static menu_t SP_GhostDef = -{ - MN_NONE, - "M_ATTACK", - sizeof(SP_GhostMenu)/sizeof(menuitem_t), - &SP_TimeAttackDef, - SP_GhostMenu, - M_DrawTimeAttackMenu, - 34, 40, - 0, - NULL -}; - -/*menu_t SP_PlayerDef = -{ - MN_NONE, - "M_PICKP", - sizeof (PlayerMenu)/sizeof (menuitem_t),//player_end, - &SP_MainDef, - PlayerMenu, - M_DrawSetupChoosePlayerMenu, - 24, 32, - 0, - NULL -};*/ - -// Multiplayer -menu_t MP_MainDef = -{ - MN_NONE, - "M_MULTI", - sizeof (MP_MainMenu)/sizeof (menuitem_t), - &MainDef, - MP_MainMenu, - M_DrawMPMainMenu, - 42, 30, - 0, -#ifndef NONET - M_CancelConnect -#else - NULL -#endif -}; - -menu_t MP_OfflineServerDef = MAPICONMENUSTYLE("M_MULTI", MP_OfflineServerMenu, &MP_MainDef); - -#ifndef NONET -menu_t MP_ServerDef = MAPICONMENUSTYLE("M_MULTI", MP_ServerMenu, &MP_MainDef); - -menu_t MP_ConnectDef = -{ - MN_NONE, - "M_MULTI", - sizeof (MP_ConnectMenu)/sizeof (menuitem_t), - &MP_MainDef, - MP_ConnectMenu, - M_DrawConnectMenu, - 27,24, - 0, - M_CancelConnect -}; -#endif -menu_t MP_PlayerSetupDef = -{ - MN_NONE, - NULL, //"M_SPLAYR" - sizeof (MP_PlayerSetupMenu)/sizeof (menuitem_t), - &MP_MainDef, - MP_PlayerSetupMenu, - M_DrawSetupMultiPlayerMenu, - 36, 14, - 0, - M_QuitMultiPlayerMenu -}; - -// Options -menu_t OP_MainDef = -{ - MN_NONE, - "M_OPTTTL", - sizeof (OP_MainMenu)/sizeof (menuitem_t), - &MainDef, - OP_MainMenu, - M_DrawGenericMenu, - 60, 30, - 0, - NULL -}; - -menu_t OP_ControlsDef = DEFAULTMENUSTYLE(MN_NONE, "M_CONTRO", OP_ControlsMenu, &OP_MainDef, 60, 30); -menu_t OP_AllControlsDef = CONTROLMENUSTYLE(MN_NONE, OP_AllControlsMenu, &OP_ControlsDef); -menu_t OP_Joystick1Def = DEFAULTMENUSTYLE(MN_NONE, "M_CONTRO", OP_Joystick1Menu, &OP_AllControlsDef, 60, 30); -menu_t OP_Joystick2Def = DEFAULTMENUSTYLE(MN_NONE, "M_CONTRO", OP_Joystick2Menu, &OP_AllControlsDef, 60, 30); -menu_t OP_Joystick3Def = DEFAULTMENUSTYLE(MN_NONE, "M_CONTRO", OP_Joystick3Menu, &OP_AllControlsDef, 60, 30); -menu_t OP_Joystick4Def = DEFAULTMENUSTYLE(MN_NONE, "M_CONTRO", OP_Joystick4Menu, &OP_AllControlsDef, 60, 30); -menu_t OP_JoystickSetDef = -{ - MN_NONE, - "M_CONTRO", - sizeof (OP_JoystickSetMenu)/sizeof (menuitem_t), - &OP_Joystick1Def, - OP_JoystickSetMenu, - M_DrawJoystick, - 50, 40, - 0, - NULL -}; - -menu_t OP_VideoOptionsDef = -{ - MN_NONE, - "M_VIDEO", - sizeof(OP_VideoOptionsMenu)/sizeof(menuitem_t), - &OP_MainDef, - OP_VideoOptionsMenu, - M_DrawVideoMenu, - 30, 30, - 0, - NULL -}; - -menu_t OP_VideoModeDef = -{ - MN_NONE, - "M_VIDEO", - 1, - &OP_VideoOptionsDef, - OP_VideoModeMenu, - M_DrawVideoMode, - 48, 26, - 0, - NULL -}; - -menu_t OP_SoundOptionsDef = -{ - MN_NONE, - "M_SOUND", - sizeof (OP_SoundOptionsMenu)/sizeof (menuitem_t), - &OP_MainDef, - OP_SoundOptionsMenu, - M_DrawSkyRoom, - 30, 30, - 0, - NULL -}; - -menu_t OP_HUDOptionsDef = -{ - MN_NONE, - "M_HUD", - sizeof (OP_HUDOptionsMenu)/sizeof (menuitem_t), - &OP_MainDef, - OP_HUDOptionsMenu, - M_DrawHUDOptions, - 30, 30, - 0, - NULL -}; - -menu_t OP_ChatOptionsDef = DEFAULTMENUSTYLE(MN_NONE, "M_HUD", OP_ChatOptionsMenu, &OP_HUDOptionsDef, 30, 30); - -menu_t OP_GameOptionsDef = DEFAULTMENUSTYLE(MN_NONE, "M_GAME", OP_GameOptionsMenu, &OP_MainDef, 30, 30); -menu_t OP_ServerOptionsDef = DEFAULTMENUSTYLE(MN_NONE, "M_SERVER", OP_ServerOptionsMenu, &OP_MainDef, 24, 30); -#ifndef NONET -menu_t OP_AdvServerOptionsDef = DEFAULTMENUSTYLE(MN_NONE, "M_SERVER", OP_AdvServerOptionsMenu, &OP_ServerOptionsDef, 24, 30); -#endif - -//menu_t OP_NetgameOptionsDef = DEFAULTMENUSTYLE(MN_NONE, "M_SERVER", OP_NetgameOptionsMenu, &OP_ServerOptionsDef, 30, 30); -//menu_t OP_GametypeOptionsDef = DEFAULTMENUSTYLE(MN_NONE, "M_SERVER", OP_GametypeOptionsMenu, &OP_ServerOptionsDef, 30, 30); -//menu_t OP_ChatOptionsDef = DEFAULTMENUSTYLE(MN_NONE, "M_GAME", OP_ChatOptionsMenu, &OP_GameOptionsDef, 30, 30); -menu_t OP_MonitorToggleDef = -{ - MN_NONE, - "M_GAME", - sizeof (OP_MonitorToggleMenu)/sizeof (menuitem_t), - &OP_GameOptionsDef, - OP_MonitorToggleMenu, - M_DrawMonitorToggles, - 47, 30, - 0, - NULL -}; - -#ifdef HWRENDER -static void M_OpenGLOptionsMenu(INT32 choice) -{ - (void)choice; - - if (rendermode == render_opengl) - M_SetupNextMenu(&OP_OpenGLOptionsDef); - else - M_StartMessage(M_GetText("You must be in OpenGL mode\nto access this menu.\n\n(Press a key)\n"), NULL, MM_NOTHING); -} - -menu_t OP_OpenGLOptionsDef = DEFAULTMENUSTYLE(MN_NONE, "M_VIDEO", OP_OpenGLOptionsMenu, &OP_VideoOptionsDef, 30, 30); -#endif -menu_t OP_DataOptionsDef = DEFAULTMENUSTYLE(MN_NONE, "M_DATA", OP_DataOptionsMenu, &OP_MainDef, 60, 30); -menu_t OP_ScreenshotOptionsDef = DEFAULTMENUSTYLE(MN_NONE, "M_SCSHOT", OP_ScreenshotOptionsMenu, &OP_DataOptionsDef, 30, 30); -menu_t OP_AddonsOptionsDef = DEFAULTMENUSTYLE(MN_NONE, "M_ADDONS", OP_AddonsOptionsMenu, &OP_DataOptionsDef, 30, 30); -#ifdef HAVE_DISCORDRPC -menu_t OP_DiscordOptionsDef = DEFAULTMENUSTYLE(MN_NONE, NULL, OP_DiscordOptionsMenu, &OP_DataOptionsDef, 30, 30); -#endif -menu_t OP_EraseDataDef = DEFAULTMENUSTYLE(MN_NONE, "M_DATA", OP_EraseDataMenu, &OP_DataOptionsDef, 30, 30); - -// ========================================================================== -// CVAR ONCHANGE EVENTS GO HERE -// ========================================================================== -// (there's only a couple anyway) - -// Prototypes -static INT32 M_FindFirstMap(INT32 gtype); -static INT32 M_GetFirstLevelInList(void); - -// Nextmap. Used for Time Attack. -void Nextmap_OnChange(void) -{ - char *leveltitle; - UINT8 active; - - // Update the string in the consvar. - Z_Free(cv_nextmap.zstring); - leveltitle = G_BuildMapTitle(cv_nextmap.value); - cv_nextmap.string = cv_nextmap.zstring = leveltitle ? leveltitle : Z_StrDup(G_BuildMapName(cv_nextmap.value)); - - if (currentMenu == &SP_TimeAttackDef) - { - // see also p_setup.c's P_LoadRecordGhosts - const size_t glen = strlen(srb2home)+1+strlen("media")+1+strlen("replay")+1+strlen(timeattackfolder)+1+strlen("MAPXX")+1; - char *gpath = malloc(glen); - INT32 i; - - if (!gpath) - return; - - sprintf(gpath,"%s"PATHSEP"media"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s", srb2home, timeattackfolder, G_BuildMapName(cv_nextmap.value)); - - CV_StealthSetValue(&cv_dummystaff, 0); - - active = 0; - SP_TimeAttackMenu[taguest].status = IT_DISABLED; - SP_TimeAttackMenu[tareplay].status = IT_DISABLED; - //SP_TimeAttackMenu[taghost].status = IT_DISABLED; - - // Check if file exists, if not, disable REPLAY option - for (i = 0; i < 4; i++) - { - SP_ReplayMenu[i].status = IT_DISABLED; - SP_GuestReplayMenu[i].status = IT_DISABLED; - } - SP_ReplayMenu[4].status = IT_DISABLED; - - SP_GhostMenu[3].status = IT_DISABLED; - SP_GhostMenu[4].status = IT_DISABLED; - - if (FIL_FileExists(va("%s-%s-time-best.lmp", gpath, cv_chooseskin.string))) { - SP_ReplayMenu[0].status = IT_WHITESTRING|IT_CALL; - SP_GuestReplayMenu[0].status = IT_WHITESTRING|IT_CALL; - active |= 3; - } - - if (levellistmode != LLM_BREAKTHECAPSULES) { - if (FIL_FileExists(va("%s-%s-lap-best.lmp", gpath, cv_chooseskin.string))) { - SP_ReplayMenu[1].status = IT_WHITESTRING|IT_CALL; - SP_GuestReplayMenu[1].status = IT_WHITESTRING|IT_CALL; - active |= 3; - } - } - - if (FIL_FileExists(va("%s-%s-last.lmp", gpath, cv_chooseskin.string))) { - SP_ReplayMenu[2].status = IT_WHITESTRING|IT_CALL; - SP_GuestReplayMenu[2].status = IT_WHITESTRING|IT_CALL; - active |= 3; - } - - if (FIL_FileExists(va("%s-guest.lmp", gpath))) - { - SP_ReplayMenu[3].status = IT_WHITESTRING|IT_CALL; - SP_GuestReplayMenu[3].status = IT_WHITESTRING|IT_CALL; - SP_GhostMenu[3].status = IT_STRING|IT_CVAR; - active |= 3; - } - - CV_SetValue(&cv_dummystaff, 1); - if (cv_dummystaff.value) - { - SP_ReplayMenu[4].status = IT_WHITESTRING|IT_KEYHANDLER; - SP_GhostMenu[4].status = IT_STRING|IT_CVAR; - CV_StealthSetValue(&cv_dummystaff, 1); - active |= 1; - } - - if (active) { - if (active & 1) - SP_TimeAttackMenu[tareplay].status = IT_WHITESTRING|IT_SUBMENU; - if (active & 2) - SP_TimeAttackMenu[taguest].status = IT_WHITESTRING|IT_SUBMENU; - } - else if (itemOn == tareplay) // Reset lastOn so replay isn't still selected when not available. - { - currentMenu->lastOn = itemOn; - itemOn = tastart; - } - - if (mapheaderinfo[cv_nextmap.value-1] && mapheaderinfo[cv_nextmap.value-1]->forcecharacter[0] != '\0') - CV_Set(&cv_chooseskin, mapheaderinfo[cv_nextmap.value-1]->forcecharacter); - - free(gpath); - } -} - -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 Dummymares_OnChange(void) -{ - if (!nightsrecords[cv_nextmap.value-1]) - { - CV_StealthSetValue(&cv_dummymares, 0); - return; - } - else - { - UINT8 mares = nightsrecords[cv_nextmap.value-1]->nummares; - - if (cv_dummymares.value < 0) - CV_StealthSetValue(&cv_dummymares, mares); - else if (cv_dummymares.value > mares) - CV_StealthSetValue(&cv_dummymares, 0); - } -}*/ - -char dummystaffname[22]; - -static void Dummystaff_OnChange(void) -{ - lumpnum_t l; - - dummystaffname[0] = '\0'; - - if ((l = W_CheckNumForName(va("%sS01",G_BuildMapName(cv_nextmap.value)))) == LUMPERROR) - { - CV_StealthSetValue(&cv_dummystaff, 0); - return; - } - else - { - char *temp = dummystaffname; - UINT8 numstaff = 1; - while (numstaff < 99 && (l = W_CheckNumForName(va("%sS%02u",G_BuildMapName(cv_nextmap.value),numstaff+1))) != LUMPERROR) - numstaff++; - - if (cv_dummystaff.value < 1) - CV_StealthSetValue(&cv_dummystaff, numstaff); - else if (cv_dummystaff.value > numstaff) - CV_StealthSetValue(&cv_dummystaff, 1); - - if ((l = W_CheckNumForName(va("%sS%02u",G_BuildMapName(cv_nextmap.value), cv_dummystaff.value))) == LUMPERROR) - return; // shouldn't happen but might as well check... - - G_UpdateStaffGhostName(l); - - while (*temp) - temp++; - - sprintf(temp, " - %d", cv_dummystaff.value); - } -} - -// Newgametype. Used for gametype changes. -static void Newgametype_OnChange(void) -{ - if (menuactive && cv_nextmap.value) - { - INT32 gt = cv_newgametype.value; - - if (!mapheaderinfo[cv_nextmap.value-1]) - P_AllocMapHeader((INT16)(cv_nextmap.value-1)); - - if (gt >= 0 && gt < gametypecount && !(mapheaderinfo[cv_nextmap.value-1]->typeoflevel & gametypetol[gt])) - CV_SetValue(&cv_nextmap, M_FindFirstMap(gt)); - } -} - -void Screenshot_option_Onchange(void) -{ - OP_ScreenshotOptionsMenu[op_screenshot_folder].status = - (cv_screenshot_option.value == 3 ? IT_CVAR|IT_STRING|IT_CV_STRING : IT_DISABLED); -} - -void Moviemode_mode_Onchange(void) -{ - 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; -} - -void Addons_option_Onchange(void) -{ - OP_AddonsOptionsMenu[op_addons_folder].status = - (cv_addons_option.value == 3 ? IT_CVAR|IT_STRING|IT_CV_STRING : IT_DISABLED); -} - -void Moviemode_option_Onchange(void) -{ - ; -} - -// ========================================================================== -// END ORGANIZATION STUFF. -// ========================================================================== - -// current menudef -menu_t *currentMenu = &MainDef; - -// ========================================================================= -// MENU PRESENTATION PARAMETER HANDLING (BACKGROUNDS) -// ========================================================================= - -// menu IDs are equal to current/prevMenu in most cases, except MN_SPECIAL when we don't want to operate on Message, Pause, etc. -UINT32 prevMenuId = 0; -UINT32 activeMenuId = 0; - -menupres_t menupres[NUMMENUTYPES]; - -void M_InitMenuPresTables(void) -{ - INT32 i; - - // Called in d_main before SOC can get to the tables - // Set menupres defaults - for (i = 0; i < NUMMENUTYPES; i++) - { - // so-called "undefined" - menupres[i].fadestrength = -1; - menupres[i].hidetitlepics = -1; // inherits global hidetitlepics - menupres[i].ttmode = TTMODE_NONE; - menupres[i].ttscale = UINT8_MAX; - menupres[i].ttname[0] = 0; - menupres[i].ttx = INT16_MAX; - menupres[i].tty = INT16_MAX; - menupres[i].ttloop = INT16_MAX; - menupres[i].tttics = UINT16_MAX; - menupres[i].enterwipe = -1; - menupres[i].exitwipe = -1; - menupres[i].bgcolor = -1; - menupres[i].titlescrollxspeed = INT32_MAX; - menupres[i].titlescrollyspeed = INT32_MAX; - menupres[i].bghide = true; - // default true - menupres[i].enterbubble = true; - menupres[i].exitbubble = true; - - if (i != MN_MAIN) - { - menupres[i].muslooping = true; - } - if (i == MN_SP_TIMEATTACK) - strncpy(menupres[i].musname, "_recat", 7); - else if (i == MN_SP_NIGHTSATTACK) - strncpy(menupres[i].musname, "_nitat", 7); - else if (i == MN_SP_MARATHON) - strncpy(menupres[i].musname, "spec8", 6); - else if (i == MN_SP_PLAYER || i == MN_SR_PLAYER) - strncpy(menupres[i].musname, "_chsel", 7); - else if (i == MN_SR_SOUNDTEST) - { - *menupres[i].musname = '\0'; - menupres[i].musstop = true; - } - } -} - -// ========================================================================= -// BASIC MENU HANDLING -// ========================================================================= - -static void M_ChangeCvar(INT32 choice) -{ - consvar_t *cv = currentMenu->menuitems[itemOn].itemaction.cvar; - - if (choice == -1) - { - if (cv == &cv_playercolor[0]) - { - SINT8 skinno = R_SkinAvailable(cv_chooseskin.string); - if (skinno != -1) - CV_SetValue(cv,skins[skinno].prefcolor); - return; - } - CV_Set(cv,cv->defaultvalue); - return; - } - - choice = (choice<<1) - 1; - - if (((currentMenu->menuitems[itemOn].status & IT_CVARTYPE) == IT_CV_SLIDER) - ||((currentMenu->menuitems[itemOn].status & IT_CVARTYPE) == IT_CV_INVISSLIDER) - ||((currentMenu->menuitems[itemOn].status & IT_CVARTYPE) == IT_CV_NOMOD)) - { - if (cv == &cv_digmusicvolume || cv == &cv_soundvolume) - { - choice *= 5; - } - - CV_SetValue(cv,cv->value+choice); - } - else if (cv->flags & CV_FLOAT) - { - char s[20]; - sprintf(s,"%f",FIXED_TO_FLOAT(cv->value)+(choice)*(1.0f/16.0f)); - CV_Set(cv,s); - } - else - { -#ifndef NONET - if (cv == &cv_nettimeout || cv == &cv_jointimeout) - choice *= (TICRATE/7); - else if (cv == &cv_maxsend) - choice *= 512; - else if (cv == &cv_maxping) - choice *= 50; -#endif - CV_AddValue(cv,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_menu1); // 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_menu1); // Tails - CV_Set(cv, ""); - } - return true; - default: - if (choice >= 32 && choice <= 127) - { - len = strlen(cv->string); - if (len < MAXSTRINGLENGTH - 1) - { - S_StartSound(NULL,sfx_menu1); // Tails - M_Memcpy(buf, cv->string, len); - buf[len++] = (char)choice; - buf[len] = 0; - CV_Set(cv, buf); - } - return true; - } - break; - } - return false; -} - -static void M_NextOpt(void) -{ - INT16 oldItemOn = itemOn; // prevent infinite loop - - do - { - if (itemOn + 1 > currentMenu->numitems - 1) - itemOn = 0; - else - itemOn++; - } while (oldItemOn != itemOn && (currentMenu->menuitems[itemOn].status & IT_TYPE) == IT_SPACE); -} - -static void M_PrevOpt(void) -{ - INT16 oldItemOn = itemOn; // prevent infinite loop - - do - { - if (!itemOn) - itemOn = currentMenu->numitems - 1; - else - itemOn--; - } while (oldItemOn != itemOn && (currentMenu->menuitems[itemOn].status & IT_TYPE) == IT_SPACE); -} - -// lock out further input in a tic when important buttons are pressed -// (in other words -- stop bullshit happening by mashing buttons in fades) -static boolean noFurtherInput = false; - -static void Command_Manual_f(void) -{ - if (modeattacking) - return; - M_StartControlPanel(); - M_Manual(INT32_MAX); - itemOn = 0; -} - -// -// M_Responder -// -boolean M_Responder(event_t *ev) -{ - INT32 ch = -1; -// INT32 i; - static tic_t joywait = 0, mousewait = 0; - static INT32 pjoyx = 0, pjoyy = 0; - static INT32 pmousex = 0, pmousey = 0; - static INT32 lastx = 0, lasty = 0; - void (*routine)(INT32 choice); // for some casting problem - - if (dedicated || (demo.playback && demo.title) - || gamestate == GS_INTRO || gamestate == GS_CUTSCENE || gamestate == GS_GAMEEND - || gamestate == GS_CREDITS || gamestate == GS_EVALUATION) - return false; - - if (noFurtherInput) - { - // Ignore input after enter/escape/other buttons - // (but still allow shift keyup so caps doesn't get stuck) - return false; - } - else if (ev->type == ev_keydown) - { - ch = ev->data1; - - // added 5-2-98 remap virtual keys (mouse & joystick buttons) - switch (ch) - { - case KEY_MOUSE1: - //case KEY_JOY1: - //case KEY_JOY1 + 2: - ch = KEY_ENTER; - break; - /*case KEY_JOY1 + 3: // Brake can function as 'n' for message boxes now. - ch = 'n'; - break;*/ - case KEY_MOUSE1 + 1: - //case KEY_JOY1 + 1: - ch = KEY_BACKSPACE; - break; - case KEY_HAT1: - ch = KEY_UPARROW; - break; - case KEY_HAT1 + 1: - ch = KEY_DOWNARROW; - break; - case KEY_HAT1 + 2: - ch = KEY_LEFTARROW; - break; - case KEY_HAT1 + 3: - ch = KEY_RIGHTARROW; - break; - } - } - else if (menuactive) - { - if (ev->type == ev_joystick && ev->data1 == 0 && joywait < I_GetTime()) - { - const INT32 jdeadzone = ((JOYAXISRANGE-1) * cv_deadzone[0].value) >> FRACBITS; - if (ev->data3 != INT32_MAX) - { - if (Joystick[0].bGamepadStyle || abs(ev->data3) > jdeadzone) - { - if (ev->data3 < 0 && pjoyy >= 0) - { - ch = KEY_UPARROW; - joywait = I_GetTime() + NEWTICRATE/7; - } - else if (ev->data3 > 0 && pjoyy <= 0) - { - ch = KEY_DOWNARROW; - joywait = I_GetTime() + NEWTICRATE/7; - } - pjoyy = ev->data3; - } - else - pjoyy = 0; - } - - if (ev->data2 != INT32_MAX) - { - if (Joystick[0].bGamepadStyle || abs(ev->data2) > jdeadzone) - { - if (ev->data2 < 0 && pjoyx >= 0) - { - ch = KEY_LEFTARROW; - joywait = I_GetTime() + NEWTICRATE/17; - } - else if (ev->data2 > 0 && pjoyx <= 0) - { - ch = KEY_RIGHTARROW; - joywait = I_GetTime() + NEWTICRATE/17; - } - pjoyx = ev->data2; - } - else - pjoyx = 0; - } - } - else if (ev->type == ev_mouse && mousewait < I_GetTime()) - { - pmousey += ev->data3; - if (pmousey < lasty-30) - { - ch = KEY_DOWNARROW; - mousewait = I_GetTime() + NEWTICRATE/7; - pmousey = lasty -= 30; - } - else if (pmousey > lasty + 30) - { - ch = KEY_UPARROW; - mousewait = I_GetTime() + NEWTICRATE/7; - pmousey = lasty += 30; - } - - pmousex += ev->data2; - if (pmousex < lastx - 30) - { - ch = KEY_LEFTARROW; - mousewait = I_GetTime() + NEWTICRATE/7; - pmousex = lastx -= 30; - } - else if (pmousex > lastx+30) - { - ch = KEY_RIGHTARROW; - mousewait = I_GetTime() + NEWTICRATE/7; - pmousex = lastx += 30; - } - } - } - - if (ch == -1) - return false; - else if (ch == gamecontrol[0][gc_systemmenu][0] || ch == gamecontrol[0][gc_systemmenu][1]) // allow remappable ESC key - ch = KEY_ESCAPE; - else if ((ch == gamecontrol[0][gc_accelerate][0] || ch == gamecontrol[0][gc_accelerate][1]) && ch >= KEY_MOUSE1) - ch = KEY_ENTER; - - // F-Keys - if (!menuactive) - { - noFurtherInput = true; - - switch (ch) - { - case KEY_F1: // Help key - Command_Manual_f(); - return true; - - case KEY_F2: // Empty - return true; - - case KEY_F3: // Toggle HUD - CV_SetValue(&cv_showhud, !cv_showhud.value); - return true; - - case KEY_F4: // Sound Volume - if (modeattacking) - return true; - M_StartControlPanel(); - M_Options(0); - currentMenu = &OP_SoundOptionsDef; - itemOn = 0; - return true; - -#ifndef DC - case KEY_F5: // Video Mode - if (modeattacking) - return true; - M_StartControlPanel(); - M_Options(0); - M_VideoModeMenu(0); - return true; -#endif - - case KEY_F6: // Empty - return true; - - case KEY_F7: // Options - if (modeattacking) - return true; - M_StartControlPanel(); - M_Options(0); - M_SetupNextMenu(&OP_MainDef); - return true; - - // Screenshots on F8 now handled elsewhere - // Same with Moviemode on F9 - - case KEY_F10: // Quit SRB2 - M_QuitSRB2(0); - return true; - - case KEY_F11: // Fullscreen - CV_AddValue(&cv_fullscreen, 1); - return true; - - // Spymode on F12 handled in game logic - - case KEY_ESCAPE: // Pop up menu - if (chat_on) - { - HU_clearChatChars(); - chat_on = false; - } - else - M_StartControlPanel(); - return true; - } - noFurtherInput = false; // turns out we didn't care - return false; - } - - if ((ch == gamecontrol[0][gc_brake][0] || ch == gamecontrol[0][gc_brake][1]) && ch >= KEY_MOUSE1) // do this here, otherwise brake opens the menu mid-game - ch = KEY_ESCAPE; - - routine = currentMenu->menuitems[itemOn].itemaction.routine; - - // Handle menuitems which need a specific key handling - if (routine && (currentMenu->menuitems[itemOn].status & IT_TYPE) == IT_KEYHANDLER) - { - if (shiftdown && ch >= 32 && ch <= 127) - ch = shiftxform[ch]; - routine(ch); - return true; - } - - if (currentMenu->menuitems[itemOn].status == IT_MSGHANDLER) - { - if (currentMenu->menuitems[itemOn].alphaKey != MM_EVENTHANDLER) - { - if (ch == ' ' || ch == 'n' || ch == 'y' || ch == KEY_ESCAPE || ch == KEY_ENTER) - { - if (routine) - routine(ch); - M_StopMessage(0); - noFurtherInput = true; - return true; - } - return true; - } - else - { - // dirty hack: for customising controls, I want only buttons/keys, not moves - if (ev->type == ev_mouse - || ev->type == ev_joystick - || ev->type == ev_joystick2 - || ev->type == ev_joystick3 - || ev->type == ev_joystick4) - return true; - if (routine) - { - currentMenu->menuitems[itemOn].itemaction.eventhandler(ev); - } - return true; - } - } - - // BP: one of the more big hack i have never made - if (routine && (currentMenu->menuitems[itemOn].status & IT_TYPE) == IT_CVAR) - { - if ((currentMenu->menuitems[itemOn].status & IT_CVARTYPE) == IT_CV_STRING) - { - - if (shiftdown && ch >= 32 && ch <= 127) - ch = shiftxform[ch]; - if (M_ChangeStringCvar(ch)) - return true; - else - routine = NULL; - } - else - routine = M_ChangeCvar; - } - - if (currentMenu == &PlaybackMenuDef && !con_destlines) - { - playback_last_menu_interaction_leveltime = leveltime; - // Flip left/right with up/down for the playback menu, since it's a horizontal icon row. - switch (ch) - { - case KEY_LEFTARROW: ch = KEY_UPARROW; break; - case KEY_UPARROW: ch = KEY_RIGHTARROW; break; - case KEY_RIGHTARROW: ch = KEY_DOWNARROW; break; - case KEY_DOWNARROW: ch = KEY_LEFTARROW; break; - - // arbitrary keyboard shortcuts because fuck you - - case '\'': // toggle freecam - M_PlaybackToggleFreecam(0); - break; - - case ']': // ffw / advance frame (depends on if paused or not) - if (paused) - M_PlaybackAdvance(0); - else - M_PlaybackFastForward(0); - break; - - case '[': // rewind /backupframe, uses the same function - M_PlaybackRewind(0); - break; - - case '\\': // pause - M_PlaybackPause(0); - break; - - // viewpoints, an annoyance (tm) - case '-': // viewpoint minus - M_PlaybackSetViews(-1); // yeah lol. - break; - - case '=': // viewpoint plus - M_PlaybackSetViews(1); // yeah lol. - break; - - // switch viewpoints: - case '1': // viewpoint for p1 (also f12) - // maximum laziness: - if (!demo.freecam) - G_AdjustView(1, 1, true); - break; - case '2': // viewpoint for p2 - if (!demo.freecam) - G_AdjustView(2, 1, true); - break; - case '3': // viewpoint for p3 - if (!demo.freecam) - G_AdjustView(3, 1, true); - break; - case '4': // viewpoint for p4 - if (!demo.freecam) - G_AdjustView(4, 1, true); - break; - - default: break; - } - } - - // Keys usable within menu - switch (ch) - { - case KEY_DOWNARROW: - M_NextOpt(); - S_StartSound(NULL, sfx_menu1); - /*if (currentMenu == &SP_PlayerDef) - { - Z_Free(char_notes); - char_notes = NULL; - }*/ - return true; - - case KEY_UPARROW: - M_PrevOpt(); - S_StartSound(NULL, sfx_menu1); - /*if (currentMenu == &SP_PlayerDef) - { - Z_Free(char_notes); - char_notes = NULL; - }*/ - return true; - - case KEY_LEFTARROW: - if (routine && ((currentMenu->menuitems[itemOn].status & IT_TYPE) == IT_ARROWS - || (currentMenu->menuitems[itemOn].status & IT_TYPE) == IT_CVAR)) - { - if (currentMenu != &OP_SoundOptionsDef || itemOn > 3) - S_StartSound(NULL, sfx_menu1); - routine(0); - } - return true; - - case KEY_RIGHTARROW: - if (routine && ((currentMenu->menuitems[itemOn].status & IT_TYPE) == IT_ARROWS - || (currentMenu->menuitems[itemOn].status & IT_TYPE) == IT_CVAR)) - { - if (currentMenu != &OP_SoundOptionsDef || itemOn > 3) - S_StartSound(NULL, sfx_menu1); - routine(1); - } - return true; - - case KEY_ENTER: - noFurtherInput = true; - currentMenu->lastOn = itemOn; - - if (currentMenu == &PlaybackMenuDef) - { - boolean held = (boolean)playback_enterheld; - if (held) - return true; - playback_enterheld = 3; - } - - if (routine) - { - if (((currentMenu->menuitems[itemOn].status & IT_TYPE)==IT_CALL - || (currentMenu->menuitems[itemOn].status & IT_TYPE)==IT_SUBMENU) - && (currentMenu->menuitems[itemOn].status & IT_CALLTYPE)) - { - if (((currentMenu->menuitems[itemOn].status & IT_CALLTYPE) & IT_CALL_NOTMODIFIED) && majormods) - { - S_StartSound(NULL, sfx_menu1); - M_StartMessage(M_GetText("This cannot be done with complex addons\nor in a cheated game.\n\n(Press a key)\n"), NULL, MM_NOTHING); - return true; - } - } - S_StartSound(NULL, sfx_menu1); - switch (currentMenu->menuitems[itemOn].status & IT_TYPE) - { - case IT_CVAR: - case IT_ARROWS: - routine(1); // right arrow - break; - case IT_CALL: - routine(itemOn); - break; - case IT_SUBMENU: - currentMenu->lastOn = itemOn; - M_SetupNextMenu((menu_t *)currentMenu->menuitems[itemOn].itemaction.submenu); - break; - } - } - return true; - - case KEY_ESCAPE: - //case KEY_JOY1 + 2: - noFurtherInput = true; - currentMenu->lastOn = itemOn; - if (currentMenu->prevMenu) - { - //If we entered the game search menu, but didn't enter a game, - //make sure the game doesn't still think we're in a netgame. - if (!Playing() && netgame && multiplayer) - { - netgame = false; - multiplayer = false; - } - - if (currentMenu == &SP_TimeAttackDef) //|| currentMenu == &SP_NightsAttackDef - { - // D_StartTitle does its own wipe, since GS_TIMEATTACK is now a complete gamestate. - menuactive = false; - D_StartTitle(); - } - else - M_SetupNextMenu(currentMenu->prevMenu); - } - else - M_ClearMenus(true); - - return true; - - case KEY_BACKSPACE: - if ((currentMenu->menuitems[itemOn].status) == IT_CONTROL) - { - // detach any keys associated with the game control - G_ClearControlKeys(setupcontrols, currentMenu->menuitems[itemOn].alphaKey); - S_StartSound(NULL, sfx_shldls); - return true; - } - - if (routine && ((currentMenu->menuitems[itemOn].status & IT_TYPE) == IT_ARROWS - || (currentMenu->menuitems[itemOn].status & IT_TYPE) == IT_CVAR)) - { - consvar_t *cv = currentMenu->menuitems[itemOn].itemaction.cvar; - - if (cv == &cv_chooseskin - || cv == &cv_dummystaff - || cv == &cv_nextmap - || cv == &cv_newgametype) - return true; - - if (currentMenu != &OP_SoundOptionsDef || itemOn > 3) - S_StartSound(NULL, sfx_menu1); - routine(-1); - return true; - } - - // Why _does_ backspace go back anyway? - //currentMenu->lastOn = itemOn; - //if (currentMenu->prevMenu) - // M_SetupNextMenu(currentMenu->prevMenu); - return false; - - default: - break; - } - - return true; -} - -// special responder for demos -boolean M_DemoResponder(event_t *ev) -{ - - INT32 ch = -1; // cur event data - boolean eatinput = false; // :omnom: - - //should be accounted for beforehand but just to be safe... - if (!demo.playback || demo.title) - return false; - - if (noFurtherInput) - { - // Ignore input after enter/escape/other buttons - // (but still allow shift keyup so caps doesn't get stuck) - return false; - } - else if (ev->type == ev_keydown && !con_destlines) // not while the console is on please - { - ch = ev->data1; - // since this is ONLY for demos, there isn't MUCH for us to do. - // mirrored from m_responder - - switch (ch) - { - // arbitrary keyboard shortcuts because fuck you - - case '\'': // toggle freecam - M_PlaybackToggleFreecam(0); - eatinput = true; - break; - - case ']': // ffw / advance frame (depends on if paused or not) - if (paused) - M_PlaybackAdvance(0); - else - M_PlaybackFastForward(0); - eatinput = true; - break; - - case '[': // rewind /backupframe, uses the same function - M_PlaybackRewind(0); - break; - - case '\\': // pause - M_PlaybackPause(0); - eatinput = true; - break; - - // viewpoints, an annoyance (tm) - case '-': // viewpoint minus - M_PlaybackSetViews(-1); // yeah lol. - eatinput = true; - break; - - case '=': // viewpoint plus - M_PlaybackSetViews(1); // yeah lol. - eatinput = true; - break; - - // switch viewpoints: - case '1': // viewpoint for p1 (also f12) - // maximum laziness: - if (!demo.freecam) - G_AdjustView(1, 1, true); - break; - case '2': // viewpoint for p2 - if (!demo.freecam) - G_AdjustView(2, 1, true); - break; - case '3': // viewpoint for p3 - if (!demo.freecam) - G_AdjustView(3, 1, true); - break; - case '4': // viewpoint for p4 - if (!demo.freecam) - G_AdjustView(4, 1, true); - break; - - default: break; - } - - } - return eatinput; -} - - -// -// M_Drawer -// Called after the view has been rendered, -// but before it has been blitted. -// -void M_Drawer(void) -{ - if (currentMenu == &MessageDef) - menuactive = true; - - if (menuactive) - { - // now that's more readable with a faded background (yeah like Quake...) - if (!WipeInAction && currentMenu != &PlaybackMenuDef) // Replay playback has its own background - V_DrawFadeScreen(0xFF00, 16); - - if (currentMenu->drawroutine) - { - M_GetGametypeColor(); - currentMenu->drawroutine(); // call current menu Draw routine - } - } - - // focus lost notification goes on top of everything, even the former everything - if (window_notinfocus && cv_showfocuslost.value) - { - M_DrawTextBox((BASEVIDWIDTH/2) - (60), (BASEVIDHEIGHT/2) - (16), 13, 2); - if (gamestate == GS_LEVEL && (P_AutoPause() || paused)) - V_DrawCenteredString(BASEVIDWIDTH/2, (BASEVIDHEIGHT/2) - (4), highlightflags, "Game Paused"); - else - V_DrawCenteredString(BASEVIDWIDTH/2, (BASEVIDHEIGHT/2) - (4), highlightflags, "Focus Lost"); - } -} - -// -// M_StartControlPanel -// -void M_StartControlPanel(void) -{ - // intro might call this repeatedly - if (menuactive) - { - CON_ToggleOff(); // move away console - return; - } - - menuactive = true; - - if (demo.playback) - { - currentMenu = &PlaybackMenuDef; - playback_last_menu_interaction_leveltime = leveltime; - } - else if (!Playing()) - { - // Secret menu! - //MainMenu[secrets].status = (M_AnySecretUnlocked()) ? (IT_STRING | IT_CALL) : (IT_DISABLED); - - currentMenu = &MainDef; -#ifdef TESTERS - itemOn = multiplr; -#else - itemOn = singleplr; -#endif - } - else if (modeattacking) - { - currentMenu = &MAPauseDef; - itemOn = mapause_continue; - } - else if (!(netgame || multiplayer)) // Single Player - { - if (gamestate != GS_LEVEL /*|| ultimatemode*/) // intermission, so gray out stuff. - { - SPauseMenu[spause_pandora].status = (M_SecretUnlocked(SECRET_PANDORA)) ? (IT_GRAYEDOUT) : (IT_DISABLED); - SPauseMenu[spause_retry].status = IT_GRAYEDOUT; - } - else - { - //INT32 numlives = 2; - - SPauseMenu[spause_pandora].status = (M_SecretUnlocked(SECRET_PANDORA)) ? (IT_STRING | IT_CALL) : (IT_DISABLED); - - /*if (&players[consoleplayer]) - { - numlives = players[consoleplayer].lives; - if (players[consoleplayer].playerstate != PST_LIVE) - ++numlives; - } - - // The list of things that can disable retrying is (was?) a little too complex - // for me to want to use the short if statement syntax - if (numlives <= 1 || G_IsSpecialStage(gamemap)) - SPauseMenu[spause_retry].status = (IT_GRAYEDOUT); - else*/ - SPauseMenu[spause_retry].status = (IT_STRING | IT_CALL); - } - - // We can always use level select though. :33 - //SPauseMenu[spause_levelselect].status = (gamecomplete) ? (IT_STRING | IT_CALL) : (IT_DISABLED); - - // And emblem hints. - SPauseMenu[spause_hints].status = (M_SecretUnlocked(SECRET_EMBLEMHINTS)) ? (IT_STRING | IT_CALL) : (IT_DISABLED); - - // Shift up Pandora's Box if both pandora and levelselect are active - /*if (SPauseMenu[spause_pandora].status != (IT_DISABLED) - && SPauseMenu[spause_levelselect].status != (IT_DISABLED)) - SPauseMenu[spause_pandora].alphaKey = 24; - else - SPauseMenu[spause_pandora].alphaKey = 32;*/ - - currentMenu = &SPauseDef; - itemOn = spause_continue; - } - else // multiplayer - { - MPauseMenu[mpause_switchmap].status = IT_DISABLED; - MPauseMenu[mpause_addons].status = IT_DISABLED; - MPauseMenu[mpause_scramble].status = IT_DISABLED; - MPauseMenu[mpause_psetupsplit].status = IT_DISABLED; - MPauseMenu[mpause_psetupsplit2].status = IT_DISABLED; - MPauseMenu[mpause_psetupsplit3].status = IT_DISABLED; - MPauseMenu[mpause_psetupsplit4].status = IT_DISABLED; - MPauseMenu[mpause_spectate].status = IT_DISABLED; - MPauseMenu[mpause_entergame].status = IT_DISABLED; - MPauseMenu[mpause_canceljoin].status = IT_DISABLED; - MPauseMenu[mpause_switchteam].status = IT_DISABLED; - MPauseMenu[mpause_switchspectate].status = IT_DISABLED; - MPauseMenu[mpause_psetup].status = IT_DISABLED; - MISC_ChangeTeamMenu[0].status = IT_DISABLED; - MISC_ChangeSpectateMenu[0].status = IT_DISABLED; - - // Reset these in case splitscreen messes things up - MPauseMenu[mpause_addons].alphaKey = 8; - MPauseMenu[mpause_scramble].alphaKey = 8; - MPauseMenu[mpause_switchmap].alphaKey = 24; - - MPauseMenu[mpause_switchteam].alphaKey = 48; - MPauseMenu[mpause_switchspectate].alphaKey = 48; - MPauseMenu[mpause_options].alphaKey = 64; - MPauseMenu[mpause_title].alphaKey = 80; - MPauseMenu[mpause_quit].alphaKey = 88; - - Dummymenuplayer_OnChange(); - - if ((server || IsPlayerAdmin(consoleplayer))) - { - MPauseMenu[mpause_switchmap].status = IT_STRING | IT_CALL; - MPauseMenu[mpause_addons].status = IT_STRING | IT_CALL; - if (G_GametypeHasTeams()) - MPauseMenu[mpause_scramble].status = IT_STRING | IT_SUBMENU; - } - - if (splitscreen) - { - MPauseMenu[mpause_psetupsplit].status = MPauseMenu[mpause_psetupsplit2].status = IT_STRING | IT_CALL; - MISC_ChangeTeamMenu[0].status = MISC_ChangeSpectateMenu[0].status = IT_STRING|IT_CVAR; - - if (netgame) - { - if (G_GametypeHasTeams()) - { - MPauseMenu[mpause_switchteam].status = IT_STRING | IT_SUBMENU; - MPauseMenu[mpause_switchteam].alphaKey += ((splitscreen+1) * 8); - MPauseMenu[mpause_options].alphaKey += 8; - MPauseMenu[mpause_title].alphaKey += 8; - MPauseMenu[mpause_quit].alphaKey += 8; - } - else if (G_GametypeHasSpectators()) - { - MPauseMenu[mpause_switchspectate].status = IT_STRING | IT_SUBMENU; - MPauseMenu[mpause_switchspectate].alphaKey += ((splitscreen+1) * 8); - MPauseMenu[mpause_options].alphaKey += 8; - MPauseMenu[mpause_title].alphaKey += 8; - MPauseMenu[mpause_quit].alphaKey += 8; - } - } - - if (splitscreen > 1) - { - MPauseMenu[mpause_psetupsplit3].status = IT_STRING | IT_CALL; - - MPauseMenu[mpause_options].alphaKey += 8; - MPauseMenu[mpause_title].alphaKey += 8; - MPauseMenu[mpause_quit].alphaKey += 8; - - if (splitscreen > 2) - { - MPauseMenu[mpause_psetupsplit4].status = IT_STRING | IT_CALL; - MPauseMenu[mpause_options].alphaKey += 8; - MPauseMenu[mpause_title].alphaKey += 8; - MPauseMenu[mpause_quit].alphaKey += 8; - } - } - } - else - { - MPauseMenu[mpause_psetup].status = IT_STRING | IT_CALL; - - if (G_GametypeHasTeams()) - MPauseMenu[mpause_switchteam].status = IT_STRING | IT_SUBMENU; - else if (G_GametypeHasSpectators()) - { - if (!players[consoleplayer].spectator) - MPauseMenu[mpause_spectate].status = IT_STRING | IT_CALL; - else if (players[consoleplayer].pflags & PF_WANTSTOJOIN) - MPauseMenu[mpause_canceljoin].status = IT_STRING | IT_CALL; - else - MPauseMenu[mpause_entergame].status = IT_STRING | IT_CALL; - } - else // in this odd case, we still want something to be on the menu even if it's useless - MPauseMenu[mpause_spectate].status = IT_GRAYEDOUT; - } - -#ifdef HAVE_DISCORDRPC - { - UINT8 i; - - for (i = 0; i < mpause_discordrequests; i++) - MPauseMenu[i].alphaKey -= 8; - - MPauseMenu[mpause_discordrequests].alphaKey = MPauseMenu[i].alphaKey; - - M_RefreshPauseMenu(); - } -#endif - - currentMenu = &MPauseDef; - itemOn = mpause_continue; - } - - CON_ToggleOff(); // move away console -} - -void M_EndModeAttackRun(void) -{ - M_ModeAttackEndGame(0); -} - -// -// M_ClearMenus -// -void M_ClearMenus(boolean callexitmenufunc) -{ - if (!menuactive) - return; - - if (currentMenu->quitroutine && callexitmenufunc && !currentMenu->quitroutine()) - return; // we can't quit this menu (also used to set parameter from the menu) - -#ifndef DC // Save the config file. I'm sick of crashing the game later and losing all my changes! - COM_BufAddText(va("saveconfig \"%s\" -silent\n", configfile)); -#endif //Alam: But not on the Dreamcast's VMUs - - if (currentMenu == &MessageDef) // Oh sod off! - currentMenu = &MainDef; // Not like it matters - menuactive = false; - hidetitlemap = false; -} - -// -// M_SetupNextMenu -// -void M_SetupNextMenu(menu_t *menudef) -{ - INT16 i; - - if (currentMenu->quitroutine) - { - // If you're going from a menu to itself, why are you running the quitroutine? You're not quitting it! -SH - if (currentMenu != menudef && !currentMenu->quitroutine()) - return; // we can't quit this menu (also used to set parameter from the menu) - } - currentMenu = menudef; - itemOn = currentMenu->lastOn; - - // in case of... - if (itemOn >= currentMenu->numitems) - itemOn = currentMenu->numitems - 1; - - // the curent item can be disabled, - // this code go up until an enabled item found - if ((currentMenu->menuitems[itemOn].status & IT_TYPE) == IT_SPACE) - { - for (i = 0; i < currentMenu->numitems; i++) - { - if ((currentMenu->menuitems[i].status & IT_TYPE) != IT_SPACE) - { - itemOn = i; - break; - } - } - } - - hidetitlemap = false; -} - -// Guess I'll put this here, idk -boolean M_MouseNeeded(void) -{ - return false; -} - -// -// M_Ticker -// -void M_Ticker(void) -{ - // reset input trigger - noFurtherInput = false; - - if (dedicated) - return; - - if (--skullAnimCounter <= 0) - skullAnimCounter = 8; - - followertimer++; - - if (currentMenu == &PlaybackMenuDef) - { - if (playback_enterheld > 0) - playback_enterheld--; - } - else - playback_enterheld = 0; - - //added : 30-01-98 : test mode for five seconds - if (vidm_testingmode > 0) - { - // restore the previous video mode - if (--vidm_testingmode == 0) - setmodeneeded = vidm_previousmode + 1; - } - -#if defined (MASTERSERVER) && defined (HAVE_THREADS) - I_lock_mutex(&ms_ServerList_mutex); - { - if (ms_ServerList) - { - CL_QueryServerList(ms_ServerList); - free(ms_ServerList); - ms_ServerList = NULL; - } - } - I_unlock_mutex(ms_ServerList_mutex); -#endif -} - -// -// M_Init -// -void M_Init(void) -{ - UINT8 i; - - CV_RegisterVar(&cv_nextmap); - CV_RegisterVar(&cv_newgametype); - CV_RegisterVar(&cv_chooseskin); - CV_RegisterVar(&cv_autorecord); - - if (dedicated) - return; - - COM_AddCommand("manual", Command_Manual_f); - - // Menu hacks - CV_RegisterVar(&cv_dummymenuplayer); - CV_RegisterVar(&cv_dummyteam); - CV_RegisterVar(&cv_dummyspectate); - CV_RegisterVar(&cv_dummyscramble); - CV_RegisterVar(&cv_dummyrings); - CV_RegisterVar(&cv_dummylives); - CV_RegisterVar(&cv_dummystaff); - - CV_RegisterVar(&cv_dummygpdifficulty); - CV_RegisterVar(&cv_dummygpencore); - CV_RegisterVar(&cv_dummygpcup); - - quitmsg[QUITMSG] = M_GetText("Eggman's tied explosives\nto your girlfriend, and\nwill activate them if\nyou press the 'Y' key!\nPress 'N' to save her!\n\n(Press 'Y' to quit)"); - quitmsg[QUITMSG1] = M_GetText("What would Tails say if\nhe saw you quitting the game?\n\n(Press 'Y' to quit)"); - quitmsg[QUITMSG2] = M_GetText("Hey!\nWhere do ya think you're goin'?\n\n(Press 'Y' to quit)"); - quitmsg[QUITMSG3] = M_GetText("Forget your studies!\nPlay some more!\n\n(Press 'Y' to quit)"); - quitmsg[QUITMSG4] = M_GetText("You're trying to say you\nlike Sonic R better than\nthis, aren't you?\n\n(Press 'Y' to quit)"); - quitmsg[QUITMSG5] = M_GetText("Don't leave yet -- there's a\nsuper emerald around that corner!\n\n(Press 'Y' to quit)"); - quitmsg[QUITMSG6] = M_GetText("You'd rather work than play?\n\n(Press 'Y' to quit)"); - quitmsg[QUITMSG7] = M_GetText("Go ahead and leave. See if I care...\n*sniffle*\n\n(Press 'Y' to quit)"); - - quitmsg[QUIT2MSG] = M_GetText("If you leave now,\nEggman will take over the world!\n\n(Press 'Y' to quit)"); - quitmsg[QUIT2MSG1] = M_GetText("On your mark,\nget set,\nhit the 'N' key!\n\n(Press 'Y' to quit)"); - quitmsg[QUIT2MSG2] = M_GetText("Aw c'mon, just\na few more laps!\n\n(Press 'Y' to quit)"); - quitmsg[QUIT2MSG3] = M_GetText("Did you get all those Chaos Emeralds?\n\n(Press 'Y' to quit)"); - quitmsg[QUIT2MSG4] = M_GetText("If you leave, I'll use\nmy Jawz on you!\n\n(Press 'Y' to quit)"); - quitmsg[QUIT2MSG5] = M_GetText("Don't go!\nYou might find the hidden\nlevels!\n\n(Press 'Y' to quit)"); - quitmsg[QUIT2MSG6] = M_GetText("Hit the 'N' key, Sonic!\nThe 'N' key!\n\n(Press 'Y' to quit)"); - - quitmsg[QUIT3MSG] = M_GetText("Are you really going to give up?\nWe certainly would never give you up.\n\n(Press 'Y' to quit)"); - quitmsg[QUIT3MSG1] = M_GetText("Come on, just ONE more netgame!\n\n(Press 'Y' to quit)"); - quitmsg[QUIT3MSG2] = M_GetText("Press 'N' to unlock\nthe Golden Kart!\n\n(Press 'Y' to quit)"); - quitmsg[QUIT3MSG3] = M_GetText("Couldn't handle\nthe banana meta?\n\n(Press 'Y' to quit)"); - quitmsg[QUIT3MSG4] = M_GetText("Every time you press 'Y', a\nRing Racers Developer cries...\n\n(Press 'Y' to quit)"); - quitmsg[QUIT3MSG5] = M_GetText("You'll be back to play soon, though...\n...right?\n\n(Press 'Y' to quit)"); - quitmsg[QUIT3MSG6] = M_GetText("Aww, is Eggman's Nightclub too\ndifficult for you?\n\n(Press 'Y' to quit)"); - - // Setup PlayerMenu table - for (i = 0; i < MAXSKINS; i++) - { - PlayerMenu[i].status = (i == 0 ? IT_CALL : IT_DISABLED); - PlayerMenu[i].patch = PlayerMenu[i].text = NULL; - PlayerMenu[i].itemaction.routine = M_ChoosePlayer; - PlayerMenu[i].alphaKey = 0; - } - -#ifdef HWRENDER - // Permanently hide some options based on render mode - if (rendermode == render_soft) - OP_VideoOptionsMenu[op_video_ogl].status = IT_DISABLED; -#endif - -#ifndef NONET - CV_RegisterVar(&cv_serversort); -#endif -} - -void M_InitCharacterTables(void) -{ - UINT8 i; - - // Setup PlayerMenu table - for (i = 0; i < MAXSKINS; i++) - { - PlayerMenu[i].status = (i < 4 ? IT_CALL : IT_DISABLED); - PlayerMenu[i].patch = PlayerMenu[i].text = NULL; - PlayerMenu[i].itemaction.routine = M_ChoosePlayer; - PlayerMenu[i].alphaKey = 0; - } - - // Setup description table - for (i = 0; i < MAXSKINS; i++) - { - if (i == 0) - { - strcpy(description[i].notes, "\x82Sonic\x80 is the fastest of the three, but also the hardest to control. Beginners beware, but experts will find Sonic very powerful.\n\n\x82""Ability:\x80 Speed Thok\nDouble jump to zoom forward with a huge burst of speed.\n\n\x82Tip:\x80 Simply letting go of forward does not slow down in SRB2. To slow down, hold the opposite direction."); - strcpy(description[i].picname, ""); - strcpy(description[i].skinname, "sonic"); - } - else if (i == 1) - { - strcpy(description[i].notes, "\x82Tails\x80 is the most mobile of the three, but has the slowest speed. Because of his mobility, he's well-\nsuited to beginners.\n\n\x82""Ability:\x80 Fly\nDouble jump to start flying for a limited time. Repetitively hit the jump button to ascend.\n\n\x82Tip:\x80 To quickly descend while flying, hit the spin button."); - strcpy(description[i].picname, ""); - strcpy(description[i].skinname, "tails"); - } - else if (i == 2) - { - strcpy(description[i].notes, "\x82Knuckles\x80 is well-\nrounded and can destroy breakable walls simply by touching them, but he can't jump as high as the other two.\n\n\x82""Ability:\x80 Glide & Climb\nDouble jump to glide in the air as long as jump is held. Glide into a wall to climb it.\n\n\x82Tip:\x80 Press spin while climbing to jump off the wall; press jump instead to jump off\nand face away from\nthe wall."); - strcpy(description[i].picname, ""); - strcpy(description[i].skinname, "knuckles"); - } - else if (i == 3) - { - strcpy(description[i].notes, "\x82Sonic & Tails\x80 team up to take on Dr. Eggman!\nControl Sonic while Tails desperately struggles to keep up.\n\nPlayer 2 can control Tails directly by setting the controls in the options menu.\nTails's directional controls are relative to Player 1's camera.\n\nTails can pick up Sonic while flying and carry him around."); - strcpy(description[i].picname, "CHRS&T"); - strcpy(description[i].skinname, "sonic&tails"); - } - else - { - strcpy(description[i].notes, "???"); - strcpy(description[i].picname, ""); - strcpy(description[i].skinname, ""); - } - } -} - -// ========================================================================== -// SPECIAL MENU OPTION DRAW ROUTINES GO HERE -// ========================================================================== - -// Converts a string into question marks. -// Used for the secrets menu, to hide yet-to-be-unlocked stuff. -static const char *M_CreateSecretMenuOption(const char *str) -{ - static char qbuf[32]; - int i; - - for (i = 0; i < 31; ++i) - { - if (!str[i]) - { - qbuf[i] = '\0'; - return qbuf; - } - else if (str[i] != ' ') - qbuf[i] = '?'; - else - qbuf[i] = ' '; - } - - qbuf[31] = '\0'; - return qbuf; -} - -static void M_DrawThermo(INT32 x, INT32 y, consvar_t *cv) -{ - INT32 xx = x, i; - lumpnum_t leftlump, rightlump, centerlump[2], cursorlump; - patch_t *p; - - leftlump = W_GetNumForName("M_THERML"); - rightlump = W_GetNumForName("M_THERMR"); - centerlump[0] = W_GetNumForName("M_THERMM"); - centerlump[1] = W_GetNumForName("M_THERMM"); - cursorlump = W_GetNumForName("M_THERMO"); - - V_DrawScaledPatch(xx, y, 0, p = W_CachePatchNum(leftlump,PU_CACHE)); - xx += SHORT(p->width) - SHORT(p->leftoffset); - for (i = 0; i < 16; i++) - { - V_DrawScaledPatch(xx, y, 0, W_CachePatchNum(centerlump[i & 1], PU_CACHE)); - xx += 8; - } - V_DrawScaledPatch(xx, y, 0, W_CachePatchNum(rightlump, PU_CACHE)); - - xx = (cv->value - cv->PossibleValue[0].value) * (15*8) / - (cv->PossibleValue[1].value - cv->PossibleValue[0].value); - - V_DrawScaledPatch((x + 8) + xx, y, 0, W_CachePatchNum(cursorlump, PU_CACHE)); -} - -// A smaller 'Thermo', with range given as percents (0-100) -static void M_DrawSlider(INT32 x, INT32 y, const consvar_t *cv, boolean ontop) -{ - INT32 i; - INT32 range; - patch_t *p; - - for (i = 0; cv->PossibleValue[i+1].strvalue; i++); - - x = BASEVIDWIDTH - x - SLIDER_WIDTH; - - if (ontop) - { - V_DrawCharacter(x - 16 - (skullAnimCounter/5), y, - '\x1C' | highlightflags, false); // left arrow - V_DrawCharacter(x+(SLIDER_RANGE*8) + 8 + (skullAnimCounter/5), y, - '\x1D' | highlightflags, false); // right arrow - } - - if ((range = atoi(cv->defaultvalue)) != cv->value) - { - range = ((range - cv->PossibleValue[0].value) * 100 / - (cv->PossibleValue[1].value - cv->PossibleValue[0].value)); - - if (range < 0) - range = 0; - if (range > 100) - range = 100; - - // draw the default - p = W_CachePatchName("M_SLIDEC", PU_CACHE); - V_DrawScaledPatch(x - 4 + (((SLIDER_RANGE)*8 + 4)*range)/100, y, 0, p); - } - - V_DrawScaledPatch(x - 8, y, 0, W_CachePatchName("M_SLIDEL", PU_CACHE)); - - p = W_CachePatchName("M_SLIDEM", PU_CACHE); - for (i = 0; i < SLIDER_RANGE; i++) - V_DrawScaledPatch (x+i*8, y, 0,p); - - p = W_CachePatchName("M_SLIDER", PU_CACHE); - V_DrawScaledPatch(x+SLIDER_RANGE*8, y, 0, p); - - range = ((cv->value - cv->PossibleValue[0].value) * 100 / - (cv->PossibleValue[1].value - cv->PossibleValue[0].value)); - - if (range < 0) - range = 0; - if (range > 100) - range = 100; - - // draw the slider cursor - p = W_CachePatchName("M_SLIDEC", PU_CACHE); - V_DrawScaledPatch(x - 4 + (((SLIDER_RANGE)*8 + 4)*range)/100, y, 0, p); -} - -// -// Draw a textbox, like Quake does, because sometimes it's difficult -// to read the text with all the stuff in the background... -// -void M_DrawTextBox(INT32 x, INT32 y, INT32 width, INT32 boxlines) -{ - // Solid color textbox. - V_DrawFill(x+5, y+5, width*8+6, boxlines*8+6, 159); - //V_DrawFill(x+8, y+8, width*8, boxlines*8, 31); -/* - patch_t *p; - INT32 cx, cy, n; - INT32 step, boff; - - step = 8; - boff = 8; - - // draw left side - cx = x; - cy = y; - V_DrawScaledPatch(cx, cy, 0, W_CachePatchNum(viewborderlump[BRDR_TL], PU_CACHE)); - cy += boff; - p = W_CachePatchNum(viewborderlump[BRDR_L], PU_CACHE); - for (n = 0; n < boxlines; n++) - { - V_DrawScaledPatch(cx, cy, V_WRAPY, p); - cy += step; - } - V_DrawScaledPatch(cx, cy, 0, W_CachePatchNum(viewborderlump[BRDR_BL], PU_CACHE)); - - // draw middle - V_DrawFlatFill(x + boff, y + boff, width*step, boxlines*step, st_borderpatchnum); - - cx += boff; - cy = y; - while (width > 0) - { - V_DrawScaledPatch(cx, cy, 0, W_CachePatchNum(viewborderlump[BRDR_T], PU_CACHE)); - V_DrawScaledPatch(cx, y + boff + boxlines*step, 0, W_CachePatchNum(viewborderlump[BRDR_B], PU_CACHE)); - width--; - cx += step; - } - - // draw right side - cy = y; - V_DrawScaledPatch(cx, cy, 0, W_CachePatchNum(viewborderlump[BRDR_TR], PU_CACHE)); - cy += boff; - p = W_CachePatchNum(viewborderlump[BRDR_R], PU_CACHE); - for (n = 0; n < boxlines; n++) - { - V_DrawScaledPatch(cx, cy, V_WRAPY, p); - cy += step; - } - V_DrawScaledPatch(cx, cy, 0, W_CachePatchNum(viewborderlump[BRDR_BR], PU_CACHE)); -*/ -} - -// -// Draw border for the savegame description -// -/*static void M_DrawSaveLoadBorder(INT32 x,INT32 y) -{ - INT32 i; - - V_DrawScaledPatch (x-8,y+7,0,W_CachePatchName("M_LSLEFT",PU_CACHE)); - - for (i = 0;i < 24;i++) - { - V_DrawScaledPatch (x,y+7,0,W_CachePatchName("M_LSCNTR",PU_CACHE)); - x += 8; - } - - V_DrawScaledPatch (x,y+7,0,W_CachePatchName("M_LSRGHT",PU_CACHE)); -}*/ - -// horizontally centered text -static void M_CentreText(INT32 y, const char *string) -{ - INT32 x; - //added : 02-02-98 : centre on 320, because V_DrawString centers on vid.width... - x = (BASEVIDWIDTH - V_StringWidth(string, V_OLDSPACING))>>1; - V_DrawString(x,y,V_OLDSPACING,string); -} - -// -// M_DrawMapEmblems -// -// used by pause & statistics to draw a row of emblems for a map -// -static void M_DrawMapEmblems(INT32 mapnum, INT32 x, INT32 y) -{ - UINT8 lasttype = UINT8_MAX, curtype; - emblem_t *emblem = M_GetLevelEmblems(mapnum); - - while (emblem) - { - switch (emblem->type) - { - case ET_TIME: //case ET_SCORE: case ET_RINGS: - curtype = 1; break; - /*case ET_NGRADE: case ET_NTIME: - curtype = 2; break;*/ - default: - curtype = 0; break; - } - - // Shift over if emblem is of a different discipline - if (lasttype != UINT8_MAX && lasttype != curtype) - x -= 4; - lasttype = curtype; - - if (emblem->collected) - V_DrawSmallMappedPatch(x, y, 0, W_CachePatchName(M_GetEmblemPatch(emblem, false), PU_CACHE), - R_GetTranslationColormap(TC_DEFAULT, M_GetEmblemColor(emblem), GTC_MENUCACHE)); - else - V_DrawSmallScaledPatch(x, y, 0, W_CachePatchName("NEEDIT", PU_CACHE)); - - emblem = M_GetLevelEmblems(-1); - x -= 8; - } -} - -static void M_DrawMenuTitle(void) -{ - if (currentMenu->menutitlepic) - { - patch_t *p = W_CachePatchName(currentMenu->menutitlepic, PU_CACHE); - - if (p->height > 24) // title is larger than normal - { - INT32 xtitle = (BASEVIDWIDTH - (SHORT(p->width)/2))/2; - INT32 ytitle = (30 - (SHORT(p->height)/2))/2; - - if (xtitle < 0) - xtitle = 0; - if (ytitle < 0) - ytitle = 0; - - V_DrawSmallScaledPatch(xtitle, ytitle, 0, p); - } - else - { - INT32 xtitle = (BASEVIDWIDTH - SHORT(p->width))/2; - INT32 ytitle = (30 - SHORT(p->height))/2; - - if (xtitle < 0) - xtitle = 0; - if (ytitle < 0) - ytitle = 0; - - V_DrawScaledPatch(xtitle, ytitle, 0, p); - } - } -} - -static void M_DrawGenericMenu(void) -{ - INT32 x, y, w, i, cursory = 0; - - // DRAW MENU - x = currentMenu->x; - y = currentMenu->y; - - // draw title (or big pic) - M_DrawMenuTitle(); - - for (i = 0; i < currentMenu->numitems; i++) - { - if (i == itemOn) - cursory = y; - switch (currentMenu->menuitems[i].status & IT_DISPLAY) - { - case IT_PATCH: - if (currentMenu->menuitems[i].patch && currentMenu->menuitems[i].patch[0]) - { - if (currentMenu->menuitems[i].status & IT_CENTER) - { - patch_t *p; - p = W_CachePatchName(currentMenu->menuitems[i].patch, PU_CACHE); - V_DrawScaledPatch((BASEVIDWIDTH - SHORT(p->width))/2, y, 0, p); - } - else - { - V_DrawScaledPatch(x, y, 0, - W_CachePatchName(currentMenu->menuitems[i].patch, PU_CACHE)); - } - } - /* FALLTHRU */ - case IT_NOTHING: - case IT_DYBIGSPACE: - y = currentMenu->y+currentMenu->menuitems[i].alphaKey;//+= LINEHEIGHT; - break; - case IT_BIGSLIDER: - M_DrawThermo(x, y, currentMenu->menuitems[i].itemaction.cvar); - y += LINEHEIGHT; - break; - case IT_STRING: - case IT_WHITESTRING: - if (currentMenu->menuitems[i].alphaKey) - y = currentMenu->y+currentMenu->menuitems[i].alphaKey; - if (i == itemOn) - cursory = y; - - if ((currentMenu->menuitems[i].status & IT_DISPLAY)==IT_STRING) - V_DrawString(x, y, 0, currentMenu->menuitems[i].text); - else - V_DrawString(x, y, highlightflags, currentMenu->menuitems[i].text); - - // Cvar specific handling - switch (currentMenu->menuitems[i].status & IT_TYPE) - case IT_CVAR: - { - consvar_t *cv = currentMenu->menuitems[i].itemaction.cvar; - switch (currentMenu->menuitems[i].status & IT_CVARTYPE) - { - case IT_CV_SLIDER: - M_DrawSlider(x, y, cv, (i == itemOn)); - case IT_CV_NOPRINT: // color use this - case IT_CV_INVISSLIDER: // monitor toggles use this - break; - case IT_CV_STRING: - M_DrawTextBox(x, y + 4, MAXSTRINGLENGTH, 1); - V_DrawString(x + 8, y + 12, V_ALLOWLOWERCASE, cv->string); - if (skullAnimCounter < 4 && i == itemOn) - V_DrawCharacter(x + 8 + V_StringWidth(cv->string, 0), y + 12, - '_' | 0x80, false); - y += 16; - break; - default: - w = V_StringWidth(cv->string, 0); - V_DrawString(BASEVIDWIDTH - x - w, y, - ((cv->flags & CV_CHEAT) && !CV_IsSetToDefault(cv) ? warningflags : highlightflags), cv->string); - if (i == itemOn) - { - V_DrawCharacter(BASEVIDWIDTH - x - 10 - w - (skullAnimCounter/5), y, - '\x1C' | highlightflags, false); // left arrow - V_DrawCharacter(BASEVIDWIDTH - x + 2 + (skullAnimCounter/5), y, - '\x1D' | highlightflags, false); // right arrow - } - break; - } - break; - } - y += STRINGHEIGHT; - break; - case IT_STRING2: - V_DrawString(x, y, 0, currentMenu->menuitems[i].text); - /* FALLTHRU */ - case IT_DYLITLSPACE: - y += SMALLLINEHEIGHT; - break; - case IT_GRAYPATCH: - if (currentMenu->menuitems[i].patch && currentMenu->menuitems[i].patch[0]) - V_DrawMappedPatch(x, y, 0, - W_CachePatchName(currentMenu->menuitems[i].patch,PU_CACHE), graymap); - y += LINEHEIGHT; - break; - case IT_TRANSTEXT: - if (currentMenu->menuitems[i].alphaKey) - y = currentMenu->y+currentMenu->menuitems[i].alphaKey; - /* FALLTHRU */ - case IT_TRANSTEXT2: - V_DrawString(x, y, V_TRANSLUCENT, currentMenu->menuitems[i].text); - y += SMALLLINEHEIGHT; - break; - case IT_QUESTIONMARKS: - if (currentMenu->menuitems[i].alphaKey) - y = currentMenu->y+currentMenu->menuitems[i].alphaKey; - - V_DrawString(x, y, V_TRANSLUCENT|V_OLDSPACING, M_CreateSecretMenuOption(currentMenu->menuitems[i].text)); - y += SMALLLINEHEIGHT; - break; - case IT_HEADERTEXT: // draws 16 pixels to the left, in yellow text - if (currentMenu->menuitems[i].alphaKey) - y = currentMenu->y+currentMenu->menuitems[i].alphaKey; - - V_DrawString(x-16, y, highlightflags, currentMenu->menuitems[i].text); - y += SMALLLINEHEIGHT; - break; - } - } - - // DRAW THE SKULL CURSOR - if (((currentMenu->menuitems[itemOn].status & IT_DISPLAY) == IT_PATCH) - || ((currentMenu->menuitems[itemOn].status & IT_DISPLAY) == IT_NOTHING)) - { - V_DrawScaledPatch(currentMenu->x + SKULLXOFF, cursory - 5, 0, - W_CachePatchName("M_CURSOR", PU_CACHE)); - } - else - { - V_DrawScaledPatch(currentMenu->x - 24, cursory, 0, - W_CachePatchName("M_CURSOR", PU_CACHE)); - V_DrawString(currentMenu->x, cursory, highlightflags, currentMenu->menuitems[itemOn].text); - } -} - -static void M_DrawGenericBackgroundMenu(void) -{ - V_DrawPatchFill(W_CachePatchName("SRB2BACK", PU_CACHE)); - M_DrawGenericMenu(); -} - -static void M_DrawPauseMenu(void) -{ -#if 0 - if (!netgame && !multiplayer && (gamestate == GS_LEVEL || gamestate == GS_INTERMISSION || gamestate == GS_VOTING)) - { - emblem_t *emblem_detail[3] = {NULL, NULL, NULL}; - char emblem_text[3][20]; - INT32 i; - - M_DrawTextBox(27, 16, 32, 6); - - // Draw any and all emblems at the top. - M_DrawMapEmblems(gamemap, 272, 28); - - if (strlen(mapheaderinfo[gamemap-1]->zonttl) > 0) - { - if (mapheaderinfo[gamemap-1]->actnum > 0) - V_DrawString(40, 28, highlightflags, va("%s %s %d", mapheaderinfo[gamemap-1]->lvlttl, mapheaderinfo[gamemap-1]->zonttl, mapheaderinfo[gamemap-1]->actnum)); - else - V_DrawString(40, 28, highlightflags, va("%s %s", mapheaderinfo[gamemap-1]->lvlttl, mapheaderinfo[gamemap-1]->zonttl)); - } - else - { - if (mapheaderinfo[gamemap-1]->actnum > 0) - V_DrawString(40, 28, highlightflags, va("%s %d", mapheaderinfo[gamemap-1]->lvlttl, mapheaderinfo[gamemap-1]->actnum)); - else - V_DrawString(40, 28, highlightflags, mapheaderinfo[gamemap-1]->lvlttl); - } - - // Set up the detail boxes. - { - emblem_t *emblem = M_GetLevelEmblems(gamemap); - while (emblem) - { - INT32 emblemslot; - char targettext[9], currenttext[9]; - - switch (emblem->type) - { - /*case ET_SCORE: - snprintf(targettext, 9, "%d", emblem->var); - snprintf(currenttext, 9, "%u", G_GetBestScore(gamemap)); - - targettext[8] = 0; - currenttext[8] = 0; - - emblemslot = 0; - break;*/ - case ET_TIME: - emblemslot = emblem->var; // dumb hack - snprintf(targettext, 9, "%i:%02i.%02i", - G_TicsToMinutes((tic_t)emblemslot, false), - G_TicsToSeconds((tic_t)emblemslot), - G_TicsToCentiseconds((tic_t)emblemslot)); - - emblemslot = (INT32)G_GetBestTime(gamemap); // dumb hack pt ii - if ((tic_t)emblemslot == UINT32_MAX) - snprintf(currenttext, 9, "-:--.--"); - else - snprintf(currenttext, 9, "%i:%02i.%02i", - G_TicsToMinutes((tic_t)emblemslot, false), - G_TicsToSeconds((tic_t)emblemslot), - G_TicsToCentiseconds((tic_t)emblemslot)); - - targettext[8] = 0; - currenttext[8] = 0; - - emblemslot = 1; - break; - /*case ET_RINGS: - snprintf(targettext, 9, "%d", emblem->var); - snprintf(currenttext, 9, "%u", G_GetBestRings(gamemap)); - - targettext[8] = 0; - currenttext[8] = 0; - - emblemslot = 2; - break; - case ET_NGRADE: - snprintf(targettext, 9, "%u", P_GetScoreForGrade(gamemap, 0, emblem->var)); - snprintf(currenttext, 9, "%u", G_GetBestNightsScore(gamemap, 0)); - - targettext[8] = 0; - currenttext[8] = 0; - - emblemslot = 1; - break; - case ET_NTIME: - emblemslot = emblem->var; // dumb hack pt iii - snprintf(targettext, 9, "%i:%02i.%02i", - G_TicsToMinutes((tic_t)emblemslot, false), - G_TicsToSeconds((tic_t)emblemslot), - G_TicsToCentiseconds((tic_t)emblemslot)); - - emblemslot = (INT32)G_GetBestNightsTime(gamemap, 0); // dumb hack pt iv - if ((tic_t)emblemslot == UINT32_MAX) - snprintf(currenttext, 9, "-:--.--"); - else - snprintf(currenttext, 9, "%i:%02i.%02i", - G_TicsToMinutes((tic_t)emblemslot, false), - G_TicsToSeconds((tic_t)emblemslot), - G_TicsToCentiseconds((tic_t)emblemslot)); - - targettext[8] = 0; - currenttext[8] = 0; - - emblemslot = 2; - break;*/ - default: - goto bademblem; - } - if (emblem_detail[emblemslot]) - goto bademblem; - - emblem_detail[emblemslot] = emblem; - snprintf(emblem_text[emblemslot], 20, "%8s /%8s", currenttext, targettext); - emblem_text[emblemslot][19] = 0; - - bademblem: - emblem = M_GetLevelEmblems(-1); - } - } - for (i = 0; i < 3; ++i) - { - emblem_t *emblem = emblem_detail[i]; - if (!emblem) - continue; - - if (emblem->collected) - V_DrawSmallMappedPatch(40, 44 + (i*8), 0, W_CachePatchName(M_GetEmblemPatch(emblem, false), PU_CACHE), - R_GetTranslationColormap(TC_DEFAULT, M_GetEmblemColor(emblem), GTC_MENUCACHE)); - else - V_DrawSmallScaledPatch(40, 44 + (i*8), 0, W_CachePatchName("NEEDIT", PU_CACHE)); - - switch (emblem->type) - { - /*case ET_SCORE: - case ET_NGRADE: - V_DrawString(56, 44 + (i*8), highlightflags, "SCORE:"); - break;*/ - case ET_TIME: - //case ET_NTIME: - V_DrawString(56, 44 + (i*8), highlightflags, "TIME:"); - break; - /*case ET_RINGS: - V_DrawString(56, 44 + (i*8), highlightflags, "RINGS:"); - break;*/ - } - V_DrawRightAlignedString(284, 44 + (i*8), V_MONOSPACE, emblem_text[i]); - } - } -#endif - -#ifdef HAVE_DISCORDRPC - // kind of hackily baked in here - if (currentMenu == &MPauseDef && discordRequestList != NULL) - { - const tic_t freq = TICRATE/2; - - if ((leveltime % freq) >= freq/2) - { - V_DrawFixedPatch(204 * FRACUNIT, - (currentMenu->y + MPauseMenu[mpause_discordrequests].alphaKey - 1) * FRACUNIT, - FRACUNIT, - 0, - W_CachePatchName("K_REQUE2", PU_CACHE), - NULL - ); - } - } -#endif - - M_DrawGenericMenu(); -} - -static void M_DrawCenteredMenu(void) -{ - INT32 x, y, i, cursory = 0; - - // DRAW MENU - x = currentMenu->x; - y = currentMenu->y; - - // draw title (or big pic) - M_DrawMenuTitle(); - - for (i = 0; i < currentMenu->numitems; i++) - { - if (i == itemOn) - cursory = y; - switch (currentMenu->menuitems[i].status & IT_DISPLAY) - { - case IT_PATCH: - if (currentMenu->menuitems[i].patch && currentMenu->menuitems[i].patch[0]) - { - if (currentMenu->menuitems[i].status & IT_CENTER) - { - patch_t *p; - p = W_CachePatchName(currentMenu->menuitems[i].patch, PU_CACHE); - V_DrawScaledPatch((BASEVIDWIDTH - SHORT(p->width))/2, y, 0, p); - } - else - { - V_DrawScaledPatch(x, y, 0, - W_CachePatchName(currentMenu->menuitems[i].patch, PU_CACHE)); - } - } - /* FALLTHRU */ - case IT_NOTHING: - case IT_DYBIGSPACE: - y += LINEHEIGHT; - break; - case IT_BIGSLIDER: - M_DrawThermo(x, y, currentMenu->menuitems[i].itemaction.cvar); - y += LINEHEIGHT; - break; - case IT_STRING: - case IT_WHITESTRING: - if (currentMenu->menuitems[i].alphaKey) - y = currentMenu->y+currentMenu->menuitems[i].alphaKey; - if (i == itemOn) - cursory = y; - - if ((currentMenu->menuitems[i].status & IT_DISPLAY)==IT_STRING) - V_DrawCenteredString(x, y, 0, currentMenu->menuitems[i].text); - else - V_DrawCenteredString(x, y, highlightflags, currentMenu->menuitems[i].text); - - // Cvar specific handling - switch(currentMenu->menuitems[i].status & IT_TYPE) - case IT_CVAR: - { - consvar_t *cv = currentMenu->menuitems[i].itemaction.cvar; - switch(currentMenu->menuitems[i].status & IT_CVARTYPE) - { - case IT_CV_SLIDER: - M_DrawSlider(x, y, cv, (i == itemOn)); - case IT_CV_NOPRINT: // color use this - break; - case IT_CV_STRING: - M_DrawTextBox(x, y + 4, MAXSTRINGLENGTH, 1); - V_DrawString(x + 8, y + 12, V_ALLOWLOWERCASE, cv->string); - if (skullAnimCounter < 4 && i == itemOn) - V_DrawCharacter(x + 8 + V_StringWidth(cv->string, 0), y + 12, - '_' | 0x80, false); - y += 16; - break; - default: - V_DrawString(BASEVIDWIDTH - x - V_StringWidth(cv->string, 0), y, - ((cv->flags & CV_CHEAT) && !CV_IsSetToDefault(cv) ? warningflags : highlightflags), cv->string); - break; - } - break; - } - y += STRINGHEIGHT; - break; - case IT_STRING2: - V_DrawCenteredString(x, y, 0, currentMenu->menuitems[i].text); - /* FALLTHRU */ - case IT_DYLITLSPACE: - y += SMALLLINEHEIGHT; - break; - case IT_QUESTIONMARKS: - if (currentMenu->menuitems[i].alphaKey) - y = currentMenu->y+currentMenu->menuitems[i].alphaKey; - - V_DrawCenteredString(x, y, V_TRANSLUCENT|V_OLDSPACING, M_CreateSecretMenuOption(currentMenu->menuitems[i].text)); - y += SMALLLINEHEIGHT; - break; - case IT_GRAYPATCH: - if (currentMenu->menuitems[i].patch && currentMenu->menuitems[i].patch[0]) - V_DrawMappedPatch(x, y, 0, - W_CachePatchName(currentMenu->menuitems[i].patch,PU_CACHE), graymap); - y += LINEHEIGHT; - break; - case IT_TRANSTEXT: - if (currentMenu->menuitems[i].alphaKey) - y = currentMenu->y+currentMenu->menuitems[i].alphaKey; - /* FALLTHRU */ - case IT_TRANSTEXT2: - V_DrawCenteredString(x, y, V_TRANSLUCENT, currentMenu->menuitems[i].text); - y += SMALLLINEHEIGHT; - break; - } - } - - // DRAW THE SKULL CURSOR - if (((currentMenu->menuitems[itemOn].status & IT_DISPLAY) == IT_PATCH) - || ((currentMenu->menuitems[itemOn].status & IT_DISPLAY) == IT_NOTHING)) - { - V_DrawScaledPatch(x + SKULLXOFF, cursory - 5, 0, - W_CachePatchName("M_CURSOR", PU_CACHE)); - } - else - { - V_DrawScaledPatch(x - V_StringWidth(currentMenu->menuitems[itemOn].text, 0)/2 - 24, cursory, 0, - W_CachePatchName("M_CURSOR", PU_CACHE)); - V_DrawCenteredString(x, cursory, highlightflags, currentMenu->menuitems[itemOn].text); - } -} - -// -// 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; -} - -// ========================================================================== -// Extraneous menu patching functions -// ========================================================================== - -// -// M_PatchSkinNameTable -// -// Like M_PatchLevelNameTable, but for cv_chooseskin -// -static void M_PatchSkinNameTable(void) -{ - INT32 j; - - memset(skins_cons_t, 0, sizeof (skins_cons_t)); - - for (j = 0; j < MAXSKINS; j++) - { - if (skins[j].name[0] != '\0') - { - skins_cons_t[j].strvalue = skins[j].name; - skins_cons_t[j].value = j+1; - } - else - { - skins_cons_t[j].strvalue = NULL; - skins_cons_t[j].value = 0; - break; - } - } - - j = R_SkinAvailable(cv_skin[0].string); - if (j == -1) - j = 0; - - CV_SetValue(&cv_chooseskin, j+1); // This causes crash sometimes?! - - return; -} - -// -// M_PrepareCupList -// -static boolean M_PrepareCupList(void) -{ - cupheader_t *cup = kartcupheaders; - INT32 i = 0; - - memset(dummygpcup_cons_t, 0, sizeof (dummygpcup_cons_t)); - - if (cup == NULL) - return false; - - while (cup != NULL) - { - dummygpcup_cons_t[i].strvalue = cup->name; - dummygpcup_cons_t[i].value = i+1; - // this will probably crash or do something stupid at over 50 cups, - // but this is all behavior that gets completely overwritten in new-menus, so I'm not worried - i++; - cup = cup->next; - } - - for (; i < 50; i++) - { - dummygpcup_cons_t[i].strvalue = NULL; - dummygpcup_cons_t[i].value = 0; - } - - CV_SetValue(&cv_dummygpcup, 1); // This causes crash sometimes?! - - return true; -} - -// Call before showing any level-select menus -static void M_PrepareLevelSelect(void) -{ - if (levellistmode != LLM_CREATESERVER) - CV_SetValue(&cv_nextmap, M_GetFirstLevelInList()); - else - Newgametype_OnChange(); // Make sure to start on an appropriate map if wads have been added -} - -// -// M_CanShowLevelInList -// -// Determines whether to show a given map in the various level-select lists. -// Set gt = -1 to ignore gametype. -// -boolean M_CanShowLevelInList(INT32 mapnum, INT32 gt) -{ - // Random map! - if (mapnum == -1) - return (levellistmode == LLM_CREATESERVER); - - // Does the map exist? - if (!mapheaderinfo[mapnum]) - return false; - - // Does the map have a name? - if (!mapheaderinfo[mapnum]->lvlttl[0]) - return false; - - switch (levellistmode) - { - case LLM_CREATESERVER: - // Should the map be hidden? - if (mapheaderinfo[mapnum]->menuflags & LF2_HIDEINMENU && mapnum+1 != gamemap) - return false; - - if (M_MapLocked(mapnum+1)) - return false; // not unlocked - - if (gt >= 0 && gt < gametypecount && mapheaderinfo[mapnum]->typeoflevel & gametypetol[gt]) - return true; - - return false; - - /*case LLM_LEVELSELECT: - if (mapheaderinfo[mapnum]->levelselect != maplistoption) - return false; - - if (M_MapLocked(mapnum+1)) - return false; // not unlocked - - return true;*/ - case LLM_TIMEATTACK: - case LLM_BREAKTHECAPSULES: - if (mapheaderinfo[mapnum]->menuflags & LF2_NOTIMEATTACK) - return false; - - if ((levellistmode == LLM_TIMEATTACK && !(mapheaderinfo[mapnum]->typeoflevel & TOL_RACE)) - || (levellistmode == LLM_BREAKTHECAPSULES && !(mapheaderinfo[mapnum]->typeoflevel & TOL_BATTLE))) - return false; - - if (M_MapLocked(mapnum+1)) - return false; // not unlocked - - if (M_SecretUnlocked(SECRET_HELLATTACK)) - return true; // now you're in hell - - if (mapheaderinfo[mapnum]->menuflags & LF2_HIDEINMENU) - return false; // map hell - - if ((mapheaderinfo[mapnum]->menuflags & LF2_VISITNEEDED) && !mapvisited[mapnum]) - return false; - - return true; - - case LLM_BOSS: - if (!(mapheaderinfo[mapnum]->typeoflevel & TOL_BOSS)) - return false; - - return true; - - default: - return false; - } - - // Hmm? Couldn't decide? - return false; -} - -static INT32 M_CountLevelsToShowInList(void) -{ - INT32 mapnum, count = 0; - - for (mapnum = 0; mapnum < NUMMAPS; mapnum++) - if (M_CanShowLevelInList(mapnum, -1)) - count++; - - return count; -} - -static INT32 M_GetFirstLevelInList(void) -{ - INT32 mapnum; - - for (mapnum = 0; mapnum < NUMMAPS; mapnum++) - if (M_CanShowLevelInList(mapnum, -1)) - return mapnum + 1; - - return 1; -} - -// ================================================== -// MESSAGE BOX (aka: a hacked, cobbled together menu) -// ================================================== -static void M_DrawMessageMenu(void); - -// Because this is just a hack-ish 'menu', I'm not putting this with the others -static menuitem_t MessageMenu[] = {0}; - -menu_t MessageDef = -{ - MN_NONE, // id - NULL, // title - 1, // # of menu items - NULL, // previous menu (TO HACK) - MessageMenu, // menuitem_t -> - M_DrawMessageMenu, // drawing routine -> - 0, 0, // x, y (TO HACK) - 0, // lastOn, flags (TO HACK) - NULL -}; - - -void M_StartMessage(const char *string, void *routine, - menumessagetype_t itemtype) -{ - size_t max = 0, start = 0, i, strlines; - 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, colors, etc. but who cares. - strlines = 0; - 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 - 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; - } - } - - start = 0; - max = 0; - - M_StartControlPanel(); // can't put menuactive to true - - if (currentMenu == &MessageDef) // Prevent recursion - MessageDef.prevMenu = ((demo.playback) ? &PlaybackMenuDef : &MainDef); - else - MessageDef.prevMenu = currentMenu; - - MessageDef.menuitems[0].text = message; - MessageDef.menuitems[0].alphaKey = (UINT8)itemtype; - if (!routine && itemtype != MM_NOTHING) itemtype = MM_NOTHING; - switch (itemtype) - { - case MM_NOTHING: - MessageDef.menuitems[0].status = IT_MSGHANDLER; - MessageDef.menuitems[0].itemaction.routine = M_StopMessage; - break; - case MM_YESNO: - MessageDef.menuitems[0].status = IT_MSGHANDLER; - *(void**)&MessageDef.menuitems[0].itemaction.routine = routine; - break; - case MM_EVENTHANDLER: - MessageDef.menuitems[0].status = IT_MSGHANDLER; - *(void**)&MessageDef.menuitems[0].itemaction.eventhandler = routine; - break; - } - //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; - } - - MessageDef.x = (INT16)((BASEVIDWIDTH - 8*max-16)/2); - MessageDef.y = (INT16)((BASEVIDHEIGHT - M_StringHeight(message))/2); - - MessageDef.lastOn = (INT16)((strlines<<8)+max); - - //M_SetupNextMenu(); - currentMenu = &MessageDef; - itemOn = 0; -} - -#define MAXMSGLINELEN 256 - -static void M_DrawMessageMenu(void) -{ - INT32 y = currentMenu->y; - size_t i, start = 0; - INT16 max; - char string[MAXMSGLINELEN]; - INT32 mlines; - const char *msg = currentMenu->menuitems[0].text; - - mlines = currentMenu->lastOn>>8; - max = (INT16)((UINT8)(currentMenu->lastOn & 0xFF)*8); - - // hack: draw RA background in RA menus - if (gamestate == GS_TIMEATTACK) - V_DrawPatchFill(W_CachePatchName("SRB2BACK", PU_CACHE)); - - M_DrawTextBox(currentMenu->x, y - 8, (max+7)>>3, mlines); - - while (*(msg+start)) - { - size_t len = strlen(msg+start); - - for (i = 0; i < len; i++) - { - if (*(msg+start+i) == '\n') - { - memset(string, 0, MAXMSGLINELEN); - if (i >= MAXMSGLINELEN) - { - CONS_Printf("M_DrawMessageMenu: too long segment in %s\n", msg); - return; - } - else - { - strncpy(string,msg+start, i); - string[i] = '\0'; - start += i; - i = (size_t)-1; //added : 07-02-98 : damned! - start++; - } - break; - } - } - - if (i == strlen(msg+start)) - { - if (i >= MAXMSGLINELEN) - { - CONS_Printf("M_DrawMessageMenu: too long segment in %s\n", msg); - return; - } - else - { - strcpy(string, msg + start); - start += i; - } - } - - V_DrawString((BASEVIDWIDTH - V_StringWidth(string, 0))/2,y,V_ALLOWLOWERCASE,string); - y += 8; //SHORT(hu_font[0]->height); - } -} - -// default message handler -static void M_StopMessage(INT32 choice) -{ - (void)choice; - if (menuactive) - M_SetupNextMenu(MessageDef.prevMenu); -} - -// ========= -// IMAGEDEFS -// ========= - -// Draw an Image Def. Aka, Help images. -// Defines what image is used in (menuitem_t)->text. -// You can even put multiple images in one menu! -static void M_DrawImageDef(void) -{ - patch_t *patch = W_CachePatchName(currentMenu->menuitems[itemOn].text,PU_CACHE); - if (patch->width <= BASEVIDWIDTH) - V_DrawScaledPatch(0,0,0,patch); - else - V_DrawSmallScaledPatch(0,0,0,patch); - - if (currentMenu->menuitems[itemOn].alphaKey) - { - V_DrawString(2,BASEVIDHEIGHT-10, V_YELLOWMAP, va("%d", (itemOn<<1)-1)); // intentionally not highlightflags, unlike below - V_DrawRightAlignedString(BASEVIDWIDTH-2,BASEVIDHEIGHT-10, V_YELLOWMAP, va("%d", itemOn<<1)); // ditto - } - else - { - INT32 x = BASEVIDWIDTH>>1, y = (BASEVIDHEIGHT>>1) - 4; - x += (itemOn ? 1 : -1)*((BASEVIDWIDTH>>2) + 10); - V_DrawCenteredString(x, y-10, highlightflags, "USE ARROW KEYS"); - V_DrawCharacter(x - 10 - (skullAnimCounter/5), y, - '\x1C' | highlightflags, false); // left arrow - V_DrawCharacter(x + 2 + (skullAnimCounter/5), y, - '\x1D' | highlightflags, false); // right arrow - V_DrawCenteredString(x, y+10, highlightflags, "TO LEAF THROUGH"); - } -} - -// Handles the ImageDefs. Just a specialized function that -// uses left and right movement. -static void M_HandleImageDef(INT32 choice) -{ - boolean exitmenu = false; - - switch (choice) - { - case KEY_RIGHTARROW: - if (itemOn >= (INT16)(currentMenu->numitems-1)) - break; - S_StartSound(NULL, sfx_menu1); - itemOn++; - break; - - case KEY_LEFTARROW: - if (!itemOn) - break; - - S_StartSound(NULL, sfx_menu1); - itemOn--; - break; - - case KEY_ESCAPE: - case KEY_ENTER: - exitmenu = true; - break; - } - - if (exitmenu) - { - if (currentMenu->prevMenu) - M_SetupNextMenu(currentMenu->prevMenu); - else - M_ClearMenus(true); - } -} - -// ====================== -// MISC MAIN MENU OPTIONS -// ====================== - -static void M_AddonsOptions(INT32 choice) -{ - (void)choice; - Addons_option_Onchange(); - - M_SetupNextMenu(&OP_AddonsOptionsDef); -} - -#define LOCATIONSTRING1 "Visit \x83SRB2.ORG/MODS\x80 to get & make addons!" -#define LOCATIONSTRING2 "Visit \x88SRB2.ORG/MODS\x80 to get & make addons!" - -static void M_Addons(INT32 choice) -{ - const char *pathname = "."; - - (void)choice; - -#if 1 - if (cv_addons_option.value == 0) - pathname = usehome ? srb2home : srb2path; - 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\n(Press a key)\n", (recommendedflags == V_SKYMAP ? LOCATIONSTRING2 : LOCATIONSTRING1)),NULL,MM_NOTHING); - return; - } - else - dir_on[menudepthleft] = 0; - - if (addonsp[0]) // never going to have some provided but not all, saves individually checking - { - size_t i; - for (i = 0; i < NUM_EXT+5; i++) - W_UnlockCachedPatch(addonsp[i]); - } - - addonsp[EXT_FOLDER] = W_CachePatchName("M_FFLDR", PU_STATIC); - addonsp[EXT_UP] = W_CachePatchName("M_FBACK", PU_STATIC); - addonsp[EXT_NORESULTS] = W_CachePatchName("M_FNOPE", PU_STATIC); - addonsp[EXT_TXT] = W_CachePatchName("M_FTXT", PU_STATIC); - addonsp[EXT_CFG] = W_CachePatchName("M_FCFG", PU_STATIC); - addonsp[EXT_WAD] = W_CachePatchName("M_FWAD", PU_STATIC); -#ifdef USE_KART - addonsp[EXT_KART] = W_CachePatchName("M_FKART", PU_STATIC); -#endif - addonsp[EXT_PK3] = W_CachePatchName("M_FPK3", PU_STATIC); - addonsp[EXT_SOC] = W_CachePatchName("M_FSOC", PU_STATIC); - addonsp[EXT_LUA] = W_CachePatchName("M_FLUA", PU_STATIC); - addonsp[NUM_EXT] = W_CachePatchName("M_FUNKN", PU_STATIC); - addonsp[NUM_EXT+1] = W_CachePatchName("M_FSEL", PU_STATIC); - addonsp[NUM_EXT+2] = W_CachePatchName("M_FLOAD", PU_STATIC); - addonsp[NUM_EXT+3] = W_CachePatchName("M_FSRCH", PU_STATIC); - addonsp[NUM_EXT+4] = W_CachePatchName("M_FSAVE", PU_STATIC); - - MISC_AddonsDef.prevMenu = currentMenu; - M_SetupNextMenu(&MISC_AddonsDef); -} - -#define width 4 -#define vpadding 27 -#define h (BASEVIDHEIGHT-(2*vpadding)) -#define NUMCOLOURS 8 // when toast's coding it's british english hacker fucker -static void M_DrawTemperature(INT32 x, fixed_t t) -{ - INT32 y; - - // bounds check - if (t > FRACUNIT) - t = FRACUNIT; - /*else if (t < 0) -- not needed - t = 0;*/ - - // scale - if (t > 1) - t = (FixedMul(h<>FRACBITS); - - // border - V_DrawFill(x - 1, vpadding, 1, h, 0); - V_DrawFill(x + width, vpadding, 1, h, 0); - V_DrawFill(x - 1, vpadding-1, width+2, 1, 0); - V_DrawFill(x - 1, vpadding+h, width+2, 1, 0); - - // bar itself - y = h; - if (t) - for (t = h - t; y > 0; y--) - { - UINT8 colours[NUMCOLOURS] = {135, 133, 92, 77, 114, 178, 161, 162}; - UINT8 c; - if (y <= t) break; - if (y+vpadding >= BASEVIDHEIGHT/2) - c = 185; - else - c = colours[(NUMCOLOURS*(y-1))/(h/2)]; - V_DrawFill(x, y-1 + vpadding, width, 1, c); - } - - // fill the rest of the backing - if (y) - V_DrawFill(x, vpadding, width, y, 30); -} -#undef width -#undef vpadding -#undef h -#undef NUMCOLOURS - -static 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);\ - M_StartMessage(va("\x82%s\x80\nThis folder no longer exists!\nAborting to main menu.\n\n(Press a key)\n", M_AddonsHeaderPath()),NULL,MM_NOTHING) - -#define CLEARNAME Z_Free(refreshdirname);\ - refreshdirname = NULL - -static boolean prevmajormods = false; - -static void M_AddonsClearName(INT32 choice) -{ - if (!majormods || prevmajormods) - { - CLEARNAME; - } - M_StopMessage(choice); -} - -// returns whether to do message draw -static boolean 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\n(Press a key)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), refreshdirname); - else - message = va("%c%s\x80\nA file was not loaded.\nCheck the console log for more information.\n\n(Press a key)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), refreshdirname); - } - 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 information.\n\n(Press a key)\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\n(Press a key)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), refreshdirname); - prevmajormods = majormods; - } - - if (message) - { - M_StartMessage(message, FUNCPTRCAST(M_AddonsClearName), MM_EVENTHANDLER); - return true; - } - - S_StartSound(NULL, sfx_s221); - CLEARNAME; - } - - return false; -} - -static void M_DrawAddons(void) -{ - INT32 x, y; - ssize_t i, m; - const UINT8 *flashcol = NULL; - UINT8 hilicol; - - // hack - need to refresh at end of frame to handle addfile... - if (refreshdirmenu & M_AddonsRefresh()) - { - M_DrawMessageMenu(); - return; - } - - if (Playing()) - V_DrawCenteredString(BASEVIDWIDTH/2, 5, warningflags, "Adding files mid-game may cause problems."); - else - V_DrawCenteredString(BASEVIDWIDTH/2, 5, 0, (recommendedflags == V_SKYMAP ? LOCATIONSTRING2 : LOCATIONSTRING1)); - - if (numwadfiles <= mainwads+1) - y = 0; - else if (numwadfiles >= MAX_WADFILES) - y = FRACUNIT; - else - { - y = FixedDiv(((ssize_t)(numwadfiles) - (ssize_t)(mainwads+1))< FRACUNIT) // happens because of how we're shrinkin' it a little - y = FRACUNIT; - } - - M_DrawTemperature(BASEVIDWIDTH - 19 - 5, y); - - // DRAW MENU - x = currentMenu->x; - y = currentMenu->y + 1; - - hilicol = V_GetStringColormap(highlightflags)[0]; - - V_DrawString(x-21, (y - 16) + (lsheadingheight - 12), highlightflags|V_ALLOWLOWERCASE, M_AddonsHeaderPath()); - V_DrawFill(x-21, (y - 16) + (lsheadingheight - 3), MAXSTRINGLENGTH*8+6, 1, hilicol); - V_DrawFill(x-21, (y - 16) + (lsheadingheight - 2), MAXSTRINGLENGTH*8+6, 1, 30); - - m = (BASEVIDHEIGHT - currentMenu->y + 2) - (y - 1); - V_DrawFill(x - 21, y - 1, MAXSTRINGLENGTH*8+6, m, 159); - - // scrollbar! - if (sizedirmenu <= (2*numaddonsshown + 1)) - i = 0; - else - { - ssize_t q = m; - m = ((2*numaddonsshown + 1) * m)/sizedirmenu; - if (dir_on[menudepthleft] <= numaddonsshown) // all the way up - i = 0; - else if (sizedirmenu <= (dir_on[menudepthleft] + numaddonsshown + 1)) // all the way down - i = q-m; - else - i = ((dir_on[menudepthleft] - numaddonsshown) * (q-m))/(sizedirmenu - (2*numaddonsshown + 1)); - } - - V_DrawFill(x + MAXSTRINGLENGTH*8+5 - 21, (y - 1) + i, 1, m, hilicol); - - // get bottom... - m = dir_on[menudepthleft] + numaddonsshown + 1; - if (m > (ssize_t)sizedirmenu) - m = sizedirmenu; - - // then compute top and adjust bottom if needed! - if (m < (2*numaddonsshown + 1)) - { - m = min(sizedirmenu, 2*numaddonsshown + 1); - i = 0; - } - else - i = m - (2*numaddonsshown + 1); - - if (i != 0) - V_DrawString(19, y+4 - (skullAnimCounter/5), highlightflags, "\x1A"); - - if (skullAnimCounter < 4) - flashcol = V_GetStringColormap(highlightflags); - - for (; i < m; i++) - { - UINT32 flags = V_ALLOWLOWERCASE; - if (y > BASEVIDHEIGHT) break; - if (dirmenu[i]) -#define type (UINT8)(dirmenu[i][DIR_TYPE]) - { - if (type & EXT_LOADED) - { - flags |= V_TRANSLUCENT; - V_DrawSmallScaledPatch(x-(16+4), y, V_TRANSLUCENT, addonsp[(type & ~EXT_LOADED)]); - V_DrawSmallScaledPatch(x-(16+4), y, 0, addonsp[NUM_EXT+2]); - } - else - V_DrawSmallScaledPatch(x-(16+4), y, 0, addonsp[(type & ~EXT_LOADED)]); - - if ((size_t)i == dir_on[menudepthleft]) - { - V_DrawFixedPatch((x-(16+4))< (charsonside*2 + 3)) - V_DrawString(x, y+4, flags, va("%.*s...%s", charsonside, dirmenu[i]+DIR_STRING, dirmenu[i]+DIR_STRING+dirmenu[i][DIR_LEN]-(charsonside+1))); -#undef charsonside - else - V_DrawString(x, y+4, flags, dirmenu[i]+DIR_STRING); - } -#undef type - y += 16; - } - - if (m != (ssize_t)sizedirmenu) - V_DrawString(19, y-12 + (skullAnimCounter/5), highlightflags, "\x1B"); - - y = BASEVIDHEIGHT - currentMenu->y + 1; - - M_DrawTextBox(x - (21 + 5), y, MAXSTRINGLENGTH, 1); - if (menusearch[0]) - V_DrawString(x - 18, y + 8, V_ALLOWLOWERCASE, menusearch+1); - else - V_DrawString(x - 18, y + 8, V_ALLOWLOWERCASE|V_TRANSLUCENT, "Type to search..."); - if (skullAnimCounter < 4) - V_DrawCharacter(x - 18 + V_StringWidth(menusearch+1, 0), y + 8, - '_' | 0x80, false); - - x -= (21 + 5 + 16); - V_DrawSmallScaledPatch(x, y + 4, (menusearch[0] ? 0 : V_TRANSLUCENT), addonsp[NUM_EXT+3]); - - x = BASEVIDWIDTH - x - 16; - V_DrawSmallScaledPatch(x, y + 4, ((!majormods) ? 0 : V_TRANSLUCENT), addonsp[NUM_EXT+4]); - - if (modifiedgame) - V_DrawSmallScaledPatch(x, y + 4, 0, addonsp[NUM_EXT+2]); -} - -static void M_AddonExec(INT32 ch) -{ - if (ch != 'y' && ch != KEY_ENTER) - return; - - S_StartSound(NULL, sfx_zoom); - COM_BufAddText(va("exec \"%s%s\"", menupath, dirmenu[dir_on[menudepthleft]]+DIR_STRING)); -} - -#define len menusearch[0] -static boolean M_ChangeStringAddons(INT32 choice) -{ - if (shiftdown && choice >= 32 && choice <= 127) - choice = shiftxform[choice]; - - switch (choice) - { - case KEY_DEL: - if (len) - { - len = menusearch[1] = 0; - return true; - } - break; - case KEY_BACKSPACE: - if (len) - { - menusearch[1+--len] = 0; - return true; - } - break; - default: - if (choice >= 32 && choice <= 127) - { - if (len < MAXSTRINGLENGTH - 1) - { - menusearch[1+len++] = (char)choice; - menusearch[1+len] = 0; - return true; - } - } - break; - } - return false; -} -#undef len - -static void M_HandleAddons(INT32 choice) -{ - boolean exitmenu = false; // exit to previous menu - - if (M_ChangeStringAddons(choice)) - { - char *tempname = NULL; - if (dirmenu && dirmenu[dir_on[menudepthleft]]) - tempname = Z_StrDup(dirmenu[dir_on[menudepthleft]]+DIR_STRING); // don't need to I_Error if can't make - not important, just QoL -#if 0 // much slower - if (!preparefilemenu(true, false)) - { - UNEXIST; - return; - } -#else // streamlined - searchfilemenu(tempname); -#endif - } - - switch (choice) - { - case KEY_DOWNARROW: - if (dir_on[menudepthleft] < sizedirmenu-1) - dir_on[menudepthleft]++; - S_StartSound(NULL, sfx_menu1); - break; - case KEY_UPARROW: - if (dir_on[menudepthleft]) - dir_on[menudepthleft]--; - S_StartSound(NULL, sfx_menu1); - break; - case KEY_PGDN: - { - UINT8 i; - for (i = numaddonsshown; i && (dir_on[menudepthleft] < sizedirmenu-1); i--) - dir_on[menudepthleft]++; - } - S_StartSound(NULL, sfx_menu1); - break; - case KEY_PGUP: - { - UINT8 i; - for (i = numaddonsshown; i && (dir_on[menudepthleft]); i--) - dir_on[menudepthleft]--; - } - S_StartSound(NULL, sfx_menu1); - break; - case KEY_ENTER: - { - boolean refresh = true; - 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\n(Press a key)\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_menu1); - 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\n(Press a key)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), M_AddonsHeaderPath()),NULL,MM_NOTHING); - menupath[menupathindex[menudepthleft]] = 0; - } - break; - case EXT_UP: - S_StartSound(NULL, sfx_menu1); - 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\n(Press 'Y' to confirm)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), dirmenu[dir_on[menudepthleft]]+DIR_STRING), FUNCPTRCAST(M_AddonExec) ,MM_YESNO); - break; - case EXT_CFG: - M_AddonExec(KEY_ENTER); - 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; - } - break; - - case KEY_ESCAPE: - exitmenu = true; - break; - - default: - break; - } - if (exitmenu) - { - closefilemenu(true); - - // Secret menu! - //MainMenu[secrets].status = (M_AnySecretUnlocked()) ? (IT_STRING | IT_CALL) : (IT_DISABLED); - - if (currentMenu->prevMenu) - M_SetupNextMenu(currentMenu->prevMenu); - else - M_ClearMenus(true); - } -} - -// ---- REPLAY HUT ----- -menudemo_t *demolist; - -#define DF_ENCORE 0x40 -static INT16 replayScrollTitle = 0; -static SINT8 replayScrollDelay = TICRATE, replayScrollDir = 1; - -static void PrepReplayList(void) -{ - size_t i; - - if (demolist) - Z_Free(demolist); - - demolist = Z_Calloc(sizeof(menudemo_t) * sizedirmenu, PU_STATIC, NULL); - - for (i = 0; i < sizedirmenu; i++) - { - if (dirmenu[i][DIR_TYPE] == EXT_UP) - { - demolist[i].type = MD_SUBDIR; - sprintf(demolist[i].title, "UP"); - } - else if (dirmenu[i][DIR_TYPE] == EXT_FOLDER) - { - demolist[i].type = MD_SUBDIR; - strncpy(demolist[i].title, dirmenu[i] + DIR_STRING, 64); - } - else - { - demolist[i].type = MD_NOTLOADED; - snprintf(demolist[i].filepath, 255, "%s%s", menupath, dirmenu[i] + DIR_STRING); - sprintf(demolist[i].title, "....."); - } - } -} - -void M_ReplayHut(INT32 choice) -{ - (void)choice; - - 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\n(Press a key)\n", NULL, MM_NOTHING); - return; - } - else if (!demo.inreplayhut) - dir_on[menudepthleft] = 0; - demo.inreplayhut = true; - - replayScrollTitle = 0; replayScrollDelay = TICRATE; replayScrollDir = 1; - - PrepReplayList(); - - menuactive = true; - M_SetupNextMenu(&MISC_ReplayHutDef); - G_SetGamestate(GS_TIMEATTACK); - titlemapinaction = TITLEMAP_OFF; // Nope don't give us HOMs please - - demo.rewinding = false; - CL_ClearRewinds(); - - S_ChangeMusicInternal("replst", true); -} - -static void M_HandleReplayHutList(INT32 choice) -{ - switch (choice) - { - case KEY_UPARROW: - if (dir_on[menudepthleft]) - dir_on[menudepthleft]--; - else - return; - //M_PrevOpt(); - - S_StartSound(NULL, sfx_menu1); - replayScrollTitle = 0; replayScrollDelay = TICRATE; replayScrollDir = 1; - break; - - case KEY_DOWNARROW: - 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_menu1); - replayScrollTitle = 0; replayScrollDelay = TICRATE; replayScrollDir = 1; - break; - - case KEY_ESCAPE: - M_QuitReplayHut(); - break; - - case KEY_ENTER: - 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\n(Press a key)\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_menu1); - dir_on[menudepthleft] = 1; - PrepReplayList(); - } - } - else - { - S_StartSound(NULL, sfx_s26d); - M_StartMessage(va("%c%s\x80\nThis folder is too deep to navigate to!\n\n(Press a key)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), M_AddonsHeaderPath()),NULL,MM_NOTHING); - menupath[menupathindex[menudepthleft]] = 0; - } - break; - case EXT_UP: - S_StartSound(NULL, sfx_menu1); - menupath[menupathindex[++menudepthleft]] = 0; - if (!preparefilemenu(false, true)) - { - M_QuitReplayHut(); - return; - } - 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 = &MISC_ReplayStartDef; - - replayScrollTitle = 0; replayScrollDelay = TICRATE; replayScrollDir = 1; - - switch (demolist[dir_on[menudepthleft]].addonstatus) - { - case DFILE_ERROR_CANNOTLOAD: - // Only show "Watch Replay Without Addons" - MISC_ReplayStartMenu[0].status = IT_DISABLED; - MISC_ReplayStartMenu[1].status = IT_CALL|IT_STRING; - //MISC_ReplayStartMenu[1].alphaKey = 0; - MISC_ReplayStartMenu[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" - MISC_ReplayStartMenu[0].status = IT_CALL|IT_STRING; - MISC_ReplayStartMenu[1].status = IT_CALL|IT_STRING; - //MISC_ReplayStartMenu[1].alphaKey = 10; - MISC_ReplayStartMenu[2].status = IT_DISABLED; - itemOn = 0; - break; - - case DFILE_ERROR_EXTRAFILES: - case DFILE_ERROR_OUTOFORDER: - default: - // Show "Watch Replay" - MISC_ReplayStartMenu[0].status = IT_DISABLED; - MISC_ReplayStartMenu[1].status = IT_DISABLED; - MISC_ReplayStartMenu[2].status = IT_CALL|IT_STRING; - //MISC_ReplayStartMenu[2].alphaKey = 0; - itemOn = 2; - break; - } - } - - break; - } -} - -#define SCALEDVIEWWIDTH (vid.width/vid.dupx) -#define SCALEDVIEWHEIGHT (vid.height/vid.dupy) -static void DrawReplayHutReplayInfo(void) -{ - lumpnum_t lumpnum; - patch_t *patch; - UINT8 *colormap; - INT32 x, y, w, h; - - switch (demolist[dir_on[menudepthleft]].type) - { - case MD_NOTLOADED: - V_DrawCenteredString(160, 40, V_SNAPTOTOP, "Loading replay information..."); - break; - - case MD_INVALID: - V_DrawCenteredString(160, 40, V_SNAPTOTOP|warningflags, "This replay cannot be played."); - break; - - case MD_SUBDIR: - break; // Can't think of anything to draw here right now - - case MD_OUTDATED: - V_DrawThinString(17, 64, V_SNAPTOTOP|V_ALLOWLOWERCASE|V_TRANSLUCENT|highlightflags, "Recorded on an outdated version."); - /* FALLTHRU */ - default: - // Draw level stuff - x = 15; y = 15; - - // A 160x100 image of the level as entry MAPxxP - //CONS_Printf("%d %s\n", demolist[dir_on[menudepthleft]].map, G_BuildMapName(demolist[dir_on[menudepthleft]].map)); - lumpnum = W_CheckNumForName(va("%sP", G_BuildMapName(demolist[dir_on[menudepthleft]].map))); - if (lumpnum != LUMPERROR) - patch = W_CachePatchNum(lumpnum, PU_CACHE); - else - patch = W_CachePatchName("M_NOLVL", PU_CACHE); - - if (!(demolist[dir_on[menudepthleft]].kartspeed & DF_ENCORE)) - V_DrawSmallScaledPatch(x, y, V_SNAPTOTOP, patch); - else - { - w = SHORT(patch->width); - h = SHORT(patch->height); - V_DrawSmallScaledPatch(x+(w>>1), y, V_SNAPTOTOP|V_FLIP, patch); - - { - static angle_t rubyfloattime = 0; - const fixed_t rubyheight = FINESINE(rubyfloattime>>ANGLETOFINESHIFT); - V_DrawFixedPatch((x+(w>>2))<>2))<width), y+20, V_SNAPTOTOP, patch, colormap); - - break; - } -} - -static void M_DrawReplayHut(void) -{ - INT32 x, y, cursory = 0; - INT16 i; - INT16 replaylistitem = currentMenu->numitems-2; - boolean processed_one_this_frame = false; - - static UINT16 replayhutmenuy = 0; - - V_DrawPatchFill(W_CachePatchName("SRB2BACK", PU_CACHE)); - - if (cv_vhseffect.value) - V_DrawVhsEffect(false); - - // Draw menu choices - x = currentMenu->x; - y = currentMenu->y; - - if (itemOn > replaylistitem) - { - itemOn = replaylistitem; - dir_on[menudepthleft] = sizedirmenu-1; - replayScrollTitle = 0; replayScrollDelay = TICRATE; replayScrollDir = 1; - } - else if (itemOn < replaylistitem) - { - dir_on[menudepthleft] = 0; - replayScrollTitle = 0; replayScrollDelay = TICRATE; replayScrollDir = 1; - } - - if (itemOn == replaylistitem) - { - INT32 maxy; - // Scroll menu items if needed - cursory = y + currentMenu->menuitems[replaylistitem].alphaKey + dir_on[menudepthleft]*10; - maxy = y + currentMenu->menuitems[replaylistitem].alphaKey + sizedirmenu*10; - - if (cursory > maxy - 20) - cursory = maxy - 20; - - if (cursory - replayhutmenuy > SCALEDVIEWHEIGHT-50) - replayhutmenuy += (cursory-SCALEDVIEWHEIGHT-replayhutmenuy + 51)/2; - else if (cursory - replayhutmenuy < 110) - replayhutmenuy += (max(0, cursory-110)-replayhutmenuy - 1)/2; - } - else - replayhutmenuy /= 2; - - y -= replayhutmenuy; - - // Draw static menu items - for (i = 0; i < replaylistitem; i++) - { - INT32 localy = y + currentMenu->menuitems[i].alphaKey; - - if (localy < 65) - continue; - - if (i == itemOn) - cursory = localy; - - if ((currentMenu->menuitems[i].status & IT_DISPLAY)==IT_STRING) - V_DrawString(x, localy, V_SNAPTOTOP|V_SNAPTOLEFT, currentMenu->menuitems[i].text); - else - V_DrawString(x, localy, V_SNAPTOTOP|V_SNAPTOLEFT|highlightflags, currentMenu->menuitems[i].text); - } - - y += currentMenu->menuitems[replaylistitem].alphaKey; - - for (i = 0; i < (INT16)sizedirmenu; i++) - { - INT32 localy = y+i*10; - INT32 localx = x; - - if (localy < 65) - continue; - if (localy >= SCALEDVIEWHEIGHT) - break; - - if (demolist[i].type == MD_NOTLOADED && !processed_one_this_frame) - { - processed_one_this_frame = true; - G_LoadDemoInfo(&demolist[i]); - } - - if (demolist[i].type == MD_SUBDIR) - { - localx += 8; - V_DrawScaledPatch(x - 4, localy, V_SNAPTOTOP|V_SNAPTOLEFT, W_CachePatchName(dirmenu[i][DIR_TYPE] == EXT_UP ? "M_RBACK" : "M_RFLDR", PU_CACHE)); - } - - if (itemOn == replaylistitem && i == (INT16)dir_on[menudepthleft]) - { - cursory = localy; - - if (replayScrollDelay) - replayScrollDelay--; - else if (replayScrollDir > 0) - { - if (replayScrollTitle < (V_StringWidth(demolist[i].title, 0) - (SCALEDVIEWWIDTH - (x<<1)))<<1) - replayScrollTitle++; - else - { - replayScrollDelay = TICRATE; - replayScrollDir = -1; - } - } - else - { - if (replayScrollTitle > 0) - replayScrollTitle--; - else - { - replayScrollDelay = TICRATE; - replayScrollDir = 1; - } - } - - V_DrawString(localx - (replayScrollTitle>>1), localy, V_SNAPTOTOP|V_SNAPTOLEFT|highlightflags|V_ALLOWLOWERCASE, demolist[i].title); - } - else - V_DrawString(localx, localy, V_SNAPTOTOP|V_SNAPTOLEFT|V_ALLOWLOWERCASE, demolist[i].title); - } - - // Draw scrollbar - y = sizedirmenu*10 + currentMenu->menuitems[replaylistitem].alphaKey + 30; - if (y > SCALEDVIEWHEIGHT-80) - { - V_DrawFill(BASEVIDWIDTH-4, 75, 4, SCALEDVIEWHEIGHT-80, V_SNAPTOTOP|V_SNAPTORIGHT|159); - V_DrawFill(BASEVIDWIDTH-3, 76 + (SCALEDVIEWHEIGHT-80) * replayhutmenuy / y, 2, (((SCALEDVIEWHEIGHT-80) * (SCALEDVIEWHEIGHT-80))-1) / y - 1, V_SNAPTOTOP|V_SNAPTORIGHT|149); - } - - // Draw the cursor - V_DrawScaledPatch(currentMenu->x - 24, cursory, V_SNAPTOTOP|V_SNAPTOLEFT, - W_CachePatchName("M_CURSOR", PU_CACHE)); - V_DrawString(currentMenu->x, cursory, V_SNAPTOTOP|V_SNAPTOLEFT|highlightflags, currentMenu->menuitems[itemOn].text); - - // Now draw some replay info! - V_DrawFill(10, 10, 300, 60, V_SNAPTOTOP|159); - - if (itemOn == replaylistitem) - { - DrawReplayHutReplayInfo(); - } -} - -static void M_DrawReplayStartMenu(void) -{ - const char *warning; - UINT8 i; - - M_DrawGenericBackgroundMenu(); - -#define STARTY 62-(replayScrollTitle>>1) - // Draw rankings beyond first - for (i = 1; i < MAXPLAYERS && demolist[dir_on[menudepthleft]].standings[i].ranking; i++) - { - patch_t *patch; - UINT8 *colormap; - - V_DrawRightAlignedString(BASEVIDWIDTH-100, STARTY + i*20, V_SNAPTOTOP|highlightflags, va("%2d", demolist[dir_on[menudepthleft]].standings[i].ranking)); - V_DrawThinString(BASEVIDWIDTH-96, STARTY + i*20, V_SNAPTOTOP|V_ALLOWLOWERCASE, demolist[dir_on[menudepthleft]].standings[i].name); - - if (demolist[dir_on[menudepthleft]].standings[i].timeorscore == UINT32_MAX-1) - V_DrawThinString(BASEVIDWIDTH-92, STARTY + i*20 + 9, V_SNAPTOTOP, "NO CONTEST"); - else if (demolist[dir_on[menudepthleft]].gametype == GT_RACE) - V_DrawRightAlignedString(BASEVIDWIDTH-40, STARTY + i*20 + 9, V_SNAPTOTOP, va("%d'%02d\"%02d", - G_TicsToMinutes(demolist[dir_on[menudepthleft]].standings[i].timeorscore, true), - G_TicsToSeconds(demolist[dir_on[menudepthleft]].standings[i].timeorscore), - G_TicsToCentiseconds(demolist[dir_on[menudepthleft]].standings[i].timeorscore) - )); - else - V_DrawString(BASEVIDWIDTH-92, STARTY + i*20 + 9, V_SNAPTOTOP, va("%d", demolist[dir_on[menudepthleft]].standings[i].timeorscore)); - - // Character face! - - // Lat: 08/06/2020: For some reason missing skins have their value set to 255 (don't even ask me why I didn't write this) - // and for an even STRANGER reason this passes the first check below, so we're going to make sure that the skin here ISN'T 255 before we do anything stupid. - - if (demolist[dir_on[menudepthleft]].standings[i].skin != 0xFF) - { - patch = faceprefix[demolist[dir_on[menudepthleft]].standings[i].skin][FACE_RANK]; - colormap = R_GetTranslationColormap( - demolist[dir_on[menudepthleft]].standings[i].skin, - demolist[dir_on[menudepthleft]].standings[i].color, - GTC_MENUCACHE); - } - else - { - patch = W_CachePatchName("M_NORANK", PU_CACHE); - colormap = R_GetTranslationColormap( - TC_RAINBOW, - demolist[dir_on[menudepthleft]].standings[i].color, - GTC_MENUCACHE); - } - - V_DrawMappedPatch(BASEVIDWIDTH-5 - SHORT(patch->width), STARTY + i*20, V_SNAPTOTOP, patch, colormap); - } -#undef STARTY - - // Handle scrolling rankings - if (replayScrollDelay) - replayScrollDelay--; - else if (replayScrollDir > 0) - { - if (replayScrollTitle < (i*20 - SCALEDVIEWHEIGHT + 100)<<1) - replayScrollTitle++; - else - { - replayScrollDelay = TICRATE; - replayScrollDir = -1; - } - } - else - { - if (replayScrollTitle > 0) - replayScrollTitle--; - else - { - replayScrollDelay = TICRATE; - replayScrollDir = 1; - } - } - - V_DrawFill(10, 10, 300, 60, V_SNAPTOTOP|159); - DrawReplayHutReplayInfo(); - - V_DrawString(10, 72, V_SNAPTOTOP|highlightflags|V_ALLOWLOWERCASE, demolist[dir_on[menudepthleft]].title); - - // Draw a warning prompt if needed - switch (demolist[dir_on[menudepthleft]].addonstatus) - { - case DFILE_ERROR_CANNOTLOAD: - warning = "Some addons in this replay cannot be loaded.\nYou can watch anyway, but desyncs may occur."; - break; - - case DFILE_ERROR_NOTLOADED: - case DFILE_ERROR_INCOMPLETEOUTOFORDER: - warning = "Loading addons will mark your game as modified, and Record Attack may be unavailable.\nYou can watch without loading addons, but desyncs may occur."; - break; - - case DFILE_ERROR_EXTRAFILES: - warning = "You have addons loaded that were not present in this replay.\nYou can watch anyway, but desyncs may occur."; - break; - - case DFILE_ERROR_OUTOFORDER: - warning = "You have this replay's addons loaded, but they are out of order.\nYou can watch anyway, but desyncs may occur."; - break; - - default: - return; - } - - V_DrawSmallString(4, BASEVIDHEIGHT-14, V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_ALLOWLOWERCASE, warning); -} - -static boolean M_QuitReplayHut(void) -{ - // D_StartTitle does its own wipe, since GS_TIMEATTACK is now a complete gamestate. - menuactive = false; - D_StartTitle(); - - if (demolist) - Z_Free(demolist); - demolist = NULL; - - demo.inreplayhut = false; - - return true; -} - -static void M_HutStartReplay(INT32 choice) -{ - (void)choice; - - M_ClearMenus(false); - demo.loadfiles = (itemOn == 0); - demo.ignorefiles = (itemOn != 0); - - G_DoPlayDemo(demolist[dir_on[menudepthleft]].filepath); -} - -void M_SetPlaybackMenuPointer(void) -{ - itemOn = playback_pause; -} - -static void M_DrawPlaybackMenu(void) -{ - INT16 i; - patch_t *icon; - UINT8 *activemap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_GOLD, GTC_MENUCACHE); - UINT32 transmap = max(0, (INT32)(leveltime - playback_last_menu_interaction_leveltime - 4*TICRATE)) / 5; - transmap = min(8, transmap) << V_ALPHASHIFT; - - if (leveltime - playback_last_menu_interaction_leveltime >= 6*TICRATE) - playback_last_menu_interaction_leveltime = leveltime - 6*TICRATE; - - // Toggle items - if (paused && !demo.rewinding) - { - PlaybackMenu[playback_pause].status = PlaybackMenu[playback_fastforward].status = PlaybackMenu[playback_rewind].status = IT_DISABLED; - PlaybackMenu[playback_resume].status = PlaybackMenu[playback_advanceframe].status = PlaybackMenu[playback_backframe].status = IT_CALL|IT_STRING; - - if (itemOn >= playback_rewind && itemOn <= playback_fastforward) - itemOn += playback_backframe - playback_rewind; - } - else - { - PlaybackMenu[playback_pause].status = PlaybackMenu[playback_fastforward].status = PlaybackMenu[playback_rewind].status = IT_CALL|IT_STRING; - PlaybackMenu[playback_resume].status = PlaybackMenu[playback_advanceframe].status = PlaybackMenu[playback_backframe].status = IT_DISABLED; - - if (itemOn >= playback_backframe && itemOn <= playback_advanceframe) - itemOn -= playback_backframe - playback_rewind; - } - - if (modeattacking) - { - for (i = playback_viewcount; i <= playback_view4; i++) - PlaybackMenu[i].status = IT_DISABLED; - PlaybackMenu[playback_freecamera].alphaKey = 72; - PlaybackMenu[playback_quit].alphaKey = 88; - - currentMenu->x = BASEVIDWIDTH/2 - 52; - } - else - { - PlaybackMenu[playback_viewcount].status = IT_ARROWS|IT_STRING; - - for (i = 0; i <= r_splitscreen; i++) - PlaybackMenu[playback_view1+i].status = IT_ARROWS|IT_STRING; - for (i = r_splitscreen+1; i < 4; i++) - PlaybackMenu[playback_view1+i].status = IT_DISABLED; - - PlaybackMenu[playback_freecamera].alphaKey = 156; - PlaybackMenu[playback_quit].alphaKey = 172; - currentMenu->x = BASEVIDWIDTH/2 - 88; - } - - // wip - //M_DrawTextBox(currentMenu->x-68, currentMenu->y-7, 15, 15); - //M_DrawCenteredMenu(); - - for (i = 0; i < currentMenu->numitems; i++) - { - UINT8 *inactivemap = NULL; - - if (i >= playback_view1 && i <= playback_view4) - { - if (modeattacking) continue; - - if (r_splitscreen >= i - playback_view1) - { - INT32 ply = displayplayers[i - playback_view1]; - - icon = faceprefix[players[ply].skin][FACE_RANK]; - if (i != itemOn) - inactivemap = R_GetTranslationColormap(players[ply].skin, players[ply].skincolor, GTC_MENUCACHE); - } - else if (currentMenu->menuitems[i].patch && W_CheckNumForName(currentMenu->menuitems[i].patch) != LUMPERROR) - icon = W_CachePatchName(currentMenu->menuitems[i].patch, PU_CACHE); - else - icon = W_CachePatchName("PLAYRANK", PU_CACHE); // temp - } - else if (currentMenu->menuitems[i].status == IT_DISABLED) - continue; - else if (currentMenu->menuitems[i].patch && W_CheckNumForName(currentMenu->menuitems[i].patch) != LUMPERROR) - icon = W_CachePatchName(currentMenu->menuitems[i].patch, PU_CACHE); - else - icon = W_CachePatchName("PLAYRANK", PU_CACHE); // temp - - if ((i == playback_fastforward && cv_playbackspeed.value > 1) || (i == playback_rewind && demo.rewinding)) - V_DrawMappedPatch(currentMenu->x + currentMenu->menuitems[i].alphaKey, currentMenu->y, transmap|V_SNAPTOTOP, icon, R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_JAWZ, GTC_MENUCACHE)); - else - V_DrawMappedPatch(currentMenu->x + currentMenu->menuitems[i].alphaKey, currentMenu->y, transmap|V_SNAPTOTOP, icon, (i == itemOn) ? activemap : inactivemap); - - if (i == itemOn) - { - V_DrawCharacter(currentMenu->x + currentMenu->menuitems[i].alphaKey + 4, currentMenu->y + 14, - '\x1A' | transmap|V_SNAPTOTOP|highlightflags, false); - - V_DrawCenteredString(BASEVIDWIDTH/2, currentMenu->y + 18, transmap|V_SNAPTOTOP|V_ALLOWLOWERCASE, currentMenu->menuitems[i].text); - - if ((currentMenu->menuitems[i].status & IT_TYPE) == IT_ARROWS) - { - char *str; - - if (!(i == playback_viewcount && r_splitscreen == 3)) - V_DrawCharacter(BASEVIDWIDTH/2 - 4, currentMenu->y + 28 - (skullAnimCounter/5), - '\x1A' | transmap|V_SNAPTOTOP|highlightflags, false); // up arrow - - if (!(i == playback_viewcount && r_splitscreen == 0)) - V_DrawCharacter(BASEVIDWIDTH/2 - 4, currentMenu->y + 48 + (skullAnimCounter/5), - '\x1B' | transmap|V_SNAPTOTOP|highlightflags, false); // down arrow - - switch (i) - { - case playback_viewcount: - str = va("%d", r_splitscreen+1); - break; - - case playback_view1: - case playback_view2: - case playback_view3: - case playback_view4: - str = player_names[displayplayers[i - playback_view1]]; // 0 to 3 - break; - - default: // shouldn't ever be reached but whatever - continue; - } - - V_DrawCenteredString(BASEVIDWIDTH/2, currentMenu->y + 38, transmap|V_SNAPTOTOP|V_ALLOWLOWERCASE|highlightflags, str); - } - } - } -} - -static 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); -} - -static 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); -} - -static 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); -} - -static void M_PlaybackAdvance(INT32 choice) -{ - (void)choice; - - paused = false; - TryRunTics(1); - paused = true; -} - - -static void M_PlaybackSetViews(INT32 choice) -{ - - if (demo.freecam) - return; // not here. - - if (choice > 0) - { - if (r_splitscreen < 3) - G_AdjustView(r_splitscreen + 2, 0, true); - } - else if (r_splitscreen) - { - r_splitscreen--; - R_ExecuteSetViewSize(); - } -} - -static void M_PlaybackAdjustView(INT32 choice) -{ - G_AdjustView(itemOn - playback_viewcount, (choice > 0) ? 1 : -1, true); -} - -// this one's rather tricky -static 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.keyboardlook = false; // reset only these. localangle / aiming gets set before the cam does anything anyway - } -} - - -static void M_PlaybackQuit(INT32 choice) -{ - (void)choice; - G_StopDemo(); - - if (demo.inreplayhut) - M_ReplayHut(choice); - else if (modeattacking) - { - M_EndModeAttackRun(); - S_ChangeMusicInternal("racent", true); - } - else - D_StartTitle(); -} - -static void M_PandorasBox(INT32 choice) -{ - (void)choice; - CV_StealthSetValue(&cv_dummyrings, players[consoleplayer].rings); - CV_StealthSetValue(&cv_dummylives, players[consoleplayer].lives); - M_SetupNextMenu(&SR_PandoraDef); -} - -static boolean M_ExitPandorasBox(void) -{ - if (cv_dummyrings.value != players[consoleplayer].rings) - COM_ImmedExecute(va("setrings %d", cv_dummyrings.value)); - if (cv_dummylives.value != players[consoleplayer].lives) - COM_ImmedExecute(va("setlives %d", cv_dummylives.value)); - return true; -} - -static void M_ChangeLevel(INT32 choice) -{ - char mapname[6]; - (void)choice; - - strlcpy(mapname, G_BuildMapName(cv_nextmap.value), sizeof (mapname)); - strlwr(mapname); - mapname[5] = '\0'; - - M_ClearMenus(true); - COM_BufAddText(va("map %s -gametype \"%s\"\n", mapname, cv_newgametype.string)); -} - -static void M_ConfirmSpectate(INT32 choice) -{ - (void)choice; - // We allow switching to spectator even if team changing is not allowed - M_ClearMenus(true); - COM_ImmedExecute("changeteam spectator"); -} - -static 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.\nPress a key.\n"), NULL, MM_NOTHING); - return; - } - M_ClearMenus(true); - COM_ImmedExecute("changeteam playing"); -} - -static void M_ConfirmTeamScramble(INT32 choice) -{ - (void)choice; - M_ClearMenus(true); - - COM_ImmedExecute(va("teamscramble %d", cv_dummyscramble.value+1)); -} - -static void M_ConfirmTeamChange(INT32 choice) -{ - (void)choice; - - if (cv_dummymenuplayer.value > splitscreen+1) - return; - - if (!cv_allowteamchange.value && cv_dummyteam.value) - { - M_StartMessage(M_GetText("The server is not allowing\nteam changes at this time.\nPress a key.\n"), NULL, MM_NOTHING); - return; - } - - M_ClearMenus(true); - - switch (cv_dummymenuplayer.value) - { - case 1: - default: - COM_ImmedExecute(va("changeteam %s", cv_dummyteam.string)); - break; - case 2: - COM_ImmedExecute(va("changeteam2 %s", cv_dummyteam.string)); - break; - case 3: - COM_ImmedExecute(va("changeteam3 %s", cv_dummyteam.string)); - break; - case 4: - COM_ImmedExecute(va("changeteam4 %s", cv_dummyteam.string)); - break; - } -} - -static void M_ConfirmSpectateChange(INT32 choice) -{ - (void)choice; - - if (cv_dummymenuplayer.value > splitscreen+1) - return; - - if (!cv_allowteamchange.value && cv_dummyspectate.value) - { - M_StartMessage(M_GetText("The server is not allowing\nteam changes at this time.\nPress a key.\n"), NULL, MM_NOTHING); - return; - } - - M_ClearMenus(true); - - switch (cv_dummymenuplayer.value) - { - case 1: - default: - COM_ImmedExecute(va("changeteam %s", cv_dummyspectate.string)); - break; - case 2: - COM_ImmedExecute(va("changeteam2 %s", cv_dummyspectate.string)); - break; - case 3: - COM_ImmedExecute(va("changeteam3 %s", cv_dummyspectate.string)); - break; - case 4: - COM_ImmedExecute(va("changeteam4 %s", cv_dummyspectate.string)); - break; - } -} - -static void M_Options(INT32 choice) -{ - (void)choice; - - // if the player is not admin or server, disable gameplay & server options - OP_MainMenu[4].status = OP_MainMenu[5].status = (Playing() && !(server || IsPlayerAdmin(consoleplayer))) ? (IT_GRAYEDOUT) : (IT_STRING|IT_SUBMENU); - - OP_MainMenu[8].status = (Playing()) ? (IT_GRAYEDOUT) : (IT_STRING|IT_CALL); // Play credits - -#ifdef HAVE_DISCORDRPC - OP_DataOptionsMenu[4].status = (Playing()) ? (IT_GRAYEDOUT) : (IT_STRING|IT_SUBMENU); // Erase data -#else - OP_DataOptionsMenu[3].status = (Playing()) ? (IT_GRAYEDOUT) : (IT_STRING|IT_SUBMENU); // Erase data -#endif - - OP_GameOptionsMenu[3].status = - (M_SecretUnlocked(SECRET_ENCORE)) ? (IT_CVAR|IT_STRING) : IT_SECRET; // cv_kartencore - - OP_MainDef.prevMenu = currentMenu; - M_SetupNextMenu(&OP_MainDef); -} - -static void M_Manual(INT32 choice) -{ - (void)choice; - - MISC_HelpDef.prevMenu = (choice == INT32_MAX ? NULL : currentMenu); - M_SetupNextMenu(&MISC_HelpDef); -} - -static void M_RetryResponse(INT32 ch) -{ - if (ch != 'y' && ch != KEY_ENTER) - return; - - if (netgame || multiplayer) // Should never happen! - return; - - M_ClearMenus(true); - G_SetRetryFlag(); -} - -static void M_Retry(INT32 choice) -{ - (void)choice; - M_StartMessage(va("Start this %s over?\n\n(Press 'Y' to confirm)\n", (gametyperules & GTR_CIRCUIT) ? "race" : "battle"),FUNCPTRCAST(M_RetryResponse),MM_YESNO); -} - -static void M_SelectableClearMenus(INT32 choice) -{ - (void)choice; - M_ClearMenus(true); -} - -void M_RefreshPauseMenu(void) -{ -#ifdef HAVE_DISCORDRPC - if (discordRequestList != NULL) - { - MPauseMenu[mpause_discordrequests].status = IT_STRING | IT_SUBMENU; - } - else - { - MPauseMenu[mpause_discordrequests].status = IT_GRAYEDOUT; - } -#endif -} - -// ====== -// CHEATS -// ====== - -static void M_UltimateCheat(INT32 choice) -{ - (void)choice; - I_Quit(); -} - -static void M_GetAllEmeralds(INT32 choice) -{ - (void)choice; - - emeralds = EMERALD_ALL; - M_StartMessage(M_GetText("You now have all 7 emeralds.\nUse them wisely.\nWith great power comes great ring drain.\n"),NULL,MM_NOTHING); - - G_SetGameModified(multiplayer, true); -} - -static void M_DestroyRobotsResponse(INT32 ch) -{ - if (ch != 'y' && ch != KEY_ENTER) - return; - - // Destroy all robots - P_DestroyRobots(); - - G_SetGameModified(multiplayer, true); -} - -static void M_DestroyRobots(INT32 choice) -{ - (void)choice; - - M_StartMessage(M_GetText("Do you want to destroy all\nrobots in the current level?\n\n(Press 'Y' to confirm)\n"), FUNCPTRCAST(M_DestroyRobotsResponse), MM_YESNO); -} - -/*static void M_LevelSelectWarp(INT32 choice) -{ - boolean fromloadgame = (currentMenu == &SP_LevelSelectDef); - - (void)choice; - - if (W_CheckNumForName(G_BuildMapName(cv_nextmap.value)) == LUMPERROR) - { -// CONS_Alert(CONS_WARNING, "Internal game map '%s' not found\n", G_BuildMapName(cv_nextmap.value)); - return; - } - - startmap = (INT16)(cv_nextmap.value); - - fromlevelselect = true; - - if (fromloadgame) - G_LoadGame((UINT32)cursaveslot, startmap); - else - { - cursaveslot = -1; - M_SetupChoosePlayer(0); - } -}*/ - -// ======== -// SKY ROOM -// ======== - -UINT8 skyRoomMenuTranslations[MAXUNLOCKABLES]; - -static char *M_GetConditionString(condition_t cond) -{ - switch(cond.type) - { - case UC_PLAYTIME: - return va("Play for %i:%02i:%02i", - G_TicsToHours(cond.requirement), - G_TicsToMinutes(cond.requirement, false), - G_TicsToSeconds(cond.requirement)); - case UC_MATCHESPLAYED: - return va("Play %d matches", cond.requirement); - case UC_POWERLEVEL: - return va("Reach power level %d in %s", cond.requirement, (cond.extrainfo1 == PWRLV_BATTLE ? "Battle" : "Race")); - case UC_GAMECLEAR: - if (cond.requirement > 1) - return va("Beat game %d times", cond.requirement); - else - return va("Beat the game"); - case UC_OVERALLTIME: - return va("Get overall Time Attack of %i:%02i:%02i", - G_TicsToHours(cond.requirement), - G_TicsToMinutes(cond.requirement, false), - G_TicsToSeconds(cond.requirement)); - case UC_MAPVISITED: - return va("Visit %s", G_BuildMapTitle(cond.requirement-1)); - case UC_MAPBEATEN: - return va("Beat %s", G_BuildMapTitle(cond.requirement-1)); - case UC_MAPENCORE: - return va("Beat %s in Encore Mode", G_BuildMapTitle(cond.requirement-1)); - case UC_MAPTIME: - return va("Beat %s in %i:%02i.%02i", G_BuildMapTitle(cond.extrainfo1-1), - G_TicsToMinutes(cond.requirement, true), - G_TicsToSeconds(cond.requirement), - G_TicsToCentiseconds(cond.requirement)); - case UC_TOTALEMBLEMS: - return va("Get %d medals", cond.requirement); - case UC_EXTRAEMBLEM: - return va("Get \"%s\" medal", extraemblems[cond.requirement-1].name); - default: - return NULL; - } -} - -#define NUMCHECKLIST 23 -static void M_DrawChecklist(void) -{ - UINT32 i, line = 0, c; - INT32 lastid; - boolean secret = false; - - for (i = 0; i < MAXUNLOCKABLES; i++) - { - const char *secretname; - - secret = (!M_Achieved(unlockables[i].showconditionset - 1) && !unlockables[i].unlocked); - - if (unlockables[i].name[0] == 0 || unlockables[i].nochecklist - || !unlockables[i].conditionset || unlockables[i].conditionset > MAXCONDITIONSETS - || (unlockables[i].type == SECRET_HELLATTACK && secret)) // TODO: turn this into an unlockable setting instead of tying it to Hell Attack - continue; - - ++line; - secretname = M_CreateSecretMenuOption(unlockables[i].name); - - V_DrawString(8, (line*8), (unlockables[i].unlocked ? recommendedflags : warningflags), (secret ? secretname : unlockables[i].name)); - - if (conditionSets[unlockables[i].conditionset - 1].numconditions) - { - c = 0; - lastid = -1; - - for (c = 0; c < conditionSets[unlockables[i].conditionset - 1].numconditions; c++) - { - condition_t cond = conditionSets[unlockables[i].conditionset - 1].condition[c]; - UINT8 achieved = M_CheckCondition(&cond); - char *str = M_GetConditionString(cond); - const char *secretstr = M_CreateSecretMenuOption(str); - - if (!str) - continue; - - ++line; - - if (lastid == -1 || cond.id != (UINT32)lastid) - { - V_DrawString(16, (line*8), V_MONOSPACE|V_ALLOWLOWERCASE|(achieved ? highlightflags : 0), "*"); - V_DrawString(32, (line*8), V_MONOSPACE|V_ALLOWLOWERCASE|(achieved ? highlightflags : 0), (secret ? secretstr : str)); - } - else - { - V_DrawString(32, (line*8), V_MONOSPACE|V_ALLOWLOWERCASE|(achieved ? highlightflags : 0), (secret ? "?" : "&")); - V_DrawString(48, (line*8), V_MONOSPACE|V_ALLOWLOWERCASE|(achieved ? highlightflags : 0), (secret ? secretstr : str)); - } - - lastid = cond.id; - } - } - - ++line; - - if (line >= NUMCHECKLIST) - break; - } -} -#undef NUMCHECKLIST - -#define NUMHINTS 5 -static void M_EmblemHints(INT32 choice) -{ - (void)choice; - SR_EmblemHintMenu[0].status = (M_SecretUnlocked(SECRET_ITEMFINDER)) ? (IT_CVAR|IT_STRING) : (IT_SECRET); - M_SetupNextMenu(&SR_EmblemHintDef); - itemOn = 1; // always start on back. -} - -static void M_DrawEmblemHints(void) -{ - INT32 i, j = 0; - UINT32 collected = 0; - emblem_t *emblem; - const char *hint; - - for (i = 0; i < numemblems; i++) - { - emblem = &emblemlocations[i]; - if (emblem->level != gamemap || emblem->type != ET_GLOBAL) - continue; - - if (emblem->collected) - { - collected = recommendedflags; - V_DrawMappedPatch(12, 12+(28*j), 0, W_CachePatchName(M_GetEmblemPatch(emblem, false), PU_CACHE), - R_GetTranslationColormap(TC_DEFAULT, M_GetEmblemColor(emblem), GTC_MENUCACHE)); - } - else - { - collected = 0; - V_DrawScaledPatch(12, 12+(28*j), 0, W_CachePatchName("NEEDIT", PU_CACHE)); - } - - if (emblem->hint[0]) - hint = emblem->hint; - else - hint = M_GetText("No hints available."); - hint = V_WordWrap(40, BASEVIDWIDTH-12, 0, hint); - V_DrawString(40, 8+(28*j), V_ALLOWLOWERCASE|collected, hint); - - if (++j >= NUMHINTS) - break; - } - if (!j) - V_DrawCenteredString(160, 48, highlightflags, "No hidden medals on this map."); - - M_DrawGenericMenu(); -} - -static void M_DrawSkyRoom(void) -{ - INT32 i, y = 0; - INT32 lengthstring = 0; - - M_DrawGenericMenu(); - - if (currentMenu == &OP_SoundOptionsDef) - { - V_DrawRightAlignedString(BASEVIDWIDTH - currentMenu->x, - currentMenu->y+currentMenu->menuitems[0].alphaKey, - (sound_disabled ? warningflags : highlightflags), - (sound_disabled ? "OFF" : "ON")); - - V_DrawRightAlignedString(BASEVIDWIDTH - currentMenu->x, - currentMenu->y+currentMenu->menuitems[2].alphaKey, - (digital_disabled ? warningflags : highlightflags), - (digital_disabled ? "OFF" : "ON")); - - if (itemOn == 0) - lengthstring = 8*(sound_disabled ? 3 : 2); - else if (itemOn == 2) - lengthstring = 8*(digital_disabled ? 3 : 2); - } - - for (i = 0; i < currentMenu->numitems; ++i) - { - if (currentMenu->menuitems[i].itemaction.routine == M_HandleSoundTest) - { - y = currentMenu->menuitems[i].alphaKey; - break; - } - } - - if (y) - { - y += currentMenu->y; - - V_DrawRightAlignedString(BASEVIDWIDTH - currentMenu->x, y, highlightflags, cv_soundtest.string); - if (cv_soundtest.value) - V_DrawRightAlignedString(BASEVIDWIDTH - currentMenu->x, y + 8, highlightflags, S_sfx[cv_soundtest.value].name); - - if (i == itemOn) - lengthstring = V_StringWidth(cv_soundtest.string, 0); - } - - if (lengthstring) - { - V_DrawCharacter(BASEVIDWIDTH - currentMenu->x - 10 - lengthstring - (skullAnimCounter/5), currentMenu->y+currentMenu->menuitems[itemOn].alphaKey, - '\x1C' | highlightflags, false); // left arrow - V_DrawCharacter(BASEVIDWIDTH - currentMenu->x + 2 + (skullAnimCounter/5), currentMenu->y+currentMenu->menuitems[itemOn].alphaKey, - '\x1D' | highlightflags, false); // right arrow - } -} - -static void M_HandleSoundTest(INT32 choice) -{ - boolean exitmenu = false; // exit to previous menu - - switch (choice) - { - case KEY_DOWNARROW: - M_NextOpt(); - S_StartSound(NULL, sfx_menu1); - break; - case KEY_UPARROW: - M_PrevOpt(); - S_StartSound(NULL, sfx_menu1); - break; - case KEY_BACKSPACE: - case KEY_ESCAPE: - exitmenu = true; - break; - - case KEY_RIGHTARROW: - CV_AddValue(&cv_soundtest, 1); - break; - case KEY_LEFTARROW: - CV_AddValue(&cv_soundtest, -1); - break; - case KEY_ENTER: - S_StopSounds(); - S_StartSound(NULL, cv_soundtest.value); - break; - - default: - break; - } - if (exitmenu) - { - if (currentMenu->prevMenu) - M_SetupNextMenu(currentMenu->prevMenu); - else - M_ClearMenus(true); - } -} - -// Entering secrets menu -/*static void M_SecretsMenu(INT32 choice) -{ - INT32 i, j, ul; - UINT8 done[MAXUNLOCKABLES]; - UINT16 curheight; - - (void)choice; - - // Clear all before starting - for (i = 1; i < MAXUNLOCKABLES+1; ++i) - SR_MainMenu[i].status = IT_DISABLED; - - memset(skyRoomMenuTranslations, 0, sizeof(skyRoomMenuTranslations)); - memset(done, 0, sizeof(done)); - - for (i = 1; i < MAXUNLOCKABLES+1; ++i) - { - curheight = UINT16_MAX; - ul = -1; - - // Autosort unlockables - for (j = 0; j < MAXUNLOCKABLES; ++j) - { - if (!unlockables[j].height || done[j] || unlockables[j].type < 0) - continue; - - if (unlockables[j].height < curheight) - { - curheight = unlockables[j].height; - ul = j; - } - } - if (ul < 0) - break; - - done[ul] = true; - - skyRoomMenuTranslations[i-1] = (UINT8)ul; - SR_MainMenu[i].text = unlockables[ul].name; - SR_MainMenu[i].alphaKey = (UINT8)unlockables[ul].height; - - if (unlockables[ul].type == SECRET_HEADER) - { - SR_MainMenu[i].status = IT_HEADER; - continue; - } - - SR_MainMenu[i].status = IT_SECRET; - - if (unlockables[ul].unlocked) - { - switch (unlockables[ul].type) - { - case SECRET_LEVELSELECT: - SR_MainMenu[i].status = IT_STRING|IT_CALL; - SR_MainMenu[i].itemaction = M_CustomLevelSelect; - break; - case SECRET_WARP: - SR_MainMenu[i].status = IT_STRING|IT_CALL; - SR_MainMenu[i].itemaction = M_CustomWarp; - break; - case SECRET_CREDITS: - SR_MainMenu[i].status = IT_STRING|IT_CALL; - SR_MainMenu[i].itemaction = M_Credits; - break; - case SECRET_SOUNDTEST: - SR_MainMenu[i].status = IT_STRING|IT_KEYHANDLER; - SR_MainMenu[i].itemaction = M_HandleSoundTest; - default: - break; - } - } - } - - M_SetupNextMenu(&SR_MainDef); -}*/ - -// ================== -// NEW GAME FUNCTIONS -// ================== - -/*INT32 ultimate_selectable = false; - -static void M_NewGame(void) -{ - fromlevelselect = false; - - startmap = spstage_start; - CV_SetValue(&cv_newgametype, GT_RACE); // SRB2kart - - M_SetupChoosePlayer(0); -}*/ - -/*static void M_CustomWarp(INT32 choice) -{ - INT32 ul = skyRoomMenuTranslations[choice-1]; - - startmap = (INT16)(unlockables[ul].variable); - - M_SetupChoosePlayer(0); -}*/ - -static void M_Credits(INT32 choice) -{ - (void)choice; - cursaveslot = -2; - M_ClearMenus(true); - F_StartCredits(); -} - -/*static void M_CustomLevelSelect(INT32 choice) -{ - INT32 ul = skyRoomMenuTranslations[choice-1]; - - SR_LevelSelectDef.prevMenu = currentMenu; - levellistmode = LLM_LEVELSELECT; - maplistoption = (UINT8)(unlockables[ul].variable); - if (M_CountLevelsToShowInList() == 0) - { - M_StartMessage(M_GetText("No selectable levels found.\n"),NULL,MM_NOTHING); - return; - } - - M_PrepareLevelSelect(); - M_SetupNextMenu(&SR_LevelSelectDef); -}*/ - -// ================== -// SINGLE PLAYER MENU -// ================== - -#ifndef TESTERS -static void M_SinglePlayerMenu(INT32 choice) -{ - (void)choice; - - SP_MainMenu[spgrandprix].status = IT_CALL|IT_STRING; - SP_MainMenu[sptimeattack].status = - (M_SecretUnlocked(SECRET_TIMEATTACK)) ? IT_CALL|IT_STRING : IT_SECRET; - SP_MainMenu[spbreakthecapsules].status = - (M_SecretUnlocked(SECRET_BREAKTHECAPSULES)) ? IT_CALL|IT_STRING : IT_SECRET; - - M_SetupNextMenu(&SP_MainDef); -} -#endif - -/*static void M_LoadGameLevelSelect(INT32 choice) -{ - (void)choice; - levellistmode = LLM_LEVELSELECT; - maplistoption = 1; - if (M_CountLevelsToShowInList() == 0) - { - M_StartMessage(M_GetText("No selectable levels found.\n"),NULL,MM_NOTHING); - return; - } - - SP_LevelSelectDef.prevMenu = currentMenu; - - M_PrepareLevelSelect(); - M_SetupNextMenu(&SP_LevelSelectDef); -}*/ - -// ============== -// LOAD GAME MENU -// ============== - -/*static INT32 saveSlotSelected = 0; -static short menumovedir = 0; - -static void M_DrawLoadGameData(void) -{ - INT32 ecks; - INT32 i; - - ecks = SP_LoadDef.x + 24; - M_DrawTextBox(SP_LoadDef.x-12,144, 24, 4); - - if (saveSlotSelected == NOSAVESLOT) // last slot is play without saving - { - if (ultimate_selectable) - { - V_DrawCenteredString(ecks + 68, 144, V_ORANGEMAP, "ULTIMATE MODE"); - V_DrawCenteredString(ecks + 68, 156, 0, "NO RINGS, NO ONE-UPS,"); - V_DrawCenteredString(ecks + 68, 164, 0, "NO CONTINUES, ONE LIFE,"); - V_DrawCenteredString(ecks + 68, 172, 0, "FINAL DESTINATION."); - } - else - { - V_DrawCenteredString(ecks + 68, 144, V_ORANGEMAP, "PLAY WITHOUT SAVING"); - V_DrawCenteredString(ecks + 68, 156, 0, "THIS GAME WILL NOT BE"); - V_DrawCenteredString(ecks + 68, 164, 0, "SAVED, BUT YOU CAN STILL"); - V_DrawCenteredString(ecks + 68, 172, 0, "GET MEDALS AND SECRETS."); - } - return; - } - - if (savegameinfo[saveSlotSelected].lives == -42) // Empty - { - V_DrawCenteredString(ecks + 68, 160, 0, "NO DATA"); - return; - } - - if (savegameinfo[saveSlotSelected].lives == -666) // savegame is bad - { - V_DrawCenteredString(ecks + 68, 144, warningflags, "CORRUPT SAVE FILE"); - V_DrawCenteredString(ecks + 68, 156, 0, "THIS SAVE FILE"); - V_DrawCenteredString(ecks + 68, 164, 0, "CAN NOT BE LOADED."); - V_DrawCenteredString(ecks + 68, 172, 0, "DELETE USING BACKSPACE."); - return; - } - - // Draw the back sprite, it looks ugly if we don't - V_DrawScaledPatch(SP_LoadDef.x, 144+8, 0, livesback); - if (savegameinfo[saveSlotSelected].skincolor == 0) - V_DrawScaledPatch(SP_LoadDef.x,144+8,0,W_CachePatchName(skins[savegameinfo[saveSlotSelected].skinnum].face, PU_CACHE)); - else - { - UINT8 *colormap = R_GetTranslationColormap(savegameinfo[saveSlotSelected].skinnum, savegameinfo[saveSlotSelected].skincolor, GTC_MENUCACHE); - V_DrawMappedPatch(SP_LoadDef.x,144+8,0,W_CachePatchName(skins[savegameinfo[saveSlotSelected].skinnum].face, PU_CACHE), colormap); - } - - V_DrawString(ecks + 12, 152, 0, savegameinfo[saveSlotSelected].playername); - -#ifdef SAVEGAMES_OTHERVERSIONS - if (savegameinfo[saveSlotSelected].gamemap & 16384) - V_DrawCenteredString(ecks + 68, 144, warningflags, "OUTDATED SAVE FILE!"); -#endif - - if (savegameinfo[saveSlotSelected].gamemap & 8192) - V_DrawString(ecks + 12, 160, recommendedflags, "CLEAR!"); - else - V_DrawString(ecks + 12, 160, 0, va("%s", savegameinfo[saveSlotSelected].levelname)); - - // Use the big face pic for lives, duh. :3 - V_DrawScaledPatch(ecks + 12, 175, 0, W_CachePatchName("STLIVEX", PU_HUDGFX)); - V_DrawTallNum(ecks + 40, 172, 0, savegameinfo[saveSlotSelected].lives); - - // Absolute ridiculousness, condensed into another function. - V_DrawContinueIcon(ecks + 58, 182, 0, savegameinfo[saveSlotSelected].skinnum, savegameinfo[saveSlotSelected].skincolor); - V_DrawScaledPatch(ecks + 68, 175, 0, W_CachePatchName("STLIVEX", PU_HUDGFX)); - V_DrawTallNum(ecks + 96, 172, 0, savegameinfo[saveSlotSelected].continues); - - for (i = 0; i < 7; ++i) - { - if (savegameinfo[saveSlotSelected].numemeralds & (1 << i)) - V_DrawScaledPatch(ecks + 104 + (i * 8), 172, 0, tinyemeraldpics[i]); - } -} - -#define LOADBARHEIGHT SP_LoadDef.y + (LINEHEIGHT * (j+1)) + ymod -#define CURSORHEIGHT SP_LoadDef.y + (LINEHEIGHT*3) - 1 -static void M_DrawLoad(void) -{ - INT32 i, j; - INT32 ymod = 0, offset = 0; - - M_DrawMenuTitle(); - fixed_t scrollfrac = FixedDiv(2, 3); - - if (menumovedir != 0) //movement illusion - { - ymod = (-(LINEHEIGHT/4))*menumovedir; - offset = ((menumovedir > 0) ? -1 : 1); - } - - V_DrawCenteredString(BASEVIDWIDTH/2, 40, 0, "Press backspace to delete a save."); - - for (i = MAXSAVEGAMES + saveSlotSelected - 2 + offset, j = 0;i <= MAXSAVEGAMES + saveSlotSelected + 2 + offset; i++, j++) - { - if ((menumovedir < 0 && j == 4) || (menumovedir > 0 && j == 0)) - continue; //this helps give the illusion of movement - - M_DrawSaveLoadBorder(SP_LoadDef.x, LOADBARHEIGHT); - - if ((i%MAXSAVEGAMES) == NOSAVESLOT) // play without saving - { - if (ultimate_selectable) - V_DrawCenteredString(SP_LoadDef.x+92, LOADBARHEIGHT - 1, V_ORANGEMAP, "ULTIMATE MODE"); - else - V_DrawCenteredString(SP_LoadDef.x+92, LOADBARHEIGHT - 1, V_ORANGEMAP, "PLAY WITHOUT SAVING"); - continue; - } - - if (savegameinfo[i%MAXSAVEGAMES].lives == -42) - V_DrawString(SP_LoadDef.x-6, LOADBARHEIGHT - 1, V_TRANSLUCENT, "NO DATA"); - else if (savegameinfo[i%MAXSAVEGAMES].lives == -666) - V_DrawString(SP_LoadDef.x-6, LOADBARHEIGHT - 1, warningflags, "CORRUPT SAVE FILE"); - else if (savegameinfo[i%MAXSAVEGAMES].gamemap & 8192) - V_DrawString(SP_LoadDef.x-6, LOADBARHEIGHT - 1, recommendedflags, "CLEAR!"); - else - V_DrawString(SP_LoadDef.x-6, LOADBARHEIGHT - 1, 0, va("%s", savegameinfo[i%MAXSAVEGAMES].levelname)); - - //Draw the save slot number on the right side - V_DrawRightAlignedString(SP_LoadDef.x+192, LOADBARHEIGHT - 1, 0, va("%d",(i%MAXSAVEGAMES) + 1)); - } - - //Draw cursors on both sides. - V_DrawScaledPatch( 32, CURSORHEIGHT, 0, W_CachePatchName("M_CURSOR", PU_CACHE)); - V_DrawScaledPatch(274, CURSORHEIGHT, 0, W_CachePatchName("M_CURSOR", PU_CACHE)); - - M_DrawLoadGameData(); - - //finishing the movement illusion - if (menumovedir) - menumovedir += ((menumovedir > 0) ? 1 : -1); - if (abs(menumovedir) > 3) - menumovedir = 0; -} -#undef LOADBARHEIGHT -#undef CURSORHEIGHT - -// -// User wants to load this game -// -static void M_LoadSelect(INT32 choice) -{ - (void)choice; - - if (saveSlotSelected == NOSAVESLOT) //last slot is play without saving - { - M_NewGame(); - cursaveslot = -1; - return; - } - - if (!FIL_ReadFileOK(va(savegamename, saveSlotSelected))) - { - // This slot is empty, so start a new game here. - M_NewGame(); - } - else if (savegameinfo[saveSlotSelected].gamemap & 8192) // Completed - M_LoadGameLevelSelect(saveSlotSelected + 1); - else - G_LoadGame((UINT32)saveSlotSelected, 0); - - cursaveslot = saveSlotSelected; -} - -#define VERSIONSIZE 16 -#define BADSAVE { savegameinfo[slot].lives = -666; Z_Free(savebuffer); return; } -#define CHECKPOS if (save_p >= end_p) BADSAVE -// Reads the save file to list lives, level, player, etc. -// Tails 05-29-2003 -static void M_ReadSavegameInfo(UINT32 slot) -{ - size_t length; - char savename[255]; - UINT8 *savebuffer; - UINT8 *end_p; // buffer end point, don't read past here - UINT8 *save_p; - INT32 fake; // Dummy variable - char temp[sizeof(timeattackfolder)]; - char vcheck[VERSIONSIZE]; -#ifdef SAVEGAMES_OTHERVERSIONS - boolean oldversion = false; -#endif - - sprintf(savename, savegamename, slot); - - length = FIL_ReadFile(savename, &savebuffer); - if (length == 0) - { - savegameinfo[slot].lives = -42; - return; - } - - end_p = savebuffer + length; - - // skip the description field - save_p = savebuffer; - - // Version check - memset(vcheck, 0, sizeof (vcheck)); - sprintf(vcheck, "version %d", VERSION); - if (strcmp((const char *)save_p, (const char *)vcheck)) - { -#ifdef SAVEGAMES_OTHERVERSIONS - oldversion = true; -#else - BADSAVE // Incompatible versions? -#endif - } - save_p += VERSIONSIZE; - - // dearchive all the modifications - // P_UnArchiveMisc() - - CHECKPOS - fake = READINT16(save_p); - - if (((fake-1) & 8191) >= NUMMAPS) BADSAVE - - if(!mapheaderinfo[(fake-1) & 8191]) - { - savegameinfo[slot].levelname[0] = '\0'; - savegameinfo[slot].actnum = 0; - } - else - { - strcpy(savegameinfo[slot].levelname, mapheaderinfo[(fake-1) & 8191]->lvlttl); - savegameinfo[slot].actnum = 0; //mapheaderinfo[(fake-1) & 8191]->actnum - } - -#ifdef SAVEGAMES_OTHERVERSIONS - if (oldversion) - { - if (fake == 24) //meh, let's count old Clear! saves too - fake |= 8192; - fake |= 16384; // marker for outdated version - } -#endif - savegameinfo[slot].gamemap = fake; - - CHECKPOS - fake = READUINT16(save_p)-357; // emeralds - - savegameinfo[slot].numemeralds = (UINT8)fake; - - CHECKPOS - READSTRINGN(save_p, temp, sizeof(temp)); // mod it belongs to - - if (strcmp(temp, timeattackfolder)) BADSAVE - - // P_UnArchivePlayer() - CHECKPOS - savegameinfo[slot].skincolor = READUINT8(save_p); - CHECKPOS - savegameinfo[slot].skinnum = READUINT8(save_p); - - CHECKPOS - (void)READINT32(save_p); // Score - - CHECKPOS - savegameinfo[slot].lives = READINT32(save_p); // lives - CHECKPOS - savegameinfo[slot].continues = READINT32(save_p); // continues - - if (fake & (1<<10)) - { - CHECKPOS - savegameinfo[slot].botskin = READUINT8(save_p); - if (savegameinfo[slot].botskin-1 >= numskins) - savegameinfo[slot].botskin = 0; - CHECKPOS - savegameinfo[slot].botcolor = READUINT8(save_p); // because why not. - } - else - savegameinfo[slot].botskin = 0; - - if (savegameinfo[slot].botskin) - snprintf(savegameinfo[slot].playername, 36, "%s & %s", - skins[savegameinfo[slot].skinnum].realname, - skins[savegameinfo[slot].botskin-1].realname); - else - strcpy(savegameinfo[slot].playername, skins[savegameinfo[slot].skinnum].realname); - - savegameinfo[slot].playername[31] = 0; - - // File end marker check - CHECKPOS - if (READUINT8(save_p) != 0x1d) BADSAVE; - - // done - Z_Free(savebuffer); -} -#undef CHECKPOS -#undef BADSAVE - -// -// M_ReadSaveStrings -// read the strings from the savegame files -// and put it in savegamestrings global variable -// -static void M_ReadSaveStrings(void) -{ - FILE *handle; - UINT32 i; - char name[256]; - - for (i = 0; i < MAXSAVEGAMES; i++) - { - snprintf(name, sizeof name, savegamename, i); - name[sizeof name - 1] = '\0'; - - handle = fopen(name, "rb"); - if (handle == NULL) - { - savegameinfo[i].lives = -42; - continue; - } - fclose(handle); - M_ReadSavegameInfo(i); - } -} - -// -// User wants to delete this game -// -static void M_SaveGameDeleteResponse(INT32 ch) -{ - char name[256]; - - if (ch != 'y' && ch != KEY_ENTER) - return; - - // delete savegame - snprintf(name, sizeof name, savegamename, saveSlotSelected); - name[sizeof name - 1] = '\0'; - remove(name); - - // Refresh savegame menu info - M_ReadSaveStrings(); -} - -static void M_HandleLoadSave(INT32 choice) -{ - boolean exitmenu = false; // exit to previous menu - - switch (choice) - { - case KEY_DOWNARROW: - S_StartSound(NULL, sfx_menu1); - ++saveSlotSelected; - if (saveSlotSelected >= MAXSAVEGAMES) - saveSlotSelected -= MAXSAVEGAMES; - menumovedir = 1; - break; - - case KEY_UPARROW: - S_StartSound(NULL, sfx_menu1); - --saveSlotSelected; - if (saveSlotSelected < 0) - saveSlotSelected += MAXSAVEGAMES; - menumovedir = -1; - break; - - case KEY_ENTER: - S_StartSound(NULL, sfx_menu1); - if (savegameinfo[saveSlotSelected].lives != -666) // don't allow loading of "bad saves" - M_LoadSelect(saveSlotSelected); - break; - - case KEY_ESCAPE: - exitmenu = true; - break; - - case KEY_BACKSPACE: - S_StartSound(NULL, sfx_menu1); - // Don't allow people to 'delete' "Play without Saving." - // Nor allow people to 'delete' slots with no saves in them. - if (saveSlotSelected != NOSAVESLOT && savegameinfo[saveSlotSelected].lives != -42) - M_StartMessage(M_GetText("Are you sure you want to delete\nthis save game?\n\n(Press 'Y' to confirm)\n"),M_SaveGameDeleteResponse,MM_YESNO); - break; - } - if (exitmenu) - { - if (currentMenu->prevMenu) - M_SetupNextMenu(currentMenu->prevMenu); - else - M_ClearMenus(true); - } -} - -// -// Selected from SRB2 menu -// -static void M_LoadGame(INT32 choice) -{ - (void)choice; - - M_ReadSaveStrings(); - M_SetupNextMenu(&SP_LoadDef); -} - -// -// Used by cheats to force the save menu to a specific spot. -// -void M_ForceSaveSlotSelected(INT32 sslot) -{ - // Already there? Out of bounds? Whatever, then! - if (sslot == saveSlotSelected || sslot >= MAXSAVEGAMES) - return; - - // Figure out whether to display up movement or down movement - menumovedir = (saveSlotSelected - sslot) > 0 ? -1 : 1; - if (abs(saveSlotSelected - sslot) > (MAXSAVEGAMES>>1)) - menumovedir *= -1; - - saveSlotSelected = sslot; -} - -// ================ -// CHARACTER SELECT -// ================ - -static void M_SetupChoosePlayer(INT32 choice) -{ - (void)choice; - - if (mapheaderinfo[startmap-1] && mapheaderinfo[startmap-1]->forcecharacter[0] != '\0') - { - M_ChoosePlayer(0); //oh for crying out loud just get STARTED, it doesn't matter! - return; - } - - if (Playing() == false) - { - S_StopMusic(); - S_ChangeMusicInternal("chrsel", true); - } - - SP_PlayerDef.prevMenu = currentMenu; - M_SetupNextMenu(&SP_PlayerDef); - char_scroll = itemOn*128*FRACUNIT; // finish scrolling the menu - Z_Free(char_notes); - char_notes = NULL; -} - -// Draw the choose player setup menu, had some fun with player anim -static void M_DrawSetupChoosePlayerMenu(void) -{ - const INT32 my = 24; - patch_t *patch; - INT32 i, o, j; - char *picname; - - // Black BG - V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31); - //V_DrawPatchFill(W_CachePatchName("SRB2BACK", PU_CACHE)); - - // Character select profile images!1 - M_DrawTextBox(0, my, 16, 20); - - if (abs(itemOn*128*FRACUNIT - char_scroll) > 256*FRACUNIT) - char_scroll = itemOn*128*FRACUNIT; - else if (itemOn*128*FRACUNIT - char_scroll > 128*FRACUNIT) - char_scroll += 48*FRACUNIT; - else if (itemOn*128*FRACUNIT - char_scroll < -128*FRACUNIT) - char_scroll -= 48*FRACUNIT; - else if (itemOn*128*FRACUNIT > char_scroll+16*FRACUNIT) - char_scroll += 16*FRACUNIT; - else if (itemOn*128*FRACUNIT < char_scroll-16*FRACUNIT) - char_scroll -= 16*FRACUNIT; - else // close enough. - char_scroll = itemOn*128*FRACUNIT; // just be exact now. - i = (char_scroll+16*FRACUNIT)/(128*FRACUNIT); - o = ((char_scroll/FRACUNIT)+16)%128; - - // prev character - if (i-1 >= 0 && PlayerMenu[i-1].status != IT_DISABLED - && o < 32) - { - picname = description[i-1].picname; - if (picname[0] == '\0') - { - picname = strtok(Z_StrDup(description[i-1].skinname), "&"); - for (j = 0; j < numskins; j++) - if (stricmp(skins[j].name, picname) == 0) - { - Z_Free(picname); - picname = skins[j].charsel; - break; - } - if (j == numskins) // AAAAAAAAAA - picname = skins[0].charsel; - } - patch = W_CachePatchName(picname, PU_CACHE); - if (SHORT(patch->width) >= 256) - V_DrawCroppedPatch(8<height) - 64 + o*2, SHORT(patch->width), SHORT(patch->height)); - else - V_DrawCroppedPatch(8<height) - 32 + o, SHORT(patch->width), SHORT(patch->height)); - W_UnlockCachedPatch(patch); - } - - // next character - if (i+1 < currentMenu->numitems && PlayerMenu[i+1].status != IT_DISABLED - && o < 128) - { - picname = description[i+1].picname; - if (picname[0] == '\0') - { - picname = strtok(Z_StrDup(description[i+1].skinname), "&"); - for (j = 0; j < numskins; j++) - if (stricmp(skins[j].name, picname) == 0) - { - Z_Free(picname); - picname = skins[j].charsel; - break; - } - if (j == numskins) // AAAAAAAAAA - picname = skins[0].charsel; - } - patch = W_CachePatchName(picname, PU_CACHE); - if (SHORT(patch->width) >= 256) - V_DrawCroppedPatch(8<width), o*2); - else - V_DrawCroppedPatch(8<width), o); - W_UnlockCachedPatch(patch); - } - - // current character - if (i < currentMenu->numitems && PlayerMenu[i].status != IT_DISABLED) - { - picname = description[i].picname; - if (picname[0] == '\0') - { - picname = strtok(Z_StrDup(description[i].skinname), "&"); - for (j = 0; j < numskins; j++) - if (stricmp(skins[j].name, picname) == 0) - { - Z_Free(picname); - picname = skins[j].charsel; - break; - } - if (j == numskins) // AAAAAAAAAA - picname = skins[0].charsel; - } - patch = W_CachePatchName(picname, PU_CACHE); - if (o >= 0 && o <= 32) - { - if (SHORT(patch->width) >= 256) - V_DrawSmallScaledPatch(8, my + 40 - o, 0, patch); - else - V_DrawScaledPatch(8, my + 40 - o, 0, patch); - } - else - { - if (SHORT(patch->width) >= 256) - V_DrawCroppedPatch(8<width), SHORT(patch->height)); - else - V_DrawCroppedPatch(8<width), SHORT(patch->height)); - } - W_UnlockCachedPatch(patch); - } - - // draw title (or big pic) - M_DrawMenuTitle(); - - // Character description - M_DrawTextBox(136, my, 21, 20); - if (!char_notes) - char_notes = V_WordWrap(0, 21*8, V_ALLOWLOWERCASE, description[itemOn].notes); - V_DrawString(146, my + 9, V_ALLOWLOWERCASE, char_notes); -} - -// Chose the player you want to use Tails 03-02-2002 -static void M_ChoosePlayer(INT32 choice) -{ - char *skin1,*skin2; - INT32 skinnum; - //boolean ultmode = (ultimate_selectable && SP_PlayerDef.prevMenu == &SP_LoadDef && saveSlotSelected == NOSAVESLOT); - - // skip this if forcecharacter - if (mapheaderinfo[startmap-1] && mapheaderinfo[startmap-1]->forcecharacter[0] == '\0') - { - // M_SetupChoosePlayer didn't call us directly, that means we've been properly set up. - char_scroll = itemOn*128*FRACUNIT; // finish scrolling the menu - M_DrawSetupChoosePlayerMenu(); // draw the finally selected character one last time for the fadeout - } - M_ClearMenus(true); - - skin1 = strtok(description[choice].skinname, "&"); - skin2 = strtok(NULL, "&"); - - if (skin2) { - // this character has a second skin - skinnum = R_SkinAvailable(skin1); - botskin = (UINT8)(R_SkinAvailable(skin2)+1); - botingame = true; - - botcolor = skins[botskin-1].prefcolor; - - // undo the strtok - description[choice].skinname[strlen(skin1)] = '&'; - } else { - skinnum = R_SkinAvailable(description[choice].skinname); - botingame = false; - botskin = 0; - botcolor = 0; - } - - if (startmap != spstage_start) - cursaveslot = -1; - - lastmapsaved = 0; - gamecomplete = false; - - G_DeferedInitNew(false, G_BuildMapName(startmap), (UINT8)skinnum, 0, fromlevelselect); - COM_BufAddText("dummyconsvar 1\n"); // G_DeferedInitNew doesn't do this -}*/ - -// =============== -// STATISTICS MENU -// =============== - -static INT32 statsLocation; -static INT32 statsMax; -static INT16 statsMapList[NUMMAPS+1]; - -static void M_Statistics(INT32 choice) -{ - INT16 i, j = 0; - - (void)choice; - - memset(statsMapList, 0, sizeof(statsMapList)); - - for (i = 0; i < NUMMAPS; i++) - { - if (!mapheaderinfo[i] || mapheaderinfo[i]->lvlttl[0] == '\0') - continue; - - if (mapheaderinfo[i]->menuflags & (LF2_HIDEINSTATS|LF2_HIDEINMENU)) - continue; - - if (M_MapLocked(i+1)) // !mapvisited[i] - continue; - - statsMapList[j++] = i; - } - statsMapList[j] = -1; - statsMax = j - 11 + numextraemblems; - statsLocation = 0; - - if (statsMax < 0) - statsMax = 0; - - M_SetupNextMenu(&SP_LevelStatsDef); -} - -static void M_DrawStatsMaps(int location) -{ - INT32 y = 88, i = -1; - INT16 mnum; - extraemblem_t *exemblem; - boolean dotopname = true, dobottomarrow = (location < statsMax); - - if (location) - V_DrawCharacter(10, y-(skullAnimCounter/5), - '\x1A' | highlightflags, false); // up arrow - - while (statsMapList[++i] != -1) - { - if (location) - { - --location; - continue; - } - else if (dotopname) - { - V_DrawString(20, y, highlightflags, "LEVEL NAME"); - V_DrawString(256, y, highlightflags, "MEDALS"); - y += 8; - dotopname = false; - } - - mnum = statsMapList[i]; - M_DrawMapEmblems(mnum+1, 295, y); - - if (mapheaderinfo[mnum]->levelflags & LF_NOZONE) - V_DrawString(20, y, 0, va("%s %d", - mapheaderinfo[mnum]->lvlttl, - mapheaderinfo[mnum]->actnum)); - else - V_DrawString(20, y, 0, va("%s %s %d", - mapheaderinfo[mnum]->lvlttl, - (mapheaderinfo[mnum]->zonttl[0] ? mapheaderinfo[mnum]->zonttl : "Zone"), - mapheaderinfo[mnum]->actnum)); - - y += 8; - - if (y >= BASEVIDHEIGHT-8) - goto bottomarrow; - } - if (dotopname && !location) - { - V_DrawString(20, y, highlightflags, "LEVEL NAME"); - V_DrawString(256, y, highlightflags, "MEDALS"); - y += 8; - } - else if (location) - --location; - - // Extra Emblems - for (i = -2; i < numextraemblems; ++i) - { - if (i == -1) - { - V_DrawString(20, y, highlightflags, "EXTRA MEDALS"); - if (location) - { - y += 8; - location++; - } - } - if (location) - { - --location; - continue; - } - - if (i >= 0) - { - exemblem = &extraemblems[i]; - - if (exemblem->collected) - V_DrawSmallMappedPatch(295, y, 0, W_CachePatchName(M_GetExtraEmblemPatch(exemblem, false), PU_CACHE), - R_GetTranslationColormap(TC_DEFAULT, M_GetExtraEmblemColor(exemblem), GTC_MENUCACHE)); - else - V_DrawSmallScaledPatch(295, y, 0, W_CachePatchName("NEEDIT", PU_CACHE)); - - V_DrawString(20, y, 0, va("%s", exemblem->description)); - } - - y += 8; - - if (y >= BASEVIDHEIGHT-8) - goto bottomarrow; - } -bottomarrow: - if (dobottomarrow) - V_DrawCharacter(10, y-8 + (skullAnimCounter/5), - '\x1B' | highlightflags, false); // down arrow -} - -static void M_DrawLevelStats(void) -{ - char beststr[40]; - - tic_t besttime = 0; - - INT32 i; - INT32 mapsunfinished = 0; - - M_DrawMenuTitle(); - - V_DrawString(20, 24, highlightflags, "Total Play Time:"); - V_DrawCenteredString(BASEVIDWIDTH/2, 32, 0, va("%i hours, %i minutes, %i seconds", - G_TicsToHours(totalplaytime), - G_TicsToMinutes(totalplaytime, false), - G_TicsToSeconds(totalplaytime))); - - V_DrawString(20, 42, highlightflags, "Total Matches:"); - V_DrawRightAlignedString(BASEVIDWIDTH-16, 42, 0, va("%i played", matchesplayed)); - - V_DrawString(20, 52, highlightflags, "Online Power Level:"); - V_DrawRightAlignedString(BASEVIDWIDTH-16, 52, 0, va("Race: %i", vspowerlevel[PWRLV_RACE])); - V_DrawRightAlignedString(BASEVIDWIDTH-16, 60, 0, va("Battle: %i", vspowerlevel[PWRLV_BATTLE])); - - for (i = 0; i < NUMMAPS; i++) - { - if (!mapheaderinfo[i] || (mapheaderinfo[i]->menuflags & LF2_NOTIMEATTACK)) - continue; - - if (!mainrecords[i] || mainrecords[i]->time <= 0) - { - mapsunfinished++; - continue; - } - - besttime += mainrecords[i]->time; - } - - V_DrawString(20, 70, highlightflags, "Combined time records:"); - - sprintf(beststr, "%i:%02i:%02i.%02i", G_TicsToHours(besttime), G_TicsToMinutes(besttime, false), G_TicsToSeconds(besttime), G_TicsToCentiseconds(besttime)); - V_DrawRightAlignedString(BASEVIDWIDTH-16, 70, (mapsunfinished ? warningflags : 0), beststr); - - if (mapsunfinished) - V_DrawRightAlignedString(BASEVIDWIDTH-16, 78, warningflags, va("(%d unfinished)", mapsunfinished)); - else - V_DrawRightAlignedString(BASEVIDWIDTH-16, 78, recommendedflags, "(complete)"); - - V_DrawString(32, 78, V_ALLOWLOWERCASE, va("x %d/%d", M_CountEmblems(), numemblems+numextraemblems)); - V_DrawSmallScaledPatch(20, 78, 0, W_CachePatchName("GOTITA", PU_STATIC)); - - M_DrawStatsMaps(statsLocation); -} - -// Handle statistics. -static void M_HandleLevelStats(INT32 choice) -{ - boolean exitmenu = false; // exit to previous menu - - switch (choice) - { - case KEY_DOWNARROW: - S_StartSound(NULL, sfx_menu1); - if (statsLocation < statsMax) - ++statsLocation; - break; - - case KEY_UPARROW: - S_StartSound(NULL, sfx_menu1); - if (statsLocation) - --statsLocation; - break; - - case KEY_PGDN: - S_StartSound(NULL, sfx_menu1); - statsLocation += (statsLocation+13 >= statsMax) ? statsMax-statsLocation : 13; - break; - - case KEY_PGUP: - S_StartSound(NULL, sfx_menu1); - statsLocation -= (statsLocation < 13) ? statsLocation : 13; - break; - - case KEY_ESCAPE: - exitmenu = true; - break; - } - if (exitmenu) - { - if (currentMenu->prevMenu) - M_SetupNextMenu(currentMenu->prevMenu); - else - M_ClearMenus(true); - } -} - -static void M_GrandPrixTemp(INT32 choice) -{ - (void)choice; - if (!M_PrepareCupList()) - { - M_StartMessage(M_GetText("No cups found for Grand Prix.\n"),NULL,MM_NOTHING); - return; - } - M_PatchSkinNameTable(); - M_SetupNextMenu(&SP_GrandPrixTempDef); -} - -// Start Grand Prix! -static void M_StartGrandPrix(INT32 choice) -{ - cupheader_t *gpcup = kartcupheaders; - - (void)choice; - - if (gpcup == NULL) - { - // welp - I_Error("No cup definitions for GP\n"); - return; - } - - M_ClearMenus(true); - - memset(&grandprixinfo, 0, sizeof(struct grandprixinfo)); - - switch (cv_dummygpdifficulty.value) - { - case KARTSPEED_EASY: - case KARTSPEED_NORMAL: - case KARTSPEED_HARD: - grandprixinfo.gamespeed = cv_dummygpdifficulty.value; - break; - case KARTGP_MASTER: - grandprixinfo.gamespeed = KARTSPEED_HARD; - grandprixinfo.masterbots = true; - break; - default: - CONS_Alert(CONS_WARNING, "Invalid GP difficulty\n"); - grandprixinfo.gamespeed = KARTSPEED_NORMAL; - break; - } - - grandprixinfo.encore = (boolean)(cv_dummygpencore.value); - - while (gpcup != NULL && gpcup->id != cv_dummygpcup.value-1) - { - gpcup = gpcup->next; - } - - if (gpcup == NULL) - { - gpcup = kartcupheaders; - } - - grandprixinfo.cup = gpcup; - - grandprixinfo.gp = true; - grandprixinfo.roundnum = 1; - grandprixinfo.wonround = false; - - grandprixinfo.initalize = true; - - G_DeferedInitNew( - false, - G_BuildMapName(grandprixinfo.cup->levellist[0] + 1), - (UINT8)(cv_chooseskin.value - 1), - (UINT8)(cv_splitplayers.value - 1), - false - ); -} - -// =========== -// MODE ATTACK -// =========== - -// Drawing function for Time Attack -void M_DrawTimeAttackMenu(void) -{ - INT32 i, x, y, cursory = 0; - UINT16 dispstatus; - - //S_ChangeMusicInternal("racent", true); // Eww, but needed for when user hits escape during demo playback - - V_DrawPatchFill(W_CachePatchName("SRB2BACK", PU_CACHE)); - - M_DrawMenuTitle(); - if (currentMenu == &SP_TimeAttackDef) - M_DrawLevelSelectOnly(true, false); - - // draw menu (everything else goes on top of it) - // Sadly we can't just use generic mode menus because we need some extra hacks - x = currentMenu->x; - y = currentMenu->y; - - // Character face! - { - UINT8 *colormap = R_GetTranslationColormap(cv_chooseskin.value-1, cv_playercolor[0].value, GTC_MENUCACHE); - V_DrawMappedPatch(BASEVIDWIDTH-x - SHORT(faceprefix[cv_chooseskin.value-1][FACE_WANTED]->width), y, 0, faceprefix[cv_chooseskin.value-1][FACE_WANTED], colormap); - } - - for (i = 0; i < currentMenu->numitems; ++i) - { - dispstatus = (currentMenu->menuitems[i].status & IT_DISPLAY); - if (dispstatus != IT_STRING && dispstatus != IT_WHITESTRING) - continue; - - y = currentMenu->y+currentMenu->menuitems[i].alphaKey; - if (i == itemOn) - cursory = y; - - V_DrawString(x, y, (dispstatus == IT_WHITESTRING) ? highlightflags : 0 , currentMenu->menuitems[i].text); - - // Cvar specific handling - if ((currentMenu->menuitems[i].status & IT_TYPE) == IT_CVAR) - { - consvar_t *cv = currentMenu->menuitems[i].itemaction.cvar; - if (currentMenu->menuitems[i].status & IT_CV_STRING) - { - M_DrawTextBox(x + 32, y - 8, MAXPLAYERNAME, 1); - V_DrawString(x + 40, y, V_ALLOWLOWERCASE, cv->string); - if (itemOn == i && skullAnimCounter < 4) // blink cursor - V_DrawCharacter(x + 40 + V_StringWidth(cv->string, V_ALLOWLOWERCASE), y, '_',false); - } - else - { - const char *str = ((cv == &cv_chooseskin) ? skins[cv_chooseskin.value-1].realname : cv->string); - INT32 soffset = 40, strw = V_StringWidth(str, 0); - - // hack to keep the menu from overlapping the level icon - if (currentMenu != &SP_TimeAttackDef || cv == &cv_nextmap) - soffset = 0; - - // Should see nothing but strings - V_DrawString(BASEVIDWIDTH - x - soffset - strw, y, highlightflags, str); - - if (i == itemOn) - { - V_DrawCharacter(BASEVIDWIDTH - x - soffset - 10 - strw - (skullAnimCounter/5), y, - '\x1C' | highlightflags, false); // left arrow - V_DrawCharacter(BASEVIDWIDTH - x - soffset + 2 + (skullAnimCounter/5), y, - '\x1D' | highlightflags, false); // right arrow - } - } - } - else if ((currentMenu->menuitems[i].status & IT_TYPE) == IT_KEYHANDLER && cv_dummystaff.value) // bad hacky assumption: IT_KEYHANDLER is assumed to be staff ghost selector - { - INT32 strw = V_StringWidth(dummystaffname, V_ALLOWLOWERCASE); - V_DrawString(BASEVIDWIDTH - x - strw, y, highlightflags|V_ALLOWLOWERCASE, dummystaffname); - if (i == itemOn) - { - V_DrawCharacter(BASEVIDWIDTH - x - 10 - strw - (skullAnimCounter/5), y, - '\x1C' | highlightflags, false); // left arrow - V_DrawCharacter(BASEVIDWIDTH - x + 2 + (skullAnimCounter/5), y, - '\x1D' | highlightflags, false); // right arrow - } - } - } - - x = currentMenu->x; - y = currentMenu->y; - - // DRAW THE SKULL CURSOR - V_DrawScaledPatch(x - 24, cursory, 0, W_CachePatchName("M_CURSOR", PU_CACHE)); - V_DrawString(x, cursory, highlightflags, currentMenu->menuitems[itemOn].text); - - // Level record list - if (cv_nextmap.value) - { - INT32 dupadjust = (vid.width/vid.dupx); - tic_t lap = 0, time = 0; - if (mainrecords[cv_nextmap.value-1]) - { - lap = mainrecords[cv_nextmap.value-1]->lap; - time = mainrecords[cv_nextmap.value-1]->time; - } - - V_DrawFill((BASEVIDWIDTH - dupadjust)>>1, 78, dupadjust, 36, 159); - - if (levellistmode != LLM_BREAKTHECAPSULES) - { - V_DrawRightAlignedString(149, 80, highlightflags, "BEST LAP:"); - K_drawKartTimestamp(lap, 19, 86, 0, 2); - } - - V_DrawRightAlignedString(292, 80, highlightflags, "BEST TIME:"); - K_drawKartTimestamp(time, 162, 86, cv_nextmap.value, 1); - } - /*{ - char beststr[40]; - emblem_t *em; - - if (!mainrecords[cv_nextmap.value-1] || !mainrecords[cv_nextmap.value-1]->time) - sprintf(beststr, "(none)"); - else - sprintf(beststr, "%i:%02i.%02i", G_TicsToMinutes(mainrecords[cv_nextmap.value-1]->time, true), - G_TicsToSeconds(mainrecords[cv_nextmap.value-1]->time), - G_TicsToCentiseconds(mainrecords[cv_nextmap.value-1]->time)); - - V_DrawString(64, y+48, highlightflags, "BEST TIME:"); - V_DrawRightAlignedString(BASEVIDWIDTH - 64 - 24 - 8, y+48, V_ALLOWLOWERCASE, beststr); - - if (!mainrecords[cv_nextmap.value-1] || !mainrecords[cv_nextmap.value-1]->lap) - sprintf(beststr, "(none)"); - else - sprintf(beststr, "%i:%02i.%02i", G_TicsToMinutes(mainrecords[cv_nextmap.value-1]->lap, true), - G_TicsToSeconds(mainrecords[cv_nextmap.value-1]->lap), - G_TicsToCentiseconds(mainrecords[cv_nextmap.value-1]->lap)); - - V_DrawString(64, y+56, highlightflags, "BEST LAP:"); - V_DrawRightAlignedString(BASEVIDWIDTH - 64 - 24 - 8, y+56, V_ALLOWLOWERCASE, beststr); - - // Draw record emblems. - em = M_GetLevelEmblems(cv_nextmap.value); - while (em) - { - switch (em->type) - { - case ET_TIME: break; - default: - goto skipThisOne; - } - - if (em->collected) - V_DrawMappedPatch(BASEVIDWIDTH - 64 - 24, y+48, 0, W_CachePatchName(M_GetEmblemPatch(em, false), PU_CACHE), - R_GetTranslationColormap(TC_DEFAULT, M_GetEmblemColor(em), GTC_MENUCACHE)); - else - V_DrawScaledPatch(BASEVIDWIDTH - 64 - 24, y+48, 0, W_CachePatchName("NEEDIT", PU_CACHE)); - - skipThisOne: - em = M_GetLevelEmblems(-1); - } - }*/ - - // ALWAYS DRAW player name, level name, skin and color even when not on this menu! - if (currentMenu != &SP_TimeAttackDef) - { - consvar_t *ncv; - - for (i = 0; i < 4; ++i) - { - y = currentMenu->y+SP_TimeAttackMenu[i].alphaKey; - V_DrawString(x, y, V_TRANSLUCENT, SP_TimeAttackMenu[i].text); - ncv = SP_TimeAttackMenu[i].itemaction.cvar; - if (SP_TimeAttackMenu[i].status & IT_CV_STRING) - { - M_DrawTextBox(x + 32, y - 8, MAXPLAYERNAME, 1); - V_DrawString(x + 40, y, V_TRANSLUCENT|V_ALLOWLOWERCASE, ncv->string); - } - else - { - const char *str = ((ncv == &cv_chooseskin) ? skins[cv_chooseskin.value-1].realname : ncv->string); - INT32 soffset = 40, strw = V_StringWidth(str, 0); - - // hack to keep the menu from overlapping the level icon - if (ncv == &cv_nextmap) - soffset = 0; - - // Should see nothing but strings - V_DrawString(BASEVIDWIDTH - x - soffset - strw, y, highlightflags|V_TRANSLUCENT, str); - } - } - } -} - -// Going to Time Attack menu... -static void M_TimeAttack(INT32 choice) -{ - (void)choice; - - memset(skins_cons_t, 0, sizeof (skins_cons_t)); - - levellistmode = LLM_TIMEATTACK; // Don't be dependent on cv_newgametype - - if (M_CountLevelsToShowInList() == 0) - { - M_StartMessage(M_GetText("No levels found for Time Attack.\n"),NULL,MM_NOTHING); - return; - } - - M_PatchSkinNameTable(); - - M_PrepareLevelSelect(); - M_SetupNextMenu(&SP_TimeAttackDef); - - G_SetGamestate(GS_TIMEATTACK); - titlemapinaction = TITLEMAP_OFF; // Nope don't give us HOMs please - - if (cv_nextmap.value) - Nextmap_OnChange(); - else - CV_AddValue(&cv_nextmap, 1); - - itemOn = tastart; // "Start" is selected. - - S_ChangeMusicInternal("racent", true); -} - -// Same as above, but sets a different levellistmode. Should probably be merged... -static void M_BreakTheCapsules(INT32 choice) -{ - (void)choice; - - memset(skins_cons_t, 0, sizeof (skins_cons_t)); - - levellistmode = LLM_BREAKTHECAPSULES; // Don't be dependent on cv_newgametype - - if (M_CountLevelsToShowInList() == 0) - { - M_StartMessage(M_GetText("No levels found for Break the Capsules.\n"),NULL,MM_NOTHING); - return; - } - - M_PatchSkinNameTable(); - - M_PrepareLevelSelect(); - M_SetupNextMenu(&SP_TimeAttackDef); - - G_SetGamestate(GS_TIMEATTACK); - titlemapinaction = TITLEMAP_OFF; // Nope don't give us HOMs please - - if (cv_nextmap.value) - Nextmap_OnChange(); - else - CV_AddValue(&cv_nextmap, 1); - - itemOn = tastart; // "Start" is selected. - - S_ChangeMusicInternal("racent", true); -} - -static boolean M_QuitTimeAttackMenu(void) -{ - // you know what? always putting these in the buffer won't hurt anything. - COM_BufAddText(va("skin \"%s\"\n", cv_chooseskin.string)); - return true; -} - -// Player has selected the "START" from the time attack screen -static void M_ChooseTimeAttack(INT32 choice) -{ - char *gpath; - const size_t glen = strlen("media")+1+strlen("replay")+1+strlen(timeattackfolder)+1+strlen("MAPXX")+1; - char nameofdemo[256]; - (void)choice; - emeralds = 0; - M_ClearMenus(true); - modeattacking = (levellistmode == LLM_BREAKTHECAPSULES ? ATTACKING_CAPSULES : ATTACKING_TIME); - - gpath = va("%s"PATHSEP"media"PATHSEP"replay"PATHSEP"%s", - srb2home, timeattackfolder); - M_MkdirEach(gpath, M_PathParts(gpath) - 3, 0755); - - if ((gpath = malloc(glen)) == NULL) - I_Error("Out of memory for replay filepath\n"); - - sprintf(gpath,"media"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s", timeattackfolder, G_BuildMapName(cv_nextmap.value)); - snprintf(nameofdemo, sizeof nameofdemo, "%s-%s-last", gpath, cv_chooseskin.string); - - if (!cv_autorecord.value) - remove(va("%s"PATHSEP"%s.lmp", srb2home, nameofdemo)); - else - G_RecordDemo(nameofdemo); - - G_DeferedInitNew(false, G_BuildMapName(cv_nextmap.value), (UINT8)(cv_chooseskin.value-1), 0, false); -} - -static void M_HandleStaffReplay(INT32 choice) -{ - boolean exitmenu = false; // exit to previous menu - lumpnum_t l = W_CheckNumForName(va("%sS%02u",G_BuildMapName(cv_nextmap.value),cv_dummystaff.value)); - - switch (choice) - { - case KEY_DOWNARROW: - M_NextOpt(); - S_StartSound(NULL, sfx_menu1); - break; - case KEY_UPARROW: - M_PrevOpt(); - S_StartSound(NULL, sfx_menu1); - break; - case KEY_BACKSPACE: - case KEY_ESCAPE: - exitmenu = true; - break; - case KEY_RIGHTARROW: - CV_AddValue(&cv_dummystaff, 1); - S_StartSound(NULL, sfx_menu1); - break; - case KEY_LEFTARROW: - CV_AddValue(&cv_dummystaff, -1); - S_StartSound(NULL, sfx_menu1); - break; - case KEY_ENTER: - if (l == LUMPERROR) - break; - M_ClearMenus(true); - modeattacking = (levellistmode == LLM_BREAKTHECAPSULES ? ATTACKING_CAPSULES : ATTACKING_TIME); - demo.loadfiles = false; demo.ignorefiles = true; // Just assume that record attack replays have the files needed - G_DoPlayDemo(va("%sS%02u",G_BuildMapName(cv_nextmap.value),cv_dummystaff.value)); - break; - default: - break; - } - if (exitmenu) - { - if (currentMenu->prevMenu) - M_SetupNextMenu(currentMenu->prevMenu); - else - M_ClearMenus(true); - } -} - -// Player has selected the "REPLAY" from the time attack screen -static void M_ReplayTimeAttack(INT32 choice) -{ - const char *which; - M_ClearMenus(true); - modeattacking = (levellistmode == LLM_BREAKTHECAPSULES ? ATTACKING_CAPSULES : ATTACKING_TIME); // set modeattacking before G_DoPlayDemo so the map loader knows - demo.loadfiles = false; demo.ignorefiles = true; // Just assume that record attack replays have the files needed - - if (currentMenu == &SP_ReplayDef) - { - switch(choice) { - default: - case 0: // best time - which = "time-best"; - break; - case 1: // best lap - which = "lap-best"; - break; - case 2: // last - which = "last"; - break; - case 3: // guest - // srb2/replay/main/map01-guest.lmp - G_DoPlayDemo(va("%s"PATHSEP"media"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s-guest.lmp", srb2home, timeattackfolder, G_BuildMapName(cv_nextmap.value))); - return; - } - // srb2/replay/main/map01-sonic-time-best.lmp - G_DoPlayDemo(va("%s"PATHSEP"media"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s-%s-%s.lmp", srb2home, timeattackfolder, G_BuildMapName(cv_nextmap.value), cv_chooseskin.string, which)); - } - /*else if (currentMenu == &SP_NightsReplayDef) - { - switch(choice) { - default: - case 0: // best score - which = "score-best"; - break; - case 1: // best time - which = "time-best"; - break; - case 2: // last - which = "last"; - break; - case 3: // staff - return; // M_HandleStaffReplay - case 4: // guest - which = "guest"; - break; - } - // srb2/replay/main/map01-score-best.lmp - G_DoPlayDemo(va("%s"PATHSEP"media"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s-%s.lmp", srb2home, timeattackfolder, G_BuildMapName(cv_nextmap.value), which)); - }*/ -} - -static void M_EraseGuest(INT32 choice) -{ - const char *rguest = va("%s"PATHSEP"media"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s-guest.lmp", srb2home, timeattackfolder, G_BuildMapName(cv_nextmap.value)); - (void)choice; - if (FIL_FileExists(rguest)) - remove(rguest); - /*if (currentMenu == &SP_NightsGuestReplayDef) - M_SetupNextMenu(&SP_NightsAttackDef); - else*/ - M_SetupNextMenu(&SP_TimeAttackDef); - CV_AddValue(&cv_nextmap, -1); - CV_AddValue(&cv_nextmap, 1); - M_StartMessage(M_GetText("Guest replay data erased.\n"),NULL,MM_NOTHING); -} - -static void M_OverwriteGuest(const char *which) -{ - char *rguest = Z_StrDup(va("%s"PATHSEP"media"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s-guest.lmp", srb2home, timeattackfolder, G_BuildMapName(cv_nextmap.value))); - UINT8 *buf; - size_t len; - len = FIL_ReadFile(va("%s"PATHSEP"media"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s-%s-%s.lmp", srb2home, timeattackfolder, G_BuildMapName(cv_nextmap.value), cv_chooseskin.string, which), &buf); - if (!len) { - return; - } - if (FIL_FileExists(rguest)) { - M_StopMessage(0); - remove(rguest); - } - FIL_WriteFile(rguest, buf, len); - Z_Free(rguest); - /*if (currentMenu == &SP_NightsGuestReplayDef) - M_SetupNextMenu(&SP_NightsAttackDef); - else*/ - M_SetupNextMenu(&SP_TimeAttackDef); - CV_AddValue(&cv_nextmap, -1); - CV_AddValue(&cv_nextmap, 1); - M_StartMessage(M_GetText("Guest replay data saved.\n"),NULL,MM_NOTHING); -} - -static void M_OverwriteGuest_Time(INT32 choice) -{ - (void)choice; - M_OverwriteGuest("time-best"); -} - -static void M_OverwriteGuest_Lap(INT32 choice) -{ - (void)choice; - M_OverwriteGuest("lap-best"); -} - -/* SRB2Kart -static void M_OverwriteGuest_Score(INT32 choice) -{ - (void)choice; - M_OverwriteGuest("score-best"); -} - -static void M_OverwriteGuest_Rings(INT32 choice) -{ - (void)choice; - M_OverwriteGuest("rings-best"); -}*/ - -static void M_OverwriteGuest_Last(INT32 choice) -{ - (void)choice; - M_OverwriteGuest("last"); -} - -static void M_SetGuestReplay(INT32 choice) -{ - void (*which)(INT32); - switch(choice) - { - case 0: // best time - which = M_OverwriteGuest_Time; - break; - case 1: // best lap - which = M_OverwriteGuest_Lap; - break; - case 2: // last - which = M_OverwriteGuest_Last; - break; - case 3: // guest - default: - M_StartMessage(M_GetText("Are you sure you want to\ndelete the guest replay data?\n\n(Press 'Y' to confirm)\n"), FUNCPTRCAST(M_EraseGuest), MM_YESNO); - return; - } - if (FIL_FileExists(va("%s"PATHSEP"media"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s-guest.lmp", srb2home, timeattackfolder, G_BuildMapName(cv_nextmap.value)))) - M_StartMessage(M_GetText("Are you sure you want to\noverwrite the guest replay data?\n\n(Press 'Y' to confirm)\n"), FUNCPTRCAST(which), MM_YESNO); - else - which(0); -} - -void M_ModeAttackRetry(INT32 choice) -{ - (void)choice; - G_CheckDemoStatus(); // Cancel recording - if (modeattacking) - M_ChooseTimeAttack(0); -} - -static void M_ModeAttackEndGame(INT32 choice) -{ - (void)choice; - G_CheckDemoStatus(); // Cancel recording - - if (gamestate == GS_LEVEL || gamestate == GS_INTERMISSION || gamestate == GS_VOTING) - Command_ExitGame_f(); - - M_StartControlPanel(); - - if (modeattacking) - currentMenu = &SP_TimeAttackDef; - - itemOn = currentMenu->lastOn; - G_SetGamestate(GS_TIMEATTACK); - modeattacking = ATTACKING_NONE; - S_ChangeMusicInternal("racent", true); - // Update replay availability. - CV_AddValue(&cv_nextmap, 1); - CV_AddValue(&cv_nextmap, -1); -} - -// ======== -// END GAME -// ======== - -static void M_ExitGameResponse(INT32 ch) -{ - if (ch != 'y' && ch != KEY_ENTER) - return; - - //Command_ExitGame_f(); - G_SetExitGameFlag(); - M_ClearMenus(true); -} - -static void M_EndGame(INT32 choice) -{ - (void)choice; - if (demo.playback) - return; - - if (!Playing()) - return; - - M_StartMessage(M_GetText("Are you sure you want to end the game?\n\n(Press 'Y' to confirm)\n"), FUNCPTRCAST(M_ExitGameResponse), MM_YESNO); -} - -//=========================================================================== -// Connect Menu -//=========================================================================== - -void -M_SetWaitingMode (int mode) -{ -#ifdef HAVE_THREADS - I_lock_mutex(&m_menu_mutex); -#endif - { - m_waiting_mode = mode; - } -#ifdef HAVE_THREADS - I_unlock_mutex(m_menu_mutex); -#endif -} - -int -M_GetWaitingMode (void) -{ - int mode; - -#ifdef HAVE_THREADS - I_lock_mutex(&m_menu_mutex); -#endif - { - mode = m_waiting_mode; - } -#ifdef HAVE_THREADS - I_unlock_mutex(m_menu_mutex); -#endif - - return mode; -} - -#ifdef MASTERSERVER -#ifdef HAVE_THREADS -static 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); -} - -static 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*/ - -static 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*/ - -#define SERVERHEADERHEIGHT 36 -#define SERVERLINEHEIGHT 12 - -#define S_LINEY(n) currentMenu->y + SERVERHEADERHEIGHT + (n * SERVERLINEHEIGHT) - -#ifndef NONET -static UINT32 localservercount; - -static void M_HandleServerPage(INT32 choice) -{ - boolean exitmenu = false; // exit to previous menu - - switch (choice) - { - case KEY_DOWNARROW: - M_NextOpt(); - S_StartSound(NULL, sfx_menu1); - break; - case KEY_UPARROW: - M_PrevOpt(); - S_StartSound(NULL, sfx_menu1); - break; - case KEY_BACKSPACE: - case KEY_ESCAPE: - exitmenu = true; - break; - - case KEY_ENTER: - case KEY_RIGHTARROW: - S_StartSound(NULL, sfx_menu1); - if ((serverlistpage + 1) * SERVERS_PER_PAGE < serverlistcount) - serverlistpage++; - break; - case KEY_LEFTARROW: - S_StartSound(NULL, sfx_menu1); - if (serverlistpage > 0) - serverlistpage--; - break; - - default: - break; - } - if (exitmenu) - { - if (currentMenu->prevMenu) - M_SetupNextMenu(currentMenu->prevMenu); - else - M_ClearMenus(true); - } -} - -static void M_Connect(INT32 choice) -{ - // do not call menuexitfunc - M_ClearMenus(false); - - COM_BufAddText(va("connect node %d\n", serverlist[choice-FIRSTSERVERLINE + serverlistpage * SERVERS_PER_PAGE].node)); -} - -static void M_Refresh(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 - - // first page of servers - serverlistpage = 0; - -#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*/ -} - -static void M_DrawConnectMenu(void) -{ - UINT16 i; - //const char *gt = "Unknown"; - //const char *spd = ""; - const char *pwr = "----"; - INT32 numPages = (serverlistcount+(SERVERS_PER_PAGE-1))/SERVERS_PER_PAGE; - int waiting; - - for (i = FIRSTSERVERLINE; i < min(localservercount, SERVERS_PER_PAGE)+FIRSTSERVERLINE; i++) - MP_ConnectMenu[i].status = IT_STRING | IT_SPACE; - - if (!numPages) - numPages = 1; - - // Page num - V_DrawRightAlignedString(BASEVIDWIDTH - currentMenu->x, currentMenu->y + MP_ConnectMenu[mp_connect_page].alphaKey, - highlightflags, va("%u of %d", serverlistpage+1, numPages)); - - // Horizontal line! - V_DrawFill(1, currentMenu->y+32, 318, 1, 0); - - if (serverlistcount <= 0) - V_DrawString(currentMenu->x,currentMenu->y+SERVERHEADERHEIGHT, 0, "No servers found"); - else - for (i = 0; i < min(serverlistcount - serverlistpage * SERVERS_PER_PAGE, SERVERS_PER_PAGE); i++) - { - INT32 slindex = i + serverlistpage * SERVERS_PER_PAGE; - UINT32 globalflags = ((serverlist[slindex].info.numberofplayer >= serverlist[slindex].info.maxplayer) ? V_TRANSLUCENT : 0) - |((itemOn == FIRSTSERVERLINE+i) ? highlightflags : 0)|V_ALLOWLOWERCASE; - - V_DrawString(currentMenu->x, S_LINEY(i), globalflags, serverlist[slindex].info.servername); - - V_DrawSmallString(currentMenu->x, S_LINEY(i)+8, globalflags, - va("Ping: %u", (UINT32)LONG(serverlist[slindex].info.time))); - - V_DrawSmallString(currentMenu->x+44,S_LINEY(i)+8, globalflags, - va("Players: %02d/%02d", serverlist[slindex].info.numberofplayer, serverlist[slindex].info.maxplayer)); - - V_DrawSmallString(currentMenu->x+108, S_LINEY(i)+8, globalflags, serverlist[slindex].info.gametypename); - - // display game speed for race gametypes - /* todo: send if the gametype is GTR_CIRCUIT, and uses game speed - if (serverlist[slindex].info.gametype == GT_RACE) - { - spd = kartspeed_cons_t[(serverlist[slindex].info.kartvars & SV_SPEEDMASK)+1].strvalue; - V_DrawSmallString(currentMenu->x+128, S_LINEY(i)+8, globalflags, va("(%s)", spd)); - } - */ - - pwr = "----"; - if (serverlist[slindex].info.avgpwrlv == -1) - pwr = "Off"; - else if (serverlist[slindex].info.avgpwrlv > 0) - pwr = va("%04d", serverlist[slindex].info.avgpwrlv); - V_DrawSmallString(currentMenu->x+171, S_LINEY(i)+8, globalflags, va("Power Level: %s", pwr)); - - // Don't use color flags intentionally, the global yellow color will auto override the text color code - if (serverlist[slindex].info.modifiedgame) - V_DrawSmallString(currentMenu->x+245, S_LINEY(i)+8, globalflags, "\x85" "Mod"); - if (serverlist[slindex].info.cheatsenabled) - V_DrawSmallString(currentMenu->x+265, S_LINEY(i)+8, globalflags, "\x83" "Cheats"); - - MP_ConnectMenu[i+FIRSTSERVERLINE].status = IT_STRING | IT_CALL; - } - - localservercount = serverlistcount; - - M_DrawGenericMenu(); - - waiting = M_GetWaitingMode(); - - if (waiting) - { - const char *message; - - if (waiting == M_WAITING_VERSION) - message = "Checking for updates..."; - else - message = "Searching for servers..."; - - // Display a little "please wait" message. - M_DrawTextBox(52, BASEVIDHEIGHT/2-10, 25, 3); - V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT/2, 0, message); - V_DrawCenteredString(BASEVIDWIDTH/2, (BASEVIDHEIGHT/2)+12, 0, "Please wait."); - } -} - -static boolean M_CancelConnect(void) -{ - D_CloseConnection(); - return true; -} - -// 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) - -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); \ -} - -// Special one for modified state. -static int ServerListEntryComparator_modified(const void *entry1, const void *entry2) -{ - const serverelem_t *sa = (const serverelem_t*)entry1, *sb = (const serverelem_t*)entry2; - - // Modified acts as 2 points, cheats act as one point. - int modstate_a = (sa->info.cheatsenabled ? 1 : 0) | (sa->info.modifiedgame ? 2 : 0); - int modstate_b = (sb->info.cheatsenabled ? 1 : 0) | (sb->info.modifiedgame ? 2 : 0); - - if (modstate_a != modstate_b) - return modstate_a - modstate_b; - - // Default to strcmp. - return strcmp(sa->info.servername, sb->info.servername); -} -#endif - -void M_SortServerList(void) -{ -#ifndef NONET - switch(cv_serversort.value) - { - case 0: // Ping. - qsort(serverlist, serverlistcount, sizeof(serverelem_t), ServerListEntryComparator_time); - break; - case 1: // Modified state. - qsort(serverlist, serverlistcount, sizeof(serverelem_t), ServerListEntryComparator_modified); - 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; - } -#endif -} - -#ifndef NONET -#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(&m_menu_mutex); -#endif - M_StartMessage(updatestring, NULL, MM_NOTHING); -#ifdef HAVE_THREADS - I_unlock_mutex(m_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)*/ - -static void M_ConnectMenu(INT32 choice) -{ - (void)choice; - // modified game check: no longer handled - // we don't request a restart unless the filelist differs - - // first page of servers - serverlistpage = 0; - M_SetupNextMenu(&MP_ConnectDef); - 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_Refresh(0); -#endif/*defined (MASTERSERVER) && defined (HAVE_THREADS)*/ -} - -static void M_ConnectMenuModChecks(INT32 choice) -{ - (void)choice; - // okay never mind we want to COMMUNICATE to the player pre-emptively instead of letting them try and then get confused when it doesn't work - - if (modifiedgame) - { - M_StartMessage(M_GetText("You have addons loaded.\nYou won't be able to join netgames!\n\nTo play online, restart the game\nand don't load any addons.\nRing Racers will automatically add\neverything you need when you join.\n\n(Press a key)\n"), FUNCPTRCAST(M_ConnectMenu), MM_EVENTHANDLER); - return; - } - - M_ConnectMenu(-1); -} -#endif //NONET - -//=========================================================================== -// Start Server Menu -//=========================================================================== - -// -// FindFirstMap -// -// Finds the first map of a particular gametype (or returns the current map) -// Defaults to 1 if nothing found. -// -static INT32 M_FindFirstMap(INT32 gtype) -{ - INT32 i; - - if (mapheaderinfo[gamemap] && (mapheaderinfo[gamemap]->typeoflevel & gametypetol[gtype])) - return gamemap; - - for (i = 0; i < NUMMAPS; i++) - { - if (!mapheaderinfo[i]) - continue; - if (!(mapheaderinfo[i]->typeoflevel & gametypetol[gtype])) - continue; - return i + 1; - } - - return 1; -} - -static void M_StartServer(INT32 choice) -{ - UINT8 ssplayers = cv_splitplayers.value-1; - - (void)choice; - - if (currentMenu == &MP_OfflineServerDef) - netgame = false; - else - netgame = true; - - multiplayer = true; - - strncpy(connectedservername, cv_servername.string, MAXSERVERNAME); - - // Still need to reset devmode - cv_debug = 0; - - if (demo.playback) - G_StopDemo(); - if (metalrecording) - G_StopMetalDemo(); - - if (!cv_nextmap.value) - CV_SetValue(&cv_nextmap, G_RandMap(G_TOLFlag(cv_newgametype.value), -1, 0, 0, false, NULL)+1); - - if (cv_maxplayers.value < ssplayers+1) - CV_SetValue(&cv_maxplayers, ssplayers+1); - - if (splitscreen != ssplayers) - { - splitscreen = ssplayers; - SplitScreen_OnChange(); - } - - if (currentMenu == &MP_OfflineServerDef) // offline server - { - paused = false; - SV_StartSinglePlayerServer(); - multiplayer = true; // yeah, SV_StartSinglePlayerServer clobbers this... - D_MapChange(cv_nextmap.value, cv_newgametype.value, (cv_kartencore.value == 1), 1, 1, false, false); - } - else - { - D_MapChange(cv_nextmap.value, cv_newgametype.value, (cv_kartencore.value == 1), 1, 1, false, false); - COM_BufAddText("dummyconsvar 1\n"); - } - - M_ClearMenus(true); -} - -static void M_DrawLevelSelectOnly(boolean leftfade, boolean rightfade) -{ - lumpnum_t lumpnum; - patch_t *PictureOfLevel; - INT32 x, y, w, i, oldval, trans, dupadjust = ((vid.width/vid.dupx) - BASEVIDWIDTH)>>1; - - // A 160x100 image of the level as entry MAPxxP - if (cv_nextmap.value) - { - lumpnum = W_CheckNumForName(va("%sP", G_BuildMapName(cv_nextmap.value))); - if (lumpnum != LUMPERROR) - PictureOfLevel = W_CachePatchNum(lumpnum, PU_CACHE); - else - PictureOfLevel = W_CachePatchName("BLANKLVL", PU_CACHE); - } - else - PictureOfLevel = W_CachePatchName("RANDOMLV", PU_CACHE); - - w = SHORT(PictureOfLevel->width)/2; - i = SHORT(PictureOfLevel->height)/2; - x = BASEVIDWIDTH/2 - w/2; - y = currentMenu->y + 130 + 8 - i; - - if (currentMenu->menuitems[itemOn].itemaction.cvar == &cv_nextmap && skullAnimCounter < 4) - trans = 0; - else - trans = G_GetGametypeColor(cv_newgametype.value); - - V_DrawFill(x-1, y-1, w+2, i+2, trans); // variable reuse... - - if ((cv_kartencore.value != 1) || gamestate == GS_TIMEATTACK || cv_newgametype.value != GT_RACE) - V_DrawSmallScaledPatch(x, y, 0, PictureOfLevel); - else - { - /*UINT8 *mappingforencore = NULL; - if ((lumpnum = W_CheckNumForName(va("%sE", mapname))) != LUMPERROR) - mappingforencore = W_CachePatchNum(lumpnum, PU_CACHE);*/ - - V_DrawFixedPatch((x+w)<>ANGLETOFINESHIFT); - V_DrawFixedPatch((x+w/2)< horizspac-dupadjust); - - x = (BASEVIDWIDTH + w)/2 + horizspac; - i = cv_nextmap.value - 1; - trans = (rightfade ? V_TRANSLUCENT : 0); - - while (x < BASEVIDWIDTH+dupadjust-horizspac) - { - oldval = i; - do - { - i++; - if (i == NUMMAPS) - i = -1; - - if (i == oldval) - return; - - if(!mapheaderinfo[i]) - continue; // Don't allocate the header. That just makes memory usage skyrocket. - - } while (!M_CanShowLevelInList(i, cv_newgametype.value)); - - // A 160x100 image of the level as entry MAPxxP - if (i+1) - { - lumpnum = W_CheckNumForName(va("%sP", G_BuildMapName(i+1))); - if (lumpnum != LUMPERROR) - PictureOfLevel = W_CachePatchNum(lumpnum, PU_CACHE); - else - PictureOfLevel = W_CachePatchName("BLANKLVL", PU_CACHE); - } - else - PictureOfLevel = W_CachePatchName("RANDOMLV", PU_CACHE); - - V_DrawTinyScaledPatch(x, y, trans, PictureOfLevel); - - x += horizspac + w/2; - } -#undef horizspac -} - -static void M_DrawServerMenu(void) -{ - M_DrawLevelSelectOnly(false, false); - M_DrawGenericMenu(); -} - -static void M_MapChange(INT32 choice) -{ - (void)choice; - - levellistmode = LLM_CREATESERVER; - - CV_SetValue(&cv_newgametype, gametype); - CV_SetValue(&cv_nextmap, gamemap); - - M_PrepareLevelSelect(); - M_SetupNextMenu(&MISC_ChangeLevelDef); -} - -#ifndef TESTERS -static void M_StartOfflineServerMenu(INT32 choice) -{ - (void)choice; - levellistmode = LLM_CREATESERVER; - M_PrepareLevelSelect(); - M_SetupNextMenu(&MP_OfflineServerDef); -} -#endif - -#ifndef NONET -#ifndef TESTERS -static void M_StartServerMenu(INT32 choice) -{ - (void)choice; - levellistmode = LLM_CREATESERVER; - M_PrepareLevelSelect(); - M_SetupNextMenu(&MP_ServerDef); - -} -#endif - -// ============== -// CONNECT VIA IP -// ============== - -static char setupm_ip[28]; -#endif -static UINT8 setupm_pselect = 1; - -// Draw the funky Connect IP menu. Tails 11-19-2002 -// So much work for such a little thing! -static void M_DrawMPMainMenu(void) -{ - INT32 x = currentMenu->x; - INT32 y = currentMenu->y; - - // use generic drawer for cursor, items and title - M_DrawGenericMenu(); - -#ifndef NOMENUHOST -#if MAXPLAYERS != 16 -Update the maxplayers label... -#endif - V_DrawRightAlignedString(BASEVIDWIDTH-x, y+MP_MainMenu[4].alphaKey, - ((itemOn == 4) ? highlightflags : 0), "(2-16 players)"); -#endif - -#ifndef TESTERS - V_DrawRightAlignedString(BASEVIDWIDTH-x, y+MP_MainMenu[5].alphaKey, - ((itemOn == 5) ? highlightflags : 0), - "(2-4 players)" - ); -#endif - -#ifndef NONET - y += MP_MainMenu[8].alphaKey; - - V_DrawFill(x+5, y+4+5, /*16*8 + 6,*/ BASEVIDWIDTH - 2*(x+5), 8+6, 159); - - // draw name string - V_DrawString(x+8,y+12, V_ALLOWLOWERCASE, setupm_ip); - - // draw text cursor for name - if (itemOn == 8 - && skullAnimCounter < 4) //blink cursor - V_DrawCharacter(x+8+V_StringWidth(setupm_ip, V_ALLOWLOWERCASE),y+12,'_',false); -#endif - - // character bar, ripped off the color bar :V - { -#define iconwidth 32 -#define spacingwidth 32 -#define incrwidth (iconwidth + spacingwidth) - UINT8 i = 0, pskin, pcol; - // player arrangement width, but there's also a chance i'm a furry, shhhhhh - const INT32 paw = iconwidth + 3*incrwidth; - INT32 trans = 0; - UINT8 *colmap; - x = BASEVIDWIDTH/2 - paw/2; - y = currentMenu->y + 32; - - while (++i <= 4) - { - pskin = R_SkinAvailable(cv_skin[i-1].string); - pcol = cv_playercolor[i-1].value; - - if (pskin >= MAXSKINS) - pskin = 0; - - if (!trans && i > cv_splitplayers.value) - trans = V_TRANSLUCENT; - - colmap = R_GetTranslationColormap(pskin, pcol, GTC_MENUCACHE); - - V_DrawFixedPatch(x< 7 * FRACUNIT; cursorframe -= 7 * FRACUNIT) {} - - V_DrawFixedPatch(x<> FRACBITS) + 1), PU_CACHE), NULL); - } - - x += incrwidth; - } -#undef incrwidth -#undef spacingwidth -#undef iconwidth - } -} - -static void Splitplayers_OnChange(void) -{ - if (cv_splitplayers.value < setupm_pselect) - setupm_pselect = 1; -} - -static void M_SetupMultiHandler(INT32 choice) -{ - boolean exitmenu = false; // exit to previous menu and send name change - - switch (choice) - { - case KEY_LEFTARROW: - if (cv_splitplayers.value > 1) - { - if (--setupm_pselect < 1) - setupm_pselect = cv_splitplayers.value; - S_StartSound(NULL,sfx_menu1); // Tails - } - break; - - case KEY_RIGHTARROW: - if (cv_splitplayers.value > 1) - { - if (++setupm_pselect > cv_splitplayers.value) - setupm_pselect = 1; - S_StartSound(NULL,sfx_menu1); // Tails - } - break; - - case KEY_DOWNARROW: - M_NextOpt(); - S_StartSound(NULL,sfx_menu1); // Tails - break; - - case KEY_UPARROW: - M_PrevOpt(); - S_StartSound(NULL,sfx_menu1); // Tails - break; - - case KEY_ENTER: - { - S_StartSound(NULL,sfx_menu1); // Tails - currentMenu->lastOn = itemOn; - switch (setupm_pselect) - { - case 2: - M_SetupMultiPlayer2(0); - return; - case 3: - M_SetupMultiPlayer3(0); - return; - case 4: - M_SetupMultiPlayer4(0); - return; - default: - M_SetupMultiPlayer(0); - return; - } - break; - } - - case KEY_ESCAPE: - exitmenu = true; - break; - } - - if (exitmenu) - { - if (currentMenu->prevMenu) - M_SetupNextMenu (currentMenu->prevMenu); - else - M_ClearMenus(true); - } -} - -#ifndef NONET - -// Tails 11-19-2002 -static void M_ConnectIP(INT32 choice) -{ - (void)choice; - - if (*setupm_ip == 0) - { - M_StartMessage("You must specify an IP address.\n", NULL, MM_NOTHING); - return; - } - - M_ClearMenus(true); - - COM_BufAddText(va("connect \"%s\"\n", setupm_ip)); - - // 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 -} - -// Tails 11-19-2002 -static void M_HandleConnectIP(INT32 choice) -{ - size_t l; - boolean exitmenu = false; // exit to previous menu and send name change - - switch (choice) - { - case KEY_DOWNARROW: - M_NextOpt(); - S_StartSound(NULL,sfx_menu1); // Tails - break; - - case KEY_UPARROW: - M_PrevOpt(); - S_StartSound(NULL,sfx_menu1); // Tails - break; - - case KEY_ENTER: - S_StartSound(NULL,sfx_menu1); // Tails - currentMenu->lastOn = itemOn; - M_ConnectIP(1); - break; - - case KEY_ESCAPE: - exitmenu = true; - break; - - case KEY_BACKSPACE: - if ((l = strlen(setupm_ip)) != 0) - { - S_StartSound(NULL,sfx_menu1); // Tails - setupm_ip[l-1] = 0; - } - break; - - case KEY_DEL: - if (setupm_ip[0]) - { - S_StartSound(NULL,sfx_menu1); // Tails - setupm_ip[0] = 0; - } - break; - - default: - l = strlen(setupm_ip); - if (l >= 28-1) - break; - - // Rudimentary number and period enforcing - also allows letters so hostnames can be used instead - if ((choice >= '-' && choice <= ':') || (choice >= 'A' && choice <= 'Z') || (choice >= 'a' && choice <= 'z')) - { - S_StartSound(NULL,sfx_menu1); // Tails - setupm_ip[l] = (char)choice; - setupm_ip[l+1] = 0; - } - else if (choice >= 199 && choice <= 211 && choice != 202 && choice != 206) //numpad too! - { - char keypad_translation[] = {'7','8','9','-','4','5','6','+','1','2','3','0','.'}; - choice = keypad_translation[choice - 199]; - S_StartSound(NULL,sfx_menu1); // Tails - setupm_ip[l] = (char)choice; - setupm_ip[l+1] = 0; - } - break; - } - - if (exitmenu) - { - if (currentMenu->prevMenu) - M_SetupNextMenu (currentMenu->prevMenu); - else - M_ClearMenus(true); - } -} -#endif //!NONET - -// ======================== -// MULTIPLAYER PLAYER SETUP -// ======================== -// Tails 03-02-2002 - -// used for skin display on player setup menu -static fixed_t multi_tics; -static state_t *multi_state; -static UINT8 multi_spr2; - -// used for follower display on player setup menu -static INT32 follower_tics; -static UINT32 follower_frame; // used for FF_ANIMATE garbo -static state_t *follower_state; - -// this is set before entering the MultiPlayer setup menu, -// for either player 1 or 2 -static char setupm_name[MAXPLAYERNAME+1]; -static player_t *setupm_player; -static consvar_t *setupm_cvskin; -static consvar_t *setupm_cvcolor; -static consvar_t *setupm_cvname; -static consvar_t *setupm_cvfollower; -static INT32 setupm_fakeskin; -static menucolor_t *setupm_fakecolor; -static INT32 setupm_fakefollower; // -1 is for none, our followers start at 0 - -static void M_DrawSetupMultiPlayerMenu(void) -{ - INT32 mx, my, st, flags = 0; - spritedef_t *sprdef; - spriteframe_t *sprframe; - patch_t *statbg = W_CachePatchName("K_STATBG", PU_CACHE); - patch_t *statlr = W_CachePatchName("K_STATLR", PU_CACHE); - patch_t *statud = W_CachePatchName("K_STATUD", PU_CACHE); - patch_t *statdot = W_CachePatchName("K_SDOT0", PU_CACHE); - patch_t *patch; - UINT8 frame; - UINT8 speed; - UINT8 weight; - const UINT8 *flashcol = V_GetStringColormap(highlightflags); - INT32 statx, staty; - char *fname; - INT16 i; - - mx = MP_PlayerSetupDef.x; - my = MP_PlayerSetupDef.y; - - statx = (BASEVIDWIDTH - mx - 118); - staty = (my+62); - - // use generic drawer for cursor, items and title - M_DrawGenericMenu(); - - // draw name string - M_DrawTextBox(mx + 32, my - 8, MAXPLAYERNAME, 1); - V_DrawString(mx + 40, my, V_ALLOWLOWERCASE, setupm_name); - - // draw text cursor for name - if (!itemOn && skullAnimCounter < 4) // blink cursor - V_DrawCharacter(mx + 40 + V_StringWidth(setupm_name, V_ALLOWLOWERCASE), my, '_',false); - - // draw skin string - st = V_StringWidth(skins[setupm_fakeskin].realname, 0); - V_DrawString(BASEVIDWIDTH - mx - st, my + 16, - ((MP_PlayerSetupMenu[2].status & IT_TYPE) == IT_SPACE ? V_TRANSLUCENT : 0)|highlightflags|V_ALLOWLOWERCASE, - skins[setupm_fakeskin].realname); - if (itemOn == 1) - { - V_DrawCharacter(BASEVIDWIDTH - mx - 10 - st - (skullAnimCounter/5), my + 16, - '\x1C' | highlightflags, false); // left arrow - V_DrawCharacter(BASEVIDWIDTH - mx + 2 + (skullAnimCounter/5), my + 16, - '\x1D' | highlightflags, false); // right arrow - } - - // draw follower string - fname = malloc(SKINNAMESIZE+1); - - if (setupm_fakefollower == -1) - strcpy(fname, "None"); - else - strcpy(fname, followers[setupm_fakefollower].name); - - st = V_StringWidth(fname, 0); - V_DrawString(BASEVIDWIDTH - mx - st, my + 26, - ((MP_PlayerSetupMenu[2].status & IT_TYPE) == IT_SPACE ? V_TRANSLUCENT : 0)|highlightflags|V_ALLOWLOWERCASE, - fname); - if (itemOn == 2) - { - V_DrawCharacter(BASEVIDWIDTH - mx - 10 - st - (skullAnimCounter/5), my + 26, - '\x1C' | highlightflags, false); // left arrow - V_DrawCharacter(BASEVIDWIDTH - mx + 2 + (skullAnimCounter/5), my + 26, - '\x1D' | highlightflags, false); // right arrow - } - - // draw the name of the color you have chosen - // Just so people don't go thinking that "Default" is Green. - st = V_StringWidth(skincolors[setupm_fakecolor->color].name, 0); - V_DrawString(BASEVIDWIDTH - mx - st, my + 152, highlightflags|V_ALLOWLOWERCASE, skincolors[setupm_fakecolor->color].name); // SRB2kart - if (itemOn == 3) - { - V_DrawCharacter(BASEVIDWIDTH - mx - 10 - st - (skullAnimCounter/5), my + 152, - '\x1C' | highlightflags, false); // left arrow - V_DrawCharacter(BASEVIDWIDTH - mx + 2 + (skullAnimCounter/5), my + 152, - '\x1D' | highlightflags, false); // right arrow - } - - // SRB2Kart: draw the stat backer - // labels - V_DrawThinString(statx+16, staty, V_6WIDTHSPACE|highlightflags, "Acceleration"); - V_DrawThinString(statx+91, staty, V_6WIDTHSPACE|highlightflags, "Max Speed"); - V_DrawThinString(statx, staty+12, V_6WIDTHSPACE|highlightflags, "Handling"); - V_DrawThinString(statx+7, staty+77, V_6WIDTHSPACE|highlightflags, "Weight"); - // label arrows - V_DrawFixedPatch((statx+64)<color) - V_DrawFixedPatch(((BASEVIDWIDTH - mx - 80) + ((speed-1)*8))<color, GTC_MENUCACHE)); - - // 2.2 color bar backported with permission -#define charw 72 -#define indexwidth 8 - { - const INT32 numcolors = (250-charw)/(2*indexwidth); // Number of colors per side - INT32 x = mx; - INT32 w = indexwidth; // Width of a singular color block - menucolor_t *mc = setupm_fakecolor->prev; // Last accessed color - UINT8 h; - - // Draw color in the middle - x += numcolors*w; - for (h = 0; h < 16; h++) - V_DrawFill(x, my+162+h, charw, 1, skincolors[setupm_fakecolor->color].ramp[h]); - - //Draw colors from middle to left - for (i=0; icolor].accessible) - mc = mc->prev; - for (h = 0; h < 16; h++) - V_DrawFill(x, my+162+h, w, 1, skincolors[mc->color].ramp[h]); - mc = mc->prev; - } - - // Draw colors from middle to right - mc = setupm_fakecolor->next; - x += numcolors*w + charw; - for (i=0; icolor].accessible) - mc = mc->next; - for (h = 0; h < 16; h++) - V_DrawFill(x, my+162+h, w, 1, skincolors[mc->color].ramp[h]); - x += w; - mc = mc->next; - } - } -#undef indexwidth - - // character bar, ripped off the color bar :V - if (setupm_fakecolor->color) // inverse should never happen -#define iconwidth 32 - { - const INT32 icons = 4; - INT32 k = -icons; - INT16 col = (setupm_fakeskin - icons) % numskins; - INT32 x = BASEVIDWIDTH/2 - ((icons+1)*24) - 4; - fixed_t scale = FRACUNIT/2; - INT32 offx = 8, offy = 8; - patch_t *cursor; - static fixed_t cursorframe = 0; - patch_t *face; - UINT8 *colmap; - - cursorframe += renderdeltatics / 4; - for (; cursorframe > 7 * FRACUNIT; cursorframe -= 7 * FRACUNIT) {} - - cursor = W_CachePatchName(va("K_BHILI%d", (cursorframe >> FRACBITS) + 1), PU_CACHE); - - if (col < 0) - col += numskins; - while (k <= icons) - { - if (!(k++)) - { - scale = FRACUNIT; - face = faceprefix[col][FACE_WANTED]; - offx = 12; - offy = 0; - } - else - { - scale = FRACUNIT/2; - face = faceprefix[col][FACE_RANK]; - offx = 8; - offy = 8; - } - colmap = R_GetTranslationColormap(col, setupm_fakecolor->color, GTC_MENUCACHE); - if (face) - V_DrawFixedPatch((x+offx)<= numskins) - col -= numskins; - x += FixedMul(iconwidth<nextstate; - if (st != S_NULL) - multi_state = &states[st]; - - if (multi_state->tics <= -1) - multi_tics += 15*FRACUNIT; - else - multi_tics += multi_state->tics * FRACUNIT; - } - - // skin 0 is default player sprite - sprdef = &skins[setupm_fakeskin].sprites[multi_spr2]; - - if (!sprdef->numframes) // No frames ?? - return; // Can't render! - - frame = multi_state->frame & FF_FRAMEMASK; - if (frame >= sprdef->numframes) // Walking animation missing - frame = 0; // Try to use standing frame - - sprframe = &sprdef->spriteframes[frame]; - patch = W_CachePatchNum(sprframe->lumppat[1], PU_CACHE); - if (sprframe->flip & 2) // Only for first sprite - flags |= V_FLIP; // This sprite is left/right flipped! - - // draw box around guy - V_DrawFill(mx + 43 - (charw/2), my+65, charw, 84, 159); - - // draw player sprite - if (setupm_fakecolor->color) // inverse should never happen - { - UINT8 *colormap = R_GetTranslationColormap(setupm_fakeskin, setupm_fakecolor->color, GTC_MENUCACHE); - - if (skins[setupm_fakeskin].flags & SF_HIRES) - { - V_DrawFixedPatch((mx+43)< -1 && setupm_fakefollower < numfollowers) - { - // animate the follower - - if (--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 (follower_state->frame & FF_ANIMATE) - { - follower_frame++; - follower_tics = follower_state->var2; - if (follower_frame > (follower_state->frame & FF_FRAMEMASK) + follower_state->var1) // that's how it works, right? - follower_frame = follower_state->frame & FF_FRAMEMASK; - } - else - { - st = follower_state->nextstate; - if (st != S_NULL) - follower_state = &states[st]; - follower_tics = follower_state->tics; - if (follower_tics == -1) - follower_tics = 15; // er, what? - // get spritedef: - follower_frame = follower_state->frame & FF_FRAMEMASK; - } - } - sprdef = &sprites[follower_state->sprite]; - - // draw the follower - - if (follower_frame >= sprdef->numframes) - follower_frame = 0; // frame doesn't exist, we went beyond it... what? - sprframe = &sprdef->spriteframes[follower_frame]; - patch = W_CachePatchNum(sprframe->lumppat[1], PU_CACHE); - if (sprframe->flip & 2) // Only for first sprite - flags |= V_FLIP; // This sprite is left/right flipped! - - // @TODO: Reminder that followers on the menu right now do NOT support the 'followercolor' command, considering this whole menu is getting remade anyway, I see no point in incorporating it in right now. - - // draw follower sprite - if (setupm_fakecolor->color) // inverse should never happen - { - - // Fake the follower's in game appearance by now also applying some of its variables! coolio, eh? - follower_t fl = followers[setupm_fakefollower]; // shortcut for our sanity - - // smooth floating, totally not stolen from rocket sneakers. - fixed_t sine = FixedMul(fl.bobamp, FINESINE(((FixedMul(4 * M_TAU_FIXED, fl.bobspeed) * followertimer)>>ANGLETOFINESHIFT) & FINEMASK)); - - UINT8 *colormap = R_GetTranslationColormap(TC_DEFAULT, setupm_fakecolor->color, 0); // why does GTC_MENUCACHE not work here...? - V_DrawFixedPatch((mx+65)*FRACUNIT, ((my+131)*FRACUNIT)-fl.zoffs+sine, fl.scale, flags, patch, colormap); - Z_Free(colormap); - } - } - -#undef charw -} - -// follower state update. This is its own function so that it's at least somewhat clean -static void M_GetFollowerState(void) -{ - - if (setupm_fakefollower <= -1 || setupm_fakefollower >= numfollowers) // yikes, there's none! - return; - // ^ we don't actually need to set anything since it won't be displayed anyway. - - //followertimer = 0; // reset timer. not like it'll overflow anytime soon but whatever. - - // set follower state - follower_state = &states[followers[setupm_fakefollower].followstate]; - - if (follower_state->frame & FF_ANIMATE) - follower_tics = follower_state->var2; // support for FF_ANIMATE - else - follower_tics = follower_state->tics; - - follower_frame = follower_state->frame & FF_FRAMEMASK; -} - -// Handle 1P/2P MP Setup -static void M_HandleSetupMultiPlayer(INT32 choice) -{ - size_t l; - INT32 prev_setupm_fakeskin; - boolean exitmenu = false; // exit to previous menu and send name change - - if ((choice == gamecontrol[0][gc_fire][0] || choice == gamecontrol[0][gc_fire][1]) && itemOn == 2) - choice = KEY_BACKSPACE; // Hack to allow resetting prefcolor on controllers - - switch (choice) - { - case KEY_DOWNARROW: - M_NextOpt(); - S_StartSound(NULL,sfx_menu1); // Tails - break; - - case KEY_UPARROW: - M_PrevOpt(); - S_StartSound(NULL,sfx_menu1); // Tails - break; - - case KEY_LEFTARROW: - if (itemOn == 1) //player skin - { - S_StartSound(NULL,sfx_menu1); // Tails - prev_setupm_fakeskin = setupm_fakeskin; - do - { - setupm_fakeskin--; - if (setupm_fakeskin < 0) - setupm_fakeskin = numskins-1; - } - while ((prev_setupm_fakeskin != setupm_fakeskin) && !(R_SkinUsable(-1, setupm_fakeskin))); - multi_spr2 = P_GetSkinSprite2(&skins[setupm_fakeskin], SPR2_FSTN, NULL); - } - else if (itemOn == 2) // follower - { - S_StartSound(NULL,sfx_menu1); - setupm_fakefollower--; - M_GetFollowerState(); // update follower state - } - else if (itemOn == 3) // player color - { - S_StartSound(NULL,sfx_menu1); // Tails - setupm_fakecolor = setupm_fakecolor->prev; - } - break; - - case KEY_RIGHTARROW: - if (itemOn == 1) //player skin - { - S_StartSound(NULL,sfx_menu1); // Tails - prev_setupm_fakeskin = setupm_fakeskin; - do - { - setupm_fakeskin++; - if (setupm_fakeskin > numskins-1) - setupm_fakeskin = 0; - } - while ((prev_setupm_fakeskin != setupm_fakeskin) && !(R_SkinUsable(-1, setupm_fakeskin))); - multi_spr2 = P_GetSkinSprite2(&skins[setupm_fakeskin], SPR2_FSTN, NULL); - } - else if (itemOn == 2) // follower - { - S_StartSound(NULL,sfx_menu1); - setupm_fakefollower++; - M_GetFollowerState(); - } - else if (itemOn == 3) // player color - { - S_StartSound(NULL,sfx_menu1); // Tails - setupm_fakecolor = setupm_fakecolor->next; - } - break; - - case KEY_ESCAPE: - exitmenu = true; - break; - - case KEY_BACKSPACE: - if (itemOn == 0) - { - if ((l = strlen(setupm_name))!=0) - { - S_StartSound(NULL,sfx_menu1); // Tails - setupm_name[l-1] =0; - } - } - else if (itemOn == 2) // follower - { - S_StartSound(NULL,sfx_menu1); - setupm_fakefollower = -1; - } - else if (itemOn == 3) - { - UINT16 col = skins[setupm_fakeskin].prefcolor; - if ((setupm_fakecolor->color != col) && skincolors[col].accessible) - { - S_StartSound(NULL,sfx_menu1); // Tails - for (setupm_fakecolor=menucolorhead;;setupm_fakecolor=setupm_fakecolor->next) - if (setupm_fakecolor->color == col || setupm_fakecolor == menucolortail) - break; - } - } - break; - - case KEY_DEL: - if (itemOn == 0 && (l = strlen(setupm_name))!=0) - { - S_StartSound(NULL,sfx_menu1); // Tails - setupm_name[0] = 0; - } - break; - - default: - if (choice < 32 || choice > 127 || itemOn != 0) - break; - l = strlen(setupm_name); - if (l < MAXPLAYERNAME) - { - S_StartSound(NULL,sfx_menu1); // Tails - setupm_name[l] =(char)choice; - setupm_name[l+1] =0; - } - break; - } - - // check followers: - if (setupm_fakefollower < -1) - { - setupm_fakefollower = numfollowers; - M_GetFollowerState(); // update follower state - } - if (setupm_fakefollower >= numfollowers) - { - setupm_fakefollower = -1; - M_GetFollowerState(); // update follower state - } - - // check color - if (itemOn == 2 && !skincolors[setupm_fakecolor->color].accessible) { - if (choice == KEY_LEFTARROW) - while (!skincolors[setupm_fakecolor->color].accessible) - setupm_fakecolor = setupm_fakecolor->prev; - else if (choice == KEY_RIGHTARROW || choice == KEY_ENTER) - while (!skincolors[setupm_fakecolor->color].accessible) - setupm_fakecolor = setupm_fakecolor->next; - } - - if (exitmenu) - { - if (currentMenu->prevMenu) - M_SetupNextMenu (currentMenu->prevMenu); - else - M_ClearMenus(true); - } -} - -// start the multiplayer setup menu -static void M_SetupMultiPlayer(INT32 choice) -{ - (void)choice; - - multi_state = &states[mobjinfo[MT_PLAYER].seestate]; - multi_tics = multi_state->tics * FRACUNIT; - - strcpy(setupm_name, cv_playername[0].string); - - // set for player 1 - setupm_player = &players[consoleplayer]; - setupm_cvskin = &cv_skin[0]; - setupm_cvcolor = &cv_playercolor[0]; - setupm_cvname = &cv_playername[0]; - setupm_cvfollower = &cv_follower[0]; - - setupm_fakefollower = setupm_cvfollower->value; - - // yikes, we don't want none of that... - if (setupm_fakefollower >= numfollowers) - setupm_fakefollower = -1; - - M_GetFollowerState(); // update follower state - - // For whatever reason this doesn't work right if you just use ->value - setupm_fakeskin = R_SkinAvailable(setupm_cvskin->string); - if (setupm_fakeskin == -1) - setupm_fakeskin = 0; - - for (setupm_fakecolor=menucolorhead;;setupm_fakecolor=setupm_fakecolor->next) - if (setupm_fakecolor->color == setupm_cvcolor->value || setupm_fakecolor == menucolortail) - break; - - // disable skin changes if we can't actually change skins - if (!CanChangeSkin(consoleplayer)) - MP_PlayerSetupMenu[2].status = (IT_GRAYEDOUT); - else - MP_PlayerSetupMenu[2].status = (IT_KEYHANDLER|IT_STRING); - - MP_PlayerSetupDef.prevMenu = currentMenu; - M_SetupNextMenu(&MP_PlayerSetupDef); -} - -// start the multiplayer setup menu, for secondary player (splitscreen mode) -static void M_SetupMultiPlayer2(INT32 choice) -{ - (void)choice; - - multi_state = &states[mobjinfo[MT_PLAYER].seestate]; - multi_tics = multi_state->tics * FRACUNIT; - strcpy (setupm_name, cv_playername[1].string); - - // set for splitscreen secondary player - setupm_player = &players[g_localplayers[1]]; - setupm_cvskin = &cv_skin[1]; - setupm_cvcolor = &cv_playercolor[1]; - setupm_cvname = &cv_playername[1]; - setupm_cvfollower = &cv_follower[1]; - - setupm_fakefollower = setupm_cvfollower->value; - - // yikes, we don't want none of that... - if (setupm_fakefollower >= numfollowers) - setupm_fakefollower = -1; - - M_GetFollowerState(); // update follower state - - // For whatever reason this doesn't work right if you just use ->value - setupm_fakeskin = R_SkinAvailable(setupm_cvskin->string); - if (setupm_fakeskin == -1) - setupm_fakeskin = 0; - - for (setupm_fakecolor=menucolorhead;;setupm_fakecolor=setupm_fakecolor->next) - if (setupm_fakecolor->color == setupm_cvcolor->value || setupm_fakecolor == menucolortail) - break; - - // disable skin changes if we can't actually change skins - if (splitscreen && !CanChangeSkin(g_localplayers[1])) - MP_PlayerSetupMenu[2].status = (IT_GRAYEDOUT); - else - MP_PlayerSetupMenu[2].status = (IT_KEYHANDLER | IT_STRING); - - MP_PlayerSetupDef.prevMenu = currentMenu; - M_SetupNextMenu(&MP_PlayerSetupDef); -} - -// start the multiplayer setup menu, for third player (splitscreen mode) -static void M_SetupMultiPlayer3(INT32 choice) -{ - (void)choice; - - multi_state = &states[mobjinfo[MT_PLAYER].seestate]; - multi_tics = multi_state->tics; - strcpy(setupm_name, cv_playername[2].string); - - // set for splitscreen third player - setupm_player = &players[g_localplayers[2]]; - setupm_cvskin = &cv_skin[2]; - setupm_cvcolor = &cv_playercolor[2]; - setupm_cvname = &cv_playername[2]; - setupm_cvfollower = &cv_follower[2]; - - setupm_fakefollower = setupm_cvfollower->value; - - // yikes, we don't want none of that... - if (setupm_fakefollower >= numfollowers) - setupm_fakefollower = -1; - - M_GetFollowerState(); // update follower state - - // For whatever reason this doesn't work right if you just use ->value - setupm_fakeskin = R_SkinAvailable(setupm_cvskin->string); - if (setupm_fakeskin == -1) - setupm_fakeskin = 0; - - for (setupm_fakecolor=menucolorhead;;setupm_fakecolor=setupm_fakecolor->next) - if (setupm_fakecolor->color == setupm_cvcolor->value || setupm_fakecolor == menucolortail) - break; - - // disable skin changes if we can't actually change skins - if (splitscreen > 1 && !CanChangeSkin(g_localplayers[2])) - MP_PlayerSetupMenu[2].status = (IT_GRAYEDOUT); - else - MP_PlayerSetupMenu[2].status = (IT_KEYHANDLER | IT_STRING); - - MP_PlayerSetupDef.prevMenu = currentMenu; - M_SetupNextMenu(&MP_PlayerSetupDef); -} - -// start the multiplayer setup menu, for third player (splitscreen mode) -static void M_SetupMultiPlayer4(INT32 choice) -{ - (void)choice; - - multi_state = &states[mobjinfo[MT_PLAYER].seestate]; - multi_tics = multi_state->tics; - strcpy(setupm_name, cv_playername[3].string); - - // set for splitscreen fourth player - setupm_player = &players[g_localplayers[3]]; - setupm_cvskin = &cv_skin[3]; - setupm_cvcolor = &cv_playercolor[3]; - setupm_cvname = &cv_playername[3]; - setupm_cvfollower = &cv_follower[3]; - - setupm_fakefollower = setupm_cvfollower->value; - - // yikes, we don't want none of that... - if (setupm_fakefollower >= numfollowers) - setupm_fakefollower = -1; - - M_GetFollowerState(); // update follower state - - // For whatever reason this doesn't work right if you just use ->value - setupm_fakeskin = R_SkinAvailable(setupm_cvskin->string); - if (setupm_fakeskin == -1) - setupm_fakeskin = 0; - - for (setupm_fakecolor=menucolorhead;;setupm_fakecolor=setupm_fakecolor->next) - if (setupm_fakecolor->color == setupm_cvcolor->value || setupm_fakecolor == menucolortail) - break; - - // disable skin changes if we can't actually change skins - if (splitscreen > 2 && !CanChangeSkin(g_localplayers[3])) - MP_PlayerSetupMenu[2].status = (IT_GRAYEDOUT); - else - MP_PlayerSetupMenu[2].status = (IT_KEYHANDLER | IT_STRING); - - MP_PlayerSetupDef.prevMenu = currentMenu; - M_SetupNextMenu(&MP_PlayerSetupDef); -} - -static boolean M_QuitMultiPlayerMenu(void) -{ - size_t l; - const char *followername = setupm_fakefollower == -1 ? - "None" : followers[setupm_fakefollower].skinname; - // send name if changed - if (strcmp(setupm_name, setupm_cvname->string)) - { - // remove trailing whitespaces - for (l= strlen(setupm_name)-1; - (signed)l >= 0 && setupm_name[l] ==' '; l--) - setupm_name[l] =0; - COM_BufAddText (va("%s \"%s\"\n",setupm_cvname->name,setupm_name)); - } - // you know what? always putting these in the buffer won't hurt anything. - COM_BufAddText (va("%s \"%s\"\n",setupm_cvskin->name,skins[setupm_fakeskin].name)); - COM_BufAddText (va("%s %d\n",setupm_cvcolor->name,setupm_fakecolor->color)); - COM_BufAddText (va("%s %s\n",setupm_cvfollower->name,followername)); - return true; -} - -void M_AddMenuColor(UINT16 color) { - menucolor_t *c; - - // SRB2Kart: I do not understand vanilla doesn't need this but WE do???!?!??! - if (!skincolors[color].accessible) { - return; - } - - if (color >= numskincolors) { - CONS_Printf("M_AddMenuColor: color %d does not exist.",color); - 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; - } -} - -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) { - menucolor_t *look; - - if (color >= numskincolors) { - CONS_Printf("M_GetColorBefore: color %d does not exist.\n",color); - return 0; - } - - for (look=menucolorhead;;look=look->next) { - if (look->color == color) - return look->prev->color; - if (look==menucolortail) - return 0; - } -} - -UINT16 M_GetColorAfter(UINT16 color) { - menucolor_t *look; - - if (color >= numskincolors) { - CONS_Printf("M_GetColorAfter: color %d does not exist.\n",color); - return 0; - } - - for (look=menucolorhead;;look=look->next) { - if (look->color == color) - return look->next->color; - if (look==menucolortail) - return 0; - } -} - -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; -} - -// ================= -// DATA OPTIONS MENU -// ================= -static UINT8 erasecontext = 0; - -static void M_EraseDataResponse(INT32 ch) -{ - UINT8 i; - - if (ch != 'y' && ch != KEY_ENTER) - return; - - S_StartSound(NULL, sfx_itrole); // bweh heh heh - - // Delete the data - if (erasecontext == 2) - { - // SRB2Kart: This actually needs to be done FIRST, so that you don't immediately regain playtime/matches secrets - totalplaytime = 0; - matchesplayed = 0; - for (i = 0; i < PWRLV_NUMTYPES; i++) - vspowerlevel[i] = PWRLVRECORD_START; - F_StartIntro(); - } - if (erasecontext != 1) - G_ClearRecords(); - if (erasecontext != 0) - M_ClearSecrets(); - M_ClearMenus(true); -} - -static void M_EraseData(INT32 choice) -{ - const char *eschoice, *esstr = M_GetText("Are you sure you want to erase\n%s?\n\n(Press 'Y' to confirm)\n"); - - erasecontext = (UINT8)choice; - - if (choice == 0) - eschoice = M_GetText("Record 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); -} - -static void M_ScreenshotOptions(INT32 choice) -{ - (void)choice; - Screenshot_option_Onchange(); - Moviemode_mode_Onchange(); - - M_SetupNextMenu(&OP_ScreenshotOptionsDef); -} - -// ============= -// JOYSTICK MENU -// ============= - -// Start the controls menu, setting it up for either the console player, -// or the secondary splitscreen player - -static void M_DrawJoystick(void) -{ - INT32 i; - INT32 compareval; - - M_DrawGenericMenu(); - - for (i = 0; i < 8; i++) - { - M_DrawTextBox(OP_JoystickSetDef.x-8, OP_JoystickSetDef.y+LINEHEIGHT*i-12, 28, 1); - //M_DrawSaveLoadBorder(OP_JoystickSetDef.x, OP_JoystickSetDef.y+LINEHEIGHT*i); - -#ifdef JOYSTICK_HOTPLUG - if (atoi(cv_usejoystick[setupcontrolplayer-1].string) > I_NumJoys()) - compareval = atoi(cv_usejoystick[setupcontrolplayer-1].string); - else -#endif - compareval = cv_usejoystick[setupcontrolplayer-1].value; - - V_DrawString(OP_JoystickSetDef.x, OP_JoystickSetDef.y+LINEHEIGHT*i-4, (i == compareval) ? V_GREENMAP : 0, joystickInfo[i]); - } -} - -void M_SetupJoystickMenu(INT32 choice) -{ - const char *joyNA = "Unavailable"; - const INT32 n = I_NumJoys(); - - INT32 i = 0; - INT32 j; - - (void)choice; - - strcpy(joystickInfo[i], "None"); - - for (i = 1; i < 8; i++) - { - if (i <= n && (I_GetJoyName(i)) != NULL) - strncpy(joystickInfo[i], I_GetJoyName(i), 28); - else - strcpy(joystickInfo[i], joyNA); - -#ifdef JOYSTICK_HOTPLUG - // We use cv_usejoystick.string as the USER-SET var - // and cv_usejoystick.value as the INTERNAL var - // - // In practice, if cv_usejoystick.string == 0, this overrides - // cv_usejoystick.value and always disables - // - // Update cv_usejoystick.string here so that the user can - // properly change this value. - for (j = 0; j < MAXSPLITSCREENPLAYERS; j++) - { - if (i == cv_usejoystick[j].value) - CV_SetValue(&cv_usejoystick[j], i); - } -#endif - } - - M_SetupNextMenu(&OP_JoystickSetDef); -} - -static void M_Setup1PJoystickMenu(INT32 choice) -{ - setupcontrolplayer = 1; - OP_JoystickSetDef.prevMenu = &OP_Joystick1Def; - M_SetupJoystickMenu(choice); -} - -static void M_Setup2PJoystickMenu(INT32 choice) -{ - setupcontrolplayer = 2; - OP_JoystickSetDef.prevMenu = &OP_Joystick2Def; - M_SetupJoystickMenu(choice); -} - -static void M_Setup3PJoystickMenu(INT32 choice) -{ - setupcontrolplayer = 3; - OP_JoystickSetDef.prevMenu = &OP_Joystick3Def; - M_SetupJoystickMenu(choice); -} - -static void M_Setup4PJoystickMenu(INT32 choice) -{ - setupcontrolplayer = 4; - OP_JoystickSetDef.prevMenu = &OP_Joystick4Def; - M_SetupJoystickMenu(choice); -} - -static void M_AssignJoystick(INT32 choice) -{ - const UINT8 p = setupcontrolplayer-1; - -#ifdef JOYSTICK_HOTPLUG - INT32 oldchoice, oldstringchoice; - INT32 numjoys = I_NumJoys(); - - oldchoice = oldstringchoice = atoi(cv_usejoystick[p].string) > numjoys ? atoi(cv_usejoystick[p].string) : cv_usejoystick[p].value; - CV_SetValue(&cv_usejoystick[p], choice); - - // Just in case last-minute changes were made to cv_usejoystick.value, - // update the string too - // But don't do this if we're intentionally setting higher than numjoys - if (choice <= numjoys) - { - CV_SetValue(&cv_usejoystick[p], cv_usejoystick[p].value); - - // reset this so the comparison is valid - if (oldchoice > numjoys) - oldchoice = cv_usejoystick[p].value; - - if (oldchoice != choice) - { - if (choice && oldstringchoice > numjoys) // if we did not select "None", we likely selected a used device - CV_SetValue(&cv_usejoystick[p], (oldstringchoice > numjoys ? oldstringchoice : oldchoice)); - - if (oldstringchoice == - (atoi(cv_usejoystick[p].string) > numjoys ? atoi(cv_usejoystick[p].string) : cv_usejoystick[p].value)) - M_StartMessage("This joystick is used by another\n" - "player. Reset the joystick\n" - "for that player first.\n\n" - "(Press a key)\n", NULL, MM_NOTHING); - } - } -#else - CV_SetValue(&cv_usejoystick[p], choice); -#endif -} - -// ============= -// CONTROLS MENU -// ============= - -static void M_Setup1PControlsMenu(INT32 choice) -{ - (void)choice; - setupcontrolplayer = 1; - setupcontrols = gamecontrol[0]; // was called from main Options (for console player, then) - currentMenu->lastOn = itemOn; - - // Set proper gamepad options - OP_AllControlsMenu[0].itemaction.submenu = &OP_Joystick1Def; - - // Unhide P1-only controls - OP_AllControlsMenu[16].status = IT_CONTROL; // Chat - //OP_AllControlsMenu[17].status = IT_CONTROL; // Team-chat - OP_AllControlsMenu[17].status = IT_CONTROL; // Rankings - //OP_AllControlsMenu[18].status = IT_CONTROL; // Viewpoint - // 19 is Reset Camera, 20 is Toggle Chasecam - OP_AllControlsMenu[21].status = IT_CONTROL; // Pause - OP_AllControlsMenu[22].status = IT_CONTROL; // Screenshot - OP_AllControlsMenu[23].status = IT_CONTROL; // GIF - OP_AllControlsMenu[24].status = IT_CONTROL; // System Menu - OP_AllControlsMenu[25].status = IT_CONTROL; // Console - /*OP_AllControlsMenu[26].status = IT_HEADER; // Spectator Controls header - OP_AllControlsMenu[27].status = IT_SPACE; // Spectator Controls space - OP_AllControlsMenu[28].status = IT_CONTROL; // Spectate - OP_AllControlsMenu[29].status = IT_CONTROL; // Look Up - OP_AllControlsMenu[30].status = IT_CONTROL; // Look Down - OP_AllControlsMenu[31].status = IT_CONTROL; // Center View - */ - - M_SetupNextMenu(&OP_AllControlsDef); -} - -static void M_Setup2PControlsMenu(INT32 choice) -{ - (void)choice; - setupcontrolplayer = 2; - setupcontrols = gamecontrol[1]; - currentMenu->lastOn = itemOn; - - // Set proper gamepad options - OP_AllControlsMenu[0].itemaction.submenu = &OP_Joystick2Def; - - // Hide P1-only controls - OP_AllControlsMenu[16].status = IT_GRAYEDOUT2; // Chat - //OP_AllControlsMenu[17].status = IT_GRAYEDOUT2; // Team-chat - OP_AllControlsMenu[17].status = IT_GRAYEDOUT2; // Rankings - //OP_AllControlsMenu[18].status = IT_GRAYEDOUT2; // Viewpoint - // 19 is Reset Camera, 20 is Toggle Chasecam - OP_AllControlsMenu[21].status = IT_GRAYEDOUT2; // Pause - OP_AllControlsMenu[22].status = IT_GRAYEDOUT2; // Screenshot - OP_AllControlsMenu[23].status = IT_GRAYEDOUT2; // GIF - OP_AllControlsMenu[24].status = IT_GRAYEDOUT2; // System Menu - OP_AllControlsMenu[25].status = IT_GRAYEDOUT2; // Console - /*OP_AllControlsMenu[26].status = IT_GRAYEDOUT2; // Spectator Controls header - OP_AllControlsMenu[27].status = IT_GRAYEDOUT2; // Spectator Controls space - OP_AllControlsMenu[28].status = IT_GRAYEDOUT2; // Spectate - OP_AllControlsMenu[29].status = IT_GRAYEDOUT2; // Look Up - OP_AllControlsMenu[30].status = IT_GRAYEDOUT2; // Look Down - OP_AllControlsMenu[31].status = IT_GRAYEDOUT2; // Center View - */ - - M_SetupNextMenu(&OP_AllControlsDef); -} - -static void M_Setup3PControlsMenu(INT32 choice) -{ - (void)choice; - setupcontrolplayer = 3; - setupcontrols = gamecontrol[2]; - currentMenu->lastOn = itemOn; - - // Set proper gamepad options - OP_AllControlsMenu[0].itemaction.submenu = &OP_Joystick3Def; - - // Hide P1-only controls - OP_AllControlsMenu[16].status = IT_GRAYEDOUT2; // Chat - //OP_AllControlsMenu[17].status = IT_GRAYEDOUT2; // Team-chat - OP_AllControlsMenu[17].status = IT_GRAYEDOUT2; // Rankings - //OP_AllControlsMenu[18].status = IT_GRAYEDOUT2; // Viewpoint - // 19 is Reset Camera, 20 is Toggle Chasecam - OP_AllControlsMenu[21].status = IT_GRAYEDOUT2; // Pause - OP_AllControlsMenu[22].status = IT_GRAYEDOUT2; // Screenshot - OP_AllControlsMenu[23].status = IT_GRAYEDOUT2; // GIF - OP_AllControlsMenu[24].status = IT_GRAYEDOUT2; // System Menu - OP_AllControlsMenu[25].status = IT_GRAYEDOUT2; // Console - /*OP_AllControlsMenu[26].status = IT_GRAYEDOUT2; // Spectator Controls header - OP_AllControlsMenu[27].status = IT_GRAYEDOUT2; // Spectator Controls space - OP_AllControlsMenu[28].status = IT_GRAYEDOUT2; // Spectate - OP_AllControlsMenu[29].status = IT_GRAYEDOUT2; // Look Up - OP_AllControlsMenu[30].status = IT_GRAYEDOUT2; // Look Down - OP_AllControlsMenu[31].status = IT_GRAYEDOUT2; // Center View - */ - - M_SetupNextMenu(&OP_AllControlsDef); -} - -static void M_Setup4PControlsMenu(INT32 choice) -{ - (void)choice; - setupcontrolplayer = 4; - setupcontrols = gamecontrol[3]; - currentMenu->lastOn = itemOn; - - // Set proper gamepad options - OP_AllControlsMenu[0].itemaction.submenu = &OP_Joystick4Def; - - // Hide P1-only controls - OP_AllControlsMenu[16].status = IT_GRAYEDOUT2; // Chat - //OP_AllControlsMenu[17].status = IT_GRAYEDOUT2; // Team-chat - OP_AllControlsMenu[17].status = IT_GRAYEDOUT2; // Rankings - //OP_AllControlsMenu[18].status = IT_GRAYEDOUT2; // Viewpoint - // 19 is Reset Camera, 20 is Toggle Chasecam - OP_AllControlsMenu[21].status = IT_GRAYEDOUT2; // Pause - OP_AllControlsMenu[22].status = IT_GRAYEDOUT2; // Screenshot - OP_AllControlsMenu[23].status = IT_GRAYEDOUT2; // GIF - OP_AllControlsMenu[24].status = IT_GRAYEDOUT2; // System Menu - OP_AllControlsMenu[25].status = IT_GRAYEDOUT2; // Console - /*OP_AllControlsMenu[26].status = IT_GRAYEDOUT2; // Spectator Controls header - OP_AllControlsMenu[27].status = IT_GRAYEDOUT2; // Spectator Controls space - OP_AllControlsMenu[28].status = IT_GRAYEDOUT2; // Spectate - OP_AllControlsMenu[29].status = IT_GRAYEDOUT2; // Look Up - OP_AllControlsMenu[30].status = IT_GRAYEDOUT2; // Look Down - OP_AllControlsMenu[31].status = IT_GRAYEDOUT2; // Center View - */ - - M_SetupNextMenu(&OP_AllControlsDef); -} - -#define controlheight 18 - -// Draws the Customise Controls menu -static void M_DrawControl(void) -{ - char tmp[50]; - INT32 x, y, i, max, cursory = 0, iter; - INT32 keys[2]; - - x = currentMenu->x; - y = currentMenu->y; - - /*i = itemOn - (controlheight/2); - if (i < 0) - i = 0; - */ - - iter = (controlheight/2); - for (i = itemOn; ((iter || currentMenu->menuitems[i].status == IT_GRAYEDOUT2) && i > 0); i--) - { - if (currentMenu->menuitems[i].status != IT_GRAYEDOUT2) - iter--; - } - if (currentMenu->menuitems[i].status == IT_GRAYEDOUT2) - i--; - - iter += (controlheight/2); - for (max = itemOn; (iter && max < currentMenu->numitems); max++) - { - if (currentMenu->menuitems[max].status != IT_GRAYEDOUT2) - iter--; - } - - if (iter) - { - iter += (controlheight/2); - for (i = itemOn; ((iter || currentMenu->menuitems[i].status == IT_GRAYEDOUT2) && i > 0); i--) - { - if (currentMenu->menuitems[i].status != IT_GRAYEDOUT2) - iter--; - } - } - - /*max = i + controlheight; - if (max > currentMenu->numitems) - { - max = currentMenu->numitems; - if (max < controlheight) - i = 0; - else - i = max - controlheight; - }*/ - - // draw title (or big pic) - M_DrawMenuTitle(); - - M_CentreText(28, - (setupcontrolplayer > 1 ? va("\x86""Set controls for ""\x82""Player %d", setupcontrolplayer) : - "\x86""Press ""\x82""ENTER""\x86"" to change, ""\x82""BACKSPACE""\x86"" to clear")); - - if (i) - V_DrawCharacter(currentMenu->x - 16, y-(skullAnimCounter/5), - '\x1A' | highlightflags, false); // up arrow - if (max != currentMenu->numitems) - V_DrawCharacter(currentMenu->x - 16, y+(SMALLLINEHEIGHT*(controlheight-1))+(skullAnimCounter/5) + (skullAnimCounter/5), - '\x1B' | highlightflags, false); // down arrow - - for (; i < max; i++) - { - if (currentMenu->menuitems[i].status == IT_GRAYEDOUT2) - continue; - - if (i == itemOn) - cursory = y; - - if (currentMenu->menuitems[i].status == IT_CONTROL) - { - V_DrawString(x, y, ((i == itemOn) ? highlightflags : 0), currentMenu->menuitems[i].text); - keys[0] = setupcontrols[currentMenu->menuitems[i].alphaKey][0]; - keys[1] = setupcontrols[currentMenu->menuitems[i].alphaKey][1]; - - tmp[0] ='\0'; - if (keys[0] == KEY_NULL && keys[1] == KEY_NULL) - { - strcpy(tmp, "---"); - } - else - { - if (keys[0] != KEY_NULL) - strcat (tmp, G_KeynumToString (keys[0])); - - if (keys[0] != KEY_NULL && keys[1] != KEY_NULL) - strcat(tmp,", "); - - if (keys[1] != KEY_NULL) - strcat (tmp, G_KeynumToString (keys[1])); - - } - V_DrawRightAlignedString(BASEVIDWIDTH-currentMenu->x, y, highlightflags, tmp); - } - /*else if (currentMenu->menuitems[i].status == IT_GRAYEDOUT2) - V_DrawString(x, y, V_TRANSLUCENT, currentMenu->menuitems[i].text);*/ - else if ((currentMenu->menuitems[i].status == IT_HEADER) && (i != max-1)) - V_DrawString(19, y+6, highlightflags, currentMenu->menuitems[i].text); - else if (currentMenu->menuitems[i].status & IT_STRING) - V_DrawString(x, y, ((i == itemOn) ? highlightflags : 0), currentMenu->menuitems[i].text); - - y += SMALLLINEHEIGHT; - } - - V_DrawScaledPatch(currentMenu->x - 20, cursory, 0, - W_CachePatchName("M_CURSOR", PU_CACHE)); -} - -#undef controlheight - -static INT32 controltochange; -static char controltochangetext[33]; - -static void M_ChangecontrolResponse(event_t *ev) -{ - INT32 control; - INT32 found; - INT32 ch = ev->data1; - - // ESCAPE cancels; dummy out PAUSE - if (ch != KEY_ESCAPE && ch != KEY_PAUSE) - { - - switch (ev->type) - { - // ignore mouse/joy movements, just get buttons - case ev_mouse: - case ev_joystick: - case ev_joystick2: - case ev_joystick3: - case ev_joystick4: - ch = KEY_NULL; // no key - break; - - // keypad arrows are converted for the menu in cursor arrows - // so use the event instead of ch - case ev_keydown: - ch = ev->data1; - break; - - default: - break; - } - - control = controltochange; - - // check if we already entered this key - found = -1; - if (setupcontrols[control][0] ==ch) - found = 0; - else if (setupcontrols[control][1] ==ch) - found = 1; - if (found >= 0) - { - // replace mouse and joy clicks by double clicks - if (ch >= KEY_MOUSE1 && ch <= KEY_MOUSE1+MOUSEBUTTONS) - setupcontrols[control][found] = ch-KEY_MOUSE1+KEY_DBLMOUSE1; - else if (ch >= KEY_JOY1 && ch <= KEY_JOY1+JOYBUTTONS) - setupcontrols[control][found] = ch-KEY_JOY1+KEY_DBLJOY1; - else if (ch >= KEY_2MOUSE1 && ch <= KEY_2MOUSE1+MOUSEBUTTONS) - setupcontrols[control][found] = ch-KEY_2MOUSE1+KEY_DBL2MOUSE1; - else if (ch >= KEY_2JOY1 && ch <= KEY_2JOY1+JOYBUTTONS) - setupcontrols[control][found] = ch-KEY_2JOY1+KEY_DBL2JOY1; - else if (ch >= KEY_3JOY1 && ch <= KEY_3JOY1+JOYBUTTONS) - setupcontrols[control][found] = ch-KEY_3JOY1+KEY_DBL3JOY1; - else if (ch >= KEY_4JOY1 && ch <= KEY_4JOY1+JOYBUTTONS) - setupcontrols[control][found] = ch-KEY_4JOY1+KEY_DBL4JOY1; - } - else - { - // check if change key1 or key2, or replace the two by the new - found = 0; - if (setupcontrols[control][0] == KEY_NULL) - found++; - if (setupcontrols[control][1] == KEY_NULL) - found++; - if (found == 2) - { - found = 0; - setupcontrols[control][1] = KEY_NULL; //replace key 1,clear key2 - } - (void)G_CheckDoubleUsage(ch, true); - setupcontrols[control][found] = ch; - } - S_StartSound(NULL, sfx_s221); - } - else if (ch == KEY_PAUSE) - { - // This buffer assumes a 125-character message plus a 32-character control name (per controltochangetext buffer size) - static char tmp[158]; - menu_t *prev = currentMenu->prevMenu; - - if (controltochange == gc_pause) - sprintf(tmp, M_GetText("The \x82Pause Key \x80is enabled, but \nyou may select another key. \n\nHit another key for\n%s\nESC for Cancel"), - controltochangetext); - else - sprintf(tmp, M_GetText("The \x82Pause Key \x80is enabled, but \nit is not configurable. \n\nHit another key for\n%s\nESC for Cancel"), - controltochangetext); - - M_StartMessage(tmp, FUNCPTRCAST(M_ChangecontrolResponse), MM_EVENTHANDLER); - currentMenu->prevMenu = prev; - - S_StartSound(NULL, sfx_s3k42); - return; - } - else - S_StartSound(NULL, sfx_s224); - - M_StopMessage(0); -} - -static void M_ChangeControl(INT32 choice) -{ - // This buffer assumes a 35-character message (per below) plus a max control name limit of 32 chars (per controltochangetext) - // If you change the below message, then change the size of this buffer! - static char tmp[68]; - - controltochange = currentMenu->menuitems[choice].alphaKey; - sprintf(tmp, M_GetText("Hit the new key for\n%s\nESC for Cancel"), - currentMenu->menuitems[choice].text); - strlcpy(controltochangetext, currentMenu->menuitems[choice].text, 33); - - M_StartMessage(tmp, FUNCPTRCAST(M_ChangecontrolResponse), MM_EVENTHANDLER); -} - -static void M_ResetControlsResponse(INT32 ch) -{ - const UINT8 p = setupcontrolplayer-1; - INT32 i; - - if (ch != 'y' && ch != KEY_ENTER) - return; - - // clear all controls - for (i = 0; i < num_gamecontrols; i++) - { - G_ClearControlKeys(gamecontrol[p], i); - } - - // Setup original defaults - G_CopyControls(gamecontrol[p], gamecontroldefault[p][gcs_kart], NULL, 0); - - // Setup gamepad option defaults (yucky) - CV_StealthSet(&cv_usejoystick[p], cv_usejoystick[p].defaultvalue); - CV_StealthSet(&cv_turnaxis[p], cv_turnaxis[p].defaultvalue); - CV_StealthSet(&cv_moveaxis[p], cv_moveaxis[p].defaultvalue); - CV_StealthSet(&cv_brakeaxis[p], cv_brakeaxis[p].defaultvalue); - CV_StealthSet(&cv_aimaxis[p], cv_aimaxis[p].defaultvalue); - CV_StealthSet(&cv_lookaxis[p], cv_lookaxis[p].defaultvalue); - CV_StealthSet(&cv_fireaxis[p], cv_fireaxis[p].defaultvalue); - CV_StealthSet(&cv_driftaxis[p], cv_driftaxis[p].defaultvalue); - - S_StartSound(NULL, sfx_s224); -} - -static void M_ResetControls(INT32 choice) -{ - (void)choice; - M_StartMessage(va(M_GetText("Reset Player %d's controls to defaults?\n\n(Press 'Y' to confirm)\n"), setupcontrolplayer), FUNCPTRCAST(M_ResetControlsResponse), MM_YESNO); -} - -// ===== -// SOUND -// ===== - -/*static void M_RestartAudio(void) -{ - COM_ImmedExecute("restartaudio"); -}*/ - -// =============== -// VIDEO MODE MENU -// =============== - -//added : 30-01-98: -#define MAXCOLUMNMODES 12 //max modes displayed in one column -#define MAXMODEDESCS (MAXCOLUMNMODES*3) - -static modedesc_t modedescs[MAXMODEDESCS]; - -static void M_VideoModeMenu(INT32 choice) -{ - INT32 i, j, vdup, nummodes; - UINT32 width, height; - const char *desc; - - (void)choice; - - memset(modedescs, 0, sizeof(modedescs)); - -#if (defined (__unix__) && !defined (MSDOS)) || defined (UNIXCOMMON) || defined (HAVE_SDL) - VID_PrepareModeList(); // FIXME: hack -#endif - vidm_nummodes = 0; - vidm_selected = 0; - nummodes = VID_NumModes(); - -#ifdef _WINDOWS - // clean that later: skip windowed mode 0, video modes menu only shows FULL SCREEN modes - if (nummodes <= NUMSPECIALMODES) - i = 0; // unless we have nothing - else - i = NUMSPECIALMODES; -#else - // DOS does not skip mode 0, because mode 0 is ALWAYS present - i = 0; -#endif - for (; i < nummodes && 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 < vidm_nummodes; j++) - { - if (!strcmp(modedescs[j].desc, desc)) - { - // mode(0): 320x200 is always standard VGA, not vesa - if (modedescs[j].modenum) - { - modedescs[j].modenum = i; - vdup = 1; - - if (i == vid.modenum) - vidm_selected = j; - } - else - vdup = 1; - - break; - } - } - - if (!vdup) - { - modedescs[vidm_nummodes].modenum = i; - modedescs[vidm_nummodes].desc = desc; - - if (i == vid.modenum) - vidm_selected = 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)) - modedescs[vidm_nummodes].goodratio = 1; - - vidm_nummodes++; - } - } - } - - vidm_column_size = (vidm_nummodes+2) / 3; - - M_SetupNextMenu(&OP_VideoModeDef); -} - -static void M_DrawVideoMenu(void) -{ - M_DrawGenericMenu(); - - V_DrawRightAlignedString(BASEVIDWIDTH - currentMenu->x, currentMenu->y + OP_VideoOptionsMenu[0].alphaKey, - (SCR_IsAspectCorrect(vid.width, vid.height) ? recommendedflags : highlightflags), - va("%dx%d", vid.width, vid.height)); -} - -static void M_DrawHUDOptions(void) -{ - const char *str0 = ")"; - const char *str1 = " Warning highlight"; - const char *str2 = ","; - const char *str3 = "Good highlight"; - INT32 x = BASEVIDWIDTH - currentMenu->x + 2, y = currentMenu->y + 115; - INT32 w0 = V_StringWidth(str0, 0), w1 = V_StringWidth(str1, 0), w2 = V_StringWidth(str2, 0), w3 = V_StringWidth(str3, 0); - - M_DrawGenericMenu(); - - x -= w0; - V_DrawString(x, y, highlightflags, str0); - x -= w1; - V_DrawString(x, y, warningflags, str1); - x -= w2; - V_DrawString(x, y, highlightflags, str2); - x -= w3; - V_DrawString(x, y, recommendedflags, str3); - V_DrawRightAlignedString(x, y, highlightflags, "("); -} - -// Draw the video modes list, a-la-Quake -static void M_DrawVideoMode(void) -{ - INT32 i, j, row, col; - - // draw title - M_DrawMenuTitle(); - - V_DrawCenteredString(BASEVIDWIDTH/2, OP_VideoModeDef.y, - highlightflags, "Choose mode, reselect to change default"); - - row = 41; - col = OP_VideoModeDef.y + 14; - for (i = 0; i < vidm_nummodes; i++) - { - if (i == vidm_selected) - V_DrawString(row, col, highlightflags, modedescs[i].desc); - // Show multiples of 320x200 as green. - else - V_DrawString(row, col, (modedescs[i].goodratio) ? recommendedflags : 0, modedescs[i].desc); - - col += 8; - if ((i % vidm_column_size) == (vidm_column_size-1)) - { - row += 7*13; - col = OP_VideoModeDef.y + 14; - } - } - - if (vidm_testingmode > 0) - { - INT32 testtime = (vidm_testingmode/TICRATE) + 1; - - M_CentreText(OP_VideoModeDef.y + 116, - va("Previewing mode %c%dx%d", - (SCR_IsAspectCorrect(vid.width, vid.height)) ? 0x83 : 0x80, - vid.width, vid.height)); - M_CentreText(OP_VideoModeDef.y + 138, - "Press ENTER again to keep this mode"); - M_CentreText(OP_VideoModeDef.y + 150, - va("Wait %d second%s", testtime, (testtime > 1) ? "s" : "")); - M_CentreText(OP_VideoModeDef.y + 158, - "or press ESC to return"); - - } - else - { - M_CentreText(OP_VideoModeDef.y + 116, - va("Current mode is %c%dx%d", - (SCR_IsAspectCorrect(vid.width, vid.height)) ? 0x83 : 0x80, - vid.width, vid.height)); - M_CentreText(OP_VideoModeDef.y + 124, - va("Default mode is %c%dx%d", - (SCR_IsAspectCorrect(cv_scr_width.value, cv_scr_height.value)) ? 0x83 : 0x80, - cv_scr_width.value, cv_scr_height.value)); - - V_DrawCenteredString(BASEVIDWIDTH/2, OP_VideoModeDef.y + 138, - recommendedflags, "Marked modes are recommended."); - V_DrawCenteredString(BASEVIDWIDTH/2, OP_VideoModeDef.y + 146, - highlightflags, "Other modes may have visual errors."); - V_DrawCenteredString(BASEVIDWIDTH/2, OP_VideoModeDef.y + 158, - highlightflags, "Larger modes may have performance issues."); - } - - // Draw the cursor for the VidMode menu - i = 41 - 10 + ((vidm_selected / vidm_column_size)*7*13); - j = OP_VideoModeDef.y + 14 + ((vidm_selected % vidm_column_size)*8); - - V_DrawScaledPatch(i - 8, j, 0, - W_CachePatchName("M_CURSOR", PU_CACHE)); -} - -// special menuitem key handler for video mode list -static void M_HandleVideoMode(INT32 ch) -{ - if (vidm_testingmode > 0) switch (ch) - { - // change back to the previous mode quickly - case KEY_ESCAPE: - setmodeneeded = vidm_previousmode + 1; - vidm_testingmode = 0; - break; - - case KEY_ENTER: - S_StartSound(NULL, sfx_menu1); - vidm_testingmode = 0; // stop testing - } - - else switch (ch) - { - case KEY_DOWNARROW: - S_StartSound(NULL, sfx_menu1); - if (++vidm_selected >= vidm_nummodes) - vidm_selected = 0; - break; - - case KEY_UPARROW: - S_StartSound(NULL, sfx_menu1); - if (--vidm_selected < 0) - vidm_selected = vidm_nummodes - 1; - break; - - case KEY_LEFTARROW: - S_StartSound(NULL, sfx_menu1); - vidm_selected -= vidm_column_size; - if (vidm_selected < 0) - vidm_selected = (vidm_column_size*3) + vidm_selected; - if (vidm_selected >= vidm_nummodes) - vidm_selected = vidm_nummodes - 1; - break; - - case KEY_RIGHTARROW: - S_StartSound(NULL, sfx_menu1); - vidm_selected += vidm_column_size; - if (vidm_selected >= (vidm_column_size*3)) - vidm_selected %= vidm_column_size; - if (vidm_selected >= vidm_nummodes) - vidm_selected = vidm_nummodes - 1; - break; - - case KEY_ENTER: - S_StartSound(NULL, sfx_menu1); - if (vid.modenum == modedescs[vidm_selected].modenum) - SCR_SetDefaultMode(); - else - { - vidm_testingmode = 15*TICRATE; - vidm_previousmode = vid.modenum; - if (!setmodeneeded) // in case the previous setmode was not finished - setmodeneeded = modedescs[vidm_selected].modenum + 1; - } - break; - - case KEY_ESCAPE: // this one same as M_Responder - if (currentMenu->prevMenu) - M_SetupNextMenu(currentMenu->prevMenu); - else - M_ClearMenus(true); - break; - - default: - break; - } -} - -// =============== -// Monitor Toggles -// =============== - -static tic_t shitsfree = 0; - -static void M_DrawMonitorToggles(void) -{ - const INT32 edges = 4; - const INT32 height = 4; - const INT32 spacing = 35; - const INT32 column = itemOn/height; - //const INT32 row = itemOn%height; - INT32 leftdraw, rightdraw, totaldraw; - INT32 x = currentMenu->x, y = currentMenu->y+(spacing/4); - INT32 onx = 0, ony = 0; - consvar_t *cv; - INT32 i, translucent, drawnum; - - M_DrawMenuTitle(); - - // Find the available space around column - leftdraw = rightdraw = column; - totaldraw = 0; - for (i = 0; (totaldraw < edges*2 && i < edges*4); i++) - { - if (rightdraw+1 < (currentMenu->numitems/height)+1) - { - rightdraw++; - totaldraw++; - } - if (leftdraw-1 >= 0) - { - leftdraw--; - totaldraw++; - } - } - - for (i = leftdraw; i <= rightdraw; i++) - { - INT32 j; - - for (j = 0; j < height; j++) - { - const INT32 thisitem = (i*height)+j; - - if (thisitem >= currentMenu->numitems) - continue; - - if (thisitem == itemOn) - { - onx = x; - ony = y; - y += spacing; - continue; - } - -#ifdef ITEMTOGGLEBOTTOMRIGHT - if (currentMenu->menuitems[thisitem].alphaKey == 255) - { - V_DrawScaledPatch(x, y, V_TRANSLUCENT, W_CachePatchName("K_ISBG", PU_CACHE)); - continue; - } -#endif - if (currentMenu->menuitems[thisitem].alphaKey == 0) - { - V_DrawScaledPatch(x, y, 0, W_CachePatchName("K_ISBG", PU_CACHE)); - V_DrawScaledPatch(x, y, 0, W_CachePatchName("K_ISTOGL", PU_CACHE)); - continue; - } - - cv = KartItemCVars[currentMenu->menuitems[thisitem].alphaKey-1]; - translucent = (cv->value ? 0 : V_TRANSLUCENT); - - switch (currentMenu->menuitems[thisitem].alphaKey) - { - case KRITEM_DUALSNEAKER: - case KRITEM_DUALJAWZ: - drawnum = 2; - break; - case KRITEM_TRIPLESNEAKER: - case KRITEM_TRIPLEBANANA: - case KRITEM_TRIPLEORBINAUT: - drawnum = 3; - break; - case KRITEM_QUADORBINAUT: - drawnum = 4; - break; - case KRITEM_TENFOLDBANANA: - drawnum = 10; - break; - default: - drawnum = 0; - break; - } - - if (cv->value) - V_DrawScaledPatch(x, y, 0, W_CachePatchName("K_ISBG", PU_CACHE)); - else - V_DrawScaledPatch(x, y, 0, W_CachePatchName("K_ISBGD", PU_CACHE)); - - if (drawnum != 0) - { - V_DrawScaledPatch(x, y, 0, W_CachePatchName("K_ISMUL", PU_CACHE)); - V_DrawScaledPatch(x, y, translucent, W_CachePatchName(K_GetItemPatch(currentMenu->menuitems[thisitem].alphaKey, true), PU_CACHE)); - V_DrawString(x+24, y+31, V_ALLOWLOWERCASE|translucent, va("x%d", drawnum)); - } - else - V_DrawScaledPatch(x, y, translucent, W_CachePatchName(K_GetItemPatch(currentMenu->menuitems[thisitem].alphaKey, true), PU_CACHE)); - - y += spacing; - } - - x += spacing; - y = currentMenu->y+(spacing/4); - } - - { -#ifdef ITEMTOGGLEBOTTOMRIGHT - if (currentMenu->menuitems[itemOn].alphaKey == 255) - { - V_DrawScaledPatch(onx-1, ony-2, V_TRANSLUCENT, W_CachePatchName("K_ITBG", PU_CACHE)); - if (shitsfree) - { - INT32 trans = V_TRANSLUCENT; - if (shitsfree-1 > TICRATE-5) - trans = ((10-TICRATE)+shitsfree-1)<menuitems[itemOn].alphaKey == 0) - { - V_DrawScaledPatch(onx-1, ony-2, 0, W_CachePatchName("K_ITBG", PU_CACHE)); - V_DrawScaledPatch(onx-1, ony-2, 0, W_CachePatchName("K_ITTOGL", PU_CACHE)); - } - else - { - cv = KartItemCVars[currentMenu->menuitems[itemOn].alphaKey-1]; - translucent = (cv->value ? 0 : V_TRANSLUCENT); - - switch (currentMenu->menuitems[itemOn].alphaKey) - { - case KRITEM_DUALSNEAKER: - case KRITEM_DUALJAWZ: - drawnum = 2; - break; - case KRITEM_TRIPLESNEAKER: - case KRITEM_TRIPLEBANANA: - drawnum = 3; - break; - case KRITEM_TENFOLDBANANA: - drawnum = 10; - break; - default: - drawnum = 0; - break; - } - - if (cv->value) - V_DrawScaledPatch(onx-1, ony-2, 0, W_CachePatchName("K_ITBG", PU_CACHE)); - else - V_DrawScaledPatch(onx-1, ony-2, 0, W_CachePatchName("K_ITBGD", PU_CACHE)); - - if (drawnum != 0) - { - V_DrawScaledPatch(onx-1, ony-2, 0, W_CachePatchName("K_ITMUL", PU_CACHE)); - V_DrawScaledPatch(onx-1, ony-2, translucent, W_CachePatchName(K_GetItemPatch(currentMenu->menuitems[itemOn].alphaKey, false), PU_CACHE)); - V_DrawScaledPatch(onx+27, ony+39, translucent, W_CachePatchName("K_ITX", PU_CACHE)); - V_DrawKartString(onx+37, ony+34, translucent, va("%d", drawnum)); - } - else - V_DrawScaledPatch(onx-1, ony-2, translucent, W_CachePatchName(K_GetItemPatch(currentMenu->menuitems[itemOn].alphaKey, false), PU_CACHE)); - } - } - - if (shitsfree) - shitsfree--; - - V_DrawCenteredString(BASEVIDWIDTH/2, currentMenu->y, highlightflags, va("* %s *", currentMenu->menuitems[itemOn].text)); -} - -static void M_HandleMonitorToggles(INT32 choice) -{ - const INT32 width = 6, height = 4; - INT32 column = itemOn/height, row = itemOn%height; - INT16 next; - UINT8 i; - boolean exitmenu = false; - - switch (choice) - { - case KEY_RIGHTARROW: - S_StartSound(NULL, sfx_menu1); - column++; - if (((column*height)+row) >= currentMenu->numitems) - column = 0; - next = min(((column*height)+row), currentMenu->numitems-1); - itemOn = next; - break; - - case KEY_LEFTARROW: - S_StartSound(NULL, sfx_menu1); - 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; - break; - - case KEY_DOWNARROW: - S_StartSound(NULL, sfx_menu1); - row = (row+1) % height; - if (((column*height)+row) >= currentMenu->numitems) - row = 0; - next = min(((column*height)+row), currentMenu->numitems-1); - itemOn = next; - break; - - case KEY_UPARROW: - S_StartSound(NULL, sfx_menu1); - 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; - break; - - case KEY_ENTER: -#ifdef ITEMTOGGLEBOTTOMRIGHT - if (currentMenu->menuitems[itemOn].alphaKey == 255) - { - //S_StartSound(NULL, sfx_s26d); - if (!shitsfree) - { - shitsfree = TICRATE; - S_StartSound(NULL, sfx_itfree); - } - } - else -#endif - if (currentMenu->menuitems[itemOn].alphaKey == 0) - { - INT32 v = cv_sneaker.value; - S_StartSound(NULL, sfx_s1b4); - for (i = 0; i < NUMKARTRESULTS-1; i++) - { - if (KartItemCVars[i]->value == v) - CV_AddValue(KartItemCVars[i], 1); - } - } - else - { - S_StartSound(NULL, sfx_s1ba); - CV_AddValue(KartItemCVars[currentMenu->menuitems[itemOn].alphaKey-1], 1); - } - break; - - case KEY_ESCAPE: - exitmenu = true; - break; - } - - if (exitmenu) - { - if (currentMenu->prevMenu) - M_SetupNextMenu(currentMenu->prevMenu); - else - M_ClearMenus(true); - } -} - -// ========= -// 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 != 'y' && ch != KEY_ENTER) - return; - if (!(netgame || cv_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(); -} - -static 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(quitmsg[M_RandomKey(NUM_QUITMESSAGES)], FUNCPTRCAST(M_QuitResponse), MM_YESNO); -} - -#ifdef HAVE_DISCORDRPC -static const tic_t confirmLength = 3*TICRATE/4; -static tic_t confirmDelay = 0; -static boolean confirmAccept = false; - -static void M_HandleDiscordRequests(INT32 choice) -{ - if (confirmDelay > 0) - return; - - switch (choice) - { - case KEY_ENTER: - Discord_Respond(discordRequestList->userID, DISCORD_REPLY_YES); - confirmAccept = true; - confirmDelay = confirmLength; - S_StartSound(NULL, sfx_s3k63); - break; - - case KEY_ESCAPE: - Discord_Respond(discordRequestList->userID, DISCORD_REPLY_NO); - confirmAccept = false; - confirmDelay = confirmLength; - S_StartSound(NULL, sfx_s3kb2); - break; - } -} - -static const char *M_GetDiscordName(discordRequest_t *r) -{ - if (r == NULL) - return ""; - - if (cv_discordstreamer.value) - return r->username; - - return va("%s#%s", r->username, r->discriminator); -} - -// (this goes in k_hud.c when merged into v2) -static void M_DrawSticker(INT32 x, INT32 y, INT32 width, INT32 flags, boolean isSmall) -{ - patch_t *stickerEnd; - INT32 height; - - if (isSmall == true) - { - stickerEnd = W_CachePatchName("K_STIKE2", PU_CACHE); - height = 6; - } - else - { - stickerEnd = W_CachePatchName("K_STIKEN", PU_CACHE); - height = 11; - } - - V_DrawFixedPatch(x*FRACUNIT, y*FRACUNIT, FRACUNIT, flags, stickerEnd, NULL); - V_DrawFill(x, y, width, height, 24|flags); - V_DrawFixedPatch((x + width)*FRACUNIT, y*FRACUNIT, FRACUNIT, flags|V_FLIP, stickerEnd, NULL); -} - -static void M_DrawDiscordRequests(void) -{ - discordRequest_t *curRequest = discordRequestList; - UINT8 *colormap; - patch_t *hand = NULL; - boolean removeRequest = false; - - const char *wantText = "...would like to join!"; - const char *controlText = "\x82" "ENTER" "\x80" " - Accept " "\x82" "ESC" "\x80" " - Decline"; - - INT32 x = 100; - INT32 y = 133; - - INT32 slide = 0; - INT32 maxYSlide = 18; - - if (confirmDelay > 0) - { - if (confirmAccept == true) - { - colormap = R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_GREEN, GTC_MENUCACHE); - hand = W_CachePatchName("K_LAPH02", PU_CACHE); - } - else - { - colormap = R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_RED, GTC_MENUCACHE); - hand = W_CachePatchName("K_LAPH03", PU_CACHE); - } - - slide = confirmLength - confirmDelay; - - confirmDelay--; - - if (confirmDelay == 0) - removeRequest = true; - } - else - { - colormap = R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_GREY, GTC_MENUCACHE); - } - - V_DrawFixedPatch(56*FRACUNIT, 150*FRACUNIT, FRACUNIT, 0, W_CachePatchName("K_LAPE01", PU_CACHE), colormap); - - if (hand != NULL) - { - fixed_t handoffset = (4 - abs((signed)(skullAnimCounter - 4))) * FRACUNIT; - V_DrawFixedPatch(56*FRACUNIT, 150*FRACUNIT + handoffset, FRACUNIT, 0, hand, NULL); - } - - M_DrawSticker(x + (slide * 32), y - 1, V_ThinStringWidth(M_GetDiscordName(curRequest), V_ALLOWLOWERCASE|V_6WIDTHSPACE), 0, false); - V_DrawThinString(x + (slide * 32), y, V_ALLOWLOWERCASE|V_6WIDTHSPACE|V_YELLOWMAP, M_GetDiscordName(curRequest)); - - M_DrawSticker(x, y + 12, V_ThinStringWidth(wantText, V_ALLOWLOWERCASE|V_6WIDTHSPACE), 0, true); - V_DrawThinString(x, y + 10, V_ALLOWLOWERCASE|V_6WIDTHSPACE, wantText); - - M_DrawSticker(x, y + 26, V_ThinStringWidth(controlText, V_ALLOWLOWERCASE|V_6WIDTHSPACE), 0, true); - V_DrawThinString(x, y + 24, V_ALLOWLOWERCASE|V_6WIDTHSPACE, controlText); - - y -= 18; - - while (curRequest->next != NULL) - { - INT32 ySlide = min(slide * 4, maxYSlide); - - curRequest = curRequest->next; - - M_DrawSticker(x, y - 1 + ySlide, V_ThinStringWidth(M_GetDiscordName(curRequest), V_ALLOWLOWERCASE|V_6WIDTHSPACE), 0, false); - V_DrawThinString(x, y + ySlide, V_ALLOWLOWERCASE|V_6WIDTHSPACE, M_GetDiscordName(curRequest)); - - y -= 12; - maxYSlide = 12; - } - - if (removeRequest == true) - { - DRPC_RemoveRequest(discordRequestList); - - if (discordRequestList == NULL) - { - // No other requests - MPauseMenu[mpause_discordrequests].status = IT_GRAYEDOUT; - - if (currentMenu->prevMenu) - { - M_SetupNextMenu(currentMenu->prevMenu); - if (currentMenu == &MPauseDef) - itemOn = mpause_continue; - } - else - M_ClearMenus(true); - - return; - } - } -} -#endif diff --git a/src/m_menu.h b/src/m_menu.h deleted file mode 100644 index be199b142..000000000 --- a/src/m_menu.h +++ /dev/null @@ -1,590 +0,0 @@ -// SONIC ROBO BLAST 2 -//----------------------------------------------------------------------------- -// Copyright (C) 1993-1996 by id Software, Inc. -// Copyright (C) 1998-2000 by DooM Legacy Team. -// Copyright (C) 2011-2016 by Matthew "Kaito Sinclaire" Walsh. -// Copyright (C) 1999-2020 by Sonic Team Junior. -// -// This program is free software distributed under the -// terms of the GNU General Public License, version 2. -// See the 'LICENSE' file for more details. -//----------------------------------------------------------------------------- -/// \file m_menu.h -/// \brief Menu widget stuff, selection and such - -#ifndef __X_MENU__ -#define __X_MENU__ - -#include "doomstat.h" // for NUMGAMETYPES -#include "d_event.h" -#include "command.h" -#include "f_finale.h" // for ttmode_enum -#include "i_threads.h" -#include "mserv.h" -#include "r_skins.h" // for SKINNAMESIZE - -// Compatibility with old-style named NiGHTS replay files. -#define OLDNREPLAYNAME - -// -// MENUS -// - -// If menu hierarchies go deeper, change this up to 5. -// Zero-based, inclusive. -#define NUMMENULEVELS 3 -#define MENUBITS 6 - -// Menu IDs sectioned by numeric places to signify hierarchy -/** - * IF YOU MODIFY THIS, MODIFY MENUTYPES_LIST[] IN dehacked.c TO MATCH. - */ -typedef enum -{ - MN_NONE, - - MN_MAIN, - - // Single Player - MN_SP_MAIN, - - MN_SP_LOAD, - MN_SP_PLAYER, - - MN_SP_LEVELSELECT, - MN_SP_LEVELSTATS, - - MN_SP_TIMEATTACK, - MN_SP_TIMEATTACK_LEVELSELECT, - MN_SP_GUESTREPLAY, - MN_SP_REPLAY, - MN_SP_GHOST, - - MN_SP_NIGHTSATTACK, - MN_SP_NIGHTS_LEVELSELECT, - MN_SP_NIGHTS_GUESTREPLAY, - MN_SP_NIGHTS_REPLAY, - MN_SP_NIGHTS_GHOST, - - MN_SP_MARATHON, - - // Multiplayer - MN_MP_MAIN, - MN_MP_SPLITSCREEN, // SplitServer - MN_MP_SERVER, - MN_MP_CONNECT, - MN_MP_ROOM, - MN_MP_PLAYERSETUP, // MP_PlayerSetupDef shared with SPLITSCREEN if #defined NONET - MN_MP_SERVER_OPTIONS, - - // Options - MN_OP_MAIN, - - MN_OP_P1CONTROLS, - MN_OP_CHANGECONTROLS, // OP_ChangeControlsDef shared with P2 - MN_OP_P1MOUSE, - MN_OP_P1JOYSTICK, - MN_OP_JOYSTICKSET, // OP_JoystickSetDef shared with P2 - MN_OP_P1CAMERA, - - MN_OP_P2CONTROLS, - MN_OP_P2MOUSE, - MN_OP_P2JOYSTICK, - MN_OP_P2CAMERA, - - MN_OP_PLAYSTYLE, - - MN_OP_VIDEO, - MN_OP_VIDEOMODE, - MN_OP_COLOR, - MN_OP_OPENGL, - MN_OP_OPENGL_LIGHTING, - - MN_OP_SOUND, - - MN_OP_SERVER, - MN_OP_MONITORTOGGLE, - - MN_OP_DATA, - MN_OP_ADDONS, - MN_OP_SCREENSHOTS, - MN_OP_ERASEDATA, - - // Extras - MN_SR_MAIN, - MN_SR_PANDORA, - MN_SR_LEVELSELECT, - MN_SR_UNLOCKCHECKLIST, - MN_SR_EMBLEMHINT, - MN_SR_PLAYER, - MN_SR_SOUNDTEST, - - // Addons (Part of MISC, but let's make it our own) - MN_AD_MAIN, - - // MISC - // MN_MESSAGE, - // MN_SPAUSE, - - // MN_MPAUSE, - // MN_SCRAMBLETEAM, - // MN_CHANGETEAM, - // MN_CHANGELEVEL, - - // MN_MAPAUSE, - // MN_HELP, - - MN_SPECIAL, - NUMMENUTYPES, -} menutype_t; // up to 63; MN_SPECIAL = 53 -#define MTREE2(a,b) (a | (b<data1; - if (ch >= KEY_MOUSE1 && menuactive) // If it's not a keyboard key, then don't allow it in the menus! + if (ch >= NUMKEYS && menuactive) // If it's not a keyboard key, then don't allow it in the menus! return false; - if (ch == KEY_F8 || ch == gamecontrol[0][gc_screenshot][0] || ch == gamecontrol[0][gc_screenshot][1]) // remappable F8 + if (ch == KEY_F8 /*|| ch == gamecontrol[0][gc_screenshot][0] || ch == gamecontrol[0][gc_screenshot][1]*/) // remappable F8 M_ScreenShot(); - else if (ch == KEY_F9 || ch == gamecontrol[0][gc_recordgif][0] || ch == gamecontrol[0][gc_recordgif][1]) // remappable F9 + else if (ch == KEY_F9 /*|| ch == gamecontrol[0][gc_recordgif][0] || ch == gamecontrol[0][gc_recordgif][1]*/) // remappable F9 ((moviemode) ? M_StopMovie : M_StartMovie)(); else return false; diff --git a/src/m_misc.h b/src/m_misc.h index d06d14397..7c12fe841 100644 --- a/src/m_misc.h +++ b/src/m_misc.h @@ -40,7 +40,26 @@ void M_SaveFrame(void); void M_StopMovie(void); // the file where game vars and settings are saved -#define CONFIGFILENAME "kartconfig.cfg" +#define CONFIGFILENAME "ringconfig.cfg" + +// The file where we'll save the last IPs we joined +#define IPLOGFILE "ringsavedips.txt" +#define IPLOGFILESEP ";" +#define NUMLOGIP 3 + +// Array where we'll store addresses to display for last servers joined +// {address, servame} +// 255 is long enough to store the text +extern char joinedIPlist[NUMLOGIP][2][255]; + +// Keep the address we're joining in mind until we've finished joining. +// Since we don't wanna add an IP address we aren't even sure worked out. +extern char joinedIP[255]; + +void M_InitJoinedIPArray(void); +void M_AddToJoinedIPs(char *address, char *servname); +void M_SaveJoinedIPs(void); +void M_LoadJoinedIPs(void); INT32 M_MapNumber(char first, char second); diff --git a/src/mserv.c b/src/mserv.c index 98dd0820b..2ea8e2159 100644 --- a/src/mserv.c +++ b/src/mserv.c @@ -20,9 +20,11 @@ #include "command.h" #include "i_threads.h" #include "mserv.h" -#include "m_menu.h" #include "z_zone.h" +// SRB2Kart +#include "k_menu.h" + #ifdef HAVE_DISCORDRPC #include "discord.h" #endif @@ -117,11 +119,11 @@ void AddMServCommands(void) static void WarnGUI (void) { #ifdef HAVE_THREADS - I_lock_mutex(&m_menu_mutex); + I_lock_mutex(&k_menu_mutex); #endif M_StartMessage(M_GetText("There was a problem connecting to\nthe Master Server\n\nCheck the console for details.\n"), NULL, MM_NOTHING); #ifdef HAVE_THREADS - I_unlock_mutex(m_menu_mutex); + I_unlock_mutex(k_menu_mutex); #endif } diff --git a/src/p_inter.c b/src/p_inter.c index b90593c2b..4f614b5ec 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -786,73 +786,76 @@ void P_CheckPointLimit(void) // Checks whether or not to end a race netgame. boolean P_CheckRacers(void) { - UINT8 i; - UINT8 numplayersingame = 0; - UINT8 numexiting = 0; - boolean eliminatelast = cv_karteliminatelast.value; - boolean everyonedone = true; - boolean eliminatebots = false; const boolean griefed = (spectateGriefed > 0); + boolean eliminateLast = cv_karteliminatelast.value; + boolean allHumansDone = true; + //boolean allBotsDone = true; + + UINT8 numPlaying = 0; + UINT8 numExiting = 0; + UINT8 numHumans = 0; + UINT8 numBots = 0; + + UINT8 i; + // Check if all the players in the race have finished. If so, end the level. for (i = 0; i < MAXPLAYERS; i++) { - if (!playeringame[i] || players[i].spectator || players[i].lives <= 0) // Not playing + if (!playeringame[i] || players[i].spectator || players[i].lives <= 0) { // Y'all aren't even playing continue; } - numplayersingame++; + numPlaying++; + + if (players[i].bot) + { + numBots++; + } + else + { + numHumans++; + } if (players[i].exiting || (players[i].pflags & PF_NOCONTEST)) { - numexiting++; + numExiting++; } else { if (players[i].bot) { - // Isn't a human, thus doesn't matter. (Sorry, robots.) - // Set this so that we can check for bots that need to get eliminated, though! - eliminatebots = true; - continue; + //allBotsDone = false; + } + else + { + allHumansDone = false; } - - everyonedone = false; } } - // If we returned here with bots left, then the last place bot may have a chance to finish the map and NOT get time over. - // Not that it affects anything, they didn't make the map take longer or even get any points from it. But... it's a bit inconsistent! - // So if there's any bots, we'll let the game skip this, continue onto calculating eliminatelast, THEN we return true anyway. - if (everyonedone && !eliminatebots) - { - // Everyone's finished, we're done here! - racecountdown = exitcountdown = 0; - return true; - } - - if (numplayersingame <= 1) + if (numPlaying <= 1) { // Never do this without enough players. - eliminatelast = false; + eliminateLast = false; } else { - if (grandprixinfo.gp == true) - { - // Always do this in GP - eliminatelast = true; - } - else if (griefed) + if (griefed == true) { // Don't do this if someone spectated - eliminatelast = false; + eliminateLast = false; + } + else if (grandprixinfo.gp == true) + { + // Always do this in GP + eliminateLast = true; } } - if (eliminatelast == true && (numexiting >= numplayersingame-1)) + if (eliminateLast == true && (numExiting >= numPlaying-1)) { // Everyone's done playing but one guy apparently. // Just kill everyone who is still playing. @@ -879,9 +882,10 @@ boolean P_CheckRacers(void) return true; } - if (everyonedone) + if (numHumans > 0 && allHumansDone == true) { - // See above: there might be bots that are still going, but all players are done, so we can exit now. + // There might be bots that are still going, + // but all of the humans are done, so we can exit now. racecountdown = exitcountdown = 0; return true; } @@ -889,22 +893,21 @@ boolean P_CheckRacers(void) // SO, we're not done playing. // Let's see if it's time to start the death counter! - if (!racecountdown) + if (racecountdown == 0) { // If the winners are all done, then start the death timer. - UINT8 winningpos = 1; + UINT8 winningPos = max(1, numPlaying / 2); - winningpos = max(1, numplayersingame/2); - if (numplayersingame % 2) // any remainder? + if (numPlaying % 2) // Any remainder? Then round up. { - winningpos++; + winningPos++; } - if (numexiting >= winningpos) + if (numExiting >= winningPos) { tic_t countdown = 30*TICRATE; // 30 seconds left to finish, get going! - if (netgame) + if (K_CanChangeRules() == true) { // Custom timer countdown = cv_countdowntime.value * TICRATE; @@ -914,13 +917,14 @@ boolean P_CheckRacers(void) } } - // We're still playing, but no one else is, so we need to reset spectator griefing. - if (numplayersingame <= 1) + // We're still playing, but no one else is, + // so we need to reset spectator griefing. + if (numPlaying <= 1) { spectateGriefed = 0; } - // Turns out we're still having a good time & playing the game, we didn't have to do anything :) + // We are still having fun and playing the game :) return false; } @@ -2012,7 +2016,7 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da source->player->invincibilitytimer += kinvextend; } - K_PlayHitEmSound(source, target); + K_TryHurtSoundExchange(target, source); K_BattleAwardHit(source->player, player, inflictor, takeBumpers); K_TakeBumpersFromPlayer(source->player, player, takeBumpers); @@ -2085,14 +2089,14 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da player->flashing = K_GetKartFlashing(player); } - P_PlayRinglossSound(player->mo); + P_PlayRinglossSound(target); if (ringburst > 0) { P_PlayerRingBurst(player, ringburst); } - K_PlayPainSound(player->mo); + K_PlayPainSound(target, source); if ((hardhit == true) || (cv_kartdebughuddrop.value && !modeattacking)) { diff --git a/src/p_map.c b/src/p_map.c index d667b0346..2c21a7718 100644 --- a/src/p_map.c +++ b/src/p_map.c @@ -2463,12 +2463,6 @@ fixed_t P_GetThingStepUp(mobj_t *thing) const fixed_t maxstepmove = P_BaseStepUp(); fixed_t maxstep = maxstepmove; - if (thing->type == MT_SKIM) - { - // Skim special (not needed for kart?) - return 0; - } - if (P_WaterStepUp(thing) == true) { // Add some extra stepmove when waterskipping @@ -2525,16 +2519,20 @@ boolean P_TryMove(mobj_t *thing, fixed_t x, fixed_t y, boolean allowdropoff) #endif do { - if (thing->flags & MF_NOCLIP) { + if (thing->flags & MF_NOCLIP) + { tryx = x; tryy = y; - } else { + } + else + { if (x-tryx > radius) tryx += radius; else if (x-tryx < -radius) tryx -= radius; else tryx = x; + if (y-tryy > radius) tryy += radius; else if (y-tryy < -radius) @@ -2603,7 +2601,8 @@ boolean P_TryMove(mobj_t *thing, fixed_t x, fixed_t y, boolean allowdropoff) return false; // mobj must lower itself to fit } } - else if (!(P_MobjTouchingSectorSpecial(thing, 1, 14, false))) // Step down + else if (thing->momz * P_MobjFlip(thing) <= 0 // Step down requires moving down. + && !(P_MobjTouchingSectorSpecial(thing, 1, 14, false))) { // If the floor difference is MAXSTEPMOVE or less, and the sector isn't Section1:14, ALWAYS // step down! Formerly required a Section1:13 sector for the full MAXSTEPMOVE, but no more. diff --git a/src/p_mobj.c b/src/p_mobj.c index a5a2eb801..b12430889 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -1132,6 +1132,12 @@ fixed_t P_GetMobjGravity(mobj_t *mo) { gravityadd = FixedMul(TUMBLEGRAVITY, gravityadd); } + + if (mo->player->fastfall != 0) + { + // Fast falling + gravityadd *= 4; + } } else { @@ -1761,7 +1767,9 @@ void P_XYMovement(mobj_t *mo) if (moved && oldslope && !(mo->flags & MF_NOCLIPHEIGHT)) { // Check to see if we ran off - if (oldslope != mo->standingslope) { // First, compare different slopes + if (oldslope != mo->standingslope) + { + // First, compare different slopes angle_t oldangle, newangle; angle_t moveangle = K_MomentumAngle(mo); @@ -1773,7 +1781,9 @@ void P_XYMovement(mobj_t *mo) newangle = 0; // Now compare the Zs of the different quantizations - if (oldangle-newangle > ANG30 && oldangle-newangle < ANGLE_180) { // Allow for a bit of sticking - this value can be adjusted later + if (oldangle-newangle > ANG30 && oldangle-newangle < ANGLE_180) + { + // Allow for a bit of sticking - this value can be adjusted later mo->standingslope = oldslope; P_SetPitchRollFromSlope(mo, mo->standingslope); P_SlopeLaunch(mo); @@ -1781,26 +1791,36 @@ void P_XYMovement(mobj_t *mo) //CONS_Printf("launched off of slope - "); } - /*CONS_Printf("old angle %f - new angle %f = %f\n", - FIXED_TO_FLOAT(AngleFixed(oldangle)), - FIXED_TO_FLOAT(AngleFixed(newangle)), - FIXED_TO_FLOAT(AngleFixed(oldangle-newangle)) - );*/ - // Sryder 2018-11-26: Don't launch here if it's a slope without physics, we stick to those like glue anyway - } else if (predictedz-mo->z > abs(slopemom.z/2) - && !(mo->standingslope->flags & SL_NOPHYSICS)) { // Now check if we were supposed to stick to this slope + /* + CONS_Printf("old angle %f - new angle %f = %f\n", + FIXED_TO_FLOAT(AngleFixed(oldangle)), + FIXED_TO_FLOAT(AngleFixed(newangle)), + FIXED_TO_FLOAT(AngleFixed(oldangle-newangle)) + ); + */ + + } + else if (predictedz - mo->z > abs(slopemom.z / 2)) + { + // Now check if we were supposed to stick to this slope //CONS_Printf("%d-%d > %d\n", (predictedz), (mo->z), (slopemom.z/2)); P_SlopeLaunch(mo); } - } else if (moved && mo->standingslope && predictedz) { + } + else if (moved && mo->standingslope && predictedz) + { angle_t moveangle = K_MomentumAngle(mo); angle_t newangle = FixedMul((signed)mo->standingslope->zangle, FINECOSINE((moveangle - mo->standingslope->xydirection) >> ANGLETOFINESHIFT)); - /*CONS_Printf("flat to angle %f - predicted z of %f\n", - FIXED_TO_FLOAT(AngleFixed(ANGLE_MAX-newangle)), - FIXED_TO_FLOAT(predictedz) - );*/ - if (ANGLE_MAX-newangle > ANG30 && newangle > ANGLE_180) { + /* + CONS_Printf("flat to angle %f - predicted z of %f\n", + FIXED_TO_FLOAT(AngleFixed(ANGLE_MAX-newangle)), + FIXED_TO_FLOAT(predictedz) + ); + */ + + if (ANGLE_MAX-newangle > ANG30 && newangle > ANGLE_180) + { mo->momz = P_MobjFlip(mo)*FRACUNIT/2; mo->z = predictedz + P_MobjFlip(mo); mo->standingslope = NULL; @@ -3120,12 +3140,6 @@ void P_MobjCheckWater(mobj_t *mobj) } } - if (mobj->watertop > top2) - mobj->watertop = top2; - - if (mobj->waterbottom < bot2) - mobj->waterbottom = bot2; - // Spectators and dead players don't get to do any of the things after this. if (p && (p->spectator || p->playerstate != PST_LIVE)) { @@ -3133,8 +3147,10 @@ void P_MobjCheckWater(mobj_t *mobj) } // The rest of this code only executes on a water state change. - if (!!(mobj->eflags & MFE_UNDERWATER) == wasinwater) + if (waterwasnotset || !!(mobj->eflags & MFE_UNDERWATER) == wasinwater) + { return; + } if (p && !p->waterskip && p->curshield != KSHIELD_BUBBLE && wasinwater) @@ -3171,62 +3187,6 @@ void P_MobjCheckWater(mobj_t *mobj) diff = -diff; } - // Time to spawn the bubbles! - { - INT32 i; - INT32 bubblecount; - UINT8 prandom[4]; - mobj_t *bubble; - mobjtype_t bubbletype; - - if (mobj->eflags & MFE_GOOWATER || wasingoo) - S_StartSound(mobj, sfx_ghit); - else if (mobj->eflags & MFE_TOUCHLAVA) - S_StartSound(mobj, sfx_splash); - else - S_StartSound(mobj, sfx_splish); // And make a sound! - - bubblecount = FixedDiv(abs(mobj->momz), mobj->scale)>>(FRACBITS-1); - // Max bubble count - if (bubblecount > 128) - bubblecount = 128; - - // Create tons of bubbles - for (i = 0; i < bubblecount; i++) - { - // P_RandomByte()s are called individually to allow consistency - // across various compilers, since the order of function calls - // in C is not part of the ANSI specification. - prandom[0] = P_RandomByte(); - prandom[1] = P_RandomByte(); - prandom[2] = P_RandomByte(); - prandom[3] = P_RandomByte(); - - bubbletype = MT_SMALLBUBBLE; - if (!(prandom[0] & 0x3)) // medium bubble chance up to 64 from 32 - bubbletype = MT_MEDIUMBUBBLE; - - bubble = P_SpawnMobj( - mobj->x + FixedMul((prandom[1]<<(FRACBITS-3)) * (prandom[0]&0x80 ? 1 : -1), mobj->scale), - mobj->y + FixedMul((prandom[2]<<(FRACBITS-3)) * (prandom[0]&0x40 ? 1 : -1), mobj->scale), - mobj->z + FixedMul((prandom[3]<<(FRACBITS-2)), mobj->scale), bubbletype); - - if (bubble) - { - if (P_MobjFlip(mobj)*mobj->momz < 0) - bubble->momz = mobj->momz >> 4; - else - bubble->momz = 0; - - bubble->destscale = mobj->scale; - P_SetScale(bubble, mobj->scale); - } - } - } - - if (waterwasnotset) - return; - // Check to make sure you didn't just cross into a sector to jump out of // that has shallower water than the block you were originally in. if (diff <= (height >> 1)) @@ -3328,6 +3288,59 @@ void P_MobjCheckWater(mobj_t *mobj) P_SetScale(splish, mobj->scale); } } + + // Time to spawn the bubbles! + { + INT32 i; + INT32 bubblecount; + UINT8 prandom[4]; + mobj_t *bubble; + mobjtype_t bubbletype; + + if (mobj->eflags & MFE_GOOWATER || wasingoo) + S_StartSound(mobj, sfx_ghit); + else if (mobj->eflags & MFE_TOUCHLAVA) + S_StartSound(mobj, sfx_splash); + else + S_StartSound(mobj, sfx_splish); // And make a sound! + + bubblecount = FixedDiv(abs(mobj->momz), mobj->scale) >> (FRACBITS-1); + // Max bubble count + if (bubblecount > 128) + bubblecount = 128; + + // Create tons of bubbles + for (i = 0; i < bubblecount; i++) + { + // P_RandomByte()s are called individually to allow consistency + // across various compilers, since the order of function calls + // in C is not part of the ANSI specification. + prandom[0] = P_RandomByte(); + prandom[1] = P_RandomByte(); + prandom[2] = P_RandomByte(); + prandom[3] = P_RandomByte(); + + bubbletype = MT_SMALLBUBBLE; + if (!(prandom[0] & 0x3)) // medium bubble chance up to 64 from 32 + bubbletype = MT_MEDIUMBUBBLE; + + bubble = P_SpawnMobj( + mobj->x + FixedMul((prandom[1]<<(FRACBITS-3)) * (prandom[0]&0x80 ? 1 : -1), mobj->scale), + mobj->y + FixedMul((prandom[2]<<(FRACBITS-3)) * (prandom[0]&0x40 ? 1 : -1), mobj->scale), + mobj->z + FixedMul((prandom[3]<<(FRACBITS-2)), mobj->scale), bubbletype); + + if (bubble) + { + if (P_MobjFlip(mobj)*mobj->momz < 0) + bubble->momz = mobj->momz >> 4; + else + bubble->momz = 0; + + bubble->destscale = mobj->scale; + P_SetScale(bubble, mobj->scale); + } + } + } } } @@ -7104,17 +7117,17 @@ static boolean P_MobjRegularThink(mobj_t *mobj) if (mobj->fuse <= 16) { - mobj->color = SKINCOLOR_KETCHUP; + mobj->color = SKINCOLOR_GOLD; /* don't draw papersprite frames after blue boost */ mobj->renderflags ^= RF_DONTDRAW; } else if (mobj->fuse <= 32) - mobj->color = SKINCOLOR_SAPPHIRE; + mobj->color = SKINCOLOR_KETCHUP; else if (mobj->fuse <= 48) - mobj->color = SKINCOLOR_PURPLE; + mobj->color = SKINCOLOR_SAPPHIRE; else if (mobj->fuse > 48) mobj->color = K_RainbowColor( - (SKINCOLOR_PURPLE - SKINCOLOR_PINK) // Smoothly transition into the other state + (SKINCOLOR_SAPPHIRE - SKINCOLOR_PINK) // Smoothly transition into the other state + ((mobj->fuse - 32) * 2) // Make the color flashing slow down while it runs out ); @@ -7129,14 +7142,14 @@ static boolean P_MobjRegularThink(mobj_t *mobj) } break; - case 3:/* purple boost */ - if ((mobj->fuse == 32)/* to blue*/ - || (mobj->fuse == 16))/* to red*/ + case 3:/* blue boost */ + if ((mobj->fuse == 32)/* to red*/ + || (mobj->fuse == 16))/* to yellow*/ K_SpawnDriftBoostClip(mobj->target->player); break; - case 2:/* blue boost */ - if (mobj->fuse == 16)/* to red*/ + case 2:/* red boost */ + if (mobj->fuse == 16)/* to yellow*/ K_SpawnDriftBoostClip(mobj->target->player); break; diff --git a/src/p_saveg.c b/src/p_saveg.c index 5569f24d5..72101fc9c 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -280,6 +280,8 @@ static void P_NetArchivePlayers(void) WRITEFIXED(save_p, players[i].spindashspeed); WRITEUINT8(save_p, players[i].spindashboost); + WRITEFIXED(save_p, players[i].fastfall); + WRITEUINT8(save_p, players[i].numboosts); WRITEFIXED(save_p, players[i].boostpower); WRITEFIXED(save_p, players[i].speedboost); @@ -337,8 +339,8 @@ static void P_NetArchivePlayers(void) WRITESINT8(save_p, players[i].lastjawztarget); WRITEUINT8(save_p, players[i].jawztargetdelay); - WRITEUINT8(save_p, players[i].confirmInflictor); - WRITEUINT8(save_p, players[i].confirmInflictorDelay); + WRITEUINT8(save_p, players[i].confirmVictim); + WRITEUINT8(save_p, players[i].confirmVictimDelay); WRITEUINT8(save_p, players[i].trickpanel); WRITEUINT8(save_p, players[i].tricktime); @@ -565,6 +567,8 @@ static void P_NetUnArchivePlayers(void) players[i].spindashspeed = READFIXED(save_p); players[i].spindashboost = READUINT8(save_p); + players[i].fastfall = READFIXED(save_p); + players[i].numboosts = READUINT8(save_p); players[i].boostpower = READFIXED(save_p); players[i].speedboost = READFIXED(save_p); @@ -622,8 +626,8 @@ static void P_NetUnArchivePlayers(void) players[i].lastjawztarget = READSINT8(save_p); players[i].jawztargetdelay = READUINT8(save_p); - players[i].confirmInflictor = READUINT8(save_p); - players[i].confirmInflictorDelay = READUINT8(save_p); + players[i].confirmVictim = READUINT8(save_p); + players[i].confirmVictimDelay = READUINT8(save_p); players[i].trickpanel = READUINT8(save_p); players[i].tricktime = READUINT8(save_p); diff --git a/src/p_setup.c b/src/p_setup.c index 43f97e666..c39d46a84 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -96,6 +96,7 @@ #include "k_boss.h" #include "k_terrain.h" // TRF_TRIPWIRE #include "k_brightmap.h" +#include "k_terrain.h" // TRF_TRIPWIRE #include "k_director.h" // K_InitDirector // Replay names have time @@ -3882,7 +3883,7 @@ static void P_ResetSpawnpoints(void) static void P_LoadRecordGhosts(void) { - // see also m_menu.c's Nextmap_OnChange + // see also k_menu.c's Nextmap_OnChange const size_t glen = strlen(srb2home)+1+strlen("media")+1+strlen("replay")+1+strlen(timeattackfolder)+1+strlen("MAPXX")+1; char *gpath = malloc(glen); INT32 i; diff --git a/src/p_slopes.c b/src/p_slopes.c index 3c124a550..1347a8759 100644 --- a/src/p_slopes.c +++ b/src/p_slopes.c @@ -894,6 +894,61 @@ fixed_t P_GetLightZAt(const lightlist_t *light, fixed_t x, fixed_t y) return light->slope ? P_GetSlopeZAt(light->slope, x, y) : light->height; } +// Returns true if we should run slope physics code on an object. +boolean P_CanApplySlopePhysics(mobj_t *mo, pslope_t *slope) +{ + if (slope == NULL || mo == NULL || P_MobjWasRemoved(mo) == true) + { + // Invalid input. + return false; + } + + if (slope->flags & SL_NOPHYSICS) + { + // Physics are turned off. + return false; + } + + if (slope->normal.x == 0 && slope->normal.y == 0) + { + // Flat slope? No such thing, man. No such thing. + return false; + } + + if (mo->player != NULL) + { + if (K_PlayerEBrake(mo->player) == true) + { + // Spindash negates slopes. + return false; + } + } + + // We can do slope physics. + return true; +} + +// Returns true if we should run slope launch code on an object. +boolean P_CanApplySlopeLaunch(mobj_t *mo, pslope_t *slope) +{ + if (slope == NULL || mo == NULL || P_MobjWasRemoved(mo) == true) + { + // Invalid input. + return false; + } + + // No physics slopes are fine to launch off of. + + if (slope->normal.x == 0 && slope->normal.y == 0) + { + // Flat slope? No such thing, man. No such thing. + return false; + } + + // We can do slope launching. + return true; +} + // // P_QuantizeMomentumToSlope // @@ -923,42 +978,23 @@ void P_ReverseQuantizeMomentumToSlope(vector3_t *momentum, pslope_t *slope) slope->zangle = InvAngle(slope->zangle); } -// SRB2Kart: This fixes all slope-based jumps for different scales in Kart automatically without map tweaking. -// However, they will always feel off every single time... see for yourself: https://cdn.discordapp.com/attachments/270211093761097728/484924392128774165/kart0181.gif -//#define GROWNEVERMISSES - // // P_SlopeLaunch // // Handles slope ejection for objects void P_SlopeLaunch(mobj_t *mo) { - if (!(mo->standingslope->flags & SL_NOPHYSICS) // If there's physics, time for launching. - && (mo->standingslope->normal.x != 0 - || mo->standingslope->normal.y != 0)) + if (P_CanApplySlopeLaunch(mo, mo->standingslope) == true) // If there's physics, time for launching. { - // Double the pre-rotation Z, then halve the post-rotation Z. This reduces the - // vertical launch given from slopes while increasing the horizontal launch - // given. Good for SRB2's gravity and horizontal speeds. vector3_t slopemom; slopemom.x = mo->momx; slopemom.y = mo->momy; slopemom.z = mo->momz; P_QuantizeMomentumToSlope(&slopemom, mo->standingslope); -#ifdef GROWNEVERMISSES - { - const fixed_t xyscale = mapobjectscale + (mapobjectscale - mo->scale); - const fixed_t zscale = mapobjectscale + (mapobjectscale - mo->scale); - mo->momx = FixedMul(slopemom.x, xyscale); - mo->momy = FixedMul(slopemom.y, xyscale); - mo->momz = FixedMul(slopemom.z, zscale); - } -#else mo->momx = slopemom.x; mo->momy = slopemom.y; mo->momz = slopemom.z; -#endif mo->eflags |= MFE_SLOPELAUNCHED; } @@ -984,14 +1020,19 @@ fixed_t P_GetWallTransferMomZ(mobj_t *mo, pslope_t *slope) vector3_t slopemom, axis; angle_t ang; - if (mo->standingslope->flags & SL_NOPHYSICS) - return 0; + if (P_CanApplySlopeLaunch(mo, mo->standingslope) == false) + { + return false; + } // If there's physics, time for launching. // Doesn't kill the vertical momentum as much as P_SlopeLaunch does. ang = slope->zangle + ANG15*((slope->zangle > 0) ? 1 : -1); if (ang > ANGLE_90 && ang < ANGLE_180) - ang = ((slope->zangle > 0) ? ANGLE_90 : InvAngle(ANGLE_90)); // hard cap of directly upwards + { + // hard cap of directly upwards + ang = ((slope->zangle > 0) ? ANGLE_90 : InvAngle(ANGLE_90)); + } slopemom.x = mo->momx; slopemom.y = mo->momy; @@ -1010,13 +1051,16 @@ fixed_t P_GetWallTransferMomZ(mobj_t *mo, pslope_t *slope) void P_HandleSlopeLanding(mobj_t *thing, pslope_t *slope) { vector3_t mom; // Ditto. - if (slope->flags & SL_NOPHYSICS || (slope->normal.x == 0 && slope->normal.y == 0)) { // No physics, no need to make anything complicated. + + if (P_CanApplySlopePhysics(thing, slope) == false) // No physics, no need to make anything complicated. + { if (P_MobjFlip(thing)*(thing->momz) < 0) // falling, land on slope { thing->standingslope = slope; P_SetPitchRollFromSlope(thing, slope); thing->momz = -P_MobjFlip(thing); } + return; } @@ -1026,7 +1070,9 @@ void P_HandleSlopeLanding(mobj_t *thing, pslope_t *slope) P_ReverseQuantizeMomentumToSlope(&mom, slope); - if (P_MobjFlip(thing)*mom.z < 0) { // falling, land on slope + if (P_MobjFlip(thing)*mom.z < 0) + { + // falling, land on slope thing->momx = mom.x; thing->momy = mom.y; thing->standingslope = slope; @@ -1041,27 +1087,29 @@ void P_ButteredSlope(mobj_t *mo) { fixed_t thrust; - if (!mo->standingslope) - return; - - if (mo->standingslope->flags & SL_NOPHYSICS) - return; // No physics, no butter. - if (mo->flags & (MF_NOCLIPHEIGHT|MF_NOGRAVITY)) return; // don't slide down slopes if you can't touch them or you're not affected by gravity - if (mo->player) { - // SRB2Kart - spindash negates slopes - if (K_PlayerEBrake(mo->player)) - return; + if (P_CanApplySlopePhysics(mo, mo->standingslope) == false) + return; // No physics, no butter. - // Changed in kart to only not apply physics on very slight slopes (I think about 4 degree angles) + if (mo->player != NULL) + { if (abs(mo->standingslope->zdelta) < FRACUNIT/21) - return; // Don't slide on non-steep slopes + { + // Don't slide on non-steep slopes. + // Changed in Ring Racers to only not apply physics on very slight slopes. + // (I think about 4 degree angles.) + return; + } - // This only means you can be stopped on slopes that aren't steeper than 45 degrees - if (abs(mo->standingslope->zdelta) < FRACUNIT/2 && !(mo->player->rmomx || mo->player->rmomy)) - return; // Allow the player to stand still on slopes below a certain steepness + if (abs(mo->standingslope->zdelta) < FRACUNIT/2 + && !(mo->player->rmomx || mo->player->rmomy)) + { + // Allow the player to stand still on slopes below a certain steepness. + // 45 degree angle steep, to be exact. + return; + } } thrust = FINESINE(mo->standingslope->zangle>>ANGLETOFINESHIFT) * 5 / 4 * (mo->eflags & MFE_VERTICALFLIP ? 1 : -1); diff --git a/src/p_slopes.h b/src/p_slopes.h index a72ca1776..5eb4f83bb 100644 --- a/src/p_slopes.h +++ b/src/p_slopes.h @@ -82,6 +82,8 @@ fixed_t P_GetFFloorBottomZAt(const ffloor_t *ffloor, fixed_t x, fixed_t y); fixed_t P_GetLightZAt(const lightlist_t *light, fixed_t x, fixed_t y); // Lots of physics-based bullshit +boolean P_CanApplySlopePhysics(mobj_t *mo, pslope_t *slope); +boolean P_CanApplySlopeLaunch(mobj_t *mo, pslope_t *slope); void P_QuantizeMomentumToSlope(vector3_t *momentum, pslope_t *slope); void P_ReverseQuantizeMomentumToSlope(vector3_t *momentum, pslope_t *slope); void P_SlopeLaunch(mobj_t *mo); diff --git a/src/p_tick.c b/src/p_tick.c index 843461db6..ce45f48f3 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -721,7 +721,7 @@ void P_Ticker(boolean run) G_WriteAllGhostTics(); if (cv_recordmultiplayerdemos.value && (demo.savemode == DSM_NOTSAVING || demo.savemode == DSM_WILLAUTOSAVE)) - if (demo.savebutton && demo.savebutton + 3*TICRATE < leveltime && PlayerInputDown(1, gc_lookback)) + if (demo.savebutton && demo.savebutton + 3*TICRATE < leveltime && G_PlayerInputDown(0, gc_y, 0)) demo.savemode = DSM_TITLEENTRY; } else if (demo.playback) // Use Ghost data for consistency checks. diff --git a/src/p_user.c b/src/p_user.c index 4030232f7..d634f7ad1 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -183,7 +183,7 @@ fixed_t P_ReturnThrustY(mobj_t *mo, angle_t angle, fixed_t move) boolean P_AutoPause(void) { // Don't pause even on menu-up or focus-lost in netgames or record attack - if (netgame || modeattacking || gamestate == GS_TITLESCREEN) + if (netgame || modeattacking || gamestate == GS_TITLESCREEN || gamestate == GS_MENU || con_startup) return false; return ((menuactive && !demo.playback) || ( window_notinfocus && cv_pauseifunfocused.value )); @@ -473,6 +473,7 @@ void P_ResetPlayer(player_t *player) //player->drift = player->driftcharge = 0; player->trickpanel = 0; player->glanceDir = 0; + player->fastfall = 0; } // @@ -1099,7 +1100,7 @@ boolean P_IsMachineLocalPlayer(player_t *player) return false; } - for (i = 0; i <= r_splitscreen; i++) + for (i = 0; i <= splitscreen; i++) { if (player == &players[g_localplayers[i]]) return true; @@ -1905,17 +1906,27 @@ static void P_3dMovement(player_t *player) } if ((totalthrust.x || totalthrust.y) - && player->mo->standingslope && (!(player->mo->standingslope->flags & SL_NOPHYSICS)) && abs(player->mo->standingslope->zdelta) > FRACUNIT/2) { + && player->mo->standingslope != NULL + && (!(player->mo->standingslope->flags & SL_NOPHYSICS)) + && abs(player->mo->standingslope->zdelta) > FRACUNIT/2) + { // Factor thrust to slope, but only for the part pushing up it! // The rest is unaffected. - angle_t thrustangle = R_PointToAngle2(0, 0, totalthrust.x, totalthrust.y)-player->mo->standingslope->xydirection; + angle_t thrustangle = R_PointToAngle2(0, 0, totalthrust.x, totalthrust.y) - player->mo->standingslope->xydirection; - if (player->mo->standingslope->zdelta < 0) { // Direction goes down, so thrustangle needs to face toward - if (thrustangle < ANGLE_90 || thrustangle > ANGLE_270) { + if (player->mo->standingslope->zdelta < 0) + { + // Direction goes down, so thrustangle needs to face toward + if (thrustangle < ANGLE_90 || thrustangle > ANGLE_270) + { P_QuantizeMomentumToSlope(&totalthrust, player->mo->standingslope); } - } else { // Direction goes up, so thrustangle needs to face away - if (thrustangle > ANGLE_90 && thrustangle < ANGLE_270) { + } + else + { + // Direction goes up, so thrustangle needs to face away + if (thrustangle > ANGLE_90 && thrustangle < ANGLE_270) + { P_QuantizeMomentumToSlope(&totalthrust, player->mo->standingslope); } } @@ -2786,12 +2797,14 @@ void P_InitCameraCmd(void) static ticcmd_t *P_CameraCmd(camera_t *cam) { + /* INT32 forward, axis; //i // these ones used for multiple conditions boolean turnleft, turnright, mouseaiming; boolean invertmouse, lookaxis, usejoystick, kbl; INT32 player_invert; INT32 screen_invert; + */ ticcmd_t *cmd = &cameracmd; (void)cam; @@ -2799,6 +2812,7 @@ static ticcmd_t *P_CameraCmd(camera_t *cam) if (!demo.playback) return cmd; // empty cmd, no. + /* kbl = democam.keyboardlook; G_CopyTiccmd(cmd, I_BaseTiccmd(), 1); // empty, or external driver @@ -2843,7 +2857,7 @@ static ticcmd_t *P_CameraCmd(camera_t *cam) cmd->turning -= (mousex * 8) * (encoremode ? -1 : 1); axis = PlayerJoyAxis(1, AXISMOVE); - if (PlayerInputDown(1, gc_accelerate) || (usejoystick && axis > 0)) + if (PlayerInputDown(1, gc_a) || (usejoystick && axis > 0)) cmd->buttons |= BT_ACCELERATE; axis = PlayerJoyAxis(1, AXISBRAKE); if (PlayerInputDown(1, gc_brake) || (usejoystick && axis > 0)) @@ -2889,8 +2903,6 @@ static ticcmd_t *P_CameraCmd(camera_t *cam) if (PlayerInputDown(1, gc_centerview)) // No need to put a spectator limit on this one though :V cmd->aiming = 0; - mousex = mousey = mlooky = 0; - cmd->forwardmove += (SINT8)forward; if (cmd->forwardmove > MAXPLMOVE) @@ -2904,6 +2916,7 @@ static ticcmd_t *P_CameraCmd(camera_t *cam) cmd->turning = -KART_FULLTURN; democam.keyboardlook = kbl; + */ return cmd; } diff --git a/src/r_fps.c b/src/r_fps.c index 32d24ba10..2c01cc571 100644 --- a/src/r_fps.c +++ b/src/r_fps.c @@ -709,7 +709,7 @@ void R_RemoveMobjInterpolator(mobj_t *mobj) if (interpolated_mobjs_len == 0) return; - for (i = 0; i < interpolated_mobjs_len - 1; i++) + for (i = 0; i < interpolated_mobjs_len; i++) { if (interpolated_mobjs[i] == mobj) { diff --git a/src/r_main.c b/src/r_main.c index 7d7e17421..58c65427d 100644 --- a/src/r_main.c +++ b/src/r_main.c @@ -24,7 +24,7 @@ #include "p_local.h" #include "keys.h" #include "i_video.h" -#include "m_menu.h" +#include "k_menu.h" #include "am_map.h" #include "d_main.h" #include "v_video.h" diff --git a/src/r_skins.h b/src/r_skins.h index cffd54da8..ded45a71f 100644 --- a/src/r_skins.h +++ b/src/r_skins.h @@ -22,7 +22,6 @@ #include "r_defs.h" // spritedef_t /// Defaults -#define SKINNAMESIZE 16 #define SKINRIVALS 3 // should be all lowercase!! S_SKIN processing does a strlwr #define DEFAULTSKIN "eggman" diff --git a/src/r_things.c b/src/r_things.c index cc5694dff..d0fd57600 100644 --- a/src/r_things.c +++ b/src/r_things.c @@ -18,7 +18,7 @@ #include "st_stuff.h" #include "w_wad.h" #include "z_zone.h" -#include "m_menu.h" // character select +#include "k_menu.h" // character select #include "m_misc.h" #include "info.h" // spr2names #include "i_video.h" // rendermode diff --git a/src/s_sound.c b/src/s_sound.c index 9d3456f59..90fd81679 100644 --- a/src/s_sound.c +++ b/src/s_sound.c @@ -2505,9 +2505,9 @@ void GameDigiMusic_OnChange(void) { P_RestoreMusic(&players[consoleplayer]); } - else if (S_MusicExists("_title")) + else if (S_MusicExists("menu")) { - S_ChangeMusicInternal("_title", looptitle); + S_ChangeMusicInternal("menu", looptitle); } } else diff --git a/src/screen.c b/src/screen.c index 90e63d40f..5d6b59ee8 100644 --- a/src/screen.c +++ b/src/screen.c @@ -405,12 +405,6 @@ void SCR_Recalc(void) // vid.recalc lasts only for the next refresh... con_recalc = true; am_recalc = true; - -#ifdef HWRENDER - // Shoot! The screen texture was flushed! - if ((rendermode == render_opengl) && (gamestate == GS_INTERMISSION)) - usebuffer = false; -#endif } // Check for screen cmd-line parms: to force a resolution. diff --git a/src/sdl/i_system.c b/src/sdl/i_system.c index d1006fc06..da63294d9 100644 --- a/src/sdl/i_system.c +++ b/src/sdl/i_system.c @@ -186,6 +186,8 @@ static char returnWadPath[256]; #include "../r_main.h" // Frame interpolation/uncapped #include "../r_fps.h" +#include "../k_menu.h" + #ifdef MAC_ALERT #include "macosx/mac_alert.h" #endif @@ -198,6 +200,48 @@ static char returnWadPath[256]; #include "../byteptr.h" #endif +void I_StoreExJoystick(SDL_GameController *dev) +{ + // ExJoystick is a massive hack to avoid needing to completely + // rewrite pretty much all of the controller support from scratch... + + // Used in favor of most instances of SDL_GameControllerClose. + // If a joystick would've been discarded, then save it in an array, + // because we want it have it for the joystick input screen. + + int index = 0; + + if (dev == NULL) + { + // No joystick? + return; + } + + index = SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(dev)); + + if (index >= MAXGAMEPADS || index < 0) + { + // Not enough space to save this joystick, completely discard. + SDL_GameControllerClose(dev); + return; + } + + if (ExJoystick[index] == dev) + { + // No need to do anything else. + return; + } + + if (ExJoystick[index] != NULL) + { + // Discard joystick in the old slot. + SDL_GameControllerClose(ExJoystick[index]); + } + + // Keep for safe-keeping. + ExJoystick[index] = dev; +} + /** \brief The JoyReset function \param JoySet Joystick info to reset @@ -208,7 +252,7 @@ static void JoyReset(SDLJoyInfo_t *JoySet) { if (JoySet->dev) { - SDL_JoystickClose(JoySet->dev); + I_StoreExJoystick(JoySet->dev); } JoySet->dev = NULL; JoySet->oldjoy = -1; @@ -223,6 +267,7 @@ static INT32 joystick_started[MAXSPLITSCREENPLAYERS] = {0,0,0,0}; /** \brief SDL info about joystick 1 */ SDLJoyInfo_t JoyInfo[MAXSPLITSCREENPLAYERS]; +SDL_GameController *ExJoystick[MAXGAMEPADS]; SDL_bool consolevent = SDL_FALSE; SDL_bool framebuffer = SDL_FALSE; @@ -536,7 +581,7 @@ static void I_StartupConsole(void) void I_GetConsoleEvents(void) { // we use this when sending back commands - event_t ev = {0,0,0,0}; + event_t ev = {0}; char key = 0; ssize_t d; @@ -944,29 +989,16 @@ void I_JoyScale4(void) JoyInfo[3].scale = Joystick[3].bGamepadStyle?1:cv_joyscale[1].value; } -// Cheat to get the device index for a joystick handle -INT32 I_GetJoystickDeviceIndex(SDL_Joystick *dev) +// Cheat to get the device index for a game controller handle +INT32 I_GetJoystickDeviceIndex(SDL_GameController *dev) { - INT32 i, count = SDL_NumJoysticks(); + SDL_Joystick *joystick = NULL; - for (i = 0; dev && i < count; i++) + joystick = SDL_GameControllerGetJoystick(dev); + + if (joystick) { - SDL_Joystick *test = SDL_JoystickOpen(i); - if (test && test == dev) - return i; - else - { - UINT8 j; - - for (j = 0; j < MAXSPLITSCREENPLAYERS; j++) - { - if (JoyInfo[j].dev == test) - break; - } - - if (j == MAXSPLITSCREENPLAYERS) - SDL_JoystickClose(test); - } + return SDL_JoystickInstanceID(joystick); } return -1; @@ -981,6 +1013,7 @@ void I_UpdateJoystickDeviceIndex(UINT8 player) if (JoyInfo[player].dev) { cv_usejoystick[player].value = I_GetJoystickDeviceIndex(JoyInfo[player].dev) + 1; + CONS_Printf("I_UpdateJoystickDeviceIndex: Device for %d set to %d\n", player, cv_usejoystick[player].value); } else { @@ -1004,6 +1037,7 @@ void I_UpdateJoystickDeviceIndex(UINT8 player) { // We DID make it through the whole loop, so we can use this one! cv_usejoystick[player].value = value; + CONS_Printf("I_UpdateJoystickDeviceIndex: Device for %d set to %d\n", player, cv_usejoystick[player].value); break; } } @@ -1013,6 +1047,7 @@ void I_UpdateJoystickDeviceIndex(UINT8 player) // We DID NOT make it through the whole loop, so we can't assign this joystick to anything. // When you try your best, but you don't succeed... cv_usejoystick[player].value = 0; + CONS_Printf("I_UpdateJoystickDeviceIndex: Device for %d set to %d\n", player, 0); } } } @@ -1032,14 +1067,6 @@ void I_UpdateJoystickDeviceIndices(UINT8 excludePlayer) } } -/** \brief Joystick buttons states -*/ -static UINT64 lastjoybuttons[MAXSPLITSCREENPLAYERS] = {0,0,0,0}; - -/** \brief Joystick hats state -*/ -static UINT64 lastjoyhats[MAXSPLITSCREENPLAYERS] = {0,0,0,0}; - /** \brief Shuts down joystick \return void */ @@ -1047,29 +1074,22 @@ void I_ShutdownJoystick(UINT8 index) { INT32 i; event_t event; - event.type=ev_keyup; + + event.device = I_GetJoystickDeviceIndex(JoyInfo[index].dev); + event.type = ev_keyup; event.data2 = 0; event.data3 = 0; - lastjoybuttons[index] = lastjoyhats[index] = 0; - // emulate the up of all joystick buttons - for (i=0;i= 0; i--) - { - joybuttons <<= 1; - if (SDL_JoystickGetButton(JoyInfo[index].dev,i)) - joybuttons |= 1; - } - - if (joybuttons != lastjoybuttons[index]) - { - INT64 j = 1; // keep only bits that changed since last time - INT64 newbuttons = joybuttons ^ lastjoybuttons[index]; - lastjoybuttons[index] = joybuttons; - - for (i = 0; i < JOYBUTTONS; i++, j <<= 1) - { - if (newbuttons & j) // button changed state? - { - if (joybuttons & j) - event.type = ev_keydown; - else - event.type = ev_keyup; - event.data1 = KEY_JOY1 + i; - D_PostEvent(&event); - } - } - } -#endif - - for (i = JoyInfo[index].hats - 1; i >= 0; i--) - { - Uint8 hat = SDL_JoystickGetHat(JoyInfo[index].dev, i); - - if (hat & SDL_HAT_UP ) joyhats|=(UINT64)0x1<<(0 + 4*i); - if (hat & SDL_HAT_DOWN ) joyhats|=(UINT64)0x1<<(1 + 4*i); - if (hat & SDL_HAT_LEFT ) joyhats|=(UINT64)0x1<<(2 + 4*i); - if (hat & SDL_HAT_RIGHT) joyhats|=(UINT64)0x1<<(3 + 4*i); - } - - if (joyhats != lastjoyhats[index]) - { - INT64 j = 1; // keep only bits that changed since last time - INT64 newhats = joyhats ^ lastjoyhats[index]; - lastjoyhats[index] = joyhats; - - for (i = 0; i < JOYHATS*4; i++, j <<= 1) - { - if (newhats & j) // hat changed state? - { - if (joyhats & j) - event.type = ev_keydown; - else - event.type = ev_keyup; - event.data1 = KEY_HAT1 + i; - D_PostEvent(&event); - } - } - } - -#if 0 - // send joystick axis positions - event.type = ev_joystick; - - for (i = JOYAXISSET - 1; i >= 0; i--) - { - event.data1 = i; - if (i*2 + 1 <= JoyInfo[index].axises) - axisx = SDL_JoystickGetAxis(JoyInfo[index].dev, i*2 + 0); - else axisx = 0; - if (i*2 + 2 <= JoyInfo[index].axises) - axisy = SDL_JoystickGetAxis(JoyInfo[index].dev, i*2 + 1); - else axisy = 0; - - - // -32768 to 32767 - axisx = axisx/32; - axisy = axisy/32; - - - if (Joystick[index].bGamepadStyle) - { - // gamepad control type, on or off, live or die - if (axisx < -(JOYAXISRANGE/2)) - event.data2 = -1; - else if (axisx > (JOYAXISRANGE/2)) - event.data2 = 1; - else event.data2 = 0; - if (axisy < -(JOYAXISRANGE/2)) - event.data3 = -1; - else if (axisy > (JOYAXISRANGE/2)) - event.data3 = 1; - else event.data3 = 0; - } - else - { - - axisx = JoyInfo[index].scale?((axisx/JoyInfo[index].scale)*JoyInfo[index].scale):axisx; - axisy = JoyInfo[index].scale?((axisy/JoyInfo[index].scale)*JoyInfo[index].scale):axisy; - -#ifdef SDL_JDEADZONE - if (-SDL_JDEADZONE <= axisx && axisx <= SDL_JDEADZONE) axisx = 0; - if (-SDL_JDEADZONE <= axisy && axisy <= SDL_JDEADZONE) axisy = 0; -#endif - - // analog control style , just send the raw data - event.data2 = axisx; // x axis - event.data3 = axisy; // y axis - } - D_PostEvent(&event); - } -#endif -} - /** \brief Open joystick handle \param fname name of joystick @@ -1221,7 +1111,7 @@ void I_GetJoystickEvents(UINT8 index) */ static int joy_open(int playerIndex, int joyIndex) { - SDL_Joystick *newdev = NULL; + SDL_GameController *newdev = NULL; int num_joy = 0; if (SDL_WasInit(SDL_INIT_JOYSTICK) == 0) @@ -1230,6 +1120,12 @@ static int joy_open(int playerIndex, int joyIndex) return -1; } + if (SDL_WasInit(SDL_INIT_GAMECONTROLLER) == 0) + { + CONS_Printf(M_GetText("Game Controller subsystem not started\n")); + return -1; + } + if (joyIndex <= 0) return -1; @@ -1241,7 +1137,7 @@ static int joy_open(int playerIndex, int joyIndex) return -1; } - newdev = SDL_JoystickOpen(joyIndex-1); + newdev = SDL_GameControllerOpen(joyIndex-1); // Handle the edge case where the device <-> joystick index assignment can change due to hotplugging // This indexing is SDL's responsibility and there's not much we can do about it. @@ -1256,9 +1152,9 @@ static int joy_open(int playerIndex, int joyIndex) if (JoyInfo[playerIndex].dev) { if (JoyInfo[playerIndex].dev == newdev // same device, nothing to do - || (newdev == NULL && SDL_JoystickGetAttached(JoyInfo[playerIndex].dev))) // we failed, but already have a working device + || (newdev == NULL && SDL_GameControllerGetAttached(JoyInfo[playerIndex].dev))) // we failed, but already have a working device { - return JoyInfo[playerIndex].axises; + return SDL_CONTROLLER_AXIS_MAX; } // Else, we're changing devices, so send neutral joy events @@ -1275,29 +1171,12 @@ static int joy_open(int playerIndex, int joyIndex) } else { - CONS_Debug(DBG_GAMELOGIC, M_GetText("Joystick%d: %s\n"), playerIndex+1, SDL_JoystickName(JoyInfo[playerIndex].dev)); + CONS_Debug(DBG_GAMELOGIC, M_GetText("Joystick%d: %s\n"), playerIndex+1, SDL_GameControllerName(JoyInfo[playerIndex].dev)); - JoyInfo[playerIndex].axises = SDL_JoystickNumAxes(JoyInfo[playerIndex].dev); - if (JoyInfo[playerIndex].axises > JOYAXISSET*2) - JoyInfo[playerIndex].axises = JOYAXISSET*2; - - /* - if (joyaxes<2) - { - I_OutputMsg("Not enought axes?\n"); - return 0; - } - */ - - JoyInfo[playerIndex].buttons = SDL_JoystickNumButtons(JoyInfo[playerIndex].dev); - if (JoyInfo[playerIndex].buttons > JOYBUTTONS) - JoyInfo[playerIndex].buttons = JOYBUTTONS; - - JoyInfo[playerIndex].hats = SDL_JoystickNumHats(JoyInfo[playerIndex].dev); - if (JoyInfo[playerIndex].hats > JOYHATS) - JoyInfo[playerIndex].hats = JOYHATS; - - JoyInfo[playerIndex].balls = SDL_JoystickNumBalls(JoyInfo[playerIndex].dev); + JoyInfo[playerIndex].axises = SDL_CONTROLLER_AXIS_MAX; + JoyInfo[playerIndex].buttons = SDL_CONTROLLER_BUTTON_MAX; + JoyInfo[playerIndex].hats = 1; + JoyInfo[playerIndex].balls = 0; //JoyInfo[playerIndex].bGamepadStyle = !stricmp(SDL_JoystickName(JoyInfo[playerIndex].dev), "pad"); @@ -1310,7 +1189,7 @@ static int joy_open(int playerIndex, int joyIndex) // void I_InitJoystick(UINT8 index) { - SDL_Joystick *newjoy = NULL; + SDL_GameController *newcontroller = NULL; UINT8 i; //I_ShutdownJoystick(); @@ -1335,23 +1214,33 @@ void I_InitJoystick(UINT8 index) } } + if (SDL_WasInit(SDL_INIT_GAMECONTROLLER) == 0) + { + if (SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER) == -1) + { + CONS_Printf(M_GetText("Couldn't initialize gamepads: %s\n"), SDL_GetError()); + return; + } + } + if (cv_usejoystick[index].value) - newjoy = SDL_JoystickOpen(cv_usejoystick[index].value-1); + newcontroller = SDL_GameControllerOpen(cv_usejoystick[index].value-1); for (i = 0; i < MAXSPLITSCREENPLAYERS; i++) { if (i == index) continue; - if (JoyInfo[i].dev == newjoy) + if (JoyInfo[i].dev == newcontroller) break; } - if (newjoy && i < MAXSPLITSCREENPLAYERS) // don't override an active device + if (newcontroller && i < MAXSPLITSCREENPLAYERS) // don't override an active device { cv_usejoystick[index].value = I_GetJoystickDeviceIndex(JoyInfo[index].dev) + 1; + CONS_Printf("I_InitJoystick: Device for %d set to %d\n", index, cv_usejoystick[index].value); } - else if (newjoy && joy_open(index, cv_usejoystick[index].value) != -1) + else if (newcontroller && joy_open(index, cv_usejoystick[index].value) != -1) { // SDL's device indexes are unstable, so cv_usejoystick may not match // the actual device index. So let's cheat a bit and find the device's current index. @@ -1363,19 +1252,20 @@ void I_InitJoystick(UINT8 index) if (JoyInfo[index].oldjoy) I_ShutdownJoystick(index); cv_usejoystick[index].value = 0; + CONS_Printf("I_InitJoystick: Device for %d set to %d\n", index, cv_usejoystick[index].value); joystick_started[index] = 0; } for (i = 0; i < MAXSPLITSCREENPLAYERS; i++) { - if (JoyInfo[i].dev == newjoy) + if (JoyInfo[i].dev == newcontroller) break; } if (i == MAXSPLITSCREENPLAYERS) { // Joystick didn't end up being used - SDL_JoystickClose(newjoy); + I_StoreExJoystick(newcontroller); } } @@ -1410,6 +1300,13 @@ static void I_ShutdownInput(void) for (i = 0; i < MAXSPLITSCREENPLAYERS; i++) I_ShutdownJoystick(i); + if (SDL_WasInit(SDL_INIT_GAMECONTROLLER) == SDL_INIT_GAMECONTROLLER) + { + CONS_Printf("Shutting down gamecontroller system\n"); + SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER); + I_OutputMsg("I_Joystick: SDL's Game Controller system has been shutdown\n"); + } + if (SDL_WasInit(SDL_INIT_JOYSTICK) == SDL_INIT_JOYSTICK) { CONS_Printf("Shutting down joy system\n"); @@ -1838,6 +1735,7 @@ void I_Quit(void) M_SaveConfig(NULL); //save game config, cvars.. #ifndef NONET D_SaveBan(); // save the ban list + M_SaveJoinedIPs(); #endif // Make sure you lose points for ALT-F4 diff --git a/src/sdl/i_video.c b/src/sdl/i_video.c index 3a5c1d3ab..7b1c09b71 100644 --- a/src/sdl/i_video.c +++ b/src/sdl/i_video.c @@ -63,7 +63,7 @@ #include "../i_system.h" #include "../v_video.h" #include "../m_argv.h" -#include "../m_menu.h" +#include "../k_menu.h" #include "../d_main.h" #include "../s_sound.h" #include "../i_sound.h" // midi pause/unpause @@ -372,7 +372,7 @@ static boolean IgnoreMouse(void) if (cv_alwaysgrabmouse.value) return false; if (menuactive) - return !M_MouseNeeded(); + return false; // return !M_MouseNeeded(); if (paused || con_destlines || chat_on) return true; if (gamestate != GS_LEVEL && gamestate != GS_INTERMISSION && @@ -526,95 +526,31 @@ static inline void SDLJoyRemap(event_t *event) (void)event; } -static INT32 SDLJoyAxis(const Sint16 axis, evtype_t which) +static INT32 SDLJoyAxis(const Sint16 axis, UINT8 pid) { // -32768 to 32767 - INT32 raxis = axis/32; - if (which == ev_joystick) + INT32 raxis = axis / 32; + + if (Joystick[pid].bGamepadStyle) { - if (Joystick[0].bGamepadStyle) - { - // gamepad control type, on or off, live or die - if (raxis < -(JOYAXISRANGE/2)) - raxis = -1; - else if (raxis > (JOYAXISRANGE/2)) - raxis = 1; - else - raxis = 0; - } + // gamepad control type, on or off, live or die + if (raxis < -(JOYAXISRANGE/2)) + raxis = -1; + else if (raxis > (JOYAXISRANGE/2)) + raxis = 1; else - { - raxis = JoyInfo[0].scale!=1?((raxis/JoyInfo[0].scale)*JoyInfo[0].scale):raxis; + raxis = 0; + } + else + { + raxis = (abs(JoyInfo[pid].scale) > 1) ? ((raxis / JoyInfo[pid].scale) * JoyInfo[pid].scale) : raxis; #ifdef SDL_JDEADZONE - if (-SDL_JDEADZONE <= raxis && raxis <= SDL_JDEADZONE) - raxis = 0; + if (-SDL_JDEADZONE <= raxis && raxis <= SDL_JDEADZONE) + raxis = 0; #endif - } } - else if (which == ev_joystick2) - { - if (Joystick[1].bGamepadStyle) - { - // gamepad control type, on or off, live or die - if (raxis < -(JOYAXISRANGE/2)) - raxis = -1; - else if (raxis > (JOYAXISRANGE/2)) - raxis = 1; - else raxis = 0; - } - else - { - raxis = JoyInfo[1].scale!=1?((raxis/JoyInfo[1].scale)*JoyInfo[1].scale):raxis; -#ifdef SDL_JDEADZONE - if (-SDL_JDEADZONE <= raxis && raxis <= SDL_JDEADZONE) - raxis = 0; -#endif - } - } - else if (which == ev_joystick3) - { - if (Joystick[2].bGamepadStyle) - { - // gamepad control type, on or off, live or die - if (raxis < -(JOYAXISRANGE/2)) - raxis = -1; - else if (raxis > (JOYAXISRANGE/2)) - raxis = 1; - else raxis = 0; - } - else - { - raxis = JoyInfo[2].scale!=1?((raxis/JoyInfo[2].scale)*JoyInfo[2].scale):raxis; - -#ifdef SDL_JDEADZONE - if (-SDL_JDEADZONE <= raxis && raxis <= SDL_JDEADZONE) - raxis = 0; -#endif - } - } - else if (which == ev_joystick4) - { - if (Joystick[3].bGamepadStyle) - { - // gamepad control type, on or off, live or die - if (raxis < -(JOYAXISRANGE/2)) - raxis = -1; - else if (raxis > (JOYAXISRANGE/2)) - raxis = 1; - else raxis = 0; - } - else - { - raxis = JoyInfo[3].scale!=1?((raxis/JoyInfo[3].scale)*JoyInfo[3].scale):raxis; - -#ifdef SDL_JDEADZONE - if (-SDL_JDEADZONE <= raxis && raxis <= SDL_JDEADZONE) - raxis = 0; -#endif - } - } return raxis; } @@ -679,7 +615,8 @@ static void Impl_HandleWindowEvent(SDL_WindowEvent evt) { SDLforceUngrabMouse(); } - memset(gamekeydown, 0, NUMKEYS); // TODO this is a scary memset + memset(gamekeydown, 0, sizeof(gamekeydown)); // TODO this is a scary memset + memset(deviceResponding, false, sizeof (deviceResponding)); if (MOUSE_MENU) { @@ -692,6 +629,9 @@ static void Impl_HandleWindowEvent(SDL_WindowEvent evt) static void Impl_HandleKeyboardEvent(SDL_KeyboardEvent evt, Uint32 type) { event_t event; + + event.device = 0; + if (type == SDL_KEYUP) { event.type = ev_keyup; @@ -773,6 +713,8 @@ static void Impl_HandleMouseButtonEvent(SDL_MouseButtonEvent evt, Uint32 type) /// \todo inputEvent.button.which if (USE_MOUSEINPUT) { + event.device = 0; + if (type == SDL_MOUSEBUTTONUP) { event.type = ev_keyup; @@ -805,6 +747,8 @@ static void Impl_HandleMouseWheelEvent(SDL_MouseWheelEvent evt) SDL_memset(&event, 0, sizeof(event_t)); + event.device = 0; + if (evt.y > 0) { event.data1 = KEY_MOUSEWHEELUP; @@ -826,138 +770,87 @@ static void Impl_HandleMouseWheelEvent(SDL_MouseWheelEvent evt) } } -static void Impl_HandleJoystickAxisEvent(SDL_JoyAxisEvent evt) +static void Impl_HandleControllerAxisEvent(SDL_ControllerAxisEvent evt) { event_t event; - SDL_JoystickID joyid[MAXSPLITSCREENPLAYERS]; - UINT8 i; + INT32 value; - // Determine the Joystick IDs for each current open joystick - for (i = 0; i < MAXSPLITSCREENPLAYERS; i++) - joyid[i] = SDL_JoystickInstanceID(JoyInfo[i].dev); + event.type = ev_joystick; + + event.device = 1 + evt.which; + if (event.device == INT32_MAX) + { + return; + } - evt.axis++; event.data1 = event.data2 = event.data3 = INT32_MAX; - if (evt.which == joyid[0]) - { - event.type = ev_joystick; - } - else if (evt.which == joyid[1]) - { - event.type = ev_joystick2; - } - else if (evt.which == joyid[2]) - { - event.type = ev_joystick3; - } - else if (evt.which == joyid[3]) - { - event.type = ev_joystick4; - } - else return; //axis - if (evt.axis > JOYAXISSET*2) - return; - //vaule - if (evt.axis%2) + if (evt.axis > 2 * JOYAXISSETS) { - event.data1 = evt.axis / 2; - event.data2 = SDLJoyAxis(evt.value, event.type); + return; + } + + //vaule[sic] + value = SDLJoyAxis(evt.value, evt.which); + + if (evt.axis & 1) + { + event.data3 = value; } else { - evt.axis--; - event.data1 = evt.axis / 2; - event.data3 = SDLJoyAxis(evt.value, event.type); + event.data2 = value; } + + event.data1 = evt.axis / 2; + D_PostEvent(&event); } -#if 0 -static void Impl_HandleJoystickHatEvent(SDL_JoyHatEvent evt) +static void Impl_HandleControllerButtonEvent(SDL_ControllerButtonEvent evt, Uint32 type) { event_t event; - SDL_JoystickID joyid[MAXSPLITSCREENPLAYERS]; - UINT8 i; - // Determine the Joystick IDs for each current open joystick - for (i = 0; i < MAXSPLITSCREENPLAYERS; i++) - joyid[i] = SDL_JoystickInstanceID(JoyInfo[i].dev); + event.device = 1 + evt.which; - if (evt.hat >= JOYHATS) - return; // ignore hats with too high an index + if (event.device == INT32_MAX) + { + return; + } - if (evt.which == joyid[0]) - { - event.data1 = KEY_HAT1 + (evt.hat*4); - } - else if (evt.which == joyid[1]) - { - event.data1 = KEY_2HAT1 + (evt.hat*4); - } - else if (evt.which == joyid[2]) - { - event.data1 = KEY_3HAT1 + (evt.hat*4); - } - else if (evt.which == joyid[3]) - { - event.data1 = KEY_4HAT1 + (evt.hat*4); - } - else return; + event.data1 = KEY_JOY1; - // NOTE: UNFINISHED -} -#endif - -static void Impl_HandleJoystickButtonEvent(SDL_JoyButtonEvent evt, Uint32 type) -{ - event_t event; - SDL_JoystickID joyid[MAXSPLITSCREENPLAYERS]; - UINT8 i; - - // Determine the Joystick IDs for each current open joystick - for (i = 0; i < MAXSPLITSCREENPLAYERS; i++) - joyid[i] = SDL_JoystickInstanceID(JoyInfo[i].dev); - - if (evt.which == joyid[0]) - { - event.data1 = KEY_JOY1; - } - else if (evt.which == joyid[1]) - { - event.data1 = KEY_2JOY1; - } - else if (evt.which == joyid[2]) - { - event.data1 = KEY_3JOY1; - } - else if (evt.which == joyid[3]) - { - event.data1 = KEY_4JOY1; - } - else return; - if (type == SDL_JOYBUTTONUP) + if (type == SDL_CONTROLLERBUTTONUP) { event.type = ev_keyup; } - else if (type == SDL_JOYBUTTONDOWN) + else if (type == SDL_CONTROLLERBUTTONDOWN) { event.type = ev_keydown; } - else return; + else + { + return; + } + if (evt.button < JOYBUTTONS) { event.data1 += evt.button; } - else return; + else + { + return; + } SDLJoyRemap(&event); - if (event.type != ev_console) D_PostEvent(&event); + + if (event.type != ev_console) + { + D_PostEvent(&event); + } } - - void I_GetEvent(void) { SDL_Event evt; @@ -998,28 +891,23 @@ void I_GetEvent(void) case SDL_MOUSEWHEEL: Impl_HandleMouseWheelEvent(evt.wheel); break; - case SDL_JOYAXISMOTION: - Impl_HandleJoystickAxisEvent(evt.jaxis); + case SDL_CONTROLLERAXISMOTION: + Impl_HandleControllerAxisEvent(evt.caxis); break; -#if 0 - case SDL_JOYHATMOTION: - Impl_HandleJoystickHatEvent(evt.jhat) - break; -#endif - case SDL_JOYBUTTONUP: - case SDL_JOYBUTTONDOWN: - Impl_HandleJoystickButtonEvent(evt.jbutton, evt.type); + case SDL_CONTROLLERBUTTONUP: + case SDL_CONTROLLERBUTTONDOWN: + Impl_HandleControllerButtonEvent(evt.cbutton, evt.type); break; //////////////////////////////////////////////////////////// - case SDL_JOYDEVICEADDED: + case SDL_CONTROLLERDEVICEADDED: { // OH BOY are you in for a good time! #abominationstation - SDL_Joystick *newjoy = SDL_JoystickOpen(evt.jdevice.which); + SDL_GameController *newcontroller = SDL_GameControllerOpen(evt.cdevice.which); - CONS_Debug(DBG_GAMELOGIC, "Joystick device index %d added\n", evt.jdevice.which + 1); + CONS_Debug(DBG_GAMELOGIC, "Controller device index %d added\n", evt.cdevice.which + 1); //////////////////////////////////////////////////////////// // Because SDL's device index is unstable, we're going to cheat here a bit: @@ -1034,7 +922,7 @@ void I_GetEvent(void) for (i = 0; i < MAXSPLITSCREENPLAYERS; i++) { - if (newjoy && (!JoyInfo[i].dev || !SDL_JoystickGetAttached(JoyInfo[i].dev))) + if (newcontroller && (!JoyInfo[i].dev || !SDL_GameControllerGetAttached(JoyInfo[i].dev))) { UINT8 j; @@ -1043,14 +931,14 @@ void I_GetEvent(void) if (i == j) continue; - if (JoyInfo[j].dev == newjoy) + if (JoyInfo[j].dev == newcontroller) break; } if (j == MAXSPLITSCREENPLAYERS) { // ensures we aren't overriding a currently active device - cv_usejoystick[i].value = evt.jdevice.which + 1; + cv_usejoystick[i].value = evt.cdevice.which + 1; I_UpdateJoystickDeviceIndices(0); } } @@ -1084,27 +972,29 @@ void I_GetEvent(void) for (i = 0; i < MAXSPLITSCREENPLAYERS; i++) CONS_Debug(DBG_GAMELOGIC, "Joystick%d device index: %d\n", i+1, JoyInfo[i].oldjoy); +#if 0 // update the menu if (currentMenu == &OP_JoystickSetDef) M_SetupJoystickMenu(0); +#endif for (i = 0; i < MAXSPLITSCREENPLAYERS; i++) { - if (JoyInfo[i].dev == newjoy) + if (JoyInfo[i].dev == newcontroller) break; } if (i == MAXSPLITSCREENPLAYERS) - SDL_JoystickClose(newjoy); + I_StoreExJoystick(newcontroller); } break; //////////////////////////////////////////////////////////// - case SDL_JOYDEVICEREMOVED: + case SDL_CONTROLLERDEVICEREMOVED: for (i = 0; i < MAXSPLITSCREENPLAYERS; i++) { - if (JoyInfo[i].dev && !SDL_JoystickGetAttached(JoyInfo[i].dev)) + if (JoyInfo[i].dev && !SDL_GameControllerGetAttached(JoyInfo[i].dev)) { CONS_Debug(DBG_GAMELOGIC, "Joystick%d removed, device index: %d\n", i+1, JoyInfo[i].oldjoy); I_ShutdownJoystick(i); @@ -1144,9 +1034,11 @@ void I_GetEvent(void) for (i = 0; i < MAXSPLITSCREENPLAYERS; i++) CONS_Debug(DBG_GAMELOGIC, "Joystick%d device index: %d\n", i+1, JoyInfo[i].oldjoy); +#if 0 // update the menu if (currentMenu == &OP_JoystickSetDef) M_SetupJoystickMenu(0); +#endif break; case SDL_QUIT: LUAh_GameQuit(true); @@ -1171,7 +1063,10 @@ void I_GetEvent(void) // In order to make wheels act like buttons, we have to set their state to Up. // This is because wheel messages don't have an up/down state. - gamekeydown[KEY_MOUSEWHEELDOWN] = gamekeydown[KEY_MOUSEWHEELUP] = 0; + for (i = 0; i < MAXSPLITSCREENPLAYERS; i++) + { + gamekeydown[i][KEY_MOUSEWHEELDOWN] = gamekeydown[i][KEY_MOUSEWHEELUP] = 0; + } } void I_StartupMouse(void) @@ -1199,16 +1094,12 @@ void I_StartupMouse(void) void I_OsPolling(void) { SDL_Keymod mod; - UINT8 i; if (consolevent) I_GetConsoleEvents(); - if (SDL_WasInit(SDL_INIT_JOYSTICK) == SDL_INIT_JOYSTICK) - { - SDL_JoystickUpdate(); - for (i = 0; i < MAXSPLITSCREENPLAYERS; i++) - I_GetJoystickEvents(i); - } + + if (SDL_WasInit(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) == (SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER)) + SDL_GameControllerUpdate(); I_GetEvent(); @@ -1583,15 +1474,22 @@ static SDL_bool Impl_CreateContext(void) int flags = 0; // Use this to set SDL_RENDERER_* flags now if (usesdl2soft) flags |= SDL_RENDERER_SOFTWARE; +#if 0 + // This shit is BROKEN. + // - The version of SDL we're using cannot toggle VSync at runtime. We'll need a new SDL version implemented to have this work properly. + // - cv_vidwait is initialized before config is loaded, so it's forced to default value at runtime, and forced off when switching. The config loading code would need restructured. + // - With both this & frame interpolation on, I_FinishUpdate takes x10 longer. At this point, it is simpler to use a standard FPS cap. + // So you can probably guess why I'm kinda over this, I'm just disabling it. else if (cv_vidwait.value) flags |= SDL_RENDERER_PRESENTVSYNC; +#endif // 3 August 2022 // Possibly a Windows 11 issue; the default // "direct3d" driver (D3D9) causes Drmingw exchndl // to not write RPT files. Every other driver // seems fine. - SDL_SetHint(SDL_HINT_RENDER_DRIVER, "direct3d11"); + SDL_SetHint(SDL_HINT_RENDER_DRIVER, "opengl"); if (!renderer) renderer = SDL_CreateRenderer(window, -1, flags); diff --git a/src/sdl/sdlmain.h b/src/sdl/sdlmain.h index 39c099a2b..86d584632 100644 --- a/src/sdl/sdlmain.h +++ b/src/sdl/sdlmain.h @@ -41,8 +41,8 @@ extern SDL_bool framebuffer; */ typedef struct SDLJoyInfo_s { - /// Joystick handle - SDL_Joystick *dev; + /// Controller handle + SDL_GameController *dev; /// number of old joystick int oldjoy; /// number of axies @@ -58,9 +58,12 @@ typedef struct SDLJoyInfo_s } SDLJoyInfo_t; -/** \brief SDL info about joysticks +/** \brief SDL info about controllers */ extern SDLJoyInfo_t JoyInfo[MAXSPLITSCREENPLAYERS]; +extern SDL_GameController *ExJoystick[MAXGAMEPADS]; + +void I_StoreExJoystick(SDL_GameController *dev); /** \brief joystick axis deadzone */ @@ -72,8 +75,8 @@ void I_GetConsoleEvents(void); // So we can call this from i_video event loop void I_ShutdownJoystick(UINT8 index); -// Cheat to get the device index for a joystick handle -INT32 I_GetJoystickDeviceIndex(SDL_Joystick *dev); +// Cheat to get the device index for a game controller handle +INT32 I_GetJoystickDeviceIndex(SDL_GameController *dev); // Quick thing to make SDL_JOYDEVICEADDED events less of an abomination void I_UpdateJoystickDeviceIndex(UINT8 player); diff --git a/src/sounds.c b/src/sounds.c index 7cbc0e8c0..9240577fa 100644 --- a/src/sounds.c +++ b/src/sounds.c @@ -1110,7 +1110,9 @@ sfxinfo_t S_sfx[NUMSFX] = {"kdtrg3", false, 64, 80, -1, NULL, 0, -1, -1, LUMPERROR, ""}, // High energy, SF_X2AWAYSOUND|SF_X8AWAYSOUND // SRB2kart - Grow/invinc clash - {"parry", false, 64, 16, -1, NULL, 0, -1, -1, LUMPERROR, ""}, // SF_X8AWAYSOUND + {"parry", false, 64, 16, -1, NULL, 0, -1, -1, LUMPERROR, ""}, // SF_X8AWAYSOUND + + {"ffbonc", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""}, // SRB2Kart - Engine sounds // Engine class A diff --git a/src/sounds.h b/src/sounds.h index 6d1c51f57..c5eb638e0 100644 --- a/src/sounds.h +++ b/src/sounds.h @@ -1176,6 +1176,9 @@ typedef enum // SRB2Kart - Powerup clash SFX sfx_parry, + // Fast fall bounce + sfx_ffbonc, + // Next up: UNIQUE ENGINE SOUNDS! Hoooooo boy... // Engine class A - Low Speed, Low Weight sfx_krta00, diff --git a/src/st_stuff.c b/src/st_stuff.c index 4de0c612d..14bdaa15d 100644 --- a/src/st_stuff.c +++ b/src/st_stuff.c @@ -26,7 +26,7 @@ #include "console.h" #include "s_sound.h" #include "i_system.h" -#include "m_menu.h" +#include "k_menu.h" #include "m_cheat.h" #include "m_misc.h" // moviemode #include "m_anigif.h" // cv_gif_downscale @@ -1057,7 +1057,7 @@ static void ST_overlayDrawer(void) else if (G_GametypeHasTeams()) itemtxt = M_GetText("Item - Join Team"); - if (cv_ingamecap.value) + if (cv_maxplayers.value) { UINT8 numingame = 0; UINT8 i; @@ -1066,7 +1066,7 @@ static void ST_overlayDrawer(void) if (playeringame[i] && !players[i].spectator) numingame++; - itemtxt = va("%s (%s: %d)", itemtxt, M_GetText("Slots left"), max(0, cv_ingamecap.value - numingame)); + itemtxt = va("%s (%s: %d)", itemtxt, M_GetText("Slots left"), max(0, cv_maxplayers.value - numingame)); } // SRB2kart: changed positions & text @@ -1088,11 +1088,11 @@ static void ST_overlayDrawer(void) void ST_DrawDemoTitleEntry(void) { - static UINT8 skullAnimCounter = 0; + static UINT8 anim = 0; char *nametodraw; - skullAnimCounter++; - skullAnimCounter %= 8; + anim++; + anim %= 8; nametodraw = demo.titlename; while (V_StringWidth(nametodraw, 0) > MAXSTRINGLENGTH*8 - 8) @@ -1102,7 +1102,7 @@ void ST_DrawDemoTitleEntry(void) #define y (BASEVIDHEIGHT/2) M_DrawTextBox(x, y + 4, MAXSTRINGLENGTH, 1); V_DrawString(x + 8, y + 12, V_ALLOWLOWERCASE, nametodraw); - if (skullAnimCounter < 4) + if (anim < 4) V_DrawCharacter(x + 8 + V_StringWidth(nametodraw, 0), y + 12, '_' | 0x80, false); diff --git a/src/v_video.c b/src/v_video.c index 7f0b86398..7c957e078 100644 --- a/src/v_video.c +++ b/src/v_video.c @@ -1966,6 +1966,48 @@ static inline fixed_t BunchedCharacterDim( return 0; } +static inline fixed_t GamemodeCharacterDim( + fixed_t scale, + fixed_t chw, + INT32 hchw, + INT32 dupx, + fixed_t * cwp) +{ + (void)chw; + (void)hchw; + (void)dupx; + (*cwp) = FixedMul (max (1, (*cwp) - 2) << FRACBITS, scale); + return 0; +} + +static inline fixed_t FileCharacterDim( + fixed_t scale, + fixed_t chw, + INT32 hchw, + INT32 dupx, + fixed_t * cwp) +{ + (void)chw; + (void)hchw; + (void)dupx; + (*cwp) = FixedMul (max (1, (*cwp) - 3) << FRACBITS, scale); + return 0; +} + +static inline fixed_t LSTitleCharacterDim( + fixed_t scale, + fixed_t chw, + INT32 hchw, + INT32 dupx, + fixed_t * cwp) +{ + (void)chw; + (void)hchw; + (void)dupx; + (*cwp) = FixedMul (max (1, (*cwp) - 4) << FRACBITS, scale); + return 0; +} + void V_DrawStringScaled( fixed_t x, fixed_t y, @@ -1973,6 +2015,7 @@ void V_DrawStringScaled( fixed_t spacescale, fixed_t lfscale, INT32 flags, + const UINT8 *colormap, int fontno, const char *s) { @@ -1993,8 +2036,6 @@ void V_DrawStringScaled( boolean uppercase; boolean notcolored; - const UINT8 *colormap; - fixed_t cx, cy; fixed_t cxoff; @@ -2006,9 +2047,13 @@ void V_DrawStringScaled( int c; uppercase = !( flags & V_ALLOWLOWERCASE ); - flags &= ~(V_FLIP);/* These two (V_ALLOWLOWERCASE) share a bit. */ + flags &= ~(V_FLIP);/* These two (V_ALLOWLOWERCASE) share a bit. */ + + if (colormap == NULL) + { + colormap = V_GetStringColormap(( flags & V_CHARCOLORMASK )); + } - colormap = V_GetStringColormap(( flags & V_CHARCOLORMASK )); notcolored = !colormap; font = &fontv[fontno]; @@ -2053,6 +2098,12 @@ void V_DrawStringScaled( spacewidth = 3;*/ } break; + case LT_FONT: + spacew = 12; + break; + case CRED_FONT: + spacew = 16; + break; case KART_FONT: spacew = 12; switch (spacing) @@ -2067,13 +2118,18 @@ void V_DrawStringScaled( spacew = 6; } break; - case LT_FONT: - spacew = 12; + case GM_FONT: + spacew = 6; break; - case CRED_FONT: + case FILE_FONT: + spacew = 0; + break; + case LSHI_FONT: + case LSLOW_FONT: spacew = 16; break; } + switch (fontno) { default: @@ -2084,8 +2140,18 @@ void V_DrawStringScaled( break; case LT_FONT: case CRED_FONT: + case FILE_FONT: lfh = 12; break; + case GM_FONT: + lfh = 32; + break; + case LSHI_FONT: + lfh = 56; + break; + case LSLOW_FONT: + lfh = 38; + break; } hchw = chw >> 1; @@ -2130,25 +2196,45 @@ void V_DrawStringScaled( right <<= FRACBITS; bot = vid.height << FRACBITS; - if (fontno == TINY_FONT) + switch (fontno) { - if (chw) - dim_fn = FixedCharacterDim; - else - { - /* Reuse this flag for the alternate bunched-up spacing. */ - if (( flags & V_6WIDTHSPACE )) - dim_fn = BunchedCharacterDim; + default: + if (chw) + dim_fn = CenteredCharacterDim; else dim_fn = VariableCharacterDim; - } - } - else - { - if (chw) - dim_fn = CenteredCharacterDim; - else - dim_fn = VariableCharacterDim; + break; + case TINY_FONT: + if (chw) + dim_fn = FixedCharacterDim; + else + { + /* Reuse this flag for the alternate bunched-up spacing. */ + if (( flags & V_6WIDTHSPACE )) + dim_fn = BunchedCharacterDim; + else + dim_fn = VariableCharacterDim; + } + break; + case GM_FONT: + if (chw) + dim_fn = FixedCharacterDim; + else + dim_fn = GamemodeCharacterDim; + break; + case FILE_FONT: + if (chw) + dim_fn = FixedCharacterDim; + else + dim_fn = FileCharacterDim; + break; + case LSHI_FONT: + case LSLOW_FONT: + if (chw) + dim_fn = FixedCharacterDim; + else + dim_fn = LSTitleCharacterDim; + break; } cx = x; @@ -2194,7 +2280,237 @@ void V_DrawStringScaled( } } } -// + +fixed_t V_StringScaledWidth( + fixed_t scale, + fixed_t spacescale, + fixed_t lfscale, + INT32 flags, + int fontno, + const char *s) +{ + fixed_t chw; + INT32 hchw;/* half-width for centering */ + fixed_t spacew; + fixed_t lfh; + + INT32 dupx; + + fixed_t (*dim_fn)(fixed_t,fixed_t,INT32,INT32,fixed_t *); + + font_t *font; + + boolean uppercase; + + fixed_t cx, cy; + + fixed_t cw; + + INT32 spacing; + + int c; + + fixed_t fullwidth = 0; + + uppercase = !( flags & V_ALLOWLOWERCASE ); + flags &= ~(V_FLIP);/* These two (V_ALLOWLOWERCASE) share a bit. */ + + font = &fontv[fontno]; + + chw = 0; + + spacing = ( flags & V_SPACINGMASK ); + + /* + Hardcoded until a better system can be implemented + for determining how fonts space. + */ + switch (fontno) + { + default: + case HU_FONT: + spacew = 4; + switch (spacing) + { + case V_MONOSPACE: + spacew = 8; + /* FALLTHRU */ + case V_OLDSPACING: + chw = 8; + break; + case V_6WIDTHSPACE: + spacew = 6; + } + break; + case TINY_FONT: + spacew = 2; + switch (spacing) + { + case V_MONOSPACE: + spacew = 5; + /* FALLTHRU */ + case V_OLDSPACING: + chw = 5; + break; + // Out of video flags, so we're reusing this for alternate charwidth instead + /*case V_6WIDTHSPACE: + spacewidth = 3;*/ + } + break; + case LT_FONT: + spacew = 12; + break; + case CRED_FONT: + spacew = 16; + break; + case KART_FONT: + spacew = 12; + switch (spacing) + { + case V_MONOSPACE: + spacew = 12; + /* FALLTHRU */ + case V_OLDSPACING: + chw = 12; + break; + case V_6WIDTHSPACE: + spacew = 6; + } + break; + case GM_FONT: + case FILE_FONT: + spacew = 0; + break; + case LSHI_FONT: + case LSLOW_FONT: + spacew = 16; + break; + } + + switch (fontno) + { + default: + case HU_FONT: + case TINY_FONT: + case KART_FONT: + lfh = 12; + break; + case LT_FONT: + case CRED_FONT: + case FILE_FONT: + lfh = 12; + break; + case GM_FONT: + lfh = 32; + break; + case LSHI_FONT: + lfh = 56; + break; + case LSLOW_FONT: + lfh = 38; + break; + } + + hchw = chw >> 1; + + chw <<= FRACBITS; + spacew <<= FRACBITS; + +#define Mul( id, scale ) ( id = FixedMul (scale, id) ) + Mul (chw, scale); + Mul (spacew, scale); + Mul (lfh, scale); + + Mul (spacew, spacescale); + Mul (lfh, lfscale); +#undef Mul + + if (( flags & V_NOSCALESTART )) + { + dupx = vid.dupx; + + hchw *= dupx; + + chw *= dupx; + spacew *= dupx; + lfh *= vid.dupy; + } + else + { + dupx = 1; + } + + switch (fontno) + { + default: + if (chw) + dim_fn = CenteredCharacterDim; + else + dim_fn = VariableCharacterDim; + break; + case TINY_FONT: + if (chw) + dim_fn = FixedCharacterDim; + else + { + /* Reuse this flag for the alternate bunched-up spacing. */ + if (( flags & V_6WIDTHSPACE )) + dim_fn = BunchedCharacterDim; + else + dim_fn = VariableCharacterDim; + } + break; + case GM_FONT: + if (chw) + dim_fn = FixedCharacterDim; + else + dim_fn = GamemodeCharacterDim; + break; + case FILE_FONT: + if (chw) + dim_fn = FixedCharacterDim; + else + dim_fn = FileCharacterDim; + break; + case LSHI_FONT: + case LSLOW_FONT: + if (chw) + dim_fn = FixedCharacterDim; + else + dim_fn = LSTitleCharacterDim; + break; + } + + cx = cy = 0; + + for (; ( c = *s ); ++s) + { + switch (c) + { + case '\n': + cy += lfh; + cx = 0; + break; + default: + if (uppercase) + c = toupper(c); + + c -= font->start; + if (c >= 0 && c < font->size && font->font[c]) + { + cw = SHORT (font->font[c]->width) * dupx; + (*dim_fn)(scale, chw, hchw, dupx, &cw); + cx += cw; + } + else + cx += spacew; + } + + fullwidth = max(cx, fullwidth); + } + + return fullwidth; +} void V_DrawCenteredString(INT32 x, INT32 y, INT32 option, const char *string) { @@ -2244,49 +2560,64 @@ void V_DrawRightAlignedThinStringAtFixed(fixed_t x, fixed_t y, INT32 option, con V_DrawThinStringAtFixed(x, y, option, string); } -// Draws a number using the PING font thingy. -// TODO: Merge number drawing functions into one with "font name" selection. - -void V_DrawPingNum(INT32 x, INT32 y, INT32 flags, INT32 num, const UINT8 *colormap) +void V_DrawCenteredKartString(INT32 x, INT32 y, INT32 option, const char *string) { - INT32 w = SHORT(fontv[PINGNUM_FONT].font[0]->width); // this SHOULD always be 5 but I guess custom graphics exist. - - if (flags & V_NOSCALESTART) - w *= vid.dupx; - - if (num < 0) - num = -num; - - // draw the number - do - { - x -= (w-1); // Oni wanted their outline to intersect. - V_DrawFixedPatch(x<= CRED_FONTSIZE) - w += 16; - else - w += fontv[CRED_FONT].font[c]->width; - } +void V_DrawRightAlignedGamemodeString(INT32 x, INT32 y, INT32 option, const UINT8 *colormap, const char *string) +{ + x -= V_GamemodeStringWidth(string, option); + V_DrawGamemodeString(x, y, option, colormap, string); +} - return w; +void V_DrawCenteredFileString(INT32 x, INT32 y, INT32 option, const char *string) +{ + x -= V_FileStringWidth(string, option)/2; + V_DrawFileString(x, y, option, string); +} + +void V_DrawRightAlignedFileString(INT32 x, INT32 y, INT32 option, const char *string) +{ + x -= V_FileStringWidth(string, option); + V_DrawFileString(x, y, option, string); +} + +void V_DrawCenteredLSTitleHighString(INT32 x, INT32 y, INT32 option, const char *string) +{ + x -= V_LSTitleHighStringWidth(string, option)/2; + V_DrawLSTitleHighString(x, y, option, string); +} + +void V_DrawRightAlignedLSTitleHighString(INT32 x, INT32 y, INT32 option, const char *string) +{ + x -= V_LSTitleHighStringWidth(string, option); + V_DrawLSTitleHighString(x, y, option, string); +} + +void V_DrawCenteredLSTitleLowString(INT32 x, INT32 y, INT32 option, const char *string) +{ + x -= V_LSTitleLowStringWidth(string, option)/2; + V_DrawLSTitleLowString(x, y, option, string); +} + +void V_DrawRightAlignedLSTitleLowString(INT32 x, INT32 y, INT32 option, const char *string) +{ + x -= V_LSTitleLowStringWidth(string, option); + V_DrawLSTitleLowString(x, y, option, string); } // Draws a tallnum. Replaces two functions in y_inter and st_stuff @@ -2335,25 +2666,43 @@ void V_DrawPaddedTallNum(INT32 x, INT32 y, INT32 flags, INT32 num, INT32 digits) } while (--digits); } -// Find string width from lt_font chars -// -INT32 V_LevelNameWidth(const char *string) +void V_DrawProfileNum(INT32 x, INT32 y, INT32 flags, UINT8 num) { - INT32 c, w = 0; - size_t i; + UINT8 digits = 3; + INT32 w = fontv[PROFNUM_FONT].font[0]->width; - for (i = 0; i < strlen(string); i++) + if (flags & V_NOSCALESTART) + w *= vid.dupx; + + // draw the number + do { - if (string[i] & 0x80) - continue; - c = string[i] - LT_FONTSTART; - if (c < 0 || c >= LT_FONTSIZE || !fontv[LT_FONT].font[c]) - w += 12; - else - w += fontv[LT_FONT].font[c]->width; - } + x -= (w-1); + V_DrawScaledPatch(x, y, flags, fontv[PROFNUM_FONT].font[num % 10]); + num /= 10; + } while (--digits); +} - return w; +// Draws a number using the PING font thingy. +// TODO: Merge number drawing functions into one with "font name" selection. + +void V_DrawPingNum(INT32 x, INT32 y, INT32 flags, INT32 num, const UINT8 *colormap) +{ + INT32 w = SHORT(fontv[PINGNUM_FONT].font[0]->width); // this SHOULD always be 5 but I guess custom graphics exist. + + if (flags & V_NOSCALESTART) + w *= vid.dupx; + + if (num < 0) + num = -num; + + // draw the number + do + { + x -= (w-1); // Oni wanted their outline to intersect. + V_DrawFixedPatch(x<= HU_FONTSIZE || !fontv[HU_FONT].font[c]) - w += spacewidth; - else - w += (charwidth ? charwidth : fontv[HU_FONT].font[c]->width); - } - - if (option & (V_NOSCALESTART|V_NOSCALEPATCH)) - w *= vid.dupx; - - return w; -} - -// -// Find string width from hu_font chars, 0.5x scale -// -INT32 V_SmallStringWidth(const char *string, INT32 option) -{ - INT32 c, w = 0; - INT32 spacewidth = 2, charwidth = 0; - size_t i; - - switch (option & V_SPACINGMASK) - { - case V_MONOSPACE: - spacewidth = 4; - /* FALLTHRU */ - case V_OLDSPACING: - charwidth = 4; - break; - case V_6WIDTHSPACE: - spacewidth = 3; - default: - break; - } - - for (i = 0; i < strlen(string); i++) - { - c = string[i]; - if ((UINT8)c & 0x80) //color parsing! -Inuyasha 2.16.09 - continue; - - c = toupper(c) - HU_FONTSTART; - if (c < 0 || c >= HU_FONTSIZE || !fontv[HU_FONT].font[c]) - w += spacewidth; - else - w += (charwidth ? charwidth : fontv[HU_FONT].font[c]->width / 2); - } - - return w; -} - -// -// Find string width from tny_font chars -// -INT32 V_ThinStringWidth(const char *string, INT32 option) -{ - INT32 c, w = 0; - INT32 spacewidth = 2, charwidth = 0; - boolean lowercase = (option & V_ALLOWLOWERCASE); - size_t i; - - switch (option & V_SPACINGMASK) - { - case V_MONOSPACE: - spacewidth = 5; - /* FALLTHRU */ - case V_OLDSPACING: - charwidth = 5; - break; - // Out of video flags, so we're reusing this for alternate charwidth instead - /*case V_6WIDTHSPACE: - spacewidth = 3;*/ - default: - break; - } - - for (i = 0; i < strlen(string); i++) - { - c = string[i]; - if ((UINT8)c & 0x80) //color parsing! -Inuyasha 2.16.09 - continue; - - if (!lowercase || !fontv[TINY_FONT].font[c-HU_FONTSTART]) - c = toupper(c); - c -= HU_FONTSTART; - - if (c < 0 || c >= HU_FONTSIZE || !fontv[TINY_FONT].font[c]) - w += spacewidth; - else - { - w += (charwidth ? charwidth - : ((option & V_6WIDTHSPACE && i < strlen(string)-1) ? max(1, fontv[TINY_FONT].font[c]->width-1) // Reuse this flag for the alternate bunched-up spacing - : fontv[TINY_FONT].font[c]->width)); - } - } - - - return w; -} - -// -// Find string width from tny_font chars, 0.5x scale -// -INT32 V_SmallThinStringWidth(const char *string, INT32 option) -{ - INT32 w = V_ThinStringWidth(string, option)<>1,option,HU_FONT,string) + V__DrawDupxString (x,y,FRACUNIT>>1,option,NULL,HU_FONT,string) + +#define V_SmallStringWidth( string,option ) \ + V__IntegerStringWidth ( FRACUNIT>>1,option,HU_FONT,string ) + void V_DrawCenteredSmallString(INT32 x, INT32 y, INT32 option, const char *string); void V_DrawRightAlignedSmallString(INT32 x, INT32 y, INT32 option, const char *string); // draw a string using the tny_font #define V_DrawThinString( x,y,option,string ) \ - V__DrawDupxString (x,y,FRACUNIT,option,TINY_FONT,string) + V__DrawDupxString (x,y,FRACUNIT,option,NULL,TINY_FONT,string) + +#define V_ThinStringWidth( string,option ) \ + V__IntegerStringWidth ( FRACUNIT,option,TINY_FONT,string ) + void V_DrawCenteredThinString(INT32 x, INT32 y, INT32 option, const char *string); void V_DrawRightAlignedThinString(INT32 x, INT32 y, INT32 option, const char *string); #define V_DrawStringAtFixed( x,y,option,string ) \ - V__DrawOneScaleString (x,y,FRACUNIT,option,HU_FONT,string) + V__DrawOneScaleString (x,y,FRACUNIT,option,NULL,HU_FONT,string) #define V_DrawThinStringAtFixed( x,y,option,string ) \ - V__DrawOneScaleString (x,y,FRACUNIT,option,TINY_FONT,string) + V__DrawOneScaleString (x,y,FRACUNIT,option,NULL,TINY_FONT,string) +void V_DrawCenteredThinStringAtFixed(fixed_t x, fixed_t y, INT32 option, const char *string); +void V_DrawRightAlignedThinStringAtFixed(fixed_t x, fixed_t y, INT32 option, const char *string); + + void V_DrawCenteredThinStringAtFixed(fixed_t x, fixed_t y, INT32 option, const char *string); void V_DrawRightAlignedThinStringAtFixed(fixed_t x, fixed_t y, INT32 option, const char *string); @@ -282,23 +316,59 @@ void V_DrawPaddedTallNum(INT32 x, INT32 y, INT32 flags, INT32 num, INT32 digits) // This is a separate function because IMO lua should have access to it as well. void V_DrawPingNum(INT32 x, INT32 y, INT32 flags, INT32 num, const UINT8 *colormap); -// Find string width from lt_font chars -INT32 V_LevelNameWidth(const char *string); -INT32 V_LevelNameHeight(const char *string); -INT16 V_LevelActNumWidth(UINT8 num); // act number width +void V_DrawProfileNum(INT32 x, INT32 y, INT32 flags, UINT8 num); #define V_DrawCreditString( x,y,option,string ) \ - V__DrawOneScaleString (x,y,FRACUNIT,option,CRED_FONT,string) -INT32 V_CreditStringWidth(const char *string); + V__DrawOneScaleString (x,y,FRACUNIT,option,NULL,CRED_FONT,string) -// Find string width from hu_font chars -INT32 V_StringWidth(const char *string, INT32 option); -// Find string width from hu_font chars, 0.5x scale -INT32 V_SmallStringWidth(const char *string, INT32 option); -// Find string width from tny_font chars -INT32 V_ThinStringWidth(const char *string, INT32 option); -// Find string width from tny_font chars, 0.5x scale -INT32 V_SmallThinStringWidth(const char *string, INT32 option); +#define V_CreditStringWidth( string ) \ + V__IntegerStringWidth ( FRACUNIT,0,CRED_FONT,string ) + +// SRB2Kart +#define V_DrawKartString( x,y,option,string ) \ + V__DrawDupxString (x,y,FRACUNIT,option,NULL,KART_FONT,string) + +#define V_KartStringWidth( string,option ) \ + V__IntegerStringWidth ( FRACUNIT,option,KART_FONT,string ) + +void V_DrawCenteredKartString(INT32 x, INT32 y, INT32 option, const char *string); +void V_DrawRightAlignedKartString(INT32 x, INT32 y, INT32 option, const char *string); + +#define V_DrawGamemodeString( x,y,option,cm,string ) \ + V__DrawDupxString (x,y,FRACUNIT,option,cm,GM_FONT,string) + +#define V_GamemodeStringWidth( string,option ) \ + V__IntegerStringWidth ( FRACUNIT,option,GM_FONT,string ) + +void V_DrawCenteredGamemodeString(INT32 x, INT32 y, INT32 option, const UINT8 *colormap, const char *string); +void V_DrawRightAlignedGamemodeString(INT32 x, INT32 y, INT32 option, const UINT8 *colormap, const char *string); + +#define V_DrawFileString( x,y,option,string ) \ + V__DrawDupxString (x,y,FRACUNIT,option,NULL,FILE_FONT,string) + +#define V_FileStringWidth( string,option ) \ + V__IntegerStringWidth ( FRACUNIT,option,FILE_FONT,string ) + +void V_DrawCenteredFileString(INT32 x, INT32 y, INT32 option, const char *string); +void V_DrawRightAlignedFileString(INT32 x, INT32 y, INT32 option, const char *string); + +#define V_DrawLSTitleHighString( x,y,option,string ) \ + V__DrawDupxString (x,y,FRACUNIT,option,NULL,LSHI_FONT,string) + +#define V_LSTitleHighStringWidth( string,option ) \ + V__IntegerStringWidth ( FRACUNIT,option,LSHI_FONT,string ) + +void V_DrawCenteredLSTitleHighString(INT32 x, INT32 y, INT32 option, const char *string); +void V_DrawRightAlignedLSTitleHighString(INT32 x, INT32 y, INT32 option, const char *string); + +#define V_DrawLSTitleLowString( x,y,option,string ) \ + V__DrawDupxString (x,y,FRACUNIT,option,NULL,LSLOW_FONT,string) + +#define V_LSTitleLowStringWidth( string,option ) \ + V__IntegerStringWidth ( FRACUNIT,option,LSLOW_FONT,string ) + +void V_DrawCenteredLSTitleLowString(INT32 x, INT32 y, INT32 option, const char *string); +void V_DrawRightAlignedLSTitleLowString(INT32 x, INT32 y, INT32 option, const char *string); void V_DoPostProcessor(INT32 view, postimg_t type, INT32 param); diff --git a/src/y_inter.c b/src/y_inter.c index 2959853e0..0f3b9964c 100644 --- a/src/y_inter.c +++ b/src/y_inter.c @@ -26,7 +26,7 @@ #include "w_wad.h" #include "y_inter.h" #include "z_zone.h" -#include "m_menu.h" +#include "k_menu.h" #include "m_misc.h" #include "i_system.h" #include "p_setup.h" @@ -41,11 +41,10 @@ #include "lua_hudlib_drawlist.h" #include "m_random.h" // M_RandomKey -#include "g_input.h" // PlayerInputDown +#include "g_input.h" // G_PlayerInputDown #include "k_battle.h" #include "k_boss.h" #include "k_pwrlv.h" -#include "console.h" // cons_menuhighlight #include "k_grandprix.h" #ifdef HWRENDER @@ -88,24 +87,10 @@ static patch_t *bgpatch = NULL; // INTERSCR static patch_t *widebgpatch = NULL; static patch_t *bgtile = NULL; // SPECTILE/SRB2BACK static patch_t *interpic = NULL; // custom picture defined in map header -static boolean usetile; static INT32 timer; -typedef struct -{ - INT32 source_width, source_height; - INT32 source_bpp, source_rowbytes; - UINT8 *source_picture; - INT32 target_width, target_height; - INT32 target_bpp, target_rowbytes; - UINT8 *target_picture; -} y_buffer_t; - -boolean usebuffer = false; -static boolean useinterpic; - +static INT32 timer; static INT32 powertype = PWRLV_DISABLED; -static y_buffer_t *y_buffer; static INT32 intertic; static INT32 endtic = -1; @@ -117,8 +102,6 @@ intertype_t intermissiontypes[NUMGAMETYPES]; static huddrawlist_h luahuddrawlist_intermission; static void Y_FollowIntermission(void); - -static void Y_RescaleScreenBuffer(void); static void Y_UnloadData(void); // SRB2Kart: voting stuff @@ -344,91 +327,6 @@ static void Y_CalculateMatchData(UINT8 rankingsmode, void (*comparison)(INT32)) } } -// -// Y_ConsiderScreenBuffer -// -// Can we copy the current screen to a buffer? -// -void Y_ConsiderScreenBuffer(void) -{ - if (gameaction != ga_completed) - return; - - if (y_buffer == NULL) - y_buffer = Z_Calloc(sizeof(y_buffer_t), PU_STATIC, NULL); - else - return; - - y_buffer->source_width = vid.width; - y_buffer->source_height = vid.height; - y_buffer->source_bpp = vid.bpp; - y_buffer->source_rowbytes = vid.rowbytes; - y_buffer->source_picture = ZZ_Alloc(y_buffer->source_width*vid.bpp * y_buffer->source_height); - VID_BlitLinearScreen(screens[1], y_buffer->source_picture, vid.width*vid.bpp, vid.height, vid.width*vid.bpp, vid.rowbytes); - - // Make the rescaled screen buffer - Y_RescaleScreenBuffer(); -} - -// -// Y_RescaleScreenBuffer -// -// Write the rescaled source picture, to the destination picture that has the current screen's resolutions. -// -static void Y_RescaleScreenBuffer(void) -{ - INT32 sx, sy; // source - INT32 dx, dy; // dest - fixed_t scalefac, yscalefac; - fixed_t rowfrac, colfrac; - UINT8 *dest; - - // Who knows? - if (y_buffer == NULL) - return; - - if (y_buffer->target_picture) - Z_Free(y_buffer->target_picture); - - y_buffer->target_width = vid.width; - y_buffer->target_height = vid.height; - y_buffer->target_rowbytes = vid.rowbytes; - y_buffer->target_bpp = vid.bpp; - y_buffer->target_picture = ZZ_Alloc(y_buffer->target_width*vid.bpp * y_buffer->target_height); - dest = y_buffer->target_picture; - - scalefac = FixedDiv(y_buffer->target_width*FRACUNIT, y_buffer->source_width*FRACUNIT); - yscalefac = FixedDiv(y_buffer->target_height*FRACUNIT, y_buffer->source_height*FRACUNIT); - - rowfrac = FixedDiv(FRACUNIT, yscalefac); - colfrac = FixedDiv(FRACUNIT, scalefac); - - for (sy = 0, dy = 0; sy < (y_buffer->source_height << FRACBITS) && dy < y_buffer->target_height; sy += rowfrac, dy++) - for (sx = 0, dx = 0; sx < (y_buffer->source_width << FRACBITS) && dx < y_buffer->target_width; sx += colfrac, dx += y_buffer->target_bpp) - dest[(dy * y_buffer->target_rowbytes) + dx] = y_buffer->source_picture[((sy>>FRACBITS) * y_buffer->source_width) + (sx>>FRACBITS)]; -} - -// -// Y_CleanupScreenBuffer -// -// Free all related memory. -// -void Y_CleanupScreenBuffer(void) -{ - // Who knows? - if (y_buffer == NULL) - return; - - if (y_buffer->target_picture) - Z_Free(y_buffer->target_picture); - - if (y_buffer->source_picture) - Z_Free(y_buffer->source_picture); - - Z_Free(y_buffer); - y_buffer = NULL; -} - // // Y_IntermissionDrawer // @@ -442,38 +340,10 @@ void Y_IntermissionDrawer(void) if (intertype == int_none || rendermode == render_none) return; - if (useinterpic) - V_DrawScaledPatch(0, 0, 0, interpic); - else if (!usetile) + // the merge was kind of a mess, how does this work -- toast 171021 { - if (rendermode == render_soft && usebuffer) - { - // no y_buffer - if (y_buffer == NULL) - VID_BlitLinearScreen(screens[1], screens[0], vid.width*vid.bpp, vid.height, vid.width*vid.bpp, vid.rowbytes); - else - { - // Maybe the resolution changed? - if ((y_buffer->target_width != vid.width) || (y_buffer->target_height != vid.height)) - Y_RescaleScreenBuffer(); - - // Blit the already-scaled screen buffer to the current screen - VID_BlitLinearScreen(y_buffer->target_picture, screens[0], vid.width*vid.bpp, vid.height, vid.width*vid.bpp, vid.rowbytes); - } - } -#ifdef HWRENDER - else if (rendermode != render_soft && usebuffer) - HWR_DrawIntermissionBG(); -#endif - else if (bgpatch) - { - fixed_t hs = vid.width * FRACUNIT / BASEVIDWIDTH; - fixed_t vs = vid.height * FRACUNIT / BASEVIDHEIGHT; - V_DrawStretchyFixedPatch(0, 0, hs, vs, V_NOSCALEPATCH, bgpatch, NULL); - } + M_DrawMenuBackground(); } - else if (bgtile) - V_DrawPatchFill(bgtile); if (renderisnewtic) { @@ -485,15 +355,10 @@ void Y_IntermissionDrawer(void) if (!LUA_HudEnabled(hud_intermissiontally)) goto skiptallydrawer; - if (usebuffer) // Fade everything out - V_DrawFadeScreen(0xFF00, 22); - if (!r_splitscreen) whiteplayer = demo.playback ? displayplayers[0] : consoleplayer; - if (cons_menuhighlight.value) - hilicol = cons_menuhighlight.value; - else if (modeattacking) + if (modeattacking) hilicol = V_ORANGEMAP; else hilicol = ((intertype == int_race) ? V_SKYMAP : V_REDMAP); @@ -639,7 +504,7 @@ void Y_IntermissionDrawer(void) { if (powertype != PWRLV_DISABLED && !clientpowerlevels[data.num[i]][powertype]) { - // No power level (splitscreen guests) + // No power level (guests) STRBUFCPY(strtime, "----"); } else @@ -710,7 +575,7 @@ skiptallydrawer: if (!LUA_HudEnabled(hud_intermissionmessages)) return; - if (timer && grandprixinfo.gp == false && bossinfo.boss == false) + if (timer && grandprixinfo.gp == false && bossinfo.boss == false && !modeattacking) { char *string; INT32 tickdown = (timer+1)/TICRATE; @@ -720,38 +585,40 @@ skiptallydrawer: else string = va("%s starts in %d", cv_advancemap.string, tickdown); - V_DrawCenteredString(BASEVIDWIDTH/2, 188, hilicol, - string); - } + V_DrawCenteredString(BASEVIDWIDTH/2, 188, hilicol, string); - if ((demo.recording || demo.savemode == DSM_SAVED) && !demo.playback) - switch (demo.savemode) + if ((demo.recording || demo.savemode == DSM_SAVED) && !demo.playback) { - case DSM_NOTSAVING: - V_DrawRightAlignedThinString(BASEVIDWIDTH - 2, 2, V_SNAPTOTOP|V_SNAPTORIGHT|V_ALLOWLOWERCASE|hilicol, "Look Backward: Save replay"); - break; + switch (demo.savemode) + { + case DSM_NOTSAVING: + V_DrawRightAlignedThinString(BASEVIDWIDTH - 2, 2, V_SNAPTOTOP|V_SNAPTORIGHT|V_ALLOWLOWERCASE|hilicol, "(B): Save replay"); + break; - case DSM_SAVED: - V_DrawRightAlignedThinString(BASEVIDWIDTH - 2, 2, V_SNAPTOTOP|V_SNAPTORIGHT|V_ALLOWLOWERCASE|hilicol, "Replay saved!"); - break; + case DSM_SAVED: + V_DrawRightAlignedThinString(BASEVIDWIDTH - 2, 2, V_SNAPTOTOP|V_SNAPTORIGHT|V_ALLOWLOWERCASE|hilicol, "Replay saved!"); + break; - case DSM_TITLEENTRY: - ST_DrawDemoTitleEntry(); - break; + case DSM_TITLEENTRY: + ST_DrawDemoTitleEntry(); + break; - default: // Don't render any text here - break; + default: // Don't render any text here + break; + } } - //if ((intertic/TICRATE) & 1) // Make it obvious that scrambling is happening next round. (OR NOT, I GUESS) - //{ - /*if (cv_scrambleonchange.value && cv_teamscramble.value) - V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT/2, hilicol, M_GetText("Teams will be scrambled next round!"));*/ + //if ((intertic/TICRATE) & 1) // Make it obvious that scrambling is happening next round. (OR NOT, I GUESS) + //{ + if (speedscramble != -1 && speedscramble != gamespeed) + { + V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-24, hilicol|V_ALLOWLOWERCASE|V_SNAPTOBOTTOM, + va(M_GetText("Next race will be %s Speed!"), kartspeed_cons_t[1+speedscramble].strvalue)); + } + //} + } - if (speedscramble != -1 && speedscramble != gamespeed) - V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-24, hilicol|V_ALLOWLOWERCASE|V_SNAPTOBOTTOM, - va(M_GetText("Next race will be %s Speed!"), kartspeed_cons_t[1+speedscramble].strvalue)); - //} + M_DrawMenuForeground(); } // @@ -766,7 +633,7 @@ void Y_Ticker(void) if (demo.recording) { - if (demo.savemode == DSM_NOTSAVING && PlayerInputDown(1, gc_lookback)) + if (demo.savemode == DSM_NOTSAVING && G_PlayerInputDown(0, gc_y, 0)) demo.savemode = DSM_TITLEENTRY; if (demo.savemode == DSM_WILLSAVE || demo.savemode == DSM_WILLAUTOSAVE) @@ -1017,12 +884,10 @@ void Y_StartIntermission(void) K_CashInPowerLevels(); } - //if (intertype == int_race || intertype == int_battle) - { - //bgtile = W_CachePatchName("SRB2BACK", PU_STATIC); - usetile = useinterpic = false; - usebuffer = true; - } + bgpatch = W_CachePatchName("MENUBG", PU_STATIC); + widebgpatch = W_CachePatchName("WEIRDRES", PU_STATIC); + + M_UpdateMenuBGImage(true); } // ====== @@ -1038,7 +903,6 @@ void Y_EndIntermission(void) endtic = -1; sorttic = -1; intertype = int_none; - usebuffer = false; } // @@ -1318,9 +1182,7 @@ void Y_VoteDrawer(void) if (timer) { INT32 hilicol, tickdown = (timer+1)/TICRATE; - if (cons_menuhighlight.value) - hilicol = cons_menuhighlight.value; - else if (gametype == GT_RACE) + if (gametype == GT_RACE) hilicol = V_SKYMAP; else //if (gametype == GT_BATTLE) hilicol = V_REDMAP; @@ -1503,15 +1365,15 @@ void Y_VoteTicker(void) if ((playeringame[p] && !players[p].spectator) && !voteclient.playerinfo[i].delay - && pickedvote == -1 && votes[p] == -1) + && pickedvote == -1 && votes[p] == -1 && menuactive == false) { - if (PlayerInputDown(i+1, gc_aimforward) || PlayerJoyAxis(i+1, AXISAIM) < 0) + if (G_PlayerInputDown(i, gc_up, 0)) { voteclient.playerinfo[i].selection--; pressed = true; } - if ((PlayerInputDown(i+1, gc_aimbackward) || PlayerJoyAxis(i+1, AXISAIM) > 0) && !pressed) + if (G_PlayerInputDown(i, gc_down, 0) && pressed == false) { voteclient.playerinfo[i].selection++; pressed = true; @@ -1522,7 +1384,7 @@ void Y_VoteTicker(void) if (voteclient.playerinfo[i].selection > 3) voteclient.playerinfo[i].selection = 0; - if ((PlayerInputDown(i+1, gc_accelerate) || PlayerJoyAxis(i+1, AXISMOVE) > 0) && !pressed) + if (G_PlayerInputDown(i, gc_a, 0) && pressed == false) { D_ModifyClientVote(consoleplayer, voteclient.playerinfo[i].selection, i); pressed = true; @@ -1667,7 +1529,7 @@ void Y_StartVote(void) // set up the gtc and gts levelinfo[i].gtc = G_GetGametypeColor(votelevels[i][1]); if (i == 2 && votelevels[i][1] != votelevels[0][1]) - levelinfo[i].gts = gametype_cons_t[votelevels[i][1]].strvalue; + levelinfo[i].gts = Gametype_Names[votelevels[i][1]]; else levelinfo[i].gts = NULL; diff --git a/src/y_inter.h b/src/y_inter.h index 296ffed31..6a887dea8 100644 --- a/src/y_inter.h +++ b/src/y_inter.h @@ -9,17 +9,12 @@ /// \file y_inter.h /// \brief Tally screens, or "Intermissions" as they were formally called in Doom -extern boolean usebuffer; - void Y_IntermissionDrawer(void); void Y_Ticker(void); void Y_StartIntermission(void); void Y_EndIntermission(void); -void Y_ConsiderScreenBuffer(void); -void Y_CleanupScreenBuffer(void); - void Y_DetermineIntermissionType(void); void Y_VoteDrawer(void);