From f7898492e701994ffed6a23aa2578f95ff482838 Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Wed, 12 Nov 2025 22:21:00 -0500 Subject: [PATCH 01/11] perf(fps): optimize Object::isHero with cached hero counter --- .../Include/GameLogic/Module/ContainModule.h | 1 + .../Include/GameLogic/Module/OpenContain.h | 2 ++ .../GameLogic/Object/Contain/OpenContain.cpp | 14 +++++++++++++- .../GameEngine/Source/GameLogic/Object/Object.cpp | 15 ++------------- .../Include/GameLogic/Module/ContainModule.h | 1 + .../Include/GameLogic/Module/OpenContain.h | 2 ++ .../GameLogic/Object/Contain/OpenContain.cpp | 12 ++++++++++++ .../Object/Contain/RiderChangeContain.cpp | 2 ++ .../GameEngine/Source/GameLogic/Object/Object.cpp | 15 ++------------- 9 files changed, 37 insertions(+), 27 deletions(-) diff --git a/Generals/Code/GameEngine/Include/GameLogic/Module/ContainModule.h b/Generals/Code/GameEngine/Include/GameLogic/Module/ContainModule.h index a07b9a856b..b62b2a7f24 100644 --- a/Generals/Code/GameEngine/Include/GameLogic/Module/ContainModule.h +++ b/Generals/Code/GameEngine/Include/GameLogic/Module/ContainModule.h @@ -155,6 +155,7 @@ class ContainModuleInterface virtual const Object *friend_getRider() const = 0; ///< Damn. The draw order dependency bug for riders means that our draw module needs to cheat to get around it. virtual Real getContainedItemsMass() const = 0; virtual UnsignedInt getStealthUnitsContained() const = 0; + virtual UnsignedInt getHeroUnitsContained() const = 0; virtual Bool calcBestGarrisonPosition( Coord3D *sourcePos, const Coord3D *targetPos ) = 0; virtual Bool attemptBestFirePointPosition( Object *source, Weapon *weapon, Object *victim ) = 0; diff --git a/Generals/Code/GameEngine/Include/GameLogic/Module/OpenContain.h b/Generals/Code/GameEngine/Include/GameLogic/Module/OpenContain.h index fecfaa2e13..922fd44b72 100644 --- a/Generals/Code/GameEngine/Include/GameLogic/Module/OpenContain.h +++ b/Generals/Code/GameEngine/Include/GameLogic/Module/OpenContain.h @@ -164,6 +164,7 @@ class OpenContain : public UpdateModule, virtual const Object *friend_getRider() const{return NULL;} ///< Damn. The draw order dependency bug for riders means that our draw module needs to cheat to get around it. virtual Real getContainedItemsMass() const; virtual UnsignedInt getStealthUnitsContained() const { return m_stealthUnitsContained; } + virtual UnsignedInt getHeroUnitsContained() const { return m_heroUnitsContained; } virtual PlayerMaskType getPlayerWhoEntered(void) const { return m_playerEnteredMask; } @@ -238,6 +239,7 @@ class OpenContain : public UpdateModule, ObjectEnterExitMap m_objectEnterExitInfo; UnsignedInt m_stealthUnitsContained; ///< number of stealth units that can't be seen by enemy players. + UnsignedInt m_heroUnitsContained; ///< cached hero count Int m_whichExitPath; ///< Cycles from 1 to n and is used only in modules whose data has numberOfExitPaths > 1. UnsignedInt m_doorCloseCountdown; ///< When should I shut my door. diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp index 9cff8d09cd..baa4941f55 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp @@ -125,6 +125,7 @@ OpenContain::OpenContain( Thing *thing, const ModuleData* moduleData ) : UpdateM m_lastLoadSoundFrame = 0; m_containListSize = 0; m_stealthUnitsContained = 0; + m_heroUnitsContained = 0; m_doorCloseCountdown = 0; m_rallyPoint.zero(); @@ -620,7 +621,7 @@ void OpenContain::scatterToNearbyPosition(Object* rider) } //------------------------------------------------------------------------------------------------- -void OpenContain::onContaining( Object * /*rider*/ ) +void OpenContain::onContaining( Object *rider ) { // Play audio if( m_loadSoundsEnabled ) @@ -629,6 +630,12 @@ void OpenContain::onContaining( Object * /*rider*/ ) enterSound.setObjectID(getObject()->getID()); TheAudio->addAudioEvent(&enterSound); } + + // TheSuperHackers @performance bobtista 13/11/2025 Cache hero count to avoid O(n) iteration in Object::isHero(). + if( rider && rider->isKindOf( KINDOF_HERO ) ) + { + m_heroUnitsContained++; + } } //------------------------------------------------------------------------------------------------- @@ -644,6 +651,11 @@ void OpenContain::onRemoving( Object *rider) AudioEventRTS fallingSound = *rider->getTemplate()->getSoundFalling(); fallingSound.setObjectID(rider->getID()); TheAudio->addAudioEvent(&fallingSound); + + if( rider->isKindOf( KINDOF_HERO ) && m_heroUnitsContained > 0 ) + { + m_heroUnitsContained--; + } } } diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Object.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Object.cpp index d0f4b52301..f62aef66e4 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Object.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Object.cpp @@ -593,25 +593,14 @@ Object::~Object() } //------------------------------------------------------------------------------------------------- -void localIsHero( Object *obj, void* userData ) -{ - Bool *hero = (Bool*)userData; - - if( obj && obj->isKindOf( KINDOF_HERO ) ) - { - *hero = TRUE; - } -} - //------------------------------------------------------------------------------------------------- +// TheSuperHackers @performance bobtista 13/11/2025 Use cached hero count for O(1) lookup instead of O(n) iteration. Bool Object::isHero() const { ContainModuleInterface *contain = getContain(); if( contain ) { - Bool heroInside = FALSE; - contain->iterateContained( localIsHero, (void*)(&heroInside), FALSE ); - if( heroInside ) + if( contain->getHeroUnitsContained() > 0 ) { return TRUE; } diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ContainModule.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ContainModule.h index f85817f831..5a2db53fd1 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ContainModule.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ContainModule.h @@ -175,6 +175,7 @@ class ContainModuleInterface virtual const Object *friend_getRider() const = 0; ///< Damn. The draw order dependency bug for riders means that our draw module needs to cheat to get around it. virtual Real getContainedItemsMass() const = 0; virtual UnsignedInt getStealthUnitsContained() const = 0; + virtual UnsignedInt getHeroUnitsContained() const = 0; virtual Bool calcBestGarrisonPosition( Coord3D *sourcePos, const Coord3D *targetPos ) = 0; virtual Bool attemptBestFirePointPosition( Object *source, Weapon *weapon, Object *victim ) = 0; diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/OpenContain.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/OpenContain.h index 180fc86aac..027b29513f 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/OpenContain.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/OpenContain.h @@ -174,6 +174,7 @@ class OpenContain : public UpdateModule, virtual const Object *friend_getRider() const{return NULL;} ///< Damn. The draw order dependency bug for riders means that our draw module needs to cheat to get around it. virtual Real getContainedItemsMass() const; virtual UnsignedInt getStealthUnitsContained() const { return m_stealthUnitsContained; } + virtual UnsignedInt getHeroUnitsContained() const { return m_heroUnitsContained; } virtual PlayerMaskType getPlayerWhoEntered(void) const { return m_playerEnteredMask; } @@ -259,6 +260,7 @@ class OpenContain : public UpdateModule, ObjectEnterExitMap m_objectEnterExitInfo; UnsignedInt m_stealthUnitsContained; ///< number of stealth units that can't be seen by enemy players. + UnsignedInt m_heroUnitsContained; ///< cached hero count Int m_whichExitPath; ///< Cycles from 1 to n and is used only in modules whose data has numberOfExitPaths > 1. UnsignedInt m_doorCloseCountdown; ///< When should I shut my door. diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp index 2f2bbea878..dafba48265 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp @@ -129,6 +129,7 @@ OpenContain::OpenContain( Thing *thing, const ModuleData* moduleData ) : UpdateM m_lastLoadSoundFrame = 0; m_containListSize = 0; m_stealthUnitsContained = 0; + m_heroUnitsContained = 0; m_doorCloseCountdown = 0; m_rallyPoint.zero(); @@ -749,6 +750,12 @@ void OpenContain::onContaining( Object *rider, Bool wasSelected ) enterSound.setObjectID(getObject()->getID()); TheAudio->addAudioEvent(&enterSound); } + + // TheSuperHackers @performance bobtista 13/11/2025 Cache hero count to avoid O(n) iteration in Object::isHero(). + if( rider && rider->isKindOf( KINDOF_HERO ) ) + { + m_heroUnitsContained++; + } } //------------------------------------------------------------------------------------------------- @@ -764,6 +771,11 @@ void OpenContain::onRemoving( Object *rider) AudioEventRTS fallingSound = *rider->getTemplate()->getSoundFalling(); fallingSound.setObjectID(rider->getID()); TheAudio->addAudioEvent(&fallingSound); + + if( rider->isKindOf( KINDOF_HERO ) && m_heroUnitsContained > 0 ) + { + m_heroUnitsContained--; + } } } diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/RiderChangeContain.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/RiderChangeContain.cpp index ed9babbea3..8a030ff0f5 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/RiderChangeContain.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/RiderChangeContain.cpp @@ -184,6 +184,8 @@ Bool RiderChangeContain::isValidContainerFor(const Object* rider, Bool checkCapa //------------------------------------------------------------------------------------------------- void RiderChangeContain::onContaining( Object *rider, Bool wasSelected ) { + TransportContain::onContaining( rider, wasSelected ); + Object *obj = getObject(); m_containing = TRUE; //Remove our existing rider diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp index 598e266bea..401a8460bd 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp @@ -2042,25 +2042,14 @@ Bool Object::isNonFactionStructure(void) const return isStructure() && !isFactionStructure(); } -void localIsHero( Object *obj, void* userData ) -{ - Bool *hero = (Bool*)userData; - - if( obj && obj->isKindOf( KINDOF_HERO ) ) - { - *hero = TRUE; - } -} - //------------------------------------------------------------------------------------------------- +// TheSuperHackers @performance bobtista 13/11/2025 Use cached hero count for O(1) lookup instead of O(n) iteration. Bool Object::isHero(void) const { ContainModuleInterface *contain = getContain(); if( contain ) { - Bool heroInside = FALSE; - contain->iterateContained( localIsHero, (void*)(&heroInside), FALSE ); - if( heroInside ) + if( contain->getHeroUnitsContained() > 0 ) { return TRUE; } From 29bc6b0f4b409e6cfa35d2edd338df2e399b64d2 Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Wed, 12 Nov 2025 22:59:56 -0500 Subject: [PATCH 02/11] build: wrap isHero optimization behind RETAIL_COMPATIBLE_CRC flag --- .../Include/GameLogic/Module/ContainModule.h | 2 ++ .../Include/GameLogic/Module/OpenContain.h | 6 +++++ .../GameLogic/Object/Contain/OpenContain.cpp | 6 +++++ .../Source/GameLogic/Object/Object.cpp | 22 +++++++++++++++++++ .../Include/GameLogic/Module/ContainModule.h | 2 ++ .../Include/GameLogic/Module/OpenContain.h | 6 +++++ .../GameLogic/Object/Contain/OpenContain.cpp | 6 +++++ .../Object/Contain/RiderChangeContain.cpp | 2 ++ .../Source/GameLogic/Object/Object.cpp | 22 +++++++++++++++++++ 9 files changed, 74 insertions(+) diff --git a/Generals/Code/GameEngine/Include/GameLogic/Module/ContainModule.h b/Generals/Code/GameEngine/Include/GameLogic/Module/ContainModule.h index b62b2a7f24..52c4881dc4 100644 --- a/Generals/Code/GameEngine/Include/GameLogic/Module/ContainModule.h +++ b/Generals/Code/GameEngine/Include/GameLogic/Module/ContainModule.h @@ -155,7 +155,9 @@ class ContainModuleInterface virtual const Object *friend_getRider() const = 0; ///< Damn. The draw order dependency bug for riders means that our draw module needs to cheat to get around it. virtual Real getContainedItemsMass() const = 0; virtual UnsignedInt getStealthUnitsContained() const = 0; +#if !RETAIL_COMPATIBLE_CRC virtual UnsignedInt getHeroUnitsContained() const = 0; +#endif virtual Bool calcBestGarrisonPosition( Coord3D *sourcePos, const Coord3D *targetPos ) = 0; virtual Bool attemptBestFirePointPosition( Object *source, Weapon *weapon, Object *victim ) = 0; diff --git a/Generals/Code/GameEngine/Include/GameLogic/Module/OpenContain.h b/Generals/Code/GameEngine/Include/GameLogic/Module/OpenContain.h index 922fd44b72..9661ae37f0 100644 --- a/Generals/Code/GameEngine/Include/GameLogic/Module/OpenContain.h +++ b/Generals/Code/GameEngine/Include/GameLogic/Module/OpenContain.h @@ -164,7 +164,11 @@ class OpenContain : public UpdateModule, virtual const Object *friend_getRider() const{return NULL;} ///< Damn. The draw order dependency bug for riders means that our draw module needs to cheat to get around it. virtual Real getContainedItemsMass() const; virtual UnsignedInt getStealthUnitsContained() const { return m_stealthUnitsContained; } +#if !RETAIL_COMPATIBLE_CRC virtual UnsignedInt getHeroUnitsContained() const { return m_heroUnitsContained; } +#else + virtual UnsignedInt getHeroUnitsContained() const { return 0; } +#endif virtual PlayerMaskType getPlayerWhoEntered(void) const { return m_playerEnteredMask; } @@ -239,7 +243,9 @@ class OpenContain : public UpdateModule, ObjectEnterExitMap m_objectEnterExitInfo; UnsignedInt m_stealthUnitsContained; ///< number of stealth units that can't be seen by enemy players. +#if !RETAIL_COMPATIBLE_CRC UnsignedInt m_heroUnitsContained; ///< cached hero count +#endif Int m_whichExitPath; ///< Cycles from 1 to n and is used only in modules whose data has numberOfExitPaths > 1. UnsignedInt m_doorCloseCountdown; ///< When should I shut my door. diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp index baa4941f55..9b244c6cbc 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp @@ -125,7 +125,9 @@ OpenContain::OpenContain( Thing *thing, const ModuleData* moduleData ) : UpdateM m_lastLoadSoundFrame = 0; m_containListSize = 0; m_stealthUnitsContained = 0; +#if !RETAIL_COMPATIBLE_CRC m_heroUnitsContained = 0; +#endif m_doorCloseCountdown = 0; m_rallyPoint.zero(); @@ -632,10 +634,12 @@ void OpenContain::onContaining( Object *rider ) } // TheSuperHackers @performance bobtista 13/11/2025 Cache hero count to avoid O(n) iteration in Object::isHero(). +#if !RETAIL_COMPATIBLE_CRC if( rider && rider->isKindOf( KINDOF_HERO ) ) { m_heroUnitsContained++; } +#endif } //------------------------------------------------------------------------------------------------- @@ -652,10 +656,12 @@ void OpenContain::onRemoving( Object *rider) fallingSound.setObjectID(rider->getID()); TheAudio->addAudioEvent(&fallingSound); +#if !RETAIL_COMPATIBLE_CRC if( rider->isKindOf( KINDOF_HERO ) && m_heroUnitsContained > 0 ) { m_heroUnitsContained--; } +#endif } } diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Object.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Object.cpp index f62aef66e4..4a82b8d8d9 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Object.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Object.cpp @@ -593,6 +593,19 @@ Object::~Object() } //------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +#if RETAIL_COMPATIBLE_CRC +void localIsHero( Object *obj, void* userData ) +{ + Bool *hero = (Bool*)userData; + + if( obj && obj->isKindOf( KINDOF_HERO ) ) + { + *hero = TRUE; + } +} +#endif + //------------------------------------------------------------------------------------------------- // TheSuperHackers @performance bobtista 13/11/2025 Use cached hero count for O(1) lookup instead of O(n) iteration. Bool Object::isHero() const @@ -600,10 +613,19 @@ Bool Object::isHero() const ContainModuleInterface *contain = getContain(); if( contain ) { +#if !RETAIL_COMPATIBLE_CRC if( contain->getHeroUnitsContained() > 0 ) { return TRUE; } +#else + Bool heroInside = FALSE; + contain->iterateContained( localIsHero, (void*)(&heroInside), FALSE ); + if( heroInside ) + { + return TRUE; + } +#endif } return isKindOf( KINDOF_HERO ); } diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ContainModule.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ContainModule.h index 5a2db53fd1..150bf8831a 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ContainModule.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ContainModule.h @@ -175,7 +175,9 @@ class ContainModuleInterface virtual const Object *friend_getRider() const = 0; ///< Damn. The draw order dependency bug for riders means that our draw module needs to cheat to get around it. virtual Real getContainedItemsMass() const = 0; virtual UnsignedInt getStealthUnitsContained() const = 0; +#if !RETAIL_COMPATIBLE_CRC virtual UnsignedInt getHeroUnitsContained() const = 0; +#endif virtual Bool calcBestGarrisonPosition( Coord3D *sourcePos, const Coord3D *targetPos ) = 0; virtual Bool attemptBestFirePointPosition( Object *source, Weapon *weapon, Object *victim ) = 0; diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/OpenContain.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/OpenContain.h index 027b29513f..f9e8fd45a4 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/OpenContain.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/OpenContain.h @@ -174,7 +174,11 @@ class OpenContain : public UpdateModule, virtual const Object *friend_getRider() const{return NULL;} ///< Damn. The draw order dependency bug for riders means that our draw module needs to cheat to get around it. virtual Real getContainedItemsMass() const; virtual UnsignedInt getStealthUnitsContained() const { return m_stealthUnitsContained; } +#if !RETAIL_COMPATIBLE_CRC virtual UnsignedInt getHeroUnitsContained() const { return m_heroUnitsContained; } +#else + virtual UnsignedInt getHeroUnitsContained() const { return 0; } +#endif virtual PlayerMaskType getPlayerWhoEntered(void) const { return m_playerEnteredMask; } @@ -260,7 +264,9 @@ class OpenContain : public UpdateModule, ObjectEnterExitMap m_objectEnterExitInfo; UnsignedInt m_stealthUnitsContained; ///< number of stealth units that can't be seen by enemy players. +#if !RETAIL_COMPATIBLE_CRC UnsignedInt m_heroUnitsContained; ///< cached hero count +#endif Int m_whichExitPath; ///< Cycles from 1 to n and is used only in modules whose data has numberOfExitPaths > 1. UnsignedInt m_doorCloseCountdown; ///< When should I shut my door. diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp index dafba48265..414dda087e 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp @@ -129,7 +129,9 @@ OpenContain::OpenContain( Thing *thing, const ModuleData* moduleData ) : UpdateM m_lastLoadSoundFrame = 0; m_containListSize = 0; m_stealthUnitsContained = 0; +#if !RETAIL_COMPATIBLE_CRC m_heroUnitsContained = 0; +#endif m_doorCloseCountdown = 0; m_rallyPoint.zero(); @@ -752,10 +754,12 @@ void OpenContain::onContaining( Object *rider, Bool wasSelected ) } // TheSuperHackers @performance bobtista 13/11/2025 Cache hero count to avoid O(n) iteration in Object::isHero(). +#if !RETAIL_COMPATIBLE_CRC if( rider && rider->isKindOf( KINDOF_HERO ) ) { m_heroUnitsContained++; } +#endif } //------------------------------------------------------------------------------------------------- @@ -772,10 +776,12 @@ void OpenContain::onRemoving( Object *rider) fallingSound.setObjectID(rider->getID()); TheAudio->addAudioEvent(&fallingSound); +#if !RETAIL_COMPATIBLE_CRC if( rider->isKindOf( KINDOF_HERO ) && m_heroUnitsContained > 0 ) { m_heroUnitsContained--; } +#endif } } diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/RiderChangeContain.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/RiderChangeContain.cpp index 8a030ff0f5..8af177008d 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/RiderChangeContain.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/RiderChangeContain.cpp @@ -184,7 +184,9 @@ Bool RiderChangeContain::isValidContainerFor(const Object* rider, Bool checkCapa //------------------------------------------------------------------------------------------------- void RiderChangeContain::onContaining( Object *rider, Bool wasSelected ) { +#if !RETAIL_COMPATIBLE_CRC TransportContain::onContaining( rider, wasSelected ); +#endif Object *obj = getObject(); m_containing = TRUE; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp index 401a8460bd..280970b377 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp @@ -2042,6 +2042,19 @@ Bool Object::isNonFactionStructure(void) const return isStructure() && !isFactionStructure(); } +//------------------------------------------------------------------------------------------------- +#if RETAIL_COMPATIBLE_CRC +void localIsHero( Object *obj, void* userData ) +{ + Bool *hero = (Bool*)userData; + + if( obj && obj->isKindOf( KINDOF_HERO ) ) + { + *hero = TRUE; + } +} +#endif + //------------------------------------------------------------------------------------------------- // TheSuperHackers @performance bobtista 13/11/2025 Use cached hero count for O(1) lookup instead of O(n) iteration. Bool Object::isHero(void) const @@ -2049,10 +2062,19 @@ Bool Object::isHero(void) const ContainModuleInterface *contain = getContain(); if( contain ) { +#if !RETAIL_COMPATIBLE_CRC if( contain->getHeroUnitsContained() > 0 ) { return TRUE; } +#else + Bool heroInside = FALSE; + contain->iterateContained( localIsHero, (void*)(&heroInside), FALSE ); + if( heroInside ) + { + return TRUE; + } +#endif } return isKindOf( KINDOF_HERO ); } From 1435034f467a81a610981cb044df765618bfae02 Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Mon, 15 Dec 2025 12:02:30 -0500 Subject: [PATCH 03/11] fix(game): call TransportContain::onContaining once in RiderChangeContain --- .../Source/GameLogic/Object/Contain/RiderChangeContain.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/RiderChangeContain.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/RiderChangeContain.cpp index 8af177008d..ed9babbea3 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/RiderChangeContain.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/RiderChangeContain.cpp @@ -184,10 +184,6 @@ Bool RiderChangeContain::isValidContainerFor(const Object* rider, Bool checkCapa //------------------------------------------------------------------------------------------------- void RiderChangeContain::onContaining( Object *rider, Bool wasSelected ) { -#if !RETAIL_COMPATIBLE_CRC - TransportContain::onContaining( rider, wasSelected ); -#endif - Object *obj = getObject(); m_containing = TRUE; //Remove our existing rider From bf1c4bbff0f9a12eab570c4ed6ad4f3e2e575954 Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Mon, 15 Dec 2025 16:55:53 -0500 Subject: [PATCH 04/11] remove crc retail flag, add Xfer handling --- .../GameEngine/Include/GameLogic/Module/OpenContain.h | 6 ------ .../Source/GameLogic/Object/Contain/OpenContain.cpp | 10 +++------- .../GameEngine/Include/GameLogic/Module/OpenContain.h | 6 ------ .../Source/GameLogic/Object/Contain/OpenContain.cpp | 10 +++------- 4 files changed, 6 insertions(+), 26 deletions(-) diff --git a/Generals/Code/GameEngine/Include/GameLogic/Module/OpenContain.h b/Generals/Code/GameEngine/Include/GameLogic/Module/OpenContain.h index 9661ae37f0..922fd44b72 100644 --- a/Generals/Code/GameEngine/Include/GameLogic/Module/OpenContain.h +++ b/Generals/Code/GameEngine/Include/GameLogic/Module/OpenContain.h @@ -164,11 +164,7 @@ class OpenContain : public UpdateModule, virtual const Object *friend_getRider() const{return NULL;} ///< Damn. The draw order dependency bug for riders means that our draw module needs to cheat to get around it. virtual Real getContainedItemsMass() const; virtual UnsignedInt getStealthUnitsContained() const { return m_stealthUnitsContained; } -#if !RETAIL_COMPATIBLE_CRC virtual UnsignedInt getHeroUnitsContained() const { return m_heroUnitsContained; } -#else - virtual UnsignedInt getHeroUnitsContained() const { return 0; } -#endif virtual PlayerMaskType getPlayerWhoEntered(void) const { return m_playerEnteredMask; } @@ -243,9 +239,7 @@ class OpenContain : public UpdateModule, ObjectEnterExitMap m_objectEnterExitInfo; UnsignedInt m_stealthUnitsContained; ///< number of stealth units that can't be seen by enemy players. -#if !RETAIL_COMPATIBLE_CRC UnsignedInt m_heroUnitsContained; ///< cached hero count -#endif Int m_whichExitPath; ///< Cycles from 1 to n and is used only in modules whose data has numberOfExitPaths > 1. UnsignedInt m_doorCloseCountdown; ///< When should I shut my door. diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp index 9b244c6cbc..62a24d3391 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp @@ -125,9 +125,7 @@ OpenContain::OpenContain( Thing *thing, const ModuleData* moduleData ) : UpdateM m_lastLoadSoundFrame = 0; m_containListSize = 0; m_stealthUnitsContained = 0; -#if !RETAIL_COMPATIBLE_CRC m_heroUnitsContained = 0; -#endif m_doorCloseCountdown = 0; m_rallyPoint.zero(); @@ -633,13 +631,10 @@ void OpenContain::onContaining( Object *rider ) TheAudio->addAudioEvent(&enterSound); } - // TheSuperHackers @performance bobtista 13/11/2025 Cache hero count to avoid O(n) iteration in Object::isHero(). -#if !RETAIL_COMPATIBLE_CRC if( rider && rider->isKindOf( KINDOF_HERO ) ) { m_heroUnitsContained++; } -#endif } //------------------------------------------------------------------------------------------------- @@ -656,12 +651,10 @@ void OpenContain::onRemoving( Object *rider) fallingSound.setObjectID(rider->getID()); TheAudio->addAudioEvent(&fallingSound); -#if !RETAIL_COMPATIBLE_CRC if( rider->isKindOf( KINDOF_HERO ) && m_heroUnitsContained > 0 ) { m_heroUnitsContained--; } -#endif } } @@ -1544,6 +1537,9 @@ void OpenContain::xfer( Xfer *xfer ) // stealth units contained xfer->xferUnsignedInt( &m_stealthUnitsContained ); + // hero units contained + xfer->xferUnsignedInt( &m_heroUnitsContained ); + // door close countdown xfer->xferUnsignedInt( &m_doorCloseCountdown ); diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/OpenContain.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/OpenContain.h index f9e8fd45a4..027b29513f 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/OpenContain.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/OpenContain.h @@ -174,11 +174,7 @@ class OpenContain : public UpdateModule, virtual const Object *friend_getRider() const{return NULL;} ///< Damn. The draw order dependency bug for riders means that our draw module needs to cheat to get around it. virtual Real getContainedItemsMass() const; virtual UnsignedInt getStealthUnitsContained() const { return m_stealthUnitsContained; } -#if !RETAIL_COMPATIBLE_CRC virtual UnsignedInt getHeroUnitsContained() const { return m_heroUnitsContained; } -#else - virtual UnsignedInt getHeroUnitsContained() const { return 0; } -#endif virtual PlayerMaskType getPlayerWhoEntered(void) const { return m_playerEnteredMask; } @@ -264,9 +260,7 @@ class OpenContain : public UpdateModule, ObjectEnterExitMap m_objectEnterExitInfo; UnsignedInt m_stealthUnitsContained; ///< number of stealth units that can't be seen by enemy players. -#if !RETAIL_COMPATIBLE_CRC UnsignedInt m_heroUnitsContained; ///< cached hero count -#endif Int m_whichExitPath; ///< Cycles from 1 to n and is used only in modules whose data has numberOfExitPaths > 1. UnsignedInt m_doorCloseCountdown; ///< When should I shut my door. diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp index 414dda087e..54d275c52c 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp @@ -129,9 +129,7 @@ OpenContain::OpenContain( Thing *thing, const ModuleData* moduleData ) : UpdateM m_lastLoadSoundFrame = 0; m_containListSize = 0; m_stealthUnitsContained = 0; -#if !RETAIL_COMPATIBLE_CRC m_heroUnitsContained = 0; -#endif m_doorCloseCountdown = 0; m_rallyPoint.zero(); @@ -753,13 +751,10 @@ void OpenContain::onContaining( Object *rider, Bool wasSelected ) TheAudio->addAudioEvent(&enterSound); } - // TheSuperHackers @performance bobtista 13/11/2025 Cache hero count to avoid O(n) iteration in Object::isHero(). -#if !RETAIL_COMPATIBLE_CRC if( rider && rider->isKindOf( KINDOF_HERO ) ) { m_heroUnitsContained++; } -#endif } //------------------------------------------------------------------------------------------------- @@ -776,12 +771,10 @@ void OpenContain::onRemoving( Object *rider) fallingSound.setObjectID(rider->getID()); TheAudio->addAudioEvent(&fallingSound); -#if !RETAIL_COMPATIBLE_CRC if( rider->isKindOf( KINDOF_HERO ) && m_heroUnitsContained > 0 ) { m_heroUnitsContained--; } -#endif } } @@ -1769,6 +1762,9 @@ void OpenContain::xfer( Xfer *xfer ) // stealth units contained xfer->xferUnsignedInt( &m_stealthUnitsContained ); + // hero units contained + xfer->xferUnsignedInt( &m_heroUnitsContained ); + // door close countdown xfer->xferUnsignedInt( &m_doorCloseCountdown ); From df4e5795e3cb76c411f86efd44fb46720c468b51 Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Mon, 15 Dec 2025 17:30:58 -0500 Subject: [PATCH 05/11] perf(fps): move hero counter updates to match stealth counter pattern" --- .../Include/GameLogic/Module/ContainModule.h | 2 -- .../GameLogic/Object/Contain/OpenContain.cpp | 18 +++++++-------- .../Source/GameLogic/Object/Object.cpp | 23 ------------------- .../Include/GameLogic/Module/ContainModule.h | 2 -- .../GameLogic/Object/Contain/OpenContain.cpp | 18 +++++++-------- .../Source/GameLogic/Object/Object.cpp | 22 ------------------ 6 files changed, 16 insertions(+), 69 deletions(-) diff --git a/Generals/Code/GameEngine/Include/GameLogic/Module/ContainModule.h b/Generals/Code/GameEngine/Include/GameLogic/Module/ContainModule.h index 52c4881dc4..b62b2a7f24 100644 --- a/Generals/Code/GameEngine/Include/GameLogic/Module/ContainModule.h +++ b/Generals/Code/GameEngine/Include/GameLogic/Module/ContainModule.h @@ -155,9 +155,7 @@ class ContainModuleInterface virtual const Object *friend_getRider() const = 0; ///< Damn. The draw order dependency bug for riders means that our draw module needs to cheat to get around it. virtual Real getContainedItemsMass() const = 0; virtual UnsignedInt getStealthUnitsContained() const = 0; -#if !RETAIL_COMPATIBLE_CRC virtual UnsignedInt getHeroUnitsContained() const = 0; -#endif virtual Bool calcBestGarrisonPosition( Coord3D *sourcePos, const Coord3D *targetPos ) = 0; virtual Bool attemptBestFirePointPosition( Object *source, Weapon *weapon, Object *victim ) = 0; diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp index 62a24d3391..a183597a94 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp @@ -353,6 +353,10 @@ void OpenContain::addToContainList( Object *rider ) { m_stealthUnitsContained++; } + if( rider->isKindOf( KINDOF_HERO ) ) + { + m_heroUnitsContained++; + } } //------------------------------------------------------------------------------------------------- @@ -543,6 +547,10 @@ void OpenContain::removeFromContainViaIterator( ContainedItemsList::iterator it, } } } + if( rider->isKindOf( KINDOF_HERO ) && m_heroUnitsContained > 0 ) + { + m_heroUnitsContained--; + } if (isEnclosingContainerFor( rider )) @@ -630,11 +638,6 @@ void OpenContain::onContaining( Object *rider ) enterSound.setObjectID(getObject()->getID()); TheAudio->addAudioEvent(&enterSound); } - - if( rider && rider->isKindOf( KINDOF_HERO ) ) - { - m_heroUnitsContained++; - } } //------------------------------------------------------------------------------------------------- @@ -650,11 +653,6 @@ void OpenContain::onRemoving( Object *rider) AudioEventRTS fallingSound = *rider->getTemplate()->getSoundFalling(); fallingSound.setObjectID(rider->getID()); TheAudio->addAudioEvent(&fallingSound); - - if( rider->isKindOf( KINDOF_HERO ) && m_heroUnitsContained > 0 ) - { - m_heroUnitsContained--; - } } } diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Object.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Object.cpp index 4a82b8d8d9..f9b16b8cc8 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Object.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Object.cpp @@ -592,20 +592,6 @@ Object::~Object() TheScriptEngine->notifyOfObjectDestruction(this); } -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -#if RETAIL_COMPATIBLE_CRC -void localIsHero( Object *obj, void* userData ) -{ - Bool *hero = (Bool*)userData; - - if( obj && obj->isKindOf( KINDOF_HERO ) ) - { - *hero = TRUE; - } -} -#endif - //------------------------------------------------------------------------------------------------- // TheSuperHackers @performance bobtista 13/11/2025 Use cached hero count for O(1) lookup instead of O(n) iteration. Bool Object::isHero() const @@ -613,19 +599,10 @@ Bool Object::isHero() const ContainModuleInterface *contain = getContain(); if( contain ) { -#if !RETAIL_COMPATIBLE_CRC if( contain->getHeroUnitsContained() > 0 ) { return TRUE; } -#else - Bool heroInside = FALSE; - contain->iterateContained( localIsHero, (void*)(&heroInside), FALSE ); - if( heroInside ) - { - return TRUE; - } -#endif } return isKindOf( KINDOF_HERO ); } diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ContainModule.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ContainModule.h index 150bf8831a..5a2db53fd1 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ContainModule.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ContainModule.h @@ -175,9 +175,7 @@ class ContainModuleInterface virtual const Object *friend_getRider() const = 0; ///< Damn. The draw order dependency bug for riders means that our draw module needs to cheat to get around it. virtual Real getContainedItemsMass() const = 0; virtual UnsignedInt getStealthUnitsContained() const = 0; -#if !RETAIL_COMPATIBLE_CRC virtual UnsignedInt getHeroUnitsContained() const = 0; -#endif virtual Bool calcBestGarrisonPosition( Coord3D *sourcePos, const Coord3D *targetPos ) = 0; virtual Bool attemptBestFirePointPosition( Object *source, Weapon *weapon, Object *victim ) = 0; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp index 54d275c52c..09768aa190 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp @@ -375,6 +375,10 @@ void OpenContain::addToContainList( Object *rider ) { m_stealthUnitsContained++; } + if( rider->isKindOf( KINDOF_HERO ) ) + { + m_heroUnitsContained++; + } } //------------------------------------------------------------------------------------------------- @@ -661,6 +665,10 @@ void OpenContain::removeFromContainViaIterator( ContainedItemsList::iterator it, } } } + if( rider->isKindOf( KINDOF_HERO ) && m_heroUnitsContained > 0 ) + { + m_heroUnitsContained--; + } if (isEnclosingContainerFor( rider )) @@ -750,11 +758,6 @@ void OpenContain::onContaining( Object *rider, Bool wasSelected ) enterSound.setObjectID(getObject()->getID()); TheAudio->addAudioEvent(&enterSound); } - - if( rider && rider->isKindOf( KINDOF_HERO ) ) - { - m_heroUnitsContained++; - } } //------------------------------------------------------------------------------------------------- @@ -770,11 +773,6 @@ void OpenContain::onRemoving( Object *rider) AudioEventRTS fallingSound = *rider->getTemplate()->getSoundFalling(); fallingSound.setObjectID(rider->getID()); TheAudio->addAudioEvent(&fallingSound); - - if( rider->isKindOf( KINDOF_HERO ) && m_heroUnitsContained > 0 ) - { - m_heroUnitsContained--; - } } } diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp index 280970b377..401a8460bd 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp @@ -2042,19 +2042,6 @@ Bool Object::isNonFactionStructure(void) const return isStructure() && !isFactionStructure(); } -//------------------------------------------------------------------------------------------------- -#if RETAIL_COMPATIBLE_CRC -void localIsHero( Object *obj, void* userData ) -{ - Bool *hero = (Bool*)userData; - - if( obj && obj->isKindOf( KINDOF_HERO ) ) - { - *hero = TRUE; - } -} -#endif - //------------------------------------------------------------------------------------------------- // TheSuperHackers @performance bobtista 13/11/2025 Use cached hero count for O(1) lookup instead of O(n) iteration. Bool Object::isHero(void) const @@ -2062,19 +2049,10 @@ Bool Object::isHero(void) const ContainModuleInterface *contain = getContain(); if( contain ) { -#if !RETAIL_COMPATIBLE_CRC if( contain->getHeroUnitsContained() > 0 ) { return TRUE; } -#else - Bool heroInside = FALSE; - contain->iterateContained( localIsHero, (void*)(&heroInside), FALSE ); - if( heroInside ) - { - return TRUE; - } -#endif } return isKindOf( KINDOF_HERO ); } From 8e2f067a365ca4c9b2035f47d4c9c9e8d979f50d Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Fri, 19 Dec 2025 11:01:11 -0500 Subject: [PATCH 06/11] perf(fps): use assert for hero count check in removeFromContainViaIterator --- .../GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp index 09768aa190..31506f44fe 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp @@ -665,8 +665,9 @@ void OpenContain::removeFromContainViaIterator( ContainedItemsList::iterator it, } } } - if( rider->isKindOf( KINDOF_HERO ) && m_heroUnitsContained > 0 ) + if( rider->isKindOf( KINDOF_HERO ) ) { + DEBUG_ASSERTCRASH( m_heroUnitsContained > 0, ("Removing hero but hero count is 0") ); m_heroUnitsContained--; } From 200e0f6ae829621a436920a164bdf62e32e1ddca Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Fri, 19 Dec 2025 11:01:36 -0500 Subject: [PATCH 07/11] perf(fps): add xfer versioning for cached hero count with retail compatibility --- .../GameLogic/Object/Contain/OpenContain.cpp | 27 ++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp index 31506f44fe..a5508f13bd 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp @@ -1677,13 +1677,19 @@ void OpenContain::crc( Xfer *xfer ) // ------------------------------------------------------------------------------------------------ /** Xfer method * Version Info: - * 1: Initial version */ + * 1: Initial version + * 2: Added m_passengerAllowedToFire + * 3: Added m_heroUnitsContained cached hero count (bobtista) */ // ------------------------------------------------------------------------------------------------ void OpenContain::xfer( Xfer *xfer ) { // version - const XferVersion currentVersion = 2; +#if RETAIL_COMPATIBLE_XFER_SAVE + XferVersion currentVersion = 2; +#else + XferVersion currentVersion = 3; +#endif XferVersion version = currentVersion; xfer->xferVersion( &version, currentVersion ); @@ -1762,7 +1768,22 @@ void OpenContain::xfer( Xfer *xfer ) xfer->xferUnsignedInt( &m_stealthUnitsContained ); // hero units contained - xfer->xferUnsignedInt( &m_heroUnitsContained ); +#if !RETAIL_COMPATIBLE_XFER_SAVE + if (version >= 3) + { + xfer->xferUnsignedInt( &m_heroUnitsContained ); + } + else if (xfer->getXferMode() == XFER_LOAD) + { + m_heroUnitsContained = 0; + } +#else + // In retail compatibility mode, hero count is restored by iterating hero objects in loadPostProcess + if (xfer->getXferMode() == XFER_LOAD) + { + m_heroUnitsContained = 0; + } +#endif // door close countdown xfer->xferUnsignedInt( &m_doorCloseCountdown ); From 464efc84e6b68962b2d5cb8add20ca3ac9180602 Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Fri, 19 Dec 2025 11:01:49 -0500 Subject: [PATCH 08/11] perf(fps): restore hero count in loadPostProcess for retail compatibility --- .../Source/GameLogic/Object/Contain/OpenContain.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp index a5508f13bd..7b12b481f2 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp @@ -1924,6 +1924,18 @@ void OpenContain::loadPostProcess( void ) } +#if RETAIL_COMPATIBLE_XFER_SAVE + // In retail compatibility mode, restore hero count by iterating hero objects + m_heroUnitsContained = 0; + for( ContainedItemsList::const_iterator it = m_containList.begin(); it != m_containList.end(); ++it ) + { + if( (*it)->isKindOf( KINDOF_HERO ) ) + { + m_heroUnitsContained++; + } + } +#endif + // sanity DEBUG_ASSERTCRASH( m_containListSize == m_containList.size(), ("OpenContain::loadPostProcess - contain list count mismatch") ); From d979f922a54a73e938591abd78fae0e322118eb5 Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Fri, 19 Dec 2025 11:09:24 -0500 Subject: [PATCH 09/11] perf(fps): use assert for hero count check in removeFromContainList --- .../GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp index a183597a94..55ad71b2ef 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp @@ -547,8 +547,9 @@ void OpenContain::removeFromContainViaIterator( ContainedItemsList::iterator it, } } } - if( rider->isKindOf( KINDOF_HERO ) && m_heroUnitsContained > 0 ) + if( rider->isKindOf( KINDOF_HERO ) ) { + DEBUG_ASSERTCRASH( m_heroUnitsContained > 0, ("Removing hero but hero count is 0") ); m_heroUnitsContained--; } From e8ae78b8bb90ee260e8f566162ff655720e61f19 Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Fri, 19 Dec 2025 11:13:29 -0500 Subject: [PATCH 10/11] perf(fps): add xfer versioning for cached hero count with retail compatibility --- .../GameLogic/Object/Contain/OpenContain.cpp | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp index 55ad71b2ef..ddc70bee94 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp @@ -1537,7 +1537,22 @@ void OpenContain::xfer( Xfer *xfer ) xfer->xferUnsignedInt( &m_stealthUnitsContained ); // hero units contained - xfer->xferUnsignedInt( &m_heroUnitsContained ); +#if !RETAIL_COMPATIBLE_XFER_SAVE + if (version >= 2) + { + xfer->xferUnsignedInt( &m_heroUnitsContained ); + } + else if (xfer->getXferMode() == XFER_LOAD) + { + m_heroUnitsContained = 0; + } +#else + // In retail compatibility mode, hero count is restored by iterating hero objects in loadPostProcess + if (xfer->getXferMode() == XFER_LOAD) + { + m_heroUnitsContained = 0; + } +#endif // door close countdown xfer->xferUnsignedInt( &m_doorCloseCountdown ); From df0caad1c2502e475196402518270156cf0fa49b Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Fri, 19 Dec 2025 11:13:53 -0500 Subject: [PATCH 11/11] perf(fps): restore hero count in loadPostProcess for retail compatibility --- .../GameLogic/Object/Contain/OpenContain.cpp | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp index ddc70bee94..72dd98ff8f 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp @@ -1452,13 +1452,18 @@ void OpenContain::crc( Xfer *xfer ) // ------------------------------------------------------------------------------------------------ /** Xfer method * Version Info: - * 1: Initial version */ + * 1: Initial version + * 2: Added m_heroUnitsContained cached hero count (bobtista) */ // ------------------------------------------------------------------------------------------------ void OpenContain::xfer( Xfer *xfer ) { // version - const XferVersion currentVersion = 1; +#if RETAIL_COMPATIBLE_XFER_SAVE + XferVersion currentVersion = 1; +#else + XferVersion currentVersion = 2; +#endif XferVersion version = currentVersion; xfer->xferVersion( &version, currentVersion ); @@ -1686,6 +1691,18 @@ void OpenContain::loadPostProcess( void ) } +#if RETAIL_COMPATIBLE_XFER_SAVE + // In retail compatibility mode, restore hero count by iterating hero objects + m_heroUnitsContained = 0; + for( ContainedItemsList::const_iterator it = m_containList.begin(); it != m_containList.end(); ++it ) + { + if( (*it)->isKindOf( KINDOF_HERO ) ) + { + m_heroUnitsContained++; + } + } +#endif + // sanity DEBUG_ASSERTCRASH( m_containListSize == m_containList.size(), ("OpenContain::loadPostProcess - contain list count mismatch") );