mirror of
https://github.com/KartKrewDev/RingRacers.git
synced 2025-10-30 08:01:28 +00:00
1120 lines
26 KiB
C
1120 lines
26 KiB
C
/// \file k_menufunc.c
|
|
/// \brief SRB2Kart's menu functions
|
|
|
|
#ifdef __GNUC__
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
#include "k_menu.h"
|
|
|
|
#include "doomdef.h"
|
|
#include "d_main.h"
|
|
#include "console.h"
|
|
#include "hu_stuff.h"
|
|
#include "s_sound.h"
|
|
#include "v_video.h"
|
|
#include "f_finale.h"
|
|
#include "m_misc.h"
|
|
|
|
#ifdef PC_DOS
|
|
#include <stdio.h> // 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;
|
|
menu_t *restoreMenu = NULL;
|
|
|
|
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];
|
|
|
|
|
|
// 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 Dummystaff_OnChange(void);
|
|
|
|
consvar_t cv_showfocuslost = CVAR_INIT ("showfocuslost", "Yes", CV_SAVE, CV_YesNo, NULL);
|
|
|
|
consvar_t cv_menujam_update = CVAR_INIT ("menujam_update", "Off", CV_SAVE, CV_OnOff, NULL);
|
|
static CV_PossibleValue_t menujam_cons_t[] = {{0, "menu"}, {1, "menu2"}, {2, "menu3"}, {0, NULL}};
|
|
static consvar_t cv_menujam = CVAR_INIT ("menujam", "0", CV_SAVE, menujam_cons_t, NULL);
|
|
|
|
// first time memory
|
|
consvar_t cv_tutorialprompt = CVAR_INIT ("tutorialprompt", "On", CV_SAVE, CV_OnOff, 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 CV_PossibleValue_t dummyteam_cons_t[] = {{0, "Spectator"}, {1, "Red"}, {2, "Blue"}, {0, NULL}};
|
|
static CV_PossibleValue_t dummyspectate_cons_t[] = {{0, "Spectator"}, {1, "Playing"}, {0, NULL}};
|
|
static CV_PossibleValue_t dummyscramble_cons_t[] = {{0, "Random"}, {1, "Points"}, {0, NULL}};
|
|
static CV_PossibleValue_t dummystaff_cons_t[] = {{0, "MIN"}, {100, "MAX"}, {0, NULL}};
|
|
|
|
static consvar_t cv_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);
|
|
consvar_t cv_dummystaff = CVAR_INIT ("dummystaff", "0", CV_HIDDEN|CV_CALL, dummystaff_cons_t, Dummystaff_OnChange);
|
|
consvar_t cv_dummyspectate = CVAR_INIT ("dummyspectate", "Spectator", CV_HIDDEN, dummyspectate_cons_t, NULL);
|
|
|
|
// ==========================================================================
|
|
// CVAR ONCHANGE EVENTS GO HERE
|
|
// ==========================================================================
|
|
// (there's only a couple anyway)
|
|
|
|
static void Dummystaff_OnChange(void)
|
|
{
|
|
#ifdef STAFFGHOSTS
|
|
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 //#ifdef STAFFGHOSTS
|
|
}
|
|
|
|
|
|
// =========================================================================
|
|
// BASIC MENU HANDLING
|
|
// =========================================================================
|
|
|
|
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
|
|
{
|
|
if (cv == &cv_nettimeout || cv == &cv_jointimeout)
|
|
choice *= (TICRATE/7);
|
|
else if (cv == &cv_maxsend)
|
|
choice *= 512;
|
|
else if (cv == &cv_maxping)
|
|
choice *= 50;
|
|
|
|
CV_AddValue(cv, choice);
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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)
|
|
{
|
|
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_SpecificMenuRestore
|
|
//
|
|
menu_t *M_SpecificMenuRestore(menu_t *torestore)
|
|
{
|
|
// I'd advise the following not be a switch case because they're pointers...
|
|
|
|
if (torestore == &PLAY_CupSelectDef
|
|
|| torestore == &PLAY_LevelSelectDef
|
|
|| torestore == &PLAY_TimeAttackDef)
|
|
{
|
|
// Handle unlock restrictions
|
|
cupheader_t *currentcup = levellist.levelsearch.cup;
|
|
|
|
M_SetupGametypeMenu(-1);
|
|
|
|
if (levellist.newgametype == GT_RACE)
|
|
{
|
|
M_SetupRaceMenu(-1);
|
|
}
|
|
|
|
if (!M_LevelListFromGametype(-1))
|
|
{
|
|
if (PLAY_LevelSelectDef.prevMenu == &PLAY_CupSelectDef)
|
|
{
|
|
torestore = PLAY_CupSelectDef.prevMenu;
|
|
}
|
|
else
|
|
{
|
|
torestore = PLAY_LevelSelectDef.prevMenu;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (currentcup != NULL && levellist.levelsearch.cup == NULL)
|
|
{
|
|
torestore = &PLAY_CupSelectDef;
|
|
}
|
|
else if (torestore == &PLAY_TimeAttackDef)
|
|
{
|
|
M_PrepareTimeAttack(0);
|
|
}
|
|
}
|
|
}
|
|
else if (torestore == &EXTRAS_ReplayHutDef)
|
|
{
|
|
// Handle modifications to the folder while playing
|
|
M_ReplayHut(0);
|
|
|
|
if (demo.inreplayhut == false)
|
|
{
|
|
torestore = &EXTRAS_MainDef;
|
|
}
|
|
}
|
|
|
|
if (setup_numplayers == 0)
|
|
{
|
|
setup_numplayers = 1;
|
|
}
|
|
|
|
return torestore;
|
|
}
|
|
|
|
//
|
|
// 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;
|
|
}
|
|
|
|
// intro might call this repeatedly
|
|
if (menuactive && gamestate != GS_NULL)
|
|
{
|
|
CON_ToggleOff(); // move away console
|
|
return;
|
|
}
|
|
|
|
if (gamestate == GS_TITLESCREEN && restoreMenu == NULL) // Set up menu state
|
|
{
|
|
// No instantly skipping the titlescreen.
|
|
// (We can change this timer later when extra animation is added.)
|
|
if (finalecount < 1)
|
|
return;
|
|
}
|
|
|
|
menuactive = true;
|
|
|
|
if (demo.playback)
|
|
{
|
|
currentMenu = &PAUSE_PlaybackMenuDef;
|
|
}
|
|
else if (!Playing())
|
|
{
|
|
M_StopMessage(0); // Doesn't work with MM_YESNO or MM_EVENTHANDLER... but good enough to get the game as it is currently functional again
|
|
|
|
if (gamestate != GS_MENU)
|
|
{
|
|
G_SetGamestate(GS_MENU);
|
|
|
|
gameaction = ga_nothing;
|
|
paused = false;
|
|
CON_ToggleOff();
|
|
|
|
modeattacking = ATTACKING_NONE;
|
|
|
|
if (cv_menujam_update.value)
|
|
{
|
|
CV_AddValue(&cv_menujam, 1);
|
|
CV_SetValue(&cv_menujam_update, 0);
|
|
}
|
|
|
|
S_ChangeMusicInternal(cv_menujam.string, true);
|
|
}
|
|
|
|
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.
|
|
|
|
// 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
|
|
{
|
|
if (restoreMenu == NULL)
|
|
restoreMenu = &MainDef;
|
|
currentMenu = M_SpecificMenuRestore(M_InterruptMenuWithChallenges(restoreMenu));
|
|
}
|
|
|
|
restoreMenu = NULL;
|
|
}
|
|
else
|
|
{
|
|
M_OpenPauseMenu();
|
|
}
|
|
|
|
itemOn = currentMenu->lastOn;
|
|
|
|
CON_ToggleOff(); // move away console
|
|
}
|
|
|
|
//
|
|
// M_ClearMenus
|
|
//
|
|
void M_ClearMenus(boolean callexitmenufunc)
|
|
{
|
|
if (!menuactive)
|
|
return;
|
|
|
|
CON_ClearHUD();
|
|
|
|
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
|
|
boolean M_MenuConfirmPressed(UINT8 pid)
|
|
{
|
|
return M_MenuButtonPressed(pid, MBT_A);
|
|
}
|
|
|
|
/*static boolean M_MenuConfirmHeld(UINT8 pid)
|
|
{
|
|
return M_MenuButtonHeld(pid, MBT_A);
|
|
}*/
|
|
|
|
// Returns true if we press the Cancel button
|
|
boolean M_MenuBackPressed(UINT8 pid)
|
|
{
|
|
return (M_MenuButtonPressed(pid, MBT_B) || M_MenuButtonPressed(pid, MBT_X));
|
|
}
|
|
|
|
/*static boolean M_MenuBackHeld(UINT8 pid)
|
|
{
|
|
return (M_MenuButtonHeld(pid, MBT_B) || M_MenuButtonHeld(pid, MBT_X));
|
|
}*/
|
|
|
|
// Retrurns true if we press the tertiary option button (C)
|
|
boolean M_MenuExtraPressed(UINT8 pid)
|
|
{
|
|
return M_MenuButtonPressed(pid, MBT_C);
|
|
}
|
|
|
|
boolean M_MenuExtraHeld(UINT8 pid)
|
|
{
|
|
return M_MenuButtonHeld(pid, MBT_C);
|
|
}
|
|
|
|
|
|
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;
|
|
|
|
// If we ever add a second horizontal menu, make it a menu_t property, not an extra check.
|
|
if (currentMenu == &PAUSE_PlaybackMenuDef)
|
|
{
|
|
ud = menucmd[pid].dpad_lr;
|
|
lr = -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\nPress (B)\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 (!menuactive)
|
|
{
|
|
noFurtherInput = false;
|
|
return;
|
|
}
|
|
|
|
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);
|
|
|
|
// don't lose your position in the jam cycle
|
|
CV_RegisterVar(&cv_menujam_update);
|
|
CV_RegisterVar(&cv_menujam);
|
|
|
|
CV_RegisterVar(&cv_serversort);
|
|
|
|
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_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);
|
|
}
|