Skip to content

bugfix: Prevent dead units from repeatedly dealing crash damage on collision with non-buildings#2204

Open
Stubbjax wants to merge 5 commits into
TheSuperHackers:mainfrom
Stubbjax:fix-dead-unit-crash-damage
Open

bugfix: Prevent dead units from repeatedly dealing crash damage on collision with non-buildings#2204
Stubbjax wants to merge 5 commits into
TheSuperHackers:mainfrom
Stubbjax:fix-dead-unit-crash-damage

Conversation

@Stubbjax
Copy link
Copy Markdown

@Stubbjax Stubbjax commented Jan 28, 2026

Closes #2015

This change fixes an issue where dead vehicles repeatedly trigger VehicleCrashesIntoNonBuildingWeapon when colliding with mines, causing continuous damage to nearby units and structures.

This weapon is hardcoded into the physics update, and is fired when a vehicle falls onto a non-building object - the intent being that a vehicle deals some 'crush' damage to a victim it lands on. It is especially problematic in the case of mines as the mines cannot destroy the already-dead vehicle. The issue is most apparent with Technicals due to their frequent involvement in bombing runs and their tendency to fling themselves forwards upon death.

The fix makes the weapon only deal damage to the collided target.

There is also a building-specific crash block that fires VehicleCrashesIntoBuildingWeapon when a vehicle crashes into a building, however this results in the immediate destruction of the crashing vehicle, and so is not as problematic.

Before

The Propaganda Center dies due to dead Technicals firing VehicleCrashesIntoNonBuildingWeapon every frame

YES_DAMAGE.mp4

After

The Propaganda Center survives unscathed as VehicleCrashesIntoNonBuildingWeapon only targets the mines

NO_DAMAGE.mp4

@Stubbjax Stubbjax self-assigned this Jan 28, 2026
@Stubbjax Stubbjax added Bug Something is not working right, typically is user facing Minor Severity: Minor < Major < Critical < Blocker Gen Relates to Generals ZH Relates to Zero Hour NoRetail This fix or change is not applicable with Retail game compatibility labels Jan 28, 2026
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Jan 28, 2026

Greptile Summary

This PR fixes a bug where dead vehicles (most notably Technicals post-bombing-run) repeatedly triggered VehicleCrashesIntoNonBuildingWeapon as an area-of-effect event on every physics collision frame, dealing continuous splash damage to nearby structures. The fix replaces the createAndFireTempWeapon call (which blasts everything in AoE radius) with a direct attemptDamage call limited to the collided target only, gated behind #if !RETAIL_COMPATIBLE_CRC to preserve CRC compatibility with the original game binary.

  • The new code correctly mirrors the DamageInfo population pattern from Weapon.cpp, including m_sourcePlayerMask, m_sourceID, m_damageType, m_deathType, and m_amount, and uses the WeaponBonus default constructor (all fields initialised to 1.0f) as a neutral multiplier.
  • Both Generals/ and GeneralsMD/ (Zero Hour) files receive identical changes, and null checks are added for the weapon template and controlling player.

Confidence Score: 5/5

Safe to merge; the fix is narrowly scoped, gated behind a compile-time flag, and correctly replicates the DamageInfo population pattern used elsewhere in the weapon system.

The change replaces a single AoE weapon-fire call with a targeted damage call. DamageInfo is fully and correctly populated, the WeaponBonus default constructor initialises all multipliers to 1.0, null guards are in place for both the weapon template and the controlling player, and FXList::doFXObj already handles a null FX list internally.

No files require special attention.

Important Files Changed

Filename Overview
Generals/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp Replaces createAndFireTempWeapon (AoE) with a targeted attemptDamage call for non-building vehicle crashes; null-checked, properly populates DamageInfo fields including m_sourcePlayerMask, and adds FX via the null-safe FXList::doFXObj helper.
GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp Identical fix applied to the Zero Hour copy of PhysicsUpdate.cpp; change is a character-for-character mirror of the Generals/ version.

