Merge branch 'profiles-polish' into 'master'

Controls menu POLISH (and some Profiles options polish); menu deadzone tweak

Closes #520

See merge request KartKrew/Kart!1940
This commit is contained in:
AJ Martinez 2024-02-23 04:29:07 +00:00
commit ecefb96229
7 changed files with 554 additions and 265 deletions

View file

@ -972,7 +972,7 @@ INT32 G_PlayerInputAnalog(UINT8 p, INT32 gc, UINT8 menuPlayers)
boolean G_PlayerInputDown(UINT8 p, INT32 gc, UINT8 menuPlayers) boolean G_PlayerInputDown(UINT8 p, INT32 gc, UINT8 menuPlayers)
{ {
return (G_PlayerInputAnalog(p, gc, menuPlayers) != 0); return (abs(G_PlayerInputAnalog(p, gc, menuPlayers)) >= JOYAXISRANGE/2);
} }
// //

View file

@ -966,6 +966,7 @@ struct modedesc_t
#define MAXCOLUMNMODES 12 //max modes displayed in one column #define MAXCOLUMNMODES 12 //max modes displayed in one column
#define MAXMODEDESCS (MAXCOLUMNMODES*3) #define MAXMODEDESCS (MAXCOLUMNMODES*3)
#define M_OPTIONS_OFSTIME 5 #define M_OPTIONS_OFSTIME 5
#define M_OPTIONS_BINDBEN_QUICK 106
// Keep track of some options properties // Keep track of some options properties
extern struct optionsmenu_s { extern struct optionsmenu_s {
@ -996,8 +997,10 @@ extern struct optionsmenu_s {
// This is only applied to the profile when you exit out of the controls menu. // This is only applied to the profile when you exit out of the controls menu.
INT16 controlscroll; // scrolling for the control 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 bindtimer; // Timer until binding is cancelled (5s)
UINT16 bindben; // Hold right timer
UINT8 bindben_swallow; // (bool) control is about to be cleared; (int) swallow/pose animation timer
INT32 bindinputs[MAXINPUTMAPPING]; // Set while binding
INT16 trycontroller; // Starts at 3*TICRATE, holding B lowers this, when at 0, cancel controller try mode. INT16 trycontroller; // Starts at 3*TICRATE, holding B lowers this, when at 0, cancel controller try mode.
@ -1068,6 +1071,8 @@ boolean M_ProfileEditInputs(INT32 ch);
void M_HandleProfileControls(void); void M_HandleProfileControls(void);
boolean M_ProfileControlsInputs(INT32 ch); boolean M_ProfileControlsInputs(INT32 ch);
void M_ProfileSetControl(INT32 ch); void M_ProfileSetControl(INT32 ch);
void M_ProfileDefaultControls(INT32 ch);
void M_ProfileClearControls(INT32 ch);
void M_MapProfileControl(event_t *ev); void M_MapProfileControl(event_t *ev);
void M_ProfileTryController(INT32 choice); void M_ProfileTryController(INT32 choice);

View file

@ -2382,7 +2382,7 @@ void M_DrawCharacterSelect(void)
UINT8 priority = 0; UINT8 priority = 0;
INT16 quadx, quady; INT16 quadx, quady;
INT16 skin; INT16 skin;
INT32 basex = optionsmenu.profile ? (64 + M_EaseWithTransition(Easing_Linear, 5 * 32)) : 0; INT32 basex = optionsmenu.profile ? (64 + M_EaseWithTransition(Easing_InSine, 5 * 48)) : 0;
boolean forceskin = M_CharacterSelectForceInAction(); boolean forceskin = M_CharacterSelectForceInAction();
if (setup_numplayers > 0) if (setup_numplayers > 0)
@ -4676,6 +4676,7 @@ void M_DrawEditProfile(void)
UINT8 *colormap = NULL; UINT8 *colormap = NULL;
INT32 tflag = (currentMenu->menuitems[i].status & IT_TRANSTEXT) ? V_TRANSLUCENT : 0; INT32 tflag = (currentMenu->menuitems[i].status & IT_TRANSTEXT) ? V_TRANSLUCENT : 0;
INT32 cx = x;
y = currentMenu->menuitems[i].mvar2; y = currentMenu->menuitems[i].mvar2;
@ -4686,13 +4687,14 @@ void M_DrawEditProfile(void)
{ {
colormap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_PLAGUE, GTC_CACHE); colormap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_PLAGUE, GTC_CACHE);
V_DrawMenuString(x - 10 - (skullAnimCounter/5), y+1, highlightflags, "\x1C"); // left arrow cx += Easing_OutSine(M_DueFrac(optionsmenu.offset.start, 2), 0, 5);
V_DrawMenuString(cx - 10 - (skullAnimCounter/5), y+1, highlightflags, "\x1C"); // left arrow
} }
// Text // Text
//V_DrawGamemodeString(x, y - 6, tflag, colormap, currentMenu->menuitems[i].text); //V_DrawGamemodeString(cx, y - 6, tflag, colormap, currentMenu->menuitems[i].text);
V_DrawStringScaled( V_DrawStringScaled(
x * FRACUNIT, cx * FRACUNIT,
(y - 3) * FRACUNIT, (y - 3) * FRACUNIT,
FRACUNIT, FRACUNIT,
FRACUNIT, FRACUNIT,
@ -4722,10 +4724,10 @@ void M_DrawEditProfile(void)
// Controller offsets to center on each button. // Controller offsets to center on each button.
INT16 controlleroffsets[][2] = { INT16 controlleroffsets[][2] = {
{0, 0}, // gc_none {0, 0}, // gc_none
{70, 112}, // gc_up {69, 142}, // gc_up
{70, 112}, // gc_down {69, 182}, // gc_down
{70, 112}, // gc_left {49, 162}, // gc_left
{70, 112}, // gc_right {89, 162}, // gc_right
{208, 200}, // gc_a {208, 200}, // gc_a
{237, 181}, // gc_b {237, 181}, // gc_b
{267, 166}, // gc_c {267, 166}, // gc_c
@ -4738,22 +4740,146 @@ INT16 controlleroffsets[][2] = {
}; };
// Controller patches for button presses. // 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) // reminder that lumpnames can only be 8 chars at most. (+1 for \0)
char controllerpresspatch[9][2][9] = { static const char *controllerpresspatch[9][2] = {
{"", "BTP_A"}, // MBT_A {"PR_BTA", "PR_BTAB"}, // MBT_A
{"", "BTP_B"}, // MBT_B {"PR_BTB", "PR_BTBB"}, // MBT_B
{"", "BTP_C"}, // MBT_C {"PR_BTC", "PR_BTCB"}, // MBT_C
{"", "BTP_X"}, // MBT_X {"PR_BTX", "PR_BTXB"}, // MBT_X
{"", "BTP_Y"}, // MBT_Y {"PR_BTY", "PR_BTYB"}, // MBT_Y
{"", "BTP_Z"}, // MBT_Z {"PR_BTZ", "PR_BTZB"}, // MBT_Z
{"BTNP_L", "BTP_L"},// MBT_L {"PR_BTL", "PR_BTLB"}, // MBT_L
{"BTNP_R", "BTP_R"},// MBT_R {"PR_BTR", "PR_BTRB"}, // MBT_R
{"", "BTP_ST"} // MBT_START {"PR_BTS", "PR_BTSB"}, // MBT_START
}; };
static const char *M_GetDPadPatchName(SINT8 ud, SINT8 lr)
{
if (ud < 0)
{
if (lr < 0)
return "PR_PADUL";
else if (lr > 0)
return "PR_PADUR";
else
return "PR_PADU";
}
else if (ud > 0)
{
if (lr < 0)
return "PR_PADDL";
else if (lr > 0)
return "PR_PADDR";
else
return "PR_PADD";
}
else
{
if (lr < 0)
return "PR_PADL";
else if (lr > 0)
return "PR_PADR";
else
return "PR_PADN";
}
}
static void M_DrawBindBen(INT32 x, INT32 y, INT32 scroll_remaining)
{
// optionsmenu.bindben_swallow
const int pose_time = 30;
const int swallow_time = pose_time + 14;
// Lid closed
int state = 'A';
int frame = 0;
if (optionsmenu.bindben_swallow > 100)
{
// Quick swallow (C button)
state = 'C';
int t = 106 - optionsmenu.bindben_swallow;
if (t < 3)
frame = 0;
else
frame = t - 3;
}
else if (scroll_remaining <= 0)
{
// Chewing (text done scrolling)
state = 'B';
frame = I_GetTime() / 2 % 4;
// When state changes from 'lid open' to 'chewing',
// play chomp sound.
if (!optionsmenu.bindben_swallow)
S_StartSound(NULL, sfx_monch);
// Ready to swallow when button is released.
optionsmenu.bindben_swallow = swallow_time + 1;
}
else if (optionsmenu.bindben)
{
// Lid open (text scrolling)
frame = 1;
}
else if (optionsmenu.bindben_swallow)
{
if (optionsmenu.bindben_swallow > pose_time)
{
// Swallow
state = 'C';
int t = swallow_time - optionsmenu.bindben_swallow;
if (t < 8)
frame = 0;
else
frame = 1 + (t - 8) / 2 % 3;
}
else
{
// Pose
state = 'D';
int t = pose_time - optionsmenu.bindben_swallow;
if (t < 10)
frame = 0;
else
frame = 1 + (t - 10) / 4 % 5;
}
}
V_DrawMappedPatch(x-30, y, 0, W_CachePatchName(va("PR_BIN%c%c", state, '1' + frame), PU_CACHE), aquamap);
}
static void M_DrawBindMediumString(INT32 y, INT32 flags, const char *string)
{
fixed_t w = V_StringScaledWidth(FRACUNIT, FRACUNIT, FRACUNIT, flags, MED_FONT, string);
fixed_t x = BASEVIDWIDTH/2 * FRACUNIT - w/2;
V_DrawStringScaled(
x,
y * FRACUNIT,
FRACUNIT,
FRACUNIT,
FRACUNIT,
flags,
NULL,
MED_FONT,
string
);
}
static INT32 M_DrawProfileLegend(INT32 x, INT32 y, const char *legend, const char *mediocre_key)
{
INT32 w = V_ThinStringWidth(legend, 0);
V_DrawThinString(x - w, y, 0, legend);
x -= w + 2;
if (mediocre_key)
M_DrawMediocreKeyboardKey(mediocre_key, &x, y, false, true);
return x;
}
// the control stuff. // the control stuff.
// Dear god. // Dear god.
@ -4764,25 +4890,28 @@ void M_DrawProfileControls(void)
INT32 x = 8; INT32 x = 8;
INT32 i, j, k; INT32 i, j, k;
const UINT8 pid = 0; const UINT8 pid = 0;
patch_t *hint = W_CachePatchName("MENUHINT", PU_CACHE);
INT32 hintofs = 3;
V_DrawScaledPatch(BASEVIDWIDTH*2/3 - optionsmenu.contx, BASEVIDHEIGHT/2 -optionsmenu.conty, 0, W_CachePatchName("PR_CONT", PU_CACHE)); V_DrawScaledPatch(BASEVIDWIDTH*2/3 - optionsmenu.contx, BASEVIDHEIGHT/2 -optionsmenu.conty, 0, W_CachePatchName("PR_CONT", PU_CACHE));
// Draw button presses... // Draw button presses...
// @TODO: Dpad when we get the sprites for it. V_DrawScaledPatch(
BASEVIDWIDTH*2/3 - optionsmenu.contx,
BASEVIDHEIGHT/2 - optionsmenu.conty,
0,
W_CachePatchName(M_GetDPadPatchName(menucmd[pid].dpad_ud, menucmd[pid].dpad_lr), PU_CACHE)
);
for (i = 0; i < 9; i++) for (i = 0; i < 9; i++)
{ {
INT32 bt = 1<<i; INT32 bt = 1<<i;
if (M_MenuButtonHeld(pid, bt)) V_DrawScaledPatch(
{ BASEVIDWIDTH*2/3 - optionsmenu.contx,
if (controllerpresspatch[i][1][0] != '\0') BASEVIDHEIGHT/2 - optionsmenu.conty,
V_DrawScaledPatch(BASEVIDWIDTH*2/3 - optionsmenu.contx, BASEVIDHEIGHT/2 -optionsmenu.conty, 0, W_CachePatchName(controllerpresspatch[i][1], PU_CACHE)); 0,
} W_CachePatchName(controllerpresspatch[i][M_MenuButtonHeld(pid, bt) != 0], PU_CACHE)
else );
{
if (controllerpresspatch[i][0][0] != '\0')
V_DrawScaledPatch(BASEVIDWIDTH*2/3 - optionsmenu.contx, BASEVIDHEIGHT/2 -optionsmenu.conty, 0, W_CachePatchName(controllerpresspatch[i][0], PU_CACHE));
}
} }
if (optionsmenu.trycontroller) if (optionsmenu.trycontroller)
@ -4790,24 +4919,39 @@ void M_DrawProfileControls(void)
optionsmenu.tcontx = BASEVIDWIDTH*2/3 - 10; optionsmenu.tcontx = BASEVIDWIDTH*2/3 - 10;
optionsmenu.tconty = BASEVIDHEIGHT/2 +70; optionsmenu.tconty = BASEVIDHEIGHT/2 +70;
V_DrawCenteredString(160, 180, highlightflags, va("PRESS NOTHING FOR %d SEC TO GO BACK", optionsmenu.trycontroller/TICRATE)); V_DrawCenteredLSTitleLowString(160, 164, 0, "TRY BUTTONS");
const char *msg = va("Press nothing for %d sec to go back", (optionsmenu.trycontroller + (TICRATE-1)) / TICRATE);
fixed_t w = V_StringScaledWidth(FRACUNIT, FRACUNIT, FRACUNIT, highlightflags, MED_FONT, msg);
V_DrawStringScaled(
160*FRACUNIT - w/2,
186*FRACUNIT,
FRACUNIT,
FRACUNIT,
FRACUNIT,
highlightflags,
NULL,
MED_FONT,
msg
);
return; // Don't draw the rest if we're trying the controller. return; // Don't draw the rest if we're trying the controller.
} }
// 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(229, 12, 0, currentMenu->menuitems[itemOn].tooltip);
}
V_DrawFill(0, 0, 138, 200, 31); // Black border V_DrawFill(0, 0, 138, 200, 31); // Black border
V_SetClipRect(
0,
0,
BASEVIDWIDTH * FRACUNIT,
(BASEVIDHEIGHT - SHORT(hint->height) + hintofs) * FRACUNIT,
0
);
// Draw the menu options... // Draw the menu options...
for (i = 0; i < currentMenu->numitems; i++) for (i = 0; i < currentMenu->numitems; i++)
{ {
char buf[256]; char buf[256];
char buf2[256];
INT32 keys[MAXINPUTMAPPING]; INT32 keys[MAXINPUTMAPPING];
// cursor // cursor
@ -4836,7 +4980,8 @@ void M_DrawProfileControls(void)
if (currentMenu->menuitems[i].patch) if (currentMenu->menuitems[i].patch)
{ {
V_DrawScaledPatch(x+12, y+12, 0, W_CachePatchName(currentMenu->menuitems[i].patch, PU_CACHE)); V_DrawScaledPatch(x-4, y+1, 0, W_CachePatchName(currentMenu->menuitems[i].patch, PU_CACHE));
V_DrawMenuString(x+12, y+2, (i == itemOn ? highlightflags : 0), currentMenu->menuitems[i].text);
drawnpatch = true; drawnpatch = true;
} }
else else
@ -4862,6 +5007,9 @@ void M_DrawProfileControls(void)
UINT8 available = 0, set = 0; UINT8 available = 0, set = 0;
if (i != itemOn)
vflags |= V_GRAYMAP;
// Get userbound controls... // Get userbound controls...
for (k = 0; k < MAXINPUTMAPPING; k++) for (k = 0; k < MAXINPUTMAPPING; k++)
{ {
@ -4882,6 +5030,7 @@ void M_DrawProfileControls(void)
}; };
buf[0] = '\0'; buf[0] = '\0';
buf2[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... // 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...
@ -4928,10 +5077,15 @@ void M_DrawProfileControls(void)
} }
}*/ }*/
char *p = buf;
if (buf[0]) if (buf[0])
; ;
else if (!set) else if (!set)
strcpy(buf, "\x85NOT BOUND"); {
vflags &= ~V_CHARCOLORMASK;
vflags |= V_REDMAP;
strcpy(buf, "NOT BOUND");
}
else else
{ {
for (k = 0; k < MAXINPUTMAPPING; k++) for (k = 0; k < MAXINPUTMAPPING; k++)
@ -4940,17 +5094,66 @@ void M_DrawProfileControls(void)
continue; continue;
if (k > 0) if (k > 0)
strcat(buf," / "); strcat(p," / ");
if (k == 2 && drawnpatch) // hacky... if (k == 2) // hacky...
strcat(buf, "\n"); p = buf2;
strcat(buf, G_KeynumToString (keys[k])); strcat(p, G_KeynumToString (keys[k]));
} }
} }
// don't shift the text if we didn't draw a patch. INT32 bindx = x;
V_DrawThinString(x + (drawnpatch ? 32 : 0), y + (drawnpatch ? 2 : 12), vflags, buf); INT32 benx = 142;
INT32 beny = y - 8;
if (i == itemOn)
{
// Extend yellow wedge down behind
// extra line.
if (buf2[0])
{
for (j=24; j < 34; j++)
V_DrawFill(0, (y)+j, 128+j, 1, 73);
benx += 10;
beny += 10;
}
// Scroll text into Bind Ben.
bindx += optionsmenu.bindben * 3;
if (buf2[0])
{
// Bind Ben: suck characters off
// the end of the first line onto
// the beginning of the second
// line.
UINT16 n = strlen(buf);
UINT16 t = min(optionsmenu.bindben, n);
memmove(&buf2[t], buf2, t + 1);
memcpy(buf2, &buf[n - t], t);
buf[n - t] = '\0';
}
}
{
cliprect_t clip;
V_SaveClipRect(&clip); // preserve cliprect for tooltip
// Clip text as it scrolls into Bind Ben.
V_SetClipRect(0, 0, (benx-14)*FRACUNIT, 200*FRACUNIT, 0);
if (i != itemOn || !optionsmenu.bindben_swallow)
{
// don't shift the text if we didn't draw a patch.
V_DrawThinString(bindx + (drawnpatch ? 13 : 1), y + 12, vflags, buf);
V_DrawThinString(bindx + (drawnpatch ? 13 : 1), y + 22, vflags, buf2);
}
V_RestoreClipRect(&clip);
}
if (i == itemOn)
M_DrawBindBen(benx, beny, (benx-14) - bindx);
// controller dest coords: // controller dest coords:
if (itemOn == i && gc > 0 && gc <= gc_start) if (itemOn == i && gc > 0 && gc <= gc_start)
@ -4967,8 +5170,29 @@ void M_DrawProfileControls(void)
} }
} }
V_ClearClipRect();
// Tooltip
// Draw it at the bottom of the screen
{
static UINT8 blue[256];
blue[31] = 253;
V_DrawMappedPatch(0, BASEVIDHEIGHT + hintofs, V_VFLIP, hint, blue);
}
if (currentMenu->menuitems[itemOn].tooltip != NULL)
{
INT32 ypos = BASEVIDHEIGHT + hintofs - 9 - 12;
V_DrawThinString(12, ypos, V_YELLOWMAP, currentMenu->menuitems[itemOn].tooltip);
boolean standardbuttons = gamedata->gonerlevel > GDGONER_PROFILE;
INT32 xpos = BASEVIDWIDTH - 12;
xpos = standardbuttons ?
M_DrawProfileLegend(xpos, ypos, "\xB2/ \xBC Clear", NULL) :
M_DrawProfileLegend(xpos, ypos, "Clear", "BKSP");
}
// Overlay for control binding // Overlay for control binding
if (optionsmenu.bindcontrol) if (optionsmenu.bindtimer)
{ {
INT16 reversetimer = TICRATE*5 - optionsmenu.bindtimer; INT16 reversetimer = TICRATE*5 - optionsmenu.bindtimer;
INT32 fade = reversetimer; INT32 fade = reversetimer;
@ -4977,14 +5201,33 @@ void M_DrawProfileControls(void)
if (fade > 9) if (fade > 9)
fade = 9; fade = 9;
ypos = (BASEVIDHEIGHT/2) - 4 +16*(9 - fade); ypos = (BASEVIDHEIGHT/2) - 20 +16*(9 - fade);
V_DrawFadeScreen(31, fade); V_DrawFadeScreen(31, fade);
M_DrawTextBox((BASEVIDWIDTH/2) - (120), ypos - 12, 30, 4); M_DrawTextBox((BASEVIDWIDTH/2) - (120), ypos - 12, 30, 8);
V_DrawCenteredString(BASEVIDWIDTH/2, ypos, 0, va("Press key #%d for control", optionsmenu.bindcontrol)); V_DrawCenteredMenuString(BASEVIDWIDTH/2, ypos, V_GRAYMAP, "Hold and release inputs for");
V_DrawCenteredString(BASEVIDWIDTH/2, ypos +10, 0, va("\"%s\"", currentMenu->menuitems[itemOn].text)); V_DrawCenteredMenuString(BASEVIDWIDTH/2, ypos + 10, V_GRAYMAP, va("\"%s\"", currentMenu->menuitems[itemOn].text));
V_DrawCenteredString(BASEVIDWIDTH/2, ypos +20, highlightflags, va("(WAIT %d SECONDS TO SKIP)", optionsmenu.bindtimer/TICRATE));
if (optionsmenu.bindtimer > 0)
{
M_DrawBindMediumString(
ypos + 50,
highlightflags,
va("(WAIT %d SEC TO SKIP)", (optionsmenu.bindtimer + (TICRATE-1)) / TICRATE)
);
}
else
{
for (i = 0; i < MAXINPUTMAPPING && optionsmenu.bindinputs[i]; ++i)
{
M_DrawBindMediumString(
ypos + (2 + i)*10,
highlightflags | V_FORCEUPPERCASE,
G_KeynumToString(optionsmenu.bindinputs[i])
);
}
}
} }
} }

