Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -425,8 +425,7 @@ void ControlBar::populatePurchaseScience( Player* player )
win = TheWindowManager->winGetWindowFromId( m_contextParent[ CP_PURCHASE_SCIENCE ], TheNameKeyGenerator->nameToKey( "GeneralsExpPoints.wnd:ProgressBarExperience" ) );
if(win)
{
Int progress;
progress = ((player->getSkillPoints() - player->getSkillPointsLevelDown()) * 100) /(player->getSkillPointsLevelUp() - player->getSkillPointsLevelDown());
const Int progress = ((player->getSkillPoints() - player->getSkillPointsLevelDown()) * 100) / (player->getSkillPointsLevelUp() - player->getSkillPointsLevelDown());
GadgetProgressBarSetProgress(win, progress);
}

Expand Down Expand Up @@ -484,8 +483,7 @@ void ControlBar::updateContextPurchaseScience()
win = TheWindowManager->winGetWindowFromId( m_contextParent[ CP_PURCHASE_SCIENCE ], TheNameKeyGenerator->nameToKey( "GeneralsExpPoints.wnd:ProgressBarExperience" ) );
if(win)
{
Int progress;
progress = ((player->getSkillPoints() - player->getSkillPointsLevelDown()) * 100) /(player->getSkillPointsLevelUp() - player->getSkillPointsLevelDown());
const Int progress = ((player->getSkillPoints() - player->getSkillPointsLevelDown()) * 100) / (player->getSkillPointsLevelUp() - player->getSkillPointsLevelDown());
GadgetProgressBarSetProgress(win, progress);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -477,14 +477,8 @@ void W3DCommandBarGenExpDraw( GameWindow *window, WinInstanceData *instData )
static const Image *endBar = TheMappedImageCollection->findImageByName("GenExpBarTop1");
static const Image *beginBar = TheMappedImageCollection->findImageByName("GenExpBarBottom1");
static const Image *centerBar = TheMappedImageCollection->findImageByName("GenExpBar1");
Int progress = 0;
Int skillPointsRequired = player->getSkillPointsLevelUp() - player->getSkillPointsLevelDown();

// TheSuperHackers @bugfix Mauller 04/05/2025 Prevent possible division by zero
if ( skillPointsRequired > 0)
{
progress = ( ((player->getSkillPoints() - player->getSkillPointsLevelDown()) * 100) / skillPointsRequired );
}
Int progress = ((player->getSkillPoints() - player->getSkillPointsLevelDown()) * 100) / (player->getSkillPointsLevelUp() - player->getSkillPointsLevelDown());
Comment thread
Caball009 marked this conversation as resolved.

if(progress <= 0)
return;
Expand Down
2 changes: 2 additions & 0 deletions GeneralsMD/Code/GameEngine/Include/Common/Player.h
Original file line number Diff line number Diff line change
Expand Up @@ -682,6 +682,8 @@ class Player : public Snapshot
*/
Bool addScience(ScienceType science);

void setSafeLevels(Int levelUp, Int levelDown);

public:
Int getSkillPoints() const { return m_skillPoints; }
Int getSciencePurchasePoints() const { return m_sciencePurchasePoints; }
Expand Down
25 changes: 20 additions & 5 deletions GeneralsMD/Code/GameEngine/Source/Common/RTS/Player.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2702,8 +2702,7 @@ void Player::resetRank()
m_rankLevel = 1;
m_skillPoints = 0;
const RankInfo* nextRank = TheRankInfoStore->getRankInfo(m_rankLevel+1);
m_levelUp = nextRank ? nextRank->m_skillPointsNeeded : INT_MAX;
m_levelDown = 0;
setSafeLevels(nextRank ? nextRank->m_skillPointsNeeded : INT_MAX, 0);
m_sciences.clear();
m_sciencePurchasePoints = getPlayerTemplate() ? getPlayerTemplate()->getIntrinsicSciencePurchasePoints() : 0;
const RankInfo* curRank = TheRankInfoStore->getRankInfo(m_rankLevel);
Expand Down Expand Up @@ -2762,7 +2761,8 @@ Bool Player::setRankLevel(Int newLevel)
}

const RankInfo* nextRank = TheRankInfoStore->getRankInfo(newLevel + 1);
m_levelUp = nextRank ? nextRank->m_skillPointsNeeded : INT_MAX;
setSafeLevels(nextRank ? nextRank->m_skillPointsNeeded : INT_MAX, m_levelDown);

