From c7d02761da26082d01cad544b0286bab581cffd1 Mon Sep 17 00:00:00 2001 From: L3-M <100478094+L3-M@users.noreply.github.com> Date: Wed, 26 Nov 2025 15:16:50 +0300 Subject: [PATCH] feat(gui): Implement Observer Player List --- .../Include/Common/UserPreferences.h | 1 + .../GameEngine/Include/Common/GlobalData.h | 2 + .../GameEngine/Include/GameClient/InGameUI.h | 50 ++++ .../GameEngine/Source/Common/GlobalData.cpp | 2 + .../GUI/GUICallbacks/Menus/OptionsMenu.cpp | 25 ++ .../GameEngine/Source/GameClient/InGameUI.cpp | 216 ++++++++++++++++++ 6 files changed, 296 insertions(+) diff --git a/Core/GameEngine/Include/Common/UserPreferences.h b/Core/GameEngine/Include/Common/UserPreferences.h index 7936cfd8ee6..0317ca22061 100644 --- a/Core/GameEngine/Include/Common/UserPreferences.h +++ b/Core/GameEngine/Include/Common/UserPreferences.h @@ -146,6 +146,7 @@ class OptionPreferences : public UserPreferences Int getRenderFpsFontSize(void); Int getSystemTimeFontSize(void); Int getGameTimeFontSize(void); + Int getObserverListFontSize(void); Real getResolutionFontAdjustment(void); diff --git a/GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h b/GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h index 337477129cf..687052baa19 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h @@ -414,6 +414,8 @@ class GlobalData : public SubsystemInterface // TheSuperHackers @feature Mauller 21/06/2025 allow the system time and game time font size to be set, a size of zero disables them Int m_systemTimeFontSize; Int m_gameTimeFontSize; + // TheSuperHackers @feature L3-M 05/11/2025 allow the observer list font size to be set, a size of zero disables it + Int m_observerListFontSize; // TheSuperHackers @feature L3-M 21/08/2025 toggle the money per minute display, false shows only the original current money Bool m_showMoneyPerMinute; diff --git a/GeneralsMD/Code/GameEngine/Include/GameClient/InGameUI.h b/GeneralsMD/Code/GameEngine/Include/GameClient/InGameUI.h index 363f5d8f3e1..1e9cb502898 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameClient/InGameUI.h +++ b/GeneralsMD/Code/GameEngine/Include/GameClient/InGameUI.h @@ -572,6 +572,7 @@ friend class Drawable; // for selection/deselection transactions virtual void refreshRenderFpsResources(void); virtual void refreshSystemTimeResources( void ); virtual void refreshGameTimeResources( void ); + virtual void refreshObserverListResources( void ); virtual void disableTooltipsUntil(UnsignedInt frameNum); virtual void clearTooltipsDisabled(); @@ -596,6 +597,7 @@ friend class Drawable; // for selection/deselection transactions void drawRenderFps(Int &x, Int &y); void drawSystemTime(Int &x, Int &y); void drawGameTime(); + void drawObserverList(); public: void registerWindowLayout(WindowLayout *layout); // register a layout for updates @@ -799,6 +801,54 @@ friend class Drawable; // for selection/deselection transactions Color m_gameTimeColor; Color m_gameTimeDropColor; + // Observer Player List + struct ObserverList + { + enum LabelType + { + LabelType_Team, + LabelType_Money, + LabelType_Rank, + LabelType_Xp, + + LabelType_Count + }; + + enum ValueType + { + ValueType_Team, + ValueType_Money, + ValueType_Rank, + ValueType_Xp, + ValueType_Name, + + ValueType_Count + }; + + struct LastValues + { + Int team[MAX_PLAYER_COUNT]; + UnsignedInt money[MAX_PLAYER_COUNT]; + Int rank[MAX_PLAYER_COUNT]; + Int xp[MAX_PLAYER_COUNT]; + UnicodeString name[MAX_PLAYER_COUNT]; + }; + + DisplayString *labels[LabelType_Count]; + DisplayString *values[ValueType_Count][MAX_PLAYER_COUNT]; + LastValues lastValues; + }; + + ObserverList m_observerList; + AsciiString m_observerListFont; + Int m_observerListPointSize; + Bool m_observerListBold; + Coord2D m_observerListPosition; + Color m_observerListLabelColor; + Color m_observerListValueColor; + Color m_observerListDropColor; + UnsignedInt m_observerListBackgroundAlpha; + // message data UIMessage m_uiMessages[ MAX_UI_MESSAGES ];/**< messages to display to the user, the array is organized with newer messages at diff --git a/GeneralsMD/Code/GameEngine/Source/Common/GlobalData.cpp b/GeneralsMD/Code/GameEngine/Source/Common/GlobalData.cpp index e7e324c57aa..5c4dc0ef5ec 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/GlobalData.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/GlobalData.cpp @@ -949,6 +949,7 @@ GlobalData::GlobalData() m_renderFpsFontSize = 8; m_systemTimeFontSize = 8; m_gameTimeFontSize = 8; + m_observerListFontSize = 8; m_showMoneyPerMinute = FALSE; m_allowMoneyPerMinuteForPlayer = FALSE; @@ -1225,6 +1226,7 @@ void GlobalData::parseGameDataDefinition( INI* ini ) TheWritableGlobalData->m_renderFpsFontSize = optionPref.getRenderFpsFontSize(); TheWritableGlobalData->m_systemTimeFontSize = optionPref.getSystemTimeFontSize(); TheWritableGlobalData->m_gameTimeFontSize = optionPref.getGameTimeFontSize(); + TheWritableGlobalData->m_observerListFontSize = optionPref.getObserverListFontSize(); TheWritableGlobalData->m_showMoneyPerMinute = optionPref.getShowMoneyPerMinute(); Int val=optionPref.getGammaValue(); diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/OptionsMenu.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/OptionsMenu.cpp index 45d096faf4a..61aab08fbc5 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/OptionsMenu.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/OptionsMenu.cpp @@ -968,6 +968,20 @@ Int OptionPreferences::getGameTimeFontSize(void) return fontSize; } +Int OptionPreferences::getObserverListFontSize(void) +{ + OptionPreferences::const_iterator it = find("ObserverListFontSize"); + if (it == end()) + return 8; + + Int fontSize = atoi(it->second.str()); + if (fontSize < 0) + { + fontSize = 0; + } + return fontSize; +} + Real OptionPreferences::getResolutionFontAdjustment(void) { OptionPreferences::const_iterator it = find("ResolutionFontAdjustment"); @@ -1553,6 +1567,17 @@ static void saveOptions( void ) TheInGameUI->refreshGameTimeResources(); } + //------------------------------------------------------------------------------------------------- + // Set Observer List Font Size + val = pref->getObserverListFontSize(); + if (val >= 0) + { + AsciiString prefString; + prefString.format("%d", val); + (*pref)["ObserverListFontSize"] = prefString; + TheInGameUI->refreshObserverListResources(); + } + //------------------------------------------------------------------------------------------------- // Set User Font Scaling Percentage val = pref->getResolutionFontAdjustment() * 100.0f; // TheSuperHackers @todo replace with options input when applicable diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp index a737146e276..3942c0add62 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp @@ -37,6 +37,7 @@ #include "Common/GameType.h" #include "Common/GameUtility.h" #include "Common/MessageStream.h" +#include "Common/NameKeyGenerator.h" #include "Common/PerfTimer.h" #include "Common/Player.h" #include "Common/PlayerList.h" @@ -87,6 +88,7 @@ #include "GameLogic/Module/SupplyWarehouseDockUpdate.h" #include "GameLogic/Module/MobMemberSlavedUpdate.h"//ML +#include "GameNetwork/GameInfo.h" #include "GameNetwork/NetworkInterface.h" #include "Common/UnitTimings.h" //Contains the DO_UNIT_TIMINGS define jba. @@ -931,6 +933,14 @@ const FieldParse InGameUI::s_fieldParseTable[] = { "GameTimeColor", INI::parseColorInt, nullptr, offsetof( InGameUI, m_gameTimeColor ) }, { "GameTimeDropColor", INI::parseColorInt, nullptr, offsetof( InGameUI, m_gameTimeDropColor ) }, + { "ObserverListFont", INI::parseAsciiString, nullptr, offsetof( InGameUI, m_observerListFont ) }, + { "ObserverListBold", INI::parseBool, nullptr, offsetof( InGameUI, m_observerListBold ) }, + { "ObserverListPosition", INI::parseCoord2D, nullptr, offsetof( InGameUI, m_observerListPosition ) }, + { "ObserverListLabelColor", INI::parseColorInt, nullptr, offsetof( InGameUI, m_observerListLabelColor ) }, + { "ObserverListValueColor", INI::parseColorInt, nullptr, offsetof( InGameUI, m_observerListValueColor ) }, + { "ObserverListDropColor", INI::parseColorInt, nullptr, offsetof( InGameUI, m_observerListDropColor ) }, + { "ObserverListBackgroundAlpha", INI::parseUnsignedInt , nullptr, offsetof( InGameUI, m_observerListBackgroundAlpha ) }, + { nullptr, nullptr, nullptr, 0 } }; @@ -961,6 +971,7 @@ namespace InGameUI::InGameUI() { Int i; + Int j; m_inputEnabled = true; @@ -1109,6 +1120,34 @@ InGameUI::InGameUI() m_gameTimeColor = GameMakeColor( 255, 255, 255, 255 ); m_gameTimeDropColor = GameMakeColor( 0, 0, 0, 255 ); + for (i = 0; i < ARRAY_SIZE(m_observerList.labels); ++i) + { + m_observerList.labels[i] = nullptr; + } + for (i = 0; i < ObserverList::ValueType_Count; ++i) + { + for (j = 0; j < MAX_PLAYER_COUNT; ++j) + { + m_observerList.values[i][j] = nullptr; + } + } + for (i = 0; i < MAX_PLAYER_COUNT; ++i) + { + m_observerList.lastValues.team[i] = -1; + m_observerList.lastValues.money[i] = ~0u; + m_observerList.lastValues.rank[i] = -1; + m_observerList.lastValues.xp[i] = -1; + } + m_observerListFont = "Tahoma"; + m_observerListPointSize = TheGlobalData->m_observerListFontSize; + m_observerListBold = TRUE; + m_observerListPosition.x = 0.0f; + m_observerListPosition.y = 0.44f; + m_observerListLabelColor = GameMakeColor(125, 124, 122, 255); + m_observerListValueColor = GameMakeColor(253, 251, 251, 255); + m_observerListDropColor = GameMakeColor(0, 0, 0, 255); + m_observerListBackgroundAlpha = 170; + m_superweaponPosition.x = 0.7f; m_superweaponPosition.y = 0.7f; m_superweaponFlashDuration = 1.0f; @@ -2165,6 +2204,9 @@ void InGameUI::freeMessageResources( void ) void InGameUI::freeCustomUiResources( void ) { + Int i; + Int j; + TheDisplayStringManager->freeDisplayString(m_networkLatencyString); m_networkLatencyString = nullptr; TheDisplayStringManager->freeDisplayString(m_renderFpsString); @@ -2177,6 +2219,19 @@ void InGameUI::freeCustomUiResources( void ) m_gameTimeString = nullptr; TheDisplayStringManager->freeDisplayString(m_gameTimeFrameString); m_gameTimeFrameString = nullptr; + for (i = 0; i < ARRAY_SIZE(m_observerList.labels); ++i) + { + TheDisplayStringManager->freeDisplayString(m_observerList.labels[i]); + m_observerList.labels[i] = nullptr; + } + for (i = 0; i < ObserverList::ValueType_Count; ++i) + { + for (j = 0; j < MAX_PLAYER_COUNT; ++j) + { + TheDisplayStringManager->freeDisplayString(m_observerList.values[i][j]); + m_observerList.values[i][j] = nullptr; + } + } } //------------------------------------------------------------------------------------------------- @@ -3677,6 +3732,11 @@ void InGameUI::postWindowDraw( void ) { drawGameTime(); } + + if (m_observerListPointSize > 0 && TheGameLogic->isInGame() && TheControlBar->isObserverControlBarOn()) + { + drawObserverList(); + } } //------------------------------------------------------------------------------------------------- @@ -5905,6 +5965,7 @@ void InGameUI::refreshCustomUiResources(void) refreshRenderFpsResources(); refreshSystemTimeResources(); refreshGameTimeResources(); + refreshObserverListResources(); } void InGameUI::refreshNetworkLatencyResources(void) @@ -5980,6 +6041,72 @@ void InGameUI::refreshGameTimeResources(void) m_gameTimeFrameString->setFont(gameTimeFont); } +void InGameUI::refreshObserverListResources(void) +{ + Int i; + Int j; + + for (i = 0; i < ObserverList::LabelType_Count; ++i) + { + if (!m_observerList.labels[i]) + { + m_observerList.labels[i] = TheDisplayStringManager->newDisplayString(); + } + } + + for (i = 0; i < ObserverList::ValueType_Count; ++i) + { + for (j = 0; j < MAX_PLAYER_COUNT; ++j) + { + if (!m_observerList.values[i][j]) + { + m_observerList.values[i][j] = TheDisplayStringManager->newDisplayString(); + switch (i) + { + case ObserverList::ValueType_Team: + m_observerList.lastValues.team[j] = -1; + break; + case ObserverList::ValueType_Money: + m_observerList.lastValues.money[j] = ~0u; + break; + case ObserverList::ValueType_Rank: + m_observerList.lastValues.rank[j] = -1; + break; + case ObserverList::ValueType_Xp: + m_observerList.lastValues.xp[j] = -1; + break; + case ObserverList::ValueType_Name: + m_observerList.lastValues.name[j] = UnicodeString::TheEmptyString; + break; + default: + break; + } + } + } + } + + m_observerListPointSize = TheGlobalData->m_observerListFontSize; + Int adjustedObserverListPointSize = TheGlobalLanguageData->adjustFontSize(m_observerListPointSize); + GameFont *listFont = TheWindowManager->winFindFont(m_observerListFont, adjustedObserverListPointSize, m_observerListBold); + + for (i = 0; i < ObserverList::LabelType_Count; ++i) + { + m_observerList.labels[i]->setFont(listFont); + } + for (i = 0; i < ObserverList::ValueType_Count; ++i) + { + for (j = 0; j < MAX_PLAYER_COUNT; ++j) + { + m_observerList.values[i][j]->setFont(listFont); + } + } + + m_observerList.labels[ObserverList::LabelType_Team]->setText(TheGameText->FETCH_OR_SUBSTITUTE_FORMAT("GUI:ObserverListLabelTeam", L"T")); + m_observerList.labels[ObserverList::LabelType_Money]->setText(TheGameText->FETCH_OR_SUBSTITUTE_FORMAT("GUI:ObserverListLabelMoney", L"$")); + m_observerList.labels[ObserverList::LabelType_Rank]->setText(TheGameText->FETCH_OR_SUBSTITUTE_FORMAT("GUI:ObserverListLabelRank", L"*")); + m_observerList.labels[ObserverList::LabelType_Xp]->setText(TheGameText->FETCH_OR_SUBSTITUTE_FORMAT("GUI:ObserverListLabelXp", L"XP")); +} + void InGameUI::disableTooltipsUntil(UnsignedInt frameNum) { if (frameNum > m_tooltipsDisabledUntil) @@ -6168,3 +6295,92 @@ void InGameUI::drawGameTime() m_gameTimeString->draw(horizontalTimerOffset, m_gameTimePosition.y, m_gameTimeColor, m_gameTimeDropColor); m_gameTimeFrameString->draw(horizontalFrameOffset, m_gameTimePosition.y, GameMakeColor(180,180,180,255), m_gameTimeDropColor); } + +void InGameUI::drawObserverList() +{ + Int baseX = (Int)(m_observerListPosition.x * TheDisplay->getWidth()); + Int baseY = (Int)(m_observerListPosition.y * TheDisplay->getHeight()); + + Int drawY = baseY; + Int lineH = m_observerList.labels[ObserverList::LabelType_Team]->getFont()->height; + + Int observerRow = 0; + for (Int slotIndex = 0; slotIndex < MAX_SLOTS && observerRow < MAX_PLAYER_COUNT; ++slotIndex) + { + AsciiString name; + name.format("player%d", slotIndex); + const NameKeyType key = TheNameKeyGenerator->nameToKey(name); + Player *observerPlayer = ThePlayerList->findPlayerWithNameKey(key); + if (!observerPlayer || observerPlayer->isPlayerObserver()) + continue; + + const GameSlot *slot = TheGameInfo->getConstSlot(slotIndex); + + const Int row = observerRow++; + const Int observerTeam = (slot && slot->getTeamNumber() >= 0) ? (slot->getTeamNumber() + 1) : 0; + UnsignedInt observerMoney = observerPlayer->getMoney()->countMoney(); + Int observerRank = observerPlayer->getRankLevel(); + Int observerXp = observerPlayer->getSkillPoints(); + const UnicodeString observerName = observerPlayer->getPlayerDisplayName(); + + UnicodeString observerListValue; + if (m_observerList.lastValues.team[row] != observerTeam) + { + observerListValue.format(L"%d", observerTeam); + m_observerList.values[ObserverList::ValueType_Team][row]->setText(observerListValue); + m_observerList.lastValues.team[row] = observerTeam; + } + if (m_observerList.lastValues.money[row] != observerMoney) + { + observerListValue = formatMoneyValue(observerMoney); + m_observerList.values[ObserverList::ValueType_Money][row]->setText(observerListValue); + m_observerList.lastValues.money[row] = observerMoney; + } + if (m_observerList.lastValues.rank[row] != observerRank) + { + observerListValue.format(L"%d", observerRank); + m_observerList.values[ObserverList::ValueType_Rank][row]->setText(observerListValue); + m_observerList.lastValues.rank[row] = observerRank; + } + if (m_observerList.lastValues.xp[row] != observerXp) + { + observerListValue.format(L"%d", observerXp); + m_observerList.values[ObserverList::ValueType_Xp][row]->setText(observerListValue); + m_observerList.lastValues.xp[row] = observerXp; + } + if (m_observerList.lastValues.name[row] != observerName) + { + m_observerList.values[ObserverList::ValueType_Name][row]->setText(observerName); + m_observerList.lastValues.name[row] = observerName; + } + + Int drawX = baseX; + Int observerMoneyDrawX = m_observerList.labels[ObserverList::LabelType_Team]->getWidth() + static_cast(lineH * (15.0f / 12.0f) + 0.5f); + Int observerRankDrawX = m_observerList.labels[ObserverList::LabelType_Money]->getWidth() + static_cast(lineH * (40.0f / 12.0f) + 0.5f); + Int observerXpDrawX = m_observerList.labels[ObserverList::LabelType_Rank]->getWidth() + static_cast(lineH * (15.0f / 12.0f) + 0.5f); + Int observerNameDrawX = m_observerList.labels[ObserverList::LabelType_Xp]->getWidth() + static_cast(lineH * (35.0f / 12.0f) + 0.5f); + const Int RowBackgroundWidth = observerMoneyDrawX + observerRankDrawX + observerXpDrawX + observerNameDrawX + m_observerList.values[ObserverList::ValueType_Name][row]->getWidth(); + + TheDisplay->drawFillRect(baseX, drawY, RowBackgroundWidth, lineH, GameMakeColor(0, 0, 0, m_observerListBackgroundAlpha)); + + m_observerList.labels[ObserverList::LabelType_Team]->draw(drawX, drawY, m_observerListLabelColor, m_observerListDropColor); + m_observerList.values[ObserverList::ValueType_Team][row]->draw(drawX + m_observerList.labels[ObserverList::LabelType_Team]->getWidth(), drawY, m_observerListValueColor, m_observerListDropColor); + observerMoneyDrawX += drawX; + + m_observerList.labels[ObserverList::LabelType_Money]->draw(observerMoneyDrawX, drawY, m_observerListLabelColor, m_observerListDropColor); + m_observerList.values[ObserverList::ValueType_Money][row]->draw(observerMoneyDrawX + m_observerList.labels[ObserverList::LabelType_Money]->getWidth(), drawY, m_observerListValueColor, m_observerListDropColor); + observerRankDrawX += observerMoneyDrawX; + + m_observerList.labels[ObserverList::LabelType_Rank]->draw(observerRankDrawX, drawY, m_observerListLabelColor, m_observerListDropColor); + m_observerList.values[ObserverList::ValueType_Rank][row]->draw(observerRankDrawX + m_observerList.labels[ObserverList::LabelType_Rank]->getWidth(), drawY, m_observerListValueColor, m_observerListDropColor); + observerXpDrawX += observerRankDrawX; + + m_observerList.labels[ObserverList::LabelType_Xp]->draw(observerXpDrawX, drawY, m_observerListLabelColor, m_observerListDropColor); + m_observerList.values[ObserverList::ValueType_Xp][row]->draw(observerXpDrawX + m_observerList.labels[ObserverList::LabelType_Xp]->getWidth(), drawY, m_observerListValueColor, m_observerListDropColor); + observerNameDrawX += observerXpDrawX; + + m_observerList.values[ObserverList::ValueType_Name][row]->draw(observerNameDrawX, drawY, observerPlayer->getPlayerColor(), m_observerListDropColor); + + drawY += lineH; + } +}