View file

@ -308,7 +308,7 @@ boolean M_Responder(event_t *ev)
// Profiles: Control mapping. // Profiles: Control mapping.
// We take the WHOLE EVENT for convenience. // We take the WHOLE EVENT for convenience.
if (optionsmenu.bindcontrol) if (optionsmenu.bindtimer)
{ {
M_MapProfileControl(ev); M_MapProfileControl(ev);
return true; // eat events. return true; // eat events.

View file

@ -1,6 +1,7 @@
/// \file menus/options-profiles-edit-1.c /// \file menus/options-profiles-edit-1.c
/// \brief Profile Editor /// \brief Profile Editor
#include "../i_time.h"
#include "../k_menu.h" #include "../k_menu.h"
#include "../s_sound.h" #include "../s_sound.h"
#include "../m_cond.h" #include "../m_cond.h"
@ -157,6 +158,11 @@ boolean M_ProfileEditInputs(INT32 ch)
return true; // No. return true; // No.
} }
if (menucmd[pid].dpad_ud != 0)
{
optionsmenu.offset.start = I_GetTime();
}
return false; return false;
} }
@ -219,7 +225,6 @@ void M_ProfileDeviceSelect(INT32 choice)
// While we're here, setup the incoming controls menu to reset the scroll & bind status: // While we're here, setup the incoming controls menu to reset the scroll & bind status:
optionsmenu.controlscroll = 0; optionsmenu.controlscroll = 0;
optionsmenu.bindcontrol = 0;
optionsmenu.bindtimer = 0; optionsmenu.bindtimer = 0;
optionsmenu.lastkey = 0; optionsmenu.lastkey = 0;

