Merge pull request #8685 from Robyt3/Client-Binds-Fixes

Support composite binds with `+` commands, fix handling of composite binds with F1-F24 keys, fix incorrect key names with multiple modifiers, refactoring
This commit is contained in:
Dennis Felsing 2024-08-04 20:44:40 +00:00 committed by GitHub
commit 8d609b9202
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 166 additions and 141 deletions

View file

@ -367,6 +367,26 @@ bool CInput::KeyState(int Key) const
return m_aInputState[Key];
}
int CInput::FindKeyByName(const char *pKeyName) const
{
// check for numeric
if(pKeyName[0] == '&')
{
int Key = str_toint(pKeyName + 1);
if(Key > KEY_FIRST && Key < KEY_LAST)
return Key; // numeric
}
// search for key
for(int Key = KEY_FIRST; Key < KEY_LAST; Key++)
{
if(str_comp_nocase(pKeyName, KeyName(Key)) == 0)
return Key;
}
return KEY_UNKNOWN;
}
void CInput::UpdateMouseState()
{
const int MouseState = SDL_GetMouseState(nullptr, nullptr);

View file

@ -136,6 +136,7 @@ public:
bool AltIsPressed() const override { return KeyState(KEY_LALT) || KeyState(KEY_RALT); }
bool KeyIsPressed(int Key) const override { return KeyState(Key); }
bool KeyPress(int Key, bool CheckCounter) const override { return CheckCounter ? (m_aInputCount[Key] == m_InputCounter) : m_aInputCount[Key]; }
int FindKeyByName(const char *pKeyName) const override;
size_t NumJoysticks() const override { return m_vJoysticks.size(); }
CJoystick *GetJoystick(size_t Index) override { return &m_vJoysticks[Index]; }

View file

@ -58,6 +58,7 @@ public:
virtual bool KeyIsPressed(int Key) const = 0;
virtual bool KeyPress(int Key, bool CheckCounter = false) const = 0;
const char *KeyName(int Key) const { return (Key >= 0 && Key < g_MaxKeys) ? g_aaKeyStrings[Key] : g_aaKeyStrings[0]; }
virtual int FindKeyByName(const char *pKeyName) const = 0;
// joystick
class IJoystick

View file

@ -11,26 +11,15 @@ static const ColorRGBA gs_BindPrintColor{1.0f, 1.0f, 0.8f, 1.0f};
bool CBinds::CBindsSpecial::OnInput(const IInput::CEvent &Event)
{
if((Event.m_Flags & (IInput::FLAG_PRESS | IInput::FLAG_RELEASE)) == 0)
return false;
// only handle F and composed F binds
if(((Event.m_Key >= KEY_F1 && Event.m_Key <= KEY_F12) || (Event.m_Key >= KEY_F13 && Event.m_Key <= KEY_F24)) && (Event.m_Key != KEY_F5 || !m_pClient->m_Menus.IsActive()))
// do not handle F5 bind while menu is active
if(((Event.m_Key >= KEY_F1 && Event.m_Key <= KEY_F12) || (Event.m_Key >= KEY_F13 && Event.m_Key <= KEY_F24)) &&
(Event.m_Key != KEY_F5 || !m_pClient->m_Menus.IsActive()))
{
int Mask = CBinds::GetModifierMask(Input());
// Look for a composed bind
bool ret = false;
if(m_pBinds->m_aapKeyBindings[Mask][Event.m_Key])
{
m_pBinds->GetConsole()->ExecuteLineStroked(Event.m_Flags & IInput::FLAG_PRESS, m_pBinds->m_aapKeyBindings[Mask][Event.m_Key]);
ret = true;
}
// Look for a non composed bind
if(!ret && m_pBinds->m_aapKeyBindings[0][Event.m_Key])
{
m_pBinds->GetConsole()->ExecuteLineStroked(Event.m_Flags & IInput::FLAG_PRESS, m_pBinds->m_aapKeyBindings[0][Event.m_Key]);
ret = true;
}
return ret;
return m_pBinds->OnInput(Event);
}
return false;
@ -44,25 +33,19 @@ CBinds::CBinds()
CBinds::~CBinds()
{
for(int i = 0; i < KEY_LAST; i++)
for(auto &apKeyBinding : m_aapKeyBindings)
free(apKeyBinding[i]);
UnbindAll();
}
void CBinds::Bind(int KeyId, const char *pStr, bool FreeOnly, int ModifierCombination)
{
if(KeyId < 0 || KeyId >= KEY_LAST)
return;
dbg_assert(KeyId >= KEY_FIRST && KeyId < KEY_LAST, "KeyId invalid");
dbg_assert(ModifierCombination >= MODIFIER_NONE && ModifierCombination < MODIFIER_COMBINATION_COUNT, "ModifierCombination invalid");
if(FreeOnly && Get(KeyId, ModifierCombination)[0])
return;
free(m_aapKeyBindings[ModifierCombination][KeyId]);
m_aapKeyBindings[ModifierCombination][KeyId] = 0;
// skip modifiers for +xxx binds
if(pStr[0] == '+')
ModifierCombination = 0;
m_aapKeyBindings[ModifierCombination][KeyId] = nullptr;
char aBuf[256];
if(!pStr[0])
@ -120,59 +103,94 @@ int CBinds::GetModifierMaskOfKey(int Key)
case KEY_RGUI:
return 1 << CBinds::MODIFIER_GUI;
default:
return 0;
return CBinds::MODIFIER_NONE;
}
}
bool CBinds::OnInput(const IInput::CEvent &Event)
{
// don't handle invalid events
if(Event.m_Key <= KEY_FIRST || Event.m_Key >= KEY_LAST)
if((Event.m_Flags & (IInput::FLAG_PRESS | IInput::FLAG_RELEASE)) == 0)
return false;
int Mask = GetModifierMask(Input());
int KeyModifierMask = GetModifierMaskOfKey(Event.m_Key);
Mask &= ~KeyModifierMask;
const int KeyModifierMask = GetModifierMaskOfKey(Event.m_Key);
const int ModifierMask = GetModifierMask(Input()) & ~KeyModifierMask;
bool ret = false;
const char *pKey = nullptr;
bool Handled = false;
if(m_aapKeyBindings[Mask][Event.m_Key])
{
if(Event.m_Flags & IInput::FLAG_PRESS)
{
Console()->ExecuteLineStroked(1, m_aapKeyBindings[Mask][Event.m_Key]);
pKey = m_aapKeyBindings[Mask][Event.m_Key];
}
// Have to check for nullptr again because the previous execute can unbind itself
if(Event.m_Flags & IInput::FLAG_RELEASE && m_aapKeyBindings[Mask][Event.m_Key])
Console()->ExecuteLineStroked(0, m_aapKeyBindings[Mask][Event.m_Key]);
ret = true;
}
if(m_aapKeyBindings[0][Event.m_Key] && !ret)
auto ActiveBind = std::find_if(m_vActiveBinds.begin(), m_vActiveBinds.end(), [&](const CBindSlot &Bind) {
return Event.m_Key == Bind.m_Key;
});
if(ActiveBind == m_vActiveBinds.end())
{
// When ctrl+shift are pressed (ctrl+shift binds and also the hard-coded ctrl+shift+d, ctrl+shift+g, ctrl+shift+e), ignore other +xxx binds
if(Event.m_Flags & IInput::FLAG_PRESS && Mask != ((1 << MODIFIER_CTRL) | (1 << MODIFIER_SHIFT)) && Mask != ((1 << MODIFIER_GUI) | (1 << MODIFIER_SHIFT)))
const auto &&OnPress = [&](int Mask) {
const char *pBind = m_aapKeyBindings[Mask][Event.m_Key];
if(g_Config.m_ClSubTickAiming)
{
Console()->ExecuteLineStroked(1, m_aapKeyBindings[0][Event.m_Key]);
pKey = m_aapKeyBindings[Mask][Event.m_Key];
}
// Have to check for nullptr again because the previous execute can unbind itself
if(Event.m_Flags & IInput::FLAG_RELEASE && m_aapKeyBindings[0][Event.m_Key])
Console()->ExecuteLineStroked(0, m_aapKeyBindings[0][Event.m_Key]);
ret = true;
}
if(g_Config.m_ClSubTickAiming && pKey)
{
if(str_comp("+fire", pKey) == 0 || str_comp("+hook", pKey) == 0)
if(str_comp("+fire", pBind) == 0 || str_comp("+hook", pBind) == 0)
{
m_MouseOnAction = true;
}
}
Console()->ExecuteLineStroked(1, pBind);
m_vActiveBinds.emplace_back(Event.m_Key, Mask);
};
return ret;
if(m_aapKeyBindings[ModifierMask][Event.m_Key])
{
OnPress(ModifierMask);
Handled = true;
}
else if(m_aapKeyBindings[MODIFIER_NONE][Event.m_Key] &&
ModifierMask != ((1 << MODIFIER_CTRL) | (1 << MODIFIER_SHIFT)) &&
ModifierMask != ((1 << MODIFIER_GUI) | (1 << MODIFIER_SHIFT)))
{
OnPress(MODIFIER_NONE);
Handled = true;
}
}
}
if(Event.m_Flags & IInput::FLAG_RELEASE)
{
// Release active bind that uses this primary key
auto ActiveBind = std::find_if(m_vActiveBinds.begin(), m_vActiveBinds.end(), [&](const CBindSlot &Bind) {
return Event.m_Key == Bind.m_Key;
});
if(ActiveBind != m_vActiveBinds.end())
{
// Have to check for nullptr again because the previous execute can unbind itself
if(m_aapKeyBindings[ActiveBind->m_ModifierMask][ActiveBind->m_Key])
{
Console()->ExecuteLineStroked(0, m_aapKeyBindings[ActiveBind->m_ModifierMask][ActiveBind->m_Key]);
}
m_vActiveBinds.erase(ActiveBind);
Handled = true;
}
// Release all active binds that use this modifier key
if(KeyModifierMask != MODIFIER_NONE)
{
while(true)
{
auto ActiveModifierBind = std::find_if(m_vActiveBinds.begin(), m_vActiveBinds.end(), [&](const CBindSlot &Bind) {
return (Bind.m_ModifierMask & KeyModifierMask) != 0;
});
if(ActiveModifierBind == m_vActiveBinds.end())
break;
// Have to check for nullptr again because the previous execute can unbind itself
if(m_aapKeyBindings[ActiveModifierBind->m_ModifierMask][ActiveModifierBind->m_Key])
{
Console()->ExecuteLineStroked(0, m_aapKeyBindings[ActiveModifierBind->m_ModifierMask][ActiveModifierBind->m_Key]);
}
m_vActiveBinds.erase(ActiveModifierBind);
Handled = true;
}
}
}
return Handled;
}
void CBinds::UnbindAll()
@ -182,35 +200,32 @@ void CBinds::UnbindAll()
for(auto &pKeyBinding : apKeyBinding)
{
free(pKeyBinding);
pKeyBinding = 0;
pKeyBinding = nullptr;
}
}
}
const char *CBinds::Get(int KeyId, int ModifierCombination)
{
if(KeyId > 0 && KeyId < KEY_LAST && m_aapKeyBindings[ModifierCombination][KeyId])
return m_aapKeyBindings[ModifierCombination][KeyId];
return "";
dbg_assert(KeyId >= KEY_FIRST && KeyId < KEY_LAST, "KeyId invalid");
dbg_assert(ModifierCombination >= MODIFIER_NONE && ModifierCombination < MODIFIER_COMBINATION_COUNT, "ModifierCombination invalid");
return m_aapKeyBindings[ModifierCombination][KeyId] ? m_aapKeyBindings[ModifierCombination][KeyId] : "";
}
void CBinds::GetKey(const char *pBindStr, char *pBuf, size_t BufSize)
{
pBuf[0] = 0;
for(int Mod = 0; Mod < MODIFIER_COMBINATION_COUNT; Mod++)
pBuf[0] = '\0';
for(int Modifier = MODIFIER_NONE; Modifier < MODIFIER_COMBINATION_COUNT; Modifier++)
{
for(int KeyId = 0; KeyId < KEY_LAST; KeyId++)
for(int KeyId = KEY_FIRST; KeyId < KEY_LAST; KeyId++)
{
const char *pBind = Get(KeyId, Mod);
const char *pBind = Get(KeyId, Modifier);
if(!pBind[0])
continue;
if(str_comp(pBind, pBindStr) == 0)
{
if(Mod)
str_format(pBuf, BufSize, "%s+%s", GetModifierName(Mod), Input()->KeyName(KeyId));
else
str_copy(pBuf, Input()->KeyName(KeyId), BufSize);
str_format(pBuf, BufSize, "%s%s", GetKeyBindModifiersName(Modifier), Input()->KeyName(KeyId));
return;
}
}
@ -219,8 +234,8 @@ void CBinds::GetKey(const char *pBindStr, char *pBuf, size_t BufSize)
void CBinds::SetDefaults()
{
// set default key bindings
UnbindAll();
Bind(KEY_F1, "toggle_local_console");
Bind(KEY_F2, "toggle_remote_console");
Bind(KEY_TAB, "+scoreboard");
@ -260,7 +275,6 @@ void CBinds::SetDefaults()
Bind(KEY_Q, "say /pause");
Bind(KEY_P, "say /pause");
// DDRace
g_Config.m_ClDDRaceBindsSet = 0;
SetDDRaceBinds(false);
}
@ -274,7 +288,6 @@ void CBinds::OnConsoleInit()
Console()->Register("unbind", "s[key]", CFGFLAG_CLIENT, ConUnbind, this, "Unbind key");
Console()->Register("unbindall", "", CFGFLAG_CLIENT, ConUnbindAll, this, "Unbind all keys");
// default bindings
SetDefaults();
}
@ -282,10 +295,9 @@ void CBinds::ConBind(IConsole::IResult *pResult, void *pUserData)
{
CBinds *pBinds = (CBinds *)pUserData;
const char *pBindStr = pResult->GetString(0);
int Modifier;
int KeyId = pBinds->GetBindSlot(pBindStr, &Modifier);
const CBindSlot BindSlot = pBinds->GetBindSlot(pBindStr);
if(!KeyId)
if(!BindSlot.m_Key)
{
char aBuf[256];
str_format(aBuf, sizeof(aBuf), "key %s not found", pBindStr);
@ -298,16 +310,16 @@ void CBinds::ConBind(IConsole::IResult *pResult, void *pUserData)
char aBuf[256];
const char *pKeyName = pResult->GetString(0);
if(!pBinds->m_aapKeyBindings[Modifier][KeyId])
str_format(aBuf, sizeof(aBuf), "%s (%d) is not bound", pKeyName, KeyId);
if(!pBinds->m_aapKeyBindings[BindSlot.m_ModifierMask][BindSlot.m_Key])
str_format(aBuf, sizeof(aBuf), "%s (%d) is not bound", pKeyName, BindSlot.m_Key);
else
str_format(aBuf, sizeof(aBuf), "%s (%d) = %s", pKeyName, KeyId, pBinds->m_aapKeyBindings[Modifier][KeyId]);
str_format(aBuf, sizeof(aBuf), "%s (%d) = %s", pKeyName, BindSlot.m_Key, pBinds->m_aapKeyBindings[BindSlot.m_ModifierMask][BindSlot.m_Key]);
pBinds->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "binds", aBuf, gs_BindPrintColor);
return;
}
pBinds->Bind(KeyId, pResult->GetString(1), false, Modifier);
pBinds->Bind(BindSlot.m_Key, pResult->GetString(1), false, BindSlot.m_ModifierMask);
}
void CBinds::ConBinds(IConsole::IResult *pResult, void *pUserData)
@ -317,35 +329,33 @@ void CBinds::ConBinds(IConsole::IResult *pResult, void *pUserData)
{
char aBuf[256];
const char *pKeyName = pResult->GetString(0);
int Modifier;
int KeyId = pBinds->GetBindSlot(pKeyName, &Modifier);
if(!KeyId)
const CBindSlot BindSlot = pBinds->GetBindSlot(pKeyName);
if(!BindSlot.m_Key)
{
str_format(aBuf, sizeof(aBuf), "key '%s' not found", pKeyName);
pBinds->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "binds", aBuf, gs_BindPrintColor);
}
else
{
if(!pBinds->m_aapKeyBindings[Modifier][KeyId])
str_format(aBuf, sizeof(aBuf), "%s (%d) is not bound", pKeyName, KeyId);
if(!pBinds->m_aapKeyBindings[BindSlot.m_ModifierMask][BindSlot.m_Key])
str_format(aBuf, sizeof(aBuf), "%s (%d) is not bound", pKeyName, BindSlot.m_Key);
else
str_format(aBuf, sizeof(aBuf), "%s (%d) = %s", pKeyName, KeyId, pBinds->m_aapKeyBindings[Modifier][KeyId]);
str_format(aBuf, sizeof(aBuf), "%s (%d) = %s", pKeyName, BindSlot.m_Key, pBinds->m_aapKeyBindings[BindSlot.m_ModifierMask][BindSlot.m_Key]);
pBinds->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "binds", aBuf, gs_BindPrintColor);
}
}
else if(pResult->NumArguments() == 0)
else
{
char aBuf[1024];
for(int i = 0; i < MODIFIER_COMBINATION_COUNT; i++)
for(int Modifier = MODIFIER_NONE; Modifier < MODIFIER_COMBINATION_COUNT; Modifier++)
{
for(int j = 0; j < KEY_LAST; j++)
for(int Key = KEY_FIRST; Key < KEY_LAST; Key++)
{
if(!pBinds->m_aapKeyBindings[i][j])
if(!pBinds->m_aapKeyBindings[Modifier][Key])
continue;
str_format(aBuf, sizeof(aBuf), "%s%s (%d) = %s", GetKeyBindModifiersName(i), pBinds->Input()->KeyName(j), j, pBinds->m_aapKeyBindings[i][j]);
str_format(aBuf, sizeof(aBuf), "%s%s (%d) = %s", GetKeyBindModifiersName(Modifier), pBinds->Input()->KeyName(Key), Key, pBinds->m_aapKeyBindings[Modifier][Key]);
pBinds->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "binds", aBuf, gs_BindPrintColor);
}
}
@ -356,10 +366,9 @@ void CBinds::ConUnbind(IConsole::IResult *pResult, void *pUserData)
{
CBinds *pBinds = (CBinds *)pUserData;
const char *pKeyName = pResult->GetString(0);
int Modifier;
int KeyId = pBinds->GetBindSlot(pKeyName, &Modifier);
const CBindSlot BindSlot = pBinds->GetBindSlot(pKeyName);
if(!KeyId)
if(!BindSlot.m_Key)
{
char aBuf[256];
str_format(aBuf, sizeof(aBuf), "key %s not found", pKeyName);
@ -367,7 +376,7 @@ void CBinds::ConUnbind(IConsole::IResult *pResult, void *pUserData)
return;
}
pBinds->Bind(KeyId, "", false, Modifier);
pBinds->Bind(BindSlot.m_Key, "", false, BindSlot.m_ModifierMask);
}
void CBinds::ConUnbindAll(IConsole::IResult *pResult, void *pUserData)
@ -376,51 +385,31 @@ void CBinds::ConUnbindAll(IConsole::IResult *pResult, void *pUserData)
pBinds->UnbindAll();
}
int CBinds::GetKeyId(const char *pKeyName)
CBinds::CBindSlot CBinds::GetBindSlot(const char *pBindString) const
{
// check for numeric
if(pKeyName[0] == '&')
{
int i = str_toint(pKeyName + 1);
if(i > 0 && i < KEY_LAST)
return i; // numeric
}
// search for key
for(int i = 0; i < KEY_LAST; i++)
{
if(str_comp_nocase(pKeyName, Input()->KeyName(i)) == 0)
return i;
}
return 0;
}
int CBinds::GetBindSlot(const char *pBindString, int *pModifierCombination)
{
*pModifierCombination = MODIFIER_NONE;
int ModifierMask = MODIFIER_NONE;
char aMod[32];
aMod[0] = '\0';
const char *pKey = str_next_token(pBindString, "+", aMod, sizeof(aMod));
while(aMod[0] && *(pKey))
{
if(!str_comp_nocase(aMod, "shift"))
*pModifierCombination |= (1 << MODIFIER_SHIFT);
ModifierMask |= (1 << MODIFIER_SHIFT);
else if(!str_comp_nocase(aMod, "ctrl"))
*pModifierCombination |= (1 << MODIFIER_CTRL);
ModifierMask |= (1 << MODIFIER_CTRL);
else if(!str_comp_nocase(aMod, "alt"))
*pModifierCombination |= (1 << MODIFIER_ALT);
ModifierMask |= (1 << MODIFIER_ALT);
else if(!str_comp_nocase(aMod, "gui"))
*pModifierCombination |= (1 << MODIFIER_GUI);
ModifierMask |= (1 << MODIFIER_GUI);
else
return 0;
return {KEY_UNKNOWN, MODIFIER_NONE};
if(str_find(pKey + 1, "+"))
pKey = str_next_token(pKey + 1, "+", aMod, sizeof(aMod));
else
break;
}
return GetKeyId(*pModifierCombination == MODIFIER_NONE ? aMod : pKey + 1);
return {Input()->FindKeyByName(ModifierMask == MODIFIER_NONE ? aMod : pKey + 1), ModifierMask};
}
const char *CBinds::GetModifierName(int Modifier)
@ -461,22 +450,22 @@ void CBinds::ConfigSaveCallback(IConfigManager *pConfigManager, void *pUserData)
CBinds *pSelf = (CBinds *)pUserData;
pConfigManager->WriteLine("unbindall");
for(int i = 0; i < MODIFIER_COMBINATION_COUNT; i++)
for(int Modifier = MODIFIER_NONE; Modifier < MODIFIER_COMBINATION_COUNT; Modifier++)
{
for(int j = 0; j < KEY_LAST; j++)
for(int Key = KEY_FIRST; Key < KEY_LAST; Key++)
{
if(!pSelf->m_aapKeyBindings[i][j])
if(!pSelf->m_aapKeyBindings[Modifier][Key])
continue;
// worst case the str_escape can double the string length
int Size = str_length(pSelf->m_aapKeyBindings[i][j]) * 2 + 30;
int Size = str_length(pSelf->m_aapKeyBindings[Modifier][Key]) * 2 + 30;
char *pBuffer = (char *)malloc(Size);
char *pEnd = pBuffer + Size;
str_format(pBuffer, Size, "bind %s%s \"", GetKeyBindModifiersName(i), pSelf->Input()->KeyName(j));
str_format(pBuffer, Size, "bind %s%s \"", GetKeyBindModifiersName(Modifier), pSelf->Input()->KeyName(Key));
// process the string. we need to escape some characters
char *pDst = pBuffer + str_length(pBuffer);
str_escape(&pDst, pSelf->m_aapKeyBindings[i][j], pEnd);
str_escape(&pDst, pSelf->m_aapKeyBindings[Modifier][Key], pEnd);
str_append(pBuffer, "\"", Size);
pConfigManager->WriteLine(pBuffer);

View file

@ -8,12 +8,12 @@
#include <game/client/component.h>
#include <vector>
class IConfigManager;
class CBinds : public CComponent
{
int GetKeyId(const char *pKeyName);
static void ConBind(IConsole::IResult *pResult, void *pUserData);
static void ConBinds(IConsole::IResult *pResult, void *pUserData);
static void ConUnbind(IConsole::IResult *pResult, void *pUserData);
@ -22,6 +22,20 @@ class CBinds : public CComponent
static void ConfigSaveCallback(IConfigManager *pConfigManager, void *pUserData);
class CBindSlot
{
public:
int m_Key;
int m_ModifierMask;
CBindSlot(int Key, int ModifierMask) :
m_Key(Key),
m_ModifierMask(ModifierMask)
{
}
};
CBindSlot GetBindSlot(const char *pBindString) const;
public:
CBinds();
~CBinds();
@ -55,7 +69,6 @@ public:
void UnbindAll();
const char *Get(int KeyId, int ModifierCombination);
void GetKey(const char *pBindStr, char *pBuf, size_t BufSize);
int GetBindSlot(const char *pBindString, int *pModifierCombination);
static int GetModifierMask(IInput *pInput);
static int GetModifierMaskOfKey(int Key);
static const char *GetModifierName(int Modifier);
@ -70,5 +83,6 @@ public:
private:
char *m_aapKeyBindings[MODIFIER_COMBINATION_COUNT][KEY_LAST];
std::vector<CBindSlot> m_vActiveBinds;
};
#endif