Skip to content

[(BGEE, BG2) Bug] Larloch's minor drain, vampiric touch bugs

CamDawgCamDawg Member, Developer Posts: 3,438
edited August 2012 in Fixed
This one's complicated, and straight out of the Fixpack:
Larloch's Minor Drain and Vampiric Touch were healing the caster, even if the target was not affected due to magic resistance. Both spells now only heal the caster if the target is affected. This also fixes an additional bug of Vampiric Touch being able to boost the caster's multiple times through multiple castings, though the description explicitly states this shouldn't happen. This also fixes a damage bug with the innate version of this spell (the one added as a Bhaalspawn ability and used by Foebane on hit) where it did 1d4 damage instead of 4 points, per its description.
So, if you cast LMD (spwi119), this is what happens:
  1. Provide an immunity to the caster to the spell just cast (spwi119), preventing self-casting and preventing the steps 2+
  2. Cast spwi119a on the original target, but this spell is subject to a magic resistance check on the target. A failed MR check then continues on to step 3
  3. If the target fails their MR check, then run the damage on target, bonus HP on caster
For vampiric touch, the HP gain is actually shuffled into a third spell that also provides the timeout to prevent the spell from being re-cast, per the spell description.

It's also old code, so it's full of cruft but functional.
// vamp touch fixes
COPY_EXISTING ~spin106.spl~ ~override/spin106a.spl~ // innate vamp touch
~spin106.spl~ ~override/spin106b.spl~ // innate vamp touch
~spin997.spl~ ~override/spin997a.spl~ // innate vamp touch
~spin997.spl~ ~override/spin997b.spl~ // innate vamp touch
~spwi314.spl~ ~override/spwi314a.spl~ // mage vamp touch
~spwi314.spl~ ~override/spwi314b.spl~ // mage vamp touch
WRITE_LONG NAME1 0xffffffff
WRITE_LONG NAME2 0xffffffff
READ_LONG 0x64 "abil_off"
READ_SHORT 0x68 "abil_num"
READ_LONG 0x6a "fx_off"
PATCH_IF ("%DEST_RES%" STRING_COMPARE_REGEXP "^.+b$" = 0) BEGIN
SET "self" = 1
END ELSE BEGIN
SET "self" = 0
END
SET "fx_delta" = 0
FOR (index = 0 ; index < abil_num ; index = index + 1) BEGIN
READ_SHORT ("%abil_off%" + 0x10 + (0x28 * "%index%")) "min_lev"
READ_SHORT ("%abil_off%" + 0x1e + (0x28 * "%index%")) "abil_fx_num"
READ_SHORT ("%abil_off%" + 0x20 + (0x28 * "%index%")) "abil_fx_idx"
SET "abil_fx_idx" = "%abil_fx_idx%" + "%fx_delta%"
WRITE_SHORT ("%abil_off%" + 0x20 + (0x28 * "%index%")) "%abil_fx_idx%"
WRITE_SHORT ("%abil_off%" + 0x26 + (0x28 * "%abil_num%")) 1 // removes projectile
FOR (index2 = 0 ; index2 < abil_fx_num ; index2 = index2 + 1) BEGIN
READ_BYTE ("%fx_off%" + 0x02 + (0x30 * ("%abil_fx_idx%" + "%index2%"))) "target"
PATCH_IF ("%target%" = 2) BEGIN // preset target
PATCH_IF ("%self%" = 0) BEGIN
READ_BYTE ("%fx_off%" + 0x0d + (0x30 * ("%abil_fx_idx%" + "%index2%"))) "dispel"
PATCH_IF ("%dispel%" = 1) BEGIN // if dispel/not bypass
WRITE_BYTE ("%fx_off%" + 0x0d + (0x30 * ("%abil_fx_idx%" + "%index2%"))) 3 // make dispel/bypass
END ELSE
PATCH_IF ("%dispel%" = 2) BEGIN // if not dispel/not bypass
WRITE_BYTE ("%fx_off%" + 0x0d + (0x30 * ("%abil_fx_idx%" + "%index2%"))) 0 // make not dispel/bypass
END
END ELSE BEGIN
DELETE_BYTES ("%fx_off%" + (0x30 * ("%abil_fx_idx%" + "%index2%"))) 0x30 // delete effect
SET "index2" = "%index2%" - 1
SET "abil_fx_num" = "%abil_fx_num%" - 1
SET "fx_delta" = "%fx_delta%" - 1
END
END ELSE
PATCH_IF ("%target%" = 1) BEGIN // target: self
PATCH_IF ("%self%" = 0) BEGIN // non-self spell
DELETE_BYTES ("%fx_off%" + (0x30 * ("%abil_fx_idx%" + "%index2%"))) 0x30 // delete effect
SET "index2" = "%index2%" - 1
SET "abil_fx_num" = "%abil_fx_num%" - 1
SET "fx_delta" = "%fx_delta%" - 1
END ELSE BEGIN
READ_BYTE ("%fx_off%" + (0x30 * ("%abil_fx_idx%" + "%index2%"))) "opcode"
PATCH_IF ("%opcode%" = 206) BEGIN
WRITE_ASCII ("%fx_off%" + 0x1b + (0x30 * ("%abil_fx_idx%" + "%index2%"))) ~b~ // changes from spwi314 to spwi314b
END
END
END
END
PATCH_IF ("%self%" = 0) BEGIN // if a, insert cast spell b
INSERT_BYTES ("%fx_off%" + (0x30 * ("%abil_fx_idx%" + "%abil_fx_num%"))) 0x30
WRITE_SHORT ("%fx_off%" + (0x30 * ("%abil_fx_idx%" + "%abil_fx_num%"))) 146 // cast spell
WRITE_BYTE ("%fx_off%" + 0x02 + (0x30 * ("%abil_fx_idx%" + "%abil_fx_num%"))) 1 // target self
WRITE_LONG ("%fx_off%" + 0x04 + (0x30 * ("%abil_fx_idx%" + "%abil_fx_num%"))) "%min_lev%" // cast at level
WRITE_LONG ("%fx_off%" + 0x08 + (0x30 * ("%abil_fx_idx%" + "%abil_fx_num%"))) 1 // cast instantly
WRITE_BYTE ("%fx_off%" + 0x0d + (0x30 * ("%abil_fx_idx%" + "%abil_fx_num%"))) 3 // dispel/bypass resistance
WRITE_LONG ("%fx_off%" + 0x0e + (0x30 * ("%abil_fx_idx%" + "%abil_fx_num%"))) 300 // duration
WRITE_BYTE ("%fx_off%" + 0x12 + (0x30 * ("%abil_fx_idx%" + "%abil_fx_num%"))) 100 // probability
WRITE_ASCIIE ("%fx_off%" + 0x14 + (0x30 * ("%abil_fx_idx%" + "%abil_fx_num%"))) ~%SOURCE_RES%b~ // spell
SET "abil_fx_num" = "%abil_fx_num%" + 1
SET "fx_delta" = "%fx_delta%" + 1
END
WRITE_SHORT ("%abil_off%" + 0x1e + (0x28 * "%index%")) "%abil_fx_num%"
END
BUT_ONLY_IF_IT_CHANGES

// larloch minor drain fixes
COPY_EXISTING ~spin104.spl~ ~override/spin104a.spl~ // innate LMD
~spwi119.spl~ ~override/spwi119a.spl~ // mage LMD
WRITE_LONG NAME1 0xffffffff
WRITE_LONG NAME2 0xffffffff
READ_LONG 0x64 "abil_off"
READ_SHORT 0x68 "abil_num"
READ_LONG 0x6a "fx_off"
READ_SHORT ("%abil_off%" + 0x1e + (0x28 * ("%abil_num%" - 1))) "abil_fx_num"
READ_SHORT ("%abil_off%" + 0x20 + (0x28 * ("%abil_num%" - 1))) "abil_fx_idx"
SET "total_fx" = ("%abil_fx_idx%" + "%abil_fx_num%")
WHILE ("%abil_num%" > 0) BEGIN
SET "abil_num" = ("%abil_num%" - 1)
WRITE_SHORT ("%abil_off%" + 0x26 + (0x28 * "%abil_num%")) 1 // removes projectile
END
WHILE ("%total_fx%" > 0) BEGIN
SET "total_fx" = ("%total_fx%" - 1)
READ_BYTE ("%fx_off%" + 0x0d + (0x30 * "%total_fx%")) "dispel"
PATCH_IF ("%dispel%" = 1) BEGIN // if dispel/not bypass
WRITE_BYTE ("%fx_off%" + 0x0d + (0x30 * "%total_fx%")) 3 // make dispel/bypass
END ELSE
PATCH_IF ("%dispel%" = 2) BEGIN // if not dispel/not bypass
WRITE_BYTE ("%fx_off%" + 0x0d + (0x30 * "%total_fx%")) 0 // make not dispel/bypass
END
END
BUT_ONLY_IF_IT_CHANGES

