Skip to content

Commit 9ad8a44

Browse files
committed
Replicated in Generals (manually).
1 parent a725544 commit 9ad8a44

2 files changed

Lines changed: 57 additions & 44 deletions

File tree

Generals/Code/GameEngine/Include/GameLogic/Module/OpenContain.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,9 @@ class OpenContain : public UpdateModule,
205205
virtual Bool hasObjectsWantingToEnterOrExit() const override;
206206

207207
virtual void processDamageToContained(Real percentDamage) override; ///< Do our % damage to units now.
208+
#if RETAIL_COMPATIBLE_CRC
209+
void processDamageToContainedInternal(Object* const* objects, size_t size, Real percentDamage);
210+
#endif
208211

209212
virtual void enableLoadSounds( Bool enable ) override { m_loadSoundsEnabled = enable; }
210213

Generals/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp

Lines changed: 54 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1296,68 +1296,78 @@ void OpenContain::orderAllPassengersToExit( CommandSourceType commandSource )
12961296
}
12971297
}
12981298

1299+
#if RETAIL_COMPATIBLE_CRC
1300+
12991301
//-------------------------------------------------------------------------------------------------
1300-
void OpenContain::processDamageToContained(Real percentDamage)
1302+
void OpenContain::processDamageToContainedInternal(Object* const* objects, size_t size, Real percentDamage)
13011303
{
13021304
const bool killContained = percentDamage == 1.0f;
13031305

1306+
for (size_t i = 0; i < size; ++i)
1307+
{
1308+
Object* object = objects[i];
1309+
1310+
// Calculate the damage to be inflicted on each unit.
1311+
Real damage = object->getBodyModule()->getMaxHealth() * percentDamage;
1312+
1313+
DamageInfo damageInfo;
1314+
damageInfo.in.m_damageType = DAMAGE_UNRESISTABLE;
1315+
damageInfo.in.m_deathType = DEATH_BURNED;
1316+
damageInfo.in.m_sourceID = getObject()->getID();
1317+
damageInfo.in.m_amount = damage;
1318+
object->attemptDamage( &damageInfo );
1319+
1320+
if( !object->isEffectivelyDead() && killContained )
1321+
object->kill(); // in case we are carrying flame proof troops we have been asked to kill
1322+
1323+
// TheSuperHackers @info Calls to Object::attemptDamage and Object::kill may not remove
1324+
// the occupant from the host container straight away. Instead it would be removed when the
1325+
// Object deletion is finalized in a Game Logic update. This will lead to strange behavior
1326+
// where the occupant will be removed after death with a delay. This behavior cannot be
1327+
// changed without breaking retail compatibility.
1328+
}
1329+
}
1330+
1331+
#endif // RETAIL_COMPATIBLE_CRC
1332+
1333+
//-------------------------------------------------------------------------------------------------
1334+
void OpenContain::processDamageToContained(Real percentDamage)
1335+
{
13041336
#if RETAIL_COMPATIBLE_CRC
13051337

1306-
const ContainedItemsList* items = getContainedItemsList();
1307-
if( items )
1338+
DEBUG_ASSERTCRASH(m_containListSize == m_containList.size(), ("contain list size doesn't match size of container"));
1339+
1340+
// TheSuperHackers @bugfix Caball009 11/03/2026 Use a temporary copy of the contain list to iterate over,
1341+
// because causing damage to the occupants may remove some or all elements from the list
1342+
// while iterating over it, which may be unsafe.
1343+
1344+
constexpr const UnsignedInt smallContainerSize = 16;
1345+
if (m_containListSize < smallContainerSize)
13081346
{
1309-
ContainedItemsList::const_iterator it = items->begin();
1310-
const size_t listSize = items->size();
1347+
Object* containCopy[smallContainerSize];
1348+
std::copy(m_containList.begin(), m_containList.end(), containCopy);
13111349

1312-
while( it != items->end() )
1313-
{
1314-
Object *object = *it++;
1315-
1316-
//Calculate the damage to be inflicted on each unit.
1317-
Real damage = object->getBodyModule()->getMaxHealth() * percentDamage;
1318-
1319-
DamageInfo damageInfo;
1320-
damageInfo.in.m_damageType = DAMAGE_UNRESISTABLE;
1321-
damageInfo.in.m_deathType = DEATH_BURNED;
1322-
damageInfo.in.m_sourceID = getObject()->getID();
1323-
damageInfo.in.m_amount = damage;
1324-
object->attemptDamage( &damageInfo );
1325-
1326-
if( !object->isEffectivelyDead() && killContained )
1327-
object->kill(); // in case we are carrying flame proof troops we have been asked to kill
1328-
1329-
// TheSuperHackers @info Calls to Object::attemptDamage and Object::kill will not remove
1330-
// the occupant from the host container straight away. Instead it will be removed when the
1331-
// Object deletion is finalized in a Game Logic update. This will lead to strange behavior
1332-
// where the occupant will be removed after death with a delay. This behavior cannot be
1333-
// changed without breaking retail compatibility.
1334-
1335-
// TheSuperHackers @bugfix xezon 05/06/2025 Stop iterating when the list was cleared.
1336-
// This scenario can happen if the killed occupant(s) apply deadly damage on death
1337-
// to the host container, which then attempts to remove all remaining occupants
1338-
// on the death of the host container. This is reproducible by destroying a
1339-
// GLA Battle Bus with at least 2 half damaged GLA Terrorists inside.
1340-
if (listSize != items->size())
1341-
{
1342-
DEBUG_ASSERTCRASH( listSize == 0, ("List is expected empty") );
1343-
break;
1344-
}
1345-
}
1350+
processDamageToContainedInternal(containCopy, m_containListSize, percentDamage);
1351+
}
1352+
else
1353+
{
1354+
const std::vector<Object*> containCopy(m_containList.begin(), m_containList.end());
1355+
1356+
processDamageToContainedInternal(&containCopy[0], containCopy.size(), percentDamage);
13461357
}
13471358

13481359
#else
13491360

13501361
// TheSuperHackers @bugfix xezon 05/06/2025 Temporarily empty the m_containList
1351-
// to prevent a potential child call to catastrophically modify the m_containList.
1352-
// This scenario can happen if the killed occupant(s) apply deadly damage on death
1353-
// to the host container, which then attempts to remove all remaining occupants
1354-
// on the death of the host container. This is reproducible by destroying a
1355-
// GLA Battle Bus with at least 2 half damaged GLA Terrorists inside.
1362+
// because causing damage to the occupants may remove some or all elements from the list
1363+
// while iterating over it, which may be unsafe.
13561364

13571365
// Caveat: While the m_containList is empty, it will not be possible to apply damage
13581366
// on death of a unit to another unit in the host container. If this functionality
13591367
// is desired, then this implementation needs to be revisited.
13601368

1369+
const bool killContained = percentDamage == 1.0f;
1370+
13611371
ContainedItemsList list;
13621372
m_containList.swap(list);
13631373
m_containListSize = 0;

0 commit comments

Comments
 (0)