Skip to content

fix(savegame): Use getFinalOverride in WeaponSet xfer load to match Object constructor#2160

Open
bobtista wants to merge 2 commits into
TheSuperHackers:mainfrom
bobtista:bobtista/fix-weapon-timing-corruption
Open

fix(savegame): Use getFinalOverride in WeaponSet xfer load to match Object constructor#2160
bobtista wants to merge 2 commits into
TheSuperHackers:mainfrom
bobtista:bobtista/fix-weapon-timing-corruption

Conversation

@bobtista

@bobtista bobtista commented Jan 21, 2026

Copy link
Copy Markdown

Summary

Fixes weapon timing state corruption after loading a saved game by ensuring WeaponSet uses the same template override as Object.

Notes

  • Object constructor calls getFinalOverride() on the template (Object.cpp:230)
  • WeaponSet::xfer(LOAD) was using TheThingFactory->findTemplate() which returns the base template, not the final override
  • This caused m_curWeaponTemplateSet to point to a different ThingTemplate's weapon sets than obj->getTemplate()
  • The pointer mismatch in updateWeaponSet() triggered unnecessary weapon reallocation, resetting timing state

Fix

Add getFinalOverride() call in WeaponSet::xfer(LOAD) to match what Object does, ensuring consistent template pointers.

Testing

  • Save game with units that have weapons mid-reload
  • Load the save and verify weapons maintain their reload state
  • Verify normal weapon switching still works correctly

Todo

  • Replicate to Generals

@greptile-apps

greptile-apps Bot commented Jan 21, 2026

Copy link
Copy Markdown

Greptile Summary

This PR fixes weapon timing state corruption on savegame load by ensuring WeaponSet::xfer(LOAD) resolves the same final template override that the Object constructor uses. Previously, findTemplate() returned the base template, causing m_curWeaponTemplateSet to point to a different template's weapon sets than obj->getTemplate(), which triggered spurious reallocation in updateWeaponSet() and reset reload timing.

  • Adds tt = static_cast<const ThingTemplate*>(tt->getFinalOverride()) after the existing null check in the LOAD branch, directly mirroring the pattern at Object.cpp:211.
  • The fix is correctly replicated to both Generals/ and GeneralsMD/ codebases.
  • getFinalOverride() always returns at least this (never nullptr), so no additional null guard is needed after the call.

Confidence Score: 5/5

Safe to merge — the change is a minimal, targeted fix confined to the XFER_LOAD branch with no impact on the save path or normal gameplay codepath.

The one-liner mirrors an identical pattern already used in Object.cpp, getFinalOverride() is guaranteed non-null by its implementation (returns this when no override exists), the existing null check on tt before the call is preserved, and the fix is correctly duplicated to both game codebases.

No files require special attention.

Important Files Changed

Filename Overview
Generals/Code/GameEngine/Source/GameLogic/Object/WeaponSet.cpp Adds getFinalOverride() call in XFER_LOAD path to match Object constructor behavior, preventing template pointer mismatch and spurious weapon reallocation on savegame load.
GeneralsMD/Code/GameEngine/Source/GameLogic/Object/WeaponSet.cpp Identical fix replicated to Zero Hour (GeneralsMD), maintaining parity between the two game codebases.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant XF as Xfer (LOAD)
    participant WS as WeaponSet::xfer
    participant TF as TheThingFactory
    participant TT as ThingTemplate (base)
    participant FO as ThingTemplate (final override)

    XF->>WS: xfer(LOAD)
    WS->>TF: findTemplate(ttName)
    TF-->>WS: tt (base template)
    Note over WS: Before fix: used base tt directly
    WS->>TT: getFinalOverride()
    TT-->>WS: tt (final override)
    Note over WS: After fix: use final override tt
    WS->>FO: findWeaponTemplateSet(wsFlags)
    FO-->>WS: m_curWeaponTemplateSet
    Note over WS: Now matches Object constructor's template pointer
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant XF as Xfer (LOAD)
    participant WS as WeaponSet::xfer
    participant TF as TheThingFactory
    participant TT as ThingTemplate (base)
    participant FO as ThingTemplate (final override)

    XF->>WS: xfer(LOAD)
    WS->>TF: findTemplate(ttName)
    TF-->>WS: tt (base template)
    Note over WS: Before fix: used base tt directly
    WS->>TT: getFinalOverride()
    TT-->>WS: tt (final override)
    Note over WS: After fix: use final override tt
    WS->>FO: findWeaponTemplateSet(wsFlags)
    FO-->>WS: m_curWeaponTemplateSet
    Note over WS: Now matches Object constructor's template pointer
Loading

Reviews (4): Last reviewed commit: "fix(savegame): Replicate WeaponSet getFi..." | Re-trigger Greptile

const WeaponTemplateSet* set = obj->getTemplate()->findWeaponTemplateSet(obj->getWeaponSetFlags());
DEBUG_ASSERTCRASH(set, ("findWeaponSet should never return null"));
if (set && set != m_curWeaponTemplateSet)
// TheSuperHackers @bugfix bobtista 20/01/2026 After checkpoint load, the m_curWeaponTemplateSet pointer

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

My first hunch here is that this change is a hack. Why is m_curWeaponTemplateSet set to something that satisfies the weapon flags but is not actually the real deal? It indicates that the issue is higher up.

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.

Ok, updated with a better approach.
When an Object is created (Object.cpp:230):
tt = (const ThingTemplate*)tt->getFinalOverride();
The Object uses the final override of the template.

But when WeaponSet::xfer(LOAD) restored weapon data (WeaponSet.cpp:231):
const ThingTemplate* tt = TheThingFactory->findTemplate(ttName);
m_curWeaponTemplateSet = tt->findWeaponTemplateSet(wsFlags);
It used findTemplate() which returns the base template, not the final override.

The fix is to just add t = (const ThingTemplate*)tt->getFinalOverride(); to WeaponSet::xfer(LOAD):

@xezon xezon added Bug Something is not working right, typically is user facing Minor Severity: Minor < Major < Critical < Blocker Investigate Gen Relates to Generals ZH Relates to Zero Hour labels Jan 22, 2026
Comment thread GeneralsMD/Code/GameEngine/Source/GameLogic/Object/WeaponSet.cpp Outdated
Comment thread GeneralsMD/Code/GameEngine/Source/GameLogic/Object/WeaponSet.cpp Outdated
@Caball009

Copy link
Copy Markdown

Looks like a good change, but the title and issue description need to be updated (the summary is no longer in line with the current code change).

@bobtista bobtista changed the title bugfix(savegame): Fix weapon timing corruption after load by comparing WeaponSet flags instead of pointer fix(savegame): Use getFinalOverride in WeaponSet xfer load to match Object constructor Jan 27, 2026
@bobtista

Copy link
Copy Markdown
Author

Looks like a good change, but the title and issue description need to be updated (the summary is no longer in line with the current code change).

Updated

@bobtista
bobtista force-pushed the bobtista/fix-weapon-timing-corruption branch from d182d6a to 536d22f Compare July 19, 2026 14:24
@bobtista

Copy link
Copy Markdown
Author

Rebased again, this is ready for re-review

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 Investigate Minor Severity: Minor < Major < Critical < Blocker ZH Relates to Zero Hour

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants