/
dieoska
/
ddnet
Обзор
Документация
Войти
/
dieoska
/
ddnet
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
src/game/client/components/binds.cpp
536 строк
15 KB
ChillerDragon
Make it more obvious how much -1 is used as client id
14 дек 2025, 18:01
14 дек 2025, 18:01
2078691
Код
Авторство
О чём код?
/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */ /* If you are missing that file, acquire a complete release at teeworlds.com. */ #include "binds.h" #include <base/log.h> #include <base/system.h> #include <engine/config.h> #include <engine/console.h> #include <engine/shared/config.h> #include <game/client/components/chat.h> #include <game/client/components/console.h> #include <game/client/gameclient.h> static constexpr LOG_COLOR BIND_PRINT_COLOR{255, 255, 204}; 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 // 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 || !GameClient()->m_Menus.IsActive())) { return m_pBinds->OnInput(Event); } return false; } CBinds::CBinds() { mem_zero(m_aapKeyBindings, sizeof(m_aapKeyBindings)); m_SpecialBinds.m_pBinds = this; } CBinds::~CBinds() { UnbindAll(); } void CBinds::Bind(int KeyId, const char *pStr, bool FreeOnly, int ModifierCombination) { dbg_assert(KeyId >= KEY_FIRST && KeyId < KEY_LAST, "KeyId invalid"); dbg_assert(ModifierCombination >= KeyModifier::NONE && ModifierCombination < KeyModifier::COMBINATION_COUNT, "ModifierCombination invalid"); if(FreeOnly && Get(KeyId, ModifierCombination)[0]) return; free(m_aapKeyBindings[ModifierCombination][KeyId]); m_aapKeyBindings[ModifierCombination][KeyId] = nullptr; char aBindName[128]; GetKeyBindName(KeyId, ModifierCombination, aBindName, sizeof(aBindName)); if(!pStr[0]) { log_info_color(BIND_PRINT_COLOR, "binds", "unbound %s", aBindName); } else { int Size = str_length(pStr) + 1; m_aapKeyBindings[ModifierCombination][KeyId] = (char *)malloc(Size); str_copy(m_aapKeyBindings[ModifierCombination][KeyId], pStr, Size); log_info_color(BIND_PRINT_COLOR, "binds", "bound %s = %s", aBindName, m_aapKeyBindings[ModifierCombination][KeyId]); } } int CBinds::GetModifierMask(IInput *pInput) { int Mask = 0; static const auto s_aModifierKeys = { KEY_LSHIFT, KEY_RSHIFT, KEY_LCTRL, KEY_RCTRL, KEY_LALT, KEY_RALT, KEY_LGUI, KEY_RGUI, }; for(const auto Key : s_aModifierKeys) { if(pInput->KeyIsPressed(Key)) { Mask |= GetModifierMaskOfKey(Key); } } return Mask; } int CBinds::GetModifierMaskOfKey(int Key) { switch(Key) { case KEY_LSHIFT: case KEY_RSHIFT: return 1 << KeyModifier::SHIFT; case KEY_LCTRL: case KEY_RCTRL: return 1 << KeyModifier::CTRL; case KEY_LALT: case KEY_RALT: return 1 << KeyModifier::ALT; case KEY_LGUI: case KEY_RGUI: return 1 << KeyModifier::GUI; default: return KeyModifier::NONE; } } bool CBinds::OnInput(const IInput::CEvent &Event) { if((Event.m_Flags & (IInput::FLAG_PRESS | IInput::FLAG_RELEASE)) == 0) return false; const int KeyModifierMask = GetModifierMaskOfKey(Event.m_Key); const int ModifierMask = GetModifierMask(Input()) & ~KeyModifierMask; bool Handled = false; if(Event.m_Flags & IInput::FLAG_PRESS) { 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()) { const auto &&OnKeyPress = [&](int Mask) { const char *pBind = m_aapKeyBindings[Mask][Event.m_Key]; if(g_Config.m_ClSubTickAiming) { if(str_comp("+fire", pBind) == 0 || str_comp("+hook", pBind) == 0) { m_MouseOnAction = true; } } Console()->ExecuteLineStroked(1, pBind, IConsole::CLIENT_ID_UNSPECIFIED); m_vActiveBinds.emplace_back(Event.m_Key, Mask); }; if(m_aapKeyBindings[ModifierMask][Event.m_Key]) { OnKeyPress(ModifierMask); Handled = true; } else if(m_aapKeyBindings[KeyModifier::NONE][Event.m_Key] && ModifierMask != ((1 << KeyModifier::CTRL) | (1 << KeyModifier::SHIFT)) && ModifierMask != ((1 << KeyModifier::GUI) | (1 << KeyModifier::SHIFT))) { OnKeyPress(KeyModifier::NONE); Handled = true; } } else { // Repeat active bind while key is held down // Have to check for nullptr again because the previous execute can unbind itself if(m_aapKeyBindings[ActiveBind->m_ModifierMask][ActiveBind->m_Key]) { Console()->ExecuteLineStroked(1, m_aapKeyBindings[ActiveBind->m_ModifierMask][ActiveBind->m_Key], IConsole::CLIENT_ID_UNSPECIFIED); } Handled = true; } } if(Event.m_Flags & IInput::FLAG_RELEASE) { const auto &&OnKeyRelease = [&](const CBindSlot &Bind) { // Prevent binds from being deactivated while chat, console and menus are open, as these components will // still allow key release events to be forwarded to this component, so the active binds can be cleared. if(GameClient()->m_Chat.IsActive() || GameClient()->m_GameConsole.IsActive() || GameClient()->m_Menus.IsActive()) { return; } // Have to check for nullptr again because the previous execute can unbind itself if(!m_aapKeyBindings[Bind.m_ModifierMask][Bind.m_Key]) { return; } Console()->ExecuteLineStroked(0, m_aapKeyBindings[Bind.m_ModifierMask][Bind.m_Key], IConsole::CLIENT_ID_UNSPECIFIED); }; // 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()) { OnKeyRelease(*ActiveBind); m_vActiveBinds.erase(ActiveBind); Handled = true; } // Release all active binds that use this modifier key if(KeyModifierMask != KeyModifier::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; OnKeyRelease(*ActiveModifierBind); m_vActiveBinds.erase(ActiveModifierBind); Handled = true; } } } return Handled; } void CBinds::UnbindAll() { for(auto &apKeyBinding : m_aapKeyBindings) { for(auto &pKeyBinding : apKeyBinding) { free(pKeyBinding); pKeyBinding = nullptr; } } } const char *CBinds::Get(int KeyId, int ModifierCombination) const { dbg_assert(KeyId >= KEY_FIRST && KeyId < KEY_LAST, "KeyId invalid"); dbg_assert(ModifierCombination >= KeyModifier::NONE && ModifierCombination < KeyModifier::COMBINATION_COUNT, "ModifierCombination invalid"); return m_aapKeyBindings[ModifierCombination][KeyId] ? m_aapKeyBindings[ModifierCombination][KeyId] : ""; } const char *CBinds::Get(const CBindSlot &BindSlot) const { return Get(BindSlot.m_Key, BindSlot.m_ModifierMask); } void CBinds::GetKey(const char *pBindStr, char *pBuf, size_t BufSize) const { pBuf[0] = '\0'; for(int Modifier = KeyModifier::NONE; Modifier < KeyModifier::COMBINATION_COUNT; Modifier++) { for(int KeyId = KEY_FIRST; KeyId < KEY_LAST; KeyId++) { const char *pBind = Get(KeyId, Modifier); if(!pBind[0]) continue; if(str_comp(pBind, pBindStr) == 0) { GetKeyBindName(KeyId, Modifier, pBuf, BufSize); return; } } } } void CBinds::SetDefaults() { UnbindAll(); Bind(KEY_F1, "toggle_local_console"); Bind(KEY_F2, "toggle_remote_console"); Bind(KEY_TAB, "+scoreboard"); Bind(KEY_EQUALS, "+statboard"); Bind(KEY_F10, "screenshot"); Bind(KEY_A, "+left"); Bind(KEY_D, "+right"); Bind(KEY_SPACE, "+jump"); Bind(KEY_MOUSE_1, "+fire"); Bind(KEY_MOUSE_2, "+hook"); Bind(KEY_LSHIFT, "+emote"); Bind(KEY_RETURN, "+show_chat; chat all"); Bind(KEY_RIGHT, "spectate_next"); Bind(KEY_LEFT, "spectate_previous"); Bind(KEY_RSHIFT, "+spectate"); Bind(KEY_1, "+weapon1"); Bind(KEY_2, "+weapon2"); Bind(KEY_3, "+weapon3"); Bind(KEY_4, "+weapon4"); Bind(KEY_5, "+weapon5"); Bind(KEY_MOUSE_WHEEL_UP, "+prevweapon"); Bind(KEY_MOUSE_WHEEL_DOWN, "+nextweapon"); Bind(KEY_T, "+show_chat; chat all"); Bind(KEY_Y, "+show_chat; chat team"); Bind(KEY_U, "+show_chat"); Bind(KEY_I, "+show_chat; chat all /c "); Bind(KEY_F3, "vote yes"); Bind(KEY_F4, "vote no"); Bind(KEY_K, "kill"); Bind(KEY_Q, "say /spec"); Bind(KEY_P, "say /pause"); g_Config.m_ClDDRaceBindsSet = 0; SetDDRaceBinds(false); } void CBinds::OnConsoleInit() { ConfigManager()->RegisterCallback(ConfigSaveCallback, this); Console()->Register("bind", "s[key] ?r[command]", CFGFLAG_CLIENT, ConBind, this, "Bind key to execute a command or view keybindings"); Console()->Register("binds", "?s[key]", CFGFLAG_CLIENT, ConBinds, this, "Print command executed by this keybinding or all binds"); Console()->Register("unbind", "s[key]", CFGFLAG_CLIENT, ConUnbind, this, "Unbind key"); Console()->Register("unbindall", "", CFGFLAG_CLIENT, ConUnbindAll, this, "Unbind all keys"); SetDefaults(); } void CBinds::ConBind(IConsole::IResult *pResult, void *pUserData) { CBinds *pBinds = (CBinds *)pUserData; const char *pBindStr = pResult->GetString(0); const CBindSlot BindSlot = pBinds->GetBindSlot(pBindStr); if(!BindSlot.m_Key) { log_info_color(BIND_PRINT_COLOR, "binds", "key %s not found", pBindStr); return; } if(pResult->NumArguments() == 1) { ConBinds(pResult, pUserData); return; } pBinds->Bind(BindSlot.m_Key, pResult->GetString(1), false, BindSlot.m_ModifierMask); } void CBinds::ConBinds(IConsole::IResult *pResult, void *pUserData) { CBinds *pBinds = (CBinds *)pUserData; if(pResult->NumArguments() == 1) { const char *pKeyName = pResult->GetString(0); const CBindSlot BindSlot = pBinds->GetBindSlot(pKeyName); if(!BindSlot.m_Key) { log_info_color(BIND_PRINT_COLOR, "binds", "key '%s' not found", pKeyName); } else { if(!pBinds->m_aapKeyBindings[BindSlot.m_ModifierMask][BindSlot.m_Key]) log_info_color(BIND_PRINT_COLOR, "binds", "%s is not bound", pKeyName); else { char *pBuf = pBinds->GetKeyBindCommand(BindSlot.m_ModifierMask, BindSlot.m_Key); log_info_color(BIND_PRINT_COLOR, "binds", "%s", pBuf); free(pBuf); } } } else { for(int Modifier = KeyModifier::NONE; Modifier < KeyModifier::COMBINATION_COUNT; Modifier++) { for(int Key = KEY_FIRST; Key < KEY_LAST; Key++) { if(!pBinds->m_aapKeyBindings[Modifier][Key]) continue; char *pBuf = pBinds->GetKeyBindCommand(Modifier, Key); log_info_color(BIND_PRINT_COLOR, "binds", "%s", pBuf); free(pBuf); } } } } void CBinds::ConUnbind(IConsole::IResult *pResult, void *pUserData) { CBinds *pBinds = (CBinds *)pUserData; const char *pKeyName = pResult->GetString(0); const CBindSlot BindSlot = pBinds->GetBindSlot(pKeyName); if(!BindSlot.m_Key) { log_info_color(BIND_PRINT_COLOR, "binds", "key %s not found", pKeyName); return; } pBinds->Bind(BindSlot.m_Key, "", false, BindSlot.m_ModifierMask); } void CBinds::ConUnbindAll(IConsole::IResult *pResult, void *pUserData) { CBinds *pBinds = (CBinds *)pUserData; pBinds->UnbindAll(); } CBindSlot CBinds::GetBindSlot(const char *pBindString) const { int ModifierMask = KeyModifier::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")) ModifierMask |= (1 << KeyModifier::SHIFT); else if(!str_comp_nocase(aMod, "ctrl")) ModifierMask |= (1 << KeyModifier::CTRL); else if(!str_comp_nocase(aMod, "alt")) ModifierMask |= (1 << KeyModifier::ALT); else if(!str_comp_nocase(aMod, "gui")) ModifierMask |= (1 << KeyModifier::GUI); else return EMPTY_BIND_SLOT; if(str_find(pKey + 1, "+")) pKey = str_next_token(pKey + 1, "+", aMod, sizeof(aMod)); else break; } return {Input()->FindKeyByName(ModifierMask == KeyModifier::NONE ? aMod : pKey + 1), ModifierMask}; } const char *CBinds::GetModifierName(int Modifier) { switch(Modifier) { case KeyModifier::SHIFT: return "shift"; case KeyModifier::CTRL: return "ctrl"; case KeyModifier::ALT: return "alt"; case KeyModifier::GUI: return "gui"; default: dbg_assert_failed("Modifier invalid: %d", Modifier); } } void CBinds::GetKeyBindName(int Key, int ModifierMask, char *pBuf, size_t BufSize) const { pBuf[0] = '\0'; for(int Modifier = KeyModifier::CTRL; Modifier < KeyModifier::COUNT; Modifier++) { if(ModifierMask & (1 << Modifier)) { str_append(pBuf, GetModifierName(Modifier), BufSize); str_append(pBuf, "+", BufSize); } } str_append(pBuf, Input()->KeyName(Key), BufSize); } char *CBinds::GetKeyBindCommand(int ModifierCombination, int Key) const { char aBindName[128]; GetKeyBindName(Key, ModifierCombination, aBindName, sizeof(aBindName)); // worst case the str_escape can double the string length int Size = str_length(m_aapKeyBindings[ModifierCombination][Key]) * 2 + str_length(aBindName) + 16; auto *pBuf = static_cast<char *>(malloc(Size)); str_format(pBuf, Size, "bind %s \"", aBindName); char *pDst = pBuf + str_length(pBuf); // process the string. we need to escape some characters str_escape(&pDst, m_aapKeyBindings[ModifierCombination][Key], pBuf + Size); str_append(pBuf, "\"", Size); return pBuf; } void CBinds::ConfigSaveCallback(IConfigManager *pConfigManager, void *pUserData) { CBinds *pSelf = (CBinds *)pUserData; pConfigManager->WriteLine("unbindall"); for(int Modifier = KeyModifier::NONE; Modifier < KeyModifier::COMBINATION_COUNT; Modifier++) { for(int Key = KEY_FIRST; Key < KEY_LAST; Key++) { if(!pSelf->m_aapKeyBindings[Modifier][Key]) continue; char *pBuf = pSelf->GetKeyBindCommand(Modifier, Key); pConfigManager->WriteLine(pBuf); free(pBuf); } } } // DDRace void CBinds::SetDDRaceBinds(bool FreeOnly) { if(g_Config.m_ClDDRaceBindsSet < 1) { Bind(KEY_KP_PLUS, "zoom+", FreeOnly); Bind(KEY_KP_MINUS, "zoom-", FreeOnly); Bind(KEY_KP_MULTIPLY, "zoom", FreeOnly); Bind(KEY_PAUSE, "say /pause", FreeOnly); Bind(KEY_UP, "+jump", FreeOnly); Bind(KEY_LEFT, "+left", FreeOnly); Bind(KEY_RIGHT, "+right", FreeOnly); Bind(KEY_LEFTBRACKET, "+prevweapon", FreeOnly); Bind(KEY_RIGHTBRACKET, "+nextweapon", FreeOnly); Bind(KEY_C, "say /rank", FreeOnly); Bind(KEY_V, "say /info", FreeOnly); Bind(KEY_B, "say /top5", FreeOnly); Bind(KEY_S, "+showhookcoll", FreeOnly); Bind(KEY_X, "toggle cl_dummy 0 1", FreeOnly); Bind(KEY_H, "toggle cl_dummy_hammer 0 1", FreeOnly); Bind(KEY_SLASH, "+show_chat; chat all /", FreeOnly); Bind(KEY_KP_0, "say /emote normal 999999", FreeOnly); Bind(KEY_KP_1, "say /emote happy 999999", FreeOnly); Bind(KEY_KP_2, "say /emote angry 999999", FreeOnly); Bind(KEY_KP_3, "say /emote pain 999999", FreeOnly); Bind(KEY_KP_4, "say /emote surprise 999999", FreeOnly); Bind(KEY_KP_5, "say /emote blink 999999", FreeOnly); Bind(KEY_MINUS, "spectate_previous", FreeOnly); Bind(KEY_EQUALS, "spectate_next", FreeOnly); } if(g_Config.m_ClDDRaceBindsSet < 2) { const bool DontModifySpectate = FreeOnly && str_comp(Get(KEY_MOUSE_3, KeyModifier::NONE), "+spectate") != 0; Bind(KEY_MOUSE_3, "toggle_scoreboard_cursor; +spectate", DontModifySpectate); Bind(KEY_LALT, "toggle_scoreboard_cursor", FreeOnly); } g_Config.m_ClDDRaceBindsSet = 2; }