From 0b656024713b133c0064d2af39ac1d37c9a76e52 Mon Sep 17 00:00:00 2001 From: Caball009 <82909616+Caball009@users.noreply.github.com> Date: Mon, 1 Dec 2025 21:15:44 +0100 Subject: [PATCH 1/6] feat(keyboard): Change detection behavior for keys that are kept pressed down. --- .../Code/GameEngine/Include/GameClient/Keyboard.h | 2 -- .../GameEngine/Source/GameClient/Input/Keyboard.cpp | 13 +++++++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Include/GameClient/Keyboard.h b/GeneralsMD/Code/GameEngine/Include/GameClient/Keyboard.h index 770a7338fd..386f173194 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameClient/Keyboard.h +++ b/GeneralsMD/Code/GameEngine/Include/GameClient/Keyboard.h @@ -84,8 +84,6 @@ struct KeyboardIO class Keyboard : public SubsystemInterface { - enum { KEY_REPEAT_DELAY = 10 }; - public: Keyboard( void ); diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/Input/Keyboard.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/Input/Keyboard.cpp index a2e67445d1..61586efa1e 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/Input/Keyboard.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/Input/Keyboard.cpp @@ -32,6 +32,7 @@ #include "Common/Language.h" #include "Common/GameEngine.h" #include "Common/MessageStream.h" +#include "Common/FramePacer.h" #include "GameClient/Keyboard.h" #include "GameClient/KeyDefs.h" @@ -218,8 +219,10 @@ Bool Keyboard::checkKeyRepeat( void ) if( BitIsSet( m_keyStatus[ key ].state, KEY_STATE_DOWN ) ) { - - if( (m_inputFrame - m_keyStatus[ key ].sequence) > Keyboard::KEY_REPEAT_DELAY ) + // Check if key has been pressed down for longer than 125 ms + constexpr const Real initialDelayInSec = 0.125125f; + UnsignedInt initialDelayInFrames = static_cast(TheFramePacer->getUpdateFps() * initialDelayInSec); + if( (m_inputFrame - m_keyStatus[ key ].sequence) > initialDelayInFrames) { // Add key to this frame m_keys[ index ].key = (UnsignedByte)key; @@ -233,8 +236,10 @@ Bool Keyboard::checkKeyRepeat( void ) for( index = 0; index< NUM_KEYS; index++ ) m_keyStatus[ index ].sequence = m_inputFrame; - // Set repeated key so it will repeat again in two frames - m_keyStatus[ key ].sequence = m_inputFrame - (Keyboard::KEY_REPEAT_DELAY + 2); + // Set repeated key so that the next delay is much shorter than the first + constexpr const Real nextDelayMultiplier = 1.0f / 3.0f; + UnsignedInt nextDelayInFrames = clamp(initialDelayInFrames * nextDelayMultiplier, 1, 1000); + m_keyStatus[ key ].sequence = m_inputFrame - (initialDelayInFrames - nextDelayInFrames); retVal = TRUE; break; // exit for key From fe26dbf4d9a09a39bfa443f3faf555689fabe523 Mon Sep 17 00:00:00 2001 From: Caball009 <82909616+Caball009@users.noreply.github.com> Date: Mon, 1 Dec 2025 21:18:35 +0100 Subject: [PATCH 2/6] feat(hotkey): Add detection behavior for keys that are kept pressed down. --- .../GameClient/MessageStream/HotKey.cpp | 57 ++++++++----------- 1 file changed, 24 insertions(+), 33 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/HotKey.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/HotKey.cpp index e71399c7e8..e6a475012d 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/HotKey.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/HotKey.cpp @@ -69,44 +69,35 @@ //----------------------------------------------------------------------------- GameMessageDisposition HotKeyTranslator::translateGameMessage(const GameMessage *msg) { - GameMessageDisposition disp = KEEP_MESSAGE; - GameMessage::Type t = msg->getType(); - - if ( t == GameMessage::MSG_RAW_KEY_UP) + switch (msg->getType()) { + case GameMessage::MSG_RAW_KEY_DOWN: + if ((msg->getArgument(1)->integer & KEY_STATE_AUTOREPEAT) == 0) + return KEEP_MESSAGE; + + break; + case GameMessage::MSG_RAW_KEY_UP: + if (msg->getArgument(1)->integer & (KEY_STATE_CONTROL | KEY_STATE_SHIFT | KEY_STATE_ALT)) + return KEEP_MESSAGE; + + break; + default: + return KEEP_MESSAGE; + } - //char key = msg->getArgument(0)->integer; - Int keyState = msg->getArgument(1)->integer; - - // for our purposes here, we don't care to distinguish between right and left keys, - // so just fudge a little to simplify things. - Int newModState = 0; + if (!TheHotKeyManager) + return KEEP_MESSAGE; - if( keyState & KEY_STATE_CONTROL ) - { - newModState |= CTRL; - } + WideChar key = TheKeyboard->getPrintableKey(msg->getArgument(0)->integer, 0); + UnicodeString uKey; + uKey.concat(key); + AsciiString aKey; + aKey.translate(uKey); - if( keyState & KEY_STATE_SHIFT ) - { - newModState |= SHIFT; - } + if (TheHotKeyManager->executeHotKey(aKey)) + return DESTROY_MESSAGE; - if( keyState & KEY_STATE_ALT ) - { - newModState |= ALT; - } - if(newModState != 0) - return disp; - WideChar key = TheKeyboard->getPrintableKey(msg->getArgument(0)->integer, 0); - UnicodeString uKey; - uKey.concat(key); - AsciiString aKey; - aKey.translate(uKey); - if(TheHotKeyManager && TheHotKeyManager->executeHotKey(aKey)) - disp = DESTROY_MESSAGE; - } - return disp; + return KEEP_MESSAGE; } //----------------------------------------------------------------------------- From be7430597de44357913ff43ecb3aca088bacde47 Mon Sep 17 00:00:00 2001 From: Caball009 <82909616+Caball009@users.noreply.github.com> Date: Mon, 1 Dec 2025 21:21:24 +0100 Subject: [PATCH 3/6] feat(hotkey): Retain messages for keystrokes that can be both meta events and hotkey events. --- .../GameClient/MessageStream/MetaEvent.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/MetaEvent.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/MetaEvent.cpp index aeaf2287b4..ddde27d32b 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/MetaEvent.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/MetaEvent.cpp @@ -500,6 +500,22 @@ GameMessageDisposition MetaEventTranslator::translateGameMessage(const GameMessa if( keyState & KEY_STATE_AUTOREPEAT ) { + if (!newModState) + { + if (const Drawable *selectedDrawable = TheInGameUI->getFirstSelectedDrawable()) + { + if (const Object *tempObject = selectedDrawable->getObject()) + { + if (tempObject->isKindOf(KINDOF_STRUCTURE)) + { + // keep message for hotkey + disp = KEEP_MESSAGE; + break; + } + } + } + } + // if it's an autorepeat of a "known" key, don't generate the meta-event, // but DO eat the keystroke so no one else can mess with it //DEBUG_LOG(("Frame %d: MetaEventTranslator::translateGameMessage() auto-repeat: %s", TheGameLogic->getFrame(), findGameMessageNameByType(map->m_meta))); From a1d2c4a955beb6d4f3b48ac4d858222b644a75f4 Mon Sep 17 00:00:00 2001 From: Caball009 <82909616+Caball009@users.noreply.github.com> Date: Mon, 1 Dec 2025 21:40:12 +0100 Subject: [PATCH 4/6] Refactored function to reduce number of return statements. --- .../GameClient/MessageStream/HotKey.cpp | 30 +++++++++---------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/HotKey.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/HotKey.cpp index e6a475012d..92ec0ac415 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/HotKey.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/HotKey.cpp @@ -75,29 +75,27 @@ GameMessageDisposition HotKeyTranslator::translateGameMessage(const GameMessage if ((msg->getArgument(1)->integer & KEY_STATE_AUTOREPEAT) == 0) return KEEP_MESSAGE; - break; + FALLTHROUGH; case GameMessage::MSG_RAW_KEY_UP: if (msg->getArgument(1)->integer & (KEY_STATE_CONTROL | KEY_STATE_SHIFT | KEY_STATE_ALT)) return KEEP_MESSAGE; - break; + if (!TheHotKeyManager) + { + WideChar key = TheKeyboard->getPrintableKey(msg->getArgument(0)->integer, 0); + UnicodeString uKey; + uKey.concat(key); + AsciiString aKey; + aKey.translate(uKey); + + if (TheHotKeyManager->executeHotKey(aKey)) + return DESTROY_MESSAGE; + } + + return KEEP_MESSAGE; default: return KEEP_MESSAGE; } - - if (!TheHotKeyManager) - return KEEP_MESSAGE; - - WideChar key = TheKeyboard->getPrintableKey(msg->getArgument(0)->integer, 0); - UnicodeString uKey; - uKey.concat(key); - AsciiString aKey; - aKey.translate(uKey); - - if (TheHotKeyManager->executeHotKey(aKey)) - return DESTROY_MESSAGE; - - return KEEP_MESSAGE; } //----------------------------------------------------------------------------- From 0ea269a66b7f4628403cfeba94713d88c6316778 Mon Sep 17 00:00:00 2001 From: Caball009 <82909616+Caball009@users.noreply.github.com> Date: Mon, 1 Dec 2025 22:21:48 +0100 Subject: [PATCH 5/6] Fixed hotkey manager check inversion. --- .../Code/GameEngine/Source/GameClient/MessageStream/HotKey.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/HotKey.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/HotKey.cpp index 92ec0ac415..7a2b220b56 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/HotKey.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/HotKey.cpp @@ -80,7 +80,7 @@ GameMessageDisposition HotKeyTranslator::translateGameMessage(const GameMessage if (msg->getArgument(1)->integer & (KEY_STATE_CONTROL | KEY_STATE_SHIFT | KEY_STATE_ALT)) return KEEP_MESSAGE; - if (!TheHotKeyManager) + if (TheHotKeyManager) { WideChar key = TheKeyboard->getPrintableKey(msg->getArgument(0)->integer, 0); UnicodeString uKey; From e58a8abd8de1d402da1abff020c2741a95e8e569 Mon Sep 17 00:00:00 2001 From: Caball009 <82909616+Caball009@users.noreply.github.com> Date: Tue, 2 Dec 2025 20:28:46 +0100 Subject: [PATCH 6/6] Changed the order of translators. To provent the meta event translator from 'eating up' hotkey events. --- .../GameEngine/Source/GameClient/GameClient.cpp | 4 ++-- .../GameClient/MessageStream/MetaEvent.cpp | 16 ---------------- 2 files changed, 2 insertions(+), 18 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp index 0c49c2a141..edbd19aadb 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp @@ -289,8 +289,8 @@ void GameClient::init( void ) // since we only allocate one of each, don't bother pooling 'em m_translators[ m_numTranslators++ ] = TheMessageStream->attachTranslator( MSGNEW("GameClientSubsystem") WindowTranslator, 10 ); - m_translators[ m_numTranslators++ ] = TheMessageStream->attachTranslator( MSGNEW("GameClientSubsystem") MetaEventTranslator, 20 ); - m_translators[ m_numTranslators++ ] = TheMessageStream->attachTranslator( MSGNEW("GameClientSubsystem") HotKeyTranslator, 25 ); + m_translators[ m_numTranslators++ ] = TheMessageStream->attachTranslator( MSGNEW("GameClientSubsystem") HotKeyTranslator, 20 ); + m_translators[ m_numTranslators++ ] = TheMessageStream->attachTranslator( MSGNEW("GameClientSubsystem") MetaEventTranslator, 25 ); m_translators[ m_numTranslators++ ] = TheMessageStream->attachTranslator( MSGNEW("GameClientSubsystem") PlaceEventTranslator, 30 ); m_translators[ m_numTranslators++ ] = TheMessageStream->attachTranslator( MSGNEW("GameClientSubsystem") GUICommandTranslator, 40 ); m_translators[ m_numTranslators++ ] = TheMessageStream->attachTranslator( MSGNEW("GameClientSubsystem") SelectionTranslator, 50 ); diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/MetaEvent.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/MetaEvent.cpp index ddde27d32b..aeaf2287b4 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/MetaEvent.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/MetaEvent.cpp @@ -500,22 +500,6 @@ GameMessageDisposition MetaEventTranslator::translateGameMessage(const GameMessa if( keyState & KEY_STATE_AUTOREPEAT ) { - if (!newModState) - { - if (const Drawable *selectedDrawable = TheInGameUI->getFirstSelectedDrawable()) - { - if (const Object *tempObject = selectedDrawable->getObject()) - { - if (tempObject->isKindOf(KINDOF_STRUCTURE)) - { - // keep message for hotkey - disp = KEEP_MESSAGE; - break; - } - } - } - } - // if it's an autorepeat of a "known" key, don't generate the meta-event, // but DO eat the keystroke so no one else can mess with it //DEBUG_LOG(("Frame %d: MetaEventTranslator::translateGameMessage() auto-repeat: %s", TheGameLogic->getFrame(), findGameMessageNameByType(map->m_meta)));