@@ -1274,20 +1274,17 @@ void OpenContain::orderAllPassengersToExit( CommandSourceType commandSource )
1274
1274
// -------------------------------------------------------------------------------------------------
1275
1275
void OpenContain::processDamageToContained ()
1276
1276
{
1277
+ #if RETAIL_COMPATIBLE_CRC
1278
+
1277
1279
const ContainedItemsList* items = getContainedItemsList ();
1278
1280
if ( items )
1279
1281
{
1280
- ContainedItemsList::const_iterator it;
1281
- it = items->begin ();
1282
+ ContainedItemsList::const_iterator it = items-> begin () ;
1283
+ const size_t listSize = items->size ();
1282
1284
1283
1285
while ( it != items->end () )
1284
1286
{
1285
- Object *object = *it;
1286
-
1287
- // Advance to the next iterator before we apply the damage.
1288
- // It's possible that the damage will kill the unit and foobar
1289
- // the iterator list.
1290
- ++it;
1287
+ Object *object = *it++;
1291
1288
1292
1289
// Calculate the damage to be inflicted on each unit.
1293
1290
Real damage = object->getBodyModule ()->getMaxHealth () * getOpenContainModuleData ()->m_damagePercentageToUnits ;
@@ -1300,9 +1297,82 @@ void OpenContain::processDamageToContained()
1300
1297
object->attemptDamage ( &damageInfo );
1301
1298
1302
1299
if ( !object->isEffectivelyDead () && getOpenContainModuleData ()->m_damagePercentageToUnits == 1 .0f )
1303
- object->kill (); // in case we are carrying flame proof troops we have been asked to kill
1300
+ object->kill (); // in case we are carrying flame proof troops we have been asked to kill
1301
+
1302
+ // TheSuperHackers @info Calls to Object::attemptDamage and Object::kill will not remove
1303
+ // the occupant from the host container straight away. Instead it will be removed when the
1304
+ // Object deletion is finalized in a Game Logic update. This will lead to strange behavior
1305
+ // where the occupant will be removed after death with a delay. This behavior cannot be
1306
+ // changed without breaking retail compatibility.
1307
+
1308
+ // TheSuperHackers @bugfix xezon 05/06/2025 Stop iterating when the list was cleared.
1309
+ // This scenario can happen if the killed occupant(s) apply deadly damage on death
1310
+ // to the host container, which then attempts to remove all remaining occupants
1311
+ // on the death of the host container. This is reproducible by destroying a
1312
+ // GLA Battle Bus with at least 2 half damaged GLA Terrorists inside.
1313
+ if (listSize != items->size ())
1314
+ {
1315
+ DEBUG_ASSERTCRASH ( listSize == 0 , (" List is expected empty\n " ) );
1316
+ break ;
1317
+ }
1318
+ }
1319
+ }
1320
+
1321
+ #else
1322
+
1323
+ // TheSuperHackers @bugfix xezon 05/06/2025 Temporarily empty the m_containList
1324
+ // to prevent a potential child call to catastrophically modify the m_containList.
1325
+ // This scenario can happen if the killed occupant(s) apply deadly damage on death
1326
+ // to the host container, which then attempts to remove all remaining occupants
1327
+ // on the death of the host container. This is reproducible by destroying a
1328
+ // GLA Battle Bus with at least 2 half damaged GLA Terrorists inside.
1329
+
1330
+ // Caveat: While the m_containList is empty, it will not be possible to apply damage
1331
+ // on death of a unit to another unit in the host container. If this functionality
1332
+ // is desired, then this implementation needs to be revisited.
1333
+
1334
+ ContainedItemsList list;
1335
+ m_containList.swap (list);
1336
+ m_containListSize = 0 ;
1337
+
1338
+ ContainedItemsList::iterator it = list.begin ();
1339
+
1340
+ while ( it != list.end () )
1341
+ {
1342
+ Object *object = *it;
1343
+
1344
+ DEBUG_ASSERTCRASH ( object, (" Contain list must not contain NULL element\n " ) );
1345
+
1346
+ // Calculate the damage to be inflicted on each unit.
1347
+ Real damage = object->getBodyModule ()->getMaxHealth () * percentDamage;
1348
+
1349
+ DamageInfo damageInfo;
1350
+ damageInfo.in .m_damageType = DAMAGE_UNRESISTABLE;
1351
+ damageInfo.in .m_deathType = data->m_isBurnedDeathToUnits ? DEATH_BURNED : DEATH_NORMAL;
1352
+ damageInfo.in .m_sourceID = getObject ()->getID ();
1353
+ damageInfo.in .m_amount = damage;
1354
+ object->attemptDamage ( &damageInfo );
1355
+
1356
+ if ( !object->isEffectivelyDead () && percentDamage == 1 .0f )
1357
+ object->kill (); // in case we are carrying flame proof troops we have been asked to kill
1358
+
1359
+ if ( object->isEffectivelyDead () )
1360
+ {
1361
+ onRemoving ( object );
1362
+ object->onRemovedFrom ( getObject () );
1363
+ it = list.erase ( it );
1364
+ }
1365
+ else
1366
+ {
1367
+ ++it;
1304
1368
}
1305
1369
}
1370
+
1371
+ // Swap the list back where it belongs.
1372
+ m_containList.swap (list);
1373
+ m_containListSize = (UnsignedInt)m_containList.size ();
1374
+
1375
+ #endif // RETAIL_COMPATIBLE_CRC
1306
1376
}
1307
1377
1308
1378
// -------------------------------------------------------------------------------------------------
0 commit comments