diff --git a/src/k_menu.h b/src/k_menu.h index e7ce4cebd..1cf47fe78 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -997,10 +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. diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 375d1c61c..201ef185b 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -4854,6 +4854,23 @@ static void M_DrawBindBen(INT32 x, INT32 y, INT32 scroll_remaining) 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 + ); +} + // the control stuff. // Dear god. void M_DrawProfileControls(void) @@ -5158,7 +5175,7 @@ void M_DrawProfileControls(void) } // Overlay for control binding - if (optionsmenu.bindcontrol) + if (optionsmenu.bindtimer) { INT16 reversetimer = TICRATE*5 - optionsmenu.bindtimer; INT32 fade = reversetimer; @@ -5167,14 +5184,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]) + ); + } + } } } diff --git a/src/k_menufunc.c b/src/k_menufunc.c index 5f21246e2..3b531879a 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -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. diff --git a/src/menus/options-profiles-edit-1.c b/src/menus/options-profiles-edit-1.c index 64ccc43fe..9fe9997b3 100644 --- a/src/menus/options-profiles-edit-1.c +++ b/src/menus/options-profiles-edit-1.c @@ -225,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; diff --git a/src/menus/options-profiles-edit-controls.c b/src/menus/options-profiles-edit-controls.c index 7353d4341..bffdd9b02 100644 --- a/src/menus/options-profiles-edit-controls.c +++ b/src/menus/options-profiles-edit-controls.c @@ -176,14 +176,10 @@ void M_HandleProfileControls(void) optionsmenu.controlscroll = 0; // bindings, cancel if timer is depleted. - if (optionsmenu.bindcontrol) + if (optionsmenu.bindtimer) { - optionsmenu.bindtimer--; - if (!optionsmenu.bindtimer) - { - optionsmenu.bindcontrol = 0; // we've gone past the max, just stop. - } - + if (optionsmenu.bindtimer > 0) + optionsmenu.bindtimer--; } else if (currentMenu->menuitems[itemOn].mvar1) // check if we're on a valid menu option... { @@ -308,7 +304,7 @@ 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... @@ -347,25 +343,11 @@ boolean M_ProfileControlsInputs(INT32 ch) 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) @@ -417,157 +399,61 @@ void M_ProfileClearControls(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