feat(client): Add CastMode option for quick casting targeted commands - #3218
feat(client): Add CastMode option for quick casting targeted commands#3218triatomic wants to merge 2 commits into
Conversation
Adds an Options.ini client preference for how targeted commands (guard, attack move, abilities) are triggered from the keyboard: CastMode = Normal ; click button, then click world (retail, default) CastMode = QuickCast ; hotkey fires immediately at the cursor CastMode = QuickCastWithIndicator ; hold to aim with the targeting decal, release to fire Quick cast synthesizes the same arm-and-click message flow a manual cast produces, so the engine's validation, voice responses and cleanup all apply and the logic side sees exactly what a mouse click would send - no determinism risk. Commands that must not fire blind decline into the normal two step flow: multi-click and single-use commands, rally points, beacons, construction placement, and superweapons. A cast requested while the ability is still recharging is queued and fires the moment the logic side reports ready, pinned to the world position it was aimed at; right click cancels it, and it expires rather than firing minutes later. A button disabled only by WIN_STATUS_NOT_READY lets the press through so a still-firing weapon can be retargeted without a Stop, matching the engine's own DragonTank firewall expectations. Zero Hour only, per the contribution guidelines: the indicator and queue live in Zero Hour's InGameUI, and the shared translator and command bar hooks are guarded with RTS_ZEROHOUR. Both titles compile. Adapted from the Contra fork: the indicator resolves the decal from the command's own radius cursor type (the fork's custom decal radius plumbing is not ported).
PR Summary by QodoAdd configurable quick casting for targeted commands
AI Description
Diagram
High-Level Assessment
Files changed (10)
|
|
| Filename | Overview |
|---|---|
| Core/GameEngine/Source/GameClient/MessageStream/HotKey.cpp | Dispatches indicator-mode hotkeys on key-down and key-up while exposing the current transition to command processing. |
| Core/GameEngine/Source/GameClient/GUI/ControlBar/ControlBarCommandProcessing.cpp | Adds quick-cast dispatch and now suppresses the key-down execution of non-targeted commands in hold-to-aim mode. |
| GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp | Implements quick-cast indicators and cooldown queues, retaining queued targets in world space and waiting for successful projection before firing. |
| Core/GameEngine/Source/GameClient/MessageStream/SelectionXlat.cpp | Extends right-click cancellation to pending quick casts. |
| Core/GameEngine/Source/Common/OptionPreferences.cpp | Parses named and numeric CastMode values with retail behavior as the fallback. |
Sequence Diagram
sequenceDiagram
participant Player
participant Hotkeys as HotKey translator
participant ControlBar
participant UI as InGameUI
participant Stream as Message stream
Player->>Hotkeys: Press targeted-command hotkey
Hotkeys->>ControlBar: Select command
alt Ability ready
ControlBar->>UI: Arm command
ControlBar->>Stream: Synthetic world click
else Ability recharging
ControlBar->>UI: Queue command and world target
UI->>UI: Wait for readiness and successful projection
UI->>Stream: Arm command and dispatch projected click
end
Reviews (2): Last reviewed commit: "fix(client): Harden quick cast against d..." | Re-trigger Greptile
Code Review by Qodo
1.
|
…angling state Addresses the review findings: - In hold to aim mode the key down pass executed every hotkey, not just the targeted commands it exists to arm - a production hotkey queued two units per press and a toggle undid itself. Non targeted commands now swallow the key down half and act on key up alone. - A queued cast whose remembered target has scrolled out of the frustum no longer falls back to the originally captured pixel, which the current camera would resolve to different terrain. It holds until the spot is back on screen, or expires. - A queued cast now requires its source to still be part of the current selection at fire time, since the synthesized click acts on the current selection - changing selection while waiting cancels rather than redirects. - The queue remembers the command by name and re-resolves it through the control bar at fire time; the retained pointer could dangle when a resolution change recreates the control bar's buttons. - The fading hint redraw recreates the radius cursor with the template and weapon slot it was made with, instead of nullptr and PRIMARY_WEAPON.
Adds an opt-in client option for how targeted commands (guard, attack move, abilities) are triggered from the keyboard. Defaults off; retail behavior unless set in Options.ini.
CastMode —
Normal(retail, default),QuickCast(hotkey fires immediately at the cursor),QuickCastWithIndicator(hold to aim with the targeting decal, release to fire).Quick cast synthesizes the same arm-and-click message flow a manual cast produces, so the engine's validation, voice responses and cleanup all apply — the logic side sees exactly what a mouse click would send, so there is no determinism risk in multiplayer or replays. Commands that must not fire blind decline into the normal two-step flow: single-use commands, rally points, beacons, construction placement, and superweapons.
A cast requested while the ability is still recharging is queued and fires the moment the logic side reports ready, pinned to the world position it was aimed at. Right click cancels the queue; it expires after a minute rather than firing out of nowhere. A button disabled only by
WIN_STATUS_NOT_READYlets the press through, so a still-firing weapon can be retargeted without a Stop first — matching the engine's own DragonTank firewall expectations documented in ControlBarCommand.cpp.Zero Hour only per the contribution guidelines: the indicator and queue live in Zero Hour's InGameUI, and the shared translator/command-bar hooks are guarded with
RTS_ZEROHOUR. Both titles compile. A Generals replica can follow after review.This option ships in the Contra mod's engine fork and has been played there extensively. One adaptation to be transparent about: the fork resolves the indicator decal through its own custom radius plumbing, which is not ported — here the decal comes from the command's
getRadiusCursorType()and the stock 3-argsetRadiusCursor, correct for retail content but not itself playtested. Code was written with LLM assistance and human-reviewed by the author.Prepared for Squash and Merge.