Skip to content

tweak(Controlbar): Allow replay observer to view contained units if not following player - #3122

Open
Mr-Sheerlock wants to merge 9 commits into
TheSuperHackers:mainfrom
Mr-Sheerlock:add-observer-inventory
Open

tweak(Controlbar): Allow replay observer to view contained units if not following player#3122
Mr-Sheerlock wants to merge 9 commits into
TheSuperHackers:mainfrom
Mr-Sheerlock:add-observer-inventory

Conversation

@Mr-Sheerlock

@Mr-Sheerlock Mr-Sheerlock commented Aug 13, 2026

Copy link
Copy Markdown

This PR is an enhancement/suggestion for observer Controlbar functionality. It allows an observer to view exact units contained by a building/unit.

I added the m_observerLookAtPlayer null check because otherwise when following a player and the player selects a containing building/unit the UI doesn't support a way to stop following the player.

Verification:
1: For replays:

  • Checked civilian buildings, technicals/battle busses, china bunkers, GLA multiple tunnels and palace in ZH and Generals.
  • Verified Evacuation & Stop Commands don't execute when pressing on any unit.
  • Tested buttons and hotkeys from observer point of view
  • Tested switching from observer to skirmish player

2: For multiplayer

  • Verified observers can't see any inventory (ally or enemy)
  • Verified no regressions regarding seeing an enemy team inventory as an active player

Potential issues:

  • Some buildings like barracks show slots despite not being garrison-able (probably because units can enter them anyway)

and also a limitation I just thought about: would be lovely if we can select a garrisoned unit and view its own garrisoned units too.

Edit:
Added more tests in the verification.

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

qodo-free-for-open-source-projects Bot commented Aug 13, 2026

Copy link
Copy Markdown

PR Summary by Qodo

Observer ControlBar: read-only structure inventory view when not following a player

✨ Enhancement 🐞 Bug fix 🕐 20-40 Minutes

Grey Divider

AI Description

• Enable observers to open structure inventory UI for containers when not following a player.
• Make observer command bar read-only, disabling inventory selection and evacuate/stop actions.
• Prevent multiplayer inventory peeking via neutrality check on apparent controlling player.
Diagram

graph TD
  cb["Observer ControlBar update"] --> obj[("Selected object")] --> has{"Contain slots?"} -->|"No"| olist["Observer list"]
  has -->|"Yes"| allow{"Not following & neutral?"} -->|"Yes"| inv["Inventory view (read-only)"]
  allow -->|"No"| deny["No inventory (return)"]

  subgraph Legend
    direction LR
    _proc["Process"] ~~~ _data[("Data")] ~~~ _dec{"Decision"}
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Explicit replay-only gate (game-mode based)
  • ➕ Simple mental model: observers can inspect inventory only in replay/single-player contexts
  • ➕ Avoids relying on relationship semantics that may vary by multiplayer observer implementation
  • ➖ Requires correct game-mode detection across replay/online/LAN cases
  • ➖ Less flexible if future spectator modes should allow partial visibility
2. Dedicated observer-inventory context (separate from Structure Inventory)
  • ➕ Cleaner separation: no risk of accidentally re-enabling commands in a shared context
  • ➕ Can tailor UI affordances (tooltips, highlighting) specifically for observers
  • ➖ More UI code and additional context wiring to maintain
  • ➖ Potential duplication of existing inventory rendering logic

Recommendation: Current approach (reuse Structure Inventory with a read-only flag + neutrality gating) is a good tradeoff: minimal UI duplication while preventing command execution. During review, double-check that the early-return on neutrality failure cannot leave a stale inventory context visible, and confirm all observer-entry paths set m_isReadOnly consistently (e.g., any observer scheme set via template vs player).

Files changed (3) +48 / -16

Enhancement (2) +46 / -14
ControlBar.hAdd read-only observer state and neutrality-check helper declaration +2/-0

Add read-only observer state and neutrality-check helper declaration

• Introduces a new helper API to evaluate neutrality against the apparent controlling player. Adds a ControlBar read-only flag used to prevent observers from issuing commands.

