Skip to content

fix(metaevent): Ignore order in which modifier keys are released to trigger meta events#2577

Open
xezon wants to merge 3 commits intoTheSuperHackers:mainfrom
xezon:xezon/fix-key-down-modifiers
Open

fix(metaevent): Ignore order in which modifier keys are released to trigger meta events#2577
xezon wants to merge 3 commits intoTheSuperHackers:mainfrom
xezon:xezon/fix-key-down-modifiers

Conversation

@xezon
Copy link
Copy Markdown

@xezon xezon commented Apr 11, 2026

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

  • Replicate in Generals
  • Test key mappings and key actions in game

@xezon xezon added Enhancement Is new feature or request Minor Severity: Minor < Major < Critical < Blocker Gen Relates to Generals ZH Relates to Zero Hour Mod Relates to Mods or modding Input labels Apr 11, 2026
@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Apr 11, 2026

Greptile Summary

Replaces the old single m_lastKeyDown/m_lastModState pair with a per-key UnsignedByte bitfield (m_keyDownInfos[KEY_COUNT]) that records which modifier-state combinations were active when each key was pressed. When a modifier is released before the mapped key, the new modStateRemoved path iterates all tracked keys and fires any pending UP meta-events correctly.

  • P1 (line 598–605): The unconditional bottom else block runs for all KEY_UP events, including modifier-key releases. When one modifier (e.g. CTRL) is released while another (e.g. SHIFT) is still held, it calls clearKeyModState(SHIFT) on KEY_NONE, erasing tracking set when SHIFT was originally pressed alone — causing SHIFT+MK_NONE UP to be silently dropped. Adding !modStateRemoved as a guard fixes it.

Confidence Score: 3/5

Safe for the common case (regular key released before modifier), but contains a functional regression for MK_NONE+multi-modifier UP events.

One P1 bug: the unconditional KEY_UP bottom block corrupts KEY_NONE modifier tracking when two modifiers are combined and one is released first, causing silent loss of MK_NONE+modifier UP events. Fix is a one-line guard. Only affects mods/debug builds in current practice.

GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/MetaEvent.cpp lines 598–605

Important Files Changed

Filename Overview
GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/MetaEvent.cpp Core fix with one P1 regression: unconditional bottom KEY_UP block can erase the wrong modifier tracking on KEY_NONE when two modifiers are combined.
GeneralsMD/Code/GameEngine/Include/GameClient/MetaEvent.h Introduces KeyDownInfo struct with compact UnsignedByte bitfield for 7 mod-state combinations; replaces m_lastKeyDown with m_keyDownInfos[KEY_COUNT].
Core/Libraries/Include/Lib/BaseType.h Adds BitsAreSet macro — straightforward and correctly placed.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/MetaEvent.cpp
Line: 598-605

Comment:
**Unconditional KEY_UP block clears wrong modifier tracking on `KEY_NONE`**

When a modifier is released while another modifier remains held, `key` is `KEY_NONE` and `newModState` is the *still-held* modifier. This block calls `clearKeyModState(newModState)` on `KEY_NONE`, erasing the tracking bit that was set when the remaining modifier was originally pressed alone.

Concrete failure: SHIFT pressed alone (bit 2 set on `KEY_NONE`), then CTRL pressed (bit 4 set). When CTRL is released: the `modStateRemoved` path correctly preserves bit 2, but this block then calls `clearKeyModState(SHIFT)` — bit 2 is gone. When SHIFT is later released, `modStateRemoved` finds `KEY_NONE` empty and `SHIFT+MK_NONE UP` is never fired.

Adding `!modStateRemoved` as a guard fixes it.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/MetaEvent.cpp
Line: 502-512

Comment:
**Missing `break` after firing meta-event**

The regular path breaks after the first matching `MetaMapRec`. The `modStateRemoved` loop fires all matching entries for a given `key + modState + UP` combination. Harmless in practice but inconsistent with the regular path.

How can I resolve this? If you propose a fix, please make it concise.

Reviews (3): Last reviewed commit: "Flip args BitsAreSet" | Re-trigger Greptile

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Enhancement Is new feature or request Gen Relates to Generals Input Minor Severity: Minor < Major < Critical < Blocker Mod Relates to Mods or modding ZH Relates to Zero Hour

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant