@@ -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