diff --git a/src/engine/shared/config_variables_bestclient.h b/src/engine/shared/config_variables_bestclient.h index f5d28c8e0..85ac85aa3 100644 --- a/src/engine/shared/config_variables_bestclient.h +++ b/src/engine/shared/config_variables_bestclient.h @@ -112,6 +112,7 @@ MACRO_CONFIG_INT(BcPrevInpMousesensSmallSens, bc_prev_inp_mousesens_small_sens, MACRO_CONFIG_INT(BcToggleSmallSens, bc_toggle_small_sens, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Use toggle mode for small sens bind") // Inputs +MACRO_CONFIG_INT(BcInputDebounce, bc_input_debounce, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Preserve button presses shorter than one simulation tick") MACRO_CONFIG_INT(BcInputs, bc_inputs, 0, 0, 6, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Input prediction mode (0 = off, 1 = fast, 2 = best, 3 = saiko, 4 = delta, 5 = f, 6 = cloud)") MACRO_CONFIG_INT(BcBestInputAmount, bc_best_input_amount, 0, 0, 1000, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Best input prediction amount in 0.01 ticks") MACRO_CONFIG_INT(BcBestInputSmoothing, bc_best_input_smoothing, 0, 0, 100, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Best input smoothing amount (0-100%)") diff --git a/src/game/client/components/bestclient/menus_bestclient.cpp b/src/game/client/components/bestclient/menus_bestclient.cpp index 38f27fe5e..fda9aa9af 100644 --- a/src/game/client/components/bestclient/menus_bestclient.cpp +++ b/src/game/client/components/bestclient/menus_bestclient.cpp @@ -2976,7 +2976,7 @@ void CMenus::RenderSettingsBestClientGameplay(CUIRect MainView) const float InputsExpandedTargetHeight = (MarginSmall + LineSize) + InputsModeFieldsHeight; const float InputsExpandedHeight = BC_MODULE_REVEAL(InputsEnabled, InputsExpandedTargetHeight, Client()->RenderFrameTime()); const float InputsHeaderHeight = LineSize + MarginSmall + LineSize; - const float InputsFooterLines = g_Config.m_ClSubTickAiming ? 2.0f : 3.0f; + const float InputsFooterLines = g_Config.m_ClSubTickAiming ? 3.0f : 4.0f; const float InputsBlockHeight = InputsHeaderHeight + InputsExpandedHeight + InputsFooterLines * (MarginSmall + LineSize); CUIRect InputsBlock; @@ -3176,6 +3176,10 @@ void CMenus::RenderSettingsBestClientGameplay(CUIRect MainView) InputsBlock.HSplitTop(LineSize, &Content, &InputsBlock); DoButton_CheckBoxAutoVMarginAndSet(&g_Config.m_BcAutoMargin, BcLocalize("Auto margin"), &g_Config.m_BcAutoMargin, &Content, LineSize); + InputsBlock.HSplitTop(MarginSmall, nullptr, &InputsBlock); + InputsBlock.HSplitTop(LineSize, &Content, &InputsBlock); + DoButton_CheckBoxAutoVMarginAndSet(&g_Config.m_BcInputDebounce, BcLocalize("Input Debounce"), &g_Config.m_BcInputDebounce, &Content, LineSize); + LeftColumn.HSplitTop(MarginBetweenViews, nullptr, &LeftColumn); const bool SnapTapBlocked = GameClient()->m_SnapTap.IsBlockedByCommunity(); diff --git a/src/game/client/components/controls.cpp b/src/game/client/components/controls.cpp index a06c09697..8a8cde908 100644 --- a/src/game/client/components/controls.cpp +++ b/src/game/client/components/controls.cpp @@ -44,6 +44,7 @@ void CControls::OnReset() void CControls::ResetInput(int Dummy) { + m_aInputDebounce[Dummy] = {}; m_aLastData[Dummy].m_Direction = 0; // simulate releasing the fire button if((m_aLastData[Dummy].m_Fire & 1) != 0) @@ -69,6 +70,7 @@ struct CInputState { CControls *m_pControls; int *m_apVariables[NUM_DUMMIES]; + int m_Input; }; void CControls::ConKeyInputState(IConsole::IResult *pResult, void *pUserData) @@ -78,7 +80,22 @@ void CControls::ConKeyInputState(IConsole::IResult *pResult, void *pUserData) if(pState->m_pControls->GameClient()->m_GameInfo.m_BugDDRaceInput && pState->m_pControls->GameClient()->m_Snap.m_SpecInfo.m_Active) return; - *pState->m_apVariables[g_Config.m_ClDummy] = pResult->GetInteger(0); + int *pVariable = pState->m_apVariables[g_Config.m_ClDummy]; + const int Value = pResult->GetInteger(0); + if(!Value && *pVariable && g_Config.m_BcInputDebounce && pState->m_Input >= 0) + pState->m_pControls->InputDebounce(g_Config.m_ClDummy, pState->m_Input); + *pVariable = Value; +} + +void CControls::InputDebounce(int Dummy, int Input) +{ + switch(Input) + { + case INPUT_LEFT: if(m_aLastData[Dummy].m_Direction != -1) m_aInputDebounce[Dummy].m_Direction = -1; break; + case INPUT_RIGHT: if(m_aLastData[Dummy].m_Direction != 1) m_aInputDebounce[Dummy].m_Direction = 1; break; + case INPUT_JUMP: m_aInputDebounce[Dummy].m_Jump = m_aLastData[Dummy].m_Jump ? -1 : 1; break; + case INPUT_HOOK: m_aInputDebounce[Dummy].m_Hook = m_aLastData[Dummy].m_Hook ? -1 : 1; break; + } } void CControls::ConKeyInputCounter(IConsole::IResult *pResult, void *pUserData) @@ -89,10 +106,10 @@ void CControls::ConKeyInputCounter(IConsole::IResult *pResult, void *pUserData) return; int *pVariable = pState->m_apVariables[g_Config.m_ClDummy]; - if(((*pVariable) & 1) != pResult->GetInteger(0)) + const bool Pressed = pResult->GetInteger(0) != 0; + if(((*pVariable) & 1) != Pressed) (*pVariable)++; *pVariable &= INPUT_STATE_MASK; - // bestclient if(pVariable == &pState->m_pControls->m_aInputData[g_Config.m_ClDummy].m_Fire) pState->m_pControls->GameClient()->m_BestClient.OnGoresFireInput(pResult->GetInteger(0)); // bestclient @@ -125,27 +142,27 @@ void CControls::OnConsoleInit() { // game commands { - static CInputState s_State = {this, {&m_aInputDirectionLeft[0], &m_aInputDirectionLeft[1]}}; + static CInputState s_State = {this, {&m_aInputDirectionLeft[0], &m_aInputDirectionLeft[1]}, INPUT_LEFT}; Console()->Register("+left", "", CFGFLAG_CLIENT, ConKeyInputState, &s_State, "Move left"); } { - static CInputState s_State = {this, {&m_aInputDirectionRight[0], &m_aInputDirectionRight[1]}}; + static CInputState s_State = {this, {&m_aInputDirectionRight[0], &m_aInputDirectionRight[1]}, INPUT_RIGHT}; Console()->Register("+right", "", CFGFLAG_CLIENT, ConKeyInputState, &s_State, "Move right"); } { - static CInputState s_State = {this, {&m_aInputData[0].m_Jump, &m_aInputData[1].m_Jump}}; + static CInputState s_State = {this, {&m_aInputData[0].m_Jump, &m_aInputData[1].m_Jump}, INPUT_JUMP}; Console()->Register("+jump", "", CFGFLAG_CLIENT, ConKeyInputState, &s_State, "Jump"); } { - static CInputState s_State = {this, {&m_aInputData[0].m_Hook, &m_aInputData[1].m_Hook}}; + static CInputState s_State = {this, {&m_aInputData[0].m_Hook, &m_aInputData[1].m_Hook}, INPUT_HOOK}; Console()->Register("+hook", "", CFGFLAG_CLIENT, ConKeyInputState, &s_State, "Hook"); } { - static CInputState s_State = {this, {&m_aInputData[0].m_Fire, &m_aInputData[1].m_Fire}}; + static CInputState s_State = {this, {&m_aInputData[0].m_Fire, &m_aInputData[1].m_Fire}, -1}; Console()->Register("+fire", "", CFGFLAG_CLIENT, ConKeyInputCounter, &s_State, "Fire"); } { - static CInputState s_State = {this, {&m_aShowHookColl[0], &m_aShowHookColl[1]}}; + static CInputState s_State = {this, {&m_aShowHookColl[0], &m_aShowHookColl[1]}, -1}; Console()->Register("+showhookcoll", "", CFGFLAG_CLIENT, ConKeyInputState, &s_State, "Show Hook Collision"); } @@ -194,6 +211,9 @@ void CControls::OnMessage(int Msg, void *pRawMsg) int CControls::SnapInput(int *pData) { + const int UnlatchedJump = m_aInputData[g_Config.m_ClDummy].m_Jump; + const int UnlatchedHook = m_aInputData[g_Config.m_ClDummy].m_Hook; + // bestclient GameClient()->m_BestClient.UpdateGoresMode(); // bestclient @@ -252,7 +272,6 @@ int CControls::SnapInput(int *pData) ResetInput(g_Config.m_ClDummy); mem_copy(pData, &m_aInputData[g_Config.m_ClDummy], sizeof(m_aInputData[0])); - // set the target anyway though so that we can keep seeing our surroundings, // even if chat or menu are activated vec2 Pos = GameClient()->m_Controls.m_aMousePos[g_Config.m_ClDummy]; @@ -303,6 +322,13 @@ int CControls::SnapInput(int *pData) const bool LeftPressed = m_aInputDirectionLeft[g_Config.m_ClDummy] != 0; const bool RightPressed = m_aInputDirectionRight[g_Config.m_ClDummy] != 0; m_aInputData[g_Config.m_ClDummy].m_Direction = GameClient()->m_SnapTap.ResolveMovementDirection(g_Config.m_ClDummy, LeftPressed, RightPressed, true); + const CInputDebounceState &DebounceState = m_aInputDebounce[g_Config.m_ClDummy]; + if(m_aInputData[g_Config.m_ClDummy].m_Direction == 0 && DebounceState.m_Direction) + m_aInputData[g_Config.m_ClDummy].m_Direction = DebounceState.m_Direction; + if(DebounceState.m_Jump) + m_aInputData[g_Config.m_ClDummy].m_Jump = DebounceState.m_Jump > 0; + if(DebounceState.m_Hook) + m_aInputData[g_Config.m_ClDummy].m_Hook = DebounceState.m_Hook > 0; // bestclient // dummy copy moves @@ -380,10 +406,22 @@ int CControls::SnapInput(int *pData) m_aLastData[g_Config.m_ClDummy] = m_aInputData[g_Config.m_ClDummy]; if(!Send) + { + if(m_aInputDebounce[g_Config.m_ClDummy].m_Jump) + m_aInputData[g_Config.m_ClDummy].m_Jump = UnlatchedJump; + if(m_aInputDebounce[g_Config.m_ClDummy].m_Hook) + m_aInputData[g_Config.m_ClDummy].m_Hook = UnlatchedHook; + m_aInputDebounce[g_Config.m_ClDummy] = {}; return 0; + } m_LastSendTime = time_get(); mem_copy(pData, &m_aInputData[g_Config.m_ClDummy], sizeof(m_aInputData[0])); + if(m_aInputDebounce[g_Config.m_ClDummy].m_Jump) + m_aInputData[g_Config.m_ClDummy].m_Jump = UnlatchedJump; + if(m_aInputDebounce[g_Config.m_ClDummy].m_Hook) + m_aInputData[g_Config.m_ClDummy].m_Hook = UnlatchedHook; + m_aInputDebounce[g_Config.m_ClDummy] = {}; return sizeof(m_aInputData[0]); } @@ -540,6 +578,10 @@ bool CControls::CheckNewInput() const bool LeftPressed = m_aInputDirectionLeft[g_Config.m_ClDummy] != 0; const bool RightPressed = m_aInputDirectionRight[g_Config.m_ClDummy] != 0; TestInput.m_Direction = GameClient()->m_SnapTap.ResolveMovementDirection(g_Config.m_ClDummy, LeftPressed, RightPressed, false); + const CInputDebounceState &DebounceState = m_aInputDebounce[g_Config.m_ClDummy]; + if(TestInput.m_Direction == 0 && DebounceState.m_Direction) TestInput.m_Direction = DebounceState.m_Direction; + if(DebounceState.m_Jump) TestInput.m_Jump = DebounceState.m_Jump > 0; + if(DebounceState.m_Hook) TestInput.m_Hook = DebounceState.m_Hook > 0; bool NewInput = false; if(m_aFastInput[g_Config.m_ClDummy].m_Direction != TestInput.m_Direction) @@ -564,7 +606,6 @@ bool CControls::CheckNewInput() } m_aFastInput[g_Config.m_ClDummy] = TestInput; - return NewInput; } // bestclient @@ -579,6 +620,10 @@ bool CControls::CheckNewInput() const bool LeftPressed = m_aInputDirectionLeft[Dummy] != 0; const bool RightPressed = m_aInputDirectionRight[Dummy] != 0; TestInput.m_Direction = GameClient()->m_SnapTap.ResolveMovementDirection(Dummy, LeftPressed, RightPressed, false); + const CInputDebounceState &DebounceState = m_aInputDebounce[Dummy]; + if(TestInput.m_Direction == 0 && DebounceState.m_Direction) TestInput.m_Direction = DebounceState.m_Direction; + if(DebounceState.m_Jump) TestInput.m_Jump = DebounceState.m_Jump > 0; + if(DebounceState.m_Hook) TestInput.m_Hook = DebounceState.m_Hook > 0; // bestclient } @@ -638,8 +683,5 @@ bool CControls::CheckNewInput() m_aFastInput[Dummy] = TestInput; } - if(NewInput[0] || NewInput[1]) - return true; - else - return false; + return NewInput[0] || NewInput[1]; } diff --git a/src/game/client/components/controls.h b/src/game/client/components/controls.h index ebe22f0a4..361dbd8ca 100644 --- a/src/game/client/components/controls.h +++ b/src/game/client/components/controls.h @@ -60,7 +60,29 @@ class CControls : public CComponent void ResetInput(int Dummy); bool CheckNewInput(); + // bestclient + void InputDebounce(int Dummy, int Input); + struct CInputDebounceState + { + int m_Direction = 0; + int m_Jump = 0; + int m_Hook = 0; + }; + CInputDebounceState m_aInputDebounce[NUM_DUMMIES]; + + // bestclient + private: + // bestclient + enum + { + INPUT_LEFT, + INPUT_RIGHT, + INPUT_JUMP, + INPUT_HOOK, + }; + // bestclient + static void ConKeyInputState(IConsole::IResult *pResult, void *pUserData); static void ConKeyInputCounter(IConsole::IResult *pResult, void *pUserData); static void ConKeyInputSet(IConsole::IResult *pResult, void *pUserData); diff --git a/src/game/client/gameclient.cpp b/src/game/client/gameclient.cpp index 1fe839fe3..4c9cec294 100644 --- a/src/game/client/gameclient.cpp +++ b/src/game/client/gameclient.cpp @@ -6365,7 +6365,10 @@ void CGameClient::StoreSave(const char *pTeamMembers, const char *pGeneratedCode bool CGameClient::CheckNewInput() { if(IsCloudInputMode()) // bestclient - return m_CloudInput.CheckNewInput(m_Controls); // bestclient + { + const bool NewInput = m_CloudInput.CheckNewInput(m_Controls); + return NewInput; + } return m_Controls.CheckNewInput(); }