Core/GameEngine/Include/GameClient/ControlBar.h

ControlBar.cppObserver update flow now supports read-only inventory context with neutrality gating +44/-14

Observer update flow now supports read-only inventory context with neutrality gating

• Adds observer-mode logic to switch into structure-inventory context when selecting a container and not following a player. Centralizes relationship checks in isApparentControllingPlayerNeutral(), and marks observer control bars as read-only when loading observer schemes.

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

Bug fix (1) +2 / -2
ControlBarStructureInventory.cppDisable inventory interaction and evacuate/stop commands when control bar is read-only +2/-2

Disable inventory interaction and evacuate/stop commands when control bar is read-only

• Gates inventory button enablement behind m_isReadOnly to prevent observers from selecting/issuing exit actions. Ensures Evacuate/Stop are only enabled when not read-only and the container is non-empty.

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

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

qodo-free-for-open-source-projects Bot commented Aug 13, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Read-only state not reset ✓ Resolved 🐞 Bug ≡ Correctness
Description
m_isReadOnly is set to TRUE when switching to observer command bar via
setControlBarSchemeByPlayer(), but it is never set back to FALSE when switching back to an active
player, and it is never set to TRUE when observer mode is entered via
setControlBarSchemeByPlayerTemplate(). This can leave active players unable to use
structure-inventory actions (exit/evacuate/stop) or leave observer-template users with enabled
inventory controls.
Code

Core/GameEngine/Source/GameClient/GUI/ControlBar/ControlBar.cpp[R2799-2802]

