diff --git a/Generals/Code/GameEngine/Include/GameClient/ControlBar.h b/Generals/Code/GameEngine/Include/GameClient/ControlBar.h index c8b686550b..6a3cd7771f 100644 --- a/Generals/Code/GameEngine/Include/GameClient/ControlBar.h +++ b/Generals/Code/GameEngine/Include/GameClient/ControlBar.h @@ -722,6 +722,7 @@ class ControlBar : public SubsystemInterface void initObserverControls( void ); void populateObserverInfoWindow ( void ); void populateObserverList( void ); + void refreshObserverUI(); Bool isObserverControlBarOn( void ) { return m_isObserverCommandBar;} void setObserverLookAtPlayer (Player *player); ///< Sets the looked at player. Used to present information about the player. diff --git a/Generals/Code/GameEngine/Source/GameClient/GUI/ControlBar/ControlBar.cpp b/Generals/Code/GameEngine/Source/GameClient/GUI/ControlBar/ControlBar.cpp index 068ef53773..a26baf2ef5 100644 --- a/Generals/Code/GameEngine/Source/GameClient/GUI/ControlBar/ControlBar.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/GUI/ControlBar/ControlBar.cpp @@ -1432,7 +1432,7 @@ void ControlBar::update( void ) if( m_isObserverCommandBar) { if((TheGameLogic->getFrame() % (LOGICFRAMES_PER_SECOND/2)) == 0) - populateObserverInfoWindow(); + refreshObserverUI(); Drawable *drawToEvaluateFor = NULL; if( TheInGameUI->getSelectCount() > 1 ) diff --git a/Generals/Code/GameEngine/Source/GameClient/GUI/ControlBar/ControlBarObserver.cpp b/Generals/Code/GameEngine/Source/GameClient/GUI/ControlBar/ControlBarObserver.cpp index f9060a8639..d03d336a62 100644 --- a/Generals/Code/GameEngine/Source/GameClient/GUI/ControlBar/ControlBarObserver.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/GUI/ControlBar/ControlBarObserver.cpp @@ -99,6 +99,187 @@ static GameWindow *staticTextNumberOfUnitsLost = NULL; static GameWindow *staticTextPlayerName = NULL; static NameKeyType s_replayObserverNameKey = NAMEKEY_INVALID; +static Int s_playerNameOriginalWidth = 0; +static Int s_playerNameOriginalHeight = 0; +static Bool s_playerNameSizeCaptured = FALSE; + +static void ensurePlayerNameLabelHeight(Bool needsTwoLines) +{ + if (!staticTextPlayerName) + { + return; + } + + if (!s_playerNameSizeCaptured) + { + staticTextPlayerName->winGetSize(&s_playerNameOriginalWidth, &s_playerNameOriginalHeight); + s_playerNameSizeCaptured = TRUE; + } + + if (s_playerNameOriginalHeight <= 0) + { + return; + } + + Int currentWidth = 0; + Int currentHeight = 0; + staticTextPlayerName->winGetSize(¤tWidth, ¤tHeight); + + const Int targetHeight = needsTwoLines ? s_playerNameOriginalHeight * 2 : s_playerNameOriginalHeight; + if (currentHeight != targetHeight) + { + staticTextPlayerName->winSetSize(currentWidth, targetHeight); + } +} + +static AsciiString formatCompactCash(UnsignedInt cash) +{ + AsciiString cashText; + + if (cash >= 1000000) + { + if ((cash % 1000000) == 0) + { + cashText.format("$%uM", cash / 1000000); + } + else + { + const float millions = static_cast(cash) / 1000000.0f; + cashText.format("$%.1fM", millions); + } + } + else if (cash >= 1000) + { + if ((cash % 1000) == 0) + { + cashText.format("$%uK", cash / 1000); + } + else + { + const float thousands = static_cast(cash) / 1000.0f; + cashText.format("$%.1fK", thousands); + } + } + else + { + cashText.format("$%u", cash); + } + + return cashText; +} + +// TheSuperHackers @feature GitHubCopilot 01/11/2025 Surface cash/rank/xp summary for observer overlays. +static UnicodeString formatObserverPlayerSummary(const Player *player) +{ + UnicodeString summary; + + if (!player) + { + return summary; + } + + UnsignedInt cash = 0; + if (player->getMoney()) + { + cash = player->getMoney()->countMoney(); + } + + const Int rankLevel = player->getRankLevel(); + const Int totalSkillPoints = player->getSkillPoints(); + const Int levelFloor = player->getSkillPointsLevelDown(); + const Int levelCeiling = player->getSkillPointsLevelUp(); + + Int xpProgress = totalSkillPoints - levelFloor; + if (xpProgress < 0) + { + xpProgress = 0; + } + + Int xpSpan = levelCeiling - levelFloor; + if (xpSpan < 0) + { + xpSpan = 0; + } + + Int xpPercent = 100; + if (xpSpan > 0) + { + xpPercent = (xpProgress * 100) / xpSpan; + if (xpPercent > 100) + { + xpPercent = 100; + } + } + + AsciiString asciiSummary = formatCompactCash(cash); + + AsciiString rankToken; + rankToken.format(" R%d", rankLevel); + asciiSummary.concat(rankToken); + + AsciiString xpToken; + if (xpSpan > 0) + { + xpToken.format(" XP %d%%", xpPercent); + } + else + { + xpToken.format(" XP %d", totalSkillPoints); + } + asciiSummary.concat(xpToken); + + summary.translate(asciiSummary); + return summary; +} + +static void appendSummaryToSecondLine(UnicodeString &text, const UnicodeString &summary) +{ + if (summary.isEmpty()) + { + return; + } + + UnicodeString firstLine; + UnicodeString secondLine; + Bool onSecondLine = FALSE; + + const Int length = text.getLength(); + for (Int index = 0; index < length; ++index) + { + const WideChar ch = text.getCharAt(index); + if (!onSecondLine) + { + firstLine.concat(ch); + if (ch == L'\n') + { + onSecondLine = TRUE; + } + } + else + { + secondLine.concat(ch); + } + } + + if (onSecondLine) + { + if (!secondLine.isEmpty()) + { + secondLine.concat(L" "); + } + secondLine.concat(summary); + text = firstLine; + text.concat(secondLine); + } + else + { + if (!text.isEmpty()) + { + text.concat(L"\n"); + } + text.concat(summary); + } +} //----------------------------------------------------------------------------- // PUBLIC FUNCTIONS /////////////////////////////////////////////////////////// @@ -126,6 +307,11 @@ void ControlBar::initObserverControls( void ) staticTextNumberOfUnitsKilled = TheWindowManager->winGetWindowFromId(NULL, TheNameKeyGenerator->nameToKey("ControlBar.wnd:StaticTextNumberOfUnitsKilled")); staticTextNumberOfUnitsLost = TheWindowManager->winGetWindowFromId(NULL, TheNameKeyGenerator->nameToKey("ControlBar.wnd:StaticTextNumberOfUnitsLost")); staticTextPlayerName = TheWindowManager->winGetWindowFromId(NULL, TheNameKeyGenerator->nameToKey("ControlBar.wnd:StaticTextPlayerName")); + if (staticTextPlayerName) + { + staticTextPlayerName->winGetSize(&s_playerNameOriginalWidth, &s_playerNameOriginalHeight); + s_playerNameSizeCaptured = TRUE; + } winFlag = TheWindowManager->winGetWindowFromId(NULL, TheNameKeyGenerator->nameToKey("ControlBar.wnd:WinFlag")); winGeneralPortrait = TheWindowManager->winGetWindowFromId(NULL, TheNameKeyGenerator->nameToKey("ControlBar.wnd:WinGeneralPortrait")); buttonIdleWorker = TheWindowManager->winGetWindowFromId(NULL, TheNameKeyGenerator->nameToKey("ControlBar.wnd:ButtonIdleWorker")); @@ -270,7 +456,14 @@ void ControlBar::populateObserverList( void ) //GadgetButtonSetHiliteImage( buttonPlayer[currentButton], p->getPlayerTemplate()->getHiliteImage() ); //GadgetButtonSetHiliteSelectedImage( buttonPlayer[currentButton], p->getPlayerTemplate()->getPushedImage() ); //GadgetButtonSetDisabledImage( buttonPlayer[currentButton], p->getPlayerTemplate()->getDisabledImage() ); - buttonPlayer[currentButton]->winSetTooltip(p->getPlayerDisplayName()); + const UnicodeString summary = formatObserverPlayerSummary(p); + UnicodeString tooltip = p->getPlayerDisplayName(); + if (!summary.isEmpty()) + { + tooltip.concat(L"\n"); + tooltip.concat(summary); + } + buttonPlayer[currentButton]->winSetTooltip(tooltip); buttonPlayer[currentButton]->winHide(FALSE); buttonPlayer[currentButton]->winSetStatus( WIN_STATUS_USE_OVERLAY_STATES ); @@ -283,10 +476,10 @@ void ControlBar::populateObserverList( void ) teamStr.format("Team:%d", slot->getTeamNumber() + 1); if (slot->isAI() && slot->getTeamNumber() == -1) teamStr = "Team:AI"; - UnicodeString text; text.format(TheGameText->fetch("CONTROLBAR:ObsPlayerLabel"), p->getPlayerDisplayName().str(), TheGameText->fetch(teamStr).str()); + appendSummaryToSecondLine(text, summary); GadgetStaticTextSetText(staticTextPlayer[currentButton], text ); @@ -312,7 +505,14 @@ void ControlBar::populateObserverList( void ) //GadgetButtonSetHiliteImage( buttonPlayer[currentButton], p->getPlayerTemplate()->getHiliteImage() ); //GadgetButtonSetHiliteSelectedImage( buttonPlayer[currentButton], p->getPlayerTemplate()->getPushedImage() ); //GadgetButtonSetDisabledImage( buttonPlayer[currentButton], p->getPlayerTemplate()->getDisabledImage() ); - buttonPlayer[currentButton]->winSetTooltip(p->getPlayerDisplayName()); + const UnicodeString summary = formatObserverPlayerSummary(p); + UnicodeString tooltip = p->getPlayerDisplayName(); + if (!summary.isEmpty()) + { + tooltip.concat(L"\n"); + tooltip.concat(summary); + } + buttonPlayer[currentButton]->winSetTooltip(tooltip); buttonPlayer[currentButton]->winHide(FALSE); buttonPlayer[currentButton]->winSetStatus( WIN_STATUS_USE_OVERLAY_STATES ); @@ -320,7 +520,9 @@ void ControlBar::populateObserverList( void ) Color backColor = GameMakeColor(0, 0, 0, 255); staticTextPlayer[currentButton]->winSetEnabledTextColors( playerColor, backColor ); staticTextPlayer[currentButton]->winHide(FALSE); - GadgetStaticTextSetText(staticTextPlayer[currentButton], p->getPlayerDisplayName()); + UnicodeString text = p->getPlayerDisplayName(); + appendSummaryToSecondLine(text, summary); + GadgetStaticTextSetText(staticTextPlayer[currentButton], text); ++currentButton; break; @@ -376,10 +578,30 @@ void ControlBar::populateObserverInfoWindow ( void ) GadgetStaticTextSetText(staticTextNumberOfUnitsKilled, uString); uString.format(L"%d",m_observerLookAtPlayer->getScoreKeeper()->getTotalUnitsLost()); GadgetStaticTextSetText(staticTextNumberOfUnitsLost, uString); - GadgetStaticTextSetText(staticTextPlayerName, m_observerLookAtPlayer->getPlayerDisplayName()); + UnicodeString playerName = m_observerLookAtPlayer->getPlayerDisplayName(); + GadgetStaticTextSetText(staticTextPlayerName, playerName); + ensurePlayerNameLabelHeight(FALSE); Color color = m_observerLookAtPlayer->getPlayerColor(); staticTextPlayerName->winSetEnabledTextColors(color, GameMakeColor(0,0,0,255)); winFlag->winSetEnabledImage(0, m_observerLookAtPlayer->getPlayerTemplate()->getFlagWaterMarkImage()); winGeneralPortrait->winHide(FALSE); buttonIdleWorker->winHide(FALSE); } + +void ControlBar::refreshObserverUI() +{ + if (!m_isObserverCommandBar) + { + return; + } + + if (ObserverPlayerInfoWindow && !ObserverPlayerInfoWindow->winIsHidden()) + { + populateObserverInfoWindow(); + } + + if (ObserverPlayerListWindow && !ObserverPlayerListWindow->winIsHidden()) + { + populateObserverList(); + } +} diff --git a/GeneralsMD/Code/GameEngine/Include/GameClient/ControlBar.h b/GeneralsMD/Code/GameEngine/Include/GameClient/ControlBar.h index e15b68975c..3d933caaa7 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameClient/ControlBar.h +++ b/GeneralsMD/Code/GameEngine/Include/GameClient/ControlBar.h @@ -736,6 +736,7 @@ class ControlBar : public SubsystemInterface void initObserverControls( void ); void populateObserverInfoWindow ( void ); void populateObserverList( void ); + void refreshObserverUI(); Bool isObserverControlBarOn( void ) { return m_isObserverCommandBar;} void setObserverLookAtPlayer (Player *player); ///< Sets the looked at player. Used to present information about the player. diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/ControlBar/ControlBar.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/ControlBar/ControlBar.cpp index efce005ebd..729727e502 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/ControlBar/ControlBar.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/ControlBar/ControlBar.cpp @@ -1455,7 +1455,7 @@ void ControlBar::update( void ) if( m_isObserverCommandBar) { if((TheGameLogic->getFrame() % (LOGICFRAMES_PER_SECOND/2)) == 0) - populateObserverInfoWindow(); + refreshObserverUI(); Drawable *drawToEvaluateFor = NULL; if( TheInGameUI->getSelectCount() > 1 ) diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/ControlBar/ControlBarObserver.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/ControlBar/ControlBarObserver.cpp index 1c8c9dd67f..64cc45c78b 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/ControlBar/ControlBarObserver.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/ControlBar/ControlBarObserver.cpp @@ -99,6 +99,187 @@ static GameWindow *staticTextNumberOfUnitsLost = NULL; static GameWindow *staticTextPlayerName = NULL; static NameKeyType s_replayObserverNameKey = NAMEKEY_INVALID; +static Int s_playerNameOriginalWidth = 0; +static Int s_playerNameOriginalHeight = 0; +static Bool s_playerNameSizeCaptured = FALSE; + +static void ensurePlayerNameLabelHeight(Bool needsTwoLines) +{ + if (!staticTextPlayerName) + { + return; + } + + if (!s_playerNameSizeCaptured) + { + staticTextPlayerName->winGetSize(&s_playerNameOriginalWidth, &s_playerNameOriginalHeight); + s_playerNameSizeCaptured = TRUE; + } + + if (s_playerNameOriginalHeight <= 0) + { + return; + } + + Int currentWidth = 0; + Int currentHeight = 0; + staticTextPlayerName->winGetSize(¤tWidth, ¤tHeight); + + const Int targetHeight = needsTwoLines ? s_playerNameOriginalHeight * 2 : s_playerNameOriginalHeight; + if (currentHeight != targetHeight) + { + staticTextPlayerName->winSetSize(currentWidth, targetHeight); + } +} + +static AsciiString formatCompactCash(UnsignedInt cash) +{ + AsciiString cashText; + + if (cash >= 1000000) + { + if ((cash % 1000000) == 0) + { + cashText.format("$%uM", cash / 1000000); + } + else + { + const float millions = static_cast(cash) / 1000000.0f; + cashText.format("$%.1fM", millions); + } + } + else if (cash >= 1000) + { + if ((cash % 1000) == 0) + { + cashText.format("$%uK", cash / 1000); + } + else + { + const float thousands = static_cast(cash) / 1000.0f; + cashText.format("$%.1fK", thousands); + } + } + else + { + cashText.format("$%u", cash); + } + + return cashText; +} + +// TheSuperHackers @feature GitHubCopilot 01/11/2025 Surface cash/rank/xp summary for observer overlays. +static UnicodeString formatObserverPlayerSummary(const Player *player) +{ + UnicodeString summary; + + if (!player) + { + return summary; + } + + UnsignedInt cash = 0; + if (player->getMoney()) + { + cash = player->getMoney()->countMoney(); + } + + const Int rankLevel = player->getRankLevel(); + const Int totalSkillPoints = player->getSkillPoints(); + const Int levelFloor = player->getSkillPointsLevelDown(); + const Int levelCeiling = player->getSkillPointsLevelUp(); + + Int xpProgress = totalSkillPoints - levelFloor; + if (xpProgress < 0) + { + xpProgress = 0; + } + + Int xpSpan = levelCeiling - levelFloor; + if (xpSpan < 0) + { + xpSpan = 0; + } + + Int xpPercent = 100; + if (xpSpan > 0) + { + xpPercent = (xpProgress * 100) / xpSpan; + if (xpPercent > 100) + { + xpPercent = 100; + } + } + + AsciiString asciiSummary = formatCompactCash(cash); + + AsciiString rankToken; + rankToken.format(" R%d", rankLevel); + asciiSummary.concat(rankToken); + + AsciiString xpToken; + if (xpSpan > 0) + { + xpToken.format(" XP %d%%", xpPercent); + } + else + { + xpToken.format(" XP %d", totalSkillPoints); + } + asciiSummary.concat(xpToken); + + summary.translate(asciiSummary); + return summary; +} + +static void appendSummaryToSecondLine(UnicodeString &text, const UnicodeString &summary) +{ + if (summary.isEmpty()) + { + return; + } + + UnicodeString firstLine; + UnicodeString secondLine; + Bool onSecondLine = FALSE; + + const Int length = text.getLength(); + for (Int index = 0; index < length; ++index) + { + const WideChar ch = text.getCharAt(index); + if (!onSecondLine) + { + firstLine.concat(ch); + if (ch == L'\n') + { + onSecondLine = TRUE; + } + } + else + { + secondLine.concat(ch); + } + } + + if (onSecondLine) + { + if (!secondLine.isEmpty()) + { + secondLine.concat(L" "); + } + secondLine.concat(summary); + text = firstLine; + text.concat(secondLine); + } + else + { + if (!text.isEmpty()) + { + text.concat(L"\n"); + } + text.concat(summary); + } +} //----------------------------------------------------------------------------- // PUBLIC FUNCTIONS /////////////////////////////////////////////////////////// @@ -126,6 +307,11 @@ void ControlBar::initObserverControls( void ) staticTextNumberOfUnitsKilled = TheWindowManager->winGetWindowFromId(NULL, TheNameKeyGenerator->nameToKey("ControlBar.wnd:StaticTextNumberOfUnitsKilled")); staticTextNumberOfUnitsLost = TheWindowManager->winGetWindowFromId(NULL, TheNameKeyGenerator->nameToKey("ControlBar.wnd:StaticTextNumberOfUnitsLost")); staticTextPlayerName = TheWindowManager->winGetWindowFromId(NULL, TheNameKeyGenerator->nameToKey("ControlBar.wnd:StaticTextPlayerName")); + if (staticTextPlayerName) + { + staticTextPlayerName->winGetSize(&s_playerNameOriginalWidth, &s_playerNameOriginalHeight); + s_playerNameSizeCaptured = TRUE; + } winFlag = TheWindowManager->winGetWindowFromId(NULL, TheNameKeyGenerator->nameToKey("ControlBar.wnd:WinFlag")); winGeneralPortrait = TheWindowManager->winGetWindowFromId(NULL, TheNameKeyGenerator->nameToKey("ControlBar.wnd:WinGeneralPortrait")); buttonIdleWorker = TheWindowManager->winGetWindowFromId(NULL, TheNameKeyGenerator->nameToKey("ControlBar.wnd:ButtonIdleWorker")); @@ -270,7 +456,14 @@ void ControlBar::populateObserverList( void ) //GadgetButtonSetHiliteImage( buttonPlayer[currentButton], p->getPlayerTemplate()->getHiliteImage() ); //GadgetButtonSetHiliteSelectedImage( buttonPlayer[currentButton], p->getPlayerTemplate()->getPushedImage() ); //GadgetButtonSetDisabledImage( buttonPlayer[currentButton], p->getPlayerTemplate()->getDisabledImage() ); - buttonPlayer[currentButton]->winSetTooltip(p->getPlayerDisplayName()); + const UnicodeString summary = formatObserverPlayerSummary(p); + UnicodeString tooltip = p->getPlayerDisplayName(); + if (!summary.isEmpty()) + { + tooltip.concat(L"\n"); + tooltip.concat(summary); + } + buttonPlayer[currentButton]->winSetTooltip(tooltip); buttonPlayer[currentButton]->winHide(FALSE); buttonPlayer[currentButton]->winSetStatus( WIN_STATUS_USE_OVERLAY_STATES ); @@ -287,6 +480,7 @@ void ControlBar::populateObserverList( void ) UnicodeString text; text.format(TheGameText->fetch("CONTROLBAR:ObsPlayerLabel"), p->getPlayerDisplayName().str(), TheGameText->fetch(teamStr).str()); + appendSummaryToSecondLine(text, summary); GadgetStaticTextSetText(staticTextPlayer[currentButton], text ); @@ -312,7 +506,14 @@ void ControlBar::populateObserverList( void ) //GadgetButtonSetHiliteImage( buttonPlayer[currentButton], p->getPlayerTemplate()->getHiliteImage() ); //GadgetButtonSetHiliteSelectedImage( buttonPlayer[currentButton], p->getPlayerTemplate()->getPushedImage() ); //GadgetButtonSetDisabledImage( buttonPlayer[currentButton], p->getPlayerTemplate()->getDisabledImage() ); - buttonPlayer[currentButton]->winSetTooltip(p->getPlayerDisplayName()); + const UnicodeString summary = formatObserverPlayerSummary(p); + UnicodeString tooltip = p->getPlayerDisplayName(); + if (!summary.isEmpty()) + { + tooltip.concat(L"\n"); + tooltip.concat(summary); + } + buttonPlayer[currentButton]->winSetTooltip(tooltip); buttonPlayer[currentButton]->winHide(FALSE); buttonPlayer[currentButton]->winSetStatus( WIN_STATUS_USE_OVERLAY_STATES ); @@ -320,7 +521,9 @@ void ControlBar::populateObserverList( void ) Color backColor = GameMakeColor(0, 0, 0, 255); staticTextPlayer[currentButton]->winSetEnabledTextColors( playerColor, backColor ); staticTextPlayer[currentButton]->winHide(FALSE); - GadgetStaticTextSetText(staticTextPlayer[currentButton], p->getPlayerDisplayName()); + UnicodeString text = p->getPlayerDisplayName(); + appendSummaryToSecondLine(text, summary); + GadgetStaticTextSetText(staticTextPlayer[currentButton], text); ++currentButton; break; @@ -376,10 +579,30 @@ void ControlBar::populateObserverInfoWindow ( void ) GadgetStaticTextSetText(staticTextNumberOfUnitsKilled, uString); uString.format(L"%d",m_observerLookAtPlayer->getScoreKeeper()->getTotalUnitsLost()); GadgetStaticTextSetText(staticTextNumberOfUnitsLost, uString); - GadgetStaticTextSetText(staticTextPlayerName, m_observerLookAtPlayer->getPlayerDisplayName()); + UnicodeString nameLine = m_observerLookAtPlayer->getPlayerDisplayName(); + GadgetStaticTextSetText(staticTextPlayerName, nameLine); + ensurePlayerNameLabelHeight(FALSE); Color color = m_observerLookAtPlayer->getPlayerColor(); staticTextPlayerName->winSetEnabledTextColors(color, GameMakeColor(0,0,0,255)); winFlag->winSetEnabledImage(0, m_observerLookAtPlayer->getPlayerTemplate()->getFlagWaterMarkImage()); winGeneralPortrait->winHide(FALSE); buttonIdleWorker->winHide(FALSE); } + +void ControlBar::refreshObserverUI() +{ + if (!m_isObserverCommandBar) + { + return; + } + + if (ObserverPlayerInfoWindow && !ObserverPlayerInfoWindow->winIsHidden()) + { + populateObserverInfoWindow(); + } + + if (ObserverPlayerListWindow && !ObserverPlayerListWindow->winIsHidden()) + { + populateObserverList(); + } +}