m_rankLevel = newLevel;

DEBUG_ASSERTCRASH(m_skillPoints >= m_levelDown && m_skillPoints < m_levelUp, ("hmm, wrong"));
Expand All @@ -2788,6 +2788,22 @@ Bool Player::setRankLevel(Int newLevel)
return true;
}

//=============================================================================
void Player::setSafeLevels(Int levelUp, Int levelDown)
{
if (levelUp == levelDown)
{
// TheSuperHackers @bugfix Prevent possible division by zero in the control bar code.
m_levelUp = INT_MAX;
m_levelDown = 0;
}
Comment on lines +2794 to +2799

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 Do not terminalize duplicates

Equal adjacent SkillPointsNeeded values are valid custom-rank input for the crash case this code handles. When a player reaches rank N and rank N+1 has the same threshold, this branch replaces the next threshold with INT_MAX; addSkillPoints() can then never advance the player to rank N+1 even though the threshold is already met. Handle a zero-width transition without converting it into the terminal-rank sentinel.

Prompt To Fix With AI
This is a comment left during a code review.
Path: GeneralsMD/Code/GameEngine/Source/Common/RTS/Player.cpp
Line: 2794-2799

Comment:
**Do not terminalize duplicates**

Equal adjacent `SkillPointsNeeded` values are valid custom-rank input for the crash case this code handles. When a player reaches rank N and rank N+1 has the same threshold, this branch replaces the next threshold with `INT_MAX`; `addSkillPoints()` can then never advance the player to rank N+1 even though the threshold is already met. Handle a zero-width transition without converting it into the terminal-rank sentinel.

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Agreed. setRankLevel() first assigns m_levelDown from the newly reached rank (Player.cpp:2759). It then reads the next rank and passes its threshold together with that lower bound to setSafeLevels() at line 2764.

When adjacent ranks have the same accepted SkillPointsNeeded, those are two legitimate equal bounds. setSafeLevels() replaces them with INT_MAX/0 at lines 2794–2799, so addSkillPoints() can no longer continue through the equal-threshold rank as its existing loop intends.

loadPostProcess() also applies the same transformation to serialized m_levelUp/m_levelDown values at lines 4605–4607. That makes this more than a UI safeguard: it changes rank-progression state both during play and after loading.

Maybe setSafeLevels() should be removed rather than adjusted. Following @xezon’s suggestion, terminal-rank detection can be derived from the current rank, rank count and applicable game rank limit. The shared UI calculation can then return zero when there is no attainable next rank or when the interval is nonpositive, without rewriting the underlying state.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Your analysis is correct, and it's a stronger argument than my original comment captured. The problem with setSafeLevels() isn't just the equal-threshold case — it's that the fix is applied at the wrong layer. The state mutation in Player (and its persistence through loadPostProcess) is a side-effect that reaches well beyond protecting a UI progress bar.

The right approach is to fix the two ControlBar divisions defensively, without touching m_levelUp/m_levelDown at all:

// In populatePurchaseScience and updateContextPurchaseScience
const Int interval = player->getSkillPointsLevelUp() - player->getSkillPointsLevelDown();
const Int progress = (interval > 0)
    ? ((player->getSkillPoints() - player->getSkillPointsLevelDown()) * 100) / interval
    : 100;
GadgetProgressBarSetProgress(win, progress);

That keeps the rank-progression state untouched, handles the zero-width interval gracefully (showing a full bar when the player is at or past the threshold), and doesn't require setSafeLevels() at all. Terminal-rank detection — where getSkillPointsLevelUp() returns INT_MAX — already flows correctly through that guard since INT_MAX - anything > 0.

The setSafeLevels() additions in resetRank(), setRankLevel(), and loadPostProcess() should all be reverted, and the method removed.

else
{
m_levelUp = levelUp;
m_levelDown = levelDown;
}
}

//=============================================================================
Bool Player::hasScience(ScienceType t) const
{
Expand Down Expand Up @@ -4588,6 +4604,5 @@ void Player::xfer( Xfer *xfer )
// ------------------------------------------------------------------------------------------------
void Player::loadPostProcess()
{

setSafeLevels(m_levelUp, m_levelDown);
}

Loading