// innate LMD should do fixed damage of 4, not 1d4
COPY_EXISTING ~spin104a.spl~ ~override~
READ_LONG 0x64 "abil_off"
READ_SHORT 0x68 "abil_num"
READ_LONG 0x6a "fx_off"
FOR ("index" = 0; "%index%" < "%abil_num%"; "index" = ("%index%" + 1)) BEGIN // fix existing effects
READ_SHORT ("%abil_off%" + 0x1e + (0x28 * "%index%")) "abil_fx_num"
READ_SHORT ("%abil_off%" + 0x20 + (0x28 * "%index%")) "abil_fx_idx"
FOR (index2 = 0 ; index2 < abil_fx_num ; index2 = index2 + 1) BEGIN
READ_SHORT ("%fx_off%" + (0x30 * ("%abil_fx_idx%" + "%index2%"))) "opcode"
READ_ASCII ("%fx_off%" + 0x14 + (0x30 * ("%abil_fx_idx%" + "%index2%"))) "spell"
PATCH_IF ("%opcode%" = 12) BEGIN // damage opcode
WRITE_LONG ("%fx_off%" + 0x04 + (0x30 * ("%abil_fx_idx%" + "%index2%"))) 4 // fixed damage
WRITE_LONG ("%fx_off%" + 0x1c + (0x30 * ("%abil_fx_idx%" + "%index2%"))) 0 // number of dice
END
END
END
BUT_ONLY_IF_IT_CHANGES

