Skip to content

feat(client): Add BuildTimerDisplayMode option for build and recharge countdowns - #3217

Open
triatomic wants to merge 3 commits into
TheSuperHackers:mainfrom
triatomic:qol/build-timer
Open

feat(client): Add BuildTimerDisplayMode option for build and recharge countdowns#3217
triatomic wants to merge 3 commits into
TheSuperHackers:mainfrom
triatomic:qol/build-timer

Conversation

@triatomic

Copy link
Copy Markdown

Adds an opt-in client option drawing remaining build/recharge times on the command bar, plus the matching time in the build tooltip. Defaults off; retail behavior unless set in Options.ini.

BuildTimerDisplayModeNone (retail, default), Seconds (plain seconds), Auto (seconds under a minute, M:SS above). Covers the head production queue slot (units and upgrades), special power recharge, and the build tooltip, where the time joins the cost line after a watch glyph — shown for free items too, which take time to build but have no cost line of their own.

Unit times come from calcTimeToBuild, so the tooltip and countdown track the player's energy penalty as power changes. Tooltip times round to nearest in integer math (a fractional BuildTime reads as the number the modder typed); live countdowns ceil (a running timer must not show 0 with work left).

The countdown's display string is returned to the manager by the W3DDisplayStringManager destructor before teardown, same pattern as #3216.

Works in both Zero Hour and Generals — the drawing lives in the shared gadget and W3D push button code.

These options ship in the Contra mod's engine fork and have been played there; this PR is the port onto current main with the fork's three follow-up fixes folded in. Code was written with LLM assistance and human-reviewed, adapted and playtested by the author.

Prepared for Squash and Merge.

…on cameos

Draws the remaining time over build queue and special power cameos, next
to the existing clock sweep:

  BuildTimerDisplayMode = None      ; retail, default
  BuildTimerDisplayMode = Seconds   ; always plain seconds
  BuildTimerDisplayMode = Auto      ; seconds under a minute, M:SS above

Covers the head production queue slot (units and upgrades) and special
power recharge. Times use an integer frame ceiling so the number never
reads one second short.

The shared display string is returned to the manager by the
W3DDisplayStringManager destructor before teardown.

Available in both Zero Hour and Generals: the drawing lives in the
shared gadget and W3D push button code.
Formats the build time with the same Auto/Seconds rules as the cameo
countdowns and appends it to the tooltip's cost line, separated by a
watch glyph - the description window sits directly beneath the cost line
in the .wnd layout, so a second line would overlap it. Shown for free
items too, which have no cost of their own but still take time to build.

Unit times come from calcTimeToBuild, which folds in the current energy
penalty, so the tooltip tracks the player's power state; upgrades carry
no penalty in the engine. Tooltip times round to nearest in integer math
so a fractional BuildTime reads as the number the modder typed, while
the live countdowns keep ceiling - a running timer must not show 0 with
work left.

Follows BuildTimerDisplayMode: with the option at None the tooltip stays
retail.
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Add configurable build and recharge timer displays

✨ Enhancement ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Adds opt-in seconds or adaptive countdowns for production queues and special-power recharge.
• Shows energy-aware build times beside tooltip costs, including free units and upgrades.
• Supports Generals and Zero Hour while preserving retail behavior by default.
Diagram

graph TD
  OPTIONS["Options.ini"] -->|read| PREF["Option Preferences"] -->|resolve mode| GLOBAL["Global Data"] -->|configure| CONTROL["Control Bar"] -->|remaining seconds| GADGET["Push Button"] -->|one-shot state| RENDER["W3D Renderer"] -->|format and draw| STRINGS["Display Strings"]
  CONTROL -->|build time| TOOLTIP["Build Tooltip"]
Loading
High-Level Assessment

The PR follows the existing one-shot clock-drawing pattern, uses authoritative engine timing APIs, and keeps the feature disabled by default. Per-button display-string allocation and a separate timer subsystem were considered, but would add resource churn and unnecessary architecture for presentation-only state; the shared string with explicit manager-owned teardown is the better fit.

Files changed (15) +299 / -2

Enhancement (7) +240 / -2
Gadget.hStore one-shot button countdown state +4/-0

Store one-shot button countdown state

• Extends push-button data with remaining seconds. Negative values suppress rendering, and drawn values are cleared each frame like clock state.

Core/GameEngine/Include/GameClient/Gadget.h

GadgetPushButton.hExpose countdown drawing API +2/-0

Expose countdown drawing API

• Declares the shared gadget function used to schedule a remaining-time overlay on a push button.

Core/GameEngine/Include/GameClient/GadgetPushButton.h

ControlBarCommand.cppCalculate live build and recharge countdowns +38/-0

Calculate live build and recharge countdowns

• Computes ceiling-rounded seconds for the head production queue entry and special-power readiness. Unit and upgrade durations use their engine build-time calculations before passing one-shot values to command buttons.

Core/GameEngine/Source/GameClient/GUI/ControlBar/ControlBarCommand.cpp

ControlBarPopupDescription.cppAppend build durations to tooltip costs +60/-2

Append build durations to tooltip costs

• Formats build times according to the selected display mode and appends them after a watch glyph on the cost line. Unit values reflect current energy penalties, and free items still receive a time-only line.

Core/GameEngine/Source/GameClient/GUI/GUICallbacks/ControlBarPopupDescription.cpp

GadgetPushButton.cppInitialize and set button countdown values +24/-0

Initialize and set button countdown values

• Initializes countdown state as hidden and implements the one-shot setter used by control-bar timing logic.

Core/GameEngine/Source/GameClient/GUI/Gadget/GadgetPushButton.cpp

W3DGadget.hExpose countdown display-string cleanup +3/-0

Expose countdown display-string cleanup

• Declares the W3D cleanup hook that returns the shared countdown string before display-string manager teardown.

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

W3DPushButton.cppRender formatted countdown overlays +109/-0

Render formatted countdown overlays

• Creates a shared localized-font display string, caches formatted Seconds or Auto text, and draws it over button clock sweeps with a translucent background. Both push-button drawing paths consume and clear the one-shot countdown value.

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

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

Release Generals countdown string during teardown

• Returns the shared countdown display string while its manager is still alive, preventing registered-string assertions during destruction.

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

W3DDisplayStringManager.cppRelease Zero Hour countdown string during teardown +7/-0

Release Zero Hour countdown string during teardown

• Returns the shared countdown display string while its manager is still alive, preventing registered-string assertions during destruction.

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

Other (6) +45 / -0
OptionPreferences.hDefine build timer display modes +13/-0

Define build timer display modes

• Adds None, Seconds, and Auto display modes with retail behavior as the default. Exposes a typed preference accessor for client timer configuration.

Core/GameEngine/Include/Common/OptionPreferences.h

OptionPreferences.cppParse BuildTimerDisplayMode from Options.ini +22/-0

Parse BuildTimerDisplayMode from Options.ini

• Reads None, Seconds, Auto, or valid numeric mode values. Missing and invalid settings safely fall back to the disabled retail default.

Core/GameEngine/Source/Common/OptionPreferences.cpp

GlobalData.hStore Generals timer display preference +3/-0

Store Generals timer display preference

• Adds client global state for the selected build timer mode without coupling GlobalData to the preference header.

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

GlobalData.cppLoad Generals timer display mode +2/-0

Load Generals timer display mode

• Initializes the mode to the retail-compatible default and copies the parsed Options.ini preference into writable global data.

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

GlobalData.hStore Zero Hour timer display preference +3/-0

Store Zero Hour timer display preference

• Adds client global state for the selected build timer mode without coupling GlobalData to the preference header.

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

GlobalData.cppLoad Zero Hour timer display mode +2/-0

Load Zero Hour timer display mode

• Initializes the mode to the retail-compatible default and copies the parsed Options.ini preference into writable global data.

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

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

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Remediation recommended

1. Build time stays stale 🐞 Bug ≡ Correctness
Description
The unit tooltip computes a power-sensitive duration once, but power production/consumption changes
do not mark the control bar dirty, so an already-open tooltip keeps showing the old normal or
low-power build time. This contradicts the feature's intended behavior of tracking the player's
energy penalty as power changes.
Code

Core/GameEngine/Source/GameClient/GUI/GUICallbacks/ControlBarPopupDescription.cpp[R454-456]

