@@ -1462,71 +1462,80 @@ void OpenContain::orderAllPassengersToHackInternet( CommandSourceType commandSou
14621462 }
14631463}
14641464
1465-
1465+ # if RETAIL_COMPATIBLE_CRC
14661466
14671467// -------------------------------------------------------------------------------------------------
1468- void OpenContain::processDamageToContained ( Real percentDamage)
1468+ void OpenContain::processDamageToContainedInternal (Object* const * objects, size_t size, Real percentDamage)
14691469{
1470- const OpenContainModuleData *data = getOpenContainModuleData ();
1470+ const DeathType deathType = getOpenContainModuleData ()-> m_isBurnedDeathToUnits ? DEATH_BURNED : DEATH_NORMAL ;
14711471 const bool killContained = percentDamage == 1 .0f ;
14721472
1473+ for (size_t i = 0 ; i < size; ++i)
1474+ {
1475+ Object* object = objects[i];
1476+
1477+ // Calculate the damage to be inflicted on each unit.
1478+ Real damage = object->getBodyModule ()->getMaxHealth () * percentDamage;
1479+
1480+ DamageInfo damageInfo;
1481+ damageInfo.in .m_damageType = DAMAGE_UNRESISTABLE;
1482+ damageInfo.in .m_deathType = deathType;
1483+ damageInfo.in .m_sourceID = getObject ()->getID ();
1484+ damageInfo.in .m_amount = damage;
1485+ object->attemptDamage ( &damageInfo );
1486+
1487+ if ( !object->isEffectivelyDead () && killContained )
1488+ object->kill (); // in case we are carrying flame proof troops we have been asked to kill
1489+
1490+ // TheSuperHackers @info Calls to Object::attemptDamage and Object::kill may not remove
1491+ // the occupant from the host container straight away. Instead it would be removed when the
1492+ // Object deletion is finalized in a Game Logic update. This will lead to strange behavior
1493+ // where the occupant will be removed after death with a delay. This behavior cannot be
1494+ // changed without breaking retail compatibility.
1495+ }
1496+ }
1497+
1498+ #endif // RETAIL_COMPATIBLE_CRC
1499+
1500+ // -------------------------------------------------------------------------------------------------
1501+ void OpenContain::processDamageToContained (Real percentDamage)
1502+ {
14731503#if RETAIL_COMPATIBLE_CRC
14741504
1475- const ContainedItemsList* items = getContainedItemsList ();
1476- if ( items )
1505+ DEBUG_ASSERTCRASH (m_containListSize == m_containList.size (), (" contain list size doesn't match size of container" ));
1506+
1507+ // TheSuperHackers @bugfix Caball009 11/03/2026 Use a temporary copy of the contain list to iterate over,
1508+ // because causing damage to the occupants may remove some or all elements from the list
1509+ // while iterating over it, which may be unsafe.
1510+
1511+ constexpr const UnsignedInt smallContainerSize = 16 ;
1512+ if (m_containListSize < smallContainerSize)
14771513 {
1478- ContainedItemsList::const_iterator it = items-> begin () ;
1479- const size_t listSize = items-> size ( );
1514+ Object* containCopy[smallContainerSize] ;
1515+ std::copy (m_containList. begin (), m_containList. end (), containCopy );
14801516
1481- while ( it != items->end () )
1482- {
1483- Object *object = *it++;
1484-
1485- // Calculate the damage to be inflicted on each unit.
1486- Real damage = object->getBodyModule ()->getMaxHealth () * percentDamage;
1487-
1488- DamageInfo damageInfo;
1489- damageInfo.in .m_damageType = DAMAGE_UNRESISTABLE;
1490- damageInfo.in .m_deathType = data->m_isBurnedDeathToUnits ? DEATH_BURNED : DEATH_NORMAL;
1491- damageInfo.in .m_sourceID = getObject ()->getID ();
1492- damageInfo.in .m_amount = damage;
1493- object->attemptDamage ( &damageInfo );
1494-
1495- if ( !object->isEffectivelyDead () && killContained )
1496- object->kill (); // in case we are carrying flame proof troops we have been asked to kill
1497-
1498- // TheSuperHackers @info Calls to Object::attemptDamage and Object::kill will not remove
1499- // the occupant from the host container straight away. Instead it will be removed when the
1500- // Object deletion is finalized in a Game Logic update. This will lead to strange behavior
1501- // where the occupant will be removed after death with a delay. This behavior cannot be
1502- // changed without breaking retail compatibility.
1503-
1504- // TheSuperHackers @bugfix xezon 05/06/2025 Stop iterating when the list was cleared.
1505- // This scenario can happen if the killed occupant(s) apply deadly damage on death
1506- // to the host container, which then attempts to remove all remaining occupants
1507- // on the death of the host container. This is reproducible by destroying a
1508- // GLA Battle Bus with at least 2 half damaged GLA Terrorists inside.
1509- if (listSize != items->size ())
1510- {
1511- DEBUG_ASSERTCRASH ( listSize == 0 , (" List is expected empty" ) );
1512- break ;
1513- }
1514- }
1517+ processDamageToContainedInternal (containCopy, m_containListSize, percentDamage);
1518+ }
1519+ else
1520+ {
1521+ const std::vector<Object*> containCopy (m_containList.begin (), m_containList.end ());
1522+
1523+ processDamageToContainedInternal (&containCopy[0 ], containCopy.size (), percentDamage);
15151524 }
15161525
15171526#else
15181527
15191528 // TheSuperHackers @bugfix xezon 05/06/2025 Temporarily empty the m_containList
1520- // to prevent a potential child call to catastrophically modify the m_containList.
1521- // This scenario can happen if the killed occupant(s) apply deadly damage on death
1522- // to the host container, which then attempts to remove all remaining occupants
1523- // on the death of the host container. This is reproducible by destroying a
1524- // GLA Battle Bus with at least 2 half damaged GLA Terrorists inside.
1529+ // because causing damage to the occupants may remove some or all elements from the list
1530+ // while iterating over it, which may be unsafe.
15251531
15261532 // Caveat: While the m_containList is empty, it will not be possible to apply damage
15271533 // on death of a unit to another unit in the host container. If this functionality
15281534 // is desired, then this implementation needs to be revisited.
15291535
1536+ const DeathType deathType = getOpenContainModuleData ()->m_isBurnedDeathToUnits ? DEATH_BURNED : DEATH_NORMAL;
1537+ const bool killContained = percentDamage == 1 .0f ;
1538+
15301539 ContainedItemsList list;
15311540 m_containList.swap (list);
15321541 m_containListSize = 0 ;
@@ -1544,7 +1553,7 @@ void OpenContain::processDamageToContained(Real percentDamage)
15441553
15451554 DamageInfo damageInfo;
15461555 damageInfo.in .m_damageType = DAMAGE_UNRESISTABLE;
1547- damageInfo.in .m_deathType = data-> m_isBurnedDeathToUnits ? DEATH_BURNED : DEATH_NORMAL ;
1556+ damageInfo.in .m_deathType = deathType ;
15481557 damageInfo.in .m_sourceID = getObject ()->getID ();
15491558 damageInfo.in .m_amount = damage;
15501559 object->attemptDamage ( &damageInfo );
0 commit comments