// makes MR-checking "shell" spell that also eliminates self-cast exploit
COPY_EXISTING ~spin104.spl~ ~override~ // innate LMD
~spin106.spl~ ~override~ // innate vamp touch
~spin997.spl~ ~override~ // innate vamp touch
~spwi119.spl~ ~override~ // mage LMD
~spwi314.spl~ ~override~ // mage vamp touch
READ_LONG 0x64 "abil_off"
READ_SHORT 0x68 "abil_num"
READ_LONG 0x6a "fx_off"
READ_SHORT 0x70 "fx_num"
PATCH_IF ((~%SOURCE_RES%~ STRING_COMPARE_CASE "spin104" = 0) OR
(~%SOURCE_RES%~ STRING_COMPARE_CASE "spwi119" = 0)) BEGIN
SET "power" = 1
END ELSE BEGIN
SET "power" = 3
END
WHILE ("%abil_num%" > 0) BEGIN
SET "abil_num" = ("%abil_num%" - 1)
READ_SHORT ("%abil_off%" + 0x10 + (0x28 * "%abil_num%")) "level"
READ_SHORT ("%abil_off%" + 0x1e + (0x28 * "%abil_num%")) "abil_fx_num"
WRITE_SHORT ("%abil_off%" + 0x1e + (0x28 * "%abil_num%")) 2
READ_SHORT ("%abil_off%" + 0x20 + (0x28 * "%abil_num%")) "abil_fx_idx"
WRITE_SHORT ("%abil_off%" + 0x20 + (0x28 * "%abil_num%")) ("%abil_num%" * 2)
DELETE_BYTES ("%fx_off%" + (0x30 * "%abil_fx_idx%")) (0x30 * "%abil_fx_num%") // deletes all effects
INSERT_BYTES ("%fx_off%" + (0x30 * "%abil_fx_idx%")) 0x30 // cast actual spell
WRITE_SHORT ("%fx_off%" + (0x30 * "%abil_fx_idx%")) 146 // cast spell
WRITE_BYTE ("%fx_off%" + 0x02 + (0x30 * "%abil_fx_idx%")) 2 // target: preset target
WRITE_BYTE ("%fx_off%" + 0x03 + (0x30 * "%abil_fx_idx%")) "%power%" // power
WRITE_LONG ("%fx_off%" + 0x04 + (0x30 * "%abil_fx_idx%")) "%level%" // cast at level
WRITE_LONG ("%fx_off%" + 0x08 + (0x30 * "%abil_fx_idx%")) 1 // cast instantly
WRITE_BYTE ("%fx_off%" + 0x0c + (0x30 * "%abil_fx_idx%")) 1 // instant/permanent
WRITE_BYTE ("%fx_off%" + 0x0d + (0x30 * "%abil_fx_idx%")) 1 // dispel/not bypass
WRITE_BYTE ("%fx_off%" + 0x12 + (0x30 * "%abil_fx_idx%")) 100 // probability
WRITE_EVALUATED_ASCII ("%fx_off%" + 0x14 + (0x30 * "%abil_fx_idx%")) ~%SOURCE_RES%a~ // spell
INSERT_BYTES ("%fx_off%" + (0x30 * "%abil_fx_idx%")) 0x30 // prevent self-casting
WRITE_SHORT ("%fx_off%" + (0x30 * "%abil_fx_idx%")) 206 // spell immunity
WRITE_BYTE ("%fx_off%" + 0x02 + (0x30 * "%abil_fx_idx%")) 1 // target: self
WRITE_BYTE ("%fx_off%" + 0x03 + (0x30 * "%abil_fx_idx%")) 0 // power
WRITE_BYTE ("%fx_off%" + 0x12 + (0x30 * "%abil_fx_idx%")) 100 // probability
WRITE_EVALUATED_ASCII ("%fx_off%" + 0x14 + (0x30 * "%abil_fx_idx%")) ~%SOURCE_RES%~ // spell: self
END
PATCH_IF ("%fx_num%" > 0) BEGIN // eliminates global fx
DELETE_BYTES "%fx_off%" (0x30 * "%fx_num%")
WRITE_SHORT 0x70 0
END
BUT_ONLY_IF_IT_CHANGES
Post edited by Bhryaen on

Comments

  • AndreaColomboAndreaColombo Member Posts: 5,533
    edited July 2012
    @CamDawg - speaking of Vampiric Touch, are you aware of this bug and is there anything you could contribute to its potential fixing? It is not strictly related to Vampiric Touch, but that's the spell used in the example so it made me think of it.
  • CamDawgCamDawg Member, Developer Posts: 3,438
    Yeah, but that's something that appears to be in the engine itself and not something that can be fixed messing around with the spell files.

    There's another unfixable bug with the drain spells. Since the drain is actually a damage opcode on the target and a heal on the caster they get adjusted by the difficulty level. Even if we could theoretically get the damage & heal figures exact, at higher difficulty the damage to your target will be reduced and make them unequal again.

    Ultimately, we decided to not lose sleep about it.
  • SethDavisSethDavis Member Posts: 1,812
    edited July 2012
    Potentially fixed - @CamDawg 's fix has been used
  • AndreaColomboAndreaColombo Member Posts: 5,533
    edited July 2012
    @CamDawg - thanks. Hopefully the devs can do something about these nasty engine limitations!

    EDIT: actually, could you post a separate bugfix request for that? It sounds like something that should really be addressed, now that the devs have access to the source code.
    Post edited by AndreaColombo on
  • Avenger_teambgAvenger_teambg Member, Developer Posts: 5,862
    edited August 2012
    I can confirm that the script was applied, but the changes don't work as advertised.

    @CamDawg:
    Self-casting prevention (with 0 duration) doesn't work in bg2.
    You need to change the duration to 1 and the resource to A to make this work (it works that way, i tested it).
    You also need to fix scrolls for these spells too.
  • CamDawgCamDawg Member, Developer Posts: 3,438
    Nah, the scrolls will cast the mainline spell, which will cycle through the shells as appropriate.

    I've got a batch of spell scroll fixes, but it's mainly to fix targeting/range/etc. that differs from the spells.
  • Avenger_teambgAvenger_teambg Member, Developer Posts: 5,862
    True, the scrolls are fine.
  • Avenger_teambgAvenger_teambg Member, Developer Posts: 5,862
    edited August 2012
    Anyone managed to use Larloch? I might have messed up the spells locally.
  • TanthalasTanthalas Member Posts: 6,738
    @Avenger_teambg

    I just killed a Monk with it in Candlekeep, so it looks like its working to me.
  • TanthalasTanthalas Member Posts: 6,738
    @CamDawg, @Avenger_teambg

    Ok, I don't fully understand what's the intended behaviour so I'm just going to post my observations.

    Larloch's Minor Drain:

    - The extra HP gained with Larloch's Minor Drain is cumulative
    - You can actually cast this spell on yourself but nothing happens (no damage and no bonus HP).
    - Spawned Viconia and buffed her Magic Resistance to 100%. Casting LMD on Viconia resulted in the magic resistance message and no extra HP for the caster.

    Vampiric Touch (Arcane or Bhaalspawn power had the same effect):

    - The extra HP gained by Vampiric Touch is not cumulative. Casting the spell again does damage to the target and a message appears in the battle log informing you that "A vampiric touch spell is already active."
    - You can actually cast this spell on yourself but nothing happens (no damage and no bonus HP).
    - Spawned Viconia and buffed her Magic Resistance to 100%. Casting Vampiric Touch on Viconia resulted in the magic resistance message and no extra HP for the caster.

    If this is the intended behaviour then its confirmed fixed on my part.
  • BhryaenBhryaen Member Posts: 2,874
    @Tanthalas
    One other aspect was whether you're getting the same amount of healing as the target is losing.
  • TanthalasTanthalas Member Posts: 6,738
    @Bhryaen

    Well, I just checked:

    1. If you use it on a party member in the highest difficulty setting, you do double damage but only get half the HP. I'm not sure if this should be changed.
    2. I tested it on an Ogre and got these results:
    - The Ogre had 30 HP
    - Vampiric Touch did 33 damage.
    - My Sorcerer only gained 30 HP.

    However with Larloch's Minor Drain:
    - Spawned a Gibberling that has 8 HP
    - punched it for 7 damage
    - Casted Larloch's Minor Drain on it dealing 4 damage
    - My Sorcerer gained 4 HP instead of just 1.
  • TanthalasTanthalas Member Posts: 6,738
    @Bhryaen

    Actually, scratch that last comment about Vampiric Touch. What seems to be happening is that the game is rolling the damage to the target and the HP for the caster separately. It was just a fluke that time that I did 33 damage and only got 30 HP.

    Tested with a Gibberling and I get way more HP than the total HP of the Gibberling.
  • TanthalasTanthalas Member Posts: 6,738
    @Bhryaen
    But I'm not sure if they were actually trying to match the HP damage with the HP gained.
  • BhryaenBhryaen Member Posts: 2,874
    @Tanthalas
    You're right- sorry- no mention in the OP about matching dam and healing. Maybe not essential to the spell, but... meh...

    The things to test would be:
    1. Healing caster despite target magic resist
    2. Bhaalspawn variant should do 4 dam rather than 1-4
    3. Vamp Touch shouldn't work repeatedly (as per desc)
    4. Self-casting not viable

    I think you did those, no? Maybe not the Bhaalspawn larlochs though?
  • TanthalasTanthalas Member Posts: 6,738
    @Bhryaen

    I actually didn't test the Bhaalspawn LMD. I confused it with Vampiric Touch.

    I already nuked my old build and I'm downloading the new one. Its something easy to check tomorrow though.
  • TanthalasTanthalas Member Posts: 6,738
    edited August 2012
    Ok, I tested the Bhaalspawn Larloch's Minor Drain and its also Confirmed Fixed in Build 0815.
  • TanthalasTanthalas Member Posts: 6,738
    I think I was thorough enough with this one.
Sign In or Register to comment.