+			// TheSuperHackers @feature calcTimeToBuild folds in the current energy penalty,
+			// so this tracks the player's power state as it changes.
+			buildTimeText = formatBuildTimeForTooltip( thingTemplate->calcTimeToBuild( player ) );
Evidence
The changed tooltip code explicitly reads the current power-adjusted build time, while tooltip
rebuilding is gated by m_UIDirty; both game variants' power-change handlers update power effects
without marking that UI dirty.

Core/GameEngine/Source/GameClient/GUI/GUICallbacks/ControlBarPopupDescription.cpp[268-276]
Core/GameEngine/Source/GameClient/GUI/GUICallbacks/ControlBarPopupDescription.cpp[454-456]
Core/GameEngine/Source/GameClient/GUI/ControlBar/ControlBar.cpp[1523-1533]
Generals/Code/GameEngine/Source/Common/RTS/Energy.cpp[212-233]
Generals/Code/GameEngine/Source/Common/RTS/Player.cpp[3101-3110]
GeneralsMD/Code/GameEngine/Source/Common/RTS/Energy.cpp[231-251]
GeneralsMD/Code/GameEngine/Source/Common/RTS/Player.cpp[3295-3304]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The build tooltip now derives unit build time from the player's current power state, but it is not repopulated when that state changes, leaving a visible tooltip stale.

## Issue Context
`repopulateBuildTooltipLayout()` runs from the control bar's dirty-update path. Energy changes invoke `Player::onPowerBrownOutChange()`, which updates object disabled states but does not dirty the control bar.

## Fix Focus Areas
- Core/GameEngine/Source/GameClient/GUI/GUICallbacks/ControlBarPopupDescription.cpp[454-456]
- Core/GameEngine/Source/GameClient/GUI/ControlBar/ControlBar.cpp[1527-1533]
- Generals/Code/GameEngine/Source/Common/RTS/Player.cpp[3101-3110]
- GeneralsMD/Code/GameEngine/Source/Common/RTS/Player.cpp[3295-3304]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


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

Adds an opt-in build and recharge timer display shared by Generals and Zero Hour.

  • Parses BuildTimerDisplayMode from client options and propagates it through variant global data.
  • Draws live production and special-power countdowns over command buttons.
  • Adds build times to tooltips and periodically refreshes them for changing power conditions.
  • Manages the shared W3D countdown display string during renderer teardown.

Confidence Score: 5/5

The PR appears safe to merge because no blocking failure remains in the eligible follow-up review scope.

No blocking failure remains.

Important Files Changed

Filename Overview
Core/GameEngine/Source/GameClient/GUI/ControlBar/ControlBarCommand.cpp Computes live production and special-power countdown values and forwards them to command-button rendering.
Core/GameEngine/Source/GameClient/GUI/GUICallbacks/ControlBarPopupDescription.cpp Formats build times, appends them to tooltip cost text, and refreshes visible tooltips periodically.
Core/GameEngineDevice/Source/W3DDevice/GameClient/GUI/Gadget/W3DPushButton.cpp Creates, formats, draws, and releases the shared countdown display string.
Core/GameEngine/Source/Common/OptionPreferences.cpp Parses named and numeric values for the new client display mode with a disabled default.
Generals/Code/GameEngine/Source/Common/GlobalData.cpp Initializes and loads the Generals variant’s timer display preference.
GeneralsMD/Code/GameEngine/Source/Common/GlobalData.cpp Initializes and loads the Zero Hour variant’s timer display preference.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  Options[Options.ini] --> Preferences[OptionPreferences]
  Preferences --> GlobalData[Variant GlobalData]
  GlobalData --> ControlBar[Control bar timing]
  Production[Production state] --> ControlBar
  Recharge[Special-power state] --> ControlBar
  ControlBar --> Gadget[Push-button countdown]
  Gadget --> W3D[W3D display string]
  GlobalData --> Tooltip[Build tooltip]
  BuildData[Unit or upgrade build time] --> Tooltip
Loading

Reviews (2): Last reviewed commit: "fix(client): Refresh the open build tool..." | Re-trigger Greptile

…changes

The unit build time folds in the player's energy penalty, but nothing
marks the control bar dirty when power production or consumption
changes, so an already open tooltip kept showing the old time. The
tooltip's own update now repopulates it about once a second while
visible - only when the timer display is on, so retail stays as cheap
as before.
@triatomic

Copy link
Copy Markdown
Author
image

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