View file

@ -5,6 +5,7 @@
#include "../command.h" #include "../command.h"
#include "../k_menu.h" #include "../k_menu.h"
#include "../m_easing.h"
#include "../p_local.h" // cv_tilting #include "../p_local.h" // cv_tilting
extern "C" consvar_t cv_mindelay; extern "C" consvar_t cv_mindelay;
@ -16,7 +17,7 @@ namespace
void draw_routine() void draw_routine()
{ {
Draw row = Draw(0, currentMenu->y).font(Draw::Font::kMenu); Draw row = Draw(M_EaseWithTransition(Easing_InSine, 5 * 48), currentMenu->y).font(Draw::Font::kMenu);
M_DrawEditProfileTooltips(); M_DrawEditProfileTooltips();

View file

@ -1,6 +1,7 @@
/// \file menus/options-profiles-edit-controls.c /// \file menus/options-profiles-edit-controls.c
/// \brief Profile Controls Editor /// \brief Profile Controls Editor
#include "../g_input.h"
#include "../k_menu.h" #include "../k_menu.h"
#include "../s_sound.h" #include "../s_sound.h"
#include "../i_joy.h" // for joystick menu controls #include "../i_joy.h" // for joystick menu controls
@ -10,44 +11,44 @@ menuitem_t OPTIONS_ProfileControls[] = {
{IT_HEADER, "MAIN CONTROLS", "That's the stuff on the controller!!", {IT_HEADER, "MAIN CONTROLS", "That's the stuff on the controller!!",
NULL, {NULL}, 0, 0}, NULL, {NULL}, 0, 0},
{IT_CONTROL, "A", "Accelerate / Confirm", {IT_CONTROL, "Accel / Confirm", "Accelerate / Confirm",
"PR_BTA", {.routine = M_ProfileSetControl}, gc_a, 0}, "TLB_A", {.routine = M_ProfileSetControl}, gc_a, 0},
{IT_CONTROL, "B", "Look backwards / Back", {IT_CONTROL, "Look back", "Look backwards / Go back",
"PR_BTB", {.routine = M_ProfileSetControl}, gc_b, 0}, "TLB_B", {.routine = M_ProfileSetControl}, gc_b, 0},
{IT_CONTROL, "C", "Spindash / Extra", {IT_CONTROL, "Spindash", "Spindash / Extra",
"PR_BTC", {.routine = M_ProfileSetControl}, gc_c, 0}, "TLB_C", {.routine = M_ProfileSetControl}, gc_c, 0},
{IT_CONTROL, "X", "Brake / Back", {IT_CONTROL, "Brake / Go back", "Brake / Go back",
"PR_BTX", {.routine = M_ProfileSetControl}, gc_x, 0}, "TLB_D", {.routine = M_ProfileSetControl}, gc_x, 0},
{IT_CONTROL, "Y", "Respawn", {IT_CONTROL, "Respawn", "Respawn",
"PR_BTY", {.routine = M_ProfileSetControl}, gc_y, 0}, "TLB_E", {.routine = M_ProfileSetControl}, gc_y, 0},
{IT_CONTROL, "Z", "Multiplayer quick-chat / quick-vote", {IT_CONTROL, "Action", "Multiplayer quick-chat / quick-vote",
"PR_BTZ", {.routine = M_ProfileSetControl}, gc_z, 0}, "TLB_F", {.routine = M_ProfileSetControl}, gc_z, 0},
{IT_CONTROL, "L", "Use item", {IT_CONTROL, "Use Item", "Use item",
"PR_BTL", {.routine = M_ProfileSetControl}, gc_l, 0}, "TLB_H", {.routine = M_ProfileSetControl}, gc_l, 0},
{IT_CONTROL, "R", "Drift", {IT_CONTROL, "Drift", "Drift",
"PR_BTR", {.routine = M_ProfileSetControl}, gc_r, 0}, "TLB_I", {.routine = M_ProfileSetControl}, gc_r, 0},
{IT_CONTROL, "Turn Left", "Turn left", {IT_CONTROL, "Turn Left", "Turn left",
"PR_PADL", {.routine = M_ProfileSetControl}, gc_left, 0}, "TLB_M", {.routine = M_ProfileSetControl}, gc_left, 0},
{IT_CONTROL, "Turn Right", "Turn right", {IT_CONTROL, "Turn Right", "Turn right",
"PR_PADR", {.routine = M_ProfileSetControl}, gc_right, 0}, "TLB_L", {.routine = M_ProfileSetControl}, gc_right, 0},
{IT_CONTROL, "Aim Forward", "Aim forwards", {IT_CONTROL, "Aim Forward", "Aim forwards",
"PR_PADU", {.routine = M_ProfileSetControl}, gc_up, 0}, "TLB_J", {.routine = M_ProfileSetControl}, gc_up, 0},
{IT_CONTROL, "Aim Backwards", "Aim backwards", {IT_CONTROL, "Aim Backwards", "Aim backwards",
"PR_PADD", {.routine = M_ProfileSetControl}, gc_down, 0}, "TLB_K", {.routine = M_ProfileSetControl}, gc_down, 0},
{IT_CONTROL, "Start", "Open pause menu", {IT_CONTROL, "Open pause menu", "Open pause menu",
"PR_BTS", {.routine = M_ProfileSetControl}, gc_start, 0}, "TLB_G", {.routine = M_ProfileSetControl}, gc_start, 0},
{IT_HEADER, "OPTIONAL CONTROLS", "Take a screenshot, chat...", {IT_HEADER, "OPTIONAL CONTROLS", "Take a screenshot, chat...",
NULL, {NULL}, 0, 0}, NULL, {NULL}, 0, 0},
@ -58,21 +59,18 @@ menuitem_t OPTIONS_ProfileControls[] = {
{IT_CONTROL, "RECORD VIDEO", "Record a video with sound.", {IT_CONTROL, "RECORD VIDEO", "Record a video with sound.",
NULL, {.routine = M_ProfileSetControl}, gc_startmovie, 0}, NULL, {.routine = M_ProfileSetControl}, gc_startmovie, 0},
{IT_CONTROL, "RECORD LOSSLESS", "Record a pixel perfect GIF.", {IT_CONTROL, "RECORD GIF", "Record a pixel perfect GIF.",
NULL, {.routine = M_ProfileSetControl}, gc_startlossless, 0}, NULL, {.routine = M_ProfileSetControl}, gc_startlossless, 0},
{IT_CONTROL, "SHOW RANKINGS", "Display the current rankings mid-game.",
NULL, {.routine = M_ProfileSetControl}, gc_rankings, 0},
{IT_CONTROL, "OPEN CHAT", "Opens full keyboard chatting for online games.", {IT_CONTROL, "OPEN CHAT", "Opens full keyboard chatting for online games.",
NULL, {.routine = M_ProfileSetControl}, gc_talk, 0}, NULL, {.routine = M_ProfileSetControl}, gc_talk, 0},
{IT_CONTROL, "OPEN TEAM CHAT", "Opens team-only full chat for online games.", {IT_CONTROL, "OPEN TEAM CHAT", "Opens team-only full chat for online games.",
NULL, {.routine = M_ProfileSetControl}, gc_teamtalk, 0}, NULL, {.routine = M_ProfileSetControl}, gc_teamtalk, 0},
{IT_CONTROL, "SHOW RANKINGS", "Display the current rankings mid-game.",
NULL, {.routine = M_ProfileSetControl}, gc_rankings, 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.", {IT_CONTROL, "LUA/A", "May be used by add-ons.",
NULL, {.routine = M_ProfileSetControl}, gc_luaa, 0}, NULL, {.routine = M_ProfileSetControl}, gc_luaa, 0},
@ -82,12 +80,21 @@ menuitem_t OPTIONS_ProfileControls[] = {
{IT_CONTROL, "LUA/C", "May be used by add-ons.", {IT_CONTROL, "LUA/C", "May be used by add-ons.",
NULL, {.routine = M_ProfileSetControl}, gc_luac, 0}, NULL, {.routine = M_ProfileSetControl}, gc_luac, 0},
{IT_HEADER, "EXTRA", "", {IT_CONTROL, "OPEN CONSOLE", "Opens the developer options console.",
NULL, {.routine = M_ProfileSetControl}, gc_console, 0},
{IT_HEADER, "TEST AND CONFIRM", "",
NULL, {NULL}, 0, 0}, NULL, {NULL}, 0, 0},
{IT_STRING | IT_CALL, "TRY MAPPINGS", "Test your controls.", {IT_STRING | IT_CALL, "TRY MAPPINGS", "Test your controls.",
NULL, {.routine = M_ProfileTryController}, 0, 0}, NULL, {.routine = M_ProfileTryController}, 0, 0},
{IT_STRING | IT_CALL, "RESET TO DEFAULT", "Reset all controls back to default.",
NULL, {.routine = M_ProfileDefaultControls}, 0, 0},
{IT_STRING | IT_CALL, "CLEAR ALL", "Unbind all controls.",
NULL, {.routine = M_ProfileClearControls}, 0, 0},
{IT_STRING | IT_CALL, "CONFIRM", "Go back to profile setup.", {IT_STRING | IT_CALL, "CONFIRM", "Go back to profile setup.",
NULL, {.routine = M_ProfileControlsConfirm}, 0, 0}, NULL, {.routine = M_ProfileControlsConfirm}, 0, 0},
}; };
@ -129,8 +136,26 @@ static void SetDeviceOnPress(void)
} }
*/ */
static boolean M_ClearCurrentControl(void)
{
// 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;
return true;
}
return false;
}
void M_HandleProfileControls(void) void M_HandleProfileControls(void)
{ {
const UINT8 pid = 0;
UINT8 maxscroll = currentMenu->numitems - 5; UINT8 maxscroll = currentMenu->numitems - 5;
M_OptionsTick(); M_OptionsTick();
@ -151,14 +176,39 @@ void M_HandleProfileControls(void)
optionsmenu.controlscroll = 0; optionsmenu.controlscroll = 0;
// bindings, cancel if timer is depleted. // bindings, cancel if timer is depleted.
if (optionsmenu.bindcontrol) if (optionsmenu.bindtimer)
{ {
optionsmenu.bindtimer--; if (optionsmenu.bindtimer > 0)
if (!optionsmenu.bindtimer) optionsmenu.bindtimer--;
}
else if (currentMenu->menuitems[itemOn].mvar1) // check if we're on a valid menu option...
{
// Hold right to begin clearing the control.
//
// If bindben timer increases enough, bindben_swallow
// will be set.
// This is a commitment to clear the control.
// You can keep holding right to postpone the clear
// but once you let go, you are locked out of
// pressing it again until the animation finishes.
if (menucmd[pid].dpad_lr > 0 && (optionsmenu.bindben || !optionsmenu.bindben_swallow))
{ {
optionsmenu.bindcontrol = 0; // we've gone past the max, just stop. optionsmenu.bindben++;
} }
else
{
optionsmenu.bindben = 0;
if (optionsmenu.bindben_swallow)
{
optionsmenu.bindben_swallow--;
if (optionsmenu.bindben_swallow == 100) // special countdown for the "quick" animation
optionsmenu.bindben_swallow = 0;
else if (!optionsmenu.bindben_swallow) // long animation, clears control when done
M_ClearCurrentControl();
}
}
} }
} }
@ -186,19 +236,37 @@ static void M_ProfileControlSaveResponse(INT32 choice)
{ {
memcpy(&gamecontrol[belongsto], optionsmenu.tempcontrols, sizeof(gamecontroldefault)); memcpy(&gamecontrol[belongsto], optionsmenu.tempcontrols, sizeof(gamecontroldefault));
} }
M_GoBack(0);
} }
else
{
// Revert changes
memcpy(optionsmenu.tempcontrols, optionsmenu.profile->controls, sizeof(gamecontroldefault));
}
M_GoBack(0);
} }
void M_ProfileControlsConfirm(INT32 choice) void M_ProfileControlsConfirm(INT32 choice)
{ {
(void)choice; if (!memcmp(optionsmenu.profile->controls, optionsmenu.tempcontrols, sizeof(gamecontroldefault)))
{
M_GoBack(0); // no change
}
else if (choice == 0)
{
M_StartMessage(
"Profiles",
"You have unsaved changes to your controls.\n"
"Please confirm if you wish to save them.\n",
&M_ProfileControlSaveResponse,
MM_YESNO,
NULL,
NULL
);
}
else
M_ProfileControlSaveResponse(MA_YES);
//M_StartMessage("Profiles", M_GetText("Exiting will save the control changes\nfor this Profile.\nIs this okay?\n"), &M_ProfileControlSaveResponse, MM_YESNO, NULL, NULL);
// TODO: Add a graphic for controls saving, instead of obnoxious prompt.
M_ProfileControlSaveResponse(MA_YES);
// Reapply player 1's real profile. // Reapply player 1's real profile.
if (cv_currprofile.value > -1) if (cv_currprofile.value > -1)
@ -217,6 +285,33 @@ boolean M_ProfileControlsInputs(INT32 ch)
{ {
if (menucmd[pid].dpad_ud || menucmd[pid].dpad_lr || menucmd[pid].buttons) if (menucmd[pid].dpad_ud || menucmd[pid].dpad_lr || menucmd[pid].buttons)
{ {
if (menucmd[pid].dpad_ud != menucmd[pid].prev_dpad_ud || menucmd[pid].dpad_lr != menucmd[pid].prev_dpad_lr)
S_StartSound(NULL, sfx_s3k5b);
UINT32 newbuttons = menucmd[pid].buttons & ~(menucmd[pid].buttonsHeld);
if (newbuttons & MBT_L)
S_StartSound(NULL, sfx_kc69);
if (newbuttons & MBT_R)
S_StartSound(NULL, sfx_s3ka2);
if (newbuttons & MBT_A)
S_StartSound(NULL, sfx_kc3c);
if (newbuttons & MBT_B)
S_StartSound(NULL, sfx_3db09);
if (newbuttons & MBT_C)
S_StartSound(NULL, sfx_s1be);
if (newbuttons & MBT_X)
S_StartSound(NULL, sfx_s1a4);
if (newbuttons & MBT_Y)
S_StartSound(NULL, sfx_s3kcas);
if (newbuttons & MBT_Z)
S_StartSound(NULL, sfx_s3kc3s);
if (newbuttons & MBT_START)
S_StartSound(NULL, sfx_gshdc);
optionsmenu.trycontroller = 5*TICRATE; optionsmenu.trycontroller = 5*TICRATE;
} }
else else
@ -236,24 +331,17 @@ boolean M_ProfileControlsInputs(INT32 ch)
return true; return true;
} }
if (optionsmenu.bindcontrol) if (optionsmenu.bindtimer)
return true; // Eat all inputs there. We'll use a stupid hack in M_Responder instead. 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... //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)) if (M_MenuExtraPressed(pid))
{ {
// check if we're on a valid menu option... if (M_ClearCurrentControl())
if (currentMenu->menuitems[itemOn].mvar1) S_StartSound(NULL, sfx_monch);
{ optionsmenu.bindben = 0;
// clear controls for that key optionsmenu.bindben_swallow = M_OPTIONS_BINDBEN_QUICK;
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); M_SetMenuDelay(pid);
return true; return true;
} }
@ -264,30 +352,73 @@ boolean M_ProfileControlsInputs(INT32 ch)
return true; return true;
} }
if (menucmd[pid].dpad_ud)
{
if (optionsmenu.bindben_swallow)
{
// Control would be cleared, but we're
// interrupting the animation so clear it
// immediately.
M_ClearCurrentControl();
}
optionsmenu.bindben = 0;
optionsmenu.bindben_swallow = 0;
}
return false; return false;
} }
void M_ProfileSetControl(INT32 ch) void M_ProfileSetControl(INT32 ch)
{ {
INT32 controln = currentMenu->menuitems[itemOn].mvar1;
UINT8 i;
(void) ch; (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; optionsmenu.bindtimer = TICRATE*5;
memset(optionsmenu.bindinputs, 0, sizeof optionsmenu.bindinputs);
G_ResetAllDeviceGameKeyDown();
}
static void M_ProfileDefaultControlsResponse(INT32 ch)
{
if (ch == MA_YES)
{
memcpy(&optionsmenu.tempcontrols, gamecontroldefault, sizeof optionsmenu.tempcontrols);
S_StartSound(NULL, sfx_s24f);
}
}
void M_ProfileDefaultControls(INT32 ch)
{
(void)ch;
M_StartMessage(
"Profiles",
"Reset all controls to the default mappings?",
&M_ProfileDefaultControlsResponse,
MM_YESNO,
NULL,
NULL
);
}
static void M_ProfileClearControlsResponse(INT32 ch)
{
if (ch == MA_YES)
{
memset(&optionsmenu.tempcontrols, 0, sizeof optionsmenu.tempcontrols);
S_StartSound(NULL, sfx_s3k66);
}
}
void M_ProfileClearControls(INT32 ch)
{
(void)ch;
M_StartMessage(
"Profiles",
"Clear all control bindings?",
&M_ProfileClearControlsResponse,
MM_YESNO,
NULL,
NULL
);
} }
// Map the event to the profile. // Map the event to the profile.
@ -295,157 +426,61 @@ void M_ProfileSetControl(INT32 ch)
#define KEYHOLDFOR 1 #define KEYHOLDFOR 1
void M_MapProfileControl(event_t *ev) void M_MapProfileControl(event_t *ev)
{ {
INT32 c = 0; if (ev->type == ev_keydown && ev->data2) // ignore repeating keys
UINT8 n = optionsmenu.bindcontrol-1; // # of input to bind return;
INT32 controln = currentMenu->menuitems[itemOn].mvar1; // gc_
UINT8 where = n; // By default, we'll save the bind where we're supposed to map. if (optionsmenu.bindtimer > TICRATE*5 - 9) // grace period after entering the bind dialog
INT32 i; return;
INT32 *DeviceGameKeyDownArray = G_GetDeviceGameKeyDownArray(ev->device); INT32 *DeviceGameKeyDownArray = G_GetDeviceGameKeyDownArray(ev->device);
if (!DeviceGameKeyDownArray) if (!DeviceGameKeyDownArray)
return; return;
//SetDeviceOnPress(); // Update player gamepad assignments // Find every held button.
boolean noinput = true;
// Only consider keydown and joystick events to make sure we ignore ev_mouse and other events for (INT32 c = 1; c < NUMINPUTS; ++c)
// See also G_MapEventsToControls
switch (ev->type)
{ {
case ev_keydown: if (DeviceGameKeyDownArray[c] < 3*JOYAXISRANGE/4)
if (ev->data1 < NUMINPUTS) continue;
{
c = ev->data1;
}
#ifdef PARANOIA
else
{
CONS_Debug(DBG_GAMELOGIC, "Bad downkey input %d\n", ev->data1);
}
#endif
break;
case ev_gamepad_axis:
if (ev->data1 >= JOYAXES)
{
#ifdef PARANOIA
CONS_Debug(DBG_GAMELOGIC, "Bad gamepad 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; noinput = false;
if (i >= JOYANALOGS) for (UINT8 i = 0; i < MAXINPUTMAPPING; ++i)
{
// 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);
// Reset this input so (keyboard keys at least) are not
// buffered and caught by menucmd.
DeviceGameKeyDownArray[c] = 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; // If this key is already bound, don't bind it again.
return; if (optionsmenu.bindinputs[i] == c)
} break;
}
// 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. // Find the first available slot.
// Unless of course you tamper with the cfg file, but then it's *your* fault, not mine. if (!optionsmenu.bindinputs[i])
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); optionsmenu.bindinputs[i] = c;
break; break;
} }
} }
} }
*/
if (noinput)
{
{
// You can hold a button before entering this
// dialog, then buffer a keyup without pressing
// anything else. If this happens, don't wipe the
// binds, just ignore it.
const UINT8 zero[sizeof optionsmenu.bindinputs] = {0};
if (!memcmp(zero, optionsmenu.bindinputs, sizeof zero))
return;
}
INT32 controln = currentMenu->menuitems[itemOn].mvar1;
memcpy(&optionsmenu.tempcontrols[controln], optionsmenu.bindinputs, sizeof optionsmenu.bindinputs);
optionsmenu.bindtimer = 0;
// Set menu delay regardless of what we're doing to avoid stupid stuff.
M_SetMenuDelay(0);
}
else
optionsmenu.bindtimer = -1; // prevent skip countdown
} }
#undef KEYHOLDFOR #undef KEYHOLDFOR