Skip to content

feat(client): Add KeyboardOverlay option to show hotkeys on command bar cameos - #3216

Open
triatomic wants to merge 2 commits into
TheSuperHackers:mainfrom
triatomic:qol/keyboard-overlay
Open

feat(client): Add KeyboardOverlay option to show hotkeys on command bar cameos#3216
triatomic wants to merge 2 commits into
TheSuperHackers:mainfrom
triatomic:qol/keyboard-overlay

Conversation

@triatomic

Copy link
Copy Markdown

Adds an opt-in client option drawing each command bar cameo's keyboard hotkey letter over the cameo, so the player can learn the shortcuts without hunting through tooltips. Defaults off; behavior is retail unless set in Options.ini.

KeyboardOverlay (= Yes) — the letter comes from what actually got registered in the hotkey manager, not from the button's label: colliding hotkeys are dropped at registration, and drawing those would advertise a key that does nothing. Cosmetics are configurable:

  • KeyboardOverlayRed / Green / Blue — letter color, 0–255 each, default white
  • KeyboardOverlayBackdrop (= Yes, default on while the overlay is on) — translucent plate behind the letter so it stays readable over busy cameo art
  • KeyboardOverlayBackdropRed / Green / Blue / Opacity — plate color, default black at 50%

Display strings are cached per letter and returned to the manager by the W3DDisplayStringManager destructor before teardown — the same pattern that destructor already uses for its group-numeral strings, and the same issue class Qodo flagged on #3214, fixed here preemptively.

Works in both Zero Hour and Generals — the drawing lives in the shared W3D push button gadget, with per-game GlobalData plumbing.

This option ships in the Contra mod's engine fork and has been played there; this PR is the port onto current main, collapsing the fork's three overlay commits into their final state. Code was written with LLM assistance and human-reviewed, adapted and playtested by the author.

Prepared for Squash and Merge.

…ar cameos

Draws each command bar cameo's keyboard hotkey letter over the cameo, so
the player can learn the shortcuts without hunting through tooltips. The
letter comes from what actually got registered in the hotkey manager,
not from the button's label - colliding hotkeys are dropped at
registration, and drawing those would advertise a key that does nothing.

  KeyboardOverlay = Yes                       ; off by default
  KeyboardOverlayRed / Green / Blue           ; letter colour, default white
  KeyboardOverlayBackdrop = Yes               ; translucent plate, on when
                                              ; the overlay itself is on
  KeyboardOverlayBackdropRed / Green / Blue / Opacity

Display strings are cached per letter and returned to the manager by the
W3DDisplayStringManager destructor before the manager itself is torn
down, which its base class asserts on.

Available in both Zero Hour and Generals: the drawing lives in the
shared W3D push button gadget.

Ported from the Contra mod's engine fork, collapsing its three overlay
commits into the final state.
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Add configurable hotkey overlays to command bar cameos

✨ Enhancement ⚙️ Configuration changes 🕐 20-40 Minutes

Grey Divider

AI Description

• Adds opt-in hotkey letters to command bar cameos using registered bindings.
• Supports configurable colors and readability backdrops in both game variants.
• Caches overlay strings and releases them safely during display-manager teardown.
Diagram

sequenceDiagram
    participant INI as Options.ini
    participant Pref as Option Preferences
    participant Global as Game Globals
    participant Button as W3D Button
    participant Hotkeys as Hotkey Manager
    participant Strings as String Manager
    INI->>Pref: Read overlay settings
    Pref->>Global: Store colors and flags
    Button->>Global: Check style settings
    Button->>Hotkeys: Query registered key
    Hotkeys-->>Button: Return active binding
    Button->>Strings: Create cached glyph
    Button->>Button: Draw plate and letter
    Strings->>Button: Trigger cache cleanup
    Button->>Strings: Free cached glyphs
Loading
High-Level Assessment

The chosen approach is appropriate: placing drawing in the shared W3D push-button path avoids duplicated rendering, querying registered hotkeys prevents advertising rejected collisions, and a bounded per-character cache avoids repeated text geometry creation. Deriving keys from labels or maintaining a second reverse index was considered but would respectively be incorrect or add synchronization complexity for a small map.

Files changed (12) +244 / -0

Enhancement (4) +130 / -0
HotKey.hExpose registered hotkey lookup by window +5/-0

Expose registered hotkey lookup by window

• Adds a reverse-lookup API that reports the hotkey actually registered to a game window, excluding bindings rejected because of collisions.

Core/GameEngine/Include/GameClient/HotKey.h

HotKey.cppResolve active hotkeys for command windows +18/-0

Resolve active hotkeys for command windows

• Implements a small-map scan to find the binding associated with a window. Returns no key for null or unregistered windows so overlays only advertise executable shortcuts.

Core/GameEngine/Source/GameClient/MessageStream/HotKey.cpp

W3DGadget.hDeclare overlay string-cache cleanup hook +3/-0

Declare overlay string-cache cleanup hook

• Exposes a cleanup function that returns cached push-button hotkey strings before the display-string manager is destroyed.

Core/GameEngineDevice/Include/W3DDevice/GameClient/W3DGadget.h

W3DPushButton.cppRender cached hotkey overlays on command cameos +104/-0

Render cached hotkey overlays on command cameos

• Draws uppercase registered hotkeys over cameo-style push buttons, with configurable colors and an optional padded backdrop. Caches display strings by byte value and provides explicit cleanup for manager-owned resources.

Core/GameEngineDevice/Source/W3DDevice/GameClient/GUI/Gadget/W3DPushButton.cpp

Bug fix (2) +14 / -0
W3DDisplayStringManager.cppRelease Generals overlay strings during teardown +7/-0

Release Generals overlay strings during teardown

• Invokes the push-button cache cleanup while the Generals display-string manager is still alive, preventing registered strings from reaching the asserting base destructor.

Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplayStringManager.cpp

W3DDisplayStringManager.cppRelease Zero Hour overlay strings during teardown +7/-0

Release Zero Hour overlay strings during teardown

• Invokes the push-button cache cleanup while the Zero Hour display-string manager is still alive, preventing registered strings from reaching the asserting base destructor.

GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplayStringManager.cpp

Other (6) +100 / -0
OptionPreferences.hExpose keyboard overlay preference accessors +9/-0

Expose keyboard overlay preference accessors

• Adds accessors for the overlay toggle, text color, backdrop toggle, and backdrop color. Declares a shared helper for reading clamped 8-bit color channels.

Core/GameEngine/Include/Common/OptionPreferences.h

OptionPreferences.cppParse keyboard overlay settings from Options.ini +65/-0

Parse keyboard overlay settings from Options.ini

• Parses the opt-in overlay and backdrop flags plus configurable RGBA channels. Applies white text, a black 50%-opacity backdrop, and clamps configured channels to 0–255.

Core/GameEngine/Source/Common/OptionPreferences.cpp

GlobalData.hAdd Generals overlay state to global data +5/-0

Add Generals overlay state to global data

• Adds runtime fields for the Generals overlay toggle, text color, backdrop toggle, and backdrop color.

Generals/Code/GameEngine/Include/Common/GlobalData.h

GlobalData.cppInitialize and load Generals overlay preferences +8/-0

Initialize and load Generals overlay preferences

• Defines retail-preserving defaults and copies parsed keyboard overlay settings into Generals global client data.

Generals/Code/GameEngine/Source/Common/GlobalData.cpp

GlobalData.hAdd Zero Hour overlay state to global data +5/-0

Add Zero Hour overlay state to global data

• Adds runtime fields for the Zero Hour overlay toggle, text color, backdrop toggle, and backdrop color.

GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h

GlobalData.cppInitialize and load Zero Hour overlay preferences +8/-0

Initialize and load Zero Hour overlay preferences

• Defines retail-preserving defaults and copies parsed keyboard overlay settings into Zero Hour global client data.

GeneralsMD/Code/GameEngine/Source/Common/GlobalData.cpp

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0)

Grey Divider

Great, no issues found!

Qodo reviewed your code and found no material issues that require review

Grey Divider

