Skip to content

Conversation

@L3-M
Copy link

@L3-M L3-M commented Jan 17, 2026

This change implements player money/rank/XP display for match Observers in Generals and Zero Hour.
It is enabled by default with a size of 8 replicating GenTool implementation.
The following option has been added:

ObserverListFontSize = 8

It can be styled and repositioned by adding the relevant fields to the InGameUI.ini data file.

Example:

image

TODO

  • Replicate in Generals

@greptile-apps
Copy link

greptile-apps bot commented Jan 17, 2026

Greptile Overview

Greptile Summary

This PR implements an observer player list display for Zero Hour that shows real-time player statistics (team, money, rank, XP, and name) during match observation. The implementation follows the existing codebase patterns for custom UI elements like FPS counter and game time display.

Key changes:

  • Added ObserverListFontSize configuration option (default: 8, matching GenTool behavior)
  • Implemented observer list rendering with configurable styling (font, colors, position, background alpha)
  • Used fixed-width columns with padding for consistent alignment across all rows
  • Implemented value caching to minimize setText() calls on DisplayStrings for performance
  • Properly handles resource initialization and cleanup through refresh methods

The code follows project conventions for null pointer handling, formatting, and resource management. The background width calculation uses fixed column widths by design (as clarified in previous thread discussions) rather than dynamic value widths.

Confidence Score: 4/5

  • This PR is safe to merge with low risk
  • The implementation follows established patterns in the codebase (similar to FPS/time display features), uses proper resource management, and handles null checks appropriately. The main implementation file (InGameUI.cpp) shows solid engineering with value caching for performance. However, the TODO mentions replication in Generals is still pending, and the feature is only implemented for Zero Hour (GeneralsMD), which prevents a perfect score.
  • No files require special attention - the implementation is clean and follows project conventions

Important Files Changed

Filename Overview
GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/OptionsMenu.cpp Implemented getObserverListFontSize() with defaults and validation, added save logic to refresh resources
GeneralsMD/Code/GameEngine/Include/GameClient/InGameUI.h Added ObserverList struct with labels/values arrays, refresh method, and draw method declarations
GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp Implemented observer list rendering with initialization, resource management, and display logic using fixed column widths

Sequence Diagram

sequenceDiagram
    participant User
    participant OptionsMenu
    participant GlobalData
    participant InGameUI
    participant DisplayStringManager
    participant GameLogic
    participant PlayerList
    participant Display

    User->>OptionsMenu: Set ObserverListFontSize option
    OptionsMenu->>GlobalData: m_observerListFontSize = value
    OptionsMenu->>InGameUI: refreshObserverListResources()
    
    InGameUI->>DisplayStringManager: newDisplayString() for labels
    InGameUI->>DisplayStringManager: newDisplayString() for values
    InGameUI->>InGameUI: Initialize lastValues cache
    InGameUI->>InGameUI: Set font properties from GlobalData
    
    Note over InGameUI: During game frame
    
    GameLogic->>InGameUI: postWindowDraw()
    InGameUI->>InGameUI: Check if observer mode active
    
    alt Observer Mode Active
        InGameUI->>InGameUI: drawObserverList()
        loop For each slot/player
            InGameUI->>PlayerList: findPlayerWithNameKey()
            PlayerList-->>InGameUI: Player object
            InGameUI->>InGameUI: Get team, money, rank, XP, name
            
            alt Value changed
                InGameUI->>DisplayStringManager: setText() on value DisplayString
            end
            
            InGameUI->>Display: drawFillRect() for background
            InGameUI->>Display: draw() labels and values
        end
    end
Loading

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

6 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

@L3-M L3-M self-assigned this Jan 17, 2026
@L3-M L3-M added GUI For graphical user interface Minor Severity: Minor < Major < Critical < Blocker Gen Relates to Generals ZH Relates to Zero Hour Enhancement Is new feature or request labels Jan 17, 2026
@L3-M L3-M added this to the GenTool features replication milestone Jan 17, 2026
@xezon
Copy link

xezon commented Jan 26, 2026

@greptileai

@xezon
Copy link

xezon commented Jan 26, 2026

Needs a Rebase

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

2 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

@L3-M L3-M force-pushed the L3-M/observer-player-list branch from b6779ec to b7d5c72 Compare January 27, 2026 13:50
@L3-M
Copy link
Author

L3-M commented Jan 27, 2026

Rebased and tweaked with the last suggestions.

@L3-M L3-M force-pushed the L3-M/observer-player-list branch from b7d5c72 to c7d0276 Compare January 27, 2026 14:05
Copy link

@xezon xezon left a comment