Reviews (5): Last reviewed commit: "tweak: Assign source player mask" | Re-trigger Greptile

@Mauller
Copy link
Copy Markdown

Mauller commented Jan 28, 2026

This doesn't properly resolve #2015, the issue with aircraft landing on mines is that the aircraftSlowDeathBehaviour module for the chinook is waiting to see when the unit hits the ground. But it never does and remains floating over the mines above the ground detection threshold.

The issue is that the mines block the slow death code from detecting when the chinook hits the ground properly and prevents the carcus from ever clearing itself.

@Stubbjax
Copy link
Copy Markdown
Author

This doesn't properly resolve #2015, the issue with aircraft landing on mines is that the aircraftSlowDeathBehaviour module for the chinook is waiting to see when the unit hits the ground. But it never does and remains floating over the mines above the ground detection threshold.

The issue is that the mines block the slow death code from detecting when the chinook hits the ground properly and prevents the carcus from ever clearing itself.

That sounds like a separate issue? This fix targets the issue as described; "GLA Technical kills/damages building when dying by China Mines".

@Mauller
Copy link
Copy Markdown

Mauller commented Jan 28, 2026

This doesn't properly resolve #2015, the issue with aircraft landing on mines is that the aircraftSlowDeathBehaviour module for the chinook is waiting to see when the unit hits the ground. But it never does and remains floating over the mines above the ground detection threshold.
The issue is that the mines block the slow death code from detecting when the chinook hits the ground properly and prevents the carcus from ever clearing itself.

That sounds like a separate issue? This fix targets the issue as described; "GLA Technical kills/damages building when dying by China Mines".

Ah yeah, i glanced over it and read the bit about migs and chinooks in the issue as well.

The other week Fish and i were looking into the same issue but with aircraft and it is a problem with the death behaviour modules.

I don't think your change here will fix their behaviour since they are never properly set to being effectively dead since they remain stuck in their death crash handling.

@Stubbjax
Copy link
Copy Markdown
Author

I don't think your change here will fix their behaviour since they are never properly set to being effectively dead since they remain stuck in their death crash handling.

Is effectively dead not exactly the status that should apply here? As far as I'm aware that status is applied as soon as health becomes depleted.

@Mauller
Copy link
Copy Markdown

Mauller commented Feb 3, 2026

I don't think your change here will fix their behaviour since they are never properly set to being effectively dead since they remain stuck in their death crash handling.

Is effectively dead not exactly the status that should apply here? As far as I'm aware that status is applied as soon as health becomes depleted.

I would need to check again, have you seen if this also prevents falling planes and helicopters applying continuous damage when they land on mines?

Or checked if falling planes and helicopters apply any damage when landing on things?

@Stubbjax
Copy link
Copy Markdown
Author

Stubbjax commented Feb 3, 2026

I would need to check again, have you seen if this also prevents falling planes and helicopters applying continuous damage when they land on mines?

This will affect anything that would have previously repeatedly applied VehicleCrashesIntoNonBuildingWeapon on collision with mines. This should include aircraft as they all have the necessary VEHICLE KindOf, but I have not explicitly tested it.

if (obj->isKindOf(KINDOF_VEHICLE))
#else
// TheSuperHackers @bugfix Stubbjax 28/01/2026 Prevent dead units from repeatedly dealing damage.
if (obj->isKindOf(KINDOF_VEHICLE) && !obj->isEffectivelyDead())
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suspect this fix is not correct. I put a breakpoint into this branch and on the very first hit the Object is already dead, which means that this weapon now never fires once.

Image

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you clarify what you mean? You have described the fix as intended, which is to prevent crash damage from triggering if the object is effectively dead.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok how do we know the intent is to not apply damage once when dead technical crashes?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do not. We can only say the new intent is to not have it deal any damage in order to improve gameplay.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I expect that a dead vehicle crashing into something can apply damage once. For example if a plane is shot and its rubble hits the ground, then it applies collision damage once. Otherwise only alive vehicles would cause crash damage, which may not be intuitive for players. This is also more in line with what the original code did (incorrectly).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest add 1 new bit flag to PhysicsBehavior, default it to 1, check if it is 1 before one of the createAndFireTempWeapon, and then set it to 0 afterwards.

This way, this physics weapon can only ever fire once.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When should it be reset to 1?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check if there is a way to determine if a collision event is "new". I suspect PhysicsBehavior::onCollide keeps being called every frame because the object is not pushed away from the collider, but keeps intersecting while flying through it.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm still struggling to understand why such behaviour is desirable. Currently, if a dead unit falls on a non-building obstacle (e.g. rocks, mines, shipping containers, etc.), then it deals crush damage in a radius while in contact. Even if we reduce this to a single damage instance per obstacle, the behaviour is still effectively arbitrary and can yield confusing and unexpected results.

THE_ROCKS.mp4

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So instead perhaps disable the crash damages entirely?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the same test exist for this damage?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would probably be better. But as a separate change?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the repro is unknown or very different for the other, then maybe make separate change.

@Piddox
Copy link
Copy Markdown

Piddox commented Apr 6, 2026

This seems to have hit a stall. I would like to showcase how badly this bug impacts competitive gameplay in GO currently, by showing some clips I've saved over the last weeks:

Marakar vs TumStep in ZH League, prize pool $280:
WF dying to its own mines

MiKwArY vs BoYcaH^, same tournament ZH League:
 stupid unfair bugs

Kill toll vs Raging_K, sponsored challenge by Yamen (they ended up replaying this game due to the bug):
mines bug

Same set, next game:
mines bug

And some ffg examples:
Massacre vs Logica:

2026-04-17.14-20-22.mp4

Rudwan vs Logica (instant ragequit):

2026-04-06.11-30-40.mp4

Memphis vs Rusty:

2026-04-06.11-29-39.mp4

Mamo in 3v3 50k TW:

2026-04-04.21-59-26.mp4

I could go on, this is just a fraction of the times I've seen it happening. Honestly, it's a plague infesting GO competitive gameplay, and since players don't know any better, it's bad for the reputation of both GO and TSH.

It appears we have a fix ready to be deployed here. It's just not completely clear whether the collision damage should not be applied at all (which this fix does), or whether the intended behavior of the EA devs was to have collision damage to occur once.

Would it be possible to continue that debate in a new issue, whilst merging this one to main, as a temporary fix, so that the players at least can enjoy this bug fix and not be confronted by this heavily game-impacting bug behavior?

@xezon
Copy link
Copy Markdown

xezon commented Apr 18, 2026

Needs a decision. Current fix does not appear to be satisfying.

@Stubbjax
Copy link
Copy Markdown
Author

Needs a decision. Current fix does not appear to be satisfying.

I think the ideal solution would be to forgo the isEffectivelyDead check and instead apply damage directly to the collided object(s) rather than spawning a weapon template.

So instead of:

TheWeaponStore->createAndFireTempWeapon(getPhysicsBehaviorModuleData()->m_vehicleCrashesIntoNonBuildingWeaponTemplate, obj, obj->getPosition());

We would have:

const WeaponTemplate* weaponTemplate = getPhysicsBehaviorModuleData()->m_vehicleCrashesIntoNonBuildingWeaponTemplate;

DamageInfo damageInfo;
damageInfo.in.m_damageType = weaponTemplate->getDamageType();
damageInfo.in.m_deathType = weaponTemplate->getDeathType();
damageInfo.in.m_sourceID = obj->getID();
damageInfo.in.m_amount = weaponTemplate->m_primaryDamage;

other->attemptDamage(&damageInfo);

This would mean only rocks or mines or whatever other prop objects would receive the damage directly, while nearby units and buildings remain unaffected. We could think about doing this for the building crash too, to avoid the inconsistent damage application entirely. We can then look at implementing dedicated death weapons for crashed aircraft further down the track.

Thoughts?

@xezon
Copy link
Copy Markdown

xezon commented Apr 19, 2026

Can crashed units collide with multiple units or just ever one?

@Stubbjax
Copy link
Copy Markdown
Author

Can crashed units collide with multiple units or just ever one?

Well it wouldn't be units, but yes, I imagine any applicable obstacles within the geometry bounds would run through onCollide.

@xezon
Copy link
Copy Markdown

xezon commented Apr 19, 2026

It appears logical to only apply damage to directly collided things. It just needs to make sure to not re-apply the damage on every repeated collision event.

@Stubbjax
Copy link
Copy Markdown
Author

It appears logical to only apply damage to directly collided things. It just needs to make sure to not re-apply the damage on every repeated collision event.

Does it matter if it does? We don't know what object(s) might be in the way. If we only apply e.g. 30 damage once to a 100-HP object, then the object will remain and potentially block the dead object.

@xezon
Copy link
Copy Markdown

xezon commented Apr 19, 2026

But isn't the repeated damage exactly the problem here? A dead vehicle falling onto a building will not be pushed off the building. It will continueosly collide and spam damage events every logic frame.

@Stubbjax
Copy link
Copy Markdown
Author

But isn't the repeated damage exactly the problem here? A dead vehicle falling onto a building will not be pushed off the building. It will continueosly collide and spam damage events every logic frame.

If the object collides with a building, the object is destroyed. The problem occurs when the object collides with non-building obstacles and spawns damage instances during those collisions which affect other nearby objects.

@xezon
Copy link
Copy Markdown

xezon commented Apr 19, 2026

Ok. You can make a POC for this and then we see how that looks.

@Stubbjax
Copy link
Copy Markdown
Author

Ok. You can make a POC for this and then we see how that looks.

Pushed an update. This essentially yields the same benefits as the original fix, with the added bonus that falling dead objects don't get stuck above destructible non-building obstacles.

Before

Colliding with an obstacle deals damage to nearby objects

LIX_CRASH_BEFORE.mp4

After

Colliding with an obstacle only deals damage to collided objects

LIX_CRASH_AFTER.mp4

@xezon xezon added Major Severity: Minor < Major < Critical < Blocker and removed Minor Severity: Minor < Major < Critical < Blocker labels Apr 20, 2026
Copy link
Copy Markdown

@xezon xezon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you also prepare a change for the crash damage on buildings?

Comment thread Generals/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp Outdated
@Stubbjax
Copy link
Copy Markdown
Author

Can you also prepare a change for the crash damage on buildings?

As part of this change or separately?

@xezon
Copy link
Copy Markdown

xezon commented Apr 30, 2026

As part of this change or separately?

Can be separate if it requires seperate description.

@Piddox
Copy link
Copy Markdown

Piddox commented May 9, 2026

I see green ticks everywhere. Is there anything else needed for this to be merged into main?

@xezon
Copy link
Copy Markdown

xezon commented May 9, 2026

I was waiting for the partner fix to merge them together