Tip of the day
💡 Did you know, you can start a comment with 'qodo' or '@qodo' to chat about any finding

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

@greptile-apps

greptile-apps Bot commented Aug 26, 2026

Copy link
Copy Markdown

Greptile Summary

The PR adds an opt-in, configurable keyboard-hotkey overlay for command-bar cameos in both game variants, backed by registered hotkeys and cached display strings. The current UTF-8 correction prevents garbled output but suppresses valid localized hotkeys entirely.

  • Adds overlay, foreground-color, backdrop-color, and opacity preferences.
  • Exposes reverse lookup of the hotkey registered to a window.
  • Draws cached hotkey strings through the shared W3D push-button path.
  • Releases cached strings during each variant’s display-string-manager teardown.

Confidence Score: 4/5

The PR is not yet safe to merge because valid localized hotkeys remain executable but disappear from the new teaching overlay.

The new length guard treats UTF-8 byte length as character count and returns before drawing any multi-byte mnemonic, although registration and keyboard execution translate those same Unicode keys through UTF-8 successfully.

Files Needing Attention: Core/GameEngineDevice/Source/W3DDevice/GameClient/GUI/Gadget/W3DPushButton.cpp

Important Files Changed

Filename Overview
Core/GameEngineDevice/Source/W3DDevice/GameClient/GUI/Gadget/W3DPushButton.cpp Adds overlay rendering and display-string caching, but the single-byte guard omits functional non-ASCII hotkeys.
Core/GameEngine/Source/GameClient/MessageStream/HotKey.cpp Adds window-to-registered-hotkey reverse lookup used to avoid advertising discarded collisions.
Core/GameEngine/Source/Common/OptionPreferences.cpp Parses overlay enablement and clamped foreground/backdrop color channels.
Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplayStringManager.cpp Releases cached overlay display strings before manager teardown for Generals.
GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplayStringManager.cpp Mirrors overlay display-string teardown for Zero Hour.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
    Label[Localized command label] --> Search[Extract mnemonic]
    Search --> UTF8[Translate to UTF-8]
    UTF8 --> Register[Register hotkey]
    Input[Printable keyboard input] --> InputUTF8[Translate to UTF-8]
    InputUTF8 --> Execute[Execute registered command]
    Register --> Lookup[Overlay reverse lookup]
    Lookup --> Guard{Exactly one byte?}
    Guard -->|Yes| Draw[Draw overlay]
    Guard -->|No| Omit[Omit valid localized overlay]
Loading
Prompt To Fix All With AI
### Issue 1
Core/GameEngineDevice/Source/W3DDevice/GameClient/GUI/Gadget/W3DPushButton.cpp:137-138
**Localized overlays disappear**

When a localized command has a functional non-ASCII mnemonic, its registered UTF-8 key occupies multiple bytes, so this guard returns without drawing it and the teaching overlay silently omits that shortcut.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Reviews (2): Last reviewed commit: "fix(client): Draw the hotkey overlay onl..." | Re-trigger Greptile

@triatomic

Copy link
Copy Markdown
Author
image Grid hotkeys overlayed.

A localized mnemonic outside ASCII arrives as a multi byte sequence, and
drawing its first byte showed a wrong or garbled shortcut while sharing
its cache slot with other keys starting on the same byte. Draw nothing
for those instead.
Comment on lines +137 to +138
if( hotKey.getLength() != 1 || !isprint( (unsigned char)hotKey.getCharAt( 0 ) ) )
return;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Localized overlays disappear

When a localized command has a functional non-ASCII mnemonic, its registered UTF-8 key occupies multiple bytes, so this guard returns without drawing it and the teaching overlay silently omits that shortcut.

Prompt To Fix With AI
This is a comment left during a code review.
Path: Core/GameEngineDevice/Source/W3DDevice/GameClient/GUI/Gadget/W3DPushButton.cpp
Line: 137-138

Comment:
**Localized overlays disappear**

When a localized command has a functional non-ASCII mnemonic, its registered UTF-8 key occupies multiple bytes, so this guard returns without drawing it and the teaching overlay silently omits that shortcut.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant