fix(saveload): Use getFinalOverride in WeaponSet xfer load to match Object constructor - #2160
fix(saveload): Use getFinalOverride in WeaponSet xfer load to match Object constructor#2160bobtista wants to merge 3 commits into
Conversation
| 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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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):
|
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 |
d182d6a to
536d22f
Compare
|
Rebased again, this is ready for re-review |
|
Was there any observable bug from this after save load? Are there potentially more of these mistakes in the code base? |
Yes, but it requires an active template override, eg from mission or map INI data. Before this fix, WeaponSet::xfer(LOAD) therefore restored m_curWeaponTemplateSet from the base template. The next updateWeaponSet() compared that pointer with the equivalent set belonging to the final override and treated it as a genuine weapon-set change. It reallocated every weapon and called loadAmmoNow(), replacing the ammo and reload timing restored from the save. If the set does not use WeaponLockSharedAcrossSets, it also releases the weapon lock and selects PRIMARY_WEAPON. Are there others? Just ScoreKeeper::xferObjectCountMap(). It reconstructs its std::map<const ThingTemplate *, Int> keys using findTemplate() (the base), while addObjectBuilt() and the related methods key the maps using o->getTemplate() (the final override). After loading, subsequent events for an overridden template can therefore create separate base and final-override entries. The problem appears after another save/load cycle. Both entries are saved under the same template name, and loading uses (*map)[thingTemplate] = count, so one entry silently overwrites the other. Could make another PR if it's worth it. The other close cases I checked already account for overrides: GameClient::xfer() compares final overrides when rebinding drawables, AttackPriorityMap normalizes both insertion and lookup, and matching code uses isEquivalentTo() where appropriate. Other load-time template lookups are passed into newObject() or are not subsequently used as identity keys. |
|
The LLM answer is not good for human consumption. What is the repro in game to see the issue before this fix? |
d182d6a to
536d22f
Compare
|
Should |
I don't think so. Map overrides are deleted by ThingFactory::reset(), while several callers cache findTemplate() results in function-local statics (GenericBridge, GenericDebris, and GarrisonGun). Returning the final override would leave those pointers dangling after a match where one was overridden. Also ScoreKeeper::xferObjectCountMap() has the same base/final mismatch and should probably be fixed separately. |
|
Can you add some code comments where appropriate to document the patterns to avoid future confusions? |
done |
Summary
Fixes weapon timing state corruption after loading a saved game by ensuring WeaponSet uses the same template override as Object.
Notes
Objectconstructor callsgetFinalOverride()on the template (Object.cpp:230)WeaponSet::xfer(LOAD)was usingTheThingFactory->findTemplate()which returns the base template, not the final overridem_curWeaponTemplateSetto point to a different ThingTemplate's weapon sets thanobj->getTemplate()updateWeaponSet()triggered unnecessary weapon reallocation, resetting timing stateFix
Add
getFinalOverride()call inWeaponSet::xfer(LOAD)to match what Object does, ensuring consistent template pointers.Testing
Todo