feat(client): Add HealthBarDisplayMode, NumericalHealth and SmartPips options - #3214
feat(client): Add HealthBarDisplayMode, NumericalHealth and SmartPips options#3214triatomic wants to merge 5 commits into
Conversation
Adds an Options.ini client preference controlling when health bars appear: HealthBarDisplayMode = Classic ; selected and moused over only (default) HealthBarDisplayMode = Damaged ; the above, plus anything below full health HealthBarDisplayMode = Always ; the above, plus all undamaged units/structures A plain index (0, 1, 2) is also accepted. Default is Classic, so behavior is unchanged unless the option is set. The mode is read into GlobalData alongside the other Options.ini overrides, so it refreshes whenever game data is parsed rather than only at process start. Deliberately a separate setting rather than an extension of m_showObjectHealth, which stays the master switch and remains bound to the existing CHEAT_SHOW_HEALTH and DEMO_SHOW_HEALTH keys. Bars are suppressed for corpses, projectiles, shrubbery, trees, mines, inert, unattackable, drawable only and non selectable objects, so the wider modes do not label scenery. Always mode is further limited to structures and objects with an AI module, i.e. real combatants and buildings. Hidden, stealthed and shrouded objects are already filtered upstream in drawablePostDraw, so no mode can reveal something the local player cannot see. Entirely client side: the new code only reads state and draws, so it cannot affect game logic, multiplayer sync or replays.
PR Summary by QodoAdd configurable health bars, numerical health, and smart pips
AI Description
Diagram
High-Level Assessment
Files changed (10)
|
Code Review by Qodo
1.
|
|
| Filename | Overview |
|---|---|
| Core/GameEngine/Source/Common/OptionPreferences.cpp | Parses the new health-bar mode, numerical-health, and smart-pip preferences with retail-compatible defaults. |
| Core/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp | Handles the Zero Hour client-only event that cycles the active health-bar display mode. |
| Core/GameEngine/Source/GameClient/MessageStream/MetaEvent.cpp | Registers and binds the health-bar mode cycling command for gameplay and observer contexts. |
| GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp | Implements the display modes, numerical health rendering, smart pips, and the null-region guard that resolves the previously reported crash. |
| GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp | Releases the shared numerical-health display string before destroying its manager. |
Reviews (3): Last reviewed commit: "fix(client): Guard the ammo pip health r..." | Re-trigger Greptile
|
I also have a unit info scaling PR in the works that i need to get back to. This change touches a lot of code in that area as well and is far too noisy for what it is doing. likely due to the development being AI driven. |
I probably should have split these three into separate PRs |
HealthBarDisplayMode could only be changed by editing Options.ini and restarting. This cycles it in game -- Classic, Damaged, Always -- with an on screen line naming the mode it landed on. Bound through generateMetaMap rather than requiring a CommandMap.ini entry, the same way pause, fast forward and the fps controls default themselves. The binding only installs if the message is still unbound, so a mod that maps CYCLE_HEALTH_BAR_MODE itself still wins. This matters here because Contra ships its command map inside a .big, where a loose override file would replace every binding rather than merge. Ctrl+` is unbound in retail and in both the debug and demo command maps. The tick key is layout dependent -- it prints something other than ` outside a US keyboard -- but the binding is by scancode, so the same physical key works everywhere. Purely a client side display setting. Nothing reaches the simulation, so it is safe in multiplayer and replays. The value is not written back to Options.ini, so it lasts for the session and the configured mode returns on the next load.
Shows current and max hit points to the right of the bar, so exact values are readable without inferring them from bar length. Hooked at the end of drawHealthBar rather than as a parallel draw path, which means every rule that already decides whether a bar appears applies unchanged -- selection, mouseover, HealthBarDisplayMode and the dead and scenery exclusions. The number therefore shows exactly where a bar shows, in all three modes, with no duplicated visibility logic to drift out of step. It reuses the bar's own colour, so it carries the same red to green reading at a glance, including the blue to cyan variant for objects under construction or disabled, and the damaged tinting. Drawn small and unbold: in Always mode this lands over every unit on screen at once, so it has to annotate the bar rather than compete with it. A drop shadow rather than a backdrop plate, since this sits over the battlefield and not over a cameo. Values are rounded rather than truncated, so a unit with a sliver of health left does not read as 0. Options.ini: NumericalHealth = Yes
Both pip types normally appear only while a unit is selected or moused over, and only with the ShowObjectHealth debug flag on. SmartPips shows them whenever there is something to report, so remaining ammo and loaded transports read at a glance. Own units only -- not allies, not enemies -- since standing pips over units the player does not control would give away information the game otherwise withholds. Nothing is drawn when there is nothing to report. Passenger pips already bailed on an empty transport; ammo pips needed a new early out, placed before the style switch so every style behaves the same, because the default style deliberately draws empty boxes for spent shots. Drawing these every frame for every owned unit rather than only for a selected one exposed assumptions the container path was quietly making, so three latent faults are guarded here as well. The contained items list can hold a null and entries can be mid removal while a transport unloads. numFull comes from getContainerPipsToShow, which counts extra slots in use and may be redirected to another module entirely -- OverlordContain forwards it to its sub container -- while the list is always the outer container's own, so the two need not agree. And the pip images were dereferenced unguarded, where drawAmmo has always checked its own. Options.ini: SmartPips = Yes
…lth string Addresses both review findings: - drawAmmo dereferenced healthBarRegion unchecked. The selection test made a valid region implicit, but SmartPips draws without that test, so an owned unit whose health region cannot be computed crashed instead of skipping its pips. Checked outright now, matching drawContained. - The shared numerical health DisplayString stayed registered with TheDisplayStringManager through shutdown, asserting in debug builds and leaking in release. It is now file scoped with a teardown hook that GameClient's destructor runs right before deleting the manager - the existing killStaticImages runs after the manager is gone, so it could not take this job.
77fdead to
b05927f
Compare

Adds three opt-in client options for health bar and pip display. All default to retail behavior when unset in Options.ini.
HealthBarDisplayMode — controls when health bars appear:
Classic— selected and moused-over objects only (retail, default)Damaged— the above, plus anything below full healthAlways— the above, plus every undamaged unit and structureA meta-event (default Ctrl+Shift+`) cycles the mode in-game; the choice is not persisted, Options.ini remains the source of truth.
NumericalHealth (
= Yes) — prints hit points beside the health bar, following HealthBarDisplayMode so the number appears exactly where a bar does.SmartPips (
= Yes) — keeps ammo and passenger pips on screen whenever there is something to report, rather than only on selection/hover. Own units only — not allies, not enemies — so it reveals nothing the game otherwise withholds. Drawing pips every frame for every owned unit exposed three latent faults in the container pip path (null entries in the contained list, numFull/list disagreement via OverlordContain, unguarded pip image dereference), which are guarded as part of the change.The in-game features are Zero Hour only per the contribution guidelines (the shared meta-event handler and key binding are guarded with
RTS_ZEROHOUR; both titles compile). A Generals replica can follow after review.These options ship in the Contra mod's engine fork and have been played there; this PR is the port onto current main. Code was written with LLM assistance and human-reviewed, adapted and playtested by the author.
Prepared for Squash and Merge.