Skip to content

Commit d12db19

Browse files
committed
fix(metaevent): Ignore order in which modifier keys are released to trigger meta events
1 parent 476a57c commit d12db19

3 files changed

Lines changed: 146 additions & 33 deletions

File tree

Core/Libraries/Include/Lib/BaseType.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ inline Real deg2rad(Real rad) { return rad * (PI/180); }
8989
//-----------------------------------------------------------------------------
9090
// TheSuperHackers @build xezon 17/03/2025 Renames BitTest to BitIsSet to prevent conflict with BitTest macro from winnt.h
9191
#define BitIsSet( x, i ) ( ( (x) & (i) ) != 0 )
92+
#define BitsAreSet( x, i ) ( ( (x) & (i) ) == x )
9293
#define BitSet( x, i ) ( (x) |= (i) )
9394
#define BitClear( x, i ) ( (x ) &= ~(i) )
9495
#define BitToggle( x, i ) ( (x) ^= (i) )

GeneralsMD/Code/GameEngine/Include/GameClient/MetaEvent.h

Lines changed: 80 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -354,9 +354,86 @@ EMPTY_DTOR(MetaMapRec)
354354
class MetaEventTranslator : public GameMessageTranslator
355355
{
356356
private:
357-
358-
Int m_lastKeyDown; // really a MappableKeyType
359-
Int m_lastModState; // really a MappableKeyModState
357+
struct KeyDownInfo
358+
{
359+
KeyDownInfo() : m_modStateBits(0) {}
360+
361+
static UnsignedInt getMaxKeyModStateCount()
362+
{
363+
return 7;
364+
}
365+
366+
static MappableKeyModState toKeyModState(UnsignedInt index)
367+
{
368+
switch (index)
369+
{
370+
case 0: return CTRL;
371+
case 1: return ALT;
372+
case 2: return SHIFT;
373+
case 3: return CTRL_ALT;
374+
case 4: return SHIFT_CTRL;
375+
case 5: return SHIFT_ALT;
376+
case 6: return SHIFT_ALT_CTRL;
377+
}
378+
return NONE;
379+
}
380+
381+
static UnsignedInt toIndex(MappableKeyModState modState)
382+
{
383+
switch (modState)
384+
{
385+
case CTRL: return 0;
386+
case ALT: return 1;
387+
case SHIFT: return 2;
388+
case CTRL_ALT: return 3;
389+
case SHIFT_CTRL: return 4;
390+
case SHIFT_ALT: return 5;
391+
case SHIFT_ALT_CTRL: return 6;
392+
}
393+
return 7;
394+
}
395+
396+
Bool isKeyDown() const
397+
{
398+
return m_modStateBits != 0;
399+
}
400+
401+
MappableKeyModState getKeyModState(UnsignedInt index)
402+
{
403+
if (BitIsSet(m_modStateBits, 1 << index))
404+
{
405+
return toKeyModState(index);
406+
}
407+
return NONE;
408+
}
409+
410+
void clearKeyModState(UnsignedInt index)
411+
{
412+
BitClear(m_modStateBits, 1 << index);
413+
}
414+
415+
Bool hasKeyModState(MappableKeyModState modState) const
416+
{
417+
return BitIsSet(m_modStateBits, 1 << toIndex(modState));
418+
}
419+
420+
void setKeyModState(MappableKeyModState modState)
421+
{
422+
BitSet(m_modStateBits, 1 << toIndex(modState));
423+
}
424+
425+
void clearKeyModState(MappableKeyModState modState)
426+
{
427+
BitClear(m_modStateBits, 1 << toIndex(modState));
428+
}
429+
430+
private:
431+
UnsignedByte m_modStateBits; ///< Fits all combinations of CTRL+ALT+SHIFT, storing 1 bit for each
432+
};
433+
434+
Int m_lastModState; // really a MappableKeyModState
435+
436+
KeyDownInfo m_keyDownInfos[KEY_COUNT];
360437

361438
enum { NUM_MOUSE_BUTTONS = 3 };
362439
ICoord2D m_mouseDownPosition[NUM_MOUSE_BUTTONS];

GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/MetaEvent.cpp

Lines changed: 65 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -378,14 +378,11 @@ static const FieldParse TheMetaMapFieldParseTable[] =
378378

379379
//-------------------------------------------------------------------------------------------------
380380
MetaEventTranslator::MetaEventTranslator() :
381-
m_lastKeyDown(MK_NONE),
382381
m_lastModState(0)
383382
{
384383
for (Int i = 0; i < NUM_MOUSE_BUTTONS; ++i) {
385384
m_nextUpShouldCreateDoubleClick[i] = FALSE;
386385
}
387-
388-
389386
}
390387

391388
//-------------------------------------------------------------------------------------------------
@@ -441,8 +438,19 @@ GameMessageDisposition MetaEventTranslator::translateGameMessage(const GameMessa
441438

442439
if (t == GameMessage::MSG_RAW_KEY_DOWN || t == GameMessage::MSG_RAW_KEY_UP)
443440
{
444-
MappableKeyType key = (MappableKeyType)msg->getArgument(0)->integer;
445-
Int keyState = msg->getArgument(1)->integer;
441+
Int systemKey = msg->getArgument(0)->integer;
442+
switch (systemKey)
443+
{
444+
case KEY_LCTRL:
445+
case KEY_RCTRL:
446+
case KEY_LSHIFT:
447+
case KEY_RSHIFT:
448+
case KEY_LALT:
449+
case KEY_RALT:
450+
systemKey = KEY_NONE;
451+
}
452+
const MappableKeyType key = (MappableKeyType)systemKey;
453+
const Int keyState = msg->getArgument(1)->integer;
446454

447455
// for our purposes here, we don't care to distinguish between right and left keys,
448456
// so just fudge a little to simplify things.
@@ -463,6 +471,52 @@ GameMessageDisposition MetaEventTranslator::translateGameMessage(const GameMessa
463471
newModState |= ALT;
464472
}
465473

474+
const Bool modStateRemoved = newModState < m_lastModState;
475+
476+
if (modStateRemoved)
477+
{
478+
// TheSuperHackers @fix The key handler now ignores the order in which modifier keys are released.
479+
// This avoids frustrating experiences where a wrong button release order would skip an important key event.
480+
481+
for (Int keyDownIndex = 0; keyDownIndex < ARRAY_SIZE(m_keyDownInfos); ++keyDownIndex)
482+
{
483+
const MappableKeyType keyDown = (MappableKeyType)keyDownIndex;
484+
KeyDownInfo &keyDownInfo = m_keyDownInfos[keyDownIndex];
485+
486+
if (!keyDownInfo.isKeyDown())
487+
continue;
488+
489+
for (UnsignedInt modStateIndex = 0; modStateIndex < KeyDownInfo::getMaxKeyModStateCount(); ++modStateIndex)
490+
{
491+
const MappableKeyModState keyDownModState = keyDownInfo.getKeyModState(modStateIndex);
492+
493+
if (keyDownModState == NONE)
494+
continue;
495+
496+
if (BitsAreSet(keyDownModState, newModState))
497+
continue;
498+
499+
// Forget that this key and mod state are pressed.
500+
keyDownInfo.clearKeyModState(modStateIndex);
501+
502+
for (const MetaMapRec *map = TheMetaMap->getFirstMetaMapRec(); map; map = map->m_next)
503+
{
504+
if (!isMessageUsable(map->m_usableIn))
505+
continue;
506+
507+
if (!(map->m_key == keyDown && map->m_modState == keyDownModState && map->m_transition == UP))
508+
continue;
509+
510+
TheMessageStream->appendMessage(map->m_meta);
511+
disp = DESTROY_MESSAGE;
512+
}
513+
}
514+
}
515+
}
516+
else
517+
{
518+
// TheSuperHackers @info The regular key handler only triggers events when the mapped key is pressed,
519+
// not when the modifier (CTRL, ALT, SHIFT) is pressed, unless the key is MK_NONE.
466520

467521
for (const MetaMapRec *map = TheMetaMap->getFirstMetaMapRec(); map; map = map->m_next)
468522
{
@@ -472,23 +526,6 @@ GameMessageDisposition MetaEventTranslator::translateGameMessage(const GameMessa
472526
if (!isMessageUsable(map->m_usableIn))
473527
continue;
474528

475-
// check for the special case of mods-only-changed.
476-
if (
477-
map->m_key == MK_NONE &&
478-
newModState != m_lastModState &&
479-
(
480-
(map->m_transition == UP && map->m_modState == m_lastModState) ||
481-
(map->m_transition == DOWN && map->m_modState == newModState)
482-
)
483-
)
484-
{
485-
//DEBUG_LOG(("Frame %d: MetaEventTranslator::translateGameMessage() Mods-only change: %s", TheGameLogic->getFrame(), findGameMessageNameByType(map->m_meta)));
486-
/*GameMessage *metaMsg =*/ TheMessageStream->appendMessage(map->m_meta);
487-
disp = DESTROY_MESSAGE;
488-
break;
489-
}
490-
491-
// ok, now check for "normal" key transitions.
492529
if (
493530
map->m_key == key &&
494531
map->m_modState == newModState &&
@@ -499,7 +536,6 @@ GameMessageDisposition MetaEventTranslator::translateGameMessage(const GameMessa
499536
)
500537
)
501538
{
502-
503539
if( keyState & KEY_STATE_AUTOREPEAT )
504540
{
505541
// if it's an autorepeat of a "known" key, don't generate the meta-event,
@@ -540,13 +576,10 @@ GameMessageDisposition MetaEventTranslator::translateGameMessage(const GameMessa
540576
}
541577
}
542578

543-
579+
}
544580

545581
if (t == GameMessage::MSG_RAW_KEY_DOWN)
546582
{
547-
m_lastKeyDown = key;
548-
549-
550583
#ifdef DUMP_ALL_KEYS_TO_LOG
551584

552585
WideChar Wkey = TheKeyboard->getPrintableKey(key, 0);
@@ -556,11 +589,13 @@ GameMessageDisposition MetaEventTranslator::translateGameMessage(const GameMessa
556589
aKey.translate(uKey);
557590
DEBUG_LOG(("^%s ", aKey.str()));
558591
#endif
559-
592+
if (newModState != NONE)
593+
{
594+
// Remember that this key and mod state are pressed.
595+
m_keyDownInfos[key].setKeyModState((MappableKeyModState)newModState);
596+
}
560597
}
561598

562-
563-
564599
m_lastModState = newModState;
565600
}
566601

0 commit comments

Comments
 (0)