diff --git a/Code/client/Events/KeyPressEvent.h b/Code/client/Events/KeyPressEvent.h new file mode 100644 index 000000000..d54e44a80 --- /dev/null +++ b/Code/client/Events/KeyPressEvent.h @@ -0,0 +1,14 @@ +#pragma once + +/** + * @brief Dispatched when VirtualKey is pressed. + */ +struct KeyPressEvent +{ + KeyPressEvent(const uint16_t acKeyCode) + : VirtualKey(acKeyCode) + { + } + + uint16_t VirtualKey{}; +}; diff --git a/Code/client/Services/Debug/DebugService.cpp b/Code/client/Services/Debug/DebugService.cpp index 9af2af964..4be577bfe 100644 --- a/Code/client/Services/Debug/DebugService.cpp +++ b/Code/client/Services/Debug/DebugService.cpp @@ -161,9 +161,11 @@ void DebugService::OnUpdate(const UpdateEvent& acUpdateEvent) noexcept static std::atomic s_f7Pressed = false; static std::atomic s_f6Pressed = false; - if (GetAsyncKeyState(VK_F3) & 0x01) + // Bool state handled via KeybindService due to sometimes missing the key state changes + if (m_debugKeyPressed) { m_showDebugStuff = !m_showDebugStuff; + m_debugKeyPressed = false; } #if (!IS_MASTER) @@ -201,7 +203,7 @@ void DebugService::OnUpdate(const UpdateEvent& acUpdateEvent) noexcept { s_f8Pressed = true; - //PlaceActorInWorld(); + // PlaceActorInWorld(); } } else @@ -226,6 +228,7 @@ static bool g_enableWeatherWindow{false}; static bool g_enableCombatWindow{false}; static bool g_enableCalendarWindow{false}; static bool g_enableDragonSpawnerWindow{false}; +static bool g_enableKeybindWindow{false}; void DebugService::DrawServerView() noexcept { @@ -315,6 +318,16 @@ void DebugService::OnDraw() noexcept ImGui::EndMenu(); } #endif + if (ImGui::BeginMenu("Keybinds")) + { + ImGui::MenuItem("Show keybinds menu", nullptr, &g_enableKeybindWindow); + if (ImGui::Button("Reset all binds to default")) + { + m_world.GetKeybindService().ResetKeybinds(KeybindService::Keybind::All); + } + + ImGui::EndMenu(); + } if (ImGui::BeginMenu("Debuggers")) { ImGui::MenuItem("Quests", nullptr, &g_enableQuestWindow); @@ -322,6 +335,7 @@ void DebugService::OnDraw() noexcept ImGui::MenuItem("Server", nullptr, &g_enableServerWindow); ImGui::MenuItem("Party", nullptr, &g_enablePartyWindow); ImGui::MenuItem("Dragon spawner", nullptr, &g_enableDragonSpawnerWindow); + ImGui::MenuItem("Keybinds", nullptr, &g_enableKeybindWindow); #if (!IS_MASTER) ImGui::MenuItem("Network", nullptr, &g_enableNetworkWindow); @@ -368,6 +382,8 @@ void DebugService::OnDraw() noexcept DrawPartyView(); if (g_enableDragonSpawnerWindow) DrawDragonSpawnerView(); + if (g_enableKeybindWindow) + DrawKeybindView(); #if (!IS_MASTER) if (g_enableNetworkWindow) diff --git a/Code/client/Services/Debug/Views/KeybindView.cpp b/Code/client/Services/Debug/Views/KeybindView.cpp new file mode 100644 index 000000000..25316cffa --- /dev/null +++ b/Code/client/Services/Debug/Views/KeybindView.cpp @@ -0,0 +1,143 @@ +#include +#include + +#include "World.h" + +#include + +TiltedPhoques::String ConvertWstringToString(const TiltedPhoques::WString& acWideString) +{ + if (acWideString.empty()) + { + return {}; + } + + int size_needed = WideCharToMultiByte(CP_UTF8, 0, acWideString.c_str(), static_cast(acWideString.length()), NULL, 0, NULL, NULL); + + TiltedPhoques::String str(size_needed, 0); + WideCharToMultiByte(CP_UTF8, 0, acWideString.c_str(), static_cast(acWideString.length()), &str[0], size_needed, NULL, NULL); + + return str; +} + +TiltedPhoques::WString BindKey(const TiltedPhoques::WString& acKeyName, bool& aBindActive, const KeybindService::Keybind& acKeyType) +{ + auto& keybindService = World::Get().GetKeybindService(); + TiltedPhoques::WString keyName = acKeyName; + + ImGui::SameLine(0); + aBindActive = true; + + for (uint16_t key = 255; key > 1; key--) + { + if (GetAsyncKeyState(key) & 0x8000) + { + if (key == VK_ESCAPE) + { + aBindActive = false; + break; + } + if (key == VK_LBUTTON || key == VK_RBUTTON) + { + continue; + } + + aBindActive = false; + + switch (acKeyType) + { + default: break; + case KeybindService::None: break; + case KeybindService::UI: + keybindService.BindKey(KeybindService::UI, key); + break; + case KeybindService::Debug: + keybindService.BindKey(KeybindService::Debug, key); + break; + case KeybindService::RevealPlayers: + keybindService.BindKey(KeybindService::RevealPlayers, key); + break; + } + + break; + } + } + + if (aBindActive) + ImGui::Text("Press a key..."); + else + { + const auto& narrowString = ConvertWstringToString(keyName); + ImGui::Text("%s", narrowString.c_str()); + } + + return keyName; +} + + +void DebugService::DrawKeybindView() +{ + auto& keybindService = World::Get().GetKeybindService(); + + ImGui::SetNextWindowSize(ImVec2(350, 150), ImGuiCond_FirstUseEver); + ImGui::Begin("Keybinds"); + + if (IsRebinding()) + ImGui::Text("Press Escape to cancel"); + + TiltedPhoques::WString uiKeyName = keybindService.GetKey(KeybindService::Keybind::UI).first; + if (ImGui::Button("Show/Hide STR UI", ImVec2(200, 30)) || m_rebindUI) + { + m_rebindUI = true; + m_rebindDebug = false; + m_rebindRevealPlayers = false; + + uiKeyName = BindKey(uiKeyName, m_rebindUI, KeybindService::Keybind::UI); + } + else + { + ImGui::SameLine(0); + const auto& narrowString = ConvertWstringToString(uiKeyName); + ImGui::Text("%s", narrowString.c_str()); + + m_rebindUI = false; + } + + TiltedPhoques::WString debugKeyName = keybindService.GetKey(KeybindService::Keybind::Debug).first; + if (ImGui::Button("Show/Hide Debug UI", ImVec2(200, 30)) || m_rebindDebug) + { + m_rebindDebug = true; + m_rebindUI = false; + m_rebindRevealPlayers = false; + + debugKeyName = BindKey(debugKeyName, m_rebindDebug, KeybindService::Keybind::Debug); + } + else + { + ImGui::SameLine(0); + const auto& narrowString = ConvertWstringToString(debugKeyName); + ImGui::Text("%s", narrowString.c_str()); + + m_rebindDebug = false; + } + + TiltedPhoques::WString revealKeyName = keybindService.GetKey(KeybindService::Keybind::RevealPlayers).first; + if (ImGui::Button("Reveal Players", ImVec2(200, 30)) || m_rebindRevealPlayers) + { + m_rebindRevealPlayers = true; + m_rebindUI = false; + m_rebindDebug = false; + + debugKeyName = BindKey(debugKeyName, m_rebindRevealPlayers, KeybindService::Keybind::RevealPlayers); + } + else + { + ImGui::SameLine(0); + const auto& narrowString = ConvertWstringToString(revealKeyName); + ImGui::Text("%s", narrowString.c_str()); + + m_rebindRevealPlayers = false; + } + + ImGui::End(); +} diff --git a/Code/client/Services/DebugService.h b/Code/client/Services/DebugService.h index 32a967855..36afd5e86 100644 --- a/Code/client/Services/DebugService.h +++ b/Code/client/Services/DebugService.h @@ -1,5 +1,7 @@ #pragma once +#include "KeybindService.h" + #include struct World; @@ -30,6 +32,11 @@ struct DebugService void SetDebugId(const uint32_t aFormId) noexcept; + const KeybindService::Key& GetDebugKey() const noexcept { return m_debugKeybind; } + void SetDebugKey(const TiltedPhoques::SharedPtr& acpKey) noexcept { m_debugKeybind = *acpKey; } + bool IsRebinding() const noexcept { return m_rebindUI || m_rebindDebug || m_rebindRevealPlayers; } + void DebugPressed() noexcept { m_debugKeyPressed = !m_debugKeyPressed; } + protected: void OnDraw() noexcept; @@ -60,6 +67,7 @@ struct DebugService void DrawCombatView(); void DrawCalendarView(); void DrawDragonSpawnerView(); + void DrawKeybindView(); public: bool m_showDebugStuff = false; @@ -80,6 +88,12 @@ struct DebugService String SubtitleText = ""; uint32_t TopicID = 0; + KeybindService::Key m_debugKeybind{}; + bool m_debugKeyPressed{false}; + bool m_rebindUI{false}; + bool m_rebindDebug{false}; + bool m_rebindRevealPlayers{false}; + entt::scoped_connection m_updateConnection; entt::scoped_connection m_drawImGuiConnection; entt::scoped_connection m_dialogueConnection; diff --git a/Code/client/Services/Generic/InputService.cpp b/Code/client/Services/Generic/InputService.cpp index 558ead849..e4c6f5a0e 100644 --- a/Code/client/Services/Generic/InputService.cpp +++ b/Code/client/Services/Generic/InputService.cpp @@ -8,13 +8,14 @@ #include #include -#include #include #include #include #include "Games/Skyrim/Interface/MenuControls.h" +#include "Events/KeyPressEvent.h" + static OverlayService* s_pOverlay = nullptr; static UINT s_currentACP = CP_ACP; @@ -89,7 +90,7 @@ uint32_t GetCefModifiers(uint16_t aVirtualKey) // remember to update this when updating toggle keys bool IsToggleKey(int aKey) noexcept { - return aKey == VK_RCONTROL || aKey == VK_F2; + return aKey == World::Get().GetInputService().GetUIKey().second.vkKeyCode; } bool IsDisableKey(int aKey) noexcept @@ -113,10 +114,58 @@ void SetUIActive(OverlayService& aOverlay, auto apRenderer, bool aActive) ; } +void ToggleUI(uint16_t aKey, uint16_t aScanCode, cef_key_event_type_t aType) noexcept +{ + auto& overlay = *s_pOverlay; + + const auto pApp = overlay.GetOverlayApp(); + if (!pApp) + return; + + const auto pClient = pApp->GetClient(); + if (!pClient) + return; + + const auto pRenderer = pClient->GetOverlayRenderHandler(); + if (!pRenderer) + return; + + const auto active = overlay.GetActive(); + + const auto& debugService = World::Get().GetDebugService(); + const auto& isRebinding = debugService.IsRebinding(); + + const auto& isToggleKey = IsToggleKey(aKey); + const auto& isDisableKey = IsDisableKey(aKey); + + const auto& textInputFocused = World::Get().GetKeybindService().GetTextInputFocus(); + + if (aType != KEYEVENT_CHAR && ((isToggleKey && !textInputFocused) || (isDisableKey && active))) + { + if (!overlay.GetInGame()) + { + TiltedPhoques::DInputHook::Get().SetEnabled(false); + } + else if ((aType == KEYEVENT_KEYUP && !isDisableKey) || (isDisableKey && !isRebinding && aType == KEYEVENT_KEYDOWN)) + { + SetUIActive(overlay, pRenderer, !active); + } + } + else if ((active && !isToggleKey) || (isToggleKey && textInputFocused)) + { + pApp->InjectKey(aType, GetCefModifiers(aKey), aKey, aScanCode); + } +} + void ProcessKeyboard(uint16_t aKey, uint16_t aScanCode, cef_key_event_type_t aType, bool aE0, bool aE1) { if (aType != KEYEVENT_CHAR) { + if (aType == KEYEVENT_KEYDOWN) + { + World::Get().GetDispatcher().trigger(KeyPressEvent{aKey}); + } + if (!aKey || aKey == 255) { return; @@ -191,21 +240,7 @@ void ProcessKeyboard(uint16_t aKey, uint16_t aScanCode, cef_key_event_type_t aTy spdlog::debug("ProcessKey, type: {}, key: {}, active: {}", aType, aKey, active); - if (aType != KEYEVENT_CHAR && (IsToggleKey(aKey) || (IsDisableKey(aKey) && active))) - { - if (!overlay.GetInGame()) - { - TiltedPhoques::DInputHook::Get().SetEnabled(false); - } - else if (aType == KEYEVENT_KEYUP) - { - SetUIActive(overlay, pRenderer, !active); - } - } - else if (active) - { - pApp->InjectKey(aType, GetCefModifiers(aKey), aKey, aScanCode); - } + ToggleUI(aKey, aScanCode, aType); } void ProcessMouseMove(uint16_t aX, uint16_t aY) @@ -291,10 +326,7 @@ UINT GetRealACP() // Call the GetLocaleInfo function to retrieve the default ANSI code page // associated with that language ID. UINT acp = CP_ACP; - GetLocaleInfo(MAKELCID(langID, SORT_DEFAULT), - LOCALE_IDEFAULTANSICODEPAGE | LOCALE_RETURN_NUMBER, - (LPTSTR) &acp, - sizeof(acp) / sizeof(TCHAR)); + GetLocaleInfo(MAKELCID(langID, SORT_DEFAULT), LOCALE_IDEFAULTANSICODEPAGE | LOCALE_RETURN_NUMBER, (LPTSTR)&acp, sizeof(acp) / sizeof(TCHAR)); return acp; } @@ -417,10 +449,23 @@ LRESULT CALLBACK InputService::WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPAR return 0; } +bool InputService::SetUIKey(const TiltedPhoques::SharedPtr& acpKey) noexcept +{ + m_pUiKey = *acpKey; + return true; +} + +void InputService::Toggle(uint16_t aKey, uint16_t aScanCode, cef_key_event_type_t aType) noexcept +{ + ToggleUI(aKey, aScanCode, aType); +} + InputService::InputService(OverlayService& aOverlay) noexcept { s_pOverlay = &aOverlay; s_currentACP = GetRealACP(); + + m_pUiKey = {}; } InputService::~InputService() noexcept diff --git a/Code/client/Services/Generic/KeybindService.cpp b/Code/client/Services/Generic/KeybindService.cpp new file mode 100644 index 000000000..62dce7056 --- /dev/null +++ b/Code/client/Services/Generic/KeybindService.cpp @@ -0,0 +1,559 @@ +#include "Services/KeybindService.h" + +#include "DInputHook.hpp" +#include "OverlayApp.hpp" + +#include "Events/KeyPressEvent.h" + +#include "Services/MagicService.h" + +KeybindService::KeybindService(entt::dispatcher& aDispatcher, InputService& aInputService, DebugService& aDebugService, MagicService& aMagicService) + : m_dispatcher(aDispatcher) + , m_inputService(aInputService) + , m_debugService(aDebugService) + , m_magicService(aMagicService) +{ + m_pInputHook = &TiltedPhoques::DInputHook::Get(); + + m_dikKeyPressConnection = m_pInputHook->OnKeyPress.Connect(std::bind(&KeybindService::OnDirectInputKeyPress, this, std::placeholders::_1)); + m_vkKeyPressConnection = m_dispatcher.sink().connect<&KeybindService::OnVirtualKeyKeyPress>(this); + + SetupConfig(); +} + +KeybindService::~KeybindService() +{ + m_pInputHook = nullptr; +} + +void KeybindService::SetupConfig() noexcept +{ + m_config.ini.SetUnicode(true); + + const auto cModPath = TiltedPhoques::GetPath(); + m_config.path = cModPath / Config::kConfigPathName / Config::kKeybindsFileName; + + if (!exists(cModPath / Config::kConfigPathName)) + { + create_directory(cModPath / Config::kConfigPathName); + } + + if (!exists(m_config.path)) + { + spdlog::debug("{} not found, creating...", Config::kKeybindsFileName); + + if (m_config.Create()) + { + InitializeKeys(true); + } + } + else + { + spdlog::debug("{} found, loading...", Config::kKeybindsFileName); + + if (m_config.Load()) + { + spdlog::debug("Successfully loaded {}", Config::kKeybindsFileName); + + InitializeKeys(false); + } + else + { + spdlog::warn("{} Failed to load {}", __FUNCTION__, Config::kKeybindsFileName); + } + } + + spdlog::info(L"UI Key: {} | Debug Key: {} | Reveal Players Key: {}", m_uiKey.first.c_str(), m_debugKey.first.c_str(), m_revealPlayersKey.first.c_str()); +} + +void KeybindService::InitializeKeys(bool aLoadDefaults) noexcept +{ + if (aLoadDefaults) + { + m_uiKey = DEFAULT_UI_KEY; + m_debugKey = DEFAULT_DEBUG_KEY; + m_revealPlayersKey = DEFAULT_REVEAL_PLAYERS_KEY; + } + else + { + m_uiKey.first = m_config.ini.GetValue(L"Keybinds", L"sUiKey", L"F2"); + m_debugKey.first = m_config.ini.GetValue(L"Keybinds", L"sDebugKey", L"F3"); + m_revealPlayersKey.first = m_config.ini.GetValue(L"Keybinds", L"sRevealPlayersKey", L"F4"); + + CheckForDuplicates(); + + m_uiKey.second = m_config.GetKeyCodes(L"ui"); + m_debugKey.second = m_config.GetKeyCodes(L"debug"); + m_revealPlayersKey.second = m_config.GetKeyCodes(L"reveal"); + } + + HandleKeybind(m_uiKey.second.vkKeyCode, m_uiKey.second.diKeyCode, true); + HandleKeybind(m_debugKey.second.vkKeyCode, m_debugKey.second.diKeyCode, true); + HandleKeybind(m_revealPlayersKey.second.vkKeyCode, m_revealPlayersKey.second.diKeyCode, true); +} + +void KeybindService::CheckForDuplicates() noexcept +{ + // Reset keys based on which are conflicting + if (m_uiKey.first == m_debugKey.first) + { + spdlog::warn("UI and Debug keys are the same, resetting Debug key"); + m_debugKey = DEFAULT_DEBUG_KEY; + } + + if (m_uiKey.first == m_revealPlayersKey.first) + { + spdlog::warn("UI and Reveal Players keys are the same, resetting Reveal Players key"); + m_revealPlayersKey = DEFAULT_REVEAL_PLAYERS_KEY; + } + + if (m_debugKey.first == m_revealPlayersKey.first) + { + spdlog::warn("Debug and Reveal Players keys are the same, resetting Reveal Players key"); + m_revealPlayersKey = DEFAULT_REVEAL_PLAYERS_KEY; + } +} + +const KeybindService::Key& KeybindService::GetKey(const Keybind& acKeyType) const noexcept +{ + switch (acKeyType) + { + default: break; + case None: break; + case UI: return m_uiKey; + case Debug: return m_debugKey; + case RevealPlayers: return m_revealPlayersKey; + } + + return Key{L"", {KeyCodes::Error, KeyCodes::Error}}; +} + +bool KeybindService::SetKey(const Keybind& acKeyType, const Key& acKey, bool acLoadFromConfig) noexcept +{ + switch (acKeyType) + { + default: + case None: spdlog::warn("{} Invalid key type", __FUNCTION__); return false; + case UI: return HandleSetUI(acKey, acLoadFromConfig); + case Debug: return HandleSetDebug(acKey, acLoadFromConfig); + case RevealPlayers: return HandleSetRevealPlayers(acKey, acLoadFromConfig); + } +} + +bool KeybindService::SetKeyValues(Key& acKeyToChange, const Key& acKey) noexcept +{ + if (acKeyToChange.first == acKey.first) + { + if (acKey.second.vkKeyCode != KeyCodes::Error) + acKeyToChange.second.vkKeyCode = acKey.second.vkKeyCode; + + if (acKey.second.diKeyCode != KeyCodes::Error) + acKeyToChange.second.diKeyCode = acKey.second.diKeyCode; + + // sanity check + if (!acKey.first.empty()) + acKeyToChange.first = acKey.first; + + return true; + } + + return false; +} + +bool KeybindService::HandleSetUI(const Key& acKey, bool acLoadFromConfig) noexcept +{ + if (SetKeyValues(m_uiKey, acKey)) + { + m_inputService.SetUIKey(TiltedPhoques::MakeShared(m_uiKey)); + m_pInputHook->SetToggleKeys({m_uiKey.second.diKeyCode}); + + m_uiKeybindConfirmed = m_uiKey.second.vkKeyCode != KeyCodes::Error && m_uiKey.second.diKeyCode != KeyCodes::Error; + } + else + { + // Keybind may have been changed via config file, start reconciling the key + m_uiKey.second = {KeyCodes::Error, KeyCodes::Error}; + m_uiKeybindConfirmed = false; + } + + // Don't toggle UI if loading from config + if (!acLoadFromConfig) + { + m_pInputHook->SetEnabled(true); + m_inputService.Toggle(m_uiKey.second.vkKeyCode, MapVirtualKey(m_uiKey.second.vkKeyCode, MAPVK_VK_TO_VSC), KEYEVENT_CHAR); + + return m_config.SetKey(L"sUiKey", m_uiKey.first.c_str()) && m_config.SetKeyCodes(L"ui", m_uiKey.second); + } + + return true; +} + +bool KeybindService::HandleSetDebug(const Key& acKey, bool acLoadFromConfig) noexcept +{ + if (SetKeyValues(m_debugKey, acKey)) + { + m_debugService.SetDebugKey(TiltedPhoques::MakeShared(m_debugKey)); + + m_debugKeybindConfirmed = m_debugKey.second.vkKeyCode != KeyCodes::Error && m_debugKey.second.diKeyCode != KeyCodes::Error; + } + else + { + // Keybind may have been changed via config file, start reconciling the key + m_debugKey.second = {KeyCodes::Error, KeyCodes::Error}; + m_debugKeybindConfirmed = false; + } + + if (!acLoadFromConfig) + return m_config.SetKey(L"sDebugKey", m_debugKey.first.c_str()) && m_config.SetKeyCodes(L"debug", m_debugKey.second); + + return true; +} + +bool KeybindService::HandleSetRevealPlayers(const Key& acKey, bool acLoadFromConfig) noexcept +{ + if (SetKeyValues(m_revealPlayersKey, acKey)) + { + m_revealPlayersKeybindConfirmed = m_revealPlayersKey.second.vkKeyCode != KeyCodes::Error && m_revealPlayersKey.second.diKeyCode != KeyCodes::Error; + } + else + { + // Keybind may have been changed via config file, start reconciling the key + m_revealPlayersKey.second = {KeyCodes::Error, KeyCodes::Error}; + m_revealPlayersKeybindConfirmed = false; + } + + if (!acLoadFromConfig) + return m_config.SetKey(L"sRevealPlayersKey", m_revealPlayersKey.first.c_str()) && m_config.SetKeyCodes(L"reveal", m_revealPlayersKey.second); + + return true; +} + +bool KeybindService::BindKey(const Keybind& acKeyType, const uint16_t& acNewKeyCode) noexcept +{ + if (acNewKeyCode == m_debugKey.second.vkKeyCode || acNewKeyCode == m_uiKey.second.vkKeyCode || acNewKeyCode == m_revealPlayersKeybindConfirmed || acKeyType == Keybind::None) + return false; + + const auto& boundKey = HandleBind(acKeyType, acNewKeyCode); + + switch (acKeyType) + { + case UI: m_uiKeybindConfirmed = !boundKey; break; + case Debug: m_debugKeybindConfirmed = !boundKey; break; + case RevealPlayers: m_revealPlayersKeybindConfirmed = !boundKey; break; + default: break; + } + + return boundKey; +} + +bool KeybindService::HandleBind(const Keybind& acKeyType, const uint16_t& acNewKeyCode) noexcept +{ + const auto& cKey = MakeKey(acNewKeyCode); + + switch (acKeyType) + { + default: break; + case None: break; + case UI: m_uiKey = cKey; break; + case Debug: m_debugKey = cKey; break; + case RevealPlayers: m_revealPlayersKey = cKey; break; + case All: return false; + } + + return SetKey(acKeyType, cKey); +} + +void KeybindService::OnVirtualKeyKeyPress(const KeyPressEvent& acEvent) noexcept +{ + m_keyCode = acEvent.VirtualKey >= 0x10 && acEvent.VirtualKey <= 0x12 ? ResolveVkKeyModifier(acEvent.VirtualKey) : acEvent.VirtualKey; + + if (!m_debugKeybindConfirmed || !m_uiKeybindConfirmed || !m_revealPlayersKeybindConfirmed) + { + HandleKeybind(m_keyCode, KeyCodes::Error); + } + + if (CanToggleDebug(m_keyCode, KeyCodes::Error)) + { + m_debugService.DebugPressed(); + } + else if (CanRevealOtherPlayers(m_keyCode, KeyCodes::Error)) + { + m_magicService.RevealKeybindPressed(); + } +} + +void KeybindService::OnDirectInputKeyPress(const unsigned long& acKeyCode) noexcept +{ + if (!m_uiKeybindConfirmed || !m_debugKeybindConfirmed || !m_revealPlayersKeybindConfirmed) + { + HandleKeybind(KeyCodes::Error, acKeyCode); + } + + // DebugService would sometimes miss debug key's state change so it is handled here + if (CanToggleDebug(KeyCodes::Error, acKeyCode)) + { + m_debugService.DebugPressed(); + } + else if (CanRevealOtherPlayers(KeyCodes::Error, acKeyCode)) + { + m_magicService.RevealKeybindPressed(); + } +} + +void KeybindService::HandleKeybind(const uint16_t& acVkKeyCode, const unsigned long& acDiKeyCode, bool acLoadFromConfig) noexcept +{ + m_keyCode = acVkKeyCode; + + // Attempt to reconcile VirtualKey + if (m_keyCode == KeyCodes::Error && !acLoadFromConfig) + { + // TODO (Toe Knee): this can still miss keystate changes but is the lesser of two evils... + for (uint16_t key = 256; key > 0; key--) + { + if (GetAsyncKeyState(key) & 0x8000) + { + m_keyCode = key; + break; + } + } + } + + wchar_t keyChar = static_cast(MapVirtualKeyW(m_keyCode, MAPVK_VK_TO_CHAR)); + + const TiltedPhoques::WString& cKeyName = {static_cast(toupper(keyChar))}; + const KeybindService::Key& cKey = {cKeyName, {acVkKeyCode, acDiKeyCode}}; + auto pModKey = std::ranges::find_if(m_customKeyNames, [&](const KeybindService::Key& acKey) { return acKey.second.vkKeyCode == m_keyCode || acKey.second.diKeyCode == acDiKeyCode; }); + + // Key has custom name + if (pModKey != m_customKeyNames.end()) + { + m_keyCode = pModKey->second.vkKeyCode; + + // UI key pressed + if (DoKeysMatch(*pModKey, m_uiKey) && !m_uiKeybindConfirmed) + { + SetKey(Keybind::UI, Key{pModKey->first, {m_keyCode, acDiKeyCode}}, acLoadFromConfig); + } + // Debug key pressed + else if (DoKeysMatch(*pModKey, m_debugKey) && !m_debugKeybindConfirmed) + { + SetKey(Keybind::Debug, Key{pModKey->first, {m_keyCode, acDiKeyCode}}); + } + // Reveal Players key pressed + else if (DoKeysMatch(*pModKey, m_revealPlayersKey) && !m_revealPlayersKeybindConfirmed) + { + SetKey(Keybind::RevealPlayers, Key{pModKey->first, {m_keyCode, acDiKeyCode}}); + } + } + // No custom key name + else + { + if (DoKeysMatch(cKey, m_uiKey) && !m_uiKeybindConfirmed) + { + SetKey(Keybind::UI, Key{cKeyName, {m_keyCode, acDiKeyCode}}, acLoadFromConfig); + } + else if (DoKeysMatch(cKey, m_debugKey) && !m_debugKeybindConfirmed) + { + SetKey(Keybind::Debug, Key{cKeyName, {m_keyCode, acDiKeyCode}}); + } + else if (DoKeysMatch(cKey, m_revealPlayersKey) && !m_revealPlayersKeybindConfirmed) + { + SetKey(Keybind::RevealPlayers, Key{cKeyName, {m_keyCode, acDiKeyCode}}); + } + } +} + +KeybindService::Key KeybindService::MakeKey(const uint16_t& acVkKeyCode) noexcept +{ + m_keyCode = acVkKeyCode; + + TiltedPhoques::WString newName = {static_cast(MapVirtualKeyW(acVkKeyCode, MAPVK_VK_TO_CHAR))}; + auto pModKey = std::ranges::find_if(m_customKeyNames, [&](const KeybindService::Key& acKey) { return acKey.second.vkKeyCode == m_keyCode; }); + + if (pModKey != m_customKeyNames.end()) + { + newName = pModKey->first; + m_keyCode = pModKey->second.vkKeyCode; + } + + return Key{newName, {m_keyCode, KeyCodes::Error}}; +} + +bool KeybindService::CanToggleDebug(const uint16_t& acVkKeyCode, const unsigned long& acDiKeyCode) const noexcept +{ + return !m_isTextInputFocused && ((acVkKeyCode != KeyCodes::Error && acVkKeyCode == m_debugKey.second.vkKeyCode) || (acDiKeyCode != KeyCodes::Error && acDiKeyCode == m_debugKey.second.diKeyCode)); +} + +bool KeybindService::CanRevealOtherPlayers(const uint16_t& acVkKeyCode, const unsigned long& acDiKeyCode) const noexcept +{ + return !m_isTextInputFocused && ((acVkKeyCode != KeyCodes::Error && acVkKeyCode == m_revealPlayersKey.second.vkKeyCode) || (acDiKeyCode != KeyCodes::Error && acDiKeyCode == m_revealPlayersKey.second.diKeyCode)); +} + +bool KeybindService::DoKeysMatch(const KeybindService::Key& acLeftKey, const KeybindService::Key& acRightKey) const noexcept +{ + return (!acLeftKey.first.empty() && acLeftKey.first == acRightKey.first) || (acLeftKey.second.vkKeyCode != KeyCodes::Error && acLeftKey.second.vkKeyCode == acRightKey.second.vkKeyCode) || + (acLeftKey.second.diKeyCode != KeyCodes::Error && acLeftKey.second.diKeyCode == acRightKey.second.diKeyCode); +} + +TiltedPhoques::WString KeybindService::ConvertToWString(const TiltedPhoques::String& acString) noexcept +{ + int nChars = MultiByteToWideChar(CP_ACP, MB_ERR_INVALID_CHARS, acString.data(), static_cast(acString.length()), nullptr, 0); + + TiltedPhoques::WString wstrTo; + if (nChars > 0) + { + wstrTo.resize(nChars); + + if (MultiByteToWideChar(CP_ACP, MB_ERR_INVALID_CHARS, acString.data(), static_cast(acString.length()), &wstrTo[0], nChars)) + { + return wstrTo; + } + } + + return {}; +} + +TiltedPhoques::String KeybindService::ConvertToString(const TiltedPhoques::WString& aString) noexcept +{ + int nChars = WideCharToMultiByte(CP_ACP, 0, aString.data(), static_cast(aString.length()), nullptr, 0, nullptr, nullptr); + + TiltedPhoques::String strTo{}; + if (nChars > 0) + { + strTo.resize(nChars); + + if (WideCharToMultiByte(CP_ACP, 0, aString.data(), static_cast(aString.length()), &strTo[0], nChars, nullptr, nullptr)) + { + return strTo; + } + } + + return {}; +} + +uint16_t KeybindService::ResolveVkKeyModifier(const uint16_t& acKeyCode) noexcept +{ + switch (acKeyCode) + { + case VK_SHIFT: + if (GetAsyncKeyState(VK_LSHIFT) & 0x80) + return VK_LSHIFT; + else + return VK_RSHIFT; + case VK_CONTROL: + if (GetAsyncKeyState(VK_LCONTROL) & 0x80) + return VK_LCONTROL; + else + return VK_RCONTROL; + case VK_MENU: + if (GetAsyncKeyState(VK_LMENU) & 0x80) + return VK_LMENU; + else + return VK_RMENU; + default: return acKeyCode; + } +} + +void KeybindService::ResetKeybinds(const Keybind& acKeyType) noexcept +{ + if (acKeyType & Keybind::UI) + { + + m_uiKey = DEFAULT_UI_KEY; + m_inputService.SetUIKey(TiltedPhoques::MakeShared(m_uiKey)); + m_pInputHook->Get().SetToggleKeys({m_uiKey.second.diKeyCode}); + + m_config.SetKey(L"sUiKey", L"F2"); + m_config.SetKeyCodes(L"ui", {VK_F2, DIK_F2}); + + m_uiKeybindConfirmed = true; + } + + if (acKeyType & Keybind::Debug) + { + m_debugKey = DEFAULT_DEBUG_KEY; + m_debugService.SetDebugKey(TiltedPhoques::MakeShared(m_debugKey)); + + m_config.SetKey(L"sDebugKey", L"F3"); + m_config.SetKeyCodes(L"debug", {VK_F3, DIK_F3}); + + m_debugKeybindConfirmed = true; + } + + if (acKeyType & Keybind::RevealPlayers) + { + m_revealPlayersKey = DEFAULT_REVEAL_PLAYERS_KEY; + + m_config.SetKey(L"sRevealPlayersKey", L"F4"); + m_config.SetKeyCodes(L"reveal", {VK_F4, DIK_F4}); + + m_revealPlayersKeybindConfirmed = true; + } +} + +bool KeybindService::Config::Create() noexcept +{ + if (this->ini.SaveFile(this->path.c_str(), true) != SI_OK) + { + spdlog::warn("{} Failed to create {}, using defaults instead", __FUNCTION__, kKeybindsFileName); + return false; + } + + spdlog::info("Successfully created {}", kKeybindsFileName); + + this->ini.SetValue(L"Keybinds", L"sUiKey", L"F2"); + this->ini.SetValue(L"Keybinds", L"sDebugKey", L"F3"); + this->ini.SetValue(L"Keybinds", L"sRevealPlayersKey", L"F4"); + + const TiltedPhoques::WString cUiKeyCodes{std::to_wstring(VK_F2) + L"," + std::to_wstring(DIK_F2)}; + const TiltedPhoques::WString cDebugKeyCodes{std::to_wstring(VK_F3) + L"," + std::to_wstring(DIK_F3)}; + const TiltedPhoques::WString cRevealPlayersKeyCodes{std::to_wstring(VK_F4) + L"," + std::to_wstring(DIK_F4)}; + + this->ini.SetValue(L"Internal", L"ui", cUiKeyCodes.c_str()); + this->ini.SetValue(L"Internal", L"debug", cDebugKeyCodes.c_str()); + this->ini.SetValue(L"Internal", L"reveal", cRevealPlayersKeyCodes.c_str()); + + if (!this->Save()) + { + spdlog::warn("Failed to save {}", kKeybindsFileName); + return false; + } + + return true; +} + +bool KeybindService::Config::Save() const noexcept +{ + return this->ini.SaveFile(this->path.c_str(), true) == SI_OK; +} + +bool KeybindService::Config::Load() noexcept +{ + return this->ini.LoadFile(this->path.c_str()) == SI_OK; +} + +bool KeybindService::Config::SetKey(const wchar_t* acpKey, const wchar_t* acpValue, const wchar_t* acpDescription) noexcept +{ + this->ini.SetValue(L"Keybinds", acpKey, acpValue, acpDescription); + + return this->Save(); +} + +bool KeybindService::Config::SetKeyCodes(const wchar_t* acpConfigKey, const KeybindService::KeyCodes& acKeyCodes) noexcept +{ + const TiltedPhoques::WString cKeyString{std::to_wstring(acKeyCodes.vkKeyCode) + L"," + std::to_wstring(acKeyCodes.diKeyCode)}; + + this->ini.SetValue(L"Internal", acpConfigKey, cKeyString.c_str()); + + return this->Save(); +} + +KeybindService::KeyCodes KeybindService::Config::GetKeyCodes(const wchar_t* acpKey) const noexcept +{ + const TiltedPhoques::WString& cKeyString = this->ini.GetValue(L"Internal", acpKey); + + const auto& vkKeyCode = static_cast(std::stoi(cKeyString.substr(cKeyString.find_first_of(L"=") + 1, cKeyString.find_first_of(L",")).c_str())); + const auto& diKeyCode = std::stoul(cKeyString.substr(cKeyString.find_first_of(L",") + 1).c_str()); + + return {vkKeyCode, diKeyCode}; +} diff --git a/Code/client/Services/Generic/MagicService.cpp b/Code/client/Services/Generic/MagicService.cpp index d1b585661..fc8a784e1 100644 --- a/Code/client/Services/Generic/MagicService.cpp +++ b/Code/client/Services/Generic/MagicService.cpp @@ -428,9 +428,7 @@ void MagicService::OnRemoveSpellEvent(const RemoveSpellEvent& acEvent) noexcept } auto view = m_world.view(); - const auto it = std::find_if(std::begin(view), std::end(view), [id = acEvent.TargetId, view](auto entity) { - return view.get(entity).Id == id; - }); + const auto it = std::find_if(std::begin(view), std::end(view), [id = acEvent.TargetId, view](auto entity) { return view.get(entity).Id == id; }); if (it == std::end(view)) { @@ -447,7 +445,7 @@ void MagicService::OnRemoveSpellEvent(const RemoveSpellEvent& acEvent) noexcept request.TargetId = serverIdRes.value(); - //spdlog::info(__FUNCTION__ ": requesting remove spell with base id {:X} from actor with server id {:X}", request.SpellId.BaseId, request.TargetId); + // spdlog::info(__FUNCTION__ ": requesting remove spell with base id {:X} from actor with server id {:X}", request.SpellId.BaseId, request.TargetId); m_transport.Send(request); } @@ -466,8 +464,7 @@ void MagicService::OnNotifyRemoveSpell(const NotifyRemoveSpell& acMessage) noexc const uint32_t cSpellId = World::Get().GetModSystem().GetGameId(acMessage.SpellId); if (cSpellId == 0) { - spdlog::error("{}: failed to retrieve spell id, GameId base: {:X}, mod: {:X}", __FUNCTION__, - acMessage.SpellId.BaseId, acMessage.SpellId.ModId); + spdlog::error("{}: failed to retrieve spell id, GameId base: {:X}, mod: {:X}", __FUNCTION__, acMessage.SpellId.BaseId, acMessage.SpellId.ModId); return; } @@ -479,7 +476,7 @@ void MagicService::OnNotifyRemoveSpell(const NotifyRemoveSpell& acMessage) noexc } // Remove the spell from the actor - //spdlog::info(__FUNCTION__ ": removing spell with form id {:X} from actor with form id {:X}", cSpellId, targetFormId); + // spdlog::info(__FUNCTION__ ": removing spell with form id {:X} from actor with form id {:X}", cSpellId, targetFormId); pActor->RemoveSpell(pSpell); } @@ -552,7 +549,7 @@ void MagicService::UpdateRevealOtherPlayersEffect(bool aForceTrigger) noexcept static std::chrono::steady_clock::time_point revealStartTimePoint; static std::chrono::steady_clock::time_point lastSendTimePoint; - const bool shouldActivate = aForceTrigger || GetAsyncKeyState(VK_F4) & 0x01; + const bool shouldActivate = aForceTrigger || m_revealKeybindPressed; if (shouldActivate && !m_revealingOtherPlayers) { @@ -608,3 +605,12 @@ void MagicService::UpdateRevealOtherPlayersEffect(bool aForceTrigger) noexcept pRemotePlayer->magicTarget.AddTarget(data, false, false); } } + +void MagicService::RevealKeybindPressed() noexcept +{ + m_revealKeybindPressed = !m_revealKeybindPressed; + + UpdateRevealOtherPlayersEffect(m_revealKeybindPressed); + + m_revealKeybindPressed = !m_revealKeybindPressed; +} diff --git a/Code/client/Services/Generic/OverlayClient.cpp b/Code/client/Services/Generic/OverlayClient.cpp index 6e6b983f5..ff1e60a20 100644 --- a/Code/client/Services/Generic/OverlayClient.cpp +++ b/Code/client/Services/Generic/OverlayClient.cpp @@ -47,6 +47,8 @@ bool OverlayClient::OnProcessMessageReceived(CefRefPtr browser, CefR ProcessDisconnectMessage(); else if (eventName == "revealPlayers") ProcessRevealPlayersMessage(); + else if (eventName == "textInputFocus") + ProcessIsTextInputFocused(eventArgs); else if (eventName == "sendMessage") ProcessChatMessage(eventArgs); else if (eventName == "setTime") @@ -150,6 +152,11 @@ void OverlayClient::ProcessToggleDebugUI() World::Get().GetDebugService().m_showDebugStuff = !World::Get().GetDebugService().m_showDebugStuff; } +void OverlayClient::ProcessIsTextInputFocused(CefRefPtr aEventArgs) +{ + World::Get().GetKeybindService().SetTextInputFocus(aEventArgs->GetBool(0)); +} + void OverlayClient::SetUIVisible(bool aVisible) noexcept { auto pRenderer = GetOverlayRenderHandler(); diff --git a/Code/client/Services/InputService.h b/Code/client/Services/InputService.h index bf86ce052..380629d49 100644 --- a/Code/client/Services/InputService.h +++ b/Code/client/Services/InputService.h @@ -1,5 +1,8 @@ #pragma once +#include +#include + struct OverlayService; /** @@ -10,7 +13,17 @@ struct InputService InputService(OverlayService& aOverlay) noexcept; ~InputService() noexcept; + const KeybindService::Key& GetUIKey() const noexcept { return m_pUiKey; } + bool SetUIKey(const TiltedPhoques::SharedPtr& acpKey) noexcept; + static LRESULT WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + void Toggle(uint16_t aKey, uint16_t aScanCode, cef_key_event_type_t aType) noexcept; + +private: + // KeybindService should handle any mutations of this key + KeybindService::Key m_pUiKey; + bool m_toggleKeyPressed{false}; + TP_NOCOPYMOVE(InputService); }; diff --git a/Code/client/Services/KeybindService.h b/Code/client/Services/KeybindService.h new file mode 100644 index 000000000..34027bd55 --- /dev/null +++ b/Code/client/Services/KeybindService.h @@ -0,0 +1,250 @@ +#pragma once + +#include "DInputHook.hpp" +#include "simpleini/SimpleIni.h" + +#include + +namespace TiltedPhoques +{ +struct OverlayClient; +struct OverlayRenderHandler; +} // namespace TiltedPhoques + +struct InputService; +struct DebugService; +struct OverlayService; +struct MagicService; + +struct KeyPressEvent; + +namespace fs = std::filesystem; + +#define DEFAULT_UI_KEY \ + { \ + L"F2", \ + { \ + VK_F2, DIK_F2 \ + } \ + } +#define DEFAULT_DEBUG_KEY \ + { \ + L"F3", \ + { \ + VK_F3, DIK_F3 \ + } \ + } +#define DEFAULT_REVEAL_PLAYERS_KEY \ + { \ + L"F4", \ + { \ + VK_F4, DIK_F4 \ + } \ + } + +/** + * @brief Handles keybinds + * + * @details Loads a config during construction. The key will not actually be set until it is pressed or loaded from config. + * This is due to needing separate VirtualKey and DirectInput keycodes that can only be determined after being pressed. This should only handle + * keybind-related functionalities. + * Does not currently support mouse buttons. + */ +struct KeybindService +{ + enum Keybind : uint8_t + { + None = 1 << 0, + UI = 1 << 1, + Debug = 1 << 2, + RevealPlayers = 1 << 3, + All = UI | Debug | RevealPlayers + }; + + struct KeyCodes + { + enum : uint8_t + { + Error = 0 + }; + + // VirtualKey + uint16_t vkKeyCode = Error; + // DirectInput + unsigned long diKeyCode = Error; + + friend bool operator==(const KeyCodes& acLhs, const KeyCodes& acRhs) noexcept { return acLhs.vkKeyCode == acRhs.vkKeyCode && acLhs.diKeyCode == acRhs.diKeyCode; } + }; + + using Key = std::pair; + + struct Config + { + static constexpr char kConfigPathName[] = "config"; + static constexpr char kKeybindsFileName[] = "keybinds.ini"; + + bool Create() noexcept; + bool Save() const noexcept; + bool Load() noexcept; + + bool SetKey(const wchar_t* acpKey, const wchar_t* acpValue, const wchar_t* acpDescription = nullptr) noexcept; + bool SetKeyCodes(const wchar_t* acpConfigKey, const KeybindService::KeyCodes& acKeyCodes) noexcept; + KeyCodes GetKeyCodes(const wchar_t* acpKey) const noexcept; + + CSimpleIniW ini{}; + fs::path path{}; + }; + + KeybindService(entt::dispatcher& aDispatcher, InputService& aInputService, DebugService& aDebugService, MagicService& aMagicService); + ~KeybindService(); + + TP_NOCOPYMOVE(KeybindService); + + const Config& GetConfig() const noexcept { return m_config; } + + bool GetTextInputFocus() const noexcept { return m_isTextInputFocused; } + void SetTextInputFocus(bool aTextInputFocused) noexcept { m_isTextInputFocused = aTextInputFocused; } + + bool BindKey(const Keybind& acKeyType, const uint16_t& acNewKeyCode) noexcept; + const Key& GetKey(const Keybind& acKeyType) const noexcept; + + void ResetKeybinds(const Keybind& acKeyType) noexcept; + +private: + // Handles DirectInput-related keybind inputs + // Handling them elsewhere could cause missed keystate changes + void OnDirectInputKeyPress(const unsigned long& acKeyCode) noexcept; + // Handles VirtualKey-related keybind inputs + // Handling them elsewhere could cause missed keystate changes + void OnVirtualKeyKeyPress(const KeyPressEvent& acKeyCode) noexcept; + + Key MakeKey(const uint16_t& acKeyCode) noexcept; + + bool SetKeyValues(Key& acKeyToChange, const Key& acKey) noexcept; + bool SetKey(const Keybind& acKeybind, const Key& acKey, bool acLoadFromConfig = false) noexcept; + bool HandleSetUI(const Key& acKey, bool acLoadFromConfig = false) noexcept; + bool HandleSetDebug(const Key& acKey, bool acLoadFromConfig = false) noexcept; + bool HandleSetRevealPlayers(const Key& acKey, bool acLoadFromConfig = false) noexcept; + bool HandleBind(const Keybind& acKeybind, const uint16_t& acNewKeyCode) noexcept; + + void InitializeKeys(bool aLoadDefaults) noexcept; + void HandleKeybind(const uint16_t& acVkKeyCode, const unsigned long& acDiKeyCode, bool acLoadFromConfig = false) noexcept; + bool DoKeysMatch(const Key& acLeftKey, const Key& acRightKey) const noexcept; + + bool CanToggleDebug(const uint16_t& acVkKeyCode, const unsigned long& acDiKeyCode) const noexcept; + bool CanRevealOtherPlayers(const uint16_t& acVkKeyCode, const unsigned long& acDiKeyCode) const noexcept; + + static TiltedPhoques::WString ConvertToWString(const TiltedPhoques::String& acString) noexcept; + static TiltedPhoques::String ConvertToString(const TiltedPhoques::WString& acString) noexcept; + static uint16_t ResolveVkKeyModifier(const uint16_t& acKeyCode) noexcept; + + void SetupConfig() noexcept; + void CheckForDuplicates() noexcept; + + entt::dispatcher& m_dispatcher; + InputService& m_inputService; + DebugService& m_debugService; + MagicService& m_magicService; + TiltedPhoques::DInputHook* m_pInputHook; + + bool m_isTextInputFocused{false}; + + uint16_t m_keyCode{0}; + + bool m_uiKeybindConfirmed{false}; + bool m_debugKeybindConfirmed{false}; + bool m_revealPlayersKeybindConfirmed{false}; + + size_t m_dikKeyPressConnection{0}; + entt::scoped_connection m_vkKeyPressConnection{}; + + Config m_config{}; + + // Keys are not actually "set" until they are pressed from both UI and ingame (to tie together VirtualKeys with DirectInput) + // or loaded from config + Key m_uiKey{}; + Key m_debugKey{}; + Key m_revealPlayersKey{}; + + const TiltedPhoques::Map& m_customKeyNames{ + {L"Backspace", {VK_BACK, DIK_BACK}}, + {L"Tab", {VK_TAB, DIK_TAB}}, + {L"Enter", {VK_RETURN, DIK_RETURN}}, + {L"LSHIFT", {VK_LSHIFT, DIK_LSHIFT}}, + {L"RSHIFT", {VK_RSHIFT, DIK_RSHIFT}}, + {L"LCTRL", {VK_LCONTROL, DIK_LCONTROL}}, + {L"RCTRL", {VK_RCONTROL, DIK_RCONTROL}}, + {L"LALT", {VK_LMENU, DIK_LMENU}}, + {L"RALT", {VK_RMENU, DIK_RMENU}}, + {L"Pause", {VK_PAUSE, DIK_PAUSE}}, + {L"Caps Lock", {VK_CAPITAL, DIK_CAPSLOCK}}, + {L"IME Kana mode", {VK_KANA, DIK_KANA}}, + {L"IME Kanji mode", {VK_KANJI, DIK_KANJI}}, + {L"Esc", {VK_ESCAPE, DIK_ESCAPE}}, + {L"IME convert", {VK_CONVERT, DIK_CONVERT}}, + {L"IME nonconvert", {VK_NONCONVERT, DIK_NOCONVERT}}, + {L"Space", {VK_SPACE, DIK_SPACE}}, + {L"Page Up", {VK_PRIOR, DIK_PRIOR}}, + {L"Page Down", {VK_NEXT, DIK_NEXT}}, + {L"End", {VK_END, DIK_END}}, + {L"Home", {VK_HOME, DIK_HOME}}, + {L"Arrow Left", {VK_LEFT, DIK_LEFT}}, + {L"Arrow Up", {VK_UP, DIK_UP}}, + {L"Arrow Right", {VK_RIGHT, DIK_RIGHT}}, + {L"Arrow Down", {VK_DOWN, DIK_DOWN}}, + {L"Ins", {VK_INSERT, DIK_INSERT}}, + {L"Del", {VK_DELETE, DIK_DELETE}}, + {L"LWIN", {VK_LWIN, DIK_LWIN}}, + {L"RWIN", {VK_RWIN, DIK_RWIN}}, + {L"Applications", {VK_APPS, DIK_APPS}}, + {L"Sleep", {VK_SLEEP, DIK_SLEEP}}, + {L"NUMPAD 0", {VK_NUMPAD0, DIK_NUMPAD0}}, + {L"NUMPAD 1", {VK_NUMPAD1, DIK_NUMPAD1}}, + {L"NUMPAD 2", {VK_NUMPAD2, DIK_NUMPAD2}}, + {L"NUMPAD 3", {VK_NUMPAD3, DIK_NUMPAD3}}, + {L"NUMPAD 4", {VK_NUMPAD4, DIK_NUMPAD4}}, + {L"NUMPAD 5", {VK_NUMPAD5, DIK_NUMPAD5}}, + {L"NUMPAD 6", {VK_NUMPAD6, DIK_NUMPAD6}}, + {L"NUMPAD 7", {VK_NUMPAD7, DIK_NUMPAD7}}, + {L"NUMPAD 8", {VK_NUMPAD8, DIK_NUMPAD8}}, + {L"NUMPAD 9", {VK_NUMPAD9, DIK_NUMPAD9}}, + {L"NUMPAD *", {VK_MULTIPLY, DIK_MULTIPLY}}, + {L"NUMPAD +", {VK_ADD, DIK_ADD}}, + {L"NUMPAD -", {VK_SUBTRACT, DIK_SUBTRACT}}, + {L"NUMPAD .", {VK_DECIMAL, DIK_DECIMAL}}, + {L"NUMPAD /", {VK_DIVIDE, DIK_DIVIDE}}, + {L"F1", {VK_F1, DIK_F1}}, + {L"F2", {VK_F2, DIK_F2}}, + {L"F3", {VK_F3, DIK_F3}}, + {L"F4", {VK_F4, DIK_F4}}, + {L"F5", {VK_F5, DIK_F5}}, + {L"F6", {VK_F6, DIK_F6}}, + {L"F7", {VK_F7, DIK_F7}}, + {L"F8", {VK_F8, DIK_F8}}, + {L"F9", {VK_F9, DIK_F9}}, + {L"F10", {VK_F10, DIK_F10}}, + {L"F11", {VK_F11, DIK_F11}}, + {L"F12", {VK_F12, DIK_F12}}, + {L"F13", {VK_F13, DIK_F13}}, + {L"F14", {VK_F14, DIK_F14}}, + {L"F15", {VK_F15, DIK_F15}}, + {L"Num Lock", {VK_NUMLOCK, DIK_NUMLOCK}}, + {L"ScrLk", {VK_SCROLL, DIK_SCROLL}}, + {L"Browser Back", {VK_BROWSER_BACK, DIK_WEBBACK}}, + {L"Browser Forward", {VK_BROWSER_FORWARD, DIK_WEBFORWARD}}, + {L"Browser Refresh", {VK_BROWSER_REFRESH, DIK_WEBREFRESH}}, + {L"Browser Stop", {VK_BROWSER_STOP, DIK_WEBSTOP}}, + {L"Browser Search", {VK_BROWSER_SEARCH, DIK_WEBSEARCH}}, + {L"Browser Favorites", {VK_BROWSER_FAVORITES, DIK_WEBFAVORITES}}, + {L"Browser Home", {VK_BROWSER_HOME, DIK_WEBHOME}}, + {L"Volume Mute", {VK_VOLUME_MUTE, DIK_MUTE}}, + {L"Volume Down", {VK_VOLUME_DOWN, DIK_VOLUMEDOWN}}, + {L"Volume Up", {VK_VOLUME_UP, DIK_VOLUMEUP}}, + {L"Next Track", {VK_MEDIA_NEXT_TRACK, DIK_NEXTTRACK}}, + {L"Previous Track", {VK_MEDIA_PREV_TRACK, DIK_PREVTRACK}}, + {L"Stop Media", {VK_MEDIA_STOP, DIK_MEDIASTOP}}, + {L"Start Mail", {VK_LAUNCH_MAIL, DIK_MAIL}}, + {L"Select Media", {VK_LAUNCH_MEDIA_SELECT, DIK_MEDIASELECT}} + + }; +}; diff --git a/Code/client/Services/MagicService.h b/Code/client/Services/MagicService.h index fd0dca102..e05827a1d 100644 --- a/Code/client/Services/MagicService.h +++ b/Code/client/Services/MagicService.h @@ -40,6 +40,7 @@ struct MagicService * @see UpdateRevealOtherPlayersEffect */ void StartRevealingOtherPlayers() noexcept; + void RevealKeybindPressed() noexcept; protected: /** @@ -105,6 +106,7 @@ struct MagicService Map m_queuedRemoteEffects; bool m_revealingOtherPlayers = false; + bool m_revealKeybindPressed = false; entt::scoped_connection m_updateConnection; entt::scoped_connection m_spellCastEventConnection; diff --git a/Code/client/Services/OverlayClient.h b/Code/client/Services/OverlayClient.h index b495b0e5f..194fd9ff9 100644 --- a/Code/client/Services/OverlayClient.h +++ b/Code/client/Services/OverlayClient.h @@ -29,6 +29,7 @@ struct OverlayClient : TiltedPhoques::OverlayClient void ProcessSetTimeCommand(CefRefPtr aEventArgs); void ProcessTeleportMessage(CefRefPtr aEventArgs); void ProcessToggleDebugUI(); + void ProcessIsTextInputFocused(CefRefPtr aEventArgs); void SetUIVisible(bool aVisible) noexcept; TransportService& m_transport; diff --git a/Code/client/World.cpp b/Code/client/World.cpp index b0596604b..7ae139e6c 100644 --- a/Code/client/World.cpp +++ b/Code/client/World.cpp @@ -25,7 +25,7 @@ #include #include -#include +#include World::World() : m_runner(m_dispatcher) @@ -54,7 +54,7 @@ World::World() ctx().emplace(*this, m_transport, m_dispatcher); ctx().emplace(*this, m_transport, m_dispatcher); ctx().emplace(*this, m_dispatcher, m_transport); - + ctx().emplace(m_dispatcher, ctx().at(), ctx().at(), ctx().at()); BehaviorVar::Get()->Init(); } diff --git a/Code/client/World.h b/Code/client/World.h index 2c3fee2eb..65aa9baf8 100644 --- a/Code/client/World.h +++ b/Code/client/World.h @@ -8,6 +8,8 @@ #include #include #include +#include "Services/InputService.h" +#include "Services/KeybindService.h" #include @@ -32,6 +34,10 @@ struct World : entt::registry const OverlayService& GetOverlayService() const noexcept { return ctx().at(); } DebugService& GetDebugService() noexcept { return ctx().at(); } const DebugService& GetDebugService() const noexcept { return ctx().at(); } + InputService& GetInputService() noexcept { return ctx().at(); } + const InputService& GetInputService() const noexcept { return ctx().at(); } + KeybindService& GetKeybindService() noexcept { return ctx().at(); } + const KeybindService& GetKeybindService() const noexcept { return ctx().at(); } MagicService& GetMagicService() noexcept { return ctx().at(); } const MagicService& GetMagicService() const noexcept { return ctx().at(); } diff --git a/Code/skyrim_ui/src/app/components/chat/chat.component.html b/Code/skyrim_ui/src/app/components/chat/chat.component.html index 5ecc4cf8b..cd4e94592 100644 --- a/Code/skyrim_ui/src/app/components/chat/chat.component.html +++ b/Code/skyrim_ui/src/app/components/chat/chat.component.html @@ -27,6 +27,8 @@ spellcheck="false" [(ngModel)]="message" (keydown.enter)="sendMessage()" + (focus)="focus()" + (blur)="blur()" />