fix(metaevent): Ignore order in which modifier keys are released to trigger meta events#2577
Conversation
|
| Filename | Overview |
|---|---|
| Core/Libraries/Include/Lib/BaseType.h | Adds BitsAreSet macro — simple, correct, and complementary to existing BitIsSet. |
| Generals/Code/GameEngine/Include/GameClient/MetaEvent.h | Replaces m_lastKeyDown/m_lastModState with KeyDownInfo[KEY_COUNT], encoding which modifier combinations were active when each key was pressed. Index mapping and bitfield fit 7 combinations cleanly in a single byte. |
| Generals/Code/GameEngine/Source/GameClient/MessageStream/MetaEvent.cpp | Core logic rewrite: splits key-up handling into a modStateRemoved path (modifier released, scans all held keys) and a normal path. Logic is functionally sound, but the else block body has an indentation regression — the for-loop and inner if/else sit at the same indent level as else itself, making the block structure visually misleading. |
| GeneralsMD/Code/GameEngine/Include/GameClient/MetaEvent.h | Exact mirror of Generals/Code version; same KeyDownInfo struct added, same analysis applies. |
| GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/MetaEvent.cpp | Mirror of Generals .cpp; carries the same indentation inconsistency in the else block. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[MSG_RAW_KEY_DOWN / MSG_RAW_KEY_UP] --> B{Is systemKey a\nmodifier key?}
B -- Yes --> C[key = MK_NONE]
B -- No --> D[key = systemKey]
C --> E[Compute newModState\nfrom keyState flags]
D --> E
E --> F{modStateRemoved?\nkey==MK_NONE &&\nt==KEY_UP}
F -- Yes --> G[Scan ALL m_keyDownInfos]
G --> H{entry.isKeyDown?}
H -- No --> I[next key]
H -- Yes --> J{BitsAreSet\nnewModState,\nkeyDownModState?}
J -- Yes, mod still active --> K[skip — combination still held]
J -- No, mod removed --> L[clearKeyModState\nfire UP meta-event]
L --> I
F -- No --> M[Iterate MetaMapRec list]
M --> N{map matches\nkey+modState+transition?}
N -- No --> O[next map]
N -- Yes --> P[Fire meta-event\nbreak]
P --> Q{t == KEY_DOWN?}
Q -- Yes, newModState!=NONE --> R[m_keyDownInfos key\n.setKeyModState]
Q -- No KEY_UP, newModState!=NONE --> S[m_keyDownInfos key\n.clearKeyModState]
Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.
---
### Issue 1 of 2
Generals/Code/GameEngine/Source/GameClient/MessageStream/MetaEvent.cpp:518-524
The body of the `else` block is indented at the same level as the `else` keyword itself rather than one level deeper. The comment at the top of the block is correctly indented to 3 tabs, but the `for` loop and the `if (t == GameMessage::MSG_RAW_KEY_DOWN)` block that follow are both at 2 tabs — making the structure visually misleading. The same pattern repeats in `GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/MetaEvent.cpp`.
```suggestion
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)
{
```
### Issue 2 of 2
Generals/Code/GameEngine/Include/GameClient/MetaEvent.h:415-418
`hasKeyModState` is declared but never called anywhere in the translation units. If it is not needed for the current implementation, removing it avoids dead-code accumulation. The same method exists in `GeneralsMD/Code/GameEngine/Include/GameClient/MetaEvent.h`.
Reviews (5): Last reviewed commit: "Replicate in Generals" | Re-trigger Greptile
1709ece to
d6a0112
Compare
|
greptile claims "stale-bit bug in the normal KEY_UP path" but there is no bug and it works as intended. |
| if (!keyDownInfo.isKeyDown()) | ||
| continue; | ||
|
|
||
| for (UnsignedInt modStateIndex = 0; modStateIndex < KeyDownInfo::getMaxKeyModStateCount(); ++modStateIndex) |
There was a problem hiding this comment.
Do you think we can extract some of the inner loop handling?
The levels of indentation can make it hard to follow.
There was a problem hiding this comment.
Yes I wanted to make a follow up change that refactors this function because it is quite a bit long.
|
Greptile claims "Missing |
|
DrGoldfish:
|
|
MetaEvent replicate is conflicting. Looks like Generals needs merging first. |
d6a0112 to
103894b
Compare
|
Replicated in Generals without conflicts |
This change ignores the order in which keys are released to trigger meta events.
This is useful because players do not necessarily and intuitively release the keys in the expected strict order. For example CTRL + A press, A + CTRL release. Trouble can arise when releasing CTRL before A, because then the mapped meta event would not trigger.
This is no issue in the original game because it has no meta event mapping on Release and Modifier, but it can be on Mods or Debug builds, for example with #2546 adding a Comma + CTRL release event.
TODO