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)
{
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 MAXMODEDESCS (MAXCOLUMNMODES*3)
#define M_OPTIONS_OFSTIME 5
#define M_OPTIONS_BINDBEN_QUICK 106
// Keep track of some options properties
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.
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)
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.
@ -1068,6 +1071,8 @@ boolean M_ProfileEditInputs(INT32 ch);
void M_HandleProfileControls(void);
boolean M_ProfileControlsInputs(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_ProfileTryController(INT32 choice);

View file

@ -2382,7 +2382,7 @@ void M_DrawCharacterSelect(void)
UINT8 priority = 0;
INT16 quadx, quady;
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();
if (setup_numplayers > 0)
@ -4676,6 +4676,7 @@ void M_DrawEditProfile(void)
UINT8 *colormap = NULL;
INT32 tflag = (currentMenu->menuitems[i].status & IT_TRANSTEXT) ? V_TRANSLUCENT : 0;
INT32 cx = x;
y = currentMenu->menuitems[i].mvar2;
@ -4686,13 +4687,14 @@ void M_DrawEditProfile(void)
{
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
//V_DrawGamemodeString(x, y - 6, tflag, colormap, currentMenu->menuitems[i].text);
//V_DrawGamemodeString(cx, y - 6, tflag, colormap, currentMenu->menuitems[i].text);
V_DrawStringScaled(
x * FRACUNIT,
cx * FRACUNIT,
(y - 3) * FRACUNIT,
FRACUNIT,
FRACUNIT,
@ -4722,10 +4724,10 @@ void M_DrawEditProfile(void)
// 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
{69, 142}, // gc_up
{69, 182}, // gc_down
{49, 162}, // gc_left
{89, 162}, // gc_right
{208, 200}, // gc_a
{237, 181}, // gc_b
{267, 166}, // gc_c
@ -4738,22 +4740,146 @@ INT16 controlleroffsets[][2] = {
};
// 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
static const char *controllerpresspatch[9][2] = {
{"PR_BTA", "PR_BTAB"}, // MBT_A
{"PR_BTB", "PR_BTBB"}, // MBT_B
{"PR_BTC", "PR_BTCB"}, // MBT_C
{"PR_BTX", "PR_BTXB"}, // MBT_X
{"PR_BTY", "PR_BTYB"}, // MBT_Y
{"PR_BTZ", "PR_BTZB"}, // MBT_Z
{"PR_BTL", "PR_BTLB"}, // MBT_L
{"PR_BTR", "PR_BTRB"}, // MBT_R
{"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.
// Dear god.
@ -4764,25 +4890,28 @@ void M_DrawProfileControls(void)
INT32 x = 8;
INT32 i, j, k;
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));
// 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++)
{
INT32 bt = 1<<i;
if (M_MenuButtonHeld(pid, bt))
{
if (controllerpresspatch[i][1][0] != '\0')
V_DrawScaledPatch(BASEVIDWIDTH*2/3 - optionsmenu.contx, BASEVIDHEIGHT/2 -optionsmenu.conty, 0, W_CachePatchName(controllerpresspatch[i][1], 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));
}
V_DrawScaledPatch(
BASEVIDWIDTH*2/3 - optionsmenu.contx,
BASEVIDHEIGHT/2 - optionsmenu.conty,
0,
W_CachePatchName(controllerpresspatch[i][M_MenuButtonHeld(pid, bt) != 0], PU_CACHE)
);
}
if (optionsmenu.trycontroller)
@ -4790,24 +4919,39 @@ void M_DrawProfileControls(void)
optionsmenu.tcontx = BASEVIDWIDTH*2/3 - 10;
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.
}
// 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_SetClipRect(
0,
0,
BASEVIDWIDTH * FRACUNIT,
(BASEVIDHEIGHT - SHORT(hint->height) + hintofs) * FRACUNIT,
0
);
// Draw the menu options...
for (i = 0; i < currentMenu->numitems; i++)
{
char buf[256];
char buf2[256];
INT32 keys[MAXINPUTMAPPING];
// cursor
@ -4836,7 +4980,8 @@ void M_DrawProfileControls(void)
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;
}
else
@ -4862,6 +5007,9 @@ void M_DrawProfileControls(void)
UINT8 available = 0, set = 0;
if (i != itemOn)
vflags |= V_GRAYMAP;
// Get userbound controls...
for (k = 0; k < MAXINPUTMAPPING; k++)
{
@ -4882,6 +5030,7 @@ void M_DrawProfileControls(void)
};
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...
@ -4928,10 +5077,15 @@ void M_DrawProfileControls(void)
}
}*/
char *p = buf;
if (buf[0])
;
else if (!set)
strcpy(buf, "\x85NOT BOUND");
{
vflags &= ~V_CHARCOLORMASK;
vflags |= V_REDMAP;
strcpy(buf, "NOT BOUND");
}
else
{
for (k = 0; k < MAXINPUTMAPPING; k++)
@ -4940,17 +5094,66 @@ void M_DrawProfileControls(void)
continue;
if (k > 0)
strcat(buf," / ");
strcat(p," / ");
if (k == 2 && drawnpatch) // hacky...
strcat(buf, "\n");
if (k == 2) // hacky...
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.
V_DrawThinString(x + (drawnpatch ? 32 : 0), y + (drawnpatch ? 2 : 12), vflags, buf);
INT32 bindx = x;
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:
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
if (optionsmenu.bindcontrol)
if (optionsmenu.bindtimer)
{
INT16 reversetimer = TICRATE*5 - optionsmenu.bindtimer;
INT32 fade = reversetimer;
@ -4977,14 +5201,33 @@ void M_DrawProfileControls(void)
if (fade > 9)
fade = 9;
ypos = (BASEVIDHEIGHT/2) - 4 +16*(9 - fade);
ypos = (BASEVIDHEIGHT/2) - 20 +16*(9 - 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_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));
V_DrawCenteredMenuString(BASEVIDWIDTH/2, ypos, V_GRAYMAP, "Hold and release inputs for");
V_DrawCenteredMenuString(BASEVIDWIDTH/2, ypos + 10, V_GRAYMAP, va("\"%s\"", currentMenu->menuitems[itemOn].text));
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.
// We take the WHOLE EVENT for convenience.
if (optionsmenu.bindcontrol)
if (optionsmenu.bindtimer)
{
M_MapProfileControl(ev);
return true; // eat events.

View file

@ -1,6 +1,7 @@
/// \file menus/options-profiles-edit-1.c
/// \brief Profile Editor
#include "../i_time.h"
#include "../k_menu.h"
#include "../s_sound.h"
#include "../m_cond.h"
@ -157,6 +158,11 @@ boolean M_ProfileEditInputs(INT32 ch)
return true; // No.
}
if (menucmd[pid].dpad_ud != 0)
{
optionsmenu.offset.start = I_GetTime();
}
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:
optionsmenu.controlscroll = 0;
optionsmenu.bindcontrol = 0;
optionsmenu.bindtimer = 0;
optionsmenu.lastkey = 0;

View file

@ -5,6 +5,7 @@
#include "../command.h"
#include "../k_menu.h"
#include "../m_easing.h"
#include "../p_local.h" // cv_tilting
extern "C" consvar_t cv_mindelay;
@ -16,7 +17,7 @@ namespace
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();

View file

@ -1,6 +1,7 @@
/// \file menus/options-profiles-edit-controls.c
/// \brief Profile Controls Editor
#include "../g_input.h"
#include "../k_menu.h"
#include "../s_sound.h"
#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!!",
NULL, {NULL}, 0, 0},
{IT_CONTROL, "A", "Accelerate / Confirm",
"PR_BTA", {.routine = M_ProfileSetControl}, gc_a, 0},
{IT_CONTROL, "Accel / Confirm", "Accelerate / Confirm",
"TLB_A", {.routine = M_ProfileSetControl}, gc_a, 0},
{IT_CONTROL, "B", "Look backwards / Back",
"PR_BTB", {.routine = M_ProfileSetControl}, gc_b, 0},
{IT_CONTROL, "Look back", "Look backwards / Go back",
"TLB_B", {.routine = M_ProfileSetControl}, gc_b, 0},
{IT_CONTROL, "C", "Spindash / Extra",
"PR_BTC", {.routine = M_ProfileSetControl}, gc_c, 0},
{IT_CONTROL, "Spindash", "Spindash / Extra",
"TLB_C", {.routine = M_ProfileSetControl}, gc_c, 0},
{IT_CONTROL, "X", "Brake / Back",
"PR_BTX", {.routine = M_ProfileSetControl}, gc_x, 0},
{IT_CONTROL, "Brake / Go back", "Brake / Go back",
"TLB_D", {.routine = M_ProfileSetControl}, gc_x, 0},
{IT_CONTROL, "Y", "Respawn",
"PR_BTY", {.routine = M_ProfileSetControl}, gc_y, 0},
{IT_CONTROL, "Respawn", "Respawn",
"TLB_E", {.routine = M_ProfileSetControl}, gc_y, 0},
{IT_CONTROL, "Z", "Multiplayer quick-chat / quick-vote",
"PR_BTZ", {.routine = M_ProfileSetControl}, gc_z, 0},
{IT_CONTROL, "Action", "Multiplayer quick-chat / quick-vote",
"TLB_F", {.routine = M_ProfileSetControl}, gc_z, 0},
{IT_CONTROL, "L", "Use item",
"PR_BTL", {.routine = M_ProfileSetControl}, gc_l, 0},
{IT_CONTROL, "Use Item", "Use item",
"TLB_H", {.routine = M_ProfileSetControl}, gc_l, 0},
{IT_CONTROL, "R", "Drift",
"PR_BTR", {.routine = M_ProfileSetControl}, gc_r, 0},
{IT_CONTROL, "Drift", "Drift",
"TLB_I", {.routine = M_ProfileSetControl}, gc_r, 0},
{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",
"PR_PADR", {.routine = M_ProfileSetControl}, gc_right, 0},
"TLB_L", {.routine = M_ProfileSetControl}, gc_right, 0},
{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",
"PR_PADD", {.routine = M_ProfileSetControl}, gc_down, 0},
"TLB_K", {.routine = M_ProfileSetControl}, gc_down, 0},
{IT_CONTROL, "Start", "Open pause menu",
"PR_BTS", {.routine = M_ProfileSetControl}, gc_start, 0},
{IT_CONTROL, "Open pause menu", "Open pause menu",
"TLB_G", {.routine = M_ProfileSetControl}, gc_start, 0},
{IT_HEADER, "OPTIONAL CONTROLS", "Take a screenshot, chat...",
NULL, {NULL}, 0, 0},
@ -58,21 +59,18 @@ menuitem_t OPTIONS_ProfileControls[] = {
{IT_CONTROL, "RECORD VIDEO", "Record a video with sound.",
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},
{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.",
NULL, {.routine = M_ProfileSetControl}, gc_talk, 0},
{IT_CONTROL, "OPEN TEAM CHAT", "Opens team-only full chat for online games.",
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.",
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.",
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},
{IT_STRING | IT_CALL, "TRY MAPPINGS", "Test your controls.",
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.",
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)
{
const UINT8 pid = 0;
UINT8 maxscroll = currentMenu->numitems - 5;
M_OptionsTick();
@ -151,14 +176,39 @@ void M_HandleProfileControls(void)
optionsmenu.controlscroll = 0;
// bindings, cancel if timer is depleted.
if (optionsmenu.bindcontrol)
if (optionsmenu.bindtimer)
{
optionsmenu.bindtimer--;
if (!optionsmenu.bindtimer)
if (optionsmenu.bindtimer > 0)
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));
}
M_GoBack(0);
}
else
{
// Revert changes
memcpy(optionsmenu.tempcontrols, optionsmenu.profile->controls, sizeof(gamecontroldefault));
}
M_GoBack(0);
}
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.
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].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;
}
else
@ -236,24 +331,17 @@ boolean M_ProfileControlsInputs(INT32 ch)
return true;
}
if (optionsmenu.bindcontrol)
if (optionsmenu.bindtimer)
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);
}
if (M_ClearCurrentControl())
S_StartSound(NULL, sfx_monch);
optionsmenu.bindben = 0;
optionsmenu.bindben_swallow = M_OPTIONS_BINDBEN_QUICK;
M_SetMenuDelay(pid);
return true;
}
@ -264,30 +352,73 @@ boolean M_ProfileControlsInputs(INT32 ch)
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;
}
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;
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.
@ -295,157 +426,61 @@ void M_ProfileSetControl(INT32 ch)
#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;
if (ev->type == ev_keydown && ev->data2) // ignore repeating keys
return;
if (optionsmenu.bindtimer > TICRATE*5 - 9) // grace period after entering the bind dialog
return;
INT32 *DeviceGameKeyDownArray = G_GetDeviceGameKeyDownArray(ev->device);
if (!DeviceGameKeyDownArray)
return;
//SetDeviceOnPress(); // Update player gamepad assignments
// Only consider keydown and joystick events to make sure we ignore ev_mouse and other events
// See also G_MapEventsToControls
switch (ev->type)
// Find every held button.
boolean noinput = true;
for (INT32 c = 1; c < NUMINPUTS; ++c)
{
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_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));
if (DeviceGameKeyDownArray[c] < 3*JOYAXISRANGE/4)
continue;
i = ev->data1;
noinput = false;
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);
// 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)
for (UINT8 i = 0; i < MAXINPUTMAPPING; ++i)
{
optionsmenu.bindcontrol = 0;
return;
}
}
// If this key is already bound, don't bind it again.
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.
// 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)
// Find the first available slot.
if (!optionsmenu.bindinputs[i])
{
PR_ApplyProfile(pnum, i);
optionsmenu.bindinputs[i] = c;
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