Jump to content

Triggering HitBy() on a caster of Project Image that is protected from the weapon with op120 does not remove the image


Guest Jukebox

Recommended Posts

Guest Jukebox

Hi!

I was testing Immunity to weapons (120) with the help of the IESDP to fasten things up, and noticed that Set image type (237) seems incorrect:

Quote

If Master ID triggers HitBy([ANYONE],0), this creature is moved to area "NO_AREA.ARE". This will delete non-global creatures from the game, unless such an area has been added to the game.

If I read this right, a Project Image should disappear if HitBy() is triggered on the caster regardless of how it happens. Immunity to weapons still allows HitBy() to trigger, as confirmed by the "hit" condition of Cast spell on condition (232)  (param2 = 0) working fine through e.g. Fireshields. (According to the IESDP op232 relies on HitBy().)

But the image doesn't disappear, at least in the only game I tested: BG2EE 2.6.6.0. I assume there's some basis for that quote above. If it's roughly correct but inaccurate, where's the inaccuracy? (Or is it wrong, outdated in 2.6.6.0, or original edition only? I have no idea.)

Link to comment

The description is technically true, but misleading. The "HitBy([ANYONE],0)" script trigger is true exactly when any type of damage is dealt to the subject.

"HitBy([ANYONE])" without the second parameter, as used for contingency effects, is true whenever the subject is hit by any attack or hostile spell/ability - even if no damage is dealt or the subject is immune.

 

One example involving Project Image: what happens if a mage is protected by Stoneskin and casts Project Image, then an illithid comes over and attacks the real mage? What happens is that the illithid auto-hits, drains 5 Int, and deals zero damage. The image sticks around. Repeat about four times - which only takes a round - and the image finally pops when the real mage dies.

Link to comment

EE hasn't changed this, I believe.

About the HitBy([EA],0)...HitBy([EA])The subsequent 0 means (oddly) crushing OR any listed damage type from DMGTYPE.ids. If you don't specify a damage type from DMGTYPE.ids HitBy([EA]) returns true for any attack regardless of the damage being blocked by Stoneskin or protection from weapon types.

The other thing is that Mirror Image is different in this regard, attacks soaked by stoneskin trigger the HitBy() condition, but not if intercepted by a Mirror Image.

For example BEAR.BCS has this block, only triggered by fire damage specifically:

IF
	HitBy([ANYONE],FIRE)
THEN
	RESPONSE #100
		SetInterrupt(FALSE)
		RunAwayFrom(LastAttackerOf(Myself),200)
		SetInterrupt(TRUE)
END

 

Link to comment
Guest Jukebox

What strange behavior. The Infinity engine is so quirky, and yet I'd argue that the quirks are part of its success as they allow a lot of emergent gameplay.

So... The IESDP might need a to make it clear. Perhaps the first "Info" bullet point in the description of Immunity to weapons (120) could be changed to:

Quote

Protected weapons will trigger HitBy(), but only if the condition's second argument is not specified. They will not interact with Mirror Image or Stone skins.

Also, what is the thing that decides Immunity to weapons is evaluated before Mirror Image, which is evaluated before Stoneskin? Is it just hard coded ordering, or is it more of a side (but intended) consequence of some other logic?

Link to comment

As of v2.6.6.0, HitBy() is triggered in three places:

CGameEffect::OnAdd()

    Prerequisites: Spell / Item ability flagged as HOSTILE; effect has a source, and source isn't the creature being affected.

    cause = Source AI type
    specific = 0

CGameEffectDamage::OnAddSpecific() - This runs after CGameEffect::OnAdd() if an effect defines it.

    Prerequisites: Effect has a source.

    cause = Source AI type
    specific = Damage type

CGameSprite::Swing()

    Prerequisites: Weapon ability type != RANGED && <real attack> && CGameSprite::Hit() != 0.

    op120 is checked after calculating the hit rolls and can still block the attack after HitBy() has been triggered.

    cause = Attacker's AI type
    specific = 0

Key points:

  • Mirror image / stoneskin resistances are processed in CGameEffectDamage::CheckSave(), before the effect is added to the target  meaning that when mirror image / stoneskin blocks an effect, it never goes through the OnAdd() / OnAddSpecific() stages. The same principle applies if the effect is blocked by any mechanism.
  • Event-based triggers, (those with IDs not in the 0x4000 range), match two things: the event cause, (the AI type triggered the event), and a "specific" int value. If a trigger checks a specific int value of 0, the engine treats that '0' to mean "any value."

So, some takeaways:

Triggers:

  • HitBy(<AI type>) and HitBy(<AI type>, 0) match any HitBy() event that matches <AI type>; i.e. <damage type> is ignored.

Effects:

  • When added, any effect that inherited the hostile flag and that has a valid source triggers HitBy(<attacker AI type>, 0), (except when the target was source of the effect).
  • When added, any op12 that has a valid source triggers HitBy(<source AI type>, <damage type>).
  • When an effect is blocked by magic resistance, saving throws, mirror image / stoneskin (in the instance of op12), or by any other mechanism, the effect doesn't trigger HitBy().

Swings:

  • Real attack swings that pass their hit roll trigger HitBy(<attacker AI type>, 0). For some reason weapon abilities with type = RANGED don't trigger HitBy() as part of the swing.
  • op120 can still block a hit even after HitBy() is triggered by a swing.

 

Though, all of this is moot, because Project Image doesn't use the HitBy() trigger to determine when to destroy the image. There are two ways the image can get destroyed:

  1. The mechanism that determines whether spell casting is disrupted is triggered: either the creature was hit by an op12 with a damage type it didn't have 100%+ resistance to, (I'm glossing over some of the finer details here), or the creature went under op39.
  2. Any op12 is applied to the caster. I'm inclined to think this is a bug, since it overrides the nuance of the previous check. Why have two checks if the latter makes the former largely redundant? In this case, the only thing the previous check does is provide image destruction in reaction to op39.
Edited by Bubb
Correct op12 explanation
Link to comment
46 minutes ago, Graion Dilach said:

Oh, does this mean it's also affected by the facing-changes-for-a-frame-preventing-disruption bug?

Thankfully not. That bug is in the spell actions, this is just co-opting how the engine determines whether the creature has taken damage.

 

Also, a correction: I missed a part of the Project Image handling. op12 specifically destroys the Project Image clone of the target, regardless of the character's resistances. To me this feels like a bug, since the clone's processing goes out of its way to check if its master "took damage," just for the nuance of that check to be ignored by op12.

Link to comment
1 hour ago, Bubb said:

Though, all of this is moot, because Project Image doesn't use the HitBy() trigger to determine when to destroy the image. It uses the same mechanism that determines whether spell casting is disrupted: either the creature was hit by an op12 with a damage type it didn't have 100%+ resistance to, (I'm glosing over some of the finer details here), or the creature went under op39.

So ... the IESDP description of op237 should definitely get an update. Replace

  • If Master ID triggers HitBy([ANYONE],0), this creature is moved to area "NO_AREA.ARE". This will delete non-global creatures from the game, unless such an area has been added to the game.

 with

  • If Master ID is dealt damage or becomes unconscious, this creature is moved to area "NO_AREA.ARE". This will delete non-global creatures from the game, unless such an area has been added to the game.

Does that look good to everyone?

Link to comment
2 hours ago, jmerry said:
  • If Master ID is dealt damage or becomes unconscious, this creature is moved to area "NO_AREA.ARE". This will delete non-global creatures from the game, unless such an area has been added to the game.

Does that look good to everyone?

"Took damage" can be misleading, since, just like spell disruption, zero damage will trigger it.

2 hours ago, Bubb said:

Also, a correction: I missed a part of the Project Image handling. op12 specifically destroys the Project Image clone of the target, regardless of the character's resistances. To me this feels like a bug, since the clone's processing goes out of its way to check if its master "took damage," just for the nuance of that check to be ignored by op12.

Interestingly this was NOT the case in v2.6.5, 100% damage resistance would prevent the image from being destroyed.  Regression maybe?

Link to comment
On 6/21/2023 at 3:13 PM, Bubb said:

Swings:

  • Real attack swings that pass their hit roll trigger HitBy(<attacker AI type>, 0). For some reason weapon abilities with type = RANGED don't trigger HitBy() as part of the swing.
  • op120 can still block a hit even after HitBy() is triggered by a swing.

The likely reason for ranged weapons is so that the "hit" doesn't trigger until the projectile impacts the target.  With melee weapons, it's not an issue, as they ignore their projectile field.  With ranged weapons, a projectile impact is not guaranteed, and can even be setup to never occur.

I would guess that the extra step for op120 is to stop effects that aren't tied to the projectile (those using anything  other than "Preset Target" and "Original Caster").

Link to comment

Join the conversation

You are posting as a guest. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...