From c69e2078ddd74fff7275816ce635cfab3e767246 Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Sun, 19 Jul 2026 18:23:43 +0400 Subject: [PATCH 1/3] fix(savegame): Use getFinalOverride in WeaponSet xfer load to match Object constructor --- .../Code/GameEngine/Source/GameLogic/Object/WeaponSet.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/WeaponSet.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/WeaponSet.cpp index 96b6da59e4d..9fbaaa65cd2 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/WeaponSet.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/WeaponSet.cpp @@ -232,6 +232,11 @@ void WeaponSet::xfer( Xfer *xfer ) if (tt == nullptr) throw INI_INVALID_DATA; + // TheSuperHackers @fix bobtista 27/01/2026 findTemplate returns the base template, but Object + // uses getFinalOverride() in its constructor. We must do the same here so m_curWeaponTemplateSet + // points to the same ThingTemplate's weapon sets, avoiding unnecessary reallocation in updateWeaponSet. + tt = static_cast(tt->getFinalOverride()); + m_curWeaponTemplateSet = tt->findWeaponTemplateSet(wsFlags); if (m_curWeaponTemplateSet == nullptr) throw INI_INVALID_DATA; From 536d22fc292d90641201224d1ec4e35dea8586e4 Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Sun, 19 Jul 2026 18:23:43 +0400 Subject: [PATCH 2/3] fix(savegame): Replicate WeaponSet getFinalOverride fix to Generals --- .../Code/GameEngine/Source/GameLogic/Object/WeaponSet.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/WeaponSet.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/WeaponSet.cpp index 460e6d25116..15dfaaadd42 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/WeaponSet.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/WeaponSet.cpp @@ -225,6 +225,11 @@ void WeaponSet::xfer( Xfer *xfer ) if (tt == nullptr) throw INI_INVALID_DATA; + // TheSuperHackers @fix bobtista 27/01/2026 findTemplate returns the base template, but Object + // uses getFinalOverride() in its constructor. We must do the same here so m_curWeaponTemplateSet + // points to the same ThingTemplate's weapon sets, avoiding unnecessary reallocation in updateWeaponSet. + tt = static_cast(tt->getFinalOverride()); + m_curWeaponTemplateSet = tt->findWeaponTemplateSet(wsFlags); if (m_curWeaponTemplateSet == nullptr) throw INI_INVALID_DATA; From 77b9db3025b0bbaff3d8bccd0ab96e721f182d4d Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Wed, 12 Aug 2026 09:23:19 -0400 Subject: [PATCH 3/3] docs(thing): Document template override lookup --- GeneralsMD/Code/GameEngine/Include/Common/ThingFactory.h | 3 +++ GeneralsMD/Code/GameEngine/Source/Common/Thing/Thing.cpp | 2 ++ .../GameEngine/Source/GameLogic/Object/WeaponSet.cpp | 9 ++++++--- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Include/Common/ThingFactory.h b/GeneralsMD/Code/GameEngine/Include/Common/ThingFactory.h index b7971930c16..98232ecca8a 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/ThingFactory.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/ThingFactory.h @@ -73,6 +73,9 @@ class ThingFactory : public SubsystemInterface get a template given template database name. return null if not found. note, this is now substantially faster (does a hash-table lookup) */ + // TheSuperHackers @info bobtista 12/08/2026 Keep returning the base template from the hash map. + // Some callers cache this pointer across matches, while reset() deletes map overrides. Code that + // needs the current override must call getFinalOverride() without caching the result. const ThingTemplate *findTemplate( const AsciiString& name, Bool check = TRUE ) { return findTemplateInternal( name, check ); } /** diff --git a/GeneralsMD/Code/GameEngine/Source/Common/Thing/Thing.cpp b/GeneralsMD/Code/GameEngine/Source/Common/Thing/Thing.cpp index b8e79f7e9fa..6d87d4d2359 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/Thing/Thing.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/Thing/Thing.cpp @@ -93,6 +93,8 @@ Thing::~Thing() //============================================================================= const ThingTemplate *Thing::getTemplate() const { + // TheSuperHackers @info bobtista 12/08/2026 Resolve the current override here. + // m_template holds the original template pointer and OVERRIDE follows the override chain each time. return m_template; } diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/WeaponSet.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/WeaponSet.cpp index 9fbaaa65cd2..55dad0973ed 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/WeaponSet.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/WeaponSet.cpp @@ -232,9 +232,10 @@ void WeaponSet::xfer( Xfer *xfer ) if (tt == nullptr) throw INI_INVALID_DATA; - // TheSuperHackers @fix bobtista 27/01/2026 findTemplate returns the base template, but Object - // uses getFinalOverride() in its constructor. We must do the same here so m_curWeaponTemplateSet - // points to the same ThingTemplate's weapon sets, avoiding unnecessary reallocation in updateWeaponSet. + // TheSuperHackers @fix bobtista 27/01/2026 Resolve the final override before restoring + // the weapon set. Object::getTemplate() returns the final override, otherwise the next + // updateWeaponSet() sees a different WeaponTemplateSet pointer and recreates the weapons, + // replacing the ammo and reload timing just loaded. tt = static_cast(tt->getFinalOverride()); m_curWeaponTemplateSet = tt->findWeaponTemplateSet(wsFlags); @@ -299,6 +300,8 @@ void WeaponSet::loadPostProcess( void ) //------------------------------------------------------------------------------------------------- void WeaponSet::updateWeaponSet(const Object* obj) { + // TheSuperHackers @info bobtista 12/08/2026 Compare the set pointers to detect a weapon set change. + // m_curWeaponTemplateSet must belong to obj->getTemplate(), including its final override. const WeaponTemplateSet* set = obj->getTemplate()->findWeaponTemplateSet(obj->getWeaponSetFlags()); DEBUG_ASSERTCRASH(set, ("findWeaponSet should never return null")); if (set && set != m_curWeaponTemplateSet)