Jump to content

Trolls suck


Recommended Posts

Trying to tackle a few issues from the Outstanding Bugs thread. Specifically, if you hit a troll with a spell like Finger of Death you get XP for killing the troll and then you can kill it again for more XP once it falls down. The other issue was that poison would kill downed trolls.

 

First a bit of background into how trolls are supposed to work, for those who care. Trolls have monhp1 equipped, designed to keep them from reaching 0 hit points. They have a script that kicks in when they're down below a fixed number of HP (10 or 12) that replaces them with a different troll; i.e. troll01 gets swapped out for troll02 at low HPs. troll02 has one hit point, is immune to everything save fire and acid, and has a script that makes them play dead for a while before turning back into the original troll. In theory this works.

 

Well, they forget poison immunity for the dead trolls, so fire/acid/poison will all permanently kill a troll. Also, the script to check for transforming into the dead troll is a straight HPLT check, so that if you kill the troll with Finger of Death, it still subs the dead troll. Both of these issues are fairly straightforward to fix.

 

The problem is that the creature file substitution is hacky at best, and Bioware apparently didn't want to bother with normal/dead versions of all trolls, especially the main ones. For example, Tor'Gal just dies--no need to drop fire or acid on him. Pretty much any troll that has either a non-standard item or has a DV that needs checking by other scripts simply dies instead of requiring fire/acid to finish. I found a number of other discrepancies:

 

