Skip to content

bugfix(energy): Don't increase power production for disabled power plants on save game load#2508

Open
Caball009 wants to merge 2 commits intoTheSuperHackers:mainfrom
Caball009:bugfix_energy_xfer
Open

bugfix(energy): Don't increase power production for disabled power plants on save game load#2508
Caball009 wants to merge 2 commits intoTheSuperHackers:mainfrom
Caball009:bugfix_energy_xfer

Conversation

@Caball009
Copy link
Copy Markdown

@Caball009 Caball009 commented Mar 30, 2026

When loading a save game with disabled power plants, players still get the power production from those plants. That's because the power production is increased before the Object disabled flags are xfer'd. The following code is supposed to prevent the increase in power production, but the disabled flag would have to be xfer'd before this is called. This PR fixes that.

void Object::friend_adjustPowerForPlayer( Bool incoming )
{
if (isDisabled() && getTemplate()->getEnergyProduction() > 0)
{
// Disabledness only affects Producers, not Consumers.
return;
}

Callstack:

generalszh.exe!Object::friend_adjustPowerForPlayer(bool incoming)
generalszh.exe!Player::becomingTeamMember(Object * obj, bool yes)
generalszh.exe!Object::setOrRestoreTeam(Team * team, bool restoring)
generalszh.exe!Object::xfer(Xfer * xfer)

TODO:

  • Replicate in Generals.

@Caball009 Caball009 added Bug Something is not working right, typically is user facing Minor Severity: Minor < Major < Critical < Blocker Gen Relates to Generals ZH Relates to Zero Hour Saveload Is Saveload/Xfer related labels Mar 30, 2026
@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Mar 30, 2026

Greptile Summary

This PR fixes a save-game bug in Zero Hour where disabled power plants incorrectly contributed to the player's power production upon load. The root cause was that setOrRestoreTeam (which triggers friend_adjustPowerForPlayer via becomingTeamMember) was called before the object's disabled flags (m_disabledMask and m_disabledTillFrame) were deserialized, causing isDisabled() to always return false at that point.

Changes:

  • Moves the setOrRestoreTeam block in Object::xfer from immediately after the status-bit xfer to after both m_disabledMask and m_disabledTillFrame are xfer'd, so isDisabled() is accurate when power production is evaluated.
  • Updates the guard comment to reflect the broader prerequisite: "now that we have xferred our status bits and disabled data".

Notes:

  • The same bug exists in the vanilla Generals counterpart (Generals/Code/GameEngine/Source/GameLogic/Object/Object.cpp, line 3641), where setOrRestoreTeam is still called before m_disabledMask is loaded at line 3681. The PR description acknowledges this with an open TODO.

Confidence Score: 5/5

  • Safe to merge — the fix is a minimal, correct reordering with no format changes or side-effects.
  • The change is a surgical one-block reorder within a serialisation function. All xfer operations between the old and new positions (geometry, vision, shroud data) are pure data loads with no dependency on the team being set, so deferring the team assignment does not break anything. The fix correctly ensures both disabled-state fields are populated before power production is evaluated. No P0/P1 issues found.
  • No files require special attention. The open TODO about the vanilla Generals counterpart is tracked in the PR description.

Important Files Changed

Filename Overview
GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp Moves setOrRestoreTeam call from before to after m_disabledMask and m_disabledTillFrame are xfer'd, ensuring isDisabled() returns the correct value when power production is evaluated on save load.

Sequence Diagram

sequenceDiagram
    participant X as Object::xfer (XFER_LOAD)
    participant S as Object::setOrRestoreTeam
    participant P as Player::becomingTeamMember
    participant A as Object::friend_adjustPowerForPlayer

    Note over X: xfer status bits (m_status, m_scriptStatus, m_privateStatus)
    Note over X: xfer geometry / vision / shroud data
    Note over X: ✅ xfer m_disabledMask (NEW ORDER)
    Note over X: ✅ xfer m_disabledTillFrame (NEW ORDER)
    X->>S: setOrRestoreTeam(team, restoring=true)
    S->>P: becomingTeamMember(this, yes=true)
    P->>A: friend_adjustPowerForPlayer(incoming=true)
    Note over A: isDisabled() now correctly reads<br/>the already-loaded m_disabledMask
    alt isDisabled() == true && energyProduction > 0
        A-->>P: return early (no power added ✅)
    else not disabled
        A-->>P: objectEnteringInfluence(this)
    end
Loading

Reviews (1): Last reviewed commit: "Updated comment." | Re-trigger Greptile

// disabled till frame
xfer->xferUser( m_disabledTillFrame, sizeof( UnsignedInt ) * DISABLED_COUNT );

// OK, now that we have xferred our status bits and disabled data, it's safe to set the team...
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Can it perhaps be moved into Object::loadPostProcess or would that be too late then?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Not sure. The most immediate issue I see is that the teamID would need to be accessible in Object::loadPostProcess.

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

Labels

Bug Something is not working right, typically is user facing Gen Relates to Generals Minor Severity: Minor < Major < Critical < Blocker Saveload Is Saveload/Xfer related ZH Relates to Zero Hour

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants