From d63b811cb7c7d67ee9327492e0113397cb950475 Mon Sep 17 00:00:00 2001 From: Joseph Ameer Aziz Date: Thu, 13 Aug 2026 07:28:54 +0300 Subject: [PATCH 1/3] tweak(Controlbar): Allow observer to view contained units if not following player --- .../GameClient/GUI/ControlBar/ControlBar.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Core/GameEngine/Source/GameClient/GUI/ControlBar/ControlBar.cpp b/Core/GameEngine/Source/GameClient/GUI/ControlBar/ControlBar.cpp index 697ee47a0a4..f3a8a920fc2 100644 --- a/Core/GameEngine/Source/GameClient/GUI/ControlBar/ControlBar.cpp +++ b/Core/GameEngine/Source/GameClient/GUI/ControlBar/ControlBar.cpp @@ -1479,6 +1479,22 @@ void ControlBar::update() exitPosition = obj->getObjectExitInterface()->getRallyPoint(); showRallyPoint(exitPosition); + + ContainModuleInterface* observerContain = obj ? obj->getContain() : nullptr; + Bool showObserverInventory = (observerContain != nullptr && observerContain->getContainMax() > 0); + + if (showObserverInventory && m_observerLookAtPlayer == nullptr) + { + if (m_currContext != CB_CONTEXT_STRUCTURE_INVENTORY || m_currentSelectedDrawable != drawToEvaluateFor) + switchToContext(CB_CONTEXT_STRUCTURE_INVENTORY, drawToEvaluateFor); + else + updateContextStructureInventory(); + } + else if (m_currContext != CB_CONTEXT_OBSERVER_LIST) + { + switchToContext(CB_CONTEXT_OBSERVER_LIST, nullptr); + } + return; } From fd01ff8e7b18c9d4fd28df091cbe703b342d6506 Mon Sep 17 00:00:00 2001 From: Joseph Ameer Aziz Date: Mon, 17 Aug 2026 06:33:25 +0300 Subject: [PATCH 2/3] tweak(Controlbar): Disable control and unit buttons for observer --- Core/GameEngine/Include/GameClient/ControlBar.h | 1 + .../Source/GameClient/GUI/ControlBar/ControlBar.cpp | 3 +++ .../GUI/ControlBar/ControlBarStructureInventory.cpp | 4 ++-- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Core/GameEngine/Include/GameClient/ControlBar.h b/Core/GameEngine/Include/GameClient/ControlBar.h index 446277a13be..f1fcfbc038f 100644 --- a/Core/GameEngine/Include/GameClient/ControlBar.h +++ b/Core/GameEngine/Include/GameClient/ControlBar.h @@ -975,6 +975,7 @@ class ControlBar : public SubsystemInterface 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 Player *m_observerLookAtPlayer; ///< The current player we're looking at, Null if we're not looking at anyone. Player *m_observedPlayer; ///< The current player we're observing, Null if we're not observing anyone. diff --git a/Core/GameEngine/Source/GameClient/GUI/ControlBar/ControlBar.cpp b/Core/GameEngine/Source/GameClient/GUI/ControlBar/ControlBar.cpp index f3a8a920fc2..26c51aa5af2 100644 --- a/Core/GameEngine/Source/GameClient/GUI/ControlBar/ControlBar.cpp +++ b/Core/GameEngine/Source/GameClient/GUI/ControlBar/ControlBar.cpp @@ -884,6 +884,7 @@ ControlBar::ControlBar() m_commandSets = nullptr; m_controlBarSchemeManager = nullptr; m_isObserverCommandBar = FALSE; + m_isReadOnly = FALSE; m_observerLookAtPlayer = nullptr; m_observedPlayer = nullptr; m_buildToolTipLayout = nullptr; @@ -1321,6 +1322,7 @@ void ControlBar::reset() m_displayedOCLTimerSeconds = 0; m_isObserverCommandBar = FALSE; // reset us to use a normal command bar + m_isReadOnly = FALSE; m_observerLookAtPlayer = nullptr; m_observedPlayer = nullptr; @@ -2805,6 +2807,7 @@ void ControlBar::setControlBarSchemeByPlayer(Player *p) if( !p->isPlayerActive() ) { m_isObserverCommandBar = TRUE; + m_isReadOnly = TRUE; switchToContext( CB_CONTEXT_OBSERVER_LIST, nullptr ); DEBUG_LOG(("We're loading the Observer Command Bar")); diff --git a/Core/GameEngine/Source/GameClient/GUI/ControlBar/ControlBarStructureInventory.cpp b/Core/GameEngine/Source/GameClient/GUI/ControlBar/ControlBarStructureInventory.cpp index 0bde39bc6f4..ebbbf7d7702 100644 --- a/Core/GameEngine/Source/GameClient/GUI/ControlBar/ControlBarStructureInventory.cpp +++ b/Core/GameEngine/Source/GameClient/GUI/ControlBar/ControlBarStructureInventory.cpp @@ -83,7 +83,7 @@ void ControlBar::populateButtonProc( Object *obj, void *userData ) GadgetButtonDrawOverlayImage( info->inventoryButtons[ info->buttonIndex ], image ); // Enable the button - info->inventoryButtons[ info->buttonIndex ]->winEnable( TRUE ); + info->inventoryButtons[ info->buttonIndex ]->winEnable( !info->self->m_isReadOnly ); // move to the next button index info->buttonIndex++; @@ -170,7 +170,7 @@ void ControlBar::populateStructureInventory( Object *building ) m_commandWindows[ STOP_ID ]->winHide( FALSE ); // if there is at least one item in there enable the evacuate and stop buttons - if( contain->getContainCount() != 0 ) + if(!m_isReadOnly && contain->getContainCount() != 0 ) { m_commandWindows[ EVACUATE_ID ]->winEnable( TRUE ); m_commandWindows[ STOP_ID ]->winEnable( TRUE ); From 0d4cf8ddd858aabd4f7b283cb78307b4a030d589 Mon Sep 17 00:00:00 2001 From: Joseph Ameer Aziz Date: Mon, 17 Aug 2026 23:33:49 +0300 Subject: [PATCH 3/3] tweak(Controlbar): Add neutrality check to prevent observers from checking inventory during multiplayer --- .../Include/GameClient/ControlBar.h | 1 + .../GameClient/GUI/ControlBar/ControlBar.cpp | 42 ++++++++++++------- 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/Core/GameEngine/Include/GameClient/ControlBar.h b/Core/GameEngine/Include/GameClient/ControlBar.h index f1fcfbc038f..658f997b6b1 100644 --- a/Core/GameEngine/Include/GameClient/ControlBar.h +++ b/Core/GameEngine/Include/GameClient/ControlBar.h @@ -702,6 +702,7 @@ class ControlBar : public SubsystemInterface Bool hasAnyShortcutSelection() const; Bool canShowSpecialPowerShortcut() const; + Bool isApparentControllingPlayerNeutral(const Object* obj) const; void showSpecialPowerShortcut(); void hideSpecialPowerShortcut(); void animateSpecialPowerShortcut( Bool isOn ); diff --git a/Core/GameEngine/Source/GameClient/GUI/ControlBar/ControlBar.cpp b/Core/GameEngine/Source/GameClient/GUI/ControlBar/ControlBar.cpp index 26c51aa5af2..7ef6b958cee 100644 --- a/Core/GameEngine/Source/GameClient/GUI/ControlBar/ControlBar.cpp +++ b/Core/GameEngine/Source/GameClient/GUI/ControlBar/ControlBar.cpp @@ -1487,6 +1487,10 @@ void ControlBar::update() if (showObserverInventory && m_observerLookAtPlayer == nullptr) { + if (!isApparentControllingPlayerNeutral(obj)) { + return; + } + if (m_currContext != CB_CONTEXT_STRUCTURE_INVENTORY || m_currentSelectedDrawable != drawToEvaluateFor) switchToContext(CB_CONTEXT_STRUCTURE_INVENTORY, drawToEvaluateFor); else @@ -1823,24 +1827,12 @@ void ControlBar::evaluateContextUI() ContainModuleInterface *contain = obj->getContain(); if( contain && contain->getContainMax() > 0 ) { - - const Player *otherPlayer = contain->getApparentControllingPlayer(ThePlayerList->getLocalPlayer()); - if (!otherPlayer) - otherPlayer = obj->getControllingPlayer(); - Player *player = ThePlayerList->getLocalPlayer(); - - if( !player || !otherPlayer ) - { - //Sanity. - return; - } - Relationship relation = player->getRelationship( otherPlayer->getDefaultTeam() ); - + Bool apparentControllingPlayerNeutral = isApparentControllingPlayerNeutral(obj); //Note: All following checks already account for the fact that this object //isn't ours. //The only case we can actually see a non-controlled controlbar is a neutral garrisonable structure. - if( !contain->isGarrisonable() || relation != NEUTRAL ) + if( !contain->isGarrisonable() || !apparentControllingPlayerNeutral) { //Can't peek inside enemy/allied containers period! return; @@ -2822,6 +2814,7 @@ void ControlBar::setControlBarSchemeByPlayer(Player *p) { switchToContext( CB_CONTEXT_NONE, nullptr ); m_isObserverCommandBar = FALSE; + m_isReadOnly = FALSE; if (buttonPlaceBeacon) buttonPlaceBeacon->winHide( @@ -2853,6 +2846,7 @@ void ControlBar::setControlBarSchemeByPlayerTemplate( const PlayerTemplate *pt) if(pt == ThePlayerTemplateStore->findPlayerTemplate(TheNameKeyGenerator->nameToKey("FactionObserver"))) { m_isObserverCommandBar = TRUE; + m_isReadOnly = TRUE; switchToContext( CB_CONTEXT_OBSERVER_LIST, nullptr ); DEBUG_LOG(("We're loading the Observer Command Bar")); @@ -2867,6 +2861,7 @@ void ControlBar::setControlBarSchemeByPlayerTemplate( const PlayerTemplate *pt) { switchToContext( CB_CONTEXT_NONE, nullptr ); m_isObserverCommandBar = FALSE; + m_isReadOnly = FALSE; if (buttonPlaceBeacon) buttonPlaceBeacon->winHide( @@ -3601,6 +3596,25 @@ Bool ControlBar::canShowSpecialPowerShortcut() const return false; } +//------------------------------------------------------------------------------------------------- +Bool ControlBar::isApparentControllingPlayerNeutral(const Object* obj) const +{ + ContainModuleInterface* contain = obj->getContain(); + const Player* otherPlayer = contain->getApparentControllingPlayer(ThePlayerList->getLocalPlayer()); + if (!otherPlayer) + otherPlayer = obj->getControllingPlayer(); + const Player* player = ThePlayerList->getLocalPlayer(); + + if (!player || !otherPlayer) + { + //Sanity. + return FALSE; + } + + Relationship relation = player->getRelationship(otherPlayer->getDefaultTeam()); + return relation == NEUTRAL; +} + //------------------------------------------------------------------------------------------------- void ControlBar::updateSpecialPowerShortcut() {