if( !p->isPlayerActive() )
{
m_isObserverCommandBar = TRUE;
+		m_isReadOnly = TRUE;
Evidence
The PR makes inventory button enabling depend on m_isReadOnly, but m_isReadOnly is only set TRUE in
one observer-entry path and not cleared on exit or set in the observer-template path used for
inactive/defeated players, so it can be incorrect depending on the transition path.

Core/GameEngine/Source/GameClient/GUI/ControlBar/ControlBar.cpp[2787-2831]
Core/GameEngine/Source/GameClient/GUI/ControlBar/ControlBar.cpp[2845-2874]
Core/GameEngine/Source/GameClient/GUI/ControlBar/ControlBarStructureInventory.cpp[63-90]
Core/GameEngine/Source/GameClient/GUI/ControlBar/ControlBarStructureInventory.cpp[168-177]
Core/GameEngine/Source/Common/GameUtility.cpp[102-111]
Generals/Code/GameEngine/Source/Common/RTS/Player.cpp[1987-1997]

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

## Issue description
`m_isReadOnly` is introduced to disable inventory commands for observers, but it is only set to `TRUE` in `setControlBarSchemeByPlayer()` when the player is inactive. It is never cleared in the active-player path, and the observer-template path (`setControlBarSchemeByPlayerTemplate`) never sets it at all.
This leaves the flag in a stale/incorrect state depending on how observer mode is entered/exited.
## Issue Context
- Active/inactive transitions happen without necessarily calling `ControlBar::reset()` (e.g. `rts::changeLocalPlayer`).
- Defeated/inactive players explicitly use `setControlBarSchemeByPlayerTemplate(FactionObserver)`.
- Structure inventory buttons/evacuate/stop are now enabled/disabled based on `m_isReadOnly`.
## Fix Focus Areas
- Core/GameEngine/Source/GameClient/GUI/ControlBar/ControlBar.cpp[2787-2831]
- Core/GameEngine/Source/GameClient/GUI/ControlBar/ControlBar.cpp[2833-2876]
## Suggested fix
1. In `setControlBarSchemeByPlayer(Player* p)`, explicitly set `m_isReadOnly = TRUE` for inactive players and `m_isReadOnly = FALSE` for active players.
2. In `setControlBarSchemeByPlayerTemplate(const PlayerTemplate* pt)`, set `m_isReadOnly = TRUE` when `pt` is the observer template, and set `m_isReadOnly = FALSE` otherwise.
3. (Optional) Consider making `m_isReadOnly` derived from `m_isObserverCommandBar` (or consolidating state) to prevent future drift.

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


2. Inventory slots overflow ✓ Resolved 🐞 Bug ☼ Reliability
Description
ControlBar::update() now switches observers into CB_CONTEXT_STRUCTURE_INVENTORY for any selected
object with getContainMax() > 0, but the structure inventory UI only supports 10 occupant slots. If
a container ever has >10 contained objects (e.g., tunnel networks when MaxTunnelCapacity is
configured above 10), populateStructureInventory() will call populateButtonProc() past the supported
slot count, tripping the MAX_STRUCTURE_INVENTORY_BUTTONS assert and/or overwriting non-inventory
buttons.
Code

Core/GameEngine/Source/GameClient/GUI/ControlBar/ControlBar.cpp[R1483-1486]

+		ContainModuleInterface* observerContain = obj ? obj->getContain() : nullptr;
+		Bool showObserverInventory = (observerContain != nullptr && observerContain->getContainMax() > 0);
+
+		if (showObserverInventory && m_observerLookAtPlayer == nullptr)
Evidence
The new observer routing uses only getContainMax() > 0 to enter structure inventory, but the
structure inventory implementation asserts that it can only populate 10 occupant buttons and still
iterates over all contained objects. Tunnel networks can be configured to have capacities above 10
via GlobalData, making this path reachable for observers.

Core/GameEngine/Source/GameClient/GUI/ControlBar/ControlBar.cpp[1451-1499]
Core/GameEngine/Source/GameClient/GUI/ControlBar/ControlBarStructureInventory.cpp[63-91]
Core/GameEngine/Source/GameClient/GUI/ControlBar/ControlBarStructureInventory.cpp[179-196]
Generals/Code/GameEngine/Source/Common/RTS/TunnelTracker.cpp[107-112]
Generals/Code/GameEngine/Source/Common/GlobalData.cpp[395-400]

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

## Issue description
Observer mode now routes any selectable container (ContainMax > 0) into `CB_CONTEXT_STRUCTURE_INVENTORY`. The structure inventory UI is hard-limited to `MAX_STRUCTURE_INVENTORY_BUTTONS` (10). If `iterateContained()` yields more than 10 occupants, `populateButtonProc()` hits its `DEBUG_ASSERTCRASH` (and in non-assert builds can start repurposing the Stop/Evacuate buttons and potentially go beyond UI expectations).
### Issue Context
- `ControlBar::update()` (observer branch) uses only `getContainMax() > 0` as the gate.
- `populateStructureInventory()` iterates *all* contained objects and calls `populateButtonProc()`.
- `populateButtonProc()` asserts `buttonIndex < MAX_STRUCTURE_INVENTORY_BUTTONS`.
- Tunnel network capacity is configurable via `GlobalData::m_maxTunnelCapacity` (INI: `MaxTunnelCapacity`), so it can exceed 10.
### Fix Focus Areas
- Core/GameEngine/Source/GameClient/GUI/ControlBar/ControlBar.cpp[1483-1496]
- Core/GameEngine/Source/GameClient/GUI/ControlBar/ControlBarStructureInventory.cpp[63-90]
- Core/GameEngine/Source/GameClient/GUI/ControlBar/ControlBarStructureInventory.cpp[179-196]
### What to change
Implement *one* of these safe guards (preferably both A and B):
1. **A (UI-level hardening):** In `populateButtonProc()`, if `buttonIndex >= MAX_STRUCTURE_INVENTORY_BUTTONS`, return early (do not write into `m_containData` / do not enable controls). This prevents asserts/crashes and prevents Stop/Evacuate slots from being repurposed.
2. **B (observer routing guard):** In observer `update()`, only route to `CB_CONTEXT_STRUCTURE_INVENTORY` when `observerContain->getContainCount() <= MAX_STRUCTURE_INVENTORY_BUTTONS` (or clamp display to 10 with a clear rule). If count exceeds, fall back to `CB_CONTEXT_OBSERVER_LIST` or add paging/scrolling support.
Include an explicit comment explaining the 10-slot UI limitation so future changes to tunnel capacity don’t reintroduce the problem.

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



Remediation recommended

3. Observer inventory early return ✓ Resolved 🐞 Bug ≡ Correctness
Description
In observer-mode ControlBar::update(), selecting a container that fails
isApparentControllingPlayerNeutral(obj) returns immediately, bypassing the fallback that switches
the UI back to CB_CONTEXT_OBSERVER_LIST. This can leave stale structure-inventory UI visible after
selecting a disallowed container.
Code

Core/GameEngine/Source/GameClient/GUI/ControlBar/ControlBar.cpp[R1488-1492]

+		if (showObserverInventory && m_observerLookAtPlayer == nullptr)
+		{
+			if (!isApparentControllingPlayerNeutral(obj)) {
+				return;
+			}
Evidence
The new early return is placed inside the observer-only update path before the existing
observer-list fallback branch, so a failed neutral check can prevent the context from being
restored.

Core/GameEngine/Source/GameClient/GUI/ControlBar/ControlBar.cpp[1453-1504]

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

## Issue description
Observer-mode `ControlBar::update()` returns early when a selected container is not neutral. That return occurs before the code that restores `CB_CONTEXT_OBSERVER_LIST`, so the control bar can remain in a previous context (e.g., structure inventory) even though the current selection is disallowed.
## Issue Context
This is in the `if (m_isObserverCommandBar)` update path and only triggers for selected objects with a contain module.
## Fix Focus Areas
- Core/GameEngine/Source/GameClient/GUI/ControlBar/ControlBar.cpp[1485-1502]
## Suggested fix
Replace the early `return` with a controlled fallback:
- Either switch to `CB_CONTEXT_OBSERVER_LIST` (or `CB_CONTEXT_NONE`) before returning, or
- Restructure the logic so the existing `else if (m_currContext != CB_CONTEXT_OBSERVER_LIST)` branch remains reachable when the neutral check fails.

ⓘ 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 keep summaries lean with Finding overflow, which tucks the rest behind 'View more'

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread Core/GameEngine/Source/GameClient/GUI/ControlBar/ControlBar.cpp Outdated
if (showObserverInventory && m_observerLookAtPlayer == nullptr)
{
if (m_currContext != CB_CONTEXT_STRUCTURE_INVENTORY || m_currentSelectedDrawable != drawToEvaluateFor)
switchToContext(CB_CONTEXT_STRUCTURE_INVENTORY, drawToEvaluateFor);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Could the observer inventory be made read-only at the UI boundary? populateStructureInventory() enables the occupant, Evacuate, and Stop buttons, and assigning a real m_currentSelectedDrawable lets clicks reach processCommandUI() and emit MSG_EXIT, MSG_EVACUATE, or MSG_DO_STOP.
To be fair, this doesn't currently do anything, but coincidentally so - would be good to make that intentional. Qodo also commented about this

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Thank you for the review. After the new push, none of the buttons are enabled or can be pressed from the observer's POV.

@xezon

xezon commented Aug 15, 2026

Copy link
Copy Markdown

The buttons should not be clickable for an observer / non-owning player.

Please fix other issues when there are.

@Mr-Sheerlock
Mr-Sheerlock force-pushed the add-observer-inventory branch from f3f0ec5 to cdd3316 Compare August 17, 2026 04:03
@Mr-Sheerlock
Mr-Sheerlock force-pushed the add-observer-inventory branch from cdd3316 to fd01ff8 Compare August 17, 2026 11:25
@Mr-Sheerlock
Mr-Sheerlock marked this pull request as draft August 17, 2026 19:33
@Mr-Sheerlock
Mr-Sheerlock marked this pull request as ready for review August 17, 2026 23:00
@Mr-Sheerlock Mr-Sheerlock changed the title tweak(Controlbar): Allow observer to view contained units if not following player tweak(Controlbar): Allow replay observer to view contained units if not following player Aug 17, 2026
Comment thread Core/GameEngine/Source/GameClient/GUI/ControlBar/ControlBar.cpp Outdated
Comment thread Core/GameEngine/Source/GameClient/GUI/ControlBar/ControlBar.cpp Outdated
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 9660d40

@Mr-Sheerlock
Mr-Sheerlock force-pushed the add-observer-inventory branch from 9660d40 to 97e5f11 Compare August 17, 2026 23:22
@Mr-Sheerlock

Copy link
Copy Markdown
Author

Okay I think the PR should be ready for review.
I added neutrality checks to prevent any observer to see inventory during multiplayer.
The replay observer can't click any inventory buttons.

@Mr-Sheerlock

Copy link
Copy Markdown
Author

However I also found one detail regarding the Jarmen Kell and didn't find it anywhere in the issues.
You can't view the inventory of the building occupied by an enemy Jarmen (although the mouse hover suggests it can be garrisoned) which can be a tell sign in multiplayer. Idk if this should be discussed in an issue.

else
{
switchToContext( CB_CONTEXT_NONE, nullptr );
m_isObserverCommandBar = FALSE;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Should clear m_isReadOnly here too right?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Yes, thank you.
Fixed.

@Mr-Sheerlock
Mr-Sheerlock force-pushed the add-observer-inventory branch from 97e5f11 to 0d4cf8d Compare August 21, 2026 06:57

if (showObserverInventory && m_observerLookAtPlayer == nullptr)
{
if (!isApparentControllingPlayerNeutral(obj)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This return sits above the else if that restores CB_CONTEXT_OBSERVER_LIST, so a failed neutrality check leaves the previous container's inventory on screen. It's reachable for a defeated player who becomes an observer, though I don't think for a replay observer if that helps.

Maybe

Bool showObserverInventory = observerContain != nullptr
                     && observerContain->getContainMax() > 0
                     && m_observerLookAtPlayer == nullptr
                     && isApparentControllingPlayerNeutral(obj);
if (showObserverInventory)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed

showRallyPoint(exitPosition);

ContainModuleInterface* observerContain = obj ? obj->getContain() : nullptr;
Bool showObserverInventory = (observerContain != nullptr && observerContain->getContainMax() > 0);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Is including non-garrisonable containers intentional? If yes, the fixed 10-slot layout needs bounding (there's a bot comment on populateButtonProc). If not, matching the isGarrisonable() gate resolves both.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

No, I missed this case.

I will go with the bounding solution for generality.


// Enable the button
info->inventoryButtons[ info->buttonIndex ]->winEnable( TRUE );
info->inventoryButtons[ info->buttonIndex ]->winEnable( !info->self->m_isReadOnly );

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The DEBUG_ASSERTCRASH at line 68 is compiled out in release, so buttonIndex keeps incrementing past MAX_STRUCTURE_INVENTORY_BUTTONS (10).
An early return once buttonIndex >= MAX_STRUCTURE_INVENTORY_BUTTONS would make the limit enforced rather than asserted.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Thank you.
Fixed.

Color m_buildUpClockColor;

Bool m_isObserverCommandBar; ///< If this is true, the command bar behaves greatly different
Bool m_isReadOnly; ///< If this is true, the command bar will not allow any commands to be issued

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

m_isReadOnly tracks m_isObserverCommandBar - both are set at the same six sites, always to the same value. Could we have the two inventory call sites use m_isObserverCommandBar directly (or isObserverControlBarOn()) and drop this member?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Thank you.
Fixed

@xezon

xezon commented Sep 3, 2026

Copy link
Copy Markdown

Comments are left unaddressed. Needs push.

@Mr-Sheerlock

Copy link
Copy Markdown
Author

Will try to push asap

@Mr-Sheerlock
Mr-Sheerlock force-pushed the add-observer-inventory branch from 0d4cf8d to 1576210 Compare September 4, 2026 13:32
@greptile-apps

greptile-apps Bot commented Sep 4, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds read-only container inventory visibility for replay observers who are not following a player.

  • Selects an inventory or command context for eligible neutral containers.
  • Disables inventory, command, production-queue, Evacuate, and Stop interactions while the observer control bar is active.
  • Refactors neutral-controller checks into reusable ControlBar helpers.
  • Adds bounds protection when populating structure-inventory buttons.

Confidence Score: 5/5

The PR appears safe to merge, with one non-blocking visual-state cleanup needed when transitioning from observer mode to an active player.

The observer authorization path rejects non-neutral occupied containers, and the newly exposed controls are disabled. The remaining issue is limited to stale colored rendering on disabled Evacuate and Stop buttons after leaving observer mode.

Files Needing Attention: Core/GameEngine/Source/GameClient/GUI/ControlBar/ControlBarStructureInventory.cpp

Important Files Changed

Filename Overview
Core/GameEngine/Include/GameClient/ControlBar.h Declares neutral-controller helpers and the observer inventory population interface.
Core/GameEngine/Source/GameClient/GUI/ControlBar/ControlBar.cpp Selects observer inventory contexts and centralizes actual and apparent controller-neutrality checks.
Core/GameEngine/Source/GameClient/GUI/ControlBar/ControlBarCommand.cpp Makes command, transport-inventory, and production-queue controls non-interactive while preserving observer-visible coloring.
Core/GameEngine/Source/GameClient/GUI/ControlBar/ControlBarStructureInventory.cpp Makes structure inventory read-only for observers, but leaves observer-specific Evacuate and Stop rendering state set after observer mode ends.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[Replay observer selects object] --> B{Following a player?}
  B -->|Yes| C[Show observer list]
  B -->|No| D{Object has capacity and neutral team?}
  D -->|No| C
  D -->|Yes| E{Garrisonable with no command set?}
  E -->|Yes| F[Show read-only structure inventory]
  E -->|No| G[Show read-only command context]
Loading
Prompt To Fix All With AI
### Issue 1
Core/GameEngine/Source/GameClient/GUI/ControlBar/ControlBarStructureInventory.cpp:178-182
**Observer color state persists**

When replay observation sets `WIN_STATUS_ALWAYS_COLOR` on the Evacuate and Stop controls, the non-observer path never clears it. The same command windows are reused when switching to an active player, so disabled Evacuate and Stop controls keep the observer-specific colored rendering instead of their normal disabled appearance. This is a non-blocking visual inconsistency that can make their state less clear.

```suggestion
	if(isObserverControlBarOn())
	{
		m_commandWindows[ EVACUATE_ID ]->winSetStatus(WIN_STATUS_ALWAYS_COLOR);
		m_commandWindows[ STOP_ID ]->winSetStatus(WIN_STATUS_ALWAYS_COLOR);
	}
	else
	{
		m_commandWindows[ EVACUATE_ID ]->winClearStatus(WIN_STATUS_ALWAYS_COLOR);
		m_commandWindows[ STOP_ID ]->winClearStatus(WIN_STATUS_ALWAYS_COLOR);
	}
```

---

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

Reviews (5): Last reviewed commit: "Tweak(ControlBar): Show stop and evacuat..." | Re-trigger Greptile

@Mr-Sheerlock
Mr-Sheerlock force-pushed the add-observer-inventory branch from 1576210 to b8bc9c5 Compare September 4, 2026 13:49
@OmarAglan

Copy link
Copy Markdown

@codex

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b8bc9c5851

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +1484 to +1487
Bool showObserverInventory = observerContain != nullptr
&& observerContain->getContainMax() > 0
&& m_observerLookAtPlayer == nullptr
&& isApparentControllingPlayerNeutral(obj);

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 Badge Restrict free-observer inventories to replay playback

In a live multiplayer match, an observer who is not following a player can satisfy this condition for a civilian building occupied exclusively by undetected stealth units. GarrisonContain::getApparentControllingPlayer() deliberately reports the building's original neutral owner to non-allies in that case, so this check opens the inventory and reveals the hidden occupants' images and veterancy. Add an explicit replay/playback guard, or verify the actual controller before exposing the inventory, to preserve the stealth-garrison mechanic for live observers.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I think the only unit that triggers such behavior is Jarmen Kell (When it's the only unit garrisoned in building) whose place can be discovered using other cues.

You can't view the inventory of the building occupied by an enemy Jarmen

Otherwise all stealth units trigger building garrison apparent to other players too.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed

Comment on lines +1492 to +1493
if (m_currContext != CB_CONTEXT_STRUCTURE_INVENTORY || m_currentSelectedDrawable != drawToEvaluateFor)
switchToContext(CB_CONTEXT_STRUCTURE_INVENTORY, drawToEvaluateFor);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve multi-slot occupancy in observer transport inventories

When a replay transport carries a passenger whose TransportSlotCount is greater than one, this routes it through populateStructureInventory(), which displays getContainMax() slots without subtracting getExtraSlotsInUse(). The existing transport path in doTransportInventoryUI() explicitly performs that subtraction, so the observer view now shows too many empty slots and misrepresents the transport's remaining capacity. Use the transport inventory accounting here or hide the corresponding extra slots.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed

@Mr-Sheerlock
Mr-Sheerlock force-pushed the add-observer-inventory branch from 7219d2d to dde9ebc Compare September 4, 2026 19:47
Comment on lines +178 to +182
if(isObserverControlBarOn())
{
m_commandWindows[ EVACUATE_ID ]->winSetStatus(WIN_STATUS_ALWAYS_COLOR);
m_commandWindows[ STOP_ID ]->winSetStatus(WIN_STATUS_ALWAYS_COLOR);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Observer color state persists

When replay observation sets WIN_STATUS_ALWAYS_COLOR on the Evacuate and Stop controls, the non-observer path never clears it. The same command windows are reused when switching to an active player, so disabled Evacuate and Stop controls keep the observer-specific colored rendering instead of their normal disabled appearance. This is a non-blocking visual inconsistency that can make their state less clear.

Suggested change
if(isObserverControlBarOn())
{
m_commandWindows[ EVACUATE_ID ]->winSetStatus(WIN_STATUS_ALWAYS_COLOR);
m_commandWindows[ STOP_ID ]->winSetStatus(WIN_STATUS_ALWAYS_COLOR);
}
if(isObserverControlBarOn())
{
m_commandWindows[ EVACUATE_ID ]->winSetStatus(WIN_STATUS_ALWAYS_COLOR);
m_commandWindows[ STOP_ID ]->winSetStatus(WIN_STATUS_ALWAYS_COLOR);
}
else
{
m_commandWindows[ EVACUATE_ID ]->winClearStatus(WIN_STATUS_ALWAYS_COLOR);
m_commandWindows[ STOP_ID ]->winClearStatus(WIN_STATUS_ALWAYS_COLOR);
}
Prompt To Fix With AI
This is a comment left during a code review.
Path: Core/GameEngine/Source/GameClient/GUI/ControlBar/ControlBarStructureInventory.cpp
Line: 178-182

Comment:
**Observer color state persists**

When replay observation sets `WIN_STATUS_ALWAYS_COLOR` on the Evacuate and Stop controls, the non-observer path never clears it. The same command windows are reused when switching to an active player, so disabled Evacuate and Stop controls keep the observer-specific colored rendering instead of their normal disabled appearance. This is a non-blocking visual inconsistency that can make their state less clear.

```suggestion
	if(isObserverControlBarOn())
	{
		m_commandWindows[ EVACUATE_ID ]->winSetStatus(WIN_STATUS_ALWAYS_COLOR);
		m_commandWindows[ STOP_ID ]->winSetStatus(WIN_STATUS_ALWAYS_COLOR);
	}
	else
	{
		m_commandWindows[ EVACUATE_ID ]->winClearStatus(WIN_STATUS_ALWAYS_COLOR);
		m_commandWindows[ STOP_ID ]->winClearStatus(WIN_STATUS_ALWAYS_COLOR);
	}
```

---

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

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

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.

4 participants