Choose a reason for hiding this comment

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

This code would be a lot simpler if there was in text coloring with DisplayStrings.

GenTool does this by using §0..§9, §a..§z for in text color presets. Is beyond the scope of this change, but a consideration for the future.

For reference, this is how the code looks in GenTool to draw the Player List with:

void CGentool::MakePlayerMoneyText(std::wstring& text) const
{
	if (m_pPlayerMoneyManager->IsActive())
	{
		const uint32 playerCount = m_pPlayerMoneyManager->GetPlayerMoneyDataCount();
		for (uint32 playerId = 0; playerId < playerCount; ++playerId)
		{
			const CPlayerMoneyData& playerMoneyData = m_pPlayerMoneyManager->GetPlayerMoneyData(playerId);

			if (playerMoneyData.IsAnyGamePlayer() && !playerMoneyData.GetName().empty())
			{
				const SGameColor gameColor = gGame.GetCachedColorValue(playerMoneyData.GetColor());

				boost::wformat form(
					L"\n§9T§0%d" // team
					L"\t.4§9$§0%u" // money
					L"\t.13§9*§0%u" // promotion
					L"\t.16§9XP§0%u" // experience
					L"\t.24§x%.8X%ls"); // name
				form % (1 + playerMoneyData.GetTeam());
				form % std::min(999999u, playerMoneyData.GetCash());
				form % std::min(9u, playerMoneyData.GetPromotion());
				form % std::min(9999u, playerMoneyData.GetExperience());
				form % gameColor.argb;
				form % playerMoneyData.GetShortName().c_str();

				text += form.str();
			}
		}
	}
}

m_gameTimeColor = GameMakeColor( 255, 255, 255, 255 );
m_gameTimeDropColor = GameMakeColor( 0, 0, 0, 255 );

for (i = 0; i < ARRAY_SIZE(m_observerList.labels); ++i)
Copy link

Choose a reason for hiding this comment

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

You can rewrite this loop as std::fill

}
for (i = 0; i < ObserverList::ValueType_Count; ++i)
{
for (j = 0; j < MAX_PLAYER_COUNT; ++j)
Copy link

Choose a reason for hiding this comment

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

You can rewrite this loop as std::fill

m_observerList.lastValues.team[i] = -1;
m_observerList.lastValues.money[i] = ~0u;
m_observerList.lastValues.rank[i] = -1;
m_observerList.lastValues.xp[i] = -1;
Copy link

Choose a reason for hiding this comment

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

It's ok. Alternatively add a Constructor to LastValues struct with 4 std::fill's.

TheDisplayStringManager->freeDisplayString(m_observerList.labels[i]);
m_observerList.labels[i] = nullptr;
}
for (i = 0; i < ObserverList::ValueType_Count; ++i)
Copy link

Choose a reason for hiding this comment

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

Above it uses ARRAY_SIZE(m_observerList.labels) and here ObserverList::ValueType_Count. Either use constants consistently or ARRAY_SIZE. Both work.

(ARRAY_SIZE also works for multi arrays)

Int observerXp = observerPlayer->getSkillPoints();
const UnicodeString observerName = observerPlayer->getPlayerDisplayName();

UnicodeString observerListValue;
Copy link

Choose a reason for hiding this comment

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

Can be moved before loop.

const Int observerTeam = (slot && slot->getTeamNumber() >= 0) ? (slot->getTeamNumber() + 1) : 0;
UnsignedInt observerMoney = observerPlayer->getMoney()->countMoney();
Int observerRank = observerPlayer->getRankLevel();
Int observerXp = observerPlayer->getSkillPoints();
Copy link

Choose a reason for hiding this comment

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

Several values can be const.

Int observerRow = 0;
for (Int slotIndex = 0; slotIndex < MAX_SLOTS && observerRow < MAX_PLAYER_COUNT; ++slotIndex)
{
AsciiString name;
Copy link

Choose a reason for hiding this comment

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

Can be moved before loop.

}
if (m_observerList.lastValues.xp[row] != observerXp)
{
observerListValue.format(L"%d", observerXp);
Copy link

@xezon xezon Jan 27, 2026

Choose a reason for hiding this comment

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

Values need clamping or reformatting if the room is not large enough, or text spacing needs extending.

GenTool clamps (not great)

  • Money at 999999
  • Promotion at 9
  • XP at 9999
  • Name to 12 characters (optional because it is at the end)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Enhancement Is new feature or request Gen Relates to Generals GUI For graphical user interface Minor Severity: Minor < Major < Critical < Blocker ZH Relates to Zero Hour

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add player money/rank/XP display for match Observers

2 participants