diff --git a/Core/Libraries/Include/Lib/BaseType.h b/Core/Libraries/Include/Lib/BaseType.h index 99361609f94..ab3d111d5ec 100644 --- a/Core/Libraries/Include/Lib/BaseType.h +++ b/Core/Libraries/Include/Lib/BaseType.h @@ -89,6 +89,7 @@ inline Real deg2rad(Real rad) { return rad * (PI/180); } //----------------------------------------------------------------------------- // TheSuperHackers @build xezon 17/03/2025 Renames BitTest to BitIsSet to prevent conflict with BitTest macro from winnt.h #define BitIsSet( x, i ) ( ( (x) & (i) ) != 0 ) +#define BitsAreSet( x, i ) ( ( (x) & (i) ) == (i) ) #define BitSet( x, i ) ( (x) |= (i) ) #define BitClear( x, i ) ( (x ) &= ~(i) ) #define BitToggle( x, i ) ( (x) ^= (i) ) diff --git a/GeneralsMD/Code/GameEngine/Include/GameClient/MetaEvent.h b/GeneralsMD/Code/GameEngine/Include/GameClient/MetaEvent.h index fed637c54ca..1d9b57d45d0 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameClient/MetaEvent.h +++ b/GeneralsMD/Code/GameEngine/Include/GameClient/MetaEvent.h @@ -354,9 +354,84 @@ EMPTY_DTOR(MetaMapRec) class MetaEventTranslator : public GameMessageTranslator { private: - - Int m_lastKeyDown; // really a MappableKeyType - Int m_lastModState; // really a MappableKeyModState + struct KeyDownInfo + { + KeyDownInfo() : m_modStateBits(0) {} + + static UnsignedInt getMaxKeyModStateCount() + { + return 7; + } + + static MappableKeyModState toKeyModState(UnsignedInt index) + { + switch (index) + { + case 0: return CTRL; + case 1: return ALT; + case 2: return SHIFT; + case 3: return CTRL_ALT; + case 4: return SHIFT_CTRL; + case 5: return SHIFT_ALT; + case 6: return SHIFT_ALT_CTRL; + } + return NONE; + } + + static UnsignedInt toIndex(MappableKeyModState modState) + { + switch (modState) + { + case CTRL: return 0; + case ALT: return 1; + case SHIFT: return 2; + case CTRL_ALT: return 3; + case SHIFT_CTRL: return 4; + case SHIFT_ALT: return 5; + case SHIFT_ALT_CTRL: return 6; + } + return 7; + } + + Bool isKeyDown() const + { + return m_modStateBits != 0; + } + + MappableKeyModState getKeyModState(UnsignedInt index) + { + if (BitIsSet(m_modStateBits, 1 << index)) + { + return toKeyModState(index); + } + return NONE; + } + + void clearKeyModState(UnsignedInt index) + { + BitClear(m_modStateBits, 1 << index); + } + + Bool hasKeyModState(MappableKeyModState modState) const + { + return BitIsSet(m_modStateBits, 1 << toIndex(modState)); + } + + void setKeyModState(MappableKeyModState modState) + { + BitSet(m_modStateBits, 1 << toIndex(modState)); + } + + void clearKeyModState(MappableKeyModState modState) + { + BitClear(m_modStateBits, 1 << toIndex(modState)); + } + + private: + UnsignedByte m_modStateBits; ///< Fits all combinations of CTRL+ALT+SHIFT, storing 1 bit for each + }; + + KeyDownInfo m_keyDownInfos[KEY_COUNT]; enum { NUM_MOUSE_BUTTONS = 3 }; ICoord2D m_mouseDownPosition[NUM_MOUSE_BUTTONS]; diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/MetaEvent.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/MetaEvent.cpp index 9d9df747c00..c004decbd8c 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/MetaEvent.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/MetaEvent.cpp @@ -377,15 +377,11 @@ static const FieldParse TheMetaMapFieldParseTable[] = // PUBLIC FUNCTIONS /////////////////////////////////////////////////////////////////////////////// //------------------------------------------------------------------------------------------------- -MetaEventTranslator::MetaEventTranslator() : - m_lastKeyDown(MK_NONE), - m_lastModState(0) +MetaEventTranslator::MetaEventTranslator() { for (Int i = 0; i < NUM_MOUSE_BUTTONS; ++i) { m_nextUpShouldCreateDoubleClick[i] = FALSE; } - - } //------------------------------------------------------------------------------------------------- @@ -441,8 +437,20 @@ GameMessageDisposition MetaEventTranslator::translateGameMessage(const GameMessa if (t == GameMessage::MSG_RAW_KEY_DOWN || t == GameMessage::MSG_RAW_KEY_UP) { - MappableKeyType key = (MappableKeyType)msg->getArgument(0)->integer; - Int keyState = msg->getArgument(1)->integer; + const Int systemKey = msg->getArgument(0)->integer; + const Int keyState = msg->getArgument(1)->integer; + + MappableKeyType key = (MappableKeyType)systemKey; + switch (systemKey) + { + case KEY_LCTRL: + case KEY_RCTRL: + case KEY_LSHIFT: + case KEY_RSHIFT: + case KEY_LALT: + case KEY_RALT: + key = MK_NONE; + } // for our purposes here, we don't care to distinguish between right and left keys, // so just fudge a little to simplify things. @@ -463,6 +471,52 @@ GameMessageDisposition MetaEventTranslator::translateGameMessage(const GameMessa newModState |= ALT; } + const Bool modStateRemoved = (key == MK_NONE) && (t == GameMessage::MSG_RAW_KEY_UP); + + if (modStateRemoved) + { + // TheSuperHackers @fix The key handler now ignores the order in which modifier keys are released. + // This avoids frustrating experiences where a wrong button release order would skip an important key event. + + for (Int keyDownIndex = 0; keyDownIndex < ARRAY_SIZE(m_keyDownInfos); ++keyDownIndex) + { + const MappableKeyType keyDown = (MappableKeyType)keyDownIndex; + KeyDownInfo &keyDownInfo = m_keyDownInfos[keyDownIndex]; + + if (!keyDownInfo.isKeyDown()) + continue; + + for (UnsignedInt modStateIndex = 0; modStateIndex < KeyDownInfo::getMaxKeyModStateCount(); ++modStateIndex) + { + const MappableKeyModState keyDownModState = keyDownInfo.getKeyModState(modStateIndex); + + if (keyDownModState == NONE) + continue; + + if (BitsAreSet(newModState, keyDownModState)) + continue; + + // Forget that this key and mod state are pressed. + keyDownInfo.clearKeyModState(modStateIndex); + + for (const MetaMapRec *map = TheMetaMap->getFirstMetaMapRec(); map; map = map->m_next) + { + if (!isMessageUsable(map->m_usableIn)) + continue; + + if (!(map->m_key == keyDown && map->m_modState == keyDownModState && map->m_transition == UP)) + continue; + + TheMessageStream->appendMessage(map->m_meta); + disp = DESTROY_MESSAGE; + } + } + } + } + else + { + // TheSuperHackers @info The regular key handler only triggers events when the mapped key is pressed, + // not when the modifier (CTRL, ALT, SHIFT) is pressed, unless the key is MK_NONE. for (const MetaMapRec *map = TheMetaMap->getFirstMetaMapRec(); map; map = map->m_next) { @@ -472,23 +526,6 @@ GameMessageDisposition MetaEventTranslator::translateGameMessage(const GameMessa if (!isMessageUsable(map->m_usableIn)) continue; - // check for the special case of mods-only-changed. - if ( - map->m_key == MK_NONE && - newModState != m_lastModState && - ( - (map->m_transition == UP && map->m_modState == m_lastModState) || - (map->m_transition == DOWN && map->m_modState == newModState) - ) - ) - { - //DEBUG_LOG(("Frame %d: MetaEventTranslator::translateGameMessage() Mods-only change: %s", TheGameLogic->getFrame(), findGameMessageNameByType(map->m_meta))); - /*GameMessage *metaMsg =*/ TheMessageStream->appendMessage(map->m_meta); - disp = DESTROY_MESSAGE; - break; - } - - // ok, now check for "normal" key transitions. if ( map->m_key == key && map->m_modState == newModState && @@ -499,7 +536,6 @@ GameMessageDisposition MetaEventTranslator::translateGameMessage(const GameMessa ) ) { - if( keyState & KEY_STATE_AUTOREPEAT ) { // if it's an autorepeat of a "known" key, don't generate the meta-event, @@ -540,13 +576,8 @@ GameMessageDisposition MetaEventTranslator::translateGameMessage(const GameMessa } } - - if (t == GameMessage::MSG_RAW_KEY_DOWN) { - m_lastKeyDown = key; - - #ifdef DUMP_ALL_KEYS_TO_LOG WideChar Wkey = TheKeyboard->getPrintableKey(key, 0); @@ -556,12 +587,24 @@ GameMessageDisposition MetaEventTranslator::translateGameMessage(const GameMessa aKey.translate(uKey); DEBUG_LOG(("^%s ", aKey.str())); #endif - + if (newModState != NONE) + { + // Remember that this key and mod state are pressed. + m_keyDownInfos[key].setKeyModState((MappableKeyModState)newModState); + } } + else + { + if (newModState != NONE) + { + DEBUG_ASSERTCRASH(key != MK_NONE, ("Key is expected to be not MK_NONE")); + // Forget that this key and mod state are pressed. + m_keyDownInfos[key].clearKeyModState((MappableKeyModState)newModState); + } + } - - m_lastModState = newModState; + } }