Skip to content

Fix PoisonedBehavior damage-over-time ignoring POISON resistance (#1616) - #2987

Closed
Nagyhoho1234 wants to merge 2 commits into
TheSuperHackers:mainfrom
Nagyhoho1234:bugfix-poisonedbehavior-resistance-1616
Closed

Fix PoisonedBehavior damage-over-time ignoring POISON resistance (#1616)#2987
Nagyhoho1234 wants to merge 2 commits into
TheSuperHackers:mainfrom
Nagyhoho1234:bugfix-poisonedbehavior-resistance-1616

Conversation

@Nagyhoho1234

Copy link
Copy Markdown

Fix PoisonedBehavior damage-over-time ignoring POISON resistance (#1616)

PoisonedBehavior::update()'s periodic damage-over-time tick dealt
DAMAGE_UNRESISTABLE instead of DAMAGE_POISON, so a target's actual POISON
resistance (e.g. Chemical Suits) was never applied to it, even though the
effect is conceptually poison damage. The existing developer comment
explained this was to avoid PoisonedBehavior::onDamage() re-arming/
re-triggering itself off its own tick.

Changed the periodic tick to deal real DAMAGE_POISON (gated behind
#if !RETAIL_COMPATIBLE_CRC, since this changes actual damage dealt - a
CRC-relevant simulation outcome - so it stays dormant by default for
multiplayer/replay compatibility with retail 1.04/1.08).

Replaced the old damage-type workaround with an explicit reentrancy guard:
a new m_dealingPeriodicDamage member is set for the duration of the
synchronous getObject()->attemptDamage() call in update(), and onDamage()
ignores POISON damage while that flag is set. This could not be done by
comparing damageInfo->in.m_sourceID against getObject()->getID() (self),
since the periodic tick's damage.in.m_sourceID is m_poisonSource - the
original poisoner, kept for XP-credit purposes (see the existing Stubbjax
fix in startPoisonedEffects) - not the poisoned object itself; a
sourceID-based self-check would never match and would also incorrectly
suppress legitimate re-poisoning by repeated attacks from the same source.

Not Core-shared; applied identically to both Generals and GeneralsMD.

TheSuperHackers @BugFix ZsoltFeher 07/20/2026 Deal real POISON damage on the
periodic tick so POISON resistance applies, guarded by a reentrancy flag
instead of DAMAGE_UNRESISTABLE so it no longer re-triggers itself.

Co-Authored-By: Claude Sonnet 5 noreply@anthropic.com


Fix stale comment in PoisonedBehavior::update() (#1616 follow-up)

A fable-model review of commit 48078fb pointed out that the comment on
the DAMAGE_POISON assignment still described the abandoned "self-source
check" approach (comparing damageInfo->in.m_sourceID against the object's
own ID), which was already known to be unsound - the periodic tick's
damage.in.m_sourceID is m_poisonSource (the original attacker, kept for XP
credit), not the poisoned object's own ID, so that comparison could never
match. The actually-committed fix uses the m_dealingPeriodicDamage
reentrancy flag instead; the code was already correct, only this one
comment was left describing the wrong mechanism.

Reviewed-by: fable (code-reviewer)

TheSuperHackers @BugFix ZsoltFeher 07/20/2026 Comment described the
abandoned self-source-check approach instead of the actual
m_dealingPeriodicDamage reentrancy guard that was committed.

Co-Authored-By: Claude Sonnet 5 noreply@anthropic.com


Fixes #1616

Prof. Zsolt Zoltán Fehér Dr. and others added 2 commits July 20, 2026 13:33
…SuperHackers#1616)

PoisonedBehavior::update()'s periodic damage-over-time tick dealt
DAMAGE_UNRESISTABLE instead of DAMAGE_POISON, so a target's actual POISON
resistance (e.g. Chemical Suits) was never applied to it, even though the
effect is conceptually poison damage. The existing developer comment
explained this was to avoid PoisonedBehavior::onDamage() re-arming/
re-triggering itself off its own tick.

Changed the periodic tick to deal real DAMAGE_POISON (gated behind
#if !RETAIL_COMPATIBLE_CRC, since this changes actual damage dealt - a
CRC-relevant simulation outcome - so it stays dormant by default for
multiplayer/replay compatibility with retail 1.04/1.08).

Replaced the old damage-type workaround with an explicit reentrancy guard:
a new m_dealingPeriodicDamage member is set for the duration of the
synchronous getObject()->attemptDamage() call in update(), and onDamage()
ignores POISON damage while that flag is set. This could not be done by
comparing damageInfo->in.m_sourceID against getObject()->getID() (self),
since the periodic tick's damage.in.m_sourceID is m_poisonSource - the
original poisoner, kept for XP-credit purposes (see the existing Stubbjax
fix in startPoisonedEffects) - not the poisoned object itself; a
sourceID-based self-check would never match and would also incorrectly
suppress legitimate re-poisoning by repeated attacks from the same source.

Not Core-shared; applied identically to both Generals and GeneralsMD.

TheSuperHackers @BugFix ZsoltFeher 07/20/2026 Deal real POISON damage on the
periodic tick so POISON resistance applies, guarded by a reentrancy flag
instead of DAMAGE_UNRESISTABLE so it no longer re-triggers itself.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
(cherry picked from commit 48078fb)
…follow-up)

A fable-model review of commit 48078fb pointed out that the comment on
the DAMAGE_POISON assignment still described the abandoned "self-source
check" approach (comparing damageInfo->in.m_sourceID against the object's
own ID), which was already known to be unsound - the periodic tick's
damage.in.m_sourceID is m_poisonSource (the original attacker, kept for XP
credit), not the poisoned object's own ID, so that comparison could never
match. The actually-committed fix uses the m_dealingPeriodicDamage
reentrancy flag instead; the code was already correct, only this one
comment was left describing the wrong mechanism.

Reviewed-by: fable (code-reviewer)

TheSuperHackers @BugFix ZsoltFeher 07/20/2026 Comment described the
abandoned self-source-check approach instead of the actual
m_dealingPeriodicDamage reentrancy guard that was committed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
(cherry picked from commit ea5ce8a)
@greptile-apps

greptile-apps Bot commented Jul 20, 2026

Copy link
Copy Markdown

Greptile Summary

This PR changes periodic poison ticks to respect poison resistance when CRC compatibility allows it. The main changes are:

  • Adds a per-instance periodic-damage reentrancy guard in both game forks.
  • Initializes the guard in each PoisonedBehavior constructor.
  • Skips self-triggered onDamage() handling while the periodic tick is being applied.
  • Uses DAMAGE_POISON for non-retail-compatible periodic ticks while keeping retail-compatible damage unchanged.

Confidence Score: 5/5

This looks safe to merge.

  • No blocking issues found in the changed code.
  • The retail-compatible path keeps the old damage type.
  • The new guard only wraps the synchronous periodic damage call.

Important Files Changed

Filename Overview
Generals/Code/GameEngine/Include/GameLogic/Module/PoisonedBehavior.h Adds the periodic poison reentrancy guard member.
Generals/Code/GameEngine/Source/GameLogic/Object/Behavior/PoisonedBehavior.cpp Initializes the guard and applies poison-typed periodic damage outside retail-compatible builds.
GeneralsMD/Code/GameEngine/Include/GameLogic/Module/PoisonedBehavior.h Mirrors the guard member addition for Zero Hour.
GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/PoisonedBehavior.cpp Mirrors the periodic poison damage and guard behavior for Zero Hour.

Reviews (1): Last reviewed commit: "Fix stale comment in PoisonedBehavior::u..." | Re-trigger Greptile

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[GEN][ZH] PoisonedBehavior damage over time ignores POISON resistance

2 participants