kptrol01 - kptrol04 (Trolls from de'Arnise Keep)

Killing with FoD vs melee yields different XP. Listed as 2400 XP, but they use troll02 as their dead form, which is only 1400 XP. kptrol03 is also missing its monhp1.itm, making it unlikely you need to use fire/acid on it at all. The other three have minhp1.itm instead, making them much more powerful.

 

obsice01.cre (Snow Troll from Planar Sphere)

Missing monhp1.itm.

 

rogtro01.cre (Sea Troll from Roger's quest)

Dead version has 1400 XP while the normal is only 650 XP.

 

trolgi01.cre (Giant Troll, generic)

Has minhp1.itm instead of monhp1.itm. The former provides a helluva lot of immunities and other goodies lacking from the latter.

 

As noted above, several 'special' trolls do not require fire/acid to kill:

 

daspitor.cre, datroll.cre (Spirit Troll and Troll from Ust Natha)

Trolls locked in Ust Natha cages. No changes needed here.

 

drshnl01.cre (Nilthiri, Druid Grove)

From one of the druid stronghold quests, Nilthiri is the troll shaman you must kill to preserve balance. There is a dead version of her, but it is unused.

 

eletro01.cre (Troll, Asylum maze)

From one of the asylum test areas, this troll has the Giant Troll head item. His partner, eletro02, still requires fire/acid.

 

grae.cre (Grae, Druid Grove)

From the limited wish quest, Grae gives you info on how to find Drush and the gong. Once reaching 1hp, Grae will initiate dialogue, and all of the options lead to EscapeArea() or Kill(Myself). No changes needed.

 

pptroll1.cre (Spirit Troll. Asylum maze)

Bogstandard trolls.

 

shtroll.cre (Giant Troll)

Creature file used by Shapechange spell. No changes needed.

 

sutroll.cre (Troll, Suldenessellar)

Two trolls used in one of the Suldenessellar scenes showing the elves fighting. These two are standard trolls, but their DVs are used by the elf scripts.

 

torgal.cre (Tor'gal, de'Arnise Keep)

Tor'gal, from the conclusion of the de'Arnise Keep quest. Should also require fire/acid.

 

trollens.cre (Giant Troll, Druid Grove)

A troll from the discarded Rynn Lanthorn lens quest that was supposed to drop a lens. No reason it can't require fire/acid.

 

Fixes on the way.

Link to comment

Found a few other errors along the way.

 

// poison immnity for 'dead' trolls
COPY_EXISTING ~trolldie.itm~ ~override~
 READ_LONG   0x64 "abil_off"
 READ_SHORT  0x68 "abil_num"
 READ_LONG   0x6a "fx_off"
 READ_SHORT  0x70 "fx_num"
 SET "new_fx" = 1
 FOR (index = 0; index < abil_num; index = index + 1) BEGIN
   READ_SHORT  (0x20 + "%abil_off%" + ("%index%" * 0x38)) "abil_fx_idx"
   WRITE_SHORT (0x20 + "%abil_off%" + ("%index%" * 0x38)) ("%abil_fx_idx%" + "%new_fx%")
 END
 WRITE_SHORT 0x70 ("%fx_num%" + "%new_fx%")
 INSERT_BYTES  ("%fx_off%" +        ("%fx_num%" * 0x30)) 0x30
   WRITE_SHORT ("%fx_off%" +        ("%fx_num%" * 0x30)) 101 // immunity to effect
   WRITE_BYTE  ("%fx_off%" + 0x02 + ("%fx_num%" * 0x30)) 1   // target self
   WRITE_BYTE  ("%fx_off%" + 0x08 + ("%fx_num%" * 0x30)) 25  // poison
   WRITE_BYTE  ("%fx_off%" + 0x0c + ("%fx_num%" * 0x30)) 2   // instant/while equipped
   WRITE_BYTE  ("%fx_off%" + 0x12 + ("%fx_num%" * 0x30)) 100 // probability 1
 BUT_ONLY_IF_IT_CHANGES

// trolls giving double XP if slain by spell or other goodies
COPY_EXISTING ~chaltrol.bcs~ ~override~ // giant troll, challenge
             ~dgtrol01.bcs~ ~override~ // troll, druid grove
             ~firamb03.bcs~ ~override~ // fire troll
             ~hgtrl01.bcs~  ~override~ // fire troll
             ~rogtro01.bcs~ ~override~ // roger's sea troll
             ~trolde01.bcs~ ~override~ // desert troll
             ~trolfr01.bcs~ ~override~ // freshwater troll
             ~trolgi01.bcs~ ~override~ // giant troll
             ~trolic01.bcs~ ~override~ // ice troll
             ~trolic03.bcs~ ~override~ // blizzard troll
             ~troll01.bcs~  ~override~ // generic troll
             ~trolsi01.bcs~ ~override~ // spirit troll
             ~trollsm2.bcs~ ~override~ // small troll - lacking timers and such
             ~trolsn01.bcs~ ~override~ // snow troll
             ~trolsp01.bcs~ ~override~ // spectral troll
 DECOMPILE_BCS_TO_BAF
   REPLACE_TEXTUALLY ~HPLT(Myself,\([0-9]+\))~ ~!StateCheck(Myself,STATE_REALLY_DEAD) HPLT(Myself,\1)~
   PATCH_IF ("%SOURCE_RES%" STRING_COMPARE_CASE "trolfr01" = 0) BEGIN // extra fix for freshwater troll
     REPLACE_TEXTUALLY ~"TROLGI02"~ ~"trolfr02"~ // freshwater trolls becoming giant trolls when knocked down
   END
 COMPILE_BAF_TO_BCS
 BUT_ONLY_IF_IT_CHANGES

// restore Nith's attack script, pt 2: assign script
COPY_EXISTING ~drshnl01.cre~ ~override~
 WRITE_ASCII 0x258 ~drshnl01~ // race script
 BUT_ONLY_IF_IT_CHANGES

// restore Nith's attack script, pt 1: false() out stuff that'll break
COPY_EXISTING ~drshnl01.bcs~ ~override~
 DECOMPILE_BCS_TO_BAF
   REPLACE_TEXTUALLY ~HPLT(Myself,10)~ ~False()~
 COMPILE_BAF_TO_BCS
 BUT_ONLY_IF_IT_CHANGES

// trollens should require fire/acid to be killed
COPY_EXISTING ~trollens.cre~ ~override~
 WRITE_ASCII 0x258 ~troll01~ #8 // transform to fire/acid dead form
 BUT_ONLY_IF_IT_CHANGES

// if killed directly via spell, fire trolls should do groovy death animations
EXTEND_TOP ~hgtrl01.bcs~ ~bg2fixpack/baf/hgtrl01.baf~

// trolls lacking the correct 1hp items
COPY_EXISTING ~hgtrl01.cre~  ~override~ // fire troll
             ~kptrol01.cre~ ~override~ // kptrol0[1-4] are trolls from de'Arnise keep
             ~kptrol02.cre~ ~override~
             ~kptrol03.cre~ ~override~
             ~kptrol04.cre~ ~override~
             ~obsice01.cre~ ~override~ // snow troll from planar sphere
             ~trolgi01.cre~ ~override~ // generic giant troll
             ~trollens.cre~ ~override~ // giant troll
 ADD_CRE_ITEM ~monhp1~ #0 #0 #0 ~NONE~ ~AMULET~ // either adds it new, or forces minhp1 into inventory (and unequipped)
 BUT_ONLY_IF_IT_CHANGES
 
// XP fixes
COPY_EXISTING ~kptrol01.cre~ ~override~
             ~kptrol02.cre~ ~override~
             ~kptrol03.cre~ ~override~
             ~kptrol04.cre~ ~override~
             ~rogtro01.cre~ ~override~
 WRITE_LONG 0x14 1400
 BUT_ONLY_IF_IT_CHANGES

// trolls should be knocked down and require fire/acid to destroy; everything from here down is to fix that

// create 'dead' or 'knocked down' versions
COPY_EXISTING ~drshnl01.cre~ ~override/drshnl02.cre~
             ~eletro01.cre~ ~override/eletro03.cre~
             ~pptroll1.cre~ ~override/pptroll2.cre~
             ~sutroll.cre~  ~override/sutroll2.cre~
             ~torgal.cre~   ~override/torgal2.cre~
 WRITE_SHORT           0x24    1            // current HP
 WRITE_SHORT           0x46   10            // natural AC
 WRITE_SHORT           0x48   10            // effective AC
 WRITE_BYTE            0x5a  100            // resist cold
 WRITE_BYTE            0x5b  100            // resist electricity
 WRITE_BYTE            0x5f  100            // resist magic cold
 WRITE_BYTE            0x60  100            // resist slashing
 WRITE_BYTE            0x61  100            // resist crushing
 WRITE_BYTE            0x62  100            // resist piercing
 WRITE_BYTE            0x63  100            // resist missile
 WRITE_BYTE            0x23c   9            // dexterity
 WRITE_BYTE            0x270 255            // enemy
 PATCH_IF ("%SOURCE_RES%" STRING_COMPARE_CASE "drshnl01" = 0) BEGIN
   WRITE_ASCII 0x248 ~drshnl03~
 END ELSE
 PATCH_IF ("%SOURCE_RES%" STRING_COMPARE_CASE "torgal" = 0) BEGIN
   WRITE_ASCII 0x248 ~torgal3~ #8 // race script
 END ELSE BEGIN
   WRITE_EVALUATED_ASCII 0x248 ~%SOURCE_RES%~ // new script
 END
 WRITE_ASCII           0x250 ~~ #32         // blanks all other script references
 // item changes
 READ_LONG 0x2bc "itm_off"
 READ_LONG 0x2c0 "itm_num"
 FOR (index = 0; index < itm_num; index = index + 1) BEGIN
   READ_ASCII ("%itm_off%" + (0x14 * "%index%")) "item"
   PATCH_IF (
              ("%item%" STRING_COMPARE_CASE "monhp1" = 0) OR // so they can die
              ("%item%" STRING_COMPARE_CASE "trollreg" = 0) OR // can't regenerate
              ("%item%" STRING_COMPARE_CASE "trollspi" = 0) // makes dead spirit troll invisible
            ) BEGIN
     WRITE_ASCII ("%itm_off%" + (0x14 * "%index%")) ~trolldie~
   END
 END

// spells to transform back from dead to alive
COPY_EXISTING ~spin955.spl~ ~override/drshnl03.spl~
             ~spin955.spl~ ~override/eletro01.spl~
             ~spin955.spl~ ~override/pptroll1.spl~
             ~spin955.spl~ ~override/sutroll.spl~
             ~spin955.spl~ ~override/torgal3.spl~
 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
   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%" +        (("%abil_fx_idx%" + "%index2%") * 0x30)) "opcode"
     READ_ASCII ("%fx_off%" + 0x14 + (("%abil_fx_idx%" + "%index2%") * 0x30)) "eff_file"
     PATCH_IF (("%opcode%" = 151) AND ("%eff_file%" STRING_COMPARE_CASE "troll01" = 0)) BEGIN
       WRITE_EVALUATED_ASCII ("%fx_off%" + 0x14 + (("%abil_fx_idx%" + "%index2%") * 0x30)) "%DEST_RES%" #8
     END
   END
 END
 BUT_ONLY_IF_IT_CHANGES

// add monhp1 item to prevent death and assign script to force transformation to dead form at low HP
COPY_EXISTING ~drshnl01.cre~ ~override~
             ~eletro01.cre~ ~override~
             ~pptroll1.cre~ ~override~
             ~sutroll.cre~  ~override~
             ~torgal.cre~   ~override~
 ADD_CRE_ITEM ~monhp1~ #0 #0 #0 ~NONE~ ~AMULET~
 PATCH_IF ("%SOURCE_RES%" STRING_COMPARE_CASE "drshnl01" = 0) BEGIN
   WRITE_ASCII 0x248 ~drshnl02~
 END ELSE
 PATCH_IF ("%SOURCE_RES%" STRING_COMPARE_CASE "eletro01" = 0) BEGIN
   WRITE_ASCII 0x248 ~eletro03~
 END ELSE
 PATCH_IF ("%SOURCE_RES%" STRING_COMPARE_CASE "pptroll1" = 0) BEGIN
   WRITE_ASCII 0x248 ~pptroll2~
 END ELSE
 PATCH_IF ("%SOURCE_RES%" STRING_COMPARE_CASE "sutroll" = 0) BEGIN
   WRITE_ASCII 0x248 ~sutroll2~
 END ELSE
 PATCH_IF ("%SOURCE_RES%" STRING_COMPARE_CASE "torgal" = 0) BEGIN
   WRITE_ASCII 0x258 ~torgal2~ #8 // race script
 END

// one-off change for Nilthiri to prevent repeating dialogue
COPY_EXISTING ~drshnl01.cre~ ~override/drshnl03.cre~
 WRITE_ASCII 0x250 ~~ #8 // no approach and speak script
 WRITE_BYTE  0x270 255   // enemy
 WRITE_ASCII 0x2cc ~~ #8 // no dialogue file

// one-off change for Tor'gal to prevent repeating dialogue
COPY_EXISTING ~torgal.cre~ ~override/torgal3.cre~
 WRITE_ASCII 0x248 ~~ #8 // no approach and speak script
 WRITE_BYTE  0x270 255   // enemy
 WRITE_ASCII 0x2cc ~~ #8 // no dialogue file

// new scripts to transform to dead versions at low HP
COPY_EXISTING ~troll01.bcs~ ~override/drshnl02.bcs~
             ~troll01.bcs~ ~override/eletro03.bcs~
             ~troll01.bcs~ ~override/pptroll2.bcs~
             ~troll01.bcs~ ~override/sutroll2.bcs~
             ~troll01.bcs~ ~override/torgal2.bcs~
 DECOMPILE_BCS_TO_BAF
   REPLACE_TEXTUALLY ~ChangeAnimationNoEffect("TROLL02")~ ~ChangeAnimationNoEffect("%DEST_RES%")~
 COMPILE_BAF_TO_BCS

// new scripts to transform from dead to alive after time expires
COPY_EXISTING ~troll02.bcs~ ~override/drshnl03.bcs~
             ~troll02.bcs~ ~override/eletro01.bcs~
             ~troll02.bcs~ ~override/pptroll1.bcs~
             ~troll02.bcs~ ~override/sutroll.bcs~
             ~troll02.bcs~ ~override/torgal3.bcs~
 DECOMPILE_BCS_TO_BAF
   REPLACE_TEXTUALLY ~ReallyForceSpell(Myself,TROLL_CHANGE)~ ~ReallyForceSpellRES("%DEST_RES%",Myself)~
 COMPILE_BAF_TO_BCS

 

edit: fixed a bug in the code

Link to comment
I should note in passing that I am utterly unable to stop poison damage (a la Wyvern's Tail, not the poison effect) from killing downed trolls.

 

Yey! I like to go get Valygar for the Keep Quest and the the Druid Grove quest. Verrrry Niccce!

Valygar's katana uses the poison effect, so it's now blocked. :down:

 

Forgot the contents of hgtrl01.baf:

 

IF
Die()
THEN
RESPONSE #100
 CreateVisualEffectObject("ICMAGICH",Myself)
 CreateVisualEffectObject("SPFIREPI",Myself)
 DestroySelf()
END

Link to comment
grae.cre (Grae, Druid Grove)

From the limited wish quest, Grae gives you info on how to find Drush and the gong. Once reaching 1hp, Grae will initiate dialogue, and all of the options lead to EscapeArea() or Kill(Myself). No changes needed.

Actually, I can think of a change that would be good here. Would it be possible to have the party stop attacking her when she initiates dialogue? It can be tricky to make sure she survives.

Link to comment
grae.cre (Grae, Druid Grove)

From the limited wish quest, Grae gives you info on how to find Drush and the gong. Once reaching 1hp, Grae will initiate dialogue, and all of the options lead to EscapeArea() or Kill(Myself). No changes needed.

Actually, I can think of a change that would be good here. Would it be possible to have the party stop attacking her when she initiates dialogue? It can be tricky to make sure she survives.

 

I agree with Kish. I've accidentally killed Grae a couple of times when I didn't intend to do so. Although, can you do anything about missiles already in the air? Thinking about what the eSeries does as soon as it sees red circled Grae.

Link to comment
Did you add reduced damage from poison? For immunity, they're always paired (reduced damage/immunity to poison).

Ah, there we go. I was looking at the Periapt of Proof Against Poison, which doesn't have this. Adding this makes Wyvern's Tail ineffective. Incidentally...

 

// periapt of proof against poison not preventing poison damage
COPY_EXISTING ~amul22.itm~ ~override~
 READ_LONG   0x64 "abil_off"
 READ_SHORT  0x68 "abil_num"
 READ_LONG   0x6a "fx_off"
 READ_SHORT  0x70 "fx_num"
 SET "new_fx" = 1
 FOR (index = 0; index < abil_num; index = index + 1) BEGIN
   READ_SHORT  (0x20 + "%abil_off%" + ("%index%" * 0x38)) "abil_fx_idx"
   WRITE_SHORT (0x20 + "%abil_off%" + ("%index%" * 0x38)) ("%abil_fx_idx%" + "%new_fx%")
 END
 WRITE_SHORT 0x70 ("%fx_num%" + "%new_fx%")
 INSERT_BYTES  ("%fx_off%" +        ("%fx_num%" * 0x30)) 0x30
   WRITE_SHORT ("%fx_off%" +        ("%fx_num%" * 0x30)) 173 // reduced damage from poison
   WRITE_BYTE  ("%fx_off%" + 0x02 + ("%fx_num%" * 0x30)) 1   // target self
   WRITE_LONG  ("%fx_off%" + 0x04 + ("%fx_num%" * 0x30)) 100 // damage
   WRITE_BYTE  ("%fx_off%" + 0x0c + ("%fx_num%" * 0x30)) 2   // instant/while equipped
   WRITE_BYTE  ("%fx_off%" + 0x12 + ("%fx_num%" * 0x30)) 100 // probability 1
 BUT_ONLY_IF_IT_CHANGES

 

grae.cre (Grae, Druid Grove)

From the limited wish quest, Grae gives you info on how to find Drush and the gong. Once reaching 1hp, Grae will initiate dialogue, and all of the options lead to EscapeArea() or Kill(Myself). No changes needed.

Actually, I can think of a change that would be good here. Would it be possible to have the party stop attacking her when she initiates dialogue? It can be tricky to make sure she survives.

 

Sure--Grae destroys her minhp1 item in her script before initiating dialogue at low hp. If we delayed it until her Kill(Myself) block, she would keep her minhp1 item on the sole EscapeArea() option in the dialogue. Verified as working:

 

// move grae's minhp1 destruction to dialogue options where she dies; see soa-dlg.d for rest of changes
COPY_EXISTING ~grae.bcs~ ~override~
 DECOMPILE_BCS_TO_BAF
   REPLACE_TEXTUALLY ~DestroyItem("minhp1")~ ~~
 COMPILE_BAF_TO_BCS
 BUT_ONLY_IF_IT_CHANGES

 

.d code:

REPLACE_ACTION_TEXT ~grae~ ~Kill(Myself)~ ~DestroyItem("minhp1") Kill(Myself)~
REPLACE_ACTION_TEXT ~grae~ ~EscapeArea()~ ~SetInterrupt(FALSE) EscapeArea()~

Link to comment

Make sure you add that to the Resilient Sphere patch.

 

We could just comment out Grae's DestroyItem() entirely (since it doesn't look like it was ever intended for the player to be able to kill her), and not have to touch the dlg at all...

Link to comment

There was a discussion somewhere that reduced damage from poison would horribly overpower the cheap SoA items (this includes the periapt, Kangaxx's ring, and some unused sword, IIRC), or at least that there was no evidence it was ever intended to be granted. In unmodded BG2, the only way for the player to get it was class abilities (monk, HL druid), or the ToB hood or anti-venom ring.

Link to comment
Make sure you add that to the Resilient Sphere patch.

Got it. We had reduced damage from poison, but not the immunity to poison.

 

We could just comment out Grae's DestroyItem() entirely (since it doesn't look like it was ever intended for the player to be able to kill her), and not have to touch the dlg at all...

Does Kill(Myself) override minhp1 items? If so, sweet.

 

There was a discussion somewhere that reduced damage from poison would horribly overpower the cheap SoA items (this includes the periapt, Kangaxx's ring, and some unused sword, IIRC), or at least that there was no evidence it was ever intended to be granted. In unmodded BG2, the only way for the player to get it was class abilities (monk, HL druid), or the ToB hood or anti-venom ring.

Meh. I don't see how a description of 'Immune to poison' would preclude poison damage. Are there really enough items/spells that do direct poison damage for this to be a concern? Most poison benefits come from damage over time and disruption of spellcasters, for which the periapt already provides protection.

Link to comment

There is nothing that can stop Kill(). I've tested in the past with IMOENHP1 and MINHP1, which run the gamut of immunities.

 

Meh. I don't see how a description of 'Immune to poison' would preclude poison damage. Are there really enough items/spells that do direct poison damage for this to be a concern? Most poison benefits come from damage over time and disruption of spellcasters, for which the periapt already provides protection.
Kish might object; otherwise, it doesn't concern me either.

 

If you like it, make sure to hit the ring and the sword (Albruin?), as well as the Protection from Poison scroll. (Actually, I'd whip up an effect iterator to find all items/spells that don't have both.)

Link to comment

Archived

This topic is now archived and is closed to further replies.

×
×
  • Create New...