Comment on lines +1269 to +1279
// TheSuperHackers @bugfix Stubbjax 19/04/2026 Prevent non-building collisions from repeatedly dealing collateral damage to other objects.
const WeaponTemplate* weaponTemplate = getPhysicsBehaviorModuleData()->m_vehicleCrashesIntoNonBuildingWeaponTemplate;
if (weaponTemplate != nullptr)
{
WeaponBonus nullBonus;

DamageInfo damageInfo;
damageInfo.in.m_damageType = weaponTemplate->getDamageType();
damageInfo.in.m_deathType = weaponTemplate->getDeathType();
damageInfo.in.m_sourceID = obj->getID();
damageInfo.in.m_amount = weaponTemplate->getPrimaryDamage(nullBonus);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 The DamageInfo is populated without setting m_sourcePlayerMask. In the standard weapon path (dealDamageInternal), this field is populated from the source object's controlling player. Object.cpp reads it to decide whether to trigger a radar alert for the locally-controlled owner, and ScriptConditions.cpp uses it to resolve which player last damaged a unit. With the field left at its default of 0, crash-damage hits will skip radar warnings on the victim's side and any script trigger keyed on "attacked by player X" will fail to identify the attacker.

Suggested change
// TheSuperHackers @bugfix Stubbjax 19/04/2026 Prevent non-building collisions from repeatedly dealing collateral damage to other objects.
const WeaponTemplate* weaponTemplate = getPhysicsBehaviorModuleData()->m_vehicleCrashesIntoNonBuildingWeaponTemplate;
if (weaponTemplate != nullptr)
{
WeaponBonus nullBonus;
DamageInfo damageInfo;
damageInfo.in.m_damageType = weaponTemplate->getDamageType();
damageInfo.in.m_deathType = weaponTemplate->getDeathType();
damageInfo.in.m_sourceID = obj->getID();
damageInfo.in.m_amount = weaponTemplate->getPrimaryDamage(nullBonus);
// TheSuperHackers @bugfix Stubbjax 19/04/2026 Prevent non-building collisions from repeatedly dealing collateral damage to other objects.
const WeaponTemplate* weaponTemplate = getPhysicsBehaviorModuleData()->m_vehicleCrashesIntoNonBuildingWeaponTemplate;
if (weaponTemplate != nullptr)
{
WeaponBonus nullBonus;
DamageInfo damageInfo;
damageInfo.in.m_damageType = weaponTemplate->getDamageType();
damageInfo.in.m_deathType = weaponTemplate->getDeathType();
damageInfo.in.m_sourceID = obj->getID();
damageInfo.in.m_sourcePlayerMask = obj->getControllingPlayer() ? obj->getControllingPlayer()->getPlayerMask() : 0;
damageInfo.in.m_amount = weaponTemplate->getPrimaryDamage(nullBonus);
Prompt To Fix With AI
This is a comment left during a code review.
Path: Generals/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp
Line: 1269-1279

Comment:
The `DamageInfo` is populated without setting `m_sourcePlayerMask`. In the standard weapon path (`dealDamageInternal`), this field is populated from the source object's controlling player. `Object.cpp` reads it to decide whether to trigger a radar alert for the locally-controlled owner, and `ScriptConditions.cpp` uses it to resolve which player last damaged a unit. With the field left at its default of 0, crash-damage hits will skip radar warnings on the victim's side and any script trigger keyed on "attacked by player X" will fail to identify the attacker.

```suggestion
						// TheSuperHackers @bugfix Stubbjax 19/04/2026 Prevent non-building collisions from repeatedly dealing collateral damage to other objects.
						const WeaponTemplate* weaponTemplate = getPhysicsBehaviorModuleData()->m_vehicleCrashesIntoNonBuildingWeaponTemplate;
						if (weaponTemplate != nullptr)
						{
							WeaponBonus nullBonus;

							DamageInfo damageInfo;
							damageInfo.in.m_damageType = weaponTemplate->getDamageType();
							damageInfo.in.m_deathType = weaponTemplate->getDeathType();
							damageInfo.in.m_sourceID = obj->getID();
							damageInfo.in.m_sourcePlayerMask = obj->getControllingPlayer() ? obj->getControllingPlayer()->getPlayerMask() : 0;
							damageInfo.in.m_amount = weaponTemplate->getPrimaryDamage(nullBonus);
```

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Greptile is complaining

@Stubbjax Stubbjax force-pushed the fix-dead-unit-crash-damage branch from 4e17363 to fd6d974 Compare May 17, 2026 12:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Bug Something is not working right, typically is user facing Gen Relates to Generals Major Severity: Minor < Major < Critical < Blocker NoRetail This fix or change is not applicable with Retail game compatibility ZH Relates to Zero Hour

Projects

None yet

Development

Successfully merging this pull request may close these issues.

GLA Technical kills/damages building when dying by China Mines

4 participants