Skip to content

bugfix(behavior): Allow multiple InstantDeathBehavior modules to be processed#2990

Merged
xezon merged 1 commit into
TheSuperHackers:mainfrom
Stubbjax:allow-multiple-instant-death-behaviors
Jul 22, 2026
Merged

bugfix(behavior): Allow multiple InstantDeathBehavior modules to be processed#2990
xezon merged 1 commit into
TheSuperHackers:mainfrom
Stubbjax:allow-multiple-instant-death-behaviors

Conversation

@Stubbjax

Copy link
Copy Markdown

This change allows multiple InstantDeathBehavior modules to be processed when an object dies.

The InstantDeathBehavior module is essentially a composite of the FXListDie, CreateObjectDie and FireWeaponWhenDeadBehavior modules - all of which can be processed multiple times when an object dies. It is counterintuitive that combining such modules into InstantDeathBehavior comes with the caveat that multiple instances can no longer be processed.

All cases in the retail data have inverted DeathTypes to ensure exclusivity. For example:

Behavior = InstantDeathBehavior DeathModuleTag_01
  DeathTypes = NONE +DETONATED
End
Behavior = InstantDeathBehavior DeathModuleTag_02
  DeathTypes = NONE +LASERED
  FX = FX_GenericMissileDisintegrate
  OCL = OCL_GenericMissileDisintegrate
End
Behavior = InstantDeathBehavior DeathModuleTag_03
  DeathTypes = ALL -LASERED -DETONATED
  FX = FX_GenericMissileDeath
End

However, this does not work when DeathTypes are not exclusive, in which case only the first applicable module will trigger. For example, ModuleTag_13 will never trigger with the following setup:

Behavior = InstantDeathBehavior ModuleTag_12
  FX = FX_BuildingDie
End
Behavior = InstantDeathBehavior ModuleTag_13
  ExemptStatus = UNDER_CONSTRUCTION
  FX = WeaponFX_BombTruckHighExplosiveBioBombDetonation
  OCL = OCL_PoisonFieldMedium
End

@Stubbjax Stubbjax self-assigned this Jul 21, 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 Jul 21, 2026
@greptile-apps

greptile-apps Bot commented Jul 21, 2026

Copy link
Copy Markdown

Greptile Summary

This PR lets non-retail builds process more than one InstantDeathBehavior during object death. The main changes are:

  • Keeps the original early AI dead-state gate for retail-compatible builds.
  • Moves the non-retail dead-state gate after FX, OCL, and death-weapon side effects.
  • Applies the same behavior to both Generals and Zero Hour source trees.

Confidence Score: 4/5

The changed non-retail death path can re-enter before the object is marked dead.

  • Death side effects now run while the object is still visible as alive to the AI dead-state guard.
  • A self-damaging death weapon can run the same instant-death side effects more than once.
  • Object destruction is idempotent, so the main breakage is duplicated side effects rather than double deletion.

Both InstantDeathBehavior.cpp copies need the same lifecycle guard adjustment.

Important Files Changed

Filename Overview
Generals/Code/GameEngine/Source/GameLogic/Object/Behavior/InstantDeathBehavior.cpp Moves the non-retail AI dead-state gate after death side effects so multiple instant-death modules can run.
GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/InstantDeathBehavior.cpp Mirrors the Generals instant-death ordering change for the Zero Hour source tree.
Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 2
Generals/Code/GameEngine/Source/GameLogic/Object/Behavior/InstantDeathBehavior.cpp:172
**Death Weapon Re-enters Alive Object**

When an `InstantDeathBehavior` death weapon immediately damages its own source, this call can re-enter death handling before the new late `markAsDead()` runs. The object still passes `isDieApplicable()` and the dead-state check has not fired yet, so the same FX, OCL, or weapon side effects can run again and produce duplicate spawns, effects, or damage in non-retail-compatible builds.

### Issue 2 of 2
GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/InstantDeathBehavior.cpp:172
**Death Weapon Re-enters Alive Object**

When an `InstantDeathBehavior` death weapon immediately damages its own source, this call can re-enter death handling before the new late `markAsDead()` runs. The object still passes `isDieApplicable()` and the dead-state check has not fired yet, so the same FX, OCL, or weapon side effects can run again and produce duplicate spawns, effects, or damage in non-retail-compatible builds.

Reviews (1): Last reviewed commit: "bugfix: Allow multiple InstantDeathBehav..." | Re-trigger Greptile

@xezon xezon left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The bot has concerns.

@@ -132,6 +133,7 @@ void InstantDeathBehavior::onDie( const DamageInfo *damageInfo )
return;
ai->markAsDead();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

SlowDeathBehavior also has it at the top. Is this a problem?

@Stubbjax

Copy link
Copy Markdown
Author

The bot has concerns.

The same concerns should apply to the existing individual modules which lack this check though, so I don't think it's valid.

SlowDeathBehavior also has it at the top. Is this a problem?

The SlowDeathBehavior works a bit differently and behaves a bit more like a state machine, so it's unlikely that you'd have multiple that are triggered at once.

@xezon

xezon commented Jul 21, 2026

Copy link
Copy Markdown

The same concerns should apply to the existing individual modules which lack this check though, so I don't think it's valid.

Did you test it? Have a unit that fires a weapon on death that can damage itself and see if the module enters an infinite loop of damaging itself.

What units will this change noticably affect?

@Stubbjax

Copy link
Copy Markdown
Author

Did you test it? Have a unit that fires a weapon on death that can damage itself and see if the module enters an infinite loop of damaging itself.

Yes. I tested this with a self-damaging Nuke Battlemaster and its FireWeaponWhenDeadBehavior module only calls onDie once. This condition in ActiveBody prevents it:

if( m_currentHealth <= 0 && m_prevHealth > 0 )
  obj->onDie( damageInfo );

I imagine this would be a much greater issue if it wasn't already handled higher up and every individual module had to guard against it. The attemptDamage logic can still be called for dead objects regardless.

What units will this change noticably affect?

None other than all SCUD Storm variants after GeneralsGamePatch2#118. Every other case with multiple InstantDeathBehavior modules in both retail and Patch2 are weapon objects and have mutually exclusive DeathTypes.

Side note: FireWeaponWhenDeadBehavior has a hard-coded early return for the OBJECT_STATUS_UNDER_CONSTRUCTION status bit, while InstantDeathBehavior does not and instead relies on its ExemptStatus field. The question is whether to apply this condition to the weapon-firing logic of InstantDeathBehavior or update the data setup for applicable buildings.

@xezon xezon left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Very well.

@xezon xezon changed the title bugfix: Allow multiple InstantDeathBehavior modules to be processed bugfix(behavior): Allow multiple InstantDeathBehavior modules to be processed Jul 22, 2026
@xezon
xezon merged commit 6aab954 into TheSuperHackers:main Jul 22, 2026
17 checks passed
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 